C++prime 第五版
第一章 开始
- 1.5.1节
介绍Sales_item
类时,讲了文件重定向,vscode利用windows输入addItems <infile >outfile
会出错,因为Powershell只支撑>
不支持<
,需要使用get-content
,因此全部输入为:get-content ./data/book_sales |./test2.exe >./data/book_sales_out
第一部分
第二章 变量和基本类型
-
基础内置类型:如int、char等,复合类型:指针、引用等。
-
顶层const:自身无法改变,顶层const对任意数据结构都适用,但顶层const指针可以指向任何数据(常量或非常量),但自身无法改变。
-
底层const:与指针和引用等复合类型的基本类型部分有关,底层const“自以为”指向顶层const。常量引用都是底层const,底层const指针和底层const引用都可以指向(绑定)非常量,该非常量可以通过其他方式修改,但无法通过底层const指针和底层const引用修改.(书上形象的说法是,底层指针和引用“自以为是”,以为自己指向的是常量,所以不能通过自己修改它)。
-
底层const无法被忽略。
-
类型别名:两种方法,1、
typedef double wages
,给double指定别名wages。2、别名声明using SI = sales_items
。
const和类型别名一起出现会产生意外的后果,例如:typedef char *pstring; const pstring cstr = 0; // cstr是指向char的常量(空)指针 const pstring *ps; // ps是一个指针,指向指向char的常量指针。(即指向常量指针的指针)
可以看出
const pstring
与const char *
声明的结果不同,前者是常量指针(top-const),后者是指向常量的指针(low-const)。原因是const是修饰数据类型的限定符,pstring是字 符指针类型,而char是字符型,后面的*
是作为声明符的一部分。因此从右往左解读,前者是一个常量指针,后者是一个指向常量char的指针。 -
c++的声明由基本数据类型和声明符组成。声明符可以被类型修饰符
*或&或const
修饰。&
定义引用,因为引用只是作为别名,所以必须有初始值。常量也必须有初始值,并且之后无法改变。 -
c++中初始化和赋值是两个不同的操作,声明和定义也不同,想要声明而非定义使用关键字
extern
。 -
auto类型说明符:auto定义必须有初始值,因为auto根据初始值类型判断表达式数据类型,并且auto会忽略顶层const,保留底层const。
-
decltype类型指示符:
decltype(f()) sum = x;
sum的类型就是f返回值的类型,decltype根据表达式的类型作为变量的类型,但并不实际计算表达式。只有在decltype中引用才作为引用出现,其他时候只是作为别名。另外decltype(*p) c; decltype((i)) c;, decltype(a = b) d;
等也会被判断为引用,及指针解除引用、变量双括号、赋值语句。可能是因为表达式的结果可以作为赋值语句的左值,与引用类似。。。。 -
decltype不会忽略顶层const。
-
任何时候,在一条语句中声明多个变量,该语句所有的变量初始数据类型必须一致。如:
auto int i, *pi = i;
-
算数类型、指针和引用都属于字面值类型,自定义类、IO库、string类不属于字面值类型,所以不能被定义成constexpr。
第三章 字符串、向量和数组
-
windows的文件结束标记是
ctrl+z+ENTER
; -
范围for循环,
for (declaration : expression) statement
,想要修改内容时,declaration需要声明一个引用,遍历多维数组时,除了最后一层循环,其他循环也必须声明引用(常量或非常量),否则编译器会把数组名当作指针而无法进行下层循环,使用范围for循环时不能向vector添加元素; -
using namespace std
,头文件不要使用using; -
string
标准库类型,表示可变长的字符序列,is >> s
或getline(is, s)
读取,内置函数empty、size。size返回的类型是string::size_type
的无符号整型; -
vector
:类模板(class template)、容器,对象的集合且所有对象类型相同,无法定义引用的vector,根据模板创建类(或函数)的过程叫做实例化(instantiation); -
vector可以进行拷贝初始化,即使用一个vector给另一个类型相同的vector赋值,使用'()'初始化时,只给定数目会进行值初始化;
-
vector的操作包括:
push_back(t)
,将元素压入vector的尾部,empty
、size
等与string类似; -
使用成员函数或者标准库函数
begin(); end();
可以获得容器或数组的迭代器,获得数组迭代器(指针)时需要传递数组名作为参数,begin获得指向第一个元素的迭代器,end获得指向最后元素的下一位置,及尾后(off the end); -
使用迭代器时,也不应该改变vector的容量,否则迭代器失效;
-
迭代器可以进行算数运算,
++/--
,+/-n
,i1 - i2
,但无法进行加法,因为没有意义。减法的返回值类型是对应的difference_type
, 对数组来说是ptrdiff_t
,与size_t
一样,定义在头文件cstddef
中; -
可以显示的获得常量迭代器
v.cbegin(); v.cend();
, 箭头运算符v->men
合并了解引用和点运算符操作(*v).mem
; -
大多数容器没有下表运算符,因此只支持
==/!=
运算,而string、vector可以进行</>
等运算,因此c++应该习惯使用!=
; -
必须用常量表达式定义数组,
int a = 5; int arr[a];
是错误的,指针数组int *p[10];
,数组指针int (*p)[10];
,数组引用int (&r)[10]
; -
字符数组的操作与标准库类型string不同,必须使用
cstring
中定义的函数,strlen, strcmp, strcpy, strcat
等; -
ia是数组名,
auto ia2 = ia;
得到的ia2是一个指针,decltype(ia) ia3;
得到的ia3是一个数组; -
标准库类型的下标是无符号整型,而内置的下标无此要求,可以为负值;
-
可以使用数组初始化vector,
vector<int> ivec(begin(int_arr), end(int_arr));
,但不可以使用vector或数组初始化数组; -
多维数组的数组名可以认为是指向指针的指针,可以通过类型别名使程序略微简洁,
using int_4array = int[4];
或typedef int int_4array[4];
,这样将四个整数组成的数组,命名为int_4array; -
应该优先使用标准库类型string和vector,之后在考虑内置的底层替代品数组或指针;
Q:
- 不改变外层vector的大小,只改变内层vector的大小时,外层的遍历是否可以使用迭代器或范围for循环?
第四章 表达式
-
优先级、结合律、求值顺序,其中只有逻辑与
&&
,逻辑或||
,条件运算符?:
以及逗号运算符规定了求值顺序,
; -
左值(lvalue):左值使用的是对象的身份(内存中的位置), 右值(rvalue):右值使用的是对象的值(内容);
-
除了导致溢出的情况外,
(-m)/n
和m/(-n)
都等价于-(m/n)
,m%(-n)
等价于m%n
,(-m)%n
等价于-(m%n)
,因为前者余数一定为正,后者余数一定为负(如果存在余数,且余数的符号与被除数相同情况下); -
只读不写时声明成常量引用可以避免拷贝;
-
sizeof运算符有两种形式,
sizeof(type)
和sizeof expr
,前者判断类型,后者判断表达式,sizeof满足右结合律,并且sizeof不实际计算表达式,因此在sizeof中解引用nullptr不违法。 -
类型转换 隐式转换:大多数表达式中,比int小的整型值首先提升为较大的整数类型;条件中,非布尔值转换成布尔值;初始化中,初始值转化为变量的类型,赋值时,右侧转化为左侧类型;算数运算或关系运算的运算对象有多类型,则需要转化为同一种类型,并且算数类型会尽可能避免精度损失。
-
类型转换 其他隐式:1.数组转化成指针,当作为decltype关键字参数、取地址符
&
、sizeof及typeid等运算符的运算对象时,不会有该转换;2.指针转换,nullptr可以转换成任意类型,指向任意非常量的指针可以转换成void *
;指向任意对象的指针可以转换成const void *
,继承关系时的另一种指针转换在15.2.2节;3.转换成布尔类型,算数或指针转布尔,非0为true,0为false;4.转换成常量,及指向常量的指针或引用可以指向非常量,int i; const int */& j = (&)i; // 非常量转换成const
,反过来则不行,int *p = j;
;5.类类型定义的转换,例如string str = "xiaoma";
字面字符串转换成string类型,while(cin >> s)
,cin转换成布尔类型; -
类型转换 显式转换:1.
static_cast<type> (expression);
不包含底层const的都可以使用static_cast。2.const_cast<type> (expr);
只能改变对象的底层从const。3.reinterpret_cast
,为运算对象的位模式提供较低层次上的重新解释,非常危险;4.dynamic_cast
将在19.2节介绍。 -
运算符优先级:
::
>.
,->
,[]
,()
等成员选择、下标、函数调用、类型构造 (前面全部左结合) > 后置递增、递减,typeid,explicit cast > 前置递增、递减,位求反~
,逻辑非!
,一元 正负号,解引用*
,取地址&
,类型转换(type)expr
,sizeof (前面全部右结合) > 乘除法、取模 > 加减法 > 左右移 > 小于大于等关系运算符 > 等于不等于 > 位与 > 位或 > 逻辑与 > 逻辑或 (前面全部左结合) > 条件 > 赋值 >+=
,*=
等复合赋值 > throw抛出异常 (前面全部右结合) > 逗号运算符,
(左结合)
第五章 语句
-
复合语句(块),用花括号括起来的语句和声明序列,一个块就是一个作用域;
-
else与离他最近的if匹配,可以使用花括号控制执行路径;
-
switch的case标签必须是整型常量表达式,switch会从第一个满足条件的case的第一条语句运行直到遇到break或switch的结尾, 因此可以让多可值共享同一组操作;
-
因为c++不允许跨过变量的初始化语句直接跳转到该变量作用域内的另一位置,因此应该把变量定义在块内,从而确保后面所有case标签都在变量作用域之外;
-
try-catch和throw,throw抛出异常,
try{program-statements} catch (exception-declaation){handle-statements}
,try可以跟多个catch。 -
抛出异常后没找到对应的catch或没有try语句且发生异常时,程序调用terminate非正常退出;
-
异常安全(exception safe),异常发生期间正确执行了“清理”工作的程序,经验表明这非常困难;
-
标准异常:exception是所有异常的父类,stdexcept定义了几种常见异常,
range_error
,overflow_error
,underflow_error
等属于runtime_error
,domain_error
(参数对应的结果值不存在),invalid_argument
,length_error
,out_of_range
等属于logic_error
。new头文件定义了bad_alloc
,type_info
头文件定义了bad_cast
。 -
异常类型只有一个what的成员函数,如果异常有一个字符串初始值,what返回该字符串。而bad_alloc,bad_cast,exception等不允许提供初始值的异常,what返回的内容由编译器决定。
-
vscode使用g++编译器,
throw runtime_error(s); catch(runtime_error err){}
会出现错误,只能使用throw s; catch(const char* msg){}
抛出一个字符串异常,catch捕获字符串异常。前者出错原因未知。。