• 数据结构初解之单链表分析篇


    数据结构初解之单链表分析篇

    一、头文件

    #include<stdio.h>

    #include<stdlib.h>

    分析:

    1stdio.h可以说是最基本的头文件,scanfprintf函数都在里面。

        2stdlib.h中包含malloc()函数。

     

    二、宏定

    #define QUIT -1

    分析:

    1、QUIT是自定义的在链表中输入数据的结束符号。

    2、宏定义和类型定义符的功能很相似。但是宏定义是由预处理完成的,所以它带有预处理指令符#,而类型定义符typedef就没有。但是typedef语句后面要有“;”。

    3、关于#define更详细的知识可以查找其他资料。

     

    三、使用类型定义符typedefint转换成elemtype

    typedef int elemtype;

    分析:

    1、定义不同的数据类型是为了程序的可读性,比如其他程序员一看到elemtype x;就知道x是结构体里面的数据。这样可以与普通的int型数据区分开。

    2、便于修改,比如结构体需要的数据不再是int型,而是double型,则只需要将typedef int elemtype;修改为typedef double elemtype;就行了,不用再一个一个去修改。

    注:其实使用typedef的好处基本上就是这两个方面:增强程序的可读性和便于程序修改。

    有事也可以用宏定义来代替typedef的功能,但是宏定义是由预处理完成的,而typedef则是在编译时完成的,后者更为灵活方便。

     

    四、使用类型定义符typedef定义结构体

    typedef struct LNode

    {

        elemtype data;

         struct LNode *next;

    }LNode,*LinkList;

    分析:

    1、第一个typedef struct LNode{}LNode;第一个LNode是结构体名,这个LNode可以去掉,因为在C语言中无名结构体是合法的。这组语句的编译过程是这样的:先编译struct LNode{}struct开始到“}”结束,表明定义了一个结构体。然后,编译结构体中的内容,因为这个时候结构体struct LNode已经呗编译定义了,所以结构体里面可以使用struct LNode来定义新的变量,但是,typedef还没有编译,所以struct LNode还不能简写成LNode,也就不能用LNode来定义变量了。最后,编译typedef struct LNode{}LNode*LinkList;这一步是将struct LNode简化成LNode,将struct LNode *简化成LinkList;所以struct LNode x=LNode x;  struct LNode *p=LinkList p;。当然,LNode *p=LinkList p;等效。同时,因为结构体的编译是最先开始的,而类型定义符是最后开始编译的,所以结构体名和简化成的名字可以相同,当然第一个LNode是可以去掉的。

    2、结构体的全称应该是struct LNode,如果没有用typedef简化的话,struct是不能省略的,因为这样在C语言中是不合法的,但是在C++语言中却可以省略struct

    对于在结构体struct LNode中还可以使用struct LNode定义变量还有一种解释:“对于非完全类型,此时能够使用使用其引用或者指针,而不能使用对象,因为对象会分配空间,而此时它的类型都不存在,谈何对象?

    而指针和引用则可以使用,因为它并非一定指向了某一实例。”。这种解释较难理解,可以参考资料http://docs.oracle.com/cd/E27071_01/html/E26437/bjals.html#bjalt

    总之,在括号外面定义的是结构体,意思就是我要定义一个结构体了,里面有什么什么数据,而在里面定义的*next结构体类型的指针,意思是,我定义的结构体每个结点里面有个指针指向的是结构体类型的数据,不是重新定义一个指针类型的成员是可以指向自己所在结构体类型的数据的。所以“在结构体struct LNode中还可以使用struct LNode定义变量”是合法的。

    3、从例子中可以看出,nextLinkList的定义相同(struct LNode *nextstruct LNode *LinkList),但是为什么一个成了指向下一个节点的指针,一个成了链表指针。其实,nextLinkList的类型、用法什么的都是一样的,只是因为放的位置不同,造成作用有所差别,但是本质没改变,都是指向结构体的指针。打个比方来理解一下:原高一(一)班的学生在高二的时候要分文理科,一部分学生去了文科,学习语数外政史地;一部分学生去了理科,学习语数外理化生。但是他们每一个都是原高一(一)班的学生,本质上没有区别。文科里面的学生你不能说他没有学习理化生的能力,理科生你同样不能说他没有学习政史地的能力。同理,next是指向下一个节点的指针,但它同样有做链表指针的能力,LinkList也可以做节点指针。next指向的是节点,但节点就是结构体,所以它指向的还是结构体,LinkList是链表指针,但链表是结构体组成的,所以LinkList还是指向结构体的指针,本质都没有发生改变。

    那么,nextLinkList因为放置的位置不同而作用又分别是什么呢?

    *next在结构体里面,也就是每个节点里面,它的作用是链接本节点和下一个节点,因为*next包含在每一个节点里面(结构体里面),所以我们通常都是通过对next的操作来寻找、插入、删除某个节点。所以我们经常看到xxx->next=xxx;等。

    *LinkList定义在结构体的外面,所以它不包含在每一个节点里面,所以它只具有指向作用,不具备查找、插入、删除这些功能。我们一般用它来定义新的结构体指针变量。所以我们经常看到LinkList Lpq;等。(很有意思的是:L我们习惯上作为头指针,而pq就是后妈的孩子,随便多了。但是,Lpq本质又都是都一样的,这一点和*next*LinkList的差异异曲同工。)

    总之,*LinkList相当于一个数组的头指针,只不过这个数组的元素是结构体罢了。*next是数组元素(结构体)里面的一个指针,用来指向下一个数组元素,只不过这个数组元素是结构体罢了。*LinkList是指定了可以申请的节点空间(结构体空间)在内存中的位置。而*next则是对所有的节点(结构体变量)进行一定的排序。

    八个字总结一下:大家默认,习惯使然。大家默认的都这么用,我们就随大流一样用,所以,不必过分纠结。

    4、LNodeLinkList之间为什么可以用逗号链接?如果我们定义两个整形变量ab,我们可以写成int aint b;也可以写成int ab;同样的道理,typedef struct LNode LNode*LinkList相当于typedef struct LNode Lnode;和typedef struct LNode *LinkList;。

    五、创建一个带头节点的单链表

    LinkList InitList()

    {

        LinkList L;

        L=(LinkList)malloc(sizeof(LNode));

    if(L==NULL)

    {

    printf(申请不到所要求的空间\n);

    return L;

    }

        L->next=NULL;

        return L;

    }

    分析:

    1、创建一个带头节点的单链表,只需将该链表的指针域设为NULL即可。

    2、一个很搞笑的问题就是,我刚接触数据结构的时候,教材讲解的很粗略,我一直都没有搞清楚教材上用黑体字写的初始化操作InitList*L、插入操作InsItem*Liitem等线性表的基本操作是不是像C语言里面abs()函数、scanf()函数一样是库函数。最后才搞明白,这些长长的、有大写有小写的、类似函数的字符串都是自己定义的,想怎么写就怎么写,根本不是啥库函数。为什么要写成这样呢?八个字:大家默认,习惯使然。

    3、里面很关键的一句L=(LinkList)malloc(sizeof(LNode));

    4、先解释一下sizeofLNode),sizeof()函数是计算括号里面常量或者变量所占内存的大小。按照上面举得例子,那么sizeofLNode=8字节。注意,这里不能写成sizeofLinkList),因为我们申请的是节点空间,LNode才是节点,LinkList是指向节点的指针,一般是4个字节。

    5、malloc()是动态内存分配函数,用来向系统请求分配内存空间。当无法知道内存具体的位置时,想要绑定真正的内存空间,就要用到malloc()函数。因为malloc只管分配内存空间,并不能对分配的空间进行初始化,所以申请到的内存中的值是随机的。

    而且,需要注意的是,当申请到的空间不再使用时,要用free()函数将内存空间释放掉,这样可以提高资源利用率,最重要的是----就是因为它可以申请内存空间,然后根据需要进行释放,才被称为“动态内存分配”!

    malloc()函数实质体现在,它有一个可以将可用内存块连接成一个长长的列表的链表,这个链表就是所谓的空闲链表。调用malloc()函数时,它沿着连接表寻找一个大到可以满足用户请求要求的连续的内存块,然后将内存块一分为二,一块的大小与用户请求的内存大小相等,另一块就是剩下的内存块。接下来,它将用户申请的那块传递给用户,将另一块返回到连接表上(如果另一块有的话)。

    调用free()函数的时候,它将用户想要释放的内存块链接到空闲链上。我们可以想到,最后的空闲链链接的内存空间一小块一块的块,如果这是用户申请分配一个较大的内存空间,那么空闲链上可能没有符合用户要求的内存块了,这个时候,malloc()函数请求延时,并开始在空闲链上翻箱倒柜的检查各内存块,对他们进行整理,将相邻的小内存块合并成较大的内存块。如果无法获得符合用于要求的内存空间,那么malloc()函数就会返回NULL,因此,调用malloc()函数的时候,一定要判断它的返回值。

    Linux Libc6是在使用free()函数整合相邻的内存块,使之能形成一个较大的内存块。这两种处理方法异曲同工。

    6、如果要详细分析(LinkList)的作用,我们不得不讨论一下malloc()函数返回值的类型。上面已经说了,如果申请不成功那么就返回NULL指针。旧版本规定,malloc()函数申请成功返回char型指针,新的ANSI C标准规定,该函数的返回类型为void型指针。值得注意的是,void型指针不是空指针,它表示未确定类型的指针,因此必要时要进行强制转换,C/C++规定,void型指针可以强制转换成任意类型的指针。因为我们定义的L是结构体类型指针,所以要将malloc()函数的返回类型转换成结构体指针类型,所以前面要加上(LinkList),LinkList也可以换成struct LNode或者LNode*。注意,我们定义typedef struct Lnode{}*LinkList;,则对应L=(LinkList)mallocsizeofLNode));,如果我们定义的是typedef struct Lnode{}LinkList;,那么就对应L=(LinkList *)mallocsizeofLNode));。要记住的是返回的指针指向的是这8字节连续存储单元的首地址。

    关于void型指针和空指针,有兴趣可以百度一下。不是想象中那么简单哦!

  • 相关阅读:
    关于VSCode如何缩进两个空格
    基于vue来开发一个仿饿了么的外卖商城(二)
    在复杂的项目开发中使用结对编程
    第一个博客
    docker容器启动失败解决办法
    windows转mac-开发环境搭建(六):mac上搭建git环境
    windows转mac-开发环境搭建(五):mac上用docker安装并运行mysql
    windows转mac-开发环境搭建(四):mac上搭建node、VS code、idea环境
    windows转mac-开发环境搭建(三):mac上搭建maven环境
    windows转mac-开发环境搭建(二):mac上搭建java环境
  • 原文地址:https://www.cnblogs.com/fjutacm/p/3041447.html
Copyright © 2020-2023  润新知