目录
2. operator new与operator delete函数
2.3 operator new与operator delete的类专属重载
在C语言中,动态内存中我们用 malloc/calloc/realloc 等来在堆上开辟空间,free 来释放,在C++中,不仅支持C语言的方式,还确定了自己的方式。本篇我们来记录C++是如何进行内存管理的。
1. new 和delete
在堆上开辟空间,C语言中我们要用到malloc,并且使用方式较为繁琐:
( int* )malloc(sizeof( int ) * 4)
且calloc/realloc 的方式也都相同,详细点击这里:动态内存管理分析理解
而在C++中使用 new 可以简化使用方式:
new 一个对象
new + 类型
int* p1 = new int;
初始化
new + 类型后面+(值)
int* p1 = new int(5);
new 多个对象
new + 类型 [个数]
int* p2 = new int[10];
初始化
new + 类型 [个数] {初始化的值}
int* p3 = new int[10]{1, 2, 3, 4, 5, 6};
delete 释放
要注意格式匹配
像单个对象,直接delete 即可
delete p1;
如果是多个对象,delete + []
delete[] p2; delete[] p3;
C++内存开辟释放和C中开辟释放有何区别?
对于内置类型来说:new 和 malloc 并没有什么区别,只是用法不同;
对于自定义类型来说,new更加方便,会自动调用其构造函数,相当于开辟空间加初始化:
而利用初始化列表,创建构造函数,使用new ,可以大大简化这些步骤:
delete和free 对内置类型的处理方式也没什么区别
但是对于自定义类型而言
delete 对于自定义类型是会调用它自己的析构函数:
2. operator new与operator delete函数
这两个函数不是new 和 delete 的重载,只是名字的区别
new和delete是用户进行动态内存申请和释放的操作符
operator new 和operator delete是系统提供的全局函数
new在底层调用operator new全局函数来申请空间
delete在底层通过operator delete全局函数来释放空间
2.1 operator new
实际上operator new函数是malloc函数封装后的成果
此函数不会调用构造函数
和malloc相同点
都是函数、都会开辟空间
使用方法和malloc相同
![]()
和malloc不同点
通过malloc来申请空间,当malloc申请空间成功时直接返回,
申请空间失败,不会返回空,会抛异常;
这一点就跟new相同了
new底层原理开辟空间,失败抛异常 + 调用构造函数
这两个功能合并到一块就构成了new
而开辟空间失败抛异常其实调用的就是封装malloc后的operator new函数。
operator new + 调用构造函数 = new
2.2 operator delete
实际上operator delete函数是free函数封装后的成果
此函数不会调用析构函数
为了跟operator new匹配对应
operator delete + 调用析构函数 = delete
2.3 operator new与operator delete的类专属重载
在项目中往往存在一直需要开辟空间的时候,就比如在链表中插入数据,每次new一个节点,都会在堆上申请,但是如果数据量较大,那么效率是会变低的,来看一个测试:
struct Listnode { Listnode* _next; Listnode* _prev; int _data; Listnode(int data = 0) :_next(nullptr) , _prev(nullptr) , _data(data) { //记录节点数 cout << "Listnode" << endl; } }; class List { public: List() { _head = new Listnode; _head->_next = _head; _head->_prev = _head; } void Push(int val) { //每一次插入数据,都需要new一个节点 Listnode* newnode = new Listnode; Listnode* tail = _head->_prev; //链接 tail->_next = newnode; newnode->_prev = tail; newnode->_next = _head; _head->_prev = newnode; } ~List() { Listnode* cur = _head->_next; while (cur!=_head) { Listnode* next = cur->_next; delete cur; cur = next; } delete _head; _head = nullptr; } private: Listnode* _head; }; int main() { List l; int n = 1000; for (int i = 0;i<n;i++) { l.Push(i); } }
为了提高速度,创建operator new与operator delete的类专属重载;
在构造节点时,会自动调用operator new的重载,清空时自动调用与operator delete的重载;
实现这两个重载函数,用到的是内存池,使用内存池申请和释放内存,提高效率;
在之后会了解到,这里先主要是以重载为主,稍作了解:
struct Listnode { Listnode* _next; Listnode* _prev; int _data; Listnode(int data = 0) :_next(nullptr) , _prev(nullptr) , _data(data) { //记录节点数 cout << "Listnode" << endl; } void* operator new(size_t n) { void* p = nullptr; p = allocator<Listnode>().allocate(1); cout << "memory pool allocate" << endl; return p; } void operator delete(void* p) { allocator<Listnode>().deallocate((Listnode*)p, 1); cout << "memory pool deallocate" << endl; } };
3. 定位new表达式
operator new 函数是调用 new 时的一部分,那我们单独调用operator new 进行空间开辟时,又不好初始化,因为构造函数不能显示调用。
使用格式
new (指针) + 类型;
new (指针) + 类型(初始化列表);
![]()
定位new什么时候用?
那么我们直接调用 new 不香吗,非得用operator new + 定位new?
定位new表达式在实际中一般是配合内存池使用:
因为内存池分配出的内存没有初始化,
所以如果是自定义类型的对象,
需要使用new的定义表达式进行显示调构造函数进行初始化