C++ Primer 7-9

原创 2013年12月02日 15:44:14
再读C++ Primer


第7章:函数
参数传递:
形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值;如果形参为引用类型,则它只是实参的别名。


非引用形参:
普通的非引用类型的参数通过赋值对应的实参实现初始化。当用实参副本初始化形参时,函数并没有访问调用所传递的实参本身,因此不会修改实参的值。
1、指针形参
函数的形参可以指针,此时赋值实参指针。与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本。如果函数将新指针值赋给形参,主调函数使用的实参指针的值没有改变,但其所指向的对象的值可能被改变。
如果需要保护指针指向的值,则形参需定义为指向const对象的指针。注意:形参是指向const对象的指针,则可以调用指向const和指向非const对象的值。但如果形参是指向非const对象的指针,则不可以调用指向const对象的实参,防止修改const实参的值。
2、const形参
在调用函数时,如果该函数使用非引用的非const形参,则既可给该函数传递const实参也可传递非const的实参。这种行为源于const对象的标准初始化规则。因为初始化复制了初始化式的值,所以可以用const对象初始化非const对象。如果形参定义为非引用的const类型则在函数中不可以改变实参的局部副本。由于实参仍然是以副本的形式传递,因此传递给fcn的既可以是const对象也可以是非const对象。编译器认为具有const形参和非const形参的函数并无区别。


引用形参:
使用情况:
1、返回额外信息
2、向函数传递大型对象时,如果使用引用的唯一目的是避免复制实参,应该将形参定义为const引用。
3、灵活使用:如果函数具有普通的非const引用形参,则显然不能通过const对象进行调用。毕竟,此时函数可以修改传递进来的对象,这样就违背了const特性;但比较容易忽略的是,调用这样的函数时,传递一个右值或者具有需要转换的类型的对象同样是不允许的。应该将不修改相应实参的形参定义为const引用,普通的非const引用形参在使用时不太灵活。这样的形参既不能用const对象初始化,也不能用字面值或者产生右值的表达式实参初始化。
4、传递指向指针的引用:如形参为int *&v1,则v1是一个引用,与指向int型对象的指针相关联。






具有返回值的函数:
cstdlib头文件定义了两个预处理变量,分别用于表示程序运行成功和失败。
返回非引用类型:用函数返回值初始化临时对象与用实参初始化形参的方法是一样的。如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象。
返回引用:当函数返回引用类型时,没有复制返回值。相反,返回的是对象本身。
如: const string &shorterString(const string &s1, const string &s2)
{return s1.size() < s2.size() ? s1 : s2;}//正确,注意与下面的程序比较
千万不要返回局部对象(不包括形参)的引用。
如:const string &manip (const string& s){
string ret =s;
return ret; //ERROR:函数返回类型为引用,但返回的值为函数本省的局部对象,已经被释放掉了
}
千万不能返回指向局部对象的指针。






递归:直接或间接调用自己的函数成为递归函数。递归函数必须有一个终止条件;否则会永远递归下去。
如求最大公约数的递归函数:
int rgcd(int v1,int v2)
{
if(v2 != 0)
return rgcd(v2,v1%v2);
return v1;
}




函数声明应当放在头文件中。






默认实参:
程序员可以为一个或多个形参定义默认值,但是,如果一个形参具有默认实参,那么他后面的所有形参都必须有默认值。
既可以在函数声明也可以在函数定义中指定默认实参,但是在一个文件中只能为一个形参指定默认实参一次。




内联函数:避免调用函数的开销(调用函数要做很多工作:调用前要先保存寄存器,并在返回时恢复;复制实参;程序还必须转向一个新位置执行)。
内联函数应该放在头文件中定义。在函数返回值前加上inline就可以将函数指定为内联函数


类的成员函数:
编译器隐式的将在类内定义的成员函数当做内联函数。类的成员函数可以访问该类的private成员。
每个成员函数都有一个额外的、隐含的形参this。在调用成员函数时,形参this初始化为调用函数的对象的地址。
成员函数声明的形参表后面的const所起了作用:const 改变了隐含的this形参的类型,隐含的this形参将是一个指向const对象指针。用这种方法使用const的函数成为常量成员函数。由于this是指向const对象的指针,const成员函数不能修改调用函数的对象,即不能修改调用他们的数据成员。
const对象,指向const对象的指针或引用只能用于调用其const成员函数,如果尝试用他们来调用非const成员函数,则是错误的。对于成员函数,函数声明必须与其定义一致。如果函数被声明为const成员函数,那么函数定义时形参表后面也必须有const。


在冒号和花括号之间的代码称为默认构造函数的初始化列表。
如: Sales_item():units_sold(0),revenue(0.0){}




合成的默认构造函数:
如果没有为一个类显式定义任何构造函数,编译器将自动为这个类生成默认构造函数。成为合成的默认构造函数。
依据如同变量初始化的规则初始化类中所有的成员。对于具有类类型的成员,则会调用该成员所属类自身的默认构造函数实现初始化。内置类型成员的初值依赖对象如何定义。如果对象在全局作用域中定义或者定义为静态局部对象,则这些成员被初始化为0.如果在局部作用域中定义,则这些成员没有被初始化。
合成的默认构造函数一般适用于仅包含类类型成员的类。而对于含有内置类型或者复合类型成员的类,则通常应该定义他们自己的默认构造函数初始化这些成员。




