“使用命名空间”到底有什么作用?

What does "using namespace" do exactly?

提问人:marcv81 提问时间:12/7/2015 最后编辑:curiousguymarcv81 更新时间:12/7/2015 访问量:6398

问:

以下 C++ 测试代码未链接(gcc 4.9.2,binutils 2.25)。错误是 。In function 'main': undefined reference to 'X::test'

01: #include <string>
02: #include <iostream>
03:
04: namespace X
05: {
06:     extern std::string test;
07: };
08:
09: using namespace X;
10: std::string test = "Test";
11:
12: int main()
13: {
14:    std::cout << X::test << std::endl;
15: }

由于第 09 行,我期望第 10 行定义在第 06 行声明的变量。我相信相反,在全局命名空间中声明和定义了一个不相关的变量,因此出现了链接错误。X::testtest

问题:谁能解释一下为什么我的期望不正确,以及到底发生了什么?

不是答案:

  • 我可以将第 10 行更改为 .std::string X::test = "Test";
  • 我不应该一开始就使用“使用命名空间”。
C++ 作用域 命名空间 forward-declaration using 指令

评论

0赞 wouter140 12/7/2015
由于您说在命名空间内,它会在命名空间之外查找它。它没有在命名空间中定义,编译器也找不到它。问你一个问题:为什么它需要是外部的?externUndefined reference
3赞 DevSolar 12/7/2015
@wouter140:与“在命名空间之外寻找东西”无关。它只是意味着“这是在其他地方(外部)al定义的”。extern
1赞 Angew is no longer proud of SO 12/7/2015
@curiousguy 字面意思是“别的地方”,或者实际上是其他任何地方。它只是意味着“这只是一个声明,而不是一个定义。
0赞 curiousguy 12/7/2015
也许您可以发布不带行号或注释中带有编号的代码。不是每个人都使用允许矩形选择的文本编辑器(就像 vim 和 emacs 那样)。
1赞 Angew is no longer proud of SO 12/7/2015
@curiousguy 就像Notepad++一样(也可以命名一个非主要的Unix世界编辑器)。

答:

9赞 Alex 12/7/2015 #1

using namespace意味着您使用您指定的命名空间中的定义,但这并不意味着您定义的所有内容都在您使用的命名空间中定义。

这种行为的逻辑非常简单。假设我们有以下示例:

namespace X
{
    extern string test;
};

namespace Y
{
    extern string test;
};

using namespace X;
using namespace Y;

string test = "value";

按照您的示例逻辑,编译器只是不知道它应该在哪个命名空间中定义,因此您必须显式声明命名空间。在现实生活中,它是在全局命名空间中定义的。test

在特定情况下,您可以在命名空间之外定义变量,该变量声明为 。链接器查找 的定义,但找不到,因此您会看到此错误。testXexternX::test

评论

1赞 marcv81 12/7/2015
我同意您的代码中存在歧义。但是,我不认为您成功地解释了为什么在我的示例中编译器决定在全局命名空间中声明和定义一个新变量,而不是在我正在使用的命名空间中找到现有变量。
0赞 Alex 12/7/2015
@marcv81,我的解释可能不是最好的解释,但在这种情况下,如果你想要一个全局命名空间中的变量,你需要将其定义为,它仍然是显式声明。一般来说,编译器会按照你说的去做,不应该猜测你想要什么。::test
2赞 DevSolar 12/7/2015
@marcv81:关键是编译器在定义标识符时根本不“搜索”声明。(标识符在定义之前不需要声明。该语句在当前命名空间中定义了一个变量,该变量不影响;它仅影响在哪些命名空间中搜索标识符。标识符只有在被引用时才会被“搜索”——而定义则不会。这正是亚历克斯想说的。std::string test = ...testusing namespace ...
3赞 DevSolar 12/7/2015
@marcv81:更好的风格是相反的,总是写,从不写。后者真的是一根拐杖,因为它从源头带走了信息。(你无法判断给定标识符来自哪个命名空间,在调试对其中六个标识符进行“使用命名空间”的源时,这是一个皇家 PITA。如果命名空间名称太长而无法重复键入,请使用 和 。X::using namespace X;namespace short = very::long::namespace;short::identifier
1赞 Ben Voigt 12/7/2015
@DevSolar:从不写作是一个好规则,但总是写作不是。限定名称会中断 ADL。在某些情况下,最好在本地范围内应用声明,将命名空间中的单个名称引入重载集,但让重载解析选择最终是否选择这些名称。using namespaceX::using
29赞 Angew is no longer proud of SO 12/7/2015 #2

该指令使命名空间中的名称在包含该指令的命名空间内可见。也就是说,在该范围内查找名称时,可以找到。但是,只有当编译器需要查找它时,才会查找它。using namespace X;XnX::n

在您的示例中,以下声明:

std::string test = "Test";

在全局命名空间内是完全有意义的。与任何其他声明一样,该名称只是简单地介绍。无需在任何地方查找。test

这将是一锅完全不同的鱼:

namespace X
{
  struct C
  {
    static std::string test;
  };
}

using namespace X;
std::string C::test = "Test";

在此代码中,编译器需要知道什么是有意义的定义。因此,它对 进行名称查找,这确实要归功于该指令。CC::testCX::Cusing

评论

1赞 marcv81 12/7/2015
当面回答。结构体的场景也非常有趣。谢谢。
3赞 Vlad from Moscow 12/7/2015 #3

这是命名空间中变量的声明。testX

04: namespace X
05: {
06:     extern std::string test;
07: };

它不是变量的定义。还必须先定义该变量,然后才能使用它来获取其值。

如果初始化变量,也可以将此声明作为定义。例如

04: namespace X
05: {
06:     extern std::string test = "Test";
07: };

在这种情况下,代码将成功编译。

在此声明中

14:    std::cout << X::test << std::endl;

可以访问限定名称 。编译器在变量中指定的命名空间中搜索此名称,并查找声明。现在它需要获取变量的值,但它无法找到它的定义。X::testX

在此声明中

10: std::string test = "Test";

全局命名空间中存在声明和定义的变量,因为它是在任何显式指定的命名空间之外声明的。test

你可以写

10: std::string X::test = "Test";
                ^^^^^^^

而不是

10: std::string test = "Test";

如果要定义在命名空间中声明的变量。X

至于 using 指令,它会在使用该指令的命名空间中引入在指定命名空间中声明的名称。

例如,如果使用非限定名称进行写入test

14:    std::cout << test << std::endl;
                    ^^^^^

然后就会有歧义,因为这个名称可以引用名称,并且由于 using 指令。X::test::test