头文件与extern

好吧,头文件是干嘛的?
放心,这个简单的问题确实把我难住了。用了多少年头文件,头文件是干嘛用的?
第一,头文件是给别人看得。
第二,头文件可以用来进行类型检查,减少出错。
好吧,第二点我似懂非懂,所以只说第一点。再配合extern。他们俩在一起很万恶的。真的哦!

头文件是给别人看的。不管是C还是C++,你把你的函数,变量或者结构体,类啥的放在你的.c或者.cpp文件里。
然后编译成lib,dll,obj,.o等等,然后别人用的时候 最基本的gcc hisfile.cpp yourfile.o|obj|dll|lib 等等。
ok,他们怎么知道你的lib,dll...里面到底有什么东西?要看你的头文件。你的头文件就是对用户的说明。函数,参数,各种各样的接口的说明。
so,既然是说明,那么头文件里面放的自然就是关于函数,变量,类的“声明”了。记着,是“声明”,不是“定义”。
那么,我假设大家知道声明和定义的区别。所以,最好不要傻嘻嘻的在头文件里定义什么东西。比如全局变量:

#ifndef _XX_头文件.H
#define _XX_头文件.H
int A;
#endif

那么,很糟糕的是,这里的int A是个全局变量的定义,所以如果这个头文件被多次引用的话,你的A会被重复定义
显然语法上错了。只不过有了这个#ifndef的条件编译,所以能保证你的头文件只被引用一次,不过也许还是会岔子,所以,最好不要在头文件里定义太多东西。
那么  轮到万恶的extern了,万恶的extern之所以万恶是因为,在声明的时候,这个extern居然可以被省略,所以会让你搞不清楚 到底是声明还是定义,尤其是对于变量来说。
ok
extern int a;//声明一个全局变量a
int a; //定义一个全局变量a

extern int a =0 ;//定义一个全局变量a 并给初值。
int a =0;//定义一个全局变量a,并给初值,

第四个 等于 第 三个,都是定义一个可以被外部使用的全局变量,并给初值。
糊涂了吧,他们看上去可真像。但是定义只能出现在一处。也就是说,不管是int a;还是extern int a=0;还是int a=0;都只能出现一次,而那个extern int a可以出现很多次。当你要引用一个全局变量的时候,你就要声明,extern int a;这时候extern不能省略,因为省略了,就变成int a;这是一个定义,不是声明。
函数,函数,对于函数也一样,也是定义和声明,定义的时候用extern,说明这个函数是可以被外部引用的,声明的时候用extern说明这是一个声明。
但是对于函数extern,不管是声明还是定义都是可以省略的,因为定义函数要有函数体,声明函数没有函数体,两者如此不同,所以省略了extern也不会有问题。
比如:

int fun(void)
{
return 0;
}

很好,我们定义了一个全局函数

int fun(void);
我们对它做了个声明,然后后面就可以用了
加不加extern都一样
我们也可以把对fun的声明 放在一个头文件里,最后变成这样

int fun(void);//函数声明,所以省略了extern,完整些是extern int fun(void);

int fun(void)
{
return 0;
}//一个完整的全局函数定义,因为有函数体,extern同样被省略了。
然后,一个客户,一个要使用你的fun的客户,把这个头文件包含进去,ok,一个全局的声明。没有问题。
但是,对应的,如果是这个客户要使用全局变量,那么要extern 某某变量;不然就成了定义了。

 

 

 

 

很多情况下,程序员可能突然产生这样的问题:为什么非得在文件的首位置#include "... .h"? 我include源文件行不行。


 

其实任何后缀的文本文件(忽视编码情况)都可以被#include,但是你去#include 源文件会不会出问题呢?我们看下面测试代码:


 

//Single.cpp
#ifndef __SINGLE__CPP_
#define __SINGLE__CPP_

#include <stdio.h>
void Fun()
{
printf("Fun...\n");
}
#endif/*__SINGLE__CPP_*/


 

//main.cpp
#include"Single.cpp"
void main()
{
Fun();
}

 

VC2008下编译,出现如下问题:

 

 

为什么会出现这样的情况呢?

 

其实C语言的编译方式是分离式的,分为两步(简单起见我们假设只有一个生成目标):
1、将一个或多个源文件编译成可重定位的目标文件,其中每个文件是分别编译的。
2、将1步生成的可重定位目标文件链接成一个可执行目标文件或者共享目标文件。


