标准模板

 

1 型程序设计

 

õ  面向过程->基于对象->面向对象->泛型

 

1.       将程序写得尽可能通用

2.       将算法从特定的数据结构中抽象出来,成为通用的

3.  C++的模板为泛型程序设计奠定了关键的基础

4.  STL是泛型程序设计的一个范例

 

 

2 STL简介

何为STL?

 标准模板库(Standard Tempate Library)是ANSI/ISO C++ 最有特色,最实用的部分之一

STL 简史

STL抽象的是什么

标准模板库(STL)

STL主要由六大部分组成:

※迭代器(iterators)

※算法(algorithms)

※容器(containers)

※函数对象(function objects)

※内存分配器(allocators)

※适配器

STL 的六大组件全都是抽象出来的概念

3 Container(容器)

õ  容器的概念

 用来管理一组元素。

 

标准库容器类

说明

顺序容器

voctor(矢量)

list(列表)

deque(双端队列)

 

从后面快速插入与删除,直接访问任何元素

从任何地方快速插入与删除,双链表

从前或后面快速插入与删除,直接访问任何元素

关联容器

set(集合)

multiset(多重集合)

map(映射)

multimap(多重映射)

 

快速查找,不允许重复值

快速查找,允许重复值

一对一映射,基于关键字快速查找,不许重复值

一对多映射,基于关键字快速查找,允许重复值

容器适配器

stack(栈)

queue(队列)

priority_queue

 

后进先出(LIFO)

先进先出(FIFO)

最高优先级元素总是第一个出列

 

 

容器的分类

õ  序列式容器(Sequence containers)

ô  每个元素都有固定位置--取决于插入时机和地点,和元素值无关。

ô  顺序容器,是因为元素的插入位置同元素的值无关。

ô  vector、deque、list

õ  关联式容器(Associated containers)

ô  元素位置取决于特定的排序准则,和插入顺序无关

ô  关联式容器内的元素是排序的,插入任何元素,都按相应的排序准则来确定其位置。关联式容器的特点是在查找时具有非常好的性能。

ô  set、multiset、map、multimap

容器的通用接口

õ  通用容器运算符

ô  ==,!=,>,>=,<,<=,=

õ  方法(函数)

ô  迭代方法

ó  begin(),end(),rbegin(),rend()

ô  访问方法

ó  size(),max_size(),swap(),empty()

顺序容器

õ  顺序容器的接口

ô  插入方法

ó  push_front(),push_back(),insert(),运算符“=”

ô  删除方法

ó  pop() ,erase(),clear()

ô  迭代访问方法

ó  使用迭代器

ô  其它顺序容器访问方法(不修改访问方法)

ó  front(),back(),下标[]运算符

顺序容器——向量

头文件 <vector>

    实际上就是个动态数组。随机存取任何元素都能在常数时间完成。在尾端增删元素具有较佳的性能。

õ  数组尾部添加或移除元素非常快速。但是在中部或头部安插元素比较费时。

õ  向量属于顺序容器,用于容纳不定长线性序列(即线性群体),提供对序列的快速随机访问(也称直接访问)

õ  向量是动态结构,它的大小不固定,可以在程序运行时增加或减少。

例1 vector

#include <iostream>

#include <vector>  //用vector前,必须包含此头文件

 

using namespace std;

 

int main()

{

     vector<int> coll; //vector container for integer

    

     // append elements with values 1 to 6

     for(int i=1; i<=6; ++i) {

         coll.push_back(i);

     }

    

     //print all elements followed by a space

     for(int i=0; i<coll.size(); ++i) {

         cout << coll[i] << ' ';

     }

     cout << endl;

}

顺序容器——双端队列

头文件 <deque>

    也是个动态数组,随机存取任何元素都能在常数时间完成(但性能次于vector)。在两端增删元素具有较佳的性能。

ô  deque,是“double-ended queue”的缩写。

