CPP之动态内存管理以及模板初阶
1.动态内存管理:
(1)一个小问题回顾内存分布:
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
1. 选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____
staticGlobalVar在哪里?____
staticVar在哪里?____
localVar在哪里?____
num1 在哪里?____
char2在哪里?____
*char2在哪里?___
pChar3在哪里?____
*pChar3在哪里?____
ptr1在哪里?____
*ptr1在哪里?____
用以下的图片来反映上图的答案:
注意:
1,栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的:
2,堆用于程序运行时动态内存分配,堆是可以上增长的;
3,数据段(静态区)存储全局变量和静态变量;
4,代码段--可执行的代码/只读常量。
(2)cpp中内存管理的方法:
在cpp中,内存管理是通过new和delete来实现的。
下面具体举例:
//动态申请一个int大小的空间
int* ptr1 = new int;//动态申请一个int的空间并初始化为0
int* ptr2 = new int(0);//动态申请一个有10个int的数组
int* ptr3 = new int[10];//分别释放动态申请的三个
delete ptr1;
delete ptr2;
delete[] ptr3;
上述代码用图片来解析:
注意:申请和销毁一个元素的空间用new和delete,申请和销毁连续的空间用new[ ]和delete[ ],注意,配套起来用。
(3)特点:
对于malloc和free,new和delete在创建自定义类型的时候有很大的区别,对于malloc函数来说,它会给自定义类型申请一块空间,并不会给这个自定义内型初始化,但是new不仅创建空间,而且还要去调用自定义类型的构造函数对其进行初始化。对于free函数,他仅仅是对自定义类型的空间进行释放,但是delete会先调用自定义类型的析构函数然后再将这块内存释放。
(他们在对内置类型进行创建的时候基本上没有区别)。
(4)operator new和operator delete函数
说明:
-->operator new函数:
void*__CRTDECLoperatornew(size_tsize)_THROW1(_STDbad_alloc)
{//trytoallocatesizebytesvoid*p;while((p=malloc(size))==0)if(_callnewh(size)==0){//reportnomemory//如果申请内存失败了,这里会抛出bad_alloc类型异常staticconststd::bad_allocnomem;_RAISE(nomem);} return(p);
}
可以看到,起始operator new 函数里面还是会去使用malloc,那为什么还要有new的存在呢,这和cpp以后的抛异常的知识有关。
-->operator delete函数:
下面是operator delete函数的实现:
voidoperatordelete(void*pUserData)
{_CrtMemBlockHeader*pHead;RTCCALLBACK(_RTC_Free_hook,(pUserData,0));if(pUserData==NULL) return;_mlock(_HEAP_LOCK); /*blockotherthreads*/__TRY/*getapointertomemoryblockheader*/pHead=pHdr(pUserData);/*verifyblocktype*/_SSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));_free_dbg(pUserData,pHead->nBlockUse);__FINALLY_munlock(_HEAP_LOCK); /*releaseotherthreads*/__ENDTRYFINALLYreturn;
}
小结:
(2)定位new:(定位new表达式(placement-new) )--->了解程度
2.模板:
(1)泛型编程:
就一个简单的交换函数,是否有一个通用的方法去解决不同类型的交换呢,虽然函数重载可以完成这件事,但是它存在这样两个问题:
(1)改变类型就要重新写函数,效率不高;
(2)代码的可维护性低,一个重载出错,则可能全部都出错了。
模板就是用来解决上述问题的:告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码。
泛型编程:编写于类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础。
(2)函数模板:
(1)模板的概念:函数模板是一类函数,该函数模型与类型无关,在使用时被参数,根实参类型产生函数的特定类型版本。
(2)函数模板格式:template<typename T1,typename T2,......typename Tn>;
下面用交换函数来举例:
templete<typename T>
void swap(T& a T& b)
{T tmp = b;b = a;a = tmp;
}
不管T是什么类型,在调用函数的时候,编译器都会根据模板生成相应的代码。
注意:typename是用来定义模板参数的关键字,也可以用class来代替,但是切记不可以用struct来代替class。
(3)函数模板的原理:
函数模板就相当于一张蓝图,并不是函数,是告诉编译器生成特定类型函数的模具。
temlate<typename T>
T add(T left,T right)
{return left+right;
}
int main()
{int a1 = 1;double b1 = 1.1;add(a1,b1);return 0;
}
add(a1,(int)b1);
add<int>(a1,b1);
(3)模板参数的匹配原则:
1.一个非模板函数可以与一个同名的模板函数同时存在,而且模板函数还可以通过实例化转化为该非模板函数。
//专用于int类型的add
int add(int a,int b)
{return a+b;
}
//通用的add函数
temlate<typename T>
T add(T left,T right)
{return left+right;
}
int add(itn& a,int& b)
{return a+b;
}
template<typename T1,typename T2>
T add(T1 a, T2 b)
{return a+b;
}
int main()
{add(1,1);//调用第一个函数add(1,1.1);//调用第二个函数
}
(4)类模板:
(1)类模板的定义格式:
template<classT1,classT2,...,classTn>
class 类模板名
{//类内成员定义
}
(2)写一个stack的模板:
#include<iostream>
using namespace std;namespace zmj
{template<typename T>class stack{public:stack(int n = 4){_array = new T[n];_size = 0;_capacity = n;}void push(const T& data){_array[_size] = data;++_size;}private:T* _array;int _size;int _capacity;};
}// 模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误。
int main()
{Stack<int> st1; //intStack<double> st2; //doublereturn 0;
}
若想把push的内容写在外面:
需要特定的格式:
#include<iostream>
using namespace std;namespace zmj
{template<typename T>class stack{public:stack(int n = 4){_array = new T[n];_size = 0;_capacity = n;}void push(const T& data);private:T* _array;int _size;int _capacity;};
}template<typename T>
void zmj::stack<T>::push(const T& x)
{_array[_size] = x;++_size;
}
需要重新写一遍template<typename T>,以及还需要在stack后面加<T>。
(3)类模板的实例化: