4.表达式、运算符


  • C/C++中任意表达式都有值
  • 表达式类型:意思就是最后一步运算是什么类型。b=(a=3*6,a/2):赋值表达式
  • 一个级别的运算符只可能是同一个结合性。

运算符优先级

(经过筛选,留下高程涉及到的)

完整版

运算符 结合性 含义 例子
优先级第 1 组
:: L-R 作用域解析运算符 Time::Time()
std::cout
优先级第 2 组(表达式)
() L-R 函数调用 fun(1)
() 值构造,即 type(expr) Time(12,0,0)
[] 数组下标 str[1]
-> 间接成员运算符 p->year
. 直接成员运算符 day1.year
static_cast 专用的类型转换 static_cast<int>(2.3)
++ 加 1 运算符,后缀 a++
-- 减 1 运算符,后缀 a--
优先级第 3 组(全是一元运算符)
! R-L 逻辑非 !true
+ 一元加号(正号) +2
- 一元减号(负号) -2
++ 加 1 运算符,前缀 ++a
-- 减 1 运算符,前缀 --a
& 地址 &num
* 解除引用(间接值) *p
() 类型转换,即 (type)expr (int)2.6
sizeof 长度,以字节为单位 sizeof(int)
优先级第 4 组
.* L-R 成员解除引用
->* 间接成员解除引用
优先级第 5 组(全是二元运算符)
* L-R a*3
/ a/3
% 模(余数) a%3
优先级第 6 组(全是二元运算符)
+ L-R a+3
- a-3
优先级第 7 组
<< L-R 左移(cout/cin后重载为流运算符) cout << a
>> 右移(cout/cin后重载为流运算符) cin >> a
优先级第 8 组
< L-R 小于 a<3
<= 小于或等于 a<=3
>= 大于或等于 a>=3
> 大于 a>3
优先级第 9 组
== L-R 等于 a==3
!= 不等于 a!=3
优先级第 13 组
&& L-R 逻辑 AND true && true
优先级第 14 组
|| L-R 逻辑 OR false || true
优先级第 15 组
?: R-L 条件 a>3?1:2
优先级第 16 组
= R-L 简单赋值 a=3
*= 乘并赋值 a*=3
/= 除并赋值 a/=3
%= 求模并赋值 a%=3
+= 加并赋值 a+=3
-= 减并赋值 a-=3
优先级第 18 组
, L-R 将两个表达式合并成一个 1,2,3,a

二次精简版

运算符 结合性
优先级第 1 组
作用域
优先级第 2 组
取成员,下标,后缀增减
优先级第 3 组
逻辑非,正负号,前缀增减,取地址,解引用
优先级第 5 组
乘除
优先级第 6 组
加减
优先级第 7 组
左右移(流运算符)
优先级第 8 组
非等关系(有方向的比较,不等式)
优先级第 9 组
等于关系(等于与不等于)
优先级第 13 组
逻辑与
优先级第 14 组
逻辑或
优先级第 15 组
三目运算符
优先级第 16 组
赋值与复合赋值
优先级第 18 组
逗号

表达式分析

  • 分析表达式就是去搞一下入栈出栈。
  • 表达式分析规则:
    • 如果即将入符号栈的符号是
    1. ①top优先级高于新符号②优先级相等,结合性都是左结合。
      则:先算之前的,算完再入新的符号
    2. ①top优先级低于新符号②优先级相等,结合性都是右结合。
      则:立刻入栈,继续往下。
    • 如果表达式已经结束,没有新的东西能进入数栈和符栈,则清理栈内元素,直接反复进行求值,算到最后数栈剩一个数字,符栈空,如果不是这样那么一定是语法错误。
  • 单次求值的含义:符栈出1个符号,这个运算符是n目运算符,则数栈出n个数字,分别为第n-1个操作数……第1个操作数。然后进行操作。操作得到一个值,压入数栈。
  • 括号的行为:当左括号(准备入栈的时候,优先级很高,为2级(基本上在常用的运算符之上),当括号进入了栈内后,优先级变成了最低,理解为不存在的19级(在所有运算符之下)。右括号)优先级最低,也是19级。但这个符号都用不着进栈,因为当他满足进栈条件的时候,top肯定是左括号,这个时候两者进行抵消,top的左括号此时弹出。
  • 进行算术运算(其他运算符不是这样),如果运算符的操作数的数据类型不是统一的,会自动进行转换,转为优先级更高的类型。char,unsigned char, short, unsigned short会经历整型提升,无论如何都会被提升到int才会参与运算(即便是两个char相加也会转换)。字符可以作为整数参与运算。'a'-32 是一个可以的操作,相当于是 1 字节整型变量转成整型后-4 字节整型变量。
  • 优先级表记法:长类型肯定在上面,浮点数肯定在整型上面,同长度unsigned在signed上面
  • 注:上方规则并不完全揭示编译器的行为。具体很复杂。