ô  双端队列是一种放松了访问权限的队列。元素可以从队列的两端入队和出队,也支持通过下标操作符“[]”进行直接访问。

ô  可以随机存取元素(用索引直接存取)。

ô  数组头部和尾部添加或移除元素都非常快速。但是在中部或头部安插元素比较费时。

例2 deque

int main()

{

     deque<float> coll; //deque container for floating-point

 

     //insert element from 1.1 to 6.6 each at the front

     for(int i=1; i<=6; ++i) {

         coll.push_front(i*1.1); // insert at the front

     }

 

     //print all elements followed by a space

     for(int i=0; i<coll.size(); ++i) {

         cout << coll[i] << ' ';

     }

     cout << endl;

}

顺序容器——列表

list头文件 <list>

õ  双向链表,在任何位置增删元素都能在常数时间完成。不支持随机存取。

ô  列表主要用于存放双向链表,可以从任意一端开始遍历。列表还提供了拼接(splicing)操作,将一个序列中的元素从插入到另一个序列中。

ô  不提供随机存取(按顺序走到需存取的元素,O(n))。

ô  在任何位置上执行插入或删除动作都非常迅速,内部只需调整一下指针。

例3链表

int main()

{

     list<char> coll; //list container for charactor

 

     //append elements from 'a' to 'z'

     for(char c='a'; c<='z'; ++c) {

         coll.push_back(c);

     }

 

     while(!coll.empty()) {

         cout << coll.front() << ' ';

         coll.pop_front();

     }

     cout << endl;

}

关联式容器

õ  Sets/Multisets

头文件 <set>

   set 即集合。set中不允许相同元素,multiset中允许存在相同的元素。

ô  内部的元素依据其值自动排序

ô  Set内的相同数值的元素只能出现一次,Multisets内可包含多个数值相同的元素。

ô  内部由二叉树实现,便于查找。

例4

int main()

{

     //type of the collection

     typedef std::set<int> IntSet;

 

     IntSet coll; //set container for int values

     coll.insert(1);

     coll.insert(6);

     coll.insert(2);

     IntSet::const_iterator pos;

 

     for(pos = coll.begin(); pos!= coll.end(); ++pos) {

         std::cout <<*pos <<' ';

     }

     std::cout <<std::endl;

}

õ  Maps/Multimaps

   头文件 <map>

   map与set的不同在于map中存放的是成对的key/value。

   并根据key对元素进行排序,可快速地根据key来检索元素

   map同multimap的不同在于是否允许多个元素有相同的key值。

ô  Map的元素是成对的键值/实值,内部的元素依据其值自动排序。

ô  Map内的相同数值的元素只能出现一次,Multimaps内可包含多个数值相同的元素。

ô  内部由二叉树实现,便于查找。

例5

#include <iostream>

#include <map>

#include <string>

using namespace std;

int main()

{

     //type of the collection

     typedef multimap<int, string> IntStringMMap;

 

     IntStringMMap coll;  //container for int/string

     //notice make_pair 为便捷函数返回一个pair 对象

     coll.insert(make_pair(6, "string"));

     coll.insert(make_pair(1, "is"));

     coll.insert(make_pair(3, "multimap"));

 

     IntStringMMap::iterator pos;

     for(pos = coll.begin(); pos!=coll.end; ++pos) {

         cout << pos->second << ' ';

     }

 

     cout << endl;

}

容器适配器简介

1) stack  :头文件 <stack>

     栈。是项的有限序列,并满足序列中被删除、检索和修改的项只能是最近插入序列的项。即按照后进先出的原则

2) queue :头文件 <queue>

   队列。插入只可以在尾部进行,删除、检索和修改只允许从头部进行。按照先进先出的原则。

3)priority_queue :头文件 <queue>

     优先级队列。最高优先级元素总是第一个出列

容器的共有成员函数

1) 所有标准库容器共有的成员函数:

ô     相当于按词典顺序比较两个容器大小的运算符:  
 =, < , <= , >  , >=, == , !=

ô     empty : 判断容器中是否有元素

