- 第1章 开始
- 第2章 变量和基本类型——第Ⅰ部分 C++基础
- 2.5 处理类型
- 2.6 自定义数据结构
- 第3章 字符串、向量和数组
- 3.4 迭代器iterator介绍
- 迭代器运算
- 3.5 数组
- 3.6 多维数组
- 第4章 表达式
- 4.3 逻辑和关系运算符
- 4.4 赋值运算符
- 4.5 递增和递减运算符
- 4.6 成员访问运算符
- 4.7 条件运算符
- 4.8 位运算符
- 4.9 sizeof运算符
- 4.10 逗号运算符
- 4.11 类型转换
- 第5章 语句
- 5.4 迭代语句
- 5.5 跳转语句
- 5.6 try语句块和异常处理
- 第6章 函数
- 6.2 参数传递
- 数组形参
- main:处理命令行选项
- 含有可变形参的函数
- 返回类型和returnyuju
- 返回数组指针
- 6.4 函数重载
- 6.5 特殊语言特性
- 6.6 函数匹配
- 6.7 函数指针
- 第7章 类
第1章 开始
不重要的地方或者已经非常熟悉的知识我不做记录,只记录新知识和难以理解的地方以便回顾
习题答案见https://github.com/huangmingchuan/Cpp_Primer_Answers
开发环境准备
尝试过vscode虽然截图编译插件很好用但是格式化和智能提示实在是难以忍受,还是换IDE了
最好还是用VS或clion写大型项目,编译起来不需要配置,文件组织也更合理
目前使用Clion,使用bundled mingw编译,cmake组织项目;Clion安装插件一定要APPLY,否则不生效
(VS编译器是微软自带,项目组织方式也不尽相同,调试最好还是VS)
- Clion乱码情况https://blog.csdn.net/Cbk_XLL/article/details/78752534
1.2 输入输出
库定义如下4个标准IO对象
| cin | 标准输入 |
|---|---|
| cout | 标准输出 |
| cerr | 标准错误 |
| clog | 标准日志 |
- 输出运算符<<
左侧是ostream对象,右侧为要输出的内容,返回值为左侧对象;输入运算符>>与其类似
所以cout<<”Hello World!”<
标准库对其进行过重载,以适应不同的输入输出值类型
- 控制符endl
首先结束当前行,然后将缓冲区中的东西刷入设备(刷新流可以清空缓冲区,避免不必要的错误)
建议调试输出时使用printf,cout如果不刷缓冲区的话不会立即输出!!!
练习
1.3
1.4
1.5
1.6
所给程序片段不合法;
原因:<<输出运算符左侧必须是ostream对象,而第二、三行<<左侧均没有cout
1.7
1.8
第一、二行语句显然合法;
第三行
可以看到编译器识别出一对/ /注释,而后面的内容当做字符串常量处理,少了一个”,则报错missing terminating “ character
第四行代码会输出/,而前后均为/ */注释
1.9
sum of 50 to 100 inclusive意思是求和区间为[50,100]的闭区间
1.10
1.11

当然上面程序健壮性不足,如果不按提示要求,输入a>b则达不到想要的结果(书上没看到if,姑且不优化了)
1.4 控制流
while和for循环语句
循环中若不停检测变量并递增,使用for循环会更方便
读取数量不定的输入,如输入int至变量int value,可以写while(cin>>value){…}
使用istream对象作为条件,实际是检测对象状态,如果碰到无效输入或文件结束符end-of-file,循环会自动结束
- Unix MacOS为CTRL+D
-
练习
1.12
此for循环完成了从-100到100的整数累加的功能,sum为和,结果自然为0
1.13
即使用for循环重做1.9-1.11三道题
1.9重做:
1.10重做:
1.11重做:
1.14
区别主要是:
while循环难以控制计数变量的作用域,循环体外依然有效;而for的控制变量一般只在循环内可见
- while更适合循环结束条件未知的场景(包括死循环和输入数量不定);for一般用计数器迭代,循环次数已知
1.16
if语句
练习
1.17
1.19
1.5 类简介
Sales_item类
例程为定义一个Sales_item的类,表示书的总销售额、售出册数和平均售价;
操作为
练习
1.21
1.22
1.23&1.24
1.6 书店程序

