第1章 开始

不重要的地方或者已经非常熟悉的知识我不做记录,只记录新知识和难以理解的地方以便回顾
习题答案见https://github.com/huangmingchuan/Cpp_Primer_Answers

开发环境准备

尝试过vscode虽然截图编译插件很好用但是格式化和智能提示实在是难以忍受,还是换IDE了
最好还是用VS或clion写大型项目,编译起来不需要配置,文件组织也更合理
目前使用Clion,使用bundled mingw编译,cmake组织项目;Clion安装插件一定要APPLY,否则不生效
(VS编译器是微软自带,项目组织方式也不尽相同,调试最好还是VS)

cin 标准输入
cout 标准输出
cerr 标准错误
clog 标准日志
  • 输出运算符<<

左侧是ostream对象,右侧为要输出的内容,返回值为左侧对象;输入运算符>>与其类似
所以cout<<”Hello World!”<其中” … “为字符串常量,std::我已省略
标准库对其进行过重载,以适应不同的输入输出值类型

  • 控制符endl

首先结束当前行,然后将缓冲区中的东西刷入设备(刷新流可以清空缓冲区,避免不必要的错误)


建议调试输出时使用printf,cout如果不刷缓冲区的话不会立即输出!!!

练习

1.3

image.png

1.4

image.png

1.5

image.png

1.6

所给程序片段不合法;
原因:<<输出运算符左侧必须是ostream对象,而第二、三行<<左侧均没有cout

1.7

image.png
报错显示如下:
image.png

1.8

第一、二行语句显然合法;
第三行
image.png
可以看到编译器识别出一对/ /注释,而后面的内容当做字符串常量处理,少了一个”,则报错missing terminating “ character
image.png
第四行代码会输出/而前后均为/ */注释

1.9

sum of 50 to 100 inclusive意思是求和区间为[50,100]的闭区间
image.png
image.png

1.10

image.png
没什么特殊情况最好还是—num

1.11

image.png
当然上面程序健壮性不足,如果不按提示要求,输入a>b则达不到想要的结果(书上没看到if,姑且不优化了)

1.4 控制流

while和for循环语句

循环中若不停检测变量并递增,使用for循环会更方便


读取数量不定的输入,如输入int至变量int value,可以写while(cin>>value){…}
使用istream对象作为条件,实际是检测对象状态,如果碰到无效输入或文件结束符end-of-file,循环会自动结束

  • Unix MacOS为CTRL+D
  • Windows为CTRL+Z

    练习

    1.12

    此for循环完成了从-100到100的整数累加的功能,sum为和,结果自然为0

    1.13

    即使用for循环重做1.9-1.11三道题
    1.9重做:
    image.png
    1.10重做:
    image.png
    1.11重做:
    image.png

    1.14

    区别主要是:

  • while循环难以控制计数变量的作用域,循环体外依然有效;而for的控制变量一般只在循环内可见

  • while更适合循环结束条件未知的场景(包括死循环和输入数量不定);for一般用计数器迭代,循环次数已知

两者底层差别不大

1.16

image.png

if语句

练习

1.17

image.png
image.png

1.19

对练习1.11进行优化
image.png

1.5 类简介

Sales_item类

例程为定义一个Sales_item的类,表示书的总销售额、售出册数和平均售价;
操作为
image.png

练习

1.21

image.pngimage.png

1.22

image.pngimage.png

1.23&1.24

image.pngimage.png

1.6 书店程序

image.png

第2章 变量和基本类型——第Ⅰ部分 C++基础

C++由于和硬件高度相关,没有规定数据类型必须使用多少位存储,使用时需要依据机器和编译器灵活判断;

2.1 基本内置类型

算术类型

一些建议:

  • 数确定不为负,使用无符号类型
  • 整型要么char,要么int,更大用long long
  • 浮点型都用double
  • bool=true or false

部分整数类型的内存大小,我的机器应该是LLP64的标准
image.png

类型转换

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

练习

2.3

image.png
从上到下分别应输出
32
4294967264= 4294967296 - 32
32
-32
0
0

字面值常量literal

整型默认int,浮点型默认double,字符型为char;
字符串为C风格数组,实质是const char*


转义字符
image.png
image.png

2.2 变量

列表初始化

image.png
四种初始化方式中,只有列表初始化在丢失信息时会报错,如long double值给int初始化

默认初始化

内置类型函数外为0,函数内为垃圾值
对象默认值由类决定

练习

2.9

