通常,程序开发最重要的部分是找到程序中表示数据的好方法。正确的表示数据可以更容易地编写程序其余部分。
以上这段话来自《C Prime Plus》第17章—高级数据表示。在工作中发现,许多有问题的程序,究其原因都是在设计之时缺乏对数据表示的深入的思考:一个不经意的变量使用,一个本可以复用却新写的函数,一个性能不佳但是可以快速实现的逻辑等等。
有时候我们感觉一个程序很烂,但是真要去改的时候却无从下手。恰好过去这一阵都在做代码的重构,大体的方法不难,但是我发现由于编程基本功的不扎实许多方法想不到。就像今天要记录的C语言链表。
注:以下示例摘自《C Prime Plus》第17章。
假如我们来编写一个程序,让用户输入一年内看过的所有电影。要存储每部电影的各种信息,如片名、发行年份、导演、主演、影片种类等等。建议使用一个结构存储每部电影,一个数组存储一年内看过的电影。为了简单起见,我们规定结构中只有两个成员:片名和等级(1~10)。
对于电影信息,可以采取结构数组的存储方式:
#define MAX_SIZE 5
#define MAX_NAME_LEN 50
struct film{
char name[MAX_NAME_LEN];
int rating;
};
int main(void){
struct film movies[MAX_SIZE];
...
}
结构数组的弊端是数据表示不太灵活:电影数组的成员数量是固定的。相对灵活的方式为,程序在运行时确定所需内存量,可以通过动态内存分配来表示数据。
我们定义一个指向结构体的指针,通过malloc动态分配内存,由用户输入电影的最大数量,在程序的运行过程中完成一次性创建。
printf("Enter the maximum number of movie list: ");
scanf("%d", &n);
struct film * movies_list = (struct film *)malloc(n*sizeof(struct film));
以上,虽然我们已经实现了动态分配数组的大小,但是理想的情况应该是,用户可以不确定的添加数据,而不是先制定要输入多少项。如果输入3部电影,程序就调用malloc() 3次;如果输入100部电影,程序就调用malloc()100次。
这样会带来一个新的问题,如果不是一次性申请内存,那么无法保证分配到连续的内存块。
同样是完成5个film结构体的内存申请。
第一种方法,调用malloc()一次,为5个film结构体分配内存空间。
struct film * movie;
movie = (struct film *) malloc(5*sizeof(struct film);
分配的内存是连续的。
第二种方法,调用malloc() 5次,分别为每个film结构请求分配足够的空间。
int i;
struct film *movies[s];
for (i = 0; i < 5; i++)
movies[i] = (struct films *) malloc(sizeof(struct films));
分配的内存地址是不连续的。
对于动态申请内存所带来的地址的不连续,我们应该如何解决呢?
答案便是使用链表结构。我们需要再原来的结构体中接入一个指针来追踪即将待生成的film数据。
struct film {
char title[TSIZE];
int rating;
struct film * next;
};
虽然结构不能含有与本身类型相同的机构,但是可以含有指向同类型结构的指针。这种定义是定义链表(linked list)的基础,链表中每一项都包含着在何处能找到下一项的信息。
对于一个链表,我们需要一个头指针(head pointer)指向链表中的第一个成员,next指向下一个成员,最后一个成员的next指针设置为NULL,表示没有其他成员了。如下:
#define TSIZE 45
struct film {
char title[TSIZE];
int rating;
struct film * next;
};
struct film * head;
假设用户输入的片名是 Modern Times,等级为 10。那么,链表结构如下:
假设用户加入新的电影 Midnight inParis 和 评分 8。那么结构更新如下:
每加入一部新的电影,就以相同的方式处理。
以上,我们便了解了链接的原理,程序的实现我们便留给《C语言链表—实战篇》。