第2章 变量和基本类型——第Ⅰ部分 C++基础
C++由于和硬件高度相关,没有规定数据类型必须使用多少位存储,使用时需要依据机器和编译器灵活判断;
2.1 基本内置类型
算术类型
一些建议:
- 数确定不为负,使用无符号类型
- 整型要么char,要么int,更大用long long
- 浮点型都用double
- bool=true or false
类型转换

含有无符号类型的表达式
带符号数会自动转换成无符号数,负数转unsigned会加上无符号数的模
//在32位系统中,int的范围是-2147483648~+2147483647,而unsigned int的范围是0~4294967295
例:
练习
2.3

从上到下分别应输出
32
4294967264= 4294967296 - 32
32
-32
0
0
字面值常量literal
整型默认int,浮点型默认double,字符型为char;
字符串为C风格数组,实质是const char*
2.2 变量
列表初始化

四种初始化方式中,只有列表初始化在丢失信息时会报错,如long double值给int初始化
默认初始化
练习
2.9
(a)应该为int input_value; cin>>input_value
(b)不符合初始化语法,见列表初始化
(c)wage未定义 
(d)语法没问题,只是i将初始化为3,小数点后截断
2.10
global_str为空串
global_int为0
local_int为随机值
local_str为空串
变量声明与定义
加extern为声明,其他全为定义,会分配存储空间和初始化
//extern初始化同样是定义
练习
2.11
标识符
字母、数字、下划线组成,字母、下划线开头;
- 自定义不允许连续两个下划线
- 不允许下划线+大写字母开头
- 全局变量不能以下划线开头
练习
2.12
(a)(c)(d)非法,不能含连字符,不能数字开头,不能用关键字
名字作用域
练习
2.13
2.14
2.3 复合类型
引用
引用/左值引用即别名,和对象绑定而不是拷贝;必须初始化
一般来说,引用的类型和绑定的对象应一致
练习
2.15
2.16
都合法,只是有些副作用
(b)会执行int到double的隐式转换
(c)(d)小数点后会截断
2.17
指针
类似于内置类型,指针存地址,解析取决于指向类型;
一般情况也要类型匹配

无效指针(包括未初始化的)不要使用,可能指向危险地址;空指针初始化为nullptr,NULL和0相同,不过新标准还是用nullptr;
void*指针无法访问其指向对象,操作有限;
指针比较的是存的地址;只要不为空指针,指针的值就不是0
练习
2.20
2.21
(a)非法,类型不匹配;(b)非法,int不可以初始化int*,即使int=0;(c)合法
2.25
(a)ip是int型指针,i是int型变量,r是i的引用
(b)i是int型变量,ip是int型空指针
(c)ip是int型指针,ip2是int型变量
2.4 const限定符
const默认作用域为当前文件;
共享需要在声明和定义时都加extern
练习
2.26
(a)(d)非法,const常量必须初始化,不可被改变;(b)(c)合法
const引用
初始化常量引用时允许用任意表达式作初始值,只要能转换为引用类型即可
const引用本质是不允许通过引用改变引用对象的值,而引用对象是不是const无关紧要
比如此处不可以通过ri改变dval的值
但是常量只可以用const引用,因为非const引用可能改变常量的值,C++将此视为非法
指针和const
const指针与const引用类似
(指针或引用自以为是,它们觉得自己指向了常量)
指针常量指the pointer itself is const
语法是把*放在const之前,表明指针将不会改变指向
但是能否通过指针改变对象值完全取决于对象是否是const,和指针常量无关
练习
2.27
(a) 不合法,引用不能引用字面值
(f) 不合法,引用本身就和对象绑定,引用默认就是引用常量,& const不是合法声明
其他语句均合法
2.28
(a)(b)(d)均不合法,指针常量和const常量没有区别,必须初始化
(c)不合法是因为ic是const常量没有初始化
(e)合法,const指针不是常量不需要初始化
2.29
(b)不合法,不可以绕道通过p1修改p3指向的对象,禁止非const指针赋值给const指针
(c)不合法,非const指针不可以指向const常量,否则可以通过指针修改const常量,而这不符合设计要求
其他语句合法
顶/底层const
顶层const表示指针、算术类型、类本身是常量;
底层const表示指针、引用指向的对象是常量

