模板:建立通用的模具,可提高复用性。
特点:
(1)模板是一个框架,不可直接使用(需确定具体类型);
(2)模板的通用性不是万能的。
C++中的泛型编程思想 ,通过模板实现,包括函数模板
和类模板
等2种模板机制。
1 函数模板
作用:将类型参数化(将固定类型抽象为通用类型),提高代码复用性。
建立一个通用函数,声明与定义函数时不具体指定返回值类型
和形参类型
,使用虚拟类型表示;调用函数时再确定其具体类型。
语法 :
template<typename T>
函数声明或定义
解释:
template
:关键字,声明创建模板。
typename
:关键字,类型名称,表明其后符号是通用数据类型,可使用class
代替。
T
:通用数据类型/虚拟类型(通常为大写字母),标识符名称可替换。
注:声明函数模板,可使用
template<typename T>
或template<class T>
;
声明类模板,可使用template<class T>
。
思路1:函数模板使用typename,类模板使用class,用作区分。
思路2:函数模板和类模板统一使用class。
使用方式:
(1)自动类型推导:直接调用模板函数,编译器根据实参类型自动推导。
(2)显式指定类型:调用模板函数时,显式指定参数的类型,如func<指定泛型类型>(...);
。
示例:
#include <iostream>
using namespace std;
//两整型变量交换
void swapInt(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
//两字符型变量交换
void swapChar(char& a, char& b) {
char temp = a;
a = b;
b = temp;
}
/* 函数模板 */
template<typename T> //声明函数模板
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
int main() {
//交换整型变量
int a = 1, b = 2;
swapInt(a, b);
cout << "a = " << a << endl; //2
cout << "b = " << b << endl; //1
//交换字符型变量
char m = 'x', n = 'y';
swapChar(m, n);
cout << "m = " << m << endl; //y
cout << "n = " << n << endl; //x
/* 函数模板的使用 */
//1.自动类型推导
double p1 = 1.1;
double q1 = 2.2;
mySwap(p1, q1);
cout << "p1 = " << p1 << endl; //2.2
cout << "q1 = " << q1 << endl; //1.1
//2.显式指定类型
double p2 = 1.1;
double q2 = 2.2;
mySwap<double>(p2, q2);
cout << "p2 = " << p2 << endl; //2.2
cout << "q2 = " << q2 << endl; //1.1
return 0;
}
2 函数模板注意事项
注意事项:
(1)自动类型推导时,必须推导出一致的数据类型T
,方可使用,否则编译器报错:没有与参数列表匹配的函数模板“func”实例
。
(2)函数模板调用时,必须明确通用数据类型T
的具体数据类型,方可使用,否则编译器报错:没有与参数列表匹配的函数模板“func”实例
。
注:使用函数模板时,必须明确通用数据类型
T
的具体数据类型,并且可推导出一致的数据类型。
示例:
#include <iostream>
using namespace std;
/* 函数模板 */
//template<typename T>
template<class T> //声明函数模板(typename可替换为class)
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
template<class T>
void func() {
cout << "func()调用..." << endl;
}
int main() {
//1.自动类型推导时,必须推导出【一致的数据类型】T,方可使用
int a = 1;
int b = 2;
char c = 'z';
mySwap(a, b); //正确
//没有与参数列表匹配的函数模板“mySwap”实例。参数类型为(int , char)
//mySwap(a, c); //错误
//2.函数模板调用时,必须明确T的【具体数据类型】,方可使用
//没有与参数列表匹配的函数模板“func”实例
//func(); //错误
func<int>(); //正确。将T的类型显式指定为int类型
return 0;
}
3 函数模板练习:数组排序
练习:利用函数模板,实现数组的降序排序。
示例:
#include <iostream>
using namespace std;
/*
数组排序:选择排序 + 降序排序
*/
//元素交换的函数模板
template<typename T>
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
//数组排序的函数模板
template<typename T>
void sortArray(T arr[], int length) {
for (int i = 0; i < length; i++) {
//降序排序
int maxIndex = i; //设定最大值的索引
for (int j = i + 1; j < length; j++) {
if (arr[maxIndex] < arr[j]) {
maxIndex = j; //实际最大值的索引
}
}
//设定最大值的索引与实际最大值的索引不相等:交换两元素
if (maxIndex != i) {
//t temp = arr[i];
//arr[i] = arr[maxindex];
//arr[maxindex] = arr[i];
//函数模板实现元素交换
mySwap(arr[i], arr[maxIndex]);
}
}
}
//遍历数组的函数模板
template<typename T>
void printArray(T arr[], int length) {
for (int i = 0; i < length; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
int main() {
//字符数组
char charArr[] = "bedafc";
int len1 = sizeof(charArr) / sizeof(charArr[0]);
sortArray(charArr, len1);
printArray(charArr, len1); //f e d c b a
//整型数组
int intArr[] = { 4, 2, 5, 3, 6, 1 };
int len2 = sizeof(intArr) / sizeof(intArr[0]);
sortArray(intArr, len2);
printArray(intArr, len2); //6 5 4 3 2 1
return 0;
}
4 普通函数与函数模板的区别
区别:
普通函数
调用时,可发生自动类型转换(隐式类型转换)
函数模板
调用时:
①若使用自动类型推导的方式,不可发生隐式类型转换(必须推导出一致的数据类型T
);
②若使用显式指定类型的方式,可发生隐式类型转换。
注:调用函数模板时,建议使用
显式指定类型
的方式,可自行确定通用类型T
的具体类型。
示例:
#include <iostream>
using namespace std;
//普通函数
int myAdd(int num1, int num2) {
return num1 + num2;
}
//函数模板
template<typename T>
T sum(T num1, T num2) {
return num1 + num2;
}
int main() {
/* 普通函数调用:可发生隐式类型转换 */
int a = 1;
int b = 2;
char ch = 'c';
cout << myAdd(a, b) << endl; //3
//隐式类型转换,char型隐式转换为int型,即a + (int)ch
cout << (int)ch << endl; //99
cout << myAdd(a, ch) << endl; //100
/* 函数模板调用 */
//1.自动类型推导时,不可发生隐式类型转换
//cout << sum(a, ch) << endl; //报错:没有与参数列表匹配的函数模板“sum”实例,参数类型为(int,char)
//2.显式指定类型时,可发生隐式类型转换
cout << sum<int>(a, ch) << endl; //100 正确
return 0;
}
5 普通函数与函数模板同名时的调用规则
调用规则:
(1)若普通函数和函数模板均可调用,则优先调用普通函数;
注:若普通函数有声明、无定义,且存在同名函数模板,调用同名函数时,编译器报错:
无法解析的外部命令
。
(2)通过空模板参数列表
,可强制调用函数模板,即func<>(...);
;
(3)函数模板可发生函数重载;
(4)若函数模板的匹配度更高,则优先调用函数模板。
注:提供函数模板时,建议不要提供同名普通函数,否则容易产生二义性。
示例:普通函数与函数模板的调用规则
#include <iostream>
using namespace std;
/* 普通函数 */
void print(int a, int b) {
cout << "普通函数调用" << endl;
}
/* 函数模板 */
template<typename T>
void print(T a, T b) {
cout << "函数模板调用" << endl;
}
/* 函数模板重载 */
//3.函数模板可发生重载
template<typename T>
void print(T a, T b, T c) { //函数模板重载
cout << "重载函数模板调用" << endl;
}
//1.若函数模板和普通函数均可调用,则优先调用普通函数
void func1() {
int a = 1;
int b = 2;
print(1, 2); //普通函数调用
}
//2.通过 空模板参数列表,可强制调用函数模板,即func<>(...);
void func2() {
int a = 1;
int b = 2;
//空模板参数列表
print<>(1, 2); //函数模板调用
}
//3.函数模板可发生重载
void func3() {
int a = 1;
int b = 2;
int c = 3;
//空模板参数列表
print<>(1, 2, 3); //重载函数模板调用
}
//4.若函数模板的匹配度更高,则优先调用函数模板
void func4() {
char a = 'a';
char b = 'b';
//普通函数:print(int a, int b); //char型需强转为int型
//函数模板:print(T a, T b); //可推导出一致数据类型(字符型),匹配度更高
print(a, b); //函数模板调用
}
int main() {
//func1();
//func2();
//func3();
func4();
return 0;
}
6 模板的局限性
局限:模板的通用性不是万能的,如函数模板不能用于数组名赋值、自定义数据类型的比较。
不适用场景1:数组名之间不能直接赋值
template<class T>
void func(T a, T b)
{
a = b; //不能用于数组名赋值
}
不适用场景2:自定义数据类型不能比较
template<class T>
void func(T a, T b)
{
if(a > b) { ... } //不能用于自定义数据类型的比较
}
针对模板的局限性,C++提供函数模板的重载,针对特定数据类型提供具体化的模板,具体化的模板优先于常规模板。
使用template<>
表明重载的函数模板,是针对特定数据类型的具体化版本。
语法:template<> 返回类型 函数模板名(自定义数据类型 &a , 自定义数据类型 &b, ...){...}
例:template<> void func(Object &a , Object &b){...}
注1:利用具体化的函数模板,通过对自定义数据类型的特殊处理,可实现自定义类型的通用化。
注2:学习模板的主要目的是在STL中运用系统自带模板,而非自行写模板。
示例:函数模板重载,针对自定义数据类型的具体化模板
#include <iostream>
using namespace std;
#include <string>
class Person {
public:
string name;
int age;
Person(string name, int age) {
this->name = name;
this->age = age;
}
};
template<typename T>
bool myCompare(T& a, T& b) {
return a == b ? true : false;
}
//使用template<> 表明重载的函数模板,是针对特定数据类型的具体化版本
template<> bool myCompare(Person& a, Person& b) {
return a.name == b.name && a.age == b.age;
}
int main() {
Person p1("Tom", 18);
Person p2("Tom", 18);
int flag = myCompare(p1, p2);
if (flag) {
cout << "p1 == p2" << endl;
}
else {
cout << "p1 != p2" << endl;
}
return 0;
}