C++模板(高阶)
约 1227 个字 197 行代码 预计阅读时间 7 分钟
非类型模版参数
前面的模版中,使用的都是针对对象类型设计的模版参数,从而便于编译器针对不同类型推演出不同类型的函数或者类
但是有一种模版参数比较特殊,即非类型模版参数,有以下特点:
- 只可以定义为整型类型的常量
- 非类型的模板参数必须在编译期就能确认结果
示例代码:
C++ |
---|
| template<class T, size_t N = 10>
class A
{
private:
T arr[N];
};
|
在上面的代码中,T
即为类型模板参数,而N
即为非类型模板参数,并且因为size_t
代表无符号整型,所以属于整型系列,编译通过
非类型模板参数主要使用在为数组开辟空间,当需要使用该类中的数组时,可以使用默认的10作为数组大小,也可以自定义N
的大小从而确定数组的大小
在C++11中,新增了一个容器名为array
,底层就是对数组进行了一个封装,目的是方便处理数组的相关问题,比如越界访问
array
容器的定义:
C++ |
---|
| template < class T, size_t N > class array;
|
原来的数组是C类型的数组,该数组对越界访问的控制并不严格,甚至有时并不能发现是越界访问,所以针对这个问题,C++11添加了array
容器,从而更好地处理越界访问等问题
模板特化
在C++中,除了可以对任意类型使用模板以外,还可以使用模板特化来针对某一种类或者函数提供特殊的模板
模板特化分为全特化和偏特化,而对于类和函数来说,也分为类模板全特化和偏特化以及函数模板全特化和偏特化
函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字
template
后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
类模板全特化
所谓全特化,即特化的模板参数类型全部用指定的类型替换普通模板中的类型
例如下面的代码:
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
31
32
33
34
35
36
37
38
39 | #include <iostream>
using namespace std;
//普通模板
template<class T1, class T2>
class A
{
public:
A(T1 val1, T2 val2)
:num1(val1)
, num2(val2)
{}
private:
T1 num1;
T2 num2;
};
//全特化为int类型
template<>
class A<int, int>
{
public:
A(int val1, int val2)
:num1(val1)
,num2(val2)
{}
private:
int num1;
int num2;
};
int main()
{
A<double, int> a(1.9, 2);//调用普通模板
A<int, int> a1(1, 2);//调用全特化模板
return 0;
}
|
在上面的代码中,针对int
类型使用了全特化的类,此时如果使用两个int
类型的值创建对象,那么编译器会直接调用全特化的类进行构造
此时考虑下面的仿函数
C++ |
---|
| //仿函数
template<class T>
class less
{
public:
bool operator()(T val1, T val2)
{
return val1 < val2;
}
};
|
对于int
类型,double
类型这种普通的数值类型来说,直接比较并不会有什么问题(此处不考虑浮点数精度问题),但是如果为指针类型,那么比较方式会有不同,因为比较指针除了比较二者地址以外,还有比较指针指向的内容,而对于上面的比较大小的仿函数,如果直接将指针类型作为模板参数,那么比较的就是指针本身存的地址,如果此时想比较指针指向的内容时就需要用到全特化,参考下面的代码:
C++ |
---|
| //仿函数
//Date为自定义类型,并且已经重载*和<
template<>
class less<Date*>
{
public:
bool operator()(Date* val1, Date* val2)
{
return *val1 < *val2;
}
};
|
类模板偏特化
对比全特化,偏特化就是只有一部分是指定的类型,其余的部分还是普通的模板参数类型,例如下面的代码:
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | //偏特化为T和int类型
template<class T>
class A<T, int>
{
public:
A(T val1, int val2)
:num1(val1)
, num2(val2)
{}
private:
T num1;
int num2;
};
|
在上面代码中,只要第二个模板参数类型时int
类型时,就会走偏特化构造函数
现在有了下面三种模板:
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44 | //普通模板
template<class T1, class T2>
class A
{
public:
A(T1 val1, T2 val2)
:num1(val1)
, num2(val2)
{}
private:
T1 num1;
T2 num2;
};
//全特化为int类型
template<>
class A<int, int>
{
public:
A(int val1, int val2)
:num1(val1)
,num2(val2)
{}
private:
int num1;
int num2;
};
//偏特化为T和int类型
template<class T>
class A<T, int>
{
public:
A(T val1, int val2)
:num1(val1)
, num2(val2)
{}
private:
T num1;
int num2;
};
|
下面有三个对象:
C++ |
---|
| A<double, double> a1(1.2, 1.2);//调用普通模板
A<int, int> a2(1, 2);//调用全特化模板
A<double, int> a3(1.9, 2);//调用偏特化
|
因为double
和double
类型没有偏特化和全特化,所以走普通模板,而int
和int
类型有全特化,所以走全特化模板,对于double
和int
类型,因为有偏特化,所以走偏特化模板
所以在普通模板、偏特化模板和全特化模板中,匹配顺序依次是:
- 全特化
- 偏特化
- 普通模板
函数模板全特化与偏特化
函数模板的全特化与偏特化方式参考下面的代码:
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | //普通函数模板
template<class T1, class T2>
T1 add(T1 val1, T2 val2)
{
return val1 + val2;
}
//全特化函数模板
template<>
int add<int, int>(int val1, int val2)
{
return val1 + val2;
}
//偏特化函数模板
template<class T>
T add<int, T>(int val1, int val2)
{
return val1 + val2;
}
|
但是对于函数模板来说,一般不需要用到特化,只需要用函数重载+最匹配原则即可
模板分离编译
在C++中本身是不支持模板的声明和定义分别放在两个文件中,所以一般的处理方式有以下两种:
- 不写声明直接定义放在
.h
文件中 - 将声明写在定义之前,一般放在
.hpp
文件中
Note
一般的.hpp
文件即为声明和定义在一起,表示该文件中既有类和函数模板的声明,也有对应的定义
例如下面的.hpp
文件
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 | //函数模板声明
template<class T>
T Add(const T& left, const T& right);
//普通函数声明
void func();
//类模板声明
template<class T>
class Stack
{
public:
//成员函数声明
void Push(const T& x);
void Pop();
private:
T* _a = nullptr;
int _top = 0;
int _capacity = 0;
};
//函数模板定义
template<class T>
T Add(const T& left, const T& right)
{
cout << "T Add(const T& left, const T& right)" << endl;
return left + right;
}
//普通函数定义
void func()
{
cout << "void func()" << endl;
}
//成员函数定义
template<class T>
void Stack<T>::Push(const T& x)
{
cout << "void Stack<T>::Push(const T& x)" << endl;
}
//成员函数定义
template<class T>
void Stack<T>::Pop()
{
cout << "void Pop()" << endl;
}
|