第2章 C程序的组成
在第1章中介绍了一个向屏幕上输出字符串的简单程序,这是最简单的C程序,仅用来演示C程序的结构外,实用价值不大。本章将编写一个完整的程序,也有一定的实用价值,使读者对C程序的组成有一个完整的概念。
2.1 一个完整的C程序
本章编写一个C程序,用来计算圆的面积和周长。运行程序时,将接收用户输入的数据,然后调用计算面积函数和计算周长的函数进行计算,并将计算结果输出到屏幕上。
2.1.1 分析程序目标
示例程序需要达到的目标:
计算圆的面积。
计算圆的周长。
这两个目标都是可行的,其中计算圆的面积的公式为:
S=πr2 (S为面积,r为圆的半径)
计算圆的周长的公式为:
L=2πr (L为周长,r为圆的半径)
从以上两个公式可以看出,要达到这两个目标,必须要知道圆的半径 r。若在程序中直接定义r的值,则程序只能得到半径为r的圆的面积和周长,不具有通用性。为了使程序具有通用性,可让程序运行时让用户输入r值。
2.1.2 设计程序
通过对程序目标进行分析,知道了程序需要的输入、输出值。接着就可以设计程序,以达到需要的目标。
本例的程序很简单,可直接进行详细设计阶段。程序可分为3个函数:
主函数main:接收用户输入的半径,调用函数area计算面积、调用函数perimeter计算周长,将并计算结果输出到屏幕上。
计算面积的函数area:根据参数r,计算并返回圆的面积。
计算周长的函数perimeter:根据参数r,计算并返回圆的周长。
C程序由多个函数组成,本例将使用上面列出的3个函数。在一个C源文件中可包含一个或多个函数,另外在函数外还可定义包含语句、定义常量、声明自定义函数的原型等语句。
本例程序的流程图如图2-1所示,其中(a)图为函数外的语句流程,(b)图为主函数main的流程,(c)图为计算面积函数area的流程,(d)图为计算周长函数perimeter的流程。
图2-1 程序流程图
2.1.3 编写代码
根据图2-1设计的程序流程编写C代码,具体的代码如下:
【程序2-1】C语言程序组成示例
1: #include <stdio.h> 2: #define PI 3.1415926 /*定义常量*/ 3: 4: float area(float r); /*声明计算面积的函数原型*/ 5: float perimeter(float r); /*声明计算周长的函数原型*/ 6: 7: void main(void) 8: { 9: float r; /*定义变量r保存圆的半径*/ 10: float s,l; /*定义变量保存面积和周长*/
11: 12: printf("请输入圆的半径:"); /*显示提示信息*/ 13: scanf("%f",&r); /*接收用户输入的半径*/ 14: 15: s=area(r); /*调用area函数计算面积*/ 16: l=perimeter(r); /*调用perimeter函数计算周长*/ 17: 18: printf("半径R=%.2f,面积S=%.2f \n",r,s); /*输出圆的面积*/ 19: printf("半径R=%.2f,周长L=%.2f \n",r,l); /*输出圆的周长*/ 20: } 21: 22: /*函数area计算圆的面积*/ 23: float area(float r) 24: { 25: float s; 26: 27: s=PI*r*r; /*计算面积*/ 28: return s; /*返回面积*/ 29: } 30: 31: /*函数perimeter计算圆的周长*/ 32: float perimeter(float r) 33: { 34: float l; 35: 36: l=2*PI*r; /*计算周长*/ 37: return l; /*返回周长*/ 38: }
提示
每行代码前的行号是为了后面分析程序方便而添加的,不是C程序中的内容,读者在输入程序代码时不能输入这些行号和后面的冒号。
执行菜单“文件/保存”命令,将输入的C代码保存到名为“2-1.c”的文件中。
2.1.4 编译运行程序
在Dev-C++开发环境中输入上面的代码后,执行菜单“运行/编译”命令对代码进行编译。本例的代码有 38 行,可能会有输入错误的情况,根据编译提示进行修改,直到通过编译为止。
另外,本例代码第7行中定义的main函数类型为void(不返回值),在编译时将产生如下所示提示信息:
[Warning] return type of 'main' is not 'int'
以上警告信息的意思是:函数main的返回值不是int类型。
编译通过后,进入Windows的命令窗口,使用DOS命令切换到保存编译文件的目录,在DOS提示符后面输入编译生成的文件“2-1”,按Enter键,将弹出提示信息,要求用户输入半径,输入5按Enter键,将显示半径为 5 的圆面积和周长,如图2-2所示。
图2-2 程序运行结果
注意
程序中使用中文字符显示提示信息,若命令窗口不在中文模式,将只能看到乱码。
2.2 C程序的组成
上节的C程序共有38行,本节将对程序中各行语句进行分析,使读者对C程序的组成有一个完整的认识。
2.2.1 包含指令
在程序的第1行为一个包含指令#include。在C程序中,一般文件的开始部分都是包含指令。C编译器遇到包含指令时,将指定的包含文件添加到程序中。包含文件的扩展名为“.h”,一般称为头文件。在这些头文件中,包含了对库函数原型的声明、常量的定义等内容。
在一般的C开发环境中都包含有多个头文件,分别对应不同的库函数。例如,要使用printf 函数,就必须在程序中包含“stdio.h”头文件;要使用数学函数 pow,则必须包含“math.h”头文件。
用户也可定义自己的头文件,在其中包含函数原型声明、常量定义等内容。
有两种方式使用#include指令:
一种是在包含指令后用“<>”将头文件名括起来。这种方式用于标准或系统提供的头文件,到保存系统标准头文件的位置查找头文件。保存系统标准头文件的位置可进行设置,Dev-C++中设置包含文件目录的相关操作,参见下一章中的介绍。
另一种是在包含指令后用双引号将头文件名括起来。这种方式常用于程序员自己的头文件。用这种格式时,C编译器先查找当前目录是否有指定名称的头文件,然后再从标准头文件目录中查找。
例如:以下两行代码是分别使用两种不同方式编写的包含语句。
#include <stdio.h> #include "stdio.h"
第1行语句指定编译器从标准头文件目录查找头文件“stdio.h”;第2行语句指定编译器先从当前目录查找头文件“stdio.h”,若当前目录未找到该头文件,再从标准头文件目录中查找。
2.2.2 定义常量
在程序的第2行,使用#define定义一个符号名PI,其值为3.1415926。当程序被编译时,程序中第2行后面的PI都会被替换成3.1415926。有关#define语句的详细使用,参见本书第17章中的介绍。
使用符号常量的好处在于,其名称可以给程序员起提示作用。例如:本例中的 PI 表示圆周率。另一个好处是修改方便,例如,在本例中要修改圆周率的精度(如修改为3.14),只需修改第2行定义符号常量的语句即可。若不使用符号常量,则需要修改第22行和第33行。
2.2.3 声明函数原型
程序中第4行和第5行声明函数原型。在C语言中,函数声明称为函数原型。使用函数原型是ANSI C的一个重要特点。它的作用主要是利用它在程序的编译阶段对调用函数的合法性进行全面检查。
以前C版本的函数声明方式不是采用函数原型,而只是声明函数名和函数类型。例如:
float area();
以上声明不包括参数类型和参数个数。系统不检查参数类型和参数个数。新版本也兼容这种用法,但不提倡这种用法,因为它未进行全面检查。
实际上,如果在函数调用前,没有对函数作声明,编译系统会把第一次遇到的该函数形式(函数定义或函数调用)作为函数的声明,并将函数类型默认为 int 型。使用这种方法时,系统无法对参数的类型做检查。或调用函数时参数使用不当,在编译时也不会报错。因此,为了程序的清晰和安全,建议都加以声明为好。
2.2.4 main函数
第7行至第20行为main函数的代码。在C程序中,必须有且只有一个名为main的函数,最简单的C程序只有一个main函数(如上一章中的例子)。
C 程序将 main 函数作为入口程序,在该函数中编写代码完成程序功能,或调用其他函数完成程序功能。执行到main函数的return语句,结束整个程序的运行。
main函数的定义有多种格式。如:
void main( void ) int main ( void ) int main ( int argc, char *argv[] )
第1种形式说明main函数没有返回值、也没有参数。
第2种形式说明main函数的返回值为int型(需在程序中使用return返回一个整型值),不接收参数。
第3种形式说明main函数的返回值为int型,并且函数接收命令行参数。
在C89标准中,第1种形式是可以接受的,但在编译时将显示一个警告信息。
而C99标准只支持后两种形式,因此,建议读者尽量使用后两种形式定义main函数。
2.2.5 程序语句
C程序是由多语句构成的,不同的语句完成不同的功能。在C程序中,一般一个语句占用一行,语句以分号“;”结束。在本例的程序中使用了以下语句:
1.变量定义
程序中的第9行、第10行、第25行、第34行为变量定义语句。C语言中,使用变量之前必须先定义。编译器遇到变量定义语句时,按变量的类型为变量分配内存空间。
2.输出语句
程序中第12行、第18行、第19行使用printf库函数向屏幕上输出信息。第12行在屏幕上显示一个提示信息,提示用户输入圆的半径;第18行输出圆的面积、第19行输出圆的周长。
3.输入语句
程序的第13行为输入语句。scanf库函数用于从键盘接收用户的输入,并将结果保存到指定的变量中。在本例第13行中,将用户输入的数值保存到变量r中。在scanf语句中,需在保存输入值的变量前加上符号“&”。本书后面将要介绍,这是一个取地址运算。
4.运算语句
程序的第15行、第16行、第27行和第36行为运算语句。第15行和第16行分别调用area和perimeter函数,将函数运算的结果保存到变量s和l中。第27行为计算圆面积的公式,第36行为计算圆周长的公式。
5.返回语句
程序的第28行和第37行为返回语句,使用return语句返回函数的结果。第15行和第 16 行的语句将接收函数的返回值。函数的返回值的类型必须与定义函数时指明的类型相同。若函数的类型为void,则不需要返回值。
2.2.6 函数定义
在C语言中的函数相当于Basic语言的子程序、Pascal语言的过程。通过函数将相关功能封装,调用函数时不必了解函数的实现细节。在ANSI C中提供了数量众多的库函数,这些库函数都是程序中常用的功能,如本例中使用的scanf和printf两个库函数。
在更多的情况下,用户需要编写自己的函数,以完成特定的功能。函数定义包括两部分:函数头和函数体。函数头定义函数的名称、参数和函数返回值的类型,而函数体则定义函数具体完成的工作。
在本例中有3个函数main、area和perimeter,第7行、第23行和第32行分别为三个函数的函数头。从函数头的定义可看出,main 函数无参数无返回值,area和perimeter函数需要一个float类型的参数,并返回一个float类型的值。
紧跟在函数头后面是一个左大括号“{”,从该处开始定义函数体。main函数的函数体为第8~20行,area函数的函数体为第24~29行,perimeter函数的函数体为第33~38行。
2.2.7 注释
为程序添加注释是一个好的编程习惯。C编译器将忽略所有注释内容,注释不是写给编译器的,而是写给程序员阅读的。当一个程序比较简单时,注释显得有点多余。但随着程序的变大,参与开发人员的增多,在程序中通过注释说明程序结构和逻辑,可使代码的可读性大大提高。
在ANSI C中,使用“/* */”作为注释的定界符,包含在该符号内部的字符都被作为注释,编译将忽略。
注释可单独占一行(如本例程序中的第22、31行),也可占用一行的部分如第2、4、5行等。另外,注释还可以占用多行,如以下代码:
/* 占用多行的注释情况(这是第一行) 占用多行的注释例子(这是第二行) */
利用这一特性,可在调试程序时快速屏蔽程序行。在需要屏蔽的程序行前面加上注释符“/*”,在屏蔽程序最后一行的后面加上“*/”,即可将这部分代码作为注释,编译器将不再对其进行编译。
注释不能嵌套,即一个注释中又包含另一个注释。如以下形式的注释在大部分编译器中都将产生错误:
/* 注释嵌套(这是第一层) /*注意嵌套(这是第二层)*/ 外层注释 */
即使有的编译器支持这种嵌套,读者也应养成不使用嵌套注释的习惯。
在C99中支持单行注释,这是C++中早就有的特性。单行注释使用双斜杠来标志注释,例如:
float r; //定义变量r保存圆的半径
与使用“/* */”的注释比,输入代码要方便些。
float r; /*定义变量r保存圆的半径*/
2.3 C程序组成总结
本章介绍了一个完整程序的结构,程序中使用了以下知识:
使用#include包含头文件;
定义变量;
定义符号常量;
用scanf库函数接收用户输入;
声明函数原型;
调用自定义函数;
定义函数;
用printf库函数显示计算结果。
C 程序可保存在一个扩展名为“.c”的源文件中,也可保存在多个源文件中。每个源文件都可按图2-3所示的顺序编写代码。
图2-3 C程序结构
其中,main函数也可放在整个源文件的最后。