第4章:表达式(Expressions)
一、导读
1.1 本章学习目标
第4章讲解C++的表达式和运算符。正确理解表达式的求值规则是避免bug的关键。本章核心目标:
- 掌握运算符优先级和结合性:复杂表达式如何求值?
- 理解逻辑运算符的短路求值:
&&和||的特殊行为 - 掌握递增递减运算符:前置和后置的区别
- 理解类型转换规则:隐式转换和显式转换
- 避免未定义行为:哪些写法是危险的?
- 学会使用C++风格的类型转换:static_cast、const_cast等
1.2 为什么本章如此重要
表达式的求值规则看似简单,实则暗藏陷阱。运算符优先级错误、未定义行为、隐式类型转换导致的意外结果——这些都是C++程序中最常见的bug来源。理解这些规则,才能写出正确的代码。
1.3 核心问题引导
i = ++i + i++ 的结果是什么?为什么这种写法是未定义行为?
为什么 && 和 || 被称为"短路"运算符?有什么实际应用?
static_cast 和 reinterpret_cast 有什么区别?
为什么C++不推荐C风格的类型转换?
成员访问运算符 . 和 -> 有什么区别?
二、核心概念详解
4.1 运算符优先级和结合性
#include <iostream>
int main() {
// 优先级示例
int a = 2 + 3 * 4; // 14(乘法优先于加法)
int b = (2 + 3) * 4; // 20(括号改变优先级)
// 结合性示例
int c = 10 - 3 - 2; // 5(左结合:(10-3)-2)
// 赋值运算符是右结合
int x, y, z;
x = y = z = 42; // 从右到左赋值
// 常见优先级陷阱
int flags = 0x03;
if (flags & 0x01 == 1) // 错误!==优先于&
std::cout << "bit 0 set" << std::endl;
if ((flags & 0x01) == 1) // 正确
std::cout << "bit 0 set" << std::endl;
return 0;
}4.2 逻辑运算符与短路求值
#include <iostream>
int main() {
// 短路求值:&&和||不一定会计算右侧表达式
int x = 0;
// && 短路:左侧为false时不计算右侧
if (x != 0 && 10 / x > 1) {
// 安全:x!=0为false时,不会执行10/x
}
// || 短路:左侧为true时不计算右侧
int* ptr = nullptr;
if (ptr == nullptr || *ptr > 0) {
// 安全:ptr为空时不执行*ptr
}
// 逗号运算符:从左到右求值,返回最右侧的值
int i = (1, 2, 3); // i = 3
std::cout << i << std::endl;
return 0;
}4.3 递增递减运算符
#include <iostream>
int main() {
int i = 0;
// 前置递增:先递增,再返回
std::cout << ++i << std::endl; // 输出1,i变为1
// 后置递增:先返回,再递增
std::cout << i++ << std::endl; // 输出1,i变为2
std::cout << i << std::endl; // 输出2
// 在表达式中混合使用(危险!)
int a = 5;
// int b = a++ + ++a; // 未定义行为!不要这样写
// 推荐:分开写
int c = a++; // c = 5, a = 6
int d = ++a; // a = 7, d = 7
// 在循环中的使用
for (int j = 0; j < 10; ++j) { // 推荐前置递增
std::cout << j << " ";
}
return 0;
}4.4 成员访问运算符
#include <iostream>
#include <string>
struct Point {
int x, y;
void display() const {
std::cout << "(" << x << ", " << y << ")" << std::endl;
}
};
int main() {
Point p = {3, 4};
Point* pp = &p;
// 点运算符:通过对象访问
std::cout << p.x << std::endl;
p.display();
// 箭头运算符:通过指针访问
std::cout << pp->x << std::endl;
pp->display();
// pp->x 等价于 (*pp).x
std::cout << (*pp).x << std::endl;
return 0;
}4.5 类型转换
#include <iostream>
int main() {
// 隐式类型转换
int i = 42;
double d = i; // int -> double(安全)
double pi = 3.14;
int n = pi; // double -> int(截断,可能丢失精度)
std::cout << n << std::endl; // 3
// 有符号和无符号的转换(危险!)
unsigned int u = 10;
int s = -1;
if (s < u) {
std::cout << "s < u" << std::endl;
} else {
std::cout << "s >= u" << std::endl; // 会执行这里!
// 因为-1被转换为unsigned int,变成很大的正数
}
// C++风格的显式转换
double x = 3.14;
// static_cast:安全的类型转换
int y = static_cast<int>(x); // 推荐
// const_cast:去除const属性
const int ci = 42;
int& ref = const_cast<int&>(ci); // 危险
// reinterpret_cast:底层位模式转换
int* ptr = nullptr;
long addr = reinterpret_cast<long>(ptr);
// dynamic_cast:安全的向下转型(用于多态)
// 需要虚函数
// 不推荐C风格转换
int z = (int)x; // C风格
int w = int(x); // 函数风格
return 0;
}4.6 未定义行为
#include <iostream>
int main() {
// 以下都是未定义行为(UB),结果不可预测
// 1. 有符号整数溢出
int max_int = 2147483647;
// int overflow = max_int + 1; // UB
// 2. 除以零
// int div_zero = 1 / 0; // UB
// 3. 数组越界
int arr[5] = {1, 2, 3, 4, 5};
// int oob = arr[10]; // UB
// 4. 空指针解引用
// int* null_ptr = nullptr;
// int val = *null_ptr; // UB
// 5. 同一表达式中多次修改同一变量
int i = 0;
// int bad = i++ + i++; // UB
// 6. 使用未初始化的变量
// int uninit;
// std::cout << uninit << std::endl; // UB
return 0;
}三、实战案例
4.1 位运算应用
#include <iostream>
int main() {
// 位运算常用于标志位操作
enum Flags {
READ = 1 << 0, // 0x01
WRITE = 1 << 1, // 0x02
EXECUTE = 1 << 2 // 0x04
};
int perms = READ | WRITE; // 设置读和写权限
// 检查标志
if (perms & READ)
std::cout << "Has read permission" << std::endl;
// 设置标志
perms |= EXECUTE;
// 清除标志
perms &= ~WRITE;
// 切换标志
perms ^= READ; // 如果已设置则清除,否则设置
std::cout << "Permissions: " << perms << std::endl;
// 位运算技巧
int x = 42;
std::cout << "x & (x-1): " << (x & (x-1)) << std::endl; // 清除最低位的1
std::cout << "x & (-x): " << (x & (-x)) << std::endl; // 提取最低位的1
return 0;
}4.2 条件运算符
#include <iostream>
#include <algorithm>
int main() {
int a = 10, b = 20;
// 三元运算符
int max_val = (a > b) ? a : b;
std::cout << "Max: " << max_val << std::endl;
// 嵌套三元运算符(不推荐,可读性差)
int score = 85;
char grade = (score >= 90) ? 'A' :
(score >= 80) ? 'B' :
(score >= 70) ? 'C' : 'D';
std::cout << "Grade: " << grade << std::endl;
// 使用std::max和std::min更清晰
int max2 = std::max(a, b);
int min2 = std::min(a, b);
return 0;
}四、本章小结
第4章讲解了C++表达式的核心规则:
运算符优先级:理解优先级和结合性,使用括号明确意图。
短路求值:&& 和 || 不一定会计算右侧表达式,可用于安全检查。
递增递减:前置和后置有区别,避免在表达式中混合使用。
类型转换:优先使用C++风格的显式转换(static_cast等),避免隐式转换陷阱。
未定义行为:溢出、除零、越界、空指针等都会导致UB,必须避免。
五、思考与练习
思考题
为什么 sizeof(char) 总是1?sizeof('a') 在C和C++中结果不同吗?
短路求值在什么场景下非常有用?
为什么同一表达式中多次修改同一变量是未定义行为?
static_cast 和 dynamic_cast 有什么区别?各自适用什么场景?
编程练习
编写程序验证运算符优先级(如 2 + 3 * 4 的结果)。
使用位运算实现一个简单的权限管理系统。
编写程序找出各种类型在当前平台上的大小。