ô     max_size: 容器中最多能装多少元素

ô     size:   容器中元素个数

ô     swap: 交换两个容器的内容

2) 只在第一类容器中的函数:

         begin  返回指向容器中第一个元素的迭代器

         end     返回指向容器中最后一个元素后面的位置的迭代器

         rbegin  返回指向容器中最后一个元素的迭代器

         rend    返回指向容器中第一个元素前面的位置的迭代器

         erase   从容器中删除一个或几个元素

         clear   从容器中删除所有元素

容器的共同能力

õ  所有容器提供的都是value语意,而非reference语意。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被拷贝(必须提供拷贝构造函数)。

õ  每个容器都提供可返回迭代器的函数,运用返回的迭代器就可以访问元素。

õ  通常STL不会丢出异常。要求使用运行者对确保传入正确的参数。

容器的共同操作

õ  初始化--每个容器都提供了一个默认构造函数,一个拷贝构造函数

list<int> l;

...

vector<int> ivec( l.begin(), l.end() ); //以某个容器的元数为初值完成初始化

int array[] = {1, 2, 3, 4};

...

//以某个数组的元素为初值完成初始化

set<int> iset(array, array+sizeof(array)/sizeof(array[0]));

õ  与大小相关的操作函数

c.size()        //返回容器中的元素数量

c.empty()       //判断容器是否为空(相当于size()==0, 但可能更加的快)

c.max_size()    //返回元素的最大可能数量

õ  返回迭代器的函数

c.begin()     //返回一个迭代器,指向第一个元素

c.end()       //返回一个迭代器,指向最后一个元素

c.rbegin()    //返回一个逆向迭代器,指向逆向回访时的第一元素

c.rend()      //返回一个逆向迭代器,指向逆向访问时最后元素的下一个位置

õ  比较操作

c1 == c2      //判断是否c1等于c2

c1 != c2      //判断是否c1不等于c2 相当于!(c1 == c2)

c1 < c2       //判断是否c1小于c2

c1 > c2       //判断是否c1大于c2 相当于!(c1 <= c2)

c1 <= c2      //判断是否c1小于等于c2 相当于!(c2 < c1)

c1 >= c2      //判断是否c1大于等于c2 相当于!(c1 < c2)

c1 = c2       //将c2的所有元素指派给c1

4 迭代器(Iterator)简述

õ  迭代器与容器

ô  通过迭代器,我们可以用相同的方式来访问、遍历容器(即为泛型的抽象)。

迭代器的概念

õ  迭代器是面向对象版本的指针

ô  指针可以指向内存中的一个地址

ô  迭代器可以指向容器中的一个位置

õ  STL的每一个容器类模版中,都定义了一组对应的迭代器类。使用迭代器,算法函数可以访问容器中指定位置的元素,而无需关心元素的具体类型。

ô  迭代器是一个“可遍历STL容器内全部或部分元素”的对象。

ô  一个迭代器指出容器中的一个特定位置。

ô  具有遍历复杂数据结构的能力。

õ  迭代器的基本操作

用法和指针一样,其实指针就是一种迭代器

õ  迭代器的类型

ô  输入迭代器

õ  可以用来从序列中读取数据

ô  输出迭代器

õ  允许向序列中写入数据

ô  前向迭代器

õ  既是输入迭代器又是输出迭代器,并且可以对序列进行单向的遍历

ô  双向迭代器

õ  与前向迭代器相似,但是在两个方向上都可以对数据遍历

ô  随机访问迭代器

õ  也是双向迭代器,但能够在序列中的任意两个位置之间进行跳转。

标准库迭代器类型

说明

输入 InputIterator

从容器中读取元素。输入迭代器只能一次一个元素地向前移动(即从容器开头到容器末尾)。

要重读必须从头开始。

输出 OutputIterator

向容器写入元素 。

输出迭代器只能一次一个元素地向前移动。

输出迭代器要重写,必须从头开始

正向 ForwardIterator

