C++基础知识之"类模板"

fengjingtu

为什么需要类模板

类模板与函数模板的定义和使用类似,我们已经进行了介绍。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:

1
2
3
4
5
6
7
//类模板由模板说明和类说明构成
//template <类型形式参数表>
template<typename Type>
class TClass{
private:
Type DateMember;
}

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

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

单个类模板语法

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
//类的类型参数化抽象的类
//单个类模板
template<typename T>
class A
{
public:
A(T t)
{
this->t = t;
}

T &getT()
{
return t;
}
protected:
public:
T t;
};

void main()
{
//模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
A<int> a(100);
a.getT();
printAA(a);
return ;
}

继承中的类模板语法

子类从模板类继承的时候,需要让编译器知道父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)

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

class B : public A<int>
{
public:
B(int i) : A<int>(i)
{

}
void printB()
{
cout<<"A:"<<t<<endl;
}
protected:
private:
};

//模板与上继承
//怎么样从基类继承
//若基类只有一个带参数的构造函数,子类是如何启动父类的构造函数
void pintBB(B &b)
{
b.printB();
}
void printAA(A<int> &a) //类模板做函数参数
{
//
a.getT();
}

void main()
{
A<int> a(100); //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
a.getT();
printAA(a);

B b(10);
b.printB();


cout<<"hello..."<<endl;
system("pause");
return ;
}

知识体系

所有的类模板函数写在类的内部
所有的类模板函数写在类的外部,在一个cpp中

构造函数 没有问题
普通函数 没有问题
友元函数:用友元函数重载 << >>

1
friend ostream& operator<< <T> (ostream &out, Complex<T> &c3) ;

友元函数:友元函数不是实现函数重载(非 << >>)
1)需要在类前增加类的前置声明函数的前置声明

1
2
3
4
5

template<typename T>
class Complex;
template<typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);

2)类的内部声明必须写成:

1
friend Complex<T> mySub <T> (Complex<T> &c1, Complex<T> &c2);

3)友元函数实现必须写成

1
2
3
4
5
6
template<typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b-c2.b);
return tmp;
}

4)友元函数调用 必须写成:

1
2
Complex<int> c4 = mySub<int>(c1, c2);
cout<<c4;

结论:友元函数只用来进行左移友移操作符重载。

所有的类模板函数写在类的外部,在不同的.h和.cpp中

也就是类模板函数说明和类模板实现分开

类模板函数

构造函数

普通成员函数

友元函数

用友元函数重载<<>>;

用友元函数重载非<< >>

要包含.cpp

总结

归纳以上的介绍,可以这样声明和使用类模板:

1) 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。

2) 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。

3) 在类声明前面加入一行,格式为:

​ template

如:

​ template //注意本行末尾无分号

​ class Compare

​ {…}; //类体

4) 用类模板定义对象时用以下形式:

​ 类模板名<实际类型名> 对象名;

​ 类模板名<实际类型名> 对象名(实参表列);

如:

​ Compare cmp;

​ Compare cmp(3,7);

5) 如果在类模板外定义成员函数,应写成类模板形式:

template

函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}

关于类模板的几点说明:

1) 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:

​ template

​ class someclass

​ {…};

在定义对象时分别代入实际的类型名,如:

​ someclass<int,double> obj;

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
#include<iostream>
using namespace std;
//类B和类A十分相似
//类中的成员可以类属
//让数据结构的表示和算法不受所包含的元素类型的影响

//单个类模板
//继承中的类模板
//能够使数据结构和算法分离
template<typename Type>
class TClass
{
public:
Type t;
TClass(Type a=0)
{
t = a;
cout << "构造函数" << endl;
}
~TClass()
{
cout << "析构函数" << endl;
}
void printfT()
{
cout << "t=" << t << endl;
}
};
//类模板做函数参数
void useTClass(TClass<int> &a)
{
a.printfT();
}
void class_template_test()
{
//模板类本身就是类型化的,由他生成具体的类,再有具体类生成对象
TClass<int> t1(11);//模板类是抽象的,需要进行类型具体化
t1.printfT();
TClass<float> t2(11.11);
t2.printfT();
TClass<char> t3('a');
t3.printfT();
useTClass(t1);//类模板做函数参数的调用

}

void main_class_template_test()
{
class_template_test();
system("pause");
return;
}

继承中的类模板

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
#include<iostream>
using namespace std;
//
template<typename Type>
class TClassA
{
public:
Type t;
TClassA(Type a = 0)
{
t = a;
cout << "构造函数" << endl;
}
~TClassA()
{
cout << "析构函数" << endl;
}
void printfT()
{
cout << "t=" << t << endl;
}
};
template<typename Type>
class TClassB:public TClassA <Type>
{
public:
Type m;
TClassB(Type a ,Type b):TClassA<Type>(a)
{
m = b;
cout << "构造函数" << endl;
}
~TClassB()
{
cout << "析构函数" << endl;
}
void printfM()
{
cout << "m=" << m << " t=" << t << endl;
}
};
void temp_inherit_temp()
{
TClassB<int> b1(10, 20);
TClassB<float> b2(10.5, 20.5);
TClassB<char> b3('a','b');

b1.printfM();
b2.printfM();
b3.printfM();
}
void main_temp_inherit_temp()
{
temp_inherit_temp();
system("pause");
return;
}

类模板中的static关键字

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
#include<iostream>
using namespace std;
//子模板类派生时,需要具体化模板类。C++编译器需要知道父类的数据类型具体是什么样子的。
//要知道父类所占的内存大小是多少。
template<typename Type>
class TClass2
{
public:
Type t;
TClass2(Type a = 0)
{
t = a;
cout << "构造函数" << endl;
}
~TClass2()
{
cout << "析构函数" << endl;
}
void printfT()
{
cout << "t=" << t << endl;
}
};
//派生
class TClass3 :public TClass2<int>
{
public:
TClass3(int a=10,int b=20):TClass2<int>(a)//使用父类的构造函数的时候需要使用
{
this->b = b;
}
void printfB()
{
cout << "b=" << b << " t=" << t << endl;
}
private:
int b;
};

void inherit_class_template_test()
{
TClass3 b1(1, 2);
b1.printfB();
b1.printfT();
}

void main_inherit_class_template_test()
{
inherit_class_template_test();
system("pause");
return;
}
类模板中的static关键字

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

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

每个模板类有自己的类模板的static数据成员副本

类模板在项目开发中的应用

小结

模板是C++类型参数化的多态工具。C++提供函数模板和类模板。

模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。

同一个类属参数可以用于多个模板。

类属参数可用于函数的参数类型、返回类型和声明函数中的变量。

模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。

​ 模板称为模板函数;实例化的类模板称为模板类。

函数模板可以用多种方式重载。

类模板可以在类层次中使用 。