C++ Primer学习笔记
本文主要是读C++ Primer,记录一些没关注到的细节,或者没学到的东西。
第一章 开始
除了cin cout,还有cerr输出警告和错误信息,clog输出运行时一般性信息。
endl
结束当前行,将与设备关联的缓冲区刷到设备中。保证到目前为止所产生的所有输出都写入到输出流中,而不是停留在内存中等待写入流。
拓展:缓冲区又称为缓存,内存空间的一部分。在内存空间中预留了一定的存储空间缓冲输入或输出。CPU可以直接读取内存而不是从磁盘 中读,减少磁盘读写次数,提高运行速度。
全缓冲:标准IO缓存满之后才实际进行IO操作;
行缓冲:遇到换行符才之际IO操作;
不缓冲:如stderr。
可以使用flush endl ends来刷新缓冲区。
第二章 变量与基本类型
char
是否带符号由编译器决定。其他字符类型可以用扩展字符集,如wchar_t
,char16_t
,char32_t
。其中wchar_t
可以确保存放最大扩展字符集的任意一个字符,16和32则为unicode服务。
初始化和赋值是完全不同的操作。初始化是赋予初始值,赋值则是先擦除在用新值替代。
列表初始化:用花括号初始化,如下:
1 | int a = 0; |
如果初始值可能丢失信息,则用列表初始化时候编译器会报错。
默认初始化:对于函数体内部的内置类型变量不被初始化,函数外的变量初始化为0。每个类各自决定初始化的方式。
c++支持分离式编译,因此定义和声明需要区别开来。声明使得名字为程序所知道,定义创建与名字关联的实体。
如果想声明而非定义,则用extern
并且不显式初始化。变量只能被定义一次,但可以被多次声明。
建议:在第一次使用变量前定义它。这样方便好找。
引用创建了对象的别名,定义时候会和初始值绑定在一起,无法重新绑定到另一个对象。
所有指针都应该初始化,而且应当在定义了对象之后再定义指向它的指针。
void*
指针可以存放任意对象的地址,但不能操作。可以进行函数的比较,或者赋值给另一个void*
。也可以强转类型后操作。
1 | int *p1, p2;//只有p1是int*类型 p2是int |
默认状态下,const对象只在文件内生效。如果需要共享,则extern const来使用。
无法通过const引用来改变值,但可以通过其他引用改变值。
引用类型必须与所引用对象类型一致,除非是字面值。
const在前,是指向常量的指针。const在后,是常量指针。前者无法通过该指针改变变量值,后者无法改变指针的指向,也就是地址不变。
constexpr常量表达式指可以在编译过程中得到计算结果的表达式。函数体外定义的变量地址不变,能用来初始化constexpr指针。这时候相当于地址不变的常量指针。
可以用typedef和using来声明类型别名。
auto通常会忽略顶层const(通道)保留底层const。需要顶层的话则要显式声明。
引用用auto也需要显式声明。
decltype可以用来推断类型,而不计算值。
1 | int *p = 5; |
decltype在加括号和不加括号时候类型不同。
第三章 字符串 向量和数组
每个函数,变量名字都需要独立的using声明。头文件不应该包含using声明,避免冲突。
用等号执行的拷贝初始化,不用等号执行的直接初始化。
触发getline函数的换行符被丢弃掉了,得到的string不含换行符。
在cctype里面有各种处理字符的函数,比如ispunct(c)
检测标点符号。
列表初始化只能花括号,不能圆括号。
vector运行时应当能高效快速添加元素,因此设定大小没什么必要。
只能对已经存在的元素执行下标操作。
只需要读的话,可以用cbegin()
常量迭代器。
可以用箭头运算符和empty()
来检测是否为空。
1 | (*it).empty(); |
对容量的操作会导致迭代器失效。
除了方法,还可以用begin()
,end()
函数。得到尾后指针(指向并不存在的元素,位于容器尾元素的下一位置),可以以数组为参数。
两个指针相减得到ptrdiff_t
类型,和size_t
类似,但可能有负数。
第四章 表达式
除非比较对象是布尔类型,否则不要用true
,false
来进行比较。
字面值是右值,算数表达式是右值。
除非必须,否则不要用递增递减运算符的后置版本,由于需要保存原始值,因此产生不必要浪费。
sizeof
存在两种形式,求类型的大小和求表达式结果的类型大小。
1 | sizeof expr; |
对数组求sizeof会得到整个数组所占空间的大小,对string或者vector则只返回固定部分大小。
隐式转换:比int小的整型值提升到较大的整数类型,在条件中非布尔值转换为布尔类型,初始化时候转换成变量类型,右侧运算对象转换成左侧运算对象的类型。算数运算多种类型会转换成同种类型,函数调用会发生类型转换。
命名的强制转换:
1 | int j; |
第五章 语句
throw
表达式:用throw标识遇到了无法处理的问题,raise了异常。
try
语句块:用try处理异常,以try
开始,一个或多个catch
子句结束。抛出的异常会被catch处理。
异常类:用于在throw表达式和catch子句之间传递异常的具体信息。
1 | try{ |
异常类:
1 | exception;//常见问题 |
异常类型有what()
成员函数,返回c风格字符串,提供文本信息。
第六章 函数
分离式编译:将.cpp文件编译成.o文件,然后将.o文件链接出可执行文件。如果需要更改,则针对更改了的文件编译一份.o文件再链接即可。
可以用引用作为参数来返回所需要信息。
尽量使用常引用。
不允许拷贝数组,数组作为形参会转换为指针。但可以以数组的形式标识参数。
argc标识数组中字符串的数量,argv表示c风格字符串数组。
如果实参数量位置但是全部实参类型相同,可以使用initializer类型形参,如下所示:
1 | void teststring(initializer_list<string> li){ |
省略符形参以及使用:
1 |
|
不要返回局部对象的引用或者指针。
返回值为左值的函数:
1 | char &getString(string& s, int index){ |
注意参数不能为常量引用,返回值也不能为常量引用。
return可以为大括号括起来的列表,注意返回值类型要能列表初始化。
main函数也有返回值,在<cstdlib>
中有定义了两个变量:EXIT_FAILURE
和EXIT_SUCCESS
。
声明返回数组指针的函数如下所示:
1 | int (*func(parameter_list))[array_len]; |
c11可以使用尾置返回类型,如下所示:
1 | auto func(int i) -> int(*)[10]; |
还可以用decltype来声明返回类型:
1 | int odd[] = {1,3,5,7,9}; |
引用和常引用是不同的参数,可以重载函数。
有一些编译器定义的常量字符串可以使用:
1 | cout<<__func__<<endl; |
对于函数指针而言,以下两种等价:
1 | p = func; |
返回值可以为函数指针,可以通过using来帮助定义。
第七章 类
可以在变量前加mutable,这样即使是const成员函数也能修改它。
返回*this
时候如果不返回引用,则会拷贝。
用赋值符号当构造函数的时候,不能赋值给常量和引用。
初始化列表顺序不确定。
c11有委托构造函数,可以用自己的其他构造函数来构造。
可以加explicit
来防止隐式转换。explicit
只应该出现在声明,出现一次就行。
聚合类:都是public,没有构造函数,没有类内初始值,并且没有virtual函数,可以用花括号初始化。
当类所有数据成员都是字面值,且包含constexpr构造函数,类内初始值没有或者是常量表达式,自带析构函数的可以成为字面值常量类。
第八章 IO库
IO库有三种:
1 |
|
IO对象没有拷贝和赋值。
io库有条件状态,如崩溃,操作失败,eof等。
程序崩溃则缓冲区不会刷新。
我们可以将一个istream关联到ostream上,如默认cin总关联到cout上。这样cin时候会刷新cout缓冲区。
文件流对象时候允许接收文件名字符串为构造参数,创建ifstream
并打开该文件。
如果定义了空文件流对象,可以随后用open关联到文件流。
流存在文件模式,如app
每次写之前均定位到文件末尾等。
第九章 顺序容器
迭代器范围是左闭合右开。
标准库array具有固定大小。
如果容器是vector或string,则存储空间重新分配后所有迭代器,指针,引用都失效。没重新分配则指向之后的迭代器,指针引用会失效。
deque插入首尾之外的位置都会失效。如果首尾,则只有迭代器会失效。
对于list和forward_list,添加元素都有效。
删除元素后,list和forward_list其他位置迭代器有效,deque则首尾之外全失效,删尾巴会尾后迭代器失效,其他不影响。删除首则都不影响。对于vector或string则之前的有效。
每次插入后重新调用end()
来便于不失效。
string有find
rfind
find_first_of
find_last_of
find_first_not_of
find_last_not_of
搜索操作。
stack
queue
priority_queue
属于适配器,可以接收已有的容器类型,也可以更改容器类型。
第十章 泛型算法
迭代器使得算法不依赖于容器,但算法依赖于元素类型操作。泛型算法只运行于迭代器之上,不会改变底层容器大小。
算法只读不改变元素,如find
,count
,accumulate
等。
equal
接收三个迭代器,第一个序列的头,第一个序列的尾,第二个序列的头(因为元素数量至少要一样多)。
fill
可以写入元素 ,但首先要创建元素。
unique
返回迭代器,不重复的在前面,重复的在后面。算法不增加删除,因此要在之后erase。
插入迭代器:可以用来插入元素。
流迭代器:绑定子啊输入输出流上,可以遍历关联的IO流。
移动迭代器:不是拷贝而是移动元素。
C++ Primer学习笔记