一个模板就是一个创建类或函数的蓝图或者说公式,其需要我们提供足够的信息,用以将蓝图(模板)准换为特定的类或是函数。(把重复的事交给编译器去做)
目录
1. 泛型编程
模板 --> 泛型编程,泛型编程(Generic Programming) 指在针对于多种数据类型上皆可操作。之前的C语言上是针对具体的类型进行编程,就会有很多的限制。于是C语言上,有一个很让人头疼的一个函数:Swap
void Swap(int& e1, int& e2)
{
int tmp = e1;
e1= e2;
e2= tmp;
}
void Swap(double& e1, double& e2)
{
double tmp = e1;
e1= e2;
e2= tmp;
}
// …………
一个工程里,针对于不同的类型交换我们需要创建不同的Swap函数,但他们的实现原理又是类似的。
2. 函数模板
2.1 函数模板概念与格式
函数模板是用于生成函数。只写一个 Swap 模板,编译器会根据 Swap 模板自动生成多个 Swap 函数,用以交换不同类型变量的值。
函数模板的写法如下:
模板定义以template开始,后跟一个模板参数列表,里用逗号分割一至多个模板参数。
template <class 类型参数1, class类型参数2, ...>
返回值类型 模板名( 形参表 )
{
函数体;
}其中的class 关键字也可以用typename 关键字替换。(切记:不能使用struct代替class)
template <typename 类型参数1, typename 类型参数2, ...>
返回值类型 模板名( 形参表 )
{
函数体;
}Note:在模板定义中,模板参数列表不能为空!
使用举例:
// 泛型编程 -- 模板
// 模板类型(定义:模板类型)
template<typename T>
void Swap(T& e1, T& e2)
{
T tmp = e1;
e1= e2;
e2= tmp;
}
T代表一个模板类型(虚拟类型),不是一个具体的类型,他只是一个名称,叫什么不重要。(可以随意取名字)
2.2 函数模板的原理
模板参数列表的作用很像函数参数列表。函数列表定义了若干特定类型的局部变量,但并未指出如何初始化它们。在运行时,调用者提供实参来初始化形参。
template<class T>
void Swap(T& e1, T& e2)
{
T tmp = e1;
e1 = e2;
e2 = tmp;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Swap(a1, a2);
Swap(d1, d2);
}
Note: Swap(a1, a2)与Swap(d1, d2)所使用的函数不是同一个!
其是通过我们所提供的模板函数,创建相对于其成立的函数,所以绝对不是同一个函数实现。并且,他们所调用指令是不一样的(不同的类型),调用的栈帧空间不一样。
使用的函数不是同一个,是通过函数模板的实例化。
模板的原理:将本应该我们所实现的事交给编译器去做。
针对于不同的类型,其实实际上还是进行了不同函数的创建,只不过是将,我们所应该创建的函数省去,只写一个模板,让编译器去创建。这样,也必然会让编译的时间变的更加长一点(编译器的工作更多了一些)。就是,我们干更少的活,编译器干更多的活。
2.3 函数模板错误
模板经过模板参数推演,推演参数实例化后才会生成代码。于是就会导致多种错误。
template<class T>
T Add(const T& e1, const T& e2)
{
return e1 + e2;
}
int main()
{
cout << Add(10, 1.12) << endl; // 推演实例化报错
}
其无法像我们平时所写的函数一样,进行隐形转换。因为在模板参数推演时,会因为无法推演出准确类型,导致推演实例化报错。
有三种方式解决这个问题:
- 传递前就进行转换。
编译器自动推演,隐式实例化。将其转换为同一个类型后,让编译器推演类型。
template<class T>
T Add(const T& e1,const T& e2)
{
return e1 + e2;
}
int main()
{
cout << Add(10, (int)1.12) << endl;
// cout << Add((double)10, 1.12) << endl;
}
- 指定模板参数推演的类型
显示实例化。不让编译器推演了,直接指定一个类型。
template<class T>
T Add(const T& e1, const T& e2)
{
return e1 + e2;
}
int main()
{
cout << Add<int>(10, 1.12) << endl; // 指定T的类型(模板参数有几个就指定几个)
// count << Add<double>(10, 1.12) << endl;
}
- 设置个数对应的模板参数
针对不同类型设置不同对应的模板参数,避免一个模板参数对应多个类型。
template<class T1, class T2>
T2 Add(const T1& e1, const T2& e2)
{
return e1 + e2;
}
int main()
{
cout << Add(10, 1.12) << endl;
}
template<class T>
T Func(int e)
{
return e + 10;
}
int main()
{
Func(10);
return 0;
}
参数是跟T没有关系的,编译器没有办法自动推演,必须要显示实例化,才可以调用。
template<class T>
T Func(int e)
{
return e + 10;
}
int main()
{
Func<int>(10);
return 0;
}
Note:保证传递给模板的实参支持模板所要求的操作,以及所进行的操作在模板中能正确工作,是调用者的职责。
2.4 模板参数的匹配原则
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
// 专门处理int的加法函数
int Add(int e1, int e2)
{
return e1 + e2;
}
// 通用加法函数
template<class T>
T Add(T e1, T e2)
{
return e1 + e2;
}
int main()
{
cout << Add(1, 2) << endl; // 与非模板函数匹配,编译器不需要特化
cout << Add<int>(1, 2) << endl; // 调用编译器特化的Add版本
return 0;
}
Add<int>(1, 2)的调用还可以被实例化为T为int类型的非模板函数。
-
对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。
// 专门处理int的加法函数
int Add(int e1, int e2)
{
return e1 + e2;
}
// 通用加法函数
template<class T>
T Add(T e1, T e2)
{
return e1 + e2;
}
int main()
{
cout << Add(1, 2) << endl;
cout << Add(1.1, 1.2) << endl;
return 0;
}
3. 类模板
类模板(Class template)是用来生成类的蓝图的。与函数模板的不同之处是,编译器不能为类模板推断模板参数类型。为了使用类模板,我们必须在模板名后的尖括号中提供额外信息,用来代替模板参数的模板实参列表。
3.1 类模板的定义格式
类模板的写法如下:
模板定义以template开始,后跟一个模板参数列表,里用逗号分割一至多个模板参数。
template<class T1, class T2, ..., class Tn>
class 类模板名{// 类内成员定义};其中的class 关键字也可以用typename 关键字替换。(切记:不能使用struct代替class)
template<typenameT1, typenameT2, ..., typenameTn>
class 类模板名{// 类内成员定义};Note:在模板定义中,模板参数列表不能为空!
3.2 类模板的使用
template<typename T>
class Stack
{
public:
Stack(size_t capacity = 4)
:_a(nullptr)
, _top(0)
, _capacity(0)
{
if (capacity > 0)
{
_a = new T[capacity];
_capacity = capacity;
_top = 0;
}
}
//…………
private:
T* _a;
size_t _top;
size_t _capacity;
};
int main()
{
// 类模板都是显示实例化
// 虽然他们用了一个类模板,但是Stack<int>,Stack<char> 是两个类型
Stack<int> st1;
Stack<char> st1;
return 0;
}
Note: 类模板的使用,必须在模板名后的尖括号中提供信息
- 将函数定义在类外
同一个文件中,模板支持声明与定义分离。
template<typename T>
class Stack
{
//…………
};
template<typename T>
void Stack<T>::Push(const T& n)
{
//…………
}
int main()
{
// 类模板都是显示实例化
// 虽然他们用了一个类模板,但是Stack<int>,Stack<char> 是两个类型
Stack<int> st1;
Stack<char> st2;
st1.Push(1);
//…………
return 0;
}
Note:模板不支持分离编译:声明放在.h(头文件) 定义放在.cpp(源文件)