1. 简介

我们对传递数值或变量给函数都很熟悉,除了传递变量,我们还能传递类型给模板。传递类型就是大家所熟知的泛型编程,因为 我们可以用泛型编写程序,而用特定的类型调用。

泛型编程的目的是为了编写的程序不依赖于数据类型。在 C 语言中,所有的代码都需要绑定到确定的数据类型,这样写的代码只能对特定的数据类型起作用。 而模板可以让我们实现泛型编程。你可以将类型作为参数来构建模板函数和类模板。当你的算法需要作用于多种数据类型的时候,模板就显得及其有用了。

C++的标准模板库(STL)提供了一些常用的容器类模板的实现,例如vector,可以用来存放所有类型的元素。

2. 示例:STL 中的 vector 类模板

C/C++中的内置数组有一些缺点:

  1. 它的大小是固定的,需要在声明的时候确定大小,不支持动态声明。你不能在执行期给数组扩容;
  2. 数组不提供下标边界校验,你可以使用超出边界的下标
  3. 你需要自己实现数组比较,和赋值操作

C++提供了一个vector类模板,作为标准模板库(STL)的一部分。vector被定义在<vector>头文件中,属于std命名空间。vector 是最常用的 STL 类,它能够取代数组,并且支持动态分配空间和一些其它操作(例如比较和赋值)。

vector 是一个类模板,它可以被特定类型的实例化,形如:vector<int>, vector<double>, vector<string>。同一个模板能够用于多种类型,而不必为每种类型都写一套实现。

 1#include <iostream>
 2#include <vector>
 3#include <string>
 4
 5using namespace std;
 6
 7void print(const vector<int> &v);
 8
 9int main(int argc, char *argv[]) {
10	vector<int> v1(5); // Create a vector with 5 elements.
11
12	// Assign values into v1, using array-like index []
13	// You can retrieve the size of vector via size()
14	for (int i = 0; i < v1.size(); i++) {
15		v1[i] = (i + 1) * 2;
16	}
17
18	// Print vector content, using at()
19	for (int i = 0; i < v1.size(); i++) {
20		cout << v1.at(i) << " ";
21	}
22	cout << endl;
23
24	vector<int> v2;
25	// Assign v1 to v2 memberwise
26	v2 = v1;
27	for (int i = 0; i < v2.size(); i++) {
28		cout << v2[i] << " ";
29	}
30	cout << endl;
31
32	// Compare 2 vectors memberwise
33	cout << boolalpha << (v1 == v2) << endl;
34
35	// Append more elements - synamically allocate memory
36	v1.push_back(80);
37	v1.push_back(81);
38	for (int i = 0; i < v1.size(); i++) {
39		cout << v1[i] << " ";
40	}
41	cout << endl;
42
43	vector<string> v3;
44	v3.push_back("a for apple");
45	v3.push_back("b for boy");
46	for (int i = 0; i < v3.size(); i++) {
47		cout << v3[i] << " ";
48	}
49
50	cout << endl;
51
52	return 0;
53}

说明:

  • 你可以通过声明vector<int> v1(n)来初始化一个int类型的vector,其中n表示初始化的元素个数
  • 可以使用v1.size()来获取元素个数
  • 可以使用v1[i]v1.at(i)来访问元素,但是[]操作符不会做边界检查,而at()
  • 使用push_back()pop_back()添加和删除元素。vector会自动调整内存分配。

3. 函数模板

把处理不同类型的公共逻辑抽象成函数,就得到了函数模板。

定义函数模板的定义语法如下:

1template <typename T> OR template <class T>
2return-type function-name(function-parameter-list) { ...... }

