文档库 最新最全的文档下载
当前位置:文档库 › c语言程序设计

c语言程序设计

本文由hjfeng1226贡献
doc文档可能在WAP端浏览体验不佳。建议您优先选择TXT,或下载源文件到本机查看。
1.编写一个 C 程序 .
#define PI 3.1416 main() { float Radius,Area; scanf(%f,&Radius); /*输入半径的值*/ Area=PI*Radius*Radius; printf(%f\n,Area); /*输出圆的面积*/ } 1.一个 C 语言程序,通常由带有#号的编译预处理语句开始。关于预处理我们在以后介 绍,这里的#define PI 3.1415926 相当于 PI 代表 3.1416,下面在程序中遇到 PI,我们就 用 3.1416 替代一下。 在以后的程序中, 在学习预处理之前, 我们都将不使用预处理语句。 2.main() 任何一个完整的程序都需要 main(),这是一个函数,具体什么是函数,以后再 讲,这儿你就要记住就行。后面有一对{}把所有的语句都括在里面,表明那些语句都属 于 main()里面。程序运行时从这个左大括号开始。 3.{}里面的 4 行语句大家应该都能明白,先定义两个变量,一个代表半径,一个代表面 积,然后输入半径的值,然后求面积,最后在屏幕上输出面积。程序到 main()的那对{} 的 右 大 括 号 结 束 。 求 面 积 的 语 句 Area=PI*Radius*Radius; 相 当 于 Area=3.1416*Radius*Radius;(完全用 3.1416 替代 PI)。 具体程序从编写到运行得到结果的步骤为: 1.双击 tc.exe,进入 Turbo C 2.0 编译界面 2.ALT+E 进入编辑模式 3.书写程序 4.F2 存储程序(也可进入 File 菜单,选择 save),第一次存储需要写上程序名称(*.C),回 车 5.ALT+F9 编译,如果有错误和警告,光标停留在错误行,回车进行修改,修改后,回 到 4;没有错,下一步 6.CTRL+F9 连接和运行程序 7.用 ALT+F5 查看程序运行结果,任意键返回程序 如何打开一个已有的 C 文件: 1.双击 tc.exe,进入 Turbo C 2.0 编译界面 2.F3 进入 load 状态,找到所要打开文件的目录,找到文件,回车;后面都一样。 具体的有哪些快捷键及其它们的作用,请查看第一节概述。 说明: 1.必须在程序的最开始部分定义所有用到的变量,例如这里的 Area,Radius。 2.变量的命名要尽量有意义,如用代表该意思的英文单词、或者是汉语拼音,例如这里 的 Radius,Area,绝对禁止用毫无干系的字母,如 a,b,c。例如下面的程序,虽然意思和
上面的一样,但是看上去意思不明朗,时间长了,很可能忘记程序本身的意思。对于仅 仅是控制程序运行,不代表实际意思时,可以用一些简单字母。 main() { float a,b; scanf(%f,&a); b=3.1416*a*a; printf(%f\n,b); } 3.采用层次书写程序的格式,要有合理的缩进,必要的时候要有空行,一行只书写一个 语句。所有语句尽量不分行,除非太长(分行时变量、运算符,格式字符等等不能拆开), 例如下面两个程序看起来就不好看了,虽然它们

的功能和前面是一样的。 main() {float Radius,Area;scanf(%f,&Radius); Area=3.1416*Radius*Radius;printf(%f\n,Area);} main() { float Radius,Area; scanf(%f, %Radius); Area=3.1416*Radius *Radius; printf(%f\n, Area); } 4.程序在适当的地方要用/*……*/注释, 它的意思表示在/* */里面的所有字符都不参加编 译。因为一个较大的程序,经过一段时间,有些地方可能连编程者都忘记了,增加注释 可以帮助恢复记忆,调试程序时,也容易找出错误。注释也可以分行写。 5.在书写{}时要对齐。虽然不对应也不影响程序运行,但对齐后方便以后检查程序,也 是为了美观,特别是后面学到流程控制时,{}一定要对齐。 程序设计方法: 1.从问题的全局出发,写出一个概括性的抽象的描述。 2.定义变量,选取函数,确定算法。算法这个东西不好说,遇到的问题多了,自然就会 形成自己一整套的算法。 3.按照解决问题的顺序把语句和函数在 main()里面堆砌起来。 一个好的 C 程序员应该做到: 1.在运行程序之前存盘 2.所有在程序中用到的常量都用预处理语句在程序开头定义 3.所有在程序中用到的函数都在程序开头声明 4.头文件的#ifndef 5.变量名和函数名使用有意思的英文单词或汉语拼音
6.尽量少用全局变量或不用全局变量 7.采用层次的书写程序格式,对 for,while,if_else,do_while,switch_case 等控制语句或他们 的多重嵌套,采用缩格结构 8.所有对应的{}都对齐 9.尽量用 for,而不用 while 做记数循环 10.尽量不用 goto 语句 11.一个函数不宜处理太多的功能,保持函数的小型化,功能单一化 12.一个函数要保持自己的独立性,如同黑匣子一样,单进单出 13.函数的返回类型不要省略 14.用 malloc()分配内存空间时,以后一定要用 free()释放 15.打开文件后,记住在退出程序前要关闭 16.出错情况的处理 17.写上必要的注释 这里说的是一些基本的,经常遇到的情况,还有其他很多要注意的地方,在实际编程中 都会遇到
2.条件语句. .条件语句
一、if 语句 if(表达式) 语句 1; 如果表达式的值为非 0,则执行语句 1,否则跳过语句继续执行下面的语句。 如果语句 1 有多于一条语句要执行时, 必须使用{和} 把这些语句包括在其中, 此时条件 语句形式为: if(表达式) { 语句体 1; } 例如: if(x>=0) y=x; if(a||b&&c) { z=a+b; c+=z; } 二、if--else 语句 除了可以指定在条件为真时执行某些语句外,还可以在条件为假时执行另外一段代码。 在 C 语句中利用 else 语句来达到这个木的。 if(表达式) 语句 1; else 语句 2; 同样,当语句 1 或语句 2 是多于一个语句时,需要用{}把语句括起来。 例如: if(x>=0) y=x; else y=-x;
三、if--else if--else 结构。 if(表达式 1) 语句 1;

else if(表达式 2) 语句 2; else if(表达式 3) 语句 3; . . . else 语句 n; 这种结构是从上到下逐个对条件进行判断,一旦发现条件满点足就执行与它有关的语句, 并跳过其它剩余阶梯;若没有一个条件满足,则执行最后一个 else 语句 n。最后这个 else 常起着缺省条件的作用。 同样,如果每一个条件中有多于一条语句要执行时,必须使用{和} 把这些语句包括在其中。 条件语句可以嵌套,这种情况经常碰到,但条件嵌套语句容易出错,其原因主要是不知道哪 个 if 对应哪个 else。 例如: if(x>20||x<-10) if(y<=100&&y>x) printf(Good); else printf(Bad); 对于上述情况, Turbo C2.0 规定: else 语句与最近的一个 if 语句匹配, 上例中的 else 与 if(y<=100&&y>x)相匹配。为了使 else 与 if(x>20||x<-10)相匹配, 必须用花括号。如下所 示: if(x>20||x<-10) { if(y<=100&&y>x) printf(Good); } else printf(Bad); 下面举几个例子: 1.输入一个数,如果大于 0,输出 plus;如果是负数,输出 negative;如果正好是 0,则输出 zero。 main() { float num; scanf(%f,&f); if(num>0) printf(plus\n);
else if(num<0) printf(negative\n); else printf(zero\n); } 先定义两个变量,然后输入一个数,然后判断这个数的范围,输出对应的字符串。 2.输入一个数 x,输出 y。其中 y 是 x 的绝对值。 main() { float x,y; scanf(%f,&x); if(x>=0) y=x; else y=-x; printf(%f\n,y); } 程序比较简单,这儿就不分析了。 其实 Trubo C 2.0 把一些常用的功能都写好了,我们只需要使用就可。例如求绝对值的 功能在 C 的库里面就有。看下面的: #include math.h main() { float x,y; scanf(%f,&x); y=fabs(x); /*求 x 的绝对值,然后赋值给 y*/ printf(%f\n,y); } 这个程序和上面的程序完成的功能是一模一样的,都是求绝对值。可以看出,用下面这 个方法比上面就要好一些。由于 fabs()是一个函数,系统自带的,所以在使用它的时候, 我们必须把它所在的库文件 math.h 包含都程序中, 即程序最前面一行。 类似的还有求开 方 sqrt(),求指数幂 exp()等等,这些与数学方面有关的函数都在 math.h 里面。具体哪些 有哪些没有,在什么库里面,可以查看一些手册。 3.输入 x,输出 y,x 和 y 满足关系: x<-5 y=x; -5<=x<1 y=2*x+5; 1<=x<4 y=x+6; x>=4 y=3*x-2; 程序如下: main() { float x,y; scanf(%f,&x);
if(x<-5) y=x; else if(-5<=x&&x<1) y=2*x+5; else if(1<=x&&x<4) y=x+6; else y=3*x-2; printf(%f\n,y); } 这里要说明两点: (1).-5<=x&&x<1 不能写成-5<=x<1;1<=x&&x<4 也不能写成 1<=x<4;在 C 语言中,不能 认识连续不等式。 (2).y=2*x+5 不能写成 y=2x+5;y=3*x-2 也不能写成 y=3x-2;这与我们平时所写的方法不一 样。 4.输入三个数 x,y,z,然后按从大到小输出。 main() { float x,y,z; scanf(%f%f%f,&x,&y,&z); if(x>=y&&x>=z) { printf(%f\t,x); if(y>=z) printf(%f\t%f\n,y,

z); else printf(%f\t%f\n,z,y); } else if(y>=x&&y>=z) { printf(%f\t,y); if(x>=z) printf(%f\t%f\n,x,z); else printf(%f\t%f\n,z,x); } else { printf(%f\t,z); if(x>=y) printf(%f\t%f\n,x,y); else printf(%f\t%f\n,y,x); } } 说明:这是一个典型的 if 语句嵌套结构,如果不使用括号,那么 if 和 else 的对应关系 就乱了。
四、switch--case 语句 在编写程序时, 经常会碰到按不同情况分转的多路问题, 这时可用嵌套 if -else-if 语句来 实现, 但 if-else-if 语句使用不方便, 并且容易出错。对这种情况, Turbo C2.0 提供了一个 开关语句。开关语句格式为: switch(变量) { case 常量 1: 语句 1 或空; case 常量 2: 语句 2 或空; . . . case 常量 n: 语句 n 或空; default: 语句 n+1 或空; } 执行 switch 开关语句时,将变量逐个与 case 后的常量进行比较,若与其中一个相等,则执行 该常量下的语句,若不与任何一个常量相等,则执行 default 后面的语句。 注意: 1.switch 中变量可以是数值,也可以是字符,但必须是整数。 2.可以省略一些 case 和 default。 3.每个 case 或 default 后的语句可以是语句体,但不需要使用{和}括起来。 例如: main() { int x,y; scanf(%d,&x); witch(x) { case 1: y=x+1; break; /*退出开关语句,遇到 break 才退出*/ case 4: y=2*x+1; break; default: y=x--; break; } printf(%d\n,y); }
从上面的例子可以看出,用开关语句编的程序一定可以用 if 语句做。那么在什么情况下 需要用 switch 语句呢?一般在出现比较整的情况下或者能转化成比较整数的情况下使 用。看下面的例子: 例子:一个学生的成绩分成五等,超过 90 分的为'A',80-89 的为'B',70-79 为'C',60-69 为'D',60 分以下为'E'。现在输入一个学生的成绩,输出他的等级。 (1).用 if 语句 main() { float num; char grade; scanf(%d,&num); if(num>=90) grade='A'; else if(num>=80&&num<89) grade='B'; else if(num>=70&&num<79) grade='C'; else if(num>=60&&num<69) grade='D'; else grade='E'; printf(%c,grade); } (2).用 switch 语句 main() { int num; char grade; scanf(%d,&num); num/=10; switch(num) { case 10: case 9: grade='A'; break; case 8: grade='B'; break; case 7: grade='C'; break; case 6: grade='D'; break;
default: grade='E'; break; } printf(%c,grade); } 说明一点,并不是每个 case 里面有都语句,有时侯里面是空的,就好象这一题。switch 语句执行的顺序是从第一 case 判断,如果正确就往下执行,直到 break;如果不正确, 就执行下一个 case。所以在这里,当成绩是 100 分时,执行 case 10:然后往下执行, grade='A';break;退出。 想想看,这里为什么要用 num/=10;? 假设当程序中有浮点数时怎么办呢?
3.循环语句 循环语句
Turbo C 2.0 提供三种基本的循环语句: for 语句、while 语句和 do-while 语句。 一、循环语句 (一)、for 循环 它的一般形式为: fo

r(<初始化>;<条件表过式>;<增量>) 语句; 初始化总是一个赋值语句,它用来给循环控制变量赋初值;条件表达式是一个关系表达 式,它决定什么时候退出循环;增量定义循环控制变量每循环一次后按什么方式变化。 这三个部分之间用;分开。 例如: for(i=1;i<=10;i++) 语句; 上例中先给 i 赋初值 1,判断 i 是否小于等于 10,若是则执行语句,之后值增加 1。再 重新判断,直到条件为假,即 i>10 时,结束循环。 注意: (1).for 循环中语句可以为语句体,但要用{和}将参加循环的语句括起来。 (2).for 循环中的初始化、条件表达式和增量都是选择项,即可以缺省,但;不能缺省。省 略了初始化,表示不对循环控制变量赋初值。省略了条件表达式,则不做其它处理时便 成为死循环。省略了增量,则不对循环控制变量进行操作,这时可在语句体中加入修改 循环控制变量的语句。 (3).for 循环可以有多层嵌套。 例如: for(;;) 语句; for(i=1;;i+=2) 语句; for(j=5;;) 语句; 这些 for 循环语句都是正确的。 main() { int i,j;
printf(i j\n); for(i=0;i<2;i++) for(j=0;j<3;j++) printf(%d %d\n,i,j); } 输出结果为: ij 00 01 02 10 11 12 用 for 循环求 1+2+……+100 的和: main() { int sn=0,i; for(i=1;i<=100;i++) sn+=i; /*1+2+……+100*/ printf(%d\n,sn); } 从程序可以看出,使用循环语句可以大大简化代码。 (二)、while 循环 它的一般形式为: while(条件) 语句; while 循环表示当条件为真时,便执行语句。直到条件为假才结束循环。并继续执行循 环程序外的后续语句。例如: #include stdio.h main() { char c; c='\0'; /*初始化 c*/ while(c!='\n') /*回车结束循环*/ c=getche(); /*带回显的从键盘接收字符*/ } 上例中,while 循环是以检查 c 是否为回车符开始,因其事先被初始化为空,所以条件 为真,进入循环等待键盘输入字符;一旦输入回车,则 c='\n',条件为假,循环便告结 束。与 for 循环一样,while 循环总是在循环的头部检验条件,这就意味着循环可能什么 也不执行就退出。 注意: (1).在 while 循环体内也允许空语句。
例如: while((c=getche())!='\n'); 这个循环直到键入回车为止。 (2).可以有多层循环嵌套。 (3).语句可以是语句体, 此时必须用{和}括起来。 用 while 循环求 1+2+……+100 的和: main() { int sn=0,i=0; while(++i<=100) sn+=i; /*求 1+2+……+100*/ printf(%d\n,sn); } (三)、do--while 循环 它的一般格式为: do { 语句块; } while(条件); 这个循环与 while 循环的不同在于:它先执行循环中的语句,然后再判断条件是否为真, 如 果为真则继续循环;如果为假,则终止循环。因此,do-while 循环至少要执行一次循环 语句。 同样当有许多语句参加循环时,要用{和}把它们括起来。 用 do--while 循环

求 1+2+……+100 的和: main() { int sn=0,i=1; do sn+=i; /*求 1+2+……+100*/ while(++i<=100); printf(%d\n,sn); } 从上面三个程序看出,使用 for,while 和 do--while 求解同样的问题,基本思路都差不 多,只是在第一次计算时,注意初值。 二、循环控制 (一)、break 语句 break 语句通常用在循环语句和开关语句中。当 break 用于开关语句 switch 中时,可使 程序跳出 switch 而执行 switch 以后的语句;如果没有 break 语句,则将成为一个死循环 而无法退出。break 在 switch 中的用法已在前面介绍开关语句时的例子中碰到,这里不 再举例。 当 break 语句用于 do-while、for、while 循环语句中时,可使程序终止循环而执行循环后 面的语句,通常 break 语句总是与 if 语句联在一起。即满足条件时便跳出循环。
例如: main() { int sn=0,i; for(i=1;i<=100;i++) { if(i==51) break; /*如果 i 等于 51,则跳出循环*/ sn+=i; /*1+2+……+50*/ } printf(%d\n,sn); } 可以看出,最终的结果是 1+2+……+50。因为在 i 等于 51 的时候,就跳出循环了。自己 写写怎样在 while 和 do--while 循环中增加 break 语句。 注意: 1. break 语句对 if-else 的条件语句不起作用。 2. 在多层循环中,一个 break 语句只向外跳一层。 例如: main() { int i,j; printf(i j\n); for(i=0;i<2;i++) for(j=0;j<3;j++) { if(j==2) break; printf(%d %d\n,i,j); } } 输出结果为: ij 00 01 10 11 当 i==0,j==2 时,执行 break 语句,跳出到外层的循环,i 变为 1。 (二)、continue 语句 continue 语句的作用是跳过循环本中剩余的语句而强行执行下一次循环。 continue 语句只用在 for、while、do-while 等循环体中, 常与 if 条件语句一起使用,用来 加速循环。 例如: main() { int sn=0,i;
for(i=1;i<=100;i++) { if(i==51) continue; /*如果 i 等于 51,则结束本次循环*/ sn+=i; /*1+2+……+50+52+……+100*/ } printf(%d\n,sn); } 从程序中可以看出, continue 语句只是当前的值没有执行, 也就是说当前的值跳过去了, 接着执行下次循环。 main() { int i,j; printf(i j\n); for(i=0;i<2;i++) for(j=0;j<3;j++) { if(j==1) continue; printf(%d %d\n,i,j); } } 输出结果为: ij 00 02 10 12 (三)、goto 语句 goto 语句是一种无条件转移语句,与 BASIC 中的 goto 语句相似。goto 语句的使用格式 为: goto 标号; 其中标号是 Turbo C 2.0 中一个有效的标识符,这个标识符加上一个:一起出现在函数内 某处,执行 goto 语句后,程序将跳转到该标号处并执行其后的语句。标号既然是一个 标识符,也就要满足标识符的命名规则。另外标号必须与 goto 语句同处于一个函数中, 但可以不在一个循环层中。通常 goto 语句与 if 条件语句连用,当满足某一条件时,程 序跳到标号处运行。goto 语句通常不用,主要因为它将使程序层次不清,

且不易读,但 在多层嵌套退出时,用 goto 语句则比较合理。 main() { int sn=0,i; for(i=1;i<=100;i++) { if(i==51) goto loop; /*如果 i 等于 51,则跳出循环*/ sn+=i; /*1+2+……+50*/ }
loop: ; printf(%d\n,sn); } 可以看出,这儿的 goto 语句和 break 作用很类似。 这儿的 loop: ; printf(%d\n,sn); 也可以写成 loop: printf(%d\n,sn); main() { int sn=0,i; for(i=1;i<=100;i++) { if(i==51) goto loop; /*如果 i 等于 51,则跳出本次循环*/ sn+=i; /*1+2+……+50+52+……+100*/ loop: ; } printf(%d\n,sn); } 可以看出这儿的 loop 语句和 continue 的作用类似。 但是某些情况下又必须使用 goto 语句,否则会让程序大大臃肿。如: main() { int i,j,k; printf(i j k\n); for(i=0;i<2;i++) for(j=0;j<3;j++) for(k=0;k<3;k++) { if(k==2) goto loop; printf(%d %d %d\n,i,j,k); } loop: ; } 输出结果为: ijk 000 001 如果不使用 goto 语句,而使用 break,continue 语句,应该这样 main() { int i,j,k; printf(i j\n); for(i=0;i<2;i++) {
for(j=0;j<3;j++) { for(k=0;k<3;k++) { if(k==2) break; printf(%d %d %d\n,i,j,k); } if(k==2) break; } if(k==2) break; } } 输出结果为: ijk 000 001 所以在同时跳出多层循环时,应该使用 goto 语句。记住,所有的 goto 语句其实都是可 以用 break,continue 代替的。 下面举几个例子: 1.求两个整数的最大公约数。例如 10 和 15 的最大公约数是 5。 分析:最大公约数一定小于等于最小的那个数一半,同时能被两数整除。 main() { int num1,num2,i,min; scanf(%d%d,&num1,&num2); min=num1 for(i=min/2;i>0;i--) if(num1%i==0&&num2%i==0) break; printf(最大公约数为%d\n,i); } 2.求 1!+2!+……+n!(n<10) main() { int n,i; long temp=1,sn=0; /*从 9!以后,所得的值就超过了 int 范围*/ scanf(%d,&n); for(i=1;i<=n;i++) { temp*=i; sn+=temp; /*如果没有这一步,求的就是 n!*/ } printf(%ld\n,sn);
} 那么想想,如果求 1!+3!+5!+……+n!应该怎么办? 3.判断一个整数是不是素数(素数就是只能被本身和 1 整除的数)。 #include math.h main() { int num,i,flag=0; scanf(%d,&num); for(i=2;i { flag=0; /*标志变量复位*/ if(num%i==0) { flag=1; break; } } if(flag==0) printf(是素数\n); else printf(不是素数\n); } 可以说,在所有的 C 语言书上,都有判断素数的例题。它的编程思想是:把一个变量作 为标志变量,用来标志是不是素数;循环体是从 2 到 sqrt(num),因为如果一个数不是素 数的话,一定能分解成 num=num1*num2,它们中的最小值一定小于 sqrt(num),所以循 环的时候只要到 sqrt(num)就可以了。同时要注意变量复位的问题。
4.数组 数组
数组,顾名思义就是一组同类型的数。 一、数组的声明 声明数组的语法为在数组名后加上用方括号括起来的维数说明。本接仅介绍一维数组。 下面是一个整型数组的例子: int array[10]; 这条语句定义了一个具有 10 个

整型元素的名为 array 的数组。 这些整数在内存中是连续 存储的。数组的大小等于每个元素的大小乘上数组元素的个数。方括号中的维数表达式 可以包含运算符,但其计算结果必须是一个长整型值。这个数组是一维的。 下面这些声明是合法的: int offset[5+3]; float count[5*2+3]; 下面是不合法的: int n=10; int offset[n]; /*在声明时,变量不能作为数组的维数*/ 二、用下标访问数组元素
int offset[10]; 表明该数组是一维数组,里面有 10 个数,它们分别为 offset[0],offset[1],……offset[9]; 千万注意,数组的第一个元素下标从 0 开始。一些刚学编程的人员经常在这儿犯一些错 误。 offset[3]=25; 上面的例子是把 25 赋值给整型数组 offset 的第四个元素。 在赋值的时候,可以使用变量作为数组下标。 main() { int i,offset[10]; for(i=0;i<10;i++) scanf(%d,&offset[i]); for(i=9;i>=0;i--) printf(%d ,offset[i]); printf(\n); } 题目的意思是先输入 10 个整数,存入到数组中,然后反序输出。 三、数组的初始化 前面说了,变量可以在定义的时候初始化,数组也可以。 int array[5]={1,2,3,4,5}; 在定义数组时,可以用放在一对大括号中的初始化表对其进行初始化。初始化值的个数 可以和数组元素个数一样多。 如果初始化的个数多于元素个数,将产生编译错误;如果少于元素个数,其余的元素被 初始化为 0。 如果维数表达式为空时,那么将用初始化值的个数来隐式地指定数组元素的个数,如下 所式: int array[]={1,2,3,4,5}; 这也表明数组 array 元素个数为 5。 main() { int i,array[]={1,3,5,7,9,11}; for(i=0;i<5;i++) printf(%d ,array[i]); printf(\n); } 最终结果为 1 3 5 7 9 四、字符数组 整数和浮点数数组很好理解,在一维数组中,还有一类字符型数组。 char array[5]={'H','E','L','L','O'}; 对于单个字符,必须要用单引号括起来。又由于字符和整型是等价的,所以上面的字符 型数组也可以这样表示: char array[5]={72,69,76,76,79}; /*用对应的 ASCII 码*/ 举一个例子: main() {
int i; char array[5]={'H','E','L','L','O'}; for(i=0;i<5;i++) printf(%d ,array[i]); printf(\n); } 最终的输出结果为 72 69 76 76 79 但是字符型数组和整型数组也有不同的地方,看下面的: char array[]=HELLO; 如果我们能看到内部的话,实际上编译器是这样处理的: char array[]={'H','E','L','L','O','\0'}; 看上面最后一个字符'\0',它是一个字符常量,Turbo C 编译器总是给字符型数组的最后 自动加上一个\0,这是字符的结束标志。所以虽然 HELLO 只有 5 个字符,但存入到数 组的个数却是 6 个。但是,数组的长度仍然是 5。 int i; i=strlen(array); /*求字符串的长度,在 string.h 里面*/ 可以看出 i 仍然是 5,表明最后的'\0'没有算。 #in

clude string.h main() { int i,j; char array[]=094387fdhgkdladhladaskdh; j=strlen(array); for(i=0;i printf(\n); } 其实我们可以根据判断'\0'来输出字符串,看下面的: main() { int i; char array[]=094387fdhgkdladhladaskdh; for(i=0;array[i]!='\0';i++) printf(%c,array[i]); printf(\n); } 举几个例子: 1.输入 10 个整数存入数组中,然后把它们从小到大排列并放在同一数组中。(思路:先 找出最小的,放在第一个位置,为了防止把原先的数覆盖掉,可以把原先的第一个数和 最小数的位置互换)。 main() { int array[10]; int i,j,min,stmp; for(i=0;i<10;i++) scanf(%d,&array[i]); for(i=0;i<9;i++)
{ min=array[i]; for(j=i+1;j<10;j++) if(min>array[j]) /*里面的 4 行语句很重要*/ { min=array[j]; stmp=array[i]; array[i]=array[j]; array[j]=stmp; } } for(i=0;i<10;i++) printf(%d ,array[i]); printf(\n); } 分析:先让第一个值作为基准,如果后面有比它小的,那么就把这两个数互换一下,同 时把基准换成小的值。两个数互换应该这样(stmp=a;a=b;b=stmp;),而不是(a=b;b=a;),想 想这是为什么?必须要用一个变量作为桥梁。 这种一个一个的把最小的放在前面的排序 方法,我们形象的叫做冒泡法。 2.输入一行字符存入数组,然后把他们反序存入到同一数组中。 #include stdio.h main() { char c,stmp,array[80]; int i=0,j; while((c=getchar())!='\n') /*注意这儿的用法*/ array[i++]=c; array[i]='\0'; /*为什么要加'\0'?是否可以不加?*/ for(j=i-1;j>=i/2;j--) { stmp=array[j]; array[j]=array[i-1-j]; array[i-1-j]=stmp; } for(i=0;array[i]!='\0';i++) printf(%c,array[i]); printf(\n); } 3.一个已经排好序的数组,输入一个数,利用二分法把这个数从原数组中删除,数组顺 序 保 持 不 变 。 如 原 数 组 为 1,3,5,7,9,11,13,15,17,19 , 待 删 除 的 数 为 13 , 则 输 出 为 1,3,5,7,9,11,15,17,19。 二分法:每次都是判断中间的数是否满足要求,若满足则删除,若不满足,则把该数当 作边界,然后再找中点。例如这一题,第一次的是 10 个数的中点,为 11,发现 11<13,
则找 11-19 的中点 15,发现 15>13,再找 11-15 的中点 13,正好,则删除。 main() { int array[10]={1,2,3,5,8,15,20,30,100,200}; int first=0,end=9,middle=(first+end)/2,num,i; scanf(%d,&num); while(array[middle]!=num) /*注意这里面的三行代码*/ { if(array[middle]>num) end=middle; else first=middle; middle=(first+end)/2; } for(i=0;i<9;i++) { if(i>=middle) array[i]=array[i+1]; printf(%d ,array[i]); } printf(\n); }
5.多维数组 .
一、高维数组 有时,数组的维数并不止一维,例如一个记录消费中心在第一季度里各个月的收入数据 就可以用二维数组来表示。 定义二维数组的方法是在一维数组定义的后面再加上一个用 方括号括起来的维数说明。例如: float array[3][8]; 实际上,这个数组可以

看成 3 个连续的一维数组,每个一维数组具有 8 个元素。该数组 在内存中的存储格式为最左边的维数相同的元素连续存储,也即按行存储的。首先存储 第一行 8 个元素,其次是第二行,最后是第三行。 main() { int array[3][3]={1,2,3,4,5,6,7,8,9}; int i,j; for(i=0;i<3;i++) { for(j=0;j<3;j++) printf(%3d); printf(\n); } } 它的输出结果为: 123 456 789
可以看出,二维数组元素是按行存储的。 我们也可以对数组进行赋值,而不是初始化。 main() { int array[3][3]; int i,j; for(j=0;j<3;j++) for(i=0;i<3;i++) scanf(%d,&array[i][j]); for(i=0;i<3;i++) { for(j=0;j<3;j++) printf(%3d); printf(\n); } } 当输入 1 2 3 4 5 6 7 8 9<回车> 输出为: 147 258 369 数组可以是二维、三维甚至是更高维数的,虽然 C 语言对维数的处理没有上限,但是处 理高维数组是很头疼的事。一般尽量避免处理四维和四维以上的数组。下面看一个三维 数组的例子: main() { int array[2][3][4]; int i,j,k; for(i=0;i<2;i++) for(j=0;j<3;j++) for(k=0;k<4;k++) array[i][j][k]=i*12+j*4+k; } 这个三维数组可以看成 2 个二维数组,每个二维数组又可以看成 3 个一维数组。可以在 头脑里想象成两个平行平面,每个平面内有 3*4 个点。所以共有 24 个元素。 二、字符串数组 上面讲的都是存放数值的,有一类数组,用来处理字符串的,我们叫字符串数组。其实 字符串数组也是二维数组,只是它的特殊性,才单独拿出来说的。 main() { char s[10][10]; int i; for(i=0;i<10;i++) scanf(%s,s[i]); }
先看它的输入特性,前面在说输入语句的时候说过,遇到字符串输入,可以不加'&',现 在只要记住这个特性就可以,以后说指针的时候再讲为什么。但是这儿为什么用 s[i], 可能很多人不太明白。 我们定义的是二维数组, 而输入的时候, 却使用一维数组的形式。 这是因为字符串在内存里地址可以用它的名字表示,就好象这种形式: main() { char s[10]; scanf(%s,s); } 定义的是一维数组,输入语句用变量形式表示一样。通过前面的'%s'形式可以看出,s[i] 是一个数组,所以 s 就是二维数组了。 这里要注意一点,scanf()函数在输入字符串时候不能支持空格,看下面的例子: main() { char s[3][10]; int i; for(i=0;i<10;i++) scanf(%s,s[i]); for(i=0;i<3;i++) printf(%s\n,s[i]); } 我们输入:1111 2222 3333 4444 我们是想把 1111 赋值给 s[0],2222 3333 赋值给 s[1],4444 赋值给 s[2]。 可实际上编译器是 这样做的,把 1111 赋值给 s[0],把 2222 赋值给[1],把 3333 赋值给 s[2]。 实际输出:1111 2222 3333 在输入字符串的时候,如果使用 scanf(),就把空格当作下一个输入了。那么我们怎么解 决这个问题呢?毕竟很多情况下,一行字符串肯定有空格出现的。我们使用新的函数 gets()。

这个函数是专门接受字符串输入的,它跳过了空格的影响。把上面的输入语言 修改为 gets(s[i])即可。 我们定义了 char s[3][10],超过 10 个字符肯定不行,如果少于 10 个字符,电脑怎么处 理呢?电脑是在每个字符串的后面自动补上'\0',作为字符串的结束标志。 我们经常在填写一些可选择的内容时经常发现,待选的字符串都是按字母排列好的,我 们怎么用 C 语言实现这个功能?在 C 语言里,字符串的排序是按照字符的 ASCII 码来 的,如果第一个字符一样,则比较第二个,依次类推。 main() { char s1[6]=addfgh,s2[5]=asdlg; int i; for(i=0;s1[i]!='\0'&&s2[i]!='\0';i++)
{ if(s1[i]s2[i]) { printf(s1>s2\n); exit(1); } else ; } if(s1[i]=='\0' && s2[i]!='\0') printf(s1s2\n); else printf(s1==s2\n); } 上面的例子就是比较两个字符串大小的,先比较第一个,如果相同,接着比较第二个, 如果不相同,则分出大小。一直往后比较,直到其中某一个到'\0',你也可以先用 strlen() 函数找出最小的长度。 exit()函数的作用是退出程序,具体它的用法可以看看相关资料。 其实 C 语言把我们经常需要的字符串处理函数都做好了,我们只需要调用它即可。如 strcmp()用来比较、strcpy()用来拷贝等等。看看它们的用法: #include string.h main() { char s1[10],s2[10],s2[10]; int k; gets(s1); gets(s2); k=strcmp(s1,s2); /*比较 s1 和 s2 大小*/ if(k==0) printf(s1==s2\n); else if(k>0) printf(s1>s2\n); else printf(s1s2;如果 k=0,则 s1=s2。实际上这是 一个函数,具体什么是函数,以及为什么写成那种形式,我们下节再说。这些函数都包 含在 string.h 头文件中,所以在程序的开头,都要写上#include string.h。
6.函数的定义与调用 .
一、函数的定义 一个函数包括函数头和语句体两部分。 函数头由下列三不分组成: 函数返回值类型 函数名 参数表 一个完整的函数应该是这样的: 函数返回值类型 函数名(参数表) { 语句体; } 函数返回值类型可以是前面说到的某个数据类型、或者是某个数据类型的指针、指向结 构的指针、指向数组的指针。指针概念到以后再介绍。 函数名在程序中必须是唯一的,它也遵循标识符命名规则。 参数表可以没有也可以有多个,在函数调用的时候,实际参数将被拷贝到这些变量中。 语句体包括局部变量的声明和可执行代码。 我们在前面其实已经接触过函数了,如 abs(),sqrt(),我们并不知道它的内部是什么,我 们只要会使用它即可。 这一节主要讲解无参数无返回值的函数调用。 二、函数的声明和调用 为了调用一个函数,必须事先声明该函数的返回值类型和参数类型,这和使用变量的道 理是一样的(有一种可以例外,就是函数的定义在调用

之前,下面再讲述)。 看一个简单的例子: void a(); /*函数声明*/ main() { a(); /*函数调用*/ } void a() /*函数定义*/ { int num; scanf(%d,&num); printf(%d\n,num); } 在 main()的前面声明了一个函数,函数类型是 void 型,函数名为 a,无参数。然后在 main()函数里面调用这个函数, 该函数的作用很简单, 就是输入一个整数然后再显示它。 在调用函数之前声明了该函数其实它和下面这个程序的功能是一样的: main() { int num;
scanf(%d,&num); printf(%d\n,num); } 可以看出,实际上就是把 a()函数里面的所有内容直接搬到 main()函数里面(注意,这句 话不是绝对的。) 我们前面已经说了,当定义在调用之前时,可以不声明函数。所以上面的程序和下面这 个也是等价的: void a() { int num; scanf(%d,&num); printf(%d\n,num); } main() { a(); } 因为定义在调用之前,所以可以不声明函数,这是因为编译器在编译的时候,已经发现 a 是一个函数名,是无返回值类型无参数的函数了。 那么很多人也许就会想, 那我们何必还要声明这一步呢?我们只要把所有的函数的定义 都放在前面不就可以了吗?这种想法是不可取的, 一个好的程序员总是在程序的开头声 明所有用到的函数和变量,这是为了以后好检查。 前面说了,在调用之前,必须先声明函数,所以下面的做法也是正确的(但在这里我个 人并不提倡)。 main() { void a(); a(); } void a() { int num; scanf(%d,&num); printf(%d\n,num); } 一般来说,比较好的程序书写顺序是,先声明函数,然后写主函数,然后再写那些自定 义的函数。 既然 main()函数可以调用别的函数,那么我们自己定义的函数能不能再调用其他函数 呢?答案是可以的。看下面的例子:
void a(); void b(); main() { a(); } void a() { b(); } void b() { int num; scanf(%d,&num); printf(%d\n,num); } main()函数先调用 a()函数,而 a()函数又调用 b()函数。在 C 语言里,对调用函数的层数 没有严格的限制,我们可以往下调用 100 层、1000 层,但是在这里我们并不提倡调用的 层数太多(除非是递归),因为层数太多,对以后的检查有一些干扰,函数调过来调过去, 容易让自己都晕头转向。
7.函数参数传递与返回值 .函数参数传递与返回值
一、函数参数传递 1.形式参数和实际参数 函数的调用值把一些表达式作为参数传递给函数。函数定义中的参数是形式参数,函数 的调用者提供给函数的参数叫实际参数。在函数调用之前,实际参数的值将被拷贝到这 些形式参数中。 2.参数传递 先看一个例子: void a(int); /*注意函数声明的形式*/ main() { int num; scanf(%d,&num); a(num); /*注意调用形式*/ } void a(int num_back) /*注意定义形式*/ {
printf(%d\n,num_back);

} 在主函数中,先定义一个变量,然后输入一个值,在 a()这个函数中输出。当程序运行 a(num);这一步时,把 num 的值赋值给 num_back,在运行程序过程中,把实际参数的值 传给形式参数,这就是函数参数的传递。 形参和实参可能不只一个,如果多于一个时,函数声明、调用、定义的形式都要一一对 应,不仅个数要对应,参数的数据类型也要对应。 void a(int,float); main() { int num1; float num2; scanf(%d,&num1); scanf(%f,&num2); a(num1,num2); } void a(int num1_back,float num2_back) { printf(%d,%f\n,num1_back,num2_back); } 上面的例子中,函数有两个参数,一个是整型,一个是浮点型,那么在声明、调用、定 义的时候,不仅个数要一样,类型也要对应。如果不对应,有可能使的编译错误,即使 没错误,也有可能让数据传递过程中出现错误。 再看一个例子: void a(int); main() { int num; scanf(%d,&num); a(num); } void a(int num) { printf(%d\n,num); }
看上面的例子,形式参数和实际参数的标识符都是 num,程序把实际参数 num 的值传 递给形式参数 num。有些人可能就不明白了,既然两个都是 num,为什么还要传递呢? 干脆这样不就行了吗: void a(); main() { int num; scanf(%d,&num); a(); } void a() { printf(%d\n,num); } 其实不然,这就要涉及到标识符作用域的问题。作用域的意思就是说,哪些变量在哪些 范围内有效。一个标识符在一个语句块中声明,那么这个标识符仅在当前和更低的语句 块中可见,在函数外部的其实地方不可见,其他地方同名的标识符不受影响,后面我们 会系统讲解作用域的问题。 在这儿你就要知道两个同名的变量在不同的函数中是互不干 扰的。 前面将的都是变量与变量之间的值传递,其实函数也可以传递数组之间的值。看下面的 例子: void a(int []); main() { int array[5],i; for(i=0;i<5;i++) scanf(%d,&array[i]); a(array); } void a(int array[]) { int i; for(i=0;i<5;i++) printf(%d\t,array[i]); printf(\n); } 这就是数组之间的值传递。注意他们的声明和定义形式,和变量参数传递有什么区别? 有了后面的[]就表明传递的是一个数组。其中在定义的时候,也可以写成 void a(int
array[5]);想想,如果我们写成了 int array[4]会有什么情况发生? 目前我们只学了数组和变量,以后还会知道指针、结构,到那是,函数也可以传递它们 之间的值。 二、函数值的返回 其实我们也可以把函数当作一个变量来看,既然是变量,那一定也可以有类型。还举最 前面的例子,现在要求在 main()函数里输入一个整数作为正方形的边长,在子函数里求 正方形的面积,然后再在主函数里输出这个面积。 我们前面的程序都是在子函数里输出的,现在要求在主函数里输

出,这就需要把算好的 值返回回来。先看例子: int a(int); /*声明函数*/ main() { int num,area; scanf(%d,&num); area=a(num); /*调用时的形式*/ printf(%d,area); } int a(int num) { int area_back; area_back=num*num; return area_back; /*返回一个值*/ } 和前面的程序有几点不同: (1).声明函数类型时,不是 void,而是 int。这是由于最后要求的面积是整型的,所以声 明函数的返回值类型是整型。 (2).return 语句 它的意思就是返回一个值。在 C 语言中,return 一定是在函数的最后一 行。 (3).调用函数的时候,由于函数有一个返回值, 所以必须要用变量接受这个返回值(不是绝 对的),如果我们不用一个变量接受这个值,函数还照样返回,但是返回的这个值没有 使用。 上面的例子运行过程是这样的,先把实参的值传递给形参,然后在子函数里计算面积得 到 area_back,然后返回这个面积到主函数,也就是把 area_back 赋值给 area,最后输出。 前面说了,返回值有时不一定非要用一个变量来接受,我们可以把上面的程序简化为: int a(int); main() {
int num; scanf(%d,&num); printf(%d,a(num)); /*函数调用放在这儿*/ } int a(int num) { int area_back; area_back=num*num; return area_back; } 这样函数返回的值就可以直接放到输出缓冲区直接输出了。 还可以再简化为: int a(int); main() { int num; scanf(%d,&num); printf(%d,a(num)); } int a(int num) { return num*num; /*直接在这儿返回*/ } 对于函数而言,一个函数只能返回一个值,如果想返回一组数值,就要使用数组或者结 构或者指针。其实对于这些,还是返回一个值,只是这个值是一个地址而已。但是对于 数组的返回有和变量不同,因为数组和地址是联系在一起的。看一个例子: void a(int []); main() { int array[5]={1,2,3,4,5},i; a(array); for(i=0;i<5;i++) printf(%d,array[i]); } void a(int array[]) { int i;
for(i=0;i<5;i++) array[i]++; } 看看这个程序,好象函数没有返回值,但是函数的功能的确实现了,在主函数当中输出 的值的确都各加了 1 上来。这就是因为数组和变量不同的缘故,在后面讲指针的时候再 详细说明。 下面看一个实际例子,加深对函数的理解: 用函数实现,判断一个整数是不是素数?在主函数里输入输出,子函数里判断。 #include math.h int judge(int); main() { int num,result; scanf(%d,&num); result=judge(num); if(result==1) printf(yes\n); else printf(no\n); } judge(int num) { int i,flag=1; for(i=2;i<=sqrt(num);i++) if(num%i==0) { flag=0; break; } return flag; } 可以看出,函数的功能就是为了让程序看起来有条理,一个函数实现一个特定的功能。 如果我们还和以前那样,把所有代码都放在 main()函数,好象程序就显的臃肿了。而且 函数有一个显著的好处就是很方便的

使用。这里面的 judge()函数判断一个数是不是素 数, 如果我们以后还有判断某个数是不是素数, 就可以直接使用这个函数了。 我们这样, 把下面的代码: judge(int num) { int i,flag=1; for(i=2;i<=sqrt(num);i++)
if(num%i==0) { flag=0; break; } return flag; } 保存为 judge.h 文件,放到 include 目录里面。 以后就可以直接使用这个函数了,就好象直接使用 abs(),sqrt()这些函数一样方便。 #include math.h /*必须要有它*/ #include judge.h main() { int num,result; scanf(%d,&num); result=judge(num); if(result==1) printf(yes\n); else printf(no\n); } 看上面的例子,我们在程序中直接使用了函数 judge(),这就是我们自己编写的第一个所 谓的库函数。 但是程序的第一行要包含 math.h 文件, 这是因为在 judge.h 里面使用了 sqrt() 函数,所以为了方便,我们可以把 math.h 放到 judge.h 里面,也就是在 judge.h 文件的第 一行加上 include math.h,这样,我们的主程序中就不需要包含它了,但是这样做也有副 作用,具体有什么副作用,我们以后接触到时再介绍。
8.变量的作用域与储存类型 .
C 程序的标识符作用域有三种:局部、全局、文件。标识符的作用域决定了程序中的 哪些语句可以使用它,换句话说,就是标识符在程序其他部分的可见性。通常,标识符 的作用域都是通过它在程序中的位置隐式说明的。 1.局部作用域 前面各个例子中的变量都是局部作用域,他们都是声明在函数内部,无法被其他函数的 代码所访问。函数的形式参数的作用域也是局部的,它们的作用范围仅限于函数内部所 用的语句块。 void add(int); main() { int num=5; add(num);
printf(%d\n,num); /*输出 5*/ } void add(int num) { num++; printf(%d\n,num); /*输出 6*/ }
上面例子里的两个 num 变量都是局部变量,只在本身函数里可见。前面我们说了,在 两个函数出现同名的变量不会互相干扰,就是这个道理。所以上面的两个输出,在主函 数里仍然是 5,在 add()函数里输出是 6。 2.全局作用域 对于具有全局作用域的变量,我们可以在程序的任何位置访问它们。当一个变量是在所 有函数的外部声明,也就是在程序的开头声明,那么这个变量就是全局变量。 void add(int); int num; main() { int n=5; add(n); printf(%d\n,num); /*输出 6*/ } void add(num) /*形式参数没有指定类型*/ { num++; printf(%d\n,num); /*输出 6*/ } 上面的 main()和 add()里面,并没有声明 num,但是在最后输出的时候却要求输出 num, 这是由于在程序的开始声明了 num 是全局变量,也就是在所有函数里都可以使用这个 变量。这时候一个函数里改变了变量的值,其他函数里的值也会出现影响。上面的例子 输出都是 6,因为在 add()函数里改变

了 num 的值,由于 num 是全局变量,就好象它们 两个函数共用一个变量,所以在 main()函数里的 num 也随之改变了。 3.文件作用域 在很多 C 语言书上,都没有说明文件作用域,或者只是略微的提到,其实文件作用域在 较大程序中很有作用(在多文件系统中)。文件作用域是指外部标识符仅在声明它的同一 个转换单元内的函数汇总可见。所谓转换单元是指定义这些变量和函数的源代码文件
(包括任何通过#include 指令包含的源代码文件)。static 存储类型修饰符指定了变量具有 文件作用域。 static int num; static void add(int); main() { scanf(%d,&num); add(num) printf(%d\n,num); } void add(num) { num++; } 上面的程序中变量 num 和函数 add()在声明是采用了 static 存储类型修饰符, 这使得它们 具有文件作用域,仅爱定义它们的文件内可见。 由于我们提到的大多数程序都只有一个编译文件组成,所以这种写法没有实际意义。但 是实际工程上的文件有很多,它们不是由一个人写成的,由很多人共同完成,这些文件 都是各自编译的,这难免使得某些人使用了一样的全局变量名,那么为了以后程序中各 自的变量和函数不互相干扰,就可以使用 static 修饰符,这样在连接到同一个程序的其 他代码文件而言就是不可见的。 二、变量存储类型 前面我们说了,声明变量时用如下类似的形式: int num; float total; 它们都没有存储类型修饰符, 我们在声明时也可以通过存储类型修饰符来告诉编译器将 要处理什么类型的变量。存储类型有以下四种:自动(auto)、静态(static)、外部(extern)、 寄存器(regiser)。 1.自动存储类型 自动存储类型修饰符指定了一个局部变量为自动的,这意味着,每次执行到定义该变量 的语句块时, 都将会为该变量在内存中产生一个新的拷贝, 并对其进行初始化。 实际上, 如果不特别指明,局部变量的存储类型就默认为自动的,因此,加不加 auto 都可以。 main() { auto int num=5; printf(%d\n,num); } 在这个例子中, 不论变量 num 的声明是否包含关键字 auto, 代码的执行效果都是一样的。
函数的形式参数存储类型默认也是自动的。 2.静态存储变量 前面已经使用了 static 关键字,但是对于局部变量,静态存储类型的意义是不一样的, 这时,它是和自动存储类型相对而言的。静态局部变量的作用域仍然近局限于声明它的 语句块中,但是在语句块执行期间,变量将始终保持它的值。而且,初始化值只在语句 块第一次执行是起作用。在随后的运行过程中,变量将保持语句块上一次执行时的值。 看下面两个对应的程序: /*1.C*/ /*2.C*/ int add(); int add(); main() main() {{ int result; int result; result=ad

d() result=add(); printf(%d ,result); printf(%d ,result); result=add(); result=add(); printf(%d ,result); printf(%d ,result); result=add(); result=add(); printf(%d,result); printf(%d,result); }} int add() int add() {{ int num=50; static int num=50; num++; num++; return num; return num; }} 上面两个源文件,只有函数 add()里的变量声明有所不同,一个是自动存储类型,一个 是静态存储类型。 对于 1.C 文件,输出结果为 51 51 51;这很好理解,每次初始值都是 50,然后加 1 上来。 对于 2.C 文件, 输出结果为 51 52 53; 这是由于变量是静态的, 只在第一次初始化了 50, 以后都是使用上次的结果值。当第一次调用 add()时,初始化为 50,然后加 1,输出为 51;当第二次调用时,就不初始化了,这时 num 的值为上次的 51,然后加 1,输出 52; 当第三次调用时,num 为 52,加 1 就是 53 了。 比较就会发现它们的不同之处了。静态变量在下一节要说的递归函数中经常使用到。 当第一次不指明静态变量的初始值时,默认为 0。 下面举一个例子,把我们说到的静态变量理解一下。 求 1+2+……+100 的值 void add();
int result; main() { int i; result=0; for(i=0;i<100;i++) add(); printf(%d\n,result); } void add() { static int num=0; num++; result+=num; } add()函数被调用了 100 次,num 的值从 1 一直变到 100,这样就可以求出它们的和了。 如果写成 int num=0;那就是求 1+1+……+1 这 100 个 1 的值了。 实际上类似的这类问题我们可以通过递归函数来解决,什么是递归,我们下一节介绍。 3.外部存储类型 外部存储类型声明了程序将要用到的、但尚未定义的外部变量。通常,外部存储类型都 是用于声明在另一个转换单元中定义的变量。 下面举一个例子, 这个例子包括两个文件。 /*1.C*/ void a(); main() { extern int num; a(); printf(%d\n,num); } /*2.C*/ int num; void a() { num=5; }
这两个程序是分别编译的,然后连接成一个执行文件。具体如何操作,可以查看一些手
册,这儿我简单说了一下。把上面两个文件都编译好后,再制作一个.prj 文件,里面的 内容是: 1.c 2.c 只有这两行,这可在编辑状态下写成,存盘,取名为 1.prj。 然后选择 project 选项,选择 project name,填入 1.prj 文件名,按 F9 后,即可生成 1.exe 文件。 main()函数中变量 num 是在另一个文件中定义的。因此,当编译器编译 1.c 时,无法确 定该变量的地址。这时,外部存储类型声明告诉编译器,把所有对 num 的引用当作暂 且无法确定的引用,等到所有便宜好的目标代码连接成一个可执行程序模块时,再来处 理对变量 num 的引用。 外部变量的声明既可以在引用它的函数的内部,也可以在外部。如果变量声明在函数外 部, 那么同一转换单

元内的所有函数都可以使用这个外部变量。 反之, 如果在函数内部, 那么只有这一个函数可以使用该变量。 前面说了文件作用域的问题,如果在声明全局变量时,加上 static 修饰符,那么该变量 只在当前文件内可见, extern 又可以引用其它文件里的变量。 而 所以在一个大型程序中, 每个程序员只是完成其中的一小块,为了让自己的变量不让其他程序员使用,保持一定 的独立性,经常在全局变量前加 static。我们可以这样来说明一下: 还是上面的两个文件,现在再增加一个文件 3.c,内容为: static int num; void a() { num=6; } 把 1.prj 文件后面加上 3.c 这样,我们生成的 1.exe 文件,执行时输出是 5,而不是 6。 因为 3.c 文件的 num 变量增加了文件作用域,在其他文件中是无法使用它的。 4.寄存器存储类型 被声明为寄存器存储类型的变量, 除了程序无法得到其地址外, 其余都和自动变量一样。 至于什么是变量地址,以后说指针时会详细介绍。 main() { register int num; num=100; printf(%d,num); } 使用寄存器存储类型的目的是让程序员指定某个局部变量存放在计算机的某个硬件寄 存器里而不是内存中, 以提高程序的运行速度。 不过, 这只是反映了程序员的主观意愿,
编译器可以忽略寄存器存储类型修饰符。 寄存器变量的地址是无法取得的,因为绝大多数计算机的硬件寄存器都不占用内存地 址。而且,即使编译器忽略寄存器类型修饰符把变量放在可设定地址的内存中,我们也 无法取地址的限制仍然存在。
9.函数递归 .
一、栈 在说函数递归的时候,顺便说一下栈的概念。 栈是一个后进先出的压入(push)和弹出(pop)式数据结构。在程序运行时,系统每次向栈 中压入一个对象,然后栈指针向下移动一个位置。当系统从栈中弹出一个对象时,最近 进栈的对象将被弹出。然后栈指针向上移动一个位置。程序员经常利用栈这种数据结构 来处理那些最适合用后进先出逻辑来描述的编程问题。 这里讨论的程序中的栈在每个程 序中都是存在的,它不需要程序员编写代码去维护,而是由运行是系统自动处理。所谓 的系统自动维护,实际上就是编译器所产生的程序代码。尽管在源代码中看不到它们, 但程序员应该对此有所了解。 再来看看程序中的栈是如何工作的。当一个函数(调用者)调用另一个函数(被调用者)时, 运行时系统将把调用者的所有实参和返回地址压入到栈中, 栈指针将移到合适的位置来 容纳这些数据。最后进栈的是调用者的返回地址。当被调用者开始执行时,系统把被调 用者的自变量压入到栈中,并把栈指针再向下移,以保证有足够的空

间存储被调用者声 明的所有自变量。当调用者把实参压入栈后,被调用者就在栈中以自变量的形式建立了 形参。被调用者内部的其他自变量也是存放在栈中的。由于这些进栈操作,栈指针已经 移动所有这些局部变量之下。但是被调用者记录了它刚开始执行时的初始栈指针,以他 为参考,用正或负的偏移值来访问栈中的变量。当被调用者准备返回时,系统弹出栈中 所有的自变量,这时栈指针移动了被调用者刚开始执行时的位置。接着被调用者返回, 系统从栈中弹出返回地址,调用者就可以继续执行了。当调用者继续执行时,系统还将 从栈中弹出调用者的实参,于是栈指针回到了调用发生前的位置。 可能刚开始学的人看不太懂上面的讲解,栈涉及到指针问题,具体可以看看一些数据结 构的书。要想学好编程语言,数据结构是一定要学的。 二、递归 递归,是函数实现的一个很重要的环节,很多程序中都或多或少的使用了递归函数。递 归的意思就是函数自己调用自己本身,或者在自己函数调用的下级函数中调用自己。 递归之所以能实现, 是因为函数的每个执行过程都在栈中有自己的形参和局部变量的拷 贝,这些拷贝和函数的其他执行过程毫不相干。这种机制是当代大多数程序设计语言实 现子程序结构的基础, 是使得递归成为可能。 假定某个调用函数调用了一个被调用函数, 再假定被调用函数又反过来调用了调用函数。这第二个调用就被称为调用函数的递归, 因为它发生在调用函数的当前执行过程运行完毕之前。 而且, 因为这个原先的调用函数、 现在的被调用函数在栈中较低的位置有它独立的一组参数和自变量, 原先的参数和变量 将不受影响,所以递归能正常工作。程序遍历执行这些函数的过程就被称为递归下降。 程序员需保证递归函数不会随意改变静态变量和全局变量的值, 以避免在递归下降过程 中的上层函数出错。程序员还必须确保有一个终止条件来结束递归下降过程,并且返回 到顶层。 例如这样的程序就是递归:
void a(int); main() { int num=5; a(num); } void a(int num) { if(num==0) return; printf(%d,num); a(--num); } 在函数 a()里面又调用了自己,也就是自己调用本身,这样就是递归。那么有些人可能 要想,这不是死循环吗?所以在递归函数中,一定要有 return 语句,没有 return 语句的 递归函数是死循环。 我们分析上面的例子,先调用 a(5),然后输出 5,再在函数中调用本身 a(4),接着回到 函数起点,输出 4,……,一直到调用 a(0),这时发现已经满足 if 条件,不在调用而是 返回了,所以这个递归一共进行了 5 次。如果没有这个 return

,肯定是死循环的。 虽然递归不难理解,但是很多在在使用递归函数的时候,问题多多。这里面一般有两个 原因:一是如何往下递归,也就是不知道怎么取一个变量递归下去;二是不知道怎么终 止递归,经常弄个死循环出来。 下面看几个例子: 1.求 1+2+……+100 的和 先分析一下。第一递归变量的问题,从题目上看应该取 1,2,……,100 这些变量的值作为 递归的条件;第二就是如何终止的问题,从题目上看应该是当数为 100 的时候就不能往 下加了。那么我们试着写一下程序。 int add(int); main() { int num=1,sn; sn=add(num); printf(%d\n,sn); getch(); } int add(int num) { static int sn; sn+=num;
if(num==100) return sn; add(++num); } 分析一下程序: 前调用 add(1), 然后在子函数中把这个 1 加到 sn 上面。 接着调用 add(2), 再把 sn 加 2 上来。这样一直到 100,到了 100 的时候,先加上来,然后发现满足了 if 条件,这时返回 sn 的值,也就是 1+2+……+100 的值了。 这里有一个问题一定要注意,就是 static int sn; 有些人就不明白,为什么要使用 static 类型修饰符,为什么不使用 int sn=0;?如果使用 int sn=0;这样的语句,在每次调用函数 add()的时候,sn 的值都是赋值为 0,也就是第一步 虽然加了 1 上来,可是第二次调用的时候,sn 又回到了 0。我们前面说了,static 能保证 本次初始化的值是上次执行后的值,这样也就保证了前面想加的结果不会丢失。如果你 修改为 int sn=0,最后结果一定是最后的 100 这个值而不是 5050。 2.求数列 s(n)=s(n-1)+s(n-2)的第 n 项。其中 s(1)=s(2)=1。 可以看出,终止条件一定是 s(1)=s(2)=1。递归下降的参数一定是 n。 int a(int); main() { int n,s; scanf(%d,&n); s=a(n); printf(%d\n,s); getch(); } int a(int n) { if(n<3) return 1; return a(n-1)+a(n-2); } 这个题目主要说明的是,在函数中,不一定只有一个 return 语句,可以有很多,但是每 次对归的时候只有一个起作用。题目不难理解,这儿不分析了。 说了这些递归,其实它和函数的调用没有大的区别,主要就是一个终止条件要选好。递 归函数很多时候都能用循环来处理。 main() { int n=20,array[20]; int i; for(i=0;i{ if(i<2) array[i]=1; else array[i]=array[i-1]+array[i-2]; } printf(%d\n,array[19]); getch(); } 上面的程序就是实现一模一样的功能的。但是它有一个缺陷,就是 n 的值不是通过键盘 输入来得到。如果想通过键盘来得到 n,可以这样: main() { int n,i; int s1=1,s2=1,temp scanf(%d,&n); for(i=3;i<=n;i++) { temp=s2; s2+=s1; s1=temp; } printf(%d\n,s2); getch(); }
10.预处理过程 .
预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。可见 预处理过程先于编译器对源代码

相关文档
相关文档 最新文档