函数模板
函数模板
- 泛型编程主要利用的技术就是模板
- C++提供两种模板机制:函数模板和类模板
1.2.1 函数模板语法
函数模板作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体指定,用一个虚拟的类型来代表
语法:
1 template<typename T> 2 函数声明或定义
template -- 声明创建模板
typename -- 表面其后面的符号是一种数据类型,可以用class代替
T -- 通用的数据类型,名称可以替换,通常为大写字母
示例代码:
#include <list> #include <iostream> using namespace std; template<typename T> //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错, T是一个通用数据类型 void mySwap (T &a, T &b) { T temp = a; a = b; b = temp; } int main() { int a,b; a = 10,b = 20; //利用函数进行交换 //1.第一种方式 -- 自动类型推导 mySwap (a, b); //2.第二种方式 -- 显式指定类型 mySwap <int> (a, b); std::cout << "a" << a << std::endl; std::cout << "b" << b << std::endl; return 0; }
总结
- 函数模板利用关键字template
- 使用函数模板有两种方式:自动类型推导,显式指定类
- 模板的目的是为了提高复用性,将类型参数化或者说“泛化”
1.2.2 函数模板注意事项
- 自动类型推导,必须推导出一致的数据类型T才能使用
- 模板必须要确定出T的准确数据类型,才可以使用
template<typename T> void test01(){ std::cout << "测试test01" << std::endl; } int main() { test01(); //错误 test01<char>(); //正确 test02<int>(); //正确 test03<double>(); //正确 return 0; }
可见,必须指定T的数据类型
1.2.3 普通函数和函数模板的区别
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导, 不会发生隐式类型转换
- 如果利用显式指定类型的方式,是可以发生隐式类型转换的
1 #include <iostream> 2 using namespace std; 3 int test01 (int a, int b) { 4 return a + b; 5 } 6 template<typename T> 7 T test02 (T a, T b) { 8 return a + b; 9 } 10 int main() 11 { 12 int a = 10; 13 int b = 20; 14 char c = 'c'; 15 //普通函数是可以发生隐式转换的 16 std::cout << "test01(int, int)" << test01 (a,b) << std::endl; 17 std::cout << "test01(int, char)" << test01 (a,c) << std::endl; 18 //函数模板能否使用,看情况 19 //1.自动类型推导 无法使用隐式转换 20 //std::cout << "test02(int, char)" << test02 (a,c) << std::endl; //报错 21 //2.显式指定类型可以发生隐式转换 22 std::cout << "test02(int, char)" << test02<int>(a,c) << std::endl; 23 return 0; 24 }
总结:建议使用显式指定类型的方式,调用函数模板,因为我们自己就可以确定通用类型T
1.2.4 普通函数与函数模板的调用规则
调用规则如下:
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
1 #include <iostream> 2 using namespace std; 3 //普通函数与函数模板调用 4 void myPrint(int a, int b) { 5 std::cout << "普通的函数调用" << std::endl; 6 } 7 template<typename T> 8 void myPrint(T a, T b){ 9 std::cout << "调用的模板" << std::endl; 10 } 11 12 template<typename T> 13 void myPrint(T a, T b, T c){ 14 std::cout << "调用重载的模板" << std::endl; 15 } 16 void test01(){ 17 int a = 10; 18 int b = 20; 19 //1.优先调用普通函数 20 myPrint (a,b); 21 22 //2.通过空模板参数列表,强制调用函数模板 23 myPrint<>(a,b); //可以发现,即使普通函数和模板函数都存在,但是 仍然只会调用模板函数 24 25 //3.重载调用 26 myPrint(a,b,100); 27 28 //4.如果函数模板产生更好的匹配,优先调用函数模板 29 char m = 'm'; 30 char n = 'n'; 31 myPrint (m,n); 32 } 33 int main() 34 { 35 test01(); 36 return 0; 37 }
总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性
1.2.5 模板的局限性
有些数据类型是无法直接使用模板的
例如:
1 template<typename T> 2 bool myCompare(T a, T b) 3 { 4 if (a == b) return true; 5 else return false; 6 }
在上述代码中,若是普通的数据类型例如int, char, double,还能正常对比,但是如果T是数组或者其他自定义类型呢?
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 //模板不是万能的,有些特定数据类型,需要用具体化的方式做特殊实现 5 //对比两个数据是否相等函数 6 class Person{ 7 public: 8 Person (string name, string age) { 9 this->name = name; 10 this->age = age; 11 } 12 string name; 13 string age; 14 }; 15 //普通函数模板 16 template<class T> 17 bool myCompare(T &a, T &b){ 18 if (a == b) 19 return true; 20 else 21 return false; 22 } 23 //利用具体化Person的版本来实现代码,具体话优先调用 24 template<> bool myCompare (Person &p1, Person &p2){ 25 if (p1.name == p2.name && p1.age == p2.age) 26 return true; 27 else 28 return false; 29 } 30 void test02(){ 31 Person p1 ("Tom", "10"); 32 Person p2 ("Tom", "15"); 33 int result = myCompare (p1, p2); 34 if (result) 35 cout << "p1 == p2" << endl; 36 else 37 cout << "p1 != p2" << endl; 38 } 39 int main() 40 { 41 test02(); 42 return 0; 43 }
- 利用具体化的模板,可以解决自定义类型的通用化
- 学习模板并不是为了写模板,而是在STL能够运用系统来提供的模板