本文接上篇文章C++_内存管理-CSDN博客
如有不懂请先查看上篇文章。
前言
本篇文章主要讲解关于C++中new和delete的理解和使用,并包含一点模版的初步使用等,如有错误,望大家指正。
回顾:
上一篇文章我们了解了关于数据的存储位置以及new和delete的基础使用等,
临时变量存储在栈区中,全局变量存储在静态区,常量存储在常量区中,而动态开辟的空间都在堆中,还有关于static修饰的局部变量存储在常量区中等。
一、new和delete
我们在上一篇了解了关于new和delete的一些基础知识之后,我们来更加详细的了解他们,new的使用可不可以自调用
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A()" << endl;
a = new int;
cout << a << endl;
};
~A()
{
delete a;
cout << "~A()" << endl;
cout << a << endl;
};
private:
int* a;
};
int main()
{
A* w = new A[2];
delete[] w;
return 0;
}
从上面的程序我们是可以看出的当我使用new时,我们首先是开辟一个A类型的空间,然后调用其构造函数进行初始化,在调用构造函数的时候,我们又使用了new开辟了一个int类型的空间。从这里我们可以看出new是可以直接调用自己的。
那么delete,在使用delete的时候,delete是先调用析构函数然后再释放空间,那么为什么是先调用析构函数在进行空间的销毁呢?这样想,如果我们先进行空间的销毁,那么A类型中我们使用new开辟了一个int类型的空间,并将其赋给了a,这个a的地址就是野指针了,这片空间也就找不到了,也就导致了存储泄漏。
二、operator new与operator delete函数
1.概念
new和delete是用户进行动态内存的申请和释放的操作符,而operator new 和 operator delete 是系统提供的全局函数,new在底层调用operator new来申请空间,delete在底层调用operator delete来释放空间。
C++中,operator new和operator delete是全局函数,他们封装了malloc 和 free。那么它们的功能是不是和new、delete的功能是一样的呢?
并不是,准确来说,operator new 和 operator delete 与malloc 和 free 的功能是一样的,
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A()" << endl;
a = new int;
cout << a << endl;
};
~A()
{
delete a;
cout << "~A()" << endl;
cout << a << endl;
};
private:
int* a;
};
int main()
{
A* c = (A*) operator new (sizeof(A));
operator delete(c);
return 0;
}
这里程序运行的结果是显示没有调用A类的构造函数的析构函数的,由此可以看出来,它们与malloc和free更为相似,它们只是开辟了A类大小的空间,并没有进行初始化等动作。
那么operator new 和 operator delete的作用是什么呢?我们来看看new的功能。
new:
1.开空间
2.调用构造函数
调用构造函数是很好处理的,但是开空间我们是否可以直接调用malloc呢?C++中我们讲究的面向对象,malloc开空间失败会直接返回0,而我们面向对象编程是开空间失败后是不能使用0来处理的,所以使用了operator new来对malloc进行封装,而operator new开空间失败后是直接抛异常。
delete:
1.调用析构函数
2.释放空间
delete这里其实我也并不是很明白为什么要对free进行封装,这里的空间释放其实可以直接调用free进行释放,没有必要还封装一个operator delete函数。
2.new和delete的使用事项
代码如下(示例):
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A()" << endl;
a = new int;
};
~A()
{
delete a;
cout << "~A()" << endl;
};
private:
int* a;
};
int main()
{
A* p1 = new A[4];
delete[]p1;
return 0;
}
从上图我们可以看见,当我们连续开辟空间时,我们查看p1的内存地址的时候,发现其前面还有一个存储开辟空间个数的地址,这个数据是为了让我们使用delete时,让delete查看的,这样它才知道要销毁多少个空间。
来带大家看一个场景:
代码如下:
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A()" << endl;
a = new int[3];
};
~A()
{
cout << "~A()" << endl;
};
private:
int* a;
};
int main()
{
A* p1 = new A[4];
delete p1;
return 0;
}
运行结果:
程序崩溃了,这里大家都可以看出这是因为内存释放是我们是直接从对象的位置开始释放的,并没有释放前面用于存储空间数量的内存。
程序又正常了,这又是为什么?
当我们不写析构函数的时候,编译器开辟空间时就不会多开辟空间用于存储空间数量,释放空间的位置就没有错,所以这里就不会崩溃。
三、函数模版
函数模版是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型的函数的模具。所以,模板其实就是将我们本因该做的事交给了编译器。
在编译器编译阶段,对模版函数的使用,编译器需要根据传入的实参来转化衍生成对应类型的函数以供调用。
#include<iostream>
using namespace std;
template<typename t> //模版类型t
void Swap(t& a, t& b)
{
t w = a;
a = b;
b = w;
}
int main()
{
int a = 1;
int b = 2;
Swap(a, b);
cout << a << '/' << b << endl;
return 0;
}
那么如果我们使用不同类型参数呢?
#include<iostream>
using namespace std;
template<typename t>
t Add(t a, t b)
{
return a + b;
}
int main()
{
cout << Add<int>(1, 1.1)<< endl;
return 0;
}
这里给大家讲一个显示实例化,当我们传入参数是不同类型的时候,我们可以在函数名后加上<>
在符号内添加类型,那么传入参数就会转换为该类型。
总结
new和delete操作符的理解可以加强我们对于空间开辟销毁的理解,而模版的使用可以使我们更加方便的工作。