第四章 表达式

版权声明:转载请注明出处 https://blog.csdn.net/weixin_39918693/article/details/86564074


C++语言提供了一套丰富的运算符,并定义了这些运算符作用于内置类型的运算对象时所执行的操作。

运算符分为语言本身定义的和标准库定义的

*标准库定义的就是标准库里面进行重载的

我们可以对一些运算符进行重载(指定运算符作用于类对象所执行的操作)

一、基础

函数调用运算符也是一种特殊的运算符,它对运算对象的数量没有限制

运算符的优先级、结合律以及运算对象的求值顺序。求值顺序与优先级和结合律无关,注意理解什么是求值顺序

*绝大多数的求值顺序都交给了编译器决定

一般的二元运算符要求两个运算对象的类型相同,所以类型转换不可避免。

我们进行运算符重载的时候,其包括运算对象的类型和返回值的类型,都可以重新指定,但是运算对象的个数、运算符的优先级和结合律都是无法改变的

当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)

如果一个表达式的结果是一个左值,那么decltype作用于这个表达式得到的是一个引用

算术运算符满足左结合律,意味着如果运算符的优先级相同,将按照从左向右的顺序组合运算对象

括号无视优先级和结合律,而且其还可以指定求值顺序(这个不确定),左结合律指的是从左向右进行计算

同一组内的运算符优先级相同,结合律也相同

逗号表达式指定了求值顺序

有四种运算符明确规定了运算对象的求值顺序:&&、||、?:、,

如何处理复合表达式:一是拿不准的时候最好用括号来强制让表达式的组合关系符合程序逻辑的要求。二是如果改变了某个运算对象的值,在表达式的其他地方就不要再使用这个运算对象了(自增和自减运算符是个例外)

C++没有明确规定大多数二元运算符的求值顺序,这实际实在代码生成效率和程序潜在缺陷之间进行了权衡(机智)


二、算术运算符

所有的算术运算符满足左结合,算术运算符的运算对象和求值结果都是右值,在表达式求值之前,小整数类型的运算对象被提升成较大的整数类型,所有运算对象最终会转换成同一类型(是一开始完成全部类型提升还是一边计算一般提升??????)

??????这没办法做实验验证??????

一元正号或负号运算符,先提升,再运算,返回一个副本

溢出不会报错,需要程序员来避免(分配的内存存不下一个大数字)

“取余”和“取模”是同一种操作,参与取余运算的运算对象必须是整数类型

C++11规定商一律向0取整

取余运算的结果的正负取决于第一个操作数(如果两个操作数符号不一致)


三、逻辑和关系运算符

关系运算符作用于算术类型或指针类型,逻辑运算符作用于任意能转换成布尔值的类型。逻辑运算符和关系运算符的返回值都是布尔值。运算对象和结果都是右值,都满足左结合律

&&和||的短路求值方式

进行比较运算时除非比较的对象是布尔类型,否则不要使用布尔字面值true和false作为运算对象


四、赋值运算符

结果的类型是左侧运算对象的类型。如果赋值运算符的左右两个运算对象类型不同,则右侧运算对象将转化为左侧运算对象的类型

满足右结合律

对于多重赋值语句中的每个对象,其类型或许与右侧对象类型相同,或者可以进行相应的转换

赋值运算符优先级较低,不要混淆赋值运算符和相等运算符

+= -= *= /= %= 复合赋值运算符
<<= >>= &= ^= |= 位运算符


五、递增和递减运算符

有些迭代器不支持算术运算,所以递增和递减运算符是必须的

前置版本将对象本身作为左值返回,后置版本将对象原始值的副本作为右值返回

除非必要建议一直使用前置版本

在一条语句中混用解引用和递增运算符(简洁是一种美德)

使用()来任意指定求值顺序,好像并不能指定求值顺序,只能指定运算对象的组合顺序?????

*求值顺序和组合顺序


六、成员访问运算符

因为解引用运算符的优先级低于点运算符,所以执行解引用运算符的子表达式两端必须加上括号

箭头运算符作用于一个指针类型的运算对象,结果是一个左值。

点运算符分成两种情况:如果成员所属对象是左值,那么结果是左值;反之,如果成员所属的对象是右值,那么结果是右值


七、条件运算符

条件运算符中后两个表达式是两个类型相同或可能转化为某个公共类型的表达式。执行过程是条件表达式为真时,运算并返回expr1的运算结果,否则运算并返回expr2的运算结果。

当条件运算符的后两个表达式都是左值或者能换成同一种左值类型时,运算结果是左值,否则运算结果是右值

条件运算符不能嵌套太多层(一般都是1~3层)

在输出表达式中使用条件运算符


八、位运算符

位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。位运算符提供检查和设置二进制位的功能

~ 位取反
<< 左移
>> 右移
& 位与
^ 位异或
| 位或

左移和右移操作存在丢弃和补充位的问题

一般来说,如果运算对象是小整型,其会被自动提升为较大的整型类型。运算对象可以是带符号的也可以是无符号的。强烈建议使用无符号的运算对象

