前言:在之前的文章中我们说过,C++是比C语言更加高级的语言,那么如何体现C++的高级呢?看完本篇文章会给你答案。下面就来探索C++模板的奥秘。
模板的概念:模板指预先设计好的标准化框架或结构,用于快速生成特定格式的内容。它通过固定部分(不变元素)和可变部分(占位符)的组合,提高重复性工作的效率。
下面再举一个例子让大家加深对模板的理解:
最近中秋节就要来了,在中秋节每家每户都会吃月饼,而这些月饼的形状是怎么做到统一大小的呢?答案是——模板。月饼生产场会根据不同的口味更换月饼内部的馅,但是不同口味的月饼都是使用一个摸具压出来的,从而就能获得形状大小均一样,口味不一样的月饼了。所以模板就可以抽象的理解成是一个磨具,每一种不同口味的月饼通过这一个摸具都能变成一个大小形状均一样的月饼。

在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;
}这就是我们在C语言阶段所写的不同类型的交换函数。试想:如果有100种甚至更多的类型需要比较如果我们还要一个一个的去写的话,那么浪费人力不说效率是极低的。
所以泛型编程就是模板的作用之一,它就像制作月饼的那个摸具一样,无论什么种类的月饼它都能压出来。
//函数模板的格式使用 template关键字 typename定义的是模板参数
template<typename T>
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}注意:定义模板要以
template关键字开头,尖括号内跟上模板参数类型模板参数以typename/class关键字开始。(类型模板参数代表未知的数据类型) 然后将函数内部有类型的地方使用模板参数替换,比如我的模板参数使用的是T,那么我定义的tmp的类型就由原来具体的int,double类型替换成T(未知类型)。
C++中的模板分成函数模板和类模板。
其实在上面的例子引用中我们就已经介绍了函数模板。但要注意的是:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
template<typename T>
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}有了上面的认识,接着我们就来说说它的原理:
函数模板的原理实际上跟我们前面说的用摸具制作月饼大同小异,函数模板更像是一张蓝图,它本身不是函数是编译器用使用方式产生特定具体类型函数的模具。

模板实例化:模板实例化就是一个得到具体类型的过程。比如函数模板的实例化就是将有具体类型的参数传给这个函数模板,让
T有了确定的类型。
比如:
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
//a1和a2是确定的类型int 因此将a1和a2传给Add函数 Add函数生成了一个int的比较函数这一过程就是函数模板的实例化过程
Add(a1, a2);
Add(d1, d2);
//上面的传参要么都是int 要么都是double如果各传一个呢?
//Add(a1,d2);
//这时候编译器就会报错,因为我们此时提供了int和double两个参数而模板参数只定义了一个参数,换句话说我们定义的类型模板参数T不知道到底是接收int还是double 而且在模板中编译器一般不会进行类型转换操作。
//所以要解决这一问题有两种方案:1,将其中一个强转(如下) 2,使用显示实例化
Add(a, (int)d);
return 0;
}上面函数模板的实例化是隐式实例化、隐式实例化实际上就是实参类型推演模板参数。 隐式实例化会引发一个问题就是面对两个或两个以上的类型参数,在函数模板参数只有一个的情况下编译器会因为不知道接收哪个类型参数而报错。 解决这种问题的第二种方法就是显示实例化:
显示实例化:就是在函数名后的尖括号<>中指定模板参数的实际类型
int main(void)
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
//显示实例化(显示指定模板参数)
Add<int>(a1, d1);
Add<double>(a2,d2);
return 0;
}显示实例化:实际上就是在尖括号内部指定模板参数,从而让模板生成指定的加法函数。这样编译器就能明确生成具体函数就不会报错了。
假设一个普通函数和一个函数模板同时存在会先调用哪一个呢?
#include<iostream>
using namespace std;
// 专门处理int的加法函数
int Add(int left, int right)
{
//调用此函数前先打印一下该函数
cout <<"Add(int left, int right)" << endl;
return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
//调用此函数前先打印一下该函数
cout << "T Add(T left, T right)" << endl;
return left + right;
}
int main()
{
Add(1, 2);
cout << endl;
//显示调用
Add<int>(1, 2);
return 0;
}
从上面的结果我们能很明显的看到:当普通函数与函数模板同时存在时编译器会优先调用普通函数。如果想要调用函数模板生成的函数就要使用显式实例化。
到这可能会有人有疑问:只要普通函数与函数模板同时存在一定优先调用普通函数吗?当然不是,如果模板可以产生一个具有更好匹配的函数, 那么将选择模板,比如:
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
return left + right;
}
int main()
{
Add(1, 2);
Add(1, 2.2);
return 0;
}在第二个
Add函数的调用中,此时普通函数也可以调已经存在的Add函数用但是由于强转会丢失精度,而函数模板能实例化一个更加适合的函数,所以就优先调用板生成的函数。
注意:函数模板是不能进行类型转换的,普通函数可以自动进行类型转换
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义....
};类模板的定义与函数模板类似,
template关键字尖括号内部跟类型模板参数,模板参数可以有多个、类内部的成员的类型根据需求替换成类型模板参数T。
#include<iostream>
using namespace std;
// 类模版
template<typename T>
class Stack
{
public:
Stack(size_t capacity = 4)
{
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
void Push(const T& data);
private:
T* _array;
size_t _capacity;
size_t _size;
};
//声明和定义分离 要加tamplate不然编译器分不清是普通函数还是模板函数
template<class T>
void Stack<T>::Push(const T& data)
{
// 扩容
_array[_size] = data;
++_size;
}
int main()
{
Stack<int> st1; // int
Stack<double> st2; // double
return 0;
}关于类模板有两点注意事项:
以上就是本篇文章的所有内容了,感谢各位大佬观看,制作不易还望各位大佬点赞支持一下!有什么问题可以加我私信交流! |
|---|