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}) {};
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

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