重载函数:
出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。
仅当形参是引用或者指针时,形参是否为const引用或const指针才有影响,可实现重载。否则,不能实现重载。




指向函数的指针:指向函数而不是指向对象的指针。
bool (*pf)(const string &, const string &)//*pf两边的括号是必须的,pf是一个函数指针
bool *pf(const string &,const string &)//pf是一个函数名,返回一个bool*指针


1、用typedef简化
如: typedef bool (*cmpFcn)(const string &,const string &);
2、指向函数的指针的初始化和赋值
在引用函数名但没有调用该函数时,函数名将自动解释为指向函数的指针。
函数指针只能通过同类型的函数名或者函数指针或者0值常量初始化或赋值。
可以不需要使用解引操作符,直接通过指针调用函数。
如:设有函数: bool lengthCompare(const string &, const string &){...}
cmpFcn pf1 = 0;//OK
cmpFcn pf2 = lengthCompare;//OK
lengthCompare("hi","bye");//OK
pf2("hi","bye");//OK
(*pf2)("hi","bye");//OK
如果指向函数的指针没有初始化,或者具有0值,则该指针不能再函数调用中使用。只有当指针已经初始化,或被赋值为指向某个函数,方能用来调用函数。


函数的形参可以是指向函数的指针:
如: void useBigger(const string &,const string &,bool (const string &, const string &))//OK
void useBigger(const string &,const string &,bool(*) (const string &, const string &))//OK




返回指向函数的指针:
int (*ff(int))(int *,int );//阅读函数指针声明的最佳方法是从声明的名字开始由里而外理解。
首先观察:ff(int) 将ff声明为一个函数,它带有一个int型的形参。
该函数返回int (*)(int*,int);它是一个指向函数的指针,带有int*和int型两个擦书并返回int型。
使用typedef更简明易懂。
typedef int (*PF)(int*,int);
PF ff(int);


允许将形参定义为函数类型,但函数的返回类型则必须是指向函数的指针,而不能是函数。具有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针。但是,当返回的事函数时,同样的转换操作则无法实现。

指向重载函数的指针:指针的类型必须与重载函数的一个版本精确匹配。
如:
extern void ff(vector<double>);
extern void ff(unsigned int);


void (*pf1)(unsigned int) = &ff; //ok
void (*pf2)(int) = &ff; //error,invalid parameter list


double (*pf3)(vector<double>); //error,invalid return typr
pf3 = &ff;








第八章:标准IO库
iostream定义读写控制窗口的类型,fstream定义读写命名文件的类型,而sstream所定义的类型则用于读写存储在内存中的string对象。
在15章可以看到:如果函数由基类类型的引用形参时,可以给函数传递其派生类型的对象。这就意味着:对istream&进行操作的函数,也可以用ifstream或者istringstream对象来调用。类似地,形参为ostream&类型的函数也可以用ofstream或者ostringstream对象调用。因为IO类型通过继承关联,所以只编写一个函数,而将它应用到三种类型的流上:控制台,磁盘文件或者字符串流。


国际字符的支持:wostream,wistream,wiostream,wchar_t,wifstream,wofstream,wfstream,wcin,wcout,wcerr。


IO对象不可以复制或者赋值。
在第9章中可以看到,
第一个含义:只有支持复制的元素类型可以存储在vector或其他容器类型里。由于流对象不能复制,因此不能存储在vector(或其他)容器中。
第二个含义:形参或者返回类型也不能用流类型。如果需要传递或者返回IO对象,则必须传递或返回指向该对象的指针或引用。
如:ofstream &print(ofstream&);while (print(out2)){};//OK,take a reference,no copy
一般情况下,如果要传递IO对象一边对它进行读写,可用非const引用的方式传递这个流对象。对IO对象的读写会改变它的状态,因此引用必须是非const的。


条件状态:IO标准库管理一系列条件状态成员,用来标记给定的IO对象是否处于可用状态,或者碰到了那种特定的错误。


IO标准库的条件状态:
strm::iostate 机器相关的整形名,由各个iostream类定义,用于定义条件状态
strm::badbit strm::iostate类型的值,用于指出被破坏的流//标志系统级故障,无法恢复错误
strm::failbit strm::iostate类型的值,用于指出失败的IO操作//读入无效数据,这种故障可以恢复
strm::eofbit strm::iostate类型的值,用于指出流已经到达文件结束符
s.eof() 如果设置了流s的eofbit值,则该函数返回true
s.fail() 如果设置了流s的failbit值,则该函数返回true
s.bad() 如果设置了流s的badbit值,则该函数返回true
s.good() 如果流s处于有效状态,则该函数返回true
s.clear() 将流s中的所有状态都重设为有效状态
s.clear(flag) 将流s中的某个指定状态重设为有效状态。flag的类型为strm::iosate
s.setstate(flag)给流s添加指定条件。flag的类型时strm::iostate
s.rdstate() 返回流s的当前条件,返回值类型为strm::iostate
如: int ival;
while(cin >> ival, !cin.eof()){
if(cin.bad())
throw runtime_error("IO stream corrupted");
if(cin,fail()){
cerr << "bad date,try again";
cin.clear(istream::failbit);
cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');
continue;
}
}




