天天看点

C语言头文件到底是什么?

C语言头文件到底是什么?

  • 在C语言学习的时候总是会引入这样的语句

    #include <stdio.h>

    ,书上解释说把

    stdio.h

    这个文件的全部内容直接插入到这个位置,然后再经过C语言的编译器编译运行。这么看来隐含的意思好像是

    .h

    头文件好想并不直接参与编译。
  • 围绕这个话题引出了下面这几个问题。

一,

.h

头文件会参与编译吗?

  • 不妨来做个实验

这个是

head.h

文件的内容

#include <stdio.h>

int main() {
	printf("Hello World!");
	return 0;
}
           

这个是

ori.c

文件的内容

#include "head.h"
           
编译执行

gcc ori.c -o ori

发现输出的是
>> .\ori.exe
>> hello world!
           

.c

文件中并没有引入任何其他的文件,除了我们自己定义的

head.h

头文件,而在这个头文件中,我们引入了

stdio.h

头文件,并且我们在

head.h

里面定义的

main

函数被执行了,由此证明了

include xxx.h

是直接原封不同的插入到引用这个头文件的

.c

文件中的。
但是会参与编译吗?
为此设计下面这个实验:
  • 开一个项目,在

    .h

    头文件里面定义

    main

    函数,
  • 不引用这个头文件直接编译整个项目,然后执行,
  • 如果没有输出

    main

    函数内部的内容,那么表明如果不加引用,

    .h

    文件不参与编译。
  • 否则就参与编译。

这个是

head.h

文件内容

#include <stdio.h>

int main() {
	printf("Hello World! I am head.h");
	return 0;
}
           

这个是

ori.c

文件的内容

// nothing 
           
编译执行

gcc ori.c -o ori

发现输出的是
C:/Program Files/MinGW/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/../lib/libmingw32.a(lib64
_libmingw32_a-crt0_c.o):crt0_c.c:(.text.startup+0x2e): undefined reference to `WinMain'
collect2.exe: error: ld returned 1 exit status
           
报错了,

undefined reference to WinMain

未定义

WinMain

这时候我们在

ori.c

内部定义这样的一个函数

#include <stdio.h>

int WinMain() {
	printf("Hello world! I am WinMain!");
	return 0;
}
           
再次编译运行

gcc ori.c -o ori

运行

.\ori.exe

>> .\ori.exe
>> Hello world! I am WinMain!
           
发现正常输出了

WinMain

函数内部的内容,这也告诉我们

WinMain

函数同样可以作为程序的入口。
经过实验发现,的的确确

.h

绝对不是直接参与编译的,而是通过

.c

文件中

#include "xxx.h"

语句手动插入的。

这其实间接解答了在

.h

头文件中定义的静态局部变量无法局部化的原因。

二,

.h

头文件中的静态全局变量为什么可以被访问?

我们在学习C语言初期就直到,如果对一个全局变量使用

static

语句修饰的话,就可以把这个变量限制在本文件的访问域内,而无法被其他文件访问,但是这一点对于

.h

文件中无效,该访问还是可以访问?下面看一下实验
创建三个文件

ori1.c

,

ori2.c

,

head.h

ori1.c

中写下以下内容

#include <stdio.h>

extern int A;
extern int B;

int main() {
	printf("A = %d\n", A);
	printf("B = %d", B);
	return 0;
}
           

ori2.c

中写下以下内容

#include <stdio.h>

int A = 12;
static B = 13;
           

此时在

head.h

中不写入任何内容

编译运行
正如和我们学过的一样,出现了未定义的错误
undefined reference to `B'
collect2.exe: error: ld returned 1 exit status
           
静态全局变量不可以被其他变量修改

这时候我们给

ori2.h

添加两个函数

void getB() {
    printf("B = %d\n", B);
}

void changeB(int num) {
    B = num;
}
           

然后在

ori1.c

中去声明应用一下这两个函数

#include <stdio.h>

extern int A;

extern void getB();
extern void changeB(int B);

int main() {
    printf("A = %d\n", A);
    getB();
    changeB(1600);
    getB();
    return 0;
}
           
编译运行
>> A = 12
   B = 13
   B = 1600
           
虽然无法直接访问,但是可以定义

ori2.c

文件里面的函数去对该变量访问,这或许就是私有变量的雏形吧!
下面再来看看有关于

.h

中的全局变量,

由于我们已经知道了是完全插入的形式,那么我们可以理解到这个定义的静态全局变量是定义在引用它的文件里面

根据前文中定义在自己文件内部的静态全局变量只能被自己访问,那么结果自然显而易见了,

.h

文件中的静态全局变量作用域是引用这个头文件的

.c

文件

 自然而然的,我们应当明白,如果一个

.h

文件中定义过多的全局变量,那么这个全局变量被其他的变量引用的时候就会出现访问无指向的错误!重复定义

建议尽可能少在

.h

头文件中定义全局变量,或者静态全局变量

三,条件编译

C语言编译是把整个项目编译,而很多的

.c

文件都需要引用同一个函数,而正对于不同的系统又出现了不同的编译模式,如果说要把所有的

.c

文件头部都写入那些描述条件编译的代码,那么所有的代码写起来会复杂无比
所以把条件编译代码放在

.h

文件里面,直接配置好才是一个好的选择。

条件编译的例子

#include <stdio.h>
#include <stdlib.h>

int main() {
    #if _WIN64
        system("color 0c");
        printf("Hello World!");
    #elif __linux__
        printf("\033[22;31mHello World!\n\\033[22;30m\\");
    #else
        printf("Hello World!");
    #endif
    return 0;
}
           
#include <stdio.h>

int main() {
#if _WIN64
    printf("This is Windows!\n");
#else
    printf("Unknown platform!\n");
#endif

#if __linux__
    printf("This is Linux!\n");
#endif
    return 0;
}
           

使用

#ifdef

判断宏是否被编译过,与之对应的还有一个

#ifndef

表示如果没有被定义的话、

#include <stdio.h>
#include <stdlib.h>
int main(){
#ifdef _DEBUG
    printf("正在使用 Debug 模式编译程序...\n");
#else
    printf("正在使用 Release 模式编译程序...\n");
#endif
    system("pause");
    return 0;
}
           

#if

后面接的是整形常量表达式,而

#ifdef

后面只能接宏名

又由于自己可以定义宏,自然而然的自己就可以配置出自己的项目的条件编译。

四,一点建议

  1. 用的多的函数放在

    .h

    头文件中定义声明。
  2. 尽量不要在

    .h

    头文件中设置全局变量,或者静态全局变量。
  3. .h

    头文件中使用条件编译控制项目的编译,简化代码书写成本。