C语言06 |数组笔记
目录一、数组基础1.1 三种定义与初始化方式方式1只指定长度不赋值方式2省略长度完全初始化方式3指定长度部分初始化1.2.数组遍历与下标规则二、数组的内存底层原理三、数组传参四、练习4.1 基础求数组最大值求和、平均数与统计4.2 查找算法顺序查找二分查找4.3 排序算法冒泡排序选择排序4.4 进阶数组反转随机打乱洗牌算法五、易错一、数组基础1.1 三种定义与初始化方式数组基本语法数据类型 数组名[长度] {初始值};方式1只指定长度不赋值intarr1[80];定义了可存放80个int的数组注意局部数组不手动初始化里面是随机垃圾值不是0方式2省略长度完全初始化intarr2[]{1,2,3};编译器会自动根据初始值数量把数组长度定为3最常用的写法不容易写错长度方式3指定长度部分初始化intarr3[5]{1,2,3};只给前3个元素赋值剩下的元素系统自动补0实用技巧int arr[100] {0};可以一键把整个数组清01.2.数组遍历与下标规则数组通过索引访问单个元素从0开始。比如长度为5的数组合法下标是0~4第1个元素是arr[0]最后一个是arr[4]。想把数组所有元素都打印出来就要用循环遍历#includestdio.hintmain(){intarr[]{1,2,3,4,5};intlensizeof(arr)/sizeof(arr[0]);// 计算数组长度for(inti0;ilen;i){printf(%d ,arr[i]);}return0;}运行结果1 2 3 4 5sizeof(arr)得到数组总字节数除以单个元素字节数就是元素个数二、数组的内存底层原理很多人学数组只停留在“会用”理解了内存逻辑后续学指针才不会懵。我们通过代码打印每个元素的地址看看规律#includestdio.hintmain(){intarr[]{1,2,3};printf(数组名地址%p\n,arr);printf(arr[0]地址%p\n,arr[0]);printf(arr[1]地址%p\n,arr[1]);printf(arr[2]地址%p\n,arr[2]);return0;}运行结果示例地址每次运行不同但规律一致数组名地址000000000061FE0C arr[0]地址000000000061FE0C arr[1]地址000000000061FE10 arr[2]地址000000000061FE14结论数组名本质就是首地址和arr[0]的值完全一样指向第0个元素int占4个字节每个元素地址依次相差4arr[i]的底层逻辑首地址 i × 单个元素大小下标就是偏移量三、数组传参把数组传给函数是高频操作但这里有个经典大坑一定要记牢。先看一段代码#includestdio.hvoidprintArr(intarr[],intlen);intmain(){intarr[]{1,2,3,4,5};intlensizeof(arr)/sizeof(arr[0]);printf(main中数组大小%zu字节\n,sizeof(arr));printArr(arr,len);return0;}voidprintArr(intarr[],intlen){printf(函数中数组大小%zu字节\n,sizeof(arr));for(inti0;ilen;i)printf(%d ,arr[i]);printf(\n);}64位环境运行结果main中数组大小20字节 函数中数组大小8字节 1 2 3 4 5为什么会这样数组作为函数参数时会退化为指针。函数里的arr只是一个存地址的指针变量sizeof得到的是指针大小32位4字节64位8字节不是整个数组的大小。重点把数组传给函数时必须同时把数组长度一起传过去函数内部无法自己算出数组长度。四、练习4.1 基础求数组最大值题目给定数组{33, 5, 22, 44, 55}找出最大值并打印。思路先假设第0个元素是最大值遍历数组挨个比较遇到更大的就更新。完整代码#includestdio.hintmain(){intarr[]{33,5,22,44,55};intlensizeof(arr)/sizeof(arr[0]);intmaxarr[0];for(inti1;ilen;i){if(arr[i]max)maxarr[i];}printf(最大值%d\n,max);return0;}运行结果最大值55求和、平均数与统计题目生成10个1~100的不重复随机数计算总和、平均数并统计有多少个数小于平均数。完整代码#includestdio.h#includestdlib.h#includetime.h// 判断数字是否已在数组中存在返回1不存在返回0intcontains(intarr[],intlen,intnum);intmain(){intarr[10]{0};intlensizeof(arr)/sizeof(arr[0]);srand(time(NULL));// 生成不重复随机数for(inti0;ilen;){intnumrand()%1001;intflagcontains(arr,len,num)if(!flag){arr[i]num;i;}}// 打印数组printf(生成的数组);for(inti0;ilen;i)printf(%d ,arr[i]);printf(\n);// 求和intsum0;for(inti0;ilen;i)sumarr[i];// 求平均数doubleavg(double)sum/len;printf(总和%d平均数%.2f\n,sum,avg);// 统计小于平均数的个数intcount0;for(inti0;ilen;i){if(arr[i]avg)count;}printf(小于平均数的数有%d个\n,count);return0;}intcontains(intarr[],intlen,intnum){for(inti0;ilen;i){if(arr[i]num)return1;}return0;}运行结果生成的数组42 68 35 1 70 25 79 59 63 65 总和507平均数50.70 小于平均数的数有4个4.2 查找算法顺序查找适用场景乱序、有序数组都能用适合数据量小的情况思路从第0个元素开始挨个比对找到就返回下标遍历完没找到返回-1。题目在数组{1,2,3,4,5}中查找数字4的下标。完整代码#includestdio.hintorderSearch(intarr[],intlen,intnum);intmain(){intarr[]{1,2,3,4,5};intlensizeof(arr)/sizeof(arr[0]);intnum4;intindexorderSearch(arr,len,num);printf(目标数字%d的下标%d\n,num,index);return0;}intorderSearch(intarr[],intlen,intnum){for(inti0;ilen;i){if(arr[i]num)returni;// 找到返回下标}return-1;// 没找到返回-1}运行结果目标数字4的下标3二分查找适用场景必须是有序数组数据量越大效率越高核心思路每次取中间元素和目标比较每次排除一半的查找范围效率远高于顺序查找。题目在升序数组{7,23,79,81,103,127,131,147}中查找数字131的下标。#includestdio.hintbinarySearch(intarr[],intlen,intnum);intmain(){intarr[]{7,23,79,81,103,127,131,147};intlensizeof(arr)/sizeof(arr[0]);intnum131;intindexbinarySearch(arr,len,num);printf(目标数字%d的下标%d\n,num,index);return0;}intbinarySearch(intarr[],intlen,intnum){intmin0;// 左边界intmaxlen-1;// 右边界while(minmax){intmid(minmax)/2;// 计算中间下标if(numarr[mid])minmid1;// 目标比中间大去右半边找elseif(numarr[mid])maxmid-1;// 目标比中间小去左半边找elsereturnmid;// 找到返回下标}return-1;// 没找到}运行结果目标数字131的下标6补充也可以用for循环实现相同逻辑本质都是控制左右边界收缩while写法更符合二分查找的语义更常用。注意二分查找的前提是数组有序乱序数组不能直接使用。4.3 排序算法排序是数组的经典应用面试和日常开发都很常见。冒泡排序核心思想相邻元素两两比较大的往后交换每一轮结束当前最大的数会“冒”到末尾。n个元素只需要排n-1轮每轮比较次数逐轮减少。题目把数组{3,5,2,1,4}按从小到大升序排列。完整代码#includestdio.hintmain(){intarr[]{3,5,2,1,4};intlensizeof(arr)/sizeof(arr[0]);// 外层循环控制轮数n个数排n-1轮for(intj0;jlen-1;j){// 内层循环每轮两两比较末尾已排好的不用再比for(inti0;ilen-1-j;i){if(arr[i]arr[i1]){inttemparr[i];arr[i]arr[i1];arr[i1]temp;}}}// 打印排序结果for(inti0;ilen;i)printf(%d ,arr[i]);return0;}运行结果1 2 3 4 5选择排序核心思想从第0个位置开始和后面所有元素比较找到最小的换到当前位置下一轮从第1个位置开始重复直到所有元素排好。相比冒泡排序交换次数更少。题目把数组{3,5,2,1,4}按升序排列。完整代码#includestdio.hintmain(){intarr[]{3,5,2,1,4};intlensizeof(arr)/sizeof(arr[0]);// 外层循环控制当前要确定的位置for(inti0;ilen-1;i){// 内层循环和后面所有元素比找最小的for(intji1;jlen;j){if(arr[i]arr[j]){inttemparr[i];arr[i]arr[j];arr[j]temp;}}}for(inti0;ilen;i)printf(%d ,arr[i]);return0;}运行结果1 2 3 4 54.4 进阶数组反转思路双指针法一个指头一个指尾交换元素后头后移、尾前移直到相遇。题目把数组{1,2,3,4,5}反转成{5,4,3,2,1}。完整代码#includestdio.hintmain(){intarr[]{1,2,3,4,5};intlensizeof(arr)/sizeof(arr[0]);inti0;intjlen-1;while(ij){inttemparr[i];arr[i]arr[j];arr[j]temp;i;j--;}for(inti0;ilen;i)printf(%d ,arr[i]);return0;}运行结果5 4 3 2 1随机打乱洗牌算法思路遍历每个位置每次生成一个随机下标和当前位置交换元素。完整代码#includestdio.h#includestdlib.h#includetime.hintmain(){intarr[]{1,2,3,4,5};intlensizeof(arr)/sizeof(arr[0]);srand((unsignedint)time(NULL));for(inti0;ilen;i){intrndrand()%len;// 生成随机下标inttemparr[i];arr[i]arr[rnd];arr[rnd]temp;}for(inti0;ilen;i)printf(%d ,arr[i]);return0;}运行结果示例3 1 5 2 4五、易错下标越界长度为n的数组下标范围是0~n-1。越界不会直接报错但会破坏内存数据导致程序异常。数组不能整体赋值只能在定义时初始化定义完成后不能用arr {1,2,3}这种写法赋值。函数里用sizeof算数组长度数组传参会退化为指针sizeof得到的是指针大小不是数组总大小。传数组必须同时传长度。局部数组不初始化是垃圾值只有部分初始化时剩余元素才会自动补0完全不初始化的局部数组里面是随机值。二分查找必须用在有序数组乱序数组直接用二分查找会得到错误结果必须先排序再查找。如果这篇文章对你有帮助欢迎点赞收藏关注后续会更新更多C语言入门干货~