编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • 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简史
      • 基础语法
        • 2.1 C++快速介绍
          • 2.1.1 C++语言介绍
          • 2.1.2 C++的特点
          • 2.1.3 C++标准库
          • 2.1.4 C++应用领域
          • 2.1.5 C++的版本
          • 2.1.6 综合案例与思考
        • 2.2 C++编译器
          • 2.2.1 编译器是什么
          • 2.2.2 GCC编译器
          • 2.2.3 Clang编译器
          • 2.2.4 MSVC编译器
          • 2.2.5 IDE开发环境
          • 2.2.6 在线编译器
          • 2.2.7 综合案例与思考
        • 2.3 helloWorld
          • 2.3.1 第一个案例
          • 2.3.2 编译代码
          • 2.3.3 代码解读
          • 2.3.4 综合案例与思考
        • 2.4 编译C++
          • 2.4.0 最简单编译
          • 2.4.1 编译单个文件
          • 2.4.2 编译多个文件
          • 2.4.3 指定C++标准
          • 2.4.4 启用优化
          • 2.4.5 生成调试信息
          • 2.4.6 综合案例与思考
        • 2.5 多种注释使用
          • 2.5.1 单行注释
          • 2.5.2 多行注释
          • 2.5.3 文档注释工具
          • 2.5.4 综合案例与思考
        • 2.6 命名空间
          • 2.6.1 命名空间概念
          • 2.6.2 定义命名空间
          • 2.6.3 访问命名空间成员
          • 2.6.4 嵌套命名空间
          • 2.6.5 匿名命名空间
          • 2.6.6 命名空间别名
          • 2.6.7 标准命名空间
          • 2.6.8 综合练习题
          • 2.6.9 综合案例与思考
        • 2.7 头文件
          • 2.7.1 头文件扩展名
          • 2.7.2 头文件和实现类
          • 2.7.3 头文件保护
          • 2.7.4 模板类头文件
          • 2.7.5 综合案例与思考
        • 2.8 修饰符和标志符
          • 2.8.1 访问控制修饰符
          • 2.8.2 存储类型修饰符
          • 2.8.3 类型限定符
          • 2.8.4 函数修饰符
          • 2.8.5 类修饰符
          • 2.8.6 其他修饰符
          • 2.8.7 标志符
          • 2.8.8 综合案例与思考
        • 2.9 关键字
          • 2.9.1 数据类型关键字
          • 2.9.2 存储类型关键字
          • 2.9.3 控制语句关键字
          • 2.9.4 类和对象关键字
          • 2.9.5 异常处理关键字
          • 2.9.6 类型转换关键字
          • 2.9.7 操作符相关关键字
          • 2.9.8 模板相关关键字
          • 2.9.9 命名空间相关关键字
          • 2.9.10 其他关键字
          • 2.9.11 综合案例与思考
      • 数据类型
      • 运算符
      • 复合类型
      • 流程语句
      • 函数
      • 指针引用
      • 类和对象
      • 继承多态
      • 内存模型
      • 动态内存
      • IO和文件
      • 异常处理
      • 线程和锁
      • STL模版
      • 预处理器
      • 特性图谱
    • 综合案例

    • 专栏博客

    • 开发技巧

  • Java入门精通

  • Go入门到精通

  • JavaScript入门

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

基础语法

# 第 2 章 C++ 基础语法

# 目录介绍

  • 2.1 C++快速介绍
    • 2.1.1 C++语言介绍
    • 2.1.2 C++的特点
    • 2.1.3 C++标准库
    • 2.1.4 C++应用领域
    • 2.1.5 C++的版本
    • 2.1.6 综合案例与思考
  • 2.2 C++编译器
    • 2.2.1 编译器是什么
    • 2.2.2 GCC编译器
    • 2.2.3 Clang编译器
    • 2.2.4 MSVC编译器
    • 2.2.5 IDE开发环境
    • 2.2.6 在线编译器
    • 2.2.7 综合案例与思考
  • 2.3 helloWorld
    • 2.3.1 第一个案例
    • 2.3.2 编译代码
    • 2.3.3 代码解读
    • 2.3.4 综合案例与思考
  • 2.4 编译C++
    • 2.4.0 最简单编译
    • 2.4.1 编译单个文件
    • 2.4.2 编译多个文件
    • 2.4.3 指定C++标准
    • 2.4.4 启用优化
    • 2.4.5 生成调试信息
    • 2.4.6 综合案例与思考
  • 2.5 多种注释使用
    • 2.5.1 单行注释
    • 2.5.2 多行注释
    • 2.5.3 文档注释工具
    • 2.5.4 综合案例与思考
  • 2.6 命名空间
    • 2.6.1 命名空间概念
    • 2.6.2 定义命名空间
    • 2.6.3 访问命名空间成员
    • 2.6.4 嵌套命名空间
    • 2.6.5 匿名命名空间
    • 2.6.6 命名空间别名
    • 2.6.7 标准命名空间
    • 2.6.8 综合练习题
    • 2.6.9 综合案例与思考
  • 2.7 头文件
    • 2.7.1 头文件扩展名
    • 2.7.2 头文件和实现类
    • 2.7.3 头文件保护
    • 2.7.4 模板类头文件
    • 2.7.5 综合案例与思考
  • 2.8 修饰符和标志符
    • 2.8.1 访问控制修饰符
    • 2.8.2 存储类型修饰符
    • 2.8.3 类型限定符
    • 2.8.4 函数修饰符
    • 2.8.5 类修饰符
    • 2.8.6 其他修饰符
    • 2.8.7 标志符
    • 2.8.8 综合案例与思考
  • 2.9 关键字
    • 2.9.1 数据类型关键字
    • 2.9.2 存储类型关键字
    • 2.9.3 控制语句关键字
    • 2.9.4 类和对象关键字
    • 2.9.5 异常处理关键字
    • 2.9.6 类型转换关键字
    • 2.9.7 操作符相关关键字
    • 2.9.8 模板相关关键字
    • 2.9.9 命名空间相关关键字
    • 2.9.10 其他关键字
    • 2.9.11 综合案例与思考

# 2.1 C++快速介绍

# 2.1.1 C++语言介绍

C++ 是一种通用的、高效的编程语言,广泛应用于系统编程、游戏开发、嵌入式系统、高性能计算等领域。

它是 C 语言 的扩展,同时支持面向过程编程和面向对象编程(OOP)。事实上,任何合法的 C 程序都是合法的 C++ 程序。

C++ 由 Bjarne Stroustrup 于 1980 年代初期在贝尔实验室开发,最初被称为“C with Classes”。

注意:使用静态类型的编程语言是在编译时执行类型检查,而不是在运行时执行类型检查。

# 2.1.2 C++的特点

  1. 高效性: C++ 直接编译为机器码,运行效率高。支持底层内存操作,适合开发高性能应用。
  2. 面向对象:支持类、对象、继承、多态等面向对象特性。
  3. 泛型编程:通过模板(template)支持泛型编程,提高代码复用性。
  4. 标准库:提供了丰富的标准库(如 STL,Standard Template Library),包括容器、算法、迭代器等。
  5. 兼容 C 语言:几乎完全兼容 C 语言,可以直接使用 C 语言的库和代码。
  6. 跨平台:支持多种操作系统(如 Windows、Linux、macOS)和硬件平台。

# 2.1.3 C++标准库

标准的 C++ 由三个重要部分组成:

  1. 核心语言,提供了所有构件块,包括变量、数据类型和常量,等等。
  2. C++ 标准库,提供了大量的函数,用于操作文件、字符串等。
  3. 标准模板库(STL),提供了大量的方法,用于操作数据结构等。

# 2.1.4 C++应用领域

  • 系统编程:操作系统、设备驱动程序、嵌入式系统。
  • 游戏开发:许多游戏引擎(如 Unreal Engine)使用 C++ 开发。
  • 高性能计算:科学计算、金融分析、人工智能。
  • 图形处理:图像处理、计算机视觉。
  • 网络编程:服务器开发、网络协议实现。
  • 桌面应用:使用 Qt、MFC 等框架开发跨平台 GUI 应用。

# 2.1.5 C++的版本

C++ 标准不断更新,主要版本包括:

  • C++98:第一个正式标准。
  • C++11:引入现代特性,如自动类型推导、Lambda 表达式。
  • C++14、C++17、C++20:逐步增强语言功能和标准库。

# 2.1.6 综合案例与思考

综合案例:探索C++的多范式特性

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 面向过程:普通函数
int add(int a, int b) {
    return a + b;
}

// 面向对象:类封装
class Calculator {
public:
    int result = 0;
    void accumulate(int val) { result += val; }
    int getResult() const { return result; }
};

// 泛型编程:模板函数
template <typename T>
T multiply(T a, T b) {
    return a * b;
}

