翁恺 程序设计进阶C语言笔记-链表(Linked List)

链表(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掉当前的块,但是现在的链表只是指向后面,所以需要用到双向链表,有一个指针指向前面一个指向后面

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值