C++类模板

一、定义 C++ 类模板

template<typename T>
class Stack {
private:
    std::vector<T> elements;
public:
    void push(T const& ele);
    void pop();
    T const& top() const;
    bool empty() const {
        return elements.empty();
    }
};

template<typename T>
void Stack<T>::push(const T &ele) {
    elements.push_back(ele);
}

template<typename T>
void Stack<T>::pop() {
    assert(!elements.empty());
    elements.pop_back();
}

template<typename T>
T const & Stack<T>::top() const {
    assert(!elements.empty());
    return elements.back();
}

定义一个 C++ 类模板和定义一个函数模板类似,可以指定一个或者多个模板参数标识符。
在类外定义成员函数的实现时,需要带上模板参数标识符。

template<typename T> xxx Stack<T>::yyy() {}

定义构造函数、拷贝构造函数以及拷贝赋值函数时,不需要指定模板参数标识符。但是你也可以指定参数标识符,只不过效果上是等价的,看起来还显得冗余。

    Stack(){}
    Stack(Stack const& s) {
        this->elements = s.elements;
    }
    Stack& operator=(Stack const & s) {
        this->elements = s.elements;
        return *this;
    }

总的来说,基本原则就是,在只需要类名字而不需要参数类型时可以不指定模板参数标识符,需要参数类型时则必须带上模板参数标识符。
另外,与非模板类不同的是,模板不能定义在函数或者局部分作用域 {} 的内部,只能定义在全局 / 命名空间内。

二、模板类的使用

    Stack<int> intStack;
    Stack<float> floatStack;
    Stack<char> charStack;
    Stack<int *> intPointerStack;
    Stack<Stack<int>> intStackStack;

定义好了模板之后,就可以把模板当作一个普通的类来使用了,比如可以用来定义变量。定义变量时,可以指定类型参数为基本的类型、类类型以及模板类型。模板参数还可以是指针,但不能是引用。为引用时,会得到下面的错误。

image.png

模板类型作为参数时,在 c++11 之前右边的尖括号必须有空格,否则 >> 会被语义解析为右移运算符。

Stack<Stack<int> > intStackStack;

除了用来定义变量,模板类还可以被当作函数参数,可传值、传引用 & 传指针。函数参数可被 const 以及 volatile 修饰。

总的来说,基本原则是,任意类型都可以作为模板参数。但对于模板如果有运算符的操作,那模板参数的实参类型也必须支持其所有的运算符操作。

三、模板类重载运算符

下面是一个重载输出的运算符实现,和非模板类一样,建议将运算法重载定义为友元函数。

  void printOn(std::ostream& strm) {
        for (T const & elem : elements) {
            strm << elem << ' ';
        }
    }

    friend std::ostream & operator<< (std::ostream& strm, Stack<T> const& s) {
        s.printOn(strm);
        return strm;
    }

四、特化

特化是指对模板的参数定义一个特定类型的实现,对于被特化的模板,其成员变量和成员函数也都定义为特化实现。

template<>
class Stack<std::string> {
private:
    std::vector<std::string> elements;
public:
    void push(std::string const& ele);
    std::string const& top() const;
    void pop();
    bool empty() const {
        return elements.empty();
    }
    void printOn(std::ostream& strm) const{
        for (std::string const & elem : elements) {
            strm << elem << ' ';
        }
    }

    friend std::ostream & operator<< (std::ostream& strm, const Stack<std::string> &s) {
        s.printOn(strm);
        return strm;
    }
};

void Stack<std::string>::push(const std::string &ele) {
    elements.push_back(ele);
}

std::string const& Stack<std::string>::top() const {
    assert(!elements.empty());
    return elements.back();
}

void Stack<std::string>::pop() {
    assert(!elements.empty());
    elements.pop_back();
}

使用特化类

    // 将使用特化版本
    Stack<std::string> stringStack;
    stringStack.push("aaaa");
    stringStack.push("bbbb");
    std::cout << stringStack << std::endl;

template<> class Stack<std::string> {}; 可以看成全特化,虽然其只有一个模板参数。相对应的还有偏特化或者叫部分特化。
偏特化有两种情况,一种是模板参数仍然是存在的,而针对模板参数的指针做特化,比如

template<typename T>  class Stack<T*> {};

另一种情况是多个模板参数的情况下,只特化其中一部分参数,比如

// 有如下原始模板类
template<typename T, typename U> class Stack {};
// 那么可以得到偏特化的模板类
template<typename T> class Stack<T,T> {};
template<typename T> class Stack<T, int> {};
template<typename T, typename U> class Stack<T* , U*> {};

五、类模板的默认参数与类模板别名

类模板的参数与函数模板一样,也可以指定默认参数。

template<typename T, typename Cont = std::vector<T>> class Queue {
private:
    Cont elements;
public:
    void push(T const& ele);
    void pop();
    T const& top() const;
};

类模板的别名是为了让模板使用起来更简单,可以通过 typedef 来定义,c++11 以后也可以通过 using 来定义。

typedef Stack<int> IntStack;
using LongStack = Stack<long>;

上面的两者是等价的,但 using 还可以用来定义别名模板,typedef 是不可以的。比如,下面的定义是合法的。

template<typename T>
using QueueStack = Queue<T, std::queue<T>>;
// 使用 QueueStack
QueueStack<int> intQueueStack;

但如果你用 typedef 来定义的话,就会得到如下的错误。

image.png

使用 using 和 typename 还可以定义类的成员别名。

template<typename T, typename Cont = std::vector<T>> class Queue {
private:
    Cont elements;
public:
    void push(T const& ele);
    void pop();
    T const& top() const;
public:
    using Iterator = int;
    // 可以这样定义一个成员别名模板
    using MyIterator = typename Queue<T>::Iterator;
};
// 然后这样使用 MyIterator
Queue<int>::MyIterator it;
// 还可以这样定义成员别名模板
template<typename T>
using MyOuterIterator = typename Queue<T>::Iterator;
// 然后这样使用
MyOuterIterator<int> outer_it;

六、类模板的类型推导

从 c++17 开始允许类模板的类型推导,实现方式比如,可以通过实现一个带参的构造函数来推导。

explicit Stack(T &ele) : elements({ele}) {};
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 容量类等有些类并不能使用继承和包含来实现。容器类设计用来存储其他对象或数据类型,比如Stack、Queue。与其编...
    秃头侠JeFF阅读 293评论 0 0
  • 函数模板 当我们想要定义一个可以支持泛型的函数时,就要采用函数模板的方式了。所谓泛型就是可以支持多种类型的操作,比...
    恋恋风辰阅读 391评论 0 0
  • 一、引言 通常C++代码会分成.h和.cpp进行编写。其中.h放类和方法的声明,提供给其他文件包含。而.cpp中放...
    JeremyYv阅读 4,039评论 0 2
  • 在C++中我们往往需要编写多个形式和功能都相似的类,于是 引人了类模板的概念,编译器从类模板可以自动生成多个类,避...
    飞扬code阅读 671评论 0 3
  • 总结起来一共有这么几条: 如果一个类模板包含一个非模板友元,则友元被授权可以访问所有类模板实例。 如果一个非模板类...
    书呆子的复仇阅读 2,940评论 0 0

友情链接更多精彩内容