使用new动态分配和初始化对象
在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针:
int *pi = new int; //pi指向一个动态的、未初始化的无名对象
此new表达式在自由空间构造一个int类型的对象,并返回指向该对象的指针
默认情况下,动态分配的对象是默认初始化的,这意味着内置类型或组合类型的对象的值是未定义的,而类类型对象将用默认构造函数进行初始化:
string *ps = new string; //初始化为空string
int *pi = new int; //pi指向一个未初始化的int
我们可以使用传统的构造方式,用圆括号,或者用列表初始化
int *pi = new int(1024); //p指向的对象的值是1024
vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9}; //vector有10个元素,值依次为0到9
也可以对动态分配的对象进行值初始化,只需在类型名后跟一对空括号即可
string *ps1 = new string; //默认初始化为空string
string *ps = new string(); //值初始化为空string
int *pi1 = new int; //默认初始化,*pi1的值是未定义的
int *pi2 = new int(); //值初始化为0,*pi2的值为0
对于定义了自己的构造函数的类类型(例如string)来说,要求值初始化是没有意义的;不管采用什么形式,对象都会通过默认构造函数来初始化。
对于内置类型,值初始化的内置类型对象有着良好定义的值,而默认初始化的对象的值则是未定义的。对于类中那些依赖于编译器合成的默认构造函数的内置类型成员,如果它们未在类内被初始化,那么它们的值也是未定义的。
动态分配的const对象
用new分配const对象是合法的:
//分配并初始化一个const int
const int *pci = new const int(1024);
//分配并默认初始化一个const的空string
const string *pcs = new const string;
类似其他任何const对象,一个动态分配的const对象必须进行初始化。对于一个定义了一个默认构造函数的类类型,其const动态对象可以隐式初始化,而其他类型的对象就必须显式初始化。由于分配的对象是const的,new返回的指针也是一个指向const的指针
内存耗尽
如果new不能分配所需要的内存空间,它会抛出一个类型为bad_alloc的异常,我们可以改变使用new的方式来阻止它抛出异常
bad_alloc和nothrow都定义在头文件new中
int *p1 = new int; //如果分配失败,new抛出std::bad_alloc
int *p2 = new (nothrow) int; //如果分配失败,new返回一个空指针
释放动态内存
delete表达式接受一个指针,指向我们想要释放的对象:
传递给delete的指针必须指向动态分配的内存,或者是一个空指针。释放一块并非new分配的内存,或者将相同的指针值释放多次,其值都是未定义的。
虽然一个const对象的值不能被改变,但它本身是可以被销毁的
动态对象的生存期直到被释放时为止
delete之后重置指针值
当delete一个指针后,指针值就变成无效了,但指针仍然保存着动态内存(已经释放)的地址。在delete之后,指针就变成了空悬指针,指向一块曾经保存数据但现在已经无效的内存的指针。
避免空悬指针的方法:在指针即要离开其作用域之前释放掉它所关联的内存。这样,在指针关联的内存被释放掉之后,就没有机会继续使用指针了。如果我们需要保留指针,可以在delete之后将nullptr赋予指针。
这只是提供了有限的保护
动态内存的一个基本问题是可能有多个指针指向相同的内存。在delete内存之后重置指针的方法只对这个指针有效,对其他任何仍指向(已释放)内存的指针是没有用的
int *p = new int(42);
auto q = p; //q和p指向相同的内存
delete p; //q和p均变为无效
p = nullptr; //指出P不再绑定到任何任何对象
但是重置p对于q没有任何作用,在我们释放p所指向的内存时,q也变为无效了。在实际系统中,查找相同内存的所有指针是异常困难的
编写函数,返回一个动态分配的int的vector,将此vector传递给另一个函数,这个函数读取标准输入,将读入的值保存在vector元素中。再将vector传递给另一个函数,打印读入的值,记得在恰当的时刻delete vector。
#include <iostream>
#include <vector>
using namespace std;
vector<int> *new_vector() {
return new (nothrow) vector<int>;
}
void read_int(vector<int> *pv) {
int v;
while (cin >> v) {
pv->push_back(v);
}
cin.clear();
}
void print_int(vector<int> *pv) {
for (const auto& v : *pv) {
cout << v << " ";
}
cout << endl;
}
int main() {
vector<int>* pv = new_vector();
if (!pv) {
cout << "内存不足" << endl;
return -1;
}
read_int(pv);
print_int(pv);
delete pv;
pv = nullptr;
return 0;
}
用shared_ptr而不是内置指针重写上面的函数
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
shared_ptr<vector<int>> new_vector() {
return make_shared<vector<int>>();
}
void read_int(shared_ptr<vector<int>> pv) {
int v;
while (cin >> v) {
pv->push_back(v);
}
cin.clear();
}
void print_int(shared_ptr<vector<int>> pv) {
for (const auto& v : *pv) {
cout << v << " ";
}
cout << endl;
}
int main() {
shared_ptr<vector<int>> pv = new_vector();
if (!pv) {
cout << "内存不足" << endl;
return -1;
}
read_int(pv);
print_int(pv);
return 0;
}
解释下面代码执行的结果并说明存在的问题
int *q = new int(42), *r = new int(100);
r = q;
1、存在内存泄漏的问题,将q地址赋值给r,会导致q和r同时指向42的内存地址,而r中原来保存的地址将再无指针管理
2、容易造成指针空悬的问题。因为r和q指向同一个动态对象,如果程序编写不当,释放了其中一个指针,如果再使用另一个指针,就会造成无法预测的结果,因为该指针所指向的原内存已经被释放了
auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);
r2 = q2;
用shared_ptr很好的解决了上面产生的两个问题。首先分配了两个共享的对象,分别由共享指针p2和q2指向,因此它们的引用计数均为1。接下来,将q2赋予r2,赋值操作会将q2指向对象的地址赋予r2,并将r2原来指向的对象的引用计数减1,将q2指向的对象的引用计数加1。这样,前者的引用计数为0,其占有的内存空间被释放,不会造成内存泄漏。而后者的引用计数变为2,也不会因为r2和q2之一的销毁而释放它的内存空间,因此也不会造成空悬指针的问题。