(a)应该为int input_value; cin>>input_value
(b)不符合初始化语法,见列表初始化
(c)wage未定义 image.png
(d)语法没问题,只是i将初始化为3,小数点后截断

2.10

global_str为空串
global_int为0
local_int为随机值
local_str为空串

变量声明与定义

加extern为声明,其他全为定义,会分配存储空间和初始化
//extern初始化同样是定义

练习

2.11

(a)定义
(b)定义
(c)声明

标识符

字母、数字、下划线组成,字母、下划线开头;

  • 自定义不允许连续两个下划线
  • 不允许下划线+大写字母开头
  • 全局变量不能以下划线开头

image.png

练习

2.12

(a)(c)(d)非法,不能含连字符,不能数字开头,不能用关键字

名字作用域

练习

2.13

j=100

2.14

输出100 55

2.3 复合类型

引用

引用/左值引用即别名,和对象绑定而不是拷贝;必须初始化
一般来说,引用的类型和绑定的对象应一致

练习

2.15

(b)(d)不合法,引用不可以绑定字面值,引用必须初始化

2.16

都合法,只是有些副作用
(b)会执行int到double的隐式转换
(c)(d)小数点后会截断

2.17

输出10 10

指针

类似于内置类型,指针存地址,解析取决于指向类型;
一般情况也要类型匹配


image.png
无效指针(包括未初始化的)不要使用,可能指向危险地址;空指针初始化为nullptr,NULL和0相同,不过新标准还是用nullptr;


void*指针无法访问其指向对象,操作有限;
指针比较的是存的地址;只要不为空指针,指针的值就不是0

练习

2.20

定义指针p1指向i;
通过指针使i平方一次

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无关紧要
image.png比如此处不可以通过ri改变dval的值
但是常量只可以用const引用,因为非const引用可能改变常量的值,C++将此视为非法
image.pngimage.png

指针和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表示指针、引用指向的对象是常量
image.png
image.png
指针可以是顶层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的加强版
    算术类型、引用、指针可定义为constexpr

  • constexpr指针初始化值必须是常量

  • 如nullptr、0、地址不变的对象
  • 函数内部变量地址一般都会变,不可以初始化constexpr指针,而全局变量可以
  • constexpr指针是指针常量

    练习

    2.32

    不可以用int值初始化int指针,要么强制转换int为int*,要么用字面值0或nullptr或NULL

    2.5 处理类型

    类型别名

  • typedef/#define
    image.png
    但是typedef数组的别名真的难理解/反人类。。

  • using newname = oldname
    image.png

指针、引用等的别名见到const之后要注意理解
image.png
不能理解为image.png
const是对给定类型的修饰,pstring是指向char的指针,那么const pstring就是指向char的指针常量,也就是pstring不变/指针不变
不可以像宏一样简单替换

auto类型说明符

  • auto一般忽略顶层const,保留底层const
  • 需要顶层const时需要写const auto或者使用auto引用
    auto引用会给出引用对象的类型,此时不会忽略顶层const了
  • 一行声明中变量类型应相同
    image.png

image.png

练习

2.35

image.png

decltype

decltype不会真的执行()中的表达式或函数,只是推断

  • decltype(variable)
    当变量是引用时返回引用,否则变量是什么就返回什么(包括顶层const和引用)
  • decltype((variable))
    把(variable)当表达式,为特殊左值,返回引用
  • decltype(expression)
    当表达式是左值时,返回引用,如指针解引用,否则返回表达式结果类型

    练习

    2.36

    image.pngimage.png

    2.37

    !赋值操作符会返回左值引用
    一种解释是对象在赋值的时候,返回引用比返回临时对象开销要更小,效率更高
    image.png
    所以本题中int a=3; int b=4; int c=3; int &d=a=3

    2.38

    image.png
    image.pngimage.png

    2.6 自定义数据结构

    C++中的 struct 和 class 基本是通用的,唯有几个细节不同:

  • 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。

  • class 继承默认是 private 继承,而 struct 继承默认是 public 继承
  • class 可以使用模板,而 struct 不能

    练习

    2.39
    image.png
    提醒类或结构体定义后加分号

头文件保护符习惯性加上即可
#ifndef
#define

#endif

第3章 字符串、向量和数组

//间接包含了,但是有保护符,不会重复包含;编译时可以-M查看包含的头文件

3.2 标准库类型string

其实是basic_string模板类

定义和初始化string对象

