C_表达式

C_表达式
BarbecueC_表达式
表达式是用来计算某个值的公式,表达式可以用运算符进行连接。
C语言拥有异常丰富的运算符:算术运算符、赋值运算符、关系运算符、判等运算符、逻辑运算符、位运算符等等。
算数运算符
算术运算符包含 +, -, *, /, % ,分别表示加,减,乘,除,取余。其中 / 和 % 需要注意:
/的两个操作数都是整数,结果也是整数 (向零取整)。因此,1/2的结果为 0 而不是 0.5。+,-,*,/,可以用于浮点数,但%要求两个操作数都是整数。%取余运算的结果可能为负数,i%j的符号总是和i的符号相同,比如 -9 % 7 的值为 -2。
运算符的优先级和结合性
当表达式包含多个运算符时,C 语言采用优先级来解决歧义性问题。
优先级:* 的优先级高于 +
1 | i + j * k 等价于 i + (j * k) |
当表达式中包含两个或者更多个具有相同优先级的运算符时,对比运算符的结合性进行运算。
左结合:运算符是从左向右结合的(二元运算符大多是左结合的)
1 | i - j + k 等价于 (i - j) + k |
右结合:运算符是从右向左结合的(一元运算符大多是右结合的)
1 | -+i 等价于 -(+i) |
赋值运算符
简单赋值
表达式 v = e 的作用是:求出表达式 e 的值,并把其赋值给变量 v。如:
1 | i = 5; /* i is now 5 */ |
如果 v 和 e 的类型不同,在赋值过程中会把 e 的值转换成 v 的类型:
1 | int i; |
注意:赋值表达式
v = e也有值,它的值等于赋值运算后v的值。
赋值运算符可以串联在一起,如:
1 | i = j = k = 0; |
由于赋值运算符是右结合的,上述表达式等价于:
1 | i = (j = (k = 0)); |
复合赋值
利用变量原有的值去计算新的值。例如:
1 | i = i + 2; |
复合赋值运算符简写表达式:
1 | i += 2; /* same as i = i + 2 */ |
复合赋值运算符也是右结合的:
1 | i += j += k; |
上面表达式等价于
1 | i += (j += k); |
自增运算符和自减运算符
自增运算符:++ ,自减运算符:--
++和--运算符既可以作为前缀运算符 (如 ++i , --i ),也可以作为后缀运算符 (如 i++ , i--),不过表达式的值不同。
前缀表达式: ++i 的值为(i + 1),副作用是i自增
1 | i = i + 1; |
后缀表达式: i++ 的值为i,副作用是i自增
1 | j = i; |
注意
++i意味着 “立即自增i”;而i++意味着 “先用 i 的原始值,稍后再自增 i”。- 后缀
++和后缀--比正号、负号的优先级高;前缀++和前缀--与正号、负号的优先级相同。- 运行速度从快到慢:
++i>i++>i+=1>i=i+1
关系运算符
逻辑表达式:用关系运算符,判等运算符和逻辑运算符来构建。逻辑表达式的值为 0 或者 1 (0表示false, 1表示true)。
关系运算符:包含 < , > , <= , >= 。关系运算符的优先级低于算术运算符,并且是左结合的
1 | i + j < k - 1 等价于 (i + j) < (k - 1) |
注意:表达式
i < j < k是合法的,但i < j < k等价于(i < j) < k,该表达式首先检测i是否小于j,然后用比较后产生的结果 (0 或者 1) 和k进行比较。若要测试j是否位于i和k之间,应该使用:i < j && j < k。
判等运算符
判等运算符:包含 ==, != 。判等运算符的优先级低于关系运算符,是左结合的。
1 | i < j == j < k 等价于 (i < j) == (j < k) |
逻辑运算符
逻辑运算符:包含 && , || , ! 。逻辑运算符把任何零值当作 false,任何非零值当作 true。
注意: && 和 || 会对操作数进行 “短路” 计算,操作符会首先计算左操作数的值,然后计算右操作数。
如果整个表达式的值可以由左操作数的值推导出来,将不会计算右操作数的值。如:
1 | (i != 0) && (j / i > 0) |
短路计算的好处是显而易见的,没有短路计算,上面的表达式会出现除零错误。
运算符 ! 的优先级和正负号的优先级是相同的,而且是右结合的; && 和 || 的优先级低于关系运算符和判等运算符。
1 | i < j && k == m 等价于 (i < j) && (k == m) |
位运算符
位操作在编写系统程序 (包括编译器和操作系统)、加密程序、图形程序以及性能要求非常高的程序时,非常有用。
移位运算符
移位运算符可以通过将位向左或向右移动来变换整数的二进制表示。
左移运算符 << :如 i << j ,将 i 的位左移 j 位,在 i 的右边补 0。
右移运算符 >> :如 i >> j ,将 i 的位右移 j 位。 i 是无符号数或者非负值,则在左边补 0 , i 是有符号负值,会在左边补 1 。
Tips: 为了可移植性,最好仅对无符号数进行移位运算。
1 | unsigned short i, j; |
对无符号整数左移 j 位,相当于乘以 2^j (不发生溢出);对无符号整数右移 j 位,相当于除以 2^j。
按位运算符
按位位运算符包含:按位取反,按位与,按位异或,按位或。其中按位取反是单目运算符,其余是双目运算符。
~i: 会对i的每一位进行取反操作,即 0 变成 1,1 变成 0。i & j: 会对i和j的每一位进行逻辑与运算。i | j: 会对i和j的每一位进行逻辑或运算。i ^ j: 会对i和j的每一位进行异或运算,如果对应的位相同则为0,如果对应的位不同则结果为 1。
提示: 千万不要将按位运算符
&和|与逻辑运算符&&和||混淆。
按位异或运算有良好的性质:
1 | a ^ 0 = a; |
除了按位取反运算符外,其余位运算符都有对应的复合赋值运算符:
1 | i = 21;j = 56; |
常见操作
请判断一个整数是否为奇数
1
2
3bool isOdd(int n) {
return n & 0x1;
}如何判断一个非 0 整数是否为2的幂(1, 2, 4, 8, 16, …)
1
2
3bool isPowerOf2(int n) {
return (n & n - 1) == 0;
}给定一个值不为0的整数,请找出值为1的最低有效位 (last set bit)。
1
2
3int lastSetBit(int n) {
return n & -n;
}给定两个不同的整数 a 和 b,请交换它们两个的值 (要求不使用中间临时变量)。
1
2
3a = a ^ b;
b = a ^ b;
a = a ^ b;给一个 非空整数数组 nums,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
1
2
3
4
5
6
7int singleNumber(int* nums, int numsSize) {
int result = 0;
for (int i = 0; i < numsSize; i++) {
result ^= nums[i];
}
return result;
}异或运算(^)的特性:
- 任何数与 0 进行异或运算的结果仍然是原来的数:
a ^ 0 = a - 任何数与自身进行异或运算的结果是0:
a ^ a = 0 - 异或运算满足交换律:
a ^ b = b ^ a - 异或运算满足结合律:
(a ^ b) ^ c = a ^ (b ^ c)
推断出如下结果:
- 如果一个数出现两次,那么与它异或两次的结果是0,即
a ^ a = 0。 - 如果一个数出现一次,而其它所有数都出现两次,那么对所有数进行异或运算,相同的数会抵消,最终剩下的结果就是只出现一次的数。
所以,通过对数组中的所有元素进行异或运算,最终得到的结果就是只出现一次的元素。
- 任何数与 0 进行异或运算的结果仍然是原来的数:
给一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。可以按任意顺序返回答案。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21void findSingleNumbers(int* nums, int numsSize, int* single1, int* single2) {
// Step 1: 对数组中的所有元素进行异或运算,得到两个只出现一次的元素的异或结果
int xorResult = 0;
for (int i = 0; i < numsSize; i++) {
xorResult ^= nums[i];
}
// Step 2: 找到异或结果中为 1 的某一位
int diffBit = xorResult & (-xorResult); // 使用补码操作得到最低位的 1
// Step 3: 根据第 k 位是否为 1,将数组分成两组,分别对这两组元素进行异或运算
*single1 = 0;
*single2 = 0;
for (int i = 0; i < numsSize; i++) {
if (nums[i] & diffBit) {
*single1 ^= nums[i];
} else {
*single2 ^= nums[i];
}
}
}具体的步骤如下:
- 对数组中的所有元素进行异或运算,得到的结果就是两个只出现一次的元素的异或结果。
- 找到这个异或结果中为 1 的某一位,这个位说明两个只出现一次的元素在这一位上是不同的。假设这一位是第 k 位。
- 根据第 k 位是否为 1,将数组分成两组,一组是第 k 位为 1 的元素,另一组是第 k 位为 0 的元素。
- 分别对这两组元素进行异或运算,得到的结果就是只出现一次的两个元素。
条件运算符
条件运算符是一种特殊运算符,通常称为三元运算符。用于根据某个条件来选择两个值中的一个。语法如下:
1 | condition ? value1 : value2 |
如果 condition 为真,则返回 value1,否则返回 value2。下面是一个简单的示例:
1 |
|
条件运算符的优点在于简洁性和表达能力,但是过度使用条件运算符会导致代码可读性降低。
当使用条件运算符时,注意:
- 运算符结合性:条件运算符是右结合的,意味着表达式
a ? b : c ? d : e相当于a ? b : (c ? d : e)。 - 类型转换:条件运算符会进行类型转换以保证两个操作数具有相同的类型。两个操作数的类型不同,较低类型的操作数将被提升到较高类型,然后再进行运算。
- 求值顺序:条件运算符保证了先评估 condition,然后根据 condition 的结果来评估 value1 或 value2。但是并没有规定 value1 和 value2 的求值顺序,因此在表达式
(condition ? func1() : func2())中,func1()和func2()可能会在条件求值之前都被调用,也可能只调用一个。 - 嵌套使用:条件运算符可以嵌套使用,但是要注意保持代码的清晰度。过度嵌套会导致代码难以理解。
- 返回值:条件运算符本身也是一个表达式,它的返回值可以被赋给变量,也可以作为另一个表达式的一部分使用。



