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: 一个求值为布尔值 (true 或 false) 的表达式。通常是关系表达式(如 x > 5, name == "Alice")或逻辑表达式。非零值被视为 true,零值被视为 false。
  • { ... }: 花括号定义了一个语句块。如果条件为 true,则执行块内的所有语句。如果只有一条语句需要根据条件执行,可以省略花括号,但为了清晰和避免错误,通常推荐总是使用花括号。

执行流程:

  1. 计算 condition 的值。
  2. 如果 condition 为 true,执行 if 语句后面的语句(或语句块)。
  3. 如果 condition 为 false,跳过 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. 如果 condition 为 true,执行 if 后面的语句块 (statement_block_1),然后跳过 else 后面的语句块 (statement_block_2)。
  3. 如果 condition 为 false,跳过 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. 使用花括号 {}: 即使 if 或 else 后面只有一条语句,也推荐使用花括号。这可以防止在后续添加代码时引入悬挂 else (dangling else) 等错误,并使代码结构更清晰。
  2. 缩进: if 和 else 块内部的语句应该相对于 if 和 else 关键字进行缩进(通常是 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. 从上到下依次检查每个 if 和 else if 的条件。
  2. 一旦找到第一个为 true 的条件,就执行其对应的语句块。
  3. 执行完该块后,跳过所有剩余的 else if 和 else 分支,直接执行整个 if else if else 结构之后的代码。
  4. 如果所有的 if 和 else 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 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
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 是标点符号(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
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. 如果 condition 为 true(非零),则计算 expression1,并且整个表达式的值就是 expression1 的值。
  3. 如果 condition 为 false(零),则计算 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 > 20 是 false,所以计算第二个表达式 b,并将 b 的值(即 20)赋给 max_val。
  2. 在第二个示例中,条件 (age >= 18) 被评估。因为 15 >= 18 是 false,所以计算第二个表达式 "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 的代码块(称为“贯穿”),直到遇到 break 或 switch 语句结束。
  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 值执行相应的 case。break 语句确保只执行匹配 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++ 提供了两个特殊的语句,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
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 不会终止整个循环。

用法:

  • 在 while 和 do 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 语句来避免一些不必要的 break 或 continue。

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 对象会根据流的状态转换为 true 或 false。
  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. break 和 continue 语句:

    • 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++ 程序。

评论