背景假设:
假如要你设计一种存储电影的程序,存储电影的信息有电影名和电影等级,要求能任意添加这种信息并且显示出存储的电影信息.
设计方案:首先第一步要考虑数据的表示方法.
这种电影信息可以用数组来表示,但是会有一些弊端:
- 浪费空间:数组的设定需要在一开始指定数组的大小,为了确保能装下足够的电影信息将会开辟很大的空间来使用,且不能保证这些空间都能被利用到(你可能想存10000部电影,但是现在只想到了10部)
- 不利于逻辑表达:数组结构上是连续的,但逻辑上不一定连续(比如有数组开辟了10个空间,对应的索引为0~9,但是空间里面有内容的只有0,3,4三个空间,这三个空间在逻辑上对应的是1,2,3;但是用数组却表示不出来)
- 不利于数据的更改(这里的更改指的是添加,删除,被函数作用等操作):由于数组的空间是连续性(结构上是连续性),需要在数据(电影信息)中间插入新加入的数据(新的电影信息)会很麻烦(也就是说逻辑顺序的更改会让数组结构的更改变的很麻烦)
所以这里使用一种叫 链表 的数据结构来解决这个问题
链表的结构
链表的节点(Node)
说明:
是此节点的地址;而是此节点的值(通常是结构体).而且结构体里面可能含有指向同类型结构的指针.
这个就是指向同类型结构的指针;这两个表示结构体里的其他成员变量(蓝色的部分就是这个成员变量的地址)
所以 这个就是一个"完整的"节点.
由这些节点便能实现链表,完成逻辑上的连续(结构上不一定连续,靠指针进行连接),方便人们处理数据:
链表需要一个单独的指针指向存储第一个结构的地址(0568),该指针称为头指针(head)(0001),最后一个节点中指向下一个节点的指针为null表明这是链表的结尾.
这样就可以构建存储电影的数据了:
//使用链表---电影数量灵活
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//定义电影名的最大长度
#define FILE_NAME_MAX 3
//定义链表结构体
struct film
{
char Title[FILE_NAME_MAX];
int rating;
struct film *next;
};
//函数的声明
char * s_get(char* st, int n);//输入函数
在主函数中:
//变量的声明----1.链表结构体指针(头指针)2.链表结构体指针(结构体本身指针 结构体里面的指针)3.电影名数组
struct film *head = NULL;
struct film *current;
struct film *pre;
char movices_name_input[FILE_NAME_MAX];
注意这三个指针指向的节点并不是对应链表中的某一个节点,更像是一种标记.
比如:
head就是指向链表的首节点,你可以指向任意节点当做首节点(此节点前面的节点就会排除到链表外了)
current是"不在链表中的"节点,比如要添加节点,是对current指向的节点进行操作(初始化),再让链表中的节点指针去指向current节点,从而达到添加的目的.
pre是指向中链表中的节点(此例中是链表最后一个节点),对此节点不做"其他内容的"初始化,而是让它去指向"链表外的节点"从而达到修改链表的目的.
可以将链表的使用分为三步:
1. 链表(节点)的构建
(1)请求分配新节点的内存空间
//--创建链表结构体空间
current = (struct film*)malloc(sizeof(struct film));
(2)检测链表中是否有节点,没有的话新节点就是链表的头指针,有的话新节点就连接到当前链表中最后面节点的后面
//--如果头指针为空 就给头指针分配空间----头指针是第一个结构
if (head == NULL)
{
head = current;
}
else
{
//---如果头指针有指向的东西 这个空间就给当前指针
pre->next = current;
}
创建了一个新的节点,但里面的结构体并没有内容.
2.节点的内容初始化
上一步只是分配新节点的空间(获取了新节点的地址)和新节点的位置(是头指针还是普通节点)
//将链表结构体里面的指针初始化为空
current->next = NULL;
//将输入的电影名 付给结构体里面的数组
strcpy(current->Title, movices_name_input);
//电影评级
puts("请输入这个电影的等级:");
scanf("%d", ¤t->rating);
while (getchar() != '\n')
{
continue;
}
将结构体的成员变量进行初始化了.
3. 将新节点放入列表中
pre = current;
这个和第1步的"新节点的位置"并没有冲突,第一步是让新节点的位置放在列表的最后面(列表已经有头指针的话),但是并不包含在链表内,这一步的操作是让新节点成为链表的一部分.
上面是链表的构建,这里是链表内容的显示
//显示电影列表
//---判断头指针是否为空---为空的话 就属于没有输入电影
if (head == NULL)
{
puts("一个电影都没有,憨憨");
}
else
{
puts("电影列表是这样的:");
//用当前指针显示信息
//---第一个 头指针赋值
current = head;
while (current != NULL)
{
//---只要后面还有值----显示当前结构体里的值 把指针交给下一代
printf("电影名是:%s 电影等级是:%d\n", current->Title, current->rating);
current = current->next;
}
}
同样是通过操控"不在链表中"的节点来完成.
这里会使人感到困惑~:明明current,head是指针,为什么又说他们是"节点"?这里current"节点"是指current指向的地址中显示的内容,这个内容是节点(结构体)的内容.简单的说就是让current指针指向current的"下一个节点",从而将这个节点变成current"节点",来达到向后循环的目的.
最后就是链表(节点)的释放
//释放内存
//从头开始释放内存
current = head;
//--内容没完 就1.释放内存 2.把下一个地址给头指针
while (current!=NULL)
{
head = current->next;
free(current);
current = head;
}
puts("88");
return 0;
}
同显示链表的步骤有点像(显示链表就是用显示函数来作用节点来完成),同样这里也是操作current,但是有不一样,因为释放节点(准确的表达是释放节点的地址)会清空节点里面所有的内容,包括指向下一节点的指针(这里取个代号叫"宝贝"),这样失去了连续性,所以在这之前需要用一个东西(这里用了head来实现)现将要清掉的节点的"宝贝"保存下来,然后释放这个节点(current),再将current(要清除的节点)指向"宝贝",这样实现连续性.
总结:一句话总结就是 链表就是一个个的节点连接起来的一种数据结构,对链表的操作是通过三根指针来完成.
这个程序的主要目的是为了更明白链表的操作与含义,本身的话还有很多瑕疵,比如其实要检测请求内存是否成功,要隐藏这些操作数据的细节等等.