[知识点] 1.5.4 模板与 STL

总目录 > 1 语言基础 > 1.5 C++ 进阶 > 1.5.4  模板与 STL

前言

模板是之前几乎没接触过的东西,在 C++ 中使用得其实也挺多的;STL 就不多说了,太好用了。

子目录列表

1、模板的概念

2、函数模板

3、类模板

4、STL

1.5.4  模板与 STL

1、模板的概念

模板(Template),是 C++ 实现代码重用机制的重要工具,是泛型技术(即与数据类型无关的通用程序设计技术)的基础。它把算法设计从具体数据类型中分离出来,能够设计出独立于数据类型的通用模板程序,分为函数模板类模板两类。

有些时候,我们设计某些功能需要针对诸多不同的数据类型,但除此之外代码完全一致,为此不得不编写多个具体到不同数据类型的程序,比如一个简单的求最大值函数

int max(int a, int b) {
    return a > b ? a : b;
}

double max(double a, double b) {
    return a > b ? a : b;
}

char max(char a, char b) {
    return a > b ? a : b;
}

1.1  C 语言基础 中的预处理器部分,我们提到的宏定义 #define 可以解决这个问题,即:

#define max(a, b) (a < b ? a : b)

但宏定义一直存在的最大问题就是会避开类型检查机制,并且因为可操作性太强,以至于非常容易出现难以意料的错误。而模板的出现,让一切变得简单而规范。

模板就像工艺制造中的模具一样,功能已经被固定下来,而数据类型就像填充的材质一样,是可以选择的;最后生产出来的产品,便是函数或者类。以上述求最大值函数为例,其函数模板为:

template <typename T>
T min(T a, T b) {
    return a < b ? a : b;
}

关键字 template 表示定义模板(这个代码编辑器竟然不识别 template),< >参数表typename 表示 T 可以为任何数据类型,称为类型参数,可以用 class 代替,两者完全等价。

2、函数模板

前面已经用函数模板举了个例子,其一般形式为:

template <模板参数表>
返回类型 函数名(参数表) {
    ...
}

注意,template 语句和函数模板的定义之间不能有任何其他语句

当编译器遇到 template 和跟随其后的函数定义时,它只是简单地知道这个函数模板以后可能会用到,而并不会据此有任何操作。当编译器遇到对其的调用时,才会根据调用语句中实参的具体类型生成代码,这一过程叫做模板的实例化,生成的函数称作模板函数

注意,使用函数模板时,编译器并不会进行任何参数类型的隐式转换,如果出现不匹配的情况,将会直接报错。诸如上述求最大值模板,如果调用 max(2, 3.2),系统会将 2 判定为 int 类型,而 3.2 为 double 类型,但模板并未允许两者为不同数据类型,故出现错误。解决这个问题,可以:

① 显式转换类型;

② 显式指定函数模板实例化的类型参数,比如:min<double>(2, 3.2);

③ 指定多个模板参数,比如:

template <typename T1, typename T2> 
T1 max(T1 a, T2 b) {
    return a > b ? a : b;
} 

(当然这样写其实依旧不够准确,但确实不会导致编译错误,更好的办法是将返回类型替换成 C++ 11 标准中的 auto,关于 auto 请参见 施工中)

模板参数还可以为确定的数据类型,称为非类型模板参数,和普通函数的形参定义方式和作用是类似的,举个例子:

template <typename T, int n>
void sort(T a[n]) {
    for (int i = 1; i < n; i++)
        for (int j = i + 1; j <= n; j++)
            if (a[i] > a[j])
                swap(a[i], a[j]);
} 

int a[] = {1, 3, 4, 2, 9, 5, 8, 10, 6, 7};
sort <int, 10> (a);

注意,非类型参数只能被传递常数,而不能传递变量。

3、类模板

类模板与类的关系形如函数模板与函数的关系,其一般形式为:

template <模板参数表>
class 类名 {
    ...
}; 

关于类模板与模板类的关系,以及模板参数不再赘述,直接以一个自定义的栈类模板举例:

template <typename T, int n>
class Stack {
    T a[n];
    int top;
public:
    Stack() {
        T = 0;
    }
    void push(T o);
    T pop() {
        if (top <= 0) {
            cout << "error" << endl;
            return 0;
        }
        top--;
        return a[top];
    }
    bool empty() {
        return top == 0;
    }
};

template <typename T, int n>
void Stack <T, n> :: push(T o) {
    if (top == n) {
        cout << "error" << endl;
        return 0;
} a[top
++] = o; } Stack <int, 10> s;

其中,pop() 函数使用的是类内定义,无异于普通类;push() 函数使用的是类外定义,则在定义时需要再次声明模板,并且加上完整的类型限定符。

由类模板实例化生成针对具体数据类型的模板类,再由模板类定义模板对象。

4、STL

① STL 简介

上面已经介绍了如何自定义模板,而 C++ 内置了一套非常完善的标准模板库,将诸多组件实现了标准化,极大便捷了开发者对程序的编写。

标准模板库(Standard Template Library, STL),包含:容器(Container),迭代器(Iterator),算法(Algorithm),空间配置器(Allocator),配接器(Adapter),仿函数(Functor),前三者为核心内容。它们被保存在如下 13 个头文件:

<algorithm>, <deque>, <functional>, <iterator>, <vector>, <list>, <map>, <memory.h>, <numeric>, <queue>, <set>, <stack>, <utility>

② STL 容器

容器(Container)是用来存储其他对象的对象。容器是容器类的实例,容器类是用类模板实现的,适用于各种数据类型,包含:顺序容器(序列式容器)、关联容器和容器适配器。

> 顺序容器

顺序容器是将同类型对象的有限集按顺序组织在一起的容器,用来表示线性数据结构,包括:向量(Vector),列表(List)和双端队列(Deque)。

还有一种特殊的顺序容器:值对(pair)

> 关联容器

关联容器是非线性容器,用来根据键(key)进行快速存储、检索数据的容器,可以存储值的集合或者键-值对(Key-Value),主要包括:集合(Set),多重集合(Multiset),映射(Map)和多重映射(Multimap)

> 容器适配器

容器适配器本质上并不是容器,因为它没有迭代器,但依旧归类于容器;它由栈(Stack),队列(Queue)和优先队列(Priority_queue)组成。

③ STL 迭代器

迭代器(Iterator)是一个对象,常用来遍历容器,可以认为是基于模板的指针,可以指示容器中的元素位置,遍历各个元素。迭代器本身提供了适用于多种容器类型的通用操作,开发者无需知道实际类型。

迭代器是 STL 的核心,用于定义哪些算法可以在容器中使用,是容器和算法之间的桥梁。如果某个容器需要使用迭代器,则需要定义它,一般形式为:

容器类型 <容器参数表> :: iterator 迭代器名; 

举个例子:

list <int> l;
list <int> :: iterator iter; 

迭代器提供的主要操作如下:

施工中

④ STL 算法

算法(Algorithm)是用模板技术实现的适用于各个容器的通用程序,一般通过迭代器间接操作容器元素,并返回迭代器作为算法运算的结果。STL 共计提供了超过 100 个实现算法的模板函数,基本都包含在 <algorithm> 库中,其中许多算法不仅适用于容器类型,还适用于普通的 C++ 数组或者自定义容器。

原文地址:https://www.cnblogs.com/jinkun113/p/13726346.html