基础语法
# 第 2 章 C++ 基础语法
# 目录介绍
# 2.1 C++快速介绍
# 2.1.1 C++语言介绍
C++ 是一种通用的、高效的编程语言,广泛应用于系统编程、游戏开发、嵌入式系统、高性能计算等领域。
它是 C 语言 的扩展,同时支持面向过程编程和面向对象编程(OOP)。事实上,任何合法的 C 程序都是合法的 C++ 程序。
C++ 由 Bjarne Stroustrup 于 1980 年代初期在贝尔实验室开发,最初被称为“C with Classes”。
注意:使用静态类型的编程语言是在编译时执行类型检查,而不是在运行时执行类型检查。
# 2.1.2 C++的特点
- 高效性: C++ 直接编译为机器码,运行效率高。支持底层内存操作,适合开发高性能应用。
- 面向对象:支持类、对象、继承、多态等面向对象特性。
- 泛型编程:通过模板(
template)支持泛型编程,提高代码复用性。 - 标准库:提供了丰富的标准库(如 STL,Standard Template Library),包括容器、算法、迭代器等。
- 兼容 C 语言:几乎完全兼容 C 语言,可以直接使用 C 语言的库和代码。
- 跨平台:支持多种操作系统(如 Windows、Linux、macOS)和硬件平台。
# 2.1.3 C++标准库
标准的 C++ 由三个重要部分组成:
- 核心语言,提供了所有构件块,包括变量、数据类型和常量,等等。
- C++ 标准库,提供了大量的函数,用于操作文件、字符串等。
- 标准模板库(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;
}
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++作为"多范式语言"的强大灵活性。
思考题:
- C++既支持面向过程又支持面向对象,那么在实际项目中如何选择使用哪种范式?什么场景下混合使用更好?
- 为什么说"任何合法的C程序都是合法的C++程序"?这种兼容性在实际开发中带来了哪些好处和潜在问题?
- 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
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编译器的知识,以及编译器选择对开发的影响。
思考题:
- 为什么不同编译器对同一份C++代码可能产生不同的行为?这对跨平台开发有什么启示?
- 在线编译器和本地IDE各有什么优缺点?在什么场景下你会选择使用在线编译器?
- 如果你要开发一个跨平台项目(同时支持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;
}
2
3
4
5
6
7
8
# 2.3.2 编译代码
通常我们使用 -o 选项指定可执行程序的文件名,以下实例生成一个 hello 的可执行文件:
$ g++ main.cpp -o hello
$ ./hello
hello world
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;
}
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. 高级特性
2
3
4
5
6
7
8
9
10
11
案例知识融合:这个案例在Hello World基础上扩展了cin输入、string字符串处理、<<链式输出等功能。每一行代码都附带注释说明其作用,呼应了1.3.3节的代码解读方法。同时展示了#include包含头文件、using namespace std命名空间引用以及return 0程序退出的完整流程。
思考题:
#include <iostream>中的尖括号< >和双引号" "有什么区别?分别在什么场景使用?- 如果去掉
using namespace std;,程序该如何修改才能正常运行? return 0中的返回值 0 有什么含义?如果返回其他值(如1)会怎样?
# 2.4 编译C++
# 2.4.0 最简单编译
最简单的编译方式:
$ g++ main.cpp
由于命令行中未指定可执行程序的文件名,编译器采用默认的 a.out。程序可以这样来运行:
$ ./a.out
hello world
2
# 2.4.1 编译单个文件
g++ program.cpp -o program
#如果是这样,那么默认会生成a.out
g++ program.cpp
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
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
2
-std=c++11:指定使用 C++11 标准。
# 2.4.4 启用优化
g++ -O2 -o program program.cpp
g++ -O2 中的 -O2 表示启用编译器的优化级别 2。这是 GCC/G++ 编译器提供的一组预定义的优化策略,旨在提高生成代码的执行效率。以下是详细解释:
- -O0 (默认级别):不进行优化;编译速度最快;生成的代码易于调试;执行速度最慢
- -O1:基础优化;减少代码体积和执行时间;不显著增加编译时间
- -O2 (推荐级别):中等强度优化;启用所有不涉及空间/时间权衡的优化;不包括循环展开和内联函数等激进优化;在程序性能和代码大小之间取得良好平衡;通常用于发布版本
- -O3:最高级别优化;包含循环展开、函数内联等激进优化;可能显著增加代码体积;某些情况下可能导致性能下降(缓存问题)
-O2 包含的主要优化技术:
| 优化技术 | 说明 |
|---|---|
| 指令调度 | 重新排列指令以充分利用CPU流水线 |
| 循环优化 | 简化循环结构,减少分支开销 |
| 死代码消除 | 删除永远不会执行的代码 |
| 常量传播 | 用已知常量值替换变量 |
| 函数内联 | 将小函数直接插入调用位置(有限度) |
| 尾调用优化 | 重用栈帧进行递归调用 |
- 开发阶段:使用
-O0或-Og(优化调试体验) - 性能测试:使用
-O2 - 最终发布:推荐
-O2(稳定性和性能的平衡) - 特殊场景:对性能要求极高的场景可尝试
-O3,但需测试稳定性
# 调试版本(无优化)
g++ -g -o program program.cpp
# 发布版本(推荐优化)
g++ -O2 -o program program.cpp
# 激进优化版本(可能风险)
g++ -O3 -o program program.cpp
2
3
4
5
6
注意:优化级别越高,编译时间越长,且可能增加调试难度(变量可能被优化掉)。
-O2在大多数情况下是最佳选择,能在性能提升和稳定性之间取得良好平衡。
# 2.4.5 生成调试信息
g++ -g -o program program.cpp
在编译程序时,使用 -g选项会生成调试信息。这些信息允许调试器(如 GDB)将机器代码与源代码关联起来,从而进行源代码级别的调试。
调试信息是什么?调试信息是嵌入在可执行文件中的额外数据,它建立了机器代码与源代码之间的映射关系,使调试器能够:
- 源代码映射:将汇编指令对应到源代码行号
- 变量跟踪:识别内存位置对应的变量名
- 符号表:保存函数名、类名、变量名等符号信息
- 数据类型:记录变量和结构体的类型信息
- 调用栈:支持函数调用栈的追踪
调试信息包含的关键内容
| 信息类型 | 作用 | 示例 |
|---|---|---|
| 行号信息 | 将机器指令映射到源代码行 | 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; | | (行号、符号、类型) | | 设置断点 |
| } | | | | 单步执行 |
+-------------------+ +-------------------+ +-------------------+
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;
}
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
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)。通过对比不同编译方式和优化级别的输出文件大小,可以直观理解编译选项的影响。
思考题:
- 分步编译(先编译再链接)相比一步编译有什么优势?在大型项目中为什么通常选择分步编译?
- 为什么发布版本推荐使用
-O2而不是-O3?在什么场景下-O3可能反而导致性能下降? - 调试版本(
-g -O0)和发布版本(-O2)的可执行文件有什么区别?为什么调试时不建议开优化?
# 2.5 多种注释使用
在 C++ 中,注释 是用于解释代码的文本,编译器会忽略注释内容。注释对于提高代码的可读性和维护性非常重要,尤其是在团队协作或长期维护的项目中。
提示:编译器在编译代码时,会忽略注释的内容
# 2.5.1 单行注释
单行注释:// 描述信息
通常放在一行代码的上方,或者一条语句的末尾,==对该行代码说明==
int main() {
// 输出 Hello, World!
cout << "Hello, World!" << endl; // 这是行尾注释
return 0;
}
2
3
4
5
# 2.5.2 多行注释
多行注释: /* 描述信息 */
通常放在一段代码的上方,==对该段代码做整体说明==
/*
这是一个简单的 C++ 程序
用于输出 Hello, World!
*/
int main() {
cout << "Hello, World!" << endl;
return 0;
}
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;
}
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标准格式,可以直接用工具生成文档。
思考题:
- "好的代码是自文档化的"——这是否意味着不需要写注释?你认为什么样的注释才是有价值的?
- 多行注释
/* */不能嵌套使用,如果需要临时注释掉一段已包含多行注释的代码,该怎么做? - 在团队协作中,统一的注释规范有什么好处?你觉得 Doxygen 风格的文档注释在哪些场景下特别有用?
# 2.6 命名空间
命名空间(Namespace) 是 C++ 中用于组织代码的机制,它可以避免命名冲突,尤其是在大型项目或使用第三方库时。命名空间将代码逻辑分组,并为其中的标识符(如变量、函数、类等)提供作用域。
谨慎使用
using namespace
虽然 using namespace std; 在示例代码中很常见,但在实际项目中(尤其是头文件中)应避免这样做。原因如下:
- 命名冲突风险:
using namespace会将整个命名空间的所有名称引入当前作用域,可能与你自定义的名称或其他库的名称发生冲突。 - 头文件中的禁忌:在头文件中使用
using namespace会影响所有包含该头文件的文件,导致不可控的命名污染。 - 最佳实践:优先使用完全限定名(如
std::cout),或在局部作用域中使用using声明(如using std::cout;)。
# 2.6.1 命名空间概念
遇到问题:一个中大型软件往往由多名程序员共同开发,会使用大量的变量和函数,不可避免地会出现变量或函数的命名冲突。当所有人的代码都测试通过,没有问题时,将它们结合到一起就有可能会出现命名冲突。
场景例如:您可能会写一个名为 xyz() 的函数,在另一个可用的库中也存在一个相同的函数 xyz()。这样,编译器就无法判断您所使用的是哪一个 xyz() 函数。
解决办法:因此,引入了命名空间这个概念,可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。
作用:防止命名冲突,将代码逻辑分组。语法:
namespace 命名空间名 {
// 变量、函数、类等
}
2
3
访问方式:
- 使用
命名空间名::标识符访问命名空间中的成员。 - 使用
using namespace 命名空间名;引入整个命名空间。 - 使用
using 命名空间名::标识符;引入特定成员。
# 2.6.2 定义命名空间
namespace MyNamespace {
int value = 42; // 变量
void print() { // 函数
cout << "Hello from MyNamespace!" << endl;
}
}
2
3
4
5
6
# 2.6.3 访问命名空间成员
#include <iostream>
using namespace std;
int main() {
cout << MyNamespace::value << endl; // 访问变量
MyNamespace::print(); // 调用函数
return 0;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
2
3
4
5
6
引入 std 命名空间
#include <iostream>
using namespace std; // 引入 std 命名空间
int main() {
cout << "Hello, World!" << endl; // 直接访问
return 0;
}
2
3
4
5
6
7
# 2.6.8 综合练习题
- 使用
namespace space名称可以定义命名空间。通过命名空间可以调用所在区域函数 - 使用
using namespace space名称可以通过该方式引入命名空间。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。 - 嵌套的命名空间,命名空间可以嵌套,可以在一个命名空间中定义另一个命名空间。可以通过使用 :: 运算符来访问嵌套的命名空间中的成员。
//定义命名空间
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;
}
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;
}
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声明的最佳实践。
思考题:
- 为什么在头文件中使用
using namespace std;被认为是不好的实践?如果所有头文件都这样做会发生什么? - 匿名命名空间和
static关键字都能实现"文件内部可见",它们有什么区别?C++更推荐哪种方式? - 在实际大型项目中,命名空间该如何组织?请思考一个包含UI、网络、数据库三个模块的项目的命名空间设计。
# 2.7 头文件
在 C++ 中,头文件是用于声明类、函数、变量等的文件,通常以 .h 或 .hpp 为扩展名。
头文件的设计直接影响编译依赖和项目的可维护性。以下是关键原则:
- 最小化头文件包含:头文件中只应包含必需的其他头文件。能用前置声明替代的,就不要
#include。 - 声明与定义分离:头文件只放声明,实现放在
.cpp文件中(模板除外),这可以降低编译依赖。 - 自给自足原则:每个头文件应该能够独立编译(自身包含所有需要的依赖)。
- 使用头文件保护:必须使用
#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();
};
2
3
4
5
实现类做具体实现:
// MyClass.cpp
#include "MyClass.h"
void MyClass::doSomething() {
// 实现
}
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
2
3
4
5
6
7
8
9
10
使用 #pragma once 的现代写法(被所有主流编译器支持):
// MyClass.h
#pragma once
class MyClass {
public:
void doSomething();
};
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();
}
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;
}
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
2
3
4
5
案例知识融合:这个案例演示了头文件的最佳实践:使用#pragma once进行头文件保护、声明与定义分离(Logger类在.h中声明、.cpp中实现)、模板类必须在头文件中完整定义(Config模板类)、前置声明减少依赖、以及include顺序规范(对应头文件→标准库→第三方库)。
思考题:
#pragma once和#ifndef/#define/#endif两种头文件保护方式各有什么优缺点?在实际项目中你更倾向于使用哪种?- 为什么模板类的实现不能放在
.cpp文件中?如果强行这样做会出现什么错误? - "最小化头文件包含"原则要求尽量使用前置声明替代
#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; // 易变变量
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; // 重写基类虚函数
};
2
3
4
5
6
7
8
9
# 2.8.5 类修饰符
类修饰符用于限定类的行为。
final:禁止类被继承(C++11 引入)。abstract:通过纯虚函数实现抽象类(没有直接的关键字,纯虚函数实现)。
示例:
class Base final { // 禁止继承
// 类定义
};
2
3
# 2.8.6 其他修饰符
friend:允许非成员函数或其他类访问私有成员。explicit:禁止构造函数的隐式转换(C++11 引入)。noexcept:表示函数不会抛出异常(C++11 引入)。
示例:
class MyClass {
friend void friendFunc(); // 友元函数
};
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;
}
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。每个修饰符都有其特定的使用场景和作用。
思考题:
const成员函数中为什么不能修改成员变量?mutable关键字的存在意义是什么?请举一个mutable的实际应用场景。explicit关键字防止了什么问题?如果不使用explicit,Animal a = "test";这行代码会发生什么?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;
}
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)。通过一个程序就能感受关键字的丰富性。
思考题:
- C++有60多个关键字,而C语言只有32个。你认为关键字越多代表语言越强大还是越复杂?这种复杂性对学习和开发有什么影响?
auto关键字在C++11中被重新定义为自动类型推导,它在C语言中的含义是什么?为什么C++可以安全地改变这个关键字的含义?- C++20引入了
concept、co_await、co_yield等新关键字,你觉得C++语言未来的发展方向是什么?新特性的引入应该遵循什么原则?