在 main() 与全局中定义 extern 变量

Defining extern variable in main() vs. globally

本文关键字:定义 extern 变量 全局 main      更新时间:2023-10-16

给定以下头文件,如果在主体内部定义了"a",我会收到警告"未使用的变量'a'"和链接器错误"未定义对'a'的引用。

标题.h:

#ifndef HEADER_H
#define HEADER_H
#include <iostream>
extern int* a;
void f()
{
    std::cout<<*a <<std::endl;
    return;
}
#endif

主.cpp:

#include "header.h"
int main()
{
    int* a = new int(10);
    f();
}

但是,如果在 main() 之外定义了"a",则程序链接没有错误,并且 f() 按预期工作(打印 10)。这是为什么呢?

例:

int* a = new int(10);
int main()
{
    f();
}
int* a = new int(10);

对于此行,如果在 Main 函数中,则正在定义一个局部变量。

因此,extern int* a;只声明一个变量,而不定义它。 然后在该符号上出现链接错误

您需要了解名称绑定,它决定了如何同名声明是相关的。 定义变量时在函数中,其名称没有链接;即它所指的实体不同于程序中的任何其他实体。

更一般地说:声明(从这个意义上说,定义也是一个声明)将符号与实体 - 对象(在在这种情况下,声明声明一个变量),一个函数,一个引用、类型或可以在C++中声明的任何其他内容。 是否同名的不同声明与同一实体关联或不由它们的联系来定义。 C++识别三个不同类型的联动:

  • 外部链接,其中实体可以通过其他交易单位的声明来引用,

  • 内部链接,其中实体可以由同一翻译单元中的其他声明引用,

  • 没有联系,其中实体不能被任何其他声明提及。

在块范围内声明的变量(即局部变量)没有链接,除非它们被明确声明extern(和本地声明extern变量不能是定义)。 所以int a main声明(并定义)一个独立于任何实体的实体程序中的其他a。 在命名空间范围内声明的变量具有外部链接,除非它们被声明为static,在这种情况下,它们有内联;在命名空间范围内定义int a时,它具有外部链接,因此是指您声明的同一实体 extern int a在标题中。

当你在 main 中定义变量时,它只在 main 函数内部有作用域。全局外部无法解决这一点。换句话说,链接器无法将全局声明的 extern 与 main 函数中的变量定义匹配。

如果你在

main 内部定义a,那么它的作用域(可见性)仅限于 main - extern声明不会使其在其他任何地方可见。

您必须在命名空间范围内(即在任何函数之外)定义它,才能在其他翻译单元中可见。

阅读 4 个解释问题所在但没有人解释如何正确解决它的答案是相当烦人的。 这可能是一个安全的猜测,如果 OP 不知道作用域,他可能也不知道将变量传递给函数。

问题所在

您正在尝试获取变量的值,但该变量位于另一个函数中。 我该怎么做? 好吧,简单的答案是,你不想得到它。 你没听错。 使用函数的全部原因是可重用性,如果您将新创建的函数绑定到另一个函数,那么您就不能在任何地方使用它。 记住,函数可以帮助你变得懒惰。 一个好的程序员是一个懒惰的程序员。 如果你能写一次函数并在一百万个地方使用它,那么你就做对了。;)

但我仍然真的很想得到那个变量的值

然后,您希望使用函数参数将变量传递给函数。

之所以命名函数,是因为您可以从数学角度考虑它们。 输入变量,在函数运行并使用这些变量完成有趣的事情后获取有用的数据。 因此,假设您有一个数学函数y = f(x),等价物是int f(int x) { /*stuff here*/ }然后您使用int y = f(a)在主函数中调用它,其中a是某个变量或数字。

您希望避免使用全局变量,因为它们并不总是执行您的期望(特别是如果您有很多代码,很容易意外使用相同的名称。

在您的情况下,您希望该函数打印出特定变量的内容,因此我认为您可能正在寻找一种将该函数与任何特定变量一起使用的方法。 所以这是你如何做到这一点的。

void f(); //hi, I'm a function prototype
void f(int a); //hi, I'm a function prototype that takes a parameter
void f(int a, int b); //hi, I'm a function prototype that takes two parameters (both ints)
void f(int a, ...); //hi, I'm a function prototype that takes an int then any number of extra parameters (this is how printf works.)

因此,您真正想做的是将代码更改为以下内容:

标题.h:

#ifndef HEADER_H
#define HEADER_H
#include <iostream>
// extern int* a; // We don't need this
void f(int* a)
{
  if (a != NULL) //always, always check to make sure a pointer isn't null (segfaults aren't fun)
    std::cout<<*a <<std::endl;
    //return;  //Don't really need this for a function declared void.
}
#endif

主.cpp:

#include "header.h"
int main()
{
    int* a = new int(10);
    f(a);
    return 0; //main is declared as returning an int, so you should.
}

按值、指针和引用的函数

所以,在你举的例子中,我用了int而不是int*。 两者之间的区别在于第一个按值传递参数。 另一个通过指针。 当你将一个变量传递给一个函数时,总是会复制它。 如果你给它传递一个 int,它会创建一个 int 的副本,如果你传递给它一个 4 MB 的结构,它将创建一个 4MB 结构的副本,如果你传递给它一个指向 4MB 结构的指针,它将创建一个指针的副本(而不是整个结构)。 这很重要,原因有二:

  1. 性能:复制 4MB 结构需要一些时间。
  2. 更改内容的能力:如果创建指针的副本,则原始数据仍位于同一位置,并且仍可通过指针访问。

如果你想要 1 而不是 2 怎么办? 那么,您可以声明指针const. 原型如下所示:int f(int const* a);

如果你想要 2 而不是 1 怎么办? 坚硬的饼干(反正没有充分的理由。

最后,您还可以声明一个函数来获取引用而不是指针,引用和指针之间的最大区别是引用不会为 NULL(并且您不能在引用上使用指针算术。 您需要正常使用按引用传递或按值传递。 需要通过指针传递是我几乎不需要做的事情,根据我的经验,这更像是一种特殊情况。

通过引用:int f(int& a);
通过常量引用传递:int f(int const& a);

所以总结一下:

if you have function that needs parameters:
  then:
    if you do not need to modify the contents:
      then:
        if the size of the variable is small:
          pass by value: int f(int a);
        else if the size of the variable is large:
          then:
            if the value of the address can be NULL:
              pass by const pointer: int f(int const* a);
            else:
              pass by const reference: int f(int const& a);
    else if you do need to modify the contents:
      then:
        if the value of the address can be NULL:
          pass by pointer: int f(int* a);
        else:
          pass by reference: int f(int& a);

还有一些情况,但这些是主要的,请参阅此网站以获取更多详细信息。

当您在函数 main 中定义变量时,它仅在 main 的范围内有效。您可以在所有函数中定义一个名为 a 的变量。但这些将是不同的变量,因为每个变量都有其欧文范围。

从技术上讲,变量是在调用函数时在堆栈上分配的,因此每个实例都有自己的存储。