编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

    • README
    • 入门教程

      • README
      • Cpp简史
      • 基础语法
      • 数据类型
      • 运算符
        • 4.1 运算符介绍
          • 4.1.1 运算符由来
          • 4.1.2 运算符本质
          • 4.1.3 常见运算符
          • 4.1.4 综合案例与思考
        • 4.2 算术运算符
          • 4.2.1 加减乘除
          • 4.2.2 取模
          • 4.2.3 递增
          • 4.2.4 底层原理
          • 4.2.5 综合案例与思考
        • 4.3 赋值运算符
          • 4.3.1 赋值案例
          • 4.3.2 赋值原理
          • 4.3.3 综合案例与思考
        • 4.4 比较运算符
          • 4.4.1 比较案例
          • 4.4.2 底层原理
          • 4.4.3 综合案例与思考
        • 4.5 逻辑运算符
          • 4.5.1 逻辑非
          • 4.5.2 逻辑与
          • 4.5.3 逻辑或
          • 4.5.4 综合案例与思考
        • 4.6 位运算符
          • 4.6.1 综合案例与思考
        • 4.7 运算符优先级
          • 4.7.1 优先级总结
          • 4.7.2 优先级案例
          • 4.7.3 综合案例与思考
        • 4.8 sizeof运算符
          • 4.8.1 sizeof介绍
          • 4.8.2 基本用法
          • 4.8.3 sizeof与数组
          • 4.8.4 sizeof与结构体
          • 4.8.5 sizeof与类
          • 4.8.6 sizeof与指针
          • 4.8.7 sizeof与字符串
          • 4.8.8 sizeof动态内存
          • 4.8.9 综合案例与思考
        • 4.9 练习题
          • 4.9.5 计算数据内存大小
        • 4.7 卷一改造增补:C++20 三路比较运算符 <=>(飞船运算符)
          • 4.7.1 痛点:C++17 之前要写 6 个比较函数
          • 4.7.2 C++20 解法:一行 default 搞定
          • 4.7.3 三种比较类别
          • 4.7.4 实战:自定义比较
          • 4.7.5 推荐阅读
        • 4.8 新手陷阱 Top 5
      • 复合类型
      • 流程语句
      • 函数
      • 指针引用
      • 类和对象
      • 继承多态
      • 内存模型
      • 动态内存
      • IO和文件
      • 异常处理
      • 线程和锁
      • STL模版
      • 预处理器
      • 特性图谱
    • 综合案例

    • 专栏博客

    • 开发技巧

  • Java入门精通

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Cpp入门到精通
  • 入门教程
杨充
2026-05-07
目录

运算符

# 第 4 章 C++ 运算符

# 目录介绍

  • 4.1 运算符介绍
    • 4.1.1 运算符由来
    • 4.1.2 运算符本质
    • 4.1.3 常见运算符
    • 4.1.4 综合案例与思考
  • 4.2 算术运算符
    • 4.2.1 加减乘除
    • 4.2.2 取模
    • 4.2.3 递增
    • 4.2.4 底层原理
    • 4.2.5 综合案例与思考
  • 4.3 赋值运算符
    • 4.3.1 赋值案例
    • 4.3.2 赋值原理
    • 4.3.3 综合案例与思考
  • 4.4 比较运算符
    • 4.4.1 比较案例
    • 4.4.2 底层原理
    • 4.4.3 综合案例与思考
  • 4.5 逻辑运算符
    • 4.5.1 逻辑非
    • 4.5.2 逻辑与
    • 4.5.3 逻辑或
    • 4.5.4 综合案例与思考
  • 4.6 位运算符
    • 4.6.1 综合案例与思考
  • 4.7 运算符优先级
    • 4.7.1 优先级总结
    • 4.7.2 优先级案例
    • 4.7.3 综合案例与思考
  • 4.8 sizeof运算符
    • 4.8.1 sizeof介绍
    • 4.8.2 基本用法
    • 4.8.3 sizeof与数组
    • 4.8.4 sizeof与结构体
    • 4.8.5 sizeof与类
    • 4.8.6 sizeof与指针
    • 4.8.7 sizeof与字符串
    • 4.8.8 sizeof动态内存
    • 4.8.9 综合案例与思考
  • 4.9 练习题
    • 4.9.5 计算数据内存大小

