C++ Primer学习笔记
第一章:快速入门
第二章:变量和基本类型
第三章:标准库类型
第四章:数组和指针
第五章:表达式
第六章:语句
第七章:函数
第八章:标准IO库
第九章:顺序容器
第十章:关联容器
第一章:快速入门
1.1 介绍一个简单的C++程序
注意程序要书写规范、标准。
编译器产生一个a.exe文件,则unix就会将a.exe这个可执行文件文件放到a.out中。
若要在Windows系统中,要访问其返回值,便要使用echo命令:C:\directory>echo% ERRORLEVEL%
其中C:\directory为系统命令提示符。
1.2 输入与输出
首先iostream处理格式化输入和输出。
endl是换行符,它与\n的区别在于它可以刷新缓冲区,通过刷新缓冲区,用户可以立即按看到输入到流中的输出。
字符串字面值中的空格符不能用换行符代替,空格符不允许出现在预处理指令中。
1.3注释
注释对不可嵌套
1.4 控制语句
while语句提供了迭代功能,适合于次数虽然不知,但是要用某一条件控制的循环。
语句可以简化代码,适合循环次数已知的情况。
读入未知数目的输入(std::cin>>value)
ctrl+z可以来输入文件结束符。
对于运算符,使用来运算对象成分的,而非对象本身。
1.5 类的简介
1.5.1 类
每个类定义一种类型,类型名与类名要相同。
1.5.2 类定义的函数
点操作符“.”的右操作符不是对象或值,而是成员的名字。
专业术语:
built-in type:内置类型。
class type:类类型,用类所定义的类型,类型名就是类名。
clog:绑定到标准错误的ostream对象。
manipulator:操作符
preprocessor directive:预处理指示
第二章:变量和基本类型
2.1 整型和浮点型
整型:整数、字符和布尔值。
除了bool外,其他整型是可以有符号,也可以无符号的。
要注意:对于变量赋值的时候,一定不要越界,因为一旦越界,编译时机器并不会检查出错误,但是你赋的值已经被改变,而导致运行错误结果。
2.2 字面值常量
只有内置类型存在字面值,没有类类型的字面值
注意:定义长整型时,应该使用L,而不用l。
不可打印的转义字符:
\v:纵向制表 \b:退格 \f:进纸 \a:报警(响铃)
C++中所有的字符串字面值都由编译器自动在末尾加一个空字符,来兼容C语言
注意:反斜线符号必为改行尾字符--不允许其后有注释或者空格。
2.3 变量
对象:内存中具有类型的区域。可用于描述程序中的大部分数据。
注意:初始化并不是赋值。初始化是创建变量并给初值。而赋值是右值给左值。
注意:定义变量的时候,最好要对它进行初始化。因为未初始化的变量事实上编译过程中,编译器会给它一个值放在内存里,若你在创建变量的时候不进行初始化便开始引用它,会出现未初始化错误或者直接导致程序错误执行。
通常把一个对象定义在它首次使用的地方。
2.4 定义const对象
const定义可以把一个对象转化成一个常量,定义时必须使用初始化。
非const定义的对象默认为extern,而被cosnt定义的对象,需要显示地表示出该对象是extern,才可以被其他文件访问,但是它并不存在初始化式。
2.5 引用
在变量名前加&符号来定义
不可以定义引用类型的引用
一旦引用初始化后,它就绑定到初始化时指向的对象。
普通的引用不能绑定到const对象
非const引用只能绑定到与该引用同类型的对象
const引用则可以绑定到不同但相关的类型的对象或绑定到右值。
2.6 typeof 名字
typref用来定义类型的同义词,而且可以用它来定义变量。
使用它的目的:隐藏特定类型的实现,强调使用类型的目的;简化类型定义;允许一种类型用于多个目的,同时使得每次使用该类型时目的明确。
2.7 枚举
枚举将相关联的元素定义咋一起,它是常量表达式,只定义一种唯一的类型。
默认后面每个枚举成员赋的值比前面大1。
2.8 类类型
类定义了一个接口(该类型的对象可以执行的操作)和一个实现(该类型的对象包含的数据)
注意:类定义后面要有分号。
类的初始化与一般变量的初始化有所不同,它是通过构造函数的特殊成员控制初始化。
private部分定义的成员只能被作为类的组成部分的代码访问
struct和class的区别在于:struct里的成员均为public,除非有其他特殊声明。
2.9 编写自己的头文件
使用自己定义的头文件用""包含起来
头文件只是用来声明,而非用来定义。但是可以定义在类、值在编译时就已知的const对象(此时一定是常量表达式初始化)和inline对象。
头文件里不应该包含有变量或函数的定义,所以这些定义应该放在源文件中。
第三章:标准库类型
3.1 命名空间的using声明
一个using声明一次只能作用域一个命名空间成员
尽量将using声明放在头文件中,一次头文件声明,即可减少多个using的声明
3.2 标准库string类型
字符串字面值是一串字符,string类型支持长度可变的字符串,两者不同
string类型的输入操作符,读取并忽略开头所有的空白字符,读取字符直至再次遇到空白字符,读取终止。
getline读取未知整行文本,接受:一个输入流对象和一个string对象。 getline并不忽略行开头的换行符,遇到,便停止读入并返回
size操作符取string对象中字符的个数,其返回值是string::size-type类型的值,该类型是string的配套类型,可以将它赋值给int,但这样做不安全,所以书中建议不要这样做,对于其他的标准容器,如vector,也是这样
当进行string对象和字符串字面值混合连接操作时,+操作符左右操作数必须有一个是string类型
toupper()要得到大写字母,这两个函数是在cctype头文件中的,并非string的成员函数
3.3标准库vector类型
是一个类模板,用于定义多个不同的数据类型
vector<T>V2(V1) V2是V1的一个副本
vector<T>V4(n) V4含有值初始化的元素的n个副本
push_back()向vector添加元素
C++中一般采用!=来编写循环判断的条件
3.4 迭代器简介
是一种数据类型,用来检查容器内元素并遍历元素
const_interator该类型只能用于读取容器内的元素,但不能改变其值,但其自身的值是可以改变的
const-iterator:iterator为const的一个对象,必须进行初始化
任何改变vector长度的操作符都会使已存在的迭代器失效,产生新的迭代器
3.5 标准库bitset类型
也是一种类模板,类型对象的区别在于其长度(位数)的不同
bitset类型长度大于unsigned long值位数,高位置0
bitset类型长度小于unsigned long值位数,多的位数自动丢弃
string和bitset对象之间是反向转化的
to_ulong获取bitset对象的值
第四章:数组和指针
一般在C++中选择使用vector来取代数组
4.1 数组
在函数体内定义的内置数组,其元素无初始化
数组元素无默认构造函数,一定要为它提供显示初始化
4.2 指针的引入
指针指向某个对象,而迭代器只能用于访问容器内的元素
string*ps1,ps2; 只是把ps1定义为指针,而ps2为string的一个对象。容易误解,一般不要采用这种定义方式
void* 可保存任何类型对象的地址
但不允许使用void*指针槽中它所指向的对象
int&ri=ival,&ri2=rval2;
ri=ri2;(修改了ri引用的值ival对象,而并非引用本身)
ptrdiff_t是signed整型,计算统一数组中两个指针之间的差距
指向const对象的指针也必须具有const特性
const double*cptr; const限定了cptr指针所指向的对象类型
而并非cptr本身,允许给cptr重新赋值
必须使用 const void* 类型的指针保存const对象的地址
int a=0;
int *const A=&a;
const double *const pi_ptr=π
const pstring cstr; (等效于 string *const cstr;
把cstr定义为指向string类型对象的const指针)
4.3 C 风格字符串
C++语言比较字符串:比较的是指针上存放的地址值,而非他们所指向的字符串
strlen计算的长度不包括null所占的空间
动态分配数组 *pia=new int [10];new后需指定指针类型和数组维数
delete[]释放指针所指向的数组空间
使用数组初始化vetor对象,必须指出初始化式的第一个元素,以及数组最后一个元素的下一位置的地址 vector<int>ivec(int_arr,int_arr+arr-size);
int *ip[4]; 定义一个4个元素的数组,数组的元素为指针
int (*ip)[4]; 指向含有4个元素的数组的指针
第五章:表达式
5.1 算数操作符
一般的算数操作符为左结合,对于表达式,要认真考虑其优先级与结结合型。 % 求模操作符 若两操作数为负数,则结果为负数或零,若只有一个操作数为负数,则结果取决于机器。例:21 % -5 结果为1 或 -4
5.2 关系操作符和逻辑操作符
它们都会产生bool值。 逻辑与和逻辑或总是先计算其左操作数 注意“短路求值”。 不能串接使用关系操作符,用&&来隔开。 bool类型可转换为任何算数据类型,bool值false用0表示;true用1表示;非零时,均为true
5.3 位操作符
位操作符将其整数操作数视为二进制的集合。 对于符号位不知如何处理时,一般采用unsigned整型操作数。 对于有操作符>> 如果操作数是有符号数,则插入符号位的副本或者0。 移位操作的有操作数不可以是负数,且小于做操作数位数的值。 bitset的对象,它的每一位都默认设置为0。 可使用bitset的成员函数set reset来对某一位置 1 0。 IO操作符为左结合 《优先级低于算数操作符高于关系 赋值 条件操作符。 cout<<42+10;输出52 cout<<(10<42); 输出1 cout<<10<42;输出10,并将其与42做比较
5.4 赋值操作符
赋值操作符必须为非const的左值,所以数组不可用作赋值操作的目标。 当左右操作数类型不同时,该操作实现将类型转换为被赋的值 int i; i=3.12;则i被赋值为3。 赋值操作符具有右结合的特性,如有多个赋值操作一起使用时,各对象必须具有相同的数据类型。 复合赋值操作符 a op= b ; 等效于 a = a op b;
5.5 自增和自减操作符
*iter++ 等效于*(iter++) 而前自增操作符和解引用操作符的优先级相同
5.6 箭头操作符
和点操作符作用相同,用来获取类类型对象的成员
5.7 条件操作符
C++中唯一的三元操作符 cond?expr1:expr2; 尽量避免嵌套使用它,看着比较乱。 在输出时使用它时,通常必须用圆括号把条件表达式括起来
5.8 sizeof操作符
使用sizeof的结果部分依赖于其所涉及的类型。 对char 得1; 对引用类型 得盾防引用类型对象的内存空间大小;对指针 得存放指针所需的内存大小。 int s=sizeof(a)/sizeof(*a);a为一个数组,此表达式得到a数组的元素的个数
5.9 逗号表达式
从左向右计算,结果为其最右边表达式的值。 复合表达式的处理:若不明确顺序,则使用圆括号强制操作数的组合;若要修改操作数的值,则不要在同一语句的其他地方使用该操作数,但是一个字表达式修改操作数的值,可以再用于另一个子表达式。
5.11 new和delete表达式
表示动态创建和释放数组 注意:创建对象的时候不要忘了对其初始化。 int *p=new int ; 未进行初始化 int *p=new int ();初始化为0。 delete表达式释放指针所指向的地址空间,此地址空间必须是由new分配的内存地址。一旦使用了delete后,应立即将指针置为0,此时指针不再指向任何对象了。 const对象的动态分配和回收 不可以删除指向动态分配内存的指针,const对象的值不可以改变,但是可以撤销对象本身。例:const int *p=new const int(1); delete p;
第六章语句
6.1 简单语句
空语句;不可随意使用 while(condition);导致了空循环
6.3 复合语句
块标示了一个作用域。在块中引入的名字只能在该块中或嵌套在块中的字块里访问。 块语句可使用{}将语句序列括起来,此时并不是以;结束语句序列的。
6.4 语句作用域
while(condition)condition条件中就是检验其初始化对象的值,一个控制结构里引入的名字为局部变量,其作用域为语句内部
6.5 if语句
如果在条件表达式中定义了变量,则必须对变量进行初始化。 只有IO类型可用作条件,vector string类型均不可以用作条件。 if语句后有多个操作时,注意{}的使用。 if和else的使用,一般如果没有{}和else if与之匹配,则会将else匹配给最后出现的尚未匹配的if子句
6.6 switch语句
每个case标号的值都必须是一个常量表达式。 注意break的使用,若省略了break,则允许程序向下执行多个case标号。 default相当于else子句的功能,考虑一切其他情况。 若要为某个case引入变量,则可引入块语句。
6.7 while语句
训话条件自身或者在循环体内必须做一些相关操作来改变循环条件表达式的值。 *dest++=*source++; 等效于 {*dest=*source; ++dest; ++source;}
6.8 for循环语句
形式for(initializer;condition;expression)statement. 若不需要初始化,则可以省略init-statement; 若省略condition,则等效于循环条件永远为true; 若省略expression,则必须用break或return语句来跳出循环
6.9 do while循环
它保证循环体至少执行一次 dostatement while(condition);condition为假时结束循环
6.10 break语句
结束最近的while 、do while、for、switch语句。跳出当前的循环语句,执行该语句的下一语句,只出现在循环或switch语句中
6.11 continue语句
导致最近的循环语句的档次迭代提前结束,进行下次循环。 只能出现在for、while或do while循环中
6.12 goto语句
函数内部的无条件跳转 goto label; label用于带标号的语句的标示符。 标示符和冒号得带符号的语句,用作goto的目标 例如 begin: goto degin;。 一般不适用goto语句,因为它使得控制流混乱
第七章:函数
7.1 函数的定义
实参个数必须和形参个数完全形同、类型匹配。函数定义或声明时必须显式指定返回类型。 两参数类型相同时,必须重复声明,指明其类型。
7.2 参数传递
当实参副本初始化形参时,函数没有访问调用所传递的实参本身,因此不会修改实参的值。(仅在局部函数内改变了值) 将形参定义为引用或者指针类型,则实参得以复制,这样可以通过改变引用或指针来修改实参的值。 指向指针的引用*&a,指针的值可以改变,则指向的对象随之变化。 注意数组形参的定义,访问时注意越界问题。(用null或指针标志结束)
7.3 return语句
return;表示返回空。 当有返回类型(非void)时,return后需加表达式来确定返回值的类型,需与函数定义时一致。 但是允许main函数没有返回值就结束。不可以返回局部对象的引用和指针,因为函数执行完毕后,会释放对象空间或指向空。 对于递归调用,必须定义一个终止条件(main函数不可以进行递归调用)
7.4 函数声明
函数声明包括:函数返回类型、函数名、形参列表。这三要素,也描述了函数的接口。 定义函数的源文件应包含声明该函数的头文件(。可检查定义、声明是否一致) 设计带有默认实参的函数,则要排列形参(使实参传递时有次序)
7.5 局部变量
自动对象是当定义它的函数被调用时才存在的对象。 在每次调用时穿件和撤销。但是对于静态局部变量static,一旦创建,则不会被撤销。
7.6 内联函数
关键字inline,它不写成函数形式,节省内存。而且对于多处使用时,容易修改(只修改定义处即可)。 内联函数在程序中定义可能不止一次???
7.7 类的成员函数
成员函数:在类内定义/在类外定义,类内声明。 this初始化为调用该函数的对象的地址。 构造函数和类同名,且没有返回值,放在类中的public中。 默认构造函数:全局/静态局部对象:初始化为0;局部对象:无初始化。
7.8 重载函数
函数名相同,但形参不同。 main不可重载。 费引用形参中,形参和const形参时等价的,都改变不了对象值。重载函数也是局部有效性的,所以每次使用时要注意声明它。 匹配结果:最佳、无、二义性(多个匹配)。 找不到精确匹配时,可以进行实参的类型转换。 如果形参是普通的引用,则不能将const对象传递给这个形参。如果传递了const对象,则只有带const引用形参的版本才是该调用的可行函数。但是非const对象既可以用于初始化const引用,也可以初始化非const引用。
7.9 函数指针
此指针指向函数,它也有其确定的类型,即为函数的返回类型。 只能通过同类型的函数或函数指针或0值常量表达式进行初始化或赋值。
第八章:标准IO库
8.1面向对象的标准库
父类:基类。继承而来的类为派生类。他们是继承关系。 通过继承关联起来的来行都共享共同的接口,可以额使用通的操作。 一个父类可以有多个派生类, 一个派生类也可以由多个父类。 如果函数有基类类型的引用形参时,可以给函数传递其派生类的对象。 流对象是不能复制的,因此不可以 存储在Vector中。 形参或返回类型也不能对流对象。 非const引用方式可以传递流对象。
8.2条件状态
流必须处于无错误状态才可以用于输入或输出。故必须检查其真值if(cin),while(cin>>word)。 bad fail eof 相当于非操作,结果为good值,若三个中有一个true,则为错误状态,若全部为true,则good为true状态。 rstate得到的返回值对应于流当前的整条状态。 可以用按位或来处理状态值。 setstate开启流中badbit failbit位(is.setstate(ifstream::badbit|ifstream::failbit))
8.3 输出缓冲区的管理
导致缓冲区内容被刷新:1 程序正常结束,便要清空所有的缓冲区;2 缓冲区满啦,再写下一个数据之前,要刷新缓冲区;3 endl 显示刷新缓冲区;4 unitbuf操纵符设置流的内部状态;5 将输出/输入关联起来,读输入流时刷新关联的输出缓冲区。 endl flush ends+null都可以刷新输出缓冲区。 若要刷新所有的输出,最好使用unitbuf。 将cout cin 绑定,则就是将输入和输出绑定了,刷新一起。
8.4文件的输入和输出
open close ifstream读 ofstream写 fstream读写同一文件。 检验文件打开if(!infile)。 若要打开一个文件,则先close当前文件,在open要打开的文件。 打开重用已存在的流对象,则while先关闭close和清空,while(it!=file.end())。 文件模式也是整型常量。 以out模式打开的文件会被清空,丢失该文件存储的所有数据。 以in out同时打开的文件则不会被清空(怎么可能同时打开呢??)
8.5字符串流
fstream sstream共享相同的基类,但他们没有其他相互关系(???)。 输入:getline()一行 string操作符stream>> while(getline(cin,line)) while(stream>>word) string stream提供自动转换或格式化 char int
第九章循序容器
顺序容器:将单一类型元素聚集起来成为容器,然后根据位置来访问这些元素。 适配器:为原始容器类型提供的,通过当以新的操作接口,来适应基础的容器类型。
9.1顺序容器的定义
将一个容器复制到另一个容器时,类型必须要匹配,容器类型和元素类型都必须相同。vector<int>ivec;vector<int>ivec2(ivec);实现了将容器ivec复制给ivec2. 传递一对迭代器实现容器内部元素的复制,其中迭代器标记处范围。 list<string>slist(svec.begin(),svec.end());将svec中从svec.begin()到svec.end())的所有的元素有复制给slist. 初始化时指定数目的元素 const list<int>::size.type, list_size=64; list<int>ilist(list_size);64个元素的值全为0。但是它只适用于vector容器,不适用于关联容器。
9.2迭代器和迭代器范围
关系操作符只适用于vector deque容器,因为他们可以为其元素提供快速、随机的访问。关系操作符必须在同一容器中对相同类型的元素进行。 end指向最后元素的下一个元素。last指向最后一个元素。当last=first时表示此迭代器是空的。erase函数会使已删除的迭代器失效。
9.3顺序容器的操作
interator:此容器类型的迭代器类型。reference:元素的左值类型。 c.push_back(t)在容器C的尾部添加t个元素,并返回void类型。 复制元素到容器内,只是副本,容器内的元素改变,原本不会发生改变。 循环调用push_front函数,表示依次往前插入输入的数据。 在vector容器中添加元素,可能导致整个容器的重新加载,使迭代器失效。 不要存储由end返回的迭代器类型,若将last=c.end();则last会失效。 c.back C.front 或c.begin ()c.end()-1都可以获得第一、最后的元素。 pop_front pop_back函数的返回值并不是删除元素的值,而是void。 c.erase(p)返回被删除元素后面的元素的迭代器。 slist.clsar()等效于slist.erase(slist.begin(),slist.end());删除了slist容器内的全部元素。 list<string>::interator e1,e2; ie1=find(slist.begin(),slist.end(),vall); ie1得到一个第一个元素是vall的迭代器。 assign和赋值效果一样,均为先删后添加元素。而swap直接交换,容器内的元素未动,所以迭代器是不失效的。
9.4vector容器的自增长
capacity获取容器需要分配更多的存储空间之前能够存储的元素总数。 reserve高速vector应预留多少个元素的存储空间。
9.5容器的选用
vector deque插入时要移动多个,而list快速,但是deque在首部insert erase很快。vector deque都可以实现快速的随机访问。
9.6谈string类型
string类型不支持以栈的方式操作的容器,故不可以使用front back pop_back。 添加元素时不可以使用push_front(),因为它只适用于list deque. string不支持带有单个容器长度作为参数的构造函数。 (下面的赋值语句基于s2="hello.world"pos2=len2=2)string s(s2,pos2); 则s2=llo.world . string s(s2,pos2,len2);s=ll. s.append(args)将串接在s后面,并返回s的引用。
9.7 容器适配器
让一种已存在的容器类型采用另一种不同的抽象类型的工作方式实现,例:
stack<int>stk(deq) 将元素从deq复制到stk.
第十章:关联容器
关联容器和顺序容器的本质差别:关联容器通过键存储和读取元素,顺序容器则通过元素在容器中的位置顺序存储和访问元素。Set是大小可变的集合
10.1 引言 :pair类型
Pair<T1,T2>P1(v1,v2);两个元素分别是T1,T2类型,其中first初始化为v1,second初始化为v2。
当需要定义多个相同的pair类型对象时,注意利用typedef来简化其声明。
对于pair类,可以直接访问数据成员,用.操作符即可。
10.2 关联容器
遍历关联容器时,可以确保案件的额顺序访问元素,与元素在容器中存放的位置没有关系。
关联容器不提供assign函数。
关联容器不能通过容器的大小来定义。
10.3 map类型
map对象的定义:map<stringint>word_count//定义一个word_count的map对象。由string类型的键索引,关联的值则为int型。
对于键类型唯一的要求就是必须支持<操作符。
给map添加元素添加键-值对,有两种实现方法。可以用insert成员实现,或者,先用下标操作符获取元素,然后给获取的元素赋值。
使用下标访问map与使用下标访问数组或vector的行为截然不同;用下标访问不存在的元素将导致在map容器中添加一个新的元素,它的键即为该下标的值。
查找并读取map中的元素:用下标操作符,是一种比较简单的方法,但是该方法有副作用,就是当该键不在map容器中,那么下标操作会插入一个具有该键的新元素。
map容器提供了两种操作:count和find。
m.count(k)返回m中k的出现次数,对于map对象只能是1或0,而对于mutimap容器,则可能会出现更多的值。
m.find(k) 返回按k索引返回的迭代器。count方法用于在map中查找指定键是否存在的问题,而find方法适合用于解决在map容器中查找指定键对应的元素的问题。
从map对象中删除元素:m.erase(k)删除m中键为k的元素。返回值为被删除元素的个数,对于map容器而言,其值必然是0或1。
m.erase(p)从m中删除迭代器p所指向的元素。返回值为void类型。m.erase(b,e)从m中删除一段由一对迭代器范围的元素。返回值为void类型。
10.4set类型
map容器是键-值对的集合,而set容器只是单纯的键的集合。当只想知道一个值是否存在时,使用set容器是最合适的。
Set容器是可以使用push_back迭代器的。
Set与map的区别
Set不支持下标操作符
而且没有定义mapped_type 类型。
在 set 容器中,value_type 不是 pair 类型,而是与 key_type 相同的类型。它们指的都是set 中存储的元素类型。这一差别也体现了set 存储的元素仅仅是键,而没有所关联的值。
与 map 一样,set 容器存储的键也必须唯一,而且不能修改。
10.5multimap 和multiset类型
multiset 和 multimap 类型则允许一个键对应多个实例.
multiset和 multiset 所支持的操作分别与 map 和 set 的操作类似。但multimap 不支持下标运算。因为某个键可能对应多个值.
Lower_bound返回的迭代器不一定指向拥有特定键的元素。但若果该键在容器中,则lower_bound返回在保持容器元素顺序的前提下该键应被插入的第一个位置。