☰
从零吃透C语言动态数组
2026/10/12 5:41:49 网站建设 项目流程

目录

一、先搞懂:静态数组为什么不能做动态扩容?(核心)

1.1 错误写法1:直接返回数组本体

1.2 错误写法2:返回局部数组指针(最经典野指针BUG)

二、动态数组的两种解决方法

2.1 方案一:外部定义数组,函数传参赋值(低封装、临时方案)

2.2 方案二:malloc堆内存动态分配(标准方案)

2.2.1 堆内存核心特性

2.2.2 动态扩容底层固定逻辑

三、动态数组功能实战逻辑拆解

前置初始化逻辑

3.1 单数据添加(1001)

3.2 按位置删除(1002)

3.3 按位置查找(1003)

3.4 按位置替换(1004)

3.5 中间插入数据(1005)

3.6 添加多个数据(1006)

四、动态数组核心知识点总结

4.1 核心原理

4.2 三大坑点

4.3 静态数组 vs 动态数组


初学C语言数组时,我们使用的都是静态数组,也就是固定长度数组,比如int arr[10]。它有一个致命短板:长度编译时固定,运行中无法修改,存满了就会溢出,定义太长又浪费内存。

而实际开发中,数据量都是不确定的,这就必须用到动态数组。

下面我来结合亲手写的两段代码,从零开始梳理动态数组的核心逻辑!


一、先搞懂:静态数组为什么不能做动态扩容?(核心)

来看手写代码:

#include <stdio.h> #include <stdlib.h> //数组扩容 /* //情景导入1:函数不能直接返回数组本体,只能返回指针(以下是错误写法!) int[] copyArr1(int arr[],int size) { int newArr[size*2]; //将arr中的值存入newArr中 for (int i = 0; i < size; i++) { newArr[i] = arr[i]; } return newArr; } */ //情景导入2:函数内部的局部变量具有生命周期 int *copyArr2(int arr[], int size) { int newArr[size * 2]; //将arr中的值存入newArr中 for (int i = 0; i < size; i++) { newArr[i] = arr[i]; } //将newArr中的值打印出来 printf("-----copyArr2函数内部可以打印-----\n"); for (int j = 0; j < 20; j++) { int n = j; printf("newArr[%d]:%d\n", n, j); } return newArr; } //解决方案一:在主函数内 调用函数copyArr3之前,先创建一个更大的数组,这样能达到在copyArr3函数之外 newArr依然存在的目的, //但缺点是封装性不好 void copyArr3(int arr[], int newArr[], int size) { //将arr中的值存入newArr中 for (int i = 0; i < size; i++) { newArr[i] = arr[i]; } } //解决方案二:调用malloc()函数,实现动态分配内存。malloc在堆:函数结束内存依然存活,必须手动 free;堆空间很大,几 MB~GB 级别。 //这是现实中更常用的数组扩容!(无需写成函数形式,直接见主函数方案二) int main() { /* int arr[10]; //初始化数组 for (int i = 0; i < 10; i++) { arr[i] = i; } //情境1 - int arr1[] = copeArr1(arr, 10); //数组初始化必须用 数据类型 数组名[] = {};的形式,且这个形式在每个数组中只能使用一次; //情境2 - int *arr2 = copyArr2(arr, 10); printf("-----copyArr2函数外部无法打印-----\n"); for (int j = 0; j < 20; j++) { printf("newArr[%d]:%d\n", j, arr2[j]); } //方案一: int size = sizeof(arr) / sizeof(int); int newArr[size * 2]; //全部初始化为0 for (int i = 0; i < size * 2; i++) { newArr[i] = 0; } copyArr3(arr, newArr, size); printf("-----数组转移成功!-----\n"); for (int i = 0; i < 20; i++) { printf("newArr[%d]:%d\n", i, *(newArr + i)); } */ //方案二: int len = 10; int final_len = len * 2; int size = len * sizeof(int); int final_size = size * 2; int *arr = (int *)malloc(size); int * newArr = (int *)malloc(final_size); //初始化arr for (int i = 0; i < 10; i++) { arr[i] = i; } //初始化newArr for (int i = 0; i < final_len; i++) { newArr[i] = 0; } //将arr中的值全部转移到newArr中 for (int i = 0; i < len; i++) { *(newArr + i) = *(arr + i); } // 重点:在覆盖arr指针之前,先释放旧arr的堆内存!! free(arr); arr = newArr; //打印newArr检验效果 for (int i = 0; i < final_len; i++) { printf("newArr[%d]:%d\n", i, newArr[i]); } //最后释放newArr(现在arr和newArr指向同一块堆内存,只需要free一次,不能free两次!) free(arr); arr = NULL; //好习惯,置空,防止野指针 return 0; }