指针可以是顶层const也可以是底层const;
赋值操作时,两者需要具有相同的底层const(=忽略顶层const),或数据类型可以相互转换;一般来说就是非const可以赋值const,反之则非法。
练习
2.30
v2 是顶层const,p2 是底层const,p3 既是顶层const又是底层const,r2 是底层const。
2.31
constexpr和常量表达式
编译过程中可得到定值才是常量表达式;
- 用常量表达式初始化的const变量、字面值也是常量表达式
函数返回值和非const对象都不是常量表达式(constexpr函数除外)
constexpr
如果认定变量或函数是常量表达式,就声明为constexpr型;有点像const的加强版
算术类型、引用、指针可定义为constexprconstexpr指针初始化值必须是常量
- 如nullptr、0、地址不变的对象
- 函数内部变量地址一般都会变,不可以初始化constexpr指针,而全局变量可以
-
练习
2.32
不可以用int值初始化int指针,要么强制转换int为int*,要么用字面值0或nullptr或NULL
2.5 处理类型
类型别名
typedef/#define

但是typedef数组的别名真的难理解/反人类。。- using newname = oldname

指针、引用等的别名见到const之后要注意理解
不能理解为
const是对给定类型的修饰,pstring是指向char的指针,那么const pstring就是指向char的指针常量,也就是pstring不变/指针不变
不可以像宏一样简单替换
auto类型说明符
- auto一般忽略顶层const,保留底层const
- 需要顶层const时需要写const auto或者使用auto引用
auto引用会给出引用对象的类型,此时不会忽略顶层const了 - 一行声明中变量类型应相同

练习
2.35
decltype
decltype不会真的执行()中的表达式或函数,只是推断
- decltype(variable)
当变量是引用时返回引用,否则变量是什么就返回什么(包括顶层const和引用) - decltype((variable))
把(variable)当表达式,为特殊左值,返回引用 decltype(expression)
当表达式是左值时,返回引用,如指针解引用,否则返回表达式结果类型练习
2.36
2.37
!赋值操作符会返回左值引用
一种解释是对象在赋值的时候,返回引用比返回临时对象开销要更小,效率更高
所以本题中int a=3; int b=4; int c=3; int &d=a=32.38
2.6 自定义数据结构
C++中的 struct 和 class 基本是通用的,唯有几个细节不同:
使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
- class 继承默认是 private 继承,而 struct 继承默认是 public 继承
- class 可以使用模板,而 struct 不能
练习
2.39

提醒类或结构体定义后加分号
头文件保护符习惯性加上即可
#ifndef
#define
…
#endif
第3章 字符串、向量和数组
//
3.2 标准库类型string
定义和初始化string对象
标准定义了很多初始化方式,十几种,常用的如下:
上述初始化方法第4和第5应该还是保留了字符串字面值尾部的’\0’(虽然书上说没有);
size函数返回size_type类型,由于是无符号数,尽量不用int;
练习
3.2
3.3
string输入运算符碰到空白字符会截断,包括制表符、空格和换行符;
getline只会截止在换行符
3.4


处理string对象中的字符

去掉.h前面加c的头文件是C++从C继承来加上namespace的头文件;
范围for语句可以遍历容器中每一个元素,但是只访问一部分元素时不太好用了
练习
3.6
3.7
3.8
3.9
合法,输出s第一个元素;但没有意义,不显式初始化的string里面存的可能是垃圾值
https://github.com/huangmingchuan/Cpp_Primer_Answers/tree/master/ch03但是这个人说不合法,书上说C++标准不要求检测下标,而且实际使用中是可以这么写的虽然很危险
3.10
3.11
除循环内代码合法,c是const char&,因为不会允许通过c改变const string中的元素
3.3 标准库类型vector
vector包含对象类型都相同,是一个类模板;
尖括号中类型帮助编译器进行实例化(instantiation),且必须指定类型(引用除外)
定义和初始化vector对象

方法4、5是值初始化,6、7是列表初始化;
如果元素类型不支持默认初始化,那不能用5这种方法;
列表初始化会尽量被当成元素值的列表,如果无法这么做,会用给的值构造重复元素然后默认初始化
只有v5是列表初始化,v7,v8都和()初始化等价,被当作构造vector对象的参数而不是元素值
练习
3.12
(a)(c)合法,(b)类型不同的vector不可以拷贝初始化
3.13
(a)无元素;
(b)10个0
(c)10个42
(d)1个10
(e)10和42
(f)10个默认string(空串)
(g)10个hi
向vector对象中添加元素
事先指定vector大小可能导致性能更差,应该运行时动态添加元素;
范围for不要改变容器大小
练习
3.14

