一、C++ 基础
1.1 语言特性与区别
-
C++ 与 C 的主要区别是什么?C++ 为何被称为 “带类的 C”?
- 主要区别:C++ 引入了面向对象编程(OOP)特性(类、继承、多态等),而 C 是过程式编程语言;C++ 支持函数重载、模板、异常处理等高级特性,C 不支持。
- 为何称为"带类的 C":早期 C++ 是在 C 语言基础上添加类和对象机制扩展而来,保留了 C 的大部分语法和功能,因此被称为"带类的 C"。
-
C++ 的基本数据类型有哪些?它们的大小(32 位 / 64 位系统)分别是多少?
- 基本数据类型:整数类型(char、short、int、long、long long)、浮点类型(float、double、long double)、布尔类型(bool)、空类型(void)。
- 大小(32位/64位系统):
- char:1字节
- short:2字节
- int:4字节
- long:4字节(32位)/ 8字节(64位)
- long long:8字节
- float:4字节
- double:8字节
- bool:1字节
-
指针和引用的区别是什么?何时使用指针,何时使用引用?
-
区别:
- 指针可以为空(nullptr),引用必须初始化且不能为null;
- 指针可以重新指向其他对象,引用一旦绑定不能更改;
- 指针需要解引用(*)访问对象,引用直接访问;
- 指针有自己的内存空间,引用不占用额外内存(编译器处理为指针)。
-
使用场景:
- 当需要表示"无对象"时使用指针(如返回可能失败的查找结果);
- 当需要动态修改指向的对象时使用指针;
- 其他情况优先使用引用(更安全、简洁)。
-
代码示例:
int a = 10; int* p = &a; // 指针 int& r = a; // 引用 *p = 20; // 解引用修改a的值 r = 30; // 直接修改a的值 p = nullptr; // 指针可以为空 // r = nullptr; // 错误:引用不能为null
-
-
结构体 struct 和共同体 union(联合)的区别是什么?
-
struct:各成员占用独立内存空间,总大小为各成员大小之和(考虑内存对齐)。
-
union:所有成员共享同一块内存空间,总大小为最大成员的大小。同一时间只能有一个成员有效。
-
代码示例:
struct S { int a; char b; }; // 大小至少为 4+1=5字节(实际可能更大,取决于对齐) union U { int a; char b; }; // 大小为4字节(int的大小)
-
-
struct 和 class 的区别?
- 默认访问权限:struct默认public,class默认private。
- 默认继承方式:struct默认public继承,class默认private继承。
- 模板参数:class可用于定义模板参数(如template),struct不行(C++11后无区别)。
-
C++ 是不是类型安全的?为什么?
- 不是完全类型安全。原因:
- 存在强制类型转换(如reinterpret_cast可以任意转换指针类型);
- 指针运算可能导致越界访问;
- 空指针解引用;
- 未初始化的变量;
- 数组与指针的隐式转换。
- 不是完全类型安全。原因:
1.2 关键字与修饰符
-
const 关键字的用法(修饰变量、函数参数、函数返回值、成员函数)?
-
修饰变量:表示变量的值不能被修改。
const int MAX = 100; // 常量
-
修饰函数参数:防止函数修改实参的值。
void print(const std::string& message); // 引用传递,不修改原字符串
-
修饰函数返回值:表示返回值不能被修改(通常用于返回指针或引用时)。
const char* getName() const; // 返回常量指针
-
修饰成员函数:表示该函数不会修改类的成员变量。
class Test { public:int getValue() const; // 不会修改成员变量 };
-
-
volatile 关键字的作用?它与 const 能否同时修饰一个变量?
-
作用:告诉编译器不要对该变量进行优化,每次都从内存中读取值。通常用于多线程环境中共享的变量或硬件寄存器映射的变量。
-
与const共存:可以同时修饰一个变量,表示变量的值不能被程序修改,但可能被其他因素(如硬件、其他线程)修改。
volatile const int* p = &someRegister; // 指向只读寄存器的指针
-
-
typedef 和 using(C++11)的区别?如何用它们定义函数指针?
-
区别:
- typedef是C语言的关键字,using是C++11引入的别名声明;
- using可以定义模板别名,typedef不能;
- using的语法更清晰,尤其是在复杂类型别名时。
-
定义函数指针:
// 使用typedef typedef int (*FuncPtr)(int, int);// 使用using(C++11) using FuncPtr = int (*)(int, int);// 使用示例 int add(int a, int b) { return a + b; } FuncPtr f = add;
-
-
typedef 和 #define 的区别是什么?
- 类型检查:typedef会进行类型检查,#define只是简单的文本替换;
- 作用域:typedef有作用域限制,#define全局有效;
- 复杂性:typedef适合定义复杂类型(如函数指针),#define更适合简单的文本替换;
- 调试:typedef在调试时更友好,可以显示类型信息。
-
extern “C” 的作用是什么?
- 作用:指定使用C语言的函数命名规则和调用约定,以便C++代码能够调用C语言编写的函数。
- 使用场景:在C++项目中调用C语言库,或者让C项目调用C++编写的接口。
// 在C++代码中声明C函数
extern "C" {void cFunction();
}// 在C++头文件中同时支持C和C++编译
#ifdef __cplusplus
extern "C" {
#endifvoid commonFunction();
#ifdef __cplusplus
}
#endif
-
static 关键字的作用有哪些(函数体内、模块内、类中)?
-
函数体内:变量在函数调用之间保持值,只初始化一次。
void increment() {static int count = 0; // 只初始化一次count++; // 每次调用都增加 }
-
模块内:限制变量或函数的作用域为本模块(.cpp文件),不能被其他模块访问。
static void helper() { /* 只在当前文件可见 */ }
-
类中:静态成员变量属于类而不是对象,静态成员函数不依赖于对象实例。
class MyClass { public:static int count; // 静态成员变量声明static void printCount() { // 静态成员函数std::cout << count << std::endl;} }; int MyClass::count = 0; // 静态成员变量定义
-
-
auto(C++11 前仅用于自动变量)和 decltype 的区别?
-
auto:根据初始化表达式自动推导变量类型,需要初始化。
auto x = 10; // x被推导为int类型 auto s = "hello"; // s被推导为const char*
-
decltype:根据表达式推导类型,不需要初始化。
int a = 5; decltype(a) b; // b被推导为int类型 decltype(a + 1.0) c; // c被推导为double类型
-
-
nullptr 与 NULL 的区别?为何推荐使用 nullptr?
-
NULL:通常是一个宏定义,在C++中被定义为0或 (void*)0,可能导致类型歧义。
-
nullptr:C++11引入的关键字,表示空指针常量,类型为nullptr_t。
-
推荐使用nullptr的原因:
- 避免整数和指针类型混淆;
- 在函数重载时提供明确的类型信息;
- 提高代码的可读性和安全性。
void f(int); void f(int*); f(NULL); // 可能调用f(int)而不是f(int*) f(nullptr); // 明确调用f(int*)
-
1.3 内存管理
-
C++ 的内存分区(栈、堆、全局 / 静态存储区、常量存储区、代码区)各自的特点?
- 栈(Stack):
- 由编译器自动分配释放;
- 存储函数参数、局部变量等;
- 空间小,访问速度快;
- 内存分配是连续的。
- 堆(Heap):
- 由程序员动态分配和释放;
- 空间较大,访问速度相对较慢;
- 内存分配不一定连续,可能产生碎片。
- 全局/静态存储区:
- 存储全局变量和静态变量;
- 程序运行期间一直存在;
- 程序结束时由系统释放。
- 常量存储区:
- 存储常量(如字符串常量、const常量);
- 内容不允许修改。
- 代码区:
- 存储程序的二进制指令;
- 通常是只读的。
- 栈(Stack):
-
堆和栈的区别(申请方式、大小限制、生命周期、碎片问题等)?
- 申请方式:栈由编译器自动分配;堆由程序员调用malloc/free或new/delete分配。
- 大小限制:栈的大小通常较小(几MB);堆的大小受限于系统虚拟内存(几GB)。
- 生命周期:栈中的数据在函数调用结束后自动销毁;堆中的数据需要手动释放,否则会造成内存泄漏。
- 碎片问题:频繁分配/释放堆内存会产生碎片;栈不会产生碎片。
- 分配效率:栈的分配效率高;堆的分配效率相对较低。
-
什么是内存泄漏?如何检测和避免内存泄漏?
- 内存泄漏:程序中动态分配的内存没有被正确释放,导致这部分内存无法被重用。
- 检测方法:
- 使用内存检测工具(如Valgrind、Visual Leak Detector);
- 在代码中添加内存跟踪日志;
- 使用智能指针自动管理内存。
- 避免方法:
- 及时释放动态分配的内存;
- 使用RAII原则(Resource Acquisition Is Initialization);
- 优先使用智能指针(如std::unique_ptr、std::shared_ptr);
- 避免复杂的内存管理逻辑。
-
C 和 C++ 动态管理内存的方法有何不同(malloc/free vs new/delete)?
-
malloc/free:C语言的函数,只负责分配/释放内存空间,不调用构造函数/析构函数。
int* p = (int*)malloc(sizeof(int)); free(p);
-
new/delete:C++的运算符,不仅分配/释放内存,还会调用构造函数/析构函数。
int* p = new int(10); // 分配内存并初始化 delete p; // 调用析构函数并释放内存// 数组版本 int* arr = new int[5]; delete[] arr; // 必须使用delete[]释放数组
-
其他区别:
- new/delete是运算符,malloc/free是函数;
- new自动计算所需内存大小,malloc需要手动计算;
- new分配失败时抛出异常,malloc返回nullptr;
- new/delete可以重载,malloc/free不能。
-
-
new 和 delete 是如何实现的?与 malloc 和 free 有何异同?
- new的实现:
- 调用operator new分配内存;
- 调用构造函数初始化对象。
- delete的实现:
- 调用析构函数清理对象;
- 调用operator delete释放内存。
- 与malloc/free的异同:
- 相同点:都是用于动态内存管理。
- 不同点:
- new/delete是运算符,malloc/free是函数;
- new/delete会调用构造函数/析构函数,malloc/free不会;
- new自动计算内存大小,malloc需要手动指定;
- new失败时抛出异常,malloc返回nullptr;
- new/delete可以重载,malloc/free不能。
- new的实现:
-
delete 和 delete [] 的区别是什么?
- delete:用于释放单个对象的内存,先调用该对象的析构函数,然后释放内存。
- delete []:用于释放数组对象的内存,先调用数组中每个对象的析构函数,然后释放内存。
- 混用的后果:用delete释放数组会导致只调用第一个对象的析构函数,造成内存泄漏;用delete []释放单个对象会导致多次调用析构函数,可能导致程序崩溃。
-
什么是野指针?其成因有哪些?
- 野指针:指向已释放内存或未分配内存的指针,无法确定其指向的内容。
- 成因:
- 指针未初始化;
- 指针指向的内存已被释放,但指针未置空;
- 指针越界访问。
- 避免方法:
- 指针初始化时置为nullptr;
- 释放内存后将指针置为nullptr;
- 使用智能指针代替裸指针。
-
栈溢出的原因是什么?有哪些解决方法?
- 原因:
- 函数调用层次过深(如递归调用没有终止条件);
- 局部变量占用过多栈空间(如大型数组);
- 栈空间设置过小。
- 解决方法:
- 优化递归算法,避免过深的调用层次;
- 使用堆内存代替栈内存存储大型数据;
- 调整栈空间大小(特定编译器/环境下)。
- 原因:
-
C++ 的内存管理中,自由存储区与堆的区别是什么?
- 自由存储区:由C++的new/delete运算符分配和释放的内存区域,是C++概念。
- 堆:由C语言的malloc/free函数分配和释放的内存区域,是操作系统概念。
- 关系:通常情况下,自由存储区和堆是同一块内存区域,但严格来说,自由存储区是基于堆实现的。
- 区别:
- 分配/释放方式不同:自由存储区使用new/delete,堆使用malloc/free;
- 内存管理机制不同:new/delete会调用构造函数/析构函数,malloc/free不会;
- new/delete可以重载,改变自由存储区的分配策略。
1.4 函数与预处理
-
函数重载的原理是什么?为何返回值不同不能构成重载?
-
原理:编译器根据函数的参数类型、数量、顺序生成不同的函数名(名称修饰),使得同名但参数列表不同的函数在编译后实际上有不同的标识符。
-
返回值不同不能构成重载:因为在函数调用时,编译器无法仅根据返回值类型来确定要调用哪个函数。函数调用的语法中,返回值是可选的,不影响函数的选择。
void print(int); void print(double); // 重载,参数类型不同 // int print(int); // 错误:仅返回值不同,不能构成重载
-
-
函数默认参数的规则(从右向左设置,调用时不能跳过前面的参数)?
-
从右向左设置:默认参数必须从最右边的参数开始设置,不能跳过中间的参数。
void func(int a, int b = 10, int c = 20); // 正确 // void func(int a = 10, int b, int c = 20); // 错误:不能跳过b
-
调用时不能跳过前面的参数:在调用有默认参数的函数时,必须从左到右提供参数,不能跳过前面的参数而直接使用后面的默认参数。
func(5); // 等同于func(5, 10, 20) func(5, 15); // 等同于func(5, 15, 20) // func(, 15); // 错误:不能跳过前面的参数
-
-
内联函数(inline)的作用?与宏定义的区别?为何内联函数不宜过长?
-
作用:减少函数调用的开销,提高程序运行效率。编译器会尝试将内联函数的代码插入到调用点,而不是进行函数调用。
-
与宏定义的区别:
- 内联函数由编译器处理,进行类型检查;宏由预处理器处理,仅进行文本替换。
- 内联函数支持调试;宏不支持调试。
- 内联函数更安全,不会有宏替换可能带来的副作用;宏可能因优先级问题导致错误。
// 内联函数 inline int max(int a, int b) { return a > b ? a : b; }// 宏定义 #define MAX(a, b) ((a) > (b) ? (a) : (b))
-
内联函数不宜过长的原因:
- 如果内联函数过长,编译器可能会忽略inline关键字,将其当作普通函数处理;
- 过长的内联函数会导致代码膨胀,增加可执行文件的大小。
-
-
预处理指令(#include、#define、#ifdef 等)的作用?#include <> 和 #include “” 的区别?
- 常用预处理指令:
#include
:包含头文件;#define
:定义宏;#undef
:取消宏定义;#ifdef
/#ifndef
:条件编译(如果宏已定义/未定义);#else
/#elif
:条件编译的分支;#endif
:结束条件编译块;#pragma
:特定编译器的指令。
- #include <> 和 #include “” 的区别:
#include <>
:用于包含标准库头文件,编译器会先在标准库目录中查找;#include ""
:用于包含用户自定义头文件,编译器会先在当前目录中查找,然后再到标准库目录中查找。
- 常用预处理指令:
-
定义和声明的区别是什么?
-
声明(Declaration):告诉编译器某个名字的存在及其类型,但不分配内存或初始化。
extern int g_var; // 变量声明 int func(int a); // 函数声明
-
定义(Definition):声明的同时分配内存或提供函数体。一个变量或函数可以有多个声明,但只能有一个定义。
int g_var = 10; // 变量定义 int func(int a) { return a * 2; } // 函数定义
-
-
引用作为函数参数以及返回值的好处是什么?有哪些限制?
-
作为函数参数的好处:
- 避免拷贝大对象,提高效率;
- 可以修改实参的值(如果不是const引用);
- 比指针更安全,不需要检查空指针。
-
作为函数返回值的好处:
- 避免返回值的拷贝,提高效率;
- 可以直接修改返回的对象(链式调用)。
-
限制:
- 引用必须绑定到有效的对象,不能返回局部变量的引用;
- 返回引用时需要确保引用的对象在函数调用结束后仍然存在。
// 正确:返回类成员的引用 class MyClass { private:int value; public:int& getValue() { return value; } };// 错误:返回局部变量的引用 int& badFunction() {int x = 10;return x; // x在函数结束后被销毁 }
-
-
C++ 文件编译与执行的四个阶段是什么?
- 预处理(Preprocessing):
- 处理预处理指令(如#include、#define);
- 删除注释;
- 宏展开;
- 生成.i文件。
- 编译(Compilation):
- 将预处理后的代码转换为汇编语言;
- 进行语法检查、语义分析、优化等;
- 生成.s文件。
- 汇编(Assembly):
- 将汇编代码转换为机器码(二进制);
- 生成.obj文件(Windows)或.o文件(Unix/Linux)。
- 链接(Linking):
- 将多个目标文件和库文件链接在一起;
- 解决符号引用;
- 生成可执行文件(.exe文件或ELF文件)。
- 预处理(Preprocessing):
-
头文件中 #ifndef/define/endif 与 #pragma once 的区别是什么?
-
#ifndef/define/endif(条件包含):
- 标准C++语法,所有编译器都支持;
- 可以嵌套使用,实现更复杂的条件包含;
- 可能存在宏名冲突的问题。
#ifndef HEADER_FILE_NAME_H #define HEADER_FILE_NAME_H// 头文件内容#endif // HEADER_FILE_NAME_H
-
#pragma once:
- 非标准但被大多数编译器支持的语法;
- 更简洁,不容易出错;
- 依赖于编译器对文件的识别,可能在某些情况下(如硬链接)导致重复包含。
#pragma once// 头文件内容
-
选择建议:两种方式都可以使用,通常推荐使用
#pragma once
,因为它更简洁且不容易出错,但如果需要兼容不支持#pragma once
的编译器,或者需要更复杂的条件包含,可以使用#ifndef/define/endif
。
-
1.5 类型转换与其他
-
C++ 的四种强制转换(static_cast、dynamic_cast、const_cast、reinterpret_cast)分别是什么?各自的适用场景?
-
static_cast:
- 适用场景:用于基本类型转换、上行转换(派生类指针/引用转为基类指针/引用)、转换编译器能明确识别的类型。
- 特点:编译时进行检查,不进行运行时类型检查。
int i = 10; double d = static_cast<double>(i); // 基本类型转换Base* base = static_cast<Base*>(derived); // 上行转换(安全)
-
dynamic_cast:
- 适用场景:主要用于下行转换(基类指针/引用转为派生类指针/引用),必须用于包含虚函数的类。
- 特点:运行时进行类型检查,如果转换失败返回nullptr(指针)或抛出异常(引用)。
Base* base = new Derived(); Derived* derived = dynamic_cast<Derived*>(base); // 下行转换,需要运行时检查
-
const_cast:
- 适用场景:用于移除变量的const或volatile限定符。
- 注意:如果原对象本身不是const的,使用const_cast修改是合法的;如果原对象是const的,修改其内容是未定义行为。
const int* p = &value; int* q = const_cast<int*>(p); // 移除const限定符
-
reinterpret_cast:
- 适用场景:用于不同类型指针之间的转换、指针与整数之间的转换,是最不安全的转换方式。
- 特点:仅重新解释指针的二进制表示,不进行任何类型检查或转换。
int* p = reinterpret_cast<int*>(0x12345678); // 指针与整数转换
-
-
左值和右值的区别?左值引用与右值引用的区别是什么?
- 左值(lvalue):表达式结束后依然存在的持久对象,可以出现在赋值语句的左侧。
- 右值(rvalue):表达式结束后就不再存在的临时对象,只能出现在赋值语句的右侧。
int a = 10; // a是左值,10是右值 int& lr = a; // 左值引用绑定到左值 // int& lr2 = 10; // 错误:左值引用不能绑定到右值 const int& lr3 = 10; // 常量左值引用可以绑定到右值// C++11引入的右值引用 int&& rr = 10; // 右值引用绑定到右值 // int&& rr2 = a; // 错误:右值引用不能直接绑定到左值 int&& rr3 = std::move(a); // 使用std::move将左值转换为右值
- 左值引用(&):
- 可以绑定到左值或const右值;
- 通常用于函数参数传递,避免拷贝。
- 右值引用(&&):
- C++11引入,用于实现移动语义和完美转发;
- 只能绑定到右值或通过std::move转换的左值;
- 主要用于移动构造函数、移动赋值运算符和完美转发。
-
指针数组和数组指针的区别是什么?
-
指针数组:一个数组,其元素是指针。
int* arr[5]; // 包含5个int*指针的数组
-
数组指针:一个指针,指向一个数组。
int (*ptr)[5]; // 指向包含5个int元素的数组的指针 int arr[5]; ptr = &arr; // 正确:ptr指向整个数组
-
记忆方法:看括号的位置,
*
和变量名在括号内的是数组指针,否则是指针数组。
-
-
sizeof 和 strlen 的区别是什么?
-
sizeof:
- 运算符,不是函数;
- 编译时计算;
- 计算变量、类型或表达式所占的字节数;
- 对于数组名,计算整个数组的大小;
- 对于指针,计算指针本身的大小(通常为4或8字节)。
-
strlen:
- 函数,定义在头文件中;
- 运行时计算;
- 计算字符串的长度,不包括结束符’\0’;
- 只适用于以’\0’结尾的字符数组(C风格字符串)。
char str[] = "hello"; sizeof(str); // 6字节(包含'\0') strlen(str); // 5字节(不包含'\0')char* p = str; sizeof(p); // 4或8字节(指针的大小) strlen(p); // 5字节
-
-
关于 sizeof 的详细小结(包括不同类型、数组、结构体等)?
-
基本类型:sizeof(char) = 1字节,sizeof(short) = 2字节,sizeof(int) = 4字节,sizeof(long) = 4或8字节,sizeof(long long) = 8字节,sizeof(float) = 4字节,sizeof(double) = 8字节,sizeof(bool) = 1字节。
-
指针类型:在32位系统上,sizeof(任意指针) = 4字节;在64位系统上,sizeof(任意指针) = 8字节。
-
数组:sizeof(数组名) = 数组元素个数 * sizeof(元素类型),但当数组名作为函数参数传递时,会退化为指针,sizeof(指针) = 指针本身的大小。
int arr[10]; sizeof(arr); // 10 * sizeof(int) = 40字节(假设int为4字节)void func(int a[]) {sizeof(a); // 4或8字节(指针的大小) }
-
结构体:sizeof(结构体)考虑内存对齐,通常大于或等于各成员大小之和。
struct S {char c; // 1字节int i; // 4字节double d; // 8字节 }; // 在大多数64位系统上,sizeof(S) = 16字节(内存对齐)
-
空类:C++中空类的大小为1字节(为了区分不同的对象实例)。
class Empty { }; sizeof(Empty); // 1字节
-
-
结构体为什么需要内存对齐?内存对齐的原因是什么?
- 内存对齐:编译器为了提高访问效率,会对结构体成员进行对齐,使每个成员的起始地址是其大小的整数倍。
- 内存对齐的原因:
- 性能考虑:处理器访问对齐的内存比未对齐的内存更快;
- 硬件限制:某些处理器架构不支持非对齐的内存访问,或者访问非对齐内存会导致性能下降;
- 兼容性:不同编译器、不同平台可能有不同的对齐要求,适当的对齐可以保证数据结构的兼容性。
- 对齐规则:
- 每个成员的起始地址必须是其自身大小的整数倍;
- 整个结构体的大小必须是其最大成员大小的整数倍(或编译器指定的最大对齐值的整数倍)。
-
引用是否占用内存空间?为什么?
- 从实现角度:引用通常在编译器内部实现为指针,因此会占用内存空间(在32位系统上为4字节,在64位系统上为8字节)。
- 从语言标准角度:C++标准没有明确规定引用是否占用内存空间,只是规定引用必须绑定到一个对象,并且不能重新绑定。
- 为何会有这样的设计:引用的设计目的是提供一个便捷、安全的别名机制,而不是作为一种新的指针类型。因此,标准并不关心其具体实现细节,只关心其行为。
-
全局变量和局部变量的区别是什么?是怎么实现的?操作系统和编译器是怎么知道的?
- 全局变量:
- 定义在函数外部,作用域为整个程序;
- 存储在全局/静态存储区,程序启动时分配内存,程序结束时释放;
- 默认初始化为0或空指针。
- 局部变量:
- 定义在函数内部,作用域为函数内部或代码块内部;
- 存储在栈区,函数调用时分配内存,函数返回时释放;
- 不默认初始化,其值是未定义的。
- 实现方式:
- 编译器在编译时为全局变量和局部变量分配不同的内存区域;
- 全局变量的地址在编译时或链接时确定;
- 局部变量的地址在运行时动态确定(基于栈指针)。
- 操作系统和编译器如何知道:
- 编译器在编译过程中会记录变量的作用域和存储类型;
- 链接器会处理全局变量的引用;
- 操作系统在加载程序时,根据可执行文件中的段信息(如.data段、.bss段)来分配和初始化全局变量的内存空间。
- 全局变量:
-
main 函数执行之前,还会执行什么代码?
-
全局变量的构造函数:程序启动时,会先初始化全局变量和静态变量,并调用它们的构造函数;
-
C++ 运行时库初始化:初始化C++标准库,如内存分配器、异常处理机制等;
-
main函数的参数设置:解析命令行参数,设置argc和argv;
-
其他初始化:如静态对象的初始化、线程局部存储的初始化等。
-
示例:
class MyClass { public:MyClass() { std::cout << "MyClass constructor called" << std::endl; } };MyClass globalObj; // 全局对象,其构造函数在main之前调用int main() {std::cout << "main function called" << std::endl;return 0; } // 输出顺序: // MyClass constructor called // main function called
-