第16章part3-STL

来源:互联网 发布:linux编写shell程序 编辑:程序博客网 时间:2024/05/24 22:45

STL:标准模板库

STL提供了一组表示容器、迭代器、函数对象和算法的模板。

容器,类似数组,可以存储相同类型的值(这里的值是泛称,可能是基本类型,也可能是对象,等等)。

算法,是操作容器中数据的框架(比如排序、查找等)。

迭代器,能够用来遍历容器,有些广义指针的味道。

函数对象,是类似于函数的对象。它就是操作框架中具体的操作,比如排序到底是从大到小还是从小到大这种。

由于STL是很庞大的一块内容,在C++primerplus里,只是略提了一下,用于引出。

vector模板类
创建:

vector<int> rating1;//a vector of intsvector<string> scores(5);//a vector of 5 doubles

操作:
size()——返回容器中元素数目
swap()——交换两个容器内容
begin()——返回首元素迭代器
end()——返回超尾迭代器
push_back()——将元素添加到末尾
erase()——删除给定区间中的元素。接受两个指向要删除区间的迭代器参数。
insert()——插入元素,三个参数分别指定被插入位置,后两个表示了需取出用于插入的区间。

迭代器为广义化的指针,像指针一样的使用和操作对象。
迭代器的类型为:iterator

//声明一个迭代器vector<int>::iterator pd;

一般也很少这样去声明一个迭代器,常用是这样:

vector<int>::iterator pd = rating.begin();

这样就除服多了,不过有没有哪里有印象(忘了g2o的那一堆堆堆堆堆的初始化了?。。。)?
不过有了auto之后,这事就又能简化了:

auto pd = rating.begin();

美滋滋~~

容器类就上面几种操作么?显然不能,一定有居多的操作。但是都能通过类方法调用么?显然不能。STL尽量将操作函数单独定义,然后在类中就不定义方法函数了,因为这样就能省去每个容器类都要定义一遍的工作量。
介绍三个比较典型的STL函数:for_earch()、random_shuffle()、sort()

for_earch()接受3个参数。前两个定义要作用容器的元素区间,最后一个为作用方法,函数指针,也就是后面的函数对象,指明如何操作这些元素。
可用于代替循环,并避免显式使用迭代器变量:

for_earch(books.begin(), books.end(), showname);

random_shuffle()函数用迭代器指定区间,并随机排列该区间中的元素。

random_shuffle(books.begin(), books.end());

sort()用于区间排序。有默认从小到大的排序,如果单独传入自定义的函数,则按照函数规则排序:

//按照默认的从小到大的排序sort(books.begin(), books.end());//自定义的比较函数:bool WorseThan(const Review& r1, const Review& r2){    if(r1.rating < r2.rating)        return true;    else        return false;}//调用自定义函数用于排序:sort(books.begin(), books.end(), WorseThan);

基于范围for循环就是一个冒号”:”

16.4泛型编程
STL是一种泛型编程。面向对象编程关注的是数据,而泛型编程关注的是算法。泛型编程旨在编写独立于数据类型的代码。

模板使得算法独立于存储的数据类型,而迭代器使算法独立于存储数据时使用的容器类型。
稍微有点绕,举个简单栗子就明白:

double* find_ar(double* ar, int n, const double& val){    for(int i = 0, i < n, i++)        if(ar[r] == val)            return &ar[i];    return nullptr;}

很明显是一个数组中查找功能,这里是查找的double。
如果我们用上模板,那就不限于double了,做到了算法独立于数据类型。
独立于数据类型后,发现find()方法还依赖于数组这种数据结构,如果想做到完全独立的find()方法,就需要剥离数组这种数据结构(也就是数据容器类型),此时就需要迭代器了。

模板提供了存储在容器中数据类型的通用表示。
迭代器提供了遍历容器中的元素的通用表示。

迭代器类型:
1、输入迭代器
2、输出迭代器
3、正向迭代器
4、双向迭代器
5、随机访问迭代器

输入迭代器:
输入指从程序角度来说,C++中的入和出都是以程序的角度来定义,只要是数据进了程序,就叫输入。从程序出去,就叫输出。
输入迭代器用来读取容器中的信息,但不让修改原容器的信息,也就限制了使用输入迭代器的算法不能修改容器的信息,算法只能读取信息,常见的就是输出显示。
基于输入迭代器的算法都应该是单通行的,不依赖前一次遍历时迭代器的值,不依赖本次遍历时前面迭代器的值。本质原因是输入迭代器在不同次遍历时,值可能不同,也就是同样是[10],但是不同次遍历可能是不同的元素。另外在迭代器被递增时,前一个位置也是不确定的,不能使用。最后就是单向的,只能前进,不能倒退。

输出迭代器:
顾名思义,与输入迭代器相反的过程,是数据从程序中出来,输出到容器中使用的迭代器。所以,它只能用于往容器中写入的算法,而不能用于读取(类似于cout到显示屏,只能输出,不能读取显示屏显示的是什么)。
同样也是单通行、只前进的。

简而言之:单通行、只读算法,用输入迭代器;单通行、只写算法用输出迭代器。

正向迭代器:
跟输入输出相同的点是都是只能正向,不能倒退。
功能强大的点是,它总按照相同顺序遍历,且递增后可对前面的位置访问,并且同时可读可写。

双向迭代器:
双向迭代器具有上方迭代器的所有性质,强大的地方是可以双向跑,既可以增,也可以减。

随机访问迭代器:
同样具有以上所有特性,增加了随机访问。可以随便访问到任何一个元素。vector[20]就有这种特性。

