码迷,mamicode.com
首页 > 其他好文 > 详细

stl容器学习——queue,stack,list与string

时间:2019-04-21 23:03:37      阅读:263      评论:0      收藏:0      [点我收藏+]

标签:英语   另一个   复杂度   写代码   排序   第一个   结构体类型   生成   uniq   

点击上面的内容可以实现跳转哦

简介:本文记录了对string list queue stack四个容器的学习总结,包含有四种容器常用的函数介绍和一些使用过程中碰到的细节总结,在list容器中介绍了迭代器的使用。

头文件

想要用哪一种容器,就要加上对应的头文件

比如想要使用queue , 就要加上 #include<queue>

以此类推,想要使用STL库中的容器,只要加上它们的头文件就好。


string

目录部分

1.string的定义及初始化

2.string的运算符和比较符使用

3.常用的一系列函数

1.string的定义及初始化

string str string可以看成一种定义的字符串类型,定义的方法和基本类型(int等)是一样的。

string str = "hello world"; string能够在定义的时候直接用"="进行赋值,这样str代表的就是"hello world"这一个字符串。

str = "hello world"; 当然在定义结束后再赋值也是可以的。

在这里要介绍一下assign这个函数,它是个用来对string赋值的函数。

assign的赋值是覆盖性的,会覆盖掉原来字符串的内容,相当于重置了一遍后再写入新的内容。

我们先定义两个字符串string str1,str2;

再定义一个字符串并初始化为"hello world": string str="hello world";

现在我们开始来介绍assign函数的几个常见用法

① 用一个字符串给另一个字符串赋值

str1.assign(str); 这里是将str的值赋给了str1,而str的值为"hello world",所以此时的str1也是"hello world";

那么如果我们给这个函数多加入两个参数呢?

str1.assign(str,int index,int size_count);

我们在函数后面多加了两个参数index 和 size_count

其中index代表着str这个字符串的下标,size_count代表着要被赋值的长度。

我们不妨令index=1,size_count=5,那么执行完str1.assign(str,1,5);

我们会得到这样的结果:str1="ello " (末尾有一个空格)

不难发现,这次的assign函数是从str字符串的下标1处开始,将往后的5个长度赋值给了str1,所以实际上str1就是str从 index下标 处开始,长度为 size_count 的一个字串。

那么如果只加入index参数,而不加入size_count这一个参数呢?

不妨再来执行下这个代码str1.assign(str,2);

此时的index=2,而str1="llo world";

很容易发现,如果不输入参数size_count,那么assign函数执行的就是将str字符串从index下标处开始的所有内容都赋给str1。

②用字符串常量对字符串进行赋值

用字符串常量,像"hello world"这种的给string赋值时:

如果输入的只有一个参数(字符串常量本身):str1.assign("hello world");

或者输入的是三个参数:str1.assign("hello world",1,8);

以上的两种赋值,跟①的结果是一样的。

但是!!!

如果输入的是两个参数,那么它的运行结果就跟①出现了不同。

举个栗子:

str1.assign("hello world",3);

最后str1="hel"而不会是"lo world"。

当只有两个参数的时候,输入的第二个参数实际上是size_count而不是index。

所以输入两个参数时,实际上默认index=0,也就是从字符串常量"hello world"的开头开始,然后截取前size_count个元素赋值给str1.

而在①的情况中,如果输入两个参数,默认的是从index开始(index为输入值),默认size_count是从index开始剩下的其他元素的个数。

③ 用n个相同的字符对字符串赋值

str1.assign(int n,char ch); 这个用法比较简单,我举个栗子大家就能明白了。

str1.assign(10,‘a‘); 执行完这个语句后,str1="aaaaaaaaaa" (10个a)

2.string的运算符及比较符

string支持 = , == , += , != , + 这几种运算符的操作

= 就是赋值作用,这一点在前面的初始化部分已经说明过了,相当于c语言中的strcpy函数。

== 和 != 就是用来比较两个字符串是否相等,作用相当于c语言中的strcmp函数。

+= 和 + 则可以用来为string赋值,这种赋值是添加性的,不会覆盖掉字符串原先的值。

string str="hello world";
string str1="qaq";
str1+=str;//执行完毕后,此时的str1="qaqhello world"
str1+='w';//执行完毕后,此时的str1="qaqhello worldw"
str1+="orz";//执行完毕后,此时的str1="qaqhello worldworz";

从上面的例子可以看出,+=这个符号不仅可以用在string类型,字符串常量和字符型也是同样适用的。

3.string的一些常用函数

① size()和length()

size函数返回的是字符串的大小,length返回的是字符串的长度,但是实际上它们的返回值是一样的,包括在cpp源码中的定义也是一样的,所以实际上没啥区别,喜欢用哪个就用哪个。

string str="hello"; 此时str.size()和str.length()的值都是5(字符串的长度).

② at()

at()函数的参数是at(int index),index是字符串的下标

在c语言中,我们要想遍历输出一个字符串数组,可以用到这样的代码:

for(i=0;i<len;i++)//len为字符数组str的长度
{
    print("%c",str[i]);
}

那么在使用c++的string时,我们可以使用这样的代码来替换:

for(i=0;i<str.size();i++)
{
    cout<<str.at(i);
}

相比较一下,我们马上就能发现,str.at(i)实际上和str[i]是一样的,只是表示的方法不同。

那么at()这个函数有什么好处呢?

at()函数是十分安全的,它会帮你检查下标是否越界,如果越界程序将无法运行,避免因为本次的越界情况导致更严重的错误,而如果用str[i]的表示方法,则没有办法保证安全性。

建议自己输出一下str.at(-1)这种肯定会越界的值,看看编译器会输出什么

③ find()

string除了find()以外,还有find_first_of这一类的函数,在此不做介绍

find() 一般用来查找子串,当然也可以用来查找单个字符是否在字符串中出现过。

如果在主串中找到了要查找的字串或者单个字符,那么会返回字串在主串中第一次出现的位置或是查找到的单个字符的位置。

如果没有找到,返回的值是 string::npos ,返回的值是 string::npos ,返回的值是 string::npos

重要的话要说三遍,这个返回值要怎么使用呢。

npos这个常量是定义为-1,但是npos的类型实际上是无符号数,所以npos实际上是这个类型正数的最大值。

说了这么多,来直接看一下例子吧:

string str="hello world";
string sub1="ell";
string sub2="wor";
string sub3="heo";
cout<<str.find(sub1)<<' '<<str.find(sub2)<<' '<<str.find(sub3);
/*
输出结果是1 6 4294967295
这里可以很明显发现,因为sub1和sub2都是可以找到的,返回的就是找到的第一个字符的下标
而sub3是找不到的,这个时候find函数的返回值是4294967295,也就是2^32-1(unsigned int 的最大值)
*/

从上面的代码中,可以很明显看出find的返回值是多少,那么如果我们用int或者long型的变量来接收find函数的这个返回值可不可行呢?

int i1,i2;
long i3;
i1=str.find(sub1);
i2=str.find(sub2);
i3=str.find(sub3);
cout<<i1<<' '<<i2<<' '<<i3;//输出的结果是 1 6 -1

我们都知道数据是依照补码的形式储存的,而2^32-1这个值储存到int和long类型的时候,代表的值都是-1,所以这个时候输出的值自然就是-1了。

看完上面两个例子,应该就知道要怎么使用find函数的返回值了,我们再举一个例子说明吧:

题目当你在str主串中寻找一个sub字串时,如果找到字串,输出yes,没有找到则输出no

string str="hello world";
string sub="hlo";//这里举一个找不到的例子
//写法1
if(str.find(sub) == string::npos)
{
    cout<<"yes"<<endl;
}
else
{
    cout<<"no"<<endl;
}
//写法2
int find;
find=str.find(sub);
if(find != -1)
{
    cout<<"yes"<<endl;
}
else
{
    cout<<"no"<<endl;
}
//写法三(错误写法!!)
if(str.find(sub) >= 0)
{
    cout<<"yes"<<endl;
}
else
{
    cout<<"no"<<endl;
}

在以上的三个写法中,前两个写法都是前面我们所提到的正确方法,第一个是直接与string::npos作比较,第二个则是用int类型或者long类型接收这个返回值,然后和-1作比较,这两种都是可行的。

那么我们来看看第三种写法:为什么if (str.find(sub) >= 0)这个判断语句是错误的呢

答案很简单,还记得我们上面提过,find函数如果没有找到,返回的值实际上是2^32-1,所以不管有没有找到字串sub,输出的都是"yes"。

那如果写成if(str.find(sub) == -1)这个判断语句呢?

