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};

也可以有值初始化:

  1. 对于定义了自己的构造函数的类类型,值初始化和默认初始化一样,都会调用默认构造函数:string *ps1 = new string; //默认初始化为空string string *ps2 = new string(); //值初始化为空string
  2. 内置类型就比较适合值初始化了,因为它没有默认构造函数,而值初始化有良好的定义:int *pi1 = new int; //默认初始化:*p1未定义 int *pi2 = new int(); //值初始化为0,pi2指向的内存值为0
  3. 依赖编译器合成默认构造函数的内置类型成员:
    1. 类内没有初始值的话,情况和2一样
    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指针指向的内存已经被释放了,它是个空悬指针啊!

怎么样,自己搞很麻烦很容易出错吧。。。

全部评论

相关推荐

头像
11-06 10:58
已编辑
门头沟学院 嵌入式工程师
双非25想找富婆不想打工:哦,这该死的伦敦腔,我敢打赌,你简直是个天才,如果我有offer的话,我一定用offer狠狠的打在你的脸上
点赞 评论 收藏
分享
诨号无敌鸭:恭喜佬,但是有一个小问题:谁问你了?我的意思是,谁在意?我告诉你,根本没人问你,在我们之中0人问了你,我把所有问你的人都请来 party 了,到场人数是0个人,誰问你了?WHO ASKED?谁问汝矣?誰があなたに聞きましたか?누가 물어봤어?我爬上了珠穆朗玛峰也没找到谁问你了,我刚刚潜入了世界上最大的射电望远镜也没开到那个问你的人的盒,在找到谁问你之前我连癌症的解药都发明了出来,我开了最大距离渲染也没找到谁问你了我活在这个被辐射蹂躏了多年的破碎世界的坟墓里目睹全球核战争把人类文明毁灭也没见到谁问你了
点赞 评论 收藏
分享
4 收藏 评论
分享
牛客网
牛客企业服务