深入了解c语言运算符优先级
引言
很多刚学编程的同学对c语言运算符的优先级往往存在一些困惑,对于一些已经入门了的同学一些不太常见的用法也较难理解,比如常见的函数指针:
//函数指针
int (*add)(int a , int b);
//返回指针的函数
int * getAddress(char [] , int n);
//数组指针
int (*a)[]
//指针数组
int * a[];
刚刚接触这些的时候我们往往很难理解这些符号,即使我们理解了也往往对于新的符号也经常出现困惑:
*p++;(*p)++;*++p;++*p;*p.f
而彻底理解C语言运算符的优先级,能让你对这些游刃有余,避免在编程的时候出现困惑,下面我们就一起来探究一下C运算符的优先级吧!
运算符的四要素——符号、操作数、优先级、结合性
符号
符号很容易理解,我们平常所见的+、-、*、/等都是符号,运算符符号指代表某一种运算的符号。划重点:运算符一定是代表了某一种运算,例如:¥虽然也是符号就不是C语言的运算符。
操作数(operand)
操作数是指需要进行运算的数字或者表达式,如1+1,有两个操作数分别是1,和1;再例如 a* b + c / d 这个表达式中+号的两个操作数分别是(a* b)和(c/d)两个表达式的值,操作数不光是数字常量、字符常量也包括可以赋值的各种变量。这些操作数就像是函数的参数,根据“参数”个数的不同运算符分为一元运算符、二元运算符、三元运算符等。
优先级
优先级的概念很好理解,我们小学数学学的先乘除再加减就是一种优先级。给出任意一个表达式例如3+2*y[i]++;
因为优先级[] = ++ > * >+ ,我们根据各项运算符优先级不难理解表达式所要表达的含义。
结合性
运算符想要进行运算需要有操作数,结合性顾名思义是操作符与操作数结合的亲密程度。例如前面的例子中++ 和[]拥有相同的优先级但是明显[]与y更加紧密([]距离y更近),所以我们知道是对数组中第i+1个元素进行++,而不是y进行++(这也不和语法)。C语言中运算符的结合性包括从左到右和从右到左两种。从左到右:即优先级相同计算时先算左边的后算右边的;从右到左:即运算符优先级相同先计算左边的在计算右边的。
c语言中运算符的结合性通常都为从左到右,只有前缀一元运算符:* 、&、++(前缀)、–(前缀)、-(负号)、+(正号)、!、~(按位取反)(typename)强制转换、sizeof 、各种赋值运算符、三元运算符:? :这三类为从右到左。
各种运算符的优先级与结合性
下表列出了C语言中运算符的结合性和优先级,可以看出我们小括号()和{}(数组或指针初始化时使用)虽然不算是严格意义上的运算符,但()和{}中的内容通常被认为一个整体(这和数学上的括号意义一致)。所以如果实在确定不了优先级,可以使用() 例如if(a==b&&c==d||d==e&&e==f)
等价于if((a==b&&c==d)||(d==e&&e==f))
,添加()也使得我们的小程序可读性更高。
除了()和{}之外优先级依次为:一元后缀运算符>一元前缀运算符>算术运算符( * / %)> 算术运算符(± )>位运算符(>>、<<)>关系运算符(>=、>、<、<=)>关系运算符(==、!=)> 位逻辑运算符(依次&、^、|)>逻辑运算符(依次&&、||)>三元运算符(?:)>赋值运算符>逗号运算符(,)。
简单的来说,优先级顺序满足以下几项:
- 一元后缀运算符>一元前缀运算符>二元运算符>三元运算符
- 二元运算符中:算术运算符>关系运算符>逻辑运算符>赋值运算符
- 逻辑运算符: && > ||
结合性比较简单:除了一元前缀运算符、三元运算符和赋值运算符为从右到左以外,其余均为从左到右。
运算符优先级 | 运算符 | 结合性 | 备注 |
---|---|---|---|
0 | ()(括号)、{}(组合文字) | 无 | 括号和数学运算的括号一致只是声明内部为一个整体,组合文字用了表示数组或结构直接量,严格讲这两个并不算是运算符 |
1 | ()(函数调用)、++(后缀)、- -(后缀)、[](数组下标). 、-> | 从左到右 | ()和[]在函数和数组声明时也适用此优先级 |
2 | ++(前缀)、- -(前缀)、*(取值)、&(取指针)、+(正号)、-(符号)、!(取反)、~(按位取反)、sizeof 、 (typename)(强制转换)、 | 从右到左 | * 在指针声明时也适用此优先级 |
3 | * 、/ 、%、 | 从左到右 | 算术运算符 |
4 | +、- | 从左到右 | 算术运算符 |
5 | <<、>> | 从左到右 | 位运算符-移位运算符 |
6 | <=、<、>、>= | 从左到右 | 关系运算符 |
7 | ==、!= | 从左到右 | 关系运算符 |
8 | & | 从左到右 | 位逻辑运算符 |
9 | ^ | 从左到右 | 位逻辑运算符 |
10 | | | 从左到右 | 位逻辑运算符 |
11 | && | 从左到右 | 逻辑运算符 |
12 | || | 从左到右 | 逻辑运算符 |
13 | ?: | 从右到左 | 三元运算符 |
14 | =、+=、-=、*=、/=、%=、<<=、>>=、&=、^=、|= | 从右到左 | 赋值运算符 |
15 | ,(逗号运算符) | 从左到右 | 逗号运算符 |
补充
关于位运算符
位运算的优先级一直饱受争议,人们认为|、&、^应该与移位运算符(<</>>)一样,例如a&b==c
我们往往期待它解析为(a&b)==c
,然而事实是它被解析为a&(b==c)
。这与我们期望并不相符。主要原因是历史上c并没有&&和||只有&和|,所以位运算&和|被认为是逻辑运算符。
关于条件表达式(?:)
通常优先级和结合性可以解决我们遇到的大部分问题。但有时候运算符本身的含义规定了另一些东西。例如a>b?c,d:e
被解释为a>b?(c,d):e
,因为(a>b?c),(d:e)
这样会毫无意义。同样sizeof (int) *x
被解释为(sizeof(int))*x
,并非sizeof((int)*x)
,一般sizeof的操作数最好加上()表示。尽管sizeof n
也是合法。
C语言中关于条件表达式的规则为:
logical-OR-expression ? expression : expression
所以对于条件表达式最好按照它的意义去理解,另外条件表达式结合性是从右到左a>b ? a : c>b ? c : b
这个表达式被解释为a>b ? a : (c>b ? c : b)
。
对于一些运算符使用特殊情况的解释
//函数指针
int (*add)(int a , int b); //括号中*add被看做一个整体,*表明add是一个指针,而int (.)()表明指针指向的是函数类型。
//返回指针的函数
int * getAddress(char [] , int n);//*getAddress(*) ,*和()两个运算符同时与getAddress结合,而优先级()>*,表明getAddress是一个函数名。
//数组指针
int (*a)[];//括号中*a被看做一个整体,*表明a是一个指针,而int (.)[]表明指针a指向的是数组类型。
//指针数组
int * a[];//*a[],*和[]同时与a结合,而优先级[]>*表明a是一个数组,数组存储的内容为int类型的指针
总结
C语言中运算符优先级是C语法中很重要的一部分,Java、C++、Python、C#中运算符的优先级与C也大多相似。只要理解之后记住这些并不算难,但在实际应用中应当注意在适当的时候使用(),即使你很确定你的表达式没有必要加(),()可以保证我们的代码更加友好、可读。