书籍:C++ Primer,C++标准程序库,Effective C++,C++ concurrency in Action, Linux多线程服务端编程,The Design and Evolution of C++
C++基础:
1. 读取数量不定的输入数据
while(std::cin >> value)
while内返回其左侧运算符,即std:cin, IO库定义了从istream向bool转换的规则,当istream作为条件时,效果是这个流的状态,有效输入(未遇到错误),条件为真,无效输入(读到文件尾,不是value变量类型), 条件为假
键盘输入文件结束符,unix Ctrl+D
2. C++是静态数据类型的语言,它的类型检查发生在编译时,编译器必须知道程序中每一个变量对应的数据类型
3. 避免依赖于实现环境的行为,比如不同机器上int类型的尺寸可能是不一样的
4. 切勿混用带符号类型和无符号类型,计算前带符号数会自动转换为无符号数,如果计算结果为负数会出现错误
5. 'A' 表示单独字符,"A" 表示字符数组A,编译器会在字符数组末尾添加空字符' '
6. 列表初始化,如果丢失精度,编译器会报错
7. 内置类型变量在函数内(包括main),如果不初始化是未初始化值,在所有函数外面,如果不初始化会采用默认初始化。其他类型变量如果有无参构造函数,无论函数内外均采用默认构造函数。
8. 引用不是一个对象,它是对象的别名,定义时必须初始化,且只能与相同类型的对象bind,字面值和表达式不行
但const引用是个例外,(1)在初始化常量引用时允许用任意表达式作为初始值,只要该值可以转换为引用的类型 (2)允许为一个常量引用绑定非常量的对象,字面值或表达式
9. 指针就是一个对象,实现了对其他对象的间接访问,空指针用nullptr或字面常量0来初始化,int *ptr中的*是类型修饰符,int是基本数据类型
10. 如果想在多个文件之间共享const对象,必须在变量的定义和声明之前添加extern
11. 常量表达式由数据类型和初始值共同决定。constexpr的变量类型便于由编译器来验证变量的值是否是一个常量表达式,声明为constexpr的变量一定是一个常量且必须用常量表达式初始化。
constexpr int * ptr 表示顶层const,常量指针与指针所指对象无关,constexpr修饰的指针所指的对象必须是存在固定地址中的变量(定义于所有函数体之外的对象),或者为nullptr或0
12. 类型别名:typedef 关键字声明,新标准可以用using关键字定义类型的别名 using SI=Sales_item;
13. 某个类型别名指代的是复合类型或常量,声明语句里需要特别注意,不能尝试把类型别名直接替换成它本来的样子,这里的基本数据类型是指针,是顶层const表示常量指针
typedef char *pstring; const pstring cstr = 0; // cstr是指向char的常量指针 const pstring *ps; // ps是一个指针,它指向的对象是指向char的常量指针
14. auto 定义的变量必须有初始值,编译器通过初始值来推断变量类型
15. auto一般会忽略顶层const,同时底层const会保留
const int ci = 2,&cr = ci; auto b = ci; // b是一个整数(ci的顶层const被忽略) auto c = cr; // c是一个整数(cr是ci的别名 ci的顶层const被忽略) auto d = &ci; // d是一个指向整型常量的指针(对常量对象取地址是一种底层const)
16. decltype 选择并返回表达式(操作数)的数据类型,与auto处理顶层const不同,如果表达式是一个变量,decltype返回该变量的类型(包括顶层const和引用在内)
int i=42, *p = &i, &r = i; decltype(r) b; // 错误,b是一个int & 且这里必须要初始化 decltype(r+0) c; // c是一个未初始化的int decltype(*p) d; //错误,c是int &,必须初始化
注意,指针解引用decltype的结果类型为引用类型int&,不是int,可以这样想,解引用指针可以得到指针所指的对象,而且还能给这个对象赋值。
注意,decltype((variable)) (注意是双层括号)的结果永远是引用,单括号时只有 variable 是引用时才是引用
注意,赋值会产生引用,引用的类型就是左值类型,i是int,表达式i=x的类型是int &,decltype(a=b)这里a的值没有赋值为b(即不实际计算表达式),只是返回对应的左值引用类型
17. 防止多次包含相同头文件出错,应该设置头文件保护符,#define指令把一个名字设定为预处理变量,#ifdef和#ifndef检查这个指定的预处理变量是否已经定义,当检查为真,则执行后续的操作直到#endif为止
18. string size函数的返回类型为string::size_type, 无符号类型的值,注意不应该和带符号数混用
直接初始化和拷贝初始化:如果使用=初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右边的初始值拷贝到新创建的对象中去,与之相反不使用=,则执行直接初始化
19. 使用C++版本的C标准库头文件,因为在名为cname的头文件中定义的名字从属于命名空间std,而定义在名为.h的头文件中不是
20. range for 遍历 string
for(auto c : str) // 这里的每次迭代将str的下一个字符拷贝给c cout << c;
如果要改变字符串中的字符则需使用引用 auto &c。 更多的是使用下标运算符随机访问,它返回的是字符的引用
21. 迭代器,end尾后迭代器指尾元素的下一个位置,如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器
泛型编程,推荐使用迭代器遍历元素!=尾后迭代器的形式,因为并不是所有容器都可以用下标索引
cbegin和cend的迭代器类型是const_iterrator常量迭代器不能修改指向的元素
凡是使用迭代器的循环体,不要向迭代器所属的容器添加元素
22. 字符数组,当用字符串字面值初始化字符数组时,字符串结尾的空字符' '会占一位,const char a[6]="Demian"; // 错误,没有空间存放空字符
数组下标的类型是size_t, 机器相关的无符号类型,也可以使用range for遍历
定义在iterator头文件中的begin和end函数可以用来获取相应的数组首元素和尾后元素指针
cstring中的C风格字符串函数,传入此类函数的指针必须指向以空字符作为结束的数组
string转C风格字符串c_str()返回const char * 类型的指针
用range for处理多维数组,除了最内层的循环外,其他所有的循环的控制变量都应该是引用类型,否则编译器初始化这些变量时会自动将这些数组形式的元素转换成指向该数组内首元素的指针
23. m%n不等于0,那么结果的符号和m相同。/则不同,结果异号为负
24. 逻辑运算采用短路求值的策略,几个关系运算符不能连起来写,进行比较运算时,除非比较的对象是bool,否则不要使用bool字面值true/false作为比较对象,因为true为转换为对应非bool类型出现错误
25. 除非必须,否则不用递增递减运算符的后置版本,因为后置版本需要将原始值存储下来,如果不需要修改前的值就是一种浪费,对复杂的迭代器类型消耗巨大。
必须使用的case: *ptr++的优先级等价于*(ptr++),先+1,然后返回原始值的副本解引用
26. 处理复合表达式:(1)拿不准时最好用括号强制表达式的组合关系符合逻辑 (2)如果改变了某个运算对象的值,在表达式的其他地方不要再使用这个运算对象(否则可能会产生未定义的结果)
27. 建议仅将位运算符用于处理无符号类型,运算对象为“小整型”会自动提升为较大的整数类型
28. sizeof返回一条表达式或一个类型名字所占的字节数,利用数组大小除以单个元素大小可以得到数组元素个数
29. 逗号表达式,首先对左侧表达式求值后将结果丢弃,真正的结果是右侧表达式的值
30. 隐式类型转换被设计的尽可能避免损失精度
无符号类型不小于带符号类型,那么带符号的运算对象转换成无符号的,如果为负数,那么取模后变成正的,这大多数不是想要的结果。如果带符号类型大小大于无符号类型则无符号类型需要转换
大多数表达式中,比int类型小的整型值首先提升为较大的整数类型
31. 强制类型转换: 尽量避免强制类型转换的使用 cast_name<type>(expr)
dynamic_cast: 支持运行时类型识别
static_cast: 任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast
const_cast: 只能改变运算对象的底层const,去掉const,不能改变表达式类型
reinterpret_cast: 有点复杂,不了解直接用很危险,它通常为运算对象的位模式提供较低层次上的重新解释(把运算对象的内容解释成另外一种类型),但存储的真实类型不变
int *ip; char *ip = reinterpret_cast<char*>(ip); // 这里pc所指的真实对象仍然是int而不是字符
32. P147 4.12 运算符优先级表
33. 右值: 指一种表达式,其结果是值而非值所在的位置
左值: 指求值结果为对象或函数的表达式,强调的是位置,并且可以作为被赋值
34. 在if、switch、while和for语句的控制结构内定义变量,定义在控制结构当中的变量只在相应语句内部可见,语句结束,变量也超出其作用范围了
35. if 或 else (while 或 for 同理) 之后必须写上花括号,确定的语句块可以避免代码的混乱不清
36. switch 中的 case标签必须是整型常量表达式,如果是char将隐式转换为整型然后和case标签比较
37. throw抛出异常将终止当前的函数,并把控制权转移给能处理该异常的代码,寻找本层的catch语句块,否则回溯上一层的catch语句块,最后没有找到的话会转到名为terminate的标准库函数,导致程序非正常退出
38. 在头文件中声明变量和函数,在源文件中定义
局部静态对象,可以将局部变量定义成static类型,在程序执行路径第一次经过对象定义语句时初始化,直到程序终止才消毁,在此期间即使对象所在的函数结束执行也不会对它有影响,其结果等于函数上一次退出时的值,如果局部静态变量没有显式的初始值,内置类型的局部静态变量初始化为0
39. 函数的传值参数,指针形参,因为指针也是变量其行为和其他非引用类型一样,当执行指针拷贝操作时,拷贝的是指针的值,拷贝之后,两个指针是不同的指针虽然指向相同的对象,当在函数内改变改指针变量时,只会改变这个局部拷贝值,不会改变实参指针的内容,当然间接指向的对象可以通过函数改变。
40. 引用形参可以避免拷贝,有的类型不支持拷贝操作时函数只能通过引用形参访问,当函数无须改变引用形参的值,最好将其声明为常量引用
尽量使用常量引用,非常量引用会极大地限制函数所能接受的实参类型,不能把const对象,字面值或者需要类型转换的对象传递过来
用实参初始化形参时会忽略顶层const,类比通用初始化规则,这里是拷贝操作不是引用
const int ci=42; int i = ci; // 忽略顶层const
41. 数组两个性质: (1)不允许拷贝数组 (2)使用数组时通常会将其转换成指针(指向数组首元素的指针)
42. 将多维数组传递给函数时,真正传递的是指向数组首元素的指针,
int *matrix[10]; // 10个指针构成的数组 int (*matrix)[10]; // 指向含有10个整数的数组的指针(多维数组) // 等价定义 void print(int matrix[][10], int rowSize) {} // 以数组的语法定义函数形参,此时编译器会忽略掉第一个维度,最好不要把它写在参数列表中,实际形参是指向含有10个整数的数组的指针
43. 含有可变形参的函数:
实参数量未知但全部实参的类型相同,可以使用initializer_list类型的形参,这是一种标准库类型,其元素是常量值,实参写在花括号里完成调用 p198
省略符形参,仅仅用于C和C++通用的类型,大多数类类型的对象在传递给省略符形参时都无法正确拷贝
44. 函数返回值:返回一个值的方式和初始化一个值的方式是一样的,返回值用于初始化调用点的一个临时量。分为值拷贝和引用这两种形式
不要返回局部对象的引用或指针,函数终止,它们将指向不再有效的内存区域
引用返回类型返回左值,其他返回类型返回右值。特别的可以为返回是非常量引用的函数结果赋值
列表初始化返回值:
vector<string> process() { // ... return {"functionX", "OK"}; }
45. 返回数组指针P205,数组不能被拷贝,所以函数不能返回数组。
(1) 使用类型别名,typedef或using
(2) 声明返回数组指针的函数,数组的维度必须跟在函数的名字后面 int (*func(int i))[10];
(3) 尾置返回类型,auto补位,eg:auto func(int i) -> int(*)[10];
(4) decltype, decltype的结果是个数组,并不负责把数组类型转换为对应的指针,返回指向数组的指针需要在函数名字前加*
46. 函数重载:不允许两个函数除了返回类型外其他所有的要素都相同,编译器可以自动地选定唯一的最佳函数(最少的类型转换次数)作为被调用的函数,如果没有会报错(未匹配或二义性)
47. constexpr函数不一定返回常量表达式
48. 函数指针在调用该函数时,无须提前解引用指针