c++ primer 第五版 笔记 第四章

因为中间装机,导致译文遗失。现在用笔记的形式记录下来,翻阅的是中文版c++ primer

第四章 表达式

本章主要介绍内置类型上面的运算符,同时,介绍几种标准库定义的运算符。第十四章将会介绍自定义类型上面的运算符。

表达式:由一个或者多个运算对象组成。可以对表达式求值,求值回返回一个结果。

例如:字面值和变量就是表达式。他们的结果就是字面值和变量的值。

4.1 基础概念

什么是一元运算符,二元运算符。。。。?

一元运算符:作用于一个运算对象的运算符
二元运算符:作用于两个运算对象的运算符
x元运算符:作用于x个运算对象的运算符

这些概念并不需要过多的记忆,这些只是给这些运算符分了个类。在后面文章中,可以方便的使用。

运算对象可能发生类型转换

每一个运算符要求,参与的运算对象尽量一致,当然也可以不一致。不一致的前提是:不一致的类型能够转换成运算符要求的类型。

运算对象的类型转换一般能够理解。详细的规则将会在4.11节中介绍。

重载运算符

自定义系统默认的运算符行为,成为运算符重载。详细内容将会在后面章节中介绍到。

左值和右值

左值:运用的是其身份(身份可以理解为地址)。
右值:运用的是其值。
因此,可以这样理解,可取地址的可以当做左值,不可以取地址的可以当成右值。
不好理解,举个例子:

// a可以取地址,而23无法取地址,所以a是左值
int a = 23;
// b可以取地址,a也可以取地址,所以a,b都是左值
int b = a;

那么思考,如下情况

a+b;//是否可以对a+b的结果进行取值

4.1.2优先级和结合律

复合表达式:含有两个或者多个运算符的表达式

当有多个运算符的时候,那么谁先运算,谁后运算,需要正确的组织,此时需要优先级和结合律。

优先级:不同运算符的优先水平

结合律:同一优先级运算符的运算方向(左结合律,右结合律)。

举例如下:

3+4*5;

*运算符的优先级比+运算符的优先级高,所以,先进行4*5的运算,然后再将结果与3进行运算。

使用括号,摆脱优先级和结合律的限制

(3+4)*5;

因为3+4,有括号,所以先进行3+4的运算,在将结果与5
进行运算。

4.1.3 求值顺序

虽然优先级和结合律,规定了运算对象的组织形式,但是大部分运算符对于运算对象的求值,却没有具体的规定,如下:

int i = f1() * f2();

虽然在乘法运算符(*)参与运算之前,f1()和f2(),都会被求值,但是谁先,谁后,却没有明确的规定。那么就存在一种潜在的危险:f1和f2的先后对乘法结果有影响

举例如下:

//<<运算符没有对求值结果进行规定,因此下面的语句是未定义的
int i = 0;
cout << i << ""  << ++i << endl;

程序的结果,可能是 1 1.也有可能是 0 1.
这取决于到底谁先被运算。

注意:有四种运算符,明确规定了求值顺序。

逻辑与(&&):它规定先求左侧运算对象,只有左侧运算对象为真时,才继续求右侧的运算对象。

逻辑或(||):它规定先求左侧运算对象,只有左侧对象为假时,才继续求右侧的运算对象

条件运算符(?:):本章后面详细讲解

逗号运算符(,):本章后面详细讲解

建议:

1.拿不准优先级和结合律的时候,最好使用括号

2.在一个表达式中改变了某个对象的值,在这个表达式的其他对方就不要在使用这个对象了。

对于第二个有一个例外:当改变对象的子表达式,就是另外一个子表达式的运算对象时,该规则无效。如

//递增表达式改变iter的值,改变之后的值又是*的运算对象
*++iter

4.2算术运算符

在这里插入图片描述

算数运算符,可以作用于所有的算术类型,或者能够转换成算术类型的类型。算术运算符的结果为右值。

注意下面的例子:

bool b = true;
bool b2 = -b;

首先,-是取负值的运算符,他的运算对象为算术类型,或者能够转换成算术类型的类型。bool类型能够转换成成算术类型。他将转换成int类型。因此,true转成成1.再对1,进行取负的运算,即-1.然后再将-1赋值给b2.因为-1不是0,所以,b2为true。

注意:
当转成算术类型之后能够用int存储下,那么就是用int类型。这是因为int类型被设计成目标硬件最容易计算的类型。既然能够用最容易的方式实现,那么就用最容易的方式实现。

注意:溢出

例如,某个机器的short为16位,最大值为32767.下面的表达式将产生溢出

shor short_value = 32767;
short_value += 1;
cout << "short_value " << short_value << endl;

