前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C/C++开发基础——类模板

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

作者头像
Coder-Z
发布2023-10-07 10:14:27
1810
发布2023-10-07 10:14:27
举报

一,基础定义

类模板是用来生成类的蓝图,是一种创建类的方式,同一套类模板可以生成很多种不同的类。

编译器基于类模板生成的每个类被称为类模板的实例。

第一次使用模板类型声明变量时,会创建类模板的一个实例, 以后定义同类型的变量时,会使用已经创建的第一个实例。

类模板有许多应用,最常见的应用是定义容器类。

类模板和类一样,可以有友元,其友元可以是类,函数或者其他模板。

如果一个派生类继承自该类模板,那么这个派生类也必须是模板。

类模板的代码样式:

代码语言:javascript
复制
template <parameter list>
class ClassName
{
    //class definition ...
}

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

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

代码语言:javascript
复制
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>等价。

以上代码可以简化为:

代码语言:javascript
复制
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; }
};

类模板参数指定默认值的方式和函数参数一样。

默认值在类模板的声明中指定即可,不需要在成员函数模板中指定默认值。

定义类模板的时候也可以这样写:

代码语言:javascript
复制
template <typename T=string>
class Array
{
    //code
}

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

代码语言:javascript
复制
Array<> myArray;

二,类模板的成员函数

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

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

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

1.构造函数模板:

代码语言:javascript
复制
template <typename T>
Array<T>::Array(size_t arraySize): 
elements{new T[arraySize]}, size{arraySize}
{}

2.拷贝构造函数模板:

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

代码语言:javascript
复制
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.析构函数模板:

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

代码语言:javascript
复制
template <typename T>
Array<T>::~Array()
{
    delete[] elements;
}

4.下标运算符模板:

代码语言:javascript
复制
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.赋值运算符模板:

代码语言:javascript
复制
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;
}

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

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

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

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

三,非类型模板参数

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

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

代码样例如下:

代码语言:javascript
复制
template <typename T, size_t size> //size: 非类型参数
class ClassName
{
    //code
};

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

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

代码语言:javascript
复制
template <typename T, size_t size = 10>
class ClassName
{
    //code
};

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

代码语言:javascript
复制
整数类型(例如size_t、long)
枚举类型
对象的指针or引用类型
函数的指针or引用类型

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

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

代码样例:

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

代码语言:javascript
复制
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.构造函数模板:

代码语言:javascript
复制
template <typename T, int startIndex>
Array<T, startIndex>::Array(size_t arraySize): 
elements{new T[arraySize]}, size{arraySize}
{

}

2.拷贝构造函数模板:

代码语言:javascript
复制
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.析构函数模板:

代码语言:javascript
复制
template <typename T, int startIndex>
Array<T, startIndex>::~Array()
{
    delete[] elements;
}

4.下标运算符模板:

代码语言:javascript
复制
template <typename T, int startIndex>
T& Array<T, startIndex>::operator[](int index)
{
    return const_cast<T&>(std::as_const(*this)[index]);
}

5.赋值运算符模板:

代码语言:javascript
复制
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;
}

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

代码样例:

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

代码语言:javascript
复制
Array<double, 0> obj1{10};
Array<double, 1> obj2{10};

四,类模板的特例

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

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

代码样例:

代码语言:javascript
复制
template <>
class Array<const char*>
{
};

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

五,参考阅读

《C++17入门经典》

《C++ Primer》

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-10-07 08:00,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员与背包客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档