c++类——模版(三)

本文最后更新于:1 年前

本文介绍模版的各种特殊情况:

(一)非类型模板参数(通常不应用于函数模版中):

  • 模板的非类型形参是内置类型形参,模板参数不是一个类型而是一个具体的值,且值是常量表达式,因此调用非类型模板形参的实参必须是一个常量表达式(因此模板代码不能修改参数的值,也不能使用参数的地址)。

    例如template<class T, int a> class B{}; 其中int a就是非类型的模板形参。

    通常能被传入的常量表达式包括全局变量的地址或引用,全局对象的地址或引用const类型变量,sizeof的表达式的结果以及const int 整型变量。

  • 形参只能是整型,枚举,指针和引用,如double,string等是不可以的,但是可以使用double&或者string*等类型是允许的。

  • 当一个模板被实例化时,非类型参数被一个用户提供的或者编译器推断出的值所代替。正因为模板在编译阶段编译器为我们生成一个对应的版本,所以其值应该能够编译时确定,那么他应该是一个常量或者常量表达式。

  • 非类型模板形参的形参和实参间所允许的转换

    • 允许从数组到指针,从函数到指针的转换。如:template <int *a> class A{}; int b[1]; A<b> m;即数组到指针的转换。
    • const修饰符的转换。如:template<const int *a> class A{}; int b; A<&b> m; 即从int *到const int *的转换。
    • 提升转换。如:template<int a> class A{}; const short b=2; A<b> m; 即从short到int的提升转换。
    • 整值转换。如:template<unsigned int a> class A{}; A<3> m; 即从int 到unsigned int的转换。
    • 常规转换。略。
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
#include<iostream>
using namespace std;

//定义一个栈,用数组实现,MAXSIZE为非类型模版类参数,其类型确定
template<typename T, int MAXSIZE>
class Stack
{
private:
int REALSIZE;
//这个非类型模版类参数可以在类中使用其名称
T elems[MAXSIZE];
public:
Stack() :REALSIZE(0) {}
bool empty() const { return REALSIZE == 0; }
bool full() const { return REALSIZE == MAXSIZE; }
void push(const T&);
void pop();
T& top();
const T& top() const;

};
template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(const T& elem)
{
if (full())
throw std::out_of_range("Stack<>::push(): full stack");
elems[REALSIZE++] = elem;
}
template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::pop()
{
if (!empty())
REALSIZE--;
else
throw std::out_of_range("Stack<>::pop(): empty stack");
}
template<typename T, int MAXSIZE>
T& Stack<T, MAXSIZE>::top()
{
if (empty())
throw std::out_of_range("Stack<>::top(): empty stack");
return elems[REALSIZE - 1];
}
template<typename T, int MAXSIZE>
const T& Stack<T, MAXSIZE>::top() const
{
if (empty())
throw std::out_of_range("Stack<>::top(): empty stack");
return elems[REALSIZE - 1];
}
//double类型,类类型不允许作为非类型模版类参数使用
//template<typename T, double MAXSIZE,string name>
//class A{};

//double引用类型,类指针类型允许作为非类型模版类参数使用
//枚举也可以,但不再演示
template<typename T,double& MAXSIZE,string*name>
class A
{
private:
double REALSIZE;
T elems[MAXSIZE];
string names;
public:
A(double d):REALSIZE(d) { names = *name; }
};

template<typename T, const string* p>
class B {};
template<typename T, unsigned int ui>
class C {};

double alld = 10.0;
double& alldd = alld;
string alls = "帅";
string* allss = &alls;
string s[3] = { "aaaaaa","bbbbbb","cccccc" };
int main()
{
//传入常量表达式
const int i = 5;
Stack<int, 10> s1;
Stack<string, i> s3;

//sizeof也可以
Stack<double, sizeof(double)> s2;

//int ii = 5;
//Stack<string, ii> s4; //错误,不能传入非常量变量

//double d = 10.0;
//double& dd = d;
//string s = "帅";
//string* ss = &s;
//A<int, dd, ss>; //错误,不能传入局部变量,非常量变量

//可以使用全局引用和全局变量的局部地址传入
A<int, alld, &alls> a1(10);
//A<int, alld, allss>; //错误,不能传入全局指针变量

//可以传入全局数组,会将其转换为指针传入
A<double, alld, s> a2(3.14);

//可以将string*提升为const string*
B<int, &alls> b;

//可以将short int提升为int
const short bbb = 2;
Stack<char, bbb>;

//可以将int提升为unsigned int
const int n = 9;
C<int, n>;
return 0;
}