标准定义了很多初始化方式,十几种,常用的如下:
image.png


上述初始化方法第4和第5应该还是保留了字符串字面值尾部的’\0’(虽然书上说没有);
size函数返回size_type类型,由于是无符号数,尽量不用int;

练习

3.2

修改前image.png
修改后image.png

3.3

string输入运算符碰到空白字符会截断,包括制表符、空格和换行符;
getline只会截止在换行符

3.4

image.pngimage.png


处理string对象中的字符

中有字符操作和判断库函数
image.png
去掉.h前面加c的头文件是C++从C继承来加上namespace的头文件;
范围for语句可以遍历容器中每一个元素,但是只访问一部分元素时不太好用了

练习

3.6

image.png

3.7

结果没变,也就是说string元素还是char类型

3.8

image.png
如果遍历所有元素还是范围for更好用了,但是也不一定

3.9

合法,输出s第一个元素;但没有意义,不显式初始化的string里面存的可能是垃圾值
https://github.com/huangmingchuan/Cpp_Primer_Answers/tree/master/ch03但是这个人说不合法,书上说C++标准不要求检测下标,而且实际使用中是可以这么写的虽然很危险

3.10

用空白字符串接收处理原字符串的结果
image.pngimage.png

3.11

除循环内代码合法,c是const char&,因为不会允许通过c改变const string中的元素

3.3 标准库类型vector

vector包含对象类型都相同,是一个类模板;
尖括号中类型帮助编译器进行实例化(instantiation),且必须指定类型(引用除外)

定义和初始化vector对象

image.png
方法4、5是值初始化,6、7是列表初始化;
如果元素类型不支持默认初始化,那不能用5这种方法;
列表初始化会尽量被当成元素值的列表,如果无法这么做,会用给的值构造重复元素然后默认初始化
image.png
只有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

image.png
3.15略,改变类型即可


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

练习

3.17

image.pngimage.png
第一行乱码可能是编码问题,按链接https://www.zhihu.com/question/386494355更改设置后,编码正常
(一开始还以为是vector里面有乱码,还调试了半天gdb版本。。。蠢死;除了注释最好还是别用中文了)
image.png

3.18

合语法但是很危险,应当用ivec.push_back(42)

3.19

image.png
显然第二种方法更简便

3.20

image.pngimage.png

3.4 迭代器iterator介绍

类似访问容器元素的指针,有效指[start, finish]区间的迭代器,其他位置属于无效;
begin在首元素,end在尾元素的后一位;如果容器空,则begin=end;
end比较特殊,不指向具体元素,不能解引用或加减;
迭代器属于iterator和const_iterator类型,后者相当于const指针;
无论元素是否为常量,cbegincend都返回常迭代器;
!循环里若使用迭代器,不要改变容器容量(如添加元素),否则迭代器会失效!

练习

3.23

image.pngimage.png
答案没有用迭代器,用的是范围for加引用

迭代器运算

it1-it2返回it2要向前移动到it1的步数(difference_type类型,可正可负),当然前提是同一容器的迭代器;
迭代器在前面的大,在后面的小;此处的前后、大小以end>begin为参考;

练习

3.24

image.pngimage.png

3.25

image.png

3.26

迭代器没有加法(猜想可能会导致越界,所以没有实现)

3.5 数组

数组长度是constexpr;不允许auto数组,需要指定类型;不存在引用的数组;
标准不允许数组拷贝

复杂数组声明

包括指针的数组、数组的指针、数组的引用
image.png
最佳阅读顺序是从内到外然后再从右到左

练习

3.27

(a)(c)(d)非法,buf_size不是常量表达式;txt_size()返回值也不是constexpr;字符串字面值末尾有’\0’,st大小至少为12

访问数组元素

数组下标为size_t类型(机器相关、无符号)

练习

3.30

ix=10时将出现越界

3.31

image.png

3.33

不初始化的时候数组/局部变量里面是垃圾值,达不到统计分数段的目的
image.png

指针和数组

数组名在初始化表达式中自动隐式转换为首元素地址的右值——wiki
auto会把数组名认为是指针,而decltype会返回真正的数组类型


左值、右值

image.png

标准库函数begin和end

与容器成员函数类似,但不是成员函数,接收数组作参数;
end()-begin()返回值为ptrdiff_t类型,带符号且与机器相关;

  • 数组下标可以为负数,意思是向后偏移,如:

image.png
f[-1]其实就是x[10-1],不过这种写法不好理解

  • 标准库容器下标就不可以是负数了

    练习

    3.34

    image.png
    将p1指向和p2相同的位置,不会非法

    3.35

    image.png

    3.36

    比较数组需要自己编写每个元素逐个对比的方法(包括两个数组的首尾指针);
    比较vector可以用封装的==操作符

    C风格字符串

    不是一种类型;一般用指针操作
    image.png
    这些函数都不验证字符串末尾是否有空字符,因此胡乱使用很危险

  • strcmp相等返回0,p1>p2返回正数,p1<p2返回负数

  • 连接、拷贝需要正确计算字符串大小,极容易出错

    练习

    3.37
    image.png
    很明显,*cp是空时循环才会结束,而ca并不是以空字符结尾的字符串,指针会操作未知地址,所以输出的字符为垃圾值
    3.40
    image.png

    与旧代码的接口

  1. 允许C风格字符串初始化string;甚至提供c_str()返回string的C风格字符串;
    image.png
    image.png
  2. 允许数组初始化vector
    image.png

    3.6 多维数组

    其实是数组的数组;
    多层范围for遍历多维数组时,除了最内层,其他层都应该声明为auto引用,否则会造成不合理的遍历指针
    image.png

    练习

    3.43
    image.png
    3.44
    image.png
    改动不大,主要是外层循环看起来简洁一些
    3.45
    把int_array换成auto,尤其是范围for会更简洁

    第4章 表达式

    4.1 基础

    左值右值

    左值可以放在=左边,但const对象不能作为左值;
    有些表达式结果为对象,但却是右值;
    总结: 对象右值是内容,左值是内存中的位置;
    左值可以代替右值,反之则不行;
    左值当右值用的时候,用的是内容
  • =左边需要左值,且返回左值
  • &作用于左值,却返回右值(地址)
  • []和*都返回左值
  • ++、—作用于左值,前置的返回也是左值
  • decltype(expression),如果表达式是左值(非变量)则返回引用
    是右值则正常返回操作符的结果类型

&操作符的结果是指针右值

很困惑的一点是,取地址运算符&返回指针右值,猜测也是这样,不然不会有int *p=&i的写法;
在标准中找到&的定义
image.png
可以看到确实是返回指针类型,那以后记住就好


优先级和结合律

image.png
image.png

  • 表中靠前的优先级高,同一组内优先级相同
  • 算术运算符和成员访问都是左结合,还有逗号,其他大多都是右结合

    练习

    4.1

    结果应为105

    4.2

    在vec.begin()两边加括号即可

    求值顺序

    和优先级、结合律无关;
    image.png这样的表达式
    函数调用顺序一般由编译器决定

    4.2 算术运算符

  • 对象和结果都是右值(当然前面提过,左值可以代替右值)

  • 会进行隐式类型转换
    尤其是bool会转成int,因此不要用于参与运算
    image.png
  • 新标准商一律舍弃小数部分
    image.png
  • m - (m/n) * n = m % n
    按照这个方程求模;如果m%n不为0,那么结果符号和m相同

    练习

    4.4

    结果应为91

    4.5

    (a)-86; (b)-18;
    (c)0; (d)-2

    4.6

    image.png

    4.7

    当计算的结果超出该类型所能表示的范围时就会产生溢出。
    这里仅给一个例子,无符号数0强行减一会变成unsigned int的模
    image.pngimage.png

    4.3 逻辑和关系运算符

    运算对象和返回值都是右值;
    ||和&&出现短路求值的现象,当第一个开关断开时,后面的表达式不再进行计算;

    练习

    4.9

    cp为字符串字面值的地址,不会为0;
    *cp是’H’字符,也不为0;
    所以此条件为true

    4.10

    image.png

    4.12

    大于小于优先级大于判等;
    j

    4.4 赋值运算符

    =左侧是可修改的左值,结果是左侧类型的左值;
    右侧对象会转成左侧类型;
    允许列表初始化
    左侧为内置类型时,列表只能包含一个值,且不可以往小类型转换;
    image.png
    =优先级非常低,一般加个括号;
    复合赋值只计算一次,在重载操作符和操作类对象时开销较小;

    练习

    4.15——解惑

    ival = pi一部分非法,int*不可以用来初始化int
    image.png
    标准对=运算符有解释,右侧必须可以隐式转换为左侧
    pi的值为16进制地址,可能这是无法隐式转换的原因
    image.png
    地址太长转为int损失了精度,经测试转为较大的long long类型可以通过
    image.pngimage.png

    4.5 递增和递减运算符

    作用于左值,前置返回对象本身左值,后置返回对象原值副本的右值;
    一般不要用后置版本,除非需要同时递增和使用原值;后置++优先于解引用*

    练习

    4.19

    (c)应修改,没有规定<=两边求值顺序

    4.6 成员访问运算符

    *低于点,最好加括号;

  • ->作用于对象指针,返回左值

  • 点:返回成员所属对象的左右值类型

    练习

    4.20

    (b)(c)(e)不合法,string没有自增操作,而且(c)这里不是想使用迭代器的成员函数empty(),应该加括号

    4.7 条件运算符

    即condition?expr1:expr2;
    当两个表达式都是左值或可以转换成同一种左值时,返回左值;否则返回右值;
    最好别嵌套;
    优先级很低,仅高于=,记得加括号;
    image.png

    练习

    4.21

    image.pngimage.png

    4.22

    image.pngimage.png
    if版本就不写了,显然是if容易理解

    4.23

    +后面加括号即可,代码功能是如果词尾没有s加上s

    4.8 位运算符

    作用于整形对象或bitset;
    尽量只用于无符号类型;
    ^异或:不同为1,相同为0;

  • <<左移在右侧补0;>>右移在左侧补符号位或0;

