C语言-动态内存分配与链表

1.动态内存分配

1.1什么是动态分配内存

c语言要求在编译时指针数组元素的个数。但是我们往往做不到,可能导致程序的失败或空间的浪费。很多编程语言允许程序员在运行时指定数组的大小,即能够在运行时计算并分配程序中变量所需的内存空间。在运行时分配内存空间的过程就称为动态内存分配。尽管c语言本身不具有这种能力,但是它有4个名为“内存管理函数”的库例程,可以用来在程序运行时分配和释放内存。

以下是与c语言有关的内存分配过程。(c程序的存储)。

 程序指令,全局变量,静态变量存储在永久内存区域中。局部变量储存在栈(stack)中。位于这两个区之间的内存空间可以用于程序运行时的动态分配,这些内存区称为堆(heap)。当程序运行时,堆的大小是不断变化的,因为会发生函数或代码块的局部变量的创建和销毁。因此在动态分配过程中,有可能会遇到内存的溢出。这种情况下,上面介绍的函数将返回空指针(当它们不能分配所请求的足够空间时)。

1.2用malloc函数分配一块内存

利用malloc函数可以分配一块内存。malloc函数将保留指定大小的内存块,并返回void类型的指针。(这意味着可以把它赋给任意类型指针)

一般形式:ptr=(cast-type *)malloc(byte-size);

ptr为cast-type类型的指针。malloc函数返回一个指向大小为byte-size的内存区的指针(类型为cast-type)。

malloc函数分配的是连续的字节块。如果堆空间不能满足要求,分配失败,返回NULL。因此应检查内存是否分配成功。

1.3malloc演示

/**
 *malloc函数使用 
 *使用一个整数表,其大小在运行时交互地指定。
 */
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *p, *table;
    int size;
    printf("表的长度是多少?\n");
    scanf("%d", &size);
    printf("\n");
    /*内存分配*/
    if ((table = (int *)malloc(sizeof(int))) == NULL)
    {
        printf("没有可用空间\n");
        exit(1);
    }
    printf("\n第一个字节的地址是%u\n", table);
    printf("\n输入表的数据\n");

    for (p = table; p < table + size; p++)
    {
        scanf("%d", p);
    }
    for (p = table + size - 1; p >= table; p--)
    {
        printf("%d储存在地址%u中\n", *p, p);
    }
    system("pause");
    return 0;
}

1.4用calloc函数分配多个内存块

calloc函数也是一个内存分配函数,通常用于在运行时为了存储派生数据类型(入数组和结构体)而分配所需的内存空间。malloc函数分配的是单个内存块,calloc函数分配的是多个内存块,每个块大小相等,并把所有字节都设为零。

calloc的一半形式:ptr=(cast type *)calloc(n,elem-size);

上述语句分配n块连续的内存空间,每块的大小为elem-size个字节。所有字节初始化为零,并返回一个指针,指向所分配区域的第一个字节。如果没有足够的空间,就返回一个空指针。


#include <stdio.h>
#include <stdlib.h>

struct student
{
    char name[30];
    float age;
    long int id_num;
};

typedef struct student record;

record *st_ptr;
int class_size = 30;
// calloc函数分配了用于保存30条记录数据的存储空间
st_ptr = (record *)calloc(class_size, sizeof(record));

// 检验内存是否已经分配
if (st_ptr == NULL)
{
    printf("可用内存不足");
    exit(1);
}

 1.5用free函数释放已用的空间

当不在需要保存在内存块中的数据,且不打算再使用这块内存,可用free函数来释放掉该内存块。

一般形式:free(ptr);

ptr为指向内存块的指针,所释放的不是指针本身,而是指向的内容。

1.6用realloc函数改变内存块的大小

在分配内存后,有可能发现分配的内存不够。或者,已分配的比需要的大,想要减少内存。都可以利用realloc函数来改变已分配内存的大小。如下:

ptr=malloc(size);

ptr=realloc(ptr,newsize);

该函数把大小为newsize的新内存空间分配给指针ptr,并返回一个指向新内存块的第一个字节的指针。newsize可以比size更大或更小。记住!新内存块的开始位置可以与旧的相同。如果在相同的区域找不到其他的内存空间,就将在全新的区域中创建,旧内存块的内容将移到新块中。该函数保证旧数据的完整性。

1.7realloc演示