输出缓冲区的管理:每个IO对象管理一个缓冲区,用于存储程序读写的数据
导致缓冲区的内容被刷新的情况:
1、程序正常结束。作为main返回工作的一部分,将清空所有输出缓冲区
2、在一些不确定的时候,缓冲区可能已经满了,在这种情况下,缓冲区将会在写下一个值之前刷新
3、用操作符显式的刷新缓冲区,例如行结束符endl,flush(用于刷新流,但不在输出中添加任何字符),ends(这个操作符在缓冲区中插入空字符null)。
cout<< "hi" << flush;
cout<< "hi" << ends;
cout<< "hi" << endl;
4.在每次输入操作执行完后,用unitbuf操作符设置流的内部状态,从而清空缓存区. unitbuf这个操作符在每次执行完写操作后都刷新流。
cout << unitbuf << "first" << "second" << nounitbuf; //等价于 cout << "first" << "second" << flush;
5、可将输出流与输入流关联起来。在这种情况下,在读输入流时将刷新其关联的输出缓冲区。tie函数可用istream或ostream对象调用,使用一个指向ostream对象的指针形参。如果一个流调用tie函数将其本身绑在传递给tie的ostream实参对象上,则该流上的任何IO操作都会刷新新实参所关联的缓冲区。
如: cin.tie(&cout);//绑定cout和cin
cin.tie(0); //断开cout和cin的绑定
一个ostream每次只能与一个istream绑定。如果调用tie函数时传递实参0,则打破该流上已存在的捆绑。






文件的输入和输出
fstream类型除了继承下来的行为外,还定义了两个自己的新操作open和close,以及形参为要打开的文件名的构造函数。
需要读取写文件时,则必须定义自己的对象,并将它们捆绑在需要的文件上。假设ifile和ofile是存储希望读写的文件名的string对象,可如下编写代码:
ifstream infile(ifile.c_str());
ofstream outfile(ofile.c_str());
IO标准库使用C风格字符串而不是C++ string类型的字符串作为文件名。在创建fstream对象时,如果调用open或者使用文件名作为初始化式,需要传递的实参应为C风格字符串,而不是标准库string对象。程序常常从标准输入获得文件名。通常,比较好的方法是将文件名读入string对象,而不是C风格字符数组,然后调用c_str成员获取C风格字符串。
上述代码定义并打开了一对fstream对象。infile是读的流,而outfile则是写的流。为ifstream或者ofstream对象提供文件名作为初始化式,就相当于打开了特定的文件。
ifstream infile;
ofstream outfile;
infile.open("in"); //打开命名为in的文件进行读
outfile.open("out"); //打开名为out的文件进行写


1.检查文件打开是否成功
打开文件后,通常要简要打开是否成功
if(!infile){
cerr << "error:unable to open input file: "
<< ifile << endl;
}
这个条件与之前测试cin是否到达文件尾部或遇到某些其他错误的条件类似。检查流等效于检查对象是否适合输入或者输出。
if(outfile)//准备好?
if(!outfile)//没准备好?


2.将文件流与新文件重新捆绑
fstream对象一旦打开,就保持与指定的文件相关联。如果要把fstream对象与另一个不同文件关联,则必须先关闭现在的文件,然后打开另一个文件。
ifstream infile("in");
infile.close();
infile.open("next"); //open函数会检查流是否已经打开,如果已经打开则设置内部状态,以指出发生了错误


3.清除文件流的状态
//假设vector对象files包含一些要打开并读取的文件名
ifstream input;
vector<string>::const_iterator it = files.begin();
while(it != files.end()){
input.open(it -> c_str());
if(!input)
break;
while(input >> s)
process(s);
input.close();
input.clear();
++it;
}
如果程序员需要重用文件流读写多个文件,必须在读写另一个文件之前调用clear清除该留的状态。






文件模式:
无论是调用了open还是以文件名作为流初始化的一部分,都需要指定文件模式。文件构造函数和open函数提供了默认实参设置文件模式。默认实参因流类型的不同而不同。
in 打开文件做读操作
out 打开文件做写操作
app 在每次写之前找到文件尾
ate 打开文件后立即将文件定位到文件尾
trunc 打开文件时清空已存放在的文件流
binary 以二进制模式进行IO操作
out,app,trunc模式只能用于指定与ofstream或fstream对象相关联的文件
in只能用于指定与ifstream或fstream对象相关联的文件
所有文件都可以用ate或binary模式打开
默认时,与ifstream流对象关联的文件以in模式打开,该模式允许文件做读的操作;与ofstream关联的文件则以out模式打开,是文件可写。以out模式打开的文件会被清空。从效果看,为ofstream对象指定out模式等效于同时制定了out和trunc模式。
如:
ofstream outfile("file1");
ofstream outfile2("file2",ofstream::out | ofstream::trunc);//效果同file1,清空
ofstream appfile("file2",ofstream::app); //append ,追加模式




