一、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; //在退出之前释放内存,此语句没有执行到,导致内存浪费
}