C和指针 5.1 操作符

缘起

《C和指针》 5.1节

分析

C提供了极为丰富的操作符

移位操作符

左移是没有什么问题的, 都是右边补0. 但是右移就有区分了.

  • 算术右移

    保符号, 例如 -6>>1=-3, 这是因为 -6作为整型 在计算机中存储为(6 作为int的源码–>6作为int的源码的反码–>6作为int的源码的反码+1 就是 -6 作为int在计算机中是如何存储的)

    1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1010‬

    然后右移动1位得到

    11111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 101

    注意,因为是保符号的, 所以最高位(亦即符号位)也是1. 而这个数字作为int就是 -3

    如果再有符号移动1位,则在计算机中存储为

    111111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 10

    即最高位(亦即符号位)依旧补充1,这个数字在计算机中就是-2. 所以 -6>>2=-2.

  • 逻辑右移

    不保符号, 仅仅是右移动, 高位补0. 在 java中有无符号右移运算符 >>>, 则 -6>>>1就是

    1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1010‬

    向右移动1位,而因为不保符号, 所以最高位填充的是0,即

    01111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 101

    而这个数字可大了——2147483645, 即 -6>>>1 = 2147483645

    如果再次向右无符号移动一位的话,就是

    0 01111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 10

    即最高位依旧补0. 得到 1073741822, 即 -6>>>2 = 1073741822

FBI WARNING

C标准规定无符号值执行所有移位操作都是逻辑移位. 但是对于有符号值,到底是采用逻辑移位还是算术移位这取决于具体的编译器实现. 因此,如果一份源代码采用了有符号数的右移位操作的话,该源代码就是不可移植的(但是java跨平台就做到了——>>> 就是逻辑移位). 还有, a<<-5 表示什么呢? 在某些机器上——它表示左移27位. 而且如果移位的位数(27)比操作数的位数还多的话,会发生什么情况呢? C标准说明这类移位操作都是未定义的! 具体依赖各个编译器的实现. 然鹅,很少有编译器的用户文档会说明这种情况会发生什么. 所以实际写代码的时候我们要避免这种情况的发生.

位操作

下面的操作将指定位(num)设置为1

1
value |= 1<<num

下面的操作将指定位(num)设置为0

1
value &= ~(1<<num)

赋值操作符

下面的代码会有问题

1
2
3
4
5
char ch;
while((ch=getchar())!=EOF)
{
...
}

因为getchar返回的是整型. 返回给char型数据ch会被截断.

  • 在默认ch解释为有符号字符类型的机器上, 如果getchar()读到了一个 ‘\377’ (也就是十进制的255)字节的话, 则单纯的截断低八位给ch, ch就是8个1,ch又是有符号的字符,则与EOF比较的时候会被提升为32个1(保符号嘛),则与EOF相等了. 则文件虽然没到末尾,但是读取也结束了. 这就有问题
  • 在默认ch解释为无符号字符类型的机器上, 则上述循环永不结束~ 因为ch是无符号的,与EOF比较提升的时候,高24位全部用0填充, 则一定与EOF(32个1)不相等. 所以循环永远不结束. 这也有问题.

所以,在写上面的代码的时候,不要用char声明ch,而要用int.

复合操作符

+= 之类的操作符被称为复合操作符. 复合操作符保证右边的表达式求完值之后才与左操作数进行运算——即便右边的表达式中存在优先级比+低的运算符. 例如

10+= 3<<1; 得到16,尽管 << 比 + 优先级低.

换言之

1
a+=expression; 等价于  a+=(expression);

但是 a+=5 和 a=a+5 在现代编译器下产生的优化代码已经没有了差别.

单目操作符

sizeof, 可以

1
2
3
4
5
6
7
8
9
10
11
12
13
printf("%d\n", sizeof int);

int a = 1;
printf("%d\n", sizeof a);

printf("%d\n", sizeof(int));

int a = 1;
printf("%d\n", sizeof(a);

int a = 1, b = 10;
printf("%d\n", sizeof(a=b+1));
printf("%d\n",a);

注意最后一个, sizeof并没有要求表达式求值,所以最后打印的a依旧是1, 而不是11.

逻辑操作符 && || !

逻辑操作符看上去与位操作符很想,但是不一样. 逻辑操作符求值的时候会短路,但是位操作符不会.

逗号操作符

expression1,expression2,…,expressionN

自左向右求各个表达式的值,整个逗号表达式最后的值就是expressionN的值.