线性表结构二——链表知识点总结,动态存储分配,程序运行空间定义

目录

〇、前言

一、链表初始化

二、链表创建

三、插入链表结点

四、删除链表结点

五、获取链表长度&链表遍历

六、获取链表元素

七、链表置空

八、链表逆序

九、链表判断是否有环

十、单链表讨论

例一、单链表实现一元指数多项式的相加

注释1.动态存储分配 malloc ()、calloc()、free()、realloc()

注释2.程序运行空间


本文在“作者:zql_3393089” 基础上进行改进总结
原文链接:https://blog.csdn.net/u012531536/article/details/80170893 

〇、前言

链表和数组作为算法中的两个基本数据结构,在程序设计过程中经常用到。尽管两种结构都可以用来存储一系列的数据,但又各有各的特点。

数组的优势,在于可以方便的遍历查找需要的数据。在查询数组指定位置(如查询数组中的第4个数据)的操作中,只需要进行1次操作即可,时间复杂度为O(1)。但是,这种时间上的便利性,是因为数组在内存中占用了连续的空间,在进行类似的查找或者遍历时,本质是指针在内存中的定向偏移。然而,当需要对数组成员进行添加和删除的操作时,数组内完成这类操作的时间复杂度则变成了O(n)。

链表的特性,使其在某些操作上比数组更加高效。例如当进行插入和删除操作时,链表操作的时间复杂度仅为O(1)。另外,因为链表在内存中不是连续存储的,所以可以充分利用内存中的碎片空间。除此之外,链表还是很多算法的基础,最常见的哈希表就是基于链表来实现的。基于以上原因,我们可以看到,链表在程序设计过程中是非常重要的。本文总结了我们在学习链表的过程中碰到的问题和体会。

接下来,我们将对链表进行介绍,用C语言分别实现:链表的初始化、创建、元素的插入和删除、链表的遍历、元素的查询、链表的删除、链表的逆序以及判断链表是否有环等这些常用操作。并附上在Visual Studio 2010 中可以运行的代码供学习者参考。

说到链表,可能有些人还对其概念不是很了解。我们可以将一条链表想象成环环相扣的结点,就如平常所见到的锁链一样。链表内包含很多结点(当然也可以包含零个结点)。其中每个结点的数据空间一般会包含一个数据结构(用于存放各种类型的数据)以及一个指针,该指针一般称为next,用来指向下一个结点的位置。由于下一个结点也是链表类型,所以next的指针也要定义为链表类型。例如以下语句即定义了链表的结构类型。


/*=================================================
next的几点说明
1.next 是一个指针变量;
2.next 的类型是struct LinkList;
3.next 的值是struct LinkList类型结点的地址
4.next 指向的空间中放置的是struct LinkList类型的值
=================================================*/
typedef struct LinkList                                                                                  
{
    int Element;
    LinkList * next;
}LinkList;

链表结构图如下所示:

线性表结构二——链表知识点总结,动态存储分配,程序运行空间定义_第1张图片

一、链表初始化

在对链表进行操作之前,需要先新建一个链表。此处讲解一种常见的场景下新建链表:在任何输入都没有的情况下对链表进行初始化。

链表初始化的作用就是生成一个链表的头指针,以便后续的函数调用操作。在没有任何输入的情况下,我们首先需要定义一个头指针用来保存即将创建的链表。所以函数实现过程中需要在函数内定义并且申请一个结点的空间,并且在函数的结尾将这个结点作为新建链表的头指针返回给主调函数。本文给出的例程是生成一个头结点的指针,具体的代码实现如下:

/*==========================================
函数功能:单链表运算——初始化
函数输入:无
函数输出:链表头指针
==========================================*/
linklist *  List_init()

{
    linklist *HeadNode = (linklist*)malloc(sizeof(linklist));  //有关 malloc 内容请看注释1
    if(HeadNode == NULL)
    {
        printf("空间缓存不足");
        return HeadNode;
    }
    HeadNode->Element = 0;   
    HeadNode->next = NULL;
    return HeadNode;
}

当然,初始化的过程或者方法不只这一种,其中也包含头指针存在的情况下对链表进行初始化,此处不再一一罗列。