Example 1

 1#include <iostream>
 2
 3using namespace std;
 4
 5template <typename T>
 6void mySwap(T &a, T &b);
 7
 8int main(int argc, char *argv[]) {
 9	int i1 = 1, i2 = 2;
10	mySwap(i1, i2);
11	cout << "i1 is " << i1 << ", i2 is " << i2 << endl;
12
13	char c1 = 'a', c2 = 'b';
14	mySwap(c1, c2);
15	cout << "c1 is " << c1 << ", c2 is " << c2 << endl;
16
17	double d1 = 1.1, d2 = 2.2;
18	mySwap(d1, d2);
19	cout << "d1 is " << d1 << ", d2 is " << d2 << endl;
20
21	return 0;
22}
23
24template <typename T>
25void mySwap(T &a, T &b) {
26	T temp;
27	temp = a;
28	a = b;
29	b = temp;
30}

C++编译器会为每种使用的类型都生成一个对应的函数,例如int型:

1void mySwap(int &a, int &b) {
2	int temp;
3	temp = a;
4	a = b;
5	b = temp;
6}

这样并不能为代码的执行效率和内存使用率带来提升,但是能够大大提高开发效率。

Example 2

 1#include <iostream>
 2
 3using namespace std;
 4
 5template<typename T>
 6T abs(T value) {
 7	T result;
 8	result = (value >= 0) ? value : -value;
 9	return result;
10}
11
12int main(int argc, char *argv[]) {
13	int i = -5;
14	cout << abs(i) << endl;
15
16	double d = - 55.5;
17	cout << abs(d) << endl;
18
19	float f = -555.5f;
20	cout << abs(f) << endl;
21
22	return 0;
23}

函数模板重载

 1#include <iostream>
 2
 3using namespace std;
 4
 5template<typename T>
 6void mySwap(T &a, T &b);
 7
 8template<typename T>
 9void mySwap(T a[], T b[], int size);
10
11template<typename T>
12void print(const T *const array, int size);
13
14int main(int argc, char *argv[]) {
15	int i1 = 1, i2 = 2;
16	mySwap(i1, i2);
17	cout << "i1 is " << i1 << ", i2 is " << i2 << endl;
18
19	const int SIZE = 3;
20	int arr1[] = {1, 2, 3}, arr2[] = {4, 5, 6};
21	mySwap(arr1, arr2, SIZE);
22
23	print(arr1, SIZE);
24	print(arr2, SIZE);
25	return 0;
26}
27
28
29template<typename T>
30void mySwap(T &a, T &b) {
31	T temp;
32	temp = a;
33	a = b;
34	b = temp;
35}
36
37template<typename T>
38void mySwap(T a[], T b[], int size) {
39	T temp;
40	for (int i = 0; i < size; i++) {
41		temp = a[i];
42		a[i] = b[i];
43		b[i] = temp;
44	}
45}
46
47template<typename T>
48void print(const T *const array, int size) {
49	cout << "(";
50	for (int i = 0; i < size; i++) {
51		cout << array[i];
52		if (i < size - 1) cout << ",";
53	}
54	cout << ")" << endl;
55}

显式特化

 1#include <iostream>
 2
 3using namespace std;
 4
 5template<typename T>
 6void mySwap(T &a, T &b);
 7
 8template<>
 9void mySwap<int>(int &a, int &b);
10
11int main(int argc, char *argv[]) {
12	double d1 = 1, d2 = 2;
13	mySwap(d1, d2);
14
15	int i1 = 1, i2 = 2;
16	mySwap(i1, i2);
17
18	return 0;
19}
20
21
22template<typename T>
23void mySwap(T &a, T &b) {
24	cout << "template" << endl;
25	T temp;
26	temp = a;
27	a = b;
28	b = temp;
29}
30
31template<>
32void mySwap<int>(int &a, int &b) {
33	cout << "specilization" << endl;
34	int temp;
35	temp = a;
36	a = b;
37	b = temp;
38}

4. 类模板

定义一个类模板的语法如下:

1template<class T>
2class ClassName {
3	......
4}

关键字’class’和’typename’都是用来定义模板的。使用定义好的模板的语法是:ClassName<actual-type>

