C++中的类模板详细讲述

一、类模板定义及实例化

1. 定义一个类模板:

View Code
复制代码
1 template<class 模板参数表>
2 
3 class 类名{
4 
5 // 类定义......
6 
7 };
复制代码

其中,template 是声明类模板的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个,可以是类型参数 ,也可以是非类型参数。类型参数由关键字class或typename及其后面的标识符构成。非类型参数由一个普通参数构成,代表模板定义中的一个常量。

例:

View Code
1 template<class type,int width>
2 
3 //type为类型参数,width为非类型参数
4 
5 class Graphics;

注意:

(1)如果在全局域中声明了与模板参数同名的变量,则该变量被隐藏掉。

(2)模板参数名不能被当作类模板定义中类成员的名字。

(3)同一个模板参数名在模板参数表中只能出现一次。

(4)在不同的类模板或声明中,模板参数名可以被重复使用。

View Code
复制代码
 1 typedef string type;
 2 
 3 template<class type,int width>
 4 
 5 class Graphics
 6 
 7 {
 8 
 9 type node;//node不是string类型
10 
11 typedef double type;//错误:成员名不能与模板参数type同名
12 
13 };
14 
15 template<class type,class type>//错误:重复使用名为type的参数
16 
17 class Rect;
18 
19 template<class type> //参数名”type”在不同模板间可以重复使用
20 
21 class Round;
复制代码


(5)
在类模板的前向声明和定义中,模板参数的名字可以不同。

View Code
复制代码
 1 // 所有三个 Image 声明都引用同一个类模板的声明
 2 
 3 template <class T> class Image;
 4 
 5 template <class U> class Image;
 6 
 7 // 模板的真正定义
 8 
 9 template <class Type>