1、对同一个文件做输入和输出运算
fstream:默认为以in和out模式同时打开,不清空。当只有out模式时,清空。当有trunc时,无论如何都清空。
2、模式时文件的属性而不是流的属性
3、打开模式的有效组合
out 打开文件做些操作,删除文件中已有的数据
out | app 打开文件做写操作,在文件尾写入
in 打开文件做读操作
in | out打开文件做读、写操作,并定位于文件开头处
in | out | trunc打开文件做读,写操作,删除文件中已有的数据




open_file函数:
ifstream& open_file(ifstream &in,const string &file)
{
in.close();
in.clear();
in.open(file.c_str());
return in;
}




字符串流:包含头文件sstring
iostream标准库支持内存中的输入/输出,只要求流与存储在程序内存中的string对象捆绑起来即可。
除了继承的操作外,还定义了一个有string形参的构造函数,这个函数将string类型的实参赋值给stringstream对象,还定义了名为str的成员,用来读取或设置stringstream对象所操纵的string值。
stringstream的操作:
stringstream strm;
stringstream strm(s);
strm.str(); //返回strm存储的string类型的对象
strm.str(s); //将string类型的s复制给strm,返回void


string line,word;
while(getline(cin,line)){
istringstream stream(line);
while(stream >>word){
//do per-word processing
}
}
这里,使用getline函数从输入读取正行内容,让获取每行中的单词。


stringstream提供的转换和/或格式化
常见用法:需要在多种数据类型之间实现自动格式化时使用该类类型。
如: int val1 =512;val2=1024;
ostringstream format_message;
format_message << "val1:" << val1 << "\n";
输出  val1: 512\n
相反用istringstream读string对象时,可重新将数据找回来
istringstream input_istring(format_message.str());
string dump;
input_istring >> dump >> val1;
cout << val1 
输出512






第9章:顺序容器
顺序容器:vector,list,deque:区别在于访问元素的方式,以及添加或删除元素相关操作的运行代价。
顺序容器适配器:stack,queue,priority_queue类型。
vector:支持快速随机访问
list:支持快速插入/删除
deque:双端队列


stack:后进先出栈
queue:先进先出队列
priority_queue:有优先级管理的队列


顺序容器的定义:
#include<vector>
#include<list>
#include<deque>
所有容器都是模板。要定义某种特殊的容器,必须在容器名后加一对尖括号,尖括号里面提供容器中存放的元素的类型。所有容器都定义了默认构造函数,用于创建指定类型的空容器对象。
容器构造函数:
C<T> c; //创建一个名为c的空容器。C是容器类型名,T是元素类型,适用于所有容器。
C c(c2); //创建容器c2的副本c;要求容器类型和元素类型都相同。适用于所有容器.
vector<int> ivec;
vector<int> ivec2(ivec);//OK
list<int> ilist(ivec);//error
vector<double> dvec(ivec);//error
C c(b,e); //创建c,其元素是迭代器b和e标示的范围内元素的副本。适用于所有容器。第二个元素是新建容器的超出末端元素。不要求容器类型相同。元素类型也可以不同,只要他们相互兼容。能够将要复制的元素转换为所构建的新容器的元素类型,即可实现复制。采用这种初始化方式可复制不能直接复制的容器,也可复制其他容器的一个子序列。
list<string> slist(svec.begin(),svec.end());
vector<string>::iterator mid = svec.begin()+svec.size()/2;
deque<string> front(svec.begin(),mid);
deque<string> back(mid,svec.end());
C c(n,t); //用n个值为t的元素创建容器c,其中值t必须是容器类型C的元素类型的值,或者是可以转换为该类型的值.只适用于顺序容器
const list<int>::size_type list_size = 64;
list<string> slist(list_size,"er?");//64 strings,each is eh?
C c(n); //创建有n个初始化元素的容器c。只适用于顺序容器




顺序容器内元素的类型约束:
1、元素类型必须支持赋值元素。
2、元素类型的对象必须可以复制。
除了引用外(引用不支持一般意义上的赋值运算),所有内置或符合类型都可以用作元素类型。除了输入输出标准库外(IO库不支持复制或者赋值),其他所有标准库类型都是有效的容器元素类型。
一些容器操作对元素还有特殊要求。如果元素类型不支持这些操作,则相关容器操作就不能执行。如C c(n)这种形式的构造函数要求元素类型提供默认构造函数。


容器的容器:必须用空格分开两个相邻的>符号,以示这是两个分开的符号
vector< vector<string> > lines;//vector of vectors


迭代器和迭代器范围:
所有迭代器具有相同的接口。
常用迭代器运算:
*iter; //返回迭代器iter所指向的元素的引用
iter->mem //对iter进行解引用,获取指定元素中名为mem的成员。等效于(*iter).mem
++iter
iter++
--iter
iter--
iter1 == iter2//比较两个迭代器是否相等。当两个迭代器指向同一个容器中的同一个元素,或者当他们都是指向同一个容器的超出末端的下一位置,两个迭代器相等
iter1 != iter2