这里引申一下,此处例程中返回的链表指针为该链表的头结点,相对应的还有一个头指针的概念。头指针内只有指针的元素,并没有数据元素,但头结点除了指针还有数据。

头指针就是链表的名字,仅仅是个指针而已。头结点是为了操作的统一与方便而设立的,放在第一个有效元素结点(首元结点)之前,其数据域一般无意义(当然有些情况下也可存放链表的长度、用做监视哨等等)。一般情况下见到的链表的指针多为头指针,但最近在一个程序员编程网站leetcode中发现,题目中所给的链表一般是首元结点作为第一个元素,而不是头指针。

下图为头指针与头结点以及首元结点的关系。

二、链表创建

创建链表需要将既定数据按照链表的结构进行存储,本文以一种最简单的方式来演示:使用数组对链表赋值。将原来在连续空间存放的数组数据,放置在不连续的链表空间中,使用指针进行链接。

链表创建的步骤一般使用给定的头指针以及需要初始化的数据序列作为输入参数,本文使用数组作为输入数据序列。在下面的例程中,先将头结点使用数组第一个元素初始化,再在首元结点之后创建新的链表结点赋值数组内余下的数据。具体实现如下:

/*==========================================
函数功能:单链表运算——尾插法建立链表
函数输入:头指针,结点数组头指针,结点个数
函数输出:链表头指针
==========================================*/
void CreatList(linklist *HeadNode,int *InData,int DataNum)
{
    int i = 0;
    linklist *a1 = (linklist*) HeadNode;//a1指向头结点
    for(i = 0; i < DataNum; i++)
    {
        a1->Element = InData[i];       //a1结点数据赋值   
        if(i < DataNum - 1)            //由于每次赋值后需要新建结点,为了保证没有多余的废结点
        {
            linklist *a2 = (linklist *)malloc(sizeof(linklist));//新开辟一个结点a2
            a1->next = a2;             //当前结点a1的next链(指针)指向a2
            a1 = a1->next;             //将当前结点a1后移一位,移到a2
            //a1 = a2;                 //和上句效果一样
        }
    }
    a1->next = NULL;                   //a1->next 相当于a2,但 a2 是局部变量,这里不可使用
}

程序内先新建了一个指针变量a1用来表示当前的结点指针。最初,我们让a1指向了首元结点HeadNode的位置。然后根据输入数组的大小进行赋值,赋值完成之后再重新申请一个结点空间a2用来存放下一个结点的内容,并且将当前结点指针a1指向新生成的结点a2。由于链表创建函数调用时已经存在一个首元结点,按照这个逻辑最终在使用最后一个数组数据赋值之后还会多生成一个结点。因此,为了保证没有冗余的结点,循环内需要用DataNum-1来控制结点数量。具体实现参考如下图。

线性表结构二——链表知识点总结,动态存储分配,程序运行空间定义_第2张图片

三、插入链表结点

链表创建完之后,下面我们将介绍如何向链表内插入结点。一般添加结点可以分为两类:一类是在链表尾部插入;另一类为在中间插入。

链表结尾添加结点的步骤就是新建一个链表结点,将其链接到当前链表尾指针。

在中间结点插入结点的步骤稍微复杂一些,其中也包含两种情况,分别是在指定结点前插入和指定结点后插入。其操作原理一样,本文只对指定位置后插入结点进行介绍。指定结点前插入结点留给大家尝试。

假设一个链表内存在如下几点a1,a2,a3,a4….,当根据要求需要在指定位置之后(比如a2结点)插入一个新结点时。首先我们需要新建立一个结点p1,然后将新结点的next指向a3,并且将a2的next指针指向新建立的结点p1,切记操作顺序不要改变。如果操作顺序变换一下,先将a2的next指向了新建立的结点,那么我们就丢失了a3的寻址方式。因此,在将a2的next指向其他任何地方之前,请务必将a3的地址存在p1或者某个新建节点内。如下图所示

线性表结构二——链表知识点总结,动态存储分配,程序运行空间定义_第3张图片

插入结点的具体操作如下:

/*==========================================
函数功能:单链表运算——在指定位置后插入结点
函数输入:头指针,插入点位置,插入点数据
函数输出:无
==========================================*/
bool InsertList(linklist *HeadNode, int LocateIndex, int InData)
{
    int i = 1;                            // 由于起始结点HeadNode是头结点,所以计数从1开始
    linklist *a1 = (linklist *) HeadNode;
    while(a1 && i < LocateIndex - 1)      //将a1指向待插入位置的前一个结点(index -1),即表示的就是a2
    {
        a1 = a1->next;                    //当前结点后移一位
        i++;
    }
    linklist *p1 = (linklist*)malloc(sizeof(linklist));
    if(p1 == NULL)
    {
        printf("空间缓存不足");
        return ERROR;
    }
    p1->Element = InData;
    p1->next = a1->next;                  //这里a1->next 表示的即是a3
    a1->next = p1;
    return OK;
}

四、删除链表结点

对应于插入链表结点,链表的基本操作中同样也有删除链表结点。删除结点包括删除指定位置的结点和指定元素的结点。其基本原理都是先锁定待删除的结点的位置,然后将该结点的后置结点链接到前置结点的next指针处。这样中间这个结点即我们要删除的结点就从原来的链表中脱离开来。相对于原来的链表,即删除了该结点。例如,删除下图中的p1结点。

线性表结构二——链表知识点总结,动态存储分配,程序运行空间定义_第4张图片

/*==========================================
函数功能:单链表运算——初始化
函数输入:头指针,被删除结点位置,数据地址
函数输出:被删除结点地址
==========================================*/
bool DeleteList(linklist *HeadNode, int index, int *DataToDel)
{
    int i = 1;
    linklist *a2  = (linklist *)HeadNode; 
    linklist *p1;                          //p1本身有自己的内存空间,删除后需要释放其空间
    while(a2 && i < index-1)               //将a2指向待删除位置的前一个结点(index -1)
    {
        a2 = a2->next;
        i++;
    }
    p1 = a2->next;                         //把要删除的结点指向 p1
    *DataToDel = p1->Element;              //保存节点内数据,供用户后续使用
    a2->next = p1->next;                   //链接a2和a3结点
    free(p1);                              //释放掉删除结点内存,防止内存泄漏
    return DataToDel;
}

此处为什么还要重新建立一个指针来记录或者保存待删除的结点呢?明明一个简单的步骤a2 ->next = a2->next->next;就可以将这个结点a2->next(即结点p1)删除了,为什么要多此一举呢?

此处新建的链表类型的指针p1是为了释放a2->next。直接使用a2 ->next = a2->next->next只是将该节点从链表中剔除,但是没有将其空间从内存中释放。而且,经过了a2 ->next = a2 ->next->next这个赋值语句之后,我们已经丢失了原本需要删除的结点的地址。所以,在删除之前新建了个结点用来保存待删除的结点地址,以便后面对内存空间的释放。   

五、获取链表长度&链表遍历

获取链表的长度实际上和遍历链表具有相同的操作。遍历的过程将链表内的结点都访问了一边。获取链表长度的具体步骤是遍历链表之后能够记录并返回链表结点个数。

本文给出获取链表长的函数代码。

int GetListLength(linklist *HeadNode)
{
    int ListLength = 0;
    linklist *CurrentNode = (linklist*) HeadNode;
    while(CurrentNode)                             // 当前指针不为空时可以计数累加
    {
        ListLength++;
        CurrentNode = CurrentNode->next;           //指针移到下一结点
    }
    return ListLength;
}

//用递归方法实现遍历
int ListLength = 0;
int GetListLength(linklist *HeadNode)
{
    if(HeadNode->next != NULL)
    {
        GetListLength(HeadNode->next);
        ListLength++;
    }
    return ListLength;
}

在该函数中,CurrentNode = CurrentNode ->next表示将当前结点指针指向当前结点的下一个结点,即表示将结点后移一位。

对于链表内的赋值操作我们总结出几种情况:

六、获取链表元素

接下来我们将“给定链表中的某一个位置,返回该位置的数据值”和“返回链表内某一个元素的位置”这两个问题放在一起介绍。

