内容介绍
Ⅰ.C++内存管理
1.C/C++内存分布
int n1 = 1;
static int n2 = 2;
void Test()
{
static int n3 = 3;
int n4 = 4;
int n5[10] = { 5,5,5,5,5, };
char n6[] = "666666";
const char* n7 = "7777777";
int* n8 = (int*)malloc(sizeof(int) * 4);
int* n9 = (int*)calloc(4, sizeof(int));
int* n10 = (int*)realloc(n9, sizeof(int) * 4);
free(n8);
free(n10);
}
对上述代码在内存区域划分如下图:
说明:1.栈又叫堆栈–存储非静态局部变量/函数参数/返回值等等,栈是向下增长的。
2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux操作系统的内容)
3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
4. 数据段–也叫静态区–存储全局数据和静态数据。
5. 代码段–也叫常量区–可执行的代码/只读常量。
2.C与C++动态内存管理方式对比
2.1C中动态内存管理方式
int* p1 = (int*)malloc(sizeof(int) * 4);
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, 8 * sizeof(int));
free(p1);
//free(p2);
free(p3);
- C中实现动态扩容主要是用malloc,calloc与realloc来实现。同时释放空间是用free。
- malloc是直接找一块连续的内存空间进行开辟。
- calloc是在malloc的基础上同时对开辟好的空间进行初始化为0。
- realloc又叫增容,是在一块空间上进行增容操作,如果原有的空间后面不足以增容,则会另找一块足够空间进行开辟,同时将原来空间的数据拷贝进新空间,并释放旧空间,若开始的空间指向为空(NULL),则一开始调用malloc进行开辟。
- 为了防止内存泄露,每次开辟的空间都要及时释放,free是对堆上开辟好的空间进行释放,注意free不能释放空的空间,且一块空间不能被连续释放多次,否则报错。
- malloc,calloc,realloc和free都是函数。
注:上述代码是不需要对p2进行释放,是因为realloc的特性,释放(p2)反倒会报错。
2.2C++中内存管理方式
- 首先C++向下兼容C,C的内存管理方式C++也可以使用,但我们发现C的方式使用起来多多少少不够方便,于是C++就推出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。 注:new和delete都是操作符。
new和delete的使用方法:
- 1.对内置类型使用
void Test()
{
//动态申请1个int类型空间
int* p1 = new int;
//动态申请1个int类型空间并初始化为2004,()中写初始化值
int* p2 = new int(2004);
//动态申请10个int类型空间,[]中写申请类型个数
int* p3 = new int[10];
//error: 无法使用带圆括号的初始值设定项初始化数组
//int* p4 = new int[10](2024);
delete p1;//申请是new,释放是delete
delete p2;
delete[] p3;//申请是用new[],释放也是用delete[]
}
对内置类型的动态申请和释放C和C++本质上没什么区别,都是开辟和释放空间。
- 对自定义类型使用
class A
{
public:
A(int a1 = 0,int a2 = 0)
:_a1(a1)
,_a2(a2)
{
cout << _a1 << " " << _a2 << endl;
cout << "A(int a1 = 0,int a2 = 0)" << endl;
}
~A()
{ cout << "~A()" << endl << endl; }
private:
int _a1;
int _a2;
};
int main()
{
A* p1 = new A;
delete p1;
A* p2 = new A(2024, 4);
delete p2;
//error: 无法使用带圆括号的初始值设定项初始化数组
//A* p3 = new A[3](2005, 8);
A* p3 = new A[3];
delete[] p3;//调用的是new[],释放也要用delete[]
return 0;
}
上图可以看出,new和delete对于自定义类型来说,在开辟空间和释放空间过程这一时间段会调用自定义类型的构造函数和析构函数,具体是new 开辟好了空间后 会调用构造,delete 释放空间前 会调用析构。
- 需注意new和delete,new[]和delete[] 需配套使用,否则会造成运行崩溃。
总结:malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。 不同的地方是:
1. malloc和free是函数,new和delete是操作符
2. malloc申请的空间不会初始化,new可以初始化
3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可
4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常(大项目一般都要捕获异常)
6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放
3.new和delete的底层实现原理(了解)
- new和delete底层是operator new 和operator delete两个函数,operator new 和operator delete是系统提供的全局函数,new和delete在底层会去调用这两个全局函数去完成资源的申请和释放。
- operator new全局函数底层是通过调用malloc函数去申请空间,operator delete全局函数底层是通过调用free函数去释放空间,他们不会去调用构造和析构,调用构造和析构还是new和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;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
Ⅱ.模板初阶
1.模板介绍
- C++引入模板可谓是C++历史上跨时代的意义,模板是泛型编程的基础,当本贾尼博士(Bjarne Stroustrup)提出模板概念后,C++从此一发不可收拾,自此面向对象的编程语言也逐渐被广大程序员们所接受。
- 模板正如它的名字一样,它是一个模具,当我们对模具浇筑不同原料时,会得到不同材料但形状大小一致的实质性物体。
- 在程序中,不同的数据类型就是不同的原料,拿函数模板来说,当我们指明函数模板时,编译器会根据不同的数据类型去生成对应这个数据类型的函数,不同数据类型生成的函数框架都是相同的,只是在替换数据类型模板的地方会不同。
- C++中模板分为函数模板和类模板,需要指出函数模板是模板,是函数的模板,模板函数是函数,是通过模板实例化出来的函数,类模板和模板类同理。
2.函数模板
- 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
- 使用格式为:
template<typename T1, typename T2,…,typename Tn>
返回值类型 函数名(参数列表){}
其中 template 和 typename 是关键字,前者直译为模板,后者直译为类型名字,这里typename也可以用class代替(不能用struct代替),根据个人习惯想用哪个用哪个。 - 用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
1.隐式实例化:让编译器通过传入实参的数据类型进行推导
template<class T>
T Add(const T& a, const T& b)//涉及类型转换时产生临时对象
{
return a + b;
}
int main()
{
int a1 = 10, a2 = 20;
double b1 = 10.0, b2 = 20.0;
cout << Add(a1, a2) << endl;//隐式实例化
cout << Add(b1, b2) << endl;
//error: “Add”: 未找到匹配的重载函数,要想解决:1.用户自己强转,2.使用显示实例化
//cout << Add(a1, b1) << endl;
cout << Add(a1, (int)b1) << endl;//强转
return 0;
}
2.显示实例化:<>中传入用户指定的数据类型,当数据类型不匹配时,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
int main()
{
int a1 = 10, a2 = 20;
double b1 = 10.1, b2 = 20.2;
cout << Add(a1, (int)b1) << endl;
cout << Add<int>(a1, b1) << endl;//显示实例化
cout << Add<double>(a2, b2) << endl;
return 0;
}
3.函数模板参数匹配原则
- 一个非模板函数和一个同名的函数模板可以同时出现,并且该函数模板可以被实例化成这个非模板函数,在实际编程环境中,编译器会去调用这两个中最匹配合适的。
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
//1.普通加法函数
int Add(int& a, int& b)
{
return a + b;
}
//2.通用的模板加法函数
template<class T1,class T2>
T1 Add(const T1& a, const T2& b)
{
return a + b;
}
int main()
{
int a1 = 10, a2 = 20;
double b1 = 2004.4, b2 = 2005.8;
cout << Add(a1, a2) << endl;//与1最匹配,会去调用1
cout << Add(a1, b1) << endl;//与2最匹配,会去调用2
cout << Add<double>(a2, b2) << endl;//显示调用模板函数,则会调用2
return 0;
}
4.类模板
- 类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
- 同时无论是函数模板还是类模板,都不建议声明和定义分离至两个文件,如.h和.cpp,否则会出现链接错误,具体原因在模板进阶部分会提到。
template<class T>
class Stack
{
public:
Stack(int size = 0, int capacity = 4, T* arr = nullptr)
: _size(size)
, _capacity(capacity)
, _arr(arr)
{ _arr = new T[_capacity]; }
void Push(const T& data)
{ _arr[_size++] = data; }
void Print()
{ cout << _arr[0] << " " << _arr[1] << " " << _arr[2] << " " << _arr[3] << endl; }
~Stack()
{
delete[] _arr;
_arr = nullptr;
_size = _capacity = 0;
}
//private:
T* _arr;
int _size;
int _capacity;
};
int main()
{
Stack<int> s1;
Stack<double> s2;
//Stack只是模板,Stack<int>和Stack<double>才是类型
s1.Push(1); s1.Print();
s2.Push(1.1); s2.Print();
return 0;
}
标签:free,函数,int,C++,内存,new,模板,delete
From: https://blog.csdn.net/2401_84420653/article/details/143225553