第五部分:数组
用我们之前学到的C语言语法,如果想要计算 30 个学生的平均成绩,我们需要定义 30 个 float 型变量,然后将它们加起来除以 30 就可以了。30 个变量的问题似乎不大,只需要定义 30 个变量(s1
、s2
、s3
……s30
)即可。换作是 1000 个变量怎么办呢?人们想了一个办法,就是用同一个名字代表具有相同属性的一群数据,再辅以下标来在它们彼此间区分他们。例如,S1
、S2
、S3
代表学生 1、学生 2、学生 3。一群具有同属性的数据在一起称为“数组”。数组是一组有序数据的集合。
数组中各数据的排列是有一定规律的,下标代表数据在数组中的序号。用一个数组名和一个下标的组合(如 a2
)来确定数组中唯一的元素,同样可以这样赋予数组实际意义。例如:S12
就可以代表第 12 位同学的成绩。数组中的每一个元素都属于同一个数据类型,不能把不是同一个类型的数据放在同一个数组中。
一、一维数组
1.一维数组的定义与引用
由于在编写程序的时候无法输入下标,所以在 C 语言中用数字加方括号表示下标。如下即是对一个一维数组的定义:
int a[10];
它表示定义了一个一维数组,数组名为 a
,这个数组有 10
个整形元素。定义一个一维数组的一般形式为:
类型标识符 数组名[常量表达式]
需要注意的是,按照 int a[10]
定义后这个数组中有十个元素,从 a[0]
开始,到 a[9]
结束,并没有 a[10]
这个元素。“常量表达式”可以为常量与符号常量,如 a[3 + 5]
,但不可以是 a[n]
,也就是说,不允许对数组做动态定义。例如:
int x;
scanf("%d", &x);
int a[x];
这种定义在 C 语言中是不允许的。
经过 int a[10]
的定义,内存中为数组 a
划分出一片存储空间,这片存储空间是连续的,可以存放 10 个整形元素。定义 10 个元素的数组的时候相当于定义了 10 个整形变量,显然比章首提到的方法方便。
当我们想要使用数组中的元素时,可以用:
数组名[下标]
的形式来引用数组中的任意一个元素。例如我们想要对 10 个元素依次进行赋值,然后按倒序输出:
#include<stdio.h>
int main()
{
int i, a[10];
for(i = 0; i < 10; i ++)
a[i] = i;
for(i = 9; i >= 0; i --)
printf("%d", a[i]);
printf("\n");
return 0;
}
2.一维数组的初始化
在定义变量的同时,对数组元素赋值,称为数组的初始化。对一维数组初始化的方法有以下几种:
a.定义数组时给全部元素赋值
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
此外,由于给全部元素赋值时长度已经确定,可以不指定数组长度。上面的代码等价于下面这句代码:
int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
使用这种方法定义数组时,需要格外注意的是数组名后面的一对方括号不能省略,方括号是数组的标识符。
b.定义数组时给部分元素赋值
int a[10] = {0, 1, 2, 3, 4};
这样的话,数组 a
前五个元素这样赋值,其余元素初值为 0
。
c.使数组元素全部为 0
用下面的方法可以将数组元素全部赋初值为0:
int a[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
或者是:
int a[10] = {0};
定义数组时应当指定数组长度并进行初始化,如果没有进行初始化,系统会自动将其初始化为 0
。如果是字符型数组则初始化为 '\0'
,如果是指针数组则初始化为 NULL
。
3.应用举例
将数组中的元素按顺序重新存放。
在这里介绍“起泡排序法”,这种方法的基本思路是,将相邻的两个数比较,将较小的数放在前面。例如有 6 个数字:9
、8
、5
、4
、2
、0
,要求按从小到大的顺序排好。最开始数字是这样排序的:
9 8 5 4 2 0
我们第一趟将最顶端的数字 9 依次与后面的数字比较,比 9 大的放在右面,比 9 小的就与9交换位置。第一次 ~ 第六次的比较过程如下:(下划线表示交换位置)
*9 8* 5 4 2 0
8 *9 5* 4 2 0
8 5 *9 4* 2 0
8 5 4 *9 2* 0
8 5 4 2 *9 0*
8 5 4 2 0 9
可以看到,经过 6 次比较,原本在最顶端的数字 9 已经沉底,第二趟我们进行如下过程的比较:
*8 5* 4 2 0 9
5 *8 4* 2 0 9
5 4 *8 2* 0 9
5 4 2 *8 0* 9
5 4 2 0 8 9
第二趟中,原本沉底的数字 9 是不动的,在 9 沉底后在顶端的数字 8 经过第二趟比较,也沉在了仅次于 9 的底端。以此来推,经过 5 趟比较,就可以将原本的 6 个数字按照升序排好。可以分析出来,当有 n 个数时,需要进行 n – 1 趟比较。上述算法我们称作“起泡排序法”,算法的程序如下:(假设有 10 个数据)
#include<stdio.h>
int main()
{
int a[10];
int i, j, t;
printf("Input 10 numbers:\n");
for(i = 0 ; i < 10 ; i ++)
scanf("%d" , &a[i]);
printf("\n");
//起泡排序开始
for(j = 0; j < 9; j ++)
for(i = 0; i < 9 – j; i ++)
{
t = a[i];
a[i] = a[i + 1];
a[i + 1] = t;
}
//起泡排序结束
printf("The sorted numbers are:\n");
for(i = 0 ;i < 10; i ++)
printf("%d\t" , a[i]);
printf("\n");
return 0;
}
二、二维数组与引申
当我们有一个表格,记录运动员各项比赛得分情况,横排是运动员姓名,竖排是项目名称,如下:
高老大 | 王二 | 张三 | 李四 | 赵五 | 钱六 | |
---|---|---|---|---|---|---|
100米跑 | 95 | 75 | 89 | 75 | 98 | 87 |
铅球 | 85 | 91 | 88 | 92 | 98 | 70 |
标枪 | 75 | 85 | 95 | 98 | 76 | 82 |
我们可以将这个表格的数据存放在二维数组中,例如我们可以用 S[1][2]
代表王二的铅球成绩,即 S[1][2] = 91
。二维数组也称为矩阵,这样的数组具有两个维度:行和列。我们可以用类似定义一维数组的方式来定义一个二维数组:
int a[3][3]
这样我们就定义了一个 3 x 3 的二维数组,写成矩阵形式如下:
a[0][0] a[0][1] a[0][2]
a[1][0] a[1][1] a[1][2]
a[2][0] a[2][1] a[2][2]
仍然需要注意的是,二维数组的两个维度标号都是从 0
开始的,这一点与一维数组相同。需要明确的概念是,二维数组写成矩阵形式是为了理解方便,但是在内存中,二维数组仍然是线性存储的。
对二维数组进行初始化时与一维数组有所不同,例如我们对上述的 3 x 3 数组进行初始化:
int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
也可以将所有元素写在一个花括号内:
int a[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
同样我们也可以对部分元素赋初值,这个时候其余元素为 0
:
int a[3][3] = {{1}, {2, 3}, {4}};
赋值结果是:
1 0 0
2 3 0
4 0 0
对二维数组进行赋初值时,要记得一个准则,那就是在一个花括号内的是同一行,隔一个花括号就是下一行。
以上就是二维数组的定义与调用方式,下面举一个例子来讲解二维数组的综合运用。例如我们需要将一个二维数组行列互换,存在另一个二维数组中。首先我们分析问题,这个问题的中心就在于将二维数组 a
的元素 a[i][j]
存放在另外一个二维数组 b[j][i]
中。假设有二维数组 a[2][3]
,写程序如下:
#include<stdio.h>
int main()
{
int i, j;
int a[2][3] = {0};
for(i = 0; i <= 1; i ++)
for(j = 0; j <=2; j ++)
scanf("%d", &a[i][j]);
int b[3][2] = {0};
printf("a:\n");
for(i = 0; i <= 1; i ++)
{
for(j = 0; j <=2; j ++)
{
printf("%d", a[i][j]);
b[j][i] = a[i][j];
}
printf("\n");
}
printf("b:\n");
for(i = 0; i <= 2; i ++)
{
for(j = 0; j <= 1; j ++)
printf("%d", b[i][j]);
printf("\n");
}
return 0;
}
二维数组的定义与调用方法可以引申为多维数组,只要有需要,数组可以有三维、四维……
三、字符数组
C 语言中不存在字符串类型,字符串是存放在字符数组中。用来存放字符的数组是字符数组,我们可以像定义一维数组那样来定义一个字符数组:
char a[10];
这样我们定义了一个可以存放十个字符的数组a。由于字符是以整数形式存储,因此我们也可以用整形的数组存放字符,例如:
int a[10];
a[0] = 'a';
这样是合法的,但浪费存储空间。因为一个 int 型的存储空间是4字节,但一个字符只占用 1 字节,剩下的 3 字节是浪费的。
对字符数组进行初始化的方法也有很多种,其中最容易理解的方法是:
char a[10] = {'W', 'E', 'L', 'C', 'O', 'M', 'E'};
这个时候,数组 a
的存放状态如下:
W E L C O M E \0 \0 \0
需要提到的是,在字符数组中,最后一位一定以空字符('\0'
)结束,作为字符数组结束的标志。除此之外,我们还有另外一种初始化字符数组的方法:
char a[] = "WELCOME";
用这种方法定义字符数组,是直接把字符串赋值给数组,字符串用双撇号括在一起。当用 printf
输出字符数组时,检查到第一个 '\0'
时即停止输出。当我们对字符数组进行输入输出时,可以对数组的某个元素进行输入输出,也可以对整个数组进行输出。例如:
char a[] = "BOY";
printf("%s" , a); //%s意为以字符串形式输出/输入
printf("%c" , a[0]); //用%c输出/输入单个元素
此外,C 语言还提供了一些字符串处理函数:
函数名与调用方式 | 作用 |
---|---|
puts(字符数组); | 将一个字符串输出到屏幕上 |
gets(字符数组); | 从输入终端获取一个字符串 |
strcat(字符数组1,字符数组2); | 将字符串2接在字符串1的后面 |
strcpy(字符数组1,字符串2); | 将字符串2赋值到字符串1里 |
strcmp(字符串1,字符串2); | 字符串比较大小 |
strlen(字符串); | 返回字符串的长度 |
srtlwr(字符串); strupr(字符串); | 将字符串转换为大写/小写 |
当使用这些函数时,要在预处理部分将 string.h
头文件包含在程序中。
四、练习题
函数部分的习题并在这里。
写一个函数可以将两个字符串连接在一起,不用
stract
函数。求一个 3 x 3 整形矩阵对角线数字之和。
有一篇英文文章,共 3 行,每行 80 个字符。要求统计出其中英文大写字母、小写字母、数字、空格以及其他字符的个数。
写一个函数,可以给 3 x 3 的数组转置。
写一个函数,可以将一个字符串倒序存放。