存储32768需要17位。而short只有16位。将一个超出最大值的数赋值给一个有符号类型,结果是未定义的。未定义表示:不同编译器的行为千差万别,有些可能是发生值环绕,有些则可能变为0,有些则可能直接崩溃。程序不应该使用未定义的行为。

加(+),减(-),乘(*),除(/),运算符的作用,跟数学上面的对应符号相同。需要注意的是:整数相除也是整数。

求余(%),他的设计规则是:m%n,其中,m和n必须是整数,获取两个数相除之后的余数。且,m = (m/n)*n+m%/n。

举例如下:

21 % 6 结果为3
-21 % -8 结果为-5
21 % -5 结果为1

4.3 逻辑和关系运算符

在这里插入图片描述

关系运算符的运算对象为:算术类型,指针类型,返回类型为bool

逻辑运算符运算对象为:bool类型。

上述两个运算符的结果都为:右值

逻辑与(&&):当且仅当两个运算对象都为真的时候,结果为真。

逻辑或(||):当且仅当两个运算对象都为假的时候,结果为假。

逻辑与和逻辑或运算符,都规定了运算对象的求值顺序,见本章开头部分.这种无须在全部运算对象上求值,就知道整个表达式的值的情况,称为短路求值.

逻辑非(!):对运算对象进行取反.

关系运算符满足,左结合律.
关系运算符的返回结果是bool值,因此将几个关系运算符结合起来写,可能并不是我们想要的结果.
例如:

if(i < j < k)

上例中,我们可能想要表达:j大于i,并且j小于k.而实际却变成了.i与j比较大小,返回一个bool值,或者为true,或者为false.然后再将这个bool值与k进行比较.在比较的同时,bool值将转换成int类型.即,与k进行比较的是1或者0. 那么整个表达式的结果,就变成了,k与1或者k与0之间的比较结果.

其中需要特别注意的用法是:

if(val == true)

上面这种写法,是万万不建议的.原因有如下:

当val为算术类型时,true将会转成成算术类型,而不是转换成bool类型.那么就变成了if(val == 1).此时正确的写法时

if(val)

4.4 赋值运算符

赋值运算符(=),的左边必须是一个可以修改的左值.赋值运算符满足右结合律.

int ival,jval;
//将0赋值给java,再将jval赋值给ival
ival = jval = 0; 
int  ival,*pval;
//错误,0不能赋值给一个指针类型,因为0不能将int类型转成int *类型
ival = pval = 0;

注意:

//先将j赋值给i,然后再判断i
if(i=j)
与
//直接将i和j进行判断
if(i==j)

复合赋值运算符

+=     -=     -=     *=     /=     %=

<<=     >>=     &=      ^=     |=

上面等价于: a op= b    a = a op b

上面两个的区别就在于:左侧运算对象的求值次数.使用复合运算符只求值一次.使用普通的运算符则求值两次.这两次分别为:第一次是右边表达式中的求值.第二次是,赋值运算符的左侧对象的求值.

所以从严格意义来讲,复合运算符比普通的运算符要快.但是这种快,几乎没有影响.

4.5 递增和递减运算符

递增(++),递减(–)有前置和后置两个版本.

运算符作用于左值对象,前置版本返回的是对象本身,因此是左值.后置版本返回对象的原始值,所以返回的是右值.

注意:因为后置类型,会返回原始值,所以,需要存储一下原始值,然后再运算.如果不需要原始值,这将造成资源消耗.所以,如非必要,不应该使用后置版本.

对于整数和指针类型来说,这种额外的工作,可以忽略不记,但是对于某些复杂的迭代器来说,则会有相当大的消耗.

注意一个忽略的问题:

*beg  = toupper(*beg++);

因为,赋值运算符的求值顺序没有规定,所以,可能会出现下面两种情况

*beg = toupper(*beg);

或者

*(beg+1) = toupper(*beg);

4.6 成员访问运算符

点运算符(.):获取某个对象的一个成员

箭头运算符(->):等价于(*ptr).mem;
ptr为一个指针

4.7 条件运算符

cond?expr1:expr2;

cond为判断条件的表达式

expr1,expr2另外两个表达式,这两个表达式可以转换成某个公共的类型.

求值过程为:先对cond求值,如果为真,则对expr1求值,否则对expr2求值.

其中,expr1和expr2,还可以为条件运算符表示的表达式.这样就可以形成嵌套关系.但是对于正常的变成来说,不建议将嵌套层次弄的太深,最好不要超过两层.

4.8位运算符

在这里插入图片描述

警告:位运算符对于有符号类型的符号位的处理,没有明确规定,所以强烈建议仅用在处理无符号类型上面.

左移运算符(<<):在右侧插入0

