c++类——模版(二)

本文最后更新于:1 年前

(一)函数模版:

重载函数通常基于不同的数据类型实现类似的操作,对不同数据类型有完全相同的操作,用函数模版实现更为简介方便。

1. 格式与定义:

template <类型形式参数表>  返回类型 函数名(参数列表) { 函数体 }

类型形式参数表= class T1, class T2, ... , class Tn or typename T1, typename T2, ... , typename 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
#include<iostream>
using namespace std;
template <typename T>
bool IsEqual(const T& left, const T& right)
{
return left == right;
}

template <typename T1,typename T2>
bool IsEqual2(const T1& left, const T2& right)
{
return left == right;
}

int main()
{
cout << IsEqual(1, 1) << endl; // 隐式实例化
//cout<<IsEqual(1,1.2)<<endl; // 模板参数不匹配,不知道是把int推断为double还是double推断为int
cout << IsEqual<int>(1, 1.2) << endl; // 显式实例化
cout << IsEqual<double>(1, 1.2) << endl; // 显式实例化

cout << IsEqual2(1, 1) << endl; //隐式实例化,表示IsEqual2<int,int>
cout << IsEqual2(1,1.2)<<endl; //隐式实例化,表示IsEqual2<int,double>
cout << IsEqual2<int,double>(1, 1.2) << endl; // 显式实例化
cout << IsEqual2<double,int>(1, 1.2) << endl; // 显式实例化,注意此处1.2转为int后为1,返回true
return 0;
}
//运行结果:
//1
//1
//0
//1
//0
//0
//1

2. 重载函数模版:

当模版类型不能满足需要(不能提供类型的隐式转换)时,函数模版可以进行重载(效果和普通的函数重载一致),而且可以和普通重载函数并存。

此时如果均存在匹配,涉及到了匹配的优先级关系,为此,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
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
#include<iostream>
using namespace std;
bool IsEqual(const int& left,const int& right)//普通函数重载
{
cout << "重载普通函数1" << endl;
return left == right;
}

bool IsEqual(const double& left, const double& right)//普通函数重载
{
cout << "重载普通函数2" << endl;
return left == right;
}

template <typename T>
bool IsEqual(const T& left, const T& right)
{
cout << "模版" << endl;
return left == right;
}

template <typename T1,typename T2>
bool IsEqual(const T1& left, const T2& right)//重载,参数类型不同
{
cout << "重载模版1,参数类型不同" << endl;
return left == right;
}

template <typename T>
bool IsEqual(const T& right) //重载,参数个数不同
{
cout << "重载模版2,参数个数不同" << endl;
return 1 == right;
}

//当同时使用下面两个函数模版时,如果传入(double,int)类型的参数,将会导致二义性错误。
//template <typename T1>
//bool IsEqual(const T1& left, const int& right)//重载,参数类型不同
//{
// cout << "重载模版3,参数类型不同" << endl;
// return left == right;
//}
//
//template <typename T2>
//bool IsEqual(const double& left, const T2& right)//重载,参数类型不同
//{
// cout << "重载模版3,参数类型不同" << endl;
// return left == right;
//}


int main()
{
cout << IsEqual(1, 1) << endl;//因为有最佳匹配的普通函数1,调用该函数
cout << IsEqual(1,1.2)<<endl; //因为没有最佳匹配的普通函数,检查模版类,检查到有最佳的匹配重载模版1,调用该函数模版
cout << IsEqual(1.2, 1.2) << endl; //因为有最佳匹配的普通函数2,调用该函数
cout << IsEqual(1.2, 1) << endl; //因为没有最佳匹配的普通函数,检查模版类,检查到有最佳的匹配重载模版1,调用该函数模版
cout << IsEqual("1", "1") << endl; //因为没有最佳匹配的普通函数,检查模版类,检查到有最佳的匹配重载模版,调用该函数模版
cout << IsEqual<int,double>(1, 1) << endl; //显式说明调用的是函数模版,寻找到最佳匹配调用
cout << IsEqual<double,int>(1, 1.2) << endl; //显式说明调用的是函数模版,寻找到最佳匹配调用
cout << IsEqual<char>('1') << endl;; //显式说明调用的是函数模版,寻找到最佳匹配调用
return 0;
}
//运行结果:
//重载普通函数1
//1
//重载模版1, 参数类型不同
//0
//重载普通函数2
//1
//重载模版1, 参数类型不同
//0
//模版
//1
//重载模版1, 参数类型不同
//1
//重载模版1, 参数类型不同
//1
//重载模版2,参数个数不同
//0

(二)类模版:

类模版用于实现类所需数据的类型参数化。

类模版在表示如数组、表、图等数据结构上显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响。

1. 格式:

template<类型形式参数表> class 类名{ ... };

类属参数可以用于声明类中的成员变量和成员函数,在类中使用内置雷类型的地方也都可以用类属参数来声明。

另外,类模版中的成员函数放到类模版定义外面写时的语法有限制:

template<类型形式参数表>

返回值类型 类模板名<类型参数名列表>::成员函数名(参数表) { 函数体 }

创建对象的格式:

类模板名<真实类型参数表> 对象名(构造函数实际参数表);

如果有无参构造函数,可以是:

类模板名<真实类型参数表> 对象名;

注意:类模版形参不存在实参推演的问题,不能给真实类型参数表传递实参,只能传递类型。

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
#include<iostream>
using namespace std;
template<class T>
class Person {
private:
T mAge;
T mID;
public:
//类内定义类的成员函数
Person(const Person<T>& a)
{
this->mAge = a.mAge;
this->mID = a.mID;
}
Person() {}
//T在返回值中
T getage()
{
return mAge;
}
T getmID()
{
return mID;
}
//类外定义类的成员函数
//T在传入形参中
Person(T age, T id);
void Show();

};
//外部实现成员函数
template<class T>
Person<T>::Person(T age, T id) {
this->mID = id;
this->mAge = age;
}
template<class T>
void Person<T>::Show() {
cout << "Age:" << mAge << " ID:" << mID << endl;
}
int main()
{
Person<int> p1(10,20021111); //一般声明
Person<string>p2("15", "20031111");
Person<string>p3(p2);
Person<char> p4; //无参构造的说明
//Person<2> p5; //错误,没有实参类型推导,不能这样操作,必须是类型说明符
p1.Show();
p2.Show();
p3.Show();
cout << p1.getage() << endl;
cout << p2.getmID() << endl;
return 0;
}
//运行结果:
//Age:10 ID : 20021111
//Age : 15 ID : 20031111
//Age : 15 ID : 20031111
//10
//20031111

2. 类模版作为函数参数:

函数的形式参数类型可以是类模版或类模版的引用,对应的实际参数是该类模版实例化的模版类对象。

因此,只有函数模版能够拥有模版类参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
using namespace std;
template<class T>
class Array {
public:
T n;
};
//声明函数模板
template<class T>
T funtest(Array<T> a) { cout << a.n << endl; return a.n; }

int main()
{
Array<int> a;
a.n = 1;
//函数模板调用类模板的实例化对象
cout << funtest(a) << endl;
return 0;
}

3. 模板类和类模板:

类模板是模板的一种,可以在使用确定类属参数。

类模板不是一个类,不会生成对象,也不会占据空间。

模板类是类模板的一个实例,是确定了类属参数的具体类,可以直接生成对象。

1
2
3
4
5
//模板类:
//template<class T>
//class Array {};
//类模板:
Array<double> array;

4. 函数模板作为类模版成员:

类模板中的成员函数还可以是一个函数模板成员,函数模板只有在被调用时才会被实例化。

无论是原则还是声明,模板语法的优先级是最高的,不同模板的优先级先根据其声明顺序来判断,其次是函数修饰,然后是返回值。

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
#include <iostream>
using namespace std;
template <class T>
class A
{
public:
template <class T2>
void Func(T2 t) { cout << t << endl; return; } //成员函数模板
template <class T3>
A<T> Func2(T3 t); //类外定义:
};
template<class T3> //最外层类模板对应的类属参数
template<class T> //内层函数模板对应的类属参数
A<T3> //返回值
A<T3>::Func2(T t) //函数名和形参表
{
cout << t << endl; //函数体
return *this;
}
int main()
{
A<int> a;
a.Func<int>('K'); //成员函数模板Func被实例化
a.Func("hello");
a.Func2("world");
return 0;
}
//运行结果:
//75
//hello
//world

5. 类层次中的类模版:

在类层次中,类模板可以是基类,也可以是派生类。

类模板可以从类模板或普通类派生:

  • 当一个类模板从普通类派生时,意味着派生类增加了类属参数。

  • 当一个类模板从类模板派生时,可以添加新的类属参数,也可以保持,同时其他性质和一般的类之间的派生性质相同。

普通类可以从模板类或普通类派生:

  • 当一个普通类从类模板派生时,意味着派生类继承基类时,需要提供实例化的类型参数使派生的普通类内没有类属参数,同时需要编译分配内存。

  • 普通类之间的派生不涉及模板。

