C extern 关键字解析

l 声明extern关键字的全局变量和函数可以使得它们能够跨文件被访问。

我们一般把所有的全局变量和全局函数的实现都放在一个*.cpp文件里面,然后用一个同名的*.h文件包含所有的函数和变量的声明。如:

/*Demo.h*/
#pragma  once
extern int a;
extern int b;
int add(int a,int b);
/*Demo.cpp*/
#include "Demo.h" /*这句话写或者不写在本例中都行,不过建议不写*/
/*不写不会出问题,写了有些情况下会出问题,下面有解释*/
int a =10;
int b =20;
 
int add(intl,intr)
{
      return l +r;
}

如果将Demo.cpp写成了Demo.c,编译器会告诉你说无法解析的外部符号。

因为Demo.c里面的实现会被C编译器处理,然而C++和C编译器在编译函数时存在差异,所以会存在找不到函数的情况。

l 全局函数的声明语句中,关键字extern可以省略,因为全局函数默认是extern类型的。

l 声明和定义

extern int a; //属于声明  extern int a = 10; //属于定义,同下
extern char g_str[]="123456";//这个时候相当于没有extern

如果在一个文件里定义了char g_str[] = "123456";在另外一个文件中必须使用extern char g_str[ ];来声明。不能使用extern char* g_str;来声明。extern是严格的声明。且extern char* g_str只是声明的一个全局字符指针。

注:声明可以拷贝n次,但是定义只能定义一次。

二、extern “C”

l extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。

被extern "C"限定的函数或变量是extern类型的:

extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句:

extern int a;

仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。

通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。

与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。

实现C++与C及其它语言的混合编程:

被extern"C"修饰的变量和函数是按照C语言方式编译和连接的,未加extern “C”则按照声明时的编译方式。

l extern "C"的惯用法

