C++Primer第5版学习笔记(三)
第四/五章的重难点内容
你可以点击这里回顾第三章内容
因为第五章的内容比较少,因此和第四章的笔记内容合并。
第四章是和表达式有关的知识,表达式是C++的基础设施,本章由三部分组成:
1.表达式概念基础,包括表达式的基本概念,左值和右值的概念,优先级结合律,求值顺序。
2.各种运算符,主要包括算数\关系\逻辑\赋值\递增递减\成员访问\条件\位运算\sizeof\逗号运算符 这10种运算符。
3.类型转换,包括隐式和显式两种转换的规则。
下面是这一章的知识点:
知识点1:P120,4.1.1,表达式的基本概念
表达式由一个或者多个运算对象组成,多个对象组成表达式时,对象之间用运算符连接形成复杂表达式。
运算符中,需要两个对象和运算符连接形成表达式的这种运算符叫做二元(双目,二目)运算符。
分析一个表达式,必须先了解运算对象的含义、运算符的优先级结合律和运算符的求值顺序。
1.对于含有子表达式的复杂表达式,应该按照求值顺序,看看应该先求哪一个子表达式的值。
2.对于不那么复杂的子表达式,应该按照优先级,查看表达式中的每个操作数(对象)应该先跟那一个运算符在一起运算。
3.如果有优先级相同的运算符同时在同一个运算对象左右,应该按照结合律选定结合顺序是从右向左还是从左向右计算表达式的值。
知识点2:P121,4.1.1,左值和右值
起源:左值和右值原来是C语言中的概念,特指赋值运算符左右两段的表达式。C语言中,能放在赋值运算符左侧被赋值的对象就是左值,反过来在赋值运算符右侧的对象就是右值。C++中的这两个概念的词义发生了改变。
概述:可以暂时概述一下C++中左值和右值的概念。从性质上来看,当一个对象做右值时,我们使用的是这个对象的内容;当一个对象做左值时,我们使用的是它在内存中的位置。
应用:表达式中有的位置需要的是左值,有的位置需要的是右值。表达式的值本身也有左右的分别。
赋值运算符中左侧操作数和表达式结果都是左值。
取地址符的操作对象是左值,得到的是右值。
解引用、下标运算符的求值结果是左值。
decltype作用于表达式时,如果表达式的结果是一个左值,decltype会返回一个引用类型。
知识点3:P122,4.1.2,优先级和结合律
1.优先级 复杂表达式中一个运算对象连接多个不同运算符时,哪个运算符优先级高,就先计算哪个运算符和对象作用后的值。
2.结合律 复杂表达式中一个运算对象连接多个优先级相同的运算符时,根据这一优先级对应的结合律,按从右至左或者从左至右的顺序计算表达式的值。
如3+2*4-7;这个表达式是一个复杂表达式,因为表达式里*号优先级比较高,所以先计算2*4,得到3+8-7;得到的新表达式更简洁了,只剩下+-两个符号,这两个符号优先级相同,因此查看这个优先级对应的结合律可知这一级别的符号满足左结合性。因此从左向右计算,得到11-7;进一步得到结果4。
知识点4:P123,4.1.3,求值顺序
一个表达式里如果运算对象都是函数返回的,都需要计算求值才知道对象的状态,函数调用符号优先级右一致,中间隔着几个优先级低的其他符号连接操作对象,比如int a=f()+g();这时候是函数f()先被调用还是g()先被调用呢?答案是未定义。C++语法没有规定这种情况应该谁先谁后。就像下面的表达式++i+i++这个表达式中,优先级最高的表达式++i和i++中间隔着优先级低的运算符+,这是,关于++i先计算还是i++先计算,这是未定义的,而因为这个表达式先计算++i或先计算i++的结果不同,所以这条表达式是错误的。一个变量如果在同一个表达式里被多次改变,这个表达式的求值顺序又不一定,就会出现二义性。应该避免这样的写法。
目前只有四种运算符明确规定了求值顺序。
1.逻辑运算符&&和||:这两个运算符先计算左边操作数的值。
2.条件运算符?: :条件运算符先计算?前的表达式,并求值,之后对视情况对:左右侧的表达式求值。
3.逗号运算符, :这个运算符的求值顺序是从左至右。
知识点5:关于运算符,左值和右值的归纳
本章各种运算符形成的表达式所返回的值的属性和运算符需要的操作数的属性如下:
算数/逻辑/位运算符: 操作对象和结果都是右值 赋值运算符:左侧的操作对象必须是可以修改的左值, · 右侧的操作对象是右值,返回一个左值。
递增/递减运算符: 前置版本的++/--返回左值,后置版本的++/--返回右值。操作对象都必须是左值。
箭头成员访问运算符: 作用于指针,表达式结果左值。
点成员访问运算符:这个成员所属的对象是左值,结果就是左值;这个成员所属的对象是右值,结果就是右值。
条件运算符: 条件运算符的三个表达式都是左值或者都能转化成左值类型时,结果为左值;否则是右值。
知识点6:P125,4.3,除法和取模的结果
两个非浮点型变量/字面值相除,结果还是原来的类型,不会有原来操作数是整数,运算之后结果是小数的情况。
C++11中, 对于除运算符,结果向零取整(直接切掉小数部分,得到的数就是结果)。对于取模运算符,结果的符号和被除数的符号一致。
知识点7:P136,4.8,位运算符
左移运算符移动二进制数后会在右侧插入零,右移运算符在处理有符号类型的操作数(尤其是带负号的)时具体行为由环境决定。
知识点8:P157,4.9,sizeof运算符
sizeof运算符有两种用法,第一种是sizeof后面直接加一条表达式语句;第二种形如sizeof (类型名);第二种形式后会得到该类对象所占空间的大小。第一种形式中,如果表达式是指针类型,sizeof运算符会返回指针本身的大小。当有一个类名叫data,类中有一个成员叫做student时,可以使用作用域标识符和sizeof联动,使用sizeof(data::student);就可以计算出student占字节数。
知识点9:P141,4.11,隐式类型转换
概述:在C++中,一些类型可以按照一定规则互相转换,很多时候语境中需要使用两个或多个相同的类型才能继续运算。因此这时一种类型的值会被自动转换成另一个类型的值。这个过程就是隐式转换,其中算术隐式转换较为常见。
主要的隐式转换发生的情况:
1、大多数表达式中,比int小的类型会被提升为int型。
2、在条件中,非布尔值要转化成布尔值。
3、在初始化和赋值语句中,赋值符号的右侧对象的类型转换成左侧对象的类型进行运算。
4、算术/关系运算中对象有有多种类型的,转化成同一类型。
5、形参转化为实参的类型(第六章)。
6、数组名会被转换为指针。
7、0,nullptr会转为任何类型的指针。任何类型的指针都可以转化为(const)void *类型。
对第四条算数转换时发生隐式转换的补充:
再算术运算符的作用下,不同的操作数要转换成同一个类型才能够进行计算。以i+a;这个表达式举例,了解算术转换的方式。
1、首先,当i和a的类型占字节比int小,如char、short,把他们转换为int型。如果他们原来类型的最大值在当前系统里大于int型最大值,则转化成unsigned int型。
2、之后,如果i和a的类型相同,结束算数隐式转换,若i和a的类型不同,把占字节少的类型的对象转成占字节多的类型的对象。
3、如果占字节多的带符号类型的最大值小于占字节少的带转换对象的最大值,带符号类型将被转换为无符号类型。
知识点10:P144,4.11.3,显示转换
一个命名的强制类型转换具有以下形式:cast-name<要转换成的类型> (被转换的值);其中,cast-name是四种强制类型转换:static_cast、dynamic_cast、const_cast和reinterpret_cast之中的一种。
static_cast用于常见的强制类型转换。只要两个类型有关联,比如浮点数类型和整数类型,整数类型和布尔值类型,布尔值类型和指针类型,就可以使用static_cast。只是不能转换常量const到变量。
const_cast用于去掉(或者加上)对象的底层const,要转换的类型和转换的类型都必须是指针或者引用类型。常用于将在第六章介绍的函数重载。
reinterpret_cast依赖机器,是强行改变一个类型到另外一个不相干的类型。
dynamic_cast支持运行时类型识别,在19章将会提到。
第五章是和语句有关的知识,语句也是C++的重要组分,本章由三部分组成:
1.语句的概念,包括简单语句和语句作用域的概念。
2.条件/循环/跳转语句,条件语句主要包括if/else语句、switc语句和:?表达式条件语句;循环语句则是for语句和while语句;跳转语句包括continue、break和goto语句。
3.try/throw和异常处理,包括异常处理的使用方法。
下面是这一章的知识点:
知识点1:P155,5.2,语句作用域
用花括号括起来的块就是作用域的标志。在作用域中定义的对象只在作用域中起作用。块之外是没法访问和控制块内部的变量的。
尤其是在switch语句中,switch的执行过程可能跨过一些标签,当标签里声明并定义了一个对象,这个对象的作用域就延伸到了所有case标签里,如果case 1定义了int a;switch执行了case2,这时这个我们不想执行的语句却产生了自己的作用域,这显然是不行的。因此可以在case标签后使用大括号形成块,这样就不会出现作用域的问题。
goto也一样不能向前(代码的下几行)跳过对象的定义。不允许跨过变量的定义到达变量的作用域内。但是goto语句可以向后(代码的前几行)跳过定义。
知识点2:P172,5.6,try语句块和异常处理
可以用try\throw和catch联动进行异常处理,形式如下:
try
{待检测块} //待检测块里面包括throw语句来抛出异常
catch (异常类型 异常对象的对象名)
{异常处理语句 }
catch (同上,可以写很多catch)
{另一组异常处理语句}
throw抛出异常和catch处理异常的头文件都在stdexcept里定义。抛出异常的语句形如:throw 异常类型("异常文本");
异常类型一般只支持赋值,初始化,调用成员函数.what之类的几种操作。