目录
- 一、数组基础
- 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};可以一键把整个数组清0
1.2.数组遍历与下标规则
数组通过索引访问单个元素,从0开始。
比如长度为5的数组,合法下标是0~4,第1个元素是arr[0],最后一个是arr[4]。
想把数组所有元素都打印出来,就要用循环遍历:
#include<stdio.h>intmain(){intarr[]={1,2,3,4,5};intlen=sizeof(arr)/sizeof(arr[0]);// 计算数组长度for(inti=0;i<len;i++){printf("%d ",arr[i]);}return0;}运行结果:
1 2 3 4 5sizeof(arr)得到数组总字节数,除以单个元素字节数就是元素个数
二、数组的内存底层原理
很多人学数组只停留在“会用”,理解了内存逻辑,后续学指针才不会懵。
我们通过代码打印每个元素的地址,看看规律:
#include<stdio.h>intmain(){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个字节,每个元素地址依次相差4
arr[i]的底层逻辑:首地址 + i × 单个元素大小,下标就是偏移量
三、数组传参
把数组传给函数是高频操作,但这里有个经典大坑,一定要记牢。
先看一段代码:
#include<stdio.h>voidprintArr(intarr[],intlen);intmain(){intarr[]={1,2,3,4,5};intlen=sizeof(arr)/sizeof(arr[0]);printf("main中数组大小:%zu字节\n",sizeof(arr));printArr(arr,len);return0;}voidprintArr(intarr[],intlen){printf("函数中数组大小:%zu字节\n",sizeof(arr));for(inti=0;i<len;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个元素是最大值,遍历数组挨个比较,遇到更大的就更新。
完整代码:
#include<stdio.h>intmain(){intarr[]={33,5,22,44,55};intlen=sizeof(arr)/sizeof(arr[0]);intmax=arr[0];for(inti=1;i<len;i++){if(arr[i]>max)max=arr[i];}printf("最大值:%d\n",max);return0;}运行结果:
最大值:55求和、平均数与统计
题目:生成10个1~100的不重复随机数,计算总和、平均数,并统计有多少个数小于平均数。
完整代码:
#include<stdio.h>#include<stdlib.h>#include<time.h>// 判断数字是否已在数组中,存在返回1,不存在返回0intcontains(intarr[],intlen,intnum);intmain(){intarr[10]={0};intlen=sizeof(arr)/sizeof(arr[0]);srand(time(NULL));// 生成不重复随机数for(inti=0;i<len;){intnum=rand()%100+1;intflag=contains(arr,len,num)if(!flag){arr[i]=num;i++;}}// 打印数组printf("生成的数组:");for(inti=0;i<len;i++)printf("%d ",arr[i]);printf("\n");// 求和intsum=0;for(inti=0;i<len;i++)sum+=arr[i];// 求平均数doubleavg=(double)sum/len;printf("总和:%d,平均数:%.2f\n",sum,avg);// 统计小于平均数的个数intcount=0;for(inti=0;i<len;i++){if(arr[i]<avg)count++;}printf("小于平均数的数有:%d个\n",count);return0;}intcontains(intarr[],intlen,intnum){for(inti=0;i<len;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的下标。
完整代码:
#include<stdio.h>intorderSearch(intarr[],intlen,intnum);intmain(){intarr[]={1,2,3,4,5};intlen=sizeof(arr)/sizeof(arr[0]);intnum=4;intindex=orderSearch(arr,len,num);printf("目标数字%d的下标:%d\n",num,index);return0;}intorderSearch(intarr[],intlen,intnum){for(inti=0;i<len;i++){if(arr[i]==num)returni;// 找到,返回下标}return-1;// 没找到,返回-1}运行结果:
目标数字4的下标:3二分查找
适用场景:必须是有序数组,数据量越大效率越高
核心思路:每次取中间元素和目标比较,每次排除一半的查找范围,效率远高于顺序查找。
题目:在升序数组{7,23,79,81,103,127,131,147}中,查找数字131的下标。
#include<stdio.h>intbinarySearch(intarr[],intlen,intnum);intmain(){intarr[]={7,23,79,81,103,127,131,147};intlen=sizeof(arr)/sizeof(arr[0]);intnum=131;intindex=binarySearch(arr,len,num);printf("目标数字%d的下标:%d\n",num,index);return0;}intbinarySearch(intarr[],intlen,intnum){intmin=0;// 左边界intmax=len-1;// 右边界while(min<=max){intmid=(min+max)/2;// 计算中间下标if(num>arr[mid])min=mid+1;// 目标比中间大,去右半边找elseif(num<arr[mid])max=mid-1;// 目标比中间小,去左半边找elsereturnmid;// 找到,返回下标}return-1;// 没找到}运行结果:
目标数字131的下标:6补充:也可以用for循环实现相同逻辑,本质都是控制左右边界收缩,while写法更符合二分查找的语义,更常用。
注意:二分查找的前提是数组有序,乱序数组不能直接使用。
4.3 排序算法
排序是数组的经典应用,面试和日常开发都很常见。
冒泡排序
核心思想:相邻元素两两比较,大的往后交换;每一轮结束,当前最大的数会“冒”到末尾。
n个元素只需要排n-1轮,每轮比较次数逐轮减少。
题目:把数组{3,5,2,1,4}按从小到大升序排列。
完整代码:
#include<stdio.h>intmain(){intarr[]={3,5,2,1,4};intlen=sizeof(arr)/sizeof(arr[0]);// 外层循环:控制轮数,n个数排n-1轮for(intj=0;j<len-1;j++){// 内层循环:每轮两两比较,末尾已排好的不用再比for(inti=0;i<len-1-j;i++){if(arr[i]>arr[i+1]){inttemp=arr[i];arr[i]=arr[i+1];arr[i+1]=temp;}}}// 打印排序结果for(inti=0;i<len;i++)printf("%d ",arr[i]);return0;}运行结果:
1 2 3 4 5选择排序
核心思想:从第0个位置开始,和后面所有元素比较,找到最小的换到当前位置;
下一轮从第1个位置开始重复,直到所有元素排好。相比冒泡排序,交换次数更少。
题目:把数组{3,5,2,1,4}按升序排列。
完整代码:
#include<stdio.h>intmain(){intarr[]={3,5,2,1,4};intlen=sizeof(arr)/sizeof(arr[0]);// 外层循环:控制当前要确定的位置for(inti=0;i<len-1;i++){// 内层循环:和后面所有元素比,找最小的for(intj=i+1;j<len;j++){if(arr[i]>arr[j]){inttemp=arr[i];arr[i]=arr[j];arr[j]=temp;}}}for(inti=0;i<len;i++)printf("%d ",arr[i]);return0;}运行结果:
1 2 3 4 54.4 进阶
数组反转
思路:双指针法,一个指头一个指尾,交换元素后头后移、尾前移,直到相遇。
题目:把数组{1,2,3,4,5}反转成{5,4,3,2,1}。
完整代码:
#include<stdio.h>intmain(){intarr[]={1,2,3,4,5};intlen=sizeof(arr)/sizeof(arr[0]);inti=0;intj=len-1;while(i<j){inttemp=arr[i];arr[i]=arr[j];arr[j]=temp;i++;j--;}for(inti=0;i<len;i++)printf("%d ",arr[i]);return0;}运行结果:
5 4 3 2 1随机打乱(洗牌算法)
思路:遍历每个位置,每次生成一个随机下标,和当前位置交换元素。
完整代码:
#include<stdio.h>#include<stdlib.h>#include<time.h>intmain(){intarr[]={1,2,3,4,5};intlen=sizeof(arr)/sizeof(arr[0]);srand((unsignedint)time(NULL));for(inti=0;i<len;i++){intrnd=rand()%len;// 生成随机下标inttemp=arr[i];arr[i]=arr[rnd];arr[rnd]=temp;}for(inti=0;i<len;i++)printf("%d ",arr[i]);return0;}运行结果示例:
3 1 5 2 4五、易错
下标越界
长度为n的数组,下标范围是0~n-1。越界不会直接报错,但会破坏内存数据,导致程序异常。数组不能整体赋值
只能在定义时初始化,定义完成后不能用arr = {1,2,3}这种写法赋值。函数里用sizeof算数组长度
数组传参会退化为指针,sizeof得到的是指针大小,不是数组总大小。传数组必须同时传长度。局部数组不初始化是垃圾值
只有部分初始化时,剩余元素才会自动补0;完全不初始化的局部数组,里面是随机值。二分查找必须用在有序数组
乱序数组直接用二分查找会得到错误结果,必须先排序再查找。
如果这篇文章对你有帮助,欢迎点赞收藏关注,后续会更新更多C语言入门干货~