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 T
  size_t size; // Number of array elements
public:
  explicit Array<T>(size_t arraySize); // Constructor
  Array<T>(const Array<T>& array); // Copy Constructor
  ~Array<T>(); // Destructor
  T& operator[](size_t index); // Subscript operator
  const T& operator[](size_t index) const; // Subscript operator-const arrays
  Array<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 T
  size_t size;                              // Number of array elements

public:
  explicit Array(size_t arraySize);         // Constructor
  Array(const Array& array);                // Copy Constructor
  ~Array();                                 // Destructor
  T& operator[](size_t index);              // Subscript operator
  const T& operator[](size_t index) const;  // Subscript operator-const arrays
  Array& operator=(const Array& rhs);       // Assignment operator
  size_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 == rhs
        return *this;       //just return lhs

    if(elements)           //If lhs array exists
        delete[]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];
}
时间: 2024-11-25 15:57:30

18.2.2 简单的类模板的相关文章

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

 1.第一个最简单的类模板案例 #include "mainwindow.h" #include <QApplication> #include <QPushButton> #include <QLabel> template<class T> class run { public: T w; void show() { w.show(); } void settext() { w.setText("A"); }

类模板 友元重载形式 各种运算符重载 new delete ++ = +=

今天的重载是基于C++ 类模板的,如果需要非类模板的重载的朋友可以把类模板拿掉,同样可以参考,谢谢. 一.类模板中的友元重载 本人喜好类声明与类成员实现分开写的代码风格,如若您喜欢将类成员函数的实现写在类声明中,那么可以跳过该部分. 请看下面这段代码: 头文件: #pragma once template<typename T> class CLA { T m_value; public: CLA(const T&); friend CLA operator+(const CLA&am

Zend Studio 中创建简单的phpfile模板和xhtml类phpfile模板

<!--简单的phpfile模板,带有创建时间和作者--><?php/*** ==============================================* @date: ${date}* @author: ${user}* @version: 1.0* ==============================================*/ ${cursor}?> <!--xhtml类phpfile模板,同样具有创作时间和作者--><?p

C++入门经典-例9.3-类模板,简单类模板

1:使用template关键字不但可以定义函数模板,而且可以定义类模板.类模板代表一族类,它是用来描述通用数据类型或处理方法的机制,它使类中的一些数据成员和成员函数的参数或返回值可以取任意数据类型.类模板可以说是用类生成类,减少了类的定义和数量. 2:类模板的一般定义形式如下: template <类型形式参数表> class 类模板名 { ...//类模板体 } 类模板成员函数的定义形式如下: template <类型形式参数表> 返回类型 类模板名 <类型名表>::

c++学习笔记啊——类模板(1)

模板 模板是泛型编程的基础,能够处理在编译时不知道类型的情况 (1)模板函数 模板函数(function template) 一个模板函数就是一个公式,可以用来生成针对特定类型的函数版本,如下compare模板的例子 1 template < typename T > 2 int compare(const T&v1,const T&v2) 3 { 4 //其实只要返回一个值,后面的就不会再执行了 5 if(v1v2) return 1; 6 return 0; 7 } 函数说

C++学习之模板 ----函数模板、类模板

本博文主要讨论函数模板与类模板以及其简单应用. 1).作用:函数模板和类模板都可以看做是一种代码产生器,往里面放入具体的类型,得到具体化的函数或者class. 2).编译(分为两步): a):实例化之前,先检查模板本身语法是否正确: b):根据 函数调用或者类模板调用 ,先去实例化模板代码,产生具体的函数/类. 也就是说, 没有函数调用或者类类型对象声明,就不会实例化模板代码,在目标文件obj中找不到模板的痕迹. 3):优缺点 模板的缺点是代码膨胀,编译速度慢,而优点是运行速度快. 一.函数模板

C++学习之模板 (二) -----类模板

由于将函数和类模板放在一块篇幅较大,我们今天将其拆分为两篇博文. 上篇博文我们讨论了函数模板的简单应用,本篇我们继续讨论模板的另一板块--类模板. 1).作用:类模板类似于代码产生器,根据用户输入的类型不同,产生不同的class: 2).编译: a):检查模板class 的自身语法: b):根据用户指定的类型 如 vector<string>,去实例化一个模板类. 注意: 不是实例化所以的代码,而仅仅实例化用户调用的部分: 类模板:简单实现如下; 1 #include <iostream

函数模板和类模板

一.函数模板 函数模板代表一类相同结构的函数,通过用户提供的具体参数,C++编译器在编译时刻能够将函数模板实例化,根据同一个模板创建出不同的具体函数,这些函数之间的不同之处主要在于函数内部一些数据类型的不同. 1 #include "stdafx.h" 2 #include <iostream> 3 using namespace std; 4 5 template <typename T> 6 T max1(T a,T b) 7 { 8 return a>

c++类模板之间友元函数调用

1 #include <stdio.h> 2 #include <iostream> 3 4 using namespace std; 5 6 template<typename T> class BTree; 7 8 /***************************节点类模板*********************************/ 9 template<typename T> 10 class BTreeNode{ 11 friend