学C++的小伙伴之前一定有学过C吧,C语言申请空间都是在堆区上开辟的,而申请的方式有3种;malloc
、calloc
和realloc
。
malloc
:函数原型void* malloc(size_t size) 参数size为要分配的字节数,返回值是void*,通常要强转为我们需要申请空间的类型,开辟成功回返回空间首地址,失败会返回NULL,但是申请成功后并不进行初始化,每个数据都是随机值。
calloc
:函数原型void* calloc(size_t number, size_t size); 参数number为要申请的个数;size为每一个数据的大小,返回值是void*,通常要强转为我们需要申请空间的类型,开辟成功回返回空间首地址,失败会返回NULL,但是申请成功会对空间进行初始化,且初始为0。
realloc
:函数原型 void*realloc(void * mem_address, unsigned int newsize); 参数address为要扩展调整的空间首地址,参数newsize为调整为多少字节的空间,返回值是void*,通常要强转为我们需要申请空间的类型,开辟成功回返回空间首地址,失败会返回NULL,但是申请成功后并不进行初始化,每个数据都是随机值。注意的是,之前申请过的空间再用realloc来扩展的话不用释放,只要释放扩展后的空间即可
C++是一门面向对象的高级语言,在我们编写代码中,常常离不开对对象的创建和清理对象资源。而兼容过来的malloc和free并不能很好的满足我们的需求,从而C++将malloc和free封装起来并起了新的名字new和delete,这两个关键字的作用不仅比malloc和free的功能强大,用起来也非常的方便,下面我们来看看new和delete的用法
new和delete都是运算符,不是库函数,不需要单独添加头文件
格式:
new
1、类型指针 指针变量名 = new 类型
2、类型指针 指针变量名 = new 类型(初始值)
3、类型指针 指针变量名 = new 类型[元素个数]
delete
1、delete 指针变量名
2、delete[] 指针变量名
//申请空间
int* ptr = new int;
//申请空间并初始化
int* ptr2 = new int(1);
//申请连续的空间,空间大小为4*10=40
int* arr = new int[10];//c++98不允许连续空间初始化
//释放单个空间
delete ptr;
delete ptr2;
//释放连续的多个空间
delete[] arr;
从上面来看,好像和C当中的malloc差别不大嘛,来看看自定义类型,我们就可以看出他们的主要区别了。
malloc和free不会对我们自定义类型完成初始化和资源的清理,而new可以完成对象的初始化和delete可以完成对象的资源清理。
class A
{
public:
A(int a = 10)
:_a(a)
{
cout << "A() " << _a << endl;
}
~A()
{
cout << "~A() " << endl;
}
private:
int _a;
};
void test()
{
A* pa1 = new A;
delete pa1;
}
总结下来就是,new去申请对象会先申请对象的空间并调用对象的构造函数完成对象的初始化;delete会先去完成对象的资源清理,再将对象所占的空间释放掉。
但是要注意,如果没有默认构造函数,我们必须在new一个对象时后面要加小括号给予初始值进行初始化。没有默认构造函数,我们也不能申请连续的多个空间。
class A
{
public:
A(int a)
:_a(a)
{
cout << "A() " << _a << endl;
}
private:
int _a;
};
void test()
{
//error,无默认构造函数
A* pa1 = new A;
delete pa1;
//ok
A* pa2 = new A(10);
delete pa2;
//error,无默认构造函数
A* arr = new A[10];
delete[] arr;
}
如果类中有多个成员变量,也只要把我们想要赋给对象的值放在小括号里,用逗号隔开。
class A
{
public:
A(int a = 1, int b = 2, int c = 3)
:_a(a)
,_b(b)
,_c(c)
{
}
private:
int _a;
int _b;
int _c;
};
void test()
{
//使用传入的值
A* pa1 = new A(10, 20, 30);
delete pa1;
//使用缺省参数
A* pa2 = new A;
delete pa2;
//全部使用缺省参数
A* arr = new A[10];
delete[] arr;
}
这两个是系统提供的全局函数,也是对malloc和free进行封装过的函数。而new和delete是对这两个全局函数进行的封装。operator new和malloc的最大区别就是当申请错误时,处理的方式不一样。malloc申请失败时会返回NULL,operator new申请失败时会抛异常
operator new的使用方式和malloc非常相似。
void test()
{
int* ptr = (int*)operator new(sizeof(int) * 2);
operator delete(ptr);
//申请失败抛异常
int* ptr1 = (int*)operator new(0x7fffffff);
operator delete(ptr1);
}
上面说过,new和delete也是operator new和operator delete的封装,其实我们调用new和delete,系统也会调用到operator new和operator delete。
//内置类型
//new-> operator new ->malloc
int* ptr = new int;
//delete-> operator delete -> free
delete ptr;
//自定义类型
//new-> operator new ->malloc ->构造函数
A* pa = new A;
//析构函数-> delete-> operator delete -> free
delete pa;
而在工作中,我们会有多个类,也会有多个对象,平凡的去申请空间会增加系统的开销,C++引入了内存池去减小系统开销,且可以提高效率。内存池的工作原理是先向系统一次性申请比较大的空间,当我们每次去申请空间时就直接使用内存池里的空间,而省略了申请和释放的两个开销动作,也减少了系统内存碎片,从而提高了开发效率。
重载代码:
class ListNode
{
public:
void* operator new(size_t n)
{
//采用内存池的方式
cout << "operator new" << endl;
allocator<ListNode> alloc; //空间配置器
return alloc.allocate(1);
}
void operator delete(void* ptr)
{
cout << "operator delete" << endl;
allocator<ListNode> alloc;//空间配置器
alloc.deallocate((ListNode*)ptr, 1);
}
private:
int _data = 0;
ListNode* _next = nullptr;
};
void test()
{
ListNode* node = new ListNode;
delete node;
}
三者的关系:
operator new = malloc + 失败抛异常
new = operator new + 调用构造函数
new = malloc + 失败抛异常 + 调用构造函数
operator delete = free
delete = operator + 调用析构函数
delete = free + 调用析构函数
定位new表达式的功能是对已存在的空间进行初始化
使用格式:new(指针名) 类型(参数值(可选))
class A
{
public:
A(int a)
:_a(a)
{
cout << "A(int)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
void test()
{
A* pa = (A*)malloc(sizeof(A));
//对已有空间进行初始化
new(pa) A(10);
//显示调用析构
pa->~A();
free(pa);
}