# 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;
}
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

案例知识融合:这个案例从运算符的本质出发,展示了内置类型的运算符是编译器实现的"函数",而自定义类型可以通过operator+等重载运算符来定义自己的行为,直观体现了"运算符本质是函数"这一核心概念。

思考题:

  1. 运算符本质上是编译器的内置函数,那么a + b和operator+(a, b)在编译后有区别吗?
  2. C++允许重载大部分运算符,但有哪些运算符不能重载?为什么?
  3. 运算符重载使代码更直观(如向量用+相加),但过度使用也可能降低可读性。你认为什么样的运算符重载是合理的?

# 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;
}
1
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;
}
1
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;
}
1
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) 上执行。理解底层原理的关键在于了解:

  1. 整数运算: 直接映射到 CPU 的整数指令。
  2. 浮点数运算: 映射到 CPU 的浮点单元 (FPU) 指令,遵循 IEEE 754 标准。
  3. 编译器优化: 编译器会进行各种优化(如常量折叠、强度削减),可能改变代码的执行方式,但保持语义一致。

加法 (+) 和 减法 (-)。底层指令: 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;
}
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

案例知识融合:这个案例涵盖了算术运算符的关键知识点——整数除法的截断行为、前置/后置递增在复合表达式中的未定义行为风险、有符号整数溢出的危险性、负数取模的结果规则、以及一个完整的计算器示例。

思考题:

  1. 为什么 i++ + ++i 是未定义行为?C++标准中的"序列点"规则是怎么定义的?
  2. 负数取模时,C++11规定结果的符号与被除数相同。如果你需要一个结果总是非负的取模操作,应该如何实现?
  3. 在性能敏感的代码中,++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;
}
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

# 4.3.2 赋值原理

内置类型(基本类型)的赋值,将右表达式的值复制到左操作数所代表的内存位置。

int a = 10;
int b;
b = a; // 赋值操作
1
2
3

底层过程: 1.计算右表达式值(加载变量 a的值);2.将值复制到左操作数内存位置(存储到变量 b的内存);3.返回左操作数的引用(支持链式赋值)

伪汇编表示:

mov eax, [a]  ; 将a的值加载到寄存器
mov [b], eax  ; 将寄存器值存储到b的内存
1
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;
}
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

案例知识融合:这个案例演示了赋值运算符的核心用法——链式赋值(返回左操作数引用支持 a=b=c=10)、各种复合赋值运算符简化代码、赋值与初始化的本质区别,以及位运算赋值在标志位操作中的实际应用。

思考题:

  1. 链式赋值 a = b = c = 10 的求值顺序是从右到左还是从左到右?为什么赋值运算符要返回引用?
  2. a += b 和 a = a + b 效果一样吗?对于内置类型没区别,但对于自定义类型(如大数类),性能上可能有差异,为什么?
  3. 赋值运算符 = 和初始化看起来很像,但对于类对象来说,调用的是不同的函数(赋值运算符 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;
}
1
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
1

# 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;
}
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

案例知识融合:这个案例涵盖了比较运算符的核心知识和常见陷阱——基本整数比较、=和==的混淆问题及防御性写法、浮点数不能直接用==比较、字符串的字典序比较、以及指针地址比较。

思考题:

  1. 为什么 if (x = 0) 不会报错而是正常执行?编译器有什么方法可以帮我们发现这类错误?
  2. C++20引入了三路比较运算符<=>(太空船运算符),它解决了什么问题?与传统的6个比较运算符相比有什么优势?
  3. 比较运算的底层通过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;
}
1
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;
}
1
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;
}
1
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;
}
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

案例知识融合:这个案例重点演示了逻辑运算符的短路求值特性——&&左侧为false时右侧不执行、||左侧为true时右侧不执行。通过带输出的函数调用,可以直观看到哪些表达式被跳过了。同时展示了短路求值在安全检查中的实际应用(先判空再解引用),以及复合逻辑条件的组合。

思考题:

  1. 短路求值除了提高效率外,还能保证程序安全(如先判空再解引用)。你能想到其他利用短路求值的实际场景吗?
  2. 如果需要确保两个函数都被调用(即使第一个的结果已经能确定整体结果),应该怎么做?
  3. 逻辑运算符 && 和位运算符 & 看起来很像,它们有什么区别?混用会导致什么问题?