组合输入迭代器和输出迭代器的功能,

并保留在容器中的位置(作为状态信息),

所以重新读写不必从头开始。

双向 BidirectionalIterator

组合正向迭代器功能与逆向移动功能

(即从容器序列末尾到容器序列开头)

随机访问 RandomAccseeIterator

组合双向迭代器的功能,并能直接访问容器中的任意元素,即可向前或向后调任意个元素。

不同迭代器所能进行的操作(功能)

õ  所有迭代器: ++p, p ++

õ  输入迭代器: * p, p = p1, p == p1 , p!= p1

õ  输出迭代器: * p, p = p1

õ  正向迭代器: 上面全部

õ  双向迭代器: 上面全部,--p, p --,

õ  随机访问迭代器: 上面全部,以及:

ô  p+= i, p -= i,

ô  p + i: 返回指向 p 后面的第i个元素的迭代器

ô  p - i: 返回指向 p 前面的第i个元素的迭代器

ô  p[i]: p 后面的第i个元素的引用

ô  p < p1, p <= p1, p > p1, p>= p1

õ  迭代器示例

例6

int main()

{

     list<char> coll;  // list container for character elements

     //append elements from 'a' to 'z'

     for(char c='a'; c<='z'; ++c) {

         coll.push_back(c);

     }

 

     list<char>::const_iterator pos;

     for(pos = coll.begin(); pos != coll.end(); ++pos) {

         cout << *pos <<' ';

     }

     cout << endl;

}

5   算法(algorithm)

õ  STL中提供能在各种容器中通用的算法,比如插入,删除,查找,排序等。大约有70种标准算法。

õ          算法就是一个个函数模板。

õ  算法表现为一系列的函数模板,它们完整定义在STL头文件中。一般这些函数模板都使用迭代器作为它的参数和返回值,因此泛型算法不依赖于具体的容器。

õ  STL中也定义了一些标准的函数对象,如果以功能划分,可以分为算术运算、关系运算、逻辑运算三大类。为了调用这些标准函数对象,需要包含头文件<functional>。

õ  泛型算法分类为

õ  (1)不修改序列的操作

õ     find( )、cout( )、equal( )、mismatch( )和search( )等。

õ  (2)修改序列的操作

õ      swap( )、copy( )、transform( )、replace( )、remove( )、

õ       reverse( )、rotate( )和fill( )等。

õ  (3)排序、合并和相关的操作

õ      sort( )、binary_search( )、merge( )、min( )和max( )等。

每个泛型算法(generic algorithm)的实现都独立于单独的容器类型,它消除了算法的类型依赖性

算法示例:find()

template<class InIt, class T>

InIt find(InIt first, InIt last, const T& val);

õ  first 和 last 这两个参数都是容器的迭代器,它们给出了容器中的查找区间起点和终点。

ô  这个区间是个左闭右开的区间,即区间的起点是位于查找范围之中的,而终点不是

õ  val参数是要查找的元素的值

õ  函数返回值是一个迭代器。如果找到,则该迭代器指向被找到的元素。如果找不到,则该迭代器指向查找区间终点。

例7

#include <vector>

#include <algorithm>

#include <iostream>

 

using namespace std;

void main()

{

     int array[10] = {10, 20, 30, 40};

     vector<int> v;

     v.push_back(1);

     v.push_back(2);

     v.push_back(3);

     v.push_back(4);

 

     vector<int>::iterator p;

     p=find(v.begin(), v.end(), 3);

     if(p != v.end()) {

         cout<< *p <<endl;

     }

 

     p=find(v.begin(), v.end(), 9);

     if(p == v.end()) {

         cout << "not find" << endl;

     }

 

     p=find(v.begin()+1, v.end()-2, 1);

     if(p != v.end()) {

         cout<< *p << endl;

     }

 

     int *pp=find(array, array+4, 20);

     cout << *pp << endl;

}

函数对象

