C++:33---动态内存管理new、delete

一、new关键字

  • new用来在内存中分配一块内存,new分配的对象是没有名称的,而是返回一个指向该对象的指针
int *p1=new int(1);  //pi指向一个动态分配的、初始化值为1的无名对象

int *p2(new int(1)); //同上

二、new的值初始化规则

通用规则

  • 如果类型名后无括号:内置类型或组合类型的对象的值是未定义的,而类类型对象将用默认构造函数进行初始化
  • 如果类型名后有括号:则内置类型/组合类型/类类型都进行默认的初始化
//如果类型名后无括号
string *ps = new string;  //ps为初始化为空的string
cout << *ps << endl; //值为空

int *pi = new int;  //pi指向一个未初始化的int
cout <<*pi << endl; //值为乱值
//如果类型名后有括号
string *ps = new string();  //ps为初始化为空的string
cout << *ps << endl; //值为空

int *pi = new int();  //pi指向一个默认初始化的int
cout <<*pi << endl; //值为0

C++11标准下的初始化规则

  • 允许使用花括号来指定初始化列表
  • 注意:花括号{}本质上是用来当一个列表的
vector<int> *vec = new vector<int> { 1, 2, 3, 4, 5 };
int *arr = new int[3] {1, 2, 3};
int *p = new int { 1 };

auto初始化

  • 如果使用圆括号初始化器,就可以使用auto来推断我们想要分配的对象类型
auto p = new auto(10);       //p是一个int*指针
auto p2 = new auto("Hello"); //p2是一个string*指针
cout <<*p <<*p2<< endl;      //输出10和Hello

auto* p = new auto(10);      //p同上
auto* p2 = new auto("Hello");//p2同上
cout <<*p <<*p2<< endl;      //输出10和Hello
  • 但是这种规则不适用于花括号
auto p2 = new auto { 1 };     //错误
auto p3 = new auto {1, 2, 3}; //错误

const初始化

  • 因为const对象为常量,初始化之后就不可以修改值了
  • 类类型初始化时可以不给出值,此时使用默认的构造函数值
  • 其它类型必须显示地给出初始化值(注意:编译器本质允许不给出值,但是定义之后就不能改变值了)
const int *p = new const int;        //*p为乱值,且*p不允许修改了
const string *p2 = new const string; //使用string默认构造,*p2为空的且不允许修改

//建议做法:
const int *p = new const int(10);    //给出初始化值

三、bad_alloc异常处理

  • bad_alloc异常出现的情景:如果一个程序可用的内存消耗完,那么new表达式就会失败。默认情况下,此时会抛出bad_alloc异常
  • bad_alloc异常的处理:如果不处理此异常那么程序就会中断。但是我们可以使用定位new的nothrow关键字来处理此异常
  • nothrow关键字:如果在new后面加一个圆括号并且加上“nothrow”,那么捕获到bad_alloc异常时,new返回空指针,而不抛出异常。我们称这种形式的new为“定位new”
  • bad_alloc和nothrow都定义在头文件new中
//如果此时内存不足:

int *p = new int;           //抛出std::bad_alloc异常,程序终止
int *p2 = new(nothrow) int; //new返回一个空指针,p2为空

四、delete关键字

  • 用来释放一块动态申请的内存,解除指针与该指针所指向的内存之间的关系
  • 如果new的动态内存没有被释放(销毁),那么该动态内存就一直存在,会造成浪费

五、delete的使用规则

规则如下

  • 不能用来释放一块静态内存(栈区)
  • 用来释放动态申请的内存(new申请的堆区)
  • 允许释放一个空指针,不会出错
  • 释放一块已经释放的内存是错误的
  • 虽然const对象的值不能被改变,但是可以使用一个const动态对象
int i=10;
int *p=&i;
int *p2=nullptr;
double *pd=new double(3.14);
double *pd2=pd;

delete i;  //错误,i不是一个指针
delete p;  //错误;p是一个指针,指向的堆区的内存,而不是动态申请的
delete p2  //正确,释放空指针是允许的
delete pd; //正确,释放一个动态内存
delete pd2 //错误,pd指向的内存已经被释放了,不能再重复释放
const int *p=new const int(30);
delete p; //正确

