模板

前言

模板(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); // 编译类型错误,int和char不一致,T无法自动推导,无法将char进行隐式转换,转换为整型

cout << "a = " << a << endl;
cout << "c = " << c << endl;
}

void test02() {
int a = 1;
char c = 'c';

mySwap<int>(a, c) // 显式指定类型时可以进行隐式转换,将char转为整型

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;
}

// 输出为“int: 10”
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(); // 输出:42

MyClass<std::string> obj2("Hello");
obj2.show(); // 输出:Hello
}

模板类在实例化时才会生成对应类型的代码。

类模板和函数模板的区别:

  • 类模板没有自动类型推导的使用方式
  • 类模板在模板参数列表中可以用默认参数
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 p("孙悟空", 1000); // 错误,类模板使用时,不可以自动类型推导
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;
};

// 1.指定传入的类型
void printPerson01(Person<String, int> &p) {
p.showPerson();
}

// 2.参数模板化
template<class T1, class T2>
void printPerson02(Person<T1, T2> &p) {
p.showPerson();
}

// 3.整个类模板化
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 // 错误,必须指定父类中T的类型
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.cpp" // 解决方法一:直接包含.cpp文件

#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); // 输出: Value: 10
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); // 输出: Value: 10
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;
}

模板
https://delta0406.github.io/2025/05/21/技术/语言/CPP/模板/
作者
执妄
发布于
2025年5月21日
许可协议