【C++】模板初阶

【本节内容】

泛型编程
函数模板
类模板

1.泛型编程

首先问大家一个问题,如果我们想实现一个通用的交换函数,该怎么做呢?
我想大家会想到用重载,将不同类型参数的函数都写出来,这是一个解决的办法,不过emmm…自己看~

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;
}

如果这样写的话,存在以下几个问题:

  1. 重载的函数仅仅只是参数类型不同,使得代码的复用率太低,而且只要有新的类型出现 我们就要增加一个对应的函数,这未免太麻烦了吧
  2. 代码的可维护性太差,只有一个函数出错时可能所有的重载都要重新写一遍,这也太可怕了!

那么我们应该怎么解决呢?
这时懒人们就想,那能不能直接告诉编辑器一个模子,让编辑器根据不同的类型利用该模子生成属于自己的代码呢?巧的是,果然有这样的好事!!
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础。
模板主要有两种:函数模板和类模板

2.函数模板

2.1 概念:函数模板代表了一个函数的家族,该函数模板与类型无关,在使用时才被参数化,根据实参类型产生函数的特定类型版本。
2.2 函数模板的格式:
template <typename T1,typename T2,typename T3>
返回值类型 函数名 (参数列表){}

例如:

template <typename T>
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

注意:typename 是用来定义模板参数类型的关键字,也可以使用class(但不能用struct!!)

2.3 函数模板的原理
模板本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以模板就是将本来应该我们做的重复的事情交给了编译器去完成。
在编译过程中,对于模板的使用,编译器根据传入的实参类型推演生成对应类型的函数以供我们的调用。
例如刚才的那段代码,当我们用int 类型的实参使用函数模板时,编译器通过对实参类型的推演,将T确定为int 类型,然后产生一份专门处理int类型的代码,从而实现。对于其他类型也都一样的处理过程。
在这里插入图片描述
2.4 函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。
模板函数的实例化包括两种:隐式实例化,显式实例化。
1)隐式实例化: 让编译器根据实参推演模板参数的实际类型

template<class T>
T Add(const T&left,const T&right)
{
	return left + right;
}

int main()
{
	int a1 = 10, a2 = 20;
	double b1 = 10.1, b2 = 20.3;
	Add(a1, a2);        //这句的运行结果是30
	system("pause");
	return 0;
}

Add(a1, b2); //这句无法通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型。此时它通过a1将T推演为int,通过b2将T推演为double,但模板的参数列表中只有一个T,所以编译器会因无法确定T的类型而报错
注意:在模板中,编译器一般不会进行类型转化操作,因为一旦转化出问题,编译器就要背黑锅

因为无法推演类型而出错时的处理方式有两种:
a.用户强制转化 Add(a1, (int)b2);
b.使用显示实例化 Add<int>(a1, b2);
2.显式实例化: 在函数名后的<>中指定模板参数的实际类型

int main()
{
	int a = 10;
	double b = 1.2;
	Add<int>(a, b);
	return 0;
}

若类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
2.5 模板参数的匹配原则
a.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化成这个非模板函数

//通用处理加法的函数
template<class T>
T Add(const T&left,const T&right)
{
	return left + right;
}
// 只处理int类型的加法
int Add(const int &left, const int &right)
{
	return left + right;
}

void  Test1()
{
	Add(1, 2);      //与非模板函数匹配,编译器无需特化
	Add<int>(1, 2);     //调用编译器特化的Add版本
}

b.对于非模板函数和同名函数,如果其他条件相同,在调用时会优先调用非模板函数,如果模板可以产生一个具有更好匹配的函数就选择模板

void Test2()
{
	Add(1, 2);           //与非模板函数完全匹配,调用非模板函数
	Add(1, 1.2);         //函数模板能生成更加匹配的版本,调用函数模板
}

c.模板函数不允许自动类型转换,但普通函数可以发生自动类型转换

3.类模板

3.1 类模板的定义格式

template<class T1,class T2,.....>
class 类模板名
{
	   //类内成员定义
};
template<class T>
class Vector
{
public:
	Vector()
		: _pData(nullptr)
		, _size(10)
		, _capacity(10)
	{}
	~Vector();      //用于演示 在类中声明,在类外定义
private:
	T* _pData;
	size_t _size;
	size_t _capacity;
};
// 注意:类模板中的函数放在类外定义时,需要加模板参数列表
template<class T>
Vector<T> ::~Vector()
{
	if (_pData)  { delete[] _pData; }

}

注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具

3.2 类模板的实例化
类模板的实例化与函数模板的实例化不同,类模板的实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中。
类模板的名字不是真正的类,而实例化的结果才是真的类。
在刚才的代码中,vector 是类名,下面vector< int >才是类型

void Test()
{
	Vector<int> s1;
	s1.pushback(1);
	for (size_t i = 0; i < s1.size(); ++i)
	{
		cout << s1[i] << "  ";
	}
	cout << endl;
}

猜你喜欢

转载自blog.csdn.net/ly_6699/article/details/88753081