六、内存泄漏问题

  • 当我们使用new申请一块动态内存后,如果没有delete掉内存,那么就会造成内存泄漏

案例:

  • 定义一个factory函数,返回一个指向与Foo类型的动态内存指针
Foo* factory(T arg)
{
    ...
    return new Foo(arg);
}
  • 错误使用:下面的函数,调用了factory函数申请了一块动态内存,但是函数结束之后,没有释放p所指向的内存,于是就造成了内存的浪费
void use_factory(T arg)
{
    Foo *p=factory(arg);
}
  • 正确使用:下面对use_factory函数进行了改造,在函数的最后delete掉了p所指向的动态内存,这样就不会导致内存的泄漏了
void use_factory(Foo arg)
{
    Foo *p=factoyr(arg);
    ...
    delete p;
}

七、delete指针之后的置空问题

  • 规则:当我们释放一个指针之后,该指针指向的是一个不确定的内存空间。因此,当释放指针之后,建议将指针值为空,来指示该指针不指向任何对象了
int *p=new int(30);  //申请
......
delete p;  //释放
p=nullptr; //置位空

八、多个指针同指一块内存的使用

特点

  • ①多个指针指向同一内存时,释放其中一个指针,其他指针均变为无效
  • ②将一个指针值为空,只与该指针有关,与其他指针无关
int *p(new int(42));

auto q=p;   //q与p指向同一块内存空间
delete p;   //释放p之后,q指针也失效
p=nullptr;  //将p置为空,但是q没有(两个指针没有任何关系)

九、shared_ptr与new的使用

使用规则

  • ①我们可以使用将shared_ptr类对象指向一个new所申请的动态内存
  • ②new申请的动态内存的使用、释放等规则仍然符合shared_ptr类的使用规则

使用语法

  • 因为智能指针的构造函数是explicit的因此:我们不能将一个内置指针隐式地转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针
shared_ptr<int> p=new int(1024);   //错误

shared_ptr<int> p2(new int(1024)); //正确:使用直接初始化
  • 动态内存作为返回值时的使用手法:限于上面的使用语法:一个返回shared_ptr的函数不能在其返回语句中隐式转换为一个普通指针 
shared_ptr<int> clone(int p)
{
    return new int(p); //错误
}

shared_ptr<int> clone(int p)
{
    return shared_ptr<int>(new int(p)); //正确
}

十、new传参时与shared_ptr的关系

  • 当一个函数的参数是shared_ptr类时,有以下规则:
    • 函数的调用是传值调用
    • 调用函数时,该shared_ptr类所指向的对象引用计数加1
    • 但是函数调用完成之后,shared_ptr类自动释放,对象的引用计数又减1
void process(shared_ptr<int> ptr){ ... }

shared_ptr<int> p(new int(42)); //初始化一个智能指针对象p
process(p);  //p所指的对象引用计数加1
//process函数调用之后,p所指的引用计数减1
int i=*p; //正确

函数参数使用时与new的关系

  • 因为shared_ptr类会在生存周期结束之后,将引用计数减1,当引用计数为0时,会释放内存空间
  •  下面是一个特殊的应用场景,需要注意
void process(shared_ptr<int> ptr){ ... }

int *x(new int(1024));
process(x);                  //错误,不能将int*转换为一个shared_ptr<int>
process(shared_ptr<int>(x)); //合法的,但是process函数返回之后内存会被释放
int j = *x;                  //错误,x所指的内存已经被释放了

 十一、异常处理

  • 当程序发生异常时,我们可以捕获异常来将资源被正确的释放
  • 但是如果没有对异常进行处理,则有以下规则:
    • shared_ptr的异常处理:如果程序发生异常,并且过早的结束了,那么智能指针也能确保在内存不再需要时将其释放
    • new的异常处理:如果释放内存在异常终止之后,那么就造成内存浪费
voif func()
{
    shared_ptr<int> sp(new int(42));
    ...//此时抛出异常,未捕获,函数终止
} //shared_ptr仍然会自动释放内存
voif func()
{
    int *ip=new int(42);
    ...//此时抛出异常,未捕获
    delete ip; //在退出之前释放内存,此语句没有执行到,导致内存浪费
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董哥的黑板报

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值