软件比硬件更困难
软件很容易修改,但在其他的各个方面都比硬件显得更困难?因为软件是如此难于开发和获得
正确的结果,作为程序员,我们需要找到尽可能容易的方法。其中一种方法(适用于所有语言,
并不限于C语言)就是使代码便于调试。当你编写程序时,提供debugging hook。
debugging hook
你知不知道绝大多数调试器都允许从调试器命令行调用函数?如果你拥有十分复杂的数据结构,它
将会非常有用。你可以编写并编译一个函数,用于遍历整个数据结构并把它打印出来。这个函数不会在代码的任何地方被调用,但它却是可执行文件的一部分。他就是debugging hook。
当调试代码并停在某个断点是,你可以很容易就通过手工调用你的打印函数来检查结构的完整性。
我们已经提示了可调试性编码。建议在为FSM编写代码时使用两个明显的阶段:首先进行状态转换,并只有当它们处于工作状态时才提供动作。不要把增量开发(incremental development)和显式代码调试(debugging code into existence)混为一谈,后者是程序员新手或那些开发时间非常紧的程序员常常采用的一种技巧。显式代码调试就是首先仓促地编写一个程序框架,然后在接下来几周的时间里通过修改无法运行的部分对程序进行持续的完善,直到程序能工作。同时,任何人如果要依赖系统组件,可能会急的发疯。Sendmail和make是两个广为人知的被认为原先是显示代码调试的程序。这就是为什么它们的命令语言是如此的考虑不周和难于学习。
可调试编码意味着把系统分成几个部分,先让程序总体结构运行。只有基本的程序能够运行之后,
你才为那些复杂的细节完善、性能调整和算法优化进行编码。
华丽的散列表
散列(Hash)表是一种提高访问表中数据元素的方法。它不是线性地搜索表中的元素,而是一下子跳到最相像的元素来存储值。
这是通过精心地向表载入元素来实现的。它并不是按照线性顺序,而是应用某种形式的转换(称为散列函数),把数据值和存储它的位置联系起来。散列函数会产生一个范围为0~表的长度减1的值,它就是所需要存储的数据的索引。
如果这个位置已经被别的元素占领,那么就从这个位置起继续向前搜索,直到找到一个空的位置。
另一个解决位置冲突的办法是建立一个链表,挂在这个位置的后面,所有散列函数值为这个位置的元素都添加到这个链表中(既可以从链表头部插入,也可以从尾部追加)。甚至可以在这个位置后面再挂一个散列表。
当查找一个数据项时,不需要从表中第一个元素起挨个查找,而是先产生该数据项的散列函数值,并根据这个值找到表中相应的位置,然后从该位置开始查找该数据项。
散列表是一种被广泛使用和严格测试的表查找优化方法,在系统编程中到处都有它的踪影:数据库、操作系统和编译器。
我的同事需要编写一个程序。他并不想一步登天,一次完成所有的任务。他首先让最简单的情况
能够运行,就是散列表函数总是返回一个零。这个散列函数如下:
/*hash_file:占位符,为将来更复杂的程序留下位置*/
int hash_filename(char *s) {
return 0;
}
调用这个散列函数的代码如下:
/*
*find_file:定为以前建立的文件描述符,需要时可以新建一下
*/
file find_file_name(char *s) {
int hash_value = hash_filename(s);
file f;
for (f = file_hash_table[hash_value]; f != NULL; f = f->flink) {
if (strcmp(f->fname, s) == SAME) {
return f;
}
}
/*文件未找到,建立一个新文件*/
f = allocate_file(s);
f->flink = file_hash_table[hash_value];
return f;
}
他总结为“brain,pain,gain”(思考、痛苦、收获)
int hash_filename(char *s) {
int length = strlen(s);
return (length + 4 * (s[0] + 4 * s[length/2])) % FILE_HASH
}
有时候,花点时间把编程问题分解成几个部分往往是解决它的最快方法。