第5 章数 组 学习了C语言的基本数据类型和程序流程控制结构后,很多问题都可以描述和解决 了。但是对于大规模的数据,尤其是相互间具有一定联系的数据,怎么表示和组织才能达 到高效呢? C语言的数组类型为同类型数据的组织提供了一种有效的形式。 为了更好地理解数组的作用,请考虑这样一个问题:在程序中如何存储和处理具有 n个整数的数列? 如果n很小,比如n等于3时,显然不成问题,简单地声明3个int变量 就可以了。如果n为1000,用int变量来表示这1000个数,就需要声明1000个int变量, 其烦琐程度可想而知。用什么方法来处理这1000个变量呢? 数组就是针对这样的问题 的,它是用于存储和处理大量同类型数据的一种构造型数据类型。数组是具有一定顺序 关系的同类型数据的集合,这些数据使用相同的名字(即数组名)和不同的下标进行区分, 我们称之为数组元素。将数组与循环结合起来,可以有效地处理大批量的数据,大大提高 了工作效率,十分方便。 字符串应用广泛,但C语言中没有专门的字符串类型,字符串是使用字符数组来存 放的。同时,C语言标准函数库中提供有专门的字符串处理函数,方便对字符串的处理。 本章介绍在C语言中如何定义和使用数组,包括一维数组,二维数组,以及字符串数 据的存储和处理。 学习目标: . 掌握一维数组的定义、引用和初始化。 . 掌握二维数组的定义、引用和初始化。 . 掌握字符数组的定义、引用和初始化。 . 掌握字符串的处理方法,熟悉字符串常用处理函数。 . 熟悉数组基本操作算法,能够正确使用数组解决一般应用问题。 5.1 一维数组 5.1.1 一维数组的定义和引用 1.一维数组的定义 在C语言中使用数组前必须先进行定义。一维数组的定义方式为: 第5 章 数组 类型声明符数组名[常量表达式]; 其中: (1)类型声明符是任一种基本数据类型或构造数据类型,即int、float、char等基本数 据类型,以及第9章中将介绍的结构体数据类型。从这里可以看出,数组是建立在其他数 据类型的基础之上的,因此数组是构造类型。 (2)数组名是用户定义的数组标识符,命名规则遵循标识符命名规则。对于数组元 素来说,它们具有一个共同的名字,即数组名。 (3)方括号中的常量表达式表示数组元素的个数,也称为数组的长度。例如: float b[10], c[20]; //定义实型数组b,有10 个元素;定义实型数组c,有20 个元素 char ch[20]; //定义字符数组ch,有20 个元素 对于数组定义,应注意以下几点: (1)数组的类型实际上是指数组元素的取值类型。对于同一个数组,其所有元素的 数据类型都是相同的。 (2)数组名不能与其他变量名相同。例如: int main() { int a; float a[10]; //错误,数组名和变量名相同 … return 0; } (3)方括号中常量表达式表示的是数组元素的个数,如a[5]表示数组a有5个元素。 但是其下标从0开始计算,因此这5个元素分别为a[0]、a[1]、a[2]、a[3]、a[4]。 (4)不能在方括号中用变量来表示数组元素的个数,但可以用符号常数或常量表达 式。例如: #define D 5 int main() { int a[3+5],b[4+D]; //合法的定义 … return 0; } 而下述定义方式是错误的: int main() { int n=10; 161 C 语言程序设计 int a[n]; //不合法的定义,因为n 为变量 … return 0; } 2.一维数组元素的存储 每个数组元素都占用内存中的一个存储单元,每个元素都是一个变量,可以像以前讲 图5.1 一维数组的存储形式 过的普通变量一样使用,只不过数组元素是通过数 组名和方括号中的下标来确定的。系统在内存中 为数组元素分配连续的存储单元。 例如,定义语句inta[15],声明了以下几个 问题: (1)数组名为a。 (2)数组元素的数据类型为int。 (3)数组元素的下标值从0开始。数组元素的 个数为15,分别是a[0]、a[1]、a[2]、…、a[13]、 a[14] (4)数组名a是数组存储区的首地址,即存放数 组第一个元素的地址。a等价于&a[0],因此数组名 是一个地址常量。不能对数组名进行赋值或运算。 在这个例子中,数据元素的存储形式如图5.1所示。 3.一维数组元素的引用 对数组元素的引用与对变量的引用类似,但与变量引用不同的是,只能对数组元素进 行引用,而不能一次引用整个数组。一维数组元素的引用格式如下: 数组名[下标] 其中: (1)下标可以是整型常量或整型常量表达式,如a[3]、a[3+2]。 (2)下标还可以是整型变量或整型变量表达式,如a[i]、a[i+j]、a[i++]。 (3)如果下标是表达式,首先要计算表达式,计算的最终结果为下标值。 (4)下标值从0开始,而不是从1开始。 (5)数组的引用下标不能越限,如用inta[15]定义的数组,引用时的下标不能超过或 等于15,即a[15]=10是错误的。 定义数组时使用的“数组名[常量表达式]”和引用数组元素时使用的“数组名[下标]” 形式相同,但含义不同。在数组定义的方括号中给出的是数组的长度,而在数组元素中的 下标是该元素在数组中的位置标识,下标取值范围为0到数组长度值减1。前者只能是 162 第5 章 数组 常量,后者可以是常量、变量或表达式。 【例5-1】 从键盘输入10个整数,求其中的最大数并输出。 【问题分析】 (1)定义一个一维数组,数组长度为10,用于存储从键盘输入的10个整数。 (2)求一组数的最大数:首先假定第一个数最大,把该数放到变量max中,然后依次 处理后面9个数,如果当前正在处理的这个数比max还大,则更改max的值为这个数。 将数组与for循环相结合,可以轻松完成此任务。 解决该问题的算法流程图如图5.2所示。 【程序代码】 #include <stdio.h> int main() { int a[10]; //定义整型数组a int i,max; //定义循环控制变量i 和存放最大数的变量max printf("Please enter ten integers:\n"); //输出屏幕提示语 for(i=0;i<=9;i++) //循环10 次 scanf("%d",&a[i]); //从键盘接收数据,并存放到数组元素a[i]中 for(i=0;i<=9;i++) / /循 环 10 次 printf("%d ",a[i]); //输出数组元素a[i]的值 max=a[0]; / /给 m ax 变量赋值,假定第一个数最大 for(i=1;i<=9;i++) / /循 环 9 次 if(max<a[i]) /*比较max 与数组中当前正在处理的数组元素的大小,将较大者赋给 max*/ max=a[i]; printf("\nThe max is %d\n",max); //输出求得的最大数 return 0; } 【运行结果】 程序运行结果如图5.3所示。 【代码解析】 (1)main()函数中的inta[10]是数组的定义语句,定义一个长度为10的整型数组, 用于存储从键盘输入的10个整数。 (2)main()函数中的第一个for循环,实现从键盘接收10个整数,并存放到数组 a中。 (3)main()函数中的第二个for循环,实现把10个整数输出到屏幕。 (4)main()函数中的第三个for循环,实现求10个整数中的最大数。在此段代码中,变 量max用来存放当前已比较过的各数中的最大数。开始时设max的值为a[0],然后将max 与a[1]比较,如果a[1]大于max,则max重新取值为a[1],下一次以max的新值与a[2]比 较,如果a[2]大于max,则max重新取值为a[2],此时max的值是a[0]、a[1]、a[2]中的最大 数。以此类推,经过9轮循环的比较,max最后的值就是10个数中的最大数。 163 图5.-1的流程图 2 例5 461 C 语言程序设计 第5 章 数组 图5.3 例5-1程序运行结果 (5)最后一条输出语句输出求得的最大数。 说明:此示例只是为了演示一维数组的定义与引用。在实际问题中,如果只是要求 找出一组数中的最大或者最小数,那么这组数据可以不保存,采用边读边求最大或者最小 数的方式,程序更简洁、高效,实现代码参见例4-7。 【例5-2】 有等差数列an =5×n+1,要求分别按正序和逆序输出该数列的前10项 到屏幕。n 为0~9的整数。 【问题分析】 (1)计算数列的第n项的值:通过示例中给出的数列通项公式计算即可。 (2)由于示例要求逆序输出数列的前10项,故需要一个长度为10的整型数组来存 储数列的前10项的值。 (3)数组与for循环相结合,完成数列前10项的计算、存储、正序和逆序输出。 【程序代码】 #include <stdio.h> int main() { int a[10]; //定义整型数组a int n; //定义循环控制变量n for(n=0;n<=9;n++) / /循环10 次,计算数列的前10 项,并按正序输出 { a[n]=5*n+1; / /计 算 数 列的第n 项的值,并赋给第n 个数组元素 printf("a[%d]=%d ",n,a[n]); //输出数列的第n 项的值 } printf("\n"); / /换 行 for(n=9;n>=0;n--) / /循环10 次,逆序输出数列的前10 项 { printf("a[%d]=%d ",n,a[n]); } printf("\n"); return 0; } 【运行结果】 程序运行结果如图5.4所示。 165 C 语言程序设计 图5.4 例5-2程序运行结果 【代码解析】 (1)mian函数中的语句inta[10],定义一个长度为10的整型数组a,用于存储数列 的前10项。 (2)main函数中的第一个10次的for循环,循环控制变量从0开始依次递增1,实现 求数列的前10项,数列的第n项存储到数组元素a[n]中,并正序输出。 (3)main函数中的第二个10次的for循环,循环控制变量从9开始依次递减1,实现 逆序输出数列的前10项。 5.1.2 一维数组的初始化 与一般变量的初始化一样,数组的初始化就是在定义数组的同时,给其数组元素赋初 值。数组初始化是在编译阶段进行的,这样将减少运行时间,提高效率。 一维数组初始化赋值的一般形式为: 类型声明符数组名[常量表达式]={数值1,数值2,…,数值n}; 其中赋值号右边大括号中的各数据值即为各数组元素的初值,各值之间用逗号间隔。 例如: int a[3]={0,1,2}; 相当于 a[0]=0;a[1]=1;a[2]=2; C语言对数组的初始化有以下几点规定: (1)可以只给部分数组元素赋初值。当大括号中值的个数少于数组元素个数时,只 为前面部分数组元素赋值。例如: int a[10]={0,1,2,3,4}; 相当于只为a[0]、a[1]、a[2]、a[3]、a[4]赋初值,而后5个元素自动赋为0值。 (2)只能给数组元素逐个赋值,不能给数组整体赋值。例如,给10个元素全部赋值 为1,只能写为: int a[10]={1,1,1,1,1,1,1,1,1,1}; 166 第5 章 数组 而不能写为: int a[10]=1; (3)如给全部元素赋值,则在数组声明中,可以不给出数组元素的个数。例如: int a[5]={1,2,3,4,5}; 可写为: int a[]={1,2,3,4,5}; (4)大括号中数值的个数多于数组元素的个数是语法错误。 5.1.3 一维数组应用举例 【例5-3】 参照例4-16中提出的兔子问题,应用数组计算2年后有多少对兔子? 【问题分析】 根据题意,以f[n]表示n个月以后兔子的总对数,其规律为f[n]=f[n-2]+f[n-1],如 此构成的数列如下: f[1]=1,f[2]=1,f[3]=2,f[4]=3,f[5]=5,…,f[n]=f[n-2]+f[n-1],… 即斐波那契数列。此问题转化为求斐波那契数列第24项的值,其算法流程图如图5.5 所示。 【程序代码】 #include <stdio.h> int main() { int n, f[25]; //定义整型变量n,整型数组f f[1]=f[2]=1; //为f[1]和f[2]赋初值1 for(n=3;n<=24;n++) //循环22 次 f[n]=f[n-1]+f[n-2]; //计算f[n]的值 for(n=1;n<=24;n++) //输出数列中所有项的值 { if((n-1)%4==0) //每输出4 个数后换行 printf("\n"); printf("%10d",f[n]); //输出数列中第n 项的值 } printf("\n"); return 0; } 【运行结果】 程序运行结果截图如图5.6所示。 说明:很多数列的问题都可以类似于上述的斐波纳契数列的计算方法,用数组来进 行存储和计算。 167 图5.-3的流程图 5 例5 图5.-3程序运行结果 6 例5 861 C 语言程序设计 第5 章 数组 【例5-4】 给定n 个任意数,按由小到大对其排序,并输出排序结果。 【问题分析】 这个问题是一组数的排序问题,排序方法有多种,这里采用冒泡排序法。 冒泡排序法的思路是,将相邻两个数比较,将小的数调到前头(或将大的数调到后 面)。比如有5个数,分别是7、6、10、4、2,依次将其放入数组a中。我们看一下冒泡排序 法的处理过程。 (1)第一趟(如图5.7所示),经过4次比较。 第1次:将第1个数a[0]和第2个数a[1]进行比较,将较大的数调到下面。也就是 说,若后面的数小,就将两数交换,否则不交换。这里把7和6对调位置,结果如图5.7(b) 所示。第 2次:将第2个数a[1]和第3个数a[2]进行比较,将较大的调到下面。这里是7 和10比较,这次比较不用对调这两个元素的位置,结果如图5.7(c)所示。 第3次:将第3个数a[2]和第4个数a[3]进行比较,将较大的调到下面。这里是10 和4比较后对调位置,结果如图5.7(d)所示。 第4次:将第4个数a[3]和第5个数a[4]进行比较,将较大的调到下面。这里是10 和2比较后对调位置,结果如图5.7(e)所示。 图5.7 冒泡排序的第一趟过程 如此进行共4次,结果得到6-7-4-2-10的顺序,最大的数10成为最下面的一个数。 最大的数“沉底”,最小的数2向上“浮起”一个位置(冒第一个泡)。 这4次处理过程都是类似的,都是“相邻两数比较,若后面的数小,则两数交换,否则 不交换”;所不同的是“比较的两个数,它们的位置不同”,先是a[0]和a[1]比较,再是a[1] 和a[2]比较,然后是a[2]和a[3]比较,最后是a[3]和a[4]比较。我们会发现一个规律, 每次比较后,往下移了一位,所以可以用一个变量i控制,每次都是a[i]和a[i+1]比较, 而每次比较完后i+1,总共比较4次,这样就可以用一个循环来实现,即: for(i=0;i<4;i++) if(a[i]>a[i+1]) { temp=a[i]; a[i]=a[i+1]; a[i+1]=temp; } 169