18.2.2 简单的类模板

  下面用前面的一个例子来说明,为数组定义一个类模板,该数组要对索引值进行边界检查,确保索引值是合法的。尽管标准库提供了数组模板的完整实现方式,但建立自己的数组模板有助于理解模板的工作原理。我们已经很清楚数组的工作原理了,因此下面集中讨论模板的特性。这也更容易使用第20章介绍的标准库中的 Array 模板。
  数组模板只有一个类型参数,所以该模板的定义如下:

  template <typename T> class Array{//definition of the template.. }

  Array 模板只有一个类型参数 T 。说它是类型参数,是因为它的前面有关键字 typename。 在实例化模板时,为这个参数指定的参数,如 int 、 double*、 string 等,确定了存储在相关类对象中的元素类型。模板体中的定义与类定义非常类似,其数据成员和成员函数可以声明为 Public、 protected 或 private,类模板一般有构造函数和析构函数。可以使用 T 或类型指针 T*声明变量或指定成员函数的参数或返回类型,而且,还可以把模板名称(在本例中是 Array )用作类型名称,也可以在构造函数和析构函数的声明中使用模板名称。

  要利用类接口,至少需要构造函数、副本构造函数(因为要为数组动态地分配内存)、副本赋值运算符(因为如果没有提供该运算符,编译器就会提供一个),重载的下标运算符和析构函数。因此,模板的最初定义如下所示:

template <typename T> class Array{
private:T* elements; // Array of type Tsize_t size; // Number of array elements
public:explicit Array<T>(size_t arraySize); // ConstructorArray<T>(const Array<T>& array); // Copy Constructor~Array<T>(); // DestructorT& operator[](size_t index); // Subscript operatorconst T& operator[](size_t index) const; // Subscript operator-const arraysArray<T>& operator=(const Array<T>& rhs); // Assignment operator
};

  模板体看起来类似于普通的类定义,只是在许多地方都使用了T。例如,有一个数据成员 elements ,其类型是 T 的指针(等价于 T 的数组)。在实例化类模板,生成一个类定义时, T 就会被用于实例化模板的类型代替.如果为 double 类型创建模板的一个实例, elements 的类型就是 doouble 数组。

  给成员 size 使用 size_t 类型,它存储一数组中的元素个数。这是在标准头文件<cstddef>,中定义的标准整数类型,对应于sizeof()运算符返回的类型值。它是指定数组维数的首选类型。注意第一个构造函数声明为 explicit 。因为这个函数带有一个整数参数,所以编写构造函数调用有两种方式:

Array<int> data(5);      //explicit constructor notation
Array<int> numbers=5 //assignment-like notation

  把构造函数声明为 explicit ,可以防止出现第二种语法形式(很不直观),还可以防止给需要 Array<int>类型参数的函数传送整数。构造函数中如果没有 explicit 声明,编译器就会插入一个构造函数调用,把整数参数转换为 Array<int>类型。

  下标运算符重载为const。第 9 章介绍了带有 const 参数和非const参数的重载函数。下标运算符的非 const 版本应用于非 const 数组对象,可以返回数组元素的一个非 const 引用。因此这个版本可以放在等号的左边, const 版本用于调用const 对象.返回元素的const 引用。显然它不能放在等号的左边。

  在副本赋值运算符的声明中,使用了类型 Array<T>&。这个类型是 Array<T>的引用。在类从模板中综合处埋时,例如 T 是 double 类型. Array<T>&就是该类的类名引用,也就是Array<double>。一般来说,模板某个实例的类名足由模板名后跟尖括号中的类型参数组成的。
模板名后跟尖括号中的参数名列表称为模板ID。

  在模板定义中,不需要使用完整的模板ID。在类模板体中, Array 本身就表示 Array<T>, 而 Array&表示 Array<T>&,所以,可以把类模板定义简化为:

template <typename T> class Array{
private:T* elements;                              // Array of type Tsize_t size;                              // Number of array elementspublic:explicit Array(size_t arraySize);         // ConstructorArray(const Array& array);                // Copy Constructor~Array();                                 // DestructorT& operator[](size_t index);              // Subscript operatorconst T& operator[](size_t index) const;  // Subscript operator-const arraysArray& operator=(const Array& rhs);       // Assignment operatorsize_t getSize() { return size; }         // Accessor for size
}; 

提示:
如果需要在模板体的外部标识模板,必须使用模板ID,在本章后面定义类模板的成员函数时,就要用到模板 ID。

  赋值运算符允许把一个数组对象赋予另一个数组对象,而 C++中的一般数组不能这样做。如果因某种原因要保留这个功能,仍旧需要把operator=()函数声明为模板的一个成员。否则,在需要对模板实例进行这个操作时,就会创建一个默认的公共赋值运算符.为了不使用赋值运算符,可以把它声明为类的私有成员,这样就不能访问它了。当然,在这种情况下,该成员函数不需要实现代码,因为除非要使用成员函数,否则 C++不需要实现它,而这个成员函数是从来都不会使用的。

  定义类模板的成员函数

  可以在类模板体中包含它的成员函数的定义。在这种情况下,成员函数隐含为模板所有实例的内联函数,这与普通的类一样。但是,有时需要在模板体的外部定义成员函数,特别是在成员函数包含许多代码时,就更是如此。在模板体的外部定义成员函数时,语法有些不同,初看时会觉得很令人沮丧。下面就介绍该语法。

  理解该语法的线索是模板类的成员函数的定义本身就是函数模板。定义成员函数的函数模板中的参数列表必须与类模板的参数列表完全相同。这听起来有点混乱,下面举例说明。我们为 Array 模板的成员函数编写定义.首先编写构造函数。

  在类模板定义的外部定义构造函数时,构造函数的名称必须用类模板名称来限定,所采用的方式与普通类成员函数相同。但是,这不是函数定义,而是函数定义的模板,所以也必须表示出来。下面是构造函数的定义:

template <typename T>                       // This is a template with parameter T
Array<T>::Array(size_t arraySize) : size (arraySize){elements = new T[size];}  

  其中,第一行把这个函数标识为模板,还把模板参数指定为T。把模板函数声明放在两行上,只是为了演示得更清晰,如果整个声明可以放在一行上,就不必放在两行上。

  在限定构造函数的名称 Array<T>时,模板参数是必须的,因为它把函数定义和类模板联系起来。注意这里没有使用关键字 typename,该关键字仅用于模板参数列表.在构造函数名的后面不需要列出参数。在为类模板的实例实例化构造函数时,例如为类型 double 实例化
构造函数,类型名会替换掉构造函数限定符中的T,于是类 Array<double>的限定构造函数名应是Array<double>::Array()。

  在构造函数中,必须在自由存储区中为elements数组分配内存,该数组包含 size 个类型T的元素,如果T是类类型,就必须在类 T 中包含一个默认的公共构造函数。如果 T 不是类类型,就不会编译这个构造函数的实例。如果不能分配内存,运算符 new 就会抛出bad_alloc
异常。 Array 构造函数应总是放在 try 块中。

  析构函数必须释放 elements 数组的内存,其定义如下所示:

template <typename T> Array<T>::~Array() {delete[] elements;
}

  要释放数组专用的内存,必须使用 delete 运算符的正确形式。

  副本构造函数必须为要创建的对象创建一个数组.其大小与参数相同,接着把后者的数据成员复制到前者中。其定义代码如下:

template <typename T> Array<T>::Array(const Array& theArray) {size=theArray.size;elements = new T[size];for(int i=0;i<size;i++)elements[i] = theArray.elements[i];
}

  这段代码假定赋值运算符处理类型 T 。这对为动态分配内存的类定义赋值运算符是非常重要的.如果类 T 没有定义副本构造函数,就使用 T 的默认副本构造函数,但这样动态分配内存的类会出现不希望的负面效应,如第 13 章所述。在使用之前不杳看模板的代码,就可能认识不到副本构造函数对赋值运算符的依赖关系。

  operator[]()函数相当简单,但应确保不能使用不合法的索引值。对于超出范围的索引值,可以抛出一个异常:

template <typename T> T& Array<T>::perator[](size_t index){if(index<0 || index >=size)throw std::out_of_range(index<0?"Negative index":"Index too large");return elements[index];
}

  这里可以定义自己的异常类,但借用在标准库的<stdexcept>头文件中定义的out_of_range类会更容易。例如,如果用超出范圈的索引值引用string对象,就会抛出该异常.这样用法就一致了.如果索引的值不在0到size-l 之间,就抛出 out_of_range类型的异常。构造函数的参数是一个描述错误的 string 对象,对应于 string 对象的非空字符串(类型为 const char*)由异常对象的 what() 成员返回。传送给out_of_range构造函数的参数是一个简单的消息, 但可以在string对象中包含索引值和数组大小等信息,以易于跟踪问题的源头。

  下标运算符函数的const版本和非const版本大致相同:

template <typename T> const T& Array<T>::perator[](size_t index)const{if(index<0 || index >=size)throw std::out_of_range(index<0?"Negative index":"Index too large");return elements[index];
}

  最后一个需要定义的函数模板是赋值运算符的函数模板,该函数模板需要释放在目标对象中分配的内存,再执行副本构造函数的操作--这当然要在检查对象是不同的之后进行。下面是定义:

template <typename T> Array<T>& Array<T>::operator=(const Array& rhs){if(&rhs == this)       //If lhs == rhsreturn *this;       //just return lhsif(elements)           //If lhs array existsdelete[]elements;   //release the free store memoy
    size = rhs.size;elements = new T[rhs.size];for(int i = 0; i< size;i++)eleents[i] =rhs.elements[i];
}

  检查左操作数与右操作数是否相同是必不可少的,否则就是释放普通 elements 成员的内存,然后在它己不存在的情况下复制它.如果操作数不问,就释放左操作数占用的内存,之后创建右操作数的副本。

  这里编写的所有定义都是函数模板的定义,它们都绑定到类模板上。但它们不是随数定义,而是在需要生成某个类成员的数的代码时由编译器使用的函数模板。因此需要在使用该模板的源文件中可用。为此,一般应将类模板的所有成员函数的定义都放在包含类模板的头文件中。

  即使把模板的成员函数定义为独立的函数模板,它们仍可以是内联函数。为了让编译器把它们看作内联实现代码,只需在定义开头的template<>之后加上关键字 inline,如下所示:

template <typename T> inline const T& Array<T>::operator[](size_t index)const {if(index<0 || index >= size)throw out_of_range(index<0?"Negative index":"Index to large");return elements[index];
}

转载于:https://www.cnblogs.com/ylfh/p/6505944.html

18.2.2 简单的类模板相关推荐

  1. 全面总结C++类模板使用的基础知识

    ✨引言 书接上文,今天来学习C++模板知识中的第二大模块,也就是类模板的使用. <C++提高编程>专栏主要针对C++泛型编程和STL技术做详细讲解,深入研究C++的使用,对C/C++感兴趣 ...

  2. 类模板,多种类型的类模板,自定义类模板,类模板的默认类型,数组的模板实现,友元和类模板,友元函数,类模板与静态变量,类模板与普通类之间互相继承,类模板作为模板参数,类嵌套,类模板嵌套,类包装器

     1.第一个最简单的类模板案例 #include "mainwindow.h" #include <QApplication> #include <QPush ...

  3. C++学习笔记:类模板

    上一章我们介绍了函数模板,今天这章我们来学习类模板. 类模板声明 template是声明类模板的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个,可以是**「类型参数」** ,也可以是**非 ...

  4. 函数模板与类模板(模板类)

    什么是泛型编程? 泛型编程:编写与类型无关的通用代码,是代码复用的一种手段.模板是泛型编程的基础. 模板分为函数模板和类模板 下面我们就来说说函数模板: 函数模板与类型无关,在使用时被参数化,根据实参 ...

  5. 关于类模板怎么用的简单介绍

    类模板的意义: 一般是减少类体内容一致但参数类型不同的类的编写: 类模板的应用流程: 1.首先你需要先写一个普通的类.参数类型先人选一样 class Compare{private:int a,b;p ...

  6. 类模板与运算符重载(一个简单的例子)

    类模板与运算符重载(一个简单的例子) 标签(空格分隔): C++ 算法竞赛 下面是一段简单的代码,表示我们建立了一个类模板Vector,可以看做是对STL中vector的简单实现. 为了让这个Vect ...

  7. C++类模板template <class T>简单使用方法

    一个简单的例子 两个数比大小 如果两个数都是int类型 class Compare_int { public :Compare(int a,int b){x=a;y=b;}int max( ){ret ...

  8. 类模板和函数模板的区别及其应用案例 c++ 简单易懂

    函数模板和类模板的区别在于: 1.函数模板有自动类型推导,但是类模板没有自动类型推导, 只能用显式指定类型来实例化类模板中的对象 2.函数模板不可以有默认参数,但是类模板允许有默认参数 当类模板中有默 ...

  9. C++设计模式由浅入深(二)—— 类模板和函数模板

    二.类和函数模板 C++的模板编程特性是一个又大又复杂的话题,有许多著作专门传授这种特性和技巧.在本书中,我们会用到许多C++中的高级泛型编程特性.那么我们该如何去理解贯穿次数中的这些语言结构呢?本章 ...

最新文章

  1. spring security源码分析心得
  2. 公司访问实录 | 数据中心的运维难题知多少
  3. 科研狗的国庆与普通人的国庆有什么不同?
  4. C语言中如何求一天是星期几,计算任何一天是星期几的C语言源代码.
  5. java基础知识回顾之---java String final类普通方法的应用之“按照字节截取字符串”...
  6. [Leetcode][JAVA] Populating Next Right Pointers in Each Node II
  7. java 136年以后的时间_136年清明查询 - 136年清明是几号 - 136年清明具体时间
  8. Trisk:在 Flink 实现以 task 为中心的流处理动态 Reconfiguration 的 Control Plane
  9. 免费网络硬盘-千脑网盘
  10. 腾讯QQ表情生意经:建开放平台 与原创者最高六四分成
  11. 学习笔记 Tianmao 篇 fresco 图片缓存加载框架
  12. 华为网络设备-生成树协议配置
  13. 计算机中丢失d3dx9-41,雨林木风win10系统提示“计算机中丢失d3dx9-41.dll”的详细技巧...
  14. python:实现base64加密和base64解密算法(附完整源码)
  15. 怎样赚钱?互联网赚钱才是草根最好的方式!
  16. 哈佛大学公开课《死亡》
  17. Rosalind Java|Overlap Graphs
  18. zynq7000 资源介绍
  19. 弹性公网IP(EIP)
  20. 【AHB协议解读 三】传输(Transfers)

热门文章

  1. 前导问题word使用技巧---解决Word 生成目录时前导符不一致的问题(即通常所谓的目录中省略号大小不一致)
  2. 修改office2007 序列号的方法
  3. Dynamic Wallpaper Mac版 精美的动态壁纸
  4. 【JTAG】STIL格式详解
  5. numpy除去nan值_pandas numpy处理缺失值,none与nan比较
  6. LNMP一键安装包教程及常用设置!
  7. element audio 标签 不显示_HTML 音频(Audio)
  8. 英语四六级见解(攻略)
  9. 电脑性能测试常用软件介绍,性能测试之常用工具介绍
  10. 华为全面屏鸿蒙,华为P50 Pro外观基本确认:居中开孔全面屏首发鸿蒙操作系统...