模板概述
引入原因
考虑这样一个问题:多个函数的参数个数相同,处理代码雷同,仅仅是处理的数据类型(参数或者返还结果)不同,如何处理?
存在多个函数的版本,代码重复,函数命名复杂混乱,客户代码难以使用
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 ; }
编译器生成的函数实例
通过函数模板技术,我们只需要维护一份代码骨架,由编译器编译时根据需要代替我们生成代码,解决了代码冗余、维护成本高的问题
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 ); max(4 , 4.2 );
函数后置类型声明
在可能出现二义性的情况下,显示指定函数的参数类型是一种良好的编程习惯
1 2 3 4 5 6 7 8 9 template <typename T>T max (T a, T b) { return a > b ? a : b; } 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 ;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 <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) { size = a.size; pData = a.pData; a.size = 0 ; a.pData = null; } template <typename T>MyVector<T>& MyVector<T>::operator =(MyVector<T>&& a) { 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 () { MyVector<int > array1 (5 ) ; 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
那句话
函数模板和类模板函数的声明与定义放在一起,分开写会报链接错误
这是因为函数模板要被实例化后才能成为真正的函数,在使用函数模板的源文件中包含函数模板的头文件 ,如果该头文件中只有声明没有定义,那编译器无法实例化该模板,最终导致链接错误
由于上述函数模板和类模板的性质我们也可以推出:在声明与定义中出现语法错误运行时未必会报错,只有被用来实例化的时候无法编译才会报错