最近看了许多关于extern关键字的文章,有一些心得体会,本篇博文综合了许多文章的内容,不正确的地方还希望大家能够给予指正!
其中先指明一个误区:许多人认为头文件只能包含声明,这其实是不对的!头文件一般包含类的定义、extern变量的声明和函数的声明。在头文件中声明的变量、函数是为了在该文件以外的地方使用,可以认为具有全局作用域?(并不确定)
一、首先,让我们来了解声明和定义的基本概念。
1.1.声明的概念
大概来讲声明主要是为了支持模块/编译单元(.cpp)的编译和链接,头文件中的声明可以让其他模块知道所用对象的链接信息而不需要知道实现细节。
而从细节方面来讲:假若我声明了变量int a,会在编译器编译阶段填写映射元素中的类型栏中填入int,名字栏中填入a,(所谓的映射元素包含变量或函数的类型栏、名字栏和地址栏),并在后续代码中找到使用该变量的地方并都填上相应的类型和名字。从这里可以看出,由于地址栏还是空着的,所以声明并不分配内存空间,所以声明可以进行多次。
1.2.定义的概念
定义用来生成映射元素的地址栏,同时将后续代码中用到该变量的地方都填入相同地址栏,完成了内存分配的工作,使对象能够找到自己的实体,因此定义只能进行一次。
小结:声明用在编译阶段,用来检查类型、名字及参数值;定义用在链接阶段,如果只声明而未定义,在程序编译阶段并不会报错,但在链接阶段会报LINK错误。
二、声明可以分为变量的声明、函数的声明和类的声明三种,下面将一一介绍:
2.1.变量的声明
extern int i ; //变量的声明
extern int i = 1; //变量的声明,只有extern中不存在初始化才是声明
int i; //变量的定义
int i = 1; //变量定义后并初始化
用来告诉编译器该变量的类型。
2.2.函数的声明
函数的定义和声明很容易区分,这是因为函数定义后一定会接符合语句,而函数的声明因为最后不会接{...复合语句... },而是直接接了个;很容易区分,所以就默认不添加extern与添加extern的效果是一致的。
extern void A(long); //以分号结尾,函数的声明,extern等同于不写
void A(long); //以分号结尾,函数的声明
void A(long) {...复合语句...} //以复合语句结尾,函数的定义
函数声明的作用如下:
1.使编译器正确处理返回值
2.使编译器可以检查输入参数的数目
3.使编译器检查输入参数的类型,如果类型不正确,则对类型进行隐式转换
2.3.类的声明
类分为声明、定义、实现三个部分
类的声明又称为类的前向声明,告诉编译器Class的类型,但是并不知道其具体实现细节。
Class A; //类的前置声明
此时使用sizeof(A)会报错。
类的定义中则包含了对数据成员(变量)的定义和对函数成员的声明,此时实际上已经给类中的数据成员分配了内存空间。使用sizeof(A)会显示类中数据成员对齐后所占用的内存空间大小。
Class A
{
public:
A ();
int Geta(); //函数的声明
protected:
...
private:
int a; //变量的声明,切记不能将它理解为定义,只有在类的构造函数中才定义并初始化这些成员变量
} //类的定义
因为在类的定义中并没有完成函数的定义,所以在类的实现中完成成员函数的定义,类的实现在.cpp文件中。
int A::Geta(){...复合语句...} //类的实现
三、那么extern关键字如何使用?
3.1.声明函数或全局变量的作用域范围
如在B.cpp中需要使用到A.cpp中的变量int g_a,则有两种方法。
第一种:在A.h中声明变量‘extern int g_a;’然后在B.cpp中写入‘include"A.h"’;即可。
第二种:直接在B.cpp中写入‘extern int g_a;’这样就不用再B.cpp中‘include"A.h";’了。
也就是说用extern声明的变量可以在本模块或其他模块中使用,即B模块(编译单元)中要使用到A模块(编译单元)中定义的全局变量或函数时,只需要包含A的头文件就可以了,这样B虽然在编译阶段找不到该变量或函数的实体,但它不会报错,它会在链接阶段链接A生成的目标代码找到相应的变量或函数。
3.2.extern与"C"连用
C++相对于C能够实现一个很重要的功能,即函数的重载,这是因为在C++对这几个重载函数进行编译时实际上是将返回值、函数名、参数类型、参数个数组合起来形成了一个新的名字,所以重载函数实际并不同名,例如void A(),void* A(long), void A(long, short)将被分别翻译为“?ABC@@YAXXZ”、“?ABC@@YAPAXJ@Z”、“?ABC@@YAXJF@Z”,在这里多说一句,重载函数实际上在编译阶段就已经被区分出来了,所以函数重载又被称为编译时多态。而C语言在编译时则只是单纯的再标识符前加上_,所以上面的3个函数均会被编译成_A,在编译阶段就会报错说重定义。
那么什么时候按C语言风格编译,什么时候按C++语言风格编译呢?
extern "C" void A(); //按C语言风格编译
extern "C++" void A(); //按C++语言风格编译
在.cpp文件中默认extern "C++",在.c文件中默认extern "C",不用写出,如果想在.cpp中使用C语言风格编译,在显示写出extern "C"。
好了,本篇就到这里结束了,俺也写累了,拜拜~