6.1 if语句

分支语句允许程序根据特定条件选择执行不同的代码路径。if 语句是 C++ 中最基本的分支结构,它允许程序根据一个条件表达式 (Condition) 的真假来决定是否执行某段代码。

基本语法:

1
2
3
4
5
6
7
8
9
if (condition) {
// 如果 condition 为 true,则执行这里的语句
statement1;
statement2;
// ...
}
// 或者如果只有一条语句
if (condition)
single_statement;
  • condition: 一个求值为布尔值 (truefalse) 的表达式。通常是关系表达式(如 x > 5, name == "Alice")或逻辑表达式。非零值被视为 true,零值被视为 false
  • { ... }: 花括号定义了一个语句块。如果条件为 true,则执行块内的所有语句。如果只有一条语句需要根据条件执行,可以省略花括号,但为了清晰和避免错误,通常推荐总是使用花括号

执行流程:

  1. 计算 condition 的值。
  2. 如果 conditiontrue,执行 if 语句后面的语句(或语句块)。
  3. 如果 conditionfalse,跳过 if 语句后面的语句(或语句块),继续执行 if 结构之后的代码。

用法与示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

int main() {
int temperature;
std::cout << "Enter the current temperature (Celsius): ";
std::cin >> temperature;

// 简单的 if 语句
if (temperature > 30) {
std::cout << "It's hot outside!" << std::endl;
std::cout << "Remember to stay hydrated." << std::endl;
}

if (temperature < 10)
std::cout << "It's cold, wear a jacket!" << std::endl; // 只有一条语句,可以省略花括号

std::cout << "Temperature check finished." << std::endl;

return 0;
}

在这个例子中,如果用户输入的 temperature 大于 30,会打印两条消息;如果小于 10,会打印另一条消息;否则,这些 if 块内的代码会被跳过。

6.1.1 if else语句

if 语句允许我们在条件为真时执行代码,但如果我们希望在条件为假时执行另一段代码,就需要使用 if else 结构。

语法:

1
2
3
4
5
6
7
if (condition) {
// 如果 condition 为 true,执行这里的语句块 (if block)
statement_block_1;
} else {
// 如果 condition 为 false,执行这里的语句块 (else block)
statement_block_2;
}

执行流程:

  1. 计算 condition 的值。
  2. 如果 conditiontrue,执行 if 后面的语句块 (statement_block_1),然后跳过 else 后面的语句块 (statement_block_2)。
  3. 如果 conditionfalse,跳过 if 后面的语句块 (statement_block_1),执行 else 后面的语句块 (statement_block_2)。
  4. 执行完选择的块后,程序继续执行 if else 结构之后的代码。

关键点: if 块和 else 块是互斥的,程序只会执行其中一个。

用法与示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

int main() {
int age;
std::cout << "Enter your age: ";
std::cin >> age;

if (age >= 18) {
std::cout << "You are eligible to vote." << std::endl;
} else {
std::cout << "You are not yet eligible to vote." << std::endl;
int years_to_wait = 18 - age;
std::cout << "You need to wait " << years_to_wait << " more year(s)." << std::endl;
}

std::cout << "Age check complete." << std::endl;

return 0;
}

这个程序会根据用户输入的年龄,打印两条不同的消息之一。

6.1.2 格式化if else语句

清晰的代码格式对于可读性和可维护性至关重要。对于 if else 语句,推荐遵循以下格式化约定:

  1. 使用花括号 {}: 即使 ifelse 后面只有一条语句,也推荐使用花括号。这可以防止在后续添加代码时引入悬挂 else (dangling else) 等错误,并使代码结构更清晰。
  2. 缩进: ifelse 块内部的语句应该相对于 ifelse 关键字进行缩进(通常是 4 个空格或一个制表符)。
  3. else 的位置: else 关键字通常与对应的 if 语句的右花括号 } 放在同一行,或者单独放在下一行并与 if 对齐。两种风格都很常见。

示例 (推荐的格式):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 风格 1: else 与 if 的 } 在同一行
if (condition) {
// ... statements ...
} else {
// ... statements ...
}

// 风格 2: else 单独一行,与 if 对齐
if (condition) {
// ... statements ...
}
else {
// ... statements ...
}

// 不推荐的格式 (即使只有一条语句)
// if (condition) statement1; else statement2; // 可读性差,易出错

// 推荐的格式 (即使只有一条语句)
if (condition) {
statement1;
} else {
statement2;
}

悬挂 else 问题:

if 语句嵌套且省略花括号时,else 会与最近的未匹配的 if 相关联,这可能不符合预期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int a = 1, b = -1;

// 错误的意图 (可能想让 else 对应外层 if)
if (a > 0)
if (b > 0)
std::cout << "Both positive";
else // 这个 else 实际上对应的是 if (b > 0)
std::cout << "a is not positive"; // 这行不会按预期执行

// 正确的写法 (使用花括号明确关联)
if (a > 0) { // 外层 if
if (b > 0) { // 内层 if
std::cout << "Both positive";
}
// 没有 else 对应内层 if
} else { // 这个 else 对应外层 if (a > 0)
std::cout << "a is not positive";
}