例如:

 1#include <iostream>
 2using namespace std;
 3
 4template <typename T>
 5class Number {
 6private:
 7	T value;
 8public:
 9	Number(T value) { this->value = value; }
10	T getValue() const { return this->value; }
11	void setValue(T value) { this->value = value; }
12};
13
14int main(int argc, char *argv[]) {
15	Number<int> i(55);
16	cout << i.getValue() << endl;
17
18	Number<double> d(55.66);
19	cout <<d.getValue() << endl;
20
21	Number<string> s("hello");
22	cout << s.getValue() << endl;
23	return 0;
24}

将模板声明和定义分开

如果将函数实现和声明分开,就需要在每个函数实现上都使用"template"关键字,例如:

1template<typename T>
2T Number<T>::getValue() {
3	return value;
4}

将所有模板代码都放在头文件中

多参数类型

1template<typename T1, typename T2, ...>
2class ClassName { ...... }

默认类型

1template<typename T = int>
2class ClassName { ...... }

特化

 1// General Template
 2template<typename T>
 3class Complex { ...... }
 4
 5// Specialization for type double
 6template<>
 7class Complex<double> { ...... }
 8
 9// Specialization for type int
10template<>
11class Complex<int> { ...... }

5. 示例:MyComplex Template Class

MyComplex.h

  1/*
  2 * The MyComplex template class header (MyComplex.h)
  3 * All template codes are kept in the header, to be included in program
  4 * (Follow, modified and simplified from GNU GCC complex template class.)
  5 */
  6#ifndef MY_COMPLEX_H
  7#define MY_COMPLEX_H
  8
  9#include <iostream>
 10
 11// Forward declaration
 12template <typename T> class MyComplex;
 13
 14template <typename T>
 15std::ostream & operator<< (std::ostream & out, const MyComplex<T> & c);
 16template <typename T>
 17std::istream & operator>> (std::istream & in, MyComplex<T> & c);
 18
 19// MyComplex template class declaration
 20template <typename T>
 21class MyComplex {
 22private:
 23   T real, imag;
 24
 25public:
 26   // Constructor
 27   explicit MyComplex<T> (T real = 0, T imag = 0)
 28         : real(real), imag(imag) { }
 29
 30   // Overload += operator for c1 += c2
 31   MyComplex<T> & operator+= (const MyComplex<T> & rhs) {
 32      real += rhs.real;
 33      imag += rhs.imag;
 34      return *this;
 35   }
 36
 37   // Overload += operator for c1 += value
 38   MyComplex<T> & operator+= (T value) {
 39      real += value;
 40      return *this;
 41   }
 42
 43   // Overload comparison == operator for c1 == c2
 44   bool operator== (const MyComplex<T> & rhs) const {
 45      return (real == rhs.real && imag == rhs.imag);
 46   }
 47
 48   // Overload comparison != operator for c1 != c2
 49   bool operator!= (const MyComplex<T> & rhs) const {
 50      return !(*this == rhs);
 51   }
 52
 53   // Overload prefix increment operator ++c
 54   // (Separate implementation for illustration)
 55   MyComplex<T> & operator++ ();
 56
 57   // Overload postfix increment operator c++
 58   const MyComplex<T> operator++ (int dummy);
 59
 60   /* friends */
 61
 62   // (Separate implementation for illustration)
 63   friend std::ostream & operator<< <>(std::ostream & out, const MyComplex<T> & c); // out << c
 64   friend std::istream & operator>> <>(std::istream & in, MyComplex<T> & c);        // in >> c
 65
 66   // Overloading + operator for c1 + c2
 67   // (inline implementation for illustration)
 68   friend const MyComplex<T> operator+ (const MyComplex<T> & lhs, const MyComplex<T> & rhs) {
 69      MyComplex<T> result(lhs);
 70      result += rhs;  // uses overload +=
 71      return result;
 72   }
 73
 74   // Overloading + operator for c + double
 75   friend const MyComplex<T> operator+ (const MyComplex<T> & lhs, T value) {
 76      MyComplex<T> result(lhs);
 77      result += value;  // uses overload +=
 78      return result;
 79   }
 80
 81   // Overloading + operator for double + c
 82   friend const MyComplex<T> operator+ (T value, const MyComplex<T> & rhs) {
 83      return rhs + value;   // swap and use above function
 84   }
 85};
 86
 87// Overload prefix increment operator ++c
 88template <typename T>
 89MyComplex<T> & MyComplex<T>::operator++ () {
 90  ++real;   // increment real part only
 91  return *this;
 92}
 93
 94// Overload postfix increment operator c++
 95template <typename T>
 96const MyComplex<T> MyComplex<T>::operator++ (int dummy) {
 97   MyComplex<T> saved(*this);
 98   ++real;  // increment real part only
 99   return saved;
100}
101
102/* Definition of friend functions */
103
104// Overload stream insertion operator out << c (friend)
105template <typename T>
106std::ostream & operator<< (std::ostream & out, const MyComplex<T> & c) {
107   out << '(' << c.real << ',' << c.imag << ')';
108   return out;
109}
110
111// Overload stream extraction operator in >> c (friend)
112template <typename T>
113std::istream & operator>> (std::istream & in, MyComplex<T> & c) {
114   T inReal, inImag;
115   char inChar;
116   bool validInput = false;
117   // Input shall be in the format "(real,imag)"
118   in >> inChar;
119   if (inChar == '(') {
120      in >> inReal >> inChar;
121      if (inChar == ',') {
122         in >> inImag >> inChar;
123         if (inChar == ')') {
124            c = MyComplex<T>(inReal, inImag);
125            validInput = true;
126         }
127      }
128   }
129   if (!validInput) in.setstate(std::ios_base::failbit);
130   return in;
131}
132
133#endif