3.15略,改变类型即可

由于vector是模板,尖括号指定类型才和类起相同作用,所以第二种写法不合法;
元素类型可以比较时才可以使用<、>等比较运算符;
下标只可以访问已有元素,如果访问不存在的元素将导致错误,但是编译器发现不了,如:

程序返回代码为异常
练习
3.17


第一行乱码可能是编码问题,按链接https://www.zhihu.com/question/386494355更改设置后,编码正常
(一开始还以为是vector里面有乱码,还调试了半天gdb版本。。。蠢死;除了注释最好还是别用中文了)
3.18
合语法但是很危险,应当用ivec.push_back(42)
3.19
3.20
3.4 迭代器iterator介绍
类似访问容器元素的指针,有效指[start, finish]区间的迭代器,其他位置属于无效;
begin在首元素,end在尾元素的后一位;如果容器空,则begin=end;
end比较特殊,不指向具体元素,不能解引用或加减;
迭代器属于iterator和const_iterator类型,后者相当于const指针;
无论元素是否为常量,cbegin和cend都返回常迭代器;
!循环里若使用迭代器,不要改变容器容量(如添加元素),否则迭代器会失效!
练习
3.23
迭代器运算
it1-it2返回it2要向前移动到it1的步数(difference_type类型,可正可负),当然前提是同一容器的迭代器;
迭代器在前面的大,在后面的小;此处的前后、大小以end>begin为参考;
练习
3.24
3.25
3.26
3.5 数组
数组长度是constexpr;不允许auto数组,需要指定类型;不存在引用的数组;
标准不允许数组拷贝
复杂数组声明
包括指针的数组、数组的指针、数组的引用
最佳阅读顺序是从内到外然后再从右到左
练习
3.27
(a)(c)(d)非法,buf_size不是常量表达式;txt_size()返回值也不是constexpr;字符串字面值末尾有’\0’,st大小至少为12
访问数组元素
练习
3.30
3.31
3.33
不初始化的时候数组/局部变量里面是垃圾值,达不到统计分数段的目的
指针和数组
数组名在初始化表达式中自动隐式转换为首元素地址的右值——wiki
auto会把数组名认为是指针,而decltype会返回真正的数组类型
左值、右值
标准库函数begin和end
与容器成员函数类似,但不是成员函数,接收数组作参数;
end()-begin()返回值为ptrdiff_t类型,带符号且与机器相关;
- 数组下标可以为负数,意思是向后偏移,如:

f[-1]其实就是x[10-1],不过这种写法不好理解
-
练习
3.34
3.35
3.36
比较数组需要自己编写每个元素逐个对比的方法(包括两个数组的首尾指针);
比较vector可以用封装的==操作符C风格字符串
不是一种类型;一般用指针操作

这些函数都不验证字符串末尾是否有空字符,因此胡乱使用很危险 strcmp相等返回0,p1>p2返回正数,p1<p2返回负数
- 连接、拷贝需要正确计算字符串大小,极容易出错
练习
3.37

很明显,*cp是空时循环才会结束,而ca并不是以空字符结尾的字符串,指针会操作未知地址,所以输出的字符为垃圾值3.40
与旧代码的接口
- 允许C风格字符串初始化string;甚至提供c_str()返回string的C风格字符串;


- 允许数组初始化vector
3.6 多维数组
其实是数组的数组;
多层范围for遍历多维数组时,除了最内层,其他层都应该声明为auto引用,否则会造成不合理的遍历指针
练习
3.43

3.44

