函数模板
本阶段主要针对C++泛型编程和STL技术做详细讲解 , 探讨C++更深层的使用。
21.0 模板的概念
模板就是建立通用的模具,大大提高复用性。
例如生活中的模板:
一寸照片模板,PPT模板,word模板
模板的特点:
- 模板不可以直接使用,它只是一个框架。
- 模板的通用并不是万能的。
C++中另一种编程思想称为泛型编程,主要利用的技术就是模板。
C++提供两种模板机制:函数模板和类模板
21.1 函数模板语法
函数模板作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体指定,用一个虚拟的类型来表示。
语法:
template<typename T>
函数声明或者定义
//其中<>为函数模板的参数列表。
解释:
- template – 声明创建模板
- typename – 表面其后面跟着的符号是一种数据类型,可以用class代替
- T – 通用的数据类型,名称可以替换,通常为大写字母。
模板的使用语法:
1.自动类型推导 函数(参数)
2.显示指定类型 函数<数据类型>(参数)
示例:
#include<iostream>
using namespace std;
//函数模板
//交换两个整型函数
void swapInt(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
//交换两个浮点型函数
void swapDouble(double& a, double& b)
{
double temp = a;
a = b;
b = temp;
}
//函数模板
template<typename T> //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
//1.自动类型推导 mySwap(a,b)
//2.显示指定类型 mySwap<数据类型>()
void test1_01()
{
int a = 10, b = 20;
cout <<"a = " << a <<" b = "<< b << endl;
swapInt(a, b);
cout << "a = " << a << " b = " << b << endl;
mySwap<int>(a, b);
cout << "a = " << a << " b = " << b << endl;
double c = 5.25, d = 10.25;
cout << "c = " << c << " d = " << d << endl;
swapDouble(c, d);
cout << "c = " << c << " d = " << d << endl;
mySwap<double>(c, d);
cout << "c = " << c << " d = " << d << endl;
float e = 1.2, f = 3.5;
cout << "e = " << e << " f = " << f << endl;
mySwap(e, f);
cout << "e = " << e << " f = " << f << endl;
}
int main()
{
test1_01();
system("pause");
return 0;
}
21.2 函数模板注意事项
注意事项:
- 自动类型推导,必须推导出一致的数据类型T,才可以使用。
- 模板必须要确定出T的数据类型,才可以使用。
示例:
#include<iostream>
using namespace std;
template<class T> //typename和class是相同的
void mySwap2(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
void test2_01()
{
int a = 10, b = 20;
cout << "a = " << a << " b = " << b << endl;
mySwap2(a, b);
cout << "a = " << a << " b = " << b << endl;
double c = 20.0;
//mySwap2(a, c); 会报错,因为传入数据类型不一致.
}
template<class T>
void func2()
{
cout << "func的调用" << endl;
}
void test2_02()
{
//func2(); 会报错,因为在这里编译器不能推导数据类型是什么
func2<int>();
}
int main()
{
test2_01();
system("pause");
return 0;
}
总结:
使用模板时必须确定出通用数据类型T,并且能够推导出一致的类型。
21.3 函数模板案例-数组排序
案例描述:
- 利用函数模板封装一个排序的函数, 可以对不同数据类型数组进行排序
- 排序规则从大到小,排序算法为选择排序
- 分别利用char数组和int数组进行测试
#include<iostream>
using namespace std;
template<class T>
int CoutArr(T& arr)
{
return sizeof(arr)/sizeof(arr[0]);
}
template<typename T>
void ArrSort(T arr[],int len)
{
for (int i = 0; i < len; i++)
{
int max = i; //假设当前的下标是最大的
for (int j = i+1; j < len; j++)
{
if (arr[j] > arr[max])
{
max = j; //如果此元素比假设的元素大,就更新下标
}
}
if (max != i) //如果下标换了,就交换元素。
{
T temp = arr[i];
arr[i] = arr[max];
arr[max] = temp;
}
}
}
template<typename T>
void printArr(T arr[],int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
void test3_01()
{
char charArr[] = "bdafeg";
printArr(charArr, CoutArr(charArr));
ArrSort(charArr,CoutArr(charArr));
printArr(charArr, CoutArr(charArr));
cout << "--------------" << endl;
int intArr[] = {
2,4,3,1,7,0,3 };
printArr(intArr, CoutArr(intArr));
ArrSort(intArr,CoutArr(charArr));
printArr(intArr, CoutArr(intArr));
}
int main()
{
test3_01();
system("pause");
return 0;
}
参考 : 在函数中计算数组大小
21.4 普通函数与函数模板的区别
- 普通函数调用时可以发生自动类型转换(隐士类型转换)。
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换。
- 如果利用显式指定类型的方式,可以发生隐式类型转换。
示例:
#include<iostream>
using namespace std;
//普通函数可以发生隐式类型转换
//函数模板如果使用自动类型推导,则不可以发生隐式类型转换
//函数模板如果使用显示指定类型,则可以发生隐式类型转换
//普通函数
int myAdd01(int a, int b)
{
return a + b;
}
template<class T>
T myAdd02(T a,T b)
{
return a + b;
}
void test4_01()
{
int a = 10, b = 20;
cout << myAdd01(a, b) << endl;
cout << myAdd02(a, b) << endl;
char c = 'c';
cout << myAdd01(a, c) << endl; //c会转换会int型,就是隐式转换为ASCII码值。
//cout << myAdd02(a, c) << endl; //会报错,因为编译器不知道应该将c转化为int型还是把a转化为char型,无法确定
cout << myAdd02<int>(a, c) << endl;
cout << myAdd02<char>(a, c) << endl; //显式指定类型后,则可以隐式类型转换。
}
int main()
{
test4_01();
system("pause");
return 0;
}
建议使用的时候使用显示指定类型,减少出错。
21.5 重名普通函数与函数模板的调用规则
调用规则如下:
- 如果函数模板和普通函数都可以实现,优先调用普通函数(即使只声明了普通函数而没有实现出现报错,也不会调用函数模板)。
- 可以通过空模板函数列表来强制调用函数模板(语法:
函数名<>(参数列表)
,只要有<>,无论<>中是否空,都会强制调用函数模板)。 - 函数模板也可以发生重载(与普通函数没区别)。
- 如果函数模板可以产生更好的匹配,优先调用函数模板(比如调用普通函数需要隐式类型转换,那么会优先调用函数模板)。
21.6 函数模板的局限性
局限性:
函数模板仅仅是使得传入函数的参数数据类型更加灵活,对于函数的重载等功能并没有直接实现的模板,需要特殊实现。
比如:
template<class T>
void f(T a, T b)
{
a = b;
}
在上述代码中提供的赋值操作,如果传入的a和b是数组,就无法实现了。
再比如:
template<class T>
void f(T a, T b)
{
if(a>b){
....}
}
在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行
因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板。
对于以上问题,解决方案有两个:
- 运算符重载
- 函数模板重载,并使程序优先调用函数模板,语法:
template<> 函数定义
可以使此函数模板优先于同名函数被调用,但是这里<>是空参数,所以要自己手动在函数列表中填入具体的参数数据类型。
示例:
#include<iostream>
using namespace std;
//对比两个数据是否相等
template<class T>
bool myCompare(T& a, T& b)
{
if (a == b)
{
return true;
}
else
{
return false;
}
}
void test6_01()
{
int a = 10, b = 20;
if (myCompare(a, b))
{
cout << "a == b" << endl;
}
else
{
cout << "a != b" << endl;
}
}
class Person
{
public:
Person(string name, int age)
{
this->name = name;
this->age = age;
}
string name;
int age;
};
//利用具体化Person的版本实现代码,具体化优先调用
class Person;
template<> bool myCompare(Person& p1, Person& p2)
{
if (p1.name == p2.name && p1.age == p2.age)
{
return true;
}
else {
return false; }
}
void test6_02()
{
Person p1("Tom", 10);
Person p2("Tom", 11);
if (myCompare(p1, p2))
{
cout << "p1 == p2" << endl;
}
else
{
cout << "p1 != p2" << endl;
}
}
int main()
{
test6_01();
test6_02();
system("pause");
return 0;
}
总结:
- 利用具体化的模板,可以解决自定义类型的通用化。
- 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板。
21.7 函数模板的分文件编写
和类模板的分文件编写原理相同。