始终使用花括号可以完全避免悬挂 else 问题。

6.1.3 if else if else结构

当需要从多个互斥的选项中选择一个执行路径时,可以使用 if else if else 结构。它本质上是一系列嵌套的 if else 语句,但通常写成更扁平的结构。

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (condition1) {
// 如果 condition1 为 true,执行这里的语句块
statement_block_1;
} else if (condition2) {
// 如果 condition1 为 false 且 condition2 为 true,执行这里的语句块
statement_block_2;
} else if (condition3) {
// 如果 condition1 和 condition2 都为 false 且 condition3 为 true,执行这里的语句块
statement_block_3;
}
// ... 可以有更多的 else if 分支
else {
// 如果以上所有条件都为 false,执行这里的语句块 (可选的默认分支)
statement_block_default;
}

执行流程:

  1. 从上到下依次检查每个 ifelse if 的条件。
  2. 一旦找到第一个为 true 的条件,就执行其对应的语句块。
  3. 执行完该块后,跳过所有剩余的 else ifelse 分支,直接执行整个 if else if else 结构之后的代码。
  4. 如果所有的 ifelse if 条件都为 false,则执行最后的 else 块(如果存在)。如果不存在最后的 else 块,则整个结构什么也不执行。

用法与示例:

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
#include <iostream>

int main() {
int score;
std::cout << "Enter your numerical score (0-100): ";
std::cin >> score;

char grade;

if (score < 0 || score > 100) {
std::cout << "Invalid score entered." << std::endl;
grade = 'I'; // Invalid
} else if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else if (score >= 60) {
grade = 'D';
} else { // score < 60
grade = 'F';
}

if (grade != 'I') {
std::cout << "Your grade is: " << grade << std::endl;
}

return 0;
}

这个例子根据分数范围判断对应的等级。程序会按顺序检查条件,一旦满足一个(例如 score >= 80),就会确定等级为 ‘B’,并跳过后续的 else ifelse。最后的 else 处理所有低于 60 分的情况。

6.2 逻辑表达式

if 或循环的条件中,我们常常需要组合多个关系表达式或者对某个条件取反。逻辑运算符 (Logical Operators) 用于组合或修改已有的布尔表达式(或可以转换为布尔值的表达式),生成一个新的布尔结果 (truefalse)。

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
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
#include <iostream>

int main() {
char input_char;
std::cout << "Enter a character: ";
std::cin >> input_char;

// 检查字符是否是元音字母 (忽略大小写)
if (input_char == 'a' || input_char == 'e' || input_char == 'i' ||
input_char == 'o' || input_char == 'u' || input_char == 'A' ||
input_char == 'E' || input_char == 'I' || input_char == 'O' ||
input_char == 'U')
{
std::cout << "'" << input_char << "' is a vowel." << std::endl;
} else {
std::cout << "'" << input_char << "' is not a vowel." << std::endl;
}

int age = 25;
bool has_ticket = false;
// 检查是否满足入场条件 (年龄小于 12 或持有门票)
if (age < 12 || has_ticket) {
std::cout << "Allowed entry." << std::endl;
} else {
std::cout << "Entry denied." << std::endl;
}

return 0;
}

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
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
#include <iostream>

int main() {
int age;
bool has_id;
char id_input;

std::cout << "Enter your age: ";
std::cin >> age;
std::cout << "Do you have an ID? (y/n): ";
std::cin >> id_input;
has_id = (id_input == 'y' || id_input == 'Y');

// 检查是否满足购买条件 (年龄大于等于 18 并且持有 ID)
if (age >= 18 && has_id) {
std::cout << "Purchase approved." << std::endl;
} else {
std::cout << "Purchase denied." << std::endl;
if (age < 18) {
std::cout << "Reason: Underage." << std::endl;
}
if (!has_id) { // 使用了逻辑非 !
std::cout << "Reason: No ID presented." << std::endl;
}
}

return 0;
}

6.2.3 用&&来设置取值范围

逻辑与运算符 && 非常适合用来检查一个值是否落在某个特定的范围内(即同时满足大于某个值和小于另一个值)。

用法与示例:

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
#include <iostream>

int main() {
int score;
std::cout << "Enter your score: ";
std::cin >> score;

// 检查分数是否在有效范围 [0, 100] 内
if (score >= 0 && score <= 100) {
std::cout << "Score is valid." << std::endl;
// 进一步判断等级
if (score >= 60 && score < 70) {
std::cout << "Grade: D" << std::endl;
} else if (score >= 70 && score < 80) {
std::cout << "Grade: C" << std::endl;
}
// ... 其他等级判断
} else {
std::cout << "Score is invalid (out of range 0-100)." << std::endl;
}

// 检查一个字符是否是大写字母
char ch = 'Q';
if (ch >= 'A' && ch <= 'Z') {
std::cout << "'" << ch << "' is an uppercase letter." << std::endl;
}

return 0;
}

注意: 不能像数学中那样写 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
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
#include <iostream>