类模板之间允许有多继承关系(性质简单,此略)。

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
#include <iostream>
using namespace std;

//类模板派生普通类
template<class T>
class Person {
public:
T age;
Person() :age(5) {}
void print()
{
cout << "age:" << age << endl;
}
};
//由类模板派生普通类时,提供实例化的类型参数,编译分配内容
class SubPerson : public Person<int>
{
public:
double age;
//构造函数调用不需要指定Person后面的类型
SubPerson() :age(10), Person() {}
void print()
{
cout << "age:" << age << endl;
}
};


//类模板派生类模板
template<class T>
class Animal {
public:
T age;
Animal() :age(5) {}
void print()
{
cout << "age" << age << endl;
}
};
template<class T, class T1> //增加新的类属参数
class Cat : public Animal<T>
{
public:
T1 age;
//构造函数调用需要指定Animal后面的类型
Cat() :Animal<T>(), age(10) {}
void print()
{
cout << "age" << age << endl;
}
};


//普通类派生类模板,需要增加类属参数
class Student
{
public:
int age;
Student() :age(5) {}
void print()
{
cout << "age" << age << endl;
}
};
template<class T>
class GoodStudent:public Student
{
public:
T age;
GoodStudent() :Student(), age(10) {}
void print()
{
cout << "age" << age << endl;
}
};


int main(void)
{
Person<double> p;
SubPerson pa;
Animal<double> a;
Cat<int,double> cat;
Student s;
GoodStudent<double> gs;
p.print();
pa.print();
a.print();
cat.print();
s.print();
gs.print();
return 0;
}
//运行结果:
//age:5
//age:10
//age5
//age10
//age5
//age10

6. 类模版和友元:

模板类的友元分三类:

1,非模板友元。

2,约束模板友元,即友元的类型取决于类被实例化时的类型。

3,非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元。

  • 一个函数或函数模板或函数模板的实例化(这个函数可以是某个类的成员函数)可以是类或类模板的友元——这会使其成为由该类模板实例化的每个类模板的友元函数。

  • 一个类或类模板或者一个类模板的实例化可以是类或类模板的友元类——逻辑同上。

所有友元中的函数的参数或者返回值可以是该模板类的类属参数。

友元的内容十分复杂,具体内容详见代码的讲解:

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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#include <iostream>
using namespace std;

//前置声明
template<class T>
class base;

//类/类模板作友元均需要提前声明
class A1;

template<class TA2>
class A2;

template<class TA3>
class A3;

template<class TA4>
class A4;

//函数模板作友元需要提前声明;
template<class T>
void externprint2();

template<class T>
T getvalue2(const base<T>& b);

template<class T>
class base
{
private:
T val;
public:
base(T value) :val(value) {}
//非模板友元:
friend void externprint(); //普通函数友元,函数没有参数,只能对全局类变量操作
friend T getvalue(const base<T>& b); //普通函数友元(不是函数模板),函数有参数,可以操作对应类型
friend class A1; //类友元
friend class A2<int>; //类模板实例化的友元,只有此一种是其所有实例化的友元
//约束模板友元
friend void externprint2<T>(); //函数模板友元,且两者的实例化友元一一对相应
friend T getvalue2<>(const base<T>& b); //函数模板友元,且两者的实例化友元一一对相应
friend class A3<T>; //类模板友元,且两者的实例化友元一一对应

//非约束模板友元
template<class T1>
friend bool externprint3(T1 t); //函数模板友元,两者的对应关系是自由的
template<class TA2>
friend class A4; //类模板友元,两者的对应关系是自由的
};
base<double> bd(3.14);
base<int> bi(5);

void externprint()
{
cout << sizeof(bi.val) << ",";
cout << sizeof(bd.val) << endl;
return;
}

//可以重载多个类型
int getvalue(const base<int>& b)
{
cout << "int base:" << b.val << endl;
return b.val;
}
double getvalue(const base<double>& b)
{
cout << "double base:" << b.val << endl;
return b.val;
}

template<class T>
void externprint2()
{
if(bd.val)//证明是友元函数
cout << sizeof(base<T>) << endl;
return;
}

template<class T>
T getvalue2(const base<T>& b)
{
cout << typeid(base<T>).id() << " base:" << b.val << endl;
return b.val;
}

template<class T1>
bool externprint3(T1 t)
{
if (t == bd.val)return true;
else return false;
}

class A1
{
base<int> b1;
base<double> b2;
base<char> b3;
base<string> b4;
public:
A1() :b1(3), b2(3.14), b3('a'), b4("bbb") {}
void print()
{
cout << b1.val << endl;
cout << b2.val << endl;
cout << b3.val << endl;
cout << b4.val << endl;
cout << endl;
}
};

