模板概述

引入原因

考虑这样一个问题:多个函数的参数个数相同,处理代码雷同,仅仅是处理的数据类型(参数或者返还结果)不同,如何处理?

  • 方法一:为不同的函数设置不同的名字

存在多个函数的版本,代码重复,函数命名复杂混乱,客户代码难以使用

1
2
3
int imax(int a, int b) { return a > b ? a : b; }
long lmax(long a, long b) { return a > b ? a : b; }
double dmax(double a, double b) { return a > b ? a : b; }
  • 方法二:函数重载

解决了函数命名的问题实现了多个函数的命名统一,但是代码冗余存在多个雷同的实现维护的代价很高

1
2
3
int max(int a, int b) { return a > b ? a : b; }
long max(long a, long b) { return a > b ? a : b; }
double max(double a, double b) { return a > b ? a : b; }
  • 方法三:模板技术

C++ 提供了模板技术:将函数类型提取出来作为"类型参数",通过创建通用的函数模板,避免函数体的重复定义。

函数模板

定义函数模板

1
2
3
4
template <typename T>  //标志模板的开始,同时指定模板中使用的类型参数
T max(T a, T b) {
return a > b ? a : b;
}

max() 定义了一个模板(并不是函数),描述了函数的骨架,比较两个值并返还较大者,两个值通过 a 和 b 传递,而参数类型还没有确定用函数模板 T 来代替

调用函数:函数模板实例化

函数模板只是骨架,在不调用的时候是不会生成代码进行编译执行的,只有实例化一次后才会生成相应数据类型的函数代码作为程序的一部分参与编译与执行

1
2
3
4
5
6
7
8
#include <iostream>
int main() {
cout << max(3, 6) << endl;
char c1 = 'a';
char c2 = 'b';
cout << max(c1, c2) << endl;
return 0;
}
  • 调用代码 max() 函数,编译器根据实参类型和函数模板的骨架生成针对不同参数类型的 max() 函数版本(函数实例

  • 实际生成 max<int>max<char> 两个版本函数,如果客户程序中不调用 max(),则可能不生成任何函数实例

编译器生成的函数实例

通过函数模板技术,我们只需要维护一份代码骨架,由编译器编译时根据需要代替我们生成代码,解决了代码冗余、维护成本高的问题

1
2
3
4
5
6
int max<int>(int a, int b) {
return a > b ? a : b;
}
char max<char>(char a, char b) {
return a > b ? a : b;
}

函数参数的演绎

1
2
3
4
5
6
7
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}

max(4, 7); //对,编译器能够推断所有的T均为int型
max(4, 4.2); //错,函数无法推断出T到底是哪个类型,出现二义性报错

函数后置类型声明

在可能出现二义性的情况下,显示指定函数的参数类型是一种良好的编程习惯

1
2
3
4
5
6
7
8
9
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}

//显示指明T的类型杜绝了二义性,允许编译执行
max<int>(4, 7);
max<double>(4, 4.2);

函数模板的类型推导

1
2
3
4
5
6
7
8
9
template <typename T1, typename T2, typename RT>
RT sum(T1 a, T2 b) {
return a + b;
}

template <typename T1, typename T2>
auto sum(T1 a, T2 b) -> decltype(a + b) { //C++11允许
return a + b;
}

编译器根据 decltype(a+b) 推导出返还值的类型

值的注意的是不可以将 decltype(a+b) 放在函数的前面,因为此时 a 和 b 还没有被定义,decltype(a+b) 无法通过编译运行

1
2
3
4
5
//报错,无法确定返还值的类型
template <typename T1, typename T2>
decltype(a + b) sum(T1 a, T2 b) {
return a + b;
}

类模板

类模板定义

1
2
3
4
5
6
7
8
template <typename T>  //标志模板的开始,同时指定模板中使用的类型参数
class Exam {
public:
void setValue(T const& value);
T getValue() const;
private:
T elems;
};

同理,Eaxm 并不是一个真正的类,只是一个类模板(生成类的骨架),类中的数据成员和部分成员函数的类型还没有确定,并且模板并没有定义任何类

类模板成员函数的定义

1
2
3
4
5
6
7
8
9
template <typename T>  //每个成员函数之前都要使用模板标志,哪怕该函数不使用类型参数。
void Exam<T>::setValue(const T& value) { //作用域需要指明类用的模板
elems = vlaue;
}

template <typename T>
T Exam<T>::getValue() const { //作用域需要指明类用的模板
return elems;
}
  • 成员函数实质上是函数模板
  • 类作用域限定符不再是 Exam,因为 Exam 不是真正类,真正的类是按照 T 实例化后的类,所以在定义的时候作用域限定符需要指明用到的类型参数,上述例子中名称为 Exam<T>

定义对象:类模板实例化

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
int main()
{
Exam<int> e1;
e1.setValue(5);
cout << e1.getValue() << endl;
Exam<double> e2;
e2.setValue(5.5);
cout << e2.getValue() << endl;
return 0;
}
  • 创建对象时编译器根据指定的类型生成特定类型的类定义(含成员函数定义)
  • 根据类骨架编译器对不同数据类型帮助我们生成类定义

编译器生成类的实例

借助模板的强大功能我们不需要编写多个类,编译器根据类型参数帮助我们生成实际需要的类