int main() {
bool is_raining = false;
int items_in_cart = 0;

// 如果不是在下雨
if (!is_raining) {
std::cout << "It's not raining. Good day for a walk!" << std::endl;
} else {
std::cout << "It's raining. Better stay inside." << std::endl;
}

// 检查购物车是否为空
// items_in_cart == 0 也可以,但 !items_in_cart 更简洁 (利用 0 转换为 false)
if (!items_in_cart) {
std::cout << "Your shopping cart is empty." << std::endl;
}

// 检查 cin 读取是否失败
int value;
std::cout << "Enter a number: ";
if (!(std::cin >> value)) { // 如果读取失败 (cin 转换为 false), !cin 为 true
std::cout << "Invalid input or EOF." << std::endl;
} else {
std::cout << "You entered: " << value << std::endl;
}

return 0;
}

6.2.5 逻辑运算符细节

  1. 优先级 (Precedence):

    • 逻辑非 ! 具有最高的优先级,高于所有关系运算符和算术运算符。

    • 逻辑与 && 的优先级高于逻辑或 ||

    • 逻辑运算符的优先级低于关系运算符 (<, ==, != 等)。

    • 赋值运算符 (=) 优先级最低。

    • *常见优先级顺序 (高到低):**

    1. !
    2. 算术运算符 (*, /, %, +, -)
    3. 关系运算符 (<, <=, >, >=)
    4. 相等运算符 (==, !=)
    5. 逻辑与 &&
    6. 逻辑或 ||
    7. 赋值运算符 (=, += 等)

    建议: 当不确定优先级或为了提高可读性时,使用括号 () 来明确指定运算顺序。

    1
    if ((age >= 18 && age < 65) || is_student) { ... } // 括号明确了 && 先于 ||
  2. 短路求值 (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
      #include <iostream>

      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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

int main() {
int x = 5, y = 10;
bool flag = false;

// 使用替代表示
if (x > 0 and y < 20) { // 等价于 x > 0 && y < 20
std::cout << "Condition (and) is true." << std::endl;
}

if (x < 0 or y == 10) { // 等价于 x < 0 || y == 10
std::cout << "Condition (or) is true." << std::endl;
}

if (not flag) { // 等价于 !flag
std::cout << "Condition (not) is true." << std::endl;
}

if (x not_eq y) { // 等价于 x != y
std::cout << "Condition (not_eq) is true." << std::endl;
}

return 0;
}

虽然这些替代表示是合法的 C++,但在现代编程实践中,直接使用符号运算符 (&&, ||, !) 更为常见和普遍接受。除非有特定的编码标准或环境限制要求使用替代表示,否则通常坚持使用符号运算符。

6.3 字符函数库cctype

C++ 继承了 C 语言的一个非常有用的函数库,用于处理字符。这个库的 C++ 头文件是 <cctype>,对应的 C 头文件是 <ctype.h>。它提供了一系列函数,可以方便地检查字符的类别(例如,是否是字母、数字、标点符号、空白等)以及转换字符的大小写。

这些函数通常接收一个 int 类型的参数(该参数值应能表示为 unsigned char 或等于 EOF),并返回一个 int 值。对于测试函数,返回非零值(通常解释为 true)表示条件满足,返回零值(解释为 false)表示条件不满足。对于转换函数,返回转换后的字符的整数表示。

包含头文件:

要使用这些函数,需要包含 <cctype> 头文件:

1
#include <cctype>

常用的字符测试函数:

函数名 描述
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 是标点符号(isgraphtrueisalnumfalse),返回 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
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
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include <cctype>
#include <string>

int main() {
std::string text = "Hello World! 123\t";
int alpha_count = 0;
int digit_count = 0;
int punct_count = 0;
int space_count = 0;

std::cout << "Analyzing text: \"" << text << "\"" << std::endl;

for (char ch : text) {
if (isalpha(ch)) {
alpha_count++;
if (isupper(ch)) {
std::cout << "'" << ch << "' is uppercase alpha." << std::endl;
} else { // islower(ch)
std::cout << "'" << ch << "' is lowercase alpha." << std::endl;
}
} else if (isdigit(ch)) {
digit_count++;
std::cout << "'" << ch << "' is a digit." << std::endl;
} else if (ispunct(ch)) {
punct_count++;
std::cout << "'" << ch << "' is punctuation." << std::endl;
} else if (isspace(ch)) {
space_count++;
std::cout << "'" << ch << "' (ASCII " << int(ch) << ") is whitespace." << std::endl;
} else if (iscntrl(ch)) {
std::cout << "'" << ch << "' (ASCII " << int(ch) << ") is a control character." << std::endl;
} else {
std::cout << "'" << ch << "' is something else." << std::endl;
}
}

std::cout << "\nSummary:" << std::endl;
std::cout << " Alphabetic: " << alpha_count << std::endl;
std::cout << " Digits: " << digit_count << std::endl;
std::cout << " Punctuation: " << punct_count << std::endl;
std::cout << " Whitespace: " << space_count << std::endl;

return 0;
}

常用的字符转换函数:

函数名 描述
tolower(ch) 如果 ch 是大写字母,返回其对应的小写字母;否则,返回 ch 不变。
toupper(ch) 如果 ch 是小写字母,返回其对应的大写字母;否则,返回 ch 不变。

用法与示例 (转换函数):

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
#include <iostream>
#include <cctype>
#include <string>

int main() {
std::string message = "CONVERT This Message.";
std::string lower_message = "";
std::string upper_message = "";

// 转换为小写
for (char ch : message) {
lower_message += tolower(ch); // 将每个字符转换为小写并附加
}
std::cout << "Original: " << message << std::endl;
std::cout << "Lowercase: " << lower_message << std::endl;

// 转换为大写
for (char ch : message) {
upper_message += toupper(ch); // 将每个字符转换为大写并附加
}
std::cout << "Uppercase: " << upper_message << std::endl;

// 示例:不区分大小写的比较
char response;
std::cout << "\nDo you want to continue? (Y/N): ";
std::cin >> response;
if (tolower(response) == 'y') { // 将输入转换为小写再比较
std::cout << "Continuing..." << std::endl;
} else {
std::cout << "Exiting..." << std::endl;
}

return 0;
}

<cctype> 库提供了一套标准、可移植的函数来处理字符分类和转换,这在处理用户输入、解析文本或进行不区分大小写的操作时非常有用。

6.4 三元运算符

C++ 提供了一个简洁的条件运算符,称为三元运算符(或条件运算符),它是 C++ 中唯一一个需要三个操作数的运算符。它通常用于根据条件将两个值中的一个赋给变量。

用法

三元运算符的语法如下:

1
condition ? expression1 : expression2;

其工作方式是:

  1. 首先计算 condition
  2. 如果 conditiontrue(非零),则计算 expression1,并且整个表达式的值就是 expression1 的值。
  3. 如果 conditionfalse(零),则计算 expression2,并且整个表达式的值就是 expression2 的值。

三元运算符通常可以替代简单的 if else 语句,使代码更紧凑。

示例代码

下面是一个使用三元运算符查找两个数中较大值的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

int main() {
int a = 10;
int b = 20;
int max_val;

// 使用三元运算符找出 a 和 b 中的较大值
max_val = (a > b) ? a : b;

std::cout << "The maximum value between " << a << " and " << b << " is: " << max_val << std::endl; // 输出: The maximum value between 10 and 20 is: 20

// 另一个例子:根据年龄判断是否成年
int age = 15;
std::string status = (age >= 18) ? "Adult" : "Minor";

std::cout << "Age: " << age << ", Status: " << status << std::endl; // 输出: Age: 15, Status: Minor

return 0;
}

代码解释:

  1. 在第一个示例中,条件 (a > b) 被评估。因为 10 > 20false,所以计算第二个表达式 b,并将 b 的值(即 20)赋给 max_val
  2. 在第二个示例中,条件 (age >= 18) 被评估。因为 15 >= 18false,所以计算第二个表达式 "Minor",并将这个字符串赋给 status

优点:

  • 简洁性: 可以用一行代码替代多行的 if else 结构,使代码更紧凑。

缺点:

  • 可读性: 对于复杂的条件或表达式,使用三元运算符可能会降低代码的可读性。在这种情况下,使用 if else 语句通常更好。

三元运算符是 C++ 中一个方便的工具,尤其适用于简单的条件赋值。

6.5 switch 语句

switch 语句是 C++ 中另一种用于控制程序流程的分支结构。它允许程序根据一个表达式的值从多个代码块中选择一个来执行。switch 语句通常用于替代冗长的 if else if else 结构,特别是当判断条件基于单个变量或表达式的离散值时。

用法

switch 语句的基本语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
switch (expression) {
case constant_expression_1:
// code block to be executed if expression equals constant_expression_1
break; // 可选,但通常需要
case constant_expression_2:
// code block to be executed if expression equals constant_expression_2
break; // 可选
// ... 可以有更多的 case
default: // 可选
// code block to be executed if expression doesn't match any case
break; // 可选
}

工作方式:

  1. 首先计算 switch 括号内的 expression(表达式)。这个表达式必须得出一个整数类型(如 int, char, enum)或可以隐式转换为整数类型的值。
  2. 程序将 expression 的值与每个 case 后面跟着的 constant_expression(常量表达式)进行比较。
  3. 如果找到匹配的 case,则执行该 case 标签下的代码块。
  4. break 语句的作用是跳出 switch 结构。如果没有 break,程序会继续执行下一个 case 的代码块(称为“贯穿”),直到遇到 breakswitch 语句结束。
  5. 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
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>

int main() {
int choice;

std::cout << "Enter a number (1-3): ";
std::cin >> choice;

switch (choice) {
case 1:
std::cout << "You chose option 1." << std::endl;
break; // 跳出 switch
case 2:
std::cout << "You chose option 2." << std::endl;
break; // 跳出 switch
case 3:
std::cout << "You chose option 3." << std::endl;
break; // 跳出 switch
default:
std::cout << "Invalid choice." << std::endl;
// default 后面通常也放 break,虽然在这里不是必需的,但保持一致性是好习惯
break;
}

// 演示没有 break 的情况 (贯穿)
char grade = 'B';
std::cout << "\nGrade example (fall-through):" << std::endl;
switch (grade) {
case 'A':
std::cout << "Excellent! ";
// 没有 break
case 'B':
std::cout << "Good! ";
// 没有 break
case 'C':
std::cout << "Passing. ";
break; // 在这里跳出
case 'D':
case 'F': // 可以将多个 case 关联到同一代码块
std::cout << "Needs improvement. ";
break;
default:
std::cout << "Invalid grade. ";
break;
}
std::cout << std::endl; // 输出: Good! Passing.

return 0;
}

代码解释:

  1. 第一个 switch 根据用户输入的 choice 值执行相应的 casebreak 语句确保只执行匹配 case 的代码。如果输入不是 1、2 或 3,则执行 default 部分。
  2. 第二个 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
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
#include <iostream>

// 定义一个枚举类型表示颜色
enum Color { RED, GREEN, BLUE, YELLOW };

int main() {
Color selectedColor = GREEN;

switch (selectedColor) {
case RED:
std::cout << "The color is Red." << std::endl;
break;
case GREEN:
std::cout << "The color is Green." << std::endl; // 这将被执行
break;
case BLUE:
std::cout << "The color is Blue." << std::endl;
break;
// 注意:如果 case 覆盖了所有枚举量,可以省略 default,
// 但如果枚举可能扩展,或者你想处理无效值,最好加上 default。
default:
std::cout << "Unknown color." << std::endl;
break;
}

return 0;
}

使用枚举量作为 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
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
// 使用 if else if
int num = 2;
if (num == 1) {
std::cout << "One" << std::endl;
} else if (num == 2) {
std::cout << "Two" << std::endl; // 执行
} else if (num == 3) {
std::cout << "Three" << std::endl;
} else {
std::cout << "Other" << std::endl;
}

// 使用 switch (等效)
switch (num) {
case 1:
std::cout << "One" << std::endl;
break;
case 2:
std::cout << "Two" << std::endl; // 执行
break;
case 3:
std::cout << "Three" << std::endl;
break;
default:
std::cout << "Other" << std::endl;
break;
}

对于这种基于单个整数值的多路分支,switch 通常被认为更清晰、有时效率也可能更高(编译器可能将其优化为跳转表)。但对于涉及范围或非整数类型的判断,则必须使用 if else if 结构。

6.6 break 和 continue 语句

C++ 提供了两个特殊的语句,breakcontinue,用于在循环(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
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
35
36
37
38
39
#include <iostream>

int main() {
// 查找数组中第一个负数的位置
int numbers[] = {10, 5, 0, -2, 8, -5};
int found_index = -1; // 初始化为 -1,表示未找到

std::cout << "Searching for the first negative number..." << std::endl;
for (int i = 0; i < 6; ++i) {
std::cout << "Checking index " << i << ", value: " << numbers[i] << std::endl;
if (numbers[i] < 0) {
found_index = i;
std::cout << "Negative number found at index " << i << ". Breaking loop." << std::endl;
break; // 找到第一个负数,立即跳出 for 循环
}
}

if (found_index != -1) {
std::cout << "The loop was terminated early." << std::endl;
} else {
std::cout << "No negative number found in the array." << std::endl;
}

// 演示在嵌套循环中的作用
std::cout << "\nNested loop example:" << std::endl;
for (int i = 1; i <= 3; ++i) {
for (int j = 1; j <= 3; ++j) {
if (i == 2 && j == 2) {
std::cout << "Breaking inner loop at i=" << i << ", j=" << j << std::endl;
break; // 只跳出内层循环 (j 循环)
}
std::cout << "i=" << i << ", j=" << j << std::endl;
}
// break 后,控制流会到达这里,继续外层循环的下一次迭代
std::cout << "End of inner loop for i=" << i << std::endl;
}

return 0;
}

代码解释:

  1. 第一个循环遍历 numbers 数组。当 i 为 3 时,numbers[3] 是 -2,满足 if 条件。found_index 被设为 3,打印消息,然后 break 语句被执行,立即终止 for 循环。后面的元素(8 和 -5)不会被检查。
  2. 在嵌套循环示例中,当 i 为 2 且 j 为 2 时,break 语句执行。它只终止了最内层的 j 循环。外层的 i 循环继续执行其下一次迭代(当 i 为 3 时)。

6.6.2 continue 语句

continue 语句用于跳过当前循环迭代中剩余的代码,并立即开始下一次迭代。与 break 不同,continue 不会终止整个循环。

用法:

  • whiledo while 循环中: 控制流跳转到循环条件的判断处。
  • for 循环中: 控制流首先跳转到 for 循环的更新表达式(例如 ++i),然后跳转到循环条件的判断处。

示例:

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
#include <iostream>

int main() {
// 打印 1 到 10 之间的奇数
std::cout << "Odd numbers between 1 and 10:" << std::endl;
for (int i = 1; i <= 10; ++i) {
if (i % 2 == 0) { // 如果 i 是偶数
continue; // 跳过本次迭代剩余的部分 (cout 语句),直接进行下一次迭代 (i++)
}
// 只有当 i 是奇数时,才会执行这行代码
std::cout << i << " ";
}
std::cout << std::endl; // 输出: 1 3 5 7 9

// 演示在 while 循环中
std::cout << "\nSkipping number 5 in while loop (1 to 7):" << std::endl;
int k = 0;
while (k < 7) {
k++; // 先递增 k
if (k == 5) {
std::cout << "(Skipping 5) ";
continue; // 跳过本次迭代的 cout
}
std::cout << k << " ";
}
std::cout << std::endl; // 输出: 1 2 3 4 (Skipping 5) 6 7

return 0;
}

代码解释:

  1. for 循环中,当 i 是偶数时(i % 2 == 0 为真),continue 语句执行。它跳过了 std::cout << i << " "; 这行代码,直接执行更新表达式 ++i,然后判断循环条件 i <= 10。因此,只有奇数被打印出来。
  2. while 循环中,当 k 递增到 5 时,if 条件满足,continue 执行,跳过了 std::cout << k << " ";,直接回到 while (k < 7) 的条件判断。

总结

  • break: 完全终止最内层的循环或 switch
  • continue: 跳过当前循环迭代的剩余部分,进入下一次迭代(如果循环条件允许)。

这两个语句可以使循环控制更加灵活,但过度使用可能会降低代码的可读性。通常,可以通过调整循环条件或使用 if 语句来避免一些不必要的 breakcontinue

6.7 读取数字的循环

在 C++ 程序中,经常需要编写循环来读取用户的数字输入,直到满足某个条件(例如输入特定值或遇到无效输入)为止。然而,处理数字输入,特别是处理潜在的错误输入,需要一些技巧。

基本的数字读取循环

一个简单的读取数字并累加的循环可能如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

int main() {
int sum = 0;
int input_number;

std::cout << "Enter numbers to sum (enter a non-number to quit): ";

// 尝试读取一个整数,如果成功,cin 返回 true
while (std::cin >> input_number) {
sum += input_number;
std::cout << "Current sum: " << sum << std::endl;
std::cout << "Enter next number: ";
}

// 当输入非数字时,循环结束
std::cout << "\nLoop terminated." << std::endl;
std::cout << "Final sum: " << sum << std::endl;

return 0;
}

工作方式:

  1. std::cin >> input_number 尝试从输入流中读取一个整数并存储到 input_number 中。
  2. 这个表达式本身会返回 std::cin 对象。在需要布尔值的上下文中(如 while 的条件),std::cin 对象会根据流的状态转换为 truefalse
  3. 如果成功读取一个整数,流状态是正常的,std::cin 转换为 true,循环体执行。
  4. 如果用户输入了非数字(例如输入 “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 操作通常也会立即失败。

解决方案:

  1. 清除错误状态: 使用 std::cin.clear() 方法重置流的错误标志。
  2. 忽略无效输入: 使用 std::cin.ignore() 方法丢弃输入缓冲区中不需要的字符。通常会忽略直到下一个换行符 \n 或达到某个最大字符数。

示例 (更健壮的数字读取循环):

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
#include <limits> // 需要包含 <limits> 来使用 numeric_limits

int main() {
int sum = 0;
int input_number;

std::cout << "Enter numbers to sum (enter a non-number to quit): ";

while (true) { // 使用无限循环,内部处理退出条件
std::cout << "Enter a number: ";
if (std::cin >> input_number) {
// 成功读取数字
sum += input_number;
std::cout << "Current sum: " << sum << std::endl;
} else {
// 读取失败 (输入了非数字或 EOF)
std::cout << "Invalid input detected or EOF reached." << std::endl;

// 检查是否是文件结束符 (EOF)
if (std::cin.eof()) {
std::cout << "End of file reached. Exiting." << std::endl;
break; // 如果是 EOF,直接退出循环
}

// 清除错误标志,使 cin 恢复正常
std::cout << "Clearing error state..." << std::endl;
std::cin.clear();

// 丢弃缓冲区中导致错误的无效输入,直到换行符
std::cout << "Discarding invalid input..." << std::endl;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

std::cout << "Please try entering a number again, or press Ctrl+Z/Ctrl+D to exit." << std::endl;
// 注意:在这个版本中,遇到非数字输入后,我们提示用户重试或退出,
// 而不是像第一个例子那样直接终止求和。
// 如果希望遇到非数字就退出,可以在 else 块的末尾加上 break;
// break; // 取消注释此行,则遇到非数字输入时退出循环
}
}

std::cout << "\nLoop finished." << std::endl;
std::cout << "Final sum: " << sum << std::endl;

// 可以在这里尝试读取其他输入,因为错误状态已被清除
std::string remaining_input;
std::cout << "\nEnter some text: ";
// 需要先忽略掉上次输入留下的换行符(如果存在)
if (std::cin.peek() == '\n') {
std::cin.ignore();
}
std::getline(std::cin, remaining_input);
std::cout << "You entered: " << remaining_input << std::endl;


return 0;
}

代码解释:

  1. 使用 while(true) 创建一个看似无限的循环,退出逻辑放在循环内部。
  2. if (std::cin >> input_number) 尝试读取数字。
  3. 如果成功,执行加法。
  4. 如果失败 (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>

步骤:

  1. 包含头文件: #include <fstream>
  2. 创建 ofstream 对象: ofstream 类(output file stream)用于向文件写入数据。你需要创建一个该类的对象,并在创建时或之后将其与一个文件名关联起来。
    1
    2
    3
    4
    std::ofstream outputFile; // 创建 ofstream 对象
    outputFile.open("mydata.txt"); // 将对象与文件关联(如果文件不存在则创建,如果存在则清空内容)
    // 或者在创建时直接关联
    // std::ofstream outputFile("mydata.txt");
  3. 检查文件是否成功打开: 在尝试写入之前,最好检查文件是否成功打开。可以使用 is_open() 方法或直接检查流对象的状态。
    1
    2
    3
    4
    if (!outputFile.is_open()) { // 或者 if (!outputFile)
    std::cerr << "Error opening file for writing!" << std::endl;
    return 1; // 或者进行其他错误处理
    }
  4. 写入数据: 使用与 cout 类似的 << 运算符将数据写入文件流。
    1
    2
    3
    4
    5
    6
    int 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;
  5. 关闭文件: 完成写入后,应该关闭文件以确保所有数据都被刷新(写入)到磁盘,并释放文件资源。可以显式调用 close() 方法,或者当 ofstream 对象离开其作用域时(例如函数结束),其析构函数会自动关闭文件。
    1
    outputFile.close(); // 显式关闭

示例代码:

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
#include <iostream>
#include <fstream> // 包含文件流头文件
#include <string>

int main() {
// 1. 创建 ofstream 对象并关联文件
std::ofstream outFile("report.txt"); // 尝试打开/创建 report.txt

// 2. 检查文件是否成功打开
if (!outFile.is_open()) {
std::cerr << "Could not open report.txt for writing." << std::endl;
return 1; // 返回错误码
}

// 3. 写入数据
std::cout << "Writing data to report.txt..." << std::endl;
outFile << "--- Sales Report ---" << std::endl;
outFile << "Product: Laptop" << std::endl;
outFile << "Quantity: 15" << std::endl;
outFile << "Revenue: " << 15 * 1200.50 << std::endl;
outFile << "--------------------" << std::endl;

// 4. 关闭文件 (虽然析构函数会自动关闭,但显式关闭是好习惯)
outFile.close();

std::cout << "Data written successfully." << std::endl;

return 0;
}

运行此程序后,将在程序所在的目录下创建一个名为 report.txt 的文件(如果不存在),其内容如下:

1
2
3
4
5
--- Sales Report ---
Product: Laptop
Quantity: 15
Revenue: 18007.5
--------------------

6.8.3 读取文本文件

从文本文件读取数据与写入类似,但使用 ifstream 类(input file stream)。

步骤:

  1. 包含头文件: #include <fstream>

  2. 创建 ifstream 对象: 创建一个 ifstream 对象并将其与要读取的文件名关联。

    1
    2
    3
    4
    std::ifstream inputFile; // 创建 ifstream 对象
    inputFile.open("mydata.txt"); // 将对象与文件关联以供读取
    // 或者在创建时直接关联
    // std::ifstream inputFile("mydata.txt");
  3. 检查文件是否成功打开: 同样,检查文件是否成功打开至关重要。如果文件不存在或无法访问,打开操作会失败。

    1
    2
    3
    4
    if (!inputFile.is_open()) { // 或者 if (!inputFile)
    std::cerr << "Error opening file for reading!" << std::endl;
    return 1;
    }
  4. 读取数据: 可以使用与 cin 类似的 >> 运算符来读取由空格分隔的数据,或者使用 std::getline() 来读取整行。读取操作通常放在循环中,直到到达文件末尾。

    • 使用 >> 读取: 它会跳过前导空白(空格、制表符、换行符),然后读取直到遇到下一个空白字符。
    • 使用 std::getline(inputFile, lineString) 读取: 它会读取整行(包括空格),直到遇到换行符 \n 为止(换行符本身会被读取并丢弃)。
  5. 检查文件末尾 (EOF): 当尝试读取但已无数据可读时(到达文件末尾),输入流会进入特殊状态。循环通常依赖于检查流的状态来终止。

    • while (inputFile >> variable): 当 >> 成功读取时,流状态为 true,循环继续;到达文件末尾或遇到无效数据时,状态变为 false,循环终止。
    • while (std::getline(inputFile, lineString)): 当 getline 成功读取一行时,流状态为 true;到达文件末尾时,状态变为 false
  6. 关闭文件: 读取完成后,关闭文件。同样,可以显式调用 close(),或者依赖对象的析构函数。

    1
    inputFile.close();

示例代码 (读取 report.txt):

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <iostream>
#include <fstream>
#include <string>
#include <vector> // 用于存储读取的行

int main() {
// 1. 创建 ifstream 对象并关联文件
std::ifstream inFile("report.txt");

// 2. 检查文件是否成功打开
if (!inFile.is_open()) {
std::cerr << "Could not open report.txt for reading." << std::endl;
return 1;
}

std::cout << "Reading data from report.txt using getline:" << std::endl;
std::string line;
std::vector<std::string> lines;

// 3. 使用 getline 逐行读取
while (std::getline(inFile, line)) {
std::cout << "Read line: " << line << std::endl;
lines.push_back(line); // 将读取的行存入 vector
}

// 检查循环是因为到达文件末尾还是发生错误
if (inFile.eof()) {
std::cout << "\nReached end of file." << std::endl;
} else if (inFile.fail()) {
std::cerr << "\nError reading file (not EOF)." << std::endl;
}

// 4. 关闭文件
inFile.close();

std::cout << "\n--- Stored Lines ---" << std::endl;
for(const std::string& stored_line : lines) {
std::cout << stored_line << std::endl;
}

// 示例:使用 >> 读取特定类型的数据 (假设文件格式已知)
std::ifstream dataFile("report.txt"); // 重新打开文件
if (!dataFile.is_open()) {
std::cerr << "Could not reopen report.txt" << std::endl;
return 1;
}

std::cout << "\nReading specific data using >> (may be fragile):" << std::endl;
std::string header, productLabel, productName, qtyLabel;
int quantity;
double revenue;

// 跳过第一行
std::getline(dataFile, header);
// 读取第二行的数据
dataFile >> productLabel >> productName;
// 读取第三行的数据
dataFile >> qtyLabel >> quantity;
// 跳过第四行的 Revenue: 标签
std::string revenueLabel;
dataFile >> revenueLabel >> revenue;


if (dataFile) { // 检查读取是否成功
std::cout << "Product: " << productName << std::endl;
std::cout << "Quantity: " << quantity << std::endl;
std::cout << "Revenue: " << revenue << std::endl;
} else {
std::cerr << "Failed to parse data using >>" << std::endl;
}

dataFile.close();


return 0;
}

代码解释:

  1. 第一个循环使用 std::getline() 读取文件的每一行,并将其打印出来,同时存储在 lines 向量中。while (std::getline(inFile, line)) 是读取文本文件的常用模式。
  2. 循环结束后,通过检查 inFile.eof()inFile.fail() 可以判断循环是正常结束(到达文件末尾)还是因为其他错误。
  3. 第二个示例演示了使用 >> 操作符读取特定格式的数据。这种方法对于格式严格固定的文件可能有效,但如果文件格式稍有变化(例如多了空格),>> 就可能读取失败或读到错误的数据,因此通常不如 getline 健壮。需要仔细处理标签和数据类型。

文件 I/O 是 C++ 编程中非常重要的部分,它使得程序能够持久化数据,并与其他程序或系统进行交互。

6.9 总结

本章介绍了 C++ 中用于控制程序流程的各种分支语句和相关概念,使得程序能够根据不同的条件执行不同的代码路径。

主要内容回顾:

  1. if 语句系列:

    • if 语句:根据条件是否为真来决定是否执行某段代码。
    • if else 语句:提供两个代码路径,根据条件为真或假选择其一执行。
    • if else if else 结构:用于处理多个互斥的条件,提供多路分支选择。
  2. 逻辑运算符:

    • || (逻辑或):两个操作数中至少一个为真时,结果为真。
    • && (逻辑与):两个操作数都为真时,结果才为真。具有短路求值特性。
    • ! (逻辑非):反转操作数的逻辑状态(真变假,假变真)。
  3. cctype 库:

    • 提供了一系列用于处理字符的函数,如 isalpha(), isdigit(), isspace(), ispunct(), toupper(), tolower() 等,方便进行字符分类和转换。
  4. 三元运算符 (?:):

    • 提供了一种简洁的方式来根据条件选择两个值中的一个,是 if else 语句的一种紧凑替代形式,常用于简单的赋值操作。
    • 语法:condition ? expression1 : expression2;
  5. switch 语句:

    • 根据一个整数表达式的值,从多个 case 标签中选择一个匹配的执行点。
    • 通常与 break 语句配合使用,以防止“贯穿”到下一个 case
    • default 标签处理所有其他不匹配的情况。
    • case 标签必须是常量表达式。
    • 适用于基于单个离散整数值的多路分支。
  6. breakcontinue 语句:

    • break:立即终止最内层的循环(for, while, do while)或 switch 语句。
    • continue:跳过当前循环迭代的剩余部分,直接开始下一次迭代(更新和条件检查)。
  7. 读取数字的循环:

    • 演示了如何使用循环(如 while (cin >> value))来连续读取数字输入。
    • 强调了处理错误输入的重要性,包括使用 cin.clear() 清除错误状态和 cin.ignore() 丢弃无效输入,以编写更健壮的输入代码。
  8. 简单文件输入/输出:

    • 引入了 <fstream> 库,用于文件操作。
    • ofstream:用于向文件写入数据(输出文件流),使用 << 操作符。
    • ifstream:用于从文件读取数据(输入文件流),使用 >>getline()
    • 强调了文件打开检查 (is_open()) 和关闭文件 (close() 或利用对象析构) 的重要性。
    • 区分了文本 I/O 和二进制 I/O 的基本概念。

通过掌握这些分支结构和控制语句,可以编写出能够响应不同情况、处理用户输入和文件数据的更复杂、更灵活的 C++ 程序。

评论