TestMyComplex.cpp

 1/* Test Driver for MyComplex template class (TestMyComplex.cpp) */
 2#include <iostream>
 3#include <iomanip>
 4#include "MyComplex.h"
 5
 6int main() {
 7   std::cout << std::fixed << std::setprecision(2);
 8
 9   MyComplex<double> c1(3.1, 4.2);
10   std::cout << c1 << std::endl;  // (3.10,4.20)
11   MyComplex<double> c2(3.1);
12   std::cout << c2 << std::endl;  // (3.10,0.00)
13
14   MyComplex<double> c3 = c1 + c2;
15   std::cout << c3 << std::endl;  // (6.20,4.20)
16   c3 = c1 + 2.1;
17   std::cout << c3 << std::endl;  // (5.20,4.20)
18   c3 = 2.2 + c1;
19   std::cout << c3 << std::endl;  // (5.30,4.20)
20
21   c3 += c1;
22   std::cout << c3 << std::endl;  // (8.40,8.40)
23   c3 += 2.3;
24   std::cout << c3 << std::endl;  // (10.70,8.40)
25
26   std::cout << ++c3 << std::endl; // (11.70,8.40)
27   std::cout << c3++ << std::endl; // (11.70,8.40)
28   std::cout << c3   << std::endl; // (12.70,8.40)
29
30// c1+c2 = c3;  // error: c1+c2 returns a const
31// c1++++;      // error: c1++ returns a const
32
33// MyComplex<int> c4 = 5;  // error: implicit conversion disabled
34   MyComplex<int> c4 = (MyComplex<int>)5;  // explicit type casting allowed
35   std::cout << c4 << std::endl; // (5,0)
36
37   MyComplex<int> c5;
38   std::cout << "Enter a complex number in (real,imag): ";
39   std::cin >> c5;
40   if (std::cin.good()) {
41      std::cout << c5 << std::endl;
42   } else {
43      std::cerr << "Invalid input" << std::endl;
44   }
45   return 0;
46}