C++语言用称为new的运算符将“自由空间”(又称为“堆”)变为可用状态。例如:
double *p = new double[4]; //在自由空间中分配4个double
这段代码要求C++运行时系统在自由空间中分配四个double,并将指向第一个double的指针返回。
我们使用运算符new来请求系统从自由空间中分配内存:
- 运算符new返回一个指向分配的内存的指针。
- 指针的值是分配的内存的首字节地址。
- 一个指针指向一个特定类型的对象。
- 一个指针并不知道它指向多少个元素。
一如以往,我们必须要保证在使用对象之前为它赋一个值;也就是说,我们希望确保指针被初始化,并且指向的对象也被初始化。
double* p0; //未初始化,可能产生问题
double* p1 = new double; //得到(分配)一个未初始化的double
double* p2 = new double{5.5}; //得到一个初始化为5.5的double
double* p3 = new double[5]; //得到(分配)5个未初始化的double
当我们定义自己的类型时,可以更好的控制初始化。如果类型X有一个默认构造函数,我们会得到:
X* px1 = new X; //一个默认初始化的X
X* px2 = new X[17]; //17个默认初始化的X
如果类型Y有一个构造函数,但不是默认构造函数,我们需要显示初始化:
Y* py1 = new Y; //错误:无默认构造函数
Y* py2 = new Y{13}; //正确:初始化为Y{13}
Y* py3 = new Y[13]; //错误:无默认构造函数
Y* py4 = new Y[17]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
为new指定很长的初始化列表可能不太实用,而少量元素通常实用这种类型。
如果你没有其他指针来初始化一个指针,那么使用空指针nullptr:
double *p0 = nullptr; //空指针
当0值被赋值给一个指针时,它被称为空指针。通常我们通过检测指针是否为nullptr来检查一个指针是否有效:
if(p0 != nullptr) //认为p0有效
实际上不必明确提及nullptr,因为if语句会检测它的条件是否是nullptr:
if(p0) //认为p0有效;等价于p0!=nullptr
名字nullptr表示空指针是C++11的新特性。旧代码中通常使用0或NULL代替nullptr。
delete有两种形式:
- delete p 释放new分配给单个对象的内存
- delete[] p 释放new分配给对象数组的内存
删除空指针是无害的!
一个对象离开作用域时会隐式调用析构函数。构造函数确保一个对象被正确创建并初始化。与之相反,析构函数确保一个对象销毁前被正确清理。
作为一个经验法则:如果你又一个类带有virtual函数,则它也需要一个virtual析构函数,原因是:
- 如果一个类有virtual函数,它很有可能作为一个基类使用;
- 且如果它是一个基类,它的派生类很可能使用new来分配;
- 且如果派生类对象使用new来分配,并通过基类指针来操作;
- 那么它很可能也是通过基类指针来进行delete操作的。
类型void*的含义是“指向编译器不知道类型的内存空间”。当我们想在两段代码之间传输一个地址,它们确实不知道对方的类型时,就可以使用void*。指向任何对象类型的指针都可以赋予void*,例如:
void* pv1 = new int; //正确:int*转换为void*
void* pv2 = new double[10]; //正确:double*转换为void*
由于编译器不知道void*是什么,我们必须告诉它:
void f(void* pv)
{
void* pv2 = pv; //正确拷贝
double* pd = pv; //错误,不能将void*转换为double*
*pv = 7; //错误,不能解引用一个void*
PV[2] = 9; //错误:不能对void*进行下标操作
int* pi = static_cast<int*>(pv); //正确,显式类型转换
//...
}
static_cast可以用于在两种相关指针类型之间进行强制转换,例如void*与double*。这样的操作称为显式类型转换。
C++提供两个比static_cast更具潜在危险的转换操作:
- reinterpret_cast可以在两个不相关的类型之间转换,例如int和double。
- const_cast可以“去掉const”。
我们可以将一个引用看做一个自动解引用的不可变指针或一个对象的替代名字。指针和引用有以下几个方面不同:
- 为一个指针赋值会改变指针自身的值(不是指针指向的值)。
- 为了得到一个指针,你通常需要使用new或&。
- 为了访问一个指针指向的对象,你可以使用*或[]。
- 为一个引用赋值会改变引用指向的值(不是引用自身的值)。
- 在初始化一个引用之后,你不能让引用指向其他对象。
- 为引用赋值执行深拷贝(赋值给引用的对象);为指针赋值不是这样(赋值给指针自身)。
- 注意避免空指针。