28-使用头文件
9.4.5 使用头文件
如果把 main()
放在第1个文件中,把函数定义放在第2个文件中,那么第1个文件仍然要使用函数原型。把函数原型放在头文件中,就不用在每次使用函数文件时都写出函数的原型。C标准库就是这样做的,例如,把I/O函数原型放在 stdio.h
中,把数学函数原型放在 math.h
中。你也可以这样用自定义的函数文件。
另外,程序中经常用C预处理器定义符号常量。这种定义只存储了那些包含 #define
指令的文件。如果把程序的一个函数放进一个独立的文件中,你也可以使用 #define
指令访问每个文件。最直接的方法是在每个文件中再次输入指令,但是这个方法既耗时又容易出错。另外,还会有维护的问题:如果修改了 #define
定义的值,就必须在每个文件中修改。更好的做法是,把 #define
指令放进头文件,然后在每个源文件中使用 #include
指令包含该文件即可。
总之,把函数原型和已定义的字符常量放在头文件中是一个良好的编程习惯。我们考虑一个例子:假设要管理4家酒店的客房服务,每家酒店的房价不同,但是每家酒店所有房间的房价相同。对于预订住宿多天的客户,第2天的房费是第1天的95%,第3天是第2天的95%,以此类推(暂不考虑这种策略的经济效益)。设计一个程序让用户指定酒店和入住天数,然后计算并显示总费用。同时,程序要实现一份菜单,允许用户反复输入数据,除非用户选择退出。
程序清单9.9、程序清单9.10和程序清单9.11演示了如何编写这样的程序。第1个程序清单包含 main()
函数,提供整个程序的组织结构。第2个程序清单包含支持的函数,我们假设这些函数在独立的文件中。最后,程序清单9.11列出了一个头文件,包含了该程序所有源文件中使用的自定义符号常量和函数原型。前面介绍过,在UNIX和DOS环境中, #include "hotels.h"
指令中的双引号表明被包含的文件位于当前目录中(通常是包含源代码的目录)。如果使用IDE,需要知道如何把头文件合并成一个项目。
程序清单9.9 usehotel.c
控制模块
/* usehotel.c -- 房间费率程序 */
/* 与程序清单9.10一起编译 */
#include <stdio.h>
#include "hotel.h" /* 定义符号常量,声明函数 */
int main(void)
{
int nights;
double hotel_rate;
int code;
while ((code = menu()) != QUIT)
{
switch (code)
{
case 1: hotel_rate = HOTEL1;
break;
case 2: hotel_rate = HOTEL2;
break;
case 3: hotel_rate = HOTEL3;
break;
case 4: hotel_rate = HOTEL4;
break;
default: hotel_rate = 0.0;
printf("Oops!\n");
break;
}
nights = getnights();
showprice(hotel_rate, nights);
}
printf("Thank you and goodbye.\n");
return 0;
}
程序清单9.10 hotel.c
函数支持模块
/* hotel.c -- 酒店管理函数 */
#include <stdio.h>
#include "hotel.h"
int menu(void)
{
int code, status;
printf("\n%s%s\n", STARS, STARS);
printf("Enter the number of the desired hotel:\n");
printf("1) Fairfield Arms 2) Hotel Olympic\n");
printf("3) Chertworthy Plaza 4) The Stockton\n");
printf("5) quit\n");
printf("%s%s\n", STARS, STARS);
while ((status = scanf("%d", &code)) != 1 ||
(code < 1 || code > 5))
{
if (status != 1)
scanf("%*s"); // 处理非整数输入
printf("Enter an integer from 1 to 5, please.\n");
}
return code;
}
int getnights(void)
{
int nights;
printf("How many nights are needed? ");
while (scanf("%d", &nights) != 1)
{
scanf("%*s"); // 处理非整数输入
printf("Please enter an integer, such as 2.\n");
}
return nights;
}
void showprice(double rate, int nights)
{
int n;
double total = 0.0;
double factor = 1.0;
for (n = 1; n <= nights; n++, factor *= DISCOUNT)
total += rate * factor;
printf("The total cost will be $%0.2f.\n", total);
}
程序清单9.11 hotel.h
头文件
/* hotel.h -- 符号常量和 hotel.c 中所有函数的原型 */
#define QUIT 5
#define HOTEL1 180.00
#define HOTEL2 225.00
#define HOTEL3 255.00
#define HOTEL4 355.00
#define DISCOUNT 0.95
#define STARS "**********************************"
// 显示选择列表
int menu(void);
// 返回预订天数
int getnights(void);
// 根据费率、入住天数计算费用
// 并显示结果
void showprice(double rate, int nights);
下面是这个多文件程序的运行示例:
********************************************************************
Enter the number of the desired hotel:
1) Fairfield Arms 2) Hotel Olympic
3) Chertworthy Plaza 4) The Stockton
5) quit
********************************************************************
3
How many nights are needed? 1
The total cost will be $255.00.
********************************************************************
Enter the number of the desired hotel:
1) Fairfield Arms 2) Hotel Olympic
3) Chertworthy Plaza 4) The Stockton
5) quit
********************************************************************
4
How many nights are needed? 3
The total cost will be $1012.64.
********************************************************************
Enter the number of the desired hotel:
1) Fairfield Arms 2) Hotel Olympic
3) Chertworthy Plaza 4) The Stockton
5) quit
********************************************************************
5
Thank you and goodbye.
顺带一提,该程序中有几处编写得很巧妙。尤其是, menu()
和 getnights()
函数通过测试 scanf()
的返回值来跳过非数值数据,而且调用 scanf("%
* s")
跳至下一个空白字符。注意, menu()
函数中是如何检查非数值输入和超出范围的数据:
while ((status = scanf("%d", &code)) != 1 ||(code < 1 || code > 5))
以上代码段利用了C语言的两个规则:从左往右对逻辑表达式求值;一旦求值结果为假,立即停止求值。在该例中,只有在 scanf()
成功读入一个整数值后,才会检查 code
的值。
用不同的函数处理不同的任务时应检查数据的有效性。当然,首次编写 menu()
或 getnights()
函数时可以暂不添加这一功能,只写一个简单的 scanf()
即可。待基本版本运行正常后,再逐步改善各模块。