vector和deque容器的迭代器提供额外的运算:
只有vector和deque两种容器提供下面两种重要的运算集合:迭代器算术预算,以及使用了除==和!=之外的关系操作符。(==和!=适用于所有容器)。
iter+n
iter-n
iter1 -= iter2
>,>=,<,<= //当一个迭代器指向的元素在容器中位于另一个迭代器执行的元素之前,则前一个迭代器小于后一个迭代器。关系操作符的两个迭代器必须指向同一个容器中的元素或者超出末端的下一个位置。
关系操作符只适用于vector和deque容器,这是因为只是这两种容器为其元素提供快速、随机的访问。list容器的迭代既不支持算术运算,也不支持关系运算,它只提供前置和后置的自增,自减运算以及相等运算。




使迭代器失效的容器操作:
每种容器都定义了一个或者多个erase函数。这些函数提供了删除容器元素的功能,任何指向已删除元素的迭代器都具有无效值。


顺序容器的操作:
1、在容器中添加元素
2、在容器中删除元素
3、设置容器大小
4、获取容器内的第一个和第二个元素


容器定义的类型别名:
1、size_type 无符号整形,足以存储次容器类型的最大可能容器长度
2、iterator 此容器类型的迭代器类型
3、const_iterator 元素的只读迭代器类型
4、reverse_iterator 按逆序寻址元素的迭代器
5、const_reverse_iterator 元素的只读逆序迭代器
6、difference_type 足够存储两个迭代器差值的有符号整形,可为负数
7、value_type 元素类型
8、reference 元素的左值类型,是value_type&的同义词
9、const_reference 元素的常量左值类型,等效于const value_type&




begin和end成员
c.begin();
c.end();
c.rbegin(); //返回一个逆序迭代器,指向最后一个元素
c.rend(); //返回一个逆序迭代器,指向第一个元素前面的位置
上诉操作都有两个不同的版本:一个const成员,另一个是非const成员。这些操作返回什么类型取决于容器是否为const。如果容器不是const,则这些操作返回iterator或reverse_iterator;如果容器是const,则要加const_前缀。


添加元素操作:
c.push_back(t);//在容器c的尾部添加值为t的元素。返回void类型
c.push_front(t);//在c的前端添加值为t的元素。返回void。只适用于list和deque容器类型。
c.insert(p,t); //在迭代器p所指向的元素的前面插入值为t的新元素。返回指向新添加元素的迭代器。
c.insert(p,n,t);//在迭代器p所指向的元素前面插入n个值为t的新元素。返回void类型。
c.insert(p,b,e);//在迭代器p所指向的元素前面插入有迭代器b和e标记的范围内的元素。返回void类型。
容器中的元素都是副本。




避免存储end操作返回的迭代器:不要存储end操作符返回的迭代器。添加或删除deque或vector容器的元素都会导致存储的迭代器失效,但赋值操作不会出错。
如:vector<int>::iterator first = v.begin();
 last = v.end();
while(first != last){
first = v.insert(++first,42);
++first;
}
上述代码的行为未定义,将陷入死循环。因为添加元素会使last中的迭代器失效。可改为如下:
while(first != v.end()){
first = v.insert(++first,42);
++first;
}




容器的关系操作符:所有的容器都支持关系操作符,所有容器的迭代器都支持!=和==操作符,但只有vector和deque容器的迭代器支持关系操作符>=,>,<,<=


关系操作符规则:容器的比较必须具有相同的容器类型,而且其元素也必须相同
如果两个容器具有相同的长度而且所有元素都相等,那么着两个容器就相等;否则,他们就不相等;
如果两个容器的长度不相等,但较短的容器中所有元素都等于较长容器中对应的元素,则称较短的容器 小雨另一个容器
如果两个容器都不是对方的初始子序列,则他们的比较结果取决于所比较的第一个不相等的元素。


容器大小的操作:
所有容器都提供四种与容器大小相关的操作。
c.size(); //返回容器c中的元素个数,返回类型为c::size_type
c.empty(); //判断容器代销是否为0,返回bool值
c.max_size(); //返回容器c可容纳的最多元素个数;返回类型为c::size_type
c.resize(n); //调整容器c的长度大小,使其能容纳n个元素;如果n<c.size(),则删除多出来的元素;否则,添加采用值初始化的新元素
c.resize(n,t); //调整容器c的大小,使其能容纳n个元素。所有新增加的元素都为t




访问元素:
如果容器非空,那么容器类型的front和back成员将返回容器内的第一个或最后一个元素的引用。
c.back() 返回容器c的最后一个元素的引用。如果c为空,则该操作未定义  相当于*c.begin();
c.front() 返回容器c的第一个元素的引用。如果c为空,则该操作未定义 相当于*--c.end();
c[n] 返回下标n的元素的引用,只适用于vector和deque容器
c.at(n) 返回下标为n的元素的引用。如果下标越界,则该操作未定义。只适用于vector和deque容器