image.png

练习

4.25

image.png

4.27

(a)3 (b)7
(c)true (d)true

4.9 sizeof运算符

返回操作对象所占字节数,返回constexpr size_t类型

  • sizeof(type)
  • sizeof expr返回表达式结果类型的大小

sizeof结果还有些规定:
image.png
image.png

练习

4.28

image.png

4.29

image.png
image.png
不是像答案说的未定义

4.30

sizeof优先级高于算术运算符,(a)(c)在运算符左边加括号即可

4.10 逗号运算符

求值顺序从左到右,结果是最右边表达式的值(无论左右)

练习

4.31

此处变为后置即可,由于是for循环的第三个迭代语句,不会产生副作用

4.32

含义即遍历数组:ix与size组合和ptr与ia组合一样,都是确定边界的作用

4.33

(someValue ? ((++x), (++y)) : (—x)), (—y)
前置自增自减优先级最高,先加一层括号;
?:优先级高于逗号,逗号前再加一层括号
image.png

4.11 类型转换

隐式转换——算术类型
image.png

  • 小整数如果可能都提升为int,否则提升为unsigned int
  • 无符号和带符号在一起转换成范围更大的类型

    练习

    4.34

    (a)float转int
    (b)int转float,float转double
    (c)char转int,int转double

    4.35

    image.pngimage.png
    image.pngimage.png
    每小段都进行了两三次隐式转换

    其他隐式转换

  • 数组转换成指针
    decltype、&、sizeof、typeid例外

  • 指针转换
    0、nullptr能转换成任意指针;
    非const指针可以转换为void
    非空指针可以转换为const void
  • 类定义的转换
    如cin转换为bool

    显式转换

    格式:cast-name (expression)

  • cast有四种:
    static_cast 、 dynamic_cast、const_cast、reinterpret_cast

  • type是引用时结果是左值?
    image.png

  1. static_cast本质上是传统c语言强制转换的替代品
    不包含底层const都可以使用
    编译通过,自己负责精度损失
  2. const_cast改变对象的底层const
    image.png消去j底层const
    image.png加上x底层const
  3. reinterpret_cast
    image.pngreinterpret_cast的例子

    补充——ASCII码对照表

    网上ASCII对照表

    第5章 语句

    5.3 条件语句

    if嵌套时会发生if多于else的情况,C++规定else与离他最近且未被匹配的if匹配——悬垂else;
    switch-case中的case必须是整型常量表达式;
    不可以在一个被跳过的case中定义将会使用的变量

    练习

    cin >> std::noskipws >> ch这种输入方式可以让输入流不跳过空白字符
    5.13
    (c)
    1. unsigned evenCnt = 0, oddCnt = 0;
    2. int digit = get_num() % 10;
    3. switch (digit) {
    4. case 1: case 3: case 5: case 7: case 9://不可以写成case 1,3,5,7,9:
    5. oddcnt++;
    6. break;
    7. case 2: case 4: case 6: case 8: case 0:
    8. evencnt++;
    9. break;
    10. }

    5.4 迭代语句

    while语句

    练习

    5.14
    image.png
    image.png
    多写这种没有使用高级数据结构的代码有助于培养机器思维

    传统for循环

    for循环第一部分只能声明多个相同类型的变量

    练习

    5.17
    image.png

    范围for语句

    遍历的对象必须可以返回begin和end迭代器;
    如果更改容器元素个数,范围for预存的end()将失效

    练习

    5.19
    image.png

    5.5 跳转语句

    练习

    5.20
    image.png
    5.21
    image.png
    5.22
    1. do{
    2. int sz=get_size();
    3. }while(sz<=0);

    5.6 try语句块和异常处理

    runtime_error对象的what()返回异常对象的初始化字符串

    标准异常

    image.png
  • exception、bad_alloc、bad_cast对象只能默认初始化
  • 其他异常对象必须用字符串初始化