õ  在C++中,为了使程序的安全性更好,采用“引用”来代替指针作为函数的参数或返回值。在C++的泛型算法中类似地采用了“函数对象”(function object)来代替函数指针。函数对象是一个类,它重载了函数调用操作符(operator())。该操作符封装了应该被实现为一个函数的操作。典型情况下,函数对象被作为实参传递给泛型算法。和“引用”一样,“函数对象”独立使用比较少。函数对象亦称拟函数对象(function_like object)和函子(functor)。下面给出一个求和函数对象的定义:

template<typename T>class Sum

{

     T res;

public:

     sum(T i=0):res(i)

     {

    

     }//构造函数,即sum(T i=0){res=i;}

     void operator()(T x)

     {

         res+=x;-

     }//累加

     T result() const

     {

         return res;

     }//

}

õ  对象与函数指针相比较有三个优点:第一,函数指针是间接引用,不能作为内联函数,而函数对象可以,这样速度更快;第二,函数对象可以拥有任意数量的额外数据,用这些数据可以缓冲当前数据和结果,当然多数情况下不一定使用,上例中res就是一个额外数据;第三,编译时对函数对象做类型检查。

泛型算法

õ  在C++标准库中给出了70余种算法,泛型算法函数名都加有后缀,这些后缀的意思如下:

õ  _if   表示函数采用的操作是在元素上,而不是对元素的值本身进行操作。如find_if算法表示查找一些值满足函数指定条件的元素,而find查找特定的值。

õ  _copy         表示算法不仅操作元素的值,而且还把修改的值复制到一个目标范围中。reverser算法颠倒范围中元素的排列顺序,而reverse_copy算法同时把结果复制到目标范围中。

õ  其它的后缀从英文意思上立即可以认出其意义

  其次我们介绍泛型算法的构造与使用方法。所有泛型算法的前两个实参是一对iterator,通常称为first和last,它们标出要操作的容器或内置数组中的元素范围。元素的范围,包括first,但不包含last的左闭合区间。即:

[first,last)

当first==last成立,则范围为空。

对iterator的类则要求在每个算法声明中指出(5个基本类别),所声明的是最低要求。

泛型算法分以下几类:

  1. 查找算法:有13种查找算法用各种策略去判断容器中是否存在一个指定值。equal_range()、lower_bound()和upper_bound()提供对半查找形式。
  2. 排序和通用整序算法:共有14种排序(sorting)和通用整序(ordering)算法,为容器中元素的排序提供各种处理方法。所谓整序,是按一定规律分类,如分割(partition)算法把容器分为两组,一组由满足某条件的元素组成,另一组由不满足某条件的元素组成。
  3. 删除和代替算法:有15种删除和代替算法。
  4. 排列组合算法:有2种算法。排列组合是指全排列。如:三个字符{a,b,c}组成的序列有6种可能的全排列:abc,acb,bac,bca,cab,cba;并且六种全排列按以上顺序排列,认为abc最小,cba最大,因为abc是全顺序(从小到大)而cba是全逆序(从大到小)。
  5. 生成和改变算法:有6种,包含生成(generate),填充(fill)等等。
  6. 关系算法:有7种关系算法,为比较两个容器提供了各种策略,包括相等(equal()),最大(max()),最小(min())等等。 
  7. 集合算法:4种集合(set)算法提供了对任何容器类型的通用集合操作。包括并(union),交(intersection),差(difference)和对称差(symmetric difference)。

小结

Ø   模板是C++类型参数化的多态工具。

        C++提供函数模板和类模板

Ø   模板定义以模板说明开始。

        类属参数必须在模板定义中至少出现一次

Ø   同一个类属参数可以用于多个模板

Ø   类属参数可用于函数的参数类型、返回类型和声明函数中的变量

Ø   模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数模板称     为模板函数;实例化的类模板称为模板类

Ø   函数模板可以用多种方式重载

Ø   类模板可以在类层次中使用

猜你喜欢

转载自blog.csdn.net/besidemyself/article/details/7296311