本文主要介绍了内存管理与模板(C++内存管理方式、new和delete的实现原理、malloc/free和new/delete的区别、函数模板、类模板),内容全由作者原创(无AI),并带有配图帮助博友们更好的理解,点个关注不迷路,下面进入正文~~
目录
[1.2 new和delete操作自定义类型](#1.2 new和delete操作自定义类型)
[1.3operator new与operator delete函数](#1.3operator new与operator delete函数)
[2. new和delete的实现原理](#2. new和delete的实现原理)
[3. malloc/free和new/delete的区别](#3. malloc/free和new/delete的区别)
1.C++内存管理方式
在C语言中,我们会使用malloc开辟空间,用free销毁空间,在C++中我们依旧可以使用。但是在某些情况下譬如类的初始化和销毁会显得很乏力。因此C++又引入了新的管理内存的方式:通过new 和delete进行动态内存管理。
1.1new/delete操作内置类型
cpp
int main()
{
int* p1 = new int;
// 动态申请一个int类型的空间
int* p2 = new int[10];
// 动态申请10个int类型的空间
delete p1;
delete[] p2;
// +
int* p3 = new int(0);
// 动态申请一个int类型的空间并初始化为0
int* p4 = new int[10]{ 0 };
// 动态申请10个int类型的空间并初始化为0
int* p5 = new int[10]{1,2,3,4,5};
// 动态申请10个int类型的空间并初始化为1,2,3,4,5
delete p3;
delete[] p4;
delete[] p5;
return 0;
}
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new\[\]和delete\[\],注意:匹配起来使用。
1.2 new和delete操作自定义类型
在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。因此,在操作自定义类型时,我们会更推荐用new/delete实现,毕竟能自动调用构造函数和析构函数还是相当方便的。
cpp
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
// 还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A) * 10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}
1.3operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果该应对措施用户设置了,则继续申请,否则抛异常。operator delete 最终是通过free来释放空间的。
总的来说,operator new和operator delete本质上都是通过调用malloc和free来进行开辟和释放空间的。
2. new和delete的实现原理
2.1内置类型
如果申请的是内置类型的空间,new/malloc和delete/free其实差距不大。需要注意的是,new/delete申请和释放的是单个元素的空间,而new\[\]和delete\[\]申请和释放的是连续空间,并且new在申请空间失败时会抛异常,而malloc申请空间失败会返回NULL。
2.2自定义类型
new的原理:
1.调用operate new函数申请空间
2.在申请的空间上调用构造函数,完整对象的构造
delete的原理:1.在空间上执行析构函数,完成对象的资源清理工作
2.调用operate delete函数释放对象的空间
new TN的原理
调用operator new\[\]函数,在operator new\[\]中实际调用operator new函数完成N个对象空间的申请
在申请的空间上执行N次构造函数
delete\[\]的原理在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
调用operator delete\[\]释放空间,实际在operator delete\[\]中调用operator delete来释放空间
3. malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
malloc和free是函数,new和delete是操作符
malloc申请的空间不会初始化,new可以初始化
malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,\[\]中指定对象个数即可
malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放
4.函数模板
在我们进行两个数据swap交换时,会因为需要交换的数据类型有很多种,导致我们需要写很多种数据类型的swap交换函数。函数模板可以通过传入数据的类型,生成对应类型的函数。
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
template<class T>
void Swap(T& left, T& right)
{
T tmp = left;
left = right;
right = tmp;
}
int main()
{
int a = 1;
int b = 2;
Swap(a, b);
cout << a << " " << b << endl;
double c = 1.1;
double d = 2.2;
Swap(c, d);
cout << c << " " << d << endl;
return 0;
}
如这段代码,当我们传入int类型数据时,编译器会判断T的类型就是int;当我们传入double类型数据时,编译器会判断T的类型就是double。此时编译器会自动生成参数分别位int和double的函数,本质上就是把我们要写多种相似代码的工作交给了编译器。
当然函数模板参数可以有很多个,比如T1、T2、T3
下面是函数模板的格式:
cpp
template<typename T1, typename T2,......,typename Tn>
typename是用来定义模拟参数的关键字,也可以使用class
当模板参数只有一个,我们传入两个不一样的参数,编译器无法判断T的类型是什么,就会报错。
那如果我们就是想在模板参数只有一个时让编译器不报错要怎么做呢?
1.用户自己强制类型转化
2.使用显示实例化
cpp
// 用函数模版生成对应的函数 -> 模版的实例化
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
template<class T1, class T2>
T1 Add(const T1& left, const T2& right)
{
return left + right;
}
template<class T>
T* func1(int n)
{
return new T[n];
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.1, d2 = 20.2;
Add(a1, a2);
Add(d1, d2);
// 推导实例化
cout << Add(a1, (int)d1) << endl;
cout << Add((double)a1, d1) << endl;
// 显示实例化
cout << Add<int>(a1, d1) << endl;
cout << Add<double>(a1, d1) << endl;
//用两个模板参数实现
cout << Add(a1, d1) << endl;
double* p1 = func1<double>(10);
return 0;
}
当一个非模板函数和模板函数同时存在时,如果类型匹配,会优先调用非模板函数。
5.类模板
类模板和函数模板的使用其实没有什么区别,主要的区别是类模板只能显示实例化,不然无法判断T的类型是什么。
类模板的定义格式如下:
cpp
template<class T1, class T2, ..., class Tn>
class // 类模板名
{
// 类内成员定义
};
下面是用类模板定义的一个栈
cpp
template<typename T>
class Stack
{
public:
Stack(int n = 4)
:_array(new T[n])
,_size(0)
,_capacity(n)
{}
~Stack()
{
delete[] _array;
_array = nullptr;
_size = _capacity = 0;
}
void Push(const T& x);
private:
T* _array;
size_t _capacity;
size_t _size;
};
template<class T>
void Stack<T>::Push(const T& x)
{
if (_size == _capacity)
{
T* tmp = new T[_capacity * 2];
memcpy(tmp, _array, sizeof(T) * _size);
delete[] _array;
_array = tmp;
_capacity *= 2;
}
_array[_size++] = x;
}
int main()
{
// 类模板都是显示实例化
Stack<int> st1; // int
st1.Push(1);
st1.Push(2);
st1.Push(3);
Stack<double> st2; // double
st2.Push(1.1);
st2.Push(1.1);
st2.Push(1.1);
Stack<double>* pst = new Stack<double>;
//...
delete pst;
return 0;
}
模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误,具体原因后面会讲
Stack是类名,Stack<int>才是类型
结语:
这篇文章全文由作者手写,图片由画图软件所制,无AI制作,希望各位博友能有所收获
欢迎各位博友的讨论,觉得不错的小伙伴,别忘了点赞关注哦~
