自学内容网 自学内容网

C语言18--头文件

头文件的作用

通常,一个常规的C语言程序会包含多个源码文件(.c),当某些公共资源需要在各个源码文件中使用时,为了避免多次编写相同的代码,一般的做法是将这些大家都需要用到的公共资源放入头文件(.h)当中,然后在各个源码文件中直接包含即可。

头文件的内容

  • 头文件中所存放的内容,就是各个源码文件的彼此可见的公共资源,包括:
  1. 全局变量的声明。
  2. 普通函数的声明。
  3. 静态函数的定义。
  4. 宏定义。
  5. 结构体、联合体的声明。
  6. 枚举常量列表的声明。
  7. 其他头文件。

示例代码:

// head.h
extern int global; // 1,全局变量的声明
extern void f1();  // 2,普通函数的声明
static void f2()   // 3,静态函数的定义
{
    ...
}
#define MAX(a, b) ((a)>(b)?(a):(b)) // 4,宏定义
struct node    // 5,结构体的定义
{
    ...
};
union attr    // 6,联合体的定义
{
    ...
};
#include <unistd.h> // 7,其他头文件
#include <string.h>
#include <stdint.h>
  • 特别说明:
    1. 全局变量、普通函数的定义一般出现在某个源文件(*.c *.cpp)中,其他的源文件想要使用都需要进行声明,因此声明语句一般放在头文件中更方便。
    2. 静态函数、宏定义、结构体、联合体的声明都只能在其所在的文件可见,因此如果多个源文件都需要使用的话,放到头文件中定义是最方便,也是最安全的选择。

头文件的使用

头文件编写好了之后,就可以被各个所需要的源码文件包含了,包含头文件的语句就是如下预处理指令:

// main.c
#include "head.h"  // 包含自定义的头文件
#include <stdio.h> // 包含系统预定义的文件

int main()
{
    ...
}

头文件包含的细节:

#include <stdio.h>  
/*
#include <...> search starts here:   <>默认从以下系统路径寻找
 /usr/lib/gcc/x86_64-linux-gnu/11/include
 /usr/local/include
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.

*/

#include "./inc/myHead.h"  // 使用双引号包含的头文件会从当前代码所处的路径下搜索
// 由于一般我们自己写的头文件会于项目存放在同一个路径下
// 因此一般会使用双引号来包含自己(非系统的头文件)编写的头文件
// 以上例子中有写该头文件的详细路径,因此编译时直接gcc 即可


#include "myHead.h" // 没有写明头文件的具体相对路径
// 因此编译时需要告知编译器被你存储与哪个文件
gcc  xxx.c   -I./inc 
-I 用于告知编译器头文件的路径
./inc  则是具体的路径值

可以看到,在源码文件中包含指定的头文件有两种不同的形式:

  • 使用双引号:在指定位置 + 系统标准路径搜索 head.h
  • 使用尖括号:在系统标准路径搜索 stdio.h

由于自定义的头文件一般放在源码文件的周围,因此需要在编译的时候通过特定的选项来指定位置,而系统头文件都统一放在标准路径下,一般无需指定位置。

假设在源码文件 main.c 中,包含了两个头文件:head.h 和 stdio.h ,由于他们一个是自定义头文件,一个是系统标准头文件,前者放在项目 pro/inc 路径下,后者存放于系统头文件标准路径下(一般位于 /usr/include),因此对于这个程序的编译指令应写作:

gec@ubuntu:~/pro$ gcc main.c -o main -I./inc

头文件的格式

由于头文件包含指令 #include 的本质是复制粘贴,并且一个头文件中可以嵌套包含其他头文件,因此很容易出现一种情况是:头文件被重复包含。

  • 使用条件编译,解决头文件重复包含的问题,格式如下:
    • 作用:防止头文件被多次重复包含后出现重复定义的问题
#ifndef _HEADNAME_H   
#define _HEADNAME_H

...
... (头文件正文)
...

#endif

其中,HEADNAME一般取头文件名称的大写

项目的基本框架:

编译命令:

gcc src/*.c -o bin/demo -I./inc

gcc         #编译命令
src/*.c     #需要编译的源文件 *.c 表示该路径下的所有.c文件
-o          #指明输出的文件名字
bin/demo    #具体输出的可执行文件路径+名字
-I./inc     # -I指定头文件的路径 ./inc 具体的路径

C语言的关键字:

在C语言中,以下是一些关键字及其作用:

1. #include:预处理指令,用于包含头文件。例如:#include <stdio.h> 包含头文件 stdio.h,以便使用其中的函数。

2. #define:预处理指令,用于定义宏。例如:#define MAX 1000 定义一个宏 MAX,值为 1000。

3. #undef:预处理指令,用于取消定义宏。例如:#undef MAX 取消定义宏 MAX。

4. #ifdef:预处理指令,用于检查当前定义的宏是否已定义。例如:#ifdef MACRO 检查是否定义了宏 MACRO。

5. #ifndef:预处理指令,用于检查当前定义的宏是否未定义。例如:#ifndef MACRO 检查是否未定义宏 MACRO。

6. #else:预处理指令,用于实现条件编译。例如:#ifdef MACRO #else 表示当宏 MACRO 已定义时执行 #else 中的代码。

7. #elif:预处理指令,用于实现条件编译的 elif 分支。例如:#ifdef MACRO #elif MACRO2 表示当宏 MACRO 已定义时执行 #elif MACRO2 中的代码。

8. #endif:预处理指令,用于结束条件编译。例如:#endif 结束条件编译。

9. #error:预处理指令,用于生成错误信息。例如:#error "Error message" 生成错误信息 "Error message"。

10. #pragma:预处理指令,用于提供编译器特定的信息。例如:#pragma pack(1) 告诉编译器使用大小为 1 的对齐方式。

11. #volatile:预处理指令,用于指示对变量的访问应该使用寄存器方式。例如:#volatile 修饰的变量在汇编语言中不会被优化。

12. #restrict:预处理指令,用于提示编译器使用 restrict 属性。例如:#restrict int *a 修饰的指针 a,编译器会使用 restrict 属性。

13. auto:关键字,用于指定变量为自动变量。例如:auto int a 声明一个自动变量 a。

14. static:关键字,用于指定变量为静态变量。例如:static int a 声明一个静态变量 a。

15. register:关键字,用于指定变量为寄存器变量。例如:register int a 声明一个寄存器变量 a。

16. volatile:关键字,用于指定变量为 volatile 变量。例如:volatile int a 声明一个 volatile 变量 a。

17. const:关键字,用于指定变量为常量。例如:const int a 声明一个常量变量 a。

18. inline:关键字,用于指定函数为内联函数。例如:inline void func() { ... } 声明一个内联函数 func。

19. extern:关键字,用于指定变量为外部变量。例如:extern int a 声明一个外部变量 a。

20. __cdecl、__stdcall、__fastcall、__thiscall:调用约定,用于指定函数的调用方式。例如:__cdecl void func() { ... } 声明一个使用 __cdecl 调用约定函数 func。

21. __attribute__:属性,用于指定函数的属性,如限制参数数量、设置栈大小等。例如:__attribute__((stdcall)) void func(int arg) { ... } 声明一个使用 stdcall 调用约定函数 func,并限制参数数量为 int arg。

22. __packed:属性,用于指定变量或结构体的对齐方式。例如:__packed int a 声明一个使用 __packed 属性变量 a。

23. __section:属性,用于指定变量或结构体放置的节区。例如:__section(".data") int a 声明一个使用 __section(".data") 属性变量 a。

24. __alignof__:运算符,用于获取变量或类型的对齐字节数。例如:__alignof__(int) 获取 int 类型的对齐字节数。

25. __builtin_offsetof:函数,用于获取结构体中成员的偏移量。例如:__builtin_offsetof(struct my_struct, my_member) 获取 struct my_struct 中的 my_member 成员的偏移量。

26. __has_feature:宏,用于检查编译器是否具有某些特性。例如:__has_feature(c++11) 检查编译器是否具有 c++11 特性。

27. __is_available:宏,用于检查某个特性是否可用。例如:__is_available("avx") 检查 avx 特性是否可用。

28. __clang_builtin_macro:宏,用于检查某个宏是否为 clang 内置宏。例如:__clang_builtin_macro("__clang_max__") 检查 __clang_max__ 是否为 clang 内置宏。

29. __GNUC_MINOR__:宏,用于获取 gcc 版本的小版本号。例如:__GNUC_MINOR__ 获取 gcc 的 minor 版本号。

30. __GNUC_PATCHLEVEL__:宏,用于获取 gcc 版本的补丁版本号。例如:__GNUC_PATCHLEVEL__ 获取 gcc 的 patchlevel 版本号。

结语:

        在这篇博客中,我们详细探讨了C语言中头文件的概念及其重要性。头文件作为代码组织的一部分,不仅便利了函数和变量的声明,也使得不同源文件之间的协作变得更加高效。通过合理地使用头文件,我们能够模块化代码,提高可读性和可维护性。

        此外,了解如何创建和管理自定义头文件,能够帮助我们减少代码重复,提升项目的结构性。在实际开发中,正确使用头文件可以降低错误的发生率,并使调试过程更加顺畅。

        希望这篇文章能够帮助您深入理解C语言中的头文件,以及如何在项目中有效地使用它们。随着编程技能的提升,对代码结构和组织方式的重视将为您的开发工作带来长远的好处。感谢您的阅读,期待与您在今后的讨论中共同分享和学习更多编程知识!


原文地址:https://blog.csdn.net/weixin_69902486/article/details/142350874

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!