int main() {
    // 1. 面向过程
    cout << "3 + 5 = " << add(3, 5) << endl;

    // 2. 面向对象
    Calculator calc;
    calc.accumulate(10);
    calc.accumulate(20);
    cout << "累加结果: " << calc.getResult() << endl;

    // 3. 泛型编程
    cout << "整数乘法: " << multiply(4, 5) << endl;
    cout << "浮点乘法: " << multiply(3.14, 2.0) << endl;

    // 4. STL标准库
    vector<int> nums = {5, 2, 8, 1, 9};
    sort(nums.begin(), nums.end());
    cout << "排序后: ";
    for (int n : nums) cout << n << " ";
    cout << 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

案例知识融合:这个案例展示了C++的三大编程范式——面向过程(普通函数add)、面向对象(Calculator类的封装)和泛型编程(模板函数multiply),同时使用了STL标准库(vector和sort算法)。通过一个案例可以感受到C++作为"多范式语言"的强大灵活性。

思考题:

  1. C++既支持面向过程又支持面向对象,那么在实际项目中如何选择使用哪种范式?什么场景下混合使用更好?
  2. 为什么说"任何合法的C程序都是合法的C++程序"?这种兼容性在实际开发中带来了哪些好处和潜在问题?
  3. C++11引入了很多现代特性(如auto、Lambda等),你认为这些特性解决了旧版C++的哪些痛点?

# 2.2 C++编译器

# 2.2.1 编译器是什么

C++ 编译器 是将 C++ 源代码转换为机器代码的工具,使程序能够在计算机上运行。

# 2.2.2 GCC编译器

GCC(GNU Compiler Collection):GCC是一个开源的编译器套件,包括C++编译器(g++)。它是许多Linux发行版的默认编译器,也可在其他操作系统上使用。

特点:开源、跨平台(支持 Linux、Windows、macOS 等)。支持最新的 C++ 标准(如 C++20)。性能优秀,广泛应用于开源项目。

# 2.2.3 Clang编译器

简介:Clang 是 LLVM 项目的一部分,专注于提供快速编译和清晰的错误信息。

特点: 开源、跨平台。编译速度快,错误信息友好。支持最新的 C++ 标准。

# 2.2.4 MSVC编译器

  • 简介:MSVC 是微软开发的 C++ 编译器,集成在 Visual Studio 中。
  • 特点:专为 Windows 平台优化。支持最新的 C++ 标准。提供强大的调试和开发工具。
  • 安装:通过 Visual Studio 安装。
  • 使用:在 Visual Studio 中创建项目并编译。

# 2.2.5 IDE开发环境

IDE 集成了编译器、编辑器、调试器等工具,提供更便捷的开发体验。以下是常用的 C++ IDE:

  • Visual Studio(Windows): 强大的调试工具,支持 MSVC 编译器。
  • Code::Blocks(跨平台):轻量级,支持多种编译器。
  • CLion(跨平台):JetBrains 开发的 C++ IDE,支持 CMake。
  • Xcode(macOS):苹果官方 IDE,支持 Clang 编译器。

# 2.2.6 在线编译器

如果你不想安装本地编译器,可以使用在线编译器快速测试代码:

Wandbox:https://wandbox.org/

OnlineGDB【体验好,有代码自动补充】:https://www.onlinegdb.com/

# 2.2.7 综合案例与思考

综合案例:用不同编译器编译同一程序并对比

# 源文件 compiler_test.cpp
cat << 'EOF' > compiler_test.cpp
#include <iostream>
using namespace std;
int main() {
    // 测试C++17特性:结构化绑定
    auto [x, y] = make_pair(10, 20);
    cout << "x = " << x << ", y = " << y << endl;

    // 测试编译器信息宏
    #if defined(__clang__)
        cout << "编译器: Clang " << __clang_major__ << "." << __clang_minor__ << endl;
    #elif defined(__GNUC__)
        cout << "编译器: GCC " << __GNUC__ << "." << __GNUC_MINOR__ << endl;
    #elif defined(_MSC_VER)
        cout << "编译器: MSVC " << _MSC_VER << endl;
    #else
        cout << "编译器: 未知" << endl;
    #endif

    cout << "C++标准版本: " << __cplusplus << endl;
    return 0;
}
EOF

# 使用GCC编译(指定C++17标准)
g++ -std=c++17 -o test_gcc compiler_test.cpp
./test_gcc

# 使用Clang编译
clang++ -std=c++17 -o test_clang compiler_test.cpp
./test_clang
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

案例知识融合:这个案例通过预处理宏检测当前使用的编译器类型和版本,同时输出C++标准版本号(__cplusplus)。通过用GCC和Clang分别编译运行,可以直观感受不同编译器的差异。这涵盖了1.2节中GCC、Clang、MSVC编译器的知识,以及编译器选择对开发的影响。

思考题:

  1. 为什么不同编译器对同一份C++代码可能产生不同的行为?这对跨平台开发有什么启示?
  2. 在线编译器和本地IDE各有什么优缺点?在什么场景下你会选择使用在线编译器?
  3. 如果你要开发一个跨平台项目(同时支持Windows、Linux、macOS),你会选择哪个编译器?为什么?

# 2.3 helloWorld

# 2.3.1 第一个案例

让我们看一段简单的代码,可以输出单词 Hello World。

#include <iostream>
using namespace std;

// main() 是程序开始执行的地方 是一个单行注释。
int main() {
    cout << "Hello world" << endl;
    return 0;
}
1
2
3
4
5
6
7
8

# 2.3.2 编译代码

通常我们使用 -o 选项指定可执行程序的文件名,以下实例生成一个 hello 的可执行文件:

$ g++ main.cpp -o hello
$ ./hello
hello world
1
2
3

# 2.3.3 代码解读

C++ 语言定义了一些头文件,这些头文件包含了程序中必需的或有用的信息。上面这段程序中,包含了头文件 。

下一行 using namespace std; 告诉编译器使用 std 命名空间。命名空间是 C++ 中一个相对新的概念。

下一行 // main() 是程序开始执行的地方 是一个单行注释。单行注释以 // 开头,在行末结束。

下一行 int main() 是主函数,程序从这里开始执行。

下一行 cout << "Hello World"; 会在屏幕上显示消息 "Hello World"。

下一行 return 0; 终止 main( )函数,并向调用进程返回值 0。

# 2.3.4 综合案例与思考

综合案例:一个完整的交互式Hello World程序

#include <iostream>  // 标准输入输出头文件
#include <string>    // 字符串处理头文件
using namespace std;  // 使用标准命名空间

// main函数:程序入口
int main() {
    // 1. 基本输出
    cout << "=== 欢迎来到C++世界 ===" << endl;

    // 2. 获取用户输入
    string name;
    cout << "请输入你的名字: ";
    cin >> name;

    // 3. 格式化输出
    cout << "Hello, " << name << "!" << endl;
    cout << "你是第 " << 1 << " 位学习者" << endl;

    // 4. 多行输出
    cout << "C++学习路线:" << endl
         << "  1. 基础语法" << endl
         << "  2. 面向对象" << endl
         << "  3. STL标准库" << endl
         << "  4. 高级特性" << endl;

    // 5. 返回值:0表示正常退出
    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

编译运行:

$ g++ hello_interactive.cpp -o hello
$ ./hello
=== 欢迎来到C++世界 ===
请输入你的名字: 张三
Hello, 张三!
你是第 1 位学习者
C++学习路线:
  1. 基础语法
  2. 面向对象
  3. STL标准库
  4. 高级特性
1
2
3
4
5
6
7
8
9
10
11

案例知识融合:这个案例在Hello World基础上扩展了cin输入、string字符串处理、<<链式输出等功能。每一行代码都附带注释说明其作用,呼应了1.3.3节的代码解读方法。同时展示了#include包含头文件、using namespace std命名空间引用以及return 0程序退出的完整流程。

思考题:

  1. #include <iostream> 中的尖括号 < > 和双引号 " " 有什么区别?分别在什么场景使用?
  2. 如果去掉 using namespace std;,程序该如何修改才能正常运行?
  3. return 0 中的返回值 0 有什么含义?如果返回其他值(如1)会怎样?

# 2.4 编译C++

# 2.4.0 最简单编译

最简单的编译方式:

$ g++ main.cpp
1

由于命令行中未指定可执行程序的文件名,编译器采用默认的 a.out。程序可以这样来运行:

$ ./a.out
hello world
1
2

# 2.4.1 编译单个文件

g++ program.cpp -o program
#如果是这样,那么默认会生成a.out
g++ program.cpp  
1
2
3
  • g++:调用 GCC 编译器。
  • program.cpp:源代码文件。
  • -o program:指定输出文件名为 program。

# 2.4.2 编译多个文件

g++ main.cpp utils.cpp -o program
g++ Account.cpp Bank.cpp FileManager.cpp BankUserManager.cpp -o BankUserManager
1
2
  • 将多个源文件一起编译。

# 2.4.3 指定C++标准

g++ 有些系统默认是使用 C++98,我们可以指定使用 C++11 来编译 main.cpp 文件:

g++ -g -Wall -std=c++11 main.cpp
g++ -std=c++11 -o program program.cpp
1
2

-std=c++11:指定使用 C++11 标准。

# 2.4.4 启用优化

g++ -O2 -o program program.cpp
1

g++ -O2 中的 -O2 表示启用编译器的优化级别 2。这是 GCC/G++ 编译器提供的一组预定义的优化策略,旨在提高生成代码的执行效率。以下是详细解释:

  1. -O0 (默认级别):不进行优化;编译速度最快;生成的代码易于调试;执行速度最慢
  2. -O1:基础优化;减少代码体积和执行时间;不显著增加编译时间
  3. -O2 (推荐级别):中等强度优化;启用所有不涉及空间/时间权衡的优化;不包括循环展开和内联函数等激进优化;在程序性能和代码大小之间取得良好平衡;通常用于发布版本
  4. -O3:最高级别优化;包含循环展开、函数内联等激进优化;可能显著增加代码体积;某些情况下可能导致性能下降(缓存问题)

-O2 包含的主要优化技术:

优化技术 说明
指令调度 重新排列指令以充分利用CPU流水线
循环优化 简化循环结构,减少分支开销
死代码消除 删除永远不会执行的代码
常量传播 用已知常量值替换变量
函数内联 将小函数直接插入调用位置(有限度)
尾调用优化 重用栈帧进行递归调用
  1. 开发阶段:使用 -O0 或 -Og(优化调试体验)
  2. 性能测试:使用 -O2
  3. 最终发布:推荐 -O2(稳定性和性能的平衡)
  4. 特殊场景:对性能要求极高的场景可尝试 -O3,但需测试稳定性
# 调试版本(无优化)
g++ -g -o program program.cpp
# 发布版本(推荐优化)
g++ -O2 -o program program.cpp
# 激进优化版本(可能风险)
g++ -O3 -o program program.cpp
1
2
3
4
5
6

注意:优化级别越高,编译时间越长,且可能增加调试难度(变量可能被优化掉)。-O2 在大多数情况下是最佳选择,能在性能提升和稳定性之间取得良好平衡。

# 2.4.5 生成调试信息

g++ -g -o program program.cpp
1

在编译程序时,使用 -g选项会生成调试信息。这些信息允许调试器(如 GDB)将机器代码与源代码关联起来,从而进行源代码级别的调试。

调试信息是什么?调试信息是嵌入在可执行文件中的额外数据,它建立了机器代码与源代码之间的映射关系,使调试器能够:

  1. 源代码映射:将汇编指令对应到源代码行号
  2. 变量跟踪:识别内存位置对应的变量名
  3. 符号表:保存函数名、类名、变量名等符号信息
  4. 数据类型:记录变量和结构体的类型信息
  5. 调用栈:支持函数调用栈的追踪

调试信息包含的关键内容

信息类型 作用 示例
行号信息 将机器指令映射到源代码行 0x401530 → main.cpp:15
符号表 函数/变量名到内存地址的映射 main() @ 0x401530
类型信息 变量类型和结构体定义 int a; struct Point {x,y};
作用域信息 变量可见范围 局部变量:函数内可见
宏定义 预处理器宏的原始定义 #define MAX_SIZE 100

调试信息如何工作

+-------------------+     +-------------------+     +-------------------+
|   源代码 (C++)     |     | 带调试信息的可执行文件 |     |      调试器       |
|                   |     |                   |     | (GDB, LLDB, IDE) |
|  int main() {     |     | 机器指令 +         |     |                   |
|    int a = 5;     |---->| 调试信息表         |---->| 显示变量值         |
|    return a;      |     | (行号、符号、类型) |     | 设置断点           |
|  }                |     |                   |     | 单步执行           |
+-------------------+     +-------------------+     +-------------------+
1
2
3
4
5
6
7
8

# 2.4.6 综合案例与思考

综合案例:多文件编译与不同优化级别对比

创建三个源文件模拟真实项目结构:

// math_utils.h - 头文件声明
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int factorial(int n);
double power(double base, int exp);
#endif

// math_utils.cpp - 实现文件
#include "math_utils.h"
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}
double power(double base, int exp) {
    double result = 1.0;
    for (int i = 0; i < exp; ++i) {
        result *= base;
    }
    return result;
}

// main.cpp - 主程序
#include <iostream>
#include "math_utils.h"
using namespace std;
int main() {
    cout << "5! = " << factorial(5) << endl;
    cout << "2^10 = " << power(2.0, 10) << 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
# 编译方式1:一步编译
g++ -std=c++17 main.cpp math_utils.cpp -o calc

# 编译方式2:分步编译(更灵活)
g++ -std=c++17 -c math_utils.cpp -o math_utils.o  # 只编译不链接
g++ -std=c++17 -c main.cpp -o main.o
g++ main.o math_utils.o -o calc                     # 链接

# 编译方式3:不同优化级别对比
g++ -O0 -o calc_debug main.cpp math_utils.cpp       # 无优化(调试用)
g++ -O2 -o calc_release main.cpp math_utils.cpp     # 推荐优化
g++ -O3 -o calc_fast main.cpp math_utils.cpp        # 激进优化

# 对比可执行文件大小
ls -la calc_debug calc_release calc_fast

# 带调试信息编译
g++ -g -std=c++17 main.cpp math_utils.cpp -o calc_gdb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

案例知识融合:这个案例涵盖了1.4节的所有编译知识点——单文件/多文件编译、指定C++标准(-std=c++17)、分步编译(-c只编译不链接)、不同优化级别(-O0到-O3)、生成调试信息(-g)。通过对比不同编译方式和优化级别的输出文件大小,可以直观理解编译选项的影响。

思考题:

  1. 分步编译(先编译再链接)相比一步编译有什么优势?在大型项目中为什么通常选择分步编译?
  2. 为什么发布版本推荐使用 -O2 而不是 -O3?在什么场景下 -O3 可能反而导致性能下降?
  3. 调试版本(-g -O0)和发布版本(-O2)的可执行文件有什么区别?为什么调试时不建议开优化?

# 2.5 多种注释使用

在 C++ 中,注释 是用于解释代码的文本,编译器会忽略注释内容。注释对于提高代码的可读性和维护性非常重要,尤其是在团队协作或长期维护的项目中。

提示:编译器在编译代码时,会忽略注释的内容

# 2.5.1 单行注释

单行注释:// 描述信息

通常放在一行代码的上方,或者一条语句的末尾,==对该行代码说明==

int main() {
    // 输出 Hello, World!
    cout << "Hello, World!" << endl; // 这是行尾注释
    return 0;
}
1
2
3
4
5

# 2.5.2 多行注释

多行注释: /* 描述信息 */

通常放在一段代码的上方,==对该段代码做整体说明==

/*
这是一个简单的 C++ 程序
用于输出 Hello, World!
*/
int main() {
    cout << "Hello, World!" << endl;
    return 0;
}
1
2
3
4
5
6
7
8

# 2.5.3 文档注释工具

C++ 支持使用文档注释生成 API 文档,常用的工具包括:

  • Doxygen:https://www.doxygen.nl/
  • Sphinx:https://www.sphinx-doc.org/

# 2.5.4 综合案例与思考

综合案例:规范注释的完整示例

/**
 * @file student_manager.cpp
 * @brief 学生信息管理系统 - 注释规范演示
 * @author YourName
 * @date 2024-01-01
 * @version 1.0
 */

#include <iostream>
#include <string>
#include <vector>
using namespace std;

/**
 * @brief 学生结构体
 * @details 用于存储学生的基本信息,包括姓名和成绩
 */
struct Student {
    string name;    // 学生姓名
    int score;      // 考试成绩(0-100)
};

/**
 * @brief 计算平均分
 * @param students 学生列表
 * @return 平均分数(double类型)
 * @note 如果列表为空,返回0.0
 */
double calcAverage(const vector<Student>& students) {
    if (students.empty()) return 0.0;  // 边界检查

    int total = 0;
    for (const auto& s : students) {
        total += s.score;
    }
    return static_cast<double>(total) / students.size();
}

/*
 * 主函数:
 * 1. 创建学生列表
 * 2. 计算并输出平均分
 * 3. 找出最高分学生
 */
int main() {
    // 初始化学生数据
    vector<Student> students = {
        {"张三", 85},
        {"李四", 92},
        {"王五", 78}
    };

    // 计算平均分
    double avg = calcAverage(students);
    cout << "平均分: " << avg << endl;  // 输出: 85

    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
56
57
58

案例知识融合:这个案例演示了三种注释类型的规范使用——单行注释(//用于行内简短说明)、多行注释(/* */用于代码段说明)、文档注释(/** @brief @param @return */用于生成API文档)。注释遵循了Doxygen标准格式,可以直接用工具生成文档。

思考题:

  1. "好的代码是自文档化的"——这是否意味着不需要写注释?你认为什么样的注释才是有价值的?
  2. 多行注释 /* */ 不能嵌套使用,如果需要临时注释掉一段已包含多行注释的代码,该怎么做?
  3. 在团队协作中,统一的注释规范有什么好处?你觉得 Doxygen 风格的文档注释在哪些场景下特别有用?

# 2.6 命名空间

命名空间(Namespace) 是 C++ 中用于组织代码的机制,它可以避免命名冲突,尤其是在大型项目或使用第三方库时。命名空间将代码逻辑分组,并为其中的标识符(如变量、函数、类等)提供作用域。

谨慎使用 using namespace

虽然 using namespace std; 在示例代码中很常见,但在实际项目中(尤其是头文件中)应避免这样做。原因如下:

  1. 命名冲突风险:using namespace 会将整个命名空间的所有名称引入当前作用域,可能与你自定义的名称或其他库的名称发生冲突。
  2. 头文件中的禁忌:在头文件中使用 using namespace 会影响所有包含该头文件的文件,导致不可控的命名污染。
  3. 最佳实践:优先使用完全限定名(如 std::cout),或在局部作用域中使用 using 声明(如 using std::cout;)。

# 2.6.1 命名空间概念

遇到问题:一个中大型软件往往由多名程序员共同开发,会使用大量的变量和函数,不可避免地会出现变量或函数的命名冲突。当所有人的代码都测试通过,没有问题时,将它们结合到一起就有可能会出现命名冲突。

场景例如:您可能会写一个名为 xyz() 的函数,在另一个可用的库中也存在一个相同的函数 xyz()。这样,编译器就无法判断您所使用的是哪一个 xyz() 函数。

解决办法:因此,引入了命名空间这个概念,可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。

作用:防止命名冲突,将代码逻辑分组。语法:

namespace 命名空间名 {
  // 变量、函数、类等
}
1
2
3

访问方式:

  • 使用 命名空间名::标识符 访问命名空间中的成员。
  • 使用 using namespace 命名空间名; 引入整个命名空间。
  • 使用 using 命名空间名::标识符; 引入特定成员。

# 2.6.2 定义命名空间

namespace MyNamespace {
    int value = 42; // 变量
    void print() {  // 函数
        cout << "Hello from MyNamespace!" << endl;
    }
}
1
2
3
4
5
6

# 2.6.3 访问命名空间成员

#include <iostream>
using namespace std;

int main() {
    cout << MyNamespace::value << endl; // 访问变量
    MyNamespace::print();               // 调用函数
    return 0;
}
1
2
3
4
5
6
7
8

使用 using namespace 可以引用整个空间

#include <iostream>
using namespace std;

namespace MyNamespace {
    int value = 42;
    void print() {
        cout << "Hello from MyNamespace!" << endl;
    }
}

using namespace MyNamespace; // 引入整个命名空间

int main() {
    cout << value << endl; // 直接访问变量
    print();               // 直接调用函数
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

使用 using 引入特定成员

#include <iostream>
using namespace std;

namespace MyNamespace {
    int value = 42;
    void print() {
        cout << "Hello from MyNamespace!" << endl;
    }
}

using MyNamespace::value; // 引入特定成员

int main() {
    cout << value << endl; // 直接访问变量
    MyNamespace::print();  // 仍需使用命名空间访问函数
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 2.6.4 嵌套命名空间

命名空间可以嵌套,形成层次结构。示例

namespace Outer {
    namespace Inner {
        void print() {
            cout << "Hello from Inner namespace!" << endl;
        }
    }
}

int main() {
    Outer::Inner::print(); // 访问嵌套命名空间
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12

# 2.6.5 匿名命名空间

匿名命名空间用于定义仅在当前文件内可见的成员,类似于 static 关键字。示例

namespace {
    int value = 42; // 仅在当前文件内可见
}

int main() {
    cout << value << endl; // 直接访问
    return 0;
}
1
2
3
4
5
6
7
8

# 2.6.6 命名空间别名

可以为命名空间定义别名,简化代码。示例

namespace VeryLongNamespaceName {
    void print() {
        cout << "Hello!" << endl;
    }
}

namespace VLN = VeryLongNamespaceName; // 定义别名

int main() {
    VLN::print(); // 使用别名访问
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12

# 2.6.7 标准命名空间

C++ 标准库的所有内容都定义在 std 命名空间中。

示例

#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl; // 使用 std:: 访问
    return 0;
}
1
2
3
4
5
6

引入 std 命名空间

#include <iostream>
using namespace std; // 引入 std 命名空间

int main() {
    cout << "Hello, World!" << endl; // 直接访问
    return 0;
}
1
2
3
4
5
6
7

# 2.6.8 综合练习题

  1. 使用 namespace space名称 可以定义命名空间。通过命名空间可以调用所在区域函数
  2. 使用 using namespace space名称 可以通过该方式引入命名空间。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。
  3. 嵌套的命名空间,命名空间可以嵌套,可以在一个命名空间中定义另一个命名空间。可以通过使用 :: 运算符来访问嵌套的命名空间中的成员。
//定义命名空间
namespace first_space {
    void fun() {
        cout << "Inside first_space" << endl;
    }
}
namespace second_space {
    void fun() {
        cout << "Inside second_space" << endl;
    }
}
void test1() {
    cout << "定义命名空间" << endl;
    // 调用第一个命名空间中的函数
    first_space::fun();
    // 调用第二个命名空间中的函数
    second_space::fun();
}

//using 指令
using namespace first_space;
//using 指令引入的名称遵循正常的范围规则。名称从使用 using 指令开始是可见的,直到该范围结束。此时,在范围以外定义的同名实体是隐藏的。
void test2() {
    cout << "using 指令" << endl;
    fun();
}

//嵌套的命名空间
namespace first_sp{
    void funSp() {
        cout << "Inside first_sp,逗比充1" << endl;
    }
    namespace second_sp {
        void funSp() {
            cout << "Inside second_sp,逗比充2" << endl;
        }
    }
}
using namespace first_sp::second_sp;


void test3() {
    cout << "嵌套的命名空间" << endl;
    // 调用第二个命名空间中的函数
    funSp();
}

int main() {
    test1();
    test2();
    test3();
    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

# 2.6.9 综合案例与思考

综合案例:模拟多模块项目的命名空间管理

#include <iostream>
#include <string>
using namespace std;

// 模块1:数据库操作
namespace Database {
    void connect() {
        cout << "[DB] 连接数据库..." << endl;
    }
    void query(const string& sql) {
        cout << "[DB] 执行查询: " << sql << endl;
    }

    // 嵌套命名空间:数据库工具
    namespace Utils {
        string escape(const string& input) {
            return "'" + input + "'";  // 简化的转义
        }
    }
}

// 模块2:网络通信
namespace Network {
    void connect() {  // 与Database::connect同名但不冲突
        cout << "[NET] 建立网络连接..." << endl;
    }
    void send(const string& data) {
        cout << "[NET] 发送数据: " << data << endl;
    }
}

// 模块3:日志系统(匿名命名空间,仅文件内可见)
namespace {
    int logLevel = 1;
    void log(const string& msg) {
        if (logLevel > 0)
            cout << "[LOG] " << msg << endl;
    }
}

// 命名空间别名
namespace DB = Database;
namespace Net = Network;

int main() {
    // 使用完全限定名(推荐方式)
    Database::connect();
    Network::connect();

    // 使用别名简化
    DB::query("SELECT * FROM users");
    Net::send("Hello Server");

    // 使用嵌套命名空间
    string safe = Database::Utils::escape("user_input");
    cout << "转义结果: " << safe << endl;

    // 局部using声明(比using namespace更安全)
    using Network::send;
    send("局部引入后直接调用");

    // 匿名命名空间中的函数可直接调用
    log("程序执行完毕");

    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
56
57
58
59
60
61
62
63
64
65
66

案例知识融合:这个案例模拟了一个多模块项目,展示了命名空间的核心用法:基本命名空间定义(Database和Network模块各自独立)、解决命名冲突(两个模块都有connect函数但互不干扰)、嵌套命名空间(Database::Utils)、匿名命名空间(文件级私有的log函数)、命名空间别名(DB简化长名称)、以及局部using声明的最佳实践。

思考题:

  1. 为什么在头文件中使用 using namespace std; 被认为是不好的实践?如果所有头文件都这样做会发生什么?
  2. 匿名命名空间和 static 关键字都能实现"文件内部可见",它们有什么区别?C++更推荐哪种方式?
  3. 在实际大型项目中,命名空间该如何组织?请思考一个包含UI、网络、数据库三个模块的项目的命名空间设计。

# 2.7 头文件

在 C++ 中,头文件是用于声明类、函数、变量等的文件,通常以 .h 或 .hpp 为扩展名。

头文件的设计直接影响编译依赖和项目的可维护性。以下是关键原则:

  1. 最小化头文件包含:头文件中只应包含必需的其他头文件。能用前置声明替代的,就不要 #include。
  2. 声明与定义分离:头文件只放声明,实现放在 .cpp 文件中(模板除外),这可以降低编译依赖。
  3. 自给自足原则:每个头文件应该能够独立编译(自身包含所有需要的依赖)。
  4. 使用头文件保护:必须使用 #ifndef / #define 或 #pragma once 防止重复包含。

# 2.7.1 头文件扩展名

扩展名 语言 用途 适用场景
.h C 和 C++ 传统头文件,兼容 C 和 C++ 兼容 C 和 C++ 的项目
.hpp C++ 现代 C++ 头文件,强调 C++ 特性 纯 C++ 项目
.hh C++ 特定社区风格的头文件 UNIX 或 Linux 社区的项目
.tpp C++ 模板类或模板函数的实现文件 需要分离模板声明与实现的项目

# 2.7.2 头文件和实现类

头文件中只放类、函数、变量的声明,具体实现放在 .cpp 文件中。建议每个类单独一个头文件,便于维护和管理。 例如:

头文件声明:

// MyClass.h
class MyClass {
public:
  void doSomething();
};
1
2
3
4
5

实现类做具体实现:

// MyClass.cpp
#include "MyClass.h"
void MyClass::doSomething() {
  // 实现
}
1
2
3
4
5

# 2.7.3 头文件保护

使用头文件保护(#ifndef / #define 或 #pragma once)防止重复包含。 例如:

// MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass {
public:
  void doSomething();
};

#endif // MYCLASS_H
1
2
3
4
5
6
7
8
9
10

使用 #pragma once 的现代写法(被所有主流编译器支持):

// MyClass.h
#pragma once

class MyClass {
public:
  void doSomething();
};
1
2
3
4
5
6
7

# 2.7.4 模板类头文件

模板类的声明和实现通常都放在头文件中,因为模板在编译时需要看到完整的定义才能实例化。如果将模板实现放在 .cpp 文件中,链接时会找不到模板实例化的代码。

// Stack.hpp - 模板类声明和实现都在头文件中
#pragma once

template <typename T>
class Stack {
public:
    void push(const T& value);
    T pop();
    bool empty() const;
private:
    std::vector<T> data_;
};

template <typename T>
void Stack<T>::push(const T& value) {
    data_.push_back(value);
}

template <typename T>
T Stack<T>::pop() {
    T val = data_.back();
    data_.pop_back();
    return val;
}

template <typename T>
bool Stack<T>::empty() const {
    return data_.empty();
}
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

提示:如果模板实现较长,可以将实现放在单独的 .tpp 文件中,然后在头文件末尾 #include "Stack.tpp" 引入。

# 2.7.5 综合案例与思考

综合案例:规范的多文件项目结构

// ========== logger.h ==========
#pragma once  // 头文件保护
#include <string>

// 前置声明(减少头文件依赖)
class FileWriter;

// 日志级别枚举
enum class LogLevel { DEBUG, INFO, WARN, ERROR };

// Logger类声明(只放声明,不放实现)
class Logger {
public:
    Logger(const std::string& tag);
    void log(LogLevel level, const std::string& message);
    static std::string levelToString(LogLevel level);
private:
    std::string tag_;
};

// ========== logger.cpp ==========
#include "logger.h"    // 对应头文件放第一个
#include <iostream>     // 标准库头文件
#include <chrono>

// 实现放在.cpp文件中
Logger::Logger(const std::string& tag) : tag_(tag) {}

void Logger::log(LogLevel level, const std::string& message) {
    std::cout << "[" << levelToString(level) << "] "
              << "[" << tag_ << "] " << message << std::endl;
}

std::string Logger::levelToString(LogLevel level) {
    switch (level) {
        case LogLevel::DEBUG: return "DEBUG";
        case LogLevel::INFO:  return "INFO";
        case LogLevel::WARN:  return "WARN";
        case LogLevel::ERROR: return "ERROR";
        default: return "UNKNOWN";
    }
}

// ========== config.h ==========
#pragma once

// 模板类必须在头文件中定义实现
template <typename T>
class Config {
public:
    void set(const std::string& key, T value) { data_[key] = value; }
    T get(const std::string& key) const {
        auto it = data_.find(key);
        if (it != data_.end()) return it->second;
        return T{};  // 返回默认值
    }
private:
    std::map<std::string, T> data_;
};

// ========== main.cpp ==========
#include "logger.h"
#include "config.h"
#include <iostream>
#include <map>
using namespace std;

int main() {
    Logger logger("App");
    logger.log(LogLevel::INFO, "应用启动");

    Config<int> config;
    config.set("port", 8080);
    logger.log(LogLevel::DEBUG, "端口: " + to_string(config.get("port")));

    logger.log(LogLevel::INFO, "应用退出");
    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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# 编译命令
g++ -std=c++17 -c logger.cpp -o logger.o
g++ -std=c++17 -c main.cpp -o main.o
g++ logger.o main.o -o app
./app
1
2
3
4
5

案例知识融合:这个案例演示了头文件的最佳实践:使用#pragma once进行头文件保护、声明与定义分离(Logger类在.h中声明、.cpp中实现)、模板类必须在头文件中完整定义(Config模板类)、前置声明减少依赖、以及include顺序规范(对应头文件→标准库→第三方库)。

思考题:

  1. #pragma once 和 #ifndef/#define/#endif 两种头文件保护方式各有什么优缺点?在实际项目中你更倾向于使用哪种?
  2. 为什么模板类的实现不能放在 .cpp 文件中?如果强行这样做会出现什么错误?
  3. "最小化头文件包含"原则要求尽量使用前置声明替代 #include,哪些场景下可以用前置声明,哪些场景下必须用 #include?

# 2.8 修饰符和标志符

在 C++ 中,修饰符和标志符是用于修饰变量、函数、类等的关键字或符号,它们可以改变对象的属性、行为或作用域。

# 2.8.1 访问控制修饰符

访问控制修饰符用于定义类成员的访问权限。

  • public:公有成员,任何地方都可以访问。
  • protected:受保护成员,只有类本身、派生类可以访问。
  • private:私有成员,只有类本身可以访问。

# 2.8.2 存储类型修饰符

存储类型修饰符用于定义变量的存储方式和生命周期。

  • auto:自动变量,编译器自动推导类型(C++11 引入)。
  • register:建议将变量存储在寄存器中(现代编译器通常忽略)。
  • static:静态变量,生命周期贯穿整个程序。
  • extern:声明变量在其他文件中定义。
  • mutable:允许在 const 对象中修改成员变量。

# 2.8.3 类型限定符

类型限定符用于修饰变量的行为或属性。

  • const:表示变量不可修改。
  • volatile:表示变量可能被外部修改,禁止编译器优化。
  • restrict:用于指针,表示指针是唯一访问内存的方式(C99 引入,C++ 中不常用)。

示例:

const int a = 10;          // 常量
volatile int b = 20;       // 易变变量
1
2

# 2.8.4 函数修饰符

函数修饰符用于限定函数的行为。

  • inline:建议编译器将函数内联。
  • virtual:表示函数是虚函数,可以被派生类重写。
  • override:明确表示重写基类的虚函数(C++11 引入)。
  • final:禁止派生类重写虚函数(C++11 引入)。
  • constexpr:表示函数可以在编译时求值(C++11 引入)。

示例:

class Base {
public:
    virtual void func() const; // 虚函数
};

class Derived : public Base {
public:
    void func() const override; // 重写基类虚函数
};
1
2
3
4
5
6
7
8
9

# 2.8.5 类修饰符

类修饰符用于限定类的行为。

  • final:禁止类被继承(C++11 引入)。
  • abstract:通过纯虚函数实现抽象类(没有直接的关键字,纯虚函数实现)。

示例:

class Base final { // 禁止继承
    // 类定义
};
1
2
3

# 2.8.6 其他修饰符

  • friend:允许非成员函数或其他类访问私有成员。
  • explicit:禁止构造函数的隐式转换(C++11 引入)。
  • noexcept:表示函数不会抛出异常(C++11 引入)。

示例:

class MyClass {
    friend void friendFunc(); // 友元函数
};
1
2
3

# 2.8.7 标志符

作用:C++规定给标识符(变量、常量)命名时,有一套自己的规则

C++ 标识符是用来标识变量、函数、类、模块,或任何其他用户自定义项目的名称。

  • 标识符不能是关键字
  • 标识符只能由字母、数字、下划线组成
  • 第一个字符必须为字母或下划线
  • 标识符中字母区分大小写

建议:给标识符命名时,争取做到见名知意的效果,方便自己和他人的阅读

有效标志符,一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。

无效标志符,C++ 标识符内不允许出现标点字符,比如 @、& 和 %。C++ 是区分大小写的编程语言。

# 2.8.8 综合案例与思考

综合案例:修饰符在类设计中的综合运用

#include <iostream>
#include <string>
using namespace std;

// 基类:使用多种修饰符
class Animal {
public:     // 访问控制修饰符:公有
    explicit Animal(const string& name) : name_(name) {}  // explicit防止隐式转换
    virtual ~Animal() = default;                            // virtual虚析构

    virtual void speak() const = 0;       // 纯虚函数(抽象类)
    const string& getName() const { return name_; }  // const成员函数

protected:  // 访问控制修饰符:受保护
    string name_;

private:    // 访问控制修饰符:私有
    static int count_;   // static静态成员
};

int Animal::count_ = 0;  // 静态成员初始化

// 派生类:使用override和final
class Dog final : public Animal {  // final禁止继续继承
public:
    Dog(const string& name, const string& breed)
        : Animal(name), breed_(breed) {}

    void speak() const override {  // override明确重写
        cout << name_ << "(" << breed_ << "): 汪汪!" << endl;
    }

    // friend友元函数:可以访问私有成员
    friend void showBreed(const Dog& dog);

private:
    string breed_;
    mutable int speakCount_ = 0;  // mutable: 在const函数中也可修改
};

// 友元函数实现
void showBreed(const Dog& dog) {
    cout << dog.name_ << " 的品种是 " << dog.breed_ << endl;
}

// inline内联函数
inline bool isValidName(const string& name) {
    return !name.empty() && name.length() < 50;
}

// constexpr编译时计算
constexpr int maxAnimals() { return 100; }

// noexcept承诺不抛异常
void safeFunction() noexcept {
    cout << "这个函数不会抛出异常" << endl;
}

int main() {
    // explicit阻止隐式转换
    // Animal a = "test";  // 错误!explicit阻止了这种隐式转换
    Dog dog("旺财", "金毛");
    dog.speak();
    showBreed(dog);  // 友元函数

    // constexpr在编译时求值
    constexpr int limit = maxAnimals();
    cout << "最大动物数: " << limit << endl;

    // volatile示例(通常用于硬件编程)
    volatile int sensorValue = 42;  // 告诉编译器不要优化此变量

    safeFunction();
    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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

案例知识融合:这个案例在一个动物管理场景中综合使用了C++的各种修饰符——访问控制(public/protected/private)、存储修饰符(static/mutable)、类型限定符(const/volatile)、函数修饰符(virtual/override/inline/constexpr/noexcept)、类修饰符(final)、以及explicit和friend。每个修饰符都有其特定的使用场景和作用。

思考题:

  1. const 成员函数中为什么不能修改成员变量?mutable 关键字的存在意义是什么?请举一个 mutable 的实际应用场景。
  2. explicit 关键字防止了什么问题?如果不使用 explicit,Animal a = "test"; 这行代码会发生什么?
  3. final 关键字可以修饰类和虚函数,两种用法分别在什么场景下有用?过度使用 final 会带来什么问题?

# 2.9 关键字

C++ 中的关键字是语言保留的标识符,具有特定的含义和用途,不能用作变量名、函数名或其他用户定义的标识符。

C++ 的关键字包括从 C 继承的关键字以及 C++ 自己引入的关键字。

作用:关键字是C++中预先保留的单词(标识符)

  • 在定义变量或者常量时候,不要用关键字

C++关键字如下:

asm do if return typedef
auto double inline short typeid
bool dynamic_cast int signed typename
break else long sizeof union
case enum mutable static unsigned
catch explicit namespace static_cast using
char export new struct virtual
class extern operator switch void
const false private template volatile
const_cast float protected this wchar_t
continue for public throw while
default friend register true
delete goto reinterpret_cast try

提示:在给变量或者常量起名称时候,不要用C++得关键字,否则会产生歧义。

# 2.9.1 数据类型关键字

这些关键字用于定义变量或函数的类型。

关键字 含义
int 整型
float 浮点型
double 双精度浮点型
char 字符型
bool 布尔型(true 或 false)
void 无类型
wchar_t 宽字符型
long 长整型
short 短整型
signed 有符号类型
unsigned 无符号类型

# 2.9.2 存储类型关键字

这些关键字用于定义变量的存储方式和生命周期。

关键字 含义
auto 自动类型推导(C++11 引入)
static 静态变量
extern 外部变量
register 寄存器变量(建议存储在寄存器中,现代编译器通常忽略)
mutable 允许在 const 对象中修改成员变量

# 2.9.3 控制语句关键字

这些关键字用于控制程序的执行流程。

关键字 含义
if 条件语句
else 条件语句的分支
switch 多分支选择语句
case switch 的分支
default switch 的默认分支
for 循环语句
while 循环语句
do 循环语句
break 跳出循环或 switch 语句
continue 跳过本次循环
return 从函数返回
goto 无条件跳转

# 2.9.4 类和对象关键字

这些关键字用于定义和操作类、对象和继承。

关键字 含义
class 定义类
struct 定义结构体
union 定义联合体
enum 定义枚举
private 私有访问权限
protected 受保护访问权限
public 公有访问权限
virtual 虚函数
override 重写基类虚函数(C++11 引入)
final 禁止继承或重写(C++11 引入)
explicit 禁止隐式转换(C++11 引入)
friend 友元函数或友元类
this 当前对象的指针
new 动态分配内存
delete 释放动态分配的内存

# 2.9.5 异常处理关键字

这些关键字用于处理异常。

关键字 含义
try 尝试执行一段代码
catch 捕获异常
throw 抛出异常
noexcept 表示函数不会抛出异常(C++11 引入)

# 2.9.6 类型转换关键字

这些关键字用于进行显式类型转换。

关键字 含义
static_cast 静态类型转换
dynamic_cast 动态类型转换
const_cast 去掉或添加 const 限定符
reinterpret_cast 重新解释类型

# 2.9.7 操作符相关关键字

这些关键字用于定义或重载操作符。

关键字 含义
operator 重载操作符
sizeof 获取对象或类型的大小
typeid 获取对象的类型信息
alignof 获取类型的对齐要求(C++11 引入)

# 2.9.8 模板相关关键字

这些关键字用于定义和操作模板。

关键字 含义
template 定义模板
typename 定义模板参数或别名
constexpr 编译时常量(C++11 引入)
decltype 推导表达式的类型(C++11 引入)

# 2.9.9 命名空间相关关键字

这些关键字用于定义和操作命名空间。

关键字 含义
namespace 定义命名空间
using 使用命名空间或类型别名

# 2.9.10 其他关键字

这些关键字用于其他特殊用途。

关键字 含义
nullptr 空指针(C++11 引入)
static_assert 静态断言(C++11 引入)
export 模块导出(C++20 引入)
import 模块导入(C++20 引入)
concept 定义概念(C++20 引入)
co_await 协程等待(C++20 引入)
co_yield 协程生成值(C++20 引入)
co_return 协程返回值(C++20 引入)

# 2.9.11 综合案例与思考

综合案例:通过一个小程序认识常用关键字

#include <iostream>
#include <string>
#include <vector>
#include <typeinfo>
using namespace std;

// enum关键字:定义枚举类型
enum class Season { SPRING, SUMMER, AUTUMN, WINTER };

// struct关键字:定义结构体
struct Point {
    double x, y;
};

// class关键字:定义类
class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;  // virtual + const关键字
    virtual string type() const = 0;
};

// 继承相关关键字:class, public, override
class Circle : public Shape {
public:
    explicit Circle(double r) : radius_(r) {}
    double area() const override { return 3.14159 * radius_ * radius_; }
    string type() const override { return "Circle"; }
private:
    double radius_;
};

// template关键字:模板
template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    // auto关键字:自动类型推导
    auto num = 42;
    auto pi = 3.14;
    auto name = string("C++");

    // decltype关键字:推导表达式类型
    decltype(num) another = 100;

    // new/delete关键字:动态内存
    Circle* circle = new Circle(5.0);
    cout << circle->type() << " 面积: " << circle->area() << endl;
    delete circle;

    // static_cast关键字:类型转换
    int intVal = static_cast<int>(pi);

    // sizeof关键字:获取大小
    cout << "int大小: " << sizeof(int) << " 字节" << endl;

    // typeid关键字:获取类型信息
    cout << "num的类型: " << typeid(num).name() << endl;

    // for/if/else/switch关键字:控制流
    vector<int> scores = {85, 92, 78, 96, 88};
    for (const auto& s : scores) {
        if (s >= 90) {
            cout << s << " -> 优秀" << endl;
        } else {
            cout << s << " -> 良好" << endl;
        }
    }

    // try/catch/throw关键字:异常处理
    try {
        if (scores.empty()) throw runtime_error("空列表");
        cout << "最高分: " << getMax(scores[0], scores[1]) << endl;
    } catch (const exception& e) {
        cout << "异常: " << e.what() << endl;
    }

    // constexpr关键字:编译时常量
    constexpr int MAX_SIZE = 1024;

    // nullptr关键字:空指针
    int* ptr = nullptr;
    if (ptr == nullptr) {
        cout << "指针为空" << endl;
    }

    // static_assert关键字:编译时断言
    static_assert(sizeof(int) >= 4, "int必须至少4字节");

    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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

案例知识融合:这个案例在一个完整程序中使用了C++中大部分常见关键字,涵盖了数据类型(int/double/bool/void)、存储类型(auto/static)、控制语句(for/if/else/switch)、类与对象(class/struct/enum/public/private/virtual/override)、异常处理(try/catch/throw)、类型转换(static_cast)、模板(template/typename)、以及C++11新增关键字(auto/decltype/constexpr/nullptr/static_assert)。通过一个程序就能感受关键字的丰富性。

思考题:

  1. C++有60多个关键字,而C语言只有32个。你认为关键字越多代表语言越强大还是越复杂?这种复杂性对学习和开发有什么影响?
  2. auto 关键字在C++11中被重新定义为自动类型推导,它在C语言中的含义是什么?为什么C++可以安全地改变这个关键字的含义?
  3. C++20引入了 concept、co_await、co_yield 等新关键字,你觉得C++语言未来的发展方向是什么?新特性的引入应该遵循什么原则?
上次更新: 2026/06/10, 11:13:41
Cpp简史
数据类型

← Cpp简史 数据类型→

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