C++ 学习之五、C++ 运算符
每种语言都有类似的运算符,如同数学运算符。
基本运算符
C++ 基本运算包括加、减、乘、除和求模运算。
- 加法运算,运算符为
+
。如4 + 20
。 - 减法运算,运算符为
-
。如20 - 3
。 - 乘法运算,运算符为
*
。如5 * 6
。 - 除法运算,运算符为
/
。如20 / 2
。 - 求模运算,它等于第一个数除以第二个数后的余数,运算符为
%
。如19 % 3
,结果为 1。
递增(++)与递减(–)运算符
常用于循环操作中,将循环计数加 1 或减 1。这两种运算符都有两种变体,前缀版本位于操作数之前,如 ++a
,后缀版本位于操作数后面,如 a++
。两个版本对操作数的影响是一样的,但是影响的时间不同。
前缀版本
前缀版本将操作数操作完成之后用于下一步运算。
1 | using namespace std; |
后缀版本
后缀版本先使用操作数当前的值计算表达式,再将值加 1 或减 1。
1 | using namespace std; |
Tips: 不要在同一条语句中对同一个值递增或递减多次,对这种语句,结果将是不确定的。
前后缀格式的效率
前缀格式和后缀格式在效率上有着微小的差别。
前缀函数将值加 1,然后返回结果。后缀函数首先复制一个副本,原变量加 1,然后将复制的副本返回。对于语言内置类型和当代编译器而言,这看似不是什么问题。但对于自定义的类而言,前缀版本的效率比后缀版本高。
递增递减运算符和指针
可以将该运算符用于指针和基本变量。运用到指针上时,将把指针的值增加或减小其指向的数据类型占用的字节数。
1 | double arr[5] = {21.1, 23, 4, 54.6, 3.99}; |
前缀递增、前缀递减和解除引用运算符的优先级相同,以从右到左的方式进行结合。后缀递增和后缀递减的优先级相同,但比前缀运算符的优先级高,这两个运算符以从左到右的方式进行结合。
1 | // 紧接上面 pt 指向 arr[1] 的语句 |
组合运算符
i = i + by
同等于使用组合运算符 i += by
以下为组合赋值运算符总结
操作符 | 作用(L 为左操作数,R 为右操作数) |
---|---|
+= | 将 L+R 赋给 L |
-= | 将 L-R 赋给 L |
*= | 将 L*R 赋给 L |
/= | 将 L/R 赋给 L |
%= | 将 L%R 赋给 L |
逗号运算符
逗号运算符允许将两个表达式放到 C++ 句法只允许放一个表达式的地方。逗号运算符最常见的用途是将两个或更多的表达式放到一个 for 循环表达式中。
C++ 为逗号运算符提供了两个特性。首先,它确保先计算第一个表达式,然后计算第二个表达式(逗号运算符是一个顺序点),其次,逗号表达式的值是第二部分的值。
1 |
|
逗号运算符的优先级最低。
1 | carts = 17,240; // 由于优先级 (carts = 17), 240,后面部分没作用 |
运算符重载
这里只介绍一下基本概念。
如除法运算符表示了 3 种不同的运算:int 除法、float 除法和 double 除法。C++ 根据上下文(这里是操作数的类型)来确定运算符的含义。使用相同的符号进行多种操作叫作运算符重载(operator overloading)。
运算符优先级
当两个运算符作用于同一个操作数时,需要考虑先运算哪部分,这里就需要了解运算符优先级。所有的运算优先级如下
运算时的类型转换
各种算术运算涉及到的硬件编译指令都是各不相同的,因此计算机需要处理大量不同的情况,尤其对不同的类型进行运算时。为处理这种潜在的混乱,C++ 自动执行很多类型转换。
- 将一种算术类型的值赋给另一种算术类型变量时,C++ 将对值进行转换。
- 表达式中包含不同类型时,C++ 将对值进行转换。
- 将参数传递给函数时,C++ 将对值进行转换。
初始化和赋值进行的转换
C++ 允许将一种类型的值赋给另一种类型的变量。该值将被转换为接收变量的类型。如
1 | double a = 3.456; // a 为 double 类型 |
这种类型的转换在一些情况下可能出现问题
转换 | 潜在的问题 |
---|---|
将较大的浮点类型转换为较小的浮点类型,如将 double 转换为 float | 精度(有效位数)降低,值可能超出目标类型的取值范围,在这种情况下,结果将是不确定的 |
将浮点型转换为整型 | 小数部分丢失,原来的值可能超出目标类型的取值范围,在这种情况下,结果将是不确定的 |
将较大的整型转换为较小的整型,如将 long 转换为 short | 原来的值可能超出目标类型的取值范围,通常只复制右边的字节 |
Tips: 将 0 赋给 bool
变量时,将被转换为 false
,而非零值将被转换为 true
。
以 { } 方式初始化时进行的转换(C++11)
C++ 将使用 { } 的初始化称为列表初始化(list-initialization),与前一种初始化转换方式相比,它对类型转换的要求更严格。列表初始化不允许缩窄(narrowing),也即不允许转换为无法表示该值的数据类型。例如:
- 不允许浮点型转换为整型。
- 不同的整型之间的转换或将整型转换为浮点型可能被允许,条件是编译器知道目标变量能够正常地存储赋给它的值。
1 | const int code = 66; // const 修饰的常量,code 值固定不可更改 |
表达式中的转换
当同一个表达式中包含两种不同的算术类型时,C++ 将执行两种自动转换。首先,一些类型在出现时便会自动转换;其次,有些类型在与其他类型同时出现在表达式中时将被转换。
一些类型在出现时便会进行自动转换
在计算表达式时,C++ 将 bool
、char
、unsigned char
、signed char
和 short
值转换为 int
。具体地说,true
被转换为 1,false
被转换为 0。这些转换被称为整型提升(integral promotion)。例如:
1 | short chickens = 20; |
还有一些其它整型提升:如果 short
比 int
短,则 unsigned short
类型将被转换为 int
;如果两种类型的长度相同,则 unsigned short
类型将被转换为 unsigned int
。这种规则确保了在对 unsigned short
进行提升时不会损失数据。
将不同的类型进行算术运算时,也会进行一些转换
当运算涉及到两种类型时,较小的类型将被转换为较大的类型。其中规则为:
long double > double > float > 整型提升
传递参数时的转换
传递参数时的类型转换通常由 C++ 函数原型控制。然而,也可以取消原型对参数传递的控制,尽管这样做并不明智。在这种情况下,C++ 将对 char 和 short 类型(signed 和 unsigned)应用整型提升。另外,为保持与传统 C 语言中大量代码的兼容性,在将参数传递给取消原型对参数控制的函数时,C++ 将 float 提升为 double。
强制类型转换
C++ 允许通过强制类型转换机制显式地进行类型转换。
普通强制类型转换语法
1 | (typeName) value // 将 value 强制转换为 typeName 类型,如 (int) 'Q',兼容 C 语言 |
C++ 引入的 4 个强制类型转换运算符
引用的强制类型转换运算符的使用要求较为严格
1 | static_cast<typeName> (value) // 将 value 转换为 typeName 类型 |