答案其实是可行的,无符号数是没有负数的,所以碰到-1这个数的时候,实际上是和-1的补码作比较,而前面也提到过了,它的补码实际上和-1的就是相同的,所以这个判断是可以的。

总结一下:千万不能把判断语句写成if (str.find(sub) >= 0),如果是这么写,那么if条件实际上是永远成立的。

好了,上面花了很多的篇幅来详细解释了一下find函数的返回值问题,从我的个人角度来看,find函数的返回值还是挺有意思的,而且如果不清楚的话,很有可能在这里犯下难以被察觉的错误。

那么接下来来介绍一下find函数的一些常用的参数。

我们以string str="hello world",sub;这样的两个字符串为例,其中str是被初始化定义过的。

第一个用法就是上文一直在使用的str.find(sub),传入的参数是一个字符串,如果找到,那么返回的就是第一个下标的位置,如果没找到,返回值就是string::npos ,也就是-1。

当然除了传入字符串,传入单个的字符也是可以的:str.find(‘l‘)如果找到,返回的就是第一个l的下标值,在这里面就是2,如果没有找到返回的也是string::npos

那如果我在后面加上第二个参数呢?

str.find(sub,int index)那么这个函数要表达的意思就是:从下标index开始,搜索sub这个字串,如果搜索到该字串,返回第一个字符的下标值。

举一个例子:string str="hello world",sub="hel";

在这个时候,如果我们使用的是str.find(sub),那么我们得到的返回值就是0

而如果我们使用的是str.find(sub,3),那么我们得到的返回值就是string::npos(从下标3开始寻找,没有办法查找到sub字串)。

?实际上,如果只输入第一个参数,第二个参数是默认为0的,也就是代表着从头开始查找

find函数是有第三个参数的,但是第三个参数的使用比较少见,这里暂时不作介绍

④ rfind()

rfind()函数实际上和find()函数是十分相似的,差别只在于它们的查找顺序不一样。

??find()函数是从index位置开始向后查找,直到串结束(只输入一个参数时,index默认为0)

?而rfind()函数是从index位置开始向前查找,直到串首(只输入一个参数时,index默认为串尾的下标)

穿插个关于英语的内容:rfind中的r是英文reverse,意思为颠倒,使...相反,相反的 ...

⑤ append()

append()函数用来向串的末尾添加内容

让我们直接看看第一种用法:

string str="hello world";
string sub="qaq";
sub.append(str);
cout<<sub;//此时输出的是"qaqhello world"
sub.append("qwq");
cout<<sub;//此时输出的是"qaqhello worldqwq"

在第一种用法里面,append函数的作用和+=实际上是一样的。

sub+=str; sub+="qwq"; 这两句代码的作用和上面两句append函数的作用实际上是一模一样的。

显然,append()函数的作用不止于此。

让我们看看第二种用法吧。

string str = "hello world";
string sub = "qaq";
sub.append(str, 3);
cout << sub;//此时输出的是"qaqlo world"

从例子中不难看到,如果传入两个参数,那么实际上的作用是:将str函数从下标为3的字符开始到整个字符串结束的字串添加到sub字符串的尾部。

str从下标3开始的字串为:"lo world",添加到sub后,sub就成了"qaqlo world".

再看看另一种情况:

string sub="qaq";
sub.append("hello world",3);
cout<<sub;//此时输出的是"qaqhel"

当输入的第一个参数是字符串常量时,这个函数的作用就变成了:将字符串常量开始的前n个元素添加到sub串的尾部,这一点和传入的第一个参数为字符串时时正好相反的。

要记住传入的第一个参数会影响到第二个参数的作用,字符串和字符串常量的经常是不一样的,这一点在前面的assign函数中也体现过

那么如果是三个参数呢?

string str = "hello world";
string sub1 = "qaq",sub2="qaq";
sub1.append(str, 2, 5);
sub2.append("hello world", 2, 5);
cout << sub1<<endl<<sub2;

虽然上文才刚刚提到过,传入的第一个参数是字符串还是字符串常量会对函数的作用产生影响,但是如果传入的是三个参数,那么它们的作用是一样的.

上面的代码中sub1和sub2输出的结果都是"qaqllo w",所以向sub1和sub2中添加的实际上是"llo w"这个字串,而这个字串正是从下标为2处开始,长度为5的一个字串。

再来最后介绍一种用法

string sub="qaq";
sub.append(10,'w');
cout<<sub;//此时输出的结果为"qaqwwwwwwwwww",10个w