10 
11 class Image { //模板定义中只能引用名字”Type”,不能引用名字”T”和”U” };
复制代码


(6)
类模板参数可以有缺省实参,给参数提供缺省实参的顺序是先右后左。

View Code
复制代码
1 template <class type, int size = 1024>
2 
3 class Image;
4 
5 template <class type=double, int size >
6 
7 class Image;
复制代码


(7)
类模板名可以被用作一个类型指示符。当一个类模板名被用作另一个模板定义中的类型指示符时,必须指定完整的实参表

View Code
复制代码
 1 template<class type>
 2 
 3 class Graphics
 4 
 5 {
 6 
 7 Graphics *next;//在类模板自己的定义中不需指定完整模板参数表
 8 
 9 };
10 
11 template <calss type>
12 
13 void show(Graphics<type> &g)
14 
15 {
16 
17 Graphics<type> *pg=&g;//必须指定完整的模板参数表
18 
19 }
复制代码


2.类模板实例化

定义:从通用的类模板定义中生成类的过程称为模板实例化。

例:Graphics<int> gi;

类模板什么时候会被实例化呢?

当使用了类模板实例的名字,并且上下文环境要求存在类的定义时。

对象类型是一个类模板实例,当对象被定义时。此点被称作类的实例化点

一个指针或引用指向一个类模板实例,当检查这个指针或引用所指的对象时。

例:

View Code
复制代码
 1 template<class Type>
 2 
 3 class Graphics{};
 4 
 5 void f1(Graphics<char>);// 仅是一个函数声明,不需实例化
 6 
 7 class Rect 
 8 
 9 {
10 
11   Graphics<double>& rsd;// 声明一个类模板引用,不需实例化
12 
13   Graphics<int> si;// si是一个Graphics类型的对象,需要实例化类模板
14 
15 }
16 
17 int main(){
18 
19   Graphcis<char>* sc;// 仅声明一个类模板指针,不需实例化
20 
21   f1(*sc);//需要实例化,因为传递给函数f1的是一个Graphics<int>对象。
22 
23   int iobj=sizeof(Graphics<string>);//需要实例化,因为sizeof会计算Graphics<string>对象的大小,为了计算大小,编译器必须根据类模板定义产生该类型。
24 
25 }
复制代码


3.非类型参数的模板实参

要点

绑定给非类型参数的表达式必须是一个常量表达式。

从模板实参到非类型模板参数的类型之间允许进行一些转换。包括左值转换、限定修饰转换、提升、整值转换。

可以被用于非类型模板参数的模板实参的种类有一些限制。

例:

View Code
复制代码
 1 Template<int* ptr> class Graphics{…….};
 2 
 3 Template<class Type,int size> class Rect{……..};
 4 
 5 const int size=1024;
 6 
 7 Graphics<&size> bp1;//错误:从const int*->int*是错误的。
 8 
 9 Graphics<0> bp2;//错误不能通过隐式转换把0转换成指针值
10 
11 const double db=3.1415;
12 
13 Rect<double,db> fa1;//错误:不能将const double转换成int.
14 
15 unsigned int fasize=255;
16 
17 Rect<String, fasize> fa2;//错误:非类型参数的实参必须是常量表达式,将unsigned改为const就正确。
18 
19 Int arr[10];
20 
21 Graphics<arr> gp;//正确
复制代码

二、类模板的成员函数

要点:

类模板的成员函数可以在类模板的定义中定义(inline函数),也可以在类模板定义之外定义(此时成员函数定义前面必须加上template及模板参数)。

类模板成员函数本身也是一个模板,类模板被实例化时它并不自动被实例化,只有当它被调用或取地址,才被实例化。

View Code
复制代码
 1 template<class type>
 2 
 3 Class Graphics{
 4 
 5 Graphics(){…}//成员函数定义在类模板的定义中
 6 
 7 void out();
 8 
 9 };
10 
11 template<class type>//成员函数定义在类模板定义之外
12 
13 void Graphics<type>::out(){…}
复制代码

三、类模板的友元声明

类模板中可以有三种友元声明:

1.非模板友元类或友元函数

View Code
复制代码
 1 class Graphics{void out();};
 2 
 3 Template<class T>
 4 
 5 Class Rect{
 6 
 7 friend class Graphics;//类Graphics、函数
 8 
 9 friend void create();// create、 out是类模板
10 
11 friend void Graphics::out();// Rect所有实例的友元
12 
13 };
复制代码

2、绑定的友元类模板或函数模板。

3、非绑定的友元类模板或函数模板。

第二种声明表示类模板的实例和它的友元之间是一种一对一的映射关系。

如图:

第三种声明表示类模板的实例和它的友元之间是一种一对多的映射关系。

如图:

例:绑定的友元模板

View Code
复制代码
 1 template<class type>
 2 
 3 void create(Graphics<type>);
 4 
 5 template<class type>
 6 
 7 class Graphics{
 8 
 9 friend void create<type>(Graphics<type>);
10 
11 };
复制代码


例:非绑定的友元模板

View Code
复制代码
1 template<class type>
2 
3 class Graphics{
4 
5 template<class T>
6 
7 friend void create(Graphics<T>);
8 
9 };
复制代码


注意:
当把非模板类或函数声明为类模板友元时,它们不必在全局域中被声明或定义,但将一个类的成员声明为类模板友元,该类必须已经被定义,另外在声明绑定的友元类模板或函数模板时,该模板也必须先声明。

例:

View Code
 1 template <class T>
 2 
 3 class A {
 4 
 5 private:
 6 
 7 friend class B<T>; //错误:类B必须先声明
 8 
 9 };
10 
11 template <class T>
12 
13 class B{};

四、类模板的静态数据成员、嵌套类型

1.类模板的静态数据成员

要点:

静态数据成员的模板定义必须出现在类模板定义之外。

类模板静态数据成员本身就是一个模板,它的定义不会引起内存被分配,只有对其实例化才会分配内存。

当程序使用静态数据成员时,它被实例化,每个静态成员实例都与一个类模板实例相对应,静态成员的实例引用要通过一个类模板实例。

例:

View Code
复制代码
 1 template<class type>
 2 
 3 class Graphics{
 4 
 5 static Graphics *next;
 6 
 7 static const type item;
 8 
 9 };
10 
11 template<class type>
12 
13 Graphics<type> * Graphics<type>::next=0;
14 
15 template<class type>
16 
17 type Graphics<type>::item=NULL;
18 
19 //静态成员定义分为两部分:前一部分是类型,比如Graphics<type>*,后一部分是名称和值,比如Graphics<type>::next=0;
复制代码

2.类模板的嵌套类型

要点:

在类模板中允许再嵌入模板,因此类模板的嵌套类也是一个模板,它可以使用外围类模板的模板参数。

当外围类模板被实例化时,它不会自动被实例化,只有当上下文需要它的完整类类型时,它才会被实例化。

公有嵌套类型可以被用在类定义之外,这时它的名字前必须加上类模板实例的名字。

例:

View Code
复制代码
 1 template<class type>
 2 
 3 class Graphics{
 4 
 5 public:
 6 
 7 template<class T>
 8 
 9 class Rect{void out(type a,T b);};
10 
11 };
12 
13 Graphics<int>::Rect<double> node;
14 
15 //引用公有嵌套类型必须加上类模板实例名字
复制代码


五、成员模板

定义:成员定义前加上template及模板参数表。

要点:

在一个类模板中定义一个成员模板,意味着该类模板的一个实例包含了可能无限多个嵌套类和无限多个成员函数.

只有当成员模板被使用时,它才被实例化.

成员模板可以定义在其外围类或类模板定义之外.

例:

View Code
 1 template<class type>
 2 
 3 class Graphics<type>{
 4 
 5 public:template<class T>
 6 
 7 class Rect{void out(type a,T b);};};
 8 
 9 template<class Gtype> template<class TT>
10 
11 void Graphics<Gtype>::Rect<TT>::out(Gtype a,TT b){}//成员模板被定义在类模板定义之外(要根上完整模板实参)
12 
13 Graphics<int>的实例可能包括下列嵌套类型:
14 
15 Graphics<int>::Rect<double>
16 
17 Graphics<int>::Rect<string>


注意:类模板参数不一定与类模板定义中指定的名字相同。

六、类模板的编译模式

1.包含编译模式

这种编译模式下,类模板的成员函数和静态成员的定义必须被包含在“要将它们实例化”的所有文件中,如果一个成员函数被定义在类模板定义之外,那么这些定义应该被放在含有该类模板定义的头文件中。

2.分离编译模式

这种模式下,类模板定义和其inline成员函数定义被放在头文件中,而非inline成员函数和静态数据成员被放在程序文本文件中。

例:

View Code
复制代码
 1 //------Graphics.h---------
 2 
 3 export template<class type>
 4 
 5 Class Graphics
 6 
 7 {void Setup(const type &);};
 8 
 9 //-------Graphics.c------------
10 
11 #include “Graphics.h”
12 
13 Template <class type>
14 
15 Void Graphics<type>::Setup(const type &){…}
16 
17 //------user.c-----
18 
19 #include “Graphics.h”
20 
21 Void main()
22 
23 {Graphics<int> *pg=new Graphics<int>;
24 
25 Int ival=1;
26 
27 //Graphics<int>::Setup(const int &)的实例(下有注解)
28 
29 Pg->Setup(ival);
30 
31 }
复制代码


Setup的成员定义在User.c中不可见,但在这个文件中仍可调用模板实例Graphics<int>::Setup(const int &)。为实现这一点,须将类模声明为可导出的:当它的成员函数实例或静态数据成员实例被使用时,编译器只要求模板的定义,它的声明方式是在关键字template前加关键字export

3.显式实例声明

当使用包含编译模式时,类模板成员的定义被包含在使用其实例的所有程序文本文件中,何时何地编译器实例化类模板成员的定义,我们并不能精确地知晓,为解决这个问题,标准C++提供了显式实例声明:关键字template后面跟着关键字class以及类模板实例的名字。

例:

View Code
1 #include “Graphics.h”
2 
3 Template class Graphics<int>;//显式实例声明


显式实例化类模板时,它的所有成员也被显式实例化。

七、类模板的特化及部分特化

1.类模板的特化

先看下面的例子:

View Code
复制代码
1 Template<class type>
2 
3 Class Graphics{
4 
5 Public:void out(type figure){…}};
6 
7 Class Rect{…};
复制代码


如果模板实参是Rect类型,我们不希望使用类模板Graphics的通用成员函数定义,来实例化成员函数out(),我们希望专门定义Graphics<Rect>::out()实例,让它使用Rect里面的成员函数。

为此,我们可以通过一个显示特化定义,为类模板实例的一个成员提供一个特化定义。

格式:template<> 成员函数特化定义

下面为类模板实例Graphics<Rect>的成员函数out()定义了显式特化:

Template<> void Graphics<Rect>::out(Rect figure){…}

注意:

只有当通用类模板被声明后,它的显式特化才可以被定义。

若定义了一个类模板特化,则必须定义与这个特化相关的所有成员函数或静态数据成员,此时类模板特化的成员定义不能以符号template<>作为打头。(template<>被省略)

类模板不能够在某些文件中根据通用模板定义被实例化,而在其他文件中却针对同一组模板实参被特化。

2.类模板部分特化

如果模板有一个以上的模板参数,则有些人就可能希望为一个特定的模板实参或者一组模板实参特化类模板,而不是为所有的模板参数特化该类模板。即,希望提供这样一个模板:它仍然是一个通用的模板,只不过某些模板参数已经被实际的类型或值取代。通过使用类模板部分特化,可以实现这一点。

例:

View Code
复制代码
1 template<int hi,int wid>
2 
3 Class Graphics{…};
4 
5 Template<int hi>//类模板的部分特化
6 
7 Class Graphics<hi,90>{…};
复制代码


格式:template<模板参数表>

注意:

部分特化的模板参数表只列出模板实参仍然未知的那些参数。

类模板部分特化是被隐式实例化的。编译器选择“针对该实例而言最为特化的模板定义”进行实例化,当没有特化可被使用时,才使用通用模板定义。

例:Graphics<24,90> figure;

它即能从通用类模板定义被实例化,也能从部分特化的定义被实例化,但编译器选择的是部分特化来实例化模板。

类模板部分特化必须有它自己对成员函数、静态数据成员和嵌套类的定义。

八、名字空间和类模板

类模板定义也可以被放在名字空间中。例如:

View Code
复制代码
 1 Namespace cplusplus_primer{
 2 
 3 Template<class type>
 4 
 5 Class Graphics{…};
 6 
 7 Template<class type>
 8 
 9 Type create()
10 
11 {…}
12 
13 }
复制代码


当类模板名字Graphics被用在名字空间之外时,它必须被名字空间名cplusplus_primer限定修,或者通过一个using声明或指示符被引入。例如:

View Code
复制代码
1 Void main()
2 
3 {
4 
5 using cplusplus_primer::Graphics;
6 
7 Graphics<int> *pg=new Graphics<int>;
8 
9 }
复制代码

注意:在名字空间中声明类模板也会影响该类模板及其成员的特化和部分特化声明的方式,类模板或类模板成员的特化声明必须被声明在定义通用模板的名字空间中(可以在名字空间之外定义模板特化)。

一个关于队列的例子,下面将其代码整理如下:

View Code
复制代码
  1 #include "iostream.h"
  2 
  3 template <class Type> class QueueItem;
  4 
  5 template <class Type>
  6 
  7 class Queue {
  8 
  9 public:
 10 
 11 friend ostream& operator<<(ostream &os,const Queue<Type> &q);
 12 
 13 Queue() : front( 0 ), back ( 0 ) { }
 14 
 15 ~Queue(){}
 16 
 17 void add( const Type & );
 18 
 19 bool is_empty() const
 20 
 21 {
 22 
 23 return front == 0;
 24 
 25 }
 26 
 27 Type remove();
 28 
 29 private:
 30 
 31 QueueItem<Type> *front;
 32 
 33 QueueItem<Type> *back;
 34 
 35 };
 36 
 37 template <class Type>
 38 
 39 class QueueItem
 40 
 41 {
 42 
 43 public:
 44 
 45 QueueItem(Type val){item=val;next=0;}
 46 
 47 friend class Queue<Type>;
 48 
 49 friend ostream& operator<<(ostream &os,const Queue<Type> &q);
 50 
 51 friend ostream& operator<<(ostream &os,const QueueItem<Type> &qi);
 52 
 53  
 54 
 55 private:
 56 
 57 Type item;
 58 
 59 QueueItem *next;
 60 
 61 };
 62 
 63 template <class Type>
 64 
 65 void Queue<Type>::add(const Type &val)
 66 
 67 {
 68 
 69 QueueItem<Type> *pt =new QueueItem<Type>(val);
 70 
 71 if ( is_empty() )
 72 
 73 front = back = pt;
 74 
 75 else
 76 
 77 {
 78 
 79 back->next = pt;
 80 
 81 back = pt;
 82 
 83 }
 84 
 85 }
 86 
 87 template <class Type>
 88 
 89 Type Queue<Type>::remove()
 90 
 91 {
 92 
 93 if ( is_empty() )
 94 
 95 {
 96 
 97 cerr << "remove() on empty queue \n";
 98 
 99 exit(-1);
100 
101 }
102 
103 QueueItem<Type> *pt = front;
104 
105 front = front->next;
106 
107 Type retval = pt->item;
108 
109 delete pt;
110 
111 return retval;
112 
113 }
114 
115 template <class Type>
116 
117 ostream& operator<<(ostream &os, const Queue<Type> &q) //输出队列成员
118 
119 {
120 
121 os << "< ";
122 
123 QueueItem<Type> *p;
124 
125 for ( p = q.front; p; p = p->next )
126 
127 os << *p << “ ;//用到了Queue和QueueItem的私有成员,因此需将此运算符重
128 
129 //载函数声明为Queue和QueueItem的友元,书上没有将此函数声明为QueueItem
130 
131 os << “ >”;//的友元。
132 
133 return os;
134 
135 }
136 
137 template <class Type>
138 
139 ostream& operator<< ( ostream &os, const QueueItem<Type> &qi )
140 
141 {
142 
143 os << qi.item;//用到了QueueItem的私有成员,因此需将此运算符重载函数声明
144 
145 //为QueueItem的友元
146 
147 return os;
148 
149 }
150 
151 void main()
152 
153 {
154 
155 Queue<int> qi;
156 
157 cout << qi << endl;
158 
159 int ival;
160 
161 for ( ival = 0; ival < 10; ++ival )
162 
163 qi.add( ival );
164 
165 cout << qi << endl;
166 
167 int err_cnt = 0;
168 
169 for ( ival = 0; ival < 10; ++ival ) {
170 
171 int qval = qi.remove();
172 
173 if ( ival != qval ) err_cnt++;
174 
175 }
176 
177 cout << qi << endl;
178 
179 if ( !err_cnt )
180 
181 cout << "!! queue executed ok\n";
182 
183 else cout << “?? queue errors: " << err_cnt << endl;
184 
185 }
复制代码

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++,vector是一个动态数组,也是一个类模板(class template),它是标准模板库(STL)的一部分。vector类定义在头文件<vector>。 使用vector需要先包含头文件<vector>,然后定义一个vector对象,可以指定元素类型,也可以使用模板参数推导自动推断元素类型。例如: ```c++ #include <vector> using namespace std; // 定义一个vector对象,存储整数类型 vector<int> vec1; // 定义一个vector对象,存储字符串类型 vector<string> vec2; ``` vector是一个动态数组,可以在运行时动态地分配和释放内存,也可以动态地添加或删除元素。例如,可以使用push_back()函数在vector的末尾添加元素: ```c++ vector<int> vec; vec.push_back(1); vec.push_back(2); vec.push_back(3); ``` 也可以使用pop_back()函数在vector的末尾删除元素: ```c++ vector<int> vec{ 1, 2, 3 }; vec.pop_back(); ``` vector提供了方便的数组访问方法和操作函数,例如,可以使用下标运算符[]访问vector的元素: ```c++ vector<int> vec{ 1, 2, 3 }; int x = vec[0]; // 访问第一个元素 ``` 也可以使用at()函数访问vector的元素,at()函数会进行越界检查: ```c++ vector<int> vec{ 1, 2, 3 }; int x = vec.at(0); // 访问第一个元素 ``` vector还提供了很多其他的操作函数,例如,可以使用size()函数获取vector的元素个数,使用empty()函数判断vector是否为空,使用clear()函数清空vector的所有元素,使用insert()函数在指定位置插入元素,使用erase()函数删除指定位置的元素等等。 总之,vector是C++面向对象编程思想的容器类,它提供了方便的动态数组操作方法,可以大大简化程序的编写,提高程序的效率和可读性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值