第2章 信息的表示与处理
布尔代数
位运算
有个有趣的C语言的值交换写法,如下。
void swap(int *x, int *y){
*y = *x ^ *y;
*x = *x ^ *y;
*y = *x ^ *y;
}
没有用到第三方元素交换,很有趣,但是并没有性能上的提高。
对于这个写法,我们首先要知道异或运算,然后有
。
可以看成
,而
,
。故有上面那种写法了。
无符号数问题
无符号数的转换
补码转为无符号数
对满足
的
有:
例如 ,同时 。
无符号数转换为补码
对满足
的
有:
C语言中的有符号数与无符号数 (前方有高能BUG出现,要画重点!!!)
当执行一个运算时,如果它的一个运算数是有符号的而另一个是无符号的,那么C语言会隐式地将有符号参数强制类型转换为无符号数,并假设这两个数都是非负的,来执行这个运算。就像我们将要看到的,这种方法对于标准的算术运算来说并无多大差异,但是对于像 和 这样的关系运算符来说,它会导致非直观的结果。如下表所示。
表达式 | 类型 | 求值 |
---|---|---|
无符号 | ||
有符号 | ||
无符号 | ||
有符号 | ||
无符号 | ||
有符号 | ||
有符号 | ||
无符号 |
就像我们看到的那样,有符号数到无符号数的隐式强制类型转换导致了某些非直观的行为。而这些非直观的特性经常导致程序错误,并且这种包含隐式强制类型转换的细微差别的错误很难被发现。因为这种强制类型转换是在代码中没有明确指示的情况下发生的,程序员经常忽视了它的影响。
下面两个练习题说明了某些由于隐式强制类型转换和无符号数据类型造成的细微的错误。
练习题1
#include<iostream>
float sum_elements(float a[], unsigned length) {
int i;
float result = 0;
for (i = 0; i <= length - 1; i++)
result += a[i];
return result;
}
int main() {
float a[5] = { 1,1,1,1,1 };
printf("%f\n", sum_elements(a, 0));
system("pause");
}
当参数length等于0时,运行这段代码应该返回0.000000。但实际上,运行时会遇到一个内存错误,如下图所示。
原因:
将参数length作为一个无符号数来传递看上去是件相当自然的事情,因为没有人会想到使用一个长度为负数的值。停止条件i <= length - 1
看上去也很自然。但是把这两点组合到一起,将产生意想不到的结果!
因为参数length是无符号的,计算0-1将使用无符号运算,这等价于模数加法。结果得到
。
比较同样使用无符号数比较,而因为任何数都是小于或者等于
的,所以这个比较总是为真!因此,代码将试图访问数组a的非法元素。
解决方法:
将length声明改为int类型。
练习题2
#include<iostream>
#include<string.h>
int strlonger(char *s, char *t){
return strlen(s) - strlen(t) > 0;
}
int main() {
char *s = "333", *t = "55555";
printf("%d\n", strlonger(s, t));
system("pause");
}
一般以为会打印0,结果是1,如下图所示。
原因:
首先我们看字符串库函数strlen的声明,如下:
/* Prototype for library function strlen */
size_t strlen(const char *s);
在头文件stdio.h中数据类型size_t是定义成unsigned int的。由于strlen被定义为产生一个无符号的结果,差和比较都采用无符号运算来计算。当s比t短的时候,strlen(s)-strlen(t)的差会为负,但是变成了一个很大的无符号数,且大于0。
解决方法:
改成return strlen(s) > strlen(t);