运算符
# 第 4 章 C++ 运算符
# 目录介绍
# 4.1 运算符介绍
# 4.1.1 运算符由来
起源:数学与逻辑的抽象。运算符的概念源于数学(+, -, *, /, =)和形式逻辑(&&, ||, !)。
编程语言将这些符号引入,用于操作变量、常量和表达式,执行特定的计算或逻辑功能。
# 4.1.2 运算符本质
本质:编译器内置的“函数”。从底层看,运算符是编译器内置的、具有特定功能的“函数”。
当你写 a + b时,编译器会将其翻译成类似 operator+(a, b)的调用(对于内置类型)。编译器知道如何执行这个“函数”。
对于用户自定义类型(类/结构体),C++ 允许你重载这些运算符,定义它们的具体行为(operator+成员函数或全局函数)。这使得运算符的应用范围扩展到自定义类型。
# 4.1.3 常见运算符
作用:用于执行代码的运算
本章我们主要讲解以下几类运算符:
| 运算符类型 | 作用 |
|---|---|
| 算术运算符 | 用于处理四则运算 |
| 赋值运算符 | 用于将表达式的值赋给变量 |
| 比较运算符 | 用于表达式的比较,并返回一个真值或假值 |
| 逻辑运算符 | 用于根据表达式的值返回真值或假值 |
# 4.1.4 综合案例与思考
综合案例:运算符重载初体验
#include <iostream>
using namespace std;
// 自定义向量类,演示运算符本质
struct Vector2D {
double x, y;
// 重载 + 运算符(本质是operator+函数)
Vector2D operator+(const Vector2D& other) const {
return {x + other.x, y + other.y};
}
// 重载 == 运算符
bool operator==(const Vector2D& other) const {
return (x == other.x && y == other.y);
}
// 重载 << 用于输出
friend ostream& operator<<(ostream& os, const Vector2D& v) {
os << "(" << v.x << ", " << v.y << ")";
return os;
}
};
int main() {
// 1. 内置类型的运算符(编译器内置实现)
int a = 10, b = 3;
cout << "内置类型: " << a << " + " << b << " = " << (a + b) << endl;
// 2. 自定义类型使用重载运算符
Vector2D v1 = {1.0, 2.0};
Vector2D v2 = {3.0, 4.0};
Vector2D v3 = v1 + v2; // 调用operator+
cout << "向量加法: " << v1 << " + " << v2 << " = " << v3 << endl;
cout << "v1 == v2? " << (v1 == v2 ? "是" : "否") << endl;
return 0;
}
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
案例知识融合:这个案例从运算符的本质出发,展示了内置类型的运算符是编译器实现的"函数",而自定义类型可以通过operator+等重载运算符来定义自己的行为,直观体现了"运算符本质是函数"这一核心概念。
思考题:
- 运算符本质上是编译器的内置函数,那么
a + b和operator+(a, b)在编译后有区别吗? - C++允许重载大部分运算符,但有哪些运算符不能重载?为什么?
- 运算符重载使代码更直观(如向量用
+相加),但过度使用也可能降低可读性。你认为什么样的运算符重载是合理的?
# 4.2 算术运算符
作用:用于处理四则运算
算术运算符包括以下符号:
| 运算符 | 术语 | 示例 | 结果 |
|---|---|---|---|
| + | 正号 | +3 | 3 |
| - | 负号 | -3 | -3 |
| + | 加 | 10 + 5 | 15 |
| - | 减 | 10 - 5 | 5 |
| * | 乘 | 10 * 5 | 50 |
| / | 除 | 10 / 5 | 2 |
| % | 取模(取余) | 10 % 3 | 1 |
| ++ | 前置递增 | a=2; b=++a; | a=3; b=3; |
| ++ | 后置递增 | a=2; b=a++; | a=3; b=2; |
| -- | 前置递减 | a=2; b=--a; | a=1; b=1; |
| -- | 后置递减 | a=2; b=a--; | a=1; b=2; |
当操作数的类型不同时,C++会进行隐式类型转换以匹配操作数的类型。在进行算术运算时,确保操作数的类型和运算结果的类型是符合预期的,以避免数据丢失或错误的结果。
# 4.2.1 加减乘除
示例1:加减乘除
int main() {
int a1 = 10;
int b1 = 3;
cout << a1 + b1 << endl;
cout << a1 - b1 << endl;
cout << a1 * b1 << endl;
cout << a1 / b1 << endl; //两个整数相除结果依然是整数
int a2 = 10;
int b2 = 20;
cout << a2 / b2 << endl;
int a3 = 10;
int b3 = 0;
//cout << a3 / b3 << endl; //报错,除数不可以为0
//两个小数可以相除
double d1 = 0.5;
double d2 = 0.25;
cout << d1 / d2 << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
总结:在除法运算中,除数不能为0
# 4.2.2 取模
示例2:取模
int main() {
int a1 = 10;
int b1 = 3;
cout << a1 % b1 << endl;
int a2 = 10;
int b2 = 20;
cout << a2 % b2 << endl;
int a3 = 10;
int b3 = 0;
cout << a3 % b3 << endl; //取模运算时,除数也不能为0
//两个小数不可以取模
double d1 = 3.14;
double d2 = 1.1;
//cout << d1 % d2 << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
总结:只有整型变量可以进行取模运算
# 4.2.3 递增
示例3:递增
int main() {
//后置递增
int a = 10;
a++; //等价于a = a + 1
cout << a << endl; // 11
//前置递增
int b = 10;
++b;
cout << b << endl; // 11
//区别
//前置递增先对变量进行++,再计算表达式
int a2 = 10;
int b2 = ++a2 * 10;
cout << b2 << endl;
//后置递增先计算表达式,后对变量进行++
int a3 = 10;
int b3 = a3++ * 10;
cout << b3 << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
总结:前置递增先对变量进行++,再计算表达式,后置递增相反
# 4.2.4 底层原理
深入探讨 C++ 中算术运算符 (+, -, *, /, %, ++, --) 的底层原理——>硬件指令与编译器翻译。理解这些原理有助于写出更高效、更安全的代码,并理解编译器行为和潜在陷阱。
C++ 的算术运算符最终会被编译器翻译成目标 CPU 架构的机器指令。这些指令直接在 CPU 的算术逻辑单元 (ALU) 上执行。理解底层原理的关键在于了解:
- 整数运算: 直接映射到 CPU 的整数指令。
- 浮点数运算: 映射到 CPU 的浮点单元 (FPU) 指令,遵循 IEEE 754 标准。
- 编译器优化: 编译器会进行各种优化(如常量折叠、强度削减),可能改变代码的执行方式,但保持语义一致。
加法 (+) 和 减法 (-)。底层指令: ADD, SUB(及其带进位版本如 ADC, SBC,用于多精度运算)。
实现方式:
CPU 的 ALU 使用全加器 (Full Adder) 电路实现二进制位的加法。减法通常通过补码加法实现:A - B = A + (-B),其中 -B是 B的二进制补码(按位取反再加 1)。
溢出 (Overflow):
当两个正数相加结果为负,或两个负数相加结果为正时,发生有符号整数溢出。
当无符号整数相加结果小于任一操作数时,发生无符号整数溢出(本质是模运算)。
C++ 标准规定:有符号整数溢出是未定义行为 (Undefined Behavior, UB)!编译器可以假设它不会发生,并据此进行激进的优化(例如,直接删除溢出检查代码)。无符号整数溢出是定义良好的,执行模 2^n运算(n是位数)。
# 4.2.5 综合案例与思考
综合案例:简单计算器与算术运算陷阱
#include <iostream>
#include <limits>
using namespace std;
int main() {
// 1. 整数除法 vs 浮点除法
cout << "=== 除法陷阱 ===" << endl;
int a = 7, b = 2;
cout << "7 / 2 (整数) = " << a / b << endl; // 3,截断小数
cout << "7 / 2 (浮点) = " << 7.0 / 2 << endl; // 3.5
cout << "7 % 2 (取模) = " << a % b << endl; // 1
// 2. 前置++和后置++在复合表达式中的区别
cout << "\n=== 递增陷阱 ===" << endl;
int x = 5;
int y = x++ + ++x; // 未定义行为!不要这样写
cout << "警告:x++ + ++x 是未定义行为,结果不可预测" << endl;
// 正确用法:分开写
x = 5;
++x; // x = 6
int z = x + x; // z = 12
cout << "分开写:x=" << x << ", z=" << z << endl;
// 3. 溢出问题
cout << "\n=== 整数溢出 ===" << endl;
int maxInt = numeric_limits<int>::max();
cout << "INT_MAX = " << maxInt << endl;
// maxInt + 1 是未定义行为(有符号溢出)
// 安全的做法:先检查再运算
if (maxInt > numeric_limits<int>::max() - 1) {
cout << "加1会溢出,拒绝操作" << endl;
}
// 4. 负数取模
cout << "\n=== 负数取模 ===" << endl;
cout << " 7 % 3 = " << (7 % 3) << endl; // 1
cout << "-7 % 3 = " << (-7 % 3) << endl; // -1
cout << " 7 % -3 = " << (7 % -3) << endl; // 1
cout << "-7 % -3 = " << (-7 % -3) << endl; // -1
// 5. 完整计算器
cout << "\n=== 简单计算器 ===" << endl;
double num1 = 15, num2 = 4;
cout << num1 << " + " << num2 << " = " << (num1 + num2) << endl;
cout << num1 << " - " << num2 << " = " << (num1 - num2) << endl;
cout << num1 << " * " << num2 << " = " << (num1 * num2) << endl;
cout << num1 << " / " << num2 << " = " << (num1 / num2) << endl;
return 0;
}
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
案例知识融合:这个案例涵盖了算术运算符的关键知识点——整数除法的截断行为、前置/后置递增在复合表达式中的未定义行为风险、有符号整数溢出的危险性、负数取模的结果规则、以及一个完整的计算器示例。
思考题:
- 为什么
i++ + ++i是未定义行为?C++标准中的"序列点"规则是怎么定义的? - 负数取模时,C++11规定结果的符号与被除数相同。如果你需要一个结果总是非负的取模操作,应该如何实现?
- 在性能敏感的代码中,
++i和i++哪个更快?对于基本类型和自定义类型(如迭代器),答案一样吗?
# 4.3 赋值运算符
# 4.3.1 赋值案例
作用:用于将表达式的值赋给变量
赋值运算符包括以下几个符号:
| 运算符 | 术语 | 示例 | 结果 |
|---|---|---|---|
| = | 赋值 | a=2; b=3; | a=2; b=3; |
| += | 加等于 | a=0; a+=2; | a=2; |
| -= | 减等于 | a=5; a-=3; | a=2; |
| *= | 乘等于 | a=2; a*=2; | a=4; |
| /= | 除等于 | a=4; a/=2; | a=2; |
| %= | 模等于 | a=3; a%2; | a=1; |
示例:
#include <iostream>
using namespace std;
int main() {
// =
int a = 10;
a = 100;
cout << "a = " << a << endl;
// +=
a = 10;
a += 2; // a = a + 2;
cout << "a = " << a << endl;
// -=
a = 10;
a -= 2; // a = a - 2
cout << "a = " << a << endl;
// *=
a = 10;
a *= 2; // a = a * 2
cout << "a = " << a << endl;
// /=
a = 10;
a /= 2; // a = a / 2;
cout << "a = " << a << endl;
// %=
a = 10;
a %= 2; // a = a % 2;
cout << "a = " << a << endl;
return 0;
}
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
# 4.3.2 赋值原理
内置类型(基本类型)的赋值,将右表达式的值复制到左操作数所代表的内存位置。
int a = 10;
int b;
b = a; // 赋值操作
2
3
底层过程: 1.计算右表达式值(加载变量 a的值);2.将值复制到左操作数内存位置(存储到变量 b的内存);3.返回左操作数的引用(支持链式赋值)
伪汇编表示:
mov eax, [a] ; 将a的值加载到寄存器
mov [b], eax ; 将寄存器值存储到b的内存
2
# 4.3.3 综合案例与思考
综合案例:赋值运算符的链式调用与自赋值
#include <iostream>
using namespace std;
int main() {
// 1. 链式赋值
cout << "=== 链式赋值 ===" << endl;
int a, b, c;
a = b = c = 10; // 从右到左赋值
cout << "a=" << a << ", b=" << b << ", c=" << c << endl;
// 2. 复合赋值简化代码
cout << "\n=== 复合赋值运算 ===" << endl;
int score = 100;
score += 10; // 加分
cout << "加分后: " << score << endl;
score -= 5; // 扣分
cout << "扣分后: " << score << endl;
score *= 2; // 翻倍
cout << "翻倍后: " << score << endl;
score /= 3; // 除以3
cout << "除3后: " << score << endl;
score %= 7; // 取模
cout << "模7后: " << score << endl;
// 3. 赋值与初始化的区别
cout << "\n=== 赋值 vs 初始化 ===" << endl;
int x = 42; // 初始化(构造时赋值)
int y;
y = 42; // 赋值(先默认构造,再赋值)
cout << "效果相同但过程不同: x=" << x << ", y=" << y << endl;
// 4. 位运算赋值
cout << "\n=== 位运算赋值 ===" << endl;
int flags = 0b1010; // 二进制 1010
flags |= 0b0101; // 设置位: 1111
cout << "设置位后: " << flags << endl; // 15
flags &= 0b1100; // 清除位: 1100
cout << "清除位后: " << flags << endl; // 12
flags ^= 0b1111; // 翻转位: 0011
cout << "翻转位后: " << flags << endl; // 3
return 0;
}
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
案例知识融合:这个案例演示了赋值运算符的核心用法——链式赋值(返回左操作数引用支持 a=b=c=10)、各种复合赋值运算符简化代码、赋值与初始化的本质区别,以及位运算赋值在标志位操作中的实际应用。
思考题:
- 链式赋值
a = b = c = 10的求值顺序是从右到左还是从左到右?为什么赋值运算符要返回引用? a += b和a = a + b效果一样吗?对于内置类型没区别,但对于自定义类型(如大数类),性能上可能有差异,为什么?- 赋值运算符
=和初始化看起来很像,但对于类对象来说,调用的是不同的函数(赋值运算符 vs 拷贝构造函数)。这两者有什么性能差异?
# 4.4 比较运算符
# 4.4.1 比较案例
作用:用于表达式的比较,并返回一个真值或假值
比较运算符有以下符号:
| 运算符 | 术语 | 示例 | 结果 |
|---|---|---|---|
| == | 相等于 | 4 == 3 | 0 |
| != | 不等于 | 4 != 3 | 1 |
| < | 小于 | 4 < 3 | 0 |
| > | 大于 | 4 > 3 | 1 |
| <= | 小于等于 | 4 <= 3 | 0 |
| >= | 大于等于 | 4 >= 1 | 1 |
示例:
#include <iostream>
using namespace std;
//比较运算符
int main() {
int a = 10;
int b = 20;
cout << (a == b) << endl; // 0
cout << (a != b) << endl; // 1
cout << (a > b) << endl; // 0
cout << (a < b) << endl; // 1
cout << (a >= b) << endl; // 0
cout << (a <= b) << endl; // 1
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
注意:C和C++ 语言的比较运算中, ==“真”用数字“1”来表示, “假”用数字“0”来表示。==
# 4.4.2 底层原理
所有比较操作最终都会转化为 CPU 的 CMP指令,该指令通过 ALU(算术逻辑单元)执行减法操作并设置标志寄存器:
CMP A, B ; 实际执行: A - B
# 4.4.3 综合案例与思考
综合案例:比较运算符的常见陷阱
#include <iostream>
#include <cmath>
#include <string>
using namespace std;
int main() {
// 1. 基本比较
cout << "=== 基本比较 ===" << endl;
int age = 18;
cout << "age >= 18: " << (age >= 18) << endl; // 1 (true)
cout << "age == 18: " << (age == 18) << endl; // 1 (true)
// 2. 常见错误:= 和 == 混淆
cout << "\n=== 赋值与比较混淆 ===" << endl;
int x = 5;
// if (x = 0) // 危险!这是赋值,不是比较,x变为0,条件为false
if (x == 0) {
cout << "x等于0" << endl;
} else {
cout << "x不等于0,x=" << x << endl;
}
// 防御性写法:把常量放左边
if (0 == x) { /* ... */ } // 如果写成 0 = x 会编译报错
// 3. 浮点数比较问题
cout << "\n=== 浮点数比较 ===" << endl;
double a = 0.1 + 0.2;
double b = 0.3;
cout << "0.1+0.2 == 0.3? " << (a == b) << endl; // 0 (false)!
// 正确做法
cout << "近似相等? " << (fabs(a - b) < 1e-9) << endl; // 1 (true)
// 4. 字符串比较
cout << "\n=== 字符串比较 ===" << endl;
string s1 = "apple", s2 = "banana";
cout << "apple < banana? " << (s1 < s2) << endl; // 按字典序比较
cout << "apple == apple? " << (s1 == "apple") << endl;
// 5. 指针比较
cout << "\n=== 指针比较 ===" << endl;
int val = 42;
int* p1 = &val;
int* p2 = &val;
int* p3 = nullptr;
cout << "p1 == p2? " << (p1 == p2) << endl; // 比较地址
cout << "p3 == nullptr? " << (p3 == nullptr) << endl;
return 0;
}
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
案例知识融合:这个案例涵盖了比较运算符的核心知识和常见陷阱——基本整数比较、=和==的混淆问题及防御性写法、浮点数不能直接用==比较、字符串的字典序比较、以及指针地址比较。
思考题:
- 为什么
if (x = 0)不会报错而是正常执行?编译器有什么方法可以帮我们发现这类错误? - C++20引入了三路比较运算符
<=>(太空船运算符),它解决了什么问题?与传统的6个比较运算符相比有什么优势? - 比较运算的底层通过CPU的
CMP指令实现减法并设置标志位,那么比较两个浮点数时底层也是做减法吗?和整数比较有什么不同?
# 4.5 逻辑运算符
作用:用于根据表达式的值返回真值或假值
逻辑运算符有以下符号:
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
&& | 逻辑与 | a && b | 如果a和b都为真,则结果为真,否则为假。 |
|| | 逻辑或 | a || b | 如果a和b有一个为真,则结果为真,二者都为假时,结果为假。 |
! | 逻辑非 | !a | 如果a为假,则!a为真; 如果a为真,则!a为假。 |
# 4.5.1 逻辑非
示例1:逻辑非,逻辑非运算符 !:用于取反操作,将真变为假,将假变为真。
int main() {
int a = 10;
cout << !a << endl; // 0
cout << !!a << endl; // 1
return 0;
}
2
3
4
5
6
总结: 真变假,假变真
# 4.5.2 逻辑与
示例2:逻辑与,逻辑与运算符 &&:当两个操作数都为真时,结果为真;否则结果为假。
int main() {
int a = 10;
int b = 10;
cout << (a && b) << endl;// 1
a = 10;
b = 0;
cout << (a && b) << endl;// 0
a = 0;
b = 0;
cout << (a && b) << endl;// 0
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
总结:逻辑==与==运算符总结: ==同真为真,其余为假==
# 4.5.3 逻辑或
示例3:逻辑或,逻辑或运算符 ||:当两个操作数中至少有一个为真时,结果为真;否则结果为假。
//逻辑运算符 --- 或
int main() {
int a = 10;
int b = 10;
cout << (a || b) << endl;// 1
a = 10;
b = 0;
cout << (a || b) << endl;// 1
a = 0;
b = 0;
cout << (a || b) << endl;// 0
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
逻辑==或==运算符总结: ==同假为假,其余为真==
# 4.5.4 综合案例与思考
综合案例:逻辑运算符与短路求值
#include <iostream>
#include <string>
using namespace std;
bool checkAge(int age) {
cout << " [检查年龄: " << age << "]" << endl;
return age >= 18;
}
bool checkScore(int score) {
cout << " [检查分数: " << score << "]" << endl;
return score >= 60;
}
int main() {
// 1. 短路求值演示
cout << "=== 短路求值(&&) ===" << endl;
cout << "条件:年龄>=18 且 分数>=60" << endl;
// 第一个条件为false时,第二个不会执行
bool result1 = checkAge(16) && checkScore(80);
cout << "结果: " << result1 << " (注意checkScore没被调用)" << endl;
cout << "\n=== 短路求值(||) ===" << endl;
cout << "条件:年龄>=18 或 分数>=60" << endl;
// 第一个条件为true时,第二个不会执行
bool result2 = checkAge(20) || checkScore(30);
cout << "结果: " << result2 << " (注意checkScore没被调用)" << endl;
// 2. 短路求值的实际应用:安全检查
cout << "\n=== 安全检查 ===" << endl;
int* ptr = nullptr;
// 短路求值保护:先检查指针非空,再解引用
if (ptr != nullptr && *ptr > 0) {
cout << "值: " << *ptr << endl;
} else {
cout << "指针为空,安全跳过解引用" << endl;
}
// 3. 复合逻辑条件
cout << "\n=== 复合条件判断 ===" << endl;
int age = 25;
bool hasLicense = true;
bool isInsured = false;
// 能否开车:年满18 且 (有驾照 且 有保险)
bool canDrive = (age >= 18) && hasLicense && isInsured;
cout << "能否开车: " << (canDrive ? "能" : "不能") << endl;
// 使用逻辑非取反
cout << "缺少保险: " << (!isInsured ? "是" : "否") << endl;
return 0;
}
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
案例知识融合:这个案例重点演示了逻辑运算符的短路求值特性——&&左侧为false时右侧不执行、||左侧为true时右侧不执行。通过带输出的函数调用,可以直观看到哪些表达式被跳过了。同时展示了短路求值在安全检查中的实际应用(先判空再解引用),以及复合逻辑条件的组合。
思考题:
- 短路求值除了提高效率外,还能保证程序安全(如先判空再解引用)。你能想到其他利用短路求值的实际场景吗?
- 如果需要确保两个函数都被调用(即使第一个的结果已经能确定整体结果),应该怎么做?
- 逻辑运算符
&&和位运算符&看起来很像,它们有什么区别?混用会导致什么问题?
# 4.6 位运算符
用于对二进制位进行操作。
| 运算符 | 描述 | 示例 |
|---|---|---|
& | 按位与 | a & b |
| | 按位或 | a | b |
^ | 按位异或 | a ^ b |
~ | 按位取反 | ~a |
<< | 左移 | a << b |
>> | 右移 | a >> b |
示例
int m = 5, n = 3; // 5: 0101, 3: 0011
cout << "m & n: " << (m & n) << endl; // 1 (0001)
cout << "m | n: " << (m | n) << endl; // 7 (0111)
cout << "m ^ n: " << (m ^ n) << endl; // 6 (0110)
cout << "~m: " << ~m << endl; // -6 (补码表示)
cout << "m << 1: " << (m << 1) << endl; // 10 (1010)
cout << "m >> 1: " << (m >> 1) << endl; // 2 (0010)
2
3
4
5
6
7
# 4.6.1 综合案例与思考
综合案例:位运算在权限管理中的应用
#include <iostream>
using namespace std;
// 用位标志表示权限
const int PERM_READ = 0b0001; // 1: 读权限
const int PERM_WRITE = 0b0010; // 2: 写权限
const int PERM_EXECUTE = 0b0100; // 4: 执行权限
const int PERM_ADMIN = 0b1000; // 8: 管理员权限
void showPermissions(int perms) {
cout << "权限: ";
if (perms & PERM_READ) cout << "读 ";
if (perms & PERM_WRITE) cout << "写 ";
if (perms & PERM_EXECUTE) cout << "执行 ";
if (perms & PERM_ADMIN) cout << "管理员 ";
cout << "(二进制: ";
for (int i = 3; i >= 0; --i)
cout << ((perms >> i) & 1);
cout << ")" << endl;
}
int main() {
// 1. 设置权限(按位或 |)
cout << "=== 设置权限 ===" << endl;
int userPerms = 0;
userPerms |= PERM_READ; // 添加读权限
userPerms |= PERM_WRITE; // 添加写权限
showPermissions(userPerms);
// 2. 检查权限(按位与 &)
cout << "\n=== 检查权限 ===" << endl;
cout << "有读权限? " << ((userPerms & PERM_READ) ? "是" : "否") << endl;
cout << "有执行权限? " << ((userPerms & PERM_EXECUTE) ? "是" : "否") << endl;
// 3. 移除权限(按位与取反 &~)
cout << "\n=== 移除权限 ===" << endl;
userPerms &= ~PERM_WRITE; // 移除写权限
showPermissions(userPerms);
// 4. 切换权限(按位异或 ^)
cout << "\n=== 切换权限 ===" << endl;
userPerms ^= PERM_EXECUTE; // 切换执行权限(无则加,有则去)
showPermissions(userPerms);
userPerms ^= PERM_EXECUTE; // 再次切换
showPermissions(userPerms);
// 5. 左移右移:快速乘除2
cout << "\n=== 移位运算 ===" << endl;
int val = 5;
cout << val << " << 1 = " << (val << 1) << " (等于 " << val << "*2)" << endl;
cout << val << " << 3 = " << (val << 3) << " (等于 " << val << "*8)" << endl;
cout << 20 << " >> 2 = " << (20 >> 2) << " (等于 20/4)" << endl;
return 0;
}
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
案例知识融合:这个案例用权限管理系统展示了位运算的典型应用——按位或设置标志、按位与检查标志、按位与取反清除标志、按位异或切换标志,以及移位运算实现快速乘除2的幂。
思考题:
- 为什么操作系统的文件权限(如Linux的rwx)通常用位标志而不是多个bool变量来实现?有什么优势?
- 异或运算有一个著名的应用:不用临时变量交换两个数(
a^=b; b^=a; a^=b;)。请验证这个算法的正确性,它有什么限制? - 左移操作
x << n等价于x * 2^n,但如果左移导致溢出会怎样?有符号数和无符号数的行为一样吗?
# 4.7 运算符优先级
# 4.7.1 优先级总结
运算符的优先级决定了表达式中运算的顺序。优先级高的运算符先执行。以下是常见运算符的优先级(从高到低):
()(括号)++、--(自增、自减)*、/、%(乘法、除法、取模)+、-(加法、减法)<<、>>(左移、右移)<、<=、>、>=(关系运算符)==、!=(相等性运算符)&、|、^(位运算符)&&、||(逻辑运算符)=、+=、-=等(赋值运算符)
# 4.7.2 优先级案例
# 4.7.3 综合案例与思考
综合案例:优先级导致的经典bug
#include <iostream>
using namespace std;
int main() {
// 1. 位运算与比较运算的优先级陷阱
cout << "=== 优先级陷阱1: & 与 == ===" << endl;
int x = 5, y = 3;
// 错误:== 优先级高于 &
cout << "x & y == 1 结果: " << (x & y == 1) << endl; // 实际是 x & (y == 1) = 5 & 0 = 0
// 正确:加括号
cout << "(x & y) == 1 结果: " << ((x & y) == 1) << endl; // (5 & 3) == 1 = 1
// 2. 逻辑运算与赋值的优先级
cout << "\n=== 优先级陷阱2: || 与 = ===" << endl;
bool a = false, b = true;
bool result;
result = a || b; // = 优先级最低,所以先算 a||b
cout << "a || b = " << result << endl;
// 3. 三目运算符的嵌套
cout << "\n=== 优先级陷阱3: 三目运算符 ===" << endl;
int score = 75;
// 不推荐:嵌套三目运算符
string grade = score >= 90 ? "优秀" : score >= 60 ? "及格" : "不及格";
cout << "成绩: " << grade << endl;
// 4. 最佳实践:用括号明确优先级
cout << "\n=== 最佳实践 ===" << endl;
int val = 10;
// 不清晰的写法
int r1 = 2 + 3 * 4; // 14
// 清晰的写法
int r2 = 2 + (3 * 4); // 14,意图明确
int r3 = (2 + 3) * 4; // 20,不同含义
cout << "2 + 3 * 4 = " << r1 << endl;
cout << "(2 + 3) * 4 = " << r3 << endl;
return 0;
}
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
案例知识融合:这个案例通过实际的优先级bug展示了运算符优先级的重要性——位运算&低于比较==的经典陷阱、嵌套三目运算符的可读性问题,以及使用括号明确优先级的最佳实践。
思考题:
- C++有十几个优先级级别,全部记住很难。实际编程中有什么简单的策略可以避免优先级错误?
a && b || c等价于(a && b) || c还是a && (b || c)?为什么?- 为什么说"当不确定优先级时,加括号永远是最安全的做法"?加括号会影响编译后的性能吗?
# 4.8 sizeof运算符
# 4.8.1 sizeof介绍
在 C++ 中,sizeof 是一个运算符,用于获取数据类型或变量所占用的内存大小(以字节为单位)。sizeof 在编译时计算,因此不会影响程序的运行时性能。
# 4.8.2 基本用法
sizeof 可以用于以下两种形式:
sizeof(类型):获取某种数据类型的大小。sizeof(表达式):获取表达式结果类型的大小。
# 4.8.3 sizeof与数组
sizeof 可以用于计算数组的总大小或数组元素的大小。
#include <iostream>
using namespace std;
int main() {
int arr[5] = {1, 2, 3, 4, 5};
cout << "sizeof(arr): " << sizeof(arr) << " bytes" << endl; // 数组总大小
cout << "sizeof(arr[0]): " << sizeof(arr[0]) << " bytes" << endl; // 单个元素大小
cout << "Number of elements in arr: " << sizeof(arr) / sizeof(arr[0]) << endl; // 数组元素个数
return 0;
}
2
3
4
5
6
7
8
9
10
输出:
sizeof(arr): 20 bytes
sizeof(arr[0]): 4 bytes
Number of elements in arr: 5
2
3
# 4.8.4 sizeof与结构体
sizeof 可以用于计算结构体的大小。注意,结构体的大小可能会受到内存对齐的影响。示例
#include <iostream>
using namespace std;
struct Point {
int x;
int y;
};
struct PackedPoint {
int x;
int y;
} __attribute__((packed)); // 取消内存对齐(仅在某些编译器中有效)
int main() {
cout << "sizeof(Point): " << sizeof(Point) << " bytes" << endl;
cout << "sizeof(PackedPoint): " << sizeof(PackedPoint) << " bytes" << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
输出:
sizeof(Point): 8 bytes
sizeof(PackedPoint): 8 bytes
2
# 4.8.5 sizeof与类
sizeof 可以用于计算类的大小。类的大小包括成员变量的大小,并可能受到内存对齐和虚函数表(如果有虚函数)的影响。示例
#include <iostream>
using namespace std;
class MyClass {
int a;
double b;
};
class MyClassWithVirtual {
int a;
double b;
virtual void foo() {}
};
int main() {
cout << "sizeof(MyClass): " << sizeof(MyClass) << " bytes" << endl;
cout << "sizeof(MyClassWithVirtual): " << sizeof(MyClassWithVirtual) << " bytes" << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
输出:
sizeof(MyClass): 16 bytes
sizeof(MyClassWithVirtual): 24 bytes
2
普通类 MyClass:
int a:4字节
double b:8字节
内存对齐:4字节填充
总计:16字节
2
3
4
带虚函数的类 MyClassWithVirtual:
虚函数表指针(vptr):8字节(64位系统)
int a:4字节
内存对齐:4字节填充
double b:8字节
总计:24字节
2
3
4
5
虚函数本身不直接占用对象内存,但会在对象中添加一个虚函数表指针(vptr)。这个指针指向虚函数表(vtable),用于实现动态多态。在64位系统上,指针大小是8字节。
# 4.8.6 sizeof与指针
sizeof 用于指针时,返回指针本身的大小,而不是指针指向的对象的大小。示例
#include <iostream>
using namespace std;
class MyClass {
int a;
double b;
char c[100]; // 大数组
};
int main() {
// 1. 基本数据类型和指针
int value = 42;
int* ptr = &value;
cout << "=== 基本类型 vs 指针 ===" << endl;
cout << "sizeof(int): " << sizeof(int) << " bytes" << endl;
cout << "sizeof(ptr): " << sizeof(ptr) << " bytes" << endl;
cout << "sizeof(*ptr): " << sizeof(*ptr) << " bytes" << endl;
// 2. 数组和指针
int arr[100];
int* arr_ptr = arr;
cout << "\n=== 数组 vs 指针 ===" << endl;
cout << "sizeof(arr): " << sizeof(arr) << " bytes" << endl;
cout << "sizeof(arr_ptr): " << sizeof(arr_ptr) << " bytes" << endl;
// 3. 类对象和指针
MyClass obj;
MyClass* obj_ptr = &obj;
cout << "\n=== 类对象 vs 指针 ===" << endl;
cout << "sizeof(MyClass): " << sizeof(MyClass) << " bytes" << endl;
cout << "sizeof(obj): " << sizeof(obj) << " bytes" << endl;
cout << "sizeof(obj_ptr): " << sizeof(obj_ptr) << " bytes" << endl;
cout << "sizeof(*obj_ptr): " << sizeof(*obj_ptr) << " bytes" << endl;
// 4. 不同类型的指针大小都相同
double* double_ptr = nullptr;
char* char_ptr = nullptr;
void* void_ptr = nullptr;
cout << "\n=== 不同类型指针的大小 ===" << endl;
cout << "sizeof(int*): " << sizeof(int*) << " bytes" << endl;
cout << "sizeof(double*): " << sizeof(double*) << " bytes" << endl;
cout << "sizeof(char*): " << sizeof(char*) << " bytes" << endl;
cout << "sizeof(void*): " << sizeof(void*) << " bytes" << endl;
cout << "sizeof(MyClass*): " << sizeof(MyClass*) << " bytes" << endl;
return 0;
}
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
输出:
=== 基本类型 vs 指针 ===
sizeof(int): 4 bytes
sizeof(ptr): 8 bytes
sizeof(*ptr): 4 bytes
=== 数组 vs 指针 ===
sizeof(arr): 400 bytes
sizeof(arr_ptr): 8 bytes
=== 类对象 vs 指针 ===
sizeof(MyClass): 120 bytes
sizeof(obj): 120 bytes
sizeof(obj_ptr): 8 bytes
sizeof(*obj_ptr): 120 bytes
=== 不同类型指针的大小 ===
sizeof(int*): 8 bytes
sizeof(double*): 8 bytes
sizeof(char*): 8 bytes
sizeof(void*): 8 bytes
sizeof(MyClass*): 8 bytes
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
通过这个示例,我们可以清楚地理解sizeof用于指针时,返回指针本身的大小,而不是指针指向的对象的大小。
指针只是一个存储地址的变量,它的大小由系统架构决定(32位系统4字节,64位系统8字节),与它指向的数据类型无关。要获取指向对象的大小,需要使用sizeof(*ptr)进行解引用。
# 4.8.7 sizeof与字符串
sizeof 用于字符串时,返回字符串数组的大小,而不是字符串的长度。示例
#include <iostream>
using namespace std;
int main() {
char str[] = "Hello";
cout << "sizeof(str): " << sizeof(str) << " bytes" << endl; // 包括 '\0'
cout << "strlen(str): " << strlen(str) << " characters" << endl; // 不包括 '\0'
return 0;
}
2
3
4
5
6
7
8
9
输出:
sizeof(str): 6 bytes
strlen(str): 5 characters
2
# 4.8.8 sizeof动态内存
sizeof 不能用于计算动态分配内存的大小,因为它只能计算编译时已知的类型或变量的大小。示例
#include <iostream>
using namespace std;
int main() {
int* arr = new int[10];
cout << "sizeof(arr): " << sizeof(arr) << " bytes" << endl; // 指针大小,不是数组大小
delete[] arr;
return 0;
}
2
3
4
5
6
7
8
9
10
输出:
sizeof(arr): 8 bytes
# 4.8.9 综合案例与思考
综合案例:用sizeof分析内存布局
#include <iostream>
using namespace std;
// 结构体内存对齐示例
struct A { char c; }; // 1字节
struct B { char c; int i; }; // 8字节(有填充)
struct C { int i; char c; }; // 8字节(有填充)
struct D { char c; int i; char c2; }; // 12字节
struct E { char c; char c2; int i; }; // 8字节(调整顺序减少填充)
// 空类
class Empty {};
// 含虚函数的类
class WithVirtual {
virtual void f() {}
};
int main() {
// 1. 基本类型大小
cout << "=== 基本类型 ===" << endl;
cout << "char: " << sizeof(char) << ", short: " << sizeof(short)
<< ", int: " << sizeof(int) << ", long: " << sizeof(long)
<< ", long long: " << sizeof(long long) << endl;
cout << "float: " << sizeof(float) << ", double: " << sizeof(double) << endl;
cout << "bool: " << sizeof(bool) << ", 指针: " << sizeof(void*) << endl;
// 2. 结构体内存对齐
cout << "\n=== 结构体对齐 ===" << endl;
cout << "A{char}: " << sizeof(A) << endl; // 1
cout << "B{char,int}: " << sizeof(B) << endl; // 8 (3字节填充)
cout << "C{int,char}: " << sizeof(C) << endl; // 8 (3字节填充)
cout << "D{char,int,char}: " << sizeof(D) << endl; // 12
cout << "E{char,char,int}: " << sizeof(E) << endl; // 8 (调整顺序更紧凑)
// 3. 数组元素计数技巧
cout << "\n=== 数组计数 ===" << endl;
int arr[] = {10, 20, 30, 40, 50};
constexpr int len = sizeof(arr) / sizeof(arr[0]);
cout << "数组长度: " << len << endl;
// 4. 空类和虚函数类
cout << "\n=== 特殊类 ===" << endl;
cout << "空类: " << sizeof(Empty) << " 字节" << endl; // 1
cout << "虚函数类: " << sizeof(WithVirtual) << " 字节" << endl; // 8 (vptr)
// 5. sizeof不求值表达式
cout << "\n=== sizeof不求值 ===" << endl;
int x = 10;
cout << "sizeof(x++): " << sizeof(x++) << endl;
cout << "x仍为: " << x << " (++没有执行)" << endl;
return 0;
}
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
案例知识融合:这个案例综合展示了sizeof的各种用法——基本类型大小查询、结构体内存对齐(成员排列顺序影响结构体大小)、数组元素计数技巧、空类和虚函数类的大小、以及sizeof不求值表达式的特性。特别是通过结构体D和E的对比,说明成员排列顺序对内存占用的影响。
思考题:
- 为什么
struct D{char,int,char}占12字节而struct E{char,char,int}只占8字节?内存对齐的规则是什么? - 空类的
sizeof为什么是1而不是0?如果是0会导致什么问题? sizeof(x++)中的x++不会执行,这是因为sizeof在编译时计算。那么对于C++11的变长数组(VLA),sizeof还是编译时计算吗?
# 4.9 练习题
# 4.9.5 计算数据内存大小
作用:利用sizeof关键字可以==统计数据类型所占内存大小==
语法: sizeof( 数据类型 / 变量)
示例:
#include <iostream>
using namespace std;
int main() {
cout << "short 类型所占内存空间为: " << sizeof(short) << endl;
cout << "int 类型所占内存空间为: " << sizeof(int) << endl;
cout << "long 类型所占内存空间为: " << sizeof(long) << endl;
cout << "long long 类型所占内存空间为: " << sizeof(long long) << endl;
return 0;
}
//short 类型所占内存空间为: 2
//int 类型所占内存空间为: 4
//long 类型所占内存空间为: 8
//long long 类型所占内存空间为: 8
2
3
4
5
6
7
8
9
10
11
12
13
整型结论:==short < int <= long <= long long==
# 4.7 卷一改造增补:C++20 三路比较运算符 <=>(飞船运算符)
本节为卷一新增。如果你写过自定义类,对比下面两段代码就能体会到 C++20 在「类型设计的人体工学」上的革命性进步。
# 4.7.1 痛点:C++17 之前要写 6 个比较函数
struct Point {
int x, y;
};
// C++17 之前:要手写 6 个!
bool operator==(const Point& a, const Point& b) { return a.x==b.x && a.y==b.y; }
bool operator!=(const Point& a, const Point& b) { return !(a==b); }
bool operator< (const Point& a, const Point& b) { return std::tie(a.x,a.y) < std::tie(b.x,b.y); }
bool operator<=(const Point& a, const Point& b) { return !(b<a); }
bool operator> (const Point& a, const Point& b) { return b<a; }
bool operator>=(const Point& a, const Point& b) { return !(a<b); }
2
3
4
5
6
7
8
9
10
11
不仅啰嗦,而且任何一处写错都会导致比较语义不一致(如 a<b && b<a 同时为真)。
# 4.7.2 C++20 解法:一行 default 搞定
#include <compare>
struct Point {
int x, y;
auto operator<=>(const Point&) const = default; // 一行代替六行
};
Point a{1,2}, b{1,3};
bool r1 = (a < b); // OK
bool r2 = (a == b); // OK,编译器同时合成 ==
bool r3 = (a <=> b) < 0; // OK,飞船运算符直接返回比较结果
2
3
4
5
6
7
8
9
10
编译器会按成员声明顺序逐字段比较(字典序),并且自动合成 <、<=、>、>=、==、!=。
# 4.7.3 三种比较类别
| 返回类型 | 含义 | 典型场景 |
|---|---|---|
std::strong_ordering | 强序:a==b 意味着 a 与 b 完全可替换 | 整数、字符串 |
std::weak_ordering | 弱序:a==b 但可能可区分(如忽略大小写比较) | 自定义集合 |
std::partial_ordering | 偏序:可能不可比较(NaN) | 浮点数 |
auto r = 1.0 <=> std::nan(""); // partial_ordering::unordered
# 4.7.4 实战:自定义比较
struct CaseInsensitive {
std::string s;
std::weak_ordering operator<=>(const CaseInsensitive& o) const {
// 自定义比较:忽略大小写
for (size_t i = 0; i < std::min(s.size(), o.s.size()); ++i) {
auto a = std::tolower(s[i]), b = std::tolower(o.s[i]);
if (a != b) return a <=> b;
}
return s.size() <=> o.s.size();
}
bool operator==(const CaseInsensitive&) const = default;
};
2
3
4
5
6
7
8
9
10
11
12
# 4.7.5 推荐阅读
- 卷一
18.特性图谱§C++20 部分 - 卷一第 9 章
09.类和对象.md——<=>与运算符重载的协同 - cppreference: Three-way comparison (opens new window)
# 4.8 新手陷阱 Top 5
| # | 陷阱 | 说明 |
|---|---|---|
| 1 | = 与 == 混用 | if (x = 1) 永真。用 -Wparentheses 警告或 Yoda 风格 if (1 == x) |
| 2 | &&/\|\| 短路求值 | if (p && p->valid) 是合法的;颠倒顺序会段错误 |
| 3 | 位运算优先级 | a & b == 0 实为 a & (b == 0),加括号 (a & b) == 0 |
| 4 | ++i 与 i++ 在表达式中 | arr[i++] = i; 行为未定义(C++17 前) |
| 5 | 整数溢出 UB | INT_MAX + 1 是 UB,不会回绕;用 unsigned 或 <numeric> 中的安全函数 |