提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里可以添加本文要记录的大概内容:
软件项目中,最让新手头疼的就是各种头文件。
提示:以下是本篇文章正文内容,下面案例可供参考
一、基本概念
头文件的作用就是对一个模块(c文件)封装的API函数进行声明,其他模块想要调用这个接口函数,必须要首先包含该对应的头文件,然后才可以直接使用。
为什么要先声明后使用 – C语言的历史遗留问题(有兴趣自行研究)
在c语言项目中,除了main,跳转符号(goto)不需要声明,任何标识符在使用之前都要提前声明。可以在函数内部声明,也可以在函数外声明,也可以在头文件中声明。一般情况下,函数的声明会被直接放在头文件中,作为本模块封装的API,供其他模块使用。程序员在其他模块想要引用这些API函数,直接#include这个头文件就可以了,然后就可以直接调用了。
变量的声明和定义
一个变量的定义最终会生成与具体平台相关的内存分配汇编指令,而变量的声明则告诉编译器,该变量可能在其他文件中定义,编译时先不要报错,等链接时可以到指定的文件里去看看有没有。一个变量只能定义一次,只能够分配一次内存空间,但是可以多次声明。变量的定义要放到C文件中,不放在头文件中,因为头文件会被多人使用,被多个文件包含,头文件经过预处理器多次展开之后就编程了多次定义。
// 变量的声明declaration是指在程序中声明变量的存在,但不进行初始化或分配内存空间。
// 声明变量告诉编译器变量的名称和类型,以便在后续的程序中使用。
// 变量声明包括:声明变量的类型,声明变量的标识符
int x; // 表示声明一个整形变量
// 变量的定义是在程序中声明变量的存在,并进行初始化或分配内存空间
// 定义变量是告诉编译器为该变量分配内存空间,并为变量赋予初始值
// 包括:变量类型,变量标识符,变量的值
int y = 20; // 表示定义一个整型变量并赋予初始值为10
// 函数的声明和定义(简单理解)
// 函数的声明不包含函数的实现代码
// 函数的定义包含函数的实现代码
头文件中,除了函数声明,还有数据类型的定义,宏定义等。
如果在一个项目中多次包含相同的头文件,编译器也不会报错,因为在预处理阶段预处理器已经将头文件展开了:一个变量或函数可以有多次声明。但是如果头文件中定义了宏或新的数据类型,头文件被多次包含,编译器在编译时就会出现重定义错误。(因为多次包含,多次展开,多次定义)。为了防止冲定义错误,可以在头文件中使用条件编译来预防头文件的多次包含。
// lcd.h
#ifndef __LCD_H__ // __文件名_H__
#define __LCD_H__
#define PI 3.14
void lcd_init(void);
struct person{
int age;
char name[10];
}
#endif
上面的预处理指令可以预防头文件多次展开,当一个C文件包含多个模块的头文件,通过这种间接包含,有可能多次包含一个头文件,通过上面的预处理指令,无论包含几次,预处理过程只展开一次。
隐式声明
定义:如果一个C程序引用了在其他文件中定义的函数而没有在本文件中声明,编译器也不会报错,编译器会认为这个函数可能会在其他文件中定义,等连接的时候找不到其定义才会报错。
会给出一个warning的警告提示。但是程序依然是可以运行的。
函数的隐式声明带来的冲突,不仅仅是与自己定义函数的冲突,如果我们引用库函数而没有包含对应的头文件,也有可能与库函数发生类型冲突。这可能会给程序带来很多无法预料到的生层次的bug。因此,为了编写高质量的程序,要养成先声明后使用的良好编程习惯。
变量的声明与定义
如何对外部文件的符号进行声明呢?
可以在本文件中使用extern关键字对其他文件中的符号(标识符)进行声明
extern int i;
extern int a[20];
extern int function();
从语法角度来看,extern关键字可以扩展一个全局变量或函数声明的作用域。从编译的角度看,extern关键字用来告诉编译器,这些变量可能已经在别的文件中定义,先不要报错。
定义和声明
- 如果省略extern并且具有初始化语句,则为定义语句。 int i= 10;
- 如果使用extern,无初始化语句,则为声明语句。extern int i;
- 如果没有extern 无初始化语句,试探性定义。 int i;
试探性定义:该变量可能在别的文件中有定义,先暂时声明,若别的文件中没有定义,按照语法规则初始化该变量i,并将该语句定性为定义:初始值为一些默认值NULL,0,undefined values
前向引用,前向声明
C语言规范:先声明后使用
一个变量的声明就是声明其类型,声明是给编译器用于编译器的语法检查的。在C语言中,并不是所有的标识符都需要先声明后使用的。在一个标识符未声明完成之前,就对其引用,一般成为前向引用。
- 隐式声明(c99, c11, c++标准已经禁止)
- 语句标号:跳转向后的标号
- 不完全类型:在被定义完整之前用于某种用途
语句标号:goto关键字可以向前跳跃也可以向后跳跃,不需要对语句标号进行事先声明。
c语言标识符的常见类型:
- object type
- fuction type
- incomplete type
不完全类型incomplete type:
- void
- int a[]
- a structure or union type of unkonwn content
代码如下(示例):
goto error;
int array_print(int a[], int len);
struct LIST_NODE{
struct LIST_NODE *next;
int data;
}; //定义完成结束符,到这里才算对LIST_NODE 标识符声明完成。
对于一个未指定长度的数组,不需要声明就可以直接使用。在链表节点LIST_NODE结构类型中,在LIST_NODE声明完成之前,就直接在结构体内使用其类型定义了一个指针成员next。
当对一个标识符前向引用时,只关注标识符类型,不关注标识符大小,值或具体的事先。当我们对一个不完全类型进行前向引用时,我们只能使用该标识符的部分属性:类型,其他一些属性,如变量值,结构成员,大小等,是不能使用是的。
原理(有兴趣可以查阅资料,读者只是略读)
定义与声明的一致性
- 模块的封装: xxx.c/xxx.h
- 模块的使用:#include"xx.h"
在实际的软件项目中,经常会看到一个模块的C源文件,它会包含自己模块对应的头文件。即自己封装的模块,自己又去调用它
除了可以使用头文件中定义的宏/数据类型,也可以让编译器检查定义与声明的一致性。将函数声明和定义放到一个文件中,编译器在编译时就会帮我们进行自检:检查函数的声明和定义是否一致。
头文件路径
#include<stdio.h>
#include"module.h"
标准库的头文件或者官方路径下的头文件使用<>括号;如果你使用的头文件或者项目中的头文件 “”
头文件的路径一般分为绝对路径和相对路径。绝对路径是以根目录"/"或者windows下的盘符作为起点,相对路径则以程序文件当前的鲁姆作为起点。
在查找头文件的过程中,编译器会按照默认的搜索顺序到不同的路径下去搜索。<>头文件
- 通过GCC参数gcc -I指定的目录
- 通过环境变量CINCLUDEPATH指定的目录
- GCC内定的目录
- 搜索规则:如果不同目录下存在相同的头文件时,先搜到哪个使用哪个,搜索到头文件后不在往下搜索。
当我们使用""双引号包含头文件路径时,编译器首先在项目当前目录搜索需要的头文件,如果在项目当前目录下搜索不到,再到其他指定路径下搜索。
- 项目当前目录
- 通过gcc参数 gcc -I指定的内容
- 通过环境变量CINCLUDEPATH指定的目录
- gcc的内定目录
- 搜素规则:当不同目录下存在相同的头文件时,先搜索到哪个就是用哪个
程序编译时,如果我们的头文件没有放到官方的路径下面,那么我们可以通过gcc -I来指定头文件的路径,如果不想通过这种方式,可以通过设置环境变量来添加头文件的搜索路径。在Linux环境下:
- PATH:可执行程序的搜索路径变量
- C_INCLUDE_PATH: C语言头文件搜索路径
- CPLUS_INCLUDE_PATH: C++头文件搜索路径
- LIBRARY_PATH:库搜索路径
可以通过在环境变量内设置多个头文件搜索路径,各个路径之间使用:分开。如果想要在每次开机时,这个环境变量设置的路径都生效,需要将export命令添加到系统的启动脚本 ~/.bashrc文件中
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/path1:/path2
也可以将头文件添加到gcc内定的官方目录下面,编译器在上面指定的各种路劲下都找不到对应的头文件,最后会到gcc内定的目录下寻找。
linux内核中的头文件
一个linux内核或者驱动源文件中,头文件的包含方式通常有以下几种
#include<linux/xx.h>
#include<asm/xx.h>
#include<mach/xx.h>
这些头文件通常分布在linux内核源码的不同路径之下
更多内容,可以自行查阅
头文件中的内联函数
使用inline关键字修饰的函数就是内联函数。内联函数与普通函数不同的是,编译器在编译内联函数时,会根据需要在调用处直接展开,从而省去了函数的调用开销。
总结
提示:这里对文章进行总结:
文章简要对C语言头文件进行一些分析。对于更加深的linux内核头文件和头文件中的内联函数,本人没有做过多的探究。感兴趣,请自行查阅