C++内存管理+模板入门知识点

Posted Booksort

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++内存管理+模板入门知识点相关的知识,希望对你有一定的参考价值。

内存管理

C

malloc();
free();

C++

new
delete

内置类型

对于C++和C中而言,如果操作的对象是内置类型,
new/mallocdelete/free基本没什么区别

new/delete操作的是单个元素空间
new[]/delete[]操作的是连续空间

自定义类型

new/new[]和delete/delete[]实际上是调用 operator 重载函数

new的原理

  1. 调用operator new函数申请空间

  2. 在申请的空间上执行构造函数,完成对象的构造

delete的原理

  1. 在空间上执行析构函数,完成对象中资源的清理工作
  2. 调用operator delete函数释放对象的空间

new T[N]的原理

  1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申
  2. 在申请的空间上执行N次构造函数

delete[]的原理

  1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
  2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

malloc/freenew/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在释放空间前会调用析构函数完成空间中资源的清理

内存泄漏

进行内存管理的时候要小心内存泄漏

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。
内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害

长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

提问:内存泄漏是指针丢失还是内存丢失

答:指针丢失。
内存泄漏是无法操作那块内存空间,无法再通过指针操作内存空间。而内存一直是在那块空间中。比如,指针指向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会自己推断返回值的类型。
当然,肯定是范围小的数据类型会升级为范围大的数据类型。

模板参数匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函模板还可以被实例化为这个非模板函数
  2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
  3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

例子:

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版本
 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)//允许没有<T>
		:_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++的类与对象

以上是关于C++内存管理+模板入门知识点的主要内容,如果未能解决你的问题,请参考以下文章

C++入门篇(15)之模板知识进阶

C++模板初阶 | 内存管理

C++内存管理+模板

[C++系列] 66. 超详解C++阶段性总结思维导图

C++从入门到入土第六篇:C/C++内存管理

C++入门C/C++的内存管理介绍