# 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)
1
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;
}
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

案例知识融合:这个案例用权限管理系统展示了位运算的典型应用——按位或设置标志、按位与检查标志、按位与取反清除标志、按位异或切换标志,以及移位运算实现快速乘除2的幂。

思考题:

  1. 为什么操作系统的文件权限(如Linux的rwx)通常用位标志而不是多个bool变量来实现?有什么优势?
  2. 异或运算有一个著名的应用:不用临时变量交换两个数(a^=b; b^=a; a^=b;)。请验证这个算法的正确性,它有什么限制?
  3. 左移操作 x << n 等价于 x * 2^n,但如果左移导致溢出会怎样?有符号数和无符号数的行为一样吗?

# 4.7 运算符优先级

# 4.7.1 优先级总结

运算符的优先级决定了表达式中运算的顺序。优先级高的运算符先执行。以下是常见运算符的优先级(从高到低):

  1. ()(括号)
  2. ++、--(自增、自减)
  3. *、/、%(乘法、除法、取模)
  4. +、-(加法、减法)
  5. <<、>>(左移、右移)
  6. <、<=、>、>=(关系运算符)
  7. ==、!=(相等性运算符)
  8. &、|、^(位运算符)
  9. &&、||(逻辑运算符)
  10. =、+=、-= 等(赋值运算符)

# 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;
}
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

案例知识融合:这个案例通过实际的优先级bug展示了运算符优先级的重要性——位运算&低于比较==的经典陷阱、嵌套三目运算符的可读性问题,以及使用括号明确优先级的最佳实践。

思考题:

  1. C++有十几个优先级级别,全部记住很难。实际编程中有什么简单的策略可以避免优先级错误?
  2. a && b || c 等价于 (a && b) || c 还是 a && (b || c)?为什么?
  3. 为什么说"当不确定优先级时,加括号永远是最安全的做法"?加括号会影响编译后的性能吗?

# 4.8 sizeof运算符

# 4.8.1 sizeof介绍

在 C++ 中,sizeof 是一个运算符,用于获取数据类型或变量所占用的内存大小(以字节为单位)。sizeof 在编译时计算,因此不会影响程序的运行时性能。

# 4.8.2 基本用法

sizeof 可以用于以下两种形式:

  1. sizeof(类型):获取某种数据类型的大小。
  2. 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;
}
1
2
3
4
5
6
7
8
9
10

输出:

sizeof(arr): 20 bytes
sizeof(arr[0]): 4 bytes
Number of elements in arr: 5
1
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;
}
1
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
1
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;
}
1
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
1
2

普通类 MyClass:

int a:4字节
double b:8字节
内存对齐:4字节填充
总计:16字节
1
2
3
4

带虚函数的类 MyClassWithVirtual:

虚函数表指针(vptr):8字节(64位系统)
int a:4字节
内存对齐:4字节填充
double b:8字节
总计:24字节
1
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;
}
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

输出:

=== 基本类型 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
1
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;
}
1
2
3
4
5
6
7
8
9

输出:

sizeof(str): 6 bytes
strlen(str): 5 characters
1
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;
}
1
2
3
4
5
6
7
8
9
10

输出:

sizeof(arr): 8 bytes
1

# 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;
}
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

案例知识融合:这个案例综合展示了sizeof的各种用法——基本类型大小查询、结构体内存对齐(成员排列顺序影响结构体大小)、数组元素计数技巧、空类和虚函数类的大小、以及sizeof不求值表达式的特性。特别是通过结构体D和E的对比,说明成员排列顺序对内存占用的影响。

思考题:

  1. 为什么 struct D{char,int,char} 占12字节而 struct E{char,char,int} 只占8字节?内存对齐的规则是什么?
  2. 空类的 sizeof 为什么是1而不是0?如果是0会导致什么问题?
  3. 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
1
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); }
1
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,飞船运算符直接返回比较结果
1
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
1

# 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;
};
1
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> 中的安全函数
上次更新: 2026/06/10, 11:13:41
数据类型
复合类型

← 数据类型 复合类型→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式