一、C++ 源文件组织与编译执行
1. 基本C++源文件的代码组织形式(内容结构)
一个标准的 .cpp 文件通常按以下顺序组织:
- 头文件包含(
#include):引入所需的库或声明。 - 宏定义与类型别名(
#define,typedef/using)。 - 命名空间引入(
using namespace,谨慎使用)。 - 全局变量/常量声明(通常尽量避免全局变量)。
- 辅助函数/类的定义(或实现)。
- 主函数入口(
int main()),一般放在文件最后。
2. 如何编译执行?
- 编译+链接一步到位(以 g++ 为例):
g++ -std=c++17 source.cpp -o program # 生成可执行文件 ./program # Linux/macOS运行 program.exe # Windows运行 - 分步编译(了解底层):
- 预处理(
g++ -E):处理宏、头文件展开。 - 编译(
g++ -S):生成汇编代码。 - 汇编(
g++ -c):生成目标文件.o/.obj。 - 链接(
g++):合并目标文件与库,生成可执行文件。
- 预处理(
二、命名空间(Namespace)
1. 主要作用是什么?
解决命名冲突(名字污染),并对逻辑功能进行模块化分组(如 std::cout 与自定义的 mylib::cout 共存)。
2. 使用时有几种形式?
- 定义命名空间:
namespace MySpace { int a; } - 作用域限定符访问:
MySpace::a - 引入整个命名空间:
using namespace MySpace;(把内部名字暴露到当前作用域) - 引入单个成员:
using MySpace::a; - 嵌套定义(C++17起支持简洁语法):
namespace A::B { ... } - 匿名命名空间:
namespace { int internal; }(文件内部私有,详见下方)
3. 同一作用域内能定义同名的命名空间吗?
可以。 这称为命名空间的扩展(Extension)。编译器会将同一作用域下同名的命名空间合并为一个。
namespace A { int x; }
namespace A { int y; } // 合法,合并后 A 中有 x 和 y
关于“匿名命名空间”
匿名命名空间(namespace { ... })中的实体具有内部链接属性,仅在当前 .cpp 文件内可见,且不能被 extern 外部访问(这恰好是你上一个问题的答案延伸)。现代 C++ 强烈推荐用它替代文件内的 static 修饰符。
三、const 修饰内置类型的特点
- 被
const修饰的内置类型变量变为只读(常量),一旦初始化,值不可修改。 - 必须在声明时初始化(除非是
extern外部声明)。 - 默认具有内部链接属性(在 C++ 中,文件作用域的
const默认是static的,只在本文件可见),这使得它非常适合替代#define宏。
四、const 与指针的四大黄金法则(笔试必考)
你列举的四条,核心在于看 const 在 * 的左边还是右边。口诀:左定值,右定向。
| 表达式 | 类型解读 | p 能否指向别处? | *p(解引用)能否修改值? |
|---|---|---|---|
const int * p | 指向常量整数的指针 | 能(p可变) | 不能(*p只读) |
int const * p | 同上(两者完全等价) | 能 | 不能 |
int * const p | 常量指针,指向整数 | 不能(p固定) | 能(*p可改) |
const int * const p | 指向常量整数的常量指针 | 不能 | 不能 |
记忆技巧:忽略类型名
int,只看const相对*的位置。const在*左边修饰的是指向的对象,在*右边修饰的是指针本身。
五、new/delete VS malloc/free 辨析(全维度对比)
| 对比维度 | new / delete(C++运算符) | malloc / free(C库函数) |
|---|---|---|
| 本质 | 关键字/运算符(可重载) | 标准库函数(不可重载) |
| 内存大小 | 编译器自动计算(无需 sizeof) | 手动指定字节数(易算错) |
| 返回类型 | 返回具体类型指针(T*),类型安全 | 返回 void*,必须强转 |
| 构造函数/析构函数 | 自动调用(对象生命周期完整) | 不调用(只分配原始内存) |
| 失败处理 | 抛出 std::bad_alloc 异常(默认) | 返回 NULL(需手动检查) |
| 内存重分配 | 不支持(需手动 new 拷贝再 delete) | 支持 realloc 扩容 |
| 数组处理 | new[] 配合 delete[],会记录元素个数以便逐一析构 | 仅分配连续空间,无元素个数概念 |
绝对红线:
new必须配delete,malloc必须配free,严禁混用(未定义行为)。
六、引用(Reference)深度剖析
1. 什么是引用?
引用是一个已存在变量的别名(Alias)。操作引用就等同于操作原变量。
2. 引用的本质是什么?
从语法层面:它是一个变量的别名,不占用独立的存储空间(语言标准规定)。
从底层实现层面(编译器视角):本质是一个常指针(Type * const),即指针指向不可变,但编译器隐藏了取地址和解引用的操作,让你用起来像普通变量。
3. 对引用取地址,得到的是什么?
得到的是被引用变量本身的地址,而不是引用这个“别名”的地址。
int a = 10;
int &ref = a;
int *p = &ref; // p 指向的是 a,p 的值为 &a
4. 引用 VS 指针 核心辨析(面试常考)
| 对比维度 | 引用(Reference) | 指针(Pointer) |
|---|---|---|
| 必须初始化 | 必须在定义时初始化 | 可以不初始化(但不推荐,易成野指针) |
| 能否重新绑定 | 不能(一旦绑定,终身指向该变量) | 能(可改变指向另一块内存) |
| 是否可为空 | 绝不能为空(必须绑定有效对象) | 可以为 nullptr |
| 访问成员 | 直接用 .(使用天然像变量) | 用 -> 或 *(需解引用) |
| 自增/自减含义 | 改变原变量的值 | 改变指针本身的地址偏移 |
| 存储空间 | 语法上不占空间(底层用指针实现) | 占栈内存空间(存放地址) |
根据您提供的思维导图,这是一份关于C++核心基础知识的复习大纲。以下为您详细总结这四个核心模块的内容:
六、 掌握函数重载的特点及原理
- 什么是函数重载?
在同一个作用域内,允许存在多个同名函数,但这些函数的参数列表必须不同。 - 形参列表不同有哪些表现?
- 参数个数不同。
- 参数类型不同。
- 参数顺序不同。
注意:仅返回值类型不同,不能构成函数重载。
- 函数重载的原理是什么?
依赖于C++的**名称修饰(Name Mangling)**机制。编译器在编译阶段,会将函数名和参数列表(包括参数类型、个数)结合起来,生成一个唯一的内部符号名。链接器根据这个独特的符号名来区分不同的函数。
七、 掌握内联函数的使用场景
- 什么时候适用?
- 函数体代码短小(通常建议在10行以内)。
- 函数被频繁调用(如简单的 getter/setter 方法)。
- 函数内部没有复杂的控制流(如循环、递归)。
优点:在编译阶段将函数体直接展开,省去了函数调用压栈、跳转、返回的开销,提高运行效率。
- 什么时候不适用?
- 函数体代码庞大(会导致代码膨胀,增加编译后二进制文件的大小,反而降低指令缓存命中率)。
- 包含复杂的循环、递归或 switch 语句。
- 虚函数(除非在编译期能确定具体调用对象,否则多态性会导致内联失效)。
注意:inline只是给编译器的一个建议,编译器有权忽略它。
八、 了解C++中异常处理的几个关键字干嘛用的
throw:用于抛出异常对象。当程序遇到无法处理的错误时,使用throw抛出一个异常。try:用于包裹可能抛出异常的代码块。它定义了异常监测的范围。catch:用于捕获并处理特定类型(或所有类型...)的异常。可以定义多个catch块来匹配不同的异常类型。- 不处理异常,程序怎么执行?
如果异常被抛出,且沿着调用栈向上一直找都没有匹配的catch块,程序会调用std::terminate()(进而调用std::abort()),导致程序直接崩溃/异常终止。 - 处理异常,程序怎么执行?
异常抛出后,try块中后续的代码立即停止执行。程序开始进行栈展开(Stack Unwinding),销毁局部对象,寻找匹配的catch块。执行完catch块内的代码后,程序会跳转到try-catch结构之后的代码继续执行。
九、 掌握内存布局
C++程序运行时的内存通常分为5个区域:
- 栈区(Stack):由编译器自动分配和释放。存放函数的局部变量、函数参数、返回地址等。空间较小,分配效率极高,但容易发生栈溢出。
- 堆区(Heap):由程序员手动分配和释放(
new/delete,malloc/free)。空间大,分配效率相对较低,容易产生内存泄漏和内存碎片。 - 全局/静态存储区(Global/Static):存放全局变量、静态变量(
static)。程序启动时分配,程序结束时释放。分为已初始化的.data段和未初始化的.bss段。 - 常量存储区(Constant):存放常量(如字符串字面量、全局
const变量等),通常位于只读内存段,不允许修改。 - 代码区(Code/Text):存放程序编译后的二进制机器指令(可执行代码),通常是只读的,防止程序运行时被修改。
(注:部分面试资料也会把“全局/静态区”和“常量区”合并为“静态存储区”,变成四大区域,但具体细分更能体现基础扎实程度)