这两种情况的思路都是需要遍历链表。在给定元素值的情况下,定义一个元素序号随着遍历的过程累加,遍历的过程校验链表的结点是否与给定的元素匹配,如果匹配则返回元素位置的序号;在给定位置的情况下就更简单一些,元素序号累加到对应位置,返回对应结点的元素即可。

本文只列出给定元素值的例子:

/*==========================================
函数功能:单链表运算——按值查找结点
函数输入:链表头指针,结点值
函数输出:结点值位置
==========================================*/
int LocateElement(linklist * HeadNode, int DataToLocate)
{
    int LocateIndex = 1;
    linklist *CurrentNode = (linklist*) HeadNode;
    while(CurrentNode)
    {
        if(CurrentNode->Element == DataToLocate)
        {
            return LocateIndex;                    //找到位置返回
        }
        CurrentNode = CurrentNode->next;
        LocateIndex++;
    }
    return -1;                                    //如果没有这个值,返回-1
}

本函数的逻辑是如果遍历链表之后能够找到与所给元素匹配的结点,则将该结点的位置返回。但如果没有匹配的结点的话,则返回一个-1,表示获取元素位置失败。

注:链表不是随机存取结构,不能像顺序表一样直接按序号 i 访问结点,而只能从链表的头指针出发,顺链域 next 逐个结点往下搜索,直至搜索到第 i 个结点为止。

七、链表置空

链表置空又可以称为销毁链表。同样是在遍历的前提下,一直到链表结尾结束,所有遍历到的链表结点均释放掉空间,具体代码如下:

bool DestroyList(linklist * HeadNode)
{
    linklist *pNext;
    linklist *CurrentNode = (linklist*) HeadNode;
    while(CurrentNode)
    {
        pNext = CurrentNode->next;
        free(CurrentNode);
        CurrentNode= pNext;
    }
    HeadNode->next = NULL;
    return OK;
}

八、链表逆序

链表的逆序有很多种思路,本文介绍一种将当前结点的下一结点一直往头指针之后移动的思路。

假设当前有5个结点,head、a1、a2、a3、a4、a5,他们的头指针是head。我们的思路便是将a1作为当前元素一直往后遍历,并且将a1后面的数据依次挪到head之后。   

在第一次搬移的过程中,需要将a1的下一个元素a2放在head之后。如图所示,当前结点选定为a1,起一个变量名为current,当前结点的下一个结点为pNext,则a2便成了pNext = current->next。如果想要将pNext移到head之后,我们按照图中第1步先将a3连接到a1的后面,然后第2步再将head后面的整体链表放到要移动的a2的后面,也就是pNext->next= head->next,第3步将a2移到head之后。这三个步骤下来,我们的第一次反转工作就算完成了。此时的链表链表就变成了head、a2、a1、a3、a4、a5,如图所示:

如果上面移动的步骤不按图中进行会出现什么情况呢?假设现在按照3-2-1的步骤来实现a2移动到head后面。当先进行第三步之后,即head->next = pNext;这一步直接将a2挪到了head之后。然后我们接下来应该再将原来head后面的一串数据链接到刚刚移动到head后面的a2后面,此处由于head后面的数据已经被pNext更新了,此时head后面是a2结点,所以在执行第二步以后,链表就变成了无限循环的链表,而且循环的元素值是a2。

按照上图正确的顺序实现第一次反转以后,可以判定当前的current指针是否已经是尾指针,如果不是就可以继续执行。第二次反转后链表就变成了head、a3、a2、a1、a4、a5。因此当把链表内的最后一个元素也移动到head之后时,链表逆序的工作就算完成了。

具体的代码实现如下。

linklist * ListRotate(linklist * HeadNode)

{

    linklist* current,*pNext,*pPrev;

    pPrev = (linklist*)malloc(sizeof(linklist));

    if(pPrev == NULL)

    {

        printf("空间缓存不足");

        return ERROR;

    }

    pPrev->next = HeadNode;

    current = HeadNode;

    while(current->next)

    {

        pNext = current->next;

        current->next = pNext->next;

        pNext->next = pPrev->next;

        pPrev->next = pNext;

    }

    return pPrev->next;

}

九、链表判断是否有环

