为什么链表
动态内存: malloc 与 free
结点类型与创建结点
-> 运算符
课堂编码与小测
数组有两个局限: 大小定死 (要么浪费、要么不够); 中间插入/删除要搬移整段元素
链表: 一串结点用指针串起来, 每个结点存数据 + 指向下一个结点的指针:
优点: 按需逐个创建, 插入、删除只需改指针; 最后结点的 next 是 NULL (空指针)
代价: 没有随机访问——找第 k 个元素必须从头走 k 步
链表结点是运行时逐个创建的——用 malloc 向系统借内存。struct node 是下一页要定义的结点类型,这里先看 malloc 的用法:
struct node *new_node; /* struct node 下一页定义 */
new_node = malloc(sizeof(struct node)); /* 借一块刚好放下一个结点的内存 */
if (new_node == NULL) { /* 借不到要检查 */
printf("内存不足\n");
exit(1);
}
exit(1) 直接终止程序,声明在 <stdlib.h> 中。
malloc(n): 借 n 字节, 不清零; 返回通用指针 void *; 需要 <stdlib.h>
free(p): 用完归还。忘记 free 就是"内存泄漏"——内存越用越多, 程序最终被系统杀掉
free 之后 p 就是悬空指针, 不能再解引用; 习惯上立刻把 p 置为 NULL
建链表, 先要有表示单个结点的结构体。
结点结构体: 数据 (本例是整数) + 指向下一个结点的指针:
struct node {
int value; /* data stored in the node */
struct node *next; /* pointer to the next node */
};
node 必须是结构标记, 而不是 typedef 名称, 否则将无法声明 next 的类型。
还需要一个始终指向第一个结点的变量:
struct node *first = NULL;
将 first 设置为 NULL 表示链表最初为空。
构建链表 = 逐个创建结点, 再逐个挂上去。步骤: ① 为结点分配内存 ② 在结点中存储数据 ③ 将结点插入到链表中。先看前两步:
临时指针变量: struct node *new_node; 用 malloc 给新结点分配内存, 返回值存进 new_node: new_node = malloc(sizeof(struct node));——现在 new_node 指向一块刚好能放下一个结点的内存:
接着把数据存进新结点的 value 成员: (*new_node).value = 10; (左图是分配内存, 右图是赋值后)
*new_node 周围的括号是强制要求的, 因为 . 运算符优先于 * 运算符.
-> 运算符 为了便于使用指针访问结构体的成员, C 提供了名为右箭头选择 (->) 的运算符: new_node->value = 10; 来代替 (*new_node).value = 10;
-> 运算符产生一个左值, 可以在任何允许普通变量的地方使用它。scanf 调用中的示例: scanf("%d", &new_node->value);——& 运算符仍然是必需的, 即使 new_node 是一个指针。
现场演示
提示: 头插只要两条语句 (new_node->next = first; first = new_node;); 尾插要先找尾再改两个指针
struct node *next; 中, 为什么必须写 struct node * 而不能只写 node *?
p->value 等价于什么 (用 * 和 . 表示)?为什么 -> 的左边必须是指针?
new_node = malloc(sizeof(struct node)); new_node->value = 10; 少了什么检查?内存不足时这两条语句会有什么后果?应怎么改?