C++允许使用函数模板,对于功能相同而数据类型不同的一些函数,不必一一定义各个函数,可以定义一个可以对任何类型变量进行操作的函数模板,在调用函数时,系统会根据实参的类型,取代函数模板中的类型参数,得到具体函数。
对于类的声明来说,也有同样的问题;有时两个或多个类,其功能是相同的,仅仅是数据类型不同。显然这种重复性工作是不必要的,C++在发展后期增加了模板(template)的功能,提供了解决这类的途径;可以声明一个通用的类模板,它可以有一个或多个虚拟的类型参数,创建可以重用的组件,这些组件可以处理多种数据类型,而不仅仅是单一的数据类型。
一、类模板定义
声明类模板时要增加一行:
template <class 类型参数名>
由于类模板包含类型参数,因此又称为参数化的类。如果说类是对象的抽象,对象是类的实例,则类模板是类的抽象,类是类模板的实例。复用类模板可以建立含各种数据类型的类。例如定义一个获取各种类型数组中最大小、最小值的类,代码如下:
#include <iostream>
#include <array>
using namespace std;
template<class T, size_t size>
class Compare_array{
private:
array<T, size> Arr; // 使用std::array来存储数组
public:
// 获取函数
Compare_array(T (&arr)[size]){ //接收一个数组的引用
// 使用std::copy来复制数组内容
std::copy(std::begin(arr), std::end(arr), std::begin(this->Arr));
}
// 获取最大值
T get_max(){
T max = Arr[0];
for(size_t i = 0; i < size; i++){
if(Arr[i] > max) max = Arr[i];
}
return max;
}
// 获取最小值
T get_min(){
T min = Arr[0];
for(size_t i = 0; i < size; i++){
if(Arr[i] < min) min = Arr[i];
}
return min;
}
};
int main(){
// int型数组
int nums_int[] = { 100, 150, 38, 15, 90, 70, 66};
// 实例化时须提供数据类型和数组大小
Compare_array <int, sizeof(nums_int) / sizeof(nums_int[0])> ca1(nums_int);
cout <<"int array max:" <<ca1.get_max() <<", min:" <<ca1.get_min() <<endl;
// float型数组
float nums_float[] = { 10.05f, 15.5f, 38.15f, 15.22f, 90.18f, 70.9f, 66.85f};
Compare_array <float, sizeof(nums_float) / sizeof(nums_float[0])> ca2(nums_float);
cout <<"float array max:" <<ca2.get_max() <<", min:" <<ca2.get_min() <<endl;
// double型数组
double nums_double[] = { 12343, 644311, 55327, 846445, 98875, 343677};
Compare_array <double, sizeof(nums_double) / sizeof(nums_double[0])> ca3(nums_double);
cout <<"double array max:" <<ca3.get_max() <<", min:" <<ca3.get_min() <<endl;
return 0;
}
运行结果可见,通过类模板定义类Compare_array,可以处理各数据类型的数组,获取最大值和最小值,如下图:
对于上述代码几个细节说明:
- 在C++中,类模板的成员数组需要指定大小,所以这里使用std::array容器来代替;
- 由于数组不能通过赋值操作来复制,所以这里使用std::copy来逐个元素地复制;
- 构造函数形参应该是一个对数组的引用,需要包含size_t size作为模板参数;
- Compare_arr中std::array<T, size>作为私有成员,它在编译时根据size参数确定大小,所以类模板实例化时要提供数组的大小。
二、函数模板
相必很多人已经学过函数模板了,上述代码存在很多重复代码,咱们用函数模板将程序再优化一下,顺便复习下函数模板相关知识。代码如下:
#include <iostream>
#include <array>
using namespace std;
template<class T, size_t size>
class Compare_array{
private:
array<T, size> Arr; // 使用std::array来存储数组
public:
// 获取函数
Compare_array(T (&arr)[size]){ //接收一个数组的引用
// 使用std::copy来复制数组内容
std::copy(std::begin(arr), std::end(arr), std::begin(this->Arr));
}
// 获取最大值
T get_max(){
T max = Arr[0];
for(size_t i = 0; i < size; i++){
if(Arr[i] > max) max = Arr[i];
}
return max;
}
// 获取最小值
T get_min(){
T min = Arr[0];
for(size_t i = 0; i < size; i++){
if(Arr[i] < min) min = Arr[i];
}
return min;
}
};
// 定义函数模板 显示最大和最小值
template<typename N, size_t S>
void showMaxMin(N (&arr)[S], const char* typeName){
// 实例对象
Compare_array <N, S> ca(arr);
// 显示最大和最小值
cout <<typeName <<" max:" <<ca.get_max() <<", min:" <<ca.get_min() <<endl;
}
int main(){
// int型数组
int nums_int[] = { 100, 150, 38, 15, 90, 70, 66};
showMaxMin(nums_int, "int");
// float型数组
float nums_float[] = { 10.05f, 15.5f, 38.15f, 15.22f, 90.18f, 70.9f, 66.85f};
showMaxMin(nums_float, "float");
// double型数组
double nums_double[] = { 12343, 644311, 55327, 846445, 98875, 343677};
showMaxMin(nums_double, "double");
return 0;
}
和前面还是一样的,但代码看起来比之前简洁了许多,优化掉了许多不必要且重复性较高的代码。结果如下图:
三、数组引用的区别
想必有些人已发现了Compare_array构造函数形参时和showMaxMin函数中形参都是数组的引用,但在第一个示例中Compare_array实例时必须要传入数组大小,而showMaxMin函数中却不需要。
Compare_array类模板使用了两个模板参数:T(数组元素的类型)和size(数组的大小)。当实例化Compare_array类时,需要为这两个模板参数提供具体的值。这是因为在类的内部,std::array<T, size>成员变量Arr的大小需要在编译时确定。由于C++标准库中的std::array是一个固定大小的容器,其大小必须在模板实例化时明确指出。
showMaxMin函数模板也是用了两个模板参数:N(数组元素的类型)和S(数组的大小)。当传递一个数组给showMaxMin函数时,编译器会自动推断出数组元素的类型N和数组的大小S,作为函数模板的参数。这是通过模板参数的类型推导(type deduction)机制实现的。
所以在showMaxMin函数内部,创建了一个Compare_array<N, S>类型的对象ca,并将传递进来的数组arr作为构造函数。此时由于N和S已被推断出来,可以直接使用它们来实例化Compare_array类。
总的来说,Compare_array类需要显示提供大小参数,是因为它内部std::array成员需要在编译时确定大小。而showMaxMin函数模板能够自动获取数组大小,是因为编译能够根据传递给函数的实际数组参数进行类型推导。
四、归纳
归纳以上的内容,可以这样声明和使用类模板:
1)先写一个实际的类。
2)将此类中准备改变的类型名改用一个自己指定的虚拟类型名。
3)在类声明前面加入一行,格式为:
template<class 虚拟类型参数>
示例代码如下:
template<class T, size_t size>
class Compare_array{
// 略...
}
4)用类模板定义对象时用以下形式:
类模板名 <实际类型名> 对象名;
类模板名 <实际类型名> 对象名(实参列表)
示例代码如下:
Compare_array <int, array_length> ca1(nums_int);
5)如果在类模板外定义成员函数,应写成类模板形式:
template<class 虚拟类型参数>
函数类型 类模板名 <虚拟类型参数> :: 成员函数名(函数形参表列){ ...... }