算术表达式

  • %操作的两个数字必须都是整型,放浮点数报错
  • %操作到底是什么:实际上,这是涉及到取模和取余的两种不同的概念。数学里的a mod b是取模,这个结果不允许为负数,-11 mod 3 = 1.python中%是取模。然而另一个则是取余,遵循的规律是a%b=a%(-b)、a%b=-(-a)%b,-11 % 3 = -2总结为余数要和被除数同符号,商要尽量向0靠。C/C++、Java都是取余。
  • /操作左右都是整数:整除操作,直接略掉小数。
  • 被0除
    • 整型,如果编译器能确定这个除数不会变,且是0,直接报错;如果这个0是个变量,那么运行起来,但是程序会运行出错。
    • 浮点型,如果编译器能确定算出表达式,且除数为0,直接报错;如果里面涉及变量,那么运行起来,程序正常运行,结果是inf/-inf。这是IEEE754良好定义的行为。
  • 自增自减:a++先用后加,++a先加后用。后缀左结合,前缀右结合,后缀优先级高于前缀。独立成句时前后缀等价。不能对常变量、字面量发生作用。一个表达式不要出现多次对同一个变量的自增自减。前缀返回左值,后缀返回右值!(什么是左右值,左值就是可以放在赋值=的左边,有地址的东西,右值是纯一个量,临时值,只能被放在=右边,没有一个确定的可引用的地址)
  • 赋值运算符、复合赋值运算符优先级16级(互相之间同级),右结合,优先级比常用算术符低,所以a+=1+2是先算1+2。值是左值的变量本身.“引用左操作数”。a=b=5,实际上是先运算b=5,b=5的结果是b,然后运算a=b,a也获得了b的值5。一个表达式不要出现多次对同一个变量的赋值。
  • 逗号(顺序求值运算符)表达式求值:优先级最低的运算符,18级,左结合。表达式1,表达式2,……,表达式n,其实就是一个一个算过来,每次逗号都选择了后一个结果。最终得到的结果是表达式n的值。

关系表达式

  • 关系表达式返回类型为bool,只有0、1两种,或者表达为falsetrue。可以作为整型参与运算。
  • 根据表达式的规则,C++不像Python那样可以连续比较:a<b<c,这在C++就是一种错误的表达方式。只能a<b && b<c.
  • 浮点数比较:由于浮点数本身的精度限制,一些数字无法比较,尤其是一样的数字(如1.2),进入double和float后再比较会不一样.所以对于浮点数的比较,禁用a==b,而是使用fabs(a-b) < 1e-6,差值足够小就认为相等。1e-6可以改,对于float参与的比较,比1e-6还小是没有意义的,精度不够。对于全是double的,可以按照需求继续小一点。
  • 赋值、运算中,bool类型遵循”非0为真0为假“,也就是说,除了0以外,不管什么值都认为是1.这种转换和之前的类型转换大转小的取低位要区分。
  • &&高于||,!优先级非常高。
  • 短路运算:如果前面算了,不管后面怎么算都对结果没有改变时,就不算了,直接得结果。a||b如果a已经是1了,b就不看了。
  • 常用等价表示:
    • a==0     \iff !a
    • a!=0     \iff a
  • &、|、^、~、<<、>>都是位操作运算符,二进制层面上进行与、或、异或、非、左移、右移操作,课程不涉及。
  • 运算符按照操作对象分为一元二元三元运算符,也叫一目二目三目运算符。所以为什么 ?: 被叫作三目运算符,是因为 C++ 里只有他一个三目运算符,就直接拿类型名字称呼了。
  • 三目运算符,表达式1?表达式2:表达式3,表达式1真就选择表达式2来求值,整个运算的值就是这个值。另一个方面同理。
  • ?:是同一个运算符,第15级。没有单独把?和:作为运算符。
  • ?:入栈前会把表达式1算掉,因为优先级低。算完了之后,?:都入栈,夹在里面的表达式都会算成值。然后出掉2个符号,出掉3个数,根据表达式1的值选择重新入栈的值。
  • 表达式123的值类型可以不同。23的类型必须“相容”,也就是要能够互相转化。一个char一个int,虽然不统一,但是可以转换,最多警告,但是一个string一个int就没有办法搞了。