如何实现动态分配,malloc,realloc,calloc的使用方法,数组,链表,结构体实现动态分配(含代码实现)

简介: 如何实现动态分配,malloc,realloc,calloc的使用方法,数组,链表,结构体实现动态分配(含代码实现)

目录


动态分配

意义

动态分配与静态分配内存的异同于优缺点

何时需要动态分配

动态分配函数

malloc

calloc

realloc

三者的异同

数组中的动态分配

结构体中的动态分配

链表中的动态分配


正文


动态分配


意义


       在计算机科学中, 动态内存分配(Dynamic memory allocation)又称为堆内存分配,是指计算机程序在运行期中分配使用内存。它可以当成是一种分配有限内存资源所有权的方法。


       动态分配的内存在被程序员明确释放或垃圾回收之前一直有效。与静态内存分配的区别在于没有一个固定的生存期。这样被分配的对象称之为有一个“动态生存期”。


       动态分配的意义是为了让程序能够根据运行时的需要,灵活地分配和释放内存空间,以提高内存利用率和程序的功能性。动态分配可以解决以下问题:


       当程序运行时,无法确定变量(如数组、结构体、链表等)的大小或个数时,静态分配的内存空间可能不够或浪费,而动态分配可以根据实际情况分配合适的空间。


       当程序运行时,需要频繁地创建和销毁变量时,静态分配的内存空间可能造成内存碎片或不足,而动态分配可以及时释放不用的空间,避免内存泄漏。


       当程序运行时,需要将变量的生命周期延长到函数调用结束后时,静态分配的局部变量会在函数返回时被销毁,而动态分配的变量可以保持有效,直到被释放。


动态分配与静态分配内存的异同于优缺点


       动态分配与静态分配的异同与优缺点如下:


       异:


               静态分配是编译器完成的,比如局部变量的分配。动态分配是程序运行时由程序员通过函数(如malloc、calloc等)进行分配。


               静态分配的内存空间是在栈上分配的,生命周期和作用域受函数限制。动态分配的内存空间是在堆上分配的,生命周期和作用域由程序员控制。


               静态分配的内存空间是连续的,大小和个数在编译时确定。动态分配的内存空间是不连续的,大小和个数在运行时确定。


       同:


               静态分配和动态分配都是为了给变量或数据结构分配内存空间,以便存储数据和信息1。

静态分配和动态分配都需要遵循一些规则和约束,以保证程序的正确性和安全性。


       优缺点:


               静态分配的优点是简单、快速、安全,不需要手动管理内存空间。缺点是浪费内存空间,不能适应程序运行时的变化。


               动态分配的优点是节省内存空间,能适应程序运行时的变化,能创建复杂的数据结构。缺点是复杂、慢速、危险,需要手动管理内存空间。


何时需要动态分配


       动态分配内存的目的是为了节省内存空间,提高内存利用率,以及适应程序运行时的不确定性1。一般来说,以下情况需要动态分配内存:


               当程序运行时,无法确定变量(如数组、结构体、链表等)的大小或个数时,需要动态分配内存来根据实际情况分配合适的空间。


               当程序运行时,需要频繁地创建和销毁变量时,需要动态分配内存来避免静态分配的内存浪费或不足。


               当程序运行时,需要将变量的生命周期延长到函数调用结束后时,需要动态分配内存来保持变量的有效性。


但是需要注意的是,动态分配由他的好处,同时也有一些弊端:


动态分配内存的好处和坏处如下:


       好处:


               可以根据程序运行时的需要,灵活地分配和释放内存空间,提高内存利用率。


               可以创建一些复杂的数据结构,如链表、树、图等,实现更多的功能。


               可以将变量的生命周期延长到函数调用结束后,避免局部变量的作用域限制。


       坏处:


               需要手动管理内存空间,容易出现内存泄漏、内存碎片、内存溢出等问题。


               动态分配的内存空间通常不连续,可能影响程序的性能和效率。


               动态分配的内存空间可能被其他程序或进程占用或修改,导致程序出错或崩溃


动态分配函数


malloc


       头文件:

#include <stdlib.h>

       语法格式:

void *malloc(size_t size);

       用法举例:

#include <stdlib.h>  
int main() {  
    int *ptr;  
    int n;  
    // 动态分配内存空间  
    ptr = (int*) malloc(sizeof(int) * n);  
    // 输出动态分配的内存空间大小  
    printf("Dynamically allocated memory size: %d\n", n);  
    // 输出动态分配的内存空间地址  
    printf("Dynamically allocated memory address: %p\n", ptr);  
    // 输出动态分配的内存空间指针  
    printf("Dynamically allocated memory pointer: %p\n", ptr);  
    // 释放动态分配的内存空间  
    free(ptr);  
    return 0;  
}


