参考整理自:
一、区分变量声明和变量定义
变量声明和变量是有区别的。C++支持
分离式编译机制,即程序可以被分割成多个文件,编译时每个文件都是单独被编译。这样,如果程序有多个文件,则需要在
文件中有共享代码的方法。比如一个文件中要使用另一个文件中定义的变量,我们将如何调用?
解决问题的办法是实现变量的声明与定义相分离,声明变量使得名字为本程序文件所知道,比如一个文件如果想使用在另外一个文件中定义的变量,则必须包含对那个变量名字的声明,以让该文件知道,
而定义变量则是创建一个与名字相关的实体。声明与定义变量的相同点就是都规定了变量的类型和名字,不同的是,定义还会给变量申请存储空间,还可能给变量赋予一个初始值。
二、extern用于变量
如果我们只是要声明一个变量,无需定义,则在变量前加关键字extern即可,而且还不要显式初始化变量,加extern关键字表明该变量已经在别的程序文件中定义,这里只是说明在本程序文件中通过这种方式告之本程序要用到那个在别处定义的变量
extern int i; //只是声明i而非定义i
int j; //声明而且还定义了j
需要注意的是,任何一个显式初始化的声明都将成为定义,而不管有没有extern,extern语句一旦变量赋予了初始值就变成了定义。
extern double pi=3.1415926; //定义
double pi=3.1415926; //定义,完全同上
在函数内部,如果试图初始化一个extern关键字标记的变量将是错误的,我们需要记住的是,如果想要在多个文件中使用同一个变量,得必须将变量的声明和定义相分离,变量的定义出现且只能出现在其中一个文件当中,而其他用到该变量的文件则必须对其进行声明,不可再重复定义。
当你要引用一个全局变量的时候,你就要声明,extern int a;这时候extern不能省略,因为省略了,就变成int a;这是一个定义,不是声明。
三、extern用于函数
函数,函数,对于函数也一样,也是定义和声明,定义的时候用extern,说明这个函数是可以被外部引用的,声明的时候用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 某某变量;不然就成了定义了。
总结下:
对变量而言,变量的声明有两种情况: 一种是需要建立存储空间的,不用加extern;2、另一种是不需要建立存储空间,需要加extern 。如果你想在本源文件中使用另一个源文件的变量,就需要在使用前用extern声明该变量,或者在头文件中用extern声明该变量;
对函数而言,如果你想在本源文件中使用另一个源文件的函数,就需要在使用前用声明该变量,声明函数加不加extern都没关系,所以在头文件中函数可以不用加extern。
四、链接指示符extern "C"
如果程序员希望调用其他程序设计语言尤其是C 写的函数,那么调用函数时必须告诉编译器使用不同的要求,例如当这样的函数被调用时函数名或参数排列的顺序可能不同,无论是C++函数调用它还是用其他语言写的函数调用它,程序员用链接指示符linkage directive 告诉编译器该函数是用其他的程序设计语言编写的,链接指示符有两种形式既可以是单一语句single statement 形式也可以是复合语句compound statement 形式。
// 单一语句形式的链接指示符
extern "C" void exit(int);
// 复合语句形式的链接指示符
extern "C" {
int printf( const char* ... );
int scanf( const char* ... );
}
// 复合语句形式的链接指示符
extern "C" {
#include <cmath>
}
链接指示符的第一种形式由关键字extern 后跟一个字符串常量以及一个普通的函数,声明构成虽然函数是用另外一种语言编写的,但调用它仍然需要类型检查。例如编译器会检查传递给函数exit()的实参的类型是否是int ,或者能够隐式地转换成int 型。多个函数声明可以用花括号包含在链接指示符复合语句中,这是链接指示符的第二种形式,花扩号被用作分割符表示链接指示符应用在哪些声明上,在其他意义上该花括号被忽略,所以在花括号中声明的函数名对外是可见的就好像函数是在复合语句外声明的一样,例如在前面的例子中复合语句extern "C"表示函数printf()和scanf()是在C 语言中写的,函数因此这个声明的意义就如同printf()和scanf()是在extern "C"复合语句外面声明的一样,当复合语句链接指示符的括号中含有#include 时,在头文件中的函数声明都被假定是用链接指示符的程序设计语言所写的,在前面的例子中,在头文件<cmath>中声明的函数都是C函数。
链接指示符不能出现在函数体中,下列代码段将会导致编译错误。
int main()
{
// 错误: 链接指示符不能出现在函数内
extern "C" double sqrt( double );
305 第七章函数
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}
如果把链接指示符移到函数体外程序编译将无错误
extern "C" double sqrt( double );
int main()
{
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}
但是把链接指示符放在头文件中更合适在那里函数声明描述了函数的接口所属,如果我们希望C++函数能够为C 程序所用又该怎么办呢我们也可以使用extern "C"链接指示符来使C++函数为C 程序可用例如。
// 函数calc() 可以被C 程序调用
extern "C" double calc( double dparm ) { /* ... */ }
如果一个函数在同一文件中不只被声明一次则链接指示符可以出现在每个声明中它,也可以只出现在函数的第一次声明中在这种情况下第二个及以后的声明都接受第一个声
明中链接指示符指定的链接规则例如
// ---- myMath.h ----
extern "C" double calc( double );
// ---- myMath.C ----
// 在Math.h 中的calc() 的声明
#include "myMath.h"
// 定义了extern "C" calc() 函数
// calc() 可以从C 程序中被调用
double calc( double dparm ) { // ...
在本节中我们只看到为C 语言提供的链接指示extern "C",extern "C"是惟一被保证由所有C++实现都支持的,每个编译器实现都可以为其环境下常用的语言提供其他链接指示例如extern "Ada"可以用来声明是用Ada 语言写的函数,extern "FORTRAN"用来声明是用FORTRAN 语言写的函数,等等因为其他的链接指示随着具体实现的不同而不同所以建议读者查看编译器的用户指南以获得其他链接指示符的进一步信息。
总结:
extern有两个作用,一是由于const和typedef在默认情况下是内部链接(静态链接)的,我们用extern去修饰可让它变成外部链接,让其他程序文件可见。
二是用extern修饰后的变量名可表示是一个变量声明,且仅仅是声明,它的定义和声明不在一起,可能是在别的文件中已经定义了该变量,我们在本文件中使用extern声明仅仅是告诉编译器,我们有这么个名字的变量要用到,它的定义来自于别的文件中。