一、 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<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
template<typename T>
void Swap(T& a1, T& a2)
{
T temp = a1;
a1 = a2;
a2 = temp;
}
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
1. 隐式实例化:让编译器根据实参推演模板参数的实际类型
2. 显式实例化:在函数名后的<>中指定模板参数的实际类型
如同下方代码就是利用显式实例化,因为两个不同的类型就没有找到对应的实例化,所以这里就是用了显式实例化,结果如图。
template<typename T>
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<int>(a1, b2) << endl;
cout << Add<double>(b1, a2) << endl;
return 0;
}
其次就是一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数,对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板,模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
八、类模板
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
类模版格式代码如下
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
// 动态顺序表
// 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具
template<class T>
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 <class T>
Vector<T>::~Vector()
{
if(_pData)
delete[] _pData;
_size = _capacity = 0;
}