一、 C/C++内存分布
首先在c语言的动态内存管理中我知道了代码是如何存储数据的,然后c++是根据c语言底层变化来的,那么c语言的内存管理就是适用c++的内存管理,在c语言中程序是分为几个部分存储,例如在栈堆等等,他们的分布如下图就是一个分布,有点抽象。
1、栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
2、 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
3、堆用于程序运行时动态内存分配,堆是可以上增长的。
4、数据段--存储全局数据和静态数据。
5、代码段--可执行的代码/只读常量。
二、C语言中动态内存管理方式
c语言的内存管理就是利用malloc/calloc/realloc/free,如下方代码所示就是利用这几个函数去管理的,这里就不细说了,在C语言内存管理说的比较详细,这里可以看出malloc申请的空间,realloc是扩容,但是都不进行初始化,这个calloc会初始化,所以可以看出下方图片test2是0。
int main() { int* testptr1 = (int*)malloc(sizeof(int)); if (testptr1 == nullptr) { perror("malloc fail"); return NULL; } int* testptr2 = (int*)calloc(4, sizeof(int)); if (testptr2 == nullptr) { perror("calloc fail"); return NULL; } int* testptr3 = (int*)realloc(testptr1, sizeof(int) * 10); if (testptr3 == nullptr) { perror("realloc fail"); return NULL; } //free(testptr1); free(testptr2); free(testptr3); return 0; }
三、 C++中动态内存管理
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因
此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。如下图代码就是利用new去开辟空间,可以看到代码中,ptr1就是开辟了一个int的空间,ptr2就是开辟了一个int然后并且初始化,delect就是相当于free,注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。
注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与
free不会。
void Test() { int* ptr1 = new int; int* ptr2 = new int(10); int* ptr3 = new int[3]; delete ptr1; delete ptr2; delete[] ptr3; }
四、 operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。怎么说呢这两个函数其实就是起的函数名叫这个,并不是函数重载这里要分清楚,从下方代码可以看出。
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/ void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) { // try to allocate size bytes void *p; while ((p = malloc(size)) == 0) if (_callnewh(size) == 0) { // report no memory // 如果申请内存失败了,这里会抛出bad_alloc 类型异常 static const std::bad_alloc nomem; _RAISE(nomem); } return (p); } /* operator delete: 该函数最终是通过free来释放空间的 */ void operator delete(void *pUserData) { _CrtMemBlockHeader * pHead; RTCCALLBACK(_RTC_Free_hook, (pUserData, 0)); if (pUserData == NULL) return; _mlock(_HEAP_LOCK); /* block other threads */ __TRY /* get a pointer to memory block header */ pHead = pHdr(pUserData); /* verify block type */ _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)); _free_dbg( pUserData, pHead->nBlockUse ); __FINALLY _munlock(_HEAP_LOCK); /* release other threads */ __END_TRY_FINALLY return; }
五、new和delete的实现原理
1、内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申
请空间失败时会抛异常,malloc会返回NULL。
2、自定义类型new的原理
① 调用operator new函数申请空间
②在申请的空间上执行构造函数,完成对象的构造
delete的原理
① 在空间上执行析构函数,完成对象中资源的清理工作
② 调用operator delete函数释放对象的空间
new T[N]的原理
①调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
②在申请的空间上执行N次构造函数
delete[]的原理
① 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
② 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
六、泛型编程
像下方代码这样交换需要使用很多次的时候,每次都要写个对应的交换就会显得很麻烦,如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件(即生成具体类型的代码),那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
void Swap(int& a1, int& a2) { int tmp = a1; a1 = a2; a2 = tmp; } void Swap(double& a1, double& a2) { double tmp = a1; a1 = a2; a2 = tmp; } int main() { int a1 = 10, a2 = 20; double b1 = 11.11, b2 = 22.22; Swap(a1, a2); Swap(b1, b2); return 0; }
七、函数模板
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
他的格式如下,编写的测试代码如下,这里也是可以class
template
返回值类型 函数名(参数列表){}
template void Swap(T& a1, T& a2) { T temp = a1; a1 = a2; a2 = temp; }
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
1. 隐式实例化:让编译器根据实参推演模板参数的实际类型
2. 显式实例化:在函数名后的<>中指定模板参数的实际类型
如同下方代码就是利用显式实例化,因为两个不同的类型就没有找到对应的实例化,所以这里就是用了显式实例化,结果如图。
template T Add(const T& a1, const T& a2) { return a1 + a2; } int main() { int a1 = 10, a2 = 20; double b1 = 11.11, b2 = 22.22; cout << Add(a1, a2) << endl; cout << Add(b1, b2) << endl; cout << Add(a1, b2) << endl; cout << Add(b1, a2) << endl; return 0; }
其次就是一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数,对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板,模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
八、类模板
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
类模版格式代码如下
template class 类模板名 { // 类内成员定义 }; // 动态顺序表 // 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具 template class Vector { public : Vector(size_t capacity = 10) : _pData(new T[capacity]) , _size(0) , _capacity(capacity) {} // 使用析构函数演示:在类中声明,在类外定义。 ~Vector(); void PushBack(const T& data); void PopBack(); // ... size_t Size() {return _size;} T& operator[](size_t pos) { assert(pos < _size); return _pData[pos]; } private: T* _pData; size_t _size; size_t _capacity; }; // 注意:类模板中函数放在类外进行定义时,需要加模板参数列表 template Vector::~Vector() { if(_pData) delete[] _pData; _size = _capacity = 0; }
九、内存管理与模板的思维导图