欢迎来到白刘的领域 Miracle_86.-CSDN博客
系列专栏 C语言知识
先赞后看,已成习惯
创作不易,多多支持!
这里是白刘,书接上文,我们讲到了一维数组,以及sizeof关键字。今天我们来介绍一下数组的其它一些知识。
目录
一、二维数组
1.二维数组的概念
之前我们学习的都是一维数组,它的元素都是内置的,但是如果我们突发奇想,把一维数组当作数组的元素,那这个时候就出现了二维数组。同理,其它多维数组都是如此。
2.二维数组的创建
基本的形式如下:
type arr_name[常量值1][常量值2];
首先,和一维数组一样,type代表数组元素的类型;arr_name是数组名;中括号里放常量,常量1代表行,常量2代表列。
eg:
int arr[3][5];
char data[4][30];
3.二维数组的初始化
二维数组的初始化有好多种,比如:不完全初始化、完全初始化、按照行初始化。
不完全初始化
这种初始化没有把数组的位置全填满,未填满的位置用0占位,eg:
int arr1[3][5] = {1,2};
int arr2[3][5] = {0};
完全初始化
这种初始化把数组位置全占满了,eg:
int arr3[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
按行初始化
如果我们想指定某个位置是某个元素的时候,这时候就可以用按行初始化,eg:
int arr4[3][5] = {{1,2},{3,4},{5,6}};
初始化的时候,我们可以省略行,但是不能省略列,eg:
int arr5[][5] = {1,2,3};
int arr6[][5] = {1,2,3,4,5,6,7};
int arr7[][5] = {{1,2}, {3,4}, {5,6}};
二、二维数组的使用
1.二维数组的下标
我们刚刚讲了常量1是行,常量2是列,那我们有了行和列,就能确定元素位置,进而我们就能对其操作。在C语言中,二维数组的行和列都是从0开始的。
int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
绿色代表行号、蓝色代表列号。比如说我想找到“7”,我只需要输出arr[2][4]即可:
#include <stdio.h>
int main()
{
int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
printf("%d\n", arr[2][4]);
return 0;
}
我们来看结果:
2.二维数组的输入和输出
刚刚我们成功访问了一个元素,那再结合我们访问整个一维数组的经验,访问整个二维数组是不是也变得简单了。我们可以使用两层循环,第一层循环来控制行,第二层控制列。理论存在,实践开始。
#include <stdio.h>
int main()
{
int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
int i = 0;//遍历⾏
//输⼊
for(i=0; i<3; i++) //产⽣⾏号
{
int j = 0;
for(j=0; j<5; j++) //产⽣列号
{
scanf("%d", &arr[i][j]); //输⼊数据
}
}
//输出
for(i=0; i<3; i++) //产⽣⾏号
{
int j = 0;
for(j=0; j<5; j++) //产⽣列号
{
printf("%d ", arr[i][j]); //输出数据
}
printf("\n");
}
return 0;
}
来看执行结果:
3.二维数组在内存中的存储
我们在一维数组那里学到了,一维数组是随着下标数增加,地址由小变大。那二维数组是否也是如此呢?
我们来操作看看:
#include <stdio.h>
int main()
{
int arr[3][5] = { 0 };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);
}
}
return 0;
}
来看结果:
还是观察,我们可以发现,二维数组每个元素依旧是连续存放的
如下:
了解其布局后,便于我们以后介绍指针在数组中的访问。
三、C99中的变长数组
在C99之前,数组的大小只能用常量以及常量表达式,但是在C99中添加了变长数组(variable-length array,简称 VLA)这样一个特性,允许变量指定数组大小。
废话不多说,直接上代码:
int n = a+b;
int arr[n];
变长数组的特性就是数组长度未知,只有在运行的时候才可能会确定,所以变长数组不可以初始化。好处是程序员不必在开发时为数组初始化一个估计值。但是变长数组不代表着数组的大小可以随时随地变化,比较迷惑,变量可以变,但是一旦确定下来数组的大小,数组就不能变了。
在VS2022中,编译器不支持变长数组,但是在gcc中,可以使用,eg:
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);//根据输⼊数值确定数组的⼤⼩
int arr[n];
int i = 0;
for (i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
for (i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
第一次,我把5赋给n,然后输入5个元素,并且输出
第二次,我把10赋给n,然后输入10个元素,并且输出
来看执行结果:
四、二分查找
在一个升序的数组中查找n,我们首先想到的可能是,挨个遍历,一个一个找。但是这种方法在某些特定情况,比较麻烦。比如说,白刘戴了一块金表,让我们猜多少钱,白刘说不超过三万块钱。那我们还能一个一个遍历吗,从一开始慢慢猜?显然不合理,一般我们可以折半去猜,比如说猜个15000,白刘说少了,再猜22500,又大了......是不是比从1开始要快啊,这种方法,就叫做二分查找,也叫折半查找。
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int left = 0;
int right = sizeof(arr)/sizeof(arr[0])-1;
int key = 7;//要找的数字
int mid = 0;//记录中间元素的下标
int find = 0;
while(left<=right)
{
mid = (left+right)/2;
if(arr[mid]>key)
{
right = mid-1;
}
else if(arr[mid] < key)
{
left = mid+1;
}
else
{
find = 1;
break;
}
}
if(1 == find )
printf("找到了,下标是%d\n", mid);
else
printf("找不到\n");
}
首先这个数组必须是有序的,这点很重要,即使无序也咩关系,我们后面会学到排序函数。
然后我们找到数组的两端的下标以及数组中间的下标(不用管奇数还是偶数)中间下标将数组分为两部分。
然后放入循环,判断要找的数在哪部分,然后重新规定左右两端的下标,直到找到那个数为止。亦或者,如果没有那个数,也要跳出循环。
跳出循环的条件我们可以想象一下,就像左右两边分别有两辆小车,然后集体往中间开,如果其中一个小车碰到目标,则重新定起点,距离目标远的那个从中间下标开,直到小车相遇(中间即是目标)。没找到的话,两个车会擦肩而过,这个时候我们就退出循环。
求中间下标的时候,有时候我们不用加在一起然后再除以2,这样容易数太大了,造成数据丢失
如上图,我们可以先将两者作差,然后把差值一分为二,分给两端。也就是:
mid = (left+right) / 2