C++中的内存管理机制和C语言是一样的,但在具体内存管理函数上,C语言的malloc已经无法满足C++面向对象销毁的需求,于是祖师爷在C++中新增了一系列内存管理函数,即 new 和 delete 著名段子:如果你还没没有对象,那就尝试 new 一个吧。
一、内存分布
在内存中存在五大分区,各个分区都各司其职,比如我们耳熟能详的栈区,堆区,静态区。

二、C语言的三种动态内存管理的函数
malloc:申请指定大小的空间
int* pi = (int*)malloc(sizeof(int) * 1); //申请一个整型 double* pd = (double*)malloc(sizeof(double) * 2); //申请两个浮点型 char* pc = (char*)malloc(sizeof(char) * 3); //申请三个字符型
注意:malloc申请的空间都是未初始化的,即被空间置为随机值。
calloc:将初始化的空间初始化为0
int* pi = (int*)calloc(1, sizeof(int)); //申请一个整型 double* pd = (double*)calloc(2, sizeof(double)); //申请两个个浮点型 char* pc = (char*)calloc(3, sizeof(char)); //申请三个字符型
注意:
calloc参数列表与malloc不同,同时calloc申请的空间会被初始化为0
realloc:对已申请的空间进行扩容
int* tmp = (int*)realloc(pi, sizeof(int) * 10); //将 pi 扩容为十个整型 pi = tmp; //常规使用方法
注意: 我们要对所有的申请函数进行空指针检查,预防野指针问题
堆区的空间由我们管理,编译器很信任我们,因此我们要做到有借有还,再借不难
凡是动态开辟的空间,用完后都需要释放
free(tmp); //此时tmp指向pi扩容后的空间,释放tmp就行了 tmp = pi = NULL; //两者都需要置空 free(pd); pd = NULL; free(pc); //只要是动态开辟的,都需要通过 free 释放 pc = NULL;
三、C++的动态管理
1、new开辟空间
void Test_CPP() {
// 动态申请一个int类型的空间
int* p1 = new int;
// 动态申请一个int类型的空间并初始化为10
int* p2 = new int(10);
// 动态申请10个int类型的空间
int* p3 = new int[10];
}
C++允许大括号进行初始化
int* p1 = new int[5]{1,2} // 1 2 0 0 0
int* p2 = new int[5]{1,2,3,4,5}; // 1 2 3 4 5
2、delete释放空间
void Test_CPP() {
int* p1 = new int;
int* p2 = new int(10);
int* p3 = new int[10]; // 多个对象
// 单个对象,delete即可。
delete p1;
delete p2;
// 多个对象,delete[] 。
delete[] p3;
p1 = nullptr;
p2 = nullptr;
p3 = nullptr;
}
3、new与malloc的区别与free与delete的区别
在申请自定义类型的空间时,new 会调用构造函数,
delete 会调用析构函数,而 malloc 与 free 不会。
new:在堆上申请空间 + 调用构造函数输出。
delete:先调用指针类型的析构函数 + 释放空间给堆上。

四、函数模版
函数模板代表了一个函数家族,该函数模板与类型无关,
在使用时被参数化,根据实参类型产生函数的特定类型版本。
1、函数模版格式
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
① template 是定义模板的关键字,后面跟的是尖括号 < >
② typename 是用来定义模板参数的关键字
③ T1, T2, ..., Tn 表示的是函数名,可以理解为模板的名字,名字你可以自己取。
2、函数模版使用方法
来看一下这一段代码,假设要实现Add函数处理各个类型的加法。
//处理整型的加法函数
int Add(const int& a, const int& b)
{
return a + b;
}
//处理浮点型的加法函数
double Add(const double& a, const double& b)
{
return a + b;
}
这时候用一个模版既可以把他们全部搞定
template <class T> //还可以写成template <typename T>
T Add(const T& a, const T& b) {
return a + b;
}
还可以实现多参数模版:
//定义一个多参数模版,求数的平均值
template <class T1 ,class T2>
T2 findaver( T1 arr[], int n) {
T1 sum = 0;
T2 aver;
for (int i = 0; i < n;i++) {
sum += arr[i];
}
aver = sum / (double)n;
return aver;
}
总之,在函数模板的存在下,我们不再需要再编写不同类型参数的相似函数了
3、实现原理
只需要两样东西:编译器与函数重载
当我们编写好函数模板后,编译器会记住这个模板的内容,当我们使用模板时,编译器又会根据参数类型,创建相应的、具体的函数供参数使用,而这就是函数重载的道理
编译器在识别参数类型生成函数时,有两种途径:
-
自动识别 (
隐式) -
我们手动指定(
显式)
3.1隐式实例化
隐式实例化就是编译器自动识别参数后生成函数的过程
//Add 模板
template <class T>
T Add(const T& a, const T& b)
{
return a + b;
}
int main()
{
Add(2, 1.5); //此时编译失败!
return 0;
}

解决方法一:强制类型转化
int main(){
Add((double)2, 1.5);
return 0;
}
3.2显式实例化
显式实例化就是给编译器打招呼,让它在建房子时按照我们的意愿来
Add<int> (2, 3.14); //此时编译器会调用 _3Addii 函数,至于传参时的类型转换,由编译器完成 Add<char> (2, 5); //调用 _3Addcc 函数
这种行为是完全合法的,< > 符号也正式和我们见面了,在后面的 STL 学习中,< > 会经常使用到,比如生成一个类型为 int 的顺序表,直接 vector<int>,生成 char 类型的顺序表 vector<char>,一键生成,非常方便,当然还有很多容器都会用到显式实例化
五、类模版
模板除了可以用在函数上面外,还可以用在类上,此时称为 类模板
STL 库中的容器,都是 类模板 的形式,我们使用时,需要什么类型的 类,直接显式实例化为对应 模板类 即可
//简单演示下 STL 中的容器,这些都是类模板的实际运用 vector<int> v1; //实例化为整型顺序表类 list<double> l1; //实例化为浮点型链表类
类模版与函数模版不同,类模版只能显式实例化。
简单写一个类模版:
//类模版,简单写一个栈模版
//简单写一个栈模板
template<class T>
class Stack
{
public:
//构造函数
Stack(int capacity = 4);
//析构函数
~Stack();
//……
private:
T* _pData;
int _top;
int _capacity;
};
//注意类模板中方法的实现方式!
//定义构造函数
template<class T>
Stack<T>::Stack(int capacity)
{
_pData = new T[capacity]; //内存管理,一次申请4块空间
_capacity = capacity;
_top = 0;
}
//定义析构函数
template<class T>
Stack<T>::~Stack()
{
delete[] _pData; //注意:匹配使用
_capacity = _top = 0;
}
int main(){
Stack<int> s1;
Stack<char> c1;
return 0;
}
类模板使用时需要注意一些问题:
-
模板类中的函数在定义时,如果没有在类域中,就需要通过类模板+类域访问的方式定义 -
类模板不支持声明与定义分开在两个文件中实现,因为会出现链接错误



