calloc


      头文件:


#include <stdlib.h>

       语法格式:


               其中,num 表示要分配的元素个数,size 表示每个元素的大小。


void* calloc(size_t num, size_t size);

       用法举例:


               在这个例子中,calloc 函数接受两个参数,num 表示要分配的元素个数,size 表示每个元素的大小。函数返回一个指向分配起始地址的指针,如果分配不成功,则返回 NULL。


其中,num 表示要分配的元素个数,size 表示每个元素的大小。

       注意:


                calloc 函数在分配内存时会先检查分配的内存大小是否足够,如果不足,则会抛出 malloc 函数的 malloc_error 异常。因此,在使用 calloc 函数时,需要确保分配的内存大小足够,否则可能会导致程序崩溃或产生不可预测的行为。


realloc


      头文件:


#include <malloc .h>

       语法格式:


               其中,ptr 是要重新分配内存空间的指针,size 是要分配的内存块的大小。realloc() 函数会在程序运行时动态计算需要分配的内存大小,并返回一个指向动态分配的内存块的指针。如果在分配内存时发生错误,realloc() 函数将返回一个空指针。


void *realloc(void *ptr, size_t size);


三者的异同


相似之处:


malloc和calloc都可以用于分配动态内存空间,并且都需要传入两个参数,第一个参数是要分配的元素个数,第二个参数是每个元素的大小。

malloc和calloc都会将分配的内存空间初始化为0,如果分配的内存原来已经被分配过,则其中可能会遗留有各种各样的数据。

malloc和calloc都可以用于分配指定大小的内存空间,并且返回类型都是void*。

不同之处:


malloc是从系统堆上分配内存的,而calloc和realloc都是在程序运行时动态分配内存空间的。

malloc返回的是指向动态分配内存空间的指针,而calloc和realloc返回的是指向分配的内存空间的指针。

malloc和calloc都可以用于扩大或缩小分配的内存空间,而realloc只能用于扩大分配的内存空间。

malloc和calloc的参数类型不同,malloc接受的是unsigned int类型的参数,而calloc和realloc接受的是size_t类型的参数。

malloc和calloc都需要对返回值进行判空,而realloc不需要。

malloc是一个标准库函数,而calloc、realloc和realloc都是第三方库函数。


数组中的动态分配


声明一个指针或指向指针的指针,作为动态数组的起始地址。

使用 malloc() 或 calloc() 函数为数组分配内存空间,根据数组的维数和元素类型确定所需的字节数。

使用循环或其他方式来为数组中的每个元素进行赋值和访问,注意使用下标或指针运算符。

使用 free() 函数释放数组所占用的内存空间,避免内存泄漏。

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *array = NULL; // 声明一个整型指针
    int len = 10; // 数组长度
    array = (int *)malloc(len * sizeof(int)); // 为数组分配内存空间
    if (array == NULL) // 检查是否分配成功
    {
        printf("动态申请内存失败!\n");
        exit(1);
    }
    for (int i = 0; i < len; i++) // 为数组赋值
    {
        array[i] = i + 1;
    }
    for (int i = 0; i < len; i++) // 打印数组元素的值和地址
    {
        printf("array[%d] = %d, &array[%d] = %p\n", i, array[i], i, &array[i]);
    }
    free(array); // 释放数组内存
    return 0;
}


结构体中的动态分配


       在上面的代码中,我们定义了一个Point结构体,它包含两个整型变量x和y。然后,我们使用malloc函数动态分配了一个大小为2的Point类型的内存空间,并将其地址赋值给指针变量p.z。最后,我们输出了动态分配的内存空间的大小、地址和指针。

#include <stdio.h>  
typedef struct {  
    int x;  
    int y;  
} Point;  
int main() {  
    Point p;  
    p.x = 10;  
    p.y = 20;  
    // 动态分配内存空间  
    p.z = (Point*) malloc(sizeof(Point) * 2);  
    // 输出动态分配的内存空间大小  
    printf("Dynamically allocated memory size: %d\n", sizeof(Point) * 2);  
    // 输出动态分配的内存空间地址  
    printf("Dynamically allocated memory address: %p\n", p.z);  
    // 输出动态分配的内存空间指针  
    printf("Dynamically allocated memory pointer: %p\n", p.z);  
    // 释放动态分配的内存空间  
    free(p.z);  
    return 0;  
}


链表中的动态分配


       在上面的代码中,我们定义了一个Node结构体,它包含一个整型变量data和一个指向下一个节点的指针next。然后,我们使用createNode函数动态分配了一个大小为2的Node类型的内存空间,并将其地址赋值给指针变量p.z。最后,我们输出了动态分配的内存空间的大小、地址和指针。

