C++_特殊工具与技术
特殊工具与技术
这部分内容的特点是:
- 不一定天天写
- 但体现 C++ 的底层能力和语言灵活性
- 很适合出概念题、判断题、细节题
复习策略:
先抓用途,再抓语法,再抓限制
控制内存分配
C++ 中对象通常通过以下方式创建:
- 自动对象:定义在函数内部,离开作用域自动销毁
- 静态对象:程序开始时创建,程序结束时销毁
- 动态对象:通过
new分配,通过delete释放
所谓“控制内存分配”,主要就是指:
自定义对象的分配和释放行为
new 和 delete 的基本过程
1 | T *p = new T; |
new T 一般做两件事:
- 分配原始内存
- 调用构造函数构造对象
delete p 一般做两件事:
- 调用析构函数销毁对象
- 释放内存
为什么要控制内存分配
某些场景下,我们希望:
- 提高效率
- 使用专用内存池
- 跟踪内存使用
- 限制对象只能在堆上/栈上创建
- 调试内存泄漏
于是可以通过重载 new / delete 来改变默认行为。
重载 new 和 delete
基本形式
类可以定义自己的 operator new 和 operator delete:
1 | class Foo { |
注意:
这两个函数通常是
static的
实际上即使不显式写static,它们本质上也不依赖对象
简单示例
1 | class Foo { |
使用:
1 | Foo* p = new Foo; |
作用范围
全局重载
可以重载全局 operator new / operator delete,影响全局动态分配。
类内重载
只影响该类对象的动态分配。
不能改变的部分
重载 operator new / operator delete 改变的是:
- 分配/释放内存的方式
但不能改变:
new先分配后构造 的整体语义delete先析构后释放 的整体语义
new 失败时
标准 operator new 分配失败时通常抛出:
1 | bad_alloc |
例如:
1 | try { |
nothrow 版本
如果不想抛异常,可以写:
1 | int* p = new (nothrow) int[100]; |
需要头文件:
1 |
placement new(定位 new)
这是控制内存分配中的重点。
placement new 的作用:
在一块已经分配好的原始内存上构造对象
例如:
1 |
|
这里:
buffer提供原始内存new (buffer) int(42)在这块内存上构造一个int
placement new 的特点
它:
- 不会分配新内存
- 只是在指定地址上构造对象
所以对应地:
对 placement new 构造的对象,不能直接用普通
delete
应显式调用析构函数(若是类类型):
1 | p->~T(); |
注
普通 new
- 分配内存
- 调构造函数
placement new
- 不分配内存
- 只在指定地址构造对象
限制对象分配方式(了解)
有时可以通过把构造函数/析构函数/operator new 设为 private,控制对象只能:
- 在堆上创建
- 在栈上创建
- 不能随意创建
这是工程技巧,复习知道即可。
运行时类型识别(RTTI)
RTTI = Run-Time Type Identification
作用:
在程序运行时识别对象的实际类型
C++ 主要提供两种 RTTI 工具:
dynamic_casttypeid
dynamic_cast
dynamic_cast 主要用于:
在继承体系中进行安全的类型转换
通常用于:
- 基类指针/引用 转换为 派生类指针/引用
- 多态体系中的向下转型
基本要求
使用 dynamic_cast 的前提:
基类必须含有至少一个虚函数
也就是说,要有多态性。
例如:
1 | class Base { |
指针形式
1 | Base* pb = new Derived; |
如果转换成功:
pd指向派生类对象
如果失败:
pd == nullptr
引用形式
1 | Base& rb = d; |
若失败:
- 会抛出
bad_cast异常
所以:
指针失败返回空指针
引用失败抛异常
为什么比 static_cast 安全
static_cast 不做运行时检查,
而 dynamic_cast 会检查对象真实类型。
因此在多态继承体系中,向下转型更安全。
典型用途
1 | if (Derived* p = dynamic_cast<Derived*>(basePtr)) { |
typeid
typeid 用于获取表达式的类型信息。
例如:
1 | cout << typeid(42).name() << endl; |
或者:
1 | Base* p = new Derived; |
动态类型与静态类型
如果操作对象本身,得到静态类型。
若操作的是多态类型对象的解引用,则可能得到动态类型。
例如:
1 | Base* p = new Derived; |
若 Base 是多态类型,则得到 Derived 的类型信息。
type_info
typeid 返回对 type_info 对象的引用,可用于:
- 比较类型是否相同
- 获取类型名字(实现相关)
例如:
1 | if (typeid(*p) == typeid(Derived)) { |
RTTI 的复习重点
dynamic_cast:安全向下转型typeid:获取运行时类型信息- 前提通常是 多态类型
枚举类型
枚举(enum)用于定义一组相关的命名常量。
传统枚举
1 | enum color { red, yellow, green }; |
此时:
red = 0yellow = 1green = 2
也可显式赋值:
1 | enum color { red = 1, yellow = 3, green = 5 }; |
枚举的作用
相比直接写数字常量:
- 语义更清晰
- 更便于维护
- 可限制取值范围
作用域问题
传统枚举的枚举值会暴露到外层作用域:
1 | enum color { red, green }; |
这是传统枚举的一个缺点。
作用域枚举(强类型枚举)
C++11 引入:
1 | enum class Color { red, yellow, green }; |
使用时必须加作用域:
1 | Color c = Color::red; |
强类型枚举的优点
- 枚举值不泄漏到外层作用域
- 类型更安全
- 不会隐式转换为整数
例如:
1 | Color c = Color::red; |
如需转换:
1 | int x = static_cast<int>(c); |
指定底层类型
1 | enum class Color : unsigned int { red, green, blue }; |
复习建议
考试里通常更推荐记:
现代 C++ 优先使用
enum class
类成员指针
类成员指针是比较容易混的一块。
它不是“指向对象的指针”,而是:
指向类成员的指针
包括:
- 数据成员指针
- 成员函数指针
数据成员指针
定义形式
1 | class Screen { |
含义:
pdata是一个指向Screen类中string成员的指针
使用方式
需要结合对象或对象指针使用:
1 | Screen myScreen; |
如果是对象指针:
1 | Screen* p = &myScreen; |
成员函数指针
定义形式
1 | class Screen { |
这类语法比较复杂,复习时重点识别结构。
调用方式
1 | Screen s; |
若是对象指针:
1 | Screen* p = &s; |
注意括号不能省。
本质理解
成员函数指针不是普通函数指针,因为成员函数调用需要隐含对象。
所以它必须和某个对象结合使用。
复习小结
成员指针必须依附对象使用:
.*或->*
嵌套类
一个类可以定义在另一个类内部,这叫:
嵌套类(nested class)
基本形式
1 | class Outer { |
使用时:
1 | Outer::Inner obj; |
作用
嵌套类常用于:
- 表示某个类的辅助类型
- 隐藏实现细节
- 表达“只和外部类相关”的类型
例如迭代器类、辅助节点类等。
访问关系
嵌套类和外层类是两个独立的类。
它们不会自动互相访问私有成员,是否能访问取决于访问控制和友元关系。
这点很容易误解。
复习小结
嵌套类只是“定义位置在类内部”,并不意味着天然拥有外层类对象或全部访问权限。
union(一种节省空间的类)
union 是一种特殊的类,特点是:
所有成员共享同一块内存
因此在任意时刻,通常只有一个成员处于有效状态。
基本定义
1 | union Token { |
这几个成员共享同一块存储空间。
大小特点
union 的大小至少等于其最大成员的大小。
例如:
charintdouble
则 union 通常至少和 double 一样大。
使用示例
1 | Token t; |
然后若写:
1 | t.dval = 3.14; |
则原来的 ival 不再是有效值。
为什么节省空间
因为多个成员共用一块内存,而不是各自独立存储。
适合在“同一时刻只需要其中一种表示”的场景中使用。
union 与类
union 也可以有:
- 成员函数
- 构造函数
- 析构函数
- 访问控制符
但限制比普通类更多。
含有类类型成员时
如果 union 的成员含有类类型,尤其是带构造/析构的类型,管理会更复杂。
现代 C++ 中通常需要手动控制活动成员。
这部分属于进阶内容,复习时知道:
union对非平凡类型的管理更复杂
匿名 union
可以定义匿名 union:
1 | union { |
其成员可直接访问:
1 | i = 10; |
但匿名 union 不能有成员函数,且成员必须是公有的。
现代替代
很多时候,现代 C++ 更推荐:
variant(C++17)- 类层次结构
- 带标签的联合体设计
不过考试里仍常考 union 的基本概念。
小结
union的所有成员共享内存,适合“多种表示中任一时刻只用一种”的场景。
局部类
在函数内部定义的类叫:
局部类(local class)
基本形式
1 | void f() { |
特点
局部类:
- 只在定义它的局部作用域中可见
- 主要用于某些局部辅助逻辑
限制
局部类和局部作用域关系密切,因此限制较多。
经典规则是:
局部类中的成员函数一般不能直接使用所在函数的普通局部变量
例如:
1 | void f() { |
原因是类成员函数不是外层函数的局部语句块一部分。
小结
局部类只在局部作用域可见,但不能随意直接访问外层函数的局部变量。
固有的不可移植的特性
C++ 提供了一些接近机器底层、与平台实现相关的特性。
这些特性能力强,但:
可移植性差,不同平台/编译器行为可能不同
因此称为“固有的不可移植的特性”。
常见内容
通常包括:
- 位域(bit-field)
volatile- 链接指示(如
extern "C")
有些教材会把它们放在这一节中统一讲。
位域(bit-field)
位域允许把一个整数成员限定为若干二进制位。
基本写法
1 | struct File { |
这里每个成员只占若干位。
作用
位域适合:
- 压缩存储
- 表示状态标志
- 与硬件/协议字段对应
注意点
位域的布局、对齐方式常与编译器和平台有关,
因此可移植性较差。
volatile
volatile 表示:
该对象的值可能在程序控制之外被改变
例如:
- 硬件寄存器
- 中断服务程序修改的变量
- 多线程早期低级同步场景(现代一般不用它做线程同步)
1 | volatile int flag; |
含义是告诉编译器:
- 不要假设它的值不会突然变化
- 不要进行某些过度优化
重要说明
volatile 不等于线程安全。
现代 C++ 线程同步应使用:
atomic- 互斥锁
- 条件变量
所以复习时记住:
volatile主要是告诉编译器“这个值可能被外部改变”,不是并发同步工具。
链接指示:extern "C"
C++ 支持函数重载、名字修饰(name mangling),
但 C 语言没有这些机制。
为了让 C++ 代码能和 C 代码链接,使用:
1 | extern "C" |
基本形式
1 | extern "C" int strcmp(const char*, const char*); |
表示该函数按 C 的链接方式处理。
也可用于一组声明:
1 | extern "C" { |
用途
- 调用 C 库函数
- C 与 C++ 混合编程
- 避免 C++ 名字改编导致链接失败
限制
由于 C 不支持函数重载,因此 extern "C" 的函数也不能靠 C++ 重载机制区分。
本章联系图
可以把本章理解成三类工具:
偏底层控制
- 控制内存分配
union- 位域
volatile
偏类型系统
- RTTI
- 枚举
- 类成员指针
偏类设计结构
- 嵌套类
- 局部类
易错点总结
重载 operator new 改变的是内存分配方式,不改变 new 的基本语义
placement new 不分配内存,只在指定地址构造对象
dynamic_cast 一般要求基类是多态类型
即至少有一个虚函数。
dynamic_cast 指针失败返回 nullptr,引用失败抛异常
typeid(*p) 只有在多态类型下才会体现动态类型
传统 enum 枚举值会泄漏到外层作用域
现代 C++ 更推荐 enum class
成员指针不是普通指针,必须和对象一起用:.* 或 ->*
嵌套类不等于自动拥有外层类全部访问能力
union 的成员共享内存,同一时刻通常只有一个有效成员
局部类不能随意直接使用外层函数的普通局部变量
volatile 不是线程同步工具
extern "C" 用于按 C 方式链接
结论速记
控制内存分配
可通过重载
new/delete或 placement new 改变对象构造所用内存方式
RTTI
dynamic_cast用于安全向下转型,typeid用于获取运行时类型信息
枚举
enum定义命名常量组,现代更推荐enum class
类成员指针
指向类成员而不是对象本身,使用时必须依附对象
嵌套类
定义在类内部的类,常作辅助类型
union
所有成员共享同一块内存,用空间换表达方式
局部类
定义在函数内部,只在局部作用域可见
不可移植特性
包括位域、
volatile、extern "C"等与平台/实现关系较强的机制
总结
这一章介绍了 C++ 中一些偏底层、偏特殊的机制:可以自定义内存分配,可以在运行时识别对象类型,可以用枚举组织常量,用成员指针描述类成员,用嵌套类和局部类组织结构,用
union节省空间,还能借助位域、volatile和extern "C"处理更接近底层和平台相关的问题。




