···························································· 第5 章 chapter5 数 组 本章重点 . 一维数组的定义和引用,顺序排序,选择排序,求一组数中的极值及其位置,逆序 存放一组数等常用算法。 . 二维数组的定义、引用和输出,二维数组中元素的表示,二维数组的填充、方阵的 转置等常用算法。 . 字符数组和字符串处理函数,不使用字符串处理函数求字符串的有效长度、复制 字符串、连接两个字符串、删除字符串中的某种字符等常用算法。 5.1 数组的概念 数组通常用来存放成批的类型相同的数据,数组属于构造数据类型。 5.1.1 引例 【例5.1】 输入30个学生的成绩,求高于平均成绩的人数。 【分析】 按照简单变量和循环结构的方法,求平均成绩的程序如下: #include "stdio.h" int main() { int i,x; float s=0,ave; for(i=1;i<=30;i++) { scanf("%d",&x); s+=x; } ave=s/30; printf("aver=%f\n",ave); } 【说明】 若按这个程序的写法统计高于平均分的人数,则无法实现,因为存放学生成绩的变 第◆5 章 数组6 9 量一直使用x,而x是简单变量,只能存放一个学生的成绩,在循环体内输入一个学生的 成绩后,就会把前一个学生的成绩覆盖掉。因此,若求完平均成绩后再求高于平均成绩 的人数,则必须重新输入这30个学生的成绩,不但带来了重复工作,还有可能出现两次 输入的成绩不一致的情况,导致统计结果错误。 解决以上问题的方法是:30个学生的成绩必须有30个独立的存放空间,而定义30 个简单变量的方法会使程序难以处理和推广,所以正确的方法是定义一个数组,使其包 含30个元素,用来存放30个学生的成绩。定义数组时需要指明数组的名字、元素个数 和数据类型。一个数组中的所有元素必须都是同一数据类型。 那么,表示30个学生的成绩的数组可以定义如下: int x[30]; 这里的x表示数组名,方括号内的30表示数组元素的个数,下标为从0到29,即 x[0],x[1],…,x[29],int表示数组中的所有元素都是整型。求高于平均分的人数的程 序如下: #include "stdio.h" int main() { int i,x[30],k=0; float s=0,ave; for(i=0;i<30;i++) { scanf("%d",&x[i]); s+=x[i]; } ave=s/30; printf("ave=%f\n",ave); for(i=0;i<30;i++) if(x[i]>ave) k++; printf("num=%d\n",k); } 运行结果: 67 68 89 90 75 83 71 56 78 96 49 88 66 65 91 90 56 77 79 91 85 74 81 59 60 69 68 90 98 768 ave=76.166664 num=15 对于类型相同且数据量较大的数据,采用数组处理更加便捷。而变量和数组如同生 活中的个体与集体的关系,小到个人,大到国家,我们每个人都是集体中的一分子,只有 每个人都积极发挥自己的能力,国家才能爆发出无穷的力量,任何一个集体的成功都离 不开每个个体的奉献。 5.1.2 数组的概念 数组是相同类型的数据组成的序列,用一个共同的名字表示,数组中数的顺序可以 7 0 ◆C 语言程序设计教程 由它的下标表示。有一个下标的数组称为一维数组,有两个下标的数组称为二维数组;C 语言用字符数组表示字符串。 数组在内存中占据一片连续的存储单元。若有定义 int a[10]; 则表示数组a中包括10个元素,分别为a[0]~a[9]。这10个元素在内存中是连续存放 的,如图5.1所示,只要找到a[0],后面的元素地址就可以计算出来。C语言中,数组名代 表数组的首地址,即a[0]的地址。 图5.1 数组a的存储分配 C语言中,数组的下标从0开始,例如定义inta[10]时,最大可用下标为9,C语言编 译器不对下标越界进行检查,因此程序员必须自己保证下标引用的正确。 5.2 一维数组 5.2.1 一维数组的定义和引用 1.一维数组的定义 数组必须先定义、后使用,定义一维数组的一般形式为 类型标识符 数组名[数组大小] 例如: int x[100],z[10]; float a[20]; 一般形式中,“[]”内的“数组大小”表示数组中元素的个数,也称数组长度,它必须是 整型常量表达式,不可以包含变量。例如,“intn=10;inta[n];”是不合法的。 2.一维数组元素的引用 一维数组元素引用的一般形式为 数组名[下标] 此处“[]”内的“下标”必须是整型表达式,可以只是一个常量、变量,也可以是表达 式。例如a[1]、a[i]、a[9-i](i为整型)都是合法的数组元素引用,数组元素称为下标 变量。 第◆5 章 数组7 1 5.2.2 一维数组元素的赋值 1.初始化 数组在定义之后,若不给其赋值,则其值是无法预料的不确定值,可以使数组在程序 运行之前初始化,即在编译期间使之得到初值。 对数组元素的初始化可以用以下方法实现。 (1)在定义数组时,对数组元素赋初值。 例如: int a[10]={0,1,2,3,4,5,6,7,8,9}; (2)只给一部分元素赋值。 例如: int a[10]={0,1,2,3,4}; 表示只给前5个元素赋初值,后5个元素自动赋0值。 (3)若对static数组不赋初值,则系统会对所有元素自动赋0值。 如果想使数组a中的全部元素值为0,则可以这样定义数组: “staticint a[5];”等价于“inta[5]={0};”。 (4)在对全部数组元素赋初值时,可以不指定数组长度。 例如: int a[]={0,1,2,3,4,5,6,7,8,9}; 和下面的写法是等价的: int a[10]={0,1,2,3,4,5,6,7,8,9}; 2.数组元素的输入 数组元素的处理是和循环分不开的,输入和输出都需要结合循环结构进行。如 例5.1中的第4行和第5行就是用循环输入数组中的所有元素的。 3.数组元素的赋值 如果数组中待赋的数组元素的值是有规律的,那么可以使用循环语句直接给数组元 素赋值,不必一个一个地输入。 【例5.2】 数组元素的赋值。 程序如下: #include "stdio.h" int main() { int i,a[10]; 7 2 ◆C 语言程序设计教程 for(i=0;i<10;i++) a[i]=i+1; for(i=0;i<10;i++) printf("%5d",a[i]); } 运行结果: 1 2 3 4 5 6 7 8 9 10 4.产生随机数 如果不要求数组中的数是有规律的,则可以用随机函数产生一组随机数并赋值给数 组中的元素,产生随机数的函数为rand,包含在stdlib.h头文件中。使用时,需要在主函 数之前使用文件包含命令#include"stdlib.h"或#include<stdlib.h>,该函数的一般形 式为rand()。 5.2.3 一维数组常用算法举例 【例5.3】 求10个数中的最大值和最小值。 【分析】 求极值的算法在第4章的循环部分已经介绍过,那时是使用简单变量完成 的,现在用数组完成,请读者体会一下使用数组的方便之处。 程序如下: #include "stdio.h" int main() { int i,a[10],min,max; for(i=0;i<=9;i++) scanf("%d",&a[i]); max=min=a[0]; for(i=1;i<10;i++) { if(a[i]<min) min=a[i]; if(a[i]>max) max=a[i]; } printf("max=%d,min=%d\n", max,min); } 运行结果: 34 56 78 31 46 79 12 34 57 22 max=79,min=12 【例5.4】 求10个数中的最小值及其位置,并将其换到第1个数的位置。 【分析】 当待处理的数据涉及顺序、位置等关系时,用数组存放数据比较方便。找 最小值及其位置的算法如下,对应的结构化流程图如图5.2所示。 (1)定义一个包含10个整型元素的一维数组,即inta[10];,然后输入10个数。 第◆5 章 数组7 3 图5.2 求最小值并将其换 到第1个数的位置 (2)找最小值及其位置。 ① 用k表示最小值的下标(位置),则a[k]就表示最小 值,设k的初值为0,即首先假设a[0]最小。 ② 找最小值:数组中从a[1]到a[9]的每个数都和最小 值a[k]比较,如果有某个a[i]<a[k],则表示a[i]为当前最 小值,需要将i的值赋给k。循环完成之后,a[k]就是找到的 最小值,k表示最小值的位置。 (3)将a[k]和a[0]互换。 程序如下: #include "stdio.h" int main() { int i,t,a[10],k; for(i=0;i<=9;i++) scanf("%d",&a[i]); k=0; for(i=1;i<10;i++) if(a[i]<a[k]) k=i; if(k!=0) /*如果k 为0 就不用换了*/ {t=a[0]; a[0]=a[k]; a[k]=t;} printf("min number is:%d\n",a[0]); printf("the position is:%d\n", k); } 【说明】 这个程序完成的是从10个数中找到最小值, 然后将其换到a[0]的位置,按照同样的做法,可以从a[1]到 a[9]这9个数中找到最小值并将其换到a[1]的位置,再从 a[2]到a[9]中找到最小值并将其换到a[2]的位置,以此类 推,最后从a[8]到a[9]中找到最小值换到a[8]的位置,剩下的a[9]就是这10个数中的 最大值。这样做完之后,就完成了将10个数按照由小到大的顺序排序。这种排序的方 法称为选择排序法。简单地说,选择排序法就是重复9次求最小值并将其换到相应位置 的操作。对n个数进行排序的算法描述如下。 (1)产生数组。 (2)对i=0,1,…,n-1做 ① k=i; ② 对j=i+1,…,n做 如果a[j]<a[k],则k=j; ③ 交换a[k]和a[i]。 【例5.5】 将一维数组中的n(n≤50)个数按逆序存放。 【分析】 当处理的数据个数n不确定时,可以先定义一个足够大的数组,然后输入 n,最后输入n个数。逆序存放数组可以采用图5.3所示的方法。 7 4 ◆C 语言程序设计教程 图5.3 逆序存放数组的方法 当n=7时,a[0]和a[6]交换,a[1]和a[5]交换,a[2]和a[4],a[3]不变或自己和自己 交换。进行循环时,i从0循环到(n-1)/2,a[i]和a[n-1-i]交换。 程序如下: #include "stdio.h" int main() { int a[50],t,n,i; scanf("%d",&n); /*输入元素个数n*/ for (i=0;i<n;i++) { scanf("%d",&a[i]); /*输入n 个数*/ printf("%5d",a[i]);} printf("\n"); for (i=0;i<=(n-1)/2;i++) { t=a[i]; a[i]=a[n-1-i]; a[n-1-i]=t; } for(i=0;i<n;i++) printf("%5d",a[i]); } 运行结果: 7 1 2 3 4 5 6 7 1 2 3 4 5 6 7 7 6 5 4 3 2 1 【例5.6】 用二分查找法查找一个数是否在一个有序数组中。 【分析】 二分查找法也称折半查找法,用来在一组数中查找是否存在某个已知的 数,前提条件是这组数必须是有序的,即这组数是按升序或降序排序的。设数组a中的n 个数是按升序排序的,查找key是否在数组a中的算法如下。 (1)设开始查找的范围为整个数组,则下标为从0到n-1,用low 和high表示查找 范围的端点,即low=0,high=n-1。 (2)取查找范围的中点mid=(low+high)/2。 (3)将待查找的数key和中间的数a[mid]进行比较,如果key=a[mid],则表示找到 了,输出key在数组中的位置mid,结束循环;如果key<a[mid],则表示key一定在数组 前半部分,即可把查找范围缩小一半,可将查找范围的右端点变为中间元素的前面一个 第◆5 章 数组7 5 元素的位置,即high=mid-1。如果key>a[mid],则表示key一定在数组的后半部分, 可将low变为mid+1,然后在新的范围内用同样的方法继续查找,直到low>high为止。 如果待查找的数不在数组中,则输出没有找到的信息。流程图如图5.4所示。 图5.4 二分查找法流程图 程序如下: #include "stdio.h" int main() { int n=12,a[]={1,3,5,7,9,12,15,19,24,27,38,66},key,mid,low,high; printf("input the number:\n"); scanf("%d",&key); low=0,high=n-1; while(low<=high) { mid=(low+high)/2; if(key==a[mid]) { printf("%d\n",mid); break; } 7 6 ◆C 语言程序设计教程 else if(key<a[mid]) high=mid-1; else low=mid+1; } if(low>high) printf("not found\n"); } 运行结果: input the number: 35 not found input the number: 27 9 使用一维数组还可以处理很多事情,例如在成绩处理中求全班学生的平均分、最高 分、最低分,排名次,查找是否存在某分数及某人,插入学生成绩,删除学生成绩等。如果 需要处理一个学生的4门课的成绩,则可以定义一个包含4个元素的一维数组,而要想 表示30个学生的4门课的成绩,就可以使用二维数组。 定义: int a[30][4]; 这里的a[i]表示第i个学生,a[i][0]、a[i][1]、a[i][2]和a[i][3]分别表示第i个学 生的4门课的成绩。 图5.5 二维数组的存储顺序示意 5.3 二维数组 5.3.1 二维数组的定义 1.二维数组的形式 二维数组的一般形式为 类型标识符 数组名[整型常量表达式][整型常量表达式] “[]”内的“整型常量表达式”表示数组每维的大 小或每维的元素个数。 例如,定义“inta[3][4]”,表示定义数组a包含3 行4列,共12个元素,这12个元素在内存中占据一 片连续的存储单元,存放的顺序是先行后列,即先放 第0行,再放第1行,最后放第2行,如图5.5所示。 第◆5 章 数组7 7 图5.5中的每行都可以看成一个一维数组,3个一维数组的名字分别为a[0]、a[1]、 a[2],每个一维数组中包含4个元素,即二维数组可以看成特殊的一维数组,特殊之处在 于每个数组元素还是一个一维数组。 2.二维数组的引用 二维数组的引用形式为 数组名[下标][下标] 注意:下标可以是整型表达式,但应在已定义的数组范围内,不能整体引用数组,只 能一个元素一个元素地引用。 3.多维数组的定义 多维数组的定义方式与二维数组类似。以下定义了两个三维数组: int x[3][4][2]; float y[4][1][3]; 5.3.2 二维数组的初始化 (1)分行给二维数组赋初值。 例如: int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; (2)可以将所有数据写在花括号内,按数组排列的顺序为各元素赋初值。 例如: int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};赋值的结果和1 中相同 (3)可以为部分元素赋初值,对于没有赋值的元素,系统自动赋0值。 例如: int a[3][4]={{1},{5},{9}}; int a[3][4]={{1},{0,6},{0,0,11}}; int a[3][4]={{1},{5,6}}; (4)如果为全部元素赋初值,则定义数组时可以不指定第一维的长度,但不能省略第 二维的长度。 例如: int a[3][3]={1,0,3,4,0,0,0,0,9} int a[ ][3]={1,0,3,4,0,0,0,0,9} int a[ ][3]={{1,0,3},{4},{0,0,9}}