template<class TA2>
class A2
{
private:
base<int> b1;
base<double> b2;
base<char> b3;
base<string> b4;
public:
TA2 bb;
A2() :b1(3), b2(3.14), b3('a'), b4("bbb") {}
void print()
{
cout << b1.val << endl;
cout << b2.val << endl;
cout << b3.val << endl;
cout << b4.val << endl;
bb = b1.val;
cout << bb << endl;
cout << endl;
}
};

template<class TA3>
class A3
{
base<int> b1;
base<double> b2;
base<char> b3;
base<string> b4;
public:
TA3 bb;
A3() :b1(3), b2(3.14), b3('a'), b4("bbb") {}
void print()
{
cout << b1.val << endl;
cout << b2.val << endl;
cout << b3.val << endl;
cout << b4.val << endl;
bb = b3.val;
cout << bb << endl;
cout << endl;
}
void print2()
{
cout << b1.val << endl;
bb = b1.val;
cout << bb << endl;
cout << endl;
}
void print3()
{
cout << b3.val << endl;
bb = b3.val;
cout << bb << endl;
cout << endl;
}
};

template<class TA4>
class A4
{
base<int> b1;
base<double> b2;
base<char> b3;
base<string> b4;
public:
TA4 bb;
A4() :b1(3), b2(3.14), b3('a'), b4("bbb") {}
void print()
{
cout << b1.val << endl;
cout << b2.val << endl;
cout << b3.val << endl;
cout << b4.val << endl;
bb = b4.val;
cout << bb << endl;
cout << endl;
}
};

int main()
{

A1 a1;
A2<int> a21;
A2<double> a22;
A3<int> a31;
A3<char> a32;
A4<string> a41;
A4<char> a42;
externprint(); //访问全局base对象的数据成员
//externprint2<int>(); //访问bd是base<double>,由一一对应关系,此处无法访问bd.val
externprint2<double>(); //bd与double均为T,是一一对应关系,可以访问
cout << externprint3(3) << endl; //由于是自由对应关系,两个externprint3均可访问b.d
cout << externprint3(3.14) << endl;
cout << endl;
a1.print();
a21.print();
//a22.print(); //只有A2<int>这个实例化是友元类,A2<double>因此没有访问base私有数据的权限
//a31.print(); //print访问多种类型,由于A3和base是模板类型的一一对应关系,此处只能访问int
a31.print2(); //A3<int>只能对base<int>访问
a32.print3(); //A3<char>只能对base<char>访问
a41.print(); //由自由对应关系,可以访问任意base实例化的val
//a42.print(); //无法转换string为char
return 0;
}
//运行结果:
//4, 8
//8
//0
//1
//
//3
//3.14
//a
//bbb
//
//3
//3.14
//a
//bbb
//3
//
//3
//3
//
//a
//a
//
//3
//3.14
//a
//bbb
//bbb

7. 类模版和static成员:

从类模版实例化的每个模版类有自己的类模版数据成员,该模版类的所有对象共享一个static数据成员。

每个模版类有自己的类模板的static数据成员副本,这之间不是直接共享的。

和非模版类的static数据成员一样,模版类的static数据成员也应该在文件范围定义和初始化。

static数据成员可以用类属参数对应的类型声明,在外部定义时无需赋值,但是需要在前面说明template和类属参数表,以对类模版的相关模版类进行定义,这些static成员在创建模版类时自动默认初始化(如int则初始化为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
#include<iostream>
using namespace std;
template<class T>
class A
{
protected:
T val;
//int类型:
static int totalcount;
//抽象类型
static T totalval;
public:

A(T v) :val(v) { totalcount += 1; totalval += val; }
static T gettotalval() { return totalval; }
static int gettotalcount() { return totalcount; }
T getval() { return val; }
~A() { totalcount -= 1; totalval -= val; }
};
//定义确定类型静态数据成员
template<class T>
T A<T>::totalval;
//定义抽象类型静态数据成员
template<class T>
int A<T>::totalcount=0;
int main()
{
A<double> a1(5.5), a2(4.5), a3(3);
A<int> a4(5), a5(4);
//两个模版类的静态数据成员在模版类内共享,但互相间独立。
cout << A<double>::gettotalcount() << endl;
cout << A<double>::gettotalval() << endl;
cout << A<int>::gettotalcount() << endl;
cout << A<int>::gettotalval() << endl;
return 0;
}
//运行结果:
//3
//13
//2
//9

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