欢迎光临
我们一直在努力

操作符详解3----操作符优先级和结合性总览

C语言操作符优先级与结合性详解

在C语言中,操作符的优先级和结合性是理解表达式求值规则的核心。很多“诡异”的bug都源于对优先级和结合性的误解。本文用一张表、几条规则和大量示例,带你彻底搞懂它们。


在这里插入图片描述

目录

  • 1. 什么是优先级与结合性
    • 1.1 优先级(Precedence)
    • 1.2 结合性(Associativity)
  • 2. C语言操作符优先级总表
  • 3. 结合性详解
    • 3.1 左结合(从左到右)
    • 3.2 右结合(从右到左)
    • 3.3 注意:函数参数中的逗号不是运算符
  • 4. 常见陷阱与易错点
    • 4.1 *p++ 到底是 (*p)++ 还是 *(p++)?
    • 4.2 a & b == c 是逻辑错误
    • 4.3 a << 2 + 1 的结果
    • 4.4 a = b || c && d 的计算
    • 4.5 x & 1 == 1 的解析
    • 4.6 赋值运算符的返回值
    • 4.7 条件运算符的嵌套
    • 4.8 逗号运算符的优先级最低
  • 5. 记忆口诀
  • 6. 总结

1. 什么是优先级与结合性

1.1 优先级(Precedence)

当一个表达式中出现多个不同的操作符时,优先级高的操作符先参与运算。

例如:

int a = 2 + 3 * 4;

  • 的优先级高于 +,所以先计算 3 * 4,再计算 2 + 12,结果为 14。

1.2 结合性(Associativity)

当表达式中出现多个相同优先级的操作符时,结合性决定它们从左到右还是从右到左进行分组。

例如:

int a = 10 3 2;

  • 是左结合,所以等价于 (10 – 3) – 2,结果为 5。

再如赋值操作符 = 是右结合:

int a, b, c;
a = b = c = 0;

等价于 a = (b = (c = 0)),从右向左依次赋值。

注意:优先级和结合性只决定表达式的语法结构(即如何加括号),并不决定子表达式的求值顺序。C语言标准没有规定大多数子表达式的求值顺序,这可能导致未定义行为(Undefined Behavior),例如 i++ + ++i。

2. C语言操作符优先级总表

优先级从高到低排列。同一行优先级相同,结合性见每行标注。

优先级操作符名称/含义结合性
1 () [] . -> ++(后缀) –(后缀) 函数调用、下标、成员访问、后缀自增/自减 左结合
2 ++(前缀) –(前缀) + – ! ~ (type) * & sizeof 前缀自增/自减、正负号、逻辑非、按位取反、类型转换、解引用、取地址、大小 右结合
3 * / % 乘法、除法、取模 左结合
4 + – 加法、减法 左结合
5 << >> 左移、右移 左结合
6 < <= > >= 关系比较 左结合
7 == != 等于、不等于 左结合
8 & 按位与 左结合
9 ^ 按位异或 左结合
10 || 按位或 左结合
11 && 逻辑与 左结合
12 | 逻辑或 左结合
13 ?: 条件运算符 右结合
14 = += -= *= /= %= <<= >>= &= ^= = 赋值运算符 右结合
15 , 逗号运算符 左结合

补充说明:

后缀 ++ / – 的优先级高于前缀 ++ / –。

[]、()、.、-> 属于最高优先级。

sizeof 是一元操作符,优先级与 !、~ 等相同。

复合字面量 (type){…}(C99)优先级与后缀操作符相同,但此处不展开。

3. 结合性详解

3.1 左结合(从左到右)

大部分二元操作符都是左结合,包括:

算术:* / % + –

移位:<< >>

关系/相等:< <= > >= == !=

位操作:& ^ |

逻辑:&& ||

逗号:,

示例:

int a = 20 / 5 * 2; // (20 / 5) * 2 = 8,不是 20 / (5 * 2) = 2
int b = 1 << 2 << 1; // (1 << 2) << 1 = 8,不是 1 << (2 << 1) = 16
int c = 1 | 2 & 3; // 1 | (2 & 3) = 3,因为 & 优先级高于 |,同优先级时才看结合性

3.2 右结合(从右到左)

右结合的操作符包括:

一元操作符:前缀 ++ –、+ – ! ~、* &、(type)、sizeof、_Alignof

条件运算符:?:

赋值运算符:= 及复合赋值

示例:

int x = 5;
int *p = &x; // & 和 * 都是一元操作符,右结合
int y = *&x; // 相当于 *(&x),因为 & 和 * 优先级相同且右结合

int a = 1, b = 2, c = 3;
a = b = c; // 相当于 a = (b = c),右结合

int max = a > b ? a : b > c ? b : c;
// 条件运算符右结合:a > b ? a : (b > c ? b : c)

3.3 注意:函数参数中的逗号不是运算符

函数调用 f(a, b) 中的逗号是分隔符,不是逗号运算符,因此没有求值顺序保证。

printf("%d %d", i++, i++); // 未定义行为,不要这样写

4. 常见陷阱与易错点

4.1 *p++ 到底是 (*p)++ 还是 *(p++)?

后缀 ++ 优先级高于解引用 *,所以:

*p++ 等价于 *(p++)

含义:先取得 p 当前指向的值,然后指针 p 自增(指向下一个元素)。

如果你想先自增指向的值,应写:

(*p)++;

4.2 a & b == c 是逻辑错误

== 的优先级高于 &,所以:

a & b == c 等价于 a & (b == c)

通常本意是 (a & b) == c。务必加括号。

4.3 a << 2 + 1 的结果

  • 的优先级高于 <<,所以:

a << 2 + 1 等价于 a << (2 + 1) = a << 3

如果你想先移位再加1,必须写 (a << 2) + 1。

4.4 a = b || c && d 的计算

&& 优先级高于 ||,所以:

a = b || c && d 等价于 a = (b || (c && d))

注意 || 和 && 有短路求值特性。

4.5 x & 1 == 1 的解析

== 高于 &,所以:

x & 1 == 1 等价于 x & (1 == 1) = x & 1

这恰好能得到“判断最低位是否为1”的效果,但语义不清晰,建议写 (x & 1) == 1。

4.6 赋值运算符的返回值

赋值表达式本身也有值,其值为赋值后左操作数的值。因为赋值右结合,可以链式赋值:

int a, b, c;
a = b = c = 5; // 相当于 a = (b = (c = 5))

但不要混淆:

if (a = 5) { ... } // 常见笔误,应写成 if (a == 5)

4.7 条件运算符的嵌套

条件运算符是右结合,可以嵌套,但可读性差:

int score = 75;
char *grade = score >= 90 ? "A" :
score >= 80 ? "B" :
score >= 70 ? "C" :
score >= 60 ? "D" : "F";

等价于:

char *grade;
if (score >= 90) grade = "A";
else if (score >= 80) grade = "B";
else if (score >= 70) grade = "C";
else if (score >= 60) grade = "D";
else grade = "F";

4.8 逗号运算符的优先级最低

int a = (1, 2, 3); // a = 3,因为逗号运算符左结合,依次求值,结果为最后一个
int b = 1, 2, 3; // 编译错误或 b = 1,因为逗号优先级低于赋值,解析为 (int b = 1), 2, 3

5. 记忆口诀

优先级从高到低可简单归纳为:

括后一,单目右,乘除加减,移位关系,相等位与,异或或,逻辑条件赋值逗号

更常见的是分段记忆:

1.括号、数组下标、成员访问、后缀 ++/–

2.一元操作符

3.乘除取模

4.加减

5.移位

6.关系

7.相等

8.按位与

9.按位异或

10.按位或

11.逻辑与

12.逻辑或

13.条件

14.赋值

15.逗号

结合性记忆:

右结合:所有一元操作符、条件运算符、赋值运算符。

左结合:其余二元操作符和逗号运算符。

6. 总结

优先级决定不同操作符之间的运算顺序。

结合性决定同优先级操作符的分组方向。

优先级和结合性只影响表达式的解析方式,不影响子表达式的求值顺序。

遇到不确定的表达式,加括号是最简单、最可靠的方式。

避免在一个表达式中多次修改同一个变量(如 i++ + ++i),这是未定义行为。

理解这些规则,阅读和编写C语言代码会更加清晰,也能避免很多隐蔽的bug。

赞(0)
未经允许不得转载:171主机测评 » 操作符详解3----操作符优先级和结合性总览
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址