6.1 if语句
分支语句允许程序根据特定条件选择执行不同的代码路径。if
语句是 C++ 中最基本的分支结构,它允许程序根据一个条件表达式 (Condition) 的真假来决定是否执行某段代码。
基本语法:
1 | if (condition) { |
-
condition
: 一个求值为布尔值 (true
或false
) 的表达式。通常是关系表达式(如x > 5
,name == "Alice"
)或逻辑表达式。非零值被视为true
,零值被视为false
。 -
{ ... }
: 花括号定义了一个语句块。如果条件为true
,则执行块内的所有语句。如果只有一条语句需要根据条件执行,可以省略花括号,但为了清晰和避免错误,通常推荐总是使用花括号。
执行流程:
- 计算
condition
的值。 - 如果
condition
为true
,执行if
语句后面的语句(或语句块)。 - 如果
condition
为false
,跳过if
语句后面的语句(或语句块),继续执行if
结构之后的代码。
用法与示例:
1 |
|
在这个例子中,如果用户输入的 temperature
大于 30,会打印两条消息;如果小于 10,会打印另一条消息;否则,这些 if
块内的代码会被跳过。
6.1.1 if else语句
if
语句允许我们在条件为真时执行代码,但如果我们希望在条件为假时执行另一段代码,就需要使用 if else
结构。
语法:
1 | if (condition) { |
执行流程:
- 计算
condition
的值。 - 如果
condition
为true
,执行if
后面的语句块 (statement_block_1
),然后跳过else
后面的语句块 (statement_block_2
)。 - 如果
condition
为false
,跳过if
后面的语句块 (statement_block_1
),执行else
后面的语句块 (statement_block_2
)。 - 执行完选择的块后,程序继续执行
if else
结构之后的代码。
关键点: if
块和 else
块是互斥的,程序只会执行其中一个。
用法与示例:
1 |
|
这个程序会根据用户输入的年龄,打印两条不同的消息之一。
6.1.2 格式化if else语句
清晰的代码格式对于可读性和可维护性至关重要。对于 if else
语句,推荐遵循以下格式化约定:
- 使用花括号
{}
: 即使if
或else
后面只有一条语句,也推荐使用花括号。这可以防止在后续添加代码时引入悬挂else
(dangling else) 等错误,并使代码结构更清晰。 - 缩进:
if
和else
块内部的语句应该相对于if
和else
关键字进行缩进(通常是 4 个空格或一个制表符)。 -
else
的位置:else
关键字通常与对应的if
语句的右花括号}
放在同一行,或者单独放在下一行并与if
对齐。两种风格都很常见。
示例 (推荐的格式):
1 | // 风格 1: else 与 if 的 } 在同一行 |
悬挂 else
问题:
当 if
语句嵌套且省略花括号时,else
会与最近的未匹配的 if
相关联,这可能不符合预期。
1 | int a = 1, b = -1; |
始终使用花括号可以完全避免悬挂 else
问题。
6.1.3 if else if else结构
当需要从多个互斥的选项中选择一个执行路径时,可以使用 if else if else
结构。它本质上是一系列嵌套的 if else
语句,但通常写成更扁平的结构。
语法:
1 | if (condition1) { |
执行流程:
- 从上到下依次检查每个
if
和else if
的条件。 - 一旦找到第一个为
true
的条件,就执行其对应的语句块。 - 执行完该块后,跳过所有剩余的
else if
和else
分支,直接执行整个if else if else
结构之后的代码。 - 如果所有的
if
和else if
条件都为false
,则执行最后的else
块(如果存在)。如果不存在最后的else
块,则整个结构什么也不执行。
用法与示例:
1 |
|
这个例子根据分数范围判断对应的等级。程序会按顺序检查条件,一旦满足一个(例如 score >= 80
),就会确定等级为 ‘B’,并跳过后续的 else if
和 else
。最后的 else
处理所有低于 60 分的情况。
6.2 逻辑表达式
在 if
或循环的条件中,我们常常需要组合多个关系表达式或者对某个条件取反。逻辑运算符 (Logical Operators) 用于组合或修改已有的布尔表达式(或可以转换为布尔值的表达式),生成一个新的布尔结果 (true
或 false
)。
C++ 主要提供三种逻辑运算符:
- 逻辑或 (Logical OR):
||
- 逻辑与 (Logical AND):
&&
- 逻辑非 (Logical NOT):
!
6.2.1 逻辑OR运算符:||
逻辑或运算符 ||
用于连接两个表达式。如果至少有一个操作数为 true
,则整个 ||
表达式的结果为 true
。只有当两个操作数都为 false
时,结果才为 false
。
真值表:
| 操作数1 | 操作数2 | 操作数1 || 操作数2
|
| :—— | :—— | :——————- |
| true
| true
| true
|
| true
| false
| true
|
| false
| true
| true
|
| false
| false
| false
|
用法与示例:
||
常用于检查多个条件中是否至少有一个满足。
1 |
|
6.2.2 逻辑AND运算符:&&
逻辑与运算符 &&
用于连接两个表达式。只有当两个操作数都为 true
时,整个 &&
表达式的结果才为 true
。只要有至少一个操作数为 false
,结果就为 false
。
真值表:
操作数1 | 操作数2 | 操作数1 && 操作数2 |
---|---|---|
true |
true |
true |
true |
false |
false |
false |
true |
false |
false |
false |
false |
用法与示例:
&&
常用于检查是否同时满足多个条件。
1 |
|
6.2.3 用&&来设置取值范围
逻辑与运算符 &&
非常适合用来检查一个值是否落在某个特定的范围内(即同时满足大于某个值和小于另一个值)。
用法与示例:
1 |
|
注意: 不能像数学中那样写 0 <= score <= 100
。这在 C++ 中会被解释为 (0 <= score) <= 100
。(0 <= score)
的结果是 true
(1) 或 false
(0),然后这个 0 或 1 再与 100 比较,结果几乎总是 true
,无法正确判断范围。必须使用 &&
连接两个独立的比较。
6.2.4 逻辑NOT运算符:!
逻辑非运算符 !
是一个一元运算符(只需要一个操作数)。它将其操作数的布尔值取反:如果操作数为 true
,结果为 false
;如果操作数为 false
,结果为 true
。
真值表:
操作数 | !操作数 |
---|---|
true |
false |
false |
true |
用法与示例:
!
用于反转一个条件的结果。
1 |
|
6.2.5 逻辑运算符细节
优先级 (Precedence):
逻辑非
!
具有最高的优先级,高于所有关系运算符和算术运算符。逻辑与
&&
的优先级高于逻辑或||
。逻辑运算符的优先级低于关系运算符 (
<
,==
,!=
等)。赋值运算符 (
=
) 优先级最低。*常见优先级顺序 (高到低):**
-
!
- 算术运算符 (
*
,/
,%
,+
,-
) - 关系运算符 (
<
,<=
,>
,>=
) - 相等运算符 (
==
,!=
) - 逻辑与
&&
- 逻辑或
||
- 赋值运算符 (
=
,+=
等)
建议: 当不确定优先级或为了提高可读性时,使用括号
()
来明确指定运算顺序。1
if ((age >= 18 && age < 65) || is_student) { ... } // 括号明确了 && 先于 ||
短路求值 (Short-Circuit Evaluation):
&&
(逻辑与): 如果&&
的左侧操作数计算结果为false
,则右侧操作数不会被计算。因为无论右侧是什么,整个表达式的结果都必然是false
。||
(逻辑或): 如果||
的左侧操作数计算结果为true
,则右侧操作数不会被计算。因为无论右侧是什么,整个表达式的结果都必然是true
。短路求值非常重要,因为它:
提高效率: 避免了不必要的计算。
允许安全检查: 可以在检查指针有效性后才解引用它,或在除数非零时才执行除法。
*短路求值示例:**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int main() {
int divisor = 0;
int value = 10;
// 安全的除法检查 (因为 divisor 为 0,右侧不会执行)
if (divisor != 0 && value / divisor > 1) {
std::cout << "Result is greater than 1." << std::endl;
} else {
std::cout << "Divisor is zero or result is not greater than 1." << std::endl;
}
int *ptr = nullptr;
// 安全的指针访问 (因为 ptr 为 nullptr,右侧不会执行)
if (ptr != nullptr && ptr->some_member == 5) {
std::cout << "Pointer member is 5." << std::endl;
} else {
std::cout << "Pointer is null or member is not 5." << std::endl;
}
int count = 0;
// || 的短路 (因为 ++count > 0 为 true,右侧不会执行)
if (++count > 0 || some_expensive_function()) {
std::cout << "Condition met. Count is " << count << std::endl; // count 变为 1
}
return 0;
}
bool some_expensive_function() {
std::cout << "Expensive function called!" << std::endl; // 这行不会被打印
return true;
}
6.2.6 其他表示方式
为了兼容某些可能缺少 |
, &
, !
字符的键盘或字符集,C++ 标准定义了一些替代表示(也称为 “digraphs” 或 “alternative tokens”)。这些是关键字,可以直接使用,无需包含特殊头文件。
逻辑运算符 | 替代表示 |
---|---|
&& |
and |
` | |
! |
not |
&= |
and_eq |
` | =` |
^= |
xor_eq |
~ |
compl |
& |
bitand |
` | ` |
^ |
xor |
!= |
not_eq |
用法与示例:
1 |
|
虽然这些替代表示是合法的 C++,但在现代编程实践中,直接使用符号运算符 (&&
, ||
, !
) 更为常见和普遍接受。除非有特定的编码标准或环境限制要求使用替代表示,否则通常坚持使用符号运算符。
6.3 字符函数库cctype
C++ 继承了 C 语言的一个非常有用的函数库,用于处理字符。这个库的 C++ 头文件是 <cctype>
,对应的 C 头文件是 <ctype.h>
。它提供了一系列函数,可以方便地检查字符的类别(例如,是否是字母、数字、标点符号、空白等)以及转换字符的大小写。
这些函数通常接收一个 int
类型的参数(该参数值应能表示为 unsigned char
或等于 EOF
),并返回一个 int
值。对于测试函数,返回非零值(通常解释为 true
)表示条件满足,返回零值(解释为 false
)表示条件不满足。对于转换函数,返回转换后的字符的整数表示。
包含头文件:
要使用这些函数,需要包含 <cctype>
头文件:
1 |
常用的字符测试函数:
函数名 | 描述 |
---|---|
isalnum(ch) |
如果 ch 是字母(isalpha )或数字(isdigit ),返回 true 。 |
isalpha(ch) |
如果 ch 是字母(大写或小写),返回 true 。 |
isblank(ch) |
(C++11) 如果 ch 是标准空白字符(通常是空格或水平制表符 \t ),返回 true 。 |
iscntrl(ch) |
如果 ch 是控制字符(例如 \n , \t , ASCII 0-31 和 127),返回 true 。 |
isdigit(ch) |
如果 ch 是十进制数字(’0’ 到 ‘9’),返回 true 。 |
isgraph(ch) |
如果 ch 是除空格外的任何可打印字符,返回 true 。 |
islower(ch) |
如果 ch 是小写字母,返回 true 。 |
isprint(ch) |
如果 ch 是任何可打印字符(包括空格),返回 true 。 |
ispunct(ch) |
如果 ch 是标点符号(isgraph 为 true 但 isalnum 为 false ),返回 true 。 |
isspace(ch) |
如果 ch 是标准空白字符(空格、换页 \f 、换行 \n 、回车 \r 、水平制表符 \t 、垂直制表符 \v ),返回 true 。 |
isupper(ch) |
如果 ch 是大写字母,返回 true 。 |
isxdigit(ch) |
如果 ch 是十六进制数字(’0’-‘9’, ‘a’-‘f’, ‘A’-‘F’),返回 true 。 |
用法与示例 (测试函数):
1 |
|
常用的字符转换函数:
函数名 | 描述 |
---|---|
tolower(ch) |
如果 ch 是大写字母,返回其对应的小写字母;否则,返回 ch 不变。 |
toupper(ch) |
如果 ch 是小写字母,返回其对应的大写字母;否则,返回 ch 不变。 |
用法与示例 (转换函数):
1 |
|
<cctype>
库提供了一套标准、可移植的函数来处理字符分类和转换,这在处理用户输入、解析文本或进行不区分大小写的操作时非常有用。
6.4 三元运算符
C++ 提供了一个简洁的条件运算符,称为三元运算符(或条件运算符),它是 C++ 中唯一一个需要三个操作数的运算符。它通常用于根据条件将两个值中的一个赋给变量。
用法
三元运算符的语法如下:
1 | condition ? expression1 : expression2; |
其工作方式是:
- 首先计算
condition
。 - 如果
condition
为true
(非零),则计算expression1
,并且整个表达式的值就是expression1
的值。 - 如果
condition
为false
(零),则计算expression2
,并且整个表达式的值就是expression2
的值。
三元运算符通常可以替代简单的 if else
语句,使代码更紧凑。
示例代码
下面是一个使用三元运算符查找两个数中较大值的示例:
1 |
|
代码解释:
- 在第一个示例中,条件
(a > b)
被评估。因为10 > 20
是false
,所以计算第二个表达式b
,并将b
的值(即20
)赋给max_val
。 - 在第二个示例中,条件
(age >= 18)
被评估。因为15 >= 18
是false
,所以计算第二个表达式"Minor"
,并将这个字符串赋给status
。
优点:
- 简洁性: 可以用一行代码替代多行的
if else
结构,使代码更紧凑。
缺点:
- 可读性: 对于复杂的条件或表达式,使用三元运算符可能会降低代码的可读性。在这种情况下,使用
if else
语句通常更好。
三元运算符是 C++ 中一个方便的工具,尤其适用于简单的条件赋值。
6.5 switch 语句
switch
语句是 C++ 中另一种用于控制程序流程的分支结构。它允许程序根据一个表达式的值从多个代码块中选择一个来执行。switch
语句通常用于替代冗长的 if else if else
结构,特别是当判断条件基于单个变量或表达式的离散值时。
用法
switch
语句的基本语法如下:
1 | switch (expression) { |
工作方式:
- 首先计算
switch
括号内的expression
(表达式)。这个表达式必须得出一个整数类型(如int
,char
,enum
)或可以隐式转换为整数类型的值。 - 程序将
expression
的值与每个case
后面跟着的constant_expression
(常量表达式)进行比较。 - 如果找到匹配的
case
,则执行该case
标签下的代码块。 -
break
语句的作用是跳出switch
结构。如果没有break
,程序会继续执行下一个case
的代码块(称为“贯穿”),直到遇到break
或switch
语句结束。 -
default
标签是可选的。如果expression
的值与所有case
的常量表达式都不匹配,则执行default
标签下的代码块。如果没有default
标签且没有匹配的case
,则switch
语句不执行任何操作。
重要限制:
-
case
标签后面的值必须是常量表达式(如字面值10
、'A'
或const
整数变量,或者枚举量)。不能是变量或非常量表达式。 -
expression
的结果必须是整数类型(int
,char
,short
,long
,long long
,bool
,enum
等)。不能是浮点数 (float
,double
) 或字符串 (std::string
)。
示例代码
1 |
|
代码解释:
- 第一个
switch
根据用户输入的choice
值执行相应的case
。break
语句确保只执行匹配case
的代码。如果输入不是 1、2 或 3,则执行default
部分。 - 第二个
switch
演示了“贯穿”行为。因为case 'A'
和case 'B'
后面没有break
,当grade
为'B'
时,程序会执行case 'B'
的代码 (std::cout << "Good! ";
),然后继续执行case 'C'
的代码 (std::cout << "Passing. ";
),直到遇到case 'C'
中的break
才跳出switch
。同时,它也展示了如何将多个case
('D'
和'F'
)关联到同一个代码块。
6.5.1 将枚举量用作标签
枚举 (enum
) 类型的值是整数常量,因此非常适合用作 switch
语句的 case
标签,这可以提高代码的可读性。
1 |
|
使用枚举量作为 case
标签比直接使用魔法数字(如 0, 1, 2)更清晰易懂。
6.5.2 switch 和 if else
switch
语句可以看作是特定类型的 if else if else
结构的替代品,即判断条件都基于同一个整数表达式的值。
何时使用 switch
:
- 当需要根据单个整数表达式的多个特定离散值进行分支时。
- 当分支逻辑清晰,可以提高可读性时。
何时使用 if else if else
:
- 当判断条件涉及范围(例如
age > 18 && age < 60
)时。 - 当判断条件涉及浮点数或字符串比较时。
- 当判断条件比较复杂,涉及多个不同变量或逻辑运算时。
- 当只有一个或两个分支时,
if else
可能更简洁。
示例比较:
1 | // 使用 if else if |
对于这种基于单个整数值的多路分支,switch
通常被认为更清晰、有时效率也可能更高(编译器可能将其优化为跳转表)。但对于涉及范围或非整数类型的判断,则必须使用 if else if
结构。
6.6 break 和 continue 语句
C++ 提供了两个特殊的语句,break
和 continue
,用于在循环(for
, while
, do while
)或 switch
语句内部改变正常的执行流程。
6.6.1 break 语句
break
语句用于立即终止包含它的最内层的循环(for
, while
, do while
)或 switch
语句的执行。程序控制流会跳转到该循环或 switch
语句之后的下一条语句。
用法:
- 在循环中: 当满足某个特定条件时,提前退出循环。
- 在
switch
语句中: 防止“贯穿”(fall-through)到下一个case
,在执行完匹配的case
代码块后跳出switch
结构(如 6.5 节所述)。
示例 (在循环中使用):
1 |
|
代码解释:
- 第一个循环遍历
numbers
数组。当i
为 3 时,numbers[3]
是 -2,满足if
条件。found_index
被设为 3,打印消息,然后break
语句被执行,立即终止for
循环。后面的元素(8 和 -5)不会被检查。 - 在嵌套循环示例中,当
i
为 2 且j
为 2 时,break
语句执行。它只终止了最内层的j
循环。外层的i
循环继续执行其下一次迭代(当i
为 3 时)。
6.6.2 continue 语句
continue
语句用于跳过当前循环迭代中剩余的代码,并立即开始下一次迭代。与 break
不同,continue
不会终止整个循环。
用法:
- 在
while
和do while
循环中: 控制流跳转到循环条件的判断处。 - 在
for
循环中: 控制流首先跳转到for
循环的更新表达式(例如++i
),然后跳转到循环条件的判断处。
示例:
1 |
|
代码解释:
- 在
for
循环中,当i
是偶数时(i % 2 == 0
为真),continue
语句执行。它跳过了std::cout << i << " ";
这行代码,直接执行更新表达式++i
,然后判断循环条件i <= 10
。因此,只有奇数被打印出来。 - 在
while
循环中,当k
递增到 5 时,if
条件满足,continue
执行,跳过了std::cout << k << " ";
,直接回到while (k < 7)
的条件判断。
总结
-
break
: 完全终止最内层的循环或switch
。 -
continue
: 跳过当前循环迭代的剩余部分,进入下一次迭代(如果循环条件允许)。
这两个语句可以使循环控制更加灵活,但过度使用可能会降低代码的可读性。通常,可以通过调整循环条件或使用 if
语句来避免一些不必要的 break
或 continue
。
6.7 读取数字的循环
在 C++ 程序中,经常需要编写循环来读取用户的数字输入,直到满足某个条件(例如输入特定值或遇到无效输入)为止。然而,处理数字输入,特别是处理潜在的错误输入,需要一些技巧。
基本的数字读取循环
一个简单的读取数字并累加的循环可能如下所示:
1 |
|
工作方式:
-
std::cin >> input_number
尝试从输入流中读取一个整数并存储到input_number
中。 - 这个表达式本身会返回
std::cin
对象。在需要布尔值的上下文中(如while
的条件),std::cin
对象会根据流的状态转换为true
或false
。 - 如果成功读取一个整数,流状态是正常的,
std::cin
转换为true
,循环体执行。 - 如果用户输入了非数字(例如输入 “hello” 或按 Ctrl+Z/Ctrl+D 表示文件结束),
std::cin >> input_number
会失败。此时,std::cin
会进入“失败”(fail)状态,转换为false
,循环终止。
处理错误输入
上面的简单循环在遇到非数字输入时会终止,但它没有明确地处理错误状态。如果循环结束后还需要继续从 std::cin
读取其他类型的输入,就需要清除错误状态并丢弃无效的输入。
问题: 当 std::cin >> input_number
失败时,输入流 std::cin
会设置一个错误标志(failbit),并且导致失败的输入(例如 “hello”)仍然留在输入缓冲区中。如果不处理,后续的 std::cin
操作通常也会立即失败。
解决方案:
- 清除错误状态: 使用
std::cin.clear()
方法重置流的错误标志。 - 忽略无效输入: 使用
std::cin.ignore()
方法丢弃输入缓冲区中不需要的字符。通常会忽略直到下一个换行符\n
或达到某个最大字符数。
示例 (更健壮的数字读取循环):
1 |
|
代码解释:
- 使用
while(true)
创建一个看似无限的循环,退出逻辑放在循环内部。 -
if (std::cin >> input_number)
尝试读取数字。 - 如果成功,执行加法。
- 如果失败 (
else
块):- 打印错误消息。
- 检查是否是文件结束符 (
std::cin.eof()
),如果是则break
。 - 调用
std::cin.clear()
清除failbit
等错误状态。 - 调用
std::cin.ignore(...)
来丢弃缓冲区中的无效输入。std::numeric_limits<std::streamsize>::max()
表示忽略尽可能多的字符,直到遇到换行符\n
。这确保了下一次循环迭代时,std::cin
不会再次读取相同的无效输入。 - 根据需要,可以选择
break
退出,或者让循环继续,提示用户重新输入。
这种模式在需要从用户那里可靠地读取数字输入时非常有用,因为它能优雅地处理输入错误,而不是让程序因为意外的输入而崩溃或行为异常。
6.8 简单文件输入/输出
到目前为止,我们主要使用 cin
从键盘读取输入,用 cout
向屏幕显示输出。C++ 还提供了强大的功能,可以让我们将数据写入文件或从文件中读取数据。这对于存储程序运行结果、读取配置文件或处理大量数据至关重要。本节将介绍基本的文本文件输入/输出(I/O)操作。
6.8.1 文本 I/O 和文本文件
文件 I/O 主要有两种模式:文本模式和二进制模式。
文本文件 (Text File): 文本文件存储的是人类可读的字符序列。文件中的数据被解释为字符,数字(如
123
)会被存储为字符序列('1'
,'2'
,'3'
)。在不同的操作系统上,文本文件对行尾的处理可能不同(例如,Windows 使用回车+换行\r\n
,Unix/Linux 使用换行\n
)。C++ 的文本 I/O 会自动处理这些行尾转换,使得代码更具可移植性。我们通常使用<<
和>>
或getline
等函数来处理文本文件。二进制文件 (Binary File): 二进制文件存储的是数据的原始字节表示。数字
123
会被存储为其在内存中的二进制形式(例如,一个 4 字节的int
)。二进制 I/O 不进行任何字符转换或行尾处理,读写速度通常更快,文件也可能更小,但内容通常不是人类直接可读的。处理二进制文件通常使用read()
和write()
成员函数。
本节重点介绍文本文件的 I/O 操作。
6.8.2 写入到文本文件中
要将数据写入文本文件,我们需要使用 C++ 的文件流库 <fstream>
。
步骤:
- 包含头文件:
#include <fstream>
- 创建
ofstream
对象:ofstream
类(output file stream)用于向文件写入数据。你需要创建一个该类的对象,并在创建时或之后将其与一个文件名关联起来。1
2
3
4std::ofstream outputFile; // 创建 ofstream 对象
outputFile.open("mydata.txt"); // 将对象与文件关联(如果文件不存在则创建,如果存在则清空内容)
// 或者在创建时直接关联
// std::ofstream outputFile("mydata.txt"); - 检查文件是否成功打开: 在尝试写入之前,最好检查文件是否成功打开。可以使用
is_open()
方法或直接检查流对象的状态。1
2
3
4if (!outputFile.is_open()) { // 或者 if (!outputFile)
std::cerr << "Error opening file for writing!" << std::endl;
return 1; // 或者进行其他错误处理
} - 写入数据: 使用与
cout
类似的<<
运算符将数据写入文件流。1
2
3
4
5
6int year = 2024;
double price = 99.99;
std::string item = "Gadget";
outputFile << "Item: " << item << std::endl;
outputFile << "Year: " << year << std::endl;
outputFile << "Price: " << price << std::endl; - 关闭文件: 完成写入后,应该关闭文件以确保所有数据都被刷新(写入)到磁盘,并释放文件资源。可以显式调用
close()
方法,或者当ofstream
对象离开其作用域时(例如函数结束),其析构函数会自动关闭文件。1
outputFile.close(); // 显式关闭
示例代码:
1 |
|
运行此程序后,将在程序所在的目录下创建一个名为 report.txt
的文件(如果不存在),其内容如下:
1 | --- Sales Report --- |
6.8.3 读取文本文件
从文本文件读取数据与写入类似,但使用 ifstream
类(input file stream)。
步骤:
包含头文件:
#include <fstream>
创建
ifstream
对象: 创建一个ifstream
对象并将其与要读取的文件名关联。1
2
3
4std::ifstream inputFile; // 创建 ifstream 对象
inputFile.open("mydata.txt"); // 将对象与文件关联以供读取
// 或者在创建时直接关联
// std::ifstream inputFile("mydata.txt");检查文件是否成功打开: 同样,检查文件是否成功打开至关重要。如果文件不存在或无法访问,打开操作会失败。
1
2
3
4if (!inputFile.is_open()) { // 或者 if (!inputFile)
std::cerr << "Error opening file for reading!" << std::endl;
return 1;
}读取数据: 可以使用与
cin
类似的>>
运算符来读取由空格分隔的数据,或者使用std::getline()
来读取整行。读取操作通常放在循环中,直到到达文件末尾。- 使用
>>
读取: 它会跳过前导空白(空格、制表符、换行符),然后读取直到遇到下一个空白字符。 - 使用
std::getline(inputFile, lineString)
读取: 它会读取整行(包括空格),直到遇到换行符\n
为止(换行符本身会被读取并丢弃)。
- 使用
检查文件末尾 (EOF): 当尝试读取但已无数据可读时(到达文件末尾),输入流会进入特殊状态。循环通常依赖于检查流的状态来终止。
-
while (inputFile >> variable)
: 当>>
成功读取时,流状态为true
,循环继续;到达文件末尾或遇到无效数据时,状态变为false
,循环终止。 -
while (std::getline(inputFile, lineString))
: 当getline
成功读取一行时,流状态为true
;到达文件末尾时,状态变为false
。
-
关闭文件: 读取完成后,关闭文件。同样,可以显式调用
close()
,或者依赖对象的析构函数。1
inputFile.close();
示例代码 (读取 report.txt
):
1 |
|
代码解释:
- 第一个循环使用
std::getline()
读取文件的每一行,并将其打印出来,同时存储在lines
向量中。while (std::getline(inFile, line))
是读取文本文件的常用模式。 - 循环结束后,通过检查
inFile.eof()
和inFile.fail()
可以判断循环是正常结束(到达文件末尾)还是因为其他错误。 - 第二个示例演示了使用
>>
操作符读取特定格式的数据。这种方法对于格式严格固定的文件可能有效,但如果文件格式稍有变化(例如多了空格),>>
就可能读取失败或读到错误的数据,因此通常不如getline
健壮。需要仔细处理标签和数据类型。
文件 I/O 是 C++ 编程中非常重要的部分,它使得程序能够持久化数据,并与其他程序或系统进行交互。
6.9 总结
本章介绍了 C++ 中用于控制程序流程的各种分支语句和相关概念,使得程序能够根据不同的条件执行不同的代码路径。
主要内容回顾:
if
语句系列:-
if
语句:根据条件是否为真来决定是否执行某段代码。 -
if else
语句:提供两个代码路径,根据条件为真或假选择其一执行。 -
if else if else
结构:用于处理多个互斥的条件,提供多路分支选择。
-
逻辑运算符:
-
||
(逻辑或):两个操作数中至少一个为真时,结果为真。 -
&&
(逻辑与):两个操作数都为真时,结果才为真。具有短路求值特性。 -
!
(逻辑非):反转操作数的逻辑状态(真变假,假变真)。
-
cctype
库:- 提供了一系列用于处理字符的函数,如
isalpha()
,isdigit()
,isspace()
,ispunct()
,toupper()
,tolower()
等,方便进行字符分类和转换。
- 提供了一系列用于处理字符的函数,如
三元运算符 (
?:
):- 提供了一种简洁的方式来根据条件选择两个值中的一个,是
if else
语句的一种紧凑替代形式,常用于简单的赋值操作。 - 语法:
condition ? expression1 : expression2;
- 提供了一种简洁的方式来根据条件选择两个值中的一个,是
switch
语句:- 根据一个整数表达式的值,从多个
case
标签中选择一个匹配的执行点。 - 通常与
break
语句配合使用,以防止“贯穿”到下一个case
。 -
default
标签处理所有其他不匹配的情况。 -
case
标签必须是常量表达式。 - 适用于基于单个离散整数值的多路分支。
- 根据一个整数表达式的值,从多个
break
和continue
语句:-
break
:立即终止最内层的循环(for
,while
,do while
)或switch
语句。 -
continue
:跳过当前循环迭代的剩余部分,直接开始下一次迭代(更新和条件检查)。
-
读取数字的循环:
- 演示了如何使用循环(如
while (cin >> value)
)来连续读取数字输入。 - 强调了处理错误输入的重要性,包括使用
cin.clear()
清除错误状态和cin.ignore()
丢弃无效输入,以编写更健壮的输入代码。
- 演示了如何使用循环(如
简单文件输入/输出:
- 引入了
<fstream>
库,用于文件操作。 -
ofstream
:用于向文件写入数据(输出文件流),使用<<
操作符。 -
ifstream
:用于从文件读取数据(输入文件流),使用>>
或getline()
。 - 强调了文件打开检查 (
is_open()
) 和关闭文件 (close()
或利用对象析构) 的重要性。 - 区分了文本 I/O 和二进制 I/O 的基本概念。
- 引入了
通过掌握这些分支结构和控制语句,可以编写出能够响应不同情况、处理用户输入和文件数据的更复杂、更灵活的 C++ 程序。