此,假设foo1.c中用include指令包含了foo2.c,foo2.c中定义了函数function且没有用static修饰。那么经过预处
理,foo1.c中也将包含function的定义。于是foo1.c生成的可重定位目标文件(假设为foo1.o)和foo2.c(假设为
foo2.o)都有function这个符号,这样上面所说的第二步就会因为冲突而失败。

事实上,在没有头文件可被包含的情况下(事实上初期,是最早最早的时候),我们可以采用如下的方式:

//main.cpp

extern Fun();

void main()
{
Fun();
}


 

程序编译运行,是我们想要的结果。其实这个就是一个前置声明。事实上,我们不加"extern"关键字,编译也正常:


 

//main.cpp
void Fun();

void main()
{
Fun();
}


 

这是因为:函数具有定义和声明,定义的时候用extern,说明这个函数是可以被外部引用的,声明的时候用extern说明这是一个声明。但由于函
数的定义和声明是有区别的,定义函数要有函数体,声明函数没有函数体,所以函数定义和声明时都可以将extern省略掉,反正其他文件也是知道这个函数是
在其他地方定义的,所以不加extern也行。两者如此不同,所以省略了extern也不会有问题。


 

我们再看变量的声明和定义:


 

extern int a;          //声明一个全局变量a
int a; //定义一个全局变量a
extern int a =0 ; //定义一个全局变量a 并给初值。
int a =0; //定义一个全局变量a,并给初值,


 

第四个等于第三个,都是定义一个可以被外部使用的全局变量,并给初值。但是定义只能出现在一处。也就是说,不管是int a;还是extern int a=0;还是int a=0;都只能出现一次,而那个extern int a可以出现很多次当你要引用一个全局变量的时候,你就要声明,extern int a;这时候extern不能省略,因为省略了,就变成int a;这是一个定义,不是声明。

 

因此,访问者模块时仍要对变量进行声明,而这时候是必须要加"extern"关键字的。

 

这样我们出现一个问题?既然extern+前置声明都能实现这些功能?为什么还得将声明放在头文件中并被包含呢?我觉得下面这段话说得很清楚:


 

在C语言家族程序中,头文件被大量使用。一般而言,每个C++/C程序通常由头文件(header files)和定义文件(definition

files)组成。头文件作为一种包含功能函数、数据接口声明的载体文件,用于保存程序的声明(declaration),而定义文件用于保存程序的实现
(implementation)。
  C++/C程序的头文件以“.h”为后缀。以下是假设名称为 graphics.h的头文件:
  #ifndef GRAPHICS_H (作用:防止graphics.h被重复引用)
  #define GRAPHICS_H
  #include.... (作用:引用标准库的头文件)
  ...
  #include... (作用:引用非标准库的头文件)
  ...
  void Function1(...); (作用:全局函数声明)
  ...
  class Box (作用:类结构声明)
  {
  ...
  };
  #endif
 
 从以上例子可以看出,头文件一般由三部分内容组成:(1)头文件开头处的版权和版本声明;(2)预处理块;(3)函数和类结构声明等。在头文件中,用
ifndef/define/endif结构产生预处理块,用 #include
格式来引用库的头文件。头文件的这种结构,是利用C语言进行开发软件所通常具备的,属于公有知识。
  一般在一个应用开发体系中,功能的真正逻辑
实现是以硬件层为基础,在驱动程序、功能层程序以及用户的应用程序中完成的。根据以上示例,可以发现头文件的主要作用在于调用库功能,对各个被调用函数给
出一个描述,其本身不包含程序的逻辑实现代码,它只起描述性作用,告诉应用程序通过相应途径寻找相应功能函数的真正逻辑实现代码。用户程序只需要按照头文
件中的接口声明来调用库功能,编译器会从库中提取相应的代码。
  从以上结构图来看,头文件是用户应用程序和函数库之间的桥梁和纽带。在整个软件
中,头文件不是最重要的部分,但它是C语言家族中不可缺少的组成部分。做一个不算很恰当的比喻,头文件就像是一本书中的目录,读者(用户程序)通过目录,
可以很方便就查阅其需要的内容(函数库)。在一本书中,目录固然重要,但绝对不是一本书的核心的、最重要的部分。

 

你既然写出了一个模块并且要提供给其它模块使用的功能,你总不能让其它模块去“猜”你模块的哪些接口吧。所以这时候干脆再带个索引一样的头文件。当然如果你只提供了索引不提供内容,则会发生编译时没问题,链接时出问题。

posted @ 2013-07-19 11:17  ~风~  阅读(3390)  评论(0编辑  收藏  举报