如果传入的是两个参数,也可以选择传入这样的参数,第一个参数代表个数n,第二个参数代表一个字符ch,它的作用就是在串的末尾添加n个字符ch。

如果你对上面的内容还有印象的话,你会发现assign函数也有一种用法跟这个是一样的,区别就在于assign函数是重置性的赋值,而append是添加性的赋值,不会清空掉原来的字符串内容,如果已经对上面的内容没有印象,不如回头瞅一眼叭。

append的用法当然不止这么几种,我在这里都只列举目前个人觉得较为常用到的函数,其他的函数暂时不作介绍

那么,string的介绍暂时告一段落啦w


stack

目录部分

1.stack的定义

2.常用函数介绍

1.stack的定义

要使用一个栈,首先就要定义一个想要使用的栈。

stack<ElementType> S;其中ElementType代表着数据类型.

struct exam
{
    int score;
};
stack<int> sint;//定义一个储存int类型的栈,名叫sint
stack<double> sdouble;//定义一个储存double类型的栈,名叫sdouble
stack<struct exam> sexam;//定义一个储存结构体exam的栈,名叫sexam
stack<string> sstring;//定义一个储存string类型的栈,名叫sstring

实际上,stack定义的时候也可以传入第二个参数,当然第二个参数并不是必须的,它代表着stack要使用什么样的容器,其中默认使用的是deque容器(双端队列 double-end queue),也可以改成使用vector容器(向量)来进行存储,例:stack<int,vector<int>> sint,这样这个stack就从默认的deque容器存储改为了使用vector容器存储,至于vector和deque这两种容器,这里不做更多介绍。

2.常用函数

① size()

stack<int> s;
cout<<s.size();//输出结果为0,因为是个空栈

size函数返回的是栈s内部元素的个数,空栈的时候就是0.

注意 size()函数的返回值也是无符号数!这个跟string里面提到的find函数的返回值是一个类型的,所以不要把size函数的值直接与负数作比较。

例:

stack<int> s;
s.push(5);
if(s.size()<-1)
{
    cout<<"yes";
}
else
{
    cout<<"no";
}

在上述代码中,实际上会输出的是yes,这一点的解释在上文string中的find函数中详细解释过。

② empty()

empty的返回值是bool类型,如果栈为空,返回true,否则返回false

if(s.empty())
{
    cout<<"stack is empty";
}
else
{
    cout<<"stack is not empty";
}

③ pop() 与 top()

函数 返回值 作用
pop() void 删除栈顶元素
top() 栈顶元素的值(引用) 返回栈顶元素的值的引用,不删除栈顶元素

从上表可以看出,pop()函数的返回值是void,它的作用仅仅只是删除栈顶元素,而如果想要获得栈顶元素的值,那么我们就需要使用top()函数。

举个栗子:

对于一个拥有{1,2,3,4,5}元素的栈s,其中元素5为栈顶元素,现在输出栈顶元素后再让栈顶元素出栈

cout<<s.top();//输出5
s.pop();//pop函数将5出栈
cout<<s.top()<<endl;//此时再一次输出栈顶元素,输出的值为4
cout<<s.size();//此时栈的大小也为4(原先为5,出栈后剩4个)

当然也可以定义一个相对应的数据类型来接收这个top函数的返回值

ElementType X=s.top(),其中ElementType取决于s栈的数据类型。

从上表中,我们还可以发现,top

[TOC]

()函数返回的实际上是栈顶元素值的引用,我们知道一个引用的值的话,它们的地址是一样的,所以可以通过直接修改top()函数来改变栈顶元素的值

stack<int> s;
s.push(5);//s只有一个元素5,此时栈顶元素自然也是5
cout<<s.top()<<endl;//输出5
s.top()++;//进行++操作,s.top()就从5变成了6
cout<<s.top()<<endl;//这个时候,输出的值就是6了

注意 :pop函数和top()函数都 不可以 在空栈的时候使用,会造成段错误。

不仅仅是stack的pop和top, queue的pop,front,back这类函数,在空栈的时候使用也会造成段错误.

记住这一点:在容器为空的时候,任何需要对容器内部元素进行操作的函数都会导致段错误

(pta的提交中如果出现段错误,可以好好想一下是不是这个原因)

④ push()

push()是用来将元素推入栈的元素 S.push(ElementType X)

