控制内存分配
重载new和delete
new
表达式的工作机理
1 | string *sp = new string("a value"); //分配并初始化一个string对象 |
上述代码实际执行了三步操作
new
表达式调用一个名为operator new
(或operator new []
)的标准库函数,它分配一块足够大的、原始的、未命名的内存空间以便存储特定类型的对象(或对象的数组)- 编译器运行相应的构造函数以构造这些对象,并为其传入初始值
- 对象被分配了空间并构造完成,返回一个指向该对象的指针
delete
表达式的工作机理
1 | delete sp; // 销毁*sp,然后释放sp指向的内存空间 |
- 上述代码实际执行了两步操作
- 对
sp
所指向的对象或者arr
所指的数组中的元素执行对应的析构函数 - 编译器调用名为
operator delete
(或operator delete[]
)的标准库函数释放内存空间
- 对
- 当自定义了全局的
operator new
函数和operator delete
函数后,我们就担负起了控制动态内存分配的职责。这两个函数必须是正确的。因为它们是程序整个处理过程中至关重要的一部分 - 标准库定义了
operator new
函数和operator delete
函数的8个重载版本
1 | // 这些版本可能抛出异常 |
- 应用程序可以自定义上面函数版本中的任意一个,前提是自定义的版本必须位于全局作用域或者类作用域中
- 注意: 提供新的
operator new
函数和operator delete
函数的目的在于改变内存分配的方式,但是不管怎样,都不能改变new
运算符和delete
运算符的基本含义 - 使用从C语言继承的函数
malloc
和free
函数能实现以某种方式执行分配内存和释放内存的操作
1 |
|
定位new表达式
- 使用new运算符后返回的是已知的用户指定的地址
- 应该使用new的定位
new(placement new)
形式传递一个地址,定位new
的形式如下:
1 | new (place_address) type |
- 当只传入一个指针类型的实参时,定位
new
表达式构造对象但是不分配内存 - 调用析构函数会销毁对象,但是不会释放内存
1 | string *sp = new string("a value"); // 分配并初始化一个string对象 |
运行时类型识别
- 运行时类型识别
(run-time type identification, RTTI)
的功能由两个运算符实现typeid
运算符, 用于返回表达式的类型dynamic_cast
运算符,用于将基类的指针或引用安全地转换曾派生类的指针或引用
- 使用
RTTI
必须要加倍小心。在可能的情况下,最好定义虚函数而非直接接管类型管理的重任
dynamic_cast运算符
- dynamic_cast运算符的使用形式如下
1 | dynamic_cast<type*>(e) // e必须是一个有效的指针 |
- 可以对一个空指针执行
dynamic_cast
,结果是所需类型的空指针
typeid运算符
typeid运算符(typeid operator)
,它允许程序向表达式提问:你的对象是什么类型?typeid
表达式的形式是typeid(e)
,其中e
可以是任意表达式或类型的名字,它操作的结果是一个常量对象的引用(type_info)。它可以作用于任意类型的表达式- 通常情况下,使用typeid比较两条表达式的类型是否相同,或者比较一条表达式的类型是否与指定类型相同
1 | Derived *dp = new Derived; |
- 当typeid作用于指针时(而非指针所指向的对象),返回的结果是该指针的静态编译时类型
1 | // 下面的检查永远是失败的:bp的类型是指向Base的指针 |
使用RTTI
- 用途:为具有继承关系的类实现相等运算符时。对于两个对象来说,如果它们的类型相同并且对应的数据成员取值相同,则说这两个对象是相等的
1 | // 类的层次关系 |
type_info类
枚举类型
- 枚举类型
(enumeration)
使我们可以将一组整型常量组织在一起。枚举属于字面值常量类型 - **限定作用域的枚举类型(scoped enumeration)**:首先是关键字
enum class(或enum struct)
,随后是枚举类型名字以及用花括号括起来的以逗号分隔的枚举成员列表,最后是一个分号
1 | enum class open_modes {input, output, append}; |
- 不限定作用域的枚举类型
(unscoped enumeration)
:省略关键字class(或struct)
,枚举类型的名字是可选的
1 | enum color {red, yellow, green}; |
类成员指针
成员指针:指可以指向类的非静态成员的指针
数据成员指针
- 和其他指针一样,在声明成员指针时也使用*来表示当前声明的名字是一个指针。与普通指针不同的时,成员指针还必须包含成员所属的类
1 | // pdata可以指向一个常量(非常量)Screen对象的string成员 |
- 当我们初始化一个成员指针或为成员指针赋值时,该指针没有指向任何数据。成员指针指定了成员而非该成员所属的对象,只有当解引用成员指针时才提供对象的信息
1 | Screen myScreen, *pScreen = &myScreen; |
成员函数指针
- 因为函数调用运算符的优先级较高,所以在声明指向成员函数的指针并使用这些的指针进行函数调用时,括号必不可少:
(C::*p)(parms)
和(obj.*p)(args)
将成员函数用作可调用对象
1 |
|
嵌套类
- 一个类可以定义在另一个类的内部,前者称为嵌套类(nested class)或嵌套类型(nested type)。嵌套类常用于定义作为实现部分的类
- 嵌套类是一个独立的类,与外层类基本没有什么关系。特别是,外层类的对象和嵌套类的对象是相互独立的
- 嵌套类的名字在外层类作用域中是可见的,在外层类作用域之外不可见
union:一种节省空间的类
联合(union)
是一种特殊的类。一个union
可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。它不能含有引用类型的成员和虚函数
1 | // Token类型的对象只有一个成员,该成员的类型可能是下列类型中的任意一种 |
匿名union(anonymous union)
是一个未命名的union
,并且在右花括号和分号之间没有任何声明
1 | union { |
- 注意:
匿名union
不能包含受保护的成员或私有成员,也不能定义成员函数
局部类
局部类(local class)
:可以定义在某个函数的内部的类。它的类型只在定义它的作用域内可见。和嵌套类不同,局部类的成员受到严格限制- 局部类的所有成员(包括函数在内)都必须完整定义在类的内部。因此,局部类的作用与嵌套类相比相差很远
- 局部类不能使用函数作用域中的变量
1 | int a, val; |
固有的不可移植的特性
所谓不可移植的特性是指因机器而异的特性,当将含有不可移植特性的程序从一台机器转移到另一台机器上时,通常需要重新编写该程序
位域
- 类可以将其(非静态)数据成员定义成**位域(bit-field)**,在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域
- 位域在内存中的布局是与机器相关的
- 位域的类型必须是整型或枚举类型。因为带符号位域的行为是由具体实现确定的,通常情况下我们使用无符号类型保存一个位域
1 | typedef unsigned int Bit; |
volatile限定符
- 当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为
volatile
。关键字volatile
告诉编译器不应对这样的对象进行优化 const
和volatile
的一个重要区别是不能使用合成的拷贝/移动构造函数及赋值运算符初始化volatile
对象或者从volatile
对象赋值
链接指示:extern “C”
C++
使用链接指示(linkage directive)
指出任意非C++
函数所用的语言- 要想把
C++
代码和其他语言(包括C
语言)编写的代码放在一起使用,要求我们必须有权访问该语言的编译器,并且这个编译器与当前的C++
编译器是兼容的 C++
从C语言继承的标准库函数可以定义为C
函数,但并非必须:决定使用C
还是C++
实现的C
标准库,是每个C++
实现的事情- 有时需要在C和C++中编译同一个源文件,为了实现这一目的,在编译C++版本的程序时预处理器定义
__cplusplus
1 |
|