找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4999|回复: 2
打印 上一主题 下一主题
收起左侧

C语言头文件编写方法

  [复制链接]
跳转到指定楼层
楼主
ID:563265 发表于 2019-6-15 22:45 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
认真说起来,头文件(Header File)是个短命的家伙——就整个编译过程来说,它的寿命是最短的。
为什么这么说呢?关于头文件的话题,讨论起来那可是孩子没娘,说来话长了,既然是闲聊、你也不
是等着这篇文章救命,那就不妨从头开始说起——先假设读者们都是不了解编译基本过程的初学者。

   
一个编译(Compilation)过程通常至少分为三个阶段:预编译(Precompiling)、编译(Make)和链接
Linking)。他们就像一个流水线一环套一环——前一工序的输出是后一工序的输入。这本没有什么稀奇的,
但对于程序员来说,这个过程中有几个基本常识是需要记住的:


1. C
语言编译的基本单位(Compilation Unit)是 C源文件(而并没有头文件)
2.
同一个工程中,不同C源文件的编译是彼此独立的(毫不相干的)
3.
头文件在预编译阶段就已经合并到对应的C源文件中了,和所有的宏以及条件编译一样,到了编译阶段,所有的头文件、宏都是不存在的,已经被替换为对应的内容和常量了。


   
理解这三点,基本上已经可以解决很多我们日常编码过程中存在的很多疑问,比如:


- Q1
:为什么不能C语言头文件里面定义变量或者函数的实体?
- Q2
:为什么有的时候宏的先后顺序并不那么重要?
- Q3
:为什么可以在源代码的任意位置(另起一行后)定义宏,甚至是include别的头文件?

   
推荐大家基于前面的三个事实自己思考,答案在附录中介绍。
   
   
头文件里可以放什么呢?这是个值得讨论的问题:


-  各类宏
-  函数的声明(也就是 extern xxxxx
-  全局变量的声明(也就是 extern xxxx
   然而,值得说明的是,这里有一个编码规则值得你去遵守:头文件里坚决不要放全局变量有关的任何东西(硬要加,也必须是const类型的,比如各类接口)
-  类型定义(typedef, struct,union 之类的)
-  static 的变量实体和函数实体。
   这个可以有,为啥呢?因为即便多个c源文件包含同一个头文件导致同样的函数和变量实体存在多份,但
   static 的另外一个名字 "private" 可以保证每一份变量和函数实体都是彼此独立的,都是每个c源代码的
   私人财产——你可以有,我也可以有。“哎?你也有啊,真巧哎,我也有……”
-  inline 的函数
   这个和static是一个道理。


   
头文件里面不能放函数的实体,想必原因大部分人都知道了,这里就不再赘述。但头文件里不放(非const)的全局变量的声明,
这怎么玩?这里需要说明一下,头文件里不是不能放(非const)的全局变量声明,而是我提供了一个人为的规定(规范),建议
不要放任何(非const)的全局变量到头文件里,具体原因和解决方案,我们在别的帖子里再讨论(其实有人讨论过,大约就是,
如何避免使用全局变量)——是的,避免使用(非const)的全局变量是可以做到的——这里也不再赘述。说了这么多废话,我们
真正要讨论的内容还没有开始:


- 如何建立头文件的使用规则,使其即灵活、使用方便,又灵活且便于扩展(模块化)——符合面向接口开发的要求,方便我们
  
建立黑盒子?
简而言之,
-
如何让头文件的使用不再头疼;永远告别循环包含;方便代码的移植?



      
首先,思考一个简单的问题?为什么我们要用头文件?答案其实很简单,因为每个.c文件都是独立编译的,因此需要在源代码
级别传递一些信息,类似一群人在唠嗑:

   
   
源代码A             我定义了一个函数,你们哥几个要用么?
   
源代码B和源代码C 我们要用啊,函数原型(prototype)什么样子啊?
   
源代码A              你们不用费脑经记(抄下来),我都写好了,放在一个头文件里了,你们直接include就可以了。
   
源代码B和源代码C 这个敢情方便。那你头文件放哪里了?
   
源代码A              有两种方式,要么你直接到我这里来拿(指定路径);要么你找编译器问(编译器指定搜索路径)。
   
源代码D              你们整这么麻烦做什么?你直接告诉我原型,我抄下来,不就不用问这个问那个,还包含文件什么的,真麻烦。
   
源代码A              D啊,你老想耍小聪明,万一我更新了你不知道怎么办?我有义务告诉你么?并没有。
   
源代码B和源代码C 是啊,是啊,A以后估计要外包了,不在这里了,到时候有变化,都记录在头文件里,你本地放一个,没法
                                       
及时同步的。
   
源代码D             我不听!我不听!我不听……


   
是不是很有画面感?抛开捂着耳朵的D,我们回到讨论的话题——既然头文件是用来交换信息的,那么如果把所有的信息都放在一起,大家
需要的时候各取所需,岂不美哉?——基于这种思想,几乎所有人都见过把所有变量、函数、宏、类型定义都放到一个叫做system.h的头文件
里的做法。你有这么做过么?不要不好意思,几乎所有人都这么做过——因为实在太方便了,世界大同,挺好,直到你尝试和别人一起合作开发
系统,并试图在不同项目间复用一些代码的时候:

    “
何首乌藤和木莲藤缠络着”……对于这种情况,我们叫做耦合。是要找个时间来理一理了,你对自己说,然后长叹了一口气,发现这句话其
实很早之前就说过了。想到还有更奇葩的循环包涵的问题,你不得不感叹,头文件真的是个头疼的东西——要不我们还是不用了吧?直接抄下来
貌似更简单啊——源程序D痴痴的笑了。

   
那么,如何解决这个问题呢?其实,从实践经验来看,头文件的用途分为两大类:

   
站在C源文件的视角上:


- 从 外部向C源文件内部 输入配置信息——我们把这类头文件叫做配置头文件(Configuration HeaderFile)。
  需要强调的是,信息的流动方向是 从外向内,所以又可以简单的理解为输入性的头文件(Header File for information input)。常见的app_cfg.h
  就是典型的配置头文件。

- 从 C源文件内部向外 输出接口信息(全局函数、类型,宏定义等信息)——我们把这类头文件叫做接口头文件(Interface Header File)。
  需要强调的是,信息的流动方向是 从内向外,所以又可以简单的理解为输出性的头文件(Header File for information output)。常见的, spi.h
  usart.h, device.h, stdint.h 就是典型的接口头文件。


   
输入和输出两个不同的职能如果被放在同一个头文件里,就有极大的风险产生循环包含(两个相反方向的箭头产生闭合的圆圈)。system.h实际
上就是一个混淆信息流动方向的例子。这就是本质上依赖system.h的工程 模块不好拆分的原因。根据上述原理,这里引入头文件使用的第一条原则:


   
对一个C源代码来说,站在它的视角上,隶属于它自己的接口头文件(Output)和配置头文件(Input)永远不要同时包含(include)在当前
C源文件中。

全部资料51hei下载地址:
头文件编写.docx (332.33 KB, 下载次数: 52)

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏6 分享淘帖 顶1 踩
回复

使用道具 举报

沙发
ID:18591 发表于 2019-6-23 14:24 | 只看该作者
下来看看,感谢分享
回复

使用道具 举报

板凳
ID:253767 发表于 2019-9-4 07:49 | 只看该作者
谢谢分享!!!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表