其中ElementType X代表和栈S相对应的数据类型

例:我们有一个空栈s,储存的是int类型,这时候将数字5入栈,看看此时栈s的状态

stack<int> s;
s.push(5);
cout<<s.size()<<endl;//输出的是1,代表此时栈s的大小为1
cout<<s.top()<<endl;//输出的是5,代表此时栈s的栈顶元素为5

悄悄话:对于入栈的函数,c++11开始新增了一个emplace函数,和push相比之下,emplace函数的效率更高,有兴趣的话可以自己了解一下,这里不做更多介绍。


queue

目录部分

1.queue的定义

2.常用函数介绍

1.queue的定义

queue<ElementType> Q,其中ElementType代表数据想用队列储存的数据类型,Q是这个队列的名字。

例:queue<int> qint,这个语句定义了一个储存int类型的队列,名叫qint

queue队列的默认存储容器依旧是deque,如果想用vector存储,只要加上第二个参数就好了(在stack解释过,这里不再重复描述了)。

2.常用函数介绍

函数 返回值 作用
size() 一个无符号数 返回queue的大小
pop() void 删除queue的队首元素
empty() bool型(true or false) 判断queue是否为空
push() void 将元素入队

以上的四个函数跟stack里的是一样的,区别就在于一个是对stack操作,一个是对queue操作。由于在stack里面已经较为详细的解释过了,所以在这里仅列表展示。

front() 与 back()

与stack不同,stack只能访问栈顶元素的值,而queue可以访问队首(front)和队尾(back)两个元素的值。

其中front()返回的便是队首元素的值,back()返回的便是队尾元素的值

举个栗子:

对于一个拥有{1,2,3,4,5}的队列q,其中1是队首,5是队尾

cout<<q.front()<<' '<<q.back()<<endl;//输出的是1 5
q.pop();//队列的pop函数是使队首元素出队,这里的队首元素是1
cout<<q.front()<<' '<<q.back()<<endl;//输出的是2 5,原先的队首元素1出队后,2变成了队首元素
cout<<q.size()<<endl;//此时队列的大小是4

front()和back()返回的值也是队首(队尾)元素的值的引用,所以实际上也可以通过它们来更改队首队尾元素的值,这里跟top()函数的用法是一样的。


list

目录部分

1.list的定义和初始化

2.常用简单函数的介绍

3.迭代器的简单介绍和使用以及和迭代器相关的函数

1.list的定义和初始化

list<ElementType> L 其中ElementType是要存储的数据类型,L是这个链表的变量名。

在最基本的定义上,可以看到list和queue和stack都是一样的,这样的定义生成是一个空链表。

如果想给list初始化一些值,可以在定义的时候新添几个参数。

① 输入两个参数的情况

list<int> L(int size_count,int value) 这里我们用存储int型的链表L举例

执行完这个语句后,会生成的是一个拥有size_count个元素,它们的值都是value的链表L。

例:list<int> L(9,3),这个时候L链表有9个元素,这9个元素的值都是3.

在这个情况下,如果省略掉第二个参数value,只输入第一个参数size_count,那么实际上第二个参数的值会默认为0。

例:list<int> L(9)实际上就是list<int> L(9,0)

生成的是一个有九个元素的链表L,这九个元素的值都是0

输入的两个参数也有可能是两个迭代器

(不明白什么是迭代器可以先跳过这里,后面有迭代器的简单介绍)

我们这里定义两个list容器的迭代器 begin 和 end

list<int> L(begin,end)

那么这里生成的L链表的值,是begin迭代器到end迭代器所包含的所有值。

这里begin到end的返回是一个左开右闭的区间 [begin,end)

赋值的过程中,end迭代器所指向的值是不会被赋值到L链表中的

举个例子:

这里有一个{1,2,3,4,5,6,7,8,9}的链表L,我们让迭代器begin指向2,迭代器end指向9。

那么执行完list<int> L1(begin,end)后,新定义的链表L1的内部元素会是什么样的呢?

那让我们来输出一下:

list<int>::iterator it;
for(it=L1.begin();it!=L1.end();it++)
{
    cout<<*it<<' ';//输出的结果是2 3 4 5 6 7 8
}

从输出的结果上很容易就能发现end迭代器指向的元素9并没有被赋值到L1链表中。

②输入一个为链表的参数

这里假设我们有一个元素为{1,2,3,4,5}的链表L;

