程序来自:
http://data.biancheng.net/view/5.html
注释为自己手动添加,仅供交流学习,欢迎指导。
#include <stdio.h>
#include <stdlib.h>
#define PAUSE printf("Press Enter key to continue..."); fgetc(stdin);
// 如果在结构体中用本结构体的变量,那么相当于一个递归,
// 编译器永远不会知道一个结构体的大小,或者说结构体大小无穷大。
// 而用本结构体指针,不管是什么数据,反正你申明一个指针就是4字节,
// 这个编译器还是没问题的。
typedef struct Link{
int elem;//代表数据域
//这个用法很好,不能用 link *next 来定义
//这肯定是一个惯例用法,不需要考虑太多。
//链表中存放的不是基本数据类型,需要用结构体实现自定义
struct Link *next;//代表指针域,指向直接后继元素
}link;
//定义了6个子函数,其中4个是 link 型子函数,一个是 int 型子函数,一个是void型子函数
link * initLink();
//链表插入的函数,p是链表,elem是插入的结点的数据域,add是插入的位置
link * insertElem(link * p, int elem, int add);
//删除结点的函数,p代表操作链表,add代表删除节点的位置
link * delElem(link * p, int add);
//查找结点的函数,elem为目标结点的数据域的值
int selectElem(link * p, int elem);
//更新结点的函数,newElem为新的数据域的值
link *amendElem(link * p, int add, int newElem);
void display(link *p);
//这个不叫成员函数,它就是 link型子函数,类似于 int FUNC()
link * initLink(){
link * p = (link*)malloc(sizeof(link));//创建一个头结点,为之分配内存,并指定头指针
//p 不是头结点,而是头指针!
//p 是头指针,指向头结点
//sizeof(link) = 8,link不是一个指针,而是一个结构体变量
link * temp = p;//声明一个指针指向头结点,用于遍历链表,遍历的过程与头指针 p 无关
//生成链表
for (int i = 1; i<5; i++) {
link *a = (link*)malloc(sizeof(link)); //循环5次,每次都创建一个结点
a->elem = i;
//为什么是空值呢——为了在开始初始化的时候,通过 for 循环初始化链表
a->next = NULL; //这样会让最后一次赋值的 a->next 指向空值,这个办法非常好
temp->next = a; //上一次定义的temp是a的前驱结点
temp = temp->next; //定义temp是a,这样为下一次循环赋值做准备
}
return p; //return 了p 与temp无关,由此也可以看出 temp 是个中间量
}
link * insertElem(link * p, int elem, int add){
link * temp = p;//创建临时结点temp, temp 等于头指针p ,这样就可以从链表首结点遍历
//首先找到要插入位置的上一个结点
for (int i = 1; i<add; i++) { //为什么不是从 0 开始????
if (temp == NULL) {
printf("插入位置无效\n"); //如果首结点是空值,那么链表是空的,所以插入位置无效
return p;
}
temp = temp->next; //temp 是要插入位置的上一个结点
}
//创建插入结点c
link * c = (link*)malloc(sizeof(link)); //插入一个节点,因为
//因为 link * c 在子函数结束时要存到 p 中返回,所以在子函数结束时不要 delete 释放
c->elem = elem;
//向链表中插入结点
c->next = temp->next;
temp->next = c;
return p;
}
link * delElem(link * p, int add){
link * temp = p;
//遍历到被删除结点的上一个结点
for (int i = 1; i<add; i++) {
temp = temp->next;
}
link * del = temp->next;//单独设置一个指针指向被删除结点,以防丢失
temp->next = temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域
//牛呀!居然只要改动前一个节点的指针域就行了!
//temp->next 其实在初始化时就已经 malloc 分配了内存,所以在删除后要释放 free
// 在程序运行的整个过程中,申请的内存空间不会自己
// 释放(只有当整个程序运行完了以后,这块内存才会被回收)
free(del);//手动释放该结点,防止内存泄漏
return p;
}
int selectElem(link * p, int elem){
//使用中间变量指针 t的目的是为了保证头指针 p 一直指向头结点,而不要变化
link * t = p;
int i = 1; //从1开始查找,因为前面有一个头结点,此时 首元结点 不是头结点
//当 t->next 不为空时
while (t->next) {
t = t->next; //t->next 本来就是 link 型指针
if (t->elem == elem) {
return i;
}
i++;
}
return -1; //如果查找失败,返回-1
}
//更新函数,其中,add 表示更改结点在链表中的位置,newElem 为新的数据域的值
//amendElem 不是addElem
link *amendElem(link * p, int add, int newElem){
link * temp = p;
temp = temp->next;//temp指向首元结点
//这一点是聪明的,把程序简化了,这样程序就不用再写成 temp->next->elem = newElem
//temp指向被删除结点
for (int i = 1; i<add; i++) {
temp = temp->next; //temp是从前向后一层一层地追,直到追到需要被 add 的地方
}
temp->elem = newElem;
return p;
}
void display(link *p){
link* temp = p;//将temp指针重新指向头结点 //包含了头结点
//只要temp指针指向的结点的next不是Null,就执行输出语句。
while (temp->next) {
temp = temp->next;
printf("%d", temp->elem);
}
printf("\n");
}
int main() {
//初始化链表(1,2,3,4)
printf("初始化链表为:\n");
link *p=initLink();
display(p);
printf("在第4的位置插入元素5:\n");
p = insertElem(p, 5, 4);
display(p);
printf("删除元素3:\n");
p = delElem(p, 3);
display(p);
printf("查找元素2的位置为:\n");
int address = selectElem(p, 2);
if (address == -1) { //这一步好聪明呀!!!
printf("没有该元素");
}
else{
printf("元素2的位置为:%d\n", address);
}
printf("更改第3的位置的数据为7:\n");
p = amendElem(p, 3, 7);
display(p);
return 0;
}