C语言基础专题 - 头文件引用

简介: C语言基础专题 - 头文件引用

C语言基础专题 - 头文件引用


1.🧐什么是头文件?

头文件是扩展名为 .h 的文件,这是一个文本文件,内容包含了:

2.🧐如何引用头文件?

这个事情我们所有人写的第一个程序Hello World.c中都做过:

#include <stdio.h>     // 引用头文件
int main(){
   printf("Hello World")
}

这里被引用的头文件stdio.h它是编译器自带的,属于系统头文件。

我们不但可以引用系统头文件,也可以引用自己写的头文件,即用户头文件,但这再语法上由略微差别:

#include <file>    //  用于引用名为file的系统头文件,默认在系统目录的标准列表中搜索该文件
#include "file"     //  用于引用名为file的用户头文件 ,默认在包含当前文件的目录中搜索该文件

C语言的编译器有很多,对于以上两种使用头文件的方法一般都提供了相关选项以加入搜索头文件的路径,也常有在名为INCLUDE环境变量中加入搜索路径,以告诉编译器头文件的位置。

比如,在GCC中(以下引用内容摘录自GCC官方文档3.16目录搜索的选项章节,有外语基础的读者可以自行阅读。)

这些选项指定目录以搜索头文件,库和编译器的一部分:

-I dir
-iquote dir
-isystem dir
-idirafter dir

它们在预处理(可以参考C语言预处理部分内容)的时候将目录dir添加到要在预处理过程中搜索头文件的目录列表中。如果dir以'='或>$SYSROOT,然后是'='或被$SYSROOTsysroot前缀替换;看到 --sysroot->isysroot

指定的目录 -I 引用仅适用于指令的引号形式。指定的目录#include "file" -I-isystem,或者 -idirafter适用于 #include "file"#include <file>指令的查找 。

您可以在命令行上指定任何数目或这些选项的组合,以在多个目录中搜索头文件。查找顺序如下:

  • (1)对于include指令的引号形式,将首先搜索当前文件的目录。
  • (2)对于include指令的引号形式,目录由 -我引用 选项在命令行中按从左到右的顺序搜索。
  • (3)指定目录 -I 选项以从左到右的顺序扫描。
  • (4)指定目录 -isystem 选项以从左到右的顺序扫描。
  • (5)扫描标准系统目录。
  • (6)指定目录 -idirafter 选项以从左到右的顺序扫描。

您可以使用 -I覆盖系统头文件,替代您自己的版本,因为这些目录是在标准系统头文件目录之前搜索的。但是,您不应使用此选项来添加包含供应商提供的系统头文件的目录。采用-isystem为了那个原因。

-isystem-idirafter 选项还将该目录标记为系统目录,以便对它进行与标准系统目录相同的特殊处理。


如果标准系统包含目录,或使用以下命令指定的目录 -isystem,也指定了 -I-I 选项将被忽略。该目录仍在搜索,但作为系统目录位于系统包含链中的正常位置。这是为了确保#include_next不会错误地更改GCC修正错误的系统头的过程以及该指令的顺序。如果您确实需要更改系统目录的搜索顺序,请使用-nostdinc 和/或 -isystem 选项。

3.🧐头文件中有一般写了什么?

如果想自己写一个头文件,那还是有必要了解一下头文件的内容的。我们不妨先打开我们从写HelloWorld.c就开始使用的stdio.h一探究竟。

/* Checking macros for stdio functions.
   Copyright (C) 2004, 2005, 2009 Free Software Foundation, Inc.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 3, or (at your option) any later
version.
In addition to the permissions in the GNU General Public License, the
Free Software Foundation gives you unlimited permission to link the
compiled version of this file into combinations with other programs,
and to distribute those combinations without any restriction coming
from the use of this file.  (The General Public License restrictions
do apply in other respects; for example, they cover modification of
the file, and distribution when not linked into a combine
executable.)
GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  */
#ifndef _SSP_STDIO_H
#define _SSP_STDIO_H 1
#include <ssp.h>
#include_next <stdio.h>
#if __SSP_FORTIFY_LEVEL > 0
#include <stdarg.h>
#undef sprintf
#undef vsprintf
#undef snprintf
#undef vsnprintf
#undef gets
#undef fgets
extern int __sprintf_chk (char *__restrict__ __s, int __flag, size_t __slen,
        __const char *__restrict__ __format, ...);
extern int __vsprintf_chk (char *__restrict__ __s, int __flag, size_t __slen,
         __const char *__restrict__ __format,
         va_list __ap);
#define sprintf(str, ...) \
  __builtin___sprintf_chk (str, 0, __ssp_bos (str), \
         __VA_ARGS__)
#define vsprintf(str, fmt, ap) \
  __builtin___vsprintf_chk (str, 0, __ssp_bos (str), fmt, ap)
extern int __snprintf_chk (char *__restrict__ __s, size_t __n, int __flag,
         size_t __slen, __const char *__restrict__ __format,
         ...);
