首次快速阅读完《C陷阱与缺陷》,感觉还是读得有些稀里糊涂的。
首先给人的感觉就是大部分人可以感受到的,作者对于较为初代C语言的研究已经达到了可以说是“炉火纯青”的程度。
尽管作者写这本书的时候距离至今已经过去了二十几年,但很多C语言所存在的缺陷以及作者的思想至今都适用。
与大部分书类似,该书也是由浅入深,由易到难的方式切入。
从相对简单的“=与==”不同开始,到后面让我看不懂到抓狂的可移植性缺陷。
下面列举些书中有意思的问题:
栏杆问题
这个问题个人觉得可以说是很经典的。也许作为程序员,C和C++数组或者说很多情况下标从0开始已经深入骨髓,但作者却通过一个很现实的情况对“下标”做了很恰当的“比喻”。
修建一个100英尺长的护栏,护栏的栏杆之间相距10英尺,你需要多少根栏杆。
答案是11根。
作者在这里由这个实例引用了边界计算与不对称边界。在所有常见的程序设计错误中,最难于察觉的一类就是“栏杆错误”。
为了避免这个错误有两个通用原则:
(1)、首先考虑最简单情况下的特例,然后将得到的结果外推。
(2)、仔细计算边界。
a+++++b
在初学C语言后,尤其是在学习了运算优先级后,很容易将这句代码理解为:a++ + ++b,但是,根据“贪心法”的规则,上式会被分解成:
a++ ++ +b
这个式子从语法上来说是不正确的。
当一个程序异常终止时
当一个程序异常终止时,程序输出的最后几行常常会丢失,原因是什么?我们能够采取怎样的措施来解决这问题?
setbuf(stdout,(char*)0);
这个语句必须在任何输出被写入到stdout(包括任何对printf函数的调用)之前执行。该语句最恰当的位置就是作为main函数的第一个语句。
除法运算时发生的截断
假设让a除以b,商为q,余数为r:
q=a/b;
r=a%b;
假设b>0。
从逻辑上讲,我们希望a,b,q,r之间维持如下关系。
1、最重要的一点,我们希望q*b+r==a,因为这是定义余数的关系,如果这个都不维持,数学会抓狂的。
2、如果我们改变a 的正负号,我们希望这会改变q的符号,但这不会改变q 的绝对值。
3、当b>0时,我们希望保证r>=0且r<b。例如,如果余数用于哈希表的索引,确保它是一个有效的索引值很重要。
这三条性质是我们认为整数除法和余数操作所应该具备的。但不幸的是,它们不可能同时成立。
考虑一个简单的例子:3/2,商为1,余数也为1 。此时,第一条性质得到了满足。(-3)/2的值应该是多少呢?如果要满足第二条性质,答案应该是-1 ,但如果是这样,余数就必定是-1,这样第三条性质就无法满足了。如果我们首先满足第三条性质,即余数是1,这种情况下根据第一条性质则商是-2,那第二条性质又无法满足了。
因此,C语言或者其他语言在实现整数除法截断运算时,必须放弃上述三条原则中的至少一条。大多数程序设计语言选择放弃第三条,而改为要求余数与被除数的正负号相同。这样,性质1和性质2就可以得到满足。大多数C编译器在实践中也都是这样做的。
然而,C语言的定义只保证了性质1,以及当a>=0且b>0时,保证|r|<|b|以及r>=0。后面部分的保证与性质2或者性质3比较起来,限制性要弱得多。
在数学上,我们可以将一个整数a除以一个非零整数b,得出两个整数c和d,使得
a = b * c + d
其中c是商,d是余数。如果a<0,那么我们可以将a表示为
a = -(-a)
因此,我们可以重写上述公式,得到:
-a = b * (-c) + d
也就是说,当a<0时,余数d的符号与b相反,即为-b或者-b的绝对值中的一个。
类似的,如果b<0,我们也可以用类似的方式来求余数d。如果a和b都小于0,就需要将上述两种情况进行合并。此时,可以先将a和b都乘以-1,之后按照以上方法得出余数,然后再将余数乘以-1得到正确的结果。
需要注意的是,在实际编程中,C++中有两个函数可以求解除法的商和余数,分别是div
和std::div
.它们的返回值是一个结构体,包含商和余数两个成员:c = div(a,b).quot
和d = div(a,b).rem
。此外,关于负数除法运算的详细请参考C++标准中[N3337]§5.6.4和expr.multip。
举例:
int main()
{int a, b, q, r;a = -3;b = -2;q = a / b;r = a % b;printf("%d %d\n", q, r);return 0;
}
输出:1,-1
int main()
{int a, b, q, r;a = -3;b = 2;q = a / b;r = a % b;printf("%d %d\n", q, r);return 0;
}
输出:-1,-1
int main()
{int a, b, q, r;a = 3;b = -2;q = a / b;r = a % b;printf("%d %d\n", q, r);return 0;
}
输出:-1,1