在C语言中我们可以利用库函数malloc和free来分配和撤销内存空间。C++提供了较简便而功能较强的运算符new和delete来取代malloc和free函数。注意:new和delete是运算符,不是函数,因此执行效率高。
一、new和delete
对于申请和释放单个变量的内存空间,我们可以使用new和delete运算符来实现。
new运算符的使用一般格式为:
new 类型(初值);
delete运算符的使用格式为:
delete 指针变量
(一)基本类型
下面是用new和delete对基本类型动态分配和撤销内存的例子:
//new 和 delete
int main()
{
//开辟空间
int* ptr1 = new int;//开辟一个存放一个整形大小的空间并返回其首地址赋值给ptr1
int* ptr2 = new int(10);//开辟一个存放一个整形大小的空间并将其用10进行初始化
cout << "*ptr1 = " << *ptr1 << endl;
cout << "*ptr2 = " << *ptr2 << endl;
//释放空间
delete ptr1;
delete ptr2;
return 0;
}
运行结果:
new和delete对基本类型的动态分配内存和释放内存和C语言的malloc和free差不多,但是比C语言下函数的好用和方便,因为C语言在返回指向内存的指针时,需要我们对其返回值进行检查,如果为NULL则代表动态内存分配失败,需要进行特殊处理,但我们的C++中的new运算符进行动态分配内存则不需要考虑这个问题。我们在使用new时,其实调用了全局函数operate new,而operate new里封装有malloc,所以C++动态分配内存的底层还是使用malloc来分配空间,operate new里已经对分配内存失败时做出了处理,所以不需要像C语言那样手动处理内存分配失败的情况。
(二)自定义类型
上面对于基本类型使用new和delete动态分配和释放内存和C语言的使用malloc和free没多大区别,但是对于自定义类型,new在对对象动态分配内存时会调用其构造函数,而使用malloc只是开辟一段内存空间,但并不会为开辟的对象调用其构造函数进行初始化。而相应的,delete会先调用对象的析构函数,进行对资源的释放,再调用全局函数operate delete来释放存储类的对象的空间。
//new自定义类型
class A
{
private:
int _a;
public:
A(int a = 0,int b = 0):_a(a)
{
cout << "a = " << a << " b = " << b << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
//开辟空间
A* pa1 = new A;//对自定义类型A动态分配内存,并且调用其构造函数
A* pa2 = new A(1);//调用构造函数时传入单个参数进行初始化
//如果调用构造函数需要传入多个参数,则使用{参数列表}
A* pa3 = new A{ 1,2 };
A* pa4 = new A{ 3,4 };
//释放动态分配的自定义类型的空间
delete pa1;
delete pa2;
delete pa3;
delete pa4;
return 0;
}
运行结果:
我们可以看到,如果动态分配内存的变量为自定义类型,我们可以传入对应的参数来调用构造函数来进行初始化,这个时候new的用法就变成了:
new 自定义类型{初始化列表...}
二、new T[]和delete[]
对于申请和释放数组的内存空间,我们需要使用new T[]和delete[]运算符来实现。
申请数组空间的方法为:
new 类型名[数组的大小]{初始化列表...}
释放数组空间的方法:
delete[] 指针变量
(一)基本类型
//遍历数组
void Print(int* arr, int size)
{
for (int i = 0; i < size; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
//开辟数组空间
int main()
{
//为数组开辟空间
int* parr1 = new int[10];//申请元素个数为10的数组的内存空间
int* parr2 = new int[10] {1,2,3,4,5};//在申请数组空间后并对其初始化,
//若未完全初始化,则剩下的编译器将会用0进行初始化
//遍历数组
Print(parr1, 10);
Print(parr2, 10);
//释放空间
delete[] parr1;
delete[] parr2;
return 0;
}
运行结果:
(二)自定义类型
对于自定义类型的数组:
class A
{
private:
int _a;
public:
A(int a = 0,int b = 0):_a(a)
{
cout << "a = " << a << " b = " << b << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
//开辟自定义类型的数组
int main()
{
//申请元素个数为10的自定义类型的数组
A* parr1 = new A[10]{ {1,2},{3,4} };//用参数对其前两个对象进行初始化
//释放自定义类型
delete[] parr1;
return 0;
}
运行结果:
可以看到,我们调用delete[]时,对其每个自定义类型的对象调用了一次析构函数进行资源的释放。
三、定位new
在用malloc对对象进行内存申请时,只能为对象申请空间,但不能随之调用其构造函数对对象进行初始化,构造函数不能够被对象显式调用,只能在创建对象的时候调用其构造函数,但是定位new可以实现malloc对自定义类型申请原始的内存空间后用定位new手动调用其对象的构造函数来初始化成一个对象,相当于实现运算符new的效果。因为对象的析构函数可以被手动调用,所以在调用了对象的析构函数后再用free来释放其对象的内存,这样可以达到运算符delete的效果。
定位new的一般使用方法:
new(指针变量)类型名{参数列表...}
下面时使用定位new调用对象的构造函数的实例:
class A
{
private:
int _a;
public:
A(int a = 0,int b = 0):_a(a)
{
cout << "a = " << a << " b = " << b << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
//定位new手动调用构造函数
int main()
{
//用malloc开辟一段自定义类型A的大小的内存空间
//此时对象指针指向的只是一段内存,并不能称为对象,需要显式调用其构造函数
A* parr1 = (A*)malloc(sizeof(A));
//定位new new(指针变量)类型名{参数列表...}
new(parr1)A(1,2);
//模拟delete的功能,用free释放A的对象
parr1->~A();//显式调用A的析构函数
free(parr1);
return 0;
}
运行结果:
四、 关于扩容
在C++中,并没有C语言的realloc可以对已开辟的内存空间进行扩容的运算符。但是我们如果真的需要扩容,最简单的方法就是手动扩容:先开辟一段要扩容的大小的新的内容空间,然后用memcpy函数将其旧的内存空间里的数据拷贝到这个新开辟的内存空间中,然后再释放掉旧的内存空间。