操作符与表达式
一、移位
在左移位中,值最左边的几位被丢弃,右边多出来的儿个空位则由0 补齐。
从左边移入新位时,可以选择两种方案。一种是逻辑移位,左边移入的位用0 填充;另一种是算术移位,左边移入的位由原先该值的符号位决定。如果值10010110 右移两位,逻辑移位的结果00100101, 但算术移位的结果是11100101 。算术左移和逻辑左移是相同的,它们只在右移时不同,而且只有当操作数是负值时才不一样。
二、赋值
在下面的语句中,认为a 和x 被赋子相同的值的说法是不正确的:
a = x = y + 3;
如果x 是一个字符型变量,那么y+3 的值就会被截去一段,以便容纳于宇符类型的变量中。那
么a 所赋的值就是这个被截短后的值。在下面这个常见的错误中,这种截短正是问题的根源所在:
// 一段错误的代码
char ch;
...
while ((ch = getchar()) ! = EOF)...
EOF 需要的位数比字符型值所能提供的位数要多,这也是getchar 返回一个整型值而不是字符
值的原因。然而,把getchar 的返回值首先存储于ch 中将导致它被截短。然后这个被截短的值被提升为整型并与EOF 进行比较。当这段存在错误的代码在使用有符号宇符集的机器上运行时,如果读取了一个值为\377的字节时,循环将会终止,因为这个值截短再提升之后与EOF 相等。当这段代码在使用无符号字符集的机器上运行时,这个循环将永远不会终止!
三、计算一个值中位为1的个数
方法一:
#include <stdio.h>
int count_ones(unsigned int value) {
int count = 0;
while (value) {
if (value & 1) { // 检查最低位是否为1
count++;
}
value >>= 1; // 将值向右移动一位
}
return count;
}
int main() {
unsigned int value = 123; // 一个示例值
int count = count_ones(value);
printf("Number of ones: %d\n", count);
return 0;
}
方法二:关键在于 value &= (value - 1)
这一行代码。它通过将 value
与 value - 1
进行按位与操作,将最右侧的1变为0。对于具有较少位为1的值,它的性能非常高,因为每次循环迭代都会消除一个1,从而减少了循环次数。
#include <stdio.h>
int count_ones(unsigned int value) {
int count = 0;
while (value) {
value &= (value - 1); // 清除最右侧的1
count++;
}
return count;
}
int main() {
unsigned int value = 123; // 一个示例值
int count = count_ones(value);
printf("Number of ones: %d\n", count);
return 0;
}
/*
value = 1010
1010
& 1001
---------
1000
1000
& 0111
---------
0000
count = 2
*/
四、++与–
在操作数之前的操作符在变量值被使用之前增加它的值;在操作数之后的操作符在变量值被使用之后才增加它的值。
五、&& 与 ||
&& 操作符的左操作数总是首先进行求值,如果它的值为真,再对右操作数进行求值。如果左操作数的值为假,那么右操作数便不再进行求值。
|| 操作符的左操作数总是首先进行求值,如果它的值为假,再对右操作数进行求值。如果左操作数的值为真,那么右操作数便不再进行求值。
六、左值与右值
有如下语句:
a = b + 25;
a 是个左值,因为它标识了一个可以存储结果值的地点, b+ 25 是个右值,因为它指定了一个值。它们可以互换吗?
b + 25 = a;
不可以。原先用作左值的a 此时也可以当作右值,因为每个位置都包含一个值。当计算机计算b+25 时,它的结果必然保存于机器的某个地方。但是,程序员并没有办法预测该结果会存储在什么地方,也无法保证这个表达式的值下次还会存储千那个地方。其结果是,这个表达式不是一个左值。基于同样的理由,字面值常量也都不是左值。
听上去似乎是变量可以作为左值而表达式不能作为左值,但这个推断并不准确。在下面的赋值
语句中,左值便是一个表达式。
int a [30];
a[ b + 10 ] = O;
下标引用实际上是一个操作符,所以表达式的左边实际上是个表达式,但它却是一个合法的左
值,因为它标识了一个特定的位置,我们以后可以在程序中引用它。这里有另外一个例子:
int a, *pi;
pi = &a;
*pi= 20;
第2 条赋值语句左边的那个值显然是一个表达式,但它却是一个合法的左值。因为指针pi 的值是内存中某个特定位置的地址,*操作符使机器指向那个位置。当它作为左值使用时,这个表达式指定需要进行修改的位置。当它作为右值使用时,它就提取当前存储千这个位置的值。