使用指针创建和使用动态数组
当预先并不知道数据的长度时,可以使用动态数组。绝大多数具有数组的编程语言都能够在运行时设置数组的长度。它们允许程序员计算需要处理的元素的数目,然后创建一个刚好能容纳这些元素的数组。比较新的语言GUN C编译器实现的语言版本等,也允许声明长度可在运行时设置的数组。
然而,在ANSI C中,数组是静态的---数组的长度在编译时便已确定不变。在这个领域,C语言的支持很弱,你甚至不能使用像下面这样的常量形式:
const int limit = 100;
char plum[limit];
error:integral constant expression expected(错误,期待整型常量表达式)
在C++中,这样的语句时合法的。
在ANSI C中引入动态数组应该是比较容易的,因为这个特性所需要的“前向艺术”(prior art)功能已经存在。
所需要做的就是把标准第5.5.4节中下面这一行
direct-declarator[const-expression opt]
改为
direct-declarator[expression opt]
如果去除这个人为限制,数组的定义事实上会更简单一些。如果真能那样做的话,C语言的功能将会得到增强,而且仍能与K&R C保持兼容。幸运的是,除此之外仍然有办法实现动态数组的功能(代价就是我们必须亲自做一些指针操作)
从程序的信息中得到启发
使用strings使用程序从二进制文件内部查看程序可能产生的错误信息是很有帮助的。如果strings已经被国际化并且可以把信息输出到另一个文件中,你甚至不需要查看这个二进制文件。如果用strings检查yass程序,会发现它的错误在最近的两个版本有着显著的不同。特别是,错误信息:
%strings yacc
:
too many states(太多的状态)
变成了
%strings yacc
:
cannot expand table of states(无法扩展状态表)
原因是yacc程序升级,它的内部表(internal table)现在是动态分配的,可以根据需要进行扩张。
有意义的错误信息
在编译器中有时也会出现有趣的字符串。据说,下列字符串都是从Apollo C编译器中找到的:
00 cpp says it's hopeless but trying anyway(cpp表示希望很渺茫,但它尽量试试)
14 parse error: I just don't get it(解析错误,我无法理解它)
77 you learned to program in Fortran, didn't you?(你是从Fortran学习编程的,是不是?)
033 linker attempting to "duct tape" this "gerbil" of a program(链接器视图“牢牢绑住”程序中的
“活蹦乱跳的沙鼠”)
信息应该具有启发性,而非煽动性,并且要避免使用诸如带有亵渎性、口语化、幽默或者夸张的
非专业用语。
C语言如何实现动态数组。它的基本思路就是使用malloc函数库(内存分配)来得到一个指向一大块内存的指针。然后,像引用数组一样引用这块内存,其机理就是一个数组下标访问可以改写为一个指针加上偏移量。
#include
#include
...
int size;
char *dynamic;
char input[10];
printf("Please enter the size of array: ") ;
size = atoi(fgets(input, 7, stdin));
dynamic = (char *)malloc(size);
...
dynamic[0] = 'a';
dynamic[size-1] = 'z';
动态数组对于避免预定义的限制也是非常有用的。这方面的经典例子是在编译器中。我们不想把编译器符号表的记录数量限制一个固定的数目上,但也不想一开始就建立一个非常巨大的固定长度的表,这样会导致其他操作的内存空间不够。
报告Bug有助于提高产品的质量
客户犯了两个错误:
一是他们没有报告这个错误;二是他们没有对这个问题进行深入调查。
对报告的问题进行修正是优先级最高的任务。
1.向客户支持中心报告你所发现的所有的产品缺陷。
2.禅宗思想和软件维护的艺术都建议,你应该花点时间调查任何所发现的Bug,也许这些Bug可以很容易就解决掉。
我们真正需要实现的是使表具有根据需要自动增长的能力,这样它的唯一限制就是内存的总容量。如果不是直接声明一个数组,而是在运行时在堆上分配数组的内存,就可以实现这个目标。有一个库函数realloc(),它能够对一个现在的内存块大小进行重新分配(通常是使之扩大),同时不会丢失原先内存块的内容。当需要在动态表中增长一个项目时,可以进行如下操作。
1.对表进行检查,看看他是否真的已满。
2.如果确实已满,使用realloc()函数扩展表的长度,并进行检查,确保realloc()操作成功进行。
3.在表中增加所需要的项目。
用C代码表示,大致如下:
int current_element = 0;
int total_element = 128;
char *dynamic = malloc(total_element);
void add_element(char c) {
if (current_element == total_element - 1) {
total_element *= 2;
dynamic = (char *)realloc(dynamic, total_element);
if (dynamic == NULL) {
error("Couldn't expand the table");
}
}
current_element++;
dynamic[current_element] = c;
}
在实践中,不要把realloc()函数的返回值直接赋给字符指针。如果realloc()函数失败,它会使该指针的值变成NULL,这样就无法对现有的表进行访问。
所有重要的固定长度的表(人们在实际使用中受到限制)都进行了修改,使之能够自动增长。这个技巧并不是在所有地方都应该使用,理由如下。
*当一个大型表格突然需要增长时,系统的运行速度可能会慢下来,而且这在什么时候发生是无法预测的。内存分配成倍增长是最关键的原因。
*重分配操作很可能把原先的整个内存块移到一个不同的位置,这样表格中元素的地址便不再有效。为避免麻烦,应该使用下标而不是元素的地址。
*所有的增加和删除操作都必须通过函数来进行,这样才能维持表的完整性。只是这样一来,修改表所涉及的内容就比仅仅使用下标要多得多。
*如果标的项目数量减少,可能应该缩小表并释放多余的内存。这样内存收缩的操作对程序的运行速度有很大的影响。每次搜索表格时,编译器最好能够知道任一时刻表的大小。
*当某个线程对表进行内存重新分配时,你可能想锁住表,保护表的访问,防止其他线程读取表。对于多线程代码,这种锁总是必要的。
数据结构动态增长的另一种方法是使用链表,但链表不能进行随机访问。你只能线性地访问链表(除非把频繁访问的链表元素的地址保存在缓冲区呢),而数组则允许随机访问,这可能在性能上造成很大的差异。