内存管理
C
malloc();
free();
C++
new
delete
内置类型
对于C++和C中而言,如果操作的对象是内置类型, new/malloc 和delete/free 基本没什么区别
new/delete 操作的是单个元素空间 new[]/delete[] 操作的是连续空间
自定义类型
new/new[ ]和delete/delete[] 实际上是调用 operator 重载函数
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来释放空间
malloc/free 和new/delete 的 共同点是:都是从堆上申请空间,并且需要用户手动释放。
不同的地方是:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间 后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
内存泄漏
进行内存管理的时候要小心内存泄漏
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存 的情况。 内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制 ,因而造成了内存的浪费。
内存泄漏的危害
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
提问:内存泄漏是指针丢失还是内存丢失
答:指针丢失。 内存泄漏是无法操作那块内存空间,无法再通过指针操作内存空间。而内存一直是在那块空间中。比如,指针指向0x00bbff80 就是那块空间的地址,当指针丢失,我们不知道空间的地址的多少,但那块空间的实际地址依然是0x00bbff80 ,其空间地址不会因为指针丢失而改变位置。
模板
泛型编程
C++提供了模板来处理很多问题 介绍一个概念:泛型编程
就是通过模板来解决问题,如:swap交换
C++可以通过函数重载来交换几个类型的数据,但依旧是效率太低,而且,可维护性太差,如果一个函数有误,那所有都需要整改。
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
void Swap(char& left, char& right)
{
char temp = left;
left = right;
right = temp;
}
函数模板
所以引入了函数模板的概念:允许以泛型(任意类型)的方式来定义函数 格式:
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
如:交换函数
template<typename T>
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
或者
template<class T>
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
我更推荐用关键字class 。 这个就是泛型编程。我提供一个模板,当使用时,会根据接受参数的类型完成函数功能。
模板就是图纸,可以根据各种需求来完成功能。 举个例子
#include<iostream>
using namespace std;
template <class T>
void swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main(void)
{
int i1 = 10, i2 = 100;
double d1 = 10.0, d2 = 100.0;
swap(i1, i2);
swap(d1, d2);
return 0;
}
提问:当这个swap() 函数被调用了,是调用了同一个函数,还是不同的函数?
答:不同的函数
理由: 这是查了汇编代码,当调用两个函数时,call两个函数的地址是不一样的,所以明显是调用了两个不同的函数、
这就涉及到另一个概念:函数模板的实例化 。
简单来说就是通过一张图纸来建造不同样子的房子。 以上面程序为例,函数模板就是图纸,当调用函数的时候就是建造不同的房子,所以就是调用了不同的函数。 隐式实例化:编译器会根据参数的类型,自动推断类型,然后去创建函数的实力化。
swap(i1, i2);
swap(d1, d2);
显式实例化:不需要编译器去推断,显式指定类型。
swap<int>(i1, i2);
swap<double>(d1, d2);
当然,还有要是不确定两个类型是否相同,但还是能转换。 为了这个比较没啥用,还无意义的要求
#include<iostream>
using namespace std;
template <class T1,class T2>
void Swap(T1& a, T2& b)
{
T1 tmp = a;
a = b;
b = (T2)tmp;
}
int main(void)
{
int i1 = 10, i2 = 400;
double d1 = 10.0, d2 = 100.0;
cout << i1 << " " << d2 << endl;
Swap(i1, d2);
cout << i1 << " " << d2 << endl;
Swap(d1, d2);
return 0;
}
不同类型数据交换会造成数据丢失,没什么用。 范围小的数据类型会截断范围大的数据类型的数据,会造成数据丢失。
对于一些加法,减法运算,对于类型未知,可以这样编写。
#include<iostream>
using namespace std;
template <class T3, class T4>
auto Add(T3 a, T4 b)
{
return a + b;
}
int main(void)
{
int i1 = 10, i2 = 400;
double d1 = 10.2, d2 = 20.3798;
auto ret = Add(i1, d2);
cout<<ret<<endl;
return 0;
}
auto 会自己推断返回值的类型。 当然,肯定是范围小的数据类型会升级为范围大的数据类型。
模板参数匹配原则
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函模板还可以被实例化为这个非模板函数
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
例子:
int Add(int left, int right)
{
return left + right;
}
template<class T>
T Add(T left, T right)
{
return left + right;
}
void Test()
{
Add(1, 2);
Add<int>(1, 2);
Add(1,2.0);
}
类模板
类模板和类又有些区别。 这才是他们之间的关系。 类模板:未知类型的类
举个例子:
template <class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{}
vector(const vector<T>& vt)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
size_t Capacity = vt.capacity();
this->reserve(Capacity);
for (auto e : vt)
{
push_back(e);
}
}
~vector()
{
if (_start)
{
delete[] _start;
}
_start = _finish = _end_of_storage = nullptr;
}
vector<T>& operator= (const vector<T>& x)
{
if (this != &x)
{
vector<T> tmp(x);
this->swap(tmp);
}
return *this;
}
T& operator[](size_t pos)
{
assert(pos < _size);
return _start[pos];
}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
这是我迷你实现过的vector 类模板(只给你们看一部分)。 在使用过程中,类模板会根据类的类型来实例化成相应类型的类
vector<int>
vector<double>
vector<string>
这些才是类,类模板是不知道类型的。
注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>
vector<T>::~vector()
{
if (_start)
{
delete[] _start;
}
_start = _finish = _end_of_storage = nullptr;
}
模板初阶大概就这些东西,谢谢观看。 对于C++入门知识点不清楚的,请移步
C++入门知识点
系统学习C++的类与对象
|