list<int> L1(L) 在执行完这个操作后,L1的元素就也变成了{1,2,3,4,5}

所以这个操作实际上就是将L链表的值复制给L1

2.常用简单函数的介绍

函数 返回值 作用
pop_back() void 删除链表尾元素
pop_front() void 删除链表头元素
push_back() void 向链表头添加元素,这个元素成为链表头元素
push_front() void 向链表尾添加元素,这个元素成为链表尾元素
size() 一个无符号数 返回链表的大小
empty() bool型(true of false) 判断链表是否为空
front() 头元素的引用 返回链表头元素的引用
back() 尾元素的引用 返回链表尾元素的引用

因为stl中的list容器是一个双向链表,同样可以对头尾元素进行操作。

所以pop和push都有一个对头元素和一个对尾元素处理的函数。

而front()和back()两个函数和queue中的front()和back()函数用法和功能也都是一样的。

①sort()

sort的英文意思便是排序,那么sort()函数很显然就是用来排序的函数。

对于一个任意的链表来说,只要执行这样一条语句L.sort()那么链表就会自动以升序进行排序。

比如元素为{1,9,8,2,3,5,7,6,4},排序后得到{1,2,3,4,5,6,7,8,9}

那如果我们想要降序进行排列呢?

这里有两种方法: 第一种是使用系统自带的降序排列的函数greater()

? 第二种就是我们自己写一个比较函数来进行排列

第一种方法比较简单,由于系统本身就定义了一个降序排列的函数,所以我们只需要使用它即可

L.sort(greater<int>()) 在sort里面输入这个函数,sort函数就从默认的升序排列转化为降序。

第二种方法其实也很简单,而且在很多地方我们都需要使用到这种方法。

我们需要定义一个bool类型的比较函数,然后将这个比较函数传入到sort里面作为它的参数,当这个比较函数都返回true的时候,sort函数排序完毕

bool cmp(int x,int y)//这里定义了一个叫cmp的比较函数,返回值为bool型
{
    return x>y;//这里的x是传入的第一个元素,y是传入的第二个元素,如果x>y返回true
}

来看看这个函数返回值为true时的条件:x>y 那么说明传入的第一个参数x要大于第二个参数y,这里就可以把x,y理解成链表中的两个元素,其中x元素在前,y元素在后,那么只有当链表的前一个元素都大于后一个元素(即链表已经完成降序排列),此时sort函数结束,排序完毕。

那么写完这个函数,只需要将这个函数作为参数传入sort函数L.sort(cmp),这样同样可以让sort函数进行降序排序。

实际上,如果只是单纯的int类型降序排序,完全可以直接调用系统的greater()而不用自己再写一个函数,但是如果是对一个结构体根据条件进行排序,那么greater就无法满足我们的需要了,而自己写一个函数可以根据我们的需求来实现我们的目的

② remove()

remove(ElementType X) 这个函数只需要传入一个参数ElmentType X

函数执行过后,会删除链表中所有和ElementType相同的元素。

例: 我们有一个元素为{1,2,3,3,3,4,5,5,6}的链表L

L.remove(3) 执行完后,链表L的元素为{1,2,4,5,5,6}

remove()函数可以帮我们删除一个特定的值,那如果我们想删除一个特定的范围呢?

这时候,就可以用上remove_if()这个函数了

remove_if()可以帮我们按照自己的意愿来删除元素,当然你需要先写一个条件函数来告诉remove_if自己想要删除什么样的元素

例: 对于一个元素为{1,2,3,4,5,6,7,8,9,10}的链表L(储存int类型),我们想要删除值为[2,5]的元素

首先,我们需要先写出这个条件函数( remove_if的判断函数如果返回true,那么执行删除 )

bool del(int x)//因为储存的是int类型,所以传入的参数是int类型,del是自己取的这个函数的名字
{
    if(x >= 2 && x <= 5)//满足这个条件的时候,返回的是true
    {
        return true;
    }
    else return false;//不满足返回false
}

写好了这个函数,我们就可以开始使用啦

L.remove_if(del) 传入的参数就是我们写出来的条件函数,语句执行完以后,L链表的元素为{1,6,7,8,9,10};

③ unique()

unique()函数可以用来删除相邻的重复元素,比如一个元素为{2,1,1,2,2,3,4}的链表L

执行完L.unique()后,链表的元素为{2,1,2,3,4}