判断链表是否存在环的过程中,通常最先想到的方法就是从定义下手,有环的话就没有尾结点,也就是说不存在一个结点的next指针是null。通过这种思路可以对有环无环进行判定,但需要判定到何时呢?

当遍历了4000个结点都没有遇到null结点,难道就可以断定这就是一个有环的链表吗?如果它的第4001个结点就是尾结点呢?很多情况下,我们是不知道链表的长度的,所以我们很难确定需要判定到哪一个结点才能确定链表是否为环形链表。因此我们需要借助快指针、慢指针的概念,这是目前用来判断链表内有环无环的最通用有效的方法。

假设有这样一种情况,有两辆车,一辆车每秒钟可以跑n米,另外一辆速度要快一些,每秒能跑2n米,这两辆车都匀速运行。如果在一个没有交叉点的跑道上,这时跑道上有一个终点,快车和慢车同时在起始点相遇出发之后,一直到终点,快车和慢车的距离只会越拉越大,等到快车到达终点的时候,两者之间的距离差最大。假想一种情况,如果跑道的终点与起始点连接了起来,虽然说从慢车的角度看,快车在前方越来越远。但快车的角度看,慢车在后面越来越远,但在前面看的话确实越来越近。所以在一个环形的跑道上,快车终究会有第二次与慢车相遇,此时正好超车一圈。

函数的执行过程如下:

bool IsListLoop(linklist *HeadNode)

{

    linklist *pFast,*pSlow;

    pFast = pSlow = HeadNode;

    while(pFast && pSlow)

    {

        pSlow = pSlow->next;

        if(pFast->next)

        {

            pFast= pFast->next->next;

        }

        else

        {

            pFast= pFast->next;

        }

        if(pFast == pSlow)

        {

            returnTRUE;

        }

    }

    return FALSE;

}


以上介绍了链表的部分基本操作,这些操作是实现很多算法的基础。希望大家共同学习进步,不足之处望指出。

十、单链表讨论

链表中的对象也是按线性顺序排列的,但与数组不同,数组的线性顺序是由数组的下标决定的,而链表中的顺序则是由各对象中的指针决定的,相比于线性表顺序结构,其操作复杂。

(1)动态结构。不需预先分配空间:使用链表结构可以克服顺序表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。

(2) 指针占用额外存储空间:链表由于增加了节点的指针域额外占用空间。

(3)不能随机存取查找速度慢:链表失去了数组的随机读取的优点,且单向链表只能顺着一个方向查找。

(4)链表上实现的插入和删除运算,不需要移动结点,仅需修改指针。

例一、单链表实现一元指数多项式的相加

//
//  main.cpp
//  链表求指数多项式和
//
//  Created by 一丁 on 2019/4/16.
//  Copyright © 2019年 LGD. All rights reserved.
//

#include 
#include 
#include 
/*==========================================
 函数功能:结构体定义,实现一元指数多项式求和
 函数输入:无
 函数输出:无
 =========================================*/
typedef struct Polynode
{
    int coef;               //系数
    int exp;                //幂次
    struct Polynode *next;  //struct 可有可无
}LinkListNode;


/*==========================================
 函数功能:链表的创建
 函数输入:结点数组指针,节点数
 函数输出:链表头指针
 =========================================*/
LinkListNode *LinkListCreate(int a[][2], int n)
{
    LinkListNode *head = (LinkListNode *)malloc(sizeof(LinkListNode));
    LinkListNode *q =head;
    for(int i = 0; i < n; i++)
    {
        LinkListNode *p = (LinkListNode *)malloc(sizeof(LinkListNode));
        p->coef = a[i][0];
        p->exp = a[i][1];
        q->next = p;
        q = q->next;
    }
    q->next = NULL;
    return head;
}


/*==========================================
 函数功能:多项式求和
 函数输入:链表A头指针,链表B头指针
 函数输出:链表C头指针
 =========================================*/
