Hello everyone! forever 在前面的内容中给大家介绍了 C 语言的各种基本语句,用 C 语言也可以解决许多实际问题,但是仍然有限。在计算机应用中常常会遇到一组组有规律的同类型数据,例如:数值计算中的矢量运算、矩阵运算以及非数值计算中的查找和排列等等。如果用大量的变量来实现,就会是程序非常繁琐复杂。因此,这里就引来了数组这一概念。上一篇无心版三子棋游戏中那个棋盘就是用数组来实现的哈!
在数学中处理问题时,用一组带有下标的变量来表示数据,使运算过程的表达非常简洁。同理,在 C 语言中也为我们提供了这样一种构造类型数据,就是以上提到的——数组。 它是由一组同名下标变量组成,用数组来存储数据,就可以利用循环来改变下标值对各下标变量进行相同操作的重复处理,使程序变得简明清晰。
下标变量由统一的数组名和方括号括起来共同表示,称为数组元素。 同一数组的各个元素只是下标不同,通过数组名和下标可以直接去访问数组元素,举例来说数组名和下标合起来就类似于一个人的身份证号码,可以简单快速通过身份证号码定位找到这个人。
数组类型是构造数据类型的一种,是一种比较常用的数据类型。它的特点就是构成数组的元素个数及类型相同,同一数组的各个元素在内存中按一定顺序排列的。像简单变量一样,数组必须先定义后使用,即程序中必须要先定义数组,然后才能引用数组以及它的数组元素。
数组名下标个数称为数组的维数,带一个下标的是一维数组,带多个下标的是多维数组。因此数组存在一维数组,二维数组……多维数组。
当然数组还可以按照内部元素来区分。有的数组里面放的是数字,有的里面放的是字符,因此可以分为数字数组、字符数组和指针数组等等。
元素类型名 数组名[元素数] 【 = { 元素初值表 } 】;
数组定义解释说明:
(1)数组元素类型名用于定义该数组中存放何种类型的数据(决定每个元素所占空的大小);
示例:
int arr[10];//整型数组arr有十个元素
float brr[7];//float 类型数组brr有七个元素
char crr[5];//字符数组 crr 有五个元素
(2)数组名的命名规则和变量名的命名规则相同;
(3)[ ] 该方括号中的数表示该数组中元素数的个数,即数组长度。元素数应为常量表达式,其中可以包含常量和符号常量。
(4)C 语言中不允许使用变量来定义元素数,即不允许动态定义元素。
错误示范:
int a(10);//错误,不能用()定义
int arr[10.0];//错误定义,元素数不能用浮点型
int n;
int brr[n];//错误,这里元素数中用了n这个变量
(5)C 语言规定,数组的下标一律从0开始连续排列。
数组在定义时候可以选择初始化,即用 “ ={元素初值列表} ” 给数组各个元素赋值。可以有以下几种方式:
(1)数组元素初值列表用逗号分隔,列表包含全部元素的初始值。
示例:
int arr[5] = {1,2,3,4,5};
这里将1~5当作初始值分别赋值给 arr[0] ~ arr[4];
如果元素赋同一个相同的值时也必须写成这样:
int arr[5] = {1,1,1,1,1};
(2)数组元素初始赋值时只包含前面部分元素。
示例:
int arr[10] = {6,7,8,9,10};
这里前五个元素 arr[0]~ arr[4] 被赋值,后面的元素系统默认为 0;
(3)如果元素初始值列表包含全部元素,可以省略方括号里面的元素数,定义的数组元素个数由初值个数自动生成。
示例:
int arr[]={1,2,3,4,5,6};
这里的元素数就是里面初始值的个数。
(4)数组存储方式:
一维数组所有元素按照下标的顺序连续分配在内存中。
示例:
int arr[]={1,2,3,4,5,6};
(5)数组名代表数组首地址,即数组名 arr 表示 arr[0] 元素的地址:arr=&arr[0]。
(6)若元素初值列表中的初值数目多于元素数,系统编译会报错。
例如:
错误示例:
int arr[5] = {1,2,3,4,5,6};
这里数组大小为 5,但给其赋了六个值,出现错误。
(7)对以下代码是如何在内存中存储的需要了解。
char arr1[] = { ‘a’,‘b’,‘c’ };
char arr2[] = “abc”;
#include
#include
int main()
{
char arr1[] = { 'a','b','c' };
printf("arr数组大小为:%d\n", sizeof(arr1));
//arr1有三个元素,数组的大小为三个字节
printf("%d\n", strlen(arr1));//strlen函数是计算字符串大小的,遇到\0就会停下来,
//arr1中没有\0所以不知何时停止,因此给出的是随机值
char arr2[] = "abc";
printf("arr2数组大小为:%d\n", sizeof(arr2));
//arr2有四个元素,数组的大小是四个字节
printf("%d\n", strlen(arr2));//"abc"这个字符串后面暗含有一个\0,因此输出结果为3
return 0;
}
这里就补充对 strlen 函数和 sizeof 操作符进行一下比较:
strlen 是一个库函数;
是计算字符串的长度的,只适用于字符串;
以 ‘\0’ 为界限,遇到 ‘\0’ 便停止,计算到 ‘\0’ 的前一个字符。
sizeof 是一个操作符(运算符);
是用来计算字符所占内存空间的大小,任何类型都能使用;
只关注空间大小,与是否有 '\0’无关。
数组名[下标]
其中下标为整型表达式,用它确定所引用元素的序号。C语言规定下标一律从 0 开始编号,引用时下标不得越界,所以最大的下标值等于定义的数组长度减 1。
[ ] 是下标引用操作符,它其实就是数组访问操作符,引用数组元素是根据数组名和下标来实现的。
虽然 C 语言在运行中一般不做越界检查,但越界往往会出现意想不到的结果,当越界到代码区时,甚至还会出现系统崩溃。
错误示例:
//数组越界错误示例
#include
int main()
{
int arr[7] = { 0 };
for (int i = 0; i < 7; i++)
{
scanf("%d", &arr[i]);
}
for (int i = 0; i <= 7; i++)//这里打印到i=7了,出现数组访问越界
{
printf("%d\n", arr[i]);
}
return 0;
}
在程序中。一般只能逐个引用数组元素。引用数组元素等价于引用与它同类型的变量。
我们来看代码1:
冒泡排序法从大到小排序数组元素
#include
//冒泡排序法从大到小排序数组元素
int main()
{
int arr[10] = { 0 };//数组不完全初始化
int i = 0;//做下标
for (int i = 0; i < 10; i++)
{
scanf("%d", &arr[i]);//scanf输入数据时,数组元素名前必须加上&
}
int temp = 0;
for (i = 0; i < 10-1; i++)//冒泡排序主体
{
for (int j = 0; j < 10 - i - 1; j++)
{
if (arr[j] < arr[j + 1])
{
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
总结1:
1.数组是使用下标来访问的,下标必须从0开始;
2.当用 scanf 给数组元素输入数据时,数组元素名前必须要有 ‘&’ 操作符。
我们来看代码2:
二分法查找数组元素
#include
//二分查找数组元素
int search(int arr[], int n,int k)
{
int left = 0;
int right = n - 1;
int mid = 0;
while (left <= right)//二分查找循环体
{
mid = (left + right) / 2;
if (k < arr[mid])
{
right = mid - 1;
}
else if (k > arr[mid])
{
left = mid + 1;
}
else
{
return mid;//找到了,直接返回该数下标
break;
}
}
if (left > right)
{
return -1;//未找到返回-1
}
}
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};//数组完全初始化
int sz = sizeof(arr) / sizeof(arr[0]);//计算数组长度
int k = 0;//定义需要查找的数
while (scanf("%d", &k) != EOF)//实现多次输入
{
int ret = search(arr, sz, k);//调用查找函数,并传参
if (-1 == ret)//判断函数返回值
{
printf("not find\n");
}
else
{
printf("find it ,the number of: %d\n", ret);
}
}
return 0;
}
总结:
1.除了函数调用语句之外,其他语句都不能单独用数组名引用整个数组,不能给数组整体赋值;
2.当函数传参时,形参里是可以直接定义数组形式来接受实参,如:int arr[]。但这里 arr[ ] 实际上是指针,只是为了方便理解学习,因此 C 提供可以直接定义数组形式的形参。
3. 数组是可以计算大小的。如下:
int arr[]={1,2,3};
int sz = sizeof(arr) / sizeof(arr[0]);
接下来,forever 和大家来探讨一下数组在内存中的存储。
看代码:
#include
//研究数组在内存中的存储结构
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < sz; i++)
{
printf("&arr[%d]=%p\n", i, &arr[i]);
}
return 0;
}
总结:
由输出结果得知,随着数组下标的增长,元素的地址,也有规律的递增。**因此,数组在内存中是连续存放的。**由低地址向高地址。
数学中矩阵的元素是二维分布的,每个元素都有行下标和列下标。C 语言中可以用数组存储矩阵,每个元素应该带两个下标,这样的数组称为二维数组。这里我们就以二维数组为例子来介绍多维数组哈!
元素类型名 数组名[行数][列数] 【 = {元素初始值}】;
定义解释说明:
(1)其本质和一维数组本质类似;
(2)行数、列数分别表示二维数组应该有多少行、多少列,它们都是常量表达式,常量表达式中可包含常量和符号常量,不允许有变量。
示例:
int arr[7][8];//数组arr有7行8列56个整型元素
float brr[5][2*4];//数组brr有5行8列40个浮点型元素
二维数组在定义时可以用 “ = {元素初值列表} ” 给数组各元素赋初值。
(1)与一维数组相同赋值时元素初值列表用逗号隔开,按储存顺序依次给前面的各个元素赋值。
示例:
int arr[2][3] = {1,2,3,4,5,6};//完全初始化
int brr[2][3] = {1,2,3};//不完全初始化,后面初始值默认为0
(2)在初值列表中,将每一行元素的初值用花括号括起来成为一组,按行赋值。
示例:
int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };//3行4列
这种初始化赋值方法非常直观,第一个花括号括起来的数据赋值给第一行,第二个花括号括起来的数据赋值给第二行,以此类推。而对于每行每列而言,也都是先存放下标为 0 的元素,再存放下标为1的元素,以此类推。这种赋值法便于检查。
(3)可以对部分元素赋初值,但要表达清楚。
示例:
int arr[3][4] = { {1,2,3},{4},{5} };//3行4列
//以上不同于以下这个
int brr[3][4] = {1,2,3,4};//3行4列
如图所示:
(4)二维数组在初始化时候,可以省略行数,但不能省略列数。
示例:
int arr[][5] = { {1,2,3},{4},{5},{6} };//4行5列
int brr[][4] = {1,2,3,4,5,6,7};//2行4列
数组名[行下标][列下标]
其中行下标和列下标均为整形表达式。引用时,数组元素的每一个下标都一律从0开始升序连续排列,最大下标分别等于数组定义的行数减1和列数减1,引用时行下标和列下标都不能越界。
这里和一维数组一样,除了函数调用语句外,其他语句都只能逐个引用数组元素,不能单独用数组名引用整个数组,不能给数组整体赋值。引用二维数组元素也相当于引用同类型的简单变量,而且在引用数组元素之前,也必须先定义该数组。
示例1:
求两个矩阵A和B之和。
要求:求两个矩阵之和,结果储存在矩阵A中。
代码如下:
//求两个矩阵A和B之和
#include
#define ROW 3//定义数组行数
#define LIN 4//定义数组列数
int main()
{
int A[ROW][LIN] = { 0 };
int B[ROW][LIN] = { 0 };
printf("矩阵A:\n");
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LIN; j++)
{
scanf("%d", &A[i][j]);//输入矩阵A的数据
}
}
printf("矩阵B:\n");
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LIN; j++)
{
scanf("%d", &B[i][j]);//输入矩阵B的数据
}
}
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LIN; j++)
{
A[i][j] += B[i][j];//进行矩阵之和计算
}
}
printf("矩阵A和B之和为:\n");
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LIN; j++)
{
printf("%-4d", A[i][j]);//输出结果 %-4d是靠左对齐,中间空4个单位长度
}
printf("\n");
}
return 0;
}
总结:
1.这里使用了宏定义来帮助实现数组创建和初始化,能够是代码更加灵活。例如:这里计算3*4的矩阵,还可以直接通过改变宏定义里面的值,计算其他大小的矩阵;
2.这里是利用键盘输入,给数组元素逐个赋值,注意必须有 ‘&’;
3.二维数组一般通过双重循环来改变行下标和列下标来对数组元素进行访问。
示例2:
转置矩阵
要求:将a矩阵(二维数组)的行和列互换并且存入b矩阵(二维数组)中。
代码如下:
//转置矩阵
#include
#define ROW 3
#define LIN 4
int main()
{
int a[ROW][LIN] = { 0 };
int b[LIN][ROW] = { 0 };
printf("矩阵A:\n");
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LIN; j++)
{
scanf("%d", &a[i][j]);
}
}
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LIN; j++)
{
b[j][i] = a[i][j];
}
}
printf("a矩阵转置后的b矩阵:\n");
for (int i = 0; i < LIN; i++)
{
for (int j = 0; j < ROW; j++)
{
printf("%-2d", b[i][j]);
}
printf("\n");
}
return 0;
}
运行结果:
示例3:
杨辉三角是 (a+b)n 展开后的各项系数。如:(a+b)4 展开后各项系数为:1,4,6,4,1。输出的杨辉三角如下图所示:
这杨辉三角的特点就是:第0列和主对角线的元素全为1,其他元素均为上一行的同列元素与前一列元素之和。
代码如下:
//输出杨辉三角
#include
#define N 6
int main()
{
int arr[N][N];
for (int i = 0; i < N; i++)
{
arr[i][0] = 1;
arr[i][i] = 1;
}
for (int i = 2; i < N; i++)
{
for (int j = 1; j < i; j++)
{
arr[i][j] = arr[i - 1][j - 1] + arr[i - 1][j];
}
}
for (int i = 0; i < N; i++)
{
for (int j = 0; j <= i; j++)
{
printf("%-4d", arr[i][j]);
}
printf("\n");
}
return 0;
}
运行结果:
示例4:
从键盘输入8个学生三门课程成绩,求每个学生各门课的平均分,并按平均分从高到低的顺序输出学生各门课的成绩和平均成绩。
算法分析:
(1)首先我们得创建一个二维数组来存放这些学生的成绩,行用来表示学生人数——一共8个学生;列用来表示学生课程门数——一共三门;
(2)然后录入学生成绩,录入成绩之后,先来计算每个学生的三门课平均分,然后创建一个数组存放平均分;
(3)这里就开始平均分高低的排序啦!
(4)将排序的结果输出,就 OK 啦!
代码如下:
//求学生平均分并且高到低排序
#include
#define ROW 8
#define LIN 3
int main()
{
double arr[ROW][LIN] = { 0 };//创建数组来存放学生成绩
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LIN; j++)
{
scanf("%lf", &arr[i][j]);//输入成绩
}
}
double aver[ROW] = { 0 };
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LIN; j++)
{
aver[i] += arr[i][j];
}
aver[i] /= 3.0;//计算每个学生的平均值
}
double temp1 = 0;
double temp2 = 0;
for (int i = 0; i < ROW-1; i++)
{
for (int j = 0; j < ROW - i - 1; j++)
{
if (aver[j] < aver[j + 1])//冒泡排序法来对成绩和平均值排序
{
for (int k = 0; k < LIN; k++)//针对二维数组,冒泡排序其实和一维数组类似,只是在冒泡循环内部再放一个控制二维数组列的循环
{
temp2 = arr[j][k];
arr[j][k] = arr[j + 1][k];
arr[j + 1][k] = temp2;
}
temp1 = aver[j];
aver[j] = aver[j + 1];
aver[j + 1] = temp1;
}
}
}
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LIN; j++)
{
printf("%.2lf ", arr[i][j]);
if (j == LIN - 1)
{
printf("%.2lf", aver[i]);
}
}
printf("\n");
}
return 0;
}
二维数组元素存放顺序是按行优先存放的。
二维数组在逻辑上是二维的,其下标在行列上都有变化,下标变量在数组中的位置处于二维平面之中。但实际上,它在硬件存储器上是按照连续的一推编址线性排列的。
在一维储存器中存放二维数组有两种方式:一是按行优先排列,同一行元素连续排列在一起,然后放完一行再放下一行,依次类推;二是按列优先排列,同一列元素连续排列在一起,然后放完一列再放下一列,依次类推。
而C 语言中规定二维数组是按行优先存放的。
可以将二维数组看成若干个一维数组组成。如下数组:
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
这是3行4列的一个二维数组,可以把他看成3个一维数组,分别是:a[0],a[1],a[2],每个数组拥有4个元素。如下图解:
通过一维数组,二维数组的定义和储存可以推广至三维数组、N维数组。N维数组定义中有N个方括号括起来的各维元素数,每一维元素数都是常量表达式。
多维数组在内存中的存放形式也有两种:一种是最右边的下标变化最快,即左边N-1个下标相同的元素连续存放在一起;另外一种是最左边的下标变化最快,右边N-1个下标相同的元素连续存放在一起。在C语言中,多维数组最右边下标变化最快,仍可以称为按行排列。
如下数组:
int c[3][2][2] = {1,2,3,4,5,6,7,8,9,10,11,12};
这个三维数组可以看成是c[0]、c[1]、c[2]的3个二维数组组成,而每一个二维数组又是由2个一维数组组成,每一个一维数组含有两个元素。如下图分析:
foever 这里给大家分享了自己对数组,其包括一维数组和二维数组以及其他维数的拓展的认识,希望能够给一些初学者带来一些帮助。如有不足之处或错误之处,还望大佬们评论批评指正哈!
下一期 forever 将继续为大家讲述数组后面的一类——字符数组哦!
谢谢观看哈!
再见啦!
以上代码均可运行,所用编译环境为 vs2019 ,运行时注意加上编译头文件#define _CRT_SECURE_NO_WARNINGS 1