很多同学写动态数组翻车,根源是没搞懂栈内存和局部变量生命周期,也就是我代码中情景1、情景2的报错逻辑。

1.1 错误写法1:直接返回数组本体

C语言不允许函数直接返回数组类型,数组名本质是数组首地址,没有办法通过return直接返回一整个数组,编译器直接报错。

1.2 错误写法2:返回局部数组指针(最经典野指针BUG)

这是非常容易踩的坑!对应代码中copyArr2函数:

在函数内部定义int newArr[size*2],这个数组存储在栈内存中。

栈内存的特性:函数调用结束后,局部变量立即销毁,内存自动释放。

所以虽然函数内部可以正常打印newArr数据,但函数执行完毕、回到主函数后,newArr对应的内存已经被回收。此时返回的指针指向一块无效内存(野指针),外部打印全是垃圾值。

核心结论:栈内存局部变量,生命周期仅限函数内部,绝对不能通过指针返回使用!


二、动态数组的两种解决方法

想要实现数组动态扩容,核心需求只有一个:让扩容后的新数组,在函数结束后依然存活。基于这个需求,有两种实现方案。

2.1 方案一:外部定义数组,函数传参赋值(低封装、临时方案)

对应代码中copyArr3函数。

实现思路:不在函数内部创建新数组,而是在主函数中提前定义大容量新数组,将新旧数组一并传入函数,完成数据拷贝。

优点:规避了野指针问题,数据不会丢失。

致命缺点:封装性极差!扩容逻辑和业务代码耦合,每次扩容都需要手动创建数组、传递参数,完全不适合实际开发。

2.2 方案二:malloc堆内存动态分配(标准方案)

这是C语言实现动态数组的唯一主流方案,也是我第二段完整功能代码的核心逻辑。

2.2.1 堆内存核心特性
  • 栈内存:系统自动分配、自动释放,生命周期随函数

  • 堆内存:手动malloc申请、手动free释放,生命周期贯穿整个程序,函数结束内存不会销毁

  • 堆内存空间极大(MB~GB级别),完全满足动态数据存储需求

2.2.2 动态扩容底层固定逻辑

C语言没有直接的数组扩容函数,动态扩容的本质是以新代旧,分为四步:

  1. 根据新长度,malloc申请一块更大的堆内存(新数组)

  2. 将旧数组的所有数据,拷贝到新数组中

  3. 释放旧数组堆内存(防止内存泄漏)

  4. 将旧数组指针指向新数组地址,完成扩容

重点:一定要free旧内存!否则频繁扩容会造成严重内存泄漏,程序运行越久占用内存越高。


三、动态数组功能实战逻辑拆解

基于malloc堆内存,我手写了一套完整的动态数组操作系统,包含6大核心功能,下面逐一对接代码拆解底层逻辑。

先看手写代码:

#include <stdio.h> #include <stdlib.h> int main() { int size = 10; int *arr = (int *)malloc(size *sizeof(int)); //申请旧数组的堆内存 //先创建原有数组 for (int i = 0; i < size; i++) { arr[i] = i; } while (1) { printf("===动态数组==="); printf("===1001 添加数据\n"); printf("===1002 删除数据\n"); printf("===1003 查找数据\n"); printf("===1004 替换数据\n"); printf("===1005 插入数据\n"); printf("===1006 添加多个数据\n"); printf("请输入数字编号:\n"); int id = 0; scanf("%d", &id); if (id == 1001) { printf("请输入需要添加的数据:\n"); int num = 0; scanf("%d", &num); size++; int *newArr = (int *)malloc(size *sizeof(int)); //申请新数组的堆内存,不会因局部变量被清除 newArr[size - 1] = num; //拷贝旧数组给新数组 for (int i = 0; i < size - 1; i++) { newArr[i] = arr[i]; } free(arr);//释放旧数组的内存 arr = newArr;//更新地址 //将新数组打印出来 printf("["); for (int i = 0; i < size; i++) { if (i > 0) printf(","); printf("%d", arr[i]); } printf("]\n"); } else if (id == 1002) { printf("请输入需要删除的位置(从0开始):\n"); int pos = 0; scanf("%d", &pos); if (pos < 0 || pos >= size) { printf("该位置不存在!请重新输入!\n"); continue; } for (int i = pos; i < size - 1; i++) { arr[i] = arr[i + 1]; } size--; printf("成功删除!当前的数组为:["); //将删除后的数组打印出来 for (int i = 0; i < size ; i++) { if (i > 0) printf(","); printf("%d", arr[i]); } printf("]\n"); } else if (id == 1003) { printf("请输入需要查找的位置(从0开始):\n"); int pos = 0; scanf("%d", &pos); if (pos < 0 || pos >= size) { printf("该位置不存在!请重新输入!\n"); continue; } printf("您需要查找的数据是:%d\n", arr[pos]); } else if (id == 1004) { printf("请输入需要替换的位置:\n"); int pos = 0; scanf("%d", &pos); if (pos < 0 || pos >= size) { printf("该位置不存在!请重新输入!\n"); continue; } printf("请输入替换后的数字:\n"); int num = 0; scanf("%d", &num); arr[pos] = num; printf("成功替换!当前的数组为:["); //将替换后的数组打印出来 for (int i = 0; i < size; i++) { if (i > 0) printf(","); printf("%d", arr[i]); } printf("]\n"); } else if (id == 1005) { printf("您想在什么位置后插入一个数字(填写从0开始的数字):\n"); int pos = 0; scanf("%d", &pos); if (pos < 0 || pos >= size) { printf("该位置不存在!请重新输入!\n"); continue; } printf("请输入需要插入的数据:\n"); int num = 0; scanf("%d", &num); size++; int *newArr = (int *)malloc(size *sizeof(int)); //申请新数组的堆内存,不会因局部变量被清除 //拷贝旧数组给新数组 for (int i = 0; i < size - 1; i++) { newArr[i] = arr[i]; } for (int i = size - 1; i > pos + 1; i--) { newArr[i] = newArr[i - 1]; } newArr[pos + 1] = num; free(arr);//释放旧数组的内存 arr = newArr;//更新地址 //将新数组打印出来 printf("["); for (int i = 0; i < size; i++) { if (i > 0) printf(","); printf("%d", arr[i]); } printf("]\n"); } else if (id == 1006) { printf("请输入添加的数据个数:\n"); int cnt = 0; scanf("%d", &cnt); printf("请输入%d个数字,数字间用空格隔开:\n", cnt); //用数组保存添加的多个值 size = size + cnt; int addArr[cnt] = {0}; for (int i = 0; i < cnt; i++) { int num = 0; scanf("%d", &num); addArr[i] = num; } int *newArr = (int *)malloc(size *sizeof(int)); //申请新数组的堆内存,不会因局部变量被清除 //拷贝旧数组给新数组 for (int i = 0; i < size - cnt; i++) { newArr[i] = arr[i]; } //添加多个数字到新数组后(核心) for (int i = 0; i < cnt; i++) { newArr[size - cnt + i] = addArr[i]; } free(arr); arr = newArr;//更新地址 //打印新数组 //将新数组打印出来 printf("["); for (int i = 0; i < size; i++) { if (i > 0) printf(","); printf("%d", arr[i]); } printf("]\n"); } else { printf("输入错误,请重新输入!\n"); } } }

前置初始化逻辑

程序启动先初始化动态数组:通过malloc申请10个int大小的堆内存,循环赋值0~9,用size变量实时记录数组当前有效长度(核心:动态数组靠size记录长度,不再靠固定定义)。

3.1 单数据添加(1001)

场景:在数组末尾追加一个数据

逻辑:长度size+1 → 申请更大堆内存 → 旧数据拷贝 → 新数据存入末尾 → free旧内存 → 指针替换。

3.2 按位置删除(1002)

这是数组删除的经典算法逻辑,无需重新开辟内存:

校验位置合法性 → 从删除位置开始,后一位数据向前覆盖前一位 → 有效长度size-1。

核心:数组删除本质是数据覆盖+长度缩减。

3.3 按位置查找(1003)

最简单的基础功能:校验位置是否合法,直接通过下标取值打印即可,动态数组指针支持下标访问,和静态数组用法一致。

3.4 按位置替换(1004)

校验位置合法性 → 直接覆盖对应下标数据,无需改动数组长度和内存,效率最高。

3.5 中间插入数据(1005)

比末尾添加复杂,核心逻辑:

长度size+1 → 开辟新内存 → 拷贝原数据 →从后往前后移数据,腾出插入位置 → 存入新数据 → 释放旧内存。

重点:必须从后往前移位,避免数据被覆盖丢失!

3.6 添加多个数据(1006)

适配多数据扩容场景:

接收用户输入的添加个数和数据 → 总长度叠加对应个数 → 开辟扩容后新内存 → 拷贝旧数据 → 批量写入新数据 → 替换指针、释放旧内存。

这里重点关注添加多个数字到新数组后的逻辑:

//添加多个数字到新数组后(核心) for (int i = 0; i < cnt; i++) { newArr[size - cnt + i] = addArr[i]; }

下面是用函数简单封装的版本,感兴趣的可供欣赏:

​ #include <stdio.h> #include <stdlib.h> // 1. 打印数组 void printArray(int *arr, int size) { printf("["); for (int i = 0; i < size; i++) { if (i > 0) printf(","); printf("%d", arr[i]); } printf("]\n"); } // 2. 判断位置是否合法:合法返回1,非法返回0 int isPosValid(int pos, int size) { if (pos < 0 || pos >= size) { return 0; } return 1; } // 3. 分配堆内存,元素个数为elemCount,返回分配好的指针 int* mallocNewArr(int elemCount) { int *p = (int *)malloc(elemCount * sizeof(int)); return p; } int main() { int size = 10; int *arr = mallocNewArr(size); //先创建原有数组 for (int i = 0; i < size; i++) { arr[i] = i; } while (1) { printf("===动态数组==="); printf("===1001 添加数据\n"); printf("===1002 删除数据\n"); printf("===1003 查找数据\n"); printf("===1004 替换数据\n"); printf("===1005 插入数据\n"); printf("===1006 添加多个数据\n"); printf("===0 退出程序\n"); printf("请输入数字编号:\n"); int id = 0; scanf("%d", &id); if (id == 1001) { printf("请输入需要添加的数据:\n"); int num = 0; scanf("%d", &num); size++; int *newArr = mallocNewArr(size); newArr[size - 1] = num; //拷贝旧数组给新数组 for (int i = 0; i < size - 1; i++) { newArr[i] = arr[i]; } free(arr);//释放旧数组的内存 arr = newArr;//更新地址 printArray(arr, size); } else if (id == 1002) { printf("请输入需要删除的位置(从0开始):\n"); int pos = 0; scanf("%d", &pos); if (!isPosValid(pos, size)) { printf("该位置不存在!请重新输入!\n"); continue; } for (int i = pos; i < size - 1; i++) { arr[i] = arr[i + 1]; } size--; printf("成功删除!当前的数组为:"); printArray(arr, size); } else if (id == 1003) { printf("请输入需要查找的位置(从0开始):\n"); int pos = 0; scanf("%d", &pos); if (!isPosValid(pos, size)) { printf("该位置不存在!请重新输入!\n"); continue; } printf("您需要查找的数据是:%d\n", arr[pos]); } else if (id == 1004) { printf("请输入需要替换的位置:\n"); int pos = 0; scanf("%d", &pos); if (!isPosValid(pos, size)) { printf("该位置不存在!请重新输入!\n"); continue; } printf("请输入替换后的数字:\n"); int num = 0; scanf("%d", &num); arr[pos] = num; printf("成功替换!当前的数组为:"); printArray(arr, size); } else if (id == 1005) { printf("您想在什么位置后插入一个数字(填写从0开始的数字):\n"); int pos = 0; scanf("%d", &pos); if (!isPosValid(pos, size)) { printf("该位置不存在!请重新输入!\n"); continue; } printf("请输入需要插入的数据:\n"); int num = 0; scanf("%d", &num); size++; int *newArr = mallocNewArr(size); //拷贝旧数组给新数组 for (int i = 0; i < size - 1; i++) { newArr[i] = arr[i]; } for (int i = size - 1; i > pos + 1; i--) { newArr[i] = newArr[i - 1]; } newArr[pos + 1] = num; free(arr);//释放旧数组的内存 arr = newArr;//更新地址 printArray(arr, size); } else if (id == 1006) { printf("请输入添加的数据个数:\n"); int cnt = 0; scanf("%d", &cnt); printf("请输入%d个数字,数字间用空格隔开:\n", cnt); //用数组保存添加的多个值 size = size + cnt; int addArr[cnt] = {0}; for (int i = 0; i < cnt; i++) { int num = 0; scanf("%d", &num); addArr[i] = num; } int *newArr = mallocNewArr(size); //拷贝旧数组给新数组 for (int i = 0; i < size - cnt; i++) { newArr[i] = arr[i]; } //添加多个数字到新数组后(核心) for (int i = 0; i < cnt; i++) { newArr[size - cnt + i] = addArr[i]; } free(arr); arr = newArr;//更新地址 printf("批量添加成功!当前数组:"); printArray(arr, size); } else if(id == 0) { free(arr); printf("程序退出,内存释放完毕\n"); break; } else { printf("输入错误,请重新输入!\n"); } } } ​

四、动态数组核心知识点总结

4.1 核心原理

C语言动态数组没有真正的动态扩容机制,本质是:堆内存重新分配+数据拷贝+内存替换,依靠指针和堆内存的长效特性实现动态效果。

4.2 三大坑点

  • 禁止返回函数内部栈数组指针,会产生野指针

  • 扩容后必须free旧堆内存,杜绝内存泄漏

  • 所有操作必须做位置合法性校验,防止数组越界

4.3 静态数组 vs 动态数组

特性

静态数组

动态数组

内存位置

栈内存

堆内存

长度特性

编译固定,不可修改

运行中动态增减

内存管理

系统自动管理

手动malloc、free

适用场景

数据量固定

数据量未知、动态变化

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询