LinkListNode *attach(int a, int exp, LinkListNode *pc);
LinkListNode *LinkListADD_headC(LinkListNode *headA, LinkListNode *headB)
{
    LinkListNode *pa, *pb, *pc, *p = nullptr;
    pa = headA->next;
    pb = headB->next;
    pc = (LinkListNode*)malloc(sizeof(LinkListNode));
    LinkListNode *headC = pc;                //headC 用于保存 pc 头指针,用于返回值
    while(pa && pb)
    {
        if(pa->exp == pb->exp)
        {
            int x = pa->coef + pb->coef;
            if(x != 0)
                pc = attach(x, pa->exp, pc);
            pa = pa->next;
            pb = pb->next;
            //continue;
        }
        else
        {
            if(pa->exp < pb->exp)
            {p = pa; pa = pa->next;}
            else
            {p = pb; pb = pb->next;}
            pc = attach(p->coef, p->exp, pc);
        }
    }
    p = pa;
    if(!pa) p = pb;
    while(p)
    {
        pc = attach(p->coef, p->exp, pc);
        p = p->next;
    }
    pc->next = NULL;
    return headC;                               //返回 pc 的头指针即 headC
}


/*==========================================
 函数功能:建立系数为 c,指数为 e 的新结点,并把它插
         在 pc 所指结点的后面,链接后 pc 指向新的
         链入的结点
 函数输入:系数 c,指数 e,pc 地址
 函数输出:pc 地址
 =========================================*/
LinkListNode *attach(int c, int e, LinkListNode *pc)
{
    LinkListNode *q = (LinkListNode*)malloc(sizeof(LinkListNode));
    q->coef = c;
    q->exp = e;
    pc->next = q;
    return q;
}


/*==========================================
 函数功能:将两个多项式headA和headB相加,结果存放
         在 headA中
 函数输入:headA和headB首地址
 函数输出:无
 =========================================*/
void LinkListADD_headA(LinkListNode *headA, LinkListNode *headB)
{
    LinkListNode *pa, *pb, *pre, *ptemp;
    pa = headA->next;
    pb = headB->next;
    pre = headA;
    while(pa && pb)
    {
        if(pa->exp == pb->exp)
        {
            pa->coef = pa->coef + pb->coef;
            if(pa->coef != 0)
            {
                pre->next = pa;
                pa = pa->next;
                pre = pre->next;
                ptemp = pb->next;
                free(pb);
                pb = ptemp;
            }
            else
            {
                ptemp = pa->next;
                free(pa);
                pa = ptemp;
                ptemp = pb->next;
                free(pb);
                pb = ptemp;
            }
        }
        else
        {
            if(pa->exp < pb->exp)
            {
                pre->next = pa;
                pa = pa->next;
                pre = pre->next;
            }
            else
            {
                pre->next = pb;
                pb = pb->next;
                pre = pre->next;
            }
        }
    }
    pre->next = pa;
    if(!pa)
        pre->next = pb;
    //return headA;
}


/*==========================================
 函数功能:采用递归调用输出结点值
 函数输入:链表首地址
 函数输出:无
 =========================================*/
void PrintLinkList(LinkListNode *HeadNode)
{
    if(HeadNode->next != NULL)
    {
        std::cout<< HeadNode->next->coef << "\t";
        PrintLinkList(HeadNode->next);
    }
    std::cout<< std::endl;
}


int main(int argc, const char * argv[])
{
    int a[][2] = {{1, 2},{2, 3},{3, 4}};
    int b[][2] = {{5, 2},{7, 3},{9, 10},{10, 11}};
    LinkListNode *headA;
    LinkListNode *headB;
    LinkListNode *linkPtr;
    headA = LinkListCreate(a, 3);
    headB = LinkListCreate(b, 4);
    linkPtr = LinkListADD_headC(headA, headB);
    LinkListADD_headA(headA, headB);
    PrintLinkList(linkPtr);
    PrintLinkList(headA);
    return 0;
}

注释1.动态存储分配 malloc ()、calloc()、free()、realloc()

程序在执行期间需要存储空间时,通过“申请”分配指定的内存空间;当闲置不用时,可随时将其释放,由系统另作他用。相关的库函数有malloc ()、calloc()、free()、realloc()等,使用这些库函数时,必须在程序开头包含文件 stdlib.h或malloc.h或alloc.h

【内存分配函数 malloc()】

函数格式: void *malloc(unsigned size);

