模板
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
void Swap(double* x, double* y)
{
double tmp = *x;
*x = *y;
*y = tmp;
}
void Swap(char* x, char* y)
{
char tmp = *x;
*x = *y;
*y = tmp;
}
如上述所示,我们在实现函数的时候,有很多函数会像上述一样,实现过程差不多,但是由些许不同,那么我们都把这些实现出来会显得很呆,C++祖师爷也想到了,所以设计了模板。
函数模板
像上述的三个函数,算法相同,只是 参数的类型不同,那么我们就可以定义 函数模板,这样不管是什么类型,都可以使用这个函数模板,来实现相同的 算法。
语法:
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
上述的 typename 是用来定义模板参数关键字,也可以使用 class (注意: 不能使用 struct 来代替 class)
其实就是在模板出来的早期用的就是 clsss ,但是后面觉得不好区分就重写定义了 typename 这个关键字。
像上述交换变量的 函数我们就不用写 三个了,写一个就行了:
template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
我们不管使用内置类型还是自定义类型都可以实现上述的 算法:
而且,模板函数并不是调用的 这个模板,而是 编译器帮我们实现了 这些重载函数,然后去调用这些重载函数:
我们发现上述 两个 Swap 函数的地址不同。
编译器会根据我们 传入的参数来推演,推演出参数的类型:
编译器推导出之后,就会自己实现这个模板当前对应类型的 实例化函数。
这里其实就是 模板的 实例化,和对象的实例化有点类似。
而且,这里推导的类型,可以是任意类型,不管是内置类型 还是 自定义类型都可以推导出来,也就是说不管是什么类型都可以使用 模板推导出来。
像上述这样使用 模板来实现的 编程,也被称为是 泛型编程,因为这里的 函数并不是针对某一种 类型,而是针对 很多的类型。
模板的 " <> " 当中其实和 函数的参数列表差不多,我们可以指定多个参数类型,同时 模板当中的参数也可以是 函数的返回值类型:
对于 template 函数模板的作用范围就是这个函数。
模板函数的实例化
模板函数的实例化分为两种:
隐式实例化,这是编译器自己去推导出来的实例化函数,但是有的时候,对应的模板参数并不能实现 我们想要的参数类型,如下例子:
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;
cout << Add(a1, a2) << endl;
cout << Add(d1, d2) << endl;
cout << Add(a1 , d1 ) << endl; // 不能编译通过
return 0;
}
我们在模板当中只定义了 一个 T 的模板参数,但是我们传入的参数确是 int 和 double 类型的,这样做编译器就很为难,他不知道应该 使用哪一个 类型作为推导出来的类型,报错:
当我们遇到这种问题的时候,有两种解决方式,第一种就是进行强制类型转换,像上述例子就是把其中一个参数 强转成 另一个参数的类型:
cout << Add(a1, (int)d1) << endl;
cout << Add((double)a1, d1) << endl;
而且向上述 使用 强制转换的方式 实现传参的时候要注意,上述是 传入的是引用:因为强制转换会产生临时变量,像上述的强制转换的 变量传参,不是直接传这个 d1 或者是 a1 这个变量引用给函数形参,而是把强制类型转换所产生的临时变量 赋值传给 函数形参,而 临时变量具有常性,所以,如果我们在函数形参位置不加 const 修饰的话,就会发生权限的放大 ,就会报错:
第二种,就是我们模板实例化的 第二种形式:显式实例化。
cout << Add<int>(a1, d1) << endl;
cout << Add<double>(a1, d1) << endl;
这个时候就不需要 编译器自己去进行参数类型的推演了,向上述代码,第一种就直接实例化为了 int ,第二种就是直接实例化为了 double。
显式实例化的 使用的场景最多是如下的 这种形式:
有些函数不能自动推导 参数类型,只能使用显示实例化。
template<typename T>
T* Carray(int n)
{
return new T[n];
}
int main()
{
Carray(10);
return 0;
}
编译器进行推导的时候,必须通过参数进行推导,返回值,或者是函数其中的 算法来推导等等的这些方式都是不行的。
所以这里就要使用 显示实例化:
Carray<double>(10);
通过显示实例化,在指定 模板当中的参数的类型,这样就可以实现上述函数的调用。
类模板
C++当中想到,在类当中也有类似的,和上述函数算法相同,但是参数类型不同的问题,所以在C++ 当中 有类模板来解决上述问题。
语法:
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
我们在C当中实现 栈的时候,是把存储的数据类型 用 typedef 来重新命名,这样做的好处是以后我们想要栈存储其他类型的 数据的时候,只需要修改 typedef 这一行代码就行了,但是这样做只能修改一个 栈的实现。当我现在想要同时实现多个 存储不同数据类型的栈的时候,typedef 就不能实现了。
像上述功能,在C++当中使用 类模板就可以很方便的实现:
template<class T>
class Stack
{
public:
Stack(size_t a = 3)
{
cout << "Stack(size_t a = 1)" << endl;
_array = new T[capacity];
if (_array == NULL)
{
perror("malloc fail");
return;
}
_capacity = a;
_size = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
protected:
T* _array;
int _size;
int _capacity;
};
那么对于上述实现的类模板,我们就可以这样来使用 这些类了:
int main()
{
Stack<int> s1;
Stack<char> s1;
Stack<double> s1;
return 0;
}
因为我们上述类的构造函数,没有使用 T 这个模板参数,所以,构造函数不一定都要 使用 类模板当中 参数,其实构造函数就不一定需要 使用 推演,像上述的初始化方式函数很香的。
那么类模板当中也可以传入多个参数,如果我们有需求的话。
对于 template 类模板的作用范围就是这个类。
如果类模板当中的成员函数在类外面 定义的话,和普通类的 类外定义有些许不一样。
- 首先,因为 对于 template 类模板的作用范围就是这个类 那么当我们类外定义的时候,编译器就不认识函数当中的 T 这个模板参数了。所以我们在类外进行 定义成员函数的时候,需要给这个函数重新声明这个 T 模板参数。
- 其次,对于普通类,类名就是类型,我们在调用普通类的时候,都是直接使用 类名来作为这个 对象的 类的类型的;但是 模板类的类名不是类型,类模板的类型是 类名<模板类型> 这样的形式。所以,我们在 用 " :: " 来指明 这个函数是哪一个 域当中函数的时候,需要使用 类名<模板类型> 这样的方式来表明这个函数的域。
那么根据上述两点,我们在类模板外声明 其中的成员函数的时候,应该这样来实现:
template <class T>
Stack<T>::Stack()
{
cout << "Stack(size_t a = 1)" << endl;
_array = new T[capacity];
if (_array == NULL)
{
perror("malloc fail");
return;
}
_capacity = a;
_size = 0;
}