概念

ZaynPei Lv6

变量相关

全局变量若在其他文件中使用,需要用extern关键字声明(告诉编译器该变量在其他文件中定义),否则会报错”未定义的引用”。

1
2
// file1.cpp
int globalVar = 42; // 定义全局变量
1
2
// file2.cpp
extern int globalVar; // 声明全局变量
若用”static”修饰的全局变量,仅限当前文件使用,避免与其他文件同名变量冲突。
1
2
// file1.cpp
static int staticGlobalVar = 100; // 仅在 file1.cpp 中可见
静态局部变量在函数内定义,生命周期贯穿程序运行,但作用域仅限函数内部, 可保留上次的值
1
2
3
4
5
6
void func() {
static int count = 0; // 静态局部变量
count++;
std::cout << "Function called " << count << " times." << std::endl;
}
// 每次调用 func() 时,count 的值都会递增,而不是每次都重新初始化为 0。
全局变量默认初始化为零,静态局部变量也会被自动初始化为零,而普通局部变量则不进行自动初始化,使用前必须手动赋值

在之前, 静态成员变量必须在类外部单独定义,以便为其分配存储空间

1
2
3
4
5
class MyClass {
public:
static int staticMember; // 声明静态成员变量
};
int MyClass::staticMember = 0; // 定义并初始化静态成员变量
不过, C++17 引入了内联变量(inline variable)的概念, 允许在类内直接初始化静态成员变量, 这样就不需要在类外单独定义了
1
2
3
4
class MyClass {
public:
inline static int staticMember = 0; // C++17 及以上版本允许这样做
};

new操作符从自由存储区上为对象动态分配内存空间 自由存储区是 C++ 语言抽象出的概念,用来描述 new 和 delete 操作符进行动态内存分配和释放时所使用的内存区域。一般来说,自由存储区对应于操作系统提供的堆内存(heap memory), 但程序员可以重载 operator new,让自由存储区从非堆内存(如一个预先分配好的内存块或内存池)进行分配,从而绕过标准的堆分配机制,这在嵌入式系统或高性能计算中非常重要。

如果constexpr声明中定义了一个指针变量,那么该指针必须在编译时就能确定其指向的地址。(constexpr仅对指针有效,和所指对象无关)

1
2
constexpr int value = 42;
constexpr int* ptr = &value; // 合法,因为 &value 是一个常量表达式

初始化顺序

全局变量(包括静态全局变量和非静态全局变量)初始化在 main 函数执行前完成,局部静态变量首次调用时初始化,确保初始化顺序正确。

静态初始化(static initialization): 在编译期就能确定初始值的变量, 例如整型、浮点型、指针型等基本类型的变量, 以及用常量表达式初始化的变量, 这些变量会在程序加载时由运行时环境自动初始化为零或指定的初始值。

动态初始化(dynamic initialization): 需要在运行时计算初始值的变量, 例如调用构造函数进行初始化的类类型变量, 这些变量会在程序运行时由编译器生成的代码进行初始化。

但是需要注意, 不同编译单元之间的初始化顺序未定义: 这里的“不同编译单元”指的是不同的源文件。如果一个源文件中的全局变量依赖于另一个源文件中的全局变量进行初始化, 那么就可能会出现初始化顺序问题, 导致未定义行为。

1
2
3
4
5
6
// a.cpp
extern int x;
int y = x;

// b.cpp
int x = 10;
上面的例子中, 如果 a.cpp 中的 y 在 b.cpp 中的 x 之前初始化, 那么 y 的值将默认为 0, 而不是预期的 10。

编译器相关

预处理指令

#pragma是 预处理指令,用来向编译器发送特定的“指示/命令”。 它不是 C/C++ 语言标准的一部分,因此不同编译器对 #pragma 的支持和行为可能有所不同。

常见的 #pragma 指令包括: - #pragma once:防止头文件被多次包含。等价于#ifndef/#define保护,但更简洁。 - #pragma pack(n):设置结构体的对齐方式为 n 字节对齐。

断言

断言(Assertion)是一种用于在程序运行时进行条件检查的调试工具。它通过验证某个条件是否为真来帮助开发者捕捉程序中的逻辑错误。如果断言条件为假,程序通常会终止执行,并输出一条错误消息,指出断言失败的位置和条件。

它是嵌入在代码中的一个布尔表达式(例如 C/C + + 中的 assert(expression)),它表达了程序员对程序状态的特定假设。

原理:仅在调试模式下执行

行为:断言只在程序的调试版本(Debug Build)中才会被编译和执行。

实现:在 C/C + + 中,这通常是通过预处理器指令来实现的。例如,如果定义了 NDEBUG 宏(通常在发布模式下定义),则 assert() 宏会被定义为空操作,从而在发布版本中完全消除断言的开销。


使用断言的目的

发现不可恢复的内部错误: 检查程序自身逻辑上的错误(Bug),而不是用户输入错误或 I/O 错误。

区分错误类型: 将程序错误 (Bug) 与运行时异常 (Exception) 区分开。 - 程序错误:应该通过断言来处理,一旦发生就应立即终止程序。 - 运行时异常:通常是可预料和可恢复的(如文件未找到、网络连接中断),应使用异常处理 (try-catch) 机制

充当文档: 清晰地表达了程序员对程序状态的假设,是自我文档化的一种形式,帮助其他开发者理解代码逻辑。


终止机制:调用底层终止函数

当断言 assert(expression) 中的表达式 expression 评估为假 (False) 时,断言宏的展开代码会执行以下操作:

  • 输出诊断信息:向标准错误输出流(stderr)打印一条诊断消息,内容包括:断言失败的表达式本身。发生失败的源文件名。发生失败的行号。
  • 调用终止函数:调用一个底层的终止函数,例如 C 语言中的 abort() 或在 C + + 中可能触发未捕获的异常。