(二)默认模板类型形参:

在c++11新规则中,可以为类模板或者函数模版的类型形参提供默认值。

形式为:template<class T1, class T2, ... , class Tk=默认类型 , ... , class Tn=默认类型 > class 类名{ 类体 };

or template<class T1 =默认类型 , class T2, ... , class Tk=默认类型 , ... , class Tn> 返回类型 类型名(形参列表){函数体}

类模版为多个默认模版参数声明指定默认值时,必须遵照“从右往左”的规则进行指定。

函数模版不需要遵循此规则,可以任意指定默认值。

函数模板的参数推导规则简单来说就是:能够从函数实参中推导出类型的话,那么默认模版参数就不会被使用,否则使用默认模版参数。

因此,函数模版如果需要提供默认类型,因为其形参列表是从右往左给出默认值的,所以只有其默认类型也是从右往左给出且与值的类型匹配(也就是不会出现不匹配情况)、值的个数匹配,才是有效的默认类型。

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
#include<iostream>
using namespace std;
template <typename T1, typename T2 = int, typename T3=double>
class A {
public:
T1 Aval1;
T2 Aval2;
T3 Aval3;
};
//template <typename T1 = int, typename T2>
//class DefClass2 {}; // 错误:模板参数的默认值没有遵循“由右往左”的规则
//
//template <typename T, int i = 0>
//class DefClass3 {};
//
//template <int i = 0, typename T>
//class DefClass4 {}; // 错误:即使是非类型模版参数,模板参数的默认值也必须遵循“由右往左”的规则

//尽管可以进行下面这样的声明,但几乎不会用上这个默认类型参数,可以直接无视
template <typename T1 = int, typename T2>
void B1(T1 a, T2 b) {
cout << a << endl;
cout << typeid(T1).name() << endl;
cout << b << endl;
cout << typeid(T2).name() << endl;
return;
}; // 函数模板不用遵循“由右往左”的规则

//允许进行这样的声明,当未提供第二个参数时,自动默认b为int类型且值为0
template <typename T1, typename T2= int>
void B2(T1 a, T2 b=0) {
cout << a << endl;
cout << typeid(T1).name() << endl;
cout << b << endl;
cout << typeid(T2).name() << endl;
return;
}; // 函数模板不用遵循“由右往左”的规则


template <int i = 0, typename T>
void B3(T a) {
cout << i << endl;
cout << typeid(i).name() << endl;
cout << a << endl;
cout << typeid(T).name() << endl;
return;
}; // 非类型模版参数也不用遵循“由右往左”的规则


int main()
{
A<string> a1;
cout << typeid(a1.Aval1).name() << endl;
cout << typeid(a1.Aval2).name() << endl; //为默认的int
cout << typeid(a1.Aval3).name() << endl; //为默认的double
A<char, unsigned int> a2;
cout << typeid(a2.Aval1).name() << endl;
cout << typeid(a2.Aval2).name() << endl; //unsigned int覆盖默认的int
cout << typeid(a2.Aval3).name() << endl; //为默认的double
A<char, unsigned int,float> a3;
cout << typeid(a3.Aval1).name() << endl;
cout << typeid(a3.Aval2).name() << endl; //unsigned int覆盖默认的int
cout << typeid(a3.Aval3).name() << endl; //float覆盖默认的double

B1(3, 4.1); //推导出int,double类型,和默认类型无关
B1(3.1, 4); //推导出double,int类型,覆盖默认类型
B2("s"); //前者推导出const char[2],后者为默认的int且值为0
return 0;
}
//运行结果:
// class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char> >
// int
// double
// char
// unsigned int
// double
// char
// unsigned int
// float
// 3
// int
// 4.1
// double
// 3.1
// double
// 4
// int
// s
// char const* __ptr64
// 0
// int