(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

extern "C"{
    #include "cExample.h"
}

而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern"C"声明,在.c文件中包含了extern"C"时会出现编译语法错误。

(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern"C",但是在C语言中不能直接引用声明了extern"C"的该头文件,应该仅将C文件中将C++中定义的extern"C"函数声明为extern类型。

三、 extern 和static

(1)extern表明该变量在别的地方已经定义过了,在这里要使用那个变量。

(2)static 表示静态的变量,分配内存的时候,存储在静态区,不存储在栈上面。

static作用范围是内部连接的关系这和extern有点相反。它和对象本身是分开存储的,extern也是分开存储的,但是extern可以被其他的对象用extern引用,而static不可以,只允许对象本身用它。具体差别首先,static与extern是一对“水火不容”的家伙,也就是说extern和static不能同时修饰一个变量;其次,static修饰的全局变量声明与定义同时进行,也就是说当你在头文件中使用static声明了全局变量后,它也同时被定义了;最后,static修饰全局变量的作用域只能是本身的编译单元,也就是说它的“全局”只对本编译单元有效,其他编译单元则看不到它,如:

/*test1.h*/
#ifndef TEST1H
#define TEST1H
static char g_str[]="123456";
void fun1();
#endif



/*test1.cpp*/
#include "test1.h"
void fun1()
{
      cout <<g_str<<endl;
}



/*test2.cpp*/
#include "test1.h"
void fun2()
{
      cout <<g_str<<endl;
}

以上两个编译单元可以连接成功,当你打开test1.obj时,你可以在它里面找到字符串"123456",同时你也可以在test2.obj中找到它们,它们之所以可以连接成功而没有报重复定义的错误是因为虽然它们有相同的内容,但是存储的物理地址并不一样,就像是两个不同变量赋了相同的值一样,而这两个变量分别作用于它们各自的编译单元。也许你比较较真,自己偷偷的跟踪调试上面的代码,结果你发现两个编译单元(test1,test2)的g_str的内存地址相同,于是你下结论static修饰的变量也可以作用于其他模块,但是我要告诉你,那是你的编译器在欺骗你,大多数编译器都对代码都有优化功能,以达到生成的目标程序更节省内存,执行效率更高,当编译器在连接各个编译单元的时候,它会把相同内容的内存只拷贝一份,比如上面的"123456",位于两个编译单元中的变量都是同样的内容,那么在连接的时候它在内存中就只会存在一份了,如果你把上面的代码改成下面的样子,你马上就可以拆穿编译器的谎言:

/*test1.cpp*/
#include "test1.h"
void fun1()
{
      g_str[0]=''a'';
      cout <<g_str<<endl;
}




/*test2.cpp*/
#include "test1.h"
void fun2()
{
      cout <<g_str<<endl;
}



/*main.cpp*/
void main()
{
      fun1();// a23456
      fun2();// 123456
}

这个时候你在跟踪代码时,就会发现两个编译单元中的g_str地址并不相同,因为你在一处修改了它,所以编译器被强行的恢复内存的原貌,在内存中存在了两份拷贝给两个模块中的变量使用。正是因为static有以上的特性,所以一般定义static全局变量时,都把它放在原文件中而不是头文件,这样就不会给其他模块造成不必要的信息污染,同样记住这个原则吧!

四、extern和const

C++中const修饰的全局常量具有跟static相同的特性,即它们只能作用于本编译模块中,且static修饰的是全局变量,但是const可以与extern连用来声明该常量可以作用于其他编译模块中,如externconst char g_str[];

然后在原文件中别忘了定义:const char g_str[] = "123456";

所以当const单独使用时它就与static相同,而当与extern一起合作的时候,它的特性就跟extern的一样了!所以对const我没有什么可以过多的描述,我只是想提醒你,const char* g_str = "123456" 与 const char g_str[] ="123465"是不同的,前面那个const修饰的是char *而不是g_str,它的g_str并不是常量,它被看做是一个定义了的全局变量(可以被其他编译单元使用), 所以如果你像让char* g_str遵守const的全局常量的规则,最好这么定义const char* const g_str="123456"。、

常用写法

//fileA.cpp
extern const int i = 1;        //定义
 
//fileB.cpp                    //声明
extern const int i;

而 extern "C" 和extern "C++"函数的区别:

// 声明printf函数使用C链接
extern "C" int printf(const char *fmt, ...);
 
 
//声明指定的头文件内所有的东西都使用 C 链接
extern "C" {
#include <stdio.h>
}
 
//  声明函数ShowChar和GetChar使用 C 链接
extern "C" {
    char ShowChar(char ch);
    char GetChar(void);
}
 
//  定义函数 ShowChar 和 GetChar 使用 C 链接
extern "C" char ShowChar(char ch) {
    putchar(ch);
    return ch;
}
 
extern "C" char GetChar(void) {
    char ch;
    ch = getchar();
    return ch;
}
 
// 声明全局变量 errno 为C链接
extern "C" int errno;
 
//又比如,在程序中常见的代码段
#ifdef __cplusplus  
extern "C" {  
#endif  
  
/**** some declaration or so *****/  
  
#ifdef __cplusplus  
}  
#endif
 
//这里__cplusplus是cpp中的自定义宏,定义了这个宏就表明这是一段cpp的代码,也就是说,
//上面的代码的含义是:如果这是一段cpp的代码,那么加入extern "C"{和}处理其中的代码。

使用extern和包含头文件来引用函数有什么区别?

与include相比,extern引用另一个文件的范围小,include可以引用另一个文件的全部内容。extern的引用方式比包含头文件要更简洁。extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。

也就是说extern有两个作用,第一个,当它与"C"一起连用时,如: extern "C" void fun(int a, int b);则告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名而不是C++的,C++的规则在翻译这个函数名时会把fun这个名字变得面目全非,可能是fun@aBc_int_int#%$也可能是别的,这要看编译器的"脾气"了(不同的编译器采用的方法不一样),为什么这么做呢,因为C++支持函数的重载啊,在这里不去过多的论述这个问题,如果你有兴趣可以去网上搜索,相信你可以得到满意的解释!

第二,当extern不与"C"在一起修饰变量或函数时,如在头文件中: extern int g_Int; 它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块活其他模块中使用,记住它是一个声明不是定义!也就是说B模块(编译单元)要是引用模块(编译单元)A中定义的全局变量或函数时,它只要包含A模块的头文件即可,在编译阶段,模块B虽然找不到该函数或变量,但它不会报错,它会在连接时从模块A生成的目标代码中找到此函数。

有moduleA、moduleB两个模块,B调用A中的代码,其中A是用C语言实现的,而B是利用C++实现的,下面给出一种实现方法:

//moduleA头文件
#ifndef __MODULE_A_H //对于模块A来说,这个宏是为了防止头文件的重复引用
#define __MODULE_A_H
int fun(int, int);
#endif
 
//moduleA实现文件moduleA.c //模块A的实现部分并没有改变
#include"moduleA"
int fun(int a, int b)
{
return a+b;
}
 
//moduleB头文件
#idndef __MODULE_B_H //很明显这一部分也是为了防止重复引用
#define __MODULE_B_H
#ifdef __cplusplus 
//这一部分就是告诉编译器,如果定义了__cplusplus(即如果是cpp文件----cpp文件默认定义了该宏),
// 则采用C语言方式进行编译
extern "C"{ 
#include"moduleA.h"
#endif
… //其他代码
 
#ifdef __cplusplus
}
#endif
#endif
 
//moduleB实现文件 moduleB.cpp //B模块的实现也没有改变,只是头文件的设计变化了
#include"moduleB.h"
int main()
{
  cout<<fun(2,3)<<endl;
}

示例2:模块1中

extern int _a = 10;
extern int _b = 20;

int maxAB(int a,int b) {
    return a > b ? a : b;
}

模块2中

extern int _a;
extern int _b;
int maxAB(int a,int b);
int main() {
    cout << "a:" << _a << " b:" << _b << endl;
    cout << maxAB(100,200) << endl;
}

结果:
a:10 b:20
200

猜你喜欢

转载自blog.csdn.net/cyy1104/article/details/129582708
今日推荐