第一章 C 程序的框架结构
计算机系统是由硬件系统和软件系统构成,硬件由IBM、HP、DLL、Acer以及联想这样的工厂制造出来,叫裸机。软件由微软、Oracle以及用友等公司的程序员用计算机语言编写出来的,叫程序。程序和编写该程序的文档一起构成了软件系统。裸机安装上了操作系统就构成了第一层虚拟机,计算机软硬件资源由操作系统来管理。在操作系统之上安装应用软件,就构成第二层虚拟机,用户一般与这层计算机打交道的。应用软件提供一个输入数据的界面,用户通过这个界面将要加工的原始数据(文字、数值 、声音、图片、图形、动画、电影等)输入给这个软件,然后它的加工部分对数据进行交换,最后将计算结果输出给用户。
软件是被记录下来的思维的逻辑推理过程,是计算机硬件的“学历”,是计算机系统的灵魂。没有安装软件的裸机就像是一台接收不到电视信号的电视一样,其“本事”不能被挖掘出来。因此,硬件的价值与安装到其上的软件功能成正比,人们借助软件间接地让硬件“劳动”。
计算机硬件的制造现在可以实现批量生产了,但软件的开发还需要人来写,目前还不能现让计算机来编写软件。用来写软件的计算机语言多达2000种,C语言能在众多计算机语言中脱颖而出,靠的就是既能写系统软软件又能写应用软件的特长。系统软件一般直接与硬件打交道,编写难度大。应用软件是用来解决特定领域的数据加工问题的。现在通常的做法是用C开发一个系统软件,然后用这个系统软件开发应用软件例如用C开发出的VFP 系统,用户使用VFP 系统开发其领域的应用软件,如考场安排系统,成绩管理系统以及售票系统等。
1.1 用C 写程序的思想框架
下面给出第一个C程序,命名为c1.c, C源程序的扩名为c。
main () // main函数的功能是调用其他函数,给其传递数据,并接收传来的数据。
{void drawsomething (char, int); // 对该函数进行声明
drawsomething (' ', 2);
drawsomething('#', 1); printf ("\n"); //括号内参数是实参
drawsomething(' ', 1);
drawspmething('#', 3); printf ("\n") //实参之间用逗号分隔
drawsomething(' ', 0);
drawsomething ('#', 5); printf("\n"); //字符常量用单括撇括起
} // 分号是语句的标志,少了它就变成了表达式了。
/*下面函数的功能是输出n个由参数c 指定的字符*/
viod drawsomething(char c, int n) // c 和 n 是形式参数,简称为形参,char 即 character.
{ int i; // 定义变量i 是integer 型, C规定变量必须先定义,后使用。
for (i=0; i<n; i++) printf ("c%", c) //循环n次,每次输出一个字符。
} // for 循环中的分号是分隔符。第三章会讲到,同一符号在不同上下文中含义迥然。
由/*&*/ 括起来的部分是C和C++的多行注释语句,// 是C++的注释语句。本书所有程序都是在Turbo C++3.0 IDE (Integrated Development Evironment)下调试和编译。注释语句也叫内部文档,一般分两种:一种是放在源码的最前面,对该程序进行整体注释,介绍其功能,I/O接口中参数的含义、权限要求、算法的简单描述,调用了哪些函数、编者、创建日期、修改日期以及审核日期;另一种是行间注释,对可能引起歧义的语句进行说明。
该程序由main和drawingsomething 两个函数构成,每个函数完成一个特定的功能。 一个程序有且只有一个main 函数,mian函数是程序的入口函数,其位置任意。程序总是从main函数开始执行, main 函数执行完了,整个程序也就执行完了。所以,在拿到一个要进行数据变换的问题后,首先要做的就是将要解决的问题分解为若干功能单一的函数,以使每个函数都有“一技之长”。接下来main函数按照输入数据、变换数据、输出数据的顺序调用相应的函数,由其完成具体的任务。当然函数之间可以相互调用,但不能调用main函数。这就是用C解决数据加工问题的思维框架。
通过上面的示例可以看出,一个函数由函数首部和用花括号括起来的函数体两部分组成。
函数首部由函数返回值类型、函数名(类型 形参、类型 形参,...)构成。 上面的main函数没有形参,叫无参函数,没有返回值类型,默认为int 型,没有返回值语句,则执行完该函数后系统返回一个随机数。 drawsomething 函数的返回值类型是viod, 说明不能有返回值。 形参是定义函数时的形式参数,必须是变量,实参是调用函数时的实际参数,必须有明确的值。 调用时,按从右到左(TC++是这样的,但Fortran等语言按从左向右的顺序)的顺序将实参的值赋给形参,所以要求实参和形参的类型、个数、顺序要对称,否则是编译不通过,或者是计算结果不正确。现在让我们故意犯个错误,将main函数第3行的,2去掉,编译后给出“too few parameters in call to 'drawsomething' in function main”的提示,将实参2改为2.9 ,运行结果不变,因为形参n是整数,它只要整数的部分。
函数内要先写局部变量定义,被调函数声明、判断、循环、赋值、 return等语句。函数体也可以是空语句,即只有一个函数首部后跟一对花括号,叫空函数。根据C的思维框架结构,我们在处理一个大的任务时,首先要做的是将大任务分解为小任务,如此分解下去,直到每个小任务只有一个功能,我们暂时就用这个空函数代表这个小任务,待分解合理了,任务之间的关系理顺了,再把空函数为真正的函数,然后上机调试、编译为EXE文件,就可以作为软件使用了。如,求两个正整数的最大公约数和最小公倍数,可以分解为如下三个函数:
int max(int, int){} // max 函数负责求最大公约数,返回值类型是int型。
min (int, int) {} // min 函数负责最小公倍数,省略了返回值类型。
void main () {} //负责接收待求数,调用max和min,接收其返回值,最后输出结果。
下面细化后的三个函数:
int max (int bcs, int cs) // bcs是被除数的拼音字母缩写,cs代表除数。
{ int yushu; // yushu代表余数,zdgys 代表最大公约数,zxgbs代表最小公倍数。
while(cs != 0) // while 循环结构,读作当条件为真时,执行循环体,然后返回到
{yushu=bcs/cs; //条件处继续判断,当条件仍为真时,继续执行循环体,如此循环,
cs=yushu; //当条件为假时,跳出循环,继续执行后继语句
bcs=cs; //当循环体语句多于一条时用{}括起来,构成复合语句;其功能相当于一条语句。
} // 下面的return语句是上面循环结构的后继语句
return (bcs): // 将加工结果返回调用处, return后面的圆括号可以省略。
} //将源代码写成锯齿状的目的是增强程序的可读性,程序的结构层次清淅,一目了然。
min(int bcs, int cs)// 形参是局部变量,与生活中的临时工类似。
{int zdgys=max(bcs, cs); // 在定义局部变量的同时调用max函数,其返回值赋之。
return bcs*cs / zdgys; // 先计算,在返回结果。
}
void main () // 无参函数,且不能有返回值,有返回值也不允许使用它。
{ int bcs=26, cs=4; // 定义局部变量的同时,给其赋值。
int zdgys=max (bcs, cs) // 调用max 函数,并将其返回值给zdgys。
int zxgbs=min (bsc, cs) // 调用min函数,并将其返回值给zxgbs。
printf ("\n max=%d, min=%d\n", zdgys,zxgbs); //输出结果
return 6; // 6可略,写上此句不算错,读者可编写一个使用此返回值的程序,编译。
} // 时会给出“not an allowed typ in function function name”的提示。
在TC++系统的IDE中输入源码,保存为c2.c, 也可以在类似于NotePad这样的文本文件编缉器中输入源码,然后在TC++系统中打开,编译通过后,生成c2.obj(Object)二进制码文件,再和库函数Link成可独立执行的c2.exe(execute)机器码文件。 可以到XP的命令提示符下运行,输入结果为Max=2, Min=52。
为什么要把具有某一功能的一段程序称为函数呢? 仔细剖析Max这段程序可以看出,其功能是将传进来的两个正整数(自变量、其所有可能的值构成定义域)经过一定的变换规则加工为最大公约数(因变量,其所有的值构成的值域), 称这段程序为函数是比较恰当的。
若被调用函数处于调用函数之前,或函数的返回值类型是int型,声明语句可以省略。上面的min函数直接调用了max函数就是这个原因。函数的声明,也可以直接写在所有函数之前,程序的开始部分。下面的程序c3,c就是这样。
long power (int, int); // 函数的声明放在所有函数之前,可不必声明而直接调用。
void main ()
{ int a=2, b=8; // 16位计算机int型变量占2字节,取值范围-32768~32767
printf ("result is:%ld\n", power (a,b); // %ld表示此处放一个长整型数,l 即long
} //d 即decimal, \n 表示 换行。
long power (int x, int n) // long int 型中的int 可省略,该函数的功能是求Xn 。
{int i ; // 定义局部变量,也叫自动变量,用auto修饰,可以省略。
long result=1; // 16 位计算机int型变量占4个字节,取值范围-2147483648~2147483647 。
for (i=0; i<n; i ++) // for 循环结构,循环n次。
result*=x // 与result = result*x等价,但前者更简洁。
return result;
}
为何printf函数没有声明,也不见其函数体,用户也没有编写这个函数,却可以拿来直接调用呢?原因是它是库函数。库函数是由C系统开发人员事先写好并编译成二进制代码后保存到库文件中,并随系统一起提供的。库函数一般完成和硬件直接打交道的底层功能,或复杂计算。前结如输出结果的printf函数,将键盘输入的数据送入到内存指定的scanf 函数, 清屏函数clrscr 等, 后者如sqrt、 sin 函数等。由于printf、 scanf 、 clrscr 函数使用频繁, TC++编译系统直接使用它们而不必声明,其他函数在使用前仍需声明,但与用户自定义函数的声明方法略有不同,程序c4.c 说明了这种差异。
#include <math.h> // 包含命令,下面三个库函数首部信息保存在这个头文件中。
viod ,main() // 库函数sin ()、 log10 () , sqrt () 的返回结果作为printf () 的实参。
{printf ("sin(3)=%lf,"sin(3)) // 求3的正弦, l 即是long, f 即 float。
printf ("log10(10) = %lf," ,log10(10) ) //求 log10(10) , lf 表示双精度数。
printf ("5's square root is = %lf\n", sqrt (5)); // 求5的平方根
} // 输出结果为sin(3)= 0.14120, ,log10(10) =1.000000, 5's square root is:2.236068 。
# include <math.h> 是编译预处理命令,编译预处理命令(不能叫语句)不是C 语言的组成部分,但却是C编译系统的重要组成部分,使用它主要是为了提高程序的可读性。所有编译预处理命令都以#号开头且后面紧跟define, undef, pragma 等命令,命令结束处勿写分号。
在正式编译这段程序前,编译预处理程序找到math.h文件(该文件是一个文本文件,保存在安装文件下include 文件夹中),并将其内容复制一份粘贴到该命令处以取代该命令,然后编译器对经编译预处理后的程序进行编译、连接、生成可执行文件。 *. h (h 即head)文件一般称为头文件,这些文件中包含有库函数的首部信息和系统定义的常量、类型等。程序中用到库函数时,须将相应的头文件写入程序的开始部分,以便在进行编译预处理时,将这些文件的内容包含进来。
顺便提一下,<math.h>也可以写作“math.h”。 用双撇时,编译处理程序先去当前文件所在的文件夹下寻找,若找不到,再到系统配置文件所指定的文件夹中查找,查找用户自定义文件一般采用这种方法。用尖括号时,编译预处理程序直接到系统配置文件所指定的文件夹中查找,查找系统自带的文件一般采用这种方法。