#include <stdio.h>  
#include <stdlib.h>  
typedef struct Node {  
    int data;  
    struct Node* next;  
} Node;  
Node* createNode(int data) {  
    Node* newNode = (Node*) malloc(sizeof(Node));  
    newNode->data = data;  
    newNode->next = NULL;  
    return newNode;  
}  
void printList(Node* head) {  
    while (head != NULL) {  
        printf("%d ", head->data);  
        head = head->next;  
    }  
    printf("\n");  
}  
int main() {  
    Node* head = createNode(1);  
    head->next = createNode(2);  
    head->next->next = createNode(3);  
    head->next->next->next = createNode(4);  
    printList(head);  
    // 动态分配内存空间  
    head->next->next->next = createNode(5);  
    printList(head);  
    return 0;  
}
相关文章
|
2月前
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
115 64
|
21天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
43 5
|
2月前
|
C语言
C语言结构体链式结构之有头单链表
文章提供了一个C语言实现的有头单链表的完整代码,包括创建链表、插入、删除和打印等基本操作。
34 1
|
2月前
|
存储
一篇文章了解区分指针数组,数组指针,函数指针,链表。
一篇文章了解区分指针数组,数组指针,函数指针,链表。
21 0
|
3月前
|
存储 C语言
C语言程序设计核心详解 第九章 结构体与链表概要详解
本文档详细介绍了C语言中的结构体与链表。首先,讲解了结构体的定义、初始化及使用方法,并演示了如何通过不同方式定义结构体变量。接着,介绍了指向结构体的指针及其应用,包括结构体变量和结构体数组的指针操作。随后,概述了链表的概念与定义,解释了链表的基本操作如动态分配、插入和删除。最后,简述了共用体类型及其变量定义与引用方法。通过本文档,读者可以全面了解结构体与链表的基础知识及实际应用技巧。
|
3月前
|
存储 算法 C语言
C语言手撕实战代码_循环单链表和循环双链表
本文档详细介绍了用C语言实现循环单链表和循环双链表的相关算法。包括循环单链表的建立、逆转、左移、拆分及合并等操作;以及双链表的建立、遍历、排序和循环双链表的重组。通过具体示例和代码片段,展示了每种算法的实现思路与步骤,帮助读者深入理解并掌握这些数据结构的基本操作方法。
|
4月前
|
存储 开发者 C#
WPF与邮件发送:教你如何在Windows Presentation Foundation应用中无缝集成电子邮件功能——从界面设计到代码实现,全面解析邮件发送的每一个细节密武器!
【8月更文挑战第31天】本文探讨了如何在Windows Presentation Foundation(WPF)应用中集成电子邮件发送功能,详细介绍了从创建WPF项目到设计用户界面的全过程,并通过具体示例代码展示了如何使用`System.Net.Mail`命名空间中的`SmtpClient`和`MailMessage`类来实现邮件发送逻辑。文章还强调了安全性和错误处理的重要性,提供了实用的异常捕获代码片段,旨在帮助WPF开发者更好地掌握邮件发送技术,提升应用程序的功能性与用户体验。
73 0
|
4月前
|
存储 Java 开发者
揭秘!HashMap底层结构大起底:从数组到链表,再到红黑树,Java性能优化的秘密武器!
【8月更文挑战第24天】HashMap是Java集合框架中的核心组件,以其高效的键值对存储和快速访问能力广受开发者欢迎。在JDK 1.8及以后版本中,HashMap采用了数组+链表+红黑树的混合结构,实现了高性能的同时解决了哈希冲突问题。数组作为基石确保了快速定位;链表则用于处理哈希冲突;而当链表长度达到一定阈值时,通过转换为红黑树进一步提升性能。此外,HashMap还具备动态扩容机制,当负载因子超过预设值时自动扩大容量并重新哈希,确保整体性能。通过对HashMap底层结构的深入了解,我们可以更好地利用其优势解决实际开发中的问题。
122 0
|
4月前
|
存储 Java 程序员
"揭秘HashMap底层实现:从数组到链表,再到红黑树,掌握高效数据结构的秘密武器!"
【8月更文挑战第21天】HashMap是Java中重要的数据结构,采用数组+链表/红黑树实现,确保高效查询与更新。构造方法初始化数组,默认容量16,负载因子0.75触发扩容。`put`操作通过计算`hashCode`定位元素,利用链表或红黑树处理冲突。`get`和`remove`操作类似地定位并返回或移除元素。JDK 1.8优化了链表转红黑树机制,提升性能。理解这些原理能帮助我们更高效地应用HashMap。
50 0
|
6月前
|
存储 SQL 算法
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表