第五章:循环和关系表达式 | C++ Primer Plus 重点带看
顺序点
顺序点(sequence point,C++11 起改称顺序规则)是程序执行过程中的一个"检查点"。在顺序点处,之前所有副作用(赋值、递增、递减)必须全部完成,之后才执行下一步。
y = (4 + ++x) - (6 - ++x); // ❌ 未定义行为! // C++ 不保证 x 的值在计算子表达式 4 + x++ 后立刻增加 1。 // 因此,若 x 执行这条语句前为 1, // 可能实际结果是 y = (4 + 2) - (6 - 2) = 2 // 也可能是 y = (4 + 2) - (6 - 3) = 3 // 所以,一条语句中不要对同一变量产生多个副作用。
- C++ 中的顺序点:
顺序点 | 说明 |
; 分号 | 一条完整语句结束时 |
, 逗号运算符 | 逗号左侧表达式求值完成后 |
&&、||、?: | 第一个操作数求值完成后(短路求值) |
函数调用前 | 所有参数求值完成后、进入函数体之前 |
return 语句 | return 表达式求值完成后 |
完整表达式结尾 | if / while 的条件表达式、for的三个表达式之间 |
x++ (后置递增) 和 ++x (前置递增)
int x = 5; int a = ++x; // 先加 1,x 变成 6,然后返回 6 → a = 6, x = 6 int b = x++; // 先返回 5,然后 x 加 1 → b = 5, x = 7 ++x(前置): ① x = x + 1 ② 返回 x → 没有临时变量,直接操作 x++(后置): ① temp = x ← 临时变量保存旧值 ② x = x + 1 ③ 返回 temp → 多了一个临时变量 编译器能不能优化: int 等内置类型 → 能优化掉,最终代码一样 结构体/类类型 → 不一定能优化,因为拷贝有成本
for 循环和 while 循环的区别
for 中的条件可以为空,空认为条件一直位真;while 不能省略条件。
C++ 创建类型别名的三种方式以及区别
1、#define new old:纯粹的文本替换,编译前由预处理器处理,不安全,不遵循作用域,尽量避免。
#define INT_PTR int* // 不受函数作用域限制,宏定义从这行开始全局生效 INT_PTR a, b; // 展开为 int* a, b → a 是 int*,b 是 int ⚠️。PTR a, b 被展开成 int* a, b,* 只修饰 a #define PI 3.14 int x = PI; // ✅ 编译通过,3.14 隐式截断为 3,但没有警告
2、typedef old new:安全,但不能定义模板别名。
typedef int* INT_PTR; INT_PTR a, b; // a 和 b 都是 int* ✅
3、using new = old:安全,支持模板别名,现代 C++ 首选
using INT_PTR = int*; INT_PTR a, b; // a 和 b 都是 int* ✅
三种方式都不能创建新类型,只能给已有类型创建别名,另外在声明指针变量的时候只能用 typedef,using。
using 是最好的方式(c++11 之后的方式),而且 using 可以用来定义模板别名,typedef 不行(只能定义具体化的模板别名)。
template<typename T> using MyVector = std::vector<T>; typedef std::vector<int> IntVector;
特性 | #define | typedef | using |
本质 | 预处理器文本替换 | C++ 类型别名 | C++ 类型别名 |
类型安全 | ❌ 不检查 | ✅ 检查 | ✅ 检查 |
作用域 | ❌ 不遵循 | ✅ 遵循 | ✅ 遵循 |
调试友好 | ❌ 展开后丢失 | ✅ 保留别名信息 | ✅ 保留别名信息 |
指针声明 | ⚠️ * 只修饰第一个变量 | ✅ 所有变量都是指针 | ✅ 所有变量都是指针 |
模板别名 | ❌ | ❌(需包装) | ✅ |
多个别名同时声明 | ❌ | ✅ | ❌(每行一个) |
C++ 版本 | C | C/C++ | C++11 |
基于范围的 for 循环
基于范围的 for 循环(也叫 range-for)是 C++11 引入的语法糖,用于简洁地遍历容器或数组的所有元素,无需手动管理迭代器或下标。
基本语法:for (声明 : 可遍历对象) { }
三种声明:
auto x → 拷贝,改不了原元素
auto& x → 引用,能改原元素
const auto& x → const 引用,只读不拷贝(最推荐)
适用对象:数组、STL 容器、string、初始化列表、自定义类型(有 begin/end)
注意事项:
1、循环中不能增删容器元素。(range-for 底层依赖 begin() 和 end(),end() 在循环开始时就计算好了,修改容器会导致迭代器失效。)
2、数组退化为指针后不能用。
void print(int arr[], int size) {
for (auto x : arr) { } // ❌ 编译报错,arr 是指针,不知道长度
}
int arr[] = {1, 2, 3};
for (auto x : arr) { } // ✅ 编译器知道数组大小
3、std::vector<bool> 按引用遍历有问题。(std::vector<bool> 是一个特殊实现,用位压缩存储,不满足标准容器的引用语义,是 C++ 的一个历史遗留问题。)
C++ Primer Plus 精读|从入门到面试,重点内容全程带看。 本专栏以《C++ Primer Plus》为蓝本,逐章提炼必考知识点、易错点、面试高频考点,跳过冗余示例,直击语法本质与工程实践,帮你高效吃透 C++ 基础,夯实底层开发必备能力。
巨人网络成长空间 114人发布