第九课 模板
类属函数 templat function
- 同一函数对不同类型的数据完成相同的操作
- 宏实现:
- #define max(a,b) ((a)>(b)?(a):(b))
- 缺陷:
- 只能实现简单的功能
- 没有类型检查
函数重载
1 | int max(int,int); |
- 缺陷:
- 需要定义的重载函数太多
- 定义不全
- 不可以只有返回值不同
函数指针
1 | void sort(void * , unsigned int, unsigned int, int (* cmp) (void *, void *) ) |
- 缺陷:
- 需要定义额外参数
- 大量指针运算
- 实现起来复杂
- 可读性差
- template更加结构清晰,实现简单
函数模板
1 | //int和double都可以使用,编译器编译的并不是之下的代码,而是T转化成具体代码,然后分别编译 |
- 必须重载操作符 >
- 函数模板定义了一类重载的函数
- 函数模板的实例化:
- 隐式实现
- 根据具体模板函数调用
- 函数模板的参数
- 可有多个类型参数,用逗号分隔
- 可带普通参数
- 必须列在类型参数之后
- 调用时需显式实例化,使用默认参数值可以不显式实例化
- 可以有默认值
1 | template <class T1, class T2> |
- 函数模板与函数重载配合使用(编译器优先使用没有使用模板的函数)
- 优先匹配重载函数,再继续调用函数的显式、具体化的版本、再调用函数模板
1
2
3
4
5
6
7
8
9
10
11template <class T> T max(T a, T b) {
return a>b?a:b;
}
int x, y, z;
double l, m, n;
z = max(x,y);
l = max(m,n);
//为了解决max(x,m)我们使用函数重载更新,优先调用
double max(int a, double b) {
return a>b? a : b;
}
类属类
min
类定义带有类型参数,类属类需要显式实例化
类模板中的静态成员属于实例化后的类
类模板的实例化:创建对象时显式指定,指明类型参数
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
30class Stack {
int buffer[100];
public:
void push(int x);
int pop();
};
void Stack::push(int x) {...}
int Stack::pop(){...}
Stack st1;
-----------------------------------------------------------------------------
template <class T>
class Stack {
T buffer[100];
public:
void push( T x);
T pop();
};
// 声明T是类型参数
template <class T>
// <T> 用一个具体类型去实例化的stack,<>是用来区分不同类型的
void Stack <T> ::push(T x) {...}
// 装double的push和装int的push不是同一个push
template <class T>
T Stack <T> ::pop() {...}
//如下是显式实例化
Stack <int> st1;
Stack <double> st2;类模板中的静态成员属于实例化后的类,不是属于模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18template <class T, int size>
class Stack {
T buffer[size];
public:
void push(T x);
T pop();
};
template <class T, int size>
void Stack <T, size>::push(T x) {...}
template <class T, int size>
T Stack <T, size>::pop() {...}
Stack <int, 100 > st1 ;
//上面改为template<class T = int,int size = 100>
// 这里可以改成stack<> st1用来显示实例化
Stack <double, 200 > st2 ;类型参数也可以给出初始值,模板类如果不按照从右往左指定默认值参数,会导致编译错误
函数模板的默认值不一定是从右向左的,C++11之后函数模板才接受默认值参数。
总而言之从右向左给出默认值总是没有问题的
- 如果在模块A中要使用模块B中定义的某模板的实例,而在B中未使用这个实例,则模板无法使用这个实例
- 为什么C++中模板的完整定义常常出现在头文件中?
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//file1.h
template <class T> class S {
T a;
public:
void f();
};
//file1.cpp
template <class T>
void S<T>::f(){...}
template <class T>
T max(T x, T y){return x>y?x:y;}
void main() {
int a,b;
max(a,b);//实例化函数模板
S<int> x;
x.f();
}
//file2.cpp
extern double max(double,double);
void sub(){
max(1.1,2.2);//error
S<float> x;
x.f();//error
}
//不能通过编译,为什么?file2.cpp找不到max定义,也找不到完整的S代码
- 因为编译器使用模板时,没有分配存储空间。只有实例化时,才会分配模板空间。所以,因为file1中没有实例化
float
的模板,因此file2
中找不到。尝试自己实例化模板代码 - 但是,
file1
中只有声明,没有实现,因此无法实例化代码 - 所以C++中模板的完整定义常常出现在头文件中
Template MetaProgramming 元编程
- 元程序就是编写一些直接可以生成或者操作其他程序的程序,要在更高层次上。
- 编写元程序就是元编程(两级编程),在编译的时候就已经完成编程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22template<int N>
class Fib
{
public:
enum { value = Fib<N - 1>::value + Fib<N - 2>::value };
};
//模板显式实例化
template<> // 需要一个template<> 标明是特殊实例化
class Fib<0>{
public:
enum { value = 1 };
};
template<>
class Fib<1>
{
public:
enum { value = 1 };
};
void main() {
cout << Fib<8>::value << endl;// calculated at compile time
}
易错
默认参数
1 | template<int i = 0, typename T = double> |
- 尖括号中的代表的是模板参数,圆括号中代表的是函数参数
- 模板参数中,可以提供类型参数
typename T
,对类型T
进行默认类型的指定T = double
,也可以提供实际的参数i
,提供默认值i = 0
类模板:通用的类描述(使用泛型来定义类),进行实例化时,其中的泛型再用具体的类型替换。
函数模板:通用的函数描述(使用泛型来定义函数),进行实例化时,其中的泛型再用具体的类型替换。
如果能够从函数实参中推导出类型的话,那么默认模板参数就不会被使用,反之,默认模板参数则可能会被使用。
1 | template <class T, class U = double> |
定义了一个函数模板f
,f
同时使用了默认模板参数和默认函数参数。
可以看到,由于函数的模板参数可以由函数的实参推导而出:
在f(1)这个函数调用中,实例化出了模板函数的调用应该为f<int, double>(1, 0),其中,第二个类型参数U使用了默认的模板类型参数double,而函数实参则为默认值0。
类似地,f
而表达式f()由于第一类型参数T的无法推导,从而导致了编译的失败。
而通过这个例子也可以看到,默认模板参数通常是需要跟默认函数参数一起使用的。
1 | template <typename R = int, typename U> |
func(97)
是根据函数的实参得出,val = 97,因此U
就是int
,所以R
就是默认值int
- 此处同理可得
U
是int
,但<>
中有显式地指明了char
,所以R
就是char
而不是默认值int
报错1
1 | template<int i, typename T> |
此处第8行会报错,因为模板参数中i
没有提供默认值了,所以需要显式的给出i
的值
报错2
类模板在为多个默认模板参数声明指定默认值时,必须遵照“从右往左”的规则进行指定。
而这个规则对函数模板来说并不是必须的。
1 | template <typename T1, typename T2 = int> |
报错3
1 | template<typename T = int, int size = 0> |
- 在类模板的外部定义类中的成员时template 后的形参表应**省略默认的形参类型 **
template<typename T = int, int size>
- 在类模板的外部定义类中的成员时template 后的形参表应省略默认的参数值
template<typename T, int size = 0>
正确的是template<typename T,int size>
小结
- 函数自身的默认参数需要遵循从右往左的原则
- 模板类中提供的默认参数需要遵循从右往左的原则
- 模板参数中提供的默认参数不需要遵循从右往左的原则
- 在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型
- 在类模板的外部定义类中的成员时template 后的形参表应省略默认的参数值
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 丁丁的小窝(*^_^*)!