C/C++开发基础——类模板

本章主要内容:
一,基础定义
二,类模板的成员函数
三,非类型模板参数
四,类模板的特例
五,参考阅读

一,基础定义

类模板是用来生成类的蓝图,是一种创建类的方式,同一套类模板可以生成很多种不同的类。编译器基于类模板生成的每个类被称为类模板的实例。第一次使用模板类型声明变量时,会创建类模板的一个实例, 以后定义同类型的变量时,会使用已经创建的第一个实例。类模板有许多应用,最常见的应用是定义容器类。类模板和类一样,可以有友元,其友元可以是类,函数或者其他模板。如果一个派生类继承自该类模板,那么这个派生类也必须是模板。
类模板的代码样式: 

template <parameter list>
class ClassName
{
    //class definition ...
}

类型模板参数 & 非类型模板参数图示:

图片

代码样例:用类模板实现的Array<T>

template <typename T>
class Array
{
private:
    T* elements;
    size_t size;
public:
    explicit Array<T>(size_t arraySize);      //构造函数
    Array<T>(const Array<T>& array);          //拷贝构造函数
    ~Array<T>();                              //析构函数
    T& operator[](size_t index);              //下标运算符
    Array<T>& operator=(const Array<T>& rhs); //赋值运算符
    size_t getSize() const {return size;}
};

在类模板的内部,可以直接使用类模板名称,不需要显式地带模板参数,因此,在类模板的内部,Array和Array<T>等价。以上代码可以简化为:

template <typename T>
class Array
{
    private:
        T* elements;
        size_t size;
    public:
        explicit Array(size_t arraySize);
        Array(const Array& array);
        ~Array();
        T& operator[](size_t index);
        Array& operator=(const Array& rhs);
        size_t getSize() const {return size; }
};

类模板参数指定默认值的方式和函数参数一样。默认值在类模板的声明中指定即可,不需要在成员函数模板中指定默认值。
定义类模板的时候也可以这样写: 

template <typename T=string>
class Array
{
    //code
}

使用默认值来实例化类模板,可以这样写:

Array<> myArray;

二,类模板的成员函数 图片

在类模板的模板体中定义的成员函数,与普通的类一样,成员函数可以看作是所有模板实例的内联函数。

但是在模板体的外部定义的成员函数,语法与普通的类不同,需要将成员函数定义为函数模板。

由于成员函数的函数模板与它们的类模板绑定在一起,所以函数模板使用的参数列表必须与类模板的参数列表完全相同。

1.构造函数模板:  

template <typename T>
Array<T>::Array(size_t arraySize): 
elements{new T[arraySize]}, size{arraySize}
{}

2.拷贝构造函数模板:

假定赋值运算符可以用于T类型的变量。

template <typename T>
Array<T>::Array(const Array& array): Array{array.size}
{
    for (size_t i {}; i < size; ++i)
    {
        elements[i] = array.elements[i];
    }
}

3.析构函数模板:

释放给数组分配的堆内存。

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

4.下标运算符模板:

template <typename T>
T& Array<T>::operator[](size_t index)
{
    if (index >= size)
    {
        throw std::out_of_range {"Index too large: " + std::to_string(index)};
    }

    return elements[index];
}

5.赋值运算符模板:

template <typename T>
Array<T>& Array<T>::operator=(const Array& rhs)
{
    if (&rhs != this)
    {
        delete[] elements;
        size = rhs.size;
        elements = new T[size];  //may throw std::bad_alloc
        for(size_t i {}; i < size; ++i)
        {
            elements[i] = rhs.elements[i];
        }
    }
    return *this;
}

由类模板创建模板实例时,并不会把所有的成员函数的函数模板都拿去生成模板实例,只有被代码用到的成员函数才会被生成模板实例,例如,由类模板生成某个类时,这个类只进行了创建对象的操作,只有构造函数和析构函数的函数模板会生成模板实例,其他暂时没用到的函数模板,比如拷贝构造函数模板,则不会生成模板实例。简单讲就是,当实例化一个类模板时,它的成员函数对应的函数模板只有在使用时才会被实例化。

声明指向对象的指针并不会创建类模板的实例: 

Array<std::string>* obj_ptr;  
//声明了一个指针,不会创建类模板的实例

Array<std::string*> str_obj {10};
//定义了一个对象,会创建类模板的实例,同时还会生成构造函数的函数模板实例

三,非类型模板参数图片

非类型参数是指模板定义中,带有指定类型的参数。

非类型参数的主要用途是指定容器的大小和上下限。

代码样例如下:

template <typename T, size_t size> //size: 非类型参数
class ClassName
{
    //code
};

注意:类型参数T必须放在非类型参数size的前面。

非类型模板参数还可以在定义的时候给一个初始值,例如:

template <typename T, size_t size = 10>
class ClassName
{
    //code
};

非类型参数支持的数据类型: 

整数类型(例如size_t、long)
枚举类型
对象的指针or引用类型
函数的指针or引用类型

非类型参数不支持浮点类型或类类型。

从C++17开始,也可以指定auto,auto& 和 auto* 等作为非类型参数,编译器会自动推导出类型。

代码样例:

a.带有非类型参数的Array类模板:

template <typename T, int startIndex>
class Array
{
private:
    T* elements;
    size_t size;
public:
    explicit Array(size_t arraySize);
    Array(const Array& array);
    ~Array();
    T& operator[](int index);
    Array& operator=(const Array& rhs);
    size_t getSize() const { return size; }
}

b.带有非类型参数的成员函数模板

1.构造函数模板:   

template <typename T, int startIndex>
Array<T, startIndex>::Array(size_t arraySize): 
elements{new T[arraySize]}, size{arraySize}
{

}

2.拷贝构造函数模板:

template <typename T, int startIndex>
Array<T, startIndex>::Array(const Array& array): Array{array.size}
{
    for (size_t i {}; i < size; ++i)
    {
        elements[i] = array.elements[i];
    }
}

3.析构函数模板: 

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

4.下标运算符模板:

template <typename T, int startIndex>
T& Array<T, startIndex>::operator[](int index)
{
    return const_cast<T&>(std::as_const(*this)[index]);
}

5.赋值运算符模板:

template <typename T, int startIndex>
Array<T, startIndex>& Array<T, startIndex>::operator=(const Array& rhs)
{
    if (&rhs != this)
    {
        delete[] elements;
        size = rhs.size;
        elements = new T[size];  //may throw std::bad_alloc
        for(size_t i {}; i < size; ++i)
        {
            elements[i] = rhs.elements[i];
        }
    }
    return *this;
}

非类型模板参数在使用时需要注意,给非类型参数传不同的实参,将生成不同的模板实例。

代码样例:

以下代码将创建两个不同的模板实例

Array<double, 0> obj1{10};
Array<double, 1> obj2{10};

四,类模板的特例图片

和函数模板一样,类模板也有特例,被称为类模板的具体化。

当有些模板参数只适用于特定的数据类型,比如可以使用string类型实例化模板,但使用char*类型实例化模板时会报错,此时需要定义类模板的特例。

代码样例:

template <>
class Array<const char*>
{
};

类模板特例的定义必须在原始类模板的定义之后。

五,参考阅读图片
《C++17入门经典》《C++ Primer》

声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/336330.html

联系我们
联系我们
分享本页
返回顶部