C语言头文件的作用

2023-05-16

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

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


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


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


文件名 First.c

printStr()

     printf(“Hello world!”);
}
main()
{
printStr();
}
     例程-2



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

文件名 First.c
main()
{
printStr();
}


printStr()

     printf(“Hello world!”);
}
     例程-3

等等,不就是改变了个顺序嘛, 但结果确是十分不同的. 让我们编译一下例程-2和例程-3,你会发现例程-3是编译不过的.这时需要我们来认识一下另一个C语言中的概念:作用域. 
我们在这里只讲述与.h文件相关的顶层作用域, 顶层作用域就是从声明点延伸到源程序文本结束, 就printStr()这个函数来说,他没有单独的声明,只有定义,那么就从他定义的行开始,到first.c文件结束, 也就是说,在在例程-2的main()函数的引用点上,已经是他的作用域. 例程-3的main()函数的引用点上,还不是他的作用域,所以会编译出错. 这种情况怎么办呢? 有两种方法 ,一个就是让我们回到例程-2, 顺序对我们来说没什么, 谁先谁后不一样呢,只要能编译通过,程序能运行, 就让main()文件总是放到最后吧. 那就让我们来看另一个例程,让我们看看这个方法是不是在任何时候都会起作用.
文件名 First.c
   play2()
   {
……………….
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函数, 如何实现呢? 

Sencond.h 文件

play1();

sencond.c文件

***()
{
…………….
Play();
……………….
}
例程-5

在sencond.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的, 简单明了,一个是引用外部函数,一个是自己生命并定义的函数.
最终如下:
Sencond.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语言头文件的作用应该有了跟多的理解吧,如果这些你原本都知道了,那么仅当是温习一下而已,如果原本不知道,那么恭喜你,现在又学到一些技巧和知识.

对于全局变量的定义和声明,其实还有另外一个解决的方法,聪明的你可能早已经猜到了:),没错,就是用宏定义的技巧实现.比如a.h文件当中有: 
#ifdef AAA
int i=0;
#else
int i;
#endif
那么,在a.c文件当中,有如下语句:
......
#define AAA
#include "a.h"
......
而对于其他的任何包含a.h文件的头文件或者.c源文件,只需要直接包含a.h就行了
......
#include "a.h"
......
这样就可以达到在a.c文件当中定义变量一次,而在其他的文件当中声明该变量的目的.
当然了,你完全可以根据自己的需要来决定在哪个需要包含a.h的文件当中定义宏AAA,但是我要说的是
在同一个工程的不同的需要包含a.h的文件当中,你只能定义AAA一次,否则在连接这些目标文件时会出现
重复定义的错误,即使你的单独目标文件编译没有任何的问题.

当然,这里说的仅仅是对全局变量的声明技巧,强烈的推介大家在头文件中使用宏定义实现对整个头文件的防止重复包含,当然了,这个技巧大多数的c语言程序员都懂.
#ifndef XXX
#define XXX

#endif
这样做会让你的程序更加稳健,很大程度上减少了不必要的麻烦...

最后给出一点点全局变量使用需要注意的问题,这也仅仅是个建议,或者说一种编程习惯 ;)
1) 所有全局变量全部以g_开头,并且尽可能声明成static类型. 
2) 尽量杜绝跨文件访问全局变量.如果的确需要在多个文件内访问同一变量,应该由该变量定义所在文件内提供GET/PUT函数实现. 
3) 全局变量必须要有一个初始值,全局变量尽量放在一个专门的函数内初始化. 
4) 如调用的函数少于三个,请考虑改为局部变量实现.

 

转自:http://blog.csdn.net/tylovemx/archive/2009/11/26/4880939.aspx

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言头文件的作用 的相关文章

  • 影响SQL Server数据库应用性能的几个常见因素 (2012/1/18)

    转自 xff1a http blogs msdn com b apgcdsd archive 2012 01 18 sql server 2012 1 18 aspx 影响SQL Server数据库应用性能的几个常见因素 性能问题是困扰数据
  • 字符移位(腾讯2017实习生)

    字符移位 1 题目 xff1a 小Q最近遇到了一个难题 xff1a 把一个字符串的大写字母放到字符串的后面 xff0c 各个字符的相对位置不变 xff0c 且不能申请额外的空间 你能帮帮小Q吗 xff1f 输入描述 输入数据有多组 xff0
  • 【群晖Nas开启WebDAV服务,路由器映射端口,实现Win10远程映射网路驱动器】

    群晖Nas开启WebDAV服务 xff0c 路由器映射端口 xff0c 实现Win10远程映射网路驱动器 群晖Nas开启WebDAV服务路由器映射端口实现Win10远程映射网路驱动器 群晖Nas开启WebDAV服务 登录群晖 xff0c 在
  • 租用游艇问题 石子合并问题 动态规划实验

    实验名称 xff1a 动态规划 一 实验预习 1 实验目的 1 理解并掌握动态规划方法的设计思想 xff1b 2 提高应用动态规划方法解决问题和设计算法的能力 xff1b 3 通过编程实现租用游艇问题和石子合并问题 xff0c 进一步理解动
  • django项目初始化

    django项目初始化 1 为了方便管理app xff0c 我们添加专门的apps文件夹来存放所有的app 结构如下 1 1设置完apps文件夹以后我们需要对配置文件做相应的更改 1 1 1 在seetings py里添加django文件的
  • 关于 Linux 输入正确账号密码显示 Sorry,that did not work的问题

    解决办法 xff1a 1 设置用户名时不要以数字开头 xff0c 字母和下划线都是可以的 xff0c 2 字母设置时记得开没开大小写 xff0c 在输入账号密码时大写Capslock是没用的 xff0c 要 shift 43 字母 xff0
  • 最小二乘与最大似然估计之间的关系

    在测量误差服从高斯分布的情况下 xff0c 最小二乘法等价于极大似然估计 Ref xff1a 最小二乘法与极大似然估计的区别与联系 最小二乘与最大似然估计之间的关系
  • Python使用pydub库对mp3与wav格式进行互转的方法

    我们需要用到一个叫pydub的类库 xff0c pydub是python的高级一个音频处理库 xff0c 可以让你以一种不那么蠢的方法处理音频 开发者原话 1 https github com jiaaro pydub 附上开发者的gith
  • 安装docker,vulhub

    1 sudo apt install apt transport https ca certificates curl gnupg agent software properties common 2 curl fsSL https dow
  • 5GC移动性管理(5GS mobility management ,5GMM)

    主要内容 介绍5GC中移动性管理功能的实现 注册管理状态模型连接管理状态模型UE Context的构成用户标识符 xff08 SUPI SUCI PEI等 xff09 移动性管理限制及术语 xff08 注册区 禁止区域 服务受限区域 xff
  • 云计算实战系列三(Linux文件管理)

    文件管理 1 Linux目录结构 1 1 WINDOWS LINUX 对比 Windows 以多根的方式组织文件 C D E Linux 以单根的方式组织文件 1 2 简介 目录结构 xff1a FSH Filesystem Hierarc
  • hashCode()和equals()的区别

    一 hashCode 和equals 是什么 xff1f hashCode 方法和equals 方法的作用其实一样 xff0c 在Java里都是用来对比两个对象是否相等一致 二 hashCode 和equals 的区别 下边从两个角度介绍了
  • debian下smplayer播放视频无声音的问题

    请先安装解码器和smplayer 解码器essential 20071007 tar bz2一般都是手动安装 xff0c smplayer使用apt get install即可 安装过程 xff0c 参考mplayer官网步骤 xff1a
  • iOS可视化界面编程XIB简单使用

    iOS的UI界面的编写现在基本分为三个流派 手写代码XIBStoryBoard 三者区别 本文主要介绍XIB的简单使用 xff0c 基于XIB在界面上放几个按钮并生成点击事件代码 以下代码及截图基于XCode12 快速创建 1 创建文本对象
  • 方面情感分析的四个情感元素介绍

    方面情感分析介绍 一般情感分析的问题由两个部分组成 xff1a 目标和情绪 目标可以是实体也可以是实体的某一个属性 xff0c 方面 xff0c 特点等等 xff0c 情绪是表达对目标的感情 xff0c 这里一般是积极 xff0c 消极 x
  • go中使用sqlite

    1 安装mingw64 1 1 下载mingw sqlite作为一个快速开发的数据库 xff0c 理应被go支持 xff0c 但是要在go里面使用sqlite xff0c 实际上是要下载sqlite的源代码编译的 xff0c 当然 xff0
  • map 详解(C++)

    现实中的数据很多是关联的 xff0c 例如书本名称和价格 xff0c 每条数据都含有两部分 xff1a 信息学竞赛一本通 xff1a 80 高等数学 xff1a 27 5 生物信息分析 xff1a 35 5 我们可以使用map存储这类一对一
  • 详解Ubuntu文件的结构

    首先我们知道 xff0c linux系统文件结构和windows系统文件结构不同之处在于 xff0c linux系统文件统一挂载在根目录下的 xff0c 而windows系统的文件是分磁盘挂载的 windows下通常分C盘D盘E盘等 xff
  • Go 语言 exec 实时获取外部命令的执行输出

    Go 语言 exec 实时获取外部命令的执行输出 在 Go 语言中调用外部 Linux 命令可以通过标准的 os exec 包实现 xff0c 我们一般的使用方式如下 xff1a span class token keyword packa

随机推荐

  • ubuntu 18.04 arm64版 安装docker 踩坑

    一 安装ubuntu 18 04系统 可以参考该系列其他文章 二 安装docker 1 先卸载可能存在的旧版本 apt remove docker docker span class token operator span engine d
  • Linux网络中的桥 (Bridge)

    桥简介 桥 xff0c 从字面来讲就是在一条河流上面建造一条路 xff0c 对 xff0c 就是这样 xff0c 甭管是多大多长跨江还是跨海 xff0c 它都是起到连接两岸的作用 在计算机的网络世界中也存在这种连接的两个网络的设备 xff0
  • MySQL8.0 开启远程连接

    一 MySQL 开启远程连接需要先在服务器上登录到 MySQL mysql u root p 1 然后 Enter password 二 修改 root 账户的 Host 1 打开 mysql 数据库 use mysql 1 2 查看 us
  • 实时天气API

    restful接口查询天气 实时天气 API 和风天气开发平台 实时天气 全国4000 个市县区和海外15万个城市实时天气数据 包括实时温度 体感温度 风力风向 相对湿度 大气压强 降水量 能见度 露点温度 云量等数据 请求URL nbsp
  • C程序的内存结构

    以类Unix环境下的程序运行为例 xff0c 说明C程序的运行过程和内存分配 xff0c windows环境下原理一致 xff0c 但实现细节会有区别 xff0c 所以首先我们要明白 xff1a 程序的内存布局 Program Memory
  • AD采用多层原理图和ROOM方式高效率绘制重复性功能电路板图

    我们在使用altium designer绘制原理图和PCB时 xff0c 往往会遇到多路重复性的电路 xff0c 其功能和走线完全一致 xff0c 在条件允许的情况下我们可以采用多层原理图和ROOM方法避免重复性劳动 本文以两路RS485电
  • Ubuntu网络频繁掉线解决方案

    转自 xff1a http www cnblogs com ljxxz p 5089863 html 年底了 xff0c 实验室终于给配了个电脑 xff08 Ubuntu系统 xff09 xff0c 博主欣喜若狂啊 xff0c 然而装好后发
  • 部分Windows 10企业版用户无法使用微软Edge浏览器

    图片来自 xff1a neowin 很多关于Windows 10的问题悬而未决 xff0c 在微软正式推出Windows 10之前还有一个多月的时间 xff0c 很多事情都会发生改变 就在本周 xff0c 来自Gartner Inc的分析师
  • 无法远程连接如何排错

    无法远程连接如何排错 去北京天安门广场 连接服务器 服务器位置 10 0 0 200 1 连接不上服务器 测试我和天安门广场之间的道路是否通畅 百度地图 连接不上10 0 0 200 测试和200通信是否正常 ping 10 0 0 200
  • 51单片机实时时钟显示

    51单片机 43 DS1302 43 DS18B20 43 LCD12864 用的IIC通信 xff0c 写的一个ds3231时钟模块的程序 xff0c 可更改时间 xff0c 下面是 h文件里面的部分代码 ifndef ds3231 h
  • RouterOS(ROS)软路由阿里云动态域名解析Aliyun DDNS

    本文讲解ROS借助阿里云的 DNS API 来实现域名与动态 IP 的绑定 xff0c 用来达到外网访问内网设备的需求 一 给域名添加A记录解析 1 点击登录阿里云域名控制台 2 给域名添加一个A记录解析 xff0c 记录值可以随意填写 x
  • <X>远程登录服务

    文章目录 一 ssh1 ssh服务的用途2 基本用法3 ssh 服务的 key 认证 二 文件传输1 实验环境2 scp 命令3 rsync命令 三 文件的归档与压缩1 文件归档2 文件的压缩3 tar 43 压缩 四 日志1 journa
  • springboot项目正常启动后却无法访问

    报错内容 xff1a Resolved org springframework http converter HttpMessageNotReadableException Required request body is missing
  • 如何解决远程桌面登录后闪退

    在cmd中输入这个命令 xff0c 可以防止登录远程桌面后闪退 mstsc admin
  • ubuntu22.0.4 kolla多节点搭建openstack ,skyline

    kolla部署openstack 基础 更新软件包索引 span class token function sudo span span class token function apt span update 2 安装 Python 构建
  • 论文阅读 | Video Super-Resolution Transformer

    引言 xff1a 2021年用Transformer实现视频超分VSR的文章 xff0c 改进了SA并在FFN中加入了光流引导 论文 xff1a here 代码 xff1a here Video Super Resolution Trans
  • PostgreSQL12 windows zhparser插件安装

    zhparser https github com amutu zhparser 1 scws编译 因为zhparser需要使用scws进行中文分词先编译scws xff0c zhparser的README有scws源码的下载地址 http
  • WM_COMMAND消息

    当用户点击菜单 按钮 下拉列表框等控件时候 xff0c 会触发WM COMMAND LOWORD wParam 是控件或菜单或加速键的ID xff0c 菜单的sparator的ID为0 如果LOWORD wParam 是控件ID xff0c
  • windows简单调试器源码2700行左右代码

    简单调试器项目中on开头的函数为接收系统的调试事件并做相应的处理 xff0c 简单调试器实现过程中主要的调试事件为异常事件 xff0c 相应的处理函数为DispatchException 在异常事件中访问异常 int3异常 单步异常是跟实现
  • C语言头文件的作用

    C语言中的 h文件和我认识由来已久 xff0c 其使用方法虽不十分复杂 xff0c 但我却是经过了几个月的 不懂 时期 xff0c 几年的 一知半解 时期才逐渐认识清楚他的本 来面目 揪其原因 xff0c 我的驽钝和好学而不求甚解固然是原因