删除的是相邻的重复元素,并不是删除所有的重复元素,必须要满足相邻才可以删除。

那么如果我们想删除链表中的所有重复元素,只留下一个不重复的元素呢?

还是上面的链表L,如果我们想删除所有重复元素,得到元素为{1,2,3,4}的链表L,可以怎么做呢?

还记得①中提到的sort()函数吧,既然unique删除相邻的重复元素,那么我们只需要先对队列进行排序,这样就可以让它们的重复元素都紧挨在一起,这个时候再使用unique函数,就可以达到我们的目的了。(此处仅仅是举出一个例子,不考虑这么做的算法复杂度好不好)

④ merge()

merge的英文意思为"合并",所以merge函数是一个用来合并两个链表的函数。

其中merge函数默认是以升序的规则来合并两个链表的,当然也可以使用greater来将规则更改为降序

list<int> L1,L2;
for(int i=0;i<10;i++)
{
    if(i)
    {
        L1.push_back(i);
    }
    else
    {
        L2.push_back(i);
    }
}//执行完for以后,L1的元素为{1,3,5,7,9},L2为{0,2,4,6,8}
L1.merge(L2);//此时的L1为{0,1,2,3,4,5,6,7,8,9}

这里要注意一个点,在上面的代码中,我们绝对不能执行L1.merge(L2,greater<int>())这条语句。

为什么呢? 因为L1和L2都是升序排列的,merge函数在使用的时候,如果想要按照降序合并,那么被合并的两个链表一定也要是按照降序合并。

也就是说,两个链表合并时,合并后的排列规则一定要和原先两条链表的排列规则一样才可以进行,否则在运行时会报错。(编译是没错的)

那么如果我们想要使用greater来进行降序排列,原先的L1和L2也必须是按照降序排列的链表。

这里就不再演示了,只要保证上述条件满足,调用L1.merge(L2,greater<int>())这个语句就好。

注意:在使用merge合并完以后,元素会集中到L1链表上,L2链表变为空链表

⑤reverse()

reverse译为"相反的",所以reverse函数的作用是将链表逆置。

对于一个元素为{1,2,3,4,5}的链表L,执行L.reverse()

链表的元素变为{5,4,3,2,1}

3.迭代器

迭代器是我们在stl容器学习的过程中会碰到的一个重要的知识,它也和许多的函数有着密切的关系。

初步接触和使用迭代器的时候,我们可以把它看作是一种指针(当然实际上并不是)。

stack和queue两种容器中并没有迭代器的存在,所以在前面的学习过程中我们对迭代器没有任何的接触,但是在list容器中我们就要使用到迭代器了,不止list,包括后面要学习的vector和deque等容器都需要用到迭代器。

介绍到此为此,让我们直接进入主题。

① 在我们以前的学习中,如何才能遍历输出一个链表的值呢?

while(!L)
{
    cout<<L->data<<endl;
    L=L->next;
}

这就是一个最基础的遍历输出写法,但这是我们自己建立的链表结构,而在list容器中,我们只能使用frontback函数来访问list的头尾元素,如果我们想要遍历输出list链表的值,又该怎么做呢?

或许有人想到了这样的做法

while(!L.empty())
{
    cout<<L.front()<<endl;
    L.pop_front();
}

这的确是一种遍历链表L并输出的方法,这个方法也是我们在stack和queue中所经常使用的。

但是这种方法必须要访问完那个元素后,就将那个元素删除,这样才能够访问后面的元素。

那如何能在不删除元素的情况下遍历输出这个链表呢?

这个时候就要用到我们的迭代器

在stl的容器里面,给每个容器都设置了专用的迭代器,但定义的方式都是一样的。

list<int>::iterator it 这里定义了一个int类型链表的迭代器,叫作 it

那么这个迭代器it就是专门用在储存int型的链表中的,不可以用在其他类型中,需要一一对应。

那么了解完定义以后,想要开始遍历,还需要了解一些函数:

begin() 和 end()

begin()和end()两个函数返回的都是迭代器

其中begin返回的迭代器指向链表的头

而end函数返回的迭代器并没有指向链表的尾,而是指向链表尾的再下一个位置

end()返回的迭代器指向的实际上是一个哨兵位置,用来说明链表遍历已经结束。

现在准备工作都已经结束,可以开始动手写代码辽:

我们先假设有一个元素为{1,2,3,4,5,6,7,8,9}的链表L

