C与C++
你这份复习清单涵盖了大厂笔试中最爱考的核心底层原理。为了方便你对照记忆,我把所有问题梳理成6大模块,逐一给出标准答案和深度辨析。
模块一: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 |
| 访问成员 | 直接用 .(使用天然像变量) | 用 -> 或 *(需解引用) |
| 自增/自减含义 | 改变原变量的值 | 改变指针本身的地址偏移 |
| 存储空间 | 语法上不占空间(底层用指针实现) | 占栈内存空间(存放地址) |