移位运算符:返回结果是经移动过的(可能还进行了提升)左侧运算对象的拷贝作为求值结果。右侧运算对象一定不能为负,而且值必须严格小于结果的位数,否则就会产生未定义的行为。移出边界的二进制位会被丢弃,左移补0,右移时,如果是无符号补0,如果是有符号数,补符号位的副本

位求反运算符:逐位取反

位异或:有且仅有一个为1,结果为1

实际使用一次

移位运算符满足左结合律

重载运算符的优先级、结合律和运算对象的个数都与它的内置版本一样

移位运算符的优先级比算术运算符低,比关系运算符、赋值运算符和条件运算符高


九、sizeof运算符

sizeof运算符返回一条表达式或一个类型名字对应的类型所占的字节数。sizeof满足右结合律,所得的值是一个size_t类型的常量表达式。建议都使用括号

在sizeof中解引用一个无效指针仍然是一种安全的行为,因为指针实际上并没有被真正使用。sizeof不需要真的解引用指针也能知道它所指对象的类型

C++11允许我们通过作用域运算符来获取类的成员的大小。通常情况下只有通过类的对象才能访问到类的成员,但是sizeof无需我们提供一个具体的对象,因为要想知道类成员的大小无需真的获取该成员

需要一个具体的例子来说明

sizeof不会把数组转化为指针来处理

细节挺多的p140


十、逗号运算符

规定了运算顺序(求值顺序)


十一、类型转换

如果两种类型可以相互转换,那么他们就是有关联的

C++语言不会直接将两个不同类型的值相加,而是先根据类型转换规则设法将运算对象的类型统一后再求值。上述的类型转换是自动执行的,无须程序员的介入,有时甚至不需要程序员了解。因此,他们称为隐式转换

算术类型之间的隐式转换被设计得尽可能避免损失精度,类型提升

在大多数表达式中,比int类型小的整型值首先提升为较大的整型类型(int或unsigned int)

在条件中,非布尔值转换为布尔类型

初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型

如果算术运算或关系运算的运算对象有多种类型,需要转换成同一种类型

函数调用时也会发生类型转换

算术转换的含义是把一种算术类型转换成另一种算术类型

算术转换的规则定义了一套类型转换的层次,其中运算符的运算对象将转换成最小能存储的下的宽类型

如果表达式中既有整型也有浮点型,那么整型转换为浮点型

整型提升负责将小整数类型转换成较大的整数类型。对于bool、char、signed char、unsigned char、short、unsigned short来说,只要他们所有可能的值都能存储在int中,就都会被提升为int类型,否则就提升为unsigned int。继续提升的顺序是long、unsigned long、long long、unsigned long long。最起码提升为int类型。如果int存不下,就提升为宽度最小但是能够存储的下的整型类型(int是最起码的)

带符号类型向无符号类型的转换其实也符合向更宽类型提升的规则

所有的提升都是向更宽的类型进行转换,而且提升后必须可以完全存储(提升后运算对象的类型也必须一致,一般都与最宽的那个类型相同)

在大多数用到数组名的表达式中,数组名会被自动转换成指向数组首元素的指针。当数组被用作decltype、&、sizeof、typeid等运算符的运算对象时,上述转换也不会发生

指针转换: 常量整数值0或者字面值nullptr能转换成任意指针类型。指向任意非常量的指针能转换成void*。指向任意对象的指针能转换成const void*。(理解不了为什么要这样转换,这样转换有什么用处??????????)

存在一种从算术类型或指针类型向布尔类型自动转换的机制(条件表达式中)

引用和指针可以由非常量转换为常量,反之不行。指的不是引用和指针本身(是底层const性质)

类类型能定义由编译器自动执行的转换,不过编译器每次只能执行一种类类型的转换

强制类型转换本质上是很危险的

命名的强制类型转换,cast_name(expression)。type是转换的目标,expression是要转换的值。如果type是引用类型,则结果是左值。dynamic_cast支持运行时类型识别。cast_name指定了执行的是哪种转换

static_cast:任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。我们可以使用static_cast找回void*中存储的指针数据

const_cast:只能改变运算对象的底层const。如果对象本身不是一个常量,使用强制类型转换获得写权限是合法的行为。然而如果对象是一个常量,在使用const_cast进行写操作就会产生未定义的后果。该符号的运算对象是一个对常量的指针,可以去除其底层const属性,然后我们就可以通过该指针改变其所指向的对象了(如果该对象不是常量)

*用之前,要想明白

只有const_cast能改变表达式的常量属性,它不能改变表达式的类型。它还常常用于有函数重载的上下文

reinterpret_cast:为运算对象的位模式提供较低层次上的重新解释。改变的只是表面的,实际内容没有被改变。reinterpret_cast本质上是依赖于机器的。要想安全的使用reinterpret_cast必须对涉及的类型和编译器实现转换的过程都非常了解p146

避免使用强制类型转换

旧式的强制类型转换包含函数形式的和C语言风格式的

????????函数形式的旧式的强制类型转换是什么鬼???????

旧式强制类型转换与新的static_cast、const_cast和reinterpret_cast之间是等价的。自己注意对应关系

新式的强制类型转换说明和分类比旧式的清楚,建议使用新式的,相对来说更安全


十二、运算符优先级表p147

猜你喜欢

转载自blog.csdn.net/weixin_39918693/article/details/86564074