最近使用C语言写MIT 6.S081课程的作业,发现自己对于C语言中的类似*
,&
,[]
,->
符号的运算优先级和结合顺序问题理解并不清楚,这篇文章就是要彻底的理解这些个符号的运算优先级和代表的意义,保证以后凡是看到这样的符号,就都不会心里发慌。
首先,我们要明确,C语言的符号优先级(Precedence)规定了运算符(Operator)的运算顺序,优先级高的符号会先进行运算,然后才轮到优先级低的符号运算。只有对于相同符号优先级的两个运算符,此时讨论结合顺序才是有意义的。结合顺序(Associativity)规定了(在相同优先级符号的情况下)先计算哪个符号的问题。
在同等优先级(Precedence)的情况下,所有的运算符结合顺序(Associativity)一定相同(都是从左到右或者都是从右到左)
举例说明优先级不同造成的区别:
// 由于优先级 * 高于 + 高于 = |
优先级相同情况下,结合顺序造成的区别:
// 由于+,-的结合顺序是从左到右 |
关于前缀和(Prefix ++)以及后缀和(Suffix ++)的问题,可以参考这篇文章显微镜下的 i++ 与 ++i的内容,简而言之就是:
前缀和 ++i:先将局部变量表中的i值+1,再将i放入操作数栈中
后缀和 i++:先将局部变量表中的i放入操作数栈中,再将局部变量表中的i值+1
这其中,操作数栈中的数值就是后续运算(比如说赋值运算int a = i++;
)中要操作的数值,因此我们也可以说:
前缀和 ++i:变量i的值+1,整个表达式(++i)的值(操作数栈中存入的值)是变量i的新值
后缀和 i++:变量i的值+1,整个表达式(i++)的值(操作数栈中存入的值)是变量i的原始值
当然,对于指针类型的变量(比如说int *p
),前缀和(++p
)和后缀和(p++
)每一次操作就不是+1了,而是+sizeof(*p)
例如,(++p)->len
先执行p
的+sizeof(*p)
的操作,然后再返回这个新p
的len值;(p++)->len
则返回的是原始p
的len值,然后再执行p
的+sizeof(*p)
的操作
全部的运算符优先级可以在网上搜索C language operator precedence得到,这里仅列出本文关注的若干运算符:
Operator | Description | Associativity |
---|---|---|
[] | Array subscript | left to right |
-> | Member selection via pointer | - |
++ | Suffix increment | - |
() | Function call | - |
* | Dereference | right to left |
& | Address (of operand) | - |
++ | Prefix increment |
注意这里的()
表示的含义是函数调用(Function call),它也被认为是一种后缀表达式(Postfix Expression),这不同于我们在数学上常用的添加括号(Parentheses)来改变运算顺序的情况。添加括号来改变运算顺序的情况(Parenthesized Expression)是构造了一种初等表达式(Primary Expression)。将括号内的表达式看作是一个整体,其类型和值与无括号的表达式相同。从某种意义上讲,添加括号(Parenthesized Expression)的这种情况是比函数调用()
更高的优先级。
其中,[]
,->
同级,它们和()
一样,属于最高优先级的那一类运算符;*
,&
同级,它们属于第二档优先级的运算符。
对于->
符号来说,它是一种对于结构体(struct)指针的简便写法,对于结构体指针p
,p->str
等价于(*p).str
由于我们有了运算符的优先级和结合顺序,因此也就可以由此读懂复杂的类型声明代码。其中,所有的类型声明语法都应该从里到外(按照优先级顺序)来读:
// 变量f是一个function,这个函数的返回值是一个pointer,这个pointer指向int,参数情况未知 |
最后简单来谈一下函数指针和数组解引用的问题:
对于函数来说,函数的变量名func
等价于这个函数定义时(func(params){定义起始位置....}
)的起始位置(address of function,这也就是操作系统中寄存器存储的return address),而一个函数的指针&func
就是对这个函数的变量名进行取地址操作
对于数组来说,数组的变量名array
等价于指向数组第一个元素的一个指针,而获得数组的第i个元素的值其实就是对数组指针进行解引用操作,array[i] = *(array + i)
,&array[i] = array + i
。与++相同,对于指针型的变量,我们的+i
操作实际上是+ i * sizeof(*array)