【C++初阶】C/C++内存分布、new/delete与模板入门
C/C内存管理1.C/C内存分布栈区栈又叫堆栈存储非静态局部变量/函数参数/返回值等等栈是向下增长的。函数调用时会在栈区开辟空间堆区堆用于程序运行时动态内存分配堆是向上增长的数据段存储全局数据和静态数据代码段存放可执行的代码和只读常量2.C语言内存管理C语言中动态内存管理方式malloc/calloc/realloc/free对于C语言内存管理这篇文章中进行了介绍【C语言】动态内存管理3.C内存管理方式C语言中的内存管理方式在C中可以继续使用但是有些地方就无能为力而且使用起来比较麻烦因此C又提出了自己的内存管理方式通过new和delete操作符进行动态内存管理3.1new/delete操作内置类型intmain(){// 动态申请一个int类型的空间int*ptr1newint;// 动态申请一个int类型的空间并初始化为10int*ptr2newint(10);// 动态申请10个int类型的空间int*ptr3newint[3];// 动态申请10个int类型的空间并初始化int*ptr4newint[10]{0};int*ptr5newint[10]{1,2,3,4,5};deleteptr1;deleteptr2;delete[]ptr3;delete[]ptr4;delete[]ptr5;}注意申请和释放单个元素的空间使用new和delete操作符申请和释放连续的空间使用new[]和delete[]。注意要匹配起来使用。3.2new和delete操作自定义类型#includeiostreamusingnamespacestd;classA{public:A(inta0):_a(a){coutA()thisendl;}~A(){cout~A()thisendl;}private:int_a;};intmain(){// new/delete 和 malloc/free 的最大区别时 new/delete对于自定义类型除了开空间还会调用构造函数和析构函数A*p1(A*)malloc(sizeof(A));A*p2newA(1);free(p1);deletep2;// 内置类型几乎是一样的int*p3(int*)malloc(sizeof(int));int*p4newint;free(p3);deletep4;A*p5(A*)malloc(sizeof(A)*10);A*p6newA[10];free(p5);delete[]p6;return0;}注意在申请自定义类型时的空间时new 会调用构造函数delete 会调用析构函数而 malloc 和 free 不会。4.malloc/free 和 new/delete 的区别共同点都是从堆上申请空间并且需要用户手动释放。不同点malloc 和 free 是函数new 和 delete 是操作符malloc 申请的空间不会初始化new 可以初始化malloc 申请空间时需要手动计算空间大小并传递new 只需在其后跟上空间的类型即可如果是多个对象[]中指定对象个数即可malloc 的返回值为 void* 在使用时必须强转new 不需要因为 new 后跟的是空间类型malloc 申请空间失败时返回的是NULL因此使用时必须判空new 不需要但是new 需要捕获异常申请自定义类型对象时malloc/free 只会开辟空间不会调用构造函数和析构函数而new 在申请空间后会调用构造函数完成对象的初始化delete 在释放空间前会调用析构函数完成空间中资源的清理释放模板初阶1.泛型编程当我们想要实现一个通用的交换函数时可以通过函数重载实现但是有一些不好的地方重载的函数只是类型不同函数体的代码非常相似代码复用率比较低只要有新类型出现就要用户自己增加对应的函数代码的可维护性比较低一个出错可能导致所有重载都出错此时就可以使用泛型编程泛型编程编写与类型无关的通用代码是代码复用的一种手段。模板是泛型编程的基础模板分为函数模板和类模板2.函数模板2.1函数模板概念函数模板代表了一个函数家族该函数模板与类型无关在使用时被参数化根据实参类型产生函数的特定类型版本。2.2函数模板格式templatetypename T1, Typename T2, …, typename Tn 返回值类型参数名(参数列表){}函数模板实现Swap函数templatetypenameTvoidSwap(Tleft,Tright){T templeft;leftright;righttemp;}注意typename(模板) 是用来定义模板参数的关键字可以用 class 代替不能用struct2.3函数模板的原理函数模板是一个蓝图它本身并不是函数是编译器用使用方式产生特定具体类型函数的模具。所以模板就是将本来应该我们做的重复的事交给了编译器在编译器编译阶段对于模板函数的使用编译器需要根据传入的实参类型来推演生成对应的函数以供调用。比如当用double类型使用函数模板时编译器通过对实参类型的推演将T确定为double类型然后产生一份专门处理double类型的代码对于字符类型也是如此。2.4函数模板的实例化不同类型的参数使用函数模板时称为函数模板的实例化。分为隐式实例化和显式实例化隐式实例化让编译器根据实参推演模板参数的实际类型templatetypenameTTAdd(constTleft,constTright){returnleftright;}intmain(){inta110,a220;doubled110.0,d220.0;Add(a1,a2);Add(d1,d2);// 这个语句会报错因为模板中两个参数的类型一样这里类型不同//Add(a1, d1);// 此时有两种处理方式1.强制类型转换 2.显式实例化Add(a1,(int)d1);}显式实例化在函数名后的中指定模板参数的实际类型templatetypenameTTAdd(constTleft,constTright){returnleftright;}intmain(){inta10;doubleb20.0;Addint(a,b);return0;}如果类型 不匹配编译器会尝试进行隐式类型转换如果无法转换成功则编译器会报错2.5模板参数的匹配原则一个非模板函数可以和一个同名的函数模板同时存在而且该函数模板还能被实例化为这个非模板函数// 专门处理int的加法函数intAdd(intleft,intright){returnleftright;}// 通用加法函数templateclassTTAdd(T left,T right){returnleftright;}intmain(){Add(1,2);// 与非模板函数匹配编译器不需要特化Addint(1,2);// 调用编译器特化的Add版本return0;}对于非模板函数和同名函数模板如果其他条件相同在调用时会优先调用非模板函数而不是从模板产生一个实例。如果模板可以产生一个更匹配的函数则会选择模板#includeiostreamusingnamespacestd;// 专门处理int的加法函数intAdd(intleft,intright){returnleftright;}// 通用加法函数templateclassTTAdd(T left,T right){returnleftright;}intmain(){Add(1,2);// 与非模板函数匹配编译器不需要特化Add(1,2.0);// 函数模板可以生成更加匹配的版本编译器根据实参生成更加匹配的Add函数return0;}模板函数不允许自动类型转换但普通函数可以进行自动类型转换3.类模板3.1类模板的定义格式// 类模板的定义格式templateclassT1,classT2,...,classTnclass类模板名{// 类内成员定义};#includeiostreamusingnamespacestd;templatetypenameTclassStack{public:Stack(size_t capacity4){_arraynewT(capacity);_capacitycapacity;_size0;}voidPush(constTdata);private:T*_array;size_t _capacity;size_t _size;};// 模板不建议声明和定义分离成两个文件会产生链接错误templateclassTvoidStackT::Push(constTdata){// 扩容_array[_size]data;_size;}intmain(){Stackintst1;// intStackdoublest2;// doublereturn0;}3.1类模板的实例化类模板实例化与函数模板实例化不同类模板实例化需要在类模板名字后跟然后将实例化的类型放在中间类模板都是显式实例化类模板名字不是真正的类实例化的结果才是真正的类// Stack是类名Stackint、Stackdouble才是真正的类型Stackintst1;// intStackdoublest2;// double