@TOC
1. 概念
数组是一组相同类型元素的集合。 从这个概念中我们就可以发现2个有价值的信息:
- 数组中存放的是1个或者多个数据,但是数组元素个数不能为0。
- 数组中存放的多个数据,类型是相同的。
数组分为一维数组和多维数组,多维数组一般比较多见的是二维数组。
2. 一维数组的创建与初始化
创建
基本语法如下:
type arr_name[常量值];
存放在数组的值被称为数组的元素,数组在创建的时候可以指定数组的大小和数组的元素类型。 type 指定的是数组中存放数据的类型,可以是:char、short、int、float 等,也可以是自定义的类型。 arr_name 指的是数组名的名字,这个名字根据实际情况,起的有意义就行。 []中的常量值是用来指定数组的大小的,这个数组的大小是根据实际的需求指定就行。
比方说,创建一个 int 类型的数组存放一个20个人的班级的数学成绩
int math[20];
初始化
在数组创建的时候给它一些初始值就叫做数组的初始化,初始化有两种类型:
- 完全初始化
- 不完全初始化
//完全初始化
int arr[5] = {1,2,3,4,5};
//不完全初始化
int arr2[6] = {1};//第⼀个元素初始化为1,剩余的元素默认初始化为0
//错误的初始化 - 初始化项太多
int arr3[3] = {1, 2, 3, 4};
数组的类型
我们知道 int 表示整形变量类型,float表示浮点数,那数组有没有类型呢? 事实上,数组也是有类型的,数组算是一种自定义类型,去掉数组名留下的就是数组的类型。如:
int arr1[10];
int arr2[12];
char ch[5];
它们的类型分别是
int arr1
int arr2
char ch
3. 一维数组的使用
了解了一维数组的基本语法,一维数组可以存放数据,存放数据的目的是对数据的操作这些知识之后,我们应该如何使用一维数组呢?
一维数组的下标
C语言规定数组是有下标的,下标是从0开始的,假设数组有n个元素,最后一个元素的下标是n-1,下标就相当于数组元素的编号,如下:
"[]"是下标引用操作符,是c语言提供的根据数组下标访问元素的操作符。
如:
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n", arr[7]);
printf("%d\n", arr[3]);
return 0;
}
输出结果很显然,是
8
4
数组元素的访问
那么,如何打印这一整个数组呢? 很简单,数组下标可以访问数组元素,那么只要产生所有的数组下标不就可以访问所有的元素了吗,利用循环结构,我们可以实现这一点:
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
for(i=0; i<10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
在能够访问所有的数组元素之后,想必你也知道该如何输入每个数组元素了吧,这里给出一个例子:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
for (i = 0; i < 10; i++)
{
scanf("%d", &arr[i]);
}
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
4. ⼀维数组在内存中的存储
要想深入地了解数组,了解数组在内存中的存储显然是十分有必要的。 我们可以通过
printf("%p",&arr[i]);
来得到一个数组元素的地址,那么通过数组元素的访问,我们可以得到数组中每个元素的地址 比如:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
for (i = 0; i < 10; i++)
{
printf("&arr[%d] = %p\n ", i, &arr[i]);
}
return 0;
}
(小提示:将编译方式改为x86可以减少地址位数)
可以看到,在这个数组中,每个元素的地址差了4,而这又刚好是 int 类型的大小,这也就意味着,数组中的元素在内存中是顺序存储的。
5. 利用 sizeof 计算数组元素个数
遍历数组的时候,我们需要使用数组中元素的个数,在上面我们是直接使用预定好的数组元素个数,但是这样会导致后期修改的时候比较麻烦,或者有时候会不知道数组的大小,那么C语言中有办法使用程序计算数组元素个数吗? sizeof 是C语言的一个可以计算类型或者变量大小关键字,其实 sizeof 也可以计算数组的大小。
可以用下面的方式计算数组的元素:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%d\n", sz);
return 0;
}
输出结果为:10
6. 二维数组
概念
前面学习的数组被称为一维数组,数组的元素都是内置类型的,如果我们把一维数组做为数组的元素,这时候就是二维数组,二维数组作为数组元素的数组被称为三维数组,二维数组以上的数组统称为多维数组。
创建
可以通过与创建一维数组相似的固定格式创建一个二维数组:
type arr_name[常量值1][常量值2];
例如:
int arr[3][5];
double data[2][8];
解释:上述代码中出现的信息 3表示数组有3行 5表示每一行有5个元素 int 表示数组的每个元素是整型类型 arr 是数组名,可以根据自己的需要指定名字data数组意思基本一致,只是数组元素类型为浮点数。
初始化
不完全初始化
int arr1[3][5] = {1,2};
int arr2[3][5] = {0};
这样就完成了二维数组的不完全初始化。arr1的第0行的第0,1个元素分别被初始化为1 2,没有被初始化的位置全部被初始化为0。arr2的所有元素都被初始化为 0。
完全初始化
int arr3[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
(当然,这里的空格只是辅助理解的,并没有实际意义,也不会影响程序的运行。)
按照行初始化
既然二维数组有行的概念,那么是否有办法只初始化每一行中的个别元素呢?当然是有的
int arr4[3][5] = {{1,2},{3,4},{5,6}};
这样,数组的0,1,2行的第0,1个元素就被初始化了,而其他没被初始化的部分同样被初始化为0。
初始化时可以省略行,但是不能省略列
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}};
如果你省略行,程序可以根据初始化时的元素个数和每行的元素个数找到最小的行数,并作为这个数组的实际行数。但如果省略列,程序就不知道应该如何初始化了。
二维数组的元素访问
与一维数组一样,二维数组也可以通过数组下标来访问二维数组的每一个元素,进而使用二维数组进行输入输出。C语言中,二维数组的行和列都是从 0 开始的。比如这样一个数组:
int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
它的行列就是这样的:
在知道二维数组的行和列之后就能通过下标引用操作符访问数组元素了!
我们可以通过循环的嵌套来输出或输入二维数组。比如我们要输入并输出上面的这个 arr数组。
#include <stdio.h>
int main()
{
int arr[3][5] = { 0 };
//输⼊
for (int i = 0; i < 3; i++) //产⽣⾏号
{
for (int j = 0; j < 5; j++) //产⽣列号
{
scanf("%d", &arr[i][j]); //输⼊数据
}
}
//输出
for (int i = 0; i < 3; i++) //产⽣⾏号
{
for (int j = 0; j < 5; j++) //产⽣列号
{
printf("%d ", arr[i][j]); //输出数据
}
printf("\n");
}
return 0;
}
二维数组在内存中的存储
可以通过
printf("%p ",&arr[i][j]);
来打印二位数组元素的地址。
从输出的结果来看,每一行内部的每个元素都是相邻的,地址之间相差4个字节,跨行位置处的两个元素(如:arr[0][4]和arr[1][0])之间也是差4个字节,所以二维数组中的每个元素都是连续存放的)。
7. C99标准引入变长数组
在C99标准之前,C语言在创建数组的时候,数组大小的指定只能使用常量、常量表达式,或者如果我们初始化数据的话,可以省略数组大小。
int arr1[10];
int arr2[3+5];
int arr3[] = {1,2,3};
很显然由于这样的语法限制,我们在创建数组时十分不灵活,为了确保数组的空间足够使用,我们往往会创建较大的数组,这样会造成空间的浪费。C99中给一个变长数组(variable-length array,简称VLA)的新特性,允许我们可以使用变量指定数组大小。
int n = a+b;
int arr[n];
数组 arr 就是变长数组,它的长度取决于变量n 的值,编译器没法事先确定,只有运行时才能知道 n是多少。变长数组的根本特征,就是数组长度只有运行时才能确定,所以变长数组不能初始化。它的好处是程序员不必在开发时,随意为数组指定一个估计的长度,程序可以在运行时为数组分配精确的长度。有个值得注意的点,变长数组的意思是数组的大小是可以使用变量来指定的,在程序运行的时候,根据变量的大小来指定数组的元素个数,而不是说数组的大小是可变的。数组的大小一旦确定就不能再变化了。
当然,要注意,即使是一些比较主流的 IDE 也是不支持C99标准的,也就无法使用变长数组,比如VS2022。下面通过搭载 gcc 编译器的 devc++ 进行演示。
#include<stdio.h>
int main()
{
int a = 0;
scanf("%d",&a);
int arr[a];//创建变长数组
int i=0;//值得注意的是,devc++中,循环变量不能在 for 循环的初始化语句中创建
for(i=0;i<a;i++)//输入
{
scanf("%d",&arr[i]);
}
for(i=0;i<a;i++)//输出
{
printf("%d ",arr[i]);
}
return 0;
}
8. 练习
在学习了数组的基本知识后,不妨来做两个常见的数组的题目。
练习1-多个字符从两端移动,向中间汇聚
#include<stdio.h>
#include<string.h>
int main()
{
char a[] = "hello world!";
char b[] = "############";
int left = 0;
int right = strlen(a) - 1;
printf("%s\n", b);
while (left <= right)
{
b[left] = a[left];
b[right] = a[right];
left++, right--;
printf("%s\n", b);
}
return 0;
这样就可以实现出题目要求的效果了,但是我们不妨进阶一下,能不能实现这样的效果:[video(video-dQiX9nKY-1719062094913)(type-csdn)(url-https://live.csdn.net/v/embed/401654)(image-https://i-blog.csdnimg.cn/blog_migrate/23bd824523514cdb3a182996699527da.jpeg)(title-练习1演示)]
当然可以!在windows开发环境下,你可以利用<windows.h>中的Sleep函数和system函数实现这个效果,可以这样:
#include<stdio.h>
#include<string.h>
#include<windows.h>
int main()
{
char a[] = "hello world!";
char b[] = "############";
int left = 0;
int right = strlen(a)-1;
printf("%s\n", b);
while (left <= right)
{
Sleep(1000);//注意大写
system("cls");//cls是清空控制台屏幕的意思
b[left] = a[left];
b[right] = a[right];
left++, right--;
printf("%s\n", b);
}
return 0;
练习2-二分查找
二分查找是一个极其高效的在升序序列中寻找一个数的算法。基本思路为:
这里给出一个举例:
#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");
}
上面求中间元素的下标,使用的是 mid=(left+right)/2 ,如果 left 和 right 比较大的时候可能存在问题(比如left和right都小于 int 的范围,但它们的和大于 int 的范围),可以使用下面的方式:
int mid = left+(right - left)/2;
来规避这一问题。
谢谢你的阅读,觉得有用的话不妨点个关注,我会持续更新更多高质量博客!