第5章用数组处理批量数据 数组是一组具有相同数据类型的变量的集合,是一种简单的构造数据类型。在一个数组中,构成该数组的成员称为数组元素,数组中的每个元素都属于同一个数据类型。数组有两个基本要素: 数据的类型和数据的位置。其中,数据的类型可以是前面介绍的基本数据类型(整型、实型、字符型),也可以是构造数据类型,数据的位置就是数据在数组中的相对位置,称之为下标。用一个统一的数组名和下标唯一地确定数组中的元素,通过数组的下标实现对数组元素的访问。由于数组元素可以看作是单个变量,所以对多个同类型变量的操作都可以推广到用数组操作。根据数组元素的数据类型,数组又可分为数值型数组(整型数组和实型数组)、字符数组、指针数组、结构型数组等。 在程序设计中,数组是一种十分有用的数据结构,可以将大量同类型的数据放到一起批量处理,它能够处理更复杂的数据,使数据的管理更加方便,许多问题如果不用数组,几乎难以解决。 为方便读者了解、学习本章内容,绘制本章思维导图,如图51所示。 图51“用数组处理批量数据”思维导图 5.1一维数组 一维数组是数组中最简单的,它的元素只需要用数组名加一个下标,就能唯一地确定。而有的数组,其元素要指定两个下标甚至多个下标,才能确定,它们是二维数组和多维数组。熟练掌握一维数组后,对二维或多维数组,很容易举一反三,迎刃而解。 5.1.1知识点介绍 1. 一维数组的定义 当数组中每个元素都只带一个下标时,称这样的数组为一维数组。在C语言中,数组必须“先定义,后使用”。定义好一个一维数组后,C语言系统就会在内存中为该数组开辟一片连续的存储空间。一维数组的定义形式见表51。 表51一维数组的定义形式及说明 类型说明符 数组名[整型常量或整型常量表达式]; 说明 示例 inta[10]; 类型说明符 数组中所有元素的类型,如int、 float、char等 int为类型说明符 数组名 遵循C语言标识符命名规则 a为数组名 方括号中的整型常量或表达式 数组中所含元素的个数,不能是变量,只能是整型常量或表达式 10为数组的长度,a数组中共含有10个元素,即a[0],a[1],a[2],…,a[9],下标从0开始,到9结束 2. 一维数组的引用 每一个数组元素就是一个变量,所以,在程序中可以把数组中的每个元素当普通的变量来使用,即所谓的数组元素的引用。C语言规定不能一次引用整个数组,只能使用循环逐个引用数组元素。引用形式为: 数组名[下标]。 下标表示对应数组元素在数组中的顺序号,必须是整型常量、整型变量或整型表达式。 C语言规定,每个数组的第一个元素的下标从0开始,称为下标下界,最后一个元素的下标为数组元素的个数减1,称为下标的上界,对数组元素引用时不能超过数组的界限,C语言编译系统不对越界进行检查,因此在编写程序中,保证数组下标不越界是十分重要的。 如: int i,j,a[100]; 则: a[3], a[i], a[i+j*2]均为合法的数组元素引用,而a[3.5],a[100]都是不合法的数组元素引用。 在程序设计中,由于数组元素排列的规律性,通常可以通过改变其下标值,用循环的方法对数组元素进行操作。 3. 一维数组初始化 当系统为所定义的数组在内存中开辟一片连续的存储单元时,这些存储单元中并没有确定的值。可以采用以下形式,在定义数组时为所定义数组的各元素赋初值,即初始化。一维数组初始化的形式见表52。 表52一维数组的初始化 形式 示例 说明 对数组元素全部赋初值 int a[5]={1,2,3,4,5}; 经过初始化后,数组各元素值: a[0]=1,a[1]=2,a[2]=3,a[3]=4,a[4]=5 部分元素初始化 int a[5]={1,2,3}; 未赋值的部分元素值为0,即: a[0]=1,a[1]=2,a[2]=3,a[3]=0,a[4]=0 使数组中全部元素值为0 int a[5]={0}; 初始化后,a数组中5个元素值均为0,即: a[0]=0,a[1]=0,a[2]=0,a[3]=0,a[4]=0 在对全部元素赋初值时,可以不指定数组长度 int a[5]={1,2,3,4,5};等价于 int a[ ]={1,2,3,4,5}; 系统会根据所赋初值的个数确定数组的长度 一维数组除了上述初始化给数组赋值外,还可以通过循环语句实现动态输入。例如: int a[10],i; for(i=0; i<10; i++) scanf("%d",&a[i]); 运行时,用户可以从键盘输入10个数。数之间用空格或回车键隔开,如: 1 2 3 4 5 6 7 8 9 10。 不能用一个语句一次给整个数组赋值,下面的写法都是错误的: scanf("%d",&a); scanf("%d",&a[10]); 5.1.2实验部分 1. 实验目的 (1) 熟练掌握一维数组的定义和数组元素的引用方法。 (2) 熟练掌握一维数组的赋值以及输入输出的方法。 (3) 熟练掌握一维数组的相关算法(特别是排序算法和查找算法)。 2. 实验内容 (1) 程序填空一 请补充main函数,函数的功能是把一维数组中的元素逆置。结果仍保持在原数组中。 1#include 2#defineN 10 3int main() 4{ 5int a[N],i,j,t; 6printf("Input array:\n"); 7/****************FILL*****************/ 8; 9while(i<=9) 10{ 11scanf("%d",&a[i]); 12/****************FILL*****************/ 13; 14} 15printf("\nThe original array:\n"); 16for(i=0;i 2#define N 10 3int main() 4{ 5int a[N],m,i,flag=0; 6printf("\n Input m :"); 7scanf("%d",&m); 8printf("Input array: \n"); 9for(i=0;i 2int fun(float x[],int n)/*求平均分并统计高于平均分的个数*/ 3{ 4int j,c=0; 5float ave=x[0]; 6/****************ERROR*****************/ 7for(j=0;jave) 15c++; 16return c; 17} 18int main() 19{ 20float x[100]={70.5,82,64.5,95.5,78.5,65.45}; 21printf("高于平均分的人数: %d\n",fun(x,6)); 22return 0; 23} 【实验提示】 本题可以拆分成两道简单的C语言题目。 ① 求出n个数的平均值。可通过循环语句对x数组元素进行累加,求出n个同学的总分,再除以n即可求出平均分。 ② 将大于平均值的数字个数统计出来。可利用循环和条件选择语句对x数组进行遍历,若数组中元素值大于平均值,则计数器累计。 ③ 程序中fun函数功能是对数组中n个分数求平均值并输出,并且统计出高于平均分的人数放入变量c中,并将c返回给main函数。main函数中fun(x,5)是调用fun函数,此时,实参数组x把首地址传递给形参数组x,实参6将值6传递给形参n,从而对x数组中的6个数求平均。 (4) 程序改错二 N个已排好序的整数数列已放在一个数组中,给定下列程序中,fun函数的功能是利用折半查找的算法查找整数m在数组中的位置。若找到该数,返回其下标值,反之,则返回-1。请改正程序中的错误。 注意: 不要改动main函数和其他函数中的任何内容,不得增行或删行,不得更改程序的结构。 1#include 2#define N 10 3int fun(int a[], int m) 4{ 5int low=0,high=N-1,mid; 6while(low<=high) 7{ 8mid=(low+high)/2; 9if(m=a[mid]) 15/****************ERROR*****************/ 16low=mid-1; 17else 18return(mid); 19} 20return(-1); 21} 22int main() 23{ 24int i,a[N]={-3,4,7,9,13,24,67,89,100,180},k,m; 25printf("Array a :"); 26for(i=0;i=0) 32printf("m=%d,index=%d\n",m,k); 33else 34printf("Not be found!\n"); 35return 0; 36} 【实验提示】 ① 折半查找算法,将数列按有序化(递增或递减)排列,当数列有序排列时,折半查找比顺序查找的平均查找速度要快得多,折半查找也称为对分查找。其基本思想是首先选取位于数组中间的元素,将其与查找键进行比较,如果它们的值相等,则查找键被找到,返回数组中间元素的下标; 否则,将查找的区间缩小为原来区间的一半,即在一半的数组元素中继续查找。 ② 本题中,每次查找前先确定数组中查找范围的上、下界: low(头位置下标)和high(尾位置下标)(lowhigh,查找结束。 ③ fun函数是在a数组中用折半查找法查找值m是否存在。若m存在,则返回其下标mid; 若m不存在,返回-1。main函数通过调用fun函数,根据得到的返回值来确定是否找到。 ④ 比较顺序查找和折半查找两种查找算法,分析各有什么特点。 (5) 程序设计一 请写一fun函数,该函数的功能是把数组a中的数按从大到小的顺序排列(用冒泡排序法)。数组的值从main函数中输入,排序结果也在main函数中输出。 部分源程序已经给出,请勿改动main函数和其他函数中的任何内容,仅在fun函数中的空白处填入编写的语句。 1#include 2#define N 10 3void fun(int a[], int n)//此函数用于冒泡法排序 4{ 5int i,j,t; 6for(i=1;i<=n-1;i++) 7for(j=0;j 2void fun(int a[11], int y) 3{ 4int i=0,j; 5if(y>a[9]) 6a[10]=y; 7else 8/*****************BEGIN********************/ 9{ 10 11} 12/*******************END********************/ 13} 14int main() 15{ 16int a[11]={3,4,6,9,13,16,19,24,35,88}; 17int i,m; 18printf("\nInput a number: "); 19scanf("%d", &m); 20fun(a,m); 21for(i=0;i<=10;i++) 22printf("%d,", a[i]); 23return 0; 24} 【实验提示】 ① 本题关键点是要找出待插新数在有序数组中要插入的位置。首先确定该数是否应插在有序数组的最后。若是,就将该数插入到数组末尾; 否则,将该数与数组每个元素依次比较,确定要插入的位置,再想办法空出该位置(从此位置开始依次向后移一个位置),继而将新数插入此处。简言之,找出位置,空出位置,插入进去。 ② 思考: 为什么程序中定义数组的长度为11而不是10? 5.1.3练习与思考 1. 单选题 (1) 若要定义一个具有5个元素的整型数组,以下错误的定义语句是()。 A. int a[5]={0};B. int b[]={0,0,0,0,0}; C. int c[2+3];D. int i=5, d[i]; (2) 若有说明: int a[10];则对a数组元素的正确引用是()。 A. a[10]B. a[3.5]C. a[5]D. a[5.0] (3) 以下程序运行后的输出结果是()。 #include int main() {int a[5]={1,2,3,4,5},b[5]={0,2,1,3,0},s=0,i; for(i=0;i<5;i++)s=s+a[b[i]]; printf("%d\n",s); return 0; } A. 6 B. 10C. 11D. 15 (4) 若有int a[10]={6,7,8,9,10};则对该说明语句正确的理解是()。 A. 将5个初值依次赋给a[1]至a[5] B. 将5个初值依次赋给a[0]至a[4] C. 将5个初值依次赋给a[0]至a[4] D. 因为数组长度与初值的个数不相同,所以此语句不正确 (5) 以下能对一维数组a进行正确初始化的语句是()。 A. int a[10]=(0,0,0,0,0);B. inta[10]={}; C. inta[]={0};D. int a[10]={10*1}; (6) 以下程序段运行的结果是()。 inta[10]={1,2,3,4,5,6,7,8,9,10}, i, t; for(i=0;i<5/2;i++) { t=a[i]; a[i]=a[5-i-1]; a[5-i-1]=t; } for(i=2;i<8;i++) printf("%d",a[i]); A. 345678B. 876543C. 1098765D. 321678 (7) 下列选项中,能正确定义数组的语句是()。 A. int a[0..2008];B. int a[ ]; C. int N=2008,int a[N]; D. #define N 2008 int a[N]; (8) 若有int a[10];能给数组a的所有元素分别赋值为1,2,3,…,10的语句是()。 A. for(i=1;i<11;i++)a[i]=i; B. for(i=1;i<11;i++)a[i-1]=i; C. for(i=1;i<11;i++)a[i+1]=i; D. for(i=1;i<11;i++)a[0]=1; (9) 下面程序段的输出结果是()。 int a[]={2,3,5,4},i; for(i=0;i<4;i++) switch(i%2) {case 0:switch(a[i]%2) {case 0:a[i]++;break; case 1:a[i]--; } break; case 1:a[i]=0; } for(i=0;i<4;i++) printf("%d",a[i]); A. 3344B. 2050C. 3040D. 0304 (10) 以下对一维数组赋初值,正确的是()。 A. int arr[5]; arr={1,2,3,4,5};B. int arr[5]={1,2,3,4,5,6}; C. int arr[5]={0,0,2};D. int arr[5]=0,1,2,3,4,5; 2. 填空题 (1) 数组是一批具有数据类型的变量的集合。 (2) C语言程序在执行过程中,不检查数组下标是否。 (3) 若有下列数组说明int a[12],则数组元素最大和最小的下标分别是和。 (4) 假设int类型变量占用两字节,若定义int a[15],则数组a占用的内存字节数是。 (5) 设int b[]={1,2,3,4},y,i=0;则执行语句y=a[i]++;之后,变量y的值为。 (6) C语言的数组名是一个常量,不可以对它进行加、减和赋值等运算。 (7) 数组在内存中占据一片连续的存储空间,由代表它的首地址。 (8) 下面程序段的输出结果是。 int a[]={0,0,0,0,0,0},i; for(i=1;i<=4;i++) { a[i]=a[i-1]*2+1; printf("%d ",a[i]); } (9) 以下程序段可以求出所有水仙花数。(所谓水仙花数是指一个3位数正整数,其各位数字的立方和等于该正整数。) int x,y,z,a[8],m,i=0; for(;m++) { x=m/100; y=; z=m%10; if(x*100+y*10+z==x*x*x+y*y*y+z*z*z) {;i++;} } for(x=0;x 2int main() 3{ 4int a[10],i,max,min,maxpos,minpos; 5for(i=0;i<10;i++) 6scanf("%d",&a[i]); 7max=min=a[0]; 8/****************FILL*****************/ 9maxpos=minpos=; 10for(i=1;i<10;i++) 11{ 12/****************FILL*****************/ 13if() 14{ 15max=a[i]; 16/****************FILL*****************/ 17maxpos=; 18} 19else 20/****************FILL*****************/ 21if() 22{ 23min=a[i]; 24/****************FILL*****************/ 25minpos=; 26} 27} 28printf("max=%d,pos=%d\n",max,maxpos); 29printf("min=%d,pos=%d\n",min,minpos); 30return 0; 31} 【实验提示】 (1) 一组数中找出最大值或最小值,即求最值,常采用“打擂台”的方法,程序中的变量max代表存放最大值的“擂台”,min表示存放最小值的“擂台”。程序中第7行“擂台”上先存放一个数,然后通过循环,数组中的每一个数依次与“擂台”上的数比较,如果比它大(小),就替代它成为“擂主”,同时,记下它所在的下标。比较完毕,“擂台”上的“擂主”就是所求的最大(小)数。 (2) 程序中,maxpos、minpos存放最大值、最小值元素的下标。 2. 程序改错一 下面给定的程序中,fun函数的功能是将十进制正整数m转换成k进制数(2≤k≤9),并按位输出。例如,若输入8和2,则应输出1000(即十进制8转换成二进制是1000)。请改正fun函数中的错误。 注意: 不要改动main函数和其他函数中的任何内容,不得增行或删行,不得更改程序的结构。 1#include 2void fun(int m,int k) 3{ 4int aa[20],i; 5for(i=0;m;i++) 6{ 7/****************ERROR*****************/ 8aa[i]=m/k; 9/****************ERROR*****************/ 10m%=k; 11} 12for(;i;i--) 13/****************ERROR*****************/ 14printf("%d",aa[i]); 15} 16int main() 17{ 18int b,n; 19printf("\n please enter a number and a base:\n"); 20scanf("%d,%d",&n,&b); 21fun(n,b); 22printf("\n"); 23return 0; 24} 【实验提示】 (1) 将十进制正整数转换成其他进制的数与十进制正整数转换成二进制数的方法是一样的。即把十进制整数m转换为k进制,采用m除以k取余数的方法来完成。 (2) 程序第5~11行,采用循环的方式用m除以k取余数,将所得余数放入数组,再用所得的商作为新的m,再除以k取余数,如此重复,直到m变成0为止。 (3) 将存入数组中的余数,从后向前输出,即为转换成的k进制数,程序第11~15行即是。 (4) 注意,取余运算和整除运算的区别。 3. 程序改错二 功能: 某个公司采用加密形式传递数据,数据是4位的整数,加密规则如下: 每位数字都加上5,然后用除以10的余数代替该位数字。再将新生成数据的第一位和第四位交换,第二位和第三位交换。 例如,输入一个4位整数1234,则结果为9876。 1#include 2int main() 3{ 4int a,i,aa[4],t; 5printf("Input a 4-digit integer: "); 6/****************ERROR*****************/ 7scanf("%d", a); 8aa[0]=a%10; 9/****************ERROR*****************/ 10aa[1]=a%100%10; 11aa[2]=a%1000/100; 12aa[3]=a/1000; 13/****************ERROR*****************/ 14for(i=0;i<3;i++) 15{ 16aa[i]+=5; 17aa[i]%=10; 18} 19for(i=0;i<=3/2;i++) 20{ 21t=aa[i]; 22aa[i]=aa[3-i]; 23aa[3-i]=t; 24} 25for(i=3;i>=0;i--) 26printf("%d",aa[i]); 27return 0; 28} 【实验提示】 (1) 注意输入scanf函数的使用格式。 (2) 设计一个数组,存放该4位整数分离出的4个数位(个、十、百、千位)。 (3) 分离一个长整型数的各个数位的方法是这个题的关键所在,比如: 整数a的个位分离方法是a%10; 将a缩小为原来的1/10,即a=a/10后,a的十位即变为个位,由此,一个长整型数的各个数位都可分离出来。 4. 程序设计一 输入某班级某门课程的成绩(最多不超过40人,具体人数由用户从键盘输入),编程统计不及格的人数。 【实验提示】 (1) 设计一个一维数组score[40]用于存放学生的成绩。具体存放多少个学生成绩由用户自定(不超过40)。代码如下: int score[40],n,i,x=0; scanf("%d",&n); (2) 通过循环输入n个学生成绩。 (3) 循环遍历该数组,数组中每个学生成绩与及格分60进行比较,小于60则计数器x累计。 5. 程序设计二 兔子繁殖问题。假设一对兔子的成熟期是一个月,即一个月可长成成兔,如果每对成兔每个月都可以生一对小兔,一对新生的小兔从第二个月起就开始生兔子。试问从一对兔子开始繁殖,假如兔子都不死,一年以后可以有多少对兔子?试编程求解。 【实验提示】 (1) 依题意,从兔子的繁殖可以发现规律: ①每月小兔的对数=上个月成兔的对数; ②每月成兔的对数=上个月成兔的对数+上个月小兔的对数; 综合①、②可以得出: 每月成兔对数=前两个月成兔对数之和。即每个月兔子的总对数为: 1,2,3,5,8,13,21,34,…,这就是著名的Fibonacci数列。 (2) 定义数组: int f[12]={1,2}; 存放每个月的兔子数。 (3) 由(1)知: f[2]=f[0]+f[1],f[3]=f[1]+f[2],以此类推,下面循环即可求出每个月的兔子数量: for(i=2; i<12; i++) f[i]=f[i-1]+f[i-2]; 5.2二维数组 如果说一维数组在逻辑上可以想象成一行长表,那么二维数组在逻辑上可以想象成由若干行、若干列组成的表格或矩阵,二维数组常称为矩阵。把二维数组写成行和列的排列形式,可以有助于形象化的理解二维数组的逻辑结构。 5.2.1知识点介绍 1. 二维数组的定义 当数组中的每个元素带有两个下标时,称这样的数组为二维数组。二维数组也必须“先定义,后使用”。在C语言中,二维数组的定义形式见表53。 表53二维数组的定义形式及说明 类型说明符数组名[整型常量表达式1][整型常量表达式2]; 示例: inta[3][4]; 类型说明符 数组中所有元素的类型,如int、float、char等 int为类型说明符 数组名 遵循C语言标识符命名规则 a为数组名 常量表达式1 二维数组的行数,只能是整型常量或整型表达式 表示二维数组a有3行 常量表达式2 二维数组的列数,只能是整型常量或整型表达式 表示二维数组a有4列 数组元素个数 常量表达式1×常量表达式2,表示二维数组元素个数 数组a共有3×4即12个元素 如果有二维数组a[M][N],则数组a含有M×N个元素,a[i][j]表示第i行第j列的元素,其中i的取值范围是0~M-1,列下标j的取值范围是0~N-1。 2. 二维数组的引用 二维数组的引用和一维数组的引用一样,也可以把二维数组中的每个元素当成普通的变量来使用,引用二维数组时必须带有两个下标。引用形式如下: 数组名[行下标] [列下标]; 行下标和列下标也可以是整型表达式,如a[2-1][2*2]、a[i][j]、a[w+v][i+k]等都是合法的数组元素引用。需要注意的是,在引用二维数组元素时,每个下标的值都必须是整型数据,且应在已定义数组大小的取值范围内,不得超越数组定义中的上、下界; 两个下标应该分别在两个方括号内。例如,a[1,2]和a[i,i]都是不合法的。 3. 二维数组元素初始化 对二维数组的初始化,即在定义二维数组的同时,给数组各元素赋值。可以有以下4种方式,见表54。 表54二维数组的初始化 形式 示例 说明 分行对二维数组初始化 int a[3][3]={{1,2,3},{4,5,6},{7,8,9}}; 分行将值依次赋给每行各元素,如第一行: a[0][0]=1 ,a[0][1]=2, a[0][2]=3 按二维数组在内存中的排列顺序给各元素赋值 int a[3][3]={1,2,3,4,5,6,7,8,9}; 二维数组在内存中是按行存储的,按顺序依次将值赋给各元素 所赋初值行数少于数组行数且各行部分赋值 int a[3][3]={{1,2},{4,5}}; 系统将自动给后面各行的元素赋初值0 所有元素赋初值时,可以省略行下标,但不能省略列下标 int a[][3]={{1,2,3},{4,5,6},{7,8,9}}; 此时,只能省略第一维下标,第二维下标不能省,系统会根据所赋值的个数及二维下标数算出二维数组的行数 除了定义二维数组时对二维数组初始化赋初值外,程序中也可通过循环嵌套语句对数组元素逐个动态赋值。如下面程序段就可以给数组a所定义的15个元素输入值。 float a[3][5]; int i,j; for(i=0;i<3;i++) for(j=0;j<5;j++) scanf("%d",&a[i][j]); 图52二维数组数 据输入示例 运行程序时,用户从键盘输入数据,数据之间可以空格隔开,按行输入,如图52所示,也可在一行中输入,但不提倡在一行中输入数据。 4. 二维数组的存储 在逻辑上可以把二维数组看成是有行有列的二维表格,在概念上是二维的,其下标在两个方向上变化,下标变量在数组中的位置也处于一个平面之中。但实际的硬件存储器却是连续编址的,存储器单元是一维的。那么,如何在一维存储器中存放二维数组呢?可以用把二维数组看作两个一维数组的叠加的方法来实现。 上例中数组a可看作由3个一维数组{a[0],a[1],a[2]}组成,其中每个元素又可看作是一个一维数组a[i]={a[i][0],a[i][1],a[i][3],a[i][4]}(i=0,1,2); 然后按一维数组的存放方式依次对数组元素进行存放,即先存放数组a[0]的元素,再存放a[1]的元素,以此类推。由此可见,在C语言中,二维数组元素在内存排列的顺序是按行存放,即放完第一行之后,按顺序放入第二行,以此类推。 5. 多维数组 C语言允许使用多维数组,有了二维数组的基础,再由二维数组推广到多维数组就相对容易了。 5.2.2实验部分 1. 实验目的 (1) 熟练掌握二维数组的定义、二维数组元素的引用、赋值以及输入输出的方法。 (2) 比较二维数组与一维数组在操作上的异同。 (3) 熟练掌握用二维数组解决矩阵、行列式、二维表及平面图等问题的算法。 (4) 掌握二维数组以及多维数组的使用。 2. 实验内容 (1) 程序填空一 给定程序中,fac函数的功能是计算N×N矩阵的主对角线元素与反向对角线元素的和,并作为函数值返回,请填空。 1#include 2#defineN4 3int fac(int t[ ][N],int n) 4{ 5int i,sum; 6/****************FILL*****************/ 7; 8for(i=0;i 2void fun(int a[3][4]) 3{ 4int i,j,row=0,colum=0,max; 5/****************FILL*****************/ 6 ; 7/****************FILL*****************/ 8for(i=0; ;i++) 9for(j=0;j<=3;j++) 10if(a[i][j]>max) 11{ 12/****************FILL*****************/ 13 ; 14row=i; 15colum=j; 16} 17printf("max=%d,row=%d,colum=%d\n",max,row,colum); 18} 19int main() 20{ 21int a[3][4],i,j; 22printf("Please input array:\n"); 23for(i=0;i<=2;i++) 24for(j=0;j<=3;j++) 25scanf("%d",&a[i][j]); 26fun(a); 27return 0; 28} 【实验提示】 ① 找出一组数中最大值或最小值,即求最值,常采用“打擂台”的方法。 ② 第6行,“擂台”上先存放一个数,第8~16行通过循环依次打擂求最值,即数组中每一个数都和“擂台”上的数比,如果比它大(小),就替代它,最后,“擂主”就是所求的最大(小)数。 ③ 如果想求每行的最大值及其所在的列号,该如何修改程序? (3) 程序改错一 下列给定程序中,yahui函数的功能是按以下形式输出杨辉三角形(要求输出10行),请修改程序中的错误。 注意: 不要改动main函数,不得增行或删行,不得更改程序的结构。 1 11 121 1331 14641 1#include 2#define N 11 3void yahui(int a[][N]) 4{ 5int i,j; 6for(i=1;i 2#defineM10 3inta[M][M]={0}; 4int main() 5{ 6int i,j,m; 7printf("ENTER m: "); 8scanf("%d",&m); 9for(i=0;i 2#define N 4 3void fun(int a[][N]) 4{ 5int i,j; 6/*****************BEGIN********************/ 7 8 9/*******************END********************/ 10} 11int main() 12{ 13int a[][N]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; 14int i,j; 15fun(a); 16for(i=0;i<=N-1;i++) 17{ 18for(j=0;j<=N-1;j++) 19printf("%4d",a[i][j]); 20printf("\n"); 21} 22return 0; 23} 【实验提示】 ① 控制N×N数组中左下三角元素的算法,实际上是下面千篇一律的循环语句,注意内循环的变化。 for(i=0;i 2#define N 4 3void fun(int t[N][N]) 4{ 5int i,j,x; 6/****************FILL*****************/ 7for(i=0;;i++) 8{ 9/****************FILL*****************/ 10x=t[i][]; 11for(j=N-1;j>0;j--) 12t[i][j]=t[i][j-1]; 13/****************FILL*****************/ 14t[i][]=x; 15} 16} 17int main() 18{ 19int i,j,t[][N]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; 20printf("\nThe original array:\n"); 21for(i=0;i 2#define N 4 3int fun1(int a[N][N])//求左下三角元素的和 4{ 5int i,j,s=0; 6for(i=0;i 2void fun(int a[3][3], int n) 3{ 4int i,j,t; 5for(i=0;i 2#define M 3 3#define N 4 4void fun(int a[M][N]) 5{ 6int i=0,j,find=0,rmax,c,k; 7while((irmax) 13{ 14rmax=a[i][j]; 15/****************FILL*****************/ 16c=; 17} 18find=1; 19k=0; 20while(k 2int fun(int s[][10], int b[], int mm, int nn) 3{ 4int n=0,i,j; 5/*****************BEGIN********************/ 6 7 8/*******************END********************/ 9return n; 10} 11int main() 12{ 13int w[10][10]={{33,33,33,33},{44,44,44,44},{55,55,55,55}}; 14int a[100]={0}, i, j, n; 15n=fun(w,a,3,4); 16printf("The A array:\n"); 17for(i=0;i 2#define M 4 3#define N 5 4int fun(int a[M][N]) 5{ 6int i,j,s=0; 7/*****************BEGIN********************/ 8 9 10/*******************END********************/ 11return s; 12} 13int main() 14{ 15int aa[M][N],i,j,y; 16for(i=0;istr2,返回正数 string.h puts int puts(char *str); 把str指向的字符串输出到标准的输出设备 返回换行符,若失败,返回EOF stdio.h gets char*gets(char *str); 从标准输入设备读入字符串放入str指向的字符数组中 成功,返回str指针,否则返回NULL指针 stdio.h 5. 二维字符数组 二维字符数组用于同时存储和处理多个字符串,其定义格式与二维数值数组类似,可以看作若干个一维字符数组的叠加。如果以字符串的形式给字符数组赋值,同样每一个一维数组也是以'\0'结束。 5.3.2实验部分 1. 实验目的 (1) 熟练掌握字符数组的定义,赋值和输入输出的方法。 (2) 熟练掌握用字符数组存储和处理字符串常量的方法。 (3) 熟记常用字符串处理函数,并掌握它们的使用方法。 (4) 掌握字符型数组和数值型数组在处理上的区别和联系。 2. 实验内容 (1) 程序填空一 下列给定的程序中,fun函数的功能是将两个字符串连接起来(不得调用字符串连接strcat函数)形成一个新串,请填空。字符串的输入与输出在main函数中进行。 1#include 2#include 3void fun(char s1[],char s2[]) 4{ 5int i=0,j=0; 6/****************FILL*****************/ 7while() 8i++; 9while(s2[j]!='\0') 10{ 11s1[i++]=s2[j]; 12/****************FILL*****************/ 13 ; 14} 15/****************FILL*****************/ 16 ; 17} 18int main() 19{ 20char s1[50],s2[20]; 21printf("Input string s1:\n"); 22gets(s1); 23printf("Input string s2:\n"); 24/****************FILL*****************/ 25scanf("%s", ); 26fun(s1,s2); 27printf("The result is:"); 28puts(s1); 29return 0; 30} 【实验提示】 ① 把第二个字符串连接到第一个字符串的后面,找出第一个字符串的连接位置是关键,程序第7行通过while循环语句遍历字符串,直到字符串结束,即结束标志'\0'的位置即为第二个串的连接点,程序第9~14行将第二个字符串的每个字符依次地循环接入到第一个字符串的后面。 ② 程序第16行,注意连接后形成新字符串的结束标志'\0'。 ③ 字符数组的输入有多种方法,读者应该清楚不同方法的区别和使用方法。 ④ 如何将字符数组s2中的全部字符复制到字符数组s1中,而不调用strcpy函数。 (2) 程序填空二 请补充fun函数,该函数的功能是删除字符数组中小于指定字符的字符,只保留大于指定字符的字符。指定字符从键盘输入,结果仍保存在原数组中。例如: 输入abcdefghij,指定字符为d,则输出结果为: defghij。 注意: 不得增行或删行,不得更改程序的结构。 1#include 2#include 3#define N 80 4void fun(char s[],char ch) 5{ 6int i=0,j=0; 7while(s[i]!='\0') 8{ 9if(s[i] 2#define N 50 3int fun(char a[]) 4{ 5int i=0,num=0,flag=1; 6do 7{ 8num++; 9/****************ERROR*****************/ 10}while(a[i]!='\0'); 11do 12{ 13/****************ERROR*****************/ 14if(a[i]!=a[num-i]) 15{ 16flag=0; 17break; 18} 19i++; 20/****************ERROR*****************/ 21}while(i 2#include 3void fun(char str[],char ch) 4{ 5int i=0; 6/****************ERROR*****************/ 7while(str[i]!='\0'||str[i]!=ch) 8i++; 9/****************ERROR*****************/ 10if(str[i]==ch) 11{ 12str[i]=ch; 13/****************ERROR*****************/ 14str[i++]='\0'; 15} 16} 17int main() 18{ 19char s[80],c; 20printf("\nInput a strinr:"); 21gets(s); 22printf("\nInput a character:"); 23c=getchar(); 24fun(s,c); 25printf("\nThe result is %s:\n",s); 26return 0; 27} 【实验提示】 ① 程序第7行对字符数组进行循环遍历,只要字符串没有结束且当前字符和字符ch不相同两个条件同时满足时,说明ch和字符串中的字符不同,将字符ch加在字符串的尾部,否则,什么都不做。因此,遍历时while语句的循环条件很重要。 ② 程序第12行将与字符串任一字符都不相同的字符ch插入到字符串的最后,代替了'\0',此时注意第14行要重新在插入ch字符后的字符串结尾赋'\0'。 (5) 程序设计一 从键盘任意输入一个字符串,将该字符串中的所有字符按其ASCII码值从小到大排序后输出。 【实验提示】 ① 根据前面所学的排序算法(冒泡排序法),将字符串中各字符的ASCII码值依次进行比较排序。 ② 利用字符串处理strlen函数,求出输入的字符串所包含字符的个数,即排序对象的个数,即可采用冒泡法排序。 (6) 程序设计二 下面给定的程序中,fun函数的功能是将字符串s中的所有数字字符移到所有非数字字符之后,并保持数字字符和非数字字符原有的次序。例如,字符串s为def35abcg4jhdt7。执行结果为def abcg jhdt3547。 部分源程序已给出,如下所示。请勿改动main函数和其他函数中的任何内容,仅在fun函数的空白处填入编写的语句。 1#include 2#include 3void fun(char s[]) 4{ 5int i,j=0,k=0; 6char t1[80],t2[80]; 7for(i=0;s[i]!='\0';i++) 8if(s[i]>='0'&&s[i]<='9') 9/*****************BEGIN********************/ 10 11 12/*******************END********************/ 13for(i=0;i='a'&& s[i]<='z')n++; printf("%d\n",n); A. 2B. 0C. 3D. 5 (3) 对两个数组a和数组b进行如下初始化,则下面选项中叙述正确的是()。 char a[]="ABCDEF"; char b[]={'A','B','C','D','E','F'}; A. 数组a与数组b完全相同B. 数组a与数组b长度相同 C. 数组a和数组b中都存放字符串D. 数组a比数组b的长度长 (4) 下列选项中,不合法的数组定义语句是()。 A. char a[9]={'s','t','i','r','n','g'};B. char a="string"; C. char a[9]={"string"};D. char a[9]="string"; (5) 下面程序段的输出结果是()。 char ch[7]={"12ab56"}; int i,s=0; for(i=0;ch[i]>='0'&&ch[i]<='9';i+=2) s=10*s+ch[i]-'0'; printf("%d\n",s); A. 1 B. 12C. 12ab56D. 1256 (6) 下面程序段的输出结果是()。 char a[]="morning", t; int i, j; for(i=0;i<7;i++) if(a[j] 2int main() 3{ 4int i,j; 5char a[5][5]; 6for(i=1;i<=4;i++) 7for(j=1;j<=4;j++) 8if(j<=i) 9a[i][j]='*' ; 10else 11a[i][j]='' ;//放入一个空格 12for(i=1;i<=3;i++) 13{ 14for(j=1;j<=4;j++) 15/****************FILL*****************/ 16 ; 17printf("\n"); 18} 19/****************FILL*****************/ 20for() 21printf("%c",a[4][j]); 22printf("\b");//光标退一格 23/****************FILL*****************/ 24for() 25printf("%c",a[4][j]); 26printf("\n"); 27for(i=3;i>=1;i--) 28{ 29printf("") ; //输出3个空格 30/****************FILL*****************/ 31for() 32printf("%c",a[i][j]); 33printf("\n"); 34} 35return 0; 36} 【实验提示】 (1) 程序是将图形分成三部分来考虑,前3行是第一部分,第4行是第二部分,后3行是第三部分。第一部分和第三部分形状类似,不同的是第三部分按第一部分的行和列相反的顺序输出图形。第二部分即第四行的7个'*'又分两次输出。 (2) 程序第6~11行是将字符'*'和空格字符放入字符数组a中应有的位置。程序第12~18行输出图形的第一部分,第20~25行输出图形的第二部分,第27~34行输出图形的第三部分。 (3) 程序中定义的数组是5行5列的二维字符数组,实际上只使用了后4行4列的元素,即没有使用0行与0列的元素。 2. 程序填空二 请补充main函数,该函数的功能是从字符串str中取出所有的数字字符,并分别统计字符0,1,2,3,4,…,9的个数,然后把计数结果保存在数组b中并输出,并把其他字符的个数保存在b[10]中。 1#include 2int main() 3{ 4int i,b[11]; 5char str[]="ab1234456789cde0990"; 6printf("\nThe original array: \n"); 7puts(str); 8for(i=0;i<11;i++) 9b[i]=0; 10/****************FILL*****************/ 11; 12while(str[i]!='\0') 13{ 14/****************FILL*****************/ 15switch() 16{ 17case '0':b[0]++;break; 18case '1':b[1]++;break; 19case '2':b[2]++;break; 20case '3':b[3]++;break; 21case '4':b[4]++;break; 22case '5':b[5]++;break; 23case '6':b[6]++;break; 24case '7':b[7]++;break; 25case '8':b[8]++;break; 26case '9':b[9]++;break; 27/****************FILL*****************/ 28default:; 29} 30/****************FILL*****************/ 31; 32} 33printf("\n The statistical results:\n"); 34for(i=0;i<10;i++) 35printf("\n%d:%d",i,b[i]); 36printf("\nThe others : %d\n",b[10]); 37return 0; 38} 【实验提示】 (1) 对数组str中字符串的所有字符进行循环遍历并判断其种类,分别计数即可。注意,switch语句的执行过程。 (2) 试用多分支的if语句替代switch语句。 3. 程序改错 下列给定的程序中,fun函数的功能是从字符串s中找出子字符串t的个数。例如,当字符串s中的内容为mnuvmnxmn,字符串t的内容为mn,则求出的个数为3。请改正程序中的错误。注意,不要改动main函数,不得增行或删行,不得更改程序的结构。 1#include 2#include 3int fun(char s[],char t[]) 4{ 5/****************ERROR*****************/ 6int i=0,j=0,n; 7while(s[i]!='\0') 8{ 9while(t[j]!='\0') 10/****************ERROR*****************/ 11if(s[i]!=t[j]) 12{ 13i++; 14j++; 15} 16else 17{ 18i++; 19j=0; 20/****************ERROR*****************/ 21continue; 22} 23if(t[j]=='\0') 24n++; 25j=0; 26} 27return n; 28} 29int main() 30{ 31char s[100],t[100]; 32int m; 33printf("\nPlease enter string s:"); 34scanf("%s",s); 35printf("\nPlease enter string t:"); 36scanf("%s",t); 37m=fun(s,t); 38printf("\nThe result is :m=%d\n",m); 39return 0; 40} 【实验提示】 (1) 程序第6行中定义的变量i是数组s的下标,j是数组t的下标,n则是存放字符串t在字符串s中存在的个数,充当计数器。 (2) 从字符串s中找出子字符串t的方法是分别循环遍历字符串s和字符串t,从字符串s的第一个字符开始,若字符串s中的当前字符与字符串t的第一个字符相同,则比较字符串和字符串t的下一个字符(程序第7~15行); 若不相同,则字符串s继续下移,而字符串t重回第一个字符,内循环退出(程序第16~20行)。如果一直循环直到字符串t结束,则说明字符串t在字符串s中出现,计数器n累加(程序第22行),字符串t重回第一个字符,再次开始遍历字符串t进行比较。 (3) fun函数中变量n用来累计字符串t在字符串s中存在的个数,通过return语句返回给main函数。 4. 程序设计一 请编写fun函数,其功能是将一个数字字符串转换为一个整数(不得调用由C语言提供的,将数字字符串转换为对应整数的函数)。例如: 若输入字符串-1234,则调用fun函数把它转换为整数值-1234。 部分代码给出,如下所示。请勿改动main函数和其他函数中的任何内容,仅在fun函数的圆括号中填入编写的语句。 1#include 2#include 3long fun(char p[ ]) 4{ 5long n=0; 6int flag=1,i; 7/*****************BEGIN********************/ 8 9 10/*******************END********************/ 11return n; 12} 13int main() 14{ 15char s[6]; 16long n; 17gets(s); 18n=fun(s); 19printf("%ld\n",n); 20return 0; 21} 【实验提示】 (1) 该题考察数字字符串转化为对应整数的算法操作。 (2) 考虑符号问题: 用if语句判断数字字符串的第一个字符是'-'还是'+'确定转换的整数的正负,用变量flag放+1或-1作为系数,调节最终转换成的整数的正负。 (3) 不得调用C语言提供的将字符串转换为整数的函数,则数字字符转换为相应的数字需要通过该数字字符减去'0'来实现,即p[i]-'0'就可以得到与p[i]这个字符对应的数字。可用下面的循环语句,在数字字符串中完成将每个数字字符转换为相应数字,并且将其组成为整数(不含符号)。 while(p[i]!='\0') { n=n*10+p[i]-'0'; i++; } (4) 综合符号问题,最后n=flag*n。 5. 程序设计二 规定输入的字符串中只包含字母和*号。编写fun函数,其功能是删除字符串中所有的*号。编写函数时,不得使用C语言提供的字符串函数。例如,字符串中的内容为****A*BC**DEF*G******,删除后,字符串中的内容应当是ABCDEFG。 注意: 部分代码给出如下。请勿改动main函数和其他函数中的任何内容,仅在fun函数的圆括号中填入编写的语句。 1#include 2#include 3void fun(char s[]) 4{ 5int i,j=0; 6/*****************BEGIN********************/ 7 8 9/*******************END********************/ 10} 11int main() 12{ 13char s[81]; 14gets(s); 15fun(s); 16printf("The string after deleted:\n"); 17puts(s); 18return 0; 19} 【实验提示】 (1) 欲删除字符串中所有*号,需用循环语句遍历字符串的每一个字符,依次判断该字符是否是*号。若不是*号,则将该元素在原数组中保留; 若是*号,则继续判断下一个字符。 (2) 从字符串下标为0的元素开始循环逐个进行比较,if(s[i]!= '*')则保留,注意原字符串的下标和删除*号后新字符串的下标的变化。 (3) 保留下来的新字符串最后要加字符串结束标志'\0'。 6. 程序设计三 下面给定的程序中,fun函数的功能是对5个国家名字,按从小到大的顺序排序。从main函数中输入5个国家的名字,排序后的结果也在main函数中输出。 部分代码已给出,如下所示。请勿改动main函数和其他函数中的任何内容,仅在fun函数的空白处填入编写的语句。 1#include 2#include 3int main() 4{ 5void fun(char name[][20]); 6char name[5][20]; 7int i; 8printf("\n Input fivenames:\n"); 9for(i=0;i<5;i++) 10gets(name[i]); 11fun(name); 12printf("The result is:\n"); 13for(i=0;i<5;i++) 14{ 15puts(name[i]); 16putchar('\n'); 17} 18return 0; 19} 20void fun(char name[5][20]) 21{ 22int i, j; 23char temp[20]; 24/*****************BEGIN********************/ 25 26 27/*******************END********************/ 28} 【实验提示】 (1) 可以将第5行的二维字符数组看成是5个一维字符数组,每个一维字符数组存放一个国家名,共5个国家名,即5个字符串。对5个字符串排序,用冒泡排序法排序即可。程序第22行定义的数组temp作为排序时字符串交换用的备用数组。 (2) 使用字符串比较函数实现字符串的比较,使用字符串复制函数实现字符串的位置互换。 5.4实践拓展 5.4.1新个人所得税计算方法与实现 个人所得税与我们每个人的利益息息相关。2019年1月1日新的《中华人民共和国个人所得税法》正式实施,个人需要缴纳的个人所得税也发生了变化,起征点从之前的3500元上调至5000元,还可以享受专享附加扣除以及社保扣除,工资超过5000元才需要纳税,个人综合所得税按全年累计计算。新个人所得税税率表见表57。 表57新个人所得税税率表 级数 全年应纳税所得 税率/% 速算扣除数/元 1 不超过36000元的部分 3 0 2 超过36000元至144000元的部分 10 2520 3 超过144000元至300000元的部分 20 16920 4 超过300000元至420000元的部分 25 31920 5 超过420000元至660000元的部分 30 52920 6 超过660000元至960000元的部分 35 85920 7 超过960000元的部分 45 181920 请编程实现: 计算个人每月应缴纳所得税额以及全年累计应缴纳所得税额。个人月工资数、三险一金每月扣除金额以及专项附加扣除金额由用户从键盘输入。 【实验提示】 (1) 计算个人全年累计应缴纳所得税额,定义两个含有12个元素的数组: float a[12],tax[12],x; 数组a用来存放每个月税前工资,数组tax用来存放每个月应纳税金额。通过循环输入每个月的税前工资到数组a,输入专项扣除额到变量x中。 (2) 累加数组a的各月工资数,再减去三险一金的金额和每月专项扣除的金额,即为应纳税额。 (3) 根据个人所得税税率表,判断应纳税额的纳税税率区间,计算应纳税额。 (4) 例如: 如果个人1月工薪收入所得为18000元,三险一金每月扣除3200元,专项附加扣除共2000元,个税起征点为5000元,则无须纳税额为3200+2000+5000,则1月份应交税为: [18000-(3200+2000+5000)]×3%=234元,放入tax[0]中; 2月工薪收入18000元,其他所得1500元,综合所得合计19500元,则1~2月累积应纳税[(18000+19500)-(3200+2000+5000)×2]×3%=513元,2月应交税=513-234=279元,放入tax[1]中; ……。依此方法,即可求出每月应纳税额及全年应纳税额。 5.4.2模拟超市寄存柜存取操作 在大中型超市门口一般都放置有很多寄存柜,顾客可以将不能带入超市的物品暂时寄存在里面,购物结束后再取回。客户按动存包按钮,如果寄存柜中有空的箱子,则打开一个空箱子,并打印一张印有密码的纸条,客户购物结束凭密码取回个人的物品。如果没有空箱子,则显示“寄存柜已满,请耐心等待”。 本拓展项目要求编写一个程序模拟自动寄存柜的存包、取包功能。 【实验提示】 (1) 一般超市寄存柜都有若干排,每排都有若干个存包箱。构造两个二维数组,一个表示超市的寄存柜,二维数组的下标对应寄存柜中每个存包箱所在的位置; 一个用来存放寄存柜中每个存包箱的密码。假设某超市的寄存柜共有50个存包箱,定义box[6][11]的二维数组,数组行下标1~5表示存包箱所在的行数编号,列下标1~10表示存包箱所在的列数编号,定义二维数组pas[6][11]存放对应存包箱的密码。 (2) 利用rand随机函数分别对两个二维数组进行初始化,其中一个初始化0~1的随机数,0表示箱子为空,可以存包; 1表示箱子非空,当前不能存包。放密码的二维数组随机生成6位数密码。 使用rand()%(b-a+1)+a可生成a~b的随机数,含a和b。 srand((unsigned)time(NULL));//用系统时间作为生成随机数的种子 rand()%2;//随机生成0~1的随机数 同理,想要生成随机6位数密码: rand()%100000+900000。 (3) 程序运行界面示例如图54所示,有带有编号的运行菜单及必要的文字提示,以便顾客操作。顾客可以循环地输入要进行的操作编号,进行相应的操作。当输入3时,结束程序运行。 图54程序运行菜单界面 (4) 当顾客存包时,需要寻找可存包的空的存包箱(二维数组元素值为0),在该二维数组中查找值为0的元素,确定该元素行下标和列下标,即可找到空的存包箱的位置。存包运行界面如图55所示。 图55存包运行界面 (5) 当顾客需要取回已寄存的包,此时需用户输入密码,将用户输入的密码与该箱子应有的密码进行比较。如果密码正确则箱子打开取出包,同时箱子变回空箱状态; 如果密码输入错误,则需重新输入密码,直到密码正确为止。取包运行界面如图56所示。 图56取包运行界面