删除元素
c.erase(p) 删除迭代器p所指向的元素。返回一个迭代器,他指向被删除元素后面的元素。如果p指向容器内的最后一个元素,则返回的迭代器指向容易的超出末端的下一位置。如果p本身就是指向超出末端的下一位置的迭代器,该函数未定义。
c.erase(b,e) 删除迭代器b和e所标记的范围内的所有元素。返回一个迭代器,他指向被删除元素段后面的元素。如果e本身就是指向超出末端的下一位置的迭代器,则返回的迭代器也指向容器的超出末端的下一位置。
c.clear() 删除容器c内的所有元素。返回void
c.pop_back() 删除容器c 的最后一个元素。返回void.如果c为空容器,则该函数未定义
c.pop_front() 删除容器c的第一个元素。返回void。如果c为空容器,则该函数未定义。只适用于list和deque,不适用于vector




程序员必须在容器中找出要删除的元素后,才使用erase操作。用find算法,需要包括algorithm头文件。
如: string searchValue("Quasimodo");
list<string>::iterator iter = find(slist.begin(),slist.end(),searchCalue);
if(iter != slist.end())
slist.erase(iter);
在删除前必须确保迭代器不是end迭代器。






赋值与swap
除swap操作外,其他操作都可以用erase和insert操作实现。赋值操作符首先删除其左操作数容器中的所有元素,然后将右操作数容器的所有元素插入到左边容器中。赋值后,左右两边容器相等:尽管赋值前两个容器的长度可能不相等,但赋值后两个容器都具有右操作数的长度。
c1=c2; 删除容器c1的所有元素,然后将c2的元素赋值给c1。c1和c2的类型(包括容器类型和元素类型)必须相同
c1.swap(c2); 交换内容:调用完该函数 后,c1中存放的是c2原来的元素,c2中存放的则是c1原来的元素。c1和c2的类型必须相同。该函数的执行速度通常要比c2的元素复制到c1的操作快。
c.assign(b,e); 重新设置c的元素:将迭代器b和e标记的范围内所有的元素赋值到c中。b和e必须不是指向c中元素的迭代器
c.assign(n,t); 将容器c重新设置为存储n个值为t的元素
赋值和assign操作使左操作数容器的所有迭代器失效。swap操作则不会使迭代器失效。完成swap操作后,尽管被交换的元素已经存放到另一容器中,但迭代器仍然指向相同的元素。


1.使用assign
assign操作首先删除容器中所有的元素,然后将其参数所指定的新元素插入到该容器中。与复制容器元素的构造函数一样,如果两个容器类型相同,其元素类型也相同,就可以使用复制操作符(=)将一个容器赋值给另一个容器。如果在不同(或相同)类型的容器内,元素类型不相同但是相互兼容,则赋值运算必须使用assign函数。例如:可通过assign操作实现将vector容器中一段char*类型的元素赋值给string类型的list容器。
由于assign操作首先删除容器中原来存储的所有元素,因此,传递assign函数的迭代器不能指向调用该函数的容器内的元素


2.使用swap操作以节省删除元素的成本
swap操作实现交换两个容器内所有元素的功能。要交换的容器类型必须匹配:操作数和元素类型必须相同。
该操作不会删除或插入任何元素,而且保证在常量时间内实现交换。由于容器内没有任何移动元素,因此迭代器不会失效。




vector容器的自增长:
vector容器的元素以连续的方式存放——每一个元素都紧挨着前一个元素存储。如果我们向容器内添加一个新元素:如果容器中已经没有空间容纳新的元素,此时,由于元素必须连续存储以便索引访问,所以不能在内存中随便找个地方存储这个新的元素。于是,vector必须重新分配存储空间,用来存放原来的元素以及新添加的元素:存放在旧存储空间中的元素被复制到新的存储空间里,接着插入新元素,最后撤销旧的存储空间。如果vector容器在每次添加新元素时,都要这么分配和撤销内存空间,其性能将会非常慢,简直无法接受。为了使vector容器实现快速的内存分配,其实际分配的容量要比当前所需的空间多一些。vector容器预留了这些额外的存储区,用于存放新添加的元素。每当vector容易不得不分配新的存储空间时,以加倍当前容量的分配策略是先重新分配。
对于不连续存储元素的容器,不存在这样的内存分配问题。例如:在list容器中添加一个元素,标准库只需创建一个新元素,然后将该新元素连接在已存在的链表中,不需要重新分配存储空间,也不必复制任何已存在的元素。


capacity和reserve成员:是程序员可与vector容器内存分配的实现部分交互工作
capacity:获取在容器需要分配更多的存储空间之前能够存储的元素总数。
reserve:告诉vector容器应该预留多少个元素的存储空间。
capacity与size的区别:size指容器当前拥有的元素个数;而capacity则指容器在必须分配新存储空间之前可以存储的元素总数。








容器的选用:
vector和deque容器提供了对元素快速随机访问,但付出的代价是,在容器的任意位置插入或删除元素,比容器尾部插入或删除的开销更大。list类型在任何位置都能快速插入和删除,但付出的代价是元素的随机访问开销较大。


