代码改变世界

白话C++系列(34)-- 类模板

2016-07-05 20:47  Keiven_LY  阅读(659)  评论(0编辑  收藏  举报

类模板

为什么会有类模板呢?这和函数模板的道理是一样的,是因为在很多使用场合下,一个类会用到很多次,而在用的时候发现很多重复的地方,只有它的数据类型不同,所以这个时候我们就要用到类模板。我们看下面一个例子:

在这里,我们定义了一个类:MyArray,其中,我们用T这种数据类型来定义了它的数据成员的指针,并且还定义了一个成员函数display,请大家注意,这个成员函数的定义时写在类内的。那么在类模板的情况下,在类内定义成员函数的时候并没有什么不同。但是,在类模板的情况下,在类外定义成员函数的时候,则大不相同,如下:

我们看到,在类模板的情况下,当在类外定义成员函数的时候,需要在这个成员函数的上方,先把template <class T>这一行代码写出来(注意,它的写法与在类的上方的写法是一样的),我们每定义一个成员函数,都要在这个定义的成员函数上方加上这一行代码;同时,我们需要在这个成员函数的类名后面用尖括号括上相应的参数T(如果有两个参数,需要用逗号隔开)。在使用的时候,我们如果实例化一个对象,我们就需要在类名的后面用尖括号括上当前这个对象是什么数据类型。

与函数模板一样,类模板并不产生实质性代码,只有当我们去实例化一个对象时,将类的后面写上一个固定的参数,这个时候才会产生数据代码,而这套数据代码,我们就称之为模板类,那么,这样的关系与前面所讲的函数模板与模板函数的道理一样。

下面我们来看一看,类模板当中使用多个参数的情况。当我们有多个参数时,我们举了一种比较复杂的情况(既有类型作为参数,也有变量作为参数)如下:

使用的时候,也分为类内定义和类外定义的成员函数。对于类内定义,我们不必多说了,而对于类外定义,如下:

在使用的时候,我们同样也要给定两个参数,如下:

特别提醒:VS中模板代码不能分离编译

类模板代码实践

题目描述:

/* ************************************************  */

/* 类模板

      定义类模板MyArray:

          成员函数:构造函数、析构函数、display函数

          数据成员:m_pArr

*/

/* ************************************************  */

程序框架:

需要大家特别注意的是如果我们要定义一个类模板,我们必须将类的声明以及类的定义部分写在同一个.h文件当中,未来在使用的时候把它包含进来,所以在MyArray.cpp文件当中没有写任何代码,而是将所有代码都写在了MyArray.h文件当中了。

另外,定义类模板的时候,还需要注意以下内容

  • 在类的上面一行,要写上template关键字,然后加上模板参数列表;
  • 如果我们在类的内部定义函数时(类内定义),那么,我们不需要有什么特别需要注意的地方;
  • 如果我们在类的外部定义函数时(类外定义),则需要在每一个函数的上面加上template关键字,然后再加上模板参数列表。另外,在函数定义时,还需要用尖括号括上相应参数。

头文件(MyArray.h

#ifndef MYARRAY_H
#define MYARRAY_H

#include <iostream>
using namespace std;

template <typename T, int KSize, int KVal>
class MyArray
{
public:
    MyArray();
    ~MyArray()            //这里我们对析构函数进行的是类内定义
    {
        delete []m_pArr;
        m_pArr = NULL;
    }
    void display();
private:
    T *m_pArr; //用到了模板参数T
};

template <typename T, int KSize, int KVal>
MyArray<T, KSize, KVal>::MyArray()    //类外定义构造函数,要加上上面这一句
{
    m_pArr = new T[KSize]; //等价于   T *m_pArr = new T[KSize];
    for(int i = 0; i < KSize; i++)
    {
        m_pArr[i] = KVal;
    }
}

//template <typename T, int KSize, int KVal>
//MyArray<T, KSize, KVal>::~MyArray()    //类外定义析构函数,要加上上面这一句
//{
//    delete []m_pArr;
//  m_pArr = NULL;
//}

template <typename T, int KSize, int KVal>
void MyArray<T, KSize, KVal>::display()  //类外定义display函数,要加上上面的这一句
{
    for(int i = 0; i < KSize; i++)
    {
        cout << m_pArr[i] << endl;
    }
}

#endif

此外,在定义数据成员的时候,往往也要用到模板中的参数,比如我们在这用到了模板参数中的T,未来我们把T定义成什么类型,就可以定义出一个什么样类型的数组。我们看到,当前我们定义的数据成员是一个指针,在它的构造函数当中,我们将其定义为一个数组,并且在构造函数当中,我们已经将这个数组中的每一个值都做了赋值操作,这样这个数组的每一个元素的值都相同,都是KVal。

接下来我们跳到demo.cpp文件中,来使用一下MyArray这个类。那么,我们必须先将这个类的模板实例化成一个模板类,如何来写呢?我们这样来写:

#include <iostream>
#include "MyArray.h"

using namespace std;

int main()
{
    //实例化一个模板类arr
    MyArray<int,5,6> arr; //实例化了一个整型数组arr,含有5个元素,每个元素的值都是6
    arr.display();
    system("pause");
    return 0;
}

我们按F5,看一看运行结果:

我们看到结果打印出了5个6,这正是我们所实例化的数组arr,其具有5个元素,并且每个元素都赋值为6

练习:定义一个矩形类模板,该模板中含有计算矩形面积和周长的成员函数,数据成员为矩形的长和宽。

#include <iostream>
using namespace std;

/**
 * 定义一个矩形类模板Rect
 * 成员函数:calcArea()、calePerimeter()
 * 数据成员:m_length、m_height
 */
template<typename T>
class Rect
{
public:
    Rect(T length, T height);
    T calcArea();
    T calcPerimeter();
public:
    T m_length;
    T m_height;
};

/**
 * 类属性赋值
 */
template<typename T>
Rect<T>::Rect(T length, T height)
{
    m_length = length;
    m_height = height;
}

/**
 * 面积方法实现
 */
template<typename T>
T Rect<T>::calcArea()
{
    return m_length * m_height;
}

/**
 * 周长方法实现
 */
template<typename T>
T Rect<T>::calcPerimeter()
{
    return ( m_length + m_height) * 2;
}

int main(void)
{
    Rect<int> rect(3, 6);
    cout << rect.calcArea() << endl;
    cout << rect.calcPerimeter() << endl;
    system("pause");
    return 0;
}

运行结果: