第5章 数组 教学目标 (1) 掌握数组数据结构。 (2) 掌握数组的声明和存放,初始化和数组元素的引用方法。 (3) 掌握数组下标的使用方法。 (4) 了解多维数组声明和操作。 (5) 掌握字符串定义及使用方法。 (6) 初步理解排序和查找等基本算法。 数组是一组类型相同的数据对象构成的集合。数组中的数据存储在一个区域内,引用时用同一名字,而且能够作为一个组或者一个整体访问,从而使得程序的书写变得更简单。数组将数据对象组织成连续的存储单元,最低地址对应于数组的第一个元素,最高地址对应于最后一个元素,每一个数组元素都占用一个或若干连续的存储单元。数组可以是一维的,也可以是多维的,C语言没有对数组的维数做严格的限制。 数据结构(data structure): 在同一个名称下存储的相关的数据项的集合。 数组(array): 类型相同的数据的集合。 5.1问 题 引 导 在第3章例3.28学生成绩统计中,只是求出了课程考试的最高分、最低分和平均分等信息,如果要把每个学生的成绩信息、排名以及课程的统计信息以短信的方式发送给学生家长,程序又该如何设计呢?一般来说应进行如下操作。 (1) 保存每个学生的考试成绩。 (2) 比较学生的成绩,找出最高分和最低分。 (3) 将所有学生的成绩累加除以学生人数,计算出课程平均分。 (4) 对学生成绩进行排名,还可以知道学生的名次。 (5) 可以输入学生的学号,查询学生成绩。 通过数组的学习和使用,能够得到合适的存储方式和计算方法实现问题的求解过程。 5.2一 维 数 组 数组是存放两个或两个以上相邻存储单元的集合,每个存储单元中存放相同数据类型的数据。这样的单元称为数组元素。在一个具有相同名称和相同类型的连续存储结构中,要引用某个元素,就要指定数组中的元素的位置号或索引(index)。 直观上,我们将数组看作一个矩形框,数组中的每一个元素在这个矩形框中都占有一个方格。图51描述了一个由5个元素组成的双精度浮点型数组。 图515个元素的双精度浮点型数组x 在C语言中,每个数组有以下两个特性。 C语言程序设计教程(第3版) 第 5 章 数组 (1) 数组元素的类型: 存储在数组元素中的值的数据类型,简称数组类型。 (2) 数组的大小: 数组中能够存放数组元素的个数,也称为数组长度。 在声明数组时,必须反映出数组的这两个特性。 5.2.1一维数组的定义 一维数组的声明形式如下: 数组元素的类型说明符 数组名称[数组的大小]; 在C语言中,要使用数组,必须事先声明,以便编译器为它们分配内存空间。在上面的声明中,类型说明符指明数组的类型,也就是数组中每一个元素的数据类型,数组的大小表示数据元素的个数。每个数组元素都有一个指定字节数的存储单元。一维数组的总字节数可按下式计算: 总字节数 = sizeof(数组元素的数据类型) * 数组的大小 其中,sizeof是计算数据类型在存储空间中占用字节数的运算符。图51中的数组x所占用的存储字节数为: sizeof( double ) * 5 如果double类型占用8字节存储,则表达式的值为40。 例5.1数组的声明语句及含义。 数组的声明语句及含义如表51所示。 表51数组的声明语句及含义 语句说明 int a[10]; 定义一个长度为10的int数组a double score[50]; 定义一个长度为50的double数组score char studentname[20]; 定义一个长度为20的char数组studentname int b[100],x[27]; 定义了一个长度为100的int数组b和一个长度为27的int数组x 由于C语言没有字符串类型,则char类型的数组可以存放字符串。字符串及字符数组将在字符串一节中详细讨论。 例5.2将学生课程考试成绩数据存入数组相应的数组元素中。 /* 程序名称:ex5_2.c 程序功能:声明数组,从键盘输入数据存储到数组元素中并输出 */ #include #include int main() { int a[10]; int i; for(i=0;i<10;i++) scanf("%d",&a[i]); for(i=0; i<10;i++ ) printf("%d",a[i]); system("pause"); return 0; } 程序运行结果: 输入: 80 70 95 54 76 90 85 88 77 69 输出: 80 70 95 54 76 90 85 88 77 69 说明: (1) 数组的大小(或长度)是一个整型常量(高版本编译器支持变量,本教材按常量处理),通常可以使用符号常量来表示,这样可以方便改变数组的大小。例如: int a[10]; 使用符号常量的形式定义数组的大小: #define N 10 int a[N]; (2) 数组的命名方式与其他变量一样,遵循C语言标识符的命名规则。 (3) 数组元素的起始下标为0,在图51中,x[1]是数组的第二个元素,x[4]是数组的最后一个元素。数组的所有单元使用同一个名称x,每个元素使用下标来区分。在使用数组元素时,必须要注意下标的使用范围。如果下标错误,会产生一个非法的引用。编译器在语法检查时,并不指出下标的越界错误。尽管有时候会输出一个运行错误信息,但大多数时候并不给出错误提示信息。非法下标引发的不正确结果,编程者有时很难查明原因。 数组在存储时要占用内存空间。在数组声明时必须指定元素的类型和元素的个数,这样编译器才可以为数组分配相应的内存空间。 例如,前面定义的double型数组x,其在内存中的存储如图52所示。 若有数组的定义为: int a[10]; 其存储形式如图53所示。 图52double型数组x的存储 图53int型数组a的存储 定义字符型数组char str[5],其存储结构如图54所示。 图54字符型数组str的存储 5.2.2一维数组元素的引用 为了处理存储在数组中的数据,一般使用数组名和一个表示下标的整数来引用每一个元素。例如,数组x的第三个元素表示为x[2],称x[2]为下标变量,方括号内的整数是数组的下标(或者称为索引),它的值必须在0到数组元素个数(数组长度)减1之间。 图558个元素的数组c 图55所示为一个拥有8个元素的整型数组c。数组中的第一个元素称为第0个元素(读作c零),记为c[0]; c数组中的第二个元素为c[1]; c数组中的最后一个元素为c[7]。 数组的下标表示数组元素的位置,下标必须为整型值。例如,假设a等于2,b等于3,则下列语句: c[a+b] = 0; 含义为将0存入数组元素c[5]中。 表52列出了一些常用的数组元素的使用方法。 表52数组元素的引用 语句说明 printf("%.2lf",x[3] ); 显示x[3]的值,其值为22.00 x[0] = 25.0; 将浮点数25.0存入x[0]中 sum = c[0] + c[1]; 将c[0]和c[1]的和存储在变量sum中 sum += c[2]; 将sum和c[2]的和存储在变量sum中 c[7] = 100; 将100的值存入c[7]元素中 x[2] + 1 = x[3]; 非法的赋值语句,赋值运算符的左边不是变量不能被赋值 一般从数组的第一个元素开始,顺序处理数组的元素。在C语言中,使用循环语句可以很容易完成对数组元素的顺序操作。例如,用for循环语句,使用计数器作为数组的下标,就可以依次存取每一个数组元素。 例5.3保存一个10以内整数的所有约数。 分析: 一个数的约数的计算过程,是一个不断尝试的过程,约数是从1开始的,使用循环变量来表示约数不断增加的过程,若输入的数据n能被某一个循环变量整除,则该循环变量即为n的一个约数,并将其存入数组中。 /* 程序名称:ex5-3.c 程序功能:保存一个10以内的整数的所有约数 */ #include #include int main() { int a[5],n; int i,j=0; printf("输入一个整数(<10): "); scanf("%d",&n); for(i=1; i<=n;i++) if (n%i==0)   /*计算数据n的约数*/ a[j++]=i;   /*使用循环语句为数组元素赋值*/ printf( "约数为:\n" ); for(i=0; i #include int main() { int a[31],i; double avg=0; for(i=1;i<=30;i++) { do { printf("第%d个学生的成绩:",i); scanf("%d",&a[i]); }while(a[i]<0||a[i]>100); avg=avg+a[i]; } printf("学生成绩:\n"); for(i=1;i<=30;i++) printf("%d ",a[i]); printf("\n平均成绩:\n"); printf("%.2lf\n",avg); system("pause"); return 0; } 说明: (1) 如果初始化的元素个数小于数组的长度,则其余元素初始化为0值。 例如,可以用下列声明数组a的元素初始化为0: int a[10] = {0}; 其显式地将第一个元素初始化为0,隐式地将其余元素自动初始化为0,因为初始化值比数组中的元素少。程序员至少要显式地将第一个元素初始化为0,才能将其余元素自动初始化为0。 (2) 初始化值超过数组元素个数会产生数组定义的错误。例如: int a[5] = {32,27,64,18,95,14}; 因为初始化了6个值,而数组只能存放5个元素。 (3) 若数组中的数据已知,使用初始化能提高程序的执行速度。 (4) 当数组声明时,有static关键字修饰时,即使没有初始化,系统也能自动为所有元素初始化为0值。 static int b[5]; 5.2.4利用一维数组解决问题 例5.5在N个学生成绩中找最高分和最低分。 分析: 寻找最大值和最小值,需要比较数组中的所有元素,才能找出最值。 /* 程序名称:ex5-5.c 程序功能:求最值 */ #include #include #define N 10 int main() { int x[N],i,max,min; printf("输入N个整数:\n"); for(i=0;ix[i]) min=x[i]; } printf("最高分是:%d\n",max); printf("最低分是:%d\n",min); system("pause"); return 0; } 例5.6已知数组a中有n个互不相等的元素,数组b中有m(m #include #define N 8 #define M 5 int main() { int i,j,k=0,a[N],b[M],c[N]; for(i=0;i=M) { c[k]=a[i];k++;} } for(i=0;i #define N 8 int main() {int a[N],i,j,t; for(i=0;ia[i+1]) { t=a[i]; a[i]=a[i+1]; a[i+1]=t; } for(i=0;i #include #define N 1000 int main() { int i,t,n,a[N]; scanf("%d",&n); for(i=0;i #include void swap(int x,int y); int main() { int a[2]; scanf("%d %d",&a[0],&a[1]); printf("调用函数之前:\n"); printf("a[0]=%d,a[1]=%d\n",a[0],a[1]); swap(a[0],a[1]); printf("调用函数之后:\n"); printf("a[0]=%d,a[1]=%d\n",a[0],a[1]); system("pause"); return 0; } void swap(int x,int y) { int t; t=x; x=y; y=t; printf("调用函数内部:\n"); printf("x=%d,y=%d\n",x,y); } 输入10□20 回车 运行结果: 调用函数之前: a[0]=10,a[1]=20 调用函数内部: x=20,y=10 调用函数之后: a[0]=10,a[1]=20 从上面的例题可以看出,数组元素作为函数实参和一般变量作为函数参数一样,也是单向值传递,即形参的值发生了改变,而实参的值没有改变。 2. 将数组名作为函数的参数使用 当数组元素作为参数传递时,函数会接收到数组元素的一个副本,因此函数对复制副本的操作不会影响数组元素原来的值。一般称这种参数传递方法为值传递。除了将数组元素作为函数的参数传递,还可以将数组名作为函数的参数,实现对整个数组的传递。那么形参和实参之间的关系就发生了变化。这样的函数可以操作与实参数组一致的数组元素。从数组的存储结构中可知,数组名称与普通变量的名称有着很大的不同。数组名实际上标记了一个连续存储区域的首地址。作为参数传递时,不再是简单的值传递的方式了,而是将数组在内存中的存储地址作为参数来传递,这种参数传递的方式称为传地址。函数操作的是同一个存储区域中的数据,而不是数组自身的副本。因此,在函数中通过语句对一个数组元素进行赋值会改变实参数组的内容。 例5.10利用数组作为函数参数,编写两个数交换的程序。 /* 程序名称:ex5-10.c 程序功能:数组名作为函数参数 */ #include #include void swap(int x[2]); int main() { int a[2]; scanf("%d %d",&a[0],&a[1]); printf("调用函数之前:\n"); printf("a[0]=%d,a[1]=%d\n",a[0],a[1]); swap(a); printf("调用函数之后:\n"); printf("a[0]=%d,a[1]=%d\n",a[0],a[1]); system("pause"); return 0; } void swap(int x[2]) { int t; t=x[0]; x[0]=x[1]; x[1]=t; printf("调用函数内部:\n"); printf("x[0]=%d,x[1]=%d\n",x[0],x[1]); } 输入10□20 回车 运行结果: 调用函数之前: a[0]=10,a[1]=20 调用函数内部: x[0]=20,x[1]=10 调用函数之后: a[0]=20,a[1]=10 从例5.10可以看出,数组定义形式作为函数形参、数组名作为函数实参和例5.9得出的结果是不一样,即形参数组的元素值发生了改变,而实参数组元素值也随之改变。 例5.11定义一个函数init,将数组中所有元素的值更改为指定的value。 void init( int a[],int n, int value ) { int i; for ( i = 0; i < n; i++ ) a[i] = value; } 在调用函数init时,函数的参数由实参数组名、数组的长度和存储到数组中的值组成,如果实参数组b是一个长度为8的整型数组,需要将数组元素的值指定为-1,则可以用下面的程序完成: #include #include void init(int a[],int,int ); int main() { int b[8],i; init(b,8,-1); /*函数调用*/ for(i=0;i<8;i++) printf("%d ",b[i]); system("pause"); return 0; } 参数传递的形式如图510所示。 图510数组作为函数的参数 当实参数组传递给形参时,形参的数组并不分配新的存储单元,而是将形参数组与实参数组共同占用一段存储区域,从而在函数中形参数组的值发生了改变,那么实参数组元素的值也随之改变。在使用形参数组时,要特别留意这个问题。一般情况下,如果不想在函数中改变实参数组的值,可以使用const关键字修饰形参数组,这样就可以避免函数在操作数组时发生值更改的意外情形。当然,这种参数传递的方式还是很有效率的,因为节省了大量的存储单元。在第6章指针中还会讨论到传地址这种特殊的参数传递形式。 C语言中提供了const关键字,其主要作用就是限定声明的变量值为常量,在程序运行时值不能改动。有时候需要将数组传入函数,但不希望函数体内改变数组元素的值,此时,就可以采用const关键字修饰形参数组来实现这样的功能。 例5.12定义一个函数sum,将数组中所有元素值的和返回,即得到一个函数值。 int sum(const int a[],int n ) { int i,s = 0; for ( i = 0; i < n; i++ ) s += a[i]; return s; } 这里,把函数计算的结果通过return返回(只能返回一个值),但return语句不能返回整个数组。 例5.13用数组表示A和B两个向量,定义函数实现将两个向量的和(对应项相加)存入数组C中。 void sum ( int a[],int b[], int n, int c[] ) { int i; for ( i = 0; i < n; i++ ) c[i] = a[i] + b[i]; } 其主函数为: int main() { int x[10],y[10],z[10],i; for(i=0;i<10;i++) scanf("%d",&x[i]); for(i=0;i<10;i++) scanf("%d",&y[i]); sum(x,y,10,z); for(i=0;i<10;i++) printf("%d ",z[i]); return 0; } 说明: (1) 数组作为函数的形参的语法为: 元素类型 数组名[] 或 const 元素类型数组名[] 两种书写方法是等价的,后一种用法将在指针章节中着重讨论。用关键字const声明的数组是一个严格的输入参数,元素的值是不能由函数改变的,这一点很重要。如果没有const,函数可以改变数组元素的值,具体的操作取决函数的功能需要。 (2) 数组名后的[]不能省略,[]的含义是参数是一个数组,是一个集合的名称,而非普通变量名称。当函数被调用时,实参数组将首地址作为参数传递给形参数组。这样,形参数组和实参数组对应同一个存储区域,而非副本。 (3) 数组作为参数时,实参中只需写实参数组名,不能再取其地址了。例如: sum(x,y,10,z); sum(&x,&y,10,&z); /*错误!x,y,z已经是数组名*/ 5.2.6一维数组应用 例5.14短信程序: 某班期中C语言课程考试有N名学生参加,输入每名学生的考试成绩,将班级平均分、最高分、最低分和学生的成绩及其排名显示出来。 分析: 输入N名学生的考试成绩(0~100),输入的过程中求出成绩的和,通过比较求出最高分和最低分,然后将学生的原始成绩从大到小排序(同时定义一个数组保存序号,这里把序号作为学号,确保排序过程中成绩交换时,序(学)号也交换)。 /* 程序名称:ex5-14.c 程序功能:学生成绩短信 */ #include #include #define N 10 int max=0,min=100; double avg=0.0; void input(int x[],int y[],int n); /*输入数据,计算平均值分、最高分、最低分*/ void sort(int x[],int y[],int n); /*排序*/ int main() { int i,a[N],b[N]; /*数组a保存学生成绩,数组b保存学号*/ input(a,b,N); sort(a,b,N); printf("最高分:%d 最低分:%d 平均分:%.2lf\n",max,min,avg); for(i=0;ix[i]) min=x[i]; } avg=avg/n; } void sort(int x[],int y[],int n) { int i,j,t; for(i=0;i #include #define N 10 void inv (int x[ ],int n); int main() { int i,a[N]={8,6,7,1,0,9,3,5,4,2}; printf("数组元素的原先顺序:\n"); for (i=0;i #include #define N 5 #define M 4 void sort(int [],int); void merge(int [],int [],int [],int,int); int main() { int j; int a[N] = {98,64,75,91,55}; int b[M] = {90,58,84,61}; int c[N+M]; /*合并后的结果数组*/ printf("a数组排序前:\n"); for(j=0;j #include int main() { int a1[3][3]={{1,2,3},{4,5,6},{7,8,9}}; int a2[3][3]={{1,2},{3,4},{5,6}}; int b1[3][3]={1,2,3,4,5,6,7,8,9}; int b2[][3]={1,2,3,4,5,6,7,8,9}; int i,j; printf( "a1:\n" ); for(i=0;i<3;i++ ) { for(j=0;j<3;j++ ) printf("%3d",a1[i][j]); printf("\n"); } printf( "\na2:\n" ); for(i=0;i<3;i++) { for(j=0;j<3;j++ ) printf("%3d",a2[i][j]); printf("\n"); } printf("\nb1:\n"); for(i=0;i<3;i++) { for(j=0;j<3;j++ ) printf("%3d",b1[i][j]); printf("\n"); } printf("\nb2:\n"); for(i=0;i<3;i++) { for(j=0;j<3;j++) printf("%3d",b2[i][j]); printf("\n"); } system("pause"); return 0; } 程序运行结果: a1: 123 456 789 a2: 120 340 560 b1: 123 456 789 b2: 123 456 789 从该例可以看出,C语言中二维数组的访问要用二重循环,各种初始化都是按行优先存储的。 5.3.3二维数组应用 通过二维数组的声明形式、存储方式、元素的访问方法以及初始化的学习,下面通过实例来讲述二维数组的应用。 例5.18实现矩阵的输入与输出。 /* 程序名称:ex5-18.c 程序功能:矩阵的输入与输出 */ #include #include #define M 3 #define N 4 int main() { int a[M][N]; int i,j; for( i = 0; i < M; i++ ) for( j = 0; j < N; j++ ) scanf( "%d",&a[i][j] ); for( i = 0; i < M; i ++ ){ for( j = 0; j < N; j++ ) printf( "%4d",a[i][j] ); printf("\n"); /*输出完一行后要换行*/ } system("pause"); return 0; } 程序运行结果: 输入: 1234 3456 4567 输出: 1234 3456 4567 例5.19编写函数实现两个矩阵的加和乘运算以及矩阵的转置运算。 分析: 在实际问题中,通常使用二维数组来定义矩阵结构,矩阵的相加即两个维数相同的二维数组的对应项相加,若要实现矩阵的乘积则须定义出满足矩阵运算要求的合适二维数组,即一个m×n的矩阵与n×p的矩阵相乘,得到一个m×p的矩阵。对于m×n的矩阵,转置运算是将该矩阵中元素的行列互换,得到一个n×m的矩阵。 /* 实现矩阵的加法*/ void add(int a[M][N],int b[M][N],int c[M][N]) { int i,j; for( i = 0; i < M; i ++ ) for( j = 0; j < N; j++ ) c[i][j] = a[i][j] + b[i][j]; } /* 实现矩阵的转置*/ void transpose( int a[M][N],int t[N][M]) { int i,j; for( i = 0; i < M; i ++ ) for( j = 0; j < N; j++ ) t[j][i] = a[i][j]; } /*实现矩阵的乘积*/ void product( int a[M][N],int b[N][P],int r[M][P]) { int i,j; int k = 0; for(i=0;i #include #define N 4 #define M 3 int main() { int i,j,x[M][N],y[M][N],z[M][N],s[N][M],t[M][M]; for(i=0;i #include #define N 5 #define M 3 void caclsum(int x[][M+1]); int countavg(int x[][M+1],double); int main() { int i,j,a[N][M+1]={0},imax,imin,max,min; double avg; for(i=0;imax) { max=a[i][M]; imax=i; } if(a[i][M]=avg) count++; return count; } 程序运行结果: 输入: 123 456 789 101112 131415 输出: 学生各科成绩和总分: 1236 45615 78924 10111233 13141542 总分最高的学生成绩: 13141542 总分最低的学生成绩: 1236 总平均分:24.00 总分大于或等于总平均分的人数:3 例5.21生成特殊矩阵: 对于一个n阶方阵,输入n=5(n<100),得到如下所示的n阶拐角矩阵。 12345 22345 33345 44445 55555 分析: 生成一个特殊矩阵,一般从特殊的下标来找规律,本题定义一个二维数组,从矩阵主对角线(行下标和列下标相同: i==j)出发,分成右上和左下两部分,i<=j为右上部分(元素值为列号+1), i>j为左下部分(元素值为行号+1)。 /* 程序名称:ex5-21.c 程序功能:生成特殊矩阵 */ #include #include #define N 100 void fun(int x[][N],int n) { int i,j; for(i=0;i #include int main() { char str[81]; int i=0,num=0,word=0; char c; while((str[i++]=getchar())!='\n'); str[i]= '\0'; for(i=0;(c=str[i])!='\0';i++) if(c==' ') word=0; else if(word==0) { word=1; num++; } printf("%d\n",num); system("pause"); return 0; } 程序运行结果: 输入: This is a long stentence for testing. 输出: 7 2. 用%s格式实现字符串的输入与输出 在格式控制字符串中,使用%s格式控制,在scanf()和printf()函数中实现对字符串的输入与输出。 scanf("%s",str); printf("%s","hello,world!"); 和其他接受字符串参数的标准库函数一样,printf()函数通过在字符数组中寻找空字符来标记字符串结束。如果printf()函数被传入一个不包含'\0'的字符数组,会首先将每个数组元素的内容当作字符显示。但会继续将内存中位于数组参数之后的内容当作字符显示,直到遇到空字符或者试图访问没有分配给该程序的内存单元,从而导致运行时错误或显示一些不可预知的内容。所以处理字符串时,必须确保在每个字符串结尾插入空字符。printf()函数格式字符串中的格式符%s可以和最小字段宽度一起使用,如下所示: printf("%8s,%-3s\n","Short","Strings" ); 屏幕显示结果: □□□Short Strings 第一个字符串占8列宽度以右对齐形式显示,第二个字符串比指定的字段宽度长,因此字段被扩展到刚好容纳该字符串而没有填充空格,并以左对齐形式显示。一般我们更习惯于字符串以左对齐方式输出。 scanf()函数可以用于输入字符串。例5.23中给出了使用scanf()函数和printf()函数执行字符串I/O的简单函数。 例5.23要求用户输入一个代表课程名称的字符串、表示星期几的整数以及表示该课程上课节次的字符串,输出该课程上课节次对应的课程表。 /* 程序名称:ex5-23.c 程序功能:字符串的输入与输出 */ #include #include #define DAY 7 #define LEN 10 char weekday[DAY][ LEN] = { "Sunday","Monday", "Tuesday", "Wednesday","Thursday","Friday","Saturday" }; int main() { char course_name[20]; int days; char time[6]; int i; scanf( "%s",course_name ); scanf( "%d",&days ); scanf( "%s",time ); for( i = 0; i < DAY; i++ ) printf( "%10s",weekday[i] ); printf( "\n%s",time ); for( i = 0; i < 10 * days; i++ ) printf( " " ); printf( "%s\n",course_name ); system("pause"); return 0; } 程序运行结果: 输入:Math 3 5~6 输出:SundayMondayTuesdayWednesdayThursdayFridaySaturday 5~6 Math scanf函数读入字符串的方法与读入数值型数据的方法类似。当scanf函数扫描字符串时,数据间的分隔符可以是空格符、换行符和制表符。从第一个非分隔字符开始,scanf函数将字符复制到它的字符数组参数的连续内存单元中。如图521所示,当遇到分隔字符时,停止读入,同时scanf函数将空字符'\0'存入数组参数中的字符串末尾处。 图521执行scanf()函数 注: scanf()函数会在字符串的结尾处自动添加一个空字符'\0' 由于scanf函数是以默认分隔符作为数据结束的标志,对于字符串中确实存在着诸如空格之类的数据时,就会出现如下情形: char str[50]; scanf( "%s",str ); 当输入: C Programming Language str中的字符串如图522所示。 图522scanf()函数使用%s格式读入字符串时遇到分隔符结束 3. 用gets()和puts()函数实现字符串的输入与输出 C语言中,可使用gets()和puts()函数来实现字符串的输入与输出。 例如: char str[50]; gets( str ); puts( str ); 使用gets()函数可以读入具有空格分隔符的字符串到指定的字符数组中。图523所示为gets()函数读入字符串的结果。也就是说,gets()函数可以输入带分隔符的字符串,而scanf()函数用%s输入字符串时,遇到分隔符就结束了。puts()函数与之类似,输出以'\0'为结束符的字符串。 图523使用gets()函数读入字符串遇到回车符结束 例5.24输入一句英文,输出该句中的最长单词。 分析: 一般英文句子中单词之间是用空格(可能有的单词之间不止一个空格)分开的,先将英文句子作为一个字符串输入,从左向右读取字符,并记录字符个数,直到某个单词结束,这样就得到了某个单词的长度; 再与前面找出的最长单词进行比较,得到目前为止的最长单词,并记下最长单词中最后一个字符的位置(下标),如此反复,直到字符串结束; 然后用最长单词下标减去最长单词的长度得到输出的开始位置,再输出“最长单词”的长度字符,就得到最长单词。 /* 程序名称:ex5-24.c 程序功能:求最长单词 */ #include #include int main() { char line[1000]; int maxlen=0,i=0,max=0,end=0; /*maxlen为当前单词的长度,max为最长单词长度,end为到目前为止,最长单词的最后一个字符位置(下标)*/ gets(line); while(line[i]) { while(line[i]!=' '&&line[i]) { i++; maxlen++; } if (max 0 ) printf( "str1字典序中排在str2的后面\n" ); else printf( "str1字典序中排在str2的前面\n" ); 上述代码段的执行结果为: str1字典序中排在str2的后面。 附录E列出了C语言常用的字符串处理函数。 在使用字符串时要注意和数值型数组的区别,特别是在比较和交换中的操作: 数值型数据直接通过关系运算符和赋值就能完成的操作,字符串或者字符数组则必须借助于函数才能完成。例如,排序中的比较和交换操作,数值型数据和字符串的操作对比如表53所示。 表53交换排序中数值型数据和字符串间的操作对比 交换排序 数值型数组字符型数组 数据定义int list[M]; char list[M][N]; 比较操作 if (list[i] < list[first])if (strcmp(list[i],list[first]) < 0) first = i; first = i; 元素交换 temp = list[min]; strcpy(temp,list[min]); list[min] = list[fill]; strcpy(list[min],list[fill]); list[fill] = temp; strcpy(list[fill],temp); 5.4.7字符数组应用 下面以一个实例介绍字符数组的应用。 例5.25编写一个函数,完成字符串的复制操作,即将一个字符串复制到一个字符数组中。 分析: 该问题中有一个假设,即用于存储字符串的目标数组必须有足够的空间,足以存放被复制字符和空字符。函数定义本身很简单,source字符串的内容不被修改,可以用const关键字修饰: void str_copy(char target[],char source[]) { int i = 0; while( source[i] != '\0' ){ target[i] = source[i]; i++; } target[i] = '\0'; } 该函数的功能类似于strcpy()函数。利用source[i]值为空字符时作为循环的结束条件,需要特别注意的是,循环语句后的赋值语句,为target[i]赋一个空字符,以表示字符串的结束。 此外,字符串复制的基本操作是赋值运算,所以上述程序常被写成: void str_copy ( char target[],char source[]) { int i = 0; while ( ( target[i] = source[i] ) != '\0' ) i++; } while循环及后面的代码可以进一步简化为: while (target[i] = source[i]) ++i; 因为空字符'\0'的ASCII码的值为0,正好可以作为循环结束条件。 例5.26假定输入的字符串中只包含字母和*。请编写程序将字符串中的前导符*全部删除,字符串中间和后面的*不删除。例如,字符串中的内容为“****A*BC*DEF*G*******”,删除前导*后,字符串中的内容应当是“A*BC*DEF*G*******”。 分析: 首先需要从字符串开始找到不是*的字符,然后从该位置开始,将后面所有字符保存并输出。 /* 程序名称:ex5-26.c 程序功能:字符串变换 */ #include #include #define N 80 int main() { int i=0,k; char a[N]; gets(a); puts(a); while(a[i]=='*') i++; k=i; for(i=0;a[k]!=0;i++,k++) a[i]=a[k]; a[i]='\0'; printf("%s\n",a); system("pause"); return 0; } 程序运行结果: 输入: ****A*BC*DEF*G******* 输出: A*BC*DEF*G******* 5.5多 维 数 组 5.5.1多维数组的定义 多维数组的定义与一维或者二维数组的定义类似:必须指明数组的名称、数据类型和数组的长度。下面定义了一个三维数组: int a1[3][2][4]; 也可以用这种形式定义更高维的数组。高维数组的声明形式语法如下: 数组元素的类型说明符 数组名称 [第一维][ 第二维]…[第N维]; 例如: 用一个数组table,来描述学校各学院、年级和课程的选课人数。 假设学校有10个学院,每个学院的在校生分成4个年级,每个年级需要修50门课程,那么描述这样一个数据结构,可以存储每一门课程的选课人数。 int table[10][4][50]; 图525三维数组的逻辑结构 table[2][2][10]数组元素表示的含义为: 学院索引号为2,年级索引号为2,选修课程索引号为10的人数。 其存储结构如图525所示。利用该结构,可以处理许多问题。如确定一门课程学生的总人数; 某个学院2年级学生选择课程2的人数等问题。 例5.27在定义table数组中寻找和输出每个学院每门课程的选修人数。 for( course = 0; course < 50; course++ ) { for( campuse = 0; campuse < 10; compuse++ ) { stu_sum = 0; for( rank = 0; rank < 4; rank++ ) stu_sum += table[campus][course][rank]; printf( "Campus %d Course: %d: Students Number: %d", campus,course,stu_sum ); } } 在处理多维数组时存在一个隐患: 同一个程序中声明了多个多维数组,那么内存空间很快就会耗尽。因此,在程序中应注意每个数组所需要的内存空间的大小。例如,数组a1是一个三维数组,大小为sizeof(int)×3×2×4,即96字节。由于占用大量内存的原因,三维或更多维数组较少使用。 5.5.2多维数组的初始化 多维数组初始化形式如下: int b[2][2][2] = { 1,2,3,4,5,6,7,8 }; 数值表是一个由逗号分隔的常量表。数组b是由两个2×2的数组构成的存储结构。由于数组在计算机内部的存储是线型的,数组b的元素存储结构如图526所示。 图526三维数组初始化后元素的存储结构 说明: (1) 对于三维数组,整型常数1、整型常数2、整型常数3可以分别看作“深”维(或“页”维)、“行”维、“列”维。可以将三维数组看作一个元素为二维数组的一维数组。三维数组在内存中先按页,再按行,最后按列存放。 (2) 多维数组在三维空间中不能用形象的图形表示。多维数组在内存中排列顺序的规律是: 第一维的下标变化最慢,最右边的下标变化最快。 (3) 多维数组的数组元素的引用: 数组名[下标1] [下标2]…[下标k]。多维数组的数组元素可以在任何相同类型变量可以使用的位置被引用。只是要注意,不要越界。 5.6变 长 数 组 5.6.1不指定维长的数组初始化 使用数组初始化的方法建立如下字符数组: char e1[12] = "read error\n"; char e2[13] = "write error\n"; char e3[18] = "cannot open file\n"; 如果用手工去计算每一条信息的字符数以确定数组的长度,这是件很麻烦的事。所以在定义数组时,可以利用C语言提供的变长数组初始化的方法,让编译系统自动地计算数组的长度。变长数组建立一个不指明长度的、足够大的数组以存放初始化数据。使用这种方法,以上信息可以定义为: char e1[] = "read error\n"; char e2[] = "write error\n"; char e3[] = "cannot open file\n"; 给定上面的初始化,如下语句: printf( "%s has length %d\n",e2,sizeof( e2 ) ); 将打印出: write error has length 13 除了可以减少麻烦外,通过应用变长数组初始化,程序员还可以修改任何信息,而不必担心随时可能发生的计算错误。 5.6.2可变长数组及定义 在C语言中,数组维数必须用常数表达式来声明。而C99扩充了这一局限性,它为数组增加了一个强大的新功能——可变长度(variable length)。在C99中,可以声明这样的数组: 其长度可以用任何有效的表达式来指定,这就是变长数组(variablelength array)。然而,只有本地数组(即带有块域或原型域的数组)可以是可变长度的数组,并要求表达式的值已确定。以下是变长数组的实例: void f( int dim) { char str[ dim ]; /*可变长字符数组str */ /* … */ } 从数组定义的角度出发,上面的例子违反了数组长度必须是常量的原则。C99标准对C语言进行了很好的扩充,使得上述定义合法化。这仅限于动态结构的数组定义,不能使用在全局数组、外部数组等具有静态特征的数据结构中。通过上面的例子可以看出: 数组str的大小用dim表示,由传递给f()的值决定。这样,每调用一次f()均可产生不同长度的局部数组str。 C99增加可变长数组的支持,使得程序可以灵活地在运行时改变数组的长度,这是一个具有广泛适用性的特性。注意,在C99之前标准中,均不支持可变长数组。 5.7数组应用举例 例5.28模拟掷骰子的游戏,验证骰子的6个面出现的概率基本相同。 分析: 在这样的模拟问题中,通常使用随机数函数,随机生成一组数据。对于骰子来说,其6个面对应的数值为1~6,如何将随机数转换成骰子各面对应的数值,是本例题的关键所在。这里选用%求余运算符将随机数变换到0~5的数,即实现了功能,接下来的任务就是计数了。C语言提供了一组随机数函数,可直接在程序中使用,具体的说明参见附录。 /* 程序名称:ex5-28.c 程序功能:模拟掷骰子的游戏 */ #include #include #include #define N 60000 int main() { int ludo[6] = {0}; int i; srand(time(NULL)); /*以现在的系统时间作为随机数的种子来产生随机数*/ for(i = 0; i < N; i++) ludo[rand()%6]++; /*产生随机数的函数*/ for(i = 0; i < 6; i++) printf("第%d面的个数%d,出现概率%8.4f\n",i+1,ludo[i],(double)ludo [i]/N); return 0; } 程序运行结果: 第1面的个数9987,出现概率 0.1664 第2面的个数9925,出现概率 0.1654 第3面的个数9885,出现概率 0.1648 第4面的个数10039,出现概率 0.1673 第5面的个数10147,出现概率 0.1691 第6面的个数10017,出现概率 0.1669 例5.29 统计子串b在母串a中出现的次数,如果母串为asd asasdfg asd as zx67 asd mklo,子串为as,则子串的个数为6。 分析: 母串用i作为循环控制变量,子串用j作为控制变量。对于每一个s[i],扫描其后面的字符是否构成子串。若是,则计数加1; 否则i移至下一个位置。具体做法是: j从i开始,比较母串与子串中的字符。若s[i]及其后续有strlen(t)个字符与子串中对应字符相等,则表示s[i]即为子串的开始; 否则s[i]开头的就不是子串。这时,i移至下一位置继续比较,直到字符串结束。 /* 程序名称:ex5-29.c 程序功能:查找子串 */ #include #include #include int count(char [],char []); int main() { char a[80],b[10]; int k; gets(a); gets(b); k=count(a,b); if(k==0) printf("没有找到!\n"); else printf("%d\n",k); system("pause"); return 0; } int count(char s[],char t[]) { int i,j,k,m=0; for(i=0;s[i]!='\0';i++) { k=0; for(j=i;s[j]==t[k]&&k #include #define N 3 int main() { int i,j,a[N][N],b[N][N]; for(i=0;i #include #include #define N 3 #define M 10 void sort(char x[][M],int); int main() { int i,j; char a[N][M],t[M]; for(i=0;i #include #include void fun(char str[ ]) { int i,j; for (i=0,j=0;str[i];i++) if (!isalpha(str[i])) str[j++]=str[i]; /*isalpha()判断是否是字母的函数*/ str[j]='\0'; } int main() { char str[100]="Current date is Thu 02-12-2008."; fun(str); printf("%s\n",str); system("pause"); return 0; } A. 02122008B. 02122008 C. Current date is ThuD. Current date is Sat 02122008. (6) 若对数组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长度长 (7) 已知下列程序段: char a[3],b[]="Hello"; a=b; printf("%s",a); 则()。 A. 运行后将输出HelloB. 运行后将输出He C. 运行后将输出HelD. 编译出错 (8) 下列程序的运行结果为()。 #include #include int main() { char a[]="morning"; int i,j=0; for(i=1; i<7; i++) if(a[j] #include int main() { int i,t[][3]={9,8,7,6,5,4,3,2,1}; for(i=0;i<3;i++) printf("%d ",t[2-i][i]); system("pause"); return 0; } 程序运行的结果是()。 A. 3 5 7B. 7 5 3 C. 3 6 9D. 7 5 1 (10) 下面二维数组定义并初始化中错误的是()。 A. int x[4][3]={{1,2,3},{4,5,6},{7,8,9},{10,11,12}}; B. int x[4][]={{1,2,3},{4,5,6},{7,8,9},{10,11,12}}; C. int x[][3]={{1},{2},{3,4,5}}; D. int x[][3]={1,2,3,4}; (11) 以下程序运行的结果是()。 #include #include #include int main() { char str[][20]={"One*World","One*Dream!"}; printf("%d,%s\n",strlen(str[1]),str[1]); system("pause"); return 0; } A. 10,One*Dream!B. 11,One*Dream! C. 9,One*WorldD. 10,One*World 2. 填空题 (1) 若有int a[10]; 则数组a的第一个元素的下标是,最后一个元素是。 (2) 写出如下数组变量的声明: ① 一个含有100个浮点数的数组realArray 。 ② 一个含有16个字符型数据的数组strArray。 ③ 一个最多含有 1000个整型数据的数组intArray。 (3) 在C语言中,确定某一数据所需的存储字节数。 (4) 设有数组定义: char array [ ]="China"; 则数组array所占的空间为。 (5) 设有数组定义: char a [ 12]="Nanjing"; 则数组a所占的空间为。 (6) 字符串"ab\n012\\\""的长度为。 3. 阅读程序,并写出程序的运行结果 (1) 从键盘输入: aa bb cc dd 表示回车,则下面程序的运行结果是。 #include #include int main() { char a1[6],a2[6],a3[6],a4[6]; scanf("%s%s",a1,a2); gets(a3); gets(a4); puts(a1); puts(a2); puts(a3); puts(a4); system("pause"); return 0; } (2) 从键盘输入: ab c def 表示回车,则下面程序的运行结果是。 #include #include #define N 6 int main() { char c[N]; int i=0; for(;i(表示回车),则下面程序的运行结果是。 #include #include int main() { char s[80],c='a'; int i=0; scanf("%s",s); while(s[i]!='\0') { if(s[i]==c) s[i]=s[i]-32; else if(s[i]==c-32) s[i]=s[i]+32; i++; } puts(s); system("pause"); return 0; } (4) 从键盘输入18时,下面程序的运行结果是。 //本程序求输入数的二进制数,结果存放在a数组中 #include #include int main() { int x,y,i,a[8],j,u,v; scanf("%d",&x); y=x;i=0; do{ u=y/2; a[i]=y%2; i++;y=u; }while(y>=1); for(j=i-1;j>=0;j--) printf("%d",a[j]); system("pause"); return 0; } (5) 下面程序是将k值按数组中原来的升序找到合适的位置插入,其运行结果是。 #include #include int main() { int i=1,n=3,j,k=3; int a[5]={1,4,5}; while(i<=n&&k>a[i])i++; for(j=n-1;j>=i;j--) a[j+1]=a[j]; a[i]=k; for(i=0;i<=n;i++) printf("%3d",a[i]); system("pause"); return 0; } (6) 下面程序的运行结果是。 #include #include int main() { int i,j; int big[8][8],large[25][12]; for (i = 0; i < 8; i++) for (j = 0; j < 8; j++) big[i][j] = i * j; for (i = 0; i < 25; i++) for (j = 0; j < 12; j++) large[i][j] = i + j; big[2][6] = large[24][10] * 22; big[2][2] = 5; big[big[2][2]][big[2][2]] = 177; for (i = 0; i < 8; i++) { for (j = 0; j < 8; j++) printf("%5d",big[i][j]); printf("\n"); } system("pause"); return 0; } (7) 下面程序运行的结果是。 #include #include #include int main() { char s[4][20]={"JAVA","C#","PHP","Objective-C"}; int i,k=0; for(i=1;i<4;i++) if(strcmp(s[k],s[i])<0) k=i; puts(s[k]); system("pause"); return 0; } 4. 程序填空题 (1) 以下程序用来检查二维数组是否对称(即: 对所有i,j都有a[i][j]=a[j][i]),请填空使程序完整。 #include #include int main() { int a[4][4]={1,2,3,4,2,2,5,6,3,5,3,7,8,6,7,4}; int i,j,found=0; for(j=0; j<4; j++){ for(i=0; i<4; i++) if() { found = ; break; } if(found) break; } if(found) printf("不对称\n"); else printf("对称\n"); system("pause"); return 0; } (2) 以下程序用来输入5个整数,并将其存放在数组中; 再找出最大数与最小数所在的下标位置,将两者对调; 然后输出调整后的5个数。请填空使程序完整。 #include #include int main() { int a[5],t,i,maxi,mini; for(i=0; i<5; i++) scanf("%d",&a[i]); mini = maxi =; for(i=1; i<5; i++) { if() mini = i; if(a[i]>a[maxi]) ; } printf("最小数的位置是:%3d\n",mini); printf("最大数的位置是:%3d\n",maxi); t = a[maxi]; ; a[mini] = t; printf("调整后的数为: "); for(i = 0; i < 5; i++) printf("%d ",a[i]); printf("\n"); system("pause"); return 0; } (3) 建立函数arraycopy(),将数组a[]的内容复制到数组b[]中。请填空使程序完整。 #include #include int arraycopy() { int i=0; while(a[i]!=-999) { ; i++; } b[i]=; return 0; } int main() { int a[]={1,2,3,4,5,6,7,8,9,10,-999}; int b[100],i = 0; ; while(b[i]!=-999) printf("%d ",); system("pause"); return 0; } (4) 以下程序将数字字符串转换成数值。请填空使程序完整。 #include #include int main() { char ch[]="600"; int a,s=0; for(a=0;;a++) if(ch[a]>='0'&&ch[a]<='9') s=10*s+ch[a]-'0'; printf("\n%d",s); system("pause"); return 0; } 5. 编程题 (1) 用循环将a[3][4]的第一行与第三行对调。 a 0297 51368 271113→271113 51368 0297 (2) 编程实现如下形式的数字。 1 0 0 0 0 0 2 1 0 0 0 0 3 2 1 0 0 0 4 3 2 1 0 0 5 4 3 2 1 0 6 5 4 3 2 1 (3) 编程输出n阶左上拐矩阵,如n=5时有: 11111 12222 12333 12344 12345 (4) 编程输出如下n阶蛇形矩阵,如n=5时有: 157621 1614853 22171394 2321181210 2524201911 (5) 输入正整数m和k,编写程序: 将大于m且紧靠m的k个非素数,保存在数组中,然后输出。 (6) 编写程序,计算具有NROWS行和NCOLS列的二维数组中指定列的平均值以及数组各行的和的最小值。 (7) 输入一段文字,统计文字中指定字符的个数。 (8) 假定输入的字符串中只包含字母和*号。请编写函数fun(),它的功能是: 除了字符串前导的*之外,将字符串中其他*号全部删除。在编写函数时,不得使用C语言提供的字符串函数。 例如,若字符串中的内容为****A*BC*DEF*G*******,删除后,字符串中的内容则应当是****ABCDEFG。 (9) 编写程序,寻找输入字符串中字符ASCII码值大的字符,并统计其位置和出现的次数。