第七章
常量成员函数
将const关键字放在成员函数的参数列表之后时,紧跟在参数列表后面的const表示this是一个指向常量的指针。像这样使用const的成员函数被称作常量成员函数。
类作用域和成员函数
成员函数体可以随意使用类中的其他成员而无需在意这些成员出现的顺序。
this指针
个人感觉this就是指向该类本身的指针
构造函数
构造函数的名字和类名相同,但是构造函数没有返回类型
类可以包含多个构造函数,和其它的重载函数差不多,不同的构造函数之间必须在参数数量或参数类型上有所区别
不同于其它成员函数的是,构造函数不能被声明成const。当我们创建类的一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量属性”。因此构造函数在const对象的构造过程中可以向其写值。
默认构造函数
如果类中没有显示的定义构造函数,那么编译器就会为我们隐式的定义一个默认构造函数
编译器创建的构造函数又被称为合成的默认构造函数
某些类不能依赖于合成的默认构造函数
①编译器只有在发现类不包含任何构造函数的情况下才会替我们生成一个默认的构造函数。一旦我们定义了一些其他的构造函数,那么除非我们再定义一个默认的构造函数,否则类将没有默认构造函数。
②合成的默认构造函数可能执行错误的操作。
③有的时候编译器不能为某些类合成默认的构造函数。
定义构造函数
①默认构造函数
定义这个构造函数的目的仅仅是因为我们既需要其他形式的构造函数,也需要默认的构造函数。这个函数的作用完全等同于编译器自动生成的默认构造函数
②构造函数初始值列表
其中的bookNo(s)相当于是将传入构造函数的参数s用来初始化类的成员bookNo
③在类的外部定义构造函数
在类的外部定义构造函数时直接 类名::类名 创建函数体即可
class或struct关键字
class和struct的访问权限不同
如果使用struct关键字,则定义在第一个访问说明符之前的成员是public的;如果使用class关键字,则这些成员是private的
通常情况下,当我们希望定义的类的所有成员是public的时,使用struct;反之,如果希望成员是private的,使用class
友元
类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元。(关键字是 friend)
如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员。(包括private)
令成员函数作为友元
可以单独指定友元中的某个函数
可变数据成员
有时会发生一种情况,我们希望能修改类的某个数据成员,即使是在一个const成员函数内。可以通过在变量的声明中加入mutable关键字做到这一点。
就是可以使用mutable来设置想要在const类型的函数里也可以改变的变量
委托构造函数
此例中,第一个构造函数是被委托的构造函数
抑制构造函数定义的隐式转换(explicit)
此时,没有任何构造函数能用于隐式的创建Sales_data对象
关键字explicit只对一个实参的构造函数有效。需要多个实参的构造函数不能用于执行隐式转换,所以无须将这些构造函数指定为explicit的。只能在类内声明构造函数时使用explicit关键字,在类外部定义时不应重复:
explicit构造函数只能用于直接初始化
为转换显示的使用构造函数
尽管编译器不会将explicit的构造函数用于隐式转换过程,但是我们可以使用这样的构造函数显式的强制进行转换
聚合类
聚合类使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。
当一个类满足以下条件时,我们说它是聚合的:
所有成员都是public的
没有定义任何构造函数
没有类内初始值
没有基类,也没有virtual函数
以下是一个聚合类:
以下是初始化的方式:
初始值的顺序必须与声明的顺序一致,也就是说,第一个成员的初始值要放在第一个,然后是第二个,以此类推。
聚合类中,如果初始值列表中的元素个数少于类的成员数量,则靠后的成员被值初始化。初始值列表的元素个数绝对不能超过类的成员数量
值得注意的是,显式的初始化类的对象的成员存在三个明显的缺点:
类的静态成员
声明静态成员可以通过关键字static
使用类的静态成员:
定义静态成员
可以在类的内部定义也可以在类的外部定义静态成员,在类的内部定义静态成员的方法如上所示,如需在类的外部定义静态成员则如下所示(如果不懂就到时候再细查吧)
第八章
IO类
标准库定义了很多不同的IO类型,分别定义在三个独立的头文件中:iostream定义了用于读写流的基本类型,fstream定义了读写命名文件的类型,sstream定义了读写内存string对象的类型。
宽字符
宽字符版本的类型和函数的名字以一个w开始。例如,wcin、wcout和wcerr是分别对应cin、cout和cerr的宽字符版对象。
IO对象无拷贝或赋值,就是说我们不能对IO对象进行拷贝或赋值,因此我们也不能讲形参或返回类型设置为流类型。
条件状态
IO操作一个与生俱来的问题是可能发生错误。一些错误是可恢复的,而其他错误则发生在系统深处,已经超出了应用程序可以修正的范围。
流的当前状态(rdstate)
cin.rdstate();//cin的当前状态
管理输出缓冲
导致缓冲刷新(即,数据真正写到输出设备或文件)的原因有很多:
程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行
缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区
我们可以使用操纵符如endl来显式刷新缓冲区
在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的
一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下,cin和cerr都关联到cout。因此,读cin或写cerr都会导致cout的缓冲区被刷新
刷新输出缓冲区
endl:完成换行并刷新缓冲区的工作
flush:刷新缓冲区,但不输出任何额外的字符
ends:向缓冲区插入一个空字符,然后刷新缓冲区
unitbuf操纵符
如果想在每次输出操作后都刷新缓冲区,我们可以使用unitbuf操纵符。它告诉流在接下来的每次写操作之后都进行一次flush操作。而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制:
如果程序崩溃,输出缓冲区不会被刷新
当调试一个已经崩溃的程序时,需要确认那些你认为已经输出的数据确实已经刷新了。否则,可能将大量时间浪费在追踪代码为什么没有执行上,而实际上代码已经执行了,只是程序崩溃后缓冲区没有被刷新,输出数据被挂起没有打印而已
关联输入和输出流
当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。
交互式系统通常应该关联输入流和输出流。这意味着所有输出,包括用户提示信息,都会在读操作之前被打印出来。
tie有两个重载的版本:一个版本不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针,如果对象未关联到流,则返回空指针。tie的第二个版本接受一个指向ostream的指针,将自己关联到此ostream。即,x.tie(&o)将流x关联到输出流o。
文件输入输出
头文件fstream定义了三个类型来支持文件IO::ifstream从一个给定文件读取数据,ofstream向一个给定文件写入数据,以及fstream可以读写给定文件。
下表中的操作仅可以对fstream、ifstream和ofstream对象调用,不可对其他IO类型调用:
文件模式
每个流都有一个关联的文件模式(file mode),用来指出如何使用文件。
指定文件模式:
无论用哪种方式打开文件,我们都可以指定文件模式,调用open打开文件时可以,用一个文件名初始化流来隐式打开文件时也可以。指定文件模式有如下限制:
只可以对ofstream或fstream对象设定out模式
只可以对ifstream或fstream对象设定in模式
只有当out也被设定时才可设定trunc模式
只要trunc没被设定,就可以设定app模式。在app模式下,即使没有显式指定out模式,文件也总是以输出方式被打开。
默认情况下,即使我们没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加写到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作
ate和binary模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用
每个文件流类型都定义了一个默认的文件模式,当我们未指定文件模式时,就使用此默认模式。与ifstream关联的文件默认以in模式打开;与ofstream关联的文件默认以out模式打开;与fstream关联的文件默认以in和out模式打开。
以out模式打开文件会丢弃已有数据
默认情况下,当打开一个ofstream时,文件的内容会被丢弃。阻止一个ofstream清空给定文件内容的方法是同时指定app模式:
//在这几条语句中,file1都被截断
ofstream out("file1");//隐含以输出模式打开文件并截断文件
ofstream out2("file1",ofstream::out);//隐含的截断文件
ofstream out3("file1",ofstream::out|ofstream::trunc);
//为了保留文件内容,我们必须显式指定app模式
ofstream app("file2",ofstream::app);//隐含为输出模式
ofstream app2("file2",ofstream::out|ofstream::app);
保留被ofstream打开的文件中的已有数据的唯一方法是显式指定app或in模式。
每次调用open时都会确定文件模式
对于一个给定流,每当打开文件时,都可以改变其文件模式。
通常情况下,out模式意味着同时使用trunc模式。
string流
sstream头文件定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像string是一个IO流一样
istringstream从string读取数据,ostringstream向string写入数据,而头文件stringstream既可从string读数据也可向string写数据。
头文件sstream中定义的类型都继承自iostream头文件中定义的类型,除此之外sstream中定义的类型还增加了一些成员来管理与流相关联的string。这些操作可以对stringstream对象调用,但不能对其他IO类型调用这些操作:
istringstream
当我们的某些工作是对整行文本进行处理,而其他一些工作是处理行内的单个单词时,通常可以使用istringstream。
第九章
顺序容器
顺序容器提供了控制元素存储和访问顺序的能力,这种顺序不依赖于元素的值,而是与元素加入容器时的位置来对应。有序和无序关联容器,则根据关键字的值类存储元素。
顺序容器类型
容器选择的基本原则
大多数情况下vector都是适用的,但是有时也需要根据功能的不同来确定不同的容器类型。
容器操作
迭代器
forward_list迭代器不支持递减运算符 (--)
迭代器支持的算术运算只能应用于string、vector、deque、和array的迭代器,我们不能将它们用于其他任何容器类型的迭代器。
迭代器范围
一个迭代器范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或是尾元素之后的位置。(begin和end或first和last,它们标记了容器中元素的一个范围)
虽然第二个迭代器被称为end或last,但是第二个迭代器从来都不会指向范围中的最后一个元素,而是指向尾元素之后的位置。迭代器范围中的元素包含first所表示的元素以及从first开始直至last(但不包含last)之间的所有元素。
这种元素范围被称为左闭合区间
使用左闭合范围蕴含的编程假定
begin和end的版本:
带r的版本返回反向迭代器;以c开头的版本则返回const迭代器 begin、rbegin、cbegin、crbegin
容器定义和初始化
将一个容器初始化为另一个容器的拷贝
当将一个容器初始化未另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同
列表初始化(除array外)
也可以这样
array类型
定义一个array时,除了指定元素类型,还要指定容器大小:
为了使用array类型,需要同时定义元素类型和大小:
array大小固定的特性也影响了它所定义的构造函数的行为。与其他容器不同,一个默认构造的array是非空的:它包含了与其大小一样多的元素,这些元素都被默认初始化。如果对array进行列表初始化,初始值的数目必须等于小于array的大小。如果元素类型是一个类类型,那么该类必须有一个默认构造函数,以使值初始化能够进行:
容器赋值运算
assign
assign仅除array之外的顺序容器可以使用(将原容器中的所有旧的元素都替换掉),以下是用法:
swap
swap操作交换两个相同类型容器的内容
假设 *p原来指向svec0[0],两个vec交换之后,*p指向的则变成了svec1[0]。(因为指针指向的是元素的地址,所以*p从始到终指向的都是同一个地址,发生改变的只是vec的元素排列而已)
容器大小操作
size返回容器中的元素的数目
empty当size为0时返回布尔值true,否则返回false
max_size返回一个大于或等于该类型容器所能容纳的最大元素数的值
forward_list支持max_size和empty,但不支持size
关系运算符
每个容器类型都支持相等运算符(==和!=)除了无序关联容器外的所有容器都支持关系运算符(>、>=、<、<=),关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。
比较两个容器实际上是进行元素的逐对比较,这些运算符的工作方式与string的关系运算类似:
只有当其元素类型也定义了相应的比较运算符时,我们才可以使用关系运算符来比较两个容器
顺序容器操作
容器分为顺序容器和关联容器,所以与它们相关的操作也分为所有容器都可以用的通用操作和只有个别容器可以用的独有操作
以下是向顺序容器添加元素的操作
可使用push_back(尾部添加)的是 vector、list、deque。(其实string也可以 string str; str.push_back("a")相当于是str+=a)
可使用push_front(首部添加)的是list、forward_list、deque。
可使用insert(在指定位置添加)的是vector、deque、list、string,而forward_list提供了特殊版本的insert
slist.insert(iter,"hello");//将"hello"添加到iter之前的位置
list<string> lst; auto iter = lst.begin(); string word; while (std::cin >> word) { iter = lst.insert(iter,word);//等价于调用push_front //在这里insert的返回值是新添加进的元素, 利用这种方式,来将iter永远指向第一个元素 }
使用emplace操作
emplace直接将参数传递给元素类型的构造函数,如下:
Sales_data的构造函数接收的参数为 (string,int,double)如果有重载的构造函数,也可使用其它形式的构造函数的参数类型
不同容器间的拷贝
int ia[] = { 0,1,1,2,3,5,8,13,21,55,89 };
vector<int> vec(ia,ia+9);
list<int> lis(ia, ia + 9);
访问元素
一下的vec代指容器
vec.begin()和vec.end()是迭代器 相当于指针
vec.front()是首元素的引用 vec.back()是尾元素的引用(注意,back是尾元素的引用,end是尾元素后一位的指针,如果要用end来获取尾元素的话,必须首先递减此迭代器)
在调用front和back(或begin和end)之前,要确保vec非空 (!vec.empty())
下标操作和安全的随机访问(以下仅为vs2015 x64的测试结果)
书上是这么说的:
下面是亲测结果:
vector<int> ivec;//ivec是一个空的vector int a = ivec.at[0]; //直接报错并指向错误的位置 int b = ivec[0];//发生不确定的状况 int c = *ivec.begin();//直接崩溃 int d = ivec.front();//直接崩溃
删除元素
顺序容器的删除操作
forward_list
forward_list中未定义insert、emplace和erase,而是定义了insert_after、emplace_after和erase_after
还有首前迭代器 before_begin
改变容器大小
顺序容器大小操作
容器操作可能使迭代器失效
在向容器添加元素后:
当删除一个元素后:
管理迭代器
更新迭代器
在循环中调用insert或erase,会更容易的更新迭代器
如果在一个循环中插入/删除deque、string或vector中的元素,不要保存end返回的迭代器,反正end执行的很快,干脆每次直接获取好了。