目录
new和delete
使用方法:
注意事项:
new申请不需要检查返回值
operator new和operator delete函数的讲解
c语言申请内存有哪些方法:
答:malloc calloc realloc三种
#include
void test()
{
int*p1 = (int*)malloc(sizeof(int));
free(p1);
int*p2 = (int*)calloc(4, sizeof(int));
int *p3 = (int*)realloc(p2, sizeof(int)* 10);
free(p3);
}
其中,malloc就是普通的动态内存申请
calloc相当于malloc加上memset把申请的空间全部初始化为0
realloc相当于内存扩容,分为异地扩容和原地扩容,当扩容的次数少,空间小时,会执行原地扩容,当扩容的次数多,空间大时,会执行异地扩容。
原地扩容和异地扩容的区别?
答:如上图代码所示,p2和p3指针指向同一块空间,而异地扩容则不然,异地扩容会先找一块新的空间,然后把原空间的内容拷贝到新空间位置,然后释放掉原空间,所以返回的就是p3.
c++是通过什么申请内存的呢?
答:c++是通过两个关键字(操作符)来申请和释放空间的。
new和delete
int main()
{
int *p1 = new int;
delete p1;
return 0;
}
相当于这里申请一个字节的空间,返回指向该空间的指针p1,然后delete表示释放空间。
注意:这里申请空间并不会对空间上的内容完成初始化:
例如:
我们申请的空间并没有进行初始化。
我们如何申请空间的同时并初始化呢?
答:我们可以这样操作
例如:
int main()
{
int *p1 = new int(0);
delete p1;
return 0;
}
我们在后面加上0表示申请空间并把空间初始化为0.
我们如何申请多个空间呢?
答:
int main()
{
int *p1 = new int[10];
delete[] p1;
return 0;
}
这里表示我们要申请十个整型空间,注意:我们在delete释放时,要和我们申请的空间进行一一对应。
假如我们要对申请的多个空间进行初始化呢?
答:我们可以这样写:
int main()
{
int *p1 = new int[10]{1, 2, 3, 4};
delete[] p1;
return 0;
}
我们发现,对于内置类型,这里的new和delete和c语言的动态内存申请函数本质上没什么区别,那为什么++要定义这两个关键字呢?
答:对于内置类型,c语言和c++的动态内存申请本质是一样的,但是对于自定义类型,结果就不同了
例如:
我们写一个简单的类:
class A
{
A(int a = 0)
:_a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
这个类中有两个成员函数,分别是构造函数和析构函数,函数的目的是当调用构造和析构函数,打印对应的提示并打印出this指针。
我们进行实验:
int main()
{
/*int *p1 = new int[10]{1, 2, 3, 4};
delete[] p1;
return 0;*/
A* p1 = new A;
}
我们动态内存申请一个类的空间,返回指向该空间的指针p1
我们进行编译:
这里表示我们的动态内存申请new调用了构造函数。
但是我们的malloc是不会调用构造函数的:
int main()
{
/*int *p1 = new int[10]{1, 2, 3, 4};
delete[] p1;
return 0;*/
/*A* p1 = new A;*/
A*p1 = (A*)malloc(sizeof(A));
}
我们进行编译:
所以new相较于malloc对于自定义类型来说,new会调用自定义类型的构造函数,而malloc不会。
new会调用自定义类型的构造函数,那么delete是不是也会调用析构函数?
答:会:
int main()
{
/*int *p1 = new int[10]{1, 2, 3, 4};
delete[] p1;
return 0;*/
A* p1 = new A;
/*A*p1 = (A*)malloc(sizeof(A));*/
delete p1;
}
我们进行调用:
所以delete也会调用自定义类型的析构函数。
我们举一个之前写的链表的例子:
struct ListNode
{
ListNode(int val = 0)
:_next(nullptr)
, _val(val)
{}
ListNode* _next;
int _val;
};
我们现在可以这样写链表:
struct和class都是类关键字,struct当我们不处理时,类中的成员的默认用public修饰。
我们可以在类中写构造函数,相当于我们之前的创建新节点
int main()
{
ListNode*n1 = new ListNode(1);
ListNode*n2 = new ListNode(2);
ListNode*n3 = new ListNode(3);
ListNode*n4 = new ListNode(4);
n1->_next = n2;
}
这样写链表就会方便很多。
注意:
new和delete一定要匹配,否则会产生意想不到的问题,我们就只举一个例子:
例如:
nt main()
{
A*p1 = new A[10];
delete p1;
}
我们写出这样的代码进行运行就会报错:
为什么会这样呢?
我们先写一个正常的进行分析:
int main()
{
A*p1 = new A[10];
delete[] p1;
}
我们的A的成员只有一个整型,所以A占四个字节的空间,十个A就占40个字节的空间。
我们先进行编译:
我们进行申请时或进行释放时,都会调用多次调用构造函数或析构函数。
我们在创建时,知道我们需要创建十个A类所占的空间
但是我们在析构的时候,并不清楚我们需要析构多少次,这时候,我们需要额外申请一个整型的空间:
接下来,我们把p1往前置:
这时候,我们就知道我们需要析构多少次,并且从这里可以把我们申请的空间全部释放。
我们返回来看之前报错的情况:
int main()
{
A*p1 = new A[10];
delete p1;
}
为什么会报错呢?
答: 因为我们释放没有写[],所以我们构造了十次,但是我们不清楚析构了多少次,所以就会报错。
上面的这些都是关于编译器vs2013的一些情况,举这些例子只是为了说明一定要把申请的空间和delete释放的空间进行对应
我们知道malloc申请大的空间或者连续申请小的空间就会报错:
int main()
{
while (1)
{
int *p1 = (int*)malloc(1024 * 1024);
if (p1)
{
cout << p1 << endl;
}
else
{
cout << "申请失败" << endl;
break;
}
}
}
并且我们知道,当malloc申请失败的时候,会返回空指针,所以我们可以写出以上代码来进行实验:
申请多次的时候,报错
接下来,我们对new进行实验:
int main()
{
while (1)
{
int *p1 = new int[1024 * 1024];
if (p1)
{
cout << p1 << endl;
}
else
{
cout << "申请失败" << endl;
break;
}
}
}
运行很快就停止了,但是并没有打印出申请失败,说明我们new失败的返回值并不是0,或者说,new失败没有返回值。
new失败的话,就会抛异常,所以我们不需要对返回值进行检查,对于抛异常的问题,我们之后再进行详解。
上述的两个函数是new和delete的底层实现:
new的底层实现就是通过new调用operator new函数,operator函数中有malloc,调用完毕之后调用构造函数。
注意:operator new函数并不是new的重载。
operator new相当于是一个新的全局函数。
我们看一下operator函数的定义:
我们可以发现,operator new函数就是malloc函数的封装,无非就是加上了当申请失败时,不反回,而是抛异常。
我们观察一下operator delete函数。
相当于我们operator的主体部分也是调用了free函数。
我们是否可以使用operator new来申请空间呢?
答:可以:
例如:
int main()
{
while (1)
{
char*p1 = (char*)operator new(1024 * 1024 * 1024);
cout << (void*)p1 << endl;
}
}
operator new的使用方法和malloc相似。
不同点在于:operator new申请失败的话不需要报错,因为会抛异常。