vector容器除了容器尾部外,其他任何位置上的插入(或删除)操作都要求移动被插入或删除元素右边所有的元素。插入或删除list中的一个元素不需要移动任何其他元素。但list不支持随机访问。
deuqe同时提供了list和vector的一些性质:
1.与vector容器一样,在deque容器的中间insert和erase元素效率比较低
2.不同于vector容器,deque容器提供更高效的在其首部实现insert和erase的操作,就像在容器尾部一样
3.与vector容器一样不同于list容器的是,deque容器支持对所有元素的随机访问。
4.在deque容器首部或尾部插入元素不会使任何迭代器失效,而在首部或尾部删除元素则只会使指向被删除元素的迭代器失效。在deque容器的任何其他位置的插入和删除操作将会指向该容器元素的所有迭代器都失效。


选择容器的提示:
1.需要随机访问用vector或deque
2.需要在容器中间插入或删除元素用list
3.需要在首部或者尾部插入或删除元素用deque
4.需要现在容器中间插入或删除元素,然后在随机访问,则考虑在输入是将元素读入list,然后对此容器排序后复制到一个vector中。






在谈sring类型:
string类型和vector容器具有相同的操作,但不同的是他不提供以栈方式操作容器:在string类型中不能使用front,back和pop_back操作。
string s(cp,n)
string s(s2,pos2) //创建string对象s,从s2开始到pos位置。pos2不能大于是.s2.size();
string s(s2,pos2,len2)  //创建string对象s,从s2的pos开始起连续len2个字符


s.insert(p,t); //p为迭代器
s.insert(p,n,t)
s.insert(p,b,e)
s.assign(b,e)
s.assign(n,t)
s.erase(p)
s.erase(b,e)




s.insert(pos,n,c) //pos为下标
s.insert(pos,s2)
s.insert(pos,s2,pos2,len)
s.insert(pos,cp,len) //复制cp所指向的数组的前的len个字符
s.inser(pos,cp)
s.assign(cp,len)
s.assign(cp)
s.assign(pos,len)


s.substr(pos,n) //返回一个string类型的字符串,它包含s中从下标pos开始的n个字符
s.substr(pos) //返回pos到s末尾的suoyouzif
s.substr() //返回s副本


s.append(args) //将args串接在s后面。返回s的引用
s.replace(pos,len,args) //删除s中从下标pos开始的len个字符,用args指定的字符替换之。返回s的引用,args不能为b2,e2
s.replace(b,e,args) //删除迭代器b和e标记的范围内所有的字符,用args替换之。返回s的引用。args不能为s2,pos2,len2
以上表达式中的args可以如下:
s2
s2,pos2,len2 //pos2小标
cp //cp指针
cp,len2
n,c
b2,e2 //b2,e2迭代器








string类型的查找操作:
提供了6种查找函数,每种函数以不同形式find命名。这些函数都返回string::size_type类型的值,以下标标记查找所发生的位置;或者返回一个名为string::npos的特殊值,说明没有查找匹配
s.find(args)
s.rfind(args) //在s中查找args最后一次出现
s.find_first_of(args) //在s中查找args的任意字符的第一次出现
s.find_last_of(args) //在s中查找args的任意字符的最后一次出现
s.find_first_not_of()args //在s中查找第一个不属于args的字符
s.find_last_not_of()args //在s中查找最后一个不属于args的字符
以上表达式中的args可以如下:
c,pos //在s中,从下标pos标记的位置开始,查找字符c。pos的默认值为0
s2,pos//在s中,从下标pos标记的位置开始,查找string对象s2。pos的默认值为0
cp,pos //在s中,从下标pos标记的位置开始,查找cp所指向的C风格的一空字符结束的字符串。pos的默认值为0
cp,pos,n //在s中,从下标pos标记的位置开始,查找cp所指向的数组的前n个字符。pos和n都没有默认值








string对象的比较:
==,!=,>,>=,<,<=


s.compare(s2);
s.compare(pos,n1,s2)
s.compare(pos1,n1,s2,pos2,n2)
s.compare(cp)
s.compare(pos1,n1,cp)
s.compare(pos1,n1,cp,n2)






容器适配器:
适配器是使以实物的行为类似于另一个事物行为的一种机制。容器适配器让一种已存在的容器类型采用另一种不同的抽象类型的工作方式实现。
适配器通用的操作和类型
size_type 一种类型,足以存储次适配器类型的最大对象的长度
value_type 元素类型
container_type 基础容器的类型,适配器再此容器类型上实现
A a; 创建一个新的空的适配器命名为a
A a(c); 创建一个名为a的新适配器,初始化为容器c的副本
关系操作符: 所有适配器都支持全部关系操作符:==,!=,<,<=,>.>=


使用适配器时必须包含相关头文件:
#include<stack>
#include<queue>


