29.头文件 - .h

  • 1.概念

头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:程序员编写的头文件和编译器自带的头文件。
在程序中要使用头文件,需要使用 C 预处理指令 #include 来引用它。前面我们已经看过 stdio.h 头文件,它是编译器自带的头文件。
引用头文件相当于复制头文件的内容,但是我们不会直接在源文件中复制头文件的内容,因为这么做很容易出错,特别在程序是由多个源文件组成的时候。
A simple practice in C 或 C++ 程序中,建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件。

  • 2.引用头文件的语法

使用预处理指令 #include 可以引用用户和系统头文件。它的形式有以下两种:
这种形式用于引用系统头文件。它在系统目录的标准列表中搜索名为 file 的文件。在编译源代码时,您可以通过 -I 选项把目录前置在该列表前。

#include <file>
如:
#include <stdio.h>

这种形式用于引用用户头文件。它在包含当前文件的目录中搜索名为 file 的文件。在编译源代码时,您可以通过 -I 选项把目录前置在该列表前。

#include "filename.h"
如:
#include "myheader.h"
  • 3.引用头文件的操作

#include 指令会指示 C 预处理器浏览指定的文件作为输入。预处理器的输出包含了已经生成的输出,被引用文件生成的输出以及 #include 指令之后的文本输出。
例如,如果您有一个头文件 header.h,内容如下:

char *test(void);

和一个使用了头文件的主程序 program.c,如下:

int x;
#include "header.h"

int main(void)
{
  puts(test ());
}

编译器会看到如下的代码信息:

int x;
char *test(void);

int main(void)
{
  puts(test ());
}
  • 只引用一次头文件

如果一个头文件被引用两次,编译器会处理两次头文件的内容,这将产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,如下:

#ifndef HEADER_FILE
#define HEADER_FILE
......
#endif

这种结构就是通常所说的包装器 #ifndef。当再次引用头文件时,条件为假,因为 HEADER_FILE 已定义。此时,预处理器会跳过文件的整个内容,编译器会忽略它。

  • 有条件引用

有时需要从多个不同的头文件中选择一个引用到程序中。例如,需要指定在不同的操作系统上使用的配置参数。您可以通过一系列条件来实现这点,如下:

#if SYSTEM_1
  #include "system_1.h"
#elif SYSTEM_2
  #include "system_2.h"
#elif SYSTEM_3
  #include "system_3.h"
....
#endif

> 但是如果头文件比较多的时候,这么做是很不妥当的,预处理器使用宏来定义头文件的名称。这就是所谓的有条件引用。它不是用头文件的名称作为 #include 的直接参数,您只需要使用宏名称代替即可:
```c
#define SYSTEM_H "system_1.h"
...
#include SYSTEM_H

SYSTEM_H 会扩展,预处理器会查找 system_1.h,就像 #include 最初编写的那样。SYSTEM_H 可通过 -D 选项被您的 Makefile 定义。

  • 4.标准库头文件

C 标准库头文件(Standard Library Header Files)是由 ANSI C(也称为 C89/C90)和 ISO C(C99 和 C11)标准定义的一组头文件,这些头文件提供了大量的函数、宏和类型定义,用于处理输入输出、字符串操作、数学计算、内存管理等常见的编程任务。
以下是一些常见的 C 标准库头文件及其功能简介:

头文件 功能简介
<stdio.h> 标准输入输出库,包含 printf、scanf 等函数
<stdlib.h> 标准库函数,包含内存分配、程序控制等函数
<string.h> 字符串操作函数,如 strlen、strcpy 等
<math.h> 数学函数库,如 sin、cos、sqrt 等
<time.h> 时间和日期函数,如 time、strftime 等
<ctype.h> 字符处理函数,如 isalpha、isdigit 等
<limits.h> 定义各种类型的限制值,如 INT_MAX 等
<float.h> 定义浮点类型的限制值,如 FLT_MAX 等
<assert.h> 断言宏 assert,用于调试检查
<errno.h> 定义错误码变量 errno 及相关宏
<stddef.h> 定义通用类型和宏,如 size_t、NULL 等
<signal.h> 处理信号的函数和宏,如 signal 等
<setjmp.h> 提供非本地跳转功能的宏和函数
<locale.h> 地域化相关的函数和宏,如 setlocale 等


  • 案例
  • 一个典型的头文件通常包含以下结构:
// 文件名:myheader.h 
#ifndef MYHEADER_H  // 头文件保护,防止重复包含
#define MYHEADER_H

// 包含其他必要的头文件
#include <stdio.h>
#include <stdlib.h>

// 宏定义
#define PI 3.1415926
#define MAX(a, b) ((a) > (b) ? (a) : (b))

// 类型定义 结构体
typedef struct {
    int x;
    int y;
} Point;

// 函数声明
void print_hello();
double circle_area(double radius);

#endif // MYHEADER_H
  • 案例2 -函数声明与实现分离

这是最常见的用法,将声明放在头文件中,实现在.c文件中。

geometry.h

#ifndef GEOMETRY_H
#define GEOMETRY_H

// 声明计算圆的面积
double circle_area(double radius);

// 声明计算矩形的面积
double rectangle_area(double width, double height);

#endif

geometry.c

#include "geometry.h"  //包含头文件

//实现计算圆的面积
double circle_area(double radius) {
    return 3.14159 * radius * radius;
}

double rectangle_area(double width, double height) {
    return width * height;
}

main.c

#include <stdio.h>
#include "geometry.h"

int main() {
    printf("Circle area: %.2f\n", circle_area(5.0));
    printf("Rectangle area: %.2f\n", rectangle_area(4.0, 6.0));
    return 0;
}
  • 结构体和联合体定义

student.h

#ifndef STUDENT_H
#define STUDENT_H

typedef struct {
    char name[50];
    int age;
    float gpa;
} Student;

void print_student(Student s);

#endif

student.c

#include <stdio.h>
#include "student.h"

void print_student(Student s) {
    printf("Name: %s\n", s.name);
    printf("Age: %d\n", s.age);
    printf("GPA: %.2f\n", s.gpa);
}
  • 条件编译和宏定义

config.h

#ifndef CONFIG_H
#define CONFIG_H

// 根据平台定义不同的宏
#ifdef _WIN32
    #define PLATFORM "Windows"
    #define PATH_SEPARATOR '\\'
#elif __linux__
    #define PLATFORM "Linux"
    #define PATH_SEPARATOR '/'
#else
    #define PLATFORM "Unknown"
#endif

// 调试模式开关
#define DEBUG_MODE 1

#endif

其中 #define DEBUG_MODE 1 用法如下:

  • (1) 控制调试输出
#include "config.h"

void some_function() {
    #if DEBUG_MODE == 1
        printf("[DEBUG] Entering some_function()\n");
    #endif

    // 业务逻辑代码...

    #if DEBUG_MODE == 1
        printf("[DEBUG] Exiting some_function()\n");
    #endif
}
  • (2) 启用/禁用断言
#include <assert.h>
#include "config.h"

void validate_input(int x) {
    #if DEBUG_MODE == 1
        assert(x >= 0 && "Input must be non-negative!");
    #endif
}
  • 更灵活的调试控制
// config.h
#define DEBUG_LEVEL 0  // 0:无调试, 1:基本日志, 2:详细日志

// 使用示例
#if DEBUG_LEVEL >= 1
    printf("[INFO] Starting process...\n");
#endif

#if DEBUG_LEVEL >= 2
    printf("[DETAIL] Current value: %d\n", internal_value);
#endif
posted @ 2025-04-08 21:54  little小新  阅读(71)  评论(0)    收藏  举报