如果初始化了,what()返回初始化字符串;否则返回值由编译器决定

练习

5.23

image.png

5.24

image.pngimage.png

5.25

image.pngimage.png
throw后面的语句不会执行;
try里面还是要写throw;
catch尽量写异常引用,不用复制异常对象

第6章 函数

6.1 函数基础

()叫调用运算符;
返回值不能是数组或函数,但可以是指针;

练习

6.4

image.png提前写了个递归。。

6.5

image.png

6.6

image.pngimage.png
可见没写return也可以编译通过,但返回值是垃圾值

6.7

image.pngimage.png

6.10

image.pngimage.png

6.2 参数传递

总结使用引用传值的场景和优势:

  1. 直接使用参数对象,无需拷贝,尤其是大类型或不支持拷贝的类型
  2. 返回多个参数可以在参数中加引用影响外部变量

    练习

    6.15
    image.png
    不需要改变s引用的对象所以s最好是const引用,而occurs用来存储字符位置,所以不能是const引用;
    s为引用可以避免拷贝(尤其是s过大的时候),occurs是引用或指针才可以更改实参(引用更简洁),c不需要声明为引用;
    s如果为普通引用则可能更改实参(不希望看到),occurs需要修改值不能为const引用
    6.16
    参数改为const引用:
    此函数不修改参数,最好是const引用
    此函数可以接收const string,但普通引用不可以引用const对象,所以此时必须是const
    6.17
    image.pngimage.png
    image.png

    数组形参

    由于数组名字被当作指针,数组长度函数未知,即使明确指出,编译器也只检查类型;
    管理方法有以下几种:
  • C风格字符串碰到空字符结束
  • 标准库启发:传递首指针和尾后指针
  • 直接传入数组大小
    image.png这样写数组的引用只可以传入大小为10的数组

    练习

    6.21

    image.png
    a写不写const都不会更改实参;
    而b有可能传入的是数组

    6.22

    image.pngimage.png
    即使传入指针,实参内存地址也不会改变,除非使用二级指针或指针的引用↑

    6.24

    输出数组ia前十个元素,可能导致越界,因为形参中的数组大小不起作用,ia如果只有5个元素还是可以传进函数,但会越界

    main:处理命令行选项

    1. int main(){...}
    2. int main(int argc, char *argv[]){...}

    程序处理的参数从argv[1]开始,argv[0]是可执行文件的名字

    练习

    image.png

    6.25

    image.pngimage.png

    含有可变形参的函数

  • 实参类型相同,传递initializer_list类型

  • 实参类型不同,写可变函数模板
  • 省略符形参主要用于和C的接口

image.png
初始化列表也是模板类型,类似vector;
list中的元素是常量,只可读不可写

练习

6.27

image.png

6.28

其实elem不带const的话,auto推断出来也会是const string&类型,因为列表元素都是常量

返回类型和returnyuju

  • 不要返回临时对象的指针或引用

image.png
返回临时对象或变量是可以的,但会导致拷贝开销,尽管有时不可避免

  • return引用则返回左值,否则返回右值

    练习

    6.30
    image.png
    最下面的是本题的报错:return语句没有值(应该为bool);for循环可能执行完,此时不写return会返回垃圾值(见习题6.6)

    返回数组指针

    主要有三种写法:
  1. type ( *function(p-list) ) [dimension]

image.png
image.png
比较好理解的一种改善是数组使用别名,如typedef int arr[10]将arr声明为int [10]

  1. 尾置返回类型
    image.png
    一般用于返回类型比较复杂的函数
  2. 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_cast

    6.5 特殊语言特性

    默认实参

  • 一个作用域中同一个函数一个形参只能拥有一个默认值

