C语言中的.h文件和我认识由来已久,其使用方法虽不十分复杂,但我却是经过了几个月的“不懂”时期,几年的“一知半解”时期才逐渐认识清楚他的本来面目。揪其原因,我的 驽钝和好学而不求甚解固然是原因之一,但另外还有其他原因。原因一:对于较小的项目,其作用不易被充分开发,换句话说就是即使不知道他的详细使用方法,项 目照样进行,程序在计算机上照样跑。原因二:现在的各种C语言书籍都是只对C语言的语法进行详细的不能再详细的说明,但对于整个程序的文件组织构架却只字不提,找了好几本比较著名的C语言著作,却没有一个把.h文件的用法写的比较透彻的。下面我就斗胆提笔,来按照我对.h的认识思路,向大家介绍一下。

让我们的思绪乘着时间机器回到大学一年级。C原来老师正在讲台上讲着我们的第一个C语言程序: Hello world!

/***************************例程-1
****************************/文件名 First.c
main()
{printf(“Hello world!”);
}

看看上面的程序,没有.h文件。
是的,就是没有,世界上的万物都是经历从没有到有的过程的,我们对.h的认识,我想也需要从这个步骤开始。这时确实不需要.h文件,因为这个程序太简单了,根本就不需要。那么如何才能需要呢?让我们把这个程序变得稍微复杂些,请看下面这个:

/***************************例程-2
****************************/
文件名 First.cprintStr()
{printf(“Hello world!”);
}
main()
{
printStr();
}

还是没有, 那就让我们把这个程序再稍微改动一下.

/***************************例程-3
****************************/
文件名 First.c
main()
{
printStr();
}printStr()
{printf(“Hello world!”);
}

等等,不就是改变了个顺序嘛, 但结果确是十分不同的。

让我们编译一下例程-2和例程-3,你会发现例程-3是编译不过的;这时需要我们来认识一下另一个C语言中的概念:作用域

我们在这里只讲述与.h文件相关的顶层作用域, 顶层作用域就是从声明点延伸到源程序文本结束, 就printStr()这个函数来说,他没有单独的声明,只有定义,那么就从他定义的行开始,到first.c文件结束, 也就是说:在在例程-2的main()函数的引用点上,已经是他的作用域;例程-3的main()函数的引用点上,还不是他的作用域,所以会编译出错。
这种情况怎么办呢? 有两种方法 ,一个就是让我们回到例程-2, 顺序对我们来说没什么, 谁先谁后不一样呢,只要能编译通过,程序能运行,就让main()文件总是放到最后吧。
那就让我们来看另一个例程,让我们看看这个方法是不是在任何时候都会起作用。

文件名 First.cplay2(){……………….play1();………………..}play1(){……………..play2();       ……………………}
main()
{
play1();
}
*************************
例程-4

也许大部分都会看出来了,这就是经常用到的一种算法, 函数嵌套, 那么让我们看看, play1和play2这两个函数哪个放到前面呢?

这时就需要我们来使用第二种方法,使用声明.

文件名 First.c
play1();
play2();
play2()
{……………….play1();………………..}play1(){…………………….play2();……………………}
main()
{
play1();
}
例程-4

经历了我的半天的唠叨, 加上四个例程的说明,我们终于开始了用量变引起的质变, 这篇文章的主题.h文件快要出现了。
一个大型的软件项目,可能有几千个,上万个play,而不只是play1,play2这么简单,这样就可能有N个类似 play1(); play2(); 这样的声明,这个时候就需要我们想办法把这样的play1(); play2(); 也另行管理, 而不是把他放在.c文件中, 于是.h文件出现了.

文件名 First.h
play1();
play2();
文件名 First.C
#include “first.h”
play2()
{
……………….
play1();
………………..
}play1();
{
……………………..play2();……………………
}
main()
{
play1();
}
例程-4

各位有可能会说,这位janders大虾也太罗嗦了,上面这些我也知道, 你还讲了这么半天, 请原谅, 如果说上面的内容80%的人都知道的话,那么我保证,下面的内容,80%的人都不完全知道。而且这也是我讲述一件事的一贯作风,我总是想把一个东西说明白,让那些刚刚接触C的人也一样明白。
上面是.h文件的最基本的功能,那么.h文件还有什么别的功能呢?让我来描述一下我手头的一个项目吧。

