C++ Primer第十二章②
C++ Primer
更多内容戳这里
动态内存
直接管理内存
我们已经学习了如何用智能指针来管理动态内存,相对来说还是比较简单的,自己可以忽略很多细节。现在我们来一种完全自力更生的,只借助C++提供的两个运算符来直接管理内存:
- new 分配内存
- delete 释放new分配的内存 这一章越到后面,你越会觉得自己管理真是又烦又容易出错。。。
使用new动态分配和初始化对象
int a; //在栈内保存,内存变量名为a,值初始化为0
int *pi = new int; //在堆保存,而且用new分配的内存是无名的,所以只能用指针指向它,
//而且它是未初始化的,直接输出的应该是一个奇怪的值
string *ps = new string; //类类型对象会调用默认构造函数初始化,ps指向空string
当然我们可以显式初始化它们:
int *pi = new int(4); //注意和int pi = 4的区别哦
string *ps = new string(10, '9');
vector *pv = new vector{0, 1, 2, 3};
也可以有值初始化:
- 对于定义了自己的构造函数的类类型,值初始化和默认初始化一样,都会调用默认构造函数:
string *ps1 = new string; //默认初始化为空string string *ps2 = new string(); //值初始化为空string
- 内置类型就比较适合值初始化了,因为它没有默认构造函数,而值初始化有良好的定义:
int *pi1 = new int; //默认初始化:*p1未定义 int *pi2 = new int(); //值初始化为0,pi2指向的内存值为0
- 依赖编译器合成默认构造函数的内置类型成员:
- 类内没有初始值的话,情况和2一样
- 类内有初始值,就用这个初始值
神器auto在这种初始化方式下有所限制:
auto p1 = new class(T); //p指向一个与T类型相同的对象,该对象用T进行初始化
auto p2 = new class{a, b, c}; //这样不行,括号中只能有单个初始化器,
//因为你这样让auto无法判断p2是什么类型
动态分配的const对象
const int *pci = new const int(24);
const string *pcs = new const string; //和其他const对象一样,一个动态分配的const必须被初始化
内存耗尽
虽然现在不太可能,你可以用这个代码试试:
while(true)
{
int *p1 = new int(24); //我没试过。。。
}
在C++中,如果new不能分配所要求的内存空间,它会抛出一个类型为bad_alloc的异常,不过,我们可以阻止它抛出异常:
int *p1 = new int; // 如果失败就会抛出异常
int *p2 = new (nothrow) int; //分配失败只会返回一个空指针
释放动态内存
为了防止内存耗尽,我们在动态内存使用好后,通过delete来将动态内存归还给系统。 delete接受一个指针,指向我们要释放的对象:
int *p1 = new int(24);
delete p1; //销毁p1指向的对象,释放对应内存
指针值和delete
我们传递给delete的指针必须指向动态分配的内存(或者是空指针)。释放一块不是new分配的内存,或者把相同的指针释放多次,其行为都是未定义的:
int i, *pi1 = &i, *pi2 = nullptr;
double *pd1 = new double(33), *pd2 = pd1;
delete i; //错误
delete pi1; //未定义的行为
delete pd1; //正确
delete pd2; //未定义,因为pd2指向的内存已经被释放了
delete pi2; //正确
动态对象的生存期直到被释放为止
返回指向动态内存的指针(不是智能指针)的函数给调用者增加了一个任务-调用者必须自己释放内存:
//函数factory返回一个指针,指向动态分配的对象
Foo* factory(T arg)
{
return new Foo(arg);
}
void use_factory(T arg)
{
Foo *p = factory(arg); //使用p
}//p离开了作用域,但是它指向的内存没有被释放!
如何修正呢?我们可以这样:
void use_factory(T arg)
{
Foo *p = factory(arg); //使用p
delete p; //释放了
}
如果以后还要用这个指针,就可以甩锅给下一个调用者:
Foo* use_factory(T arg)
{
Foo *p = factory(arg); //使用p
return p; //释放了
}
delete之后重置指针值(真的好麻烦。。。)
我们delete指针后,指针值就是无效了,但是其实指针一般仍然保留着那个动态内存的地址(虽然这个内存已经被释放了)。在delete之后,这个指针就指向了一块曾经保存数据对象但现在已经无效的内存(可以把它看成一个痴情的指针,虽然指向的对象内存已经没了,它还是指着那个地址),这就是空悬指针。 一般来说,我们是没机会碰到空悬指针的问题的,因为这会已经离开了指针的作用域了;如果还没离开它的作用域,我们还想保留它(不知道为啥有这种奇怪的爱好),可以将它赋值为nullptr,这样就指明了它不指向任何对象。
你以为这样就好了吗,还会有问题。。。
抛一段可能有问题的代码:
int *p(new int(42)); //p指向动态内存
auto q = p; //q也指向那个内存
delete p; //那个内存被释放
p = nullptr; //指出了p是空指针,解决了空悬指针的问题
然而,这样还是会有问题,因为有人可能会去使用q指针啊,然而q指针指向的内存已经被释放了,它是个空悬指针啊!
怎么样,自己搞很麻烦很容易出错吧。。。