情景引入:
现在假设我们要写一个swap函数交换两个值的大小,我们知道在之前讲过的函数重载是支持不同类型的参数重载的,因此我们只要写出int ,double ,char ,float ,bool的类型函数重载就行了,但是这个函数的功能差距不大,就因为类型不同我们就要写这么多,这显然不划算,因此C++里面提出了一种方法,支持模糊的类型匹配————模板
一:模板
我们前面知道模板支持模糊匹配,但是这并不代表模板的类型匹配不严格,相反模板里面是不允许类型的隐式转化(除非显示标注类型),从某种意义上来说,模板的类型匹配更加严格,那这是什么情况呢?看起来我们确实利用函数模板偷懒了很多,其实真正意义上并没有偷懒,只是我们偷的懒被系统做了而已。我们先看下面这个模板
template<class A,typename B>//类模板的定义 void swap(A& a,B& b){ A temp=a; a=b; b=temp; } 出了作用域若想要再次使用模板就需要重新定义, 模板只管模板定义处的下一行或者下一个作用域
那么编译器到底是怎么帮我们偷懒的呢?在我们没有使用这个函数的时候是并没有这个函数的,当我们使用的时候编译器通过对类型的辨认如何形成这个函数,在通过这个函数去进行操作。
现在有这么一个函数的模板
template<typename B> void swap(B& a,B& b){ B temp=a; a=b; b=temp; }
如果我们传一个这个参数过去会怎么样呢?
int a=1; double b=0.0; swap(a,b);
编译器会帮我们成功的交换吗?事实是编译器会报错,这是因为只有一个模板却有两个类型,有些情况就是这样的那我们怎么办呢?当然有办法,那就是使用的时候显示指定类型,如下
int a=1; double b=0.0; swap<int>(a,b); swap<double>(a,b);
二:内存管理
1)内存分布
在C++中内存大概可以分为五块
向下生长的栈空间:栈是用来储存局部变量和函数的空间,因为使用的时候,地址是越来越小,所以称为向下生长,因为这一块空间用完了它就会向比它地址低的空间去申请,并且栈只支持静态增长。
向上生长的堆:堆是支持动态增长的,像malloc,calloc,realloc,new的空间都是在这里开辟的,这里的空间直到程序结束才会释放,因此需要我们手动释放开辟的空间,但要注意释放空间不能多次释放。使用这里的空间地址是越来越大的,因为这一块空间用完了它就会向比它地址高的空间去申请
内存映射段:内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
数据段:存储全局数据和静态数据
代码段:存储可执行代码和只读常量
2)new和delete
在C++中new和delete是类似与C里面的malloc和realloc还有calloc的开辟空间的手段
区别:new和delete是内置基本操作符,malloc等是函数,
new=开辟空间+调用构造函数
delete=调用析构函数+释放空间
malloc=开辟空间
free=释放空间
new,malloc,delete,free能混着用吗?
答案:不能,因为可能会造成内存泄漏,new的类里面可能还有在堆上开辟的空间,如果不调用析构函数释放会造成内存泄漏
有些人就要问了,为什么有了malloc和realloc和calloc还有发明new?
那我们想象一下,我们创建了一个类,我们使用malloc的方式给它开辟空间,我们该如果给它初始化呢?
这就暴露的malloc等的一个大弊端,malloc,calloc是无法给自定义类型初始化的,new应运而生,new支持对自定义类型的初始化。我们看代码,里面有从基本内置类型到类的new空间基本操作
int* a=new int;//只开辟空间,不初始化 int* a=new int(6);//给a开辟空间并且初始化为6 delete a;//释放空间 int* a=new int[6];//给a开辟一个数组大小为6 int* a=new int[6]{1,2,3,4,5,6};//给a开辟一个数组大小为6,并且全部赋值 int* a=new int[6];//数组里面全是随机值 int* a=new int[6]{};//数组里面全是0 delete[] a; //释放空间 Date a=new Date{2023,11,11};//日期类并且赋初值 delete a;释放空间 Date a[3]=new Date[3]{(2023,11,11),(2024,11,11),(2025,11,11)};//自定义类型数组赋初值 delete[] a;
看到上面,你有没有想为什么数组释放空间需要[],这个真的毫无用处吗?
错,首先我们想一下,编译器是如何判断这个数组有多大的空间,如果是你你会怎么做?编译器在开辟空间会多开四个字节,正好一个int大小,这个int用来储存数组的数量,通过这个int我们就可以准确的释放数组空间,这个int类型一般在开辟空间的前面,这样就可以快速准确拿到数组大小,释放空间,如果我们不使用[]这个,那么空间就会从中间被释放,导致报错。
3)简单介绍new和delete底层原理。
在new和delete的底层原理其实就是调用了被封装的malloc和free,malloc被封装在new的函数重载里面,free被封装在delete的函数重载里面,为什么要封装呢?这是因为malloc分配空间出错是直接返回0,而C++的要求的抛错,为了达到这个效果,必需封装一下malloc和free,并且不要忘记new里面会调用构造函数delete里面会调用析构函数。new的函数重载只负责开辟空间,delete的函数重载只负责释放空间,调用析构函数和构造函数还是在new运算符里面。
new=malloc+调用构造函数
delete=free+释放空间
malloc=开辟空间
free=释放空间