运算符的结合性和优先级
优先级 | 运算符 | 描述 | 结合性 |
---|---|---|---|
1 | () [] -> . | 函数调用、数组下标、结构体 / 联合体成员通过指针访问、结构体 / 联合体成员访问 | 从左到右 |
2 | ! ~ ++ (前缀)-- (前缀)+ (一元)- (一元)* (间接寻址)& (取地址)sizeof (type) | 逻辑非、按位取反、前缀自增、前缀自减、一元正号、一元负号、间接寻址、取地址、获取操作数的大小、类型转换 | 从右到左 |
3 | * / % | 乘法、除法、取模 | 从左到右 |
4 | + - | 加法、减法 | 从左到右 |
5 | << >> | 左移、右移 | 从左到右 |
6 | < <= > >= | 小于、小于等于、大于、大于等于 | 从左到右 |
7 | == != | 相等、不相等 | 从左到右 |
8 | & | 按位与 | 从左到右 |
9 | ^ | 按位异或 | 从左到右 |
10 | <code>|</code> | 按位或 | 从左到右 |
11 | && | 逻辑与 | 从左到右 |
12 | <code>||</code> | 逻辑或 | 从左到右 |
13 | ? : | 条件运算符(三元运算符) | 从右到左 |
14 | = += -= *= /= %= <<= >>= &= ^= <code>|=</code> | 赋值、复合赋值运算符 | 从右到左 |
15 | , | 逗号运算符 | 从左到右 |
上手demo
#include <stdio.h>int main() {int a = 2, b = 3, c = 4;// 乘法优先级高于加法int result1 = a + b * c; // 先计算 b * c,结果为 12,再加上 a,最终结果为 14printf("result1: %d\n", result1); // 使用括号改变优先级int result2 = (a + b) * c; // 先计算 a + b,结果为 5,再乘以 c,最终结果为 20printf("result2: %d\n", result2); // 后缀自增和前缀自增int d = 5;int result3 = d++ + ++d; // 先使用 d 的值 5 进行计算,然后 d 自增,再将自增后的 d(此时为 7)加 1,最终结果为 12printf("result3: %d\n", result3); // 逻辑运算符int result4 = (a < b) && (b < c); // 先计算 a < b 和 b < c,结果都为真,最终结果为 1printf("result4: %d\n", result4); // 条件运算符int result5 = (a > b)? a : b; // a 不大于 b,所以结果为 b 的值,即 3printf("result5: %d\n", result5); // 复合赋值运算符a += b; // 等价于 a = a + b,a 的结果为 5printf("a after +=: %d\n", a); return 0;
}
括号运算符 ()
用于改变运算的优先级,也用于函数调用和类型转换,其在表达式中,括号内的运算总是优先进行。
#include <stdio.h>int main() {int a = 5, b = 3, c = 2;// 改变运算顺序int result1 = (a + b) * c; // 先计算 a + b,结果为 8,再乘以 c,最终结果为 16printf("result1: %d\n", result1); // 函数调用int sum(int x, int y) {return x + y;}int result2 = sum(a, b); // 调用 sum 函数,将 a 和 b 的值传递给函数,结果为 8printf("result2: %d\n", result2); // 类型转换float f = (float)a; // 将整型 a 转换为浮点型,结果为 5.0printf("result3: %f\n", f); return 0;
}
数组下标运算符 []
用于访问数组中的元,其下标必须是整数,可以是常量或变量。
*越界访问数组(使用超出数组长度的下标)会导致未定义行为!!!
#include <stdio.h>int main() {int arr[5] = {10, 20, 30, 40, 50};int index = 2;int element = arr[index]; // 访问数组 arr 中索引为 2 的元素,结果为 30printf("element: %d\n", element); return 0;
}
结构体和联合体成员访问运算符 .
和 ->
.
用于直接访问结构体或联合体的成员。->
用于通过指针访问结构体或联合体的成员。
#include <stdio.h>
#include <stdlib.h>struct Point {int x;int y;
};int main() {struct Point p = {10, 20};// 直接访问成员int x = p.x; // 访问结构体 p 的成员 x,结果为 10printf("x: %d\n", x); struct Point *ptr = (struct Point *)malloc(sizeof(struct Point));ptr->x = 30; // 通过指针访问成员 xptr->y = 40; // 通过指针访问成员 yprintf("ptr->x: %d\n", ptr->x); printf("ptr->y: %d\n", ptr->y); free(ptr); return 0;
}
一元运算符
逻辑非 !
对操作数取逻辑反,将非零值变为 0,当操作数 0 返回结果为 1。
#include <stdio.h>int main() {int a = 0;int result =!a; // 结果为 1printf("result: %d\n", result); return 0;
}
按位取反 ~
对操作数的二进制表示按位取反,也包括符号位(0为正,1为负)。
#include <stdio.h>int main() {int a = 5; // 二进制: 0101int result = ~a; // 结果为 -6(二进制: 1010)printf("result: %d\n", result); return 0;
}
前缀 / 后缀自增 ++
和前缀 / 后缀自减 --
- 前缀:先将操作数加 / 减 1,再使用结果。
- 后缀:先使用操作数的值,再将操作数加 / 减 1。
#include <stdio.h>int main() {int a = 5;int b = ++a; // 前缀自增,a 先加 1 变为 6,b 为 6int c = a++; // 后缀自增,使用 a 的值 6,然后 a 加 1 变为 7printf("b: %d, c: %d, a: %d\n", b, c, a); return 0;
}
一元正号 +
和一元负号 -
+
:不改变操作数的值,主要用于明确表示正数。-
:对操作数取负。
#include <stdio.h>int main() {int a = 5;int b = -a; // 结果为 -5int c = +a; // 结果为 5printf("b: %d, c: %d\n", b, c); return 0;
}
间接寻址 *
和取地址 &
*
:解引用指针,获取指针访问所指向的值。&
:获取变量的地址。
#include <stdio.h>int main() {int a = 10;int *ptr = &a; // 获取 a 的地址存储在 ptr 中int value = *ptr; // 通过 ptr 访问 a 的值,结果为 10printf("value: %d\n", value); return 0;
}
sizeof
运算符
获取操作数的大小(以字节为单位)。
#include <stdio.h>int main() {int a;size_t size = sizeof(a); // 获取 int 类型的大小,通常为 4printf("size: %zu\n", size); return 0;
}
类型转换 (type)
将操作数转换为指定的数据类型。当是多精度转单精度类型时,可能导致数据丢失(如浮点数转整数)或截断。
#include <stdio.h>int main() {double d = 3.14;int i = (int)d; // 将双精度浮点数转换为整数,结果为 3printf("i: %d\n", i); return 0;
}
算术运算符
乘法 *
和除法 /
*
:两个操作数相乘。/
:两个操作数相除,对于整数除法,结果会舍去小数部分;对于浮点数除法,使用float
或double
类型。
#include <stdio.h>int main() {int a = 10, b = 3;int product = a * b; // 结果为 30int quotient = a / b; // 结果为 3(整数除法)printf("product: %d, quotient: %d\n", product, quotient); return 0;
}
取模 %
计算两个整数相除的余数。操作数必须是整数,否则会导致编译错误!!!
#include <stdio.h>int main() {int a = 10, b = 3;int remainder = a % b; // 结果为 1printf("remainder: %d\n", remainder); return 0;
}
加法 +
和减法 -
+
:两个操作数相加。-
:两个操作数相减。
#include <stdio.h>int main() {int a = 5, b = 3;int sum = a + b; // 结果为 8int difference = a - b; // 结果为 2printf("sum: %d, difference: %d\n", sum, difference); return 0;
}
移位运算符
左移 <<
将操作数的二进制表示向左移动指定的位数,右边补 0。
#include <stdio.h>int main() {int a = 5; // 二进制: 0101int result = a << 2; // 左移 2 位,结果为 20(二进制: 10100)printf("result: %d\n", result); return 0;
}
右移 >>
将操作数的二进制表示向右移动指定的位数,对于无符号数左边补 0,对于有符号数根据编译器和系统可能补 0 或符号位。
#include <stdio.h>int main() {int a = 10; // 二进制: 1010int result = a >> 1; // 右移 1 位,结果为 5(二进制: 0101)printf("result: %d\n", result); return 0;
}
关系运算符:<
(小于)、<=
(小于等于)、>
(大于)、>=
(大于等于)、==
(相等)、!=
(不相等)
用于比较两个操作数的大小或相等性,结果为 1(真)或 0(假)。
#include <stdio.h>int main() {int a = 5, b = 3;int less_than = a < b; // 结果为 0(假)int less_than_or_equal = a <= b; // 结果为 0(假)int greater_than = a > b; // 结果为 1(真)int greater_than_or_equal = a >= b; // 结果为 1(真)int equal = a == b; // 结果为 0(假)int not_equal = a!= b; // 结果为 1(真)printf("less_than: %d, less_than_or_equal: %d, greater_than: %d, greater_than_or_equal: %d, equal: %d, not_equal: %d\n", less_than, less_than_or_equal, greater_than, greater_than_or_equal, equal, not_equal); return 0;
}
位运算符
- 位运算符用于对二进制位进行操作,常用于底层编程、硬件控制和性能优化。
- 按位与
&
常用于清除某些位或检查特定位是否置位。 - 按位或
|
常用于设置某些位。 - 按位异或
^
常用于反转某些位或交换两个数的值(不使用临时变量)。
按位与 &
对操作数的每一位进行逻辑与操作,都为 1 时结果为 1,否则为 0。
#include <stdio.h>int main() {int a = 5; // 二进制: 0101int b = 3; // 二进制: 0011int result = a & b; // 结果为 1(二进制: 0001)printf("result: %d\n", result); return 0;
}
按位异或 ^
对操作数的每一位进行逻辑异或操作,不同为 1,相同为 0。
#include <stdio.h>int main() {int a = 5; // 二进制: 0101int b = 3; // 二进制: 0011int result = a ^ b; // 结果为 6(二进制: 0110)printf("result: %d\n", result); return 0;
}
按位或 |
对操作数的每一位进行逻辑或操作,有一个为 1 时结果为 1,否则为 0。
#include <stdio.h>int a = 5; // 二进制: 0101int b = 3; // 二进制: 0011int result = a | b; // 结果为 7(二进制: 0111)printf("result: %d\n", result); return 0;
}
逻辑运算符
逻辑与 &&
和逻辑或 ||
具有短路特性,即对于 &&
,如果第一个操作数为假,则不计算第二个操作数;对于 ||
,如果第一个操作数为真,则不计算第二个操作数。
逻辑与 &&
当且仅当两个操作数都为真(非零)时,结果为真。
#include <stdio.h>int main() {int a = 5, b = 0;int result = (a > 0) && (b > 0); // 结果为 0(假)printf("result: %d\n", result); return 0;
}
逻辑或 ||
当两个操作数中至少有一个为真时,结果为真。
#include <stdio.h>int main() {int a = 5, b = 0;int result = (a > 0) || (b > 0); // 结果为 1(真)printf("result: %d\n", result); return 0;
}
条件运算符(三元运算符)
根据条件表达式的结果选择两个表达式中的一个,condition? expression1 : expression2
,如果 condition
为真,结果为 expression1
,否则为 expression2
。
#include <stdio.h>int main() {int a = 5, b = 3;int max = (a > b)? a : b; // 结果为 5printf("max: %d\n", max); return 0;
}
赋值运算符:=
,+=
,-=
,*=
,/=
,%=
,<<=
,>>=
,&=
,^=
,|=
将右侧表达式的值赋给左侧的变量。赋值表达式的值就是被赋的值。例如,int b = (a = 5);
中,先将 5 赋给 a
,然后将 a
的值赋给 b
。
#include <stdio.h>int main() {int a;a = 10; // 将 10 赋值给 aprintf("a: %d\n", a); return 0;
}
复合赋值运算符
结合了算术、移位或位运算与赋值操作,提高代码的简洁性,先进行算术、移位或位运算,然后将结果赋给左侧变量。
#include <stdio.h>int main() {int a = 5;a += 3; // 等价于 a = a + 3,结果为 8a -= 2; // 等价于 a = a - 2,结果为 6a *= 4; // 等价于 a = a * 4,结果为 24a /= 3; // 等价于 a = a / 3,结果为 8a %= 5; // 等价于 a = a % 5,结果为 3a <<= 1; // 等价于 a = a << 1,结果为 6(二进制:110)a >>= 1; // 等价于 a = a >> 1,结果为 3(二进制:011)a &= 2; // 等价于 a = a & 2,结果为 2(二进制:010)a ^= 1; // 等价于 a = a ^ 1,结果为 3(二进制:011)a |= 4; // 等价于 a = a | 4,结果为 7(二进制:111)printf("a: %d\n", a); return 0;
}
逗号运算符 ,
从左到右依次计算操作数,并返回最后一个操作数的值。
#include <stdio.h>int main() {int a = (1, 2, 3); // 结果为 3int b = 5, c = 10;int d = (b++, c++, b + c); // 先执行 b++ 和 c++,然后计算 b + c,结果为 17printf("a: %d, d: %d\n", a, d); return 0;
}
常用于在 for
循环的初始化或更新部分中执行多个操作
#include <stdio.h>int main() {for (int i = 0, j = 10; i < 5; i++, j--) {printf("i: %d, j: %d\n", i, j);}return 0;
}
运算符优先级和结合性
- 优先级:
- 运算符的优先级决定了表达式中运算的顺序,在复杂表达式中可能导致混淆。例如,在
a + b * c
中,先计算乘法b * c
,再计算加法。为避免混淆,可使用括号明确运算顺序,如(a + b) * c
。
- 运算符的优先级决定了表达式中运算的顺序,在复杂表达式中可能导致混淆。例如,在
- 结合性:
- 从左到右结合性:大多数二元运算符是从左到右结合的,如
a + b + c
先计算a + b
,再加上c
。 - 从右到左结合性:一元运算符和赋值运算符通常是从右到左结合的,如
a = b = c
先将c
赋给b
,再将b
的值赋给a
。
- 从左到右结合性:大多数二元运算符是从左到右结合的,如