解释"extern "在C++中对变量和函数的使用

136 阅读7分钟

C++中的 "extern "一词是一个指定符。本文将解释它在C++中对变量和函数的使用。首先,我们给出了C++中声明和定义的含义。考虑一下下面几行代码。

    int it;

    char fn(int itg, char ch);

    it = 5;

    char fn(int itg, char ch) {
        char var = 'o';
        if (itg == 1 && ch == 'a')
            var = 'z';
        return var;
    }

第一行是一个变量声明。第二行是一个函数签名,如果它没有以分号结束,后面是一个函数体。这第二行以分号结尾是一个函数原型。它也是一个函数声明。第三行为整数变量赋值:这是一个变量初始化,但仍然可以大致看作是一个变量定义。这段代码的其余部分是一个函数定义。它以函数签名开始,接着是函数体。

在C++中,当涉及到声明和定义时,有一个细微的差别。下面的语句是一个变量声明。

    int it = 5;

像这样一个完整的变量声明,在其中介绍了变量,然后分配了一个值,仍然是一个变量声明。所以,一个变量声明可以只是单独介绍变量,也可以是带有定义的介绍。

下面的代码(从上面复制的)是一个函数声明。

    char fn(int itg, char ch) {
        char var = 'o';
        if (itg == 1 && ch == 'a')
            var = 'z';
        return var;
    }

像这样一个完整的函数声明,它的签名介绍了函数,然后后面是函数体,仍然是一个函数声明。所以,一个函数声明可以只是单独的函数原型,也可以是函数签名和函数体一起。

所以,一个声明可以有也可以没有定义。定义就像是声明的一个子部分。对于变量,第一次赋值实际上是初始化,并不是真正的定义。当一个变量第一次被声明而没有初始化时,它在内存中的位置已经被提供,但它是空的。在这个位置上装入一个值就是初始化,也就是完成了定义。

传统上,一个简单的C++应用程序有三个文件。它有一个主文件,可以被称为第一个文件。它有一个第二文件和一个头文件。使用extern指定符可以将文件的数量减少到两个(从三个)。这篇文章解释了在变量和函数中使用extern指定符来避免头文件。注意:在C++词汇中,这样的两个文件被称为翻译单元。

文章内容

没有extern的头文件

传统上,一个简单的C++应用程序有三个文件:带有main()函数的主文件,可以调用第一个文件,第二个文件,以及一个头文件。头文件应该有变量和函数的声明,但没有它们的定义。头部声明的定义应该在第二个文件中。在第一个文件的顶部,必须要有。

    #include "head.hh"

其中head.hh是头文件的名称,它位于用户主页目录中。include指令没有以分号结尾。在这种情况下,头文件中没有定义的变量声明和没有函数定义的函数原型,不应该在前面加上extern指定符。而应用程序应该可以正常工作。

插图
上面的变量和函数在这里是用来说明的。

在文本编辑器中键入以下代码,并将其保存在用户主页目录中,名称为head.hh。

    int it = 5;
    char fn(int itg, char ch);

头部只有两条语句。接下来,在文本编辑器的无标题文档中输入以下内容,并保存在用户主页目录中,名称为second.cpp 。

    char fn(int itg, char ch) {
        char var = 'o';
        if (itg == 1 && ch == 'a')
            var = 'z';
        return var;
    }

接下来,在文本编辑器的另一个无标题文档中输入以下代码,并将其保存在用户主页目录下,名称为first.CPP。

    #include "head.hh"
    #include
    using namespace std;

    int main()
    {
        cout << it << endl;
        cout << fn(1, 'a') << endl;
   
        return 0;
    }

用以下终端命令编译该应用程序。

    g++ first.cpp second.cpp -o complete.exe

用以下命令执行该应用程序。

    ./complete.exe

输出结果是。

    5
    z

不幸的是,头文件不允许在没有初始化的情况下简单声明一个变量(例如,it)。然而,这个问题可以按照下面的方法解决。

没有头文件的extern

如果适当地使用extern指定符,就可以省去头文件。在这种情况下,会有一个变量和函数的声明,在第一个(主)文件中没有定义。每一个都会在前面加上extern。

示例
在文本编辑器中键入以下代码,并将其保存在用户主页目录中,名称为 first.cpp 。

    #include
    using namespace std;

    extern int it;

    extern char fn(int itg, char ch);

    int main()
    {
        cout << it << endl;
        cout << fn(1, 'a') << endl;

        return 0;
    }

接下来,在文本编辑器的无标题文档中输入以下内容,并保存在用户主页目录下,名称为second.cpp。

    int it = 5;

    char fn(int itg, char ch) {
        char var = 'o';
        if (itg == 1 && ch == 'a')
            var = 'z';
        return var;
    }

变量和函数的定义已经在第二个文件中发生了。在这里的第一个文件中,它们的声明没有定义。在这个新的应用程序中没有包含头文件。只涉及两个文件。请注意,在第二个文件中,变量已经被完全声明,但没有extern这个词。即使是函数,也是在没有extern这个词的情况下完全声明的。然而,"extern "这个词必须在第一个文件中的部分声明之前出现。

用下面的终端命令编译该程序。

    g++ first.cpp second.cpp -o complete.exe

用以下命令运行该应用程序

    ./complete.exe

输出结果是。

    5
    z

和以前一样,但没有任何头文件。

所以,extern指定符链接了两个文件之间的声明。一个文件应该做没有定义的声明,并带有extern。另一个文件应该做一个定义,这将是一个完整的声明,但没有extern。

头文件和extern

上面的应用程序有一个问题,就是变量必须在头文件中完全声明。为了让头文件中的变量声明没有定义,该变量必须在前面加上extern。所以,如果有

    extern int it;

在头文件中,就会出现

    int it = 5;

在第二个文件中,仍然会有

    #include "head.hh"

在第一个文件(主文件)的顶部。

常量和extern

在正常情况下,一个常量必须被初始化。比如说

    const char ch = 'e';

是允许的,而

    const char ch;

是不允许的。

然而,通过extern指定符,一个常量可以在第一和第二文件中不经初始化就被声明。因此,如果在第一个文件中,有

    extern const char ch;

在第二个文件中,将有

    char ch = 'e';

在第二个文件中没有const。两个文件中的ch是同一个实体。

用以下内容替换第一个.cpp文件并保存。

    #include
    using namespace std;

    extern const char ch;

    int main()
    {
        cout << ch << endl;

        return 0;
    }

用以下内容替换第二个.cpp文件并保存。

    char ch = 'e';

用下面的终端命令编译该应用程序。

    g++ first.cpp second.cpp -o complete.exe

用以下命令运行该应用程序

    ./complete.exe

输出结果应该是,e.

Extern和static

C++中的存储类指定器是static、thread_local、extern、mutable。在一个给定的声明中不能使用多于一个。然而,在少数情况下,thread_local和static可能出现在一个实体声明的前面,或者thread_local和extern可能出现在一个实体声明的前面。所以,extern和static永远不能作为一个声明的指定符出现。

结论

extern指定符链接了同一实体的两个声明,这两个声明在两个不同的文件中。拥有extern指定符的声明不应该被初始化或定义。另一个文件中没有extern指定符的声明应该被初始化或定义。这个方案适用于变量和函数。它消除了为感兴趣的变量和函数编写头文件的需要。它允许在一个文件和另一个文件中声明一个常量而不进行初始化。如果程序员想要一个头文件,那么为了让头文件中的变量不被初始化,程序员必须对头文件中的变量使用extern。