
目录一、先搞懂静态数组为什么不能做动态扩容核心1.1 错误写法1直接返回数组本体1.2 错误写法2返回局部数组指针最经典野指针BUG二、动态数组的两种解决方法2.1 方案一外部定义数组函数传参赋值低封装、临时方案2.2 方案二malloc堆内存动态分配标准方案2.2.1 堆内存核心特性2.2.2 动态扩容底层固定逻辑三、动态数组功能实战逻辑拆解前置初始化逻辑3.1 单数据添加10013.2 按位置删除10023.3 按位置查找10033.4 按位置替换10043.5 中间插入数据10053.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场景在数组末尾追加一个数据逻辑长度size1 → 申请更大堆内存 → 旧数据拷贝 → 新数据存入末尾 → free旧内存 → 指针替换。3.2 按位置删除1002这是数组删除的经典算法逻辑无需重新开辟内存校验位置合法性 → 从删除位置开始后一位数据向前覆盖前一位 → 有效长度size-1。核心数组删除本质是数据覆盖长度缩减。3.3 按位置查找1003最简单的基础功能校验位置是否合法直接通过下标取值打印即可动态数组指针支持下标访问和静态数组用法一致。3.4 按位置替换1004校验位置合法性 → 直接覆盖对应下标数据无需改动数组长度和内存效率最高。3.5 中间插入数据1005比末尾添加复杂核心逻辑长度size1 → 开辟新内存 → 拷贝原数据 →从后往前后移数据腾出插入位置 → 存入新数据 → 释放旧内存。重点必须从后往前移位避免数据被覆盖丢失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适用场景数据量固定数据量未知、动态变化