C语言的算符优先级
C语言中提供许多算符,比如算术运算、逻辑运算、关系运算等,必须规定它们的优先级,否则将它们放到一起运算肯定要出乱子,正如算术中有先乘除后加减的规则,C语言同样要有确定的运算规则。C语言定义了15个算符优先级,其优先级规则如下:
规则1:优先级高的先运算
规则2:同一个优先级则需要按照结合性确定运算顺序,大部分为从左到右,仅有少数结合性为从右到左。
1 C语言运算符的优先级和结合性列表
优先级 | 运算符 | 含义 | 结合性 |
---|---|---|---|
1 | ( ) [ ] . -> |
圆括号 数组下标 a[N] 结构体成员 对象.成员 结构体指针 对象指针->成员名 |
从左到右 |
2 | ! ~ ++ -- - (类型) * & sizeof |
逻辑非 按位取反 自增 自减 负号运算符 强制类型转换 取值, *p表示某指针的内容 取址, &a表示变量a的地址 长度运算 |
从右到左 |
3 | * / % |
乘 除 求余 |
从左到右 |
4 | + - |
加 减 |
从左到右 |
5 | >> << |
右移 左移 |
从左到右 |
6 | < > <= >= |
小于 大于 小于等于 大于等于 |
从左到右 |
7 | == != |
等于运算符 不等于运算符 |
从左到右 |
8 | & | 按位余 | 从左到右 |
9 | ^ | 按位异或 | 从左到右 |
10 | | | 按位或 | 从左到右 |
11 | && | 逻辑与 | 从左到右 |
12 | || | 逻辑或 | 从左到右 |
13 | ? : | 条件判断 | 从右到左 |
14 | = += -= *= /= %= &= ^= |= >>= <<= |
赋值运算符 加后赋值 减后赋值 乘后赋值 除后赋值 取余后赋值 按位与后赋值 按位异或后赋值 按位或后赋值 右移后赋值 左移后赋值 |
从右到左 |
15 | , | 逗号运算符 | 从左到右 |
2 C语言容易弄错的算符优先级
此表摘录《C专家编程》
优先级问题 | 表达式 | 错误的理解 | 正确的结果 |
---|---|---|---|
.的优先级高于* ->操作符用于消除这个问题 |
*p.f | p所指对象的字段 *(p).f | 对p取f偏移,作为指针,然后进行解除引用操作*(p.f) |
[] 高于* | int *ap[] | ap是个指向int数组的指针 int (*ap)[] | 指针数组 int *(ap[]) |
==和!=高于位操作运算符 | (val & mask != 0) | (val & mask) != 0 | val & (mask != 0) |
==和!= 高于赋值运算符 | c = getchar() != EOF | (c = getchar()) != EOF | c = (getchar() != EOF) |
算术运算符高于移位运算符 | msb << 4 + lsb | (msb << 4) + lsb | msb << (4 + lsb) |
逗号运算符在所有运算符中优先级最低 | i = 1, 2 | i = (1, 2) | (i = 1), 2 |
另外补充几个:
优先级问题 | 表达式 | 错误的理解 | 正确的结果 |
---|---|---|---|
==的优先级高于&& 和 || | a && b == c | (a && b) == c | a && (b == c) |
->的优先级高于& | &p->offset | (&p)->offset | &(p>offset) |
小结:
完全记住这些优先级有点困难,并且没有必要,可以简单的记一些:
- 括号(圆括号,中括号),结构体运算符(. 与 ->)优先级最高
- 赋值,逗号运算符优先级最低
- 单目运算符(* & 等 )优先级排第2
- 算术>移位>关系(比较)> 按位与或>逻辑与或(逻辑非处于第二等级)>赋值
- 只有单目运算符,三目运算符,赋值运算符的结合性为从右到左,其它结合性都是从左到右
3 算符优先级有关的几个知识点
3.1 指针数组与数组指针
下标引用[ ]的优先级高于间接访问*
指针数组:int *p[5]
数组指针:int (*p)[5]
指针数组:
本质是数组,只不过是是指针数组,如下例多个指针:
int a = 1;
int b = 2;
int c = 3;
int d = 4;
int *pa = &a;
int *pb = &b;
int *pc = &c;
int *pd = &d;
printf("a = %d b = %d c = %d, d = %d\r\n", *pa, *pb, *pc, *pd);
/* 或者定义指针数组,*p[4]是一个指针数组,数组里面的每个元素都是一个指针 */
int *p[4] = {&a, &b, &c, &d};
for (int i = 0; i < 4; i++) {
printf("%d ", *p[i]);
}
printf("\r\n");
结果为:
a = 1 b = 2 c = 3, d = 4
1 2 3 4
指针数组多用来存放字符串列表,便于管理。
char *str[] = {
"hello",
"what's your name",
"my name is hanmeimei",
"how are you"
};
for (int i = 0; i < 4; i++) {
printf("%s \r\n", str[i]); // 因为要打印字符串,所以这里用的是str[i],而非*str[i]
}
结果为:
hello
what's your name
my name is hanmeimei
how are you
数组指针:
本质是一个指针,这个指针指向数组,相当于行指针,一般用来指向二维数组。
int aa[6] = {1, 2, 3, 4, 5, 6};
int *pa = aa;
int (*p)[3] = &aa;
printf("aa = 0x%x, &aa = 0x%x, &aa[0] = 0x%x\r\n", aa, &aa, &aa[0]);
printf("p = 0x%x, *p = 0x%x *(p + 0) = 0x%x, **(p + 0) = 0x%x, p[0] = 0x%x, *p[0] = 0x%x, pa = 0x%x, *pa = 0x%x\r\n", p, *p, *(p + 0), **(p + 0), p[0], *p[0], pa, *pa);
p = p + 1;
pa = pa + 1;
printf("p = 0x%x, *p = 0x%x *(p + 0) = 0x%x, **(p + 0) = 0x%x, p[0] = 0x%x, *p[0] = 0x%x, pa = 0x%x, *pa = 0x%x\r\n", p, *p, *(p + 0), **(p + 0), p[0], *p[0], pa, *pa);
打印如下:
aa = 0x900fffa8, &aa = 0x900fffa8, &aa[0] = 0x900fffa8
p = 0x900fffa8, *p = 0x900fffa8 *(p + 0) = 0x900fffa8, **(p + 0) = 0x1, p[0] = 0x900fffa8, *p[0] = 0x1, pa = 0x900fffa8, *pa = 0x1
p = 0x900fffb4, *p = 0x900fffb4 *(p + 0) = 0x900fffb4, **(p + 0) = 0x4, p[0] = 0x900fffb4, *p[0] = 0x4, pa = 0x900fffac, *pa = 0x2
第一行打印说明:数组的地址与数组名称相同,数组的地址和数组首元素的地址数值上相同(但这并不意味着数组的地址和数组首元素的地址是完全相同的)
第二行打印说明:数组指针的解引用为行首元素地址,而数组元素指针的解引用为元素值。
第三行打印说明:数组指针指向的是数组的地址(不是元素的地址),指针加1相当于跳过当前数组,指向下一个数组。
数组指针和多维数组的关系:
数组 | 数组指针 | |
---|---|---|
二维 | int arr[3][4] | int (*p)[4] |
三维 | int arr[3][4][5] | int (*p)[4][5] |
四维 | int arr[3][4][5][6] | int (*p)[4][5][6] |
如下用数组指针来遍历二维数组:
int aa[3][2] = {1, 2, 3, 4, 5, 6};
int (*p)[2] = aa;
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 2; j++) {
printf("%d ", *(*(p + i) + j)); // *(p + i) 为第i行数组指针的解引用,表示第i行首元素地址,
// 所以 *(p + i) + j) 表示第i行第j列元素地址
}
printf("\r\n");
}
输出日志为:
1 2
3 4
5 6
3.2 函数指针与指针函数
圆括号的优先级高于*
指针函数:int * fun(int x,int y);
函数指针:int (*fun)(int x,int y);
指针函数:
类似于 int * fun(int x,int y); 这种形式,本质是函数,函数的返回值为指针。
函数指针:
本质是指针变量,这个指针指向函数。
int (*fun)(int x,int y); // 定义
fun = &Function; // 赋值,以下两种方式均可
fun = Function;
x = (*fun)(); // 调用,以下两种方式均可,建议用1,可以清楚的指明这是通过指针的方式来调用函数
x = fun();
3.2 *p++与(*p)++与*(p++)与*++p与++*p
*的优先级与++相同,这时候需要查看其结合性,* 与++算符的结合性是从右到左
所以:*p++与*(p++)等价
*p++与*(p++) 表示先取*p的值,然后p指针自增,等价于 res = *p; p++;
(*p)++ 表示取*p的值,然后*p的值加1,等价于:res = *p; (*p)++;
*++p 等价*(++p),表示p先自增,然后取这个指针位置的元素,等价于 ++p; res = *p
++*p等价于++(*p),表示*p的值加1, 然后取值,等价于:(*p) = *(p) + 1; res = *(p)
参考: