高程整理4-表达式、运算符
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就没有办法搞了。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 PhyCmdBlock的博客!