【类模板】
创始人
2024-03-02 11:41:56
0

与函数模板相似,类也可以被一种或多种类型参数化。

容器类是一个典型案例,通常用于管理和组织某种类型的元素。只要使用类模板,就可以实现容器类,而不需要确定容器中元素的类型。

我们以stack作为类模板的例子。

1 类模板stack的实现

与函数模板的处理方式一样,在头文件中声明和定义类statck<>

#include 
#include template
class stack{
private:std::vector elems; // 存储元素的容器
public:void push(T const&); // 压入元素void pop(); // 弹出元素T top() const; // 返回栈顶元素bool empty() const { // 识别栈是否为空return elems.empty();}
};
template
void stack::push(T const& a)
{elems.push_back(a);
}
template
void stack::pop()
{if(elems.empty()) {throw std::out_of_range("stack<> pop(): empty stack");}elems.pop_back(); // 删除最后一个元素
}template
T stack::top() const
{if(elems.empty()) {throw std::out_of_range("stack<>::top(): empty stack");}return elems.back(); // 返回最后一个元素的拷贝
}
  • 这里继续使用T作为类型参数的标识符。在类模板的内部,T可以像其他任何类型一样,用于声明成员变量和成员函数。

这个类的类型是stack,T是模板参数; 类名是stack。
当你在声明中需要使用该类的类型时,就必须使用stack, 当需要使用类名时就应该是stack。

template
class stack{stack(stack const&); // 拷贝构造,这里使用类型stack作为参数, 函数名是类名stack(); // 构造函数
};
  • 为了定义类模板的成员函数,你必须指定该成员函数是一个函数模板,而且你还需要使用这个类模板的完整类型限定符:stack::

对于类模板的任何成员函数,你都可以把它实现为内联函数,将它定义于类声明里面。

template
class stack{...void push(T const& elem) {elems.push_back(elem);}...
};

2 类模板stack的使用

为了使用类模板对象,必须显式地指定模板参数。下面例子展示如何使用类模板stack<>

#include 
#include 
#include 
#include "stack.h" // 这里声明和定义stack的实现int main()
{try {stack intStack;stack stringStack;// 使用init 栈intStack.push(7);std::cout<std::cerr << "Exception: " << ex.what() << std::endl;return EXIT_FAILURE;}
}

通过声明类型stack, 在类模板内部就可以用int实例化T。对于所有被调用的成员函数,都会实例化出基于int类型的函数代码。类似stackstd::string, 将会创建一个stackstd::string对象,对于所有被调用的成员函数,也会实例化出基于std::string的函数代码。

  • 只有那些被调用的成员函数,才会生产这些函数的实例代码。对于类模板,成员函数只有在被使用的时候才会被实例化。
  • 这样的好处是节省空间和时间。
  • 对于那些“未能提供所有成员函数中所有操作的”类型,你也可以使用该类型来实例化类模板。只要那些"未能提供某些操作的"成员函数,模板内部不使用就可以。
  • 如果类模板中含有静态成员,那么用来实例化的每种类型,都会实例化这些静态成员。
  • 你可以像使用其他任何类型一样地使用实例化后的类模板类型,只要它支持所调用的操作就可以。
void foo(stack const&  s)
{stack istack[10];  /// istack是含有10个栈的数组
}

借助于类型定义,你可以方便地使用类模板:

typedef stack   intStack;
void foo(intstack const& s)
{intStack istack[10]; /// istack 是一个含有10个int栈的数组....
}

C++的类型定义只是定义了一个类型别名,并没有定义一个新类型,是可以互相赋值。

模板实参可以是任何类型, eg:

stack  floatStack; // 元素类型为浮点型指针的栈
stack > intStackStack; // 元素类型为int栈的栈

要求是该类型必须提供被调用的所有操作

3 类模板的特化

  • 用模板实参来特化类模板。和函数模板的重载类似,通过特化类模板,你可以优先基于某种特定类型的实现,或者克服某种特定类型在实例化模板时所出现的不足,比如该类型没有提供某种操作。

  • 特化一个类模板,你还要特化该类模板的所有成员函数。虽然也可以之特化某个成员函数,但这个做法并没有特化整个类,也就是没有特化整个类模板。

  • 为了特化一个类模板,必须在起始处声明一个template<>, 接下来声明用来特化类模板的类型。这个类型被用作模板实参,且必须在类名的后面直接指定:

    template<>
    class stack {...
    };
    

进行类模板的特化时,每个成员函数都必须重新定义为普通函数,原来模板函数中的每个T也相应地被进行特化的类型取代:

    void stack::push(std::string const& elem){elems.push_back(elem);}

用std::string特化stack<>

template<>
class stack
{
private:std::deque elems;
public:void push(std::string const& );void pop();std::string top() const;bool empty() const{return elems.empty();}
};void stack::push(std::string const& elem)
{elems.push_back(elem);
}void stack::pop()
{if (elems.empty()) {throw std::out_of_range("stack::pop(): empty stack");}elems.pop();
}std::string stack::top()const 
{if (elems.empty()) {throw std::out_of_range("stack::top(): empty stack");}return elems.back();
}

我们使用deque而不是vector来管理stack内部的元素,目的是为了说明一个特点:特化的实现可以和基本类模板的实现完全不同。

4 局部特化

类模板可以被局部特化。你可以在特定环境下指定类模板的特定类型,并且要求某些模板参数仍然必须由用户来定义。
譬如下面我们给类模板

template
class Myclass {...
};

的几种局部特化:

  • 局部特化一
    // 两个模板参数具有相同的类型
    template
    class Myclass {...
    };
    
  • 局部特化二:
    // 第二个模板参数的类型是int
    template
    class Myclass {.... 
    };
    
  • 局部特化三:
    template
    class Myclass {...
    };
    

然后我们看一下,下面各种声明使用哪个模板:

Myclass mif; // 使用Myclass
Myclass mff; // 使用Myclass
Myclass mfi; //使用Myclass
Myclass mp; //使用Myclass

如果有多个局部特化同等程度地匹配某个声明,那么就称为声明具有二义性:

Myclass m; // 错误,同等程度地匹配Myclass和Myclass
Myclass m; // 错误,同等程度地匹配Myclass和Myclass

为了解决二义性,你可以另外提供一个指向相同类型指针的特化:

template
class Myclass {};

5 缺省模板实参

类模板还可以定义缺省值;这些值就被称为缺省模板实参;而且,它们还可以引用之前的模板参数。例如类stack<>中,可以把用于管理元素的容器定义为第二个模板参数,并且使用std::vector<>作为它的缺省值:

#include
#include
template >
class stack {
private:COUNT elems; // 包含元素的容器
public:void push(T const&);void pop();T top() const;bool empty() const {return elems.empty();}
};template
void stack::push(T const& elem)
{elems.push_back(elem);
}template
void stack::pop()
{if (elems.empty()) {throw std::out_of_range("stack<>::pop(): empty stack");}elems.pop_back();
}template
T stack::top() const
{if (elems.empty()) {throw std::out_of_range("stack<>::top(): empty stack");}return elems.back();    
}

类模板含有两个模板参数,因此每个成员函数的定义都必须具有这两个参数:

template
void stack::push(T const& elem)
{elems.push_back(elem);
}

你仍然可以像前面例子一样使用这个栈,只是说,你只传递一个类型实参给这个类模板,将使用缺省的vector来管理栈元素。

下面我们看一下在程序中使用stack对象,指定容器类型:

#include 
#include 
#include 
#include "stack3.h"int main()
{stack intStack; // int栈stack > dblStack; // double栈,使用std::deque管理元素
}

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...