热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C语言头文件的使用(转载)

C语言头文件的使用——byjanders转载请注名作者和出处,谢谢!原文:http:blog.csdn.netjandersarticledetails611081C语言中的.h文

C语言头文件的使用

                         ——by janders

  转载请注名作者和出处,谢谢!

原文:http://blog.csdn.net/janders/article/details/611081 

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文件结束,也就是说,在在例程-2main()函数的引用点上,已经是他的作用域例程-3main()函数的引用点上,还不是他的作用域,所以会编译出错这种情况怎么办呢有两种方法 ,一个就是让我们回到例程-2, 顺序对我们来说没什么谁先谁后不一样呢,只要能编译通过,程序能运行就让main()文件总是放到最后吧.那就让我们来看另一个例程,让我们看看这个方法是不是在任何时候都会起作用.

文件名 First.c

play2()

{

  ……………….

  play1()

  ………………..

}

play1()

{

……………………..

     play2()

  ……………………

);

}

main()

{

play1()

}

例程-4

 

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

 

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

文件名 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.hfirst.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[10];

 

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

 

extern int G_glob[10];

 

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

 

extern int G_glob[];

 

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

C语言头文件的使用(转载)


推荐阅读
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文是一位90后程序员分享的职业发展经验,从年薪3w到30w的薪资增长过程。文章回顾了自己的青春时光,包括与朋友一起玩DOTA的回忆,并附上了一段纪念DOTA青春的视频链接。作者还提到了一些与程序员相关的名词和团队,如Pis、蛛丝马迹、B神、LGD、EHOME等。通过分享自己的经验,作者希望能够给其他程序员提供一些职业发展的思路和启示。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • Oracle Database 10g许可授予信息及高级功能详解
    本文介绍了Oracle Database 10g许可授予信息及其中的高级功能,包括数据库优化数据包、SQL访问指导、SQL优化指导、SQL优化集和重组对象。同时提供了详细说明,指导用户在Oracle Database 10g中如何使用这些功能。 ... [详细]
author-avatar
jawshan212
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有