1. 简介
我们对传递数值或变量给函数都很熟悉,除了传递变量,我们还能传递类型给模板。传递类型就是大家所熟知的泛型编程,因为
我们可以用泛型编写程序,而用特定的类型调用。
泛型编程的目的是为了编写的程序不依赖于数据类型。在 C 语言中,所有的代码都需要绑定到确定的数据类型,这样写的代码只能对特定的数据类型起作用。
而模板可以让我们实现泛型编程。你可以将类型作为参数来构建模板函数和类模板。当你的算法需要作用于多种数据类型的时候,模板就显得及其有用了。
C++的标准模板库(STL)提供了一些常用的容器类模板的实现,例如vector,可以用来存放所有类型的元素。
2. 示例:STL 中的 vector 类模板
C/C++中的内置数组有一些缺点:
- 它的大小是固定的,需要在声明的时候确定大小,不支持动态声明。你不能在执行期给数组扩容;
- 数组不提供下标边界校验,你可以使用超出边界的下标
- 你需要自己实现数组比较,和赋值操作
C++提供了一个vector类模板,作为标准模板库(STL)的一部分。vector被定义在<vector>头文件中,属于std命名空间。vector 是最常用的 STL 类,它能够取代数组,并且支持动态分配空间和一些其它操作(例如比较和赋值)。
vector 是一个类模板,它可以被特定类型的实例化,形如:vector<int>, vector<double>, vector<string>。同一个模板能够用于多种类型,而不必为每种类型都写一套实现。
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
| #include <iostream>
#include <vector>
#include <string>
using namespace std;
void print(const vector<int> &v);
int main(int argc, char *argv[]) {
vector<int> v1(5); // Create a vector with 5 elements.
// Assign values into v1, using array-like index []
// You can retrieve the size of vector via size()
for (int i = 0; i < v1.size(); i++) {
v1[i] = (i + 1) * 2;
}
// Print vector content, using at()
for (int i = 0; i < v1.size(); i++) {
cout << v1.at(i) << " ";
}
cout << endl;
vector<int> v2;
// Assign v1 to v2 memberwise
v2 = v1;
for (int i = 0; i < v2.size(); i++) {
cout << v2[i] << " ";
}
cout << endl;
// Compare 2 vectors memberwise
cout << boolalpha << (v1 == v2) << endl;
// Append more elements - synamically allocate memory
v1.push_back(80);
v1.push_back(81);
for (int i = 0; i < v1.size(); i++) {
cout << v1[i] << " ";
}
cout << endl;
vector<string> v3;
v3.push_back("a for apple");
v3.push_back("b for boy");
for (int i = 0; i < v3.size(); i++) {
cout << v3[i] << " ";
}
cout << endl;
return 0;
}
|
说明:
- 你可以通过声明
vector<int> v1(n)来初始化一个int类型的vector,其中n表示初始化的元素个数 - 可以使用
v1.size()来获取元素个数 - 可以使用
v1[i]或v1.at(i)来访问元素,但是[]操作符不会做边界检查,而at()会 - 使用
push_back()和pop_back()添加和删除元素。vector会自动调整内存分配。
3. 函数模板
把处理不同类型的公共逻辑抽象成函数,就得到了函数模板。
定义函数模板的定义语法如下:
1
2
| template <typename T> OR template <class T>
return-type function-name(function-parameter-list) { ...... }
|
Example 1
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
| #include <iostream>
using namespace std;
template <typename T>
void mySwap(T &a, T &b);
int main(int argc, char *argv[]) {
int i1 = 1, i2 = 2;
mySwap(i1, i2);
cout << "i1 is " << i1 << ", i2 is " << i2 << endl;
char c1 = 'a', c2 = 'b';
mySwap(c1, c2);
cout << "c1 is " << c1 << ", c2 is " << c2 << endl;
double d1 = 1.1, d2 = 2.2;
mySwap(d1, d2);
cout << "d1 is " << d1 << ", d2 is " << d2 << endl;
return 0;
}
template <typename T>
void mySwap(T &a, T &b) {
T temp;
temp = a;
a = b;
b = temp;
}
|
C++编译器会为每种使用的类型都生成一个对应的函数,例如int型:
1
2
3
4
5
6
| void mySwap(int &a, int &b) {
int temp;
temp = a;
a = b;
b = temp;
}
|
这样并不能为代码的执行效率和内存使用率带来提升,但是能够大大提高开发效率。
Example 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| #include <iostream>
using namespace std;
template<typename T>
T abs(T value) {
T result;
result = (value >= 0) ? value : -value;
return result;
}
int main(int argc, char *argv[]) {
int i = -5;
cout << abs(i) << endl;
double d = - 55.5;
cout << abs(d) << endl;
float f = -555.5f;
cout << abs(f) << endl;
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| #include <iostream>
using namespace std;
template<typename T>
void mySwap(T &a, T &b);
template<typename T>
void mySwap(T a[], T b[], int size);
template<typename T>
void print(const T *const array, int size);
int main(int argc, char *argv[]) {
int i1 = 1, i2 = 2;
mySwap(i1, i2);
cout << "i1 is " << i1 << ", i2 is " << i2 << endl;
const int SIZE = 3;
int arr1[] = {1, 2, 3}, arr2[] = {4, 5, 6};
mySwap(arr1, arr2, SIZE);
print(arr1, SIZE);
print(arr2, SIZE);
return 0;
}
template<typename T>
void mySwap(T &a, T &b) {
T temp;
temp = a;
a = b;
b = temp;
}
template<typename T>
void mySwap(T a[], T b[], int size) {
T temp;
for (int i = 0; i < size; i++) {
temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}
template<typename T>
void print(const T *const array, int size) {
cout << "(";
for (int i = 0; i < size; i++) {
cout << array[i];
if (i < size - 1) cout << ",";
}
cout << ")" << 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
| #include <iostream>
using namespace std;
template<typename T>
void mySwap(T &a, T &b);
template<>
void mySwap<int>(int &a, int &b);
int main(int argc, char *argv[]) {
double d1 = 1, d2 = 2;
mySwap(d1, d2);
int i1 = 1, i2 = 2;
mySwap(i1, i2);
return 0;
}
template<typename T>
void mySwap(T &a, T &b) {
cout << "template" << endl;
T temp;
temp = a;
a = b;
b = temp;
}
template<>
void mySwap<int>(int &a, int &b) {
cout << "specilization" << endl;
int temp;
temp = a;
a = b;
b = temp;
}
|
4. 类模板
定义一个类模板的语法如下:
1
2
3
4
| template<class T>
class ClassName {
......
}
|
关键字’class’和’typename’都是用来定义模板的。使用定义好的模板的语法是:ClassName<actual-type>
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| #include <iostream>
using namespace std;
template <typename T>
class Number {
private:
T value;
public:
Number(T value) { this->value = value; }
T getValue() const { return this->value; }
void setValue(T value) { this->value = value; }
};
int main(int argc, char *argv[]) {
Number<int> i(55);
cout << i.getValue() << endl;
Number<double> d(55.66);
cout <<d.getValue() << endl;
Number<string> s("hello");
cout << s.getValue() << endl;
return 0;
}
|
将模板声明和定义分开
如果将函数实现和声明分开,就需要在每个函数实现上都使用"template"关键字,例如:
1
2
3
4
| template<typename T>
T Number<T>::getValue() {
return value;
}
|
将所有模板代码都放在头文件中
多参数类型
1
2
| template<typename T1, typename T2, ...>
class ClassName { ...... }
|
默认类型
1
2
| template<typename T = int>
class ClassName { ...... }
|
特化
1
2
3
4
5
6
7
8
9
10
11
| // General Template
template<typename T>
class Complex { ...... }
// Specialization for type double
template<>
class Complex<double> { ...... }
// Specialization for type int
template<>
class Complex<int> { ...... }
|
5. 示例:MyComplex Template Class
MyComplex.h
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
| /*
* The MyComplex template class header (MyComplex.h)
* All template codes are kept in the header, to be included in program
* (Follow, modified and simplified from GNU GCC complex template class.)
*/
#ifndef MY_COMPLEX_H
#define MY_COMPLEX_H
#include <iostream>
// Forward declaration
template <typename T> class MyComplex;
template <typename T>
std::ostream & operator<< (std::ostream & out, const MyComplex<T> & c);
template <typename T>
std::istream & operator>> (std::istream & in, MyComplex<T> & c);
// MyComplex template class declaration
template <typename T>
class MyComplex {
private:
T real, imag;
public:
// Constructor
explicit MyComplex<T> (T real = 0, T imag = 0)
: real(real), imag(imag) { }
// Overload += operator for c1 += c2
MyComplex<T> & operator+= (const MyComplex<T> & rhs) {
real += rhs.real;
imag += rhs.imag;
return *this;
}
// Overload += operator for c1 += value
MyComplex<T> & operator+= (T value) {
real += value;
return *this;
}
// Overload comparison == operator for c1 == c2
bool operator== (const MyComplex<T> & rhs) const {
return (real == rhs.real && imag == rhs.imag);
}
// Overload comparison != operator for c1 != c2
bool operator!= (const MyComplex<T> & rhs) const {
return !(*this == rhs);
}
// Overload prefix increment operator ++c
// (Separate implementation for illustration)
MyComplex<T> & operator++ ();
// Overload postfix increment operator c++
const MyComplex<T> operator++ (int dummy);
/* friends */
// (Separate implementation for illustration)
friend std::ostream & operator<< <>(std::ostream & out, const MyComplex<T> & c); // out << c
friend std::istream & operator>> <>(std::istream & in, MyComplex<T> & c); // in >> c
// Overloading + operator for c1 + c2
// (inline implementation for illustration)
friend const MyComplex<T> operator+ (const MyComplex<T> & lhs, const MyComplex<T> & rhs) {
MyComplex<T> result(lhs);
result += rhs; // uses overload +=
return result;
}
// Overloading + operator for c + double
friend const MyComplex<T> operator+ (const MyComplex<T> & lhs, T value) {
MyComplex<T> result(lhs);
result += value; // uses overload +=
return result;
}
// Overloading + operator for double + c
friend const MyComplex<T> operator+ (T value, const MyComplex<T> & rhs) {
return rhs + value; // swap and use above function
}
};
// Overload prefix increment operator ++c
template <typename T>
MyComplex<T> & MyComplex<T>::operator++ () {
++real; // increment real part only
return *this;
}
// Overload postfix increment operator c++
template <typename T>
const MyComplex<T> MyComplex<T>::operator++ (int dummy) {
MyComplex<T> saved(*this);
++real; // increment real part only
return saved;
}
/* Definition of friend functions */
// Overload stream insertion operator out << c (friend)
template <typename T>
std::ostream & operator<< (std::ostream & out, const MyComplex<T> & c) {
out << '(' << c.real << ',' << c.imag << ')';
return out;
}
// Overload stream extraction operator in >> c (friend)
template <typename T>
std::istream & operator>> (std::istream & in, MyComplex<T> & c) {
T inReal, inImag;
char inChar;
bool validInput = false;
// Input shall be in the format "(real,imag)"
in >> inChar;
if (inChar == '(') {
in >> inReal >> inChar;
if (inChar == ',') {
in >> inImag >> inChar;
if (inChar == ')') {
c = MyComplex<T>(inReal, inImag);
validInput = true;
}
}
}
if (!validInput) in.setstate(std::ios_base::failbit);
return in;
}
#endif
|
TestMyComplex.cpp
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
| /* Test Driver for MyComplex template class (TestMyComplex.cpp) */
#include <iostream>
#include <iomanip>
#include "MyComplex.h"
int main() {
std::cout << std::fixed << std::setprecision(2);
MyComplex<double> c1(3.1, 4.2);
std::cout << c1 << std::endl; // (3.10,4.20)
MyComplex<double> c2(3.1);
std::cout << c2 << std::endl; // (3.10,0.00)
MyComplex<double> c3 = c1 + c2;
std::cout << c3 << std::endl; // (6.20,4.20)
c3 = c1 + 2.1;
std::cout << c3 << std::endl; // (5.20,4.20)
c3 = 2.2 + c1;
std::cout << c3 << std::endl; // (5.30,4.20)
c3 += c1;
std::cout << c3 << std::endl; // (8.40,8.40)
c3 += 2.3;
std::cout << c3 << std::endl; // (10.70,8.40)
std::cout << ++c3 << std::endl; // (11.70,8.40)
std::cout << c3++ << std::endl; // (11.70,8.40)
std::cout << c3 << std::endl; // (12.70,8.40)
// c1+c2 = c3; // error: c1+c2 returns a const
// c1++++; // error: c1++ returns a const
// MyComplex<int> c4 = 5; // error: implicit conversion disabled
MyComplex<int> c4 = (MyComplex<int>)5; // explicit type casting allowed
std::cout << c4 << std::endl; // (5,0)
MyComplex<int> c5;
std::cout << "Enter a complex number in (real,imag): ";
std::cin >> c5;
if (std::cin.good()) {
std::cout << c5 << std::endl;
} else {
std::cerr << "Invalid input" << std::endl;
}
return 0;
}
|