C语言头文件的使用.docx
- 文档编号:8372652
- 上传时间:2023-01-30
- 格式:DOCX
- 页数:12
- 大小:24.08KB
C语言头文件的使用.docx
《C语言头文件的使用.docx》由会员分享,可在线阅读,更多相关《C语言头文件的使用.docx(12页珍藏版)》请在冰豆网上搜索。
C语言头文件的使用
C语言头文件的使用
——byjanders
转载请注名作者和出处,谢谢!
转自
C语言中的.h文件和我认识由来已久,其使用方法虽不十分复杂,但我却是经过了几个月的“不懂”时期,几年的“一知半解”时期才逐渐认识清楚他的本来面目。
揪其原因,我的驽钝和好学而不求甚解固然是原因之一,但另外还有其他原因。
原因一:
对于较小的项目,其作用不易被充分开发,换句话说就是即使不知道他的详细使用方法,项目照样进行,程序在计算机上照样跑。
原因二:
现在的各种C语言书籍都是只对C语言的语法进行详细的不能再详细的说明,但对于整个程序的文件组织构架却只字不提,找了好几本比较著名的C语言著作,却没有一个把.h文件的用法写的比较透彻的。
下面我就斗胆提笔,来按照我对.h的认识思路,向大家介绍一下。
让我们的思绪乘着时间机器回到大学一年级。
C原来老师正在讲台上讲着我们的第一个C语言程序:
Helloworld!
文件名First.c
main()
{
printf(“Helloworld!
”);
}
例程-1
看看上面的程序,没有.h文件。
是的,就是没有,世界上的万物都是经历从没有到有的过程的,我们对.h的认识,我想也需要从这个步骤开始。
这时确实不需要.h文件,因为这个程序太简单了,根本就不需要。
那么如何才能需要呢?
让我们把这个程序变得稍微复杂些,请看下面这个,
文件名First.c
printStr()
{
printf(“Helloworld!
”);
}
main()
{
printStr()
}
例程-2
还是没有,那就让我们把这个程序再稍微改动一下.
文件名First.c
main()
{
printStr()
}
printStr()
{
printf(“Helloworld!
”);
}
例程-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文件
Externplay1();
上面洋洋洒洒写了那么多都是针对函数的,而实际上.h文件却不是为函数所御用的.打开我们项目的一个.h文件我们发现除了函数外,还有其他的东西,那就是全局变量.
在大型项目中,对全局变量的使用不可避免,比如,在first.c中需要使用一个全局变量G_test,那么我们可以在first.h中,定义TPYEG_test.与对函数的使用类似,在second.c中我们的开发人员发现他也需要使用这个全局变量,而且要与first.c中一样的那个,如何处理?
对,我们可以仿照函数中的处理方法,在second.h中再次声明TPYEG_test,根据extern的用法,以及c语言中默认的存储类型,在两个头文件中声明的TPYEG_test,其实其存储类型都是extern,也就是说不必我们操心,连接程序会帮助我们处理一切.但我们又如何区分全局变量哪个是定义声明,哪个是引用声明呢?
这个比函数要复杂一些,一般在C语言中有如下几种模型来区分:
1、初始化语句模型
顶层声明中,存在初始化语句是,表示这个声明是定义声明,其他声明是引用声明。
C语言的所有文件之中,只能有一个定义声明。
按照这个模型,我们可以在first.h中定义如下TPYEG_test=1;那么就确定在first中的是定义声明,在其他的所有声明都是引用声明。
2、省略存储类型说明
在这个模型中,所有引用声明要显示的包括存储类extern,而每个外部变量的唯一定义声明中省略存储类说明符。
这个与我们对函数的处理方法类似,不再举例说明。
这里还有一个需要说明,本来与本文并不十分相关,但前一段有个朋友遇到此问题,相信很多人都会遇到,那就是数组全局变量。
他遇到的问题如下:
在声明定义时,定义数组如下:
intG_glob[100];
在另一个文件中引用声明如下:
int*G_glob;
在vc中,是可以编译通过的,这种情况大家都比较模糊并且需要注意,数组与指针类似,但并不等于说对数组的声明起变量就是指针。
上面所说的的程序在运行时发现了问题,在引用声明的那个文件中,使用这个指针时总是提示内存访问错误,原来我们的连接程序并不把指针与数组等同,连接时,也不把他们当做同一个定义,而是认为是不相关的两个定义,当然会出现错误。
正确的使用方法是在引用声明中声明如下:
intG_glob[10];
并且最好再加上一个extern,更加明了。
externintG_glob[10];
另外需要说明的是,在引用声明中由于不需要涉及到内存分配,可以简化如下,这样在需要对全局变量的长度进行修改时,不用把所有的引用声明也全部修改了。
externintG_glob[];
C语言是现今为止在底层核心编程中,使用最广泛的语言,以前是,以后也不会有太大改变,虽然现在java,.net等语言和工具对c有了一定冲击,但我们看到在计算机最为核心的地方,其他语言是无论如何也代替不了的,而这个领域也正是我们对计算机痴迷的程序员所向往的。
Trackback:
tianlebo补上:
我最近也碰到类似的问题,在DSP2407开发过程中有一个头文件是定义寄存器地址的文件,这个文件在很多.c文件中都是要包含的,所以我就在相应要包含的的地方用include"***.h",
这个***.h文件中是这样写的
#ifndef_SEED_DEC_2407_H
#define_SEED_DEC_2407_H
volatileunsignedint*IMR=(volatileunsignedint*)0x0004;
volatileunsignedint*IFR=(volatileunsignedint*)0x0006;
volatileunsignedint*SCSR1=(volatileunsignedint*)0x7018;
................
..............
#endif
结果在CCS2.0中编译:
是每一个C文件编译都能过,但是.obj文件链接时就报错,说重复定义寄存器,搞了一天多都没搞定,我就换到VC6.0中编译也一样通不过。
最后一个朋友告诉我,当第一个头文件用到***.h时相当于定义了全局变量,并初始化了,第二个c文件再用***.h时应该用extern+寄存器了,按他一试,还真行啊!
但是我不清楚,为何不能再在其他c文件中包含***.h,知道的朋友可以讲讲!
!
!
系统分类:
软件开发 | 用户分类:
无分类 | 来源:
整理 | 【推荐给朋友】 | 【添加到收藏夹】
2
阅读(2933) | 评论(9) | 收藏(0) | 举报
投一票您将和博主都有获奖机会!
最新评论
∙
livemylife
2008/1/3017:
09:
19
上面的改正:
如果许多地方要用到,这个***.h文件应该这样写
#ifndef_SEED_DEC_2407_H
#define_SEED_DEC_2407_H
externvolatileunsignedint*IMR;
externvolatileunsignedint*IFR;
externvolatileunsignedint*SCSR1;
................
..............
#endif
然后。
。
。
∙
livemylife
2008/1/3017:
08:
06
如果许多地方要用到,这个***.h文件应该这样写
#ifndef_SEED_DEC_2407_H
#define_SEED_DEC_2407_H
externvolatileunsignedint*IMR=(volatileunsignedint*)0x0004;
externvolatileunsignedint*IFR=(volatileunsignedint*)0x0006;
externvolatileunsignedint*SCSR1=(volatileunsignedint*)0x7018;
................
..............
#endif
然后在一个c文件中定义
volatileunsignedint*IMR=(volatileunsignedint*)0x0004;
volatileunsignedint*IFR=(volatileunsignedint*)0x0006;
volatileunsignedint*SCSR1=(volatileunsignedint*)0x7018;
其他需要使用的c文件包含头文件即可
总之一句话,头文件是用来声明的地方
∙
eric
2007/11/2018:
43:
15
文章写的很好!
最后面那个题出现"重复定义寄存器"的原因就是文章中写到的"初始化语句模型"中说的(顶层声明中,存在初始化语句是,表示这个声明是定义声明,其他声明是引用声明。
C语言的所有文件之中,只能有一个定义声明),上面的那个题"***.h"就是定义声明,个所以在每个.c文件中都引用就会出现"重复定义寄存器"的提示
我想是这样的
∙
王艳秋
2007/11/169:
02:
01
文章写得很好啊,佩服!
但是不知道最后那个问题有答案了没?
就是那个dsp2407的头文件重复包含的问题,我这几天也遇到了,说句真话,真头疼!
以前我写的程序,都是这样包含的,没有问题,偏偏在CCS中遇到了.都有些怀疑CCS不支持这个了.所以我的程序现在变得很长,调试起来很不好看!
我的邮箱是langziaqiu@, 这个也是我的MSN, qq是85469843,诚希望一起交流探讨下.
∙
...
2007/11/1315:
14:
06
还有问题,请到讨论,欢迎探讨程序相关问题
∙
...
2007/11/1315:
12:
03
编译是可以通过的,连接时候多个obj文件中的有相同的全局变量定义,就出错了,
你可以在***.h中定义那些全局变量
//b.cpp
unsignedintabc[][2]=
{
0xA1A1,0x3000
} ;
externinti=0;
externintk=2;
在***.h中声明,如果只是整形不声明也行
//b.h
#ifndef__ABC_H_
#define__ABC_H_
unsignedintabc[][2];
#definej10
externinti;
#endif
在其他的cpp文件使用的时候,再声明一下就OK了
//----------
//main.cpp
//----------
#include "stdio.h"
externinti;
externunsignedintabc[][2];
void main()
{
printf("thevalueofiis:
%d\n",i);
printf("thevalueof kis:
%d\n",k);
}
∙
taohuahua
2007/9/2111:
16:
38
受益匪浅,非常感谢你的文章!
∙
Sinimon
2007/9/1216:
15:
38
最后一段“一个用extern+寄存器了”
该是“应该用extern+寄存器了”吧。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 语言 文件 使用