1
2
3
4
5
6
7
8
9
10
class Exam<int> {
public:
void setVal(int const&);
int getVal() const;
private:
int elems;
};

void Exam<int>::setVal(const int& e) { elems = e; }
int Exam<int>::getVal() const { return elems; }
1
2
3
4
5
6
7
8
9
10
11
class Exam<double> {
public:
void setVal(double const&);
double getVal() const;

private:
double elems;
};

void Exam<double>::setVal(const double& e) { elems = e; }
double Exam<double>::getVal() const { return elems; }

通用数组类 MyVector

具体实现

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#include <bits/stdc++.h>
using namespace std;

//此时Array变成了模板,而不是真正的类,编译器将根据模板,帮助我们生成所需要的类
template <typename T>
class MyVector {
public:
MyVector(int maxSize = 1);
MyVector(const MyVector<T>& a);
MyVector(MyVector<T>&& a);
MyVector& operator=(const MyVector<T>& a);
MyVector& operator=(MyVector<T>&& a);
~MyVector();
int getSize() const { return size; }
T getValue(int index) const;
void setValue(int index, T value);
T& operator[](int index);
const T& operator[](int index) const;
void pushData(T data);
void removeData();
void removeData(int index);

private:
T* pData;
int size;
int maxSize;
bool validIndex(int index) const;
void copyData(T* data, int maxSize, int curSize);
void reAllocMemory();
};

//鉴于模板的特殊机制,要求将成员函数的实现和模板定义都放在头文件中
//每个成员函数前的template声明都需要保留,数据元素的类型用T来代替

template <typename T>
void MyVector<T>::copyData(T* data, int maxSize, int curSize) {
int i;
this->maxSize = maxSize;
size = curSize;
pData = new T[maxSize];
for (i = 0; i < size; ++i) {
pData[i] = data[i];
}
}

template <typename T>
MyVector<T>::MyVector(int maxSize) {
this->maxSize = maxSize;
pData = new T[maxSize];
size = 0;
}

template <typename T>
MyVector<T>::~MyVector() {
delete[] pData;
}

template <typename T>
MyVector<T>::MyVector(const MyVector<T>& a) {
copyData(a.pData, a.maxSize, a.size);
}

template <typename T>
MyVector<T>& MyVector<T>::operator=(const MyVector<T>& a) {
if (this == &a)
return *this;
delete[] pData;
copyData(a.pData, a.maxSize, a.size);
return *this;
}

template <typename T>
MyVector<T>::MyVector(MyVector<T>&& a) { //C++ 11 的move语义
size = a.size;
pData = a.pData;
a.size = 0;
a.pData = null;
}

template <typename T>
MyVector<T>& MyVector<T>::operator=(MyVector<T>&& a) { //C++ 11 的move语义
if (this == &a)
return *this;
delete[] pData;
size = a.size;
pData = a.pData;
a.size = 0;
a.pData = null;
}

template <typename T>
bool MyVector<T>::validIndex(int index) const {
if (index < 0 || index >= size)
return false;
return true;
}

template <typename T>
void MyVector<T>::setValue(int index, T value) {
if (validIndex(index) == false)
return;
pData[index] = value;
}

template <typename T>
T MyVector<T>::getValue(int index) const {
if (validIndex(index) == false) {
cout << "error: Invalid index!\n";
exit(0);
}
return pData[index];
}

template <typename T>
T& MyVector<T>::operator[](int index) {
if (validIndex(index) == false) {
cout << "error: Invalid index\n";
exit(0);
}
return pData[index];
}

template <typename T>
const T& MyVector<T>::operator[](int index) const {
if (validIndex(index) == false) {
cout << "error: Invalid index\n";
exit(0);
}
return pData[index];
}

template <typename T>
void MyVector<T>::pushData(T data) {
if (size == maxSize)
reAllocMemory();
pData[size] = data;
size++;
}

template <typename T>
void MyVector<T>::reAllocMemory() {
T* temp = pData;
pData = new T[maxSize * 2];
for (int i = 0; i < size; ++i)
pData[i] = temp[i];
delete[] temp;
}

template <typename T>
void MyVector<T>::removeData() {
if (size == 0) {
cout << "No data!\n";
return;
}
size--;
}

template <typename T>
void MyVector<T>::removeData(int index) {
if (validIndex(index) == false || size == 0)
return;
int i;
size--;
for (i = index; i < size; ++i) {
pData[i] = pData[i + 1];
}
}

int main() { //debug
MyVector<int> array1(5);
/*要处理double类型数据,声明MyVector<double>对象即可,
编译器根据需要创建雷同一致的代码,然后进行编译*/
array1.pushData(1);
array1.pushData(2);
array1.pushData(3);
array1.pushData(4);
for (int i = 0; i < array1.getSize(); ++i)
cout << array1[i] << ' ';
cout << endl;
return 0;
}

注意事项

  • 模板类的函数无论是否需要传入参数,都需要当做函数模板加上 template 那句话
  • 函数模板和类模板函数的声明与定义放在一起,分开写会报链接错误

这是因为函数模板要被实例化后才能成为真正的函数,在使用函数模板的源文件中包含函数模板的头文件,如果该头文件中只有声明没有定义,那编译器无法实例化该模板,最终导致链接错误

由于上述函数模板和类模板的性质我们也可以推出:在声明与定义中出现语法错误运行时未必会报错,只有被用来实例化的时候无法编译才会报错