image.png

  • 局部变量不可以作默认参数

    练习

    6.42

    image.pngimage.png

    内联函数和constexpr函数

    这两类函数定义必须一致,经常实现在头文件

  • 内联可省去函数开销(包括保存寄存器、参数拷贝、语句跳转)

  • 内联只是请求,实现情况还是要看编译器

constexpr函数:

  • 返回值和形参必须是字面值
  • 有且仅有一条return
  • 隐式指定为inline
  • constexpr不一定返回常量表达式

image.png尽管有红线,编译器并没有报错


字面值类型!

image.png
image.png
算术类型、枚举、指针、成员指针以及这些类型的const、volatile类型都算是标量/scalar type

调试帮助

assert()预处理宏;
NDEBUG预处理变量

6.6 函数匹配

参数类型转换

image.png

第二点指非const指针或引用转换成const指针和引用(加上底层const)

练习

6.52

(a)两个char转int,属于第3级
(b)两个double转int,属于第4级

6.53

(c)会重复声明,重载不区分形参的顶层const

6.7 函数指针

用指针替换函数名即可,类似于指向数组的指针,函数名也被当作指针处理;
typedef函数指针和数组别名一样,比较反人类;
image.png
image.png
image.png
decltype(函数名)也返回函数类型,需要加上*才是函数指针

练习

6.54

image.png

6.55 & 6.56

image.pngimage.png

第7章 类

第2章提过类,13章讲对象的行为包括复制、移动、销毁等,14章讲运算符重载;
类的用户应该是调用类的程序员,而程序的使用者是屏幕前的人;

7.1定义抽象数据类型

设计Sales_data类

image.png

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

    练习

    7.4 & 7.5

    image.png
    应该定义为const成员函数,因为这些函数对对象都是只读不写

    定义类相关的非成员函数

  • 用到类的非成员函数概念上属于类的接口,但实际不属于类本身;

一般定义到头文件

  • IO类不支持拷贝,只可以使用引用
    不加#include,<<无法重载

    练习

    7.9

    image.png

    构造函数

    有一些情况编译器无法生成默认构造函数,如数据成员包含另一个没有默认构造函数的类对象;

  • =default可以保留默认行为,如构造函数、析构函数等

    练习

    7.13

    image.png

    7.15

    image.png
    最好参数写成const引用

    拷贝、赋值和析构

    复制/拷贝构造函数调用场景:

  1. 用同类对象初始化另一个对象
  2. 形参是对象,函数传参时调用
  3. 返回值为函数局部临时对象

    7.2 访问控制与封装

    class第一个访问符之前默认为private;
    struct默认为public;
    还有继承时的访问权限,除此之外没有不同

    友元

    image.png
    函数是类接口的一部分但不是成员函数,且需要访问私有成员时,可以写为友元函数

    7.3 类的其他特性

    可变数据成员

    可变数据成员一直可以改变,即使是const对象、const成员函数;
    指明数据成员为mutable
    image.png

    返回*this的成员函数

  • const成员函数如果返回引用,则*this是const引用,不可以改变对象
  • const成员函数谁都可以用,但非const成员函数只有非const对象才可以用
    image.png

    前向声明

    在创建对象之前必须有类的定义;但类的名字出现之后就被认为声明过;
    类的成员类型不可以是自己,但可以是同类的指针或引用
    image.png因为不知道类的成员,所以这个类不完整

    练习

    7.31
    image.png必须有Y的前向声明,否则会报错image.png

    友元类

    除了非成员函数定义为友元还有两种友元:
  1. 声明友元类
    友元类的成员函数可以访问此类的所有成员
    友元类无传递性
  2. 声明已定义类的成员函数
    加上类作用域,当然这个类需要定义过
    重载的函数需要一一声明

友元声明和作用域——易错
友元函数最好是类内声明,类外定义;
即使是类内定义,类外也需要写一次,否则作用域只限于类内,外部不可见
image.png
image.png虽然友元函数实现了,但是构造函数中仍不可见,因为f声明在类后
image.png即使实现在类内也一样对于其他函数不可见

7.4 类的作用域

名字查找和类的作用域

名字查找分三步:

  1. 当前块这一句之前找声明
  2. 找外层
  3. 没找到就报错

类定义分两步:

  1. 编译成员的声明
  2. 类全部可见后编译成员函数

