Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 394|回复: 0

[默认分类] C++中的类模板详细讲述

[复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2018-7-11 15:03:18 | 显示全部楼层 |阅读模式
    一、类模板定义及实例化
    . 定义一个类模板:



    View Code

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



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



    View Code

    1. 1 template<class type,int width>
    2. 2
    3. 3 //type为类型参数,width为非类型参数
    4. 4
    5. 5 class Graphics;
    复制代码



    注意:
    (1)如果在全局域中声明了与模板参数同名的变量,则该变量被隐藏掉。
    (2)模板参数名不能被当作类模板定义中类成员的名字。
    (3)同一个模板参数名在模板参数表中只能出现一次。
    (4)在不同的类模板或声明中,模板参数名可以被重复使用。



    View Code

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



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



    View Code

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



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



    View Code

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



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



    View Code

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



    2.
    类模板实例化
    定义:从通用的类模板定义中生成类的过程称为模板实例化。
    例:Graphics<int> gi;
    类模板什么时候会被实例化呢?
    当使用了类模板实例的名字,并且上下文环境要求存在类的定义时。
    对象类型是一个类模板实例,当对象被定义时。此点被称作类的实例化点
    一个指针或引用指向一个类模板实例,当检查这个指针或引用所指的对象时。
    例:



    View Code

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



    3.
    非类型参数的模板实参
    要点
    绑定给非类型参数的表达式必须是一个常量表达式。
    从模板实参到非类型模板参数的类型之间允许进行一些转换。包括左值转换、限定修饰转换、提升、整值转换。
    可以被用于非类型模板参数的模板实参的种类有一些限制。
    例:



    View Code

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



    二、类模板的成员函数
    要点:
    类模板的成员函数可以在类模板的定义中定义(inline函数),也可以在类模板定义之外定义(此时成员函数定义前面必须加上template及模板参数)。
    类模板成员函数本身也是一个模板,类模板被实例化时它并不自动被实例化,只有当它被调用或取地址,才被实例化。



    View Code

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



    三、类模板的友元声明
    类模板中可以有三种友元声明:
    .非模板友元类或友元函数



    View Code

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



    2、绑定的友元类模板或函数模板。
    3、非绑定的友元类模板或函数模板。
    第二种声明表示类模板的实例和它的友元之间是一种一对一的映射关系。
    如图:

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

    例:绑定的友元模板



    View Code

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



    例:非绑定的友元模板



    View Code

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



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



    View Code

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



    四、类模板的静态数据成员、嵌套类型
    .类模板的静态数据成员
    要点:
    静态数据成员的模板定义必须出现在类模板定义之外。
    类模板静态数据成员本身就是一个模板,它的定义不会引起内存被分配,只有对其实例化才会分配内存。
    当程序使用静态数据成员时,它被实例化,每个静态成员实例都与一个类模板实例相对应,静态成员的实例引用要通过一个类模板实例。
    例:



    View Code

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



    2.类模板的嵌套类型
    要点
    在类模板中允许再嵌入模板,因此类模板的嵌套类也是一个模板,它可以使用外围类模板的模板参数。
    当外围类模板被实例化时,它不会自动被实例化,只有当上下文需要它的完整类类型时,它才会被实例化。
    公有嵌套类型可以被用在类定义之外,这时它的名字前必须加上类模板实例的名字。
    例:



    View Code

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



    五、成员模板
    定义:成员定义前加上template及模板参数表。
    要点:
    在一个类模板中定义一个成员模板,意味着该类模板的一个实例包含了可能无限多个嵌套类和无限多个成员函数.
    只有当成员模板被使用时,它才被实例化.
    成员模板可以定义在其外围类或类模板定义之外.
    例:



    View Code

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



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

    六、类模板的编译模式
    1.包含编译模式
    这种编译模式下,类模板的成员函数和静态成员的定义必须被包含在“要将它们实例化”的所有文件中,如果一个成员函数被定义在类模板定义之外,那么这些定义应该被放在含有该类模板定义的头文件中。
    2.分离编译模式
    这种模式下,类模板定义和其inline成员函数定义被放在头文件中,而非inline成员函数和静态数据成员被放在程序文本文件中。
    例:



    View Code

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



    Setup的成员定义在User.c中不可见,但在这个文件中仍可调用模板实例Graphics<int>::Setup(const int &)。为实现这一点,须将类模声明为可导出的:当它的成员函数实例或静态数据成员实例被使用时,编译器只要求模板的定义,它的声明方式是在关键字template前加关键字export
    .显式实例声明
    当使用包含编译模式时,类模板成员的定义被包含在使用其实例的所有程序文本文件中,何时何地编译器实例化类模板成员的定义,我们并不能精确地知晓,为解决这个问题,标准C++提供了显式实例声明:关键字template后面跟着关键字class以及类模板实例的名字。
    例:



    View Code

    1. 1 #include “Graphics.h”
    2. 2
    3. 3 Template class Graphics<int>;//显式实例声明
    复制代码



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


    七、类模板的特化及部分特化
    1.类模板的特化
    先看下面的例子:



    View Code

    1. 1 Template<class type>
    2. 2
    3. 3 Class Graphics{
    4. 4
    5. 5 Public:void out(type figure){…}};
    6. 6
    7. 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. 1 template<int hi,int wid>
    2. 2
    3. 3 Class Graphics{…};
    4. 4
    5. 5 Template<int hi>//类模板的部分特化
    6. 6
    7. 7 Class Graphics<hi,90>{…};
    复制代码



    格式:
    template<模板参数表>
    注意:
    部分特化的模板参数表只列出模板实参仍然未知的那些参数。
    类模板部分特化是被隐式实例化的。编译器选择“针对该实例而言最为特化的模板定义”进行实例化,当没有特化可被使用时,才使用通用模板定义。
    例:Graphics<24,90> figure;
    它即能从通用类模板定义被实例化,也能从部分特化的定义被实例化,但编译器选择的是部分特化来实例化模板。
    类模板部分特化必须有它自己对成员函数、静态数据成员和嵌套类的定义。

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



    View Code

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



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



    View Code

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



    注意:在名字空间中声明类模板也会影响该类模板及其成员的特化和部分特化声明的方式,类模板或类模板成员的特化声明必须被声明在定义通用模板的名字空间中(可以在名字空间之外定义模板特化)。
    一个关于队列的例子,下面将其代码整理如下:



    View Code

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



    运行结果
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-4-25 03:20 , Processed in 0.398283 second(s), 37 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表