改动不大,主要是外层循环看起来简洁一些3.45
把int_array换成auto,尤其是范围for会更简洁第4章 表达式
4.1 基础
左值右值
左值可以放在=左边,但const对象不能作为左值;
有些表达式结果为对象,但却是右值;
总结: 对象右值是内容,左值是内存中的位置;
左值可以代替右值,反之则不行;
左值当右值用的时候,用的是内容
- =左边需要左值,且返回左值
- &作用于左值,却返回右值(地址)
- []和*都返回左值
- ++、—作用于左值,前置的返回也是左值
- decltype(expression),如果表达式是左值(非变量)则返回引用
是右值则正常返回操作符的结果类型
&操作符的结果是指针右值
很困惑的一点是,取地址运算符&返回指针右值,猜测也是这样,不然不会有int *p=&i的写法;
在标准中找到&的定义
可以看到确实是返回指针类型,那以后记住就好
优先级和结合律


- 表中靠前的优先级高,同一组内优先级相同
算术运算符和成员访问都是左结合,还有逗号,其他大多都是右结合
练习
4.1
4.2
求值顺序
和优先级、结合律无关;
如
这样的表达式
函数调用顺序一般由编译器决定4.2 算术运算符
对象和结果都是右值(当然前面提过,左值可以代替右值)
- 会进行隐式类型转换
尤其是bool会转成int,因此不要用于参与运算
- 新标准商一律舍弃小数部分

m - (m/n) * n = m % n
按照这个方程求模;如果m%n不为0,那么结果符号和m相同练习
4.4
4.5
4.6
4.7
当计算的结果超出该类型所能表示的范围时就会产生溢出。
这里仅给一个例子,无符号数0强行减一会变成unsigned int的模
4.3 逻辑和关系运算符
运算对象和返回值都是右值;
||和&&出现短路求值的现象,当第一个开关断开时,后面的表达式不再进行计算;练习
4.9
cp为字符串字面值的地址,不会为0;
*cp是’H’字符,也不为0;
所以此条件为true4.10
4.12
大于小于优先级大于判等;
j4.4 赋值运算符
=左侧是可修改的左值,结果是左侧类型的左值;
右侧对象会转成左侧类型;
允许列表初始化
左侧为内置类型时,列表只能包含一个值,且不可以往小类型转换;
=优先级非常低,一般加个括号;
复合赋值只计算一次,在重载操作符和操作类对象时开销较小;练习
4.15——解惑
ival = pi一部分非法,int*不可以用来初始化int

标准对=运算符有解释,右侧必须可以隐式转换为左侧
pi的值为16进制地址,可能这是无法隐式转换的原因
地址太长转为int损失了精度,经测试转为较大的long long类型可以通过
4.5 递增和递减运算符
作用于左值,前置返回对象本身左值,后置返回对象原值副本的右值;
一般不要用后置版本,除非需要同时递增和使用原值;后置++优先于解引用*练习
4.19
4.6 成员访问运算符
*低于点,最好加括号;
->作用于对象指针,返回左值
-
练习
4.20
(b)(c)(e)不合法,string没有自增操作,而且(c)这里不是想使用迭代器的成员函数empty(),应该加括号
4.7 条件运算符
即condition?expr1:expr2;
当两个表达式都是左值或可以转换成同一种左值时,返回左值;否则返回右值;
最好别嵌套;
优先级很低,仅高于=,记得加括号;
练习
4.21
4.22
4.23
4.8 位运算符
作用于整形对象或bitset;
尽量只用于无符号类型;
^异或:不同为1,相同为0; <<左移在右侧补0;>>右移在左侧补符号位或0;
练习
4.25
4.27
4.9 sizeof运算符
返回操作对象所占字节数,返回constexpr size_t类型
- sizeof(type)
- sizeof expr返回表达式结果类型的大小
练习
4.28
4.29
4.30
sizeof优先级高于算术运算符,(a)(c)在运算符左边加括号即可
4.10 逗号运算符
练习
4.31
此处变为后置即可,由于是for循环的第三个迭代语句,不会产生副作用
4.32
含义即遍历数组:ix与size组合和ptr与ia组合一样,都是确定边界的作用
4.33
(someValue ? ((++x), (++y)) : (—x)), (—y)
前置自增自减优先级最高,先加一层括号;
?:优先级高于逗号,逗号前再加一层括号
4.11 类型转换
隐式转换——算术类型

- 小整数如果可能都提升为int,否则提升为unsigned int
-
练习
4.34
(a)float转int
(b)int转float,float转double
(c)char转int,int转double4.35
其他隐式转换
数组转换成指针
decltype、&、sizeof、typeid例外- 指针转换
0、nullptr能转换成任意指针;
非const指针可以转换为void;
非空指针可以转换为const void -
显式转换
格式:cast-name
(expression) cast有四种:
static_cast 、 dynamic_cast、const_cast、reinterpret_cast- type是引用时结果是左值?

