前言
模板(Template)是支持泛型编程的核心机制,用于编写与类型无关的代码。其可以分为以下两类:
- 函数模板
- 类模板
模板使得在编写逻辑相同但类型不同的函数或类时,可以只写一份代码,由编译器在使用时根据具体类型自动生成对应的代码。
函数模板
函数模板用于编写通用函数,其函数返回值和形参类型可以不事先指定,用虚拟的数据类型表示。
基本语法
1 2 3 4
| template <typename T> T add(T a, T b) { return a + b; }
|
其中,template是创建模板的声明;typename表示其后面的符号是一种数据类型,可以用class代替;T表示虚拟数据类型,名称可以更改,一般使用大写字母表示。
示例
函数模板有两种使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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);
mySwap<int>(a, b);
cout << "a = " << a << endl; cout << "b = " << b << endl; }
|
模板函数与普通函数的区别
- 能否发生隐式类型转换
普通函数调用时可以发生隐式类型转换。函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换,显式指定类型可以发生隐式类型转换。
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
| template<typename T> void mySwap01(T& a, T& b) { T temp = a; a = b; b = temp; }
void mySwap02(int a, int b) { int temp = a; a = b; b = temp; }
void test01() { int a = 1; char c = 'c';
mySwap01(a, c);
cout << "a = " << a << endl; cout << "c = " << c << endl; }
void test02() { int a = 1; char c = 'c';
mySwap<int>(a, c)
cout << "a = " << a << endl; cout << "c = " << c << endl; }
void test03() { int a = 1; char c = 'c';
mySwap02(a, c);
cout << "a = " << a << endl; cout << "c = " << c << endl; }
|
- 普通函数优先于模板函数
如果模板函数和普通函数都可以实现,优先调用普通函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void myPrint(int a) { cout << "int: " << a << << endl; }
template<typename T> void myPrint(T a) { cout << "template: " << a << endl; }
void test() { int a = 10; myPrint(a) }
|
- 模板定义必须与声明在同一个文件中
模板函数在编译时才进行实例化,定义通常应写在头文件中,否则链接时会出现未定义引用错误,通常将其后缀改为“.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 31 32 33 34 35 36 37 38 39 40 41 42
| class Person { public: Person(string name, int age) { this.m_Name = name; this.m_age = age; }
string m_Name; int m_Age; }
template<typename 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 result = myCompare(p1, p2);
if (result) { cout << "p1 == p2" << endl; } else { cout << "p1 != p2" << endl; } }
|
类模板
类模板允许我们定义与类型无关的类。通过类模板,我们可以编写一个逻辑统一、类型灵活的类结构,让编译器在使用时根据实际类型自动生成具体类。
基本语法
1 2 3 4 5 6 7 8 9
| template <typename T> class MyClass { public: T data; MyClass(T val) : data(val) {} void show() { std::cout << data << std::endl; } };
|
其中,template用于声明这是一个模板;typename表示后面的符号是一种数据类型,可以用class代替;T是虚拟类的名字,可以更改。
类模板使用时必须指定类型,如:MyClass、MyClass
示例
1 2 3 4 5 6 7
| int main() { MyClass<int> obj1(42); obj1.show();
MyClass<std::string> obj2("Hello"); obj2.show(); }
|
模板类在实例化时才会生成对应类型的代码。
类模板和函数模板的区别:
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以用默认参数
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
| template<class NameType, class AgeType = int> class Person { public: Person(NameType name, AgeType age) { this.mName = name; this.mAge = age; }
void showPerson() { cout << "name: " << this.mName << " age: " << this.mAge << endl; }
NameType mName; AgeType mAge; };
void test01() { Person<String, int> p("孙悟空", 1000); p.showPerson(); }
void test02() { Person<String> p("猪八戒", 999); p.showPerson(); }
|
类模板成员函数创建时机
普通成员函数一开始就会创建,类模板的成员函数在调用时才会创建。
在下列示例中,test01()函数中的实例obj只有一个方法showPerson1(),并没有showPerson2()方法,但可以编译成功,说明类模板成员函数只有在调用的时候才会创建。
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
| 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<Person> m;
m.fun1(); }
void test02() { MyClass<Person> m;
m.fun1();
m.fun2(); }
|
类模板成员函数的类外实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| template<class T1, class T2> class Person { public: person(T1 name, T2 age); void showPerson();
public: T1 m_Name; T2 m_Age; }
template<class T1, class T2> void 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 << "name: " << this.m_Name << " age: " << age << endl; }
|
类模板作为函数参数
用类模板实例化出的对象向函数传递参数,有以下三种方式:
- 指定传入的类型——直接显式对象的数据类型
- 参数模板化——将对象中的参数变为模板进行传递
- 整个类模板化——将实例模板化进行传递
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
| template<class NameType, class AgeType = int> class Person { public: Person(NameType name, AgeType age) { this.mName = name; this.mAge = age; }
void showPerson() { cout << "name: " << this.mName << " age: " << this.mAge << endl; }
NameType mName; AgeType mAge; };
void printPerson01(Person<String, int> &p) { p.showPerson(); }
template<class T1, class T2> void printPerson02(Person<T1, T2> &p) { p.showPerson(); }
template<class T> void printPerson03(T &p) { T.showPerson(); }
|
类模板的继承
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中虚拟类T的类型。如果不指定,编译器无法给子类分配内存。如果想灵活指定父类中T的类型,子类也需要定义为模板类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| template<class T> class Base { T m; };
class Son: public Base<int> {
};
template<class T1, class T2> class Son02: public Base<T2> { public: Son02() { } }
|
类模板分文件编写
类模板中成员函数在调用时才创建,导致分文件编写时链接不到
- 解决方法一:直接包含.cpp源文件
- 解决方法二:将声明和实现写到同一个文件中,并更改后缀名为.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 31 32 33
| #pragma once #include <iostream> using namespace std; #include <string>
template<class NameType, class AgeType = int> class Person { public: Person(NameType name, AgeType age) { this.mName = name; this.mAge = age; }
void showPerson() { cout << "name: " << this.mName << " age: " << this.mAge << endl; }
NameType mName; AgeType mAge; };
template<class T1, class T2> void 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 << "name: " << this.m_Name << " age: " << age << endl; }
|
类模板分文件中.cpp代码:
1 2 3 4 5 6 7 8 9 10
| #include <iostream> using namespace std;
#include "person.hpp"
void test01() { Person<string, int> p("Tom", 7); p.showPerson(); }
|
主流的解决方法是第二种。
类模型与友元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <iostream> using namespace std;
template <typename T> class MyClass { private: T value; public: MyClass(T val) : value(val) {}
friend void show(const MyClass<T>& obj) { cout << "Value: " << obj.value << endl; } };
int main() { MyClass<int> obj(10); show(obj); return 0; }
|
- 全局函数类外实现——需要提前让编译器知道全局函数存在
没有事先声明的错误示例:
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
| #include <iostream> using namespace std;
template <typename T> class MyClass { private: T value; public: MyClass(T val) : value(val) {}
friend void show(const MyClass<T>& obj); };
template <typename T> void show(const MyClass<T>& obj) { cout << "Value: " << obj.value << endl; }
int main() { MyClass<int> obj(10); show(obj); return 0; }
|
报错如下:

正确写法:
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
| #include <iostream> using namespace std;
template <typename T> class MyClass;
template <typename T> void show(const MyClass<T>& obj);
template <typename T> class MyClass { private: T value; public: MyClass(T val) : value(val) {}
friend void show<T>(const MyClass<T>& obj); };
template <typename T> void show(const MyClass<T>& obj) { cout << "Value: " << obj.value << endl; }
int main() { MyClass<int> obj(42); show(obj); return 0; }
|
1 2 3 4 5 6 7 8 9 10 11
| class MyClass { private: int x = 42; public: friend void show(const MyClass& obj); };
void show(const MyClass& obj) { std::cout << obj.x << std::endl; }
|