目录
一、先搞懂:静态数组为什么不能做动态扩容?(核心)
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语言没有直接的数组扩容函数,动态扩容的本质是以新代旧,分为四步:
根据新长度,malloc申请一块更大的堆内存(新数组)
将旧数组的所有数据,拷贝到新数组中
释放旧数组堆内存(防止内存泄漏)
将旧数组指针指向新数组地址,完成扩容
重点:一定要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 |
适用场景 | 数据量固定 | 数据量未知、动态变化 |