8 IO库
8.1 IO类
1、大部分的IO库工具:C++语言不直接处理输入输出,而是通过一族定义在标准库中的类型来处理IO。这些类型支持从设备读取数据、向设备写入数据的10操作,设备可以是文件、控制台窗口等。还有一些类型允许内存lO,即,从string读取数据,向string写入数据。大部分IO库工具:
- istream (输入流)类型,提供输入操作。
- ostream ( 输出流)类型,提供输出操作。
- cin,一个istream对象,从标准输入读取数据。
- cout,一个ostream对象,向标准输出写入数据。
- cerr,一个ostream对象,通常用于输出程序错误消息,写入到标准错误。
>>
运算符,用来从一个istream对象读取输入数据。<<
运算符,用来向一个ostream对象写入输出数据。- getline函数(参见C++ primer第五版 3.3.2节,第78页),从一个给定的istream读取一行数据,存入一个给定的string对象中。
2、其他IO类型:为了支持这些不同种类的IO处理操作,在istream和ostream之外,标准库还定义了其他一些IO类型,分别定义在三个独立的头文件中: iostream
定义了用于读写流的基本类型,fstream
定义了读写命名文件的类型,sstream
定义了读写内存string对象的类型。
- iostream头文件:从标准流中读写数据,istream、ostream等,总结为DOS窗口和内存之间读取数据。
- fstream头文件:从文件中读写数据,ifstream、ofstream等,总结为内存和文件之间读取数据。
- sstream头文件:从字符串中读写数据,istringstream、ostringstream,即为内存和内存之间读取字符数据。
3、IO之间的关系:标准库使我们能忽略这些不同类型的流之间的差异,这是通过继承机制(inheritance)实现的。利用模板(参见C++ primer第五版3.3 节,第87页),我们可以使用具有继承关系的类,而不必了解继承机制如何T作的细节。简单地说,继承机制使我们可以声明一个特定的类继承自另一个类。我们通常可以将一个派生类(继承类)对象当作其基类(所继承的类)对象来使用。类型ifstream和istringstream都继承自istream。 因此,我们可以像使用istream对象一样来使用ifstream和istringstream对象。也就是说,我们是如何使用cin的,就可以同样地使用这些类型的对象。例如,可以对一个ifstream 或istringstream对象调用getline ,也可以使用>>从一个ifstream或istringstream对象中读取数据。类似的,类型ofstream和ostringstream都继承自ostream。因此,我们是如何使用cout的,就可以同样地使用这些类型的对象。
4、IO对象无法拷贝或者赋值:IO对象不能存在容器里,形参和返回类型也不能是流类型,形参和返回类型一般是流的引用,读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。
ofstream out1, out2;
out1 = out2;//错误:不能对流对象赋值
ofstream print(ofstream); //错误:不能将形参或是返回类型设为流类型
out2 = print(out2); //错误: out2不是可修改的左值
5、操作IO的条件状态:下面表中,strm是一种IO类型(如istream),s是一个流对象。
8.2 文件输入输出
头文件fstream定义了三个类型来支持文件IO: ifstream从一个给定文件读取数据,ofstream向一个给定文件写入数据,以及fstream可以读写给定文件。这些类型提供的操作与cin和cout的操作一样,可以用IO运算符(<<和>>)来读写文件,可以用getline从一个ifstream读取数据。
8.2.1 使用文件流对象
除了继承自iostream类型的行为之外,fstream中定义的类型还增加了- -些新的成员来管理与流关联的文件。下表列出的这些操作,可以对fstream、ifstream和ofstream对象调用这些操作,但不能对其他IO类型调用这些操作。
8.2.2 文件模式
每个流都有一个关联的文件模式(file mode),用来指出如何使用文件。
1、文件模式的限制:
- 只可以对ofstream或fstream对象设定out模式。
- 只可以对ifstream或fstream对象设定in模式。
- 只有当out也被设定时才可设定trunc模式。
- 只要trunc没被设定,就可以设定app模式。在app模式下,即使没有显式指定out模式,文件也总是以输出方式被打开。
- 默认情况下,即使我们没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加写到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作
- ate和binary模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用。
2、文件流的默认模式:与ifstream关联的文件默认以in模式打开;与ofstream关联的文件默认以out模式打开;与fstream关联的文件默认以in和out模式打开。
3、以out模式打开文件会丢弃已有数据:默认情况下,当我们打开一个ofstream时,文件的内容会被丢弃。阻止一个ofstream清空给定文件内容的方法是同时指定app模式:
//在这几条语句中,filel都被截断
ofstream out ("file1"); //隐含以输出模式打开文件并截断文件
ofstream out2 ("file1", ofstream: :out); //隐含地截断文件
ofstream out3("file1", ofstream: :outI ofstream: :trunc) ;
//为了保留文件内容,我们必须显式指定app模式
ofstream app("file2", ofstream: :app); //隐含为输出模式
ofstream app2 ("file2", ofstream: :out | ofstream: :app) ;
4、每次调用open时都会确定文件模式:在每次打开文件时,都要设置文件模式,可能是显式地设置,也可能是隐式地设置。当程序未指定模式时,就使用默认值。
8.3 string流
sstream头文件定义了三个类型来支持内存I0,这些类型可以向string写入数据,从string读取数据,就像string是一个IO流一样。
istringstream从string读取数据,ostringstream 向string写入数据,而头文件stringstream既可从string读数据也可向string写数据。与fstream类型类似,头文件sstream中定义的类型都继承自我们已经使用过的iostream头文件
中定义的类型。除了继承得来的操作,sstream中定义的类型还增加了一些成员来管理与流相关联的string。 下表操作,可以对stringstream对象调用这些操作,但不能对其他IO类型调用这些操作。
8.3.1 使用istringstream
例子:假定有一个文件,列出了一些人和他们的电话号码。某些人只有
一个号码,而另一些人则有多个一家庭电话、 工作电话、移动电话等。如
#include<iostream>
#include<vector>
#include<sstream>
using namespace std;
struct PhoneList
{
string name;
vector<string> phonenum;
};
int main()
{
// phonestring 用于保存电话号码
// cinstring 用于将getline的数据流入istringstram,且以空格割开的多个数据全部记录下来
string phonestring,cinstring;
// 定义一个电话列表
vector<PhoneList> phlist;
while(getline(cin,cinstring)){
// 定义一个电话表对象,保存一个电话表信息
PhoneList phobj;
// 从DOS读入string对象 不能直接拷贝
// istringstream isobj = cinstring; 用法是错误的
istringstream isobj(cinstring);
// listone.name = isobj; 用法是错误的,必须运用流运算符
isobj>>phobj.name;
// 根据getline读入的数据,第一个直接流入name,剩下空格割开的流入存放号码的列表
while(isobj>>phonestring){
phobj.phonenum.push_back(phonestring);
}
// 将这个电话对象压入电话列表
phlist.push_back(phobj);
}
// 循环输出对象
for(auto p:phlist){
cout<<p.name<<" ";
for(auto n:p.phonenum){
cout<<n<<" ";
}
cout<<endl;
}
return 0;
}
程序最后输入zhangsan 123 11 22
lisi 11 22 11
wangwu 11
^Z以crtl+Z结束输入
最后输出为上输入一模一样。
程序流程图:
1、容器都定义在一个头文件中,文件名与类型名相同。
9 顺序容器
9.1 顺序容器概述
1、顺序容器的类型:所有顺序容器都提供了快速顺序访问元素的能力但是,这些容器在以下方面都有不同的性能折中:①、向容器添加或从容器中删除元素的代价,②、非顺序访问容器中元素的代价
2、容器的比较: 除了固定大小的array外,其他容器都提供高效、灵活的内存管理。我们可以添加和删除元素,扩张和收缩容器的大小。容器保存元素的策略对容器操作的效率有着固有的,有时是重大的影响。在某些情况下,存储策略还会影响特定容器是否支持特定操作。
- 例如,string和vector将元素保存在连续的内存空间中。由于元素是连续存储的,由元素的下标来计算其地址是非常快速的。但是,在这两种容器的中间位置添加或删除元素就会非常耗时:在一次插入或删除 操作后,需要移动插入/删除位置之后的所有元素,来保持连续存储。而且,添加一个元素有时可能还需要分配额外的存储空间。在这种情况下,每个元素都必须移动到新的存储空间中。
- list和forward_list两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价,这两个容器不支持元素的随机访问:为了访问一个元素,我们只能遍历整个容器。而且,与vector、deque和array
相比,这两个容器的额外内存开销也很大。 - deque是个更 为复杂的数据结构。与string和vector类似, deque支持快速的随机访问。与string和vector一样,在deque的中间位置添加或删除元素的代价(可能)很高。但是,在deque的两端添加或删除元素都是很快的,与list或forward list添加删除元素的速度相当。
- forward_list 和array是新C++标准增加的类型。与内置数组相比,array是一种更安全、更容易使用的数组类型。与内置数组类似,array对象的大小是固定的。因此,array不支持添加和删除元素以及改变容器大小的操作。forward list 的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此,forward_list没有size操作,因为保存或计算其大小就会比手写链表多出额外的开销。对其他容器而言,size保证是一个快速的常量时间的操作。
3、选择容器的基本规则:通常使用vector是最好的,其他的选用规则如:
- 如果你的程序有很多小的元素,且空间的额外开销很重要,则不要使用list 或forward_list。
- 如果程序要求随机访问元素,应使用vector或deque。
- 如果程序要求在容器的中间插入或删除元素,应使用list或forward_list。
- 如果程序需要在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,则使用deque.
- 如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则首先,确定是香真的需要在容器中间位置添加元素。当处理输入数据时,通常可以很容易地向vector追加数据,然后再调用标准库的sort函数来重排容器中的元素,从而避免在中间位置添加元素。如果必须在中间位置插入元素,考虑在输入阶段使用list一旦输入完成,将list中的内容拷贝到一个vector中。
9.2 容器库预览
1、容器的操作并非是全通用的:某些操作是所有容器类型都提供的;另外一些操作仅针对某特定容器。
2、对容器可以保存的元素类型的限制:顺序容器几乎可以保存任意类型的元素,同时可以定义容器的容器,如vector的vector:vector<vector<string>> lines;
3、默认构造函数的影响:某些类没有默认构造函数。我们可以定义一个保存这种类型对象的容器,但我们在构造这种容器时不能只传递给它一个元素数目参数:
vector<noDefault> v1 (10,init) ;// 正确:提供了元素初始化器
vector<noDefault> v2 (10) ;//错误:必须提供一个元素初始化器
9.2.1 迭代器
标准容器类型上的所有迭代器都允许我们访问容器中的元素,而所有迭代器都是通过解引用运算符来实现这个操作的。
1、迭代器的基本操作:
Tips:forward_list迭代器不支持递减运算符(–)。
2、迭代器的范围:begin指向第一个元素,end指向最后一个元素的后面元素,数学描述为[begin,end)
的关系,简称左闭合规范,如图所示:
3、使用左闭合规范的编程假定:包括以下三个部分
- 如果begin与end相等,则范围为空
- 如果begin与end不等,则范围至少包含一个元素,且begin指向该范围中的第一个元素
- 可以对begin递增若干次,使得begin==end
4、begin和and的版本:带r的版本返回反向迭代器;以c开头的版本则返回const迭代器,与const指针和引用类似,可以将一个普通的iterator转换为对应的const_iterator,但反之不行。
9.2.2 关系运算符
1、容器比较的条件:每个容器类型都支持相等运算符(==和!=
);除了无序关联容器外的所有容器都支持关系运算符(>、>=、<、<=
)。关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。
我们只能将一个vector<int>与另一个vector<int>进行比较,
而不能将一个vector<int>与一个list<int>或一个vector<double>进行比较。
2、比较关系的运输机制:对元素进行逐对比较,工作方式与string的关系运算类似。
- 如果两个容器具有相同大小且所有元素都两两对应相等,则这两个容器相等;否则两个容器不等。
- 如果两个容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器。
- 如果两个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等的元素的比较结果。
vector<int>v1={1,3,5,7,9,12};
vector<int> v2 = { 1,3,9 };
vector<int> v3 = { 1, 3,5,7 };
vector<int>v4={1,3,5,7,9,12};
v1 < v2 // true; v1和v2在元素[2]处不同: v1[2]小于等于v2[2]
vl < v3 // false;所有元素都相等,但v3中元素数目更少
v1 == v4 // true; 每个元素都相等,且v1和v4大小相同
vl == v2 // false; v2元素数目比v1少
3、对自定义类型的容器比较必须要提前定义好比较运算:只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器。
/*容器的相等运算符实际上是使用元素的==运算符实现比较的,而其他关系运算符是使用元素的<运算符。
如果元素类型不支持所需运算符,那么保存这种元素的容器就不能使用相应的关系运算。
例如Sales_ data 类型并未定义==和<运算。因此,就不能比较两个保存Sales_data 元素的容器:*/
vector<Sales_ data> storeA, storeB;
if (storeA < storeB) // 错误: Sales_ data没有<运算符
9.2.3 容器的尺寸大小
求容器大小的函数:如下图所示
9.3 顺序容器的操作
总结为对顺序容器进行定义初始化和增删改查基本操作。
9.3.1 容器定义和初始化
1、容器的定义:每个容器类型都定义了一个默认构造函数,除array之外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。
2、利用一个容器初始化为另外一个容器的拷贝::可以直接拷贝整个容器,或者拷贝由一个迭代器对指定的元素范围(array除外)。
当将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。
//每个容器有三个元素,用给定的初始化器进行初始化
list<string> authors = {"Milton", "Shakespeare", "Austen"};
vector<const char*> articles = {"a", "an", "the"};
list<string> list2 (authors); // 正确:类型匹配
// deque<string> authList (authors); // 错误:容器类型不匹配
// vector<string> words (articles); // 错误:容器类型必须匹配
//正确:可以将const char*元素转换为string
forward_list<string> words (articles.begin(), articles.end()) ;
3、拷贝一个迭代器的范围:除了array:被赋值的容器元素根据迭代器容器的类型而定:
deque<string> deq221 (list211.begin(),list211.end());
for(auto out:deq221){cout<<out<<" ";}cout<<endl;
4、array的特殊性:(1)array的大小是固定的;(2)array的构造函数行为特殊:;(3)与内置数组类型的区别
// (1)array的大小是固定的,对其定义需要指定大小和类型
array<int,10> arr311;
// (2)array特殊构造函数
// (3)array不同于内置数组,其可以对数组类型进行拷贝或者对对象进行赋值
// int digs[10] = {0, 1,2,3,4,5,6, 7,8,9};
// int cpy[10] = digs;//错误:内置数组不支持拷贝或赋值
array<int, 10> digits = {0,1,2,3,4,5,6, 7,8,9};
array<int, 10> copy = digits; //正确:只要数组类型匹配即合法
9.3.2 顺序容器的拷贝和赋值
1、适用于所有容器的赋值操作:如下图所示,赋值运算符将其左边容器中的全部元素替换为右边容器中元素的拷贝。
2、使用assign(仅顺序容器,顺序容器中除array):允许从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。assign 操作用参数所指定的元素(的拷贝)替换左边容器中的所有元素。例如,我们可以用assgin实现将一个vector中的段 char值赋予一个list中的string:
list<string> names;
vector<const char*> oldstyle;
//names = oldstyle; //错误:容器类型不匹配
//正确:可以将const char*转换为string
names.assign(oldstyle.cbegin(),oldstyle.cend());
1、assign的参数决定了容器中将有多少个元素以及它们的值都是什么。
2、由于其旧元素被替换,因此传递给assign的迭代器不能指向调用assign的容器
3、使用swap:swap操作交换两个相同类型容器的内容。调用swap 之后,两个容器中的元素将会交换,用法:swap(容器A,容器B)
vector<string> svec1 (10) ; // 10个元素的vector
vector<string> svec2 (24) ; // 24个元素的vector
swap (svec1,svec2) ;
1、swap只是交换了两个容器的内部数据结构——元素本身并未交换。
2、除array外,swap不对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。
4、swap交换后元素的结构:swap并未交换元素本身,即元素不会被移动,这意味着,除string 外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。它们仍指向swap操作之前所指向的那些元素。但是,在swap之后,这些元素已经属于不同的容器了。
例如,假定iter在swap之前指向svec1[3]的string,那么在swap之后它指向svec2[3]的元素。
与其他容器不同,对一个string调用swap会导致迭代器、引用和指针失效。
9.3.3 向顺序容器添加元素
1、首尾添加单个元素的基本操作:如下图所示,注意array不支持这些操作(因为array的大小固定)。
1、这些操作会改变容器的大小, array不支持这些操作。
2、forward_ list 有自己专有版本的insert和emplace。
3、forward_ list 不支持push_ back 和emplace_ back。
4、vector和string不支持push_ front 和emplace_ front。
5、新标准引入了三个新成员:emplace_ front、emplace和emplace_ back,emplace是构造而不是拷贝元素。
2、使用insert在容器任意位置插入元素:vector、deque、list和string都支持insert成员,forward_list提供了特殊版本的insert成员,array不支持插入元素。
- insert操作包括迭代器位置,插入元素个数和元素内容:seqc.insert(迭代器位置,元素个数,元素);
- nsert重载版本很多,包括seqc.insert(迭代器位置,元素)插入单个;seqc.insert(迭代器A位置,迭代器B始,迭代器B终)等等
- insert插入元素是在元素前面插入,故forward_list不支持通用版本的insert
- 新标准的insert返回值是插入元素的位置的迭代器,如124在4前面插3,则最后insert返回值为指向3的迭代器,
- 如果插入多个则是最前面的元素,即在123554的end()迭代器前面插入0123最后为1235540123切返回值为在0位置的迭代器
- insert插入范围超出时候,插入不成功,返回为void
基本操作表(不包括stack,tree等基本数据类型):
#include<iostream>
#include<vector>
#include<deque>
#include<forward_list>
#include<array>
#include<list>
#include<string>
using namespace std;
void coutall(vector<int> v,deque<int> d,list<int> l,forward_list<int> f,array<int,5> a,string s){
cout<<"vector: ";for(auto c : v){cout<<c<<" ";}cout<<endl;
cout<<"deque: ";for(auto c : d){cout<<c<<" ";}cout<<endl;
cout<<"list: ";for(auto c : l){cout<<c<<" ";}cout<<endl;
cout<<"forward_list: ";for(auto c : f){cout<<c<<" ";}cout<<endl;
cout<<"array: ";for(auto c : a){cout<<c<<" ";}cout<<endl;
cout<<"string: ";for(auto c : s){cout<<c<<" ";}cout<<endl;
};
int main()
{
vector<int> veca = {1,2,3}; vector<int> vecb;
deque<int> deqa = {1,2,3}; deque<int> deqb;
list<int> lista = {1,2,3}; list<int> listb;
forward_list<int> flista = {1,2,3}; forward_list<int> flistb;
array<int,5> arra = {1,2,3}; array<int,10> arrb;
string stra = "123"; string strb;
cout<<endl<<"--------------Add member [without iterator] in sequential_container----------------------"<<endl;
// 1、在容器两端增加单个元素,通用方法,不包括forward_list和array
// 除array和forward_list 之外,每个顺序容器(包括string类型)都支持push_back队尾增元素
// vector对象的增加元素操作,push_back(v)只在队尾
veca.push_back(4);
// deque增加元素,包括队尾有push_back(d),emplace_back(d);队头有:push_front(d),emplace_front(d)
deqa.push_back(4);deqa.emplace_back(5);
deqa.push_front(0);deqa.emplace_front(-1);
// list增加元素,包括队尾有push_back(l),emplace_back(l);队头有:push_front(l),emplace_front(l)
lista.push_back(4);lista.emplace_back(5);
lista.push_front(0);lista.emplace_front(-1);
// string增加元素,在队尾增加元素push_back(s);
stra.push_back('4'); // 类似于stra+='4';
coutall(veca,deqa,lista,flista,arra,stra);
cout<<endl<<"--------------Add member [with insert and iterator] in sequential_container----------------------"<<endl;
// 2、使用insert在容器任意位置插入任意个元素
// 2.1 vector、deque、list和string都支持insert成员.forward_list提供了特殊版本的insert成员,array不支持插入元素
// 2.2 insert操作包括迭代器位置,插入元素个数和元素内容:seqc.insert(迭代器位置,元素个数,元素);
// insert重载版本很多,包括seqc.insert(迭代器位置,元素)插入单个;seqc.insert(迭代器A位置,迭代器B始,迭代器B终)等等
// 2.3 insert插入元素是在元素前面插入,故forward_list不支持通用版本的insert
// 2.4 新标准的insert返回值是插入元素的位置的迭代器,如124在4前面插3,则最后insert返回值为指向3的迭代器,
// 如果插入多个则是最前面的元素,即在123554的end()迭代器前面插入0123最后为1235540123切返回值为在0位置的迭代器
// 2.5 insert插入范围超出时候,插入不成功,返回为void
veca.insert(--veca.end(),2,5); // veca最后一个元素前面插入两个5,即1234最后一个是4,4前插两个5;故veca=123554
vector<int> temp_insert = {0,1,2,3};
// 在veca尾部插入veca头到尾的数据即veca = 1235540123
auto insert_sit = veca.insert(veca.end(),temp_insert.begin(),temp_insert.end());
// insert插入位置超过容器范围发生的情况:即元素插入不成功,insert返回值为void
deqa.insert(deqa.begin() + 10,7);
coutall(veca,deqa,lista,flista,arra,stra);
cout<<endl<<"0123 insert to 123554 the insert sit is: "<<*insert_sit<<endl;
cout<<endl<<"--------------some add number operate in forward_list ----------------------"<<endl;
// 3、关于forward_list和array的特殊操作
// 3.1 array由于数组长度固定不允许增加元素
// 3.2 forword_list增加元素,在队头或者元素前面增加元素,包括操作push_front(f)和emplace_front(f);
// 3.3 利用迭代器在元素后面增加元素,emplace_after(iter,f);注意迭代器的位置,如end后面是空,即迭代器范围:[begin,end)
// 3.4 在迭代器任意位置插入元素
flista.push_front(0);flista.emplace_front(-1);
flista.emplace_after(flista.begin(),4);
coutall(veca,deqa,lista,flista,arra,stra);
return 0;
}
9.3.4 向顺序容器删除元素
1、通用删除元素操作:除了forward_list和array外的通用几个操作:
1、删除元素会改变容器大小,故全部适用于array。
2、forward_ list 有特殊版本的erase。
3、forward list不支持pop_back, vector 和string不支持pop_ front。
2、erase操作的特点: erase删除容器中间单个或多个元素
- erase(迭代器起,迭代器终),删除范围在[起,终)
- erase的返回值:删除后返回下一元素的位置;迭代器如果在尾元素上,删除后则返回尾后迭代器nullptr;超出范围则为void
操作图表:
#include<iostream>
#include<vector>
#include<deque>
#include<forward_list>
#include<array>
#include<list>
#include<string>
using namespace std;
void coutall(vector<int> v,deque<int> d,list<int> l,forward_list<int> f,array<int,5> a,string s){
cout<<"vector: ";for(auto c : v){cout<<c<<" ";}cout<<endl;
cout<<"deque: ";for(auto c : d){cout<<c<<" ";}cout<<endl;
cout<<"list: ";for(auto c : l){cout<<c<<" ";}cout<<endl;
cout<<"string: ";for(auto c : s){cout<<c<<" ";}cout<<endl<<endl;
cout<<"forward_list: ";for(auto c : f){cout<<c<<" ";}cout<<endl;
cout<<"array: ";for(auto c : a){cout<<c<<" ";}cout<<endl;
};
int main()
{
vector<int> veca = {1,2,3,4,5}; vector<int> vecb;
deque<int> deqa = {1,2,3,4,5}; deque<int> deqb;
list<int> lista = {1,2,3,4,5}; list<int> listb;
forward_list<int> flista = {1,2,3,4,5}; forward_list<int> flistb;
array<int,5> arra = {1,2,3,4,5}; array<int,10> arrb;
string stra = "12345"; string strb;
cout<<endl<<"--------------reduce member [without iterator] in sequential_container----------------------"<<endl;
// 1、在容器两端删除单个元素,通用方法,不包括forward_list和array
// 1.1 vector:仅支持队尾删除pop_back()
// 1.2 deque:队尾删除元素:pop_back();队头删除pop_front()
// 1.3 list:队尾删除元素:pop_back();队头删除pop_front()
// 1.4 string:串尾删除pop_back()
// 1.5 若删除元素不存在,则返回为void
veca.pop_back(); // veca = 1234
deqa.pop_back();deqa.pop_front(); // deqa = 234
lista.pop_back();lista.pop_front(); // lista = 234
stra.pop_back(); //stra = 1234
coutall(veca,deqa,lista,flista,arra,stra);
cout<<endl<<"--------------Add member [with insert and iterator] in sequential_container----------------------"<<endl;
// 2、使用erase删除容器中间单个或多个元素
// 2.1 删除迭代器特定位置元素:erase(迭代器位置)
// 2.2 删除迭代器位置之间所有元素:erase(迭代器起,迭代器终),删除范围在[起,终)
// 2.3 erase的返回值:删除后返回下一元素的位置;迭代器如果在尾元素上,删除后则返回尾后迭代器nullptr;超出范围则为void
auto erase_sit = veca.erase(veca.begin(),veca.begin()+2); // veca = 34;erase_sit指向3
deqa.erase(deqa.begin(),deqa.begin()+2); // deqa = 4
lista.erase(lista.begin()); // lista = 34
stra.erase(stra.begin(),stra.begin()+2); // stra = 34
coutall(veca,deqa,lista,flista,arra,stra);
cout<<"1234 to earse(begin(),begin()+2) the sit of earse is : "<<*erase_sit<<endl;
cout<<endl<<"--------------some add number operate in forward_list ----------------------"<<endl;
// 3、关于forward_list的特殊操作
// 3.1 删除单个元素:erase_after(迭代器)删除迭代器指向的位置之后的元素,如迭代器在begin则删除begin后的元素即第二个元素
// 3.2 删除多个元素:erase_ after(迭代器起,迭代器尾),删除范围在[起-1,终)
// 3.3 返回一个指向被删元素之后元素的迭代器,越界则为void
flista.erase_after(flista.begin()); // flista = 1345
coutall(veca,deqa,lista,flista,arra,stra);
return 0;
}
9.3.5 访问容器的元素
1、访问元素返回的是引用:在容器中访问元素的成员函数(即,front、 back、下标和at)返回的都是引用。如果容器是一个const对象,则返回值是const的引用。如果容器不是const的,则返回值是普通引用,我们可以用来改变元素的值:①直接赋值:c.front () = 42;
;②解引用赋值:auto &v = c.back() ; v = 1024;
:
vector<int> c = {1,2,3,4,5};
if (!c.empty()) {
c.front () = 42; //将42赋予C中的第一个元素
auto &v = c.back() ; //获得指向最后一个元素的引用
v = 1024; //改变C中的元素
auto v2 = c.back() ; // v2不是一个引用,它是c.back()的一个拷贝
v2 = 0; //未改变C中的元素
}
for(auto o : c){
cout<<o<<" ";
}
最后输出结果为:42 2 3 4 1024
2、防止下标溢出的at操作:string、 vector、deque和array都提供下标运算符,下标运算符接受一个下标参数,返回容器中该位置的元素的引用。当访问下标不在数组范围内,编译器不会保存但是运行异常,故采用at防止下标溢出,c.at(n)
在下标溢出时,抛出out_of_range异常。
vector<string> svec;//空vector
cout << svec[0] ;//运行时错误: svec中没有元素!
cout << svec.at (0) ;//抛出一个out_ of_ range异常
3、访问元素的操作:操作见图表,说明如下:
9.4 string类型的额外操作
9.4.1 构造string的其他方法
1、string的其他构造函数:string类型还包括其他三个构造函数。
const char *cp = "Hello World! ! !";//以空字符结束的数组
charnoNull[]={'H','i'};//不是以空字符结柬
string s1 (cp) ; //拷贝cp中的字符直到遇到空字符; s1 == "Hello World! ! !"
string s2 (noNull,2) ;//从noNull拷贝两个字符; s2 == "Hi"
string s3 (noNull) ;//未定义: noNull不是以空字符结束
strings4(cp+6,5);//从cp[6]开始拷贝5个字符; s4 == "World"
string s5(s1, 6,5) ;//从s1 [6]开始拷贝5个字符; s5 == "World"
string s6 (s1,6) ;//从s1[6]开始拷贝,直至sl末尾; s6== "World!! !"
string s7 (s1, 6,20) ;//正确,只拷贝到s1末尾; s7 == "World! ! !"
string s8 (s1, 16) ;//抛出一个out_ of_ range异常
2、substr切片操作:substr返回一个string,它是原始string的一部分或全部的拷贝。可以传递给substr一个可选的开始位置和计数值:string strb = stra.substr(index,len);
string s ("hello world") ;
string s2 = s.substr (0,5) ;//s2=hello
string s3 = s.substr (6) ;// s3 = world
string s4 =s.substr (6, 11) ;// s3 = world
string s5 =s.substr (12) ;//抛出一个out_ of range异常
9.4.2 string类型的搜索操作
1、基本的搜索函数:string类提供了6个不同的搜索函数,每个函数都有4个重载版本。每个搜索操作都返回一个string:size_ type(int) 值,表示匹配发生位置的下标(下标是从0开始的到size()-1)。如果搜索失败,则返回一个名为string::npos的static成员。标准库将npos定义为一个const string::size_ type类型,并初始化为值-1。
2、指定在哪里开始搜索:即find族(str,pos)
;,例如下面的例子,从4号开始,包括第4个个字符开始搜索逗号的位置,输出结果为7。
string s = "012,456,89";
cout<<s.find(',',4);
3、逆向搜索:默认find操作都是由左至右搜索。标准库还提供了类似的,但由右至左搜索的操作。rfind
成员函数搜索最后一个匹配,即子字符串最靠右的出现位置。
9.4.3 compare函数比较关系
1、compare函数有6个版本:根据是要比较两个string还是一个string与一个字符数组,参数各有不同。在这两种情况下,都可以比较整个或部分字符串。
2、compare的返回值:根据比较的字符串是等于则返回0、大于则返回1,小于返回-1。
9.4.4 数值转换
包括装换成整型stoi
;浮点型stof
,具体也可参考C++数据类型的相互转换
9.5 容器适配器
1、顺序容器用于实现数据的存储,方便数据的增、删、改。
2、适配器是种机制(某种数据结构),容器适配器是将容器的元素能够直接装换到这个数据结构中进行操作,故现在顺序容器只定义了是哪个个容器适配器:stack,queue和priority_queue。(注意区别queue数据结构的队列,容器库的deque双端队列,二者不要混淆)
本节主要了解
1、怎么把顺序容器的东西直接赋值到适配器里;
2、对适配器(数据结构)的基本操作
9.5.1 定义一个容器适配器
1、适配器的两个构造函数:
- ①默认构造函数创建一个空对象(即一个空的数据结构,依次存放元素),例如
stack<int> stk
;然后这种方式接受个容器的构造函数拷贝该容器来初始化适配器。即deq是一个 deque, 我们可以用deq来初始化一个新的stack,如下所示:stack<int> stk(deq)
- ②默认情况下,stack和queue是基于deque实现的,priority_queue 是在vector之上实现的。可以在创建个适配器时将个命名的顺序容器作为第二个类型参数,来重载默认容器类型。如在vector.上实现的空栈:
stack<str ing,vector<string>> str_ stk;
,以及 str stk2在vector 上实现,初始化时保存svec的拷贝stack<string, vector<string>> str stk2 (svec) ;
2、适配器对应容器的限制:
- 1、适配器均不能再array上构造。
- 2、stack可以接受除了array和forward_list以外的所有容器
- 3、queue可以构造在lisy或deque上,但是不能构造在vector上。
- 4、priority_queue可以构造在vector和deque容器上,但是不能基于list构造。
9.5.2 适配器的操作
1、三种容器适配器都支持的操作:
2、栈适配器的操作:stack定义在stack的头文件里,在数据结构里栈是一种先进后出的数据类型。
每个容器适配器都基于底层容器类型的操作定义了自己的特殊操作。我们只可以使用适配器操作,而不能使用底层容器类型的操作。
intStack.push(ix); // intStack 保存0到9十个数
/*此语句试图在intStack的底层deque对象上调用push_ back。
虽然stack是基于deque实现的,但我们不能直接使用deque操作。
不能在一个stack上调用push_back,而必须使用stack自己的操作即push。*/
3、队列适配器:queue和priority_queue适配器定义在queue头文件中:queue队列即先进先出的数据结构。进入队列的对象被放置到队尾,而离开队列的对象则从队首删除。priority_queue允许为队列中的元素建立优先级。新加入的元素会排在所有优先级比它低的已有元素之前。例如:饭店按照客人预定时间而不是到来时间的早晚来为他们安排座位,就是一个优先队列的例子。默认情况下, 标准库在元素类型上使用<
运算符来确定相对优先级。
10 泛型算法
10.1 概述
1、泛型算法的头文件:泛型算法一般包括在 #include <algorithm>
和#include <numeric>
2、泛型算法是如何工作的–以find算法为例:企图希望知道在vector里面是否包含这个元素:
int val = 42;
//如果在vec中找到想要的元素,则返回结果指向它,否则返回结果为vec. cend (
auto result = find(vec.cbegin(),vec.cend(),val) ;
//报告结果
cout << "The value"<< val<< (result == vec.cend()? "is not present" :" is present") << endl;
find在一个未排序的元素序列中查找一个特定元素。 概念上,find 应执行如下步骤:
- 1、访问序列中的首元素。
- 2、比较此元素与我们要查找的值。
- 3、如果此元素与我们要查找的值匹配,find 返回标识此元素的值。
- 4、否则,find前进到下一个元素,重复执行步骤2和3。
- 5、如果到达序列尾,find 应停止。
- 6、如果find到达序列末尾,它应该返回一个指出元素未找到的值。此值和步骤3,返回的值必须具有相容的类型。
这些步骤都不依赖于容器所保存的元素类型。因此,只要有一个迭代器可用来访问元素,find就完全不依赖于容器类型(甚至无须理会保存元素的是不是容器)。即 迭代器算法不依赖于容器,但是依赖于元素类型的操作!
3、泛型算法不会执行容器的操作:泛型算法本身不会执行容器的操作,它们只会运行于迭代器之上,执行迭代器的操作。算法永远不会改变底层容器的大小。算法可能改变容器中保存的元的值,也可能在容器内移动元素,但永远不会直接添加或删除元素。
10.2 初识泛型算法
10.2.1 只读算法
1、求和函数accumulate:针对单个序列,用法accumulate(迭代器起,迭代器末,初始值)
;返回值和初始值类型一致,或者能把初始值强制装换过去的类型。
// 数字求和
vector<int> arr = {1,2,3,4};
int irea = accumulate(arr.begin(),arr.end(),0);
2、序列值相等equal:针对两个不同的序列,用法equal(容器A起,容器A末,容器B起)
,其中容器B的元素至少和容器A一样多;返回值为1表示相同,为0表示不同(bool类型)。
string a = "hello,world";
string b = "world";
cout<<equal(a.begin() + 6,a.end(),b.begin());// 结果为1表示相等
10.2.2 写容器算法
1、