这个项目已经做了有10年以上了,具体多少年我们部门的人谁都说不太准确,况且时间并不是最主要的,不再详查了。是一个通讯设备的前台软件, 源文件大小共 51.6M, 大小共1601个文件,编译后大约10M,其庞大可想而知,在这里充斥着错综复杂的调用关系,如在second.c中还有一个函数需要调用first.c文件中的play1函数, 如何实现呢?

Second.h 文件play1();second.c文件***()
{
…………….
Play();
……………….
}
例程-5

在second.h文件内声明play1函数,怎么能调用到first.c文件中的哪个play1函数中呢? 是不是搞错了,没有搞错, 这里涉及到c语言的另一个特性:

  • 存储类说明符
    C语言的存储类说明符有以下几个, 我来列表说明一下:

|说明符| 用 法|
Auto 只在块内变量声明中被允许, 表示变量具有本地生存期。
Extern 出现在顶层或块的外部变量函数与变量声明中,表示声明的对象具有静态生存期, 连接程序知道其名字。
Static 可以放在函数与变量声明中,在函数定义时,只用于指定函数名,而不将函数导出到链接程序,在函数声明中,表示其后边会有定义声明的函数,存储类型static。在数据声明中,总是表示定义的声明不导出到连接程序。

无疑,在例程-5中的second.h和first.h中,需要我们用extern标志符来修饰play1函数的声明,这样,play1()函数就可以被导出到连接程序,也就是实现了无论在first.c文件中调用,还是在second.c文件中调用,连接程序都会很聪明的按照我们的意愿,把他连接到first.c文件中的play1函数的定义上去,而不必我们在second.c文件中也要再写一个一样的play1函数。

但随之有一个小问题,在例程-5中,我们并没有用extern标志符来修饰play1啊, 这里涉及到另一个问题, C语言中有默认的存储类标志符。

C99中规定所有顶层的默认存储类标志符都是extern。

原来如此啊,哈哈,回想一下例程-4,也是好险,我们在无知的情况下,竟然也误打误撞,用到了extern修饰符,否则在first.h中声明的play1函数如果不被连接程序导出,那么我们在在play2()中调用他时,是找不到其实际定义位置的。

那么我们如何来区分哪个头文件中的声明在其对应的.c文件中有定义,而哪个又没有呢?
这也许不是必须的,因为无论在哪个文件中定义,聪明的连接程序都会义无返顾的帮我们找到,并导出到连接程序,但我觉得他确实必要的。
因为我们需要知道这个函数的具体内容是什么,有什么功能,有了新需求后我也许要修改他,我需要在短时间内能找到这个函数的定义,那么我来介绍一下在C语言中一个人为的规范:

在.h文件中声明的函数,如果在其对应的.c文件中有定义,那么我们在声明这个函数时,不使用extern修饰符,反之,则必须显示使用extern修饰符。

这样,在C语言的.h文件中,我们会看到两种类型的函数声明。带extern的,和不带extern的,简单明了。
一个是引用外部函数,一个是自己申明并定义的函数。

最终如下:
Second.h 文件

Extern play1();

上面洋洋洒洒写了那么多都是针对函数的,而实际上.h文件却不是为函数所御用的. 打开我们项目的一个.h文件我们发现除了函数外,还有其他的东西, 那就是全局变量.

在大型项目中,对全局变量的使用不可避免, 比如,在first.c中需要使用一个全局变量G_test, 那么我们可以在first.h中,定义 TPYE G_test. 与对函数的使用类似, 在second.c中我们的开发人员发现他也需要使用这个全局变量, 而且要与first.c中一样的那个, 如何处理? 对,我们可以仿照函数中的处理方法, 在second.h中再次声明TPYE G_test, 根据extern的用法,以及c语言中默认的存储类型, 在两个头文件中声明的TPYE G_test,其实其存储类型都是extern, 也就是说不必我们操心, 连接程序会帮助我们处理一切. 但我们又如何区分全局变量哪个是定义声明,哪个是引用声明呢?这个比函数要复杂一些, 一般在C语言中有如下几种模型来区分:

1、初始化语句模型
顶层声明中,存在初始化语句是,表示这个声明是定义声明,其他声明是引用声明。C语言的所有文件之中,只能有一个定义声明。
按照这个模型,我们可以在first.h中定义如下TPYE G_test=1;那么就确定在first中的是定义声明,在其他的所有声明都是引用声明。
2、省略存储类型说明
在这个模型中,所有引用声明要显示的包括存储类extern,而每个外部变量的唯一定义声明中省略存储类说明符。
这个与我们对函数的处理方法类似,不再举例说明。

这里还有一个需要说明,本来与本文并不十分相关,但前一段有个朋友遇到此问题,相信很多人都会遇到,那就是数组全局变量。

他遇到的问题如下:
在声明定义时,定义数组如下:
int G_glob[100];

在另一个文件中引用声明如下:
int * G_glob;

在vc中,是可以编译通过的,这种情况大家都比较模糊并且需要注意,数组与指针类似,但并不等于说对数组的声明起变量就是指针。上面所说的的程序在运行时发现了问题,在引用声明的那个文件中,使用这个指针时总是提示内存访问错误,原来我们的连接程序并不把指针与数组等同,连接时,也不把他们当做同一个定义,而是认为是不相关的两个定义,当然会出现错误。正确的使用方法是在引用声明中声明如下:

int G_glob[100];

并且最好再加上一个extern,更加明了。

extern int G_glob[100];

另外需要说明的是,在引用声明中由于不需要涉及到内存分配,可以简化如下,这样在需要对全局变量的长度进行修改时,不用把所有的引用声明也全部修改了。

extern int G_glob[];

C语言是现今为止在底层核心编程中,使用最广泛的语言,以前是,以后也不会有太大改变,虽然现在java,.net等语言和工具对c有了一定冲击,但我们看到在计算机最为核心的地方,其他语言是无论如何也代替不了的,而这个领域也正是我们对计算机痴迷的程序员所向往的。

c语言中.c和.h文件关系以及区别:

  本质上没有任何区别。 只不过一般:.h文件是头文件,内含函数声明、宏定义、结构体定义等内容

  .c文件是程序文件,内含函数实现,变量定义等内容。而且是什么后缀也没有关系,只不过编译器会默认对某些后缀的文件采取某些动作。你可以强制编译器把任何后缀的文件都当作c文件来编。

  这样分开写成两个文件是一个良好的编程风格。

  而且,比方说 我在aaa.h里定义了一个函数的声明,然后我在aaa.h的同一个目录下建立aaa.c ,aaa.c里定义了这个函数的实现,然后是在main函数所在.c文件里#include这个aaa.h 然后我就可以使用这个函数了。 main在运行时就会找到这个定义了这个函数的aaa.c文件。

  这是因为:

  main函数为标准C/C++的程序入口,编译器会先找到该函数所在的文件。

  假定编译程序编译myproj.c(其中含main())时,发现它include了mylib.h(其中声明了函数void test()),那么此时编译器将按照事先设定的路径(Include路径列表及代码文件所在的路径)查找与之同名的实现文件(扩展名为.cpp或.c,此例中为mylib.c)(应该是跟java的import一样),如果找到该文件,并在其中找到该函数(此例中为void test())的实现代码,则继续编译;如果在指定目录找不到实现文件,或者在该文件及后续的各include文件中未找到实现代码,则返回一个编译错误.其实include的过程完全可以"看成"是一个文件拼接的过程,将声明和实现分别写在头文件及C文件中,或者将二者同时写在头文件中,理论上没有本质的区别。