extern int __vsnprintf_chk (char *__restrict__ __s, size_t __n, int __flag,
          size_t __slen, __const char *__restrict__ __format,
          va_list __ap);
#define snprintf(str, len, ...) \
  __builtin___snprintf_chk (str, len, 0, __ssp_bos (str), __VA_ARGS__)
#define vsnprintf(str, len, fmt, ap) \
  __builtin___vsnprintf_chk (str, len, 0, __ssp_bos (str), fmt, ap)
extern char *__gets_chk (char *__str, size_t);
extern char *__SSP_REDIRECT (__gets_alias, (char *__str), gets);
extern inline __attribute__((__always_inline__)) char *
gets (char *__str)
{
  if (__ssp_bos (__str) != (size_t) -1)
    return __gets_chk (__str, __ssp_bos (__str));
  return __gets_alias (__str);
}
extern char *__SSP_REDIRECT (__fgets_alias,
           (char *__restrict__ __s, int __n,
            FILE *__restrict__ __stream), fgets);
extern inline __attribute__((__always_inline__)) char *
fgets (char *__restrict__ __s, int __n, FILE *__restrict__ __stream)
{
  if (__ssp_bos (__s) != (size_t) -1 && (size_t) __n > __ssp_bos (__s))
    __chk_fail ();
  return __fgets_alias (__s, __n, __stream);
}
#endif /* __SSP_FORTIFY_LEVEL > 0 */
#endif /* _SSP_STDIO_H */

可以看到:

  • 写在最前边的一般是文件内容概述(该文件为检查标准函数的宏(Checking macros for stdio functions.))、版权说明等。
  • 对于程序预处理与之后编译实质内容时后面的c代码,这些代码一般是从以前中写在源文件最前面(头部)的位置,我们将这些代码摘出来,因此叫做头文件。
  • 该文件中还引入了大量其它的头文件。
  • 除此之外我们还看到了大量的extern有关的语句,extern用于调用另一个c文件里的变量或者函数。

4.👨‍🏫条件引用

更多相关指令可以参考:C语言-预处理

这里我们先了解下面这几条预处理指令:

指令 说明
#if 如果给定条件为真,则执行预处理的内容
#else 表示#if后的条件为假时执行预处理的内容
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if……#else 条件编译块
比如:
#if CONDITION_1              // 若第1个条件成立
   # include "headfile_1.h"  // 引入头文件"headfile_1.h"
#elif CONDITION_2            // 若第2个条件成立
   # include "headfile_2.h"  // 引入头文件"headfile_2.h"
   ...
#endif                       // 条件指令的结束

5.👨‍🏭用宏定义确保单次引用

在C语言中提供了以下两个指令:

指令 说明
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
为了防止由于多次头文件多次引用导致某些情况下产生错误,我们需要使用上面两个指令。请看代码:
#ifndef A                        //  当没有宏定义A时
#define A  包含所有头文件的文件B   // 宏定义A为包含所有头文件的文件B 
#endif                           // 用于结束一个 #if……#else 条件编译块

为了防止意外,而把头文件的内容都放在#ifndef#endif指令块中。这样作无论头文件会不会被多个文件引用,也就是当再次引用到头文件HeadFileB时,条件为假。

目录
相关文章
C4.
|
1月前
|
存储 程序员 C语言
C语言中如何通过指针引用字符串
C语言中如何通过指针引用字符串
C4.
16 0
C4.
|
1月前
|
算法 安全 程序员
C语言中如何通过指针引用数组
C语言中如何通过指针引用数组
C4.
20 0
|
5月前
|
编译器 程序员 C语言
26 C语言 - 头文件
26 C语言 - 头文件
36 0
C4.
|
1月前
|
存储 程序员 数据处理
C语言怎样定义和引用一维数组
C语言怎样定义和引用一维数组
C4.
16 0
C4.
|
1月前
|
存储 C语言
C语言怎样定义和引用二维数组
C语言怎样定义和引用二维数组
C4.
16 0
|
4天前
|
存储 C语言
C语言中字符串的引用与数组元素操作
C语言中字符串的引用与数组元素操作
12 0
|
1月前
|
编译器 API C语言
C语言头文件
C语言头文件
12 0
|
3月前
|
C语言
详解C语言可变参数列表(stdarg头文件及其定义的宏)
详解C语言可变参数列表(stdarg头文件及其定义的宏)
48 0
|
4月前
|
编译器 C语言
C语言标准头文件 如何打印“hello,world”
C语言标准头文件 如何打印“hello,world”
|
4月前
|
前端开发 算法 JavaScript
【新手解答3】深入探索 C 语言:头文件提供必要的接口、源文件保持实现细节的私有性 + 进一步学习的方向 + 如何快速编写程序并最终能制作小游戏
【新手解答3】深入探索 C 语言:头文件提供必要的接口、源文件保持实现细节的私有性 + 进一步学习的方向 + 如何快速编写程序并最终能制作小游戏
61 0