正确使用"extern"关键字

Using the 'extern' keyword properly

本文关键字:extern 关键字      更新时间:2023-10-16

有一些来源(书籍,在线材料)解释extern的用法如下:

extern int i;        // declaration - has 'extern'
int i = 1;           // definition  - specified by the absence of 'extern'

并且有一些来源支持以下语法:

extern int i;        // declaration
extern int i = 1;    // definition  - specified by the equal sign
                     // Both marked with 'extern'

我的问题是 - 这是 CC++ 的区别,还是 ANSI 与 ANSI 之前的实践?

现在,更实际的问题:

使用第二种语法,我想创建一个全局对象(从每个编译单元中可见)。构造函数不带参数,因此括号和等号都不是必需的。

extern MyClass myobject;

现在编译器如何区分声明和定义?

编辑:回到学校,我已经习惯了第一种语法(Borland C)。后来我使用了一个编译器(可能是GCC的某个古老版本),它拒绝编译没有"extern"的定义。这就是让我感到困惑的原因。

具体到您的示例,C 和 C++ 在这里没有区别。在两种语言中都有效的基本规则是:如果您的声明包含初始值设定项,那么它是一个定义。时期。不管它是否有明确的extern。如果它有一个初始值设定项,那么它是一个定义

这意味着在命名空间范围内,extern int i = 1int i = 1都是等效的,即 这种声明中的extern是多余的。在C++ extern中,当声明的对象被const时,定义变得非冗余,因为C++中的const对象默认具有内部链接。例如,extern const int c = 42;定义了具有外部链接的常量c

如果声明没有初始值设定项,则(并且只有这样)它才开始依赖于extern关键字的存在。对于extern,它是一个非定义声明。没有extern它是一个定义。(在 C 中,这将是一个暂定定义,但这在我们的上下文中无关紧要)。

现在,对于您的实际问题。为了创建一个全局对象,您必须将其声明

extern MyClass myobject;

(通常在头文件中完成),然后在某个翻译单元中将其定义为

MyClass myobject;

由于构造函数不带参数,因此这是定义对象的唯一方法。(从 C++11 开始,如果您愿意,您也可以使用 MyClass myobject{};

如果您必须向构造函数提供参数(例如,42 ),您将能够同时使用两者

MyClass myobject(42);

extern MyClass myobject(42);

作为定义,因为初始值设定项的存在确保它确实被解释为定义

对于文件范围的变量,无论它们是类类型还是基元类型:

  • 没有初始化器的extern T t;是一个声明;
  • extern T t = expression;任何语法(赋值、构造或统一)的初始化器都是一个定义;
  • 没有初始化器的T t;是一个定义,初始化为默认值 T ;
  • T t = expression;使用任何语法的初始化器作为定义。

extern int i = 1;int i = 1; 之间没有区别,并且两种样式都有争论,但总的来说,我会主张第二种,因为您应该已经知道文件范围内的定义具有链接。

从历史上看,在ANSI C之前,extern关键字似乎不是必需的;例如参见 http://www.jetcafe.org/jim/c-style.html#Declarations

因此,对于类类型,请为声明编写extern MyClass myobject;,为定义编写MyClass myobject;

草案 n3337, 3.1.2

声明

是一个定义,除非它声明了一个没有 指定函数的主体 (8.4),它包含 extern 说明符 (7.1.1) 或链接规范25 (7.5),但都不是初始值设定项 也不是函数体,它在类中声明静态数据成员 定义(9.2,9.4),它是一个类名声明(9.1),它是一个 不透明枚举声明(7.2),它是一个模板参数(14.1),它 是函数声明符中的参数声明 (8.3.5),它是 不是函数定义的声明符,或者它是一个 typedef 声明 (7.1.3)、别名声明 (7.1.3)、使用声明 (7.3.3)、static_assert声明(第7条)、 属性声明(条款 7)、空声明(条款 7)或 使用指令 (7.3.4)。[ 示例:除以下一项外,其他所有项均为 定义:

int a; // defines a
extern const int c = 1; // defines c
int f(int x) { return x+a; } // defines f and defines x
struct S { int a; int b; }; // defines S, S::a, and S::b
struct X { // defines X
int x; // defines non-static data member x
static int y; // declares static data member y
X(): x(0) { } // defines a constructor of X
};
int X::y = 1; // defines X::y
enum { up, down }; // defines up and down
namespace N { int d; } // defines N and N::d
namespace N1 = N; // defines N1
X anX; // defines anX
whereas these are just declarations:
extern int a; // declares a
extern const int c; // declares c
int f(int); // declares f
struct S; // declares S
typedef int Int; // declares Int
extern X anotherX; // declares anotherX
using N::d; // declares d

—结束示例 ]

我想创建一个全局对象(从每个编译单元中可见)

extern对于定义不是必需的,因为外部链接是默认设置。

你应该做的是把:

extern MyClass myobject;

头文件(这不是定义)中,以使编译器在编译其他编译单元时知道数据类型。 然后,在一个编译单元中,写入:

MyClass myobject;

这是一个具有外部联系的定义。

好的,所以 extern 关键字放在外部文件中变量名称之前。假设您的项目中有一个单独的文件。假设该文件是一个名为MyMath.h的头文件(就好像您正在制作带有一些有用函数/类的便携式数学文件一样)。在你的头文件中,你会把所有的原型,或前向引用,用于你的酷数学函数和类。此数学头文件的实际代码或函数等将位于名为 MyMath 的.cpp文件中.cpp(通常您使用相同的名称来保持其组织)。extern关键字在这里发挥作用:如果你想在你的数学文件中有一个 PI (3.1415) 的全局变量,你需要像往常一样定义它(在.cpp文件中),float PI = 3.1415;然后在你的 .h 或头文件中编写原型或变量的声明, 前缀为 extern .

所以完整的示例可能如下所示:

----我的数学.h----

#ifndef MYMATH_H_INCLUDED
#define MYMATH_H_INCLUDED
extern float PI;
#endif // MYMATH_H_INCLUDED

----我的数学.cpp----

#include "MyMath.h"
float PI = 3.1415;

----主要.cpp----

#include <iostream>
#include "MyMath.h"
using namespace std;
int main()
{
    cout << "PI = " << PI << endl;
    return 0;
}

希望我解释得彻底!请记住,这是为了在文件之间使用变量!

"

initializer"胜过"extern"。行动胜过空谈。