C语言中的.h文件的作用相关推荐

  1. C语言中,头文件的作用,头文件和源文件的关系(转)

    简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段  2.词法与语法分析阶段  3.编译阶段,首先编译成纯汇编语 ...

  2. C语言.h文件的作用

    转载自此 总结在前面的话: 顶层的函数与变量默认extern修饰(C99内), 非本文件夹内的函数引用加上external修饰:非定义处的变量加上extern修饰(建议这么做,用以区分变量定义与变量引 ...

  3. c语言 conio h,c语言中conio.h是什么?

    c语言中conio.h是什么? 发布时间:2020-04-29 09:34:37 来源:亿速云 阅读:523 作者:小新 c语言中conio.h是什么?相信有很多人都不太了解,今天小编为了让大家更加了 ...

  4. c语言中math的库函数,C语言中math.h库中的常用函数

    C语言中math.h库中的常用函数 int abs(int i) 返回整型参数i的绝对值 double cabs(struct complex znum) 返回复数znum的绝对值 double fa ...

  5. 汇编语言中PTR的含义及作用

    汇编语言中PTR的含义及作用 mov ax,bx ;是把BX寄存器"里"的值赋予AX,由于二者都是word型,所以没有必要加"WORD"    mov ax,w ...

  6. c语言中的头文件stdlib.h的作用,c语言中includestdlib.h的意思是什么

    c语言中include的意思是什么 发布时间:2020-07-16 09:17:22 来源:亿速云 阅读:1720 作者:Leah 这篇文章将为大家详细讲解有关c语言中include的意思是什么,文章 ...

  7. c语言中的.c文件和.h文件

    大神的详细解读:传送门 记录下一点点自己对两个文件的理解 函数声明可以有多分,但函数定义只能有一份.所以一般不在头文件里面定义函数,因为同一个程序的多个代码文件可能都会包含这个头文件. 但c++中的i ...

  8. C语言中,头文件和源文件的关系(转)

    转载自:http://www.cnblogs.com/infiniti/archive/2013/03/19/2968689.html 简略的摘抄一点内容,全文请参考转载地址. 1.c语言中.c和.h ...

  9. C语言中void具体有什么作用

    1.概述   许多初学者对C/C++语言中的void及void指针类型不甚理解,因此在使用上出现了一些错误.本文将对void关键字的深刻含义进行解说,并 详述void及void指针类型的使用方法与技巧 ...

  10. c语言中eles后面分号的作用,C语言 if else 语句详细讲解

    前面我们看到的代码都是顺序执行的,也就是先执行第一条语句,然后是第二条.第三条--一直到最后一条语句. 但是对于很多情况,顺序结构的代码是远远不够的,比如一个程序限制了只能成年人使用,儿童因为年龄不够 ...

最新文章

  1. Linux 有关管理进程的命令小结
  2. 开发工具,编辑器字体
  3. 使用Systemd包装SpringBoot应用
  4. c++的STL中的map(哈希表)与unordered_map
  5. linux下源码安装vsftpd-3.0.2
  6. leetcode 48. 旋转图像
  7. Python学习——02-Python基础——【9-面向对象进阶】——isinstance(obj,cls)、反射等...
  8. python模块_python模块
  9. 乐鑫ESP32开发 1.Vscode创建新工程,编译,下载烧录,监视端口,点亮一个LED
  10. 为什么要用 SpringMVC 的 SessionStatus
  11. 为您详细比较三个 CSS 预处理器(框架):Sass、LESS 和 Stylus
  12. linux下QOS--理论篇
  13. eclipse3.7.2+KEmulator搭建J2ME开发环境
  14. win10修改命令行默认字体
  15. 基于matlab的16QAM调制解调仿真
  16. bitvise ssh client 连接linux,secureCRT + Bitvise SSH Client实现ssh隧道远程
  17. mac 删除ABC输入法
  18. 编译超频Android内核,安卓超频工具apk下载
  19. 【STM32学习】(30)STM32实现18B20温度采集(标准库和HAL库实现)
  20. Python小工具:批量给视频加水印!

热门文章

  1. [渝粤教育] 西安交通大学 土力学 参考 资料
  2. 脉冲电磁阀工作原理图(给到爱学习的你)(转)
  3. Kconfig内容(详细)总结附示例快速掌握
  4. vs2017调用目标发生了异常
  5. 性能测试_cpu使用率和平均负载简述
  6. linux系统下的打印机驱动下载,用于UNIXLinux系统的打印机驱动程序-Lexmark.PDF
  7. 关于PC电脑微信进粉计数器的选择?
  8. java供应商管理系统项目简介,基于jsp的供应商管理系统-JavaEE实现供应商管理系统 - java项目源码...
  9. Sallen-Key 低通滤波器设计过程
  10. 糖葫芦低通滤波器的设计