除成员函数外使用的名字都必须确保声明过
image.pngimage.png
类内Money自然是double类型,而bal从类内先找声明,因此是double类型而非全局变量string类型
成员函数解析过程:

  1. 函数内
  2. 类内所有成员
  3. 类外部函数定义之前的全局作用域

    练习

    7.34
    image.pngimage.png
    7.35
    image.pngimage.png

    7.5 构造函数

    初始值列表

  • 第一种普通情况

image.png
image.png
此时两者效果相同,但是初始化和赋值操作还是有差别,完全取决于成员类型,初始化一定是比赋值开销小的


  • 第二种成员含const、引用和无默认构造函数的类对象

image.pngimage.png
由于const和引用必须初始化,不允许声明后再赋值,所以必须要用初始值列表;
没有默认构造函数的类对象被当作成员时需要初始化,否则无法构造对象成员
image.pngimage.png
B的构造函数是默认的,没有显式构造对象a
image.pngB构造函数需要构造自己的对象成员,尤其是那个成员没有默认构造函数时

成员初始化顺序

初始值列表只说明值,不指示初始化顺序;一般依据成员定义出现的顺序进行初始化;
image.png
i先声明,所以尽管列表中j写在前面,还是先初始化i,但是j的值未定义,可能会报错;
因此尽量避免用一个成员初始化另一个成员;


当构造函数所有参数都有默认值时,相当于提供了默认构造函数

练习

7.36

image.png
和上面提到的例子相同,用了未定义的base初始化rem;
自定义类还是注意下顺序

7.37

本节定义参考如下
image.pngimage.png
next默认初始化,bookNo为空串,其他为0;
last的bookNo为9-999-99999-9,其他为0;
两者都是用第一个构造函数

7.38

image.png

委托构造函数!

image.png
A委托B帮他构造,先执行B代码,然后返还A继续执行

默认构造函数

内置类型和复合类型的成员只有在对象是全局作用域时才初始化,否则是垃圾值
默认初始化发生在:

  • 局部作用域定义非静态对象或数组
  • 类的成员有对象且使用默认构造函数
  • 类的对象成员没有在初始值列表中显式初始化

值初始化发生在:

  • 数组初始化列表中元素不够
  • 局部静态变量
  • classname a()使用只含一个参数的构造函数请求值初始化

    隐式的类类型转换

    如果构造函数只有一个参数,又叫转换构造函数,实际上定义了内置类型转换为类的隐式规则;
    explicit加在声明前可以阻止隐式转换,不过只能写在类内声明前,定义的时候不要再重复explicit;
    explicit只能用于直接初始化,拷贝初始化/=都不行;
    编译器只会执行一步隐式类类型转换
    image.png

  • string接收const string*的构造函数不是explicit
    所以可以=初始化

  • vector接收容量的构造函数是explicit
    所以没有下面的写法

image.pngimage.png报错

练习

7.49

image.png
注意非const引用不可以绑定临时对象!

  • 以引用的方式传递一个临时变量做为函数参数,如果函数内部对此临时变量做了修改,那么函数返回后,程序员无法获得函数对临时变量的修改。函数对临时变量所做出的所有更改,都将丢失
  • C++中产生的临时对象是不可修改的
    默认为const的非常量引用必须绑定左值,非const引用只能绑定到与该引用同类型的对象,而const引用则可以绑定到不同的但相关的对象或绑定到右值
    非const不可以转换为const类型
  • C++标准的规定:非常量的引用不能指向临时对象

    7.51

    vector v=1;
    这种写法不清楚是初始化容量还是元素值,禁止这种二义性,(所以我猜)v的单参构造函数是explicit
    image.png

    聚合类

  • 全public

  • 无构造函数
  • 没有类内成员初始值
  • 没有基类、没有virtual函数

这样的类可以用初始值列表初始化
image.pngimage.png
如果表里元素不够,剩下的被值初始化

字面值常量类

不知道有啥用,要求也很繁琐,暂时略过,看不到应用场景
image.png
image.png

7.6 类的静态成员

  • 属于类不属于对象
  • 不能为const,不能使用this
  • static只出现在类内部的声明
  • 必须在外部定义和初始化静态成员
    为保证只定义一次,最好static和非内联函数的定义放一起

    类内初始化

    非常量静态成员不可以类内初始化
    image.png
    如果是常量表达式则可以类内初始化
    image.png
    const还可以类内声明类外定义

静态数据成员可以是不完全类型(声明且没有完整定义);
静态成员可以作默认实参;非静态成员属于对象,作参数不知道属于哪个对象会报错