可以发现,迭代器的功能是有层次的,功能越来越强大。那么为什么不只用最强大的随机访问迭代器呢?因为算法有很多种,每种算法在设计时,对迭代器的要求是够用就行,没必要限制死只能用最高等级的随机访问,这样的好处是,这个算法能够适配更多的迭代器,不会挑剔。放一张图:
这里写图片描述

预定义迭代器类型:
ostream_iterator、istream_itrator、reverse_itrator、back_insert_itrator、front_insert_itrator、insert_itrator等。

ostream_iterator可用于将输出流用于STL:
举栗子前先介绍一下copy()的用法,copy()方法将容器中的数据copy进另一个容器中去:

int cast[10] = {0,1,2,3,4,5,6,7,8,9};vector<int> dice;copy(cast, cast+10, dice.begin());

一开始看书上这里内容的时候,一眼撇过,以为是这样理解的:
参数也很直观,源容器的开始和结束位置,目标容器的位置,发现三个参数都是迭代器类型。

后来敲的时候越来越感觉不对劲,先一点点说:
上面的代码跑起来是没问题的。copy()三个参数前两个没啥问题,为两个迭代器,表征要复制的范围。第三个参数其实是:
要将第一个元素赋值到什么地方的一个迭代器因为整个复制的数据是一串,而迭代器只是个指针,指向第一个元素来代表这一串数据了,也就是说后面接的数据,自动copy过来然后往后面码了。

另外,书上的代码其实是这样的:

int cast[10] = {0,1,2,3,4,5,6,7,8,9};vector<int> dice[10];copy(cast, cast+10, dice.begin());

一开始想当然的以为vector<int> dice[10];这句是创建一个能存10个int元素的vector容器,命名为dice**错误错误错误!!!**
而是,dice是个数组,有10个元素,每个元素都是一个装int的vector容器!!!!
也就是说书上的这段代码写完,只是将cast放在了dice数组第一个元素的位置上,其他位置上还是空的。(有点类似于二维数组的感觉)。

看书还是要动脑子啊!!!切不可囫囵吞枣!!!

那我要是将数据从容器copy进标准输出,用于显示行不行呢?可以,将目标容器位置参数改为标准输出迭代器就行了:

copy(dice.begin(), dice.end(), ostream_iterator <int, char> (cout, " "));

其中ostream_iterator <int, char> (cout, " ")直接是相当于现场定义了一个无名的输出迭代器。int是发送个输出流的类型、char是输出流使用的类型、cout表示要使用的输出流、最后一个” “是指发送给输出流的每个数据后显示的分隔符。

其他的顾名思义就是输入、翻转、尾部插入、头部插入、随机插入:
istream_itrator、reverse_itrator、back_insert_itrator、front_insert_itrator、insert_itrator
就不细说功能了,用时候看看示例就明白了,都是基本操作。

STL具有容器概念和容器种类。
容器概念是具有名称(如容器、序列容器、关联容器)的通用类别;
容器类型是可用于创建具体容器对象的模板。比如序列容器中就有list、vector等类型。

说一下序列容器,序列容器中有不同的容器类型。打个比方类比一下,可能不大确切,但是能直观的表示,类型到底指的是啥。
队列queue:单向羽毛球桶,只能一头进,另一头出。
双端队列deque:乒乓球筒,两头没啥区别,都可进可出。
列表list:一条麻将,随便你操作哪个,无论出牌(删除操作),摸牌(插入操作)。还能把整副牌倒过来,也就是list为翻转容器,对顺序没啥要求,能胡就行,谁管你怎么排。。。
等等还有一些就不再打比方了,反正就是这个意思。。。

介绍几种常用的序列容器:

vector,尾部增删时间固定、头部中部增删时间线性。可反转(用rbegin(),rend())。支持随机访问。若大多数操作只在尾部进行,用vector。另外vector是最简单的序列类型,只要能满足需求,尽量使用

deque,尾部和头部增删时间都是固定的,中部还是线性。支持随机访问。可反转。若大多数操作只在首位进行,用deque。

list,双向链表。任意位置增删都是固定时间。但不支持随机访问。不可反转。
这里写图片描述

queue,不允许随机访问。不允许遍历。队首删除,队尾添加。查看队尾队首的值、检查元素数目、检查队列是否为空。
这里写图片描述

stack,不允许随机访问,不允许遍历。栈顶压入栈顶弹出、查看栈顶值、检查元素数目、查看是否为空。
这里写图片描述

关联容器,将值与键关联在一起,并使用键来查找值。以提供快速访问。
STL中有四种关联容器:set、 multiset、map、 multimap。
set在头文件set中定义,map在头文件map中定义。
set,值类型与键相同,键唯一。简单说,值就是键。
multiset,值类型与键相同,键不唯一。简单说,一个键可以与多个值关联。
map,值与键的类型不同,键是唯一的 。每个键只对应一个值。
multimap,值与键的类型不同,键是不唯一的。一个键可以与多个值关联。
简单用法示例:

    string s1[] = {"hehe", "haha", "cnm"};    string s2[] = {"hehe", "haha", "nmb"};    set<string> A(s1, s1+3);    for (auto p : A)    {        cout<<p<<"  ";    }    cout<<"\n";    set<string> B(s2, s2+3);    for (auto q : B)    {        cout<<q<<"  ";    }
cnm  haha  hehe  haha  hehe  nmb 

。。。后续东西实在是太多了,整个STL也是一大块内容,不想在这里写了,感觉写出来也是附录的表格一样的东西,单开一个STL再写,以后碰到相应坑,写在其中。

原创粉丝点击