610 likes | 760 Views
高级语言程序设计. 第七章 数组 ( Arrays ) 莆田学院 《C 语言程序设计 》 精品课程组 2005 年 5 月 制作. 目录. 一、概述 二、数组的定义 三、数组的存储结构 四、数组的初始化 五、数组的引用 六、与数值数组有关的常用算法 七、字符数组和字符串 作业. 一、概述. C 的数据类型: P38 ◎ 基本类(简单类) —— 字符型 / 整型 / 实型 / 枚举型 ◎构造类(组合类) —— 数组 / 结构体 / 共用体 ◎指针类 ◎空类型( void ). 对于变量
E N D
高级语言程序设计 第七章 数组(Arrays) 莆田学院《C语言程序设计》精品课程组 2005年5月 制作
目录 • 一、概述 • 二、数组的定义 • 三、数组的存储结构 • 四、数组的初始化 • 五、数组的引用 • 六、与数值数组有关的常用算法 • 七、字符数组和字符串 • 作业
一、概述 • C的数据类型:P38 ◎基本类(简单类)——字符型/整型/实型/枚举型 ◎构造类(组合类)——数组/结构体/共用体 ◎指针类 ◎空类型(void)
对于变量 • 基本类型——单个出现的变量,每个变量可以代表一个确定的数据(变量值),且具有一定属性。 如static int x,y; 但变量间不存在确定的相互关系。 • 构造类型——由基本类型按一定规则组成。其中: 数组:由一组有序数据(数组元素)组成。 每个元素:有相同类型,统一数组名; 用下标(index)确定其顺序;但可以取各自值。 如static int a[5]; 其中:[ ]表示a是个数组,而不是一个简单变量a。 5表示该数组共有5个元素 元素编号从0开始,a[0]表示第1个元素,a[4]表示第5个元素(最后一个)
数组的用处很多。简单例子:要读入某班全体50位同学某科学习成绩,然后进行简单处理(求平均成绩、最高分、最低分……)数组的用处很多。简单例子:要读入某班全体50位同学某科学习成绩,然后进行简单处理(求平均成绩、最高分、最低分……) • 若用简单变量,需50个不同变量名,要用很多个scanf命令。如 int score1,score2,…score50; scanf(“%d,%d,%d”,&score1,&score2,&score3); scanf(“%d,%d,%d”,&score4,&score5,&score6); • 而用数组,可共用一个scanf命令,并利用循环结构读取。 int score[50],i; for (i=0;i<50;i++) scanf(“%d”,&score(i));
#define NUM 50 main() { int i,score[NUM],highest; float sum=0,average; clrscr(); /*依次读入全班同学分数*/ for (i=0;i<NUM;i++) { printf("请输入第%d位同学的成绩:",i+1); scanf("%d",&score[i]); } /*公布全班同学分数*/ printf("\n全班同学成绩公布如下:\n"); for (i=0;i<NUM;i++) { printf("%d号同学:%5d\n ",i+1,score[i]); if (i%10==9) printf("\n"); } } /*求平均成绩并显示出来*/ for (i=0;i<NUM;i++) sum+=score[i]; average=sum/NUM; printf("\n全班平均成绩是:%.1f\n",average); /*求最高分并显示出来*/ highest=score[0]; for (i=0;i<NUM;i++) if (score[i]>highest) highest=score[i]; printf("\n最高分是:%d\n",highest); } 示例程序如下:
二、数组的定义 • 同变量一样,数组也必须“先定义,后使用”。 • 定义内容: □数组名(同变量名:字母、下划线、数字;字母或下划线开头;长度≤32) □类型(存储属性/数据类型) □大小(维数/元素个数) • 定义的一般形式: 存储属性 数据类型数组名[常量表达式][常量表达式]… 注意:数组长度必须是常量表达式—— 常量或符号常量,其值必须为正,不能为变量。 数组长度 (数组元素最大个数)
错误: int n=5; int a[n]; 正确: int a[10],b[5][4]; char name[8],ch[2][3]; static float x[8*2+1],table[2][3][4]; #define NUM 40; int a[NUM],b[NUM+2]; 正确: #define N 5 int a[N];
一维数组和多维数组 特别注意 数组名表示每个数组的首地址,即: a 表示 &a[0] b 表示 &b[0][0] • 一维数组 用一个下标来确定各元素在数组中的顺序。可用排列成一行的元素组来表示。 如 int a[5]; (右上图) • 二维数组 用两个下标来确定各元素在数组中的顺序。可用排列成i×j列的元素组来表示。 如 int b[2][3]; (右下图) • n维数组 用n个下标来确定各元素在数组中的顺序。 如 int c[3][2][4]; n≥3时,维数组无法在平面上表示其各元素的位置。 n维数组的元素总数等于各维长度之积。
more about multidimensional arrays… 为了便于理解,可以把多维数组看成一维数组的扩展: • 二维数组(n×m)可以看成n行m个元素的一维数组; • 三维数组(p×n×m)可以看成p页n行m个元素的一维数组,即p页二维数组(n×m); • 四维数组(v×p×n×m)可以看成v册p页n行m个元素的一维数组,即v册三维数组(p×n×m) • 以此类推。
三、数组的存储结构 • 根据数组的数据类型,为每一元素安排相同长度的存储单元。 • 根据数组的存储属性,将其安排在内存动态、静态存储区或寄存器区。
四、数组的初始化(赋初值) • 旧版标准:只允许对外部或静态的数组初始化 • 新版标准:也允许对auto数组初始化(只能用常量表达式)。 • 一般形式: int a[5]={1,2,3,4,5}; int b[2][3]={{1,2,3},{4,5,6}}; 或 int b[2][3]={1,2,3,4,5,6}; 【演示】设置watch,观看下列数组初值情况: int a[2][3]; static b[2][3]; int c[2][3]={1,2};
简略形式: 1、省略第一维数组大小。如: int a[ ]={…},b[][3]={…}; 2、省略元素值。如: int x[6]={1,2,3,4}; (x[4]、x[5]自动用0补足) int a[5]={0}; int b[3][2]={0}; (全部元素初始 化为0)
【例一】以下程序的运行结果是什么? main() { int a[][3]={{1,2,3},{4,5},{6},{0}}; printf("%d,%d,%d\n",a[1][1],a[2][1],a[3][1]); } 1 2 3 4 5 0 6 0 0 0 0 0 1 2 3 4 5 6 7 0 0 结果:5, 0, 0 【例二】若int a[ ][3]={1,2,3,4,5,6,7},则a数组的第一维大小是多少? 说明:静态/外部数组未初始化,默认初值是0(数值)或空格(字符) auto数组未初始化,初值为某个随机数。
五、数组的引用(使用数组元素) • 原则:先定义后引用 • 引用形式: 数组名[下标] • 只能逐个引用数组元素,不能一次引用整个数组。 • 引用数组元素要注意下标不要出界(编译程序不检查是否“出界”)。 • 每个数组元素均可按一个简单变量的方式进行处理(如参加运算、赋值等)。
(设已有定义int a[3][6],x,y ,i=0,j=0;) a[2][3]=4; /*下标为常量*/ scanf(“%d”,&a[i][j]); y=sqrt(a[2][3]); a[i][j]=20; /*下标为 int 型变量i,j) */ a[i+1][j*3+1] = 1; /*下标为一个复杂的整型表达式*/ 错误: a[2,3]=1 调试时,可将数组名或数组元素名设置为watch进行跟踪.
讨论:如果下标值小于0或超过数组长度时会出现什么情况?讨论:如果下标值小于0或超过数组长度时会出现什么情况? 例: main() { int a=1,b[5],c=2,i; for (i=0;i<=5;i++) b[i]=i+1; …… } 1 2 3 4 5 6 1 0 2 3 4 5 6 运行程序或单步执行观察变量变化情况可以看到,变量c的值因数组越界而被悄悄破坏了(可能产生严重的错误后果!)
法一 int i,a[100]; for(i=0;i<100;i++) a[i]=i; 法二 int i,a[100]; for(i=0;i<=99;i++) a[i]=i; 数组常用的循环形式 C程序中常用的是第一种方法。注意在此法中,循环变量的终值是“小于”而非“等于”数组长度!否则将导致“越界”的发生。
/*错误编程*/ main() { int a[4]={1,2,3,4],b[4]; b=a; } 解决方法 法1:逐个元素赋值 b[0]=a[0]; b[1]=a[1]; b[2]=a[2]; b[3]=a[3]; 法2:通过循环赋值 int i; for (i=0;i<4;i++) b[i]=a[i]; 如何使两个数组的值相等: 原因: 数组名表示数组的首地址,其值不可改变!
【分析】这类题的元素值排列很有规律,所以一般要从分析行数 i、列数 j与元素值的关系着手。分析下图可知,当 i<j 时的各元素值均为0;而 i>=j时,元素值随行数 i 增加而增加,随列数 j 增加而减小,这样就很容易得出其元素值与 i,j 的关系是 i+1-j。 应用示例: 下列程序的功能是显示如下图形,请填空。 main() { int a[5][5],i,j; clrscr(); for (i=0;i<5;i++) { for (j=0;j<5;j++) { if ( 【1】 ) a[i][j]=0; else a[i][j]=【2】; printf("%3d",a[i][j]); } printf("\n"); } } 1 0 0 0 0 2 1 0 0 0 3 2 1 0 0 4 3 2 1 0 5 4 3 2 1 i<j i+1-j a[5][5]分析: a00 a01 a02 a03 a04 a10 a11 a12 a13 a14 a20 a21 a22 a23 a24 a30 a31 a32 a33 a34 a40 a41 a42 a43 a44
应用示例: 分析以下程序的功能是什么? #include <stdio.h> main(void){ int c,i,cs[10]={0}; clrscr(); while ((c=getchar())!=EOF) if(c>='0'&&c<='9') cs[c-'0']++; for(i=0;i<10;i++) printf("Num of %d=%d\n",i,cs[i]); putchar('\n'); getch(); } 原题要求:写一个程序,统计由键盘输入的各个数字字符出现的次数(按Ctrl+Z结束)。
六、与数值数组有关的常用算法 • 排序:起泡法/选择法/插入法 • 查找:顺序查找法/折半查找法 • 矩阵运算 1、常用排序算法 ①起泡法(冒泡法/气泡法) P124 有n个杂乱无序的数,要求将这n个数从小到大(或从大到小)排序后输出。
13 35 13 -8 13 35 72 21 72 35 -8 72 21 21 21 -8 72 -8 35 21 -8 72 13 13 35 这种排序方法之所以叫“冒泡法”,是因为在排序过程中,较小的数象气泡一样逐渐往前冒(向上冒),大的数逐渐向后沉,最终完成排序。 【例一】排序原理示意如下(n=5): 排序前 第1轮(i=1) 第2轮(i=2) 第3轮(i=3) 第4轮(i=4) • 共需进行n-1 =4轮; • 从第1个开始,两两比较,大者交换到后面(右边); • 每轮从第1个比到第n-i个。
流程图和程序如下: #define N 5 main() { int i,j,t,a[N+1]; for (i=1;i<=N;i++) scanf("%d",&a[i]); for (i=1;i<=N-1;i++) for (j=1;j<=N-i;j++) if (a[j]>a[j+1]) { t=a[j]; a[j]=a[j+1]; a[j+1]=t; } for (i=1;i<=N;i++) printf("%d ",a[i]); }
②选择法 • 从算法优化的角度对“冒泡法”进行改进。 • 冒泡法每一轮都要将数组中的数两两比较,并根据大小交换之——效率低。 • 选择法改进处:两两比较后并不马上交换,而是找到最小数后记下其下标。在一轮比较完毕后,再将最小的数一次交换到位。——比较次数不变,交换次数减少。
流程图和程序如下: 法1:从左到右依次从小到大排放 #define N 5 main() { int i,j,t, ,p a[N+1] ={0,5,10,-7,3,7}; for (i=1;i<=N-1;i++) { p=i; for (j=i+1;j<=N;j++) if (a[p]>a[j]) p=j; if(p!=i) { t=a[p];a[p]=a[i]; a[i]=t; } } for (i=1;i<=N;i++) printf("%d ",a[i]); printf("\n"); }
流程图和程序如下: 法2:从右到左依次从大到小排放 #define N 5 main() { int i,j,t,p,a[N+1]={0,5,10,-7,3,7}; for (i=1;i<=N-1;i++) { p=N-i+1; for (j=1;j<=N-i;j++) if (a[j]>a[p]) p=j; if(p!=N-i+1) {t=a[p];a[p]=a[N-i+1];a[N-i+1]=t;} } for (i=1;i<=N;i++) printf("%d,",a[i]); printf("\n"); }
③插入法 main() { int a[5]={4,7,2,5,8}; int i,j,m; for (i=1;i<5;i++) { m=a[i]; j=i-1; while (j>=0&&m>a[j]) { a[j+1]=a[j]; j--; } a[j+1]=m; } for (i=0;i<5;i++) printf("%d",a[i]); printf("\n"); } 如果有N个元素,也是要比较N-1轮,但每轮取第i个(i从1开始)元素的值为暂存值m,然后与左边的各数(从j=i-1开始)比较一直到左边第一个(j=0)为止。如果m比左边大,就让左边的值右移,最后将该轮的第i个数插到左边的合适位置(如果它比较大的话)。 【讨论】如果要求升序(结果为24578)呢? while (j>=0&&m<a[j])
2.查找 main() { int a[8]={25,57,48,37,12,92,86,33}; int i,x; clrscr( ); printf("请输入要查找的数 x="); scanf("%d",&x); for(i=0;i<8;i++) if(x==a[i]) { printf("找到了!是第%d个元素。\n",i); break; } if(i==8) printf("没找到!\n"); } ①顺序查找法 【例一】
2.查找 main() { int a[9]={25,57,48,37,12,92,86,33}; int i,x; printf("请输入要查找的数 x="); scanf("%d",&x); a[8]=x; i=0; while(a[i]!=x) i++; if(i<8) printf("找到了!是第%d个元素。\n",i); else printf("没找到!\n"); } ①顺序查找法 【例二】 快速顺序查找法 • 讨论: • 为什么数组长度要设为9? • 为什么要用a[8]=x? • 去掉a[8]=x行不行?
②折半查找法 前提:数据已按一定规律(升或降序)排列好。 思路:先检索当中的一个数据是否所需,如不是,判断要找的数据在哪一边,缩小范围后再按同样方法继续检索,直到找到或找遍。 算法:设要找的数为x,n+1个数据已排好序存放在数组a中。 设low=0,high=n mid=(low+high)/2 if (x==a[mid]) 找到了; else if (x>a[mid]) 说明x在右边,让low=mid+1; else 说明x在左边,让high=mid-1 重复b和c两步操作,直到x=mid(找到)或low>high(找遍了)为止。
main() { int a[9]={6,12,18,42,44,52,67,94,99}; int low=0,mid,high=8,x; clrscr(); printf("请输入要查找的数 x="); scanf("%d",&x); do { mid=(low+high)/2; if (x==a[mid]) { printf("找到了!是第%d号元素。\n",mid);break; } else if(x>a[mid]) low=mid+1; else high=mid-1; } while (low<=high); if (low>high) printf("没找到!\n"); }
3、矩阵运算 main() { int i,j,x,y,max; int a[][4]={3,5,8,1,6,9,7,12,-6}; max=a[0][0]; for (i=0;i<3;i++) for (j=0;j<4;j++) if (a[i][j]>max) { max=a[i][j]; x=i; y=j; } printf("max is a[%d][%d]=%d\n",x,y,max); } 【例一】有一3×4矩阵,编程求其元素最大值并输出其行、列号。 3 5 8 1 6 9 7 12 -6 0 0 0 要点:用两重循环遍历所有元素。 【讨论】 如果求最小值?
【例二】求矩阵a、b乘积,结果存入矩阵c中并按矩阵形式输出。【例二】求矩阵a、b乘积,结果存入矩阵c中并按矩阵形式输出。 【说明】矩阵相乘的规则:一个m×p矩阵A=(aij),可以右乘一个p×n矩阵B(bij),得到一个m×n矩阵C(cij),其中(i=1,2,…,m,j=1,2,…,n) :
main() { int a[3][2]={2,-1,-4,0,3,1}; int b[2][2]={7,-9,-8,10}; int i,j,k,s,c[3][2]; for(i=0;i<3;i++) for(j=0;j<2;j++) { for(k=s=0;k<2;k++) s+=a[i][k]*b[k][j]; c[i][j]=s; } for(i=0;i<3;i++) { for(j=0;j<2;j++) printf("%6d",c[i][j]); printf("\n"); } }
七、字符数组和字符串 P130 【字符数组】存放字符(每个数组元素存放一个字符) 1、字符数组的定义 如: char a[10]; char a[2][3]; 字符数组——各个元素分别存放一个字符的数组。 字 符 串—— “……” (字符串常量)以“\0”结尾。 实际使用中,可以将字符串看成是特殊的字符数组(以“\0”为最后一个元素值)。如字符串”ab12”可以看成: int a[5]={‘a’,’b’,’1’,’2’,’\0’};
2、字符数组的初始化 单字符方式 char a[10]={‘A’, ‘B’, ‘C’, ‘D’}; char b[2][3]={ ‘A’, ‘B’, ‘C’, ‘D’, ‘E’,’F’}; 【注意】如果初值个数小于数组长度,则多余的数组元素自动为空字符(’\0’)P130 字符串方式 char a[5]={“ABCD”}; char c[ ]=”ABCD”; char a[2][5]={{‘A’,’B’,’C’,’\0’},{‘x’,’y’,’\0’}}; char a[2][5]={“ABC”,”XY”}; 二维字符数组可以认为由若干个一维字符数组(字符串)组成。
示例 【例一】比较以下字符数组长度是否相同: char a[ ]=”ABCD”; char b[ ]= {“ABCD”}; char c[ ]={‘A’,’B’,’C’,’D’}; 【例二】下列程序段的运行结果是: char a[5]={‘a’,’b’, ’\0’,’d’,’\0’}; printf(“%s”,a); 如果 printf(“%s”,a+1);结果是? 说明:%s的作用是输出一个字符串,直到遇到’\0’为止。
注意:在二维数组中,双下标引用——表示某行某列的某个元素 单下标引用——表示某行的字符串 【例三】分析以下程序的运行结果: main() { char word[3][10]; int i; for (i=0;i<3;i++) scanf("%s",word[i]); printf("%s",word[i-2]); } 运行时,输入: 12345 abcdef ABCDEFG 结果:abcdef word[i]表示第i+1行word元素首地址(二维数组用单下标表示某行的字符串)
3、字符数组的输入(赋值) 如果定义有 char a[10]; 除了直接在定义时初始化,定义后赋值的方法有: 法1:逐个元素赋值 a[0]=’A’,a[1]=’X’; 法2:循环+scanf(“%c”, &…); for (i=0;i<10;i++) scanf(“%c”,&a[i]);
字符数组的输入(赋值) 法3:scanf(“%s”, 字符数组名); 用于从键盘接收不带空格的字符串。如: scanf(“%s”,a);/*数组名前不可加&号*/ 此法可自动在所输入的字符串末尾加上’\0’。但输入的字符串中不能有空格。(C语言规定scanf用%s时,以空格或回车符作为字符串的间隔符。所以,上例中,如果从键盘输入“Computer”然后回车,则数组a存放的是”Computer\0”。如果输入“Turbo C“,数组a中实际只存放”Turbo\0”)。 法4:gets(字符数组名); 用于从键盘接收一个任意字符串。 gets(a)执行时,将一直读取用户从键盘输入的所有字符,直到遇到回车符(’\n’)为止。成功:返回数组a首地址,否则返回NULL。gets(a)亦会自动在字符串末尾加上’\0’(代替’\n’)。
示例 main() { char name[20]; printf("What’s your name? "); gets(name); printf("Hi,%s,nice to meet you!\n",name); } 运行程序时,从键盘输入:XXX(XXX为考生姓名) 结果:Hi,XXX ,nice to meet you! (XXX为考生姓名)
示例 main() { char a[10]; gets(a); printf("%s\n",a+2); } 运行程序时,从键盘输入:abc defg 结果:c defg
4、字符数组的输出 法1:逐个元素输出 循环+printf(“%c”,…); 法2:字符串输出 printf(“%s”,字符数组名); 法3:字符串输出 puts(字符数组名); 将一个以NULL(’\0’)结尾的字符串输出到屏幕上,并自动换行。
示例 已知函数isaplha(ch)的功能是判断自变量ch是否为字母,若是,该函数值为1,否则为0。以下程序的输出结果是。 #include <stdio.h> #include <ctype.h> void fun(char str[ ]) { int i,j; for (i=0,j=0;str[i];i++) if (!isalpha(str[i])) str[j++]=str[i]; str[j]='\0'; } main() { char str[100]="Current date is Sat 12-30-2000."; clrscr(); fun(str); printf("%s\n",str); } 结果: 12-30-2000
示例讨论 • 常用s[i] 作循环条件,当循环(i递增)到字符串结束处(’\0’)便自动结束循环。 • 典型结构:如果s是字符串,则下列结构可以将s中不符合 条件的字符删去。 int i,j; for (i=0,j=0;str[i];i++) if (条件) str[j++]=str[i]; str[j]='\0’; • 思考:如果上题中去掉if (!isalpha(str[i]))中的!号,结果是什么? • 如果将isalpha()函数改为islower、isupper、isdigit,结果是什么? (教材P383-384) (CurrentdateisSat) (urrentdateisat、CS、12302000)
5、字符串运算函数 ①gets( )和puts( ) 字符串输入/输出函数 ②strcpy( )字符串考贝函数 (包含在string.h中,下同) 形式:strcpy(目的字符数组,源字符串); 作用:将源字符串考贝到目的字符数组中,直到遇到源字符串的终止符’\0’为止。 函数返回值:目的字符数组的地址。 注意:目的字符数组要定义得足够大。 若要将一个字符串常量或从某一首地址开始的字符串复制给别的数组,只能用本函数或指针。
示例 main( ) { char a[ ]="abcde"; char b[10]; b="abcde"; b=a; } /*编译出错*/ /*编译出错*/ 【讨论】编译出错原因何在? a,b都是两个数组定义时分配的内存存储单元首地址,是个常量,不能改变(不能赋值)。
示例 #include “string.h” main( ) { char s[10],sp[ ]=”HELLO”; strcpy(s,sp); s[0]=’h’; s[6]=’!’; puts(s); } 【讨论】 结果为什么不是:hELLO! 如果让s[5]=’!’,结果又会如何? 结果: hELLO
如果只需考贝字符串的一部分… 如果是考贝字符串的一部分,可用函数 strncpy(目的字符数组,源字符串,考贝字符数) #include “string.h” main( ) { char s[ ]=”This is a source string.”, b[20]; strncpy(b,s,16); b[16]=’\0’; printf(“%s\n”,b); } 结果:This is a source