- static_cast本质上是传统c语言强制转换的替代品
不包含底层const都可以使用
编译通过,自己负责精度损失 - const_cast改变对象的底层const
消去j底层const
加上x底层const - reinterpret_cast
reinterpret_cast的例子
补充——ASCII码对照表
第5章 语句
5.3 条件语句
if嵌套时会发生if多于else的情况,C++规定else与离他最近且未被匹配的if匹配——悬垂else;
switch-case中的case必须是整型常量表达式;
不可以在一个被跳过的case中定义将会使用的变量练习
cin >> std::noskipws >> ch这种输入方式可以让输入流不跳过空白字符5.13
(c)unsigned evenCnt = 0, oddCnt = 0;int digit = get_num() % 10;switch (digit) {case 1: case 3: case 5: case 7: case 9://不可以写成case 1,3,5,7,9:oddcnt++;break;case 2: case 4: case 6: case 8: case 0:evencnt++;break;}
5.4 迭代语句
while语句
练习
5.14


多写这种没有使用高级数据结构的代码有助于培养机器思维传统for循环
for循环第一部分只能声明多个相同类型的变量练习
5.17
范围for语句
遍历的对象必须可以返回begin和end迭代器;
如果更改容器元素个数,范围for预存的end()将失效练习
5.19
5.5 跳转语句
练习
5.20
5.21
5.22
do{int sz=get_size();}while(sz<=0);
5.6 try语句块和异常处理
runtime_error对象的what()返回异常对象的初始化字符串标准异常

- exception、bad_alloc、bad_cast对象只能默认初始化
- 其他异常对象必须用字符串初始化
如果初始化了,what()返回初始化字符串;否则返回值由编译器决定
练习
5.23
5.24
5.25


throw后面的语句不会执行;
try里面还是要写throw;
catch尽量写异常引用,不用复制异常对象
第6章 函数
6.1 函数基础
练习
6.4
6.5
6.6
6.7
6.10
6.2 参数传递
总结使用引用传值的场景和优势:
- 直接使用参数对象,无需拷贝,尤其是大类型或不支持拷贝的类型
- 返回多个参数可以在参数中加引用影响外部变量
练习
6.15
不需要改变s引用的对象所以s最好是const引用,而occurs用来存储字符位置,所以不能是const引用;
s为引用可以避免拷贝(尤其是s过大的时候),occurs是引用或指针才可以更改实参(引用更简洁),c不需要声明为引用;
s如果为普通引用则可能更改实参(不希望看到),occurs需要修改值不能为const引用6.16
参数改为const引用:
此函数不修改参数,最好是const引用
此函数可以接收const string,但普通引用不可以引用const对象,所以此时必须是const6.17


数组形参
由于数组名字被当作指针,数组长度函数未知,即使明确指出,编译器也只检查类型;
管理方法有以下几种:
- C风格字符串碰到空字符结束
- 标准库启发:传递首指针和尾后指针
-
练习
6.21
6.22


即使传入指针,实参内存地址也不会改变,除非使用二级指针或指针的引用↑6.24
输出数组ia前十个元素,可能导致越界,因为形参中的数组大小不起作用,ia如果只有5个元素还是可以传进函数,但会越界
main:处理命令行选项
int main(){...}int main(int argc, char *argv[]){...}
程序处理的参数从argv[1]开始,argv[0]是可执行文件的名字
练习
6.25
含有可变形参的函数
实参类型相同,传递initializer_list类型
- 实参类型不同,写可变函数模板
- 省略符形参主要用于和C的接口

初始化列表也是模板类型,类似vector;
list中的元素是常量,只可读不可写
练习
6.27
6.28
其实elem不带const的话,auto推断出来也会是const string&类型,因为列表元素都是常量
返回类型和returnyuju
- 不要返回临时对象的指针或引用

返回临时对象或变量是可以的,但会导致拷贝开销,尽管有时不可避免
- return引用则返回左值,否则返回右值
练习
6.30

最下面的是本题的报错:return语句没有值(应该为bool);for循环可能执行完,此时不写return会返回垃圾值(见习题6.6)返回数组指针
主要有三种写法:
- type ( *function(p-list) ) [dimension]


比较好理解的一种改善是数组使用别名,如typedef int arr[10]将arr声明为int [10]
- 尾置返回类型

一般用于返回类型比较复杂的函数 - decltype(数组名) *
要注意decltype不会把数组名当指针练习
6.36 & 6.37
```cpp string (& fun()) [10];//6.36
using string[10] = str_array;//最好的别名 //或者typedef string str_array[10]——太反人类 str_array & fun();//别名返回数组引用
auto fun() -> string(&)[10];//尾置类型返回数组引用 //还是尾置比较好理解
string arr[10]; decltype(arr)& fun(); ```
6.4 函数重载
- 重载时形参的顶层const不区分
fun(int)和fun(const int)被视为相同的参数列表 区分底层const,涉及到类型转换
非const实参会优先选择非const形参进行匹配
此时可以用到const_cast6.5 特殊语言特性
默认实参
一个作用域中同一个函数一个形参只能拥有一个默认值

constexpr函数:
- 返回值和形参必须是字面值
- 有且仅有一条return
- 隐式指定为inline
- constexpr不一定返回常量表达式
尽管有红线,编译器并没有报错
字面值类型!


算术类型、枚举、指针、成员指针以及这些类型的const、volatile类型都算是标量/scalar type
调试帮助
6.6 函数匹配
参数类型转换
第二点指非const指针或引用转换成const指针和引用(加上底层const)
练习
6.52
(a)两个char转int,属于第3级
(b)两个double转int,属于第4级
6.53
6.7 函数指针
用指针替换函数名即可,类似于指向数组的指针,函数名也被当作指针处理;
typedef函数指针和数组别名一样,比较反人类;


decltype(函数名)也返回函数类型,需要加上*才是函数指针
练习
6.54
6.55 & 6.56
第7章 类
第2章提过类,13章讲对象的行为包括复制、移动、销毁等,14章讲运算符重载;
类的用户应该是调用类的程序员,而程序的使用者是屏幕前的人;
7.1定义抽象数据类型
设计Sales_data类

- 成员函数声明在内部
若定义在外部需要加类作用域::
定义在内部为隐式inline
实际上是通过this指针隐式访问对象,this是个指针常量 - 函数声明()后加const为常量成员函数
实际是给this加上底层const,因为不允许显式定义this 常量对象、常引用、常指针只能调用const成员函数,因为不允许通过调用改变对象
练习
7.4 & 7.5

应该定义为const成员函数,因为这些函数对对象都是只读不写定义类相关的非成员函数
用到类的非成员函数概念上属于类的接口,但实际不属于类本身;
一般定义到头文件
IO类不支持拷贝,只可以使用引用
不加#include,<<无法重载 练习
7.9
构造函数
有一些情况编译器无法生成默认构造函数,如数据成员包含另一个没有默认构造函数的类对象;
-
练习
7.13
7.15
拷贝、赋值和析构
复制/拷贝构造函数调用场景:
- 用同类对象初始化另一个对象
- 形参是对象,函数传参时调用
- 返回值为函数局部临时对象
7.2 访问控制与封装
class第一个访问符之前默认为private;
struct默认为public;
还有继承时的访问权限,除此之外没有不同友元

函数是类接口的一部分但不是成员函数,且需要访问私有成员时,可以写为友元函数7.3 类的其他特性
可变数据成员
可变数据成员一直可以改变,即使是const对象、const成员函数;
指明数据成员为mutable
返回*this的成员函数
- const成员函数如果返回引用,则*this是const引用,不可以改变对象
- const成员函数谁都可以用,但非const成员函数只有非const对象才可以用
前向声明
在创建对象之前必须有类的定义;但类的名字出现之后就被认为声明过;
类的成员类型不可以是自己,但可以是同类的指针或引用
因为不知道类的成员,所以这个类不完整
练习
7.31
必须有Y的前向声明,否则会报错
友元类
除了非成员函数定义为友元还有两种友元:
- 声明友元类
友元类的成员函数可以访问此类的所有成员
友元类无传递性 - 声明已定义类的成员函数
加上类作用域,当然这个类需要定义过
重载的函数需要一一声明
友元声明和作用域——易错
友元函数最好是类内声明,类外定义;
即使是类内定义,类外也需要写一次,否则作用域只限于类内,外部不可见
虽然友元函数实现了,但是构造函数中仍不可见,因为f声明在类后
即使实现在类内也一样对于其他函数不可见
7.4 类的作用域
名字查找和类的作用域
名字查找分三步:
- 当前块这一句之前找声明
- 找外层
- 没找到就报错
类定义分两步:
- 编译成员的声明
- 类全部可见后编译成员函数体
除成员函数外使用的名字都必须确保声明过

类内Money自然是double类型,而bal从类内先找声明,因此是double类型而非全局变量string类型
成员函数解析过程:
- 第一种普通情况


此时两者效果相同,但是初始化和赋值操作还是有差别,完全取决于成员类型,初始化一定是比赋值开销小的
- 第二种成员含const、引用和无默认构造函数的类对象


由于const和引用必须初始化,不允许声明后再赋值,所以必须要用初始值列表;
没有默认构造函数的类对象被当作成员时需要初始化,否则无法构造对象成员

B的构造函数是默认的,没有显式构造对象a
B构造函数需要构造自己的对象成员,尤其是那个成员没有默认构造函数时
成员初始化顺序
初始值列表只说明值,不指示初始化顺序;一般依据成员定义出现的顺序进行初始化;
i先声明,所以尽管列表中j写在前面,还是先初始化i,但是j的值未定义,可能会报错;
因此尽量避免用一个成员初始化另一个成员;
练习
7.36

和上面提到的例子相同,用了未定义的base初始化rem;
自定义类还是注意下顺序
7.37
本节定义参考如下

next默认初始化,bookNo为空串,其他为0;
last的bookNo为9-999-99999-9,其他为0;
两者都是用第一个构造函数
7.38
委托构造函数!
默认构造函数
内置类型和复合类型的成员只有在对象是全局作用域时才初始化,否则是垃圾值
默认初始化发生在:
- 局部作用域定义非静态对象或数组
- 类的成员有对象且使用默认构造函数
- 类的对象成员没有在初始值列表中显式初始化
值初始化发生在:
- 数组初始化列表中元素不够
- 局部静态变量
classname a()使用只含一个参数的构造函数请求值初始化
隐式的类类型转换
如果构造函数只有一个参数,又叫转换构造函数,实际上定义了内置类型转换为类的隐式规则;
explicit加在声明前可以阻止隐式转换,不过只能写在类内声明前,定义的时候不要再重复explicit;
explicit只能用于直接初始化,拷贝初始化/=都不行;
编译器只会执行一步隐式类类型转换
string接收const string*的构造函数不是explicit
所以可以=初始化- vector接收容量的构造函数是explicit
所以没有下面的写法
练习
7.49

注意非const引用不可以绑定临时对象!
- 以引用的方式传递一个临时变量做为函数参数,如果函数内部对此临时变量做了修改,那么函数返回后,程序员无法获得函数对临时变量的修改。函数对临时变量所做出的所有更改,都将丢失
- C++中产生的临时对象是不可修改的
默认为const的非常量引用必须绑定左值,非const引用只能绑定到与该引用同类型的对象,而const引用则可以绑定到不同的但相关的对象或绑定到右值
非const不可以转换为const类型 -
7.51
vector
v=1;
这种写法不清楚是初始化容量还是元素值,禁止这种二义性,(所以我猜)v的单参构造函数是explicit
聚合类
全public
- 无构造函数
- 没有类内成员初始值
- 没有基类、没有virtual函数
这样的类可以用初始值列表初始化

如果表里元素不够,剩下的被值初始化
字面值常量类
7.6 类的静态成员
- 属于类不属于对象
- 不能为const,不能使用this
- static只出现在类内部的声明
- 必须在外部定义和初始化静态成员
为保证只定义一次,最好static和非内联函数的定义放一起类内初始化
非常量静态成员不可以类内初始化
如果是常量表达式则可以类内初始化
const还可以类内声明类外定义
静态数据成员可以是不完全类型(声明且没有完整定义);
静态成员可以作默认实参;非静态成员属于对象,作参数不知道属于哪个对象会报错































提前写了个递归。。




这样写数组的引用只可以传入大小为10的数组









报错

