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 组 | |
| 逗号 | 左 |
表达式分析
- 分析表达式就是去搞一下入栈出栈。
- 表达式分析规则:
- 如果即将入符号栈的符号是
- ①top优先级高于新符号②优先级相等,结合性都是左结合。
则:先算之前的,算完再入新的符号 - ①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两种,或者表达为
false、true。可以作为整型参与运算。 - 根据表达式的规则,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 !a
- a!=0 a
- &、|、^、~、«、»都是位操作运算符,二进制层面上进行与、或、异或、非、左移、右移操作,课程不涉及。
- 运算符按照操作对象分为一元二元三元运算符,也叫一目二目三目运算符。所以为什么
?:被叫作三目运算符,是因为 C++ 里只有他一个三目运算符,就直接拿类型名字称呼了。 - 三目运算符,表达式1?表达式2:表达式3,表达式1真就选择表达式2来求值,整个运算的值就是这个值。另一个方面同理。
- ?:是同一个运算符,第15级。没有单独把?和:作为运算符。
- ?:入栈前会把表达式1算掉,因为优先级低。算完了之后,?:都入栈,夹在里面的表达式都会算成值。然后出掉2个符号,出掉3个数,根据表达式1的值选择重新入栈的值。
- 表达式123的值类型可以不同。23的类型必须“相容”,也就是要能够互相转化。一个char一个int,虽然不统一,但是可以转换,最多警告,但是一个string一个int就没有办法搞了。
- 本文链接: https://7994c6cd.arisu-blog.pages.dev/posts/apl/apl-4/
- 版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。