右移运算符(>>):如果运算对象是无符号的,则插入0;如果运算对象带符号,插入符号位的副本,或者0.如何选择视具体情况而定.

移位运算符满足左结合律

位求反(~):按位,取反

位与(&),位或(|),位异或(^).按照位,分别进行与,或,异或

4.9 sizeof运算符

sizeof(type);//返回类型所占的字节数

sizeof expr;//返回表达式结果所占的字节数

sizeof不进行真正的求值运算,因此如下的写法是安全的

//就是p位一个空指针,他也是安全的,因为sizeof不会进行求值
sizeof *p

注意:对数组,进行sizeof进行运算,则计算的是整个数组的大小,而对string和vector使用sizeof返回的大小不会包含里面元素的大小.

4.10 逗号运算符

逗号运算符,求值顺序:从左向右,整个表达式的结果位最后一个表达式的结果.

4.11 类型转换

运算符作用的运算对象,可以不用严格要求,因为运算对象可以转换成运算符期望的类型.

这种不需要程序员介入的转换,称为:隐式转换

隐式转换规则有两条:1.更利于硬件处理;2.尽量避免精度损失.第二个条件的优先级更高.

对于第一个条件,则在算术计算中,short,bool将会转换成int类型.因为int类型更利于硬件处理.

对于第二个条件,则在整个表达式中,向精度最高的类型进行准换.

4.11.1 算术转换

算术转换:将一种算术类型,转换成另外一种算术类型

将上面使用的规则,更具体一点则是:

1.对于bool,signed char ,unsigned char ,short,unsignd short类型来说,如果他们能够存储在int中,则转换成int,否则转换成unsigned int.

2.对于较大类型的char(wchar_t,char16_t,char32_t),提升成int,unsinged int ,long,unsigned long,long long ,unsigned long long 中最小的,能容纳其值的类型.

根据这两条规则,考虑如下一种情况,一个运算对象带有符号,另外一个运算对象为无符号,而且,无符号类型不小于带符号类型,那么带符号的运算对象将会转换成无符号的类型。如果有符号类型的对象恰好为负值。则会产生意想不到的结果。

当有符号类型大于无符号类型的时候,转换结果依赖机器。如果无符号的所有值都能存在有符号类型中,则无符号转换成有符号。如果不能,则将有符号转换成无符号。

此处的类型转换,需要进行大量的例子,进行加深理解。

4.11.2 其他的隐式类型转换

1.数组转成指针。

在大多数用到数组的表达式中,数组自动的转换成数组首元素的指针。

int ia[10];
int *ip = ia;//自动将其转换成数组首元素的指针

将数组用在decltype,取地址运算符,sizeof,typeid运算符时,上述转换不会发生。

2.指针的转换

常量整数值0,字面量nullptr能够转换成任意的指针。

指向任意的非常亮指针都能转换成void *.

指向任意的指针都能转换成const void *

3.转换成bool类型

在条件表示出现的地方,可以将算术类型自动转成bool类型,例如

int val;
...
if(val)
...
while(val)
...

4.转换成常量

允许将指向非常量的指针,转换成指向常量的指针。对于引用来说也是这样。如:

int i;
const int &j = i;//正确
const int *p = &i;//正确
int &r = j,*q = p;//错误,不能将const转换成非const

5.类类型转换

此处将在后面章节中详细讨论

4.11.3 显示类型转换

有时候,隐式类型转换不能满足要求,只能进行强制类型转换。

命名的强制类型转换

cast-name<type>(expression);

其中type是要转换的目标类型。expression是要转换的值。如果type为引用类型,则结果为左值。

cast-name 是static_cast,dynamic_cast,const_cast和reinterpret_cast

static_cast

任何类型,只要不包含底层const,都可以使用static_cast进行转换。

const_cast

const_cast,只能改变底层的const。不能改变表达式的类型。

const char *pc;
//下面的转换是正确的,但是,通过p写值,将是为定义的
char *p = const_cast<char*>(pc);

const char * cp;
//错误static_cast不能去掉const性质
char *q = static_cast<char *>(cp);
static_cast<string>(cp);//正确,字符串字面值转换成string类型
const_cast<string>(cp);//错误:const_cast只改变const性质

reinterpret_cast

reinterpret_cast表示的:重新解释这个指针,将这个指针重新解释为目标类型。

这在类类型的指针之间转换时,将会看到不同的区别。

此处的具体详解,将会在继承部分,详细讨论。

旧版本的强制类型转换

type (expr);//函数形式的类型转换

(type) expr;//c风格的类型转换

注意:对于旧版本的强制类型转换来说,其转换不够明确,因此不建议使用,应该使用最新的强制类型转换运算符

4.12 运算符优先级表

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值