模板是一种泛型技术,目的是将数据的类型参数化,以增强 C++
语言(强类型语言)的灵活性。C++
对模板的支持非常自由,模板中除了可以包含类型参数,还可以包含非类型参数,例如:
1 2
| template<typename T, int N> class Demo{ }; template<class T, int N> void func(T (&arr)[N]);
|
T 是一个类型参数,它通过 class
或 typename
关键字指定。N
是一个非类型参数,用来传递数据的值,而不是类型,它和普通函数的形参一样,都需要指明具体的类型。类型参数和非类型参数都可以用在函数体或者类体中。
当调用一个函数模板或者通过一个类模板创建对象时,非类型参数会被用户提供的、或者编译器推断出的值所取代。
在函数模板中使用非类型参数
在 《C++函数模板的重载》一节中,我们通过 Swap()
函数来交换两个数组的值,其原型为:
1
| template<typename T> void Swap(T a[], T b[], int len);
|
形参 len 用来指明要交换的数组的长度,调用 Swap() 函数之前必须先通过
sizeof 求得数组长度再传递给它。
这是因为,数组作为参数传递给函数形参的时候,会被转换为指针类型。而单纯通过这个指针无法获取数组长度。
多出来的形参 len
给编码带来了不便,我们可以借助模板中的非类型参数将它消除,请看下面的代码:
1 2 3 4 5 6 7 8
| template<typename T, unsigned N> void Swap(T (&a)[N], T(&b)[N]) { T temp; for (int i=0; i<N; i++) { temp = a[i]; a[i] = b[i]; b[i] = temp; } }
|
T (&a)[N]
表明 a 是一个引用,它引用的数据类型是
T[N]
,也即一个数组;T (&b)[N]
也是类似的道理。分析一个引用和分析一个指针的方法类似,编译器总是从它的名字开始读取,然后按照优先级顺序依次解析。
调用 Swap() 函数时,需要将数组名字传递给它:
1 2 3
| int a[5] = {1, 2, 3, 4, 5}; int b[5] = {10, 20, 30, 40, 50}; Swap(a, b);
|
编译器会使用数组类型 int 来代替类型参数 T,使用数组长度 5
来代替非类型参数 N。
下面是一个完整的示例:
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
| #include <iostream> using namespace std;
template<class T> void Swap(T &a, T &b); template<typename T, unsigned N> void Swap(T (&a)[N], T (&b)[N]);
template<typename T, unsigned N> void printArray(T (&arr)[N]);
int main() { int m = 10, n = 99; Swap(m, n); cout << m << ", " << n << endl;
int a[5] = {1, 2, 3, 4, 5}; int b[5] = {10, 20, 30, 40, 50}; Swap(a, b); printArray(a); printArray(b);
return 0; }
template<class T> void Swap(T &a, T &b) { T temp = a; a = b; b = temp; }
template<typename T, unsigned N> void Swap(T (&a)[N], T (&b)[N]) { T temp; for (int i = 0; i < N; ++i) { temp = a[i]; a[i] = b[i]; b[i] = temp; } }
template<typename T, unsigned N> void printArray(T (&arr)[N]) { for (int i = 0; i < N; ++i) { if (i == N - 1) { cout << arr[i] << endl; } else { cout << arr[i] << ", "; } } }
|
在模板中使用非类型参数
CPP
规定,数组一旦定义后,它的长度就不能改变了;换句话说,数组容量不能动态地增大或者减小。这样的数组称为静态数组(static
array)。静态数组有时候会给编写代码带来不便,我们可以通过自定义的 Array
类来实现动态数组(dynamic
array)。所谓动态数组,是指数组容量能够在使用的过程中随时增大或减小。
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
| #include <iostream> #include <cstring> #include <cstdlib> using namespace std;
template<typename T, int N> class Array { public: Array(); ~Array();
public: T& operator[](int i); int length() const { return m_length; } bool capacity(int n); void print(); private: int m_length; int m_capacity; T *m_p; };
template<typename T, int N> Array<T, N>::Array() { m_p = new T[N]; m_capacity = m_length = N; }
template<typename T, int N> Array<T, N>::~Array() { delete []m_p; }
template<typename T, int N> T & Array<T, N>::operator[](int i) { if (i < 0 || i > m_length -1) { cout << "Exception: index out of range." << endl; }
return m_p[i]; }
template<typename T, int N> bool Array<T, N>::capacity(int n) { if (n > 0) { int len = m_length + n; if (len < m_capacity) { m_length = len; return true; } else { T *pTemp = new T[m_length + 2 * n * sizeof(T)]; if (pTemp == NULL) { cout << "Exception: Failed to allocate memory" << endl; return false; } else { memcpy(pTemp, m_p, m_length * sizeof(T)); delete []m_p; m_p = pTemp; m_capacity = m_length = len; return true; } } } else { int len = m_length - abs(n); if (len < 0) { cout<<"Exception: Array length is too small!"<<endl; return false; } else { m_length = len; return true; } } }
template<typename T, int N> void Array<T, N>::print() { for (int i = 0; i < m_length; ++i) { cout << m_p[i] << " "; } cout << endl; }
int main() { Array<int, 5> arr;
for (int i = 0; i < arr.length(); ++i) { arr[i] = 2 * i; }
arr.print();
arr.capacity(8); for (int j = 5; j < arr.length(); ++j) { arr[j] = 2 * j; }
arr.print();
arr.capacity(-4); arr.print(); }
|
运行结果:
1 2 3
| 0 2 4 6 8 0 2 4 6 8 10 12 14 16 18 20 22 24 0 2 4 6 8 10 12 14 16
|
Array 是一个类模板,它有一个类型参数 T 和一个非类型参数 N,T
指明了数组元素的类型,N 指明了数组长度。
capacity() 成员函数是 Array
类的关键,它使得数组容量可以动态地增加或者减小。传递给它一个正数时,数组容量增大;传递给它一个负数时,数组容量减小。
之所以能通过 [] 来访问数组元素,是因为在 Array
类中以成员函数的形式重载了 []
运算符,并且返回值是数组元素的引用。如果直接返回数组元素的值,那么将无法给数组元素赋值。
非类型参数的限制
非类型参数不能随意指定,它受到了严格的限制,只能是一个整数,或者是一个指向对象或函数的指针(也可以是引用)。引用和指针在本质上是一样的。
- 当非类型参数是一个整数时,传递给它的实参,或者由编译器推导出的实参必须是一个常量表达式,例如
10、 2 * 30 等,但不能是 n、10 + n 等(n 是变量)。
对于上面的 Swap() 函数,下面的调用就是错误的:
1 2 3 4 5
| int len; cin >> len; int a[len]; int b[len]; Swap(a, b);
|
对上面的 Array 类,以下创建对象的方式是错误的:
1 2 3
| int len; cin >> len; Array<int, len> arr;
|
这两种情况,编译器推导出来的实参是 len,是一个变量,而不是常量。
- 当非类型参数是一个指针(引用)时,绑定到该指针的实参必须具有静态的生存期;换句话说,实参必须存储在虚拟地址空间中的静态数据区。局部变量位于栈区,动态创建的对象位于堆区,它们都不能用作实参。