(三)模版的特化:

模版的特化是模版参数在某种特定类型下的具体实现,是对单一模版提供的一个特殊实例,它将一个或多个模版参数绑定到特定的类型或值上。

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,比如字符串不能进行比较,此时就需要特化出一个专门用于字符串的模版参数。

特化分为函数模版特化和类模版特化:

  • 函数模板特化:

    必须要先有一个基础的函数模板,且使用特换模板函数时格式有要求:

    1. 关键字template后面接一对空的尖括号<>。

    2. 函数名<特化类型>(特化类型 参数1, 特化类型 参数2 , …) 在函数名后跟<>其中写要特化的类型。

    函数形参表必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

    (实际上,此为全特化,c++11中,函数模版也可以进行偏特化的部分特化相似操作,但实际上这是一种重载,会导致函数有多个匹配,此时无法区分;不能进行偏特化的部分限制)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #include <iostream>
    using namespace std;

    template< typename T >
    int compare(const T& a, const T& b)
    {
    if (a > b)return 1;
    else if (a == b)return 0;
    else return -1;
    }

    template<>
    int compare(const char* const & a, const char* const & b)
    {
    return strcmp(a, b);
    }
    int main()
    {
    cout << compare(3, 4) << endl;
    cout << compare(4.2, 2.3) << endl;
    cout << compare((const char*)"vvv", (const char*)"ttt") << endl;
    return 0;
    }
  • 类模版特化:

    分为全特化和偏特化:

    • 全特化:将模版参数类型中所有的参数都确定化。

      全特化的优先级比偏特化要高。

    • 偏特化:对于模版的类型进行限制。

      偏特化有两种表现方式:

      • 部分特化:将模版参数表中的一部分参数特化。

      • 对于模版类型进行进一步的限制。(如必须特化为指针类型、引用类型)

    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
    #include<iostream>
    using namespace std;
    //类模版
    template<class T1, class T2>
    class Data
    {
    public:
    Data() {
    cout << "Data<T1,T2>" << endl;
    }
    private:
    T1 _d1;
    T2 _d2;
    };
    //全特化:
    template<>
    class Data<int, char>
    {
    public:
    Data() {
    cout << "Data<int,char>" << endl;
    }
    private:
    int _d1;
    char _d2;
    };
    //偏特化1:部分特化
    template<class T1>
    class Data<int, T1>
    {
    public:
    Data() {
    cout << "Data<int,T1>" << endl;
    }
    private:
    int _d1;
    T1 _d2;
    };

    //偏特化2:添加限制
    //两个参数偏特化为指针类型
    template <typename T1, typename T2>
    class Data <T1*, T2*>
    {
    public:
    Data() {
    cout << "Data<T1*,T2*>" << endl;
    }
    private:
    T1 _d1;
    T2 _d2;
    };

    //两个参数偏特化为引用类型
    template<class T1, class T2>
    class Data<T1&, T2&> {
    T1& _d1;
    T2& _d2;
    public:
    Data(T1& a, T2& b) :
    _d1(a),
    _d2(b)
    {
    cout << "Data<T1&,T2&>" << endl;
    }
    };


    int main()
    {
    Data<int, int> d1; // Data<T1,T2>
    Data<string, string> d2; // Data<T1,T2>
    Data<int, char> d3; // Data<int,char>,其实部分特化也满足,但优先进行全特化
    Data<int, double> d4; // Data<int,T1>
    Data<int*, double*> d5; // Data<T1*,T2*>
    int i = 5;
    int& pi = i;
    double d = 6.5;
    double& pd = d;
    Data<int&, double&> d6(pi,pd); //Data<T1&,T2&>
    return 0;
    }

c++类——模版(三)
https://github.com/xiaohei07/xiaohei07.github.io/2023/04/14/c++类——模版(三)/
作者
07xiaohei
发布于
2023年4月14日
许可协议