list<int>::iterator it;
for(it=L.begin();it!=L.end();it++)
{
    cout<<*it<<' ';//输出的结果就是{1,2,3,4,5,6,7,8,9},遍历输出
}

以上的代码就是利用迭代器来遍历输出list容器里面的值的方法。

这里的it就和我们以前写for循环时候的i是差不多的。

it=L.begin()相当于以前的i=0,it!=L.end()就相当于i<n,而it++i++就更是相像了。

要注意到这里我们输出的时候使用的是*it ,在最开始的介绍中就提到过,在初学迭代器的时候我们可以把迭代器看作是一种指针(本质并不是),因为它的使用方法和指针没什么不同。

既然它的用法和指针没什么不同,如果我们链表储存的是结构体类型,要怎么输出呢?

struct time
{
    int hour;
    int min;
    int score;
};
list<struct time> L;

对于这样的一个链表L,在使用迭代器输出的时候,有两种写法。

for(it=L.begin();it!=L.end();it++)
{
    cout<<(*it).hour<<' ';
    cout<<it->hour<<' ';
}

这两种写法输出的都是结构体中hour的值,其中在第一个写法中,要注意 *it 一定要用 () 包起来,原因关乎于运算符的优先级,忘记辽就去自己查一下表。

为了方便迭代器的使用,stl中还定义了反向迭代器

list<int>::reverse_iterator rit 这里定义了一个反向迭代器,叫做rit

反向迭代器和迭代器就是一样的,区别就在于迭代器是从头遍历到尾,而反向迭代器是从尾到头。

为了配合反向迭代器,还有与begin和end相对应的函数——rbegin()和rend()

函数前面加的字母r就代表着reverse

list<int>::reverse_iterator rit;
for(rit=L.rbegin();rit!=L.rend();rit++)
{
    cout<<*rit<<' ';
}

代码的逻辑都是一样的,只是使用的函数不一样而已。

② 修改list容器中的值以及在中间插入的方法

先来看看如何修改容器中的值

修改容器中的值实际上是一件比较简单的事情,在①中的遍历操作时,我们不仅仅可以用来遍历输出,在遍历找到你想要修改的值时,可以直接对值进行修改。

比如我们要对一个元素为{1,2,3,4,5,5,6,7}的链表动手,将其中的元素5改为10

for(it=L.begin();it!=L.end();it++)
{
    if(*it==5)
    {
        (*it)=10;
    }
}

这样,就可以将链表中的元素5都改为元素10,想要进行其他类型的修改,只需要按照你的意愿去修改*it的值就好了。

那么再来看看如何在链表中间进行插入操作吧

这里我们需要用到insert()函数

insert函数最少要传入两个参数,最多可以传入三个参数。

其中第一个参数必须是要插入位置的迭代器

??假设我们拥有一个元素为{1,2,3,4,5}的链表L

Ⅰ.如果我们想在第二个元素(2)这个位置插入一个元素7

L.insert(++L.begin(),7),此时链表元素为{1,7,2,3,4,5}

从结果可以看出,插入的元素是插入在插入位置的前方

L.begin()代表着L链表的第一个元素1,那么第二个元素2的迭代器就是++L.begin.()

Ⅱ.如果想在第二个元素(2)这个位置插入n个元素x,比如插入3个7

L.insert(++L.begin(),3,7),会发现这个时候7反而是位于第三个参数,而第二个参数代表的是插入元素的个数。

插入后的结果为{1,7,7,7,2,3,4,5}

Ⅲ.那么如果我们想要插入一个范围的值呢?

这个时候就需要传入三个参数,且三个参数都为迭代器

第一个参数依旧是要插入位置的迭代器,而后面两个迭代器所包含的范围会被插入到要插入的位置中。

假设我们这个时候还有一个链表L1,元素为{6,7,8,9}

如果想要在L的第一个元素(1)前插入L1的后三个元素{7,8,9},那么可以这么写:

L.sert(L.begin(), ++L1.begin(), L1.end())

第一个参数是L第一个元素1的迭代器,第二个元素为L1的第二个元素7的迭代器,第三个元素为L1的尾迭代器。

代码执行后,链表L元素为{7,8,9,1,2,3,4,5};


stl容器学习——queue,stack,list与string

标签:英语   另一个   复杂度   写代码   排序   第一个   结构体类型   生成   uniq   

原文地址:https://www.cnblogs.com/suyuan333/p/10747570.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!