函数功能:从内存中分配一大小为 size 字节的块。

参数说明:size 为无符号整型,用于指定需要分配的内存空间的字节数。

返回值:新分配内存的地址,如无足够的内存可分配,则返回 NULL。

说明:

(1)当size 为0时,返回 NULL

(2)void* 为无类型指针,可以指向任何类型的数据存储单元,无类型指针需要强制类型转换后赋值给其他类型的指针。

【内存分配函数 calloc()】

函数格式:void *calloc(size_t n, size_t size);

函数功能: 在内存的动态存储区中分配n个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。

参数说明:n:Number of elements成员数量 。size: Length in bytes of each element每个成员字节长度.

返回值:新分配内存的地址,如无足够的内存可分配,则返回 NULL。

与malloc的区别:calloc()在动态分配完内存后,自动初始化该内存空间为零,而malloc()不初始化,里边数据是随机的垃圾数据。

//malloc()用法
#include
#include
#include
 
int main()
{
    char*str = NULL;
 
    /*分配内存空间*/
    str = (char*)malloc(10,sizeof(char));
 
    /*将hello写入*/
    strcpy(str, "Hello");
 
    /*显示变量内容*/
    printf("String is %s\n",str);
 
    /*释放空间*/
    free(str);
    return 0;
}


//calloc()用法
#include
#include
int main()
{
    int i;
    int* pn = (int*)calloc(10, sizeof(int));
    for(i = 0;i < 10;i++)
        printf("%d", pn[i]);
    printf("\n");
    free(pn);
    return 0;
}

【内存分配函数realloc()】

函数格式:extern void *realloc(void *mem_address, unsigned int newsize);

函数功能: 先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。

参数说明:指针名 =(数据类型*)realloc(要改变内存大小的指针名,新的大小)。

返回值:如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。

#include
#include

int main()
{
int i;
int *pn = (int*)malloc(5*sizeof(int));
if (!pn)                                   //如果返回值为空则退出
{
    printf("malloc fail\n");
    exit(-1);
}
printf("malloc%p\n",pn);
for(i = 0; i < 5; i++)
pn[i] = i;
pn = (int*)realloc(pn, 10*sizeof(int));
if (!pn) 
{
    printf("realloc fail\n");
    exit(-1);
}
printf("realloc%p\n",pn);
for(i = 5; i < 10; i++)
pn[i] = i;
for(i = 0; i < 10; i++)
printf("%3d", pn[i]);
free(pn);
pn = NULL;
return 0;
}

【释放内存函数free()】

函数格式:void free(void *block);

函数功能:将 calloc()、malloc()和realloc()函数所分配的内存空间释放为自由空间。

参数说明:block 为 void 类型的指针,指向要释放的内存空间。

返回值:无、

如:void free(void *p);

从动态存储区释放p指向的内存区,p是调用malloc()的值。free()函数没有返回值。

说明:

1)结点的申请 linklist *HeadNode = (linklist*)malloc(sizeof(linklist));

函数malloc()分配一个大小为linklist字节的空间,并将其首地址放入指针变量HeadNode中。

2)节点的释放 free(HeadNode);

释放HeadNode所指的节点变量空间。

3)结点数据项的访问

利用结点指针HeadNode访问结点分量。

方法一:(*HeadNode).data 和 (*HeadNode).next

方法二:HeadNode->data 和 HeadNode->next

注释2.程序运行空间

一个程序将操作系统分配给其运行的内存块分为4个区域,如图所示。

线性表结构二——链表知识点总结,动态存储分配,程序运行空间定义_第5张图片

一个由 C/C++编译的程序占用的内存分为以下几个部分。

1)栈区(stack)——由编译器自动分配释放,存放为运行函数而分配的局部变量、函数参数、返回数据、返回地址等。其操作方式类似于数据结构中的栈。

2)堆区(heap)——一般有程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。管理方式类似于链表。

3)全局区(静态区)(static)——存放全局变量、静态数据、常量。程序结束后由系统释放。

4)常量区——存放各种常量。程序结束后由系统释放。

5)程序代码区——存放函数体的二进制代码。

你可能感兴趣的:(#,C++容器详解)