/**
 * 把一个字符串保存在由malloc创建的内存块中,然后修改它以存储更大的字符串
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    char *buffer;
    if ((buffer = (char *)malloc(10)) == NULL)
    {
        printf("失败");
        exit(1);
    }
    printf("创建大小为:%d的缓冲区\n", _msize(buffer)); //_msize 函数可以用于获取之前使用 malloc 函数分配的堆空间的大小
    strcpy(buffer, "CHINABYDP");
    printf("\nBuffer:%s\n", buffer);

    if ((buffer = (char *)realloc(buffer, 15)) == NULL)
    {
        printf("失败");
        exit(1);
    }
    printf("修改缓冲区大小\n");
    printf("缓冲区仍然包含:%s\n", buffer);
    strcpy(buffer, "HELLOWORLD");
    printf("\n缓冲区现在包含:%s\n", buffer);
    free(buffer);
    system("pause");
    return 0;
}

2.链表

2.1链表的概念

大家都知道,列表是按序组成的项集。数组就是一种列表。在数组中,元素的顺序是由索引隐式地给定的。可以使用索引来访问和操作数组元素。但是数组的大小必须在开始时就给定。这在很多实际应用中是件困难的事情。

一种完全不同的列表表示方法是用结构体表示一个列表成员,它含有指向下一个结构体成员的“链接”。称为链表(linked list)。

链表的每个结构体称为节点(node),它由两个字段组成:一个包含数据项,一个包含指向链表中下一个数据项的地址(也就是指向下一个数据项的指针)。因此链表是结构体的集合。

通常如下表示:

struct node{

int item;

struct node *next;

};

结构体可以包含不同数据类型的多项,但是必须有一项是结构体指针。

/**
 * 简单示例:链表的链接
 */
#include <stdio.h>

struct link_list
{
    float age;
    struct link_list *next;
};

int main()
{
    struct link_list node1, node2;
    node1.next = &node2; // 建立链接
    node1.age = 35.0;
    node2.age = 49.0;

    node2.next = 0; // 链表的结尾

    printf("%f", node1.next->age); // 可以利用node1的next成员来访问node2的age成员。
    return 0;
}

2.2链表的优点

主要优点:其大小可以在程序运行时增大或缩小,长度可以按需求决定。其次链表不会浪费内存空间,任何时候链表所使用的内存就是它所需要的。更为重要的是,链表的灵活性允许高效地重排数据项,可以很容易的插入,删除数据项。

主要局限:访问任意数据时有些费时,无论何时只要列表的长度固定,最好使用数组而不是链表。对于相同数量的数据项,链表所使用的内存空间比数组更多,因为在链表中,每个数据项还包含额外的链接字段(指针)。

链表也分几种,如下图所示:

 2.3创建链表

可以把链表看成一种抽象数据类型,可以执行以下基本操作:

创建链表,遍历链表,计算链表中数据项的数目,显示链表,查找某项数据(用于编辑或显示),插入数据项,删除数据项,连接两个链表。

我们写好了结构体只是描述了结点格式,没有分配存储空间,并且上面我们手动的创建结点node1,node2等。尽管我们可以继续添加,但是这种方法十分愚蠢。使用递归与迭代技术可以很容易实现。如下语句就可以将指针从当前结点移到下一个结点:

head=head->next;

2.4创建链表演示

/**
 * 创建完整的链表,并利用递归显示其内容
 */
#include <stdio.h>
#include <stdlib.h>
struct linked_list
{
    int number;
    struct linked_list *next;
};
typedef struct linked_list node;

int main()
{
    node *head;
    void create(node * p);
    int count(node * p);
    void print(node * p);
    head = (node *)malloc(sizeof(node));//此时才为结点创建了储存空间
    create(head);
    printf("\n");
    print(head);
    printf("\n");
    printf("\n数据个数: %d\n", count(head));
    system("pause");
    return 0;
}

void create(node *list)
{
    printf("\n输入一个数据项数据(输入-1停止):");
    scanf("%d", &list->number);

    if (list->number == -1)
    {
        list->next = NULL;
    }
    else
    {
        list->next = (node *)malloc(sizeof(node));
        create(list->next);
    }
    return;
}

void print(node *list)
{
    if (list->next != NULL)
    {
        printf("%d-->", list->number);
        if (list->next->next == NULL)
        {
            printf("%d", list->next->number);
        }
        print(list->next);
    }
    return;
}

int count(node *list)
{
    if (list->next == NULL)
    {
        return 0;
    }
    else
    {
        return (1 + count(list->next));
    }
}

 创建了一个链表,利用递归来显示内容。

2.5插入一个数据项

插入之前要先检查插入的位置。一般要定位数据项将插入哪个结点后面。插入的一般算法如下:

开始

     if 链表为空,或

     新结点位于头节点之前,then

     插入新节点作为头节点

     else

     if新节点位于尾节点之后,then

     插入新节点作为尾节点

     else

     把新节点插入链表中

结束

    把新数据项置于链表开头的算法如下:

    1.为新节点分配空间

    2.把数据赋给新节点的数据项字段

    3.把新节点的next字段指向链表的开头

    4.把head指针修改为指向新节点

    把新节点X插入当两个已有节点N1,N2之间的算法如下:

    1.为新节点分配空间

    2.把值赋给x的数据项字段

    3.把x的next指向N2

    4.把N1的next字段指向节点X

    插入到尾部算法类似,只不过next字段被置为NULL。

2.6插入节点演示

3.链表的应用案例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值