19 模板
模板是 C++ 中泛型编程的基础,它允许我们将类型作为参数,做到「一次编写,多处复用」。模板分为函数模板和类模板两种形式。
19.1 模板的基本语法
作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体指定,用虚拟的类型来表示。
语法:
1 2
| template<typename T> 函数声明或定义
|
template:声明创建模板typename:表明其后面的符号是一种数据类型,可以用 class 代替T:通用的数据类型,名称可以替换,通常为大写字母
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include <iostream> using namespace std;
void mySwap01(int& a, int& b) { int temp = a; a = b; b = temp; }
template<typename T> void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; }
void test01() { int a = 10; int b = 20;
mySwap(a, b); cout << "a = " << a << " b = " << b << endl;
double c = 1.1; double d = 2.2; mySwap(c, d); cout << "c = " << c << " d = " << d << endl;
int m = 100; int n = 200; mySwap<int>(m, n); cout << "m = " << m << " n = " << n << endl; }
int main() {
test01();
system("pause");
return 0; }
|
总结:函数模板利用关键字 template;使用函数模板有两种方式:自动类型推导、显示指定类型;模板的目的是为了提高复用性,将类型参数化。
19.2 函数模板注意事项
- 自动类型推导,必须推导出一致的数据类型 T 才可以使用
- 模板必须要确定出 T 的数据类型,才可以使用
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include <iostream> using namespace std;
template<class T> void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; }
void test01() { int a = 10; int b = 20; char c = 'c';
mySwap(a, b); }
template<class T> void func() { cout << "调用func" << endl; }
void test02() { func<int>(); }
int main() {
test01(); test02();
system("pause");
return 0; }
|
注意:使用模板时必须确定出通用数据类型 T,并且能够推导出一致的类型。
19.3 函数模板案例
利用函数模板封装一个排序的函数,可以对不同类型数组进行排序;排序规则从大到小,排序算法为选择排序。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| #include <iostream> using namespace std;
template<typename T> void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; }
template<class T> void mySort(T arr[], int len) { for (int i = 0; i < len; i++) { int max = i; for (int j = i + 1; j < len; j++) { if (arr[max] < arr[j]) { max = j; } } if (max != i) { mySwap(arr[max], arr[i]); } } }
template<class T> void printArray(T arr[], int len) { for (int i = 0; i < len; i++) { cout << arr[i] << " "; } cout << endl; }
void test01() { char charArr[] = "badcfe"; int num = sizeof(charArr) / sizeof(char); mySort(charArr, num); printArray(charArr, num); }
void test02() { int intArr[] = { 7, 5, 1, 3, 9, 2, 4, 6, 8 }; int num = sizeof(intArr) / sizeof(int); mySort(intArr, num); printArray(intArr, num); }
int main() {
test01(); test02();
system("pause");
return 0; }
|
19.4 普通函数与函数模板的区别
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果是自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #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 test01() { int a = 10; int b = 20; char c = 'c';
cout << myAdd01(a, c) << endl;
myAdd02<int>(a, c); }
int main() {
test01();
system("pause");
return 0; }
|
提示:建议使用显示指定类型的方式调用函数模板,因为可以自己确定通用类型 T。
19.5 普通函数与函数模板的调用规则
调用规则如下:
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表
myPrint<>(a, b); 来强制调用函数模板 - 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <iostream> using namespace std;
void myPrint(int a, int b) { cout << "调用的普通函数" << endl; }
template<typename T> void myPrint(T a, T b) { cout << "调用的模板" << endl; }
template<typename T> void myPrint(T a, T b, T c) { cout << "调用重载的模板" << endl; }
void test01() { int a = 10; int b = 20; myPrint(a, b);
myPrint<>(a, b);
int c = 30; myPrint(a, b, c);
char c1 = 'a'; char c2 = 'b'; myPrint(c1, c2); }
int main() {
test01();
system("pause");
return 0; }
|
总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性。
19.6 模板的局限性
模板并不是万能的,有些特定数据类型,需要用具体化方式做特殊实现。
示例:比较两个 Person 对象是否相等,模板无法直接比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| #include <iostream> using namespace std; #include <string>
class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; }
string m_Name; int m_Age; };
template<class T> bool myCompare(T& a, T& b) { if (a == b) { return true; } else { return false; } }
template<> bool myCompare(Person& p1, Person& p2) { if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) { return true; } else { return false; } }
void test01() { Person p1("Tom", 10); Person p2("Tom", 10);
bool ret = myCompare(p1, p2); if (ret) { cout << "p1 == p2" << endl; } else { cout << "p1 != p2" << endl; } }
int main() {
test01();
system("pause");
return 0; }
|
提示:利用具体化的模板,可以解决自定义类型的通用化。
19.7 类模板基本语法
作用:建立一个通用类,类中的成员数据类型可以不具体指定,用一个虚拟的类型来代表。
语法:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <iostream> using namespace std; #include <string>
template<class NameType, class AgeType> class Person { public:
Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; }
void showPerson() { cout << "name: " << this->m_Name << " age: " << this->m_Age << endl; }
NameType m_Name; AgeType m_Age; };
void test01() { Person<string, int> p1("孙悟空", 999); p1.showPerson(); }
int main() {
test01();
system("pause");
return 0; }
|
19.8 类模板与函数模板区别
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include <iostream> using namespace std; #include <string>
template<class NameType, class AgeType = int> class Person { public:
Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; }
void showPerson() { cout << "name: " << this->m_Name << " age: " << this->m_Age << endl; }
NameType m_Name; AgeType m_Age; };
void test01() { Person<string, int> p("张三", 18); p.showPerson(); }
void test02() { Person<string> p("李四", 20); p.showPerson(); }
int main() {
test01(); test02();
system("pause");
return 0; }
|
19.9 类模板中成员函数创建时机
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <iostream> using namespace std;
class Person1 { public: void showPerson1() { cout << "Person1 show" << endl; } };
class Person2 { public: void showPerson2() { cout << "Person2 show" << endl; } };
template<class T> class MyClass { public:
T obj;
void fun1() { obj.showPerson1(); }
void fun2() { obj.showPerson2(); } };
void test01() { MyClass<Person1> m; m.fun1();
}
int main() {
test01();
system("pause");
return 0; }
|
19.10 类模板对象做函数参数
类模板实例化出的对象,向函数传参的方式有三种:
- 指定传入的类型:直接显示对象的数据类型
- 参数模板化:将对象中的参数变为模板进行传递
- 整个类模板化:将这个对象类型模板化进行传递
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| #include <iostream> using namespace std; #include <string>
template<class T1, class T2> class Person { public:
Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; }
void showPerson() { cout << "姓名: " << this->m_Name << " 年龄: " << this->m_Age << endl; }
T1 m_Name; T2 m_Age; };
void printPerson1(Person<string, int>& p) { p.showPerson(); }
void test01() { Person<string, int> p("孙悟空", 100); printPerson1(p); }
template<class T1, class T2> void printPerson2(Person<T1, T2>& p) { p.showPerson(); cout << "T1 的类型为: " << typeid(T1).name() << endl; cout << "T2 的类型为: " << typeid(T2).name() << endl; }
void test02() { Person<string, int> p("猪八戒", 90); printPerson2(p); }
template<class T> void printPerson3(T& p) { p.showPerson(); cout << "T 的类型为: " << typeid(T).name() << endl; }
void test03() { Person<string, int> p("唐僧", 30); printPerson3(p); }
int main() {
test01(); test02(); test03();
system("pause");
return 0; }
|
19.11 类模板与继承
当类模板碰到继承时,需要注意以下几点:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中 T 的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中 T 的类型,子类也需要变为类模板
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include <iostream> using namespace std;
template<class T> class Base { public: T m; };
class Son : public Base<int> { public: };
void test01() { Son c; }
template<class T1, class T2> class Son2 : public Base<T2> { public:
Son2() { cout << "T1 的类型为: " << typeid(T1).name() << endl; cout << "T2 的类型为: " << typeid(T2).name() << endl; }
T1 obj; };
void test02() { Son2<int, char> s; }
int main() {
test02();
system("pause");
return 0; }
|
19.12 类模板成员函数类外实现
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <iostream> using namespace std; #include <string>
template<class T1, class T2> class Person { public:
Person(T1 name, T2 age);
void showPerson();
T1 m_Name; T2 m_Age; };
template<class T1, class T2> Person<T1, T2>::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; }
template<class T1, class T2> void Person<T1, T2>::showPerson() { cout << "姓名: " << this->m_Name << " 年龄: " << this->m_Age << endl; }
void test01() { Person<string, int> p("Tom", 20); p.showPerson(); }
int main() {
test01();
system("pause");
return 0; }
|
注意:类模板中成员函数类外实现时,需要加上模板参数列表。
19.13 类模板分文件编写
问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到。
解决方式:
- 直接包含
Person.cpp 源码 - 将声明和实现写到同一个文件中,并更改后缀名为
.hpp(hpp 是约定的名称,并不是强制)
示例:Person.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #pragma once #include <iostream> using namespace std; #include <string>
template<class T1, class T2> class Person { public:
Person(T1 name, T2 age);
void showPerson();
T1 m_Name; T2 m_Age; };
template<class T1, class T2> Person<T1, T2>::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; }
template<class T1, class T2> void Person<T1, T2>::showPerson() { cout << "姓名: " << this->m_Name << " 年龄: " << this->m_Age << endl; }
|
主文件直接 #include "Person.hpp" 即可正常调用。
19.14 类模板与友元
- 全局函数类内实现:直接在类内声明友元即可
- 全局函数类外实现:需要提前让编译器知道全局函数的存在
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| #include <iostream> using namespace std; #include <string>
template<class T1, class T2> class Person;
template<class T1, class T2> void printPerson2(Person<T1, T2> p);
template<class T1, class T2> class Person { friend void printPerson(Person<T1, T2> p) { cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl; }
friend void printPerson2<>(Person<T1, T2> p);
public:
Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; }
private: T1 m_Name; T2 m_Age; };
void test01() { Person<string, int> p("Tom", 20); printPerson(p); }
template<class T1, class T2> void printPerson2(Person<T1, T2> p) { cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl; }
void test02() { Person<string, int> p("Jerry", 18); printPerson2(p); }
int main() {
test01(); test02();
system("pause");
return 0; }
|
总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别。