js css html

C++类模板

2023-01-07  本文已影响0人  仰简

一、定义 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}) {};
上一篇 下一篇

猜你喜欢

热点阅读