链表(Linked List)
- 可变数组
普通定义数组的大小在刚开始就固定了,后面就不能更改了,为了克服,定义一个可变数组
- 链表
可变数组有他的局限性,如下图所示,想要增加一个数组时,就会free掉这个空间并且在后面重新开辟一个更大的空间来存放数据,所以前面的空间就会被浪费掉了,虽然还要很多剩余空间但是你用不了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7mOaGaoK-1612159396700)(https://i.loli.net/2021/01/28/xq3curWCgwNJtAh.png)]
链表就是为了解决这个问题,不是复制出一个新的大的空间(节省了复制这个费时的操作)设定一个个 block,把他们链接起来。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JBHSqPzy-1612159396703)(https://i.loli.net/2021/01/28/d8i7wzgt5Z3DoeH.png)]
链表就不是用一个个数组了,因为如果你的数据是char,但是指针需要一个int的空间,就放不下了,所以构建一个结构体,分成两个部分 一个放数据,一个放指针。最后一个块指针指向空(NULL),第一个叫做head,前一个块的指针指向后一个块。
//file node.h
#ifndef _NODE_H_
#define _NODE_H_
typedef struct node
{
/* data */
int value;
struct node *next; //定义一个指针指向node这种结构体类型的空间,指针名是next.
}Node;
#endif
#include <stdio.h>
#include <stdbool.h>
#include <node.h>
#include <stdlib.h>
//void number(int);
int main()
{
//实现读入一个数字就存起来,知道读到-1为止
int number;
Node *head = NULL; //定义链表的头部,这是一个指向Node类型结构体的指针
do {
//添加一个新的链表(数据块),加在程序的最后面
scanf("%d",&number);
if (number != -1){
Node *p = (Node*)malloc(sizeof(Node)); //指针p指向新创建的链表,用malloc给他开辟内存
p->next = NULL; //新加入的链表放到最后面,所以是空指针
p->value = number;
//找到最后的那个链表
Node *last = head; //设用last指向最后那个链表,先设last为head
//last和head一样,是指向头链表的指针 *head就是头链表那个结构体 last->next 等价于 (*head).next
//判断last不为空,如果整个链表为空,此时head=NULL,last->就是NULL的next元素,这样是非法的,所以要判断
if (last){
/*遍历,不能用if,这里是只要last指向的结构体的指针不为空,
即指向的链表的下一个链表不为空,则不停止循环,if只判断一次*/
while (last->next){
last = last->next; //指向下一个链表
}
last->next = p; /*last->next等于NULL了,说明last已经指向原本的最后一个链表了,这里让last的next为我们新添加的链表
就是将老链表的最后一个中的next指针指向我们添加的新链表*/
} else {
head = p; //空链表,直接将head指向我们新创建的链表,此时新链表是头链表,也是唯一的链表。
}
}
}while( number != -1);
return 0;
}
- 封装成函数
将上述程序封装成add()函数
//方案1,不行
void add(Node* head,int number){
...
else{
head = p; //形参在函数结束的时候就会释放,如果没有直接将内存里的数改掉,那么对指针的操作也带不到函数外面去
//在这里head是形参指针,出了函数以后head就被释放了。
}
}
//证明
void test(int* head);
int a = 1;
int b = 2;
int* p = &a;
int* t = &b;
int main()
{
test(p);
//printf("%d\n",*p);
printf("函数外:%d\n",*p);
return 0;
}
void test(int* p){
p = t;
printf("函数内:%d\n",*p);
}
/*输出为
函数内:2
函数外:1*/
- 如果指针p,和a都是在main函数中定义的,在test函数中不能直接调用,main也是个函数只不过是第一个执行的函数,mian里面定义的变量也是局部变量
- 有选择的情况下不用全局变量,全局指针,特别是在一个大型的程序里,因为全局的东西大家都可以修改他,你不知道哪个函数就给他调用了修改了,就会导致程序出错,指针还有可能在别的地方将内存释放掉。
//方案2,将指针的值返回
Node* add(Node* head,int number){
...
return head;
}
//调用时
head = add(head,number);
//方案3,穿入指针的指针
//调用
add(&head,number); //对head取地址,就是head这个指针的指针传进去了
add(Node** phead,int number){ //两个*,表示指针的指针
... //head等价于*phead,将head全改为*phead
//return head; 不需要返回了
}
-
方案4:推荐,在方案3的基础上,用一个结构体里面放一个指针指向head。好处:可以在结构体里面扩充各种自己想要的东西
-
#include <stdio.h> #include <stdbool.h> #include <node.h> #include <stdlib.h> typedef struct _list{ Node* head; Node* tail; //可以拓展功能,增加tail总是指向链表的末尾,然后不用遍历了,直接将tail指向新指针即可 }List; int main() { //实现读入一个数字就存起来,知道读到-1为止 int number; List list; list.head = NULL; //定义链表的头部,这是一个指向Node类型结构体的指针 do { //添加一个新的链表(数据块),加在程序的最后面 scanf("%d",&number); if (number != -1){ add(&list,number); //传入指向head的指针 Node *p = (Node*)malloc(sizeof(Node)); //指针p指向新创建的链表,用malloc给他开辟内存 p->next = NULL; //新加入的链表放到最后面,所以是空指针 p->value = number; //找到最后的那个链表 Node *last = head; //设用last指向最后那个链表,先设last为head //last和head一样,是指向头链表的指针 *head就是头链表那个结构体 last->next 等价于 (*head).next //判断last不为空,如果整个链表为空,此时head=NULL,last->就是NULL的next元素,这样是非法的,所以要判断 if (last){ /*遍历,不能用if,这里是只要last指向的结构体的指针不为空, 即指向的链表的下一个链表不为空,则不停止循环,if只判断一次*/ while (last->next){ last = last->next; //指向下一个链表 } last->next = p; /*last->next等于NULL了,说明last已经指向原本的最后一个链表了,这里让last的next为我们新添加的链表 就是将老链表的最后一个中的next指针指向我们添加的新链表*/ } else { head = p; //空链表,直接将head指向我们新创建的链表,此时新链表是头链表,也是唯一的链表。 } } }while( number != -1); return 0; }
-
链表的拓展,如果要删除一个链表,我们需要找到那个块的前面一个和后面一个,将前一个的指针指向后一个,然后free掉当前的块,但是现在的链表只是指向后面,所以需要用到双向链表,有一个指针指向前面一个指向后面