1.适配器的初始化
所有的适配器都定义了两个构造函数:默认的构造函数用于创建空对象,而带有一个容器参数的构造函数将参数容器的副本作为其基础值。
例如:假设deq是deque<int>类型的容器,则可以用deq初始化一个新的栈,如下所示:stack<int> stk(deq);//copies elements from deq into stk
2.覆盖基础容器类型
默认的stack和queue都基于deqeue容器实现,而priority_queue则在vector容器上实现。在创建适配器时,通过将一个顺序容器指定为适配器的第二个实参,可覆盖其关联的基础容器类型。
stack< string, vector<string> > str_stk;;
stack<string,vector<string> > str_stk2(svec);
stack适配器所关联的基础容器可以是任意一种顺序容器类型。因此,stack栈可以建立在vector、list或者deque容器上。而queue适配器要求其关联的基础容器必须提供push_front运算,因此只能简历在list容器上,而不能建立在vector容器上。priority_queue适配器要求提供随机访问功能,因此可简历在vector或deque容器上,但不能建立在list容器上。




适配器的关系运算:要求基础元素类型支持等于或小于操作符,那么两个相同类型的适配器就可以做相等,不等,小于,大于,小于等于和大于等于的关系运算。
第一队不相等的元素将决定两者之间的关系。




栈适配器:stack
支持的操作:
s.empty //判断是否为空
s.size //返回栈中的个数
s.pop //删除栈顶元素,不返回其值
s.top //返回栈顶元素,但不删除
s.push(item) //在栈顶压入新元素
如: const stack<int>::size_type stk_size = 10;
stack<int> intStack;//empty stack
int ix = 0;
while(intStack.size() != stk_size)
intStack.push(ix++);
int error_cnt = 0;
while(intStack.empty() == false){
int value = intStack.top();
if(value != --ix){
cerr << "oops expected" << ix 
    << "received" << "value" << endl;
++error_cnt;
}
intStack.pop();
}
cout << "our program ran with" << errot_cnt << "errors!" << endl;




队列和优先级队列:使用这两种队列,必须包括queue头文件。
queue;将元素放置在队列尾部,取走队列首部的元素。
priority_queue允许用户为队列中存储的元素设置优先级。这种队列不是直接将新元素放在队列尾部,而是放在比它优先级低的元素前面。标准库默认使用元素类型的 < 操作符来确定他们之间的优先级关系。
队列和优先级队列的操作:
q.empty();
q.size();
q.pop();
q.front();返回队首元素,但不删除。只适用于与queue
q.back();返回队尾元素,但不删除。只适用于queue
q.top();返回最高优先级的元素值。只适用于priority_queue
q.push(item);对于queue,在队尾压入一个新元素;对于priority_queue,在基于优先级的适当位置插入新元素。

相关文章推荐

《C++ Primer Plus(第六版)》(9)(第七章 函数 笔记和答案)

1.ANSI C是C语言的标准,任何C语言的编译器都在ANSI C的基础上扩充。 ANSI C几乎不能完成任何程序的开发。TC、VC等都对ANSI C进行了扩充,加入了自己的规则和库之类的。 对于函数...

c++ primer 学习笔记9--文件输入输出

书285页,练习8.4 #include #include #include #include using namespace std; int main() { ifstream i...
  • wm_1991
  • wm_1991
  • 2015年04月11日 12:32
  • 250

Chapter 9<C++ Primer Plus>

接下来的一个系列都是进行C++的知识点积累,其实之前就已经看过两次这本书了,但是练习题没做,编程看了大概思路就自以为懂了,没有项目接触没有实际题目去思考去解决,也就导致了现在出现了三个方面的问题。 1...

c++primer第九章小结2-9

转载自http://www.cnblogs.com/crazyant/archive/2011/06/04/2072852.html 这篇文章总结了顺序容器的各种常见操作函数。因为是是第四版,所以和...

C++ primer 读书笔记(9)

函数的声明 在函数声明里值得注意的一点是

2012/1/9 《C++ Primer Plus》第三章:处理数据 学习笔记

《C++ Primer Plus》第三章学习笔记   11:以两个下划线或下划线和大写字母打头的名称被保留给实现(编译器及其使用的资源)使用。以一个下划线开头的名称被保留给实现,用作全局标识符。 ...
  • Zyearn
  • Zyearn
  • 2012年01月14日 10:12
  • 623

C++primer读书笔记9-转换与类类型

有时候指定了自己类类型来表示某种类型数据如SmallInt,那么为了方便计算就会指定一个转换操作符,将该类类型在某种情况下自动的转换为指定的类型 转换操作符 operator type(); 转换函数...

C++primer pe13_15(还有13——4、9、12中NoName类的指针疑惑)

new Sales_item;     new Sales_item;     new Sales_item;     new Sales_item;//这些必须主动delete//计算析构函数...

重学C++Primer笔记9---回调函数与函数指针的应用

1 函数指针—指向函数的指针  函数指针是指向函数而非对象的指针。像其他指针一样,函数指针也指向某个特定的类型,函数类型由其返回类型以及形参表确定,而与函数名无关。例如:bool (*pf)(cons...
  • FreeApe
  • FreeApe
  • 2015年10月14日 13:35
  • 904

C++Primer学习笔记(9)顺序容器

(1)顺序容器将单一类型元素聚集起来成为容器,然后根据位置来存储和访问。 (2)标准库提供了vector,list,deque三种顺序容器以及stack,queue, priority_queue三种...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C++ Primer 7-9
举报原因:
原因补充:

(最多只允许输入30个字)