【问题标题】:What's the relation between libraries and namespaces?库和命名空间之间有什么关系?
【发布时间】:2020-12-16 16:44:22
【问题描述】:

刚开始拼贴,我是编程界的新手。因此,当我学习 C++ 时,我遇到了一个让我无法回答的问题:“如果我已经获得了 iostream,为什么我需要在我的代码中包含“使用命名空间 std”才能写入或读取?” ,因为我被告知“cin/cout”已经在 iostream 库中定义,但我注意到如果我单独编写其中的一行,它会产生编译错误。并且,因此,iostream 和“std”命名空间之间的关系是什么......有人可以解释一下吗?谢谢!

【问题讨论】:

标签: c++ input namespaces output iostream


【解决方案1】:

#include 与使用

简单来说:

#include <iostream> // include the header

int main() {
    // now you can use stuff declared in that header
    std::cout << "Hello world" << std::endl;

    // if you are lazy you can "use" things:
    using std::cout;
    using std::endl;
    cout << "Hello World" << endl;
}

你确实不必using namespace std;!适合这样做的情况如此罕见,而真正造成巨大伤害的情况如此频繁,以至于根据经验,您可以记住:永远不要使用它!详情见这里:Why is “using namespace std;” considered bad practice?。重要的是要意识到完全限定名称 std::cout 和不完全限定名称 cout 之间的区别不仅仅是输入或多或少 5 个字符(请继续阅读...)。


库与命名空间

库和命名空间有什么关系?

标准库将所有内容都放在std 命名空间中。命名空间有助于将事物分开。不同的库可以包含other_namespace::vector,不会与std::vector 混淆,因为我们有命名空间。


非常酷的东西

使用命名空间的一个更深层次的原因是Argument Dependent Lookup。我将尝试用一个简单的例子来解释。假设您正在使用带有一些函数模板的库,该函数模板对您必须提供的类型的对象执行某些操作:

namespace library {
    template<typename T>
    void do_something(T& a,T& b){
        std::cout << "wrong...\n";
        std::swap(a,b);    // (1) 
        std::cout << "correct\n";
        using std::swap;   
        swap(a,b);         // (2)
    }
}

我拿了两个对象并交换了两次。你必须忍受我一秒钟才能理解为什么(1)是错误的,只有(2)是正确的。现在我们有一个库函数模板,要使用它,我们需要一些类型T

namespace A {
    struct foo{};
    void swap(foo& a,foo& b) {
        std::cout << "A::swap" << "\n";
    }
}

想象foo 是这样的,我们知道比std::swapswap 更好的方法。实际上foo 是空的,所以对于swap 两个对象我们什么都不做。

让我们回顾一下:标准库附带有std::swap。有人写了一个我们想要使用的库(称为library)。我们希望库代码调用A::swap 而不是std::swap。库作者甚至不知道A::swap 的存在。

连同上面的Alibrary,这段代码

int main() {
    A::foo a,b;
    library::do_something(a,b);
}

将打印:

wrong...
correct
A::swap

Live Example。发生了什么?这一行:

std::swap(a,b);    // (1) 

毫无疑问,请致电std::swap。不是我们想要的。我们希望库代码调用我们的A::swap

现在这个:

using std::swap;   
swap(a,b);         // (2)

第一行将名称swapstd 拉入函数范围。在第二行中,ADL 终于启动了,因为它说的是 swap 而不是 std::swap。简而言之,ADL 是:ab 来自命名空间 A,因此当编译器搜索所有可能的 swaps 时,它也会搜索 A。如果它在A 中找到一个,那么它会调用它(如果它在A 中找不到一个,则仍然有来自stdswap)。因此只有 (2) 调用了我们的自定义交换。

这仅适用于命名空间。 “很酷的东西”是库作者不需要知道任何关于你的命名空间的事情,但如果它存在,他们的库代码仍然会从你的命名空间调用你的函数。

我应该注意,并非所有代码都是通用库代码。通常你想编写代码,你知道每个细节会发生什么,你想知道调用了哪些函数。通常,您不希望您的代码根据是否包含特定标头而表现不同。因此,大量代码最好使用完全限定的函数调用:std::foo


结论

我希望我能说服您,命名空间不仅仅是或多或少地键入一些字符。 using namespace std; 懒惰完全忽略了命名空间的意义。另一方面,通过using std::foo; foo(); 将名称拉入范围是 完全没问题并启用 ADL。

【讨论】:

  • 是的,还有..如果在iostream中定义了cout和cin,为什么我们需要使用std::...
  • @Robert 因为那是他们的全名。为什么我叫你罗伯特而不是伯特?
  • 你说这很有趣,这应该在一本介绍性的书中介绍,因为在 Stroustrup 的书Programming Principles and Practice Using C++中他实际上使用了@ 987654368@。但是,他确实像您所说的那样提到,以避免对任何名称空间使用 using 指令,但名称空间除外,例如 std。但我同意你的观点,你应该一起避免using namespace std;。只是觉得我应该提一下。
  • @GenoC 我真的很想知道 Bjarne 会对他的防守说些什么。通常的论点是这样的:using namespace std 在简短的示例中或在空间非常有限的演示幻灯片上是可以的。假设读者知道这只是为了演示而不是实际代码。
  • @idclev463035818 我同意!
【解决方案2】:

库和命名空间按照约定是相关的。

按照惯例,库提供给程序员用户的符号包含在命名空间中。这可以组织事物,并且有一些更高级别的语言特性 (ADL),这意味着命名空间中的代码与外部代码的行为不同。

当您输入using namespace std; 时,您会告诉编译器“当您遇到符号时,还要查看std 以查找您是否可以确定它是什么”。在“文件”范围内执行此操作通常是一个非常糟糕的主意;在一个简短的函数中执行此操作是可用的,但除此之外可能会导致非常棘手的错误。

namespace std 交互的标准、专业的方式是在符号前面加上命名空间:

std::cout << "Hello world\n";

而不是

using namespace std;
cout << "Hello world\n";

绝对不会:

using namespace std;
int main() {
  cout << "Hello world\n";
}

您还可以获取单个符号,这还不如导入整个命名空间:

using std::cout;
cout << "Hello world\n";

但也应避免在“文件”范围内。


#include <iostream>

这包括来自系统搜索路径的名为iostream头文件iostream 是标准库的一部分。按照惯例(和 C++ 标准),iostream 为您的程序提供的符号位于 namespace std 内。

通过将符号放在命名空间中,您可以避免与您的代码发生冲突。 std 中有很多很多符号,如果 #include &lt;iostream&gt; 将一些未知数量的符号推入你的全局命名空间,你很容易得到错误或以意想不到的方式调用错误的函数。

std::coutusing namespace std; coutusing std::cout 都是告诉编译器在哪个命名空间中找到符号 cout 的所有方法。

#include &lt;iostream&gt;namespace std 中包含cout;没有它,您的代码将不知道它的存在。

C++ 从 C 发展而来,C 有一个textual 包含模型。 #include 实际上获取文件 iostream 的内容并将其复制/粘贴到您的文件中。然后,您的编译器会读取该扩展文件并在 &lt;iostream&gt; 中找到符号。

因为这种文本包含可能会推动很多内容,所以将其隔离到namespace 可以防止程序员遇到问题。


最近,C++ 添加了模块。模块是#include 指令的替代品,因为它直接从库中获取符号并将其注入到您的代码中无需大量复制粘贴

在模块中,命名空间仍然没有直接连接到模块。你可以

import std;

import std.iostream;

这只会将仍在namespace std 中的std 库符号导入您的代码。 (C++ 标准添加了模块,但还没有模块化 std 库,所以上面的名字是推测)。

符号查找不直接连接到符号导入。

这可以让符号导入以大块的形式完成,同时更仔细地进行查找。

【讨论】:

  • 我部分理解这一点,但还有一件事。命名空间是在库中定义的,反之亦然,或者它们是单独定义的,但相互之间有一些引用?
  • @Robert 按照惯例,它们仅与库相关。按照惯例,编写库的人将代码放入命名空间。您可以在自己的 cpp 文件 namespace bob { struct hello {}; } 和另一个 namespace alice { struct world {}; } 中编写自己的命名空间,这使得两个符号称为 bob::helloalice::world 名称(空)结构。
【解决方案3】:

iostream 是一个库。这是某人为您编写的代码,因此您不必这样做。通过添加#include &lt;iostream&gt;,您告诉预处理器粘贴该代码。但是此代码提供的函数和结构可能具有干扰其他名称的名称。但这不是问题,因为您可以通过将它们放在 命名空间 中将它们分开,STL(上游是其中的一部分)使用 std(标准的缩写,发音为 '站着')。当命名空间中存在某些内容时,您必须命名该命名空间才能访问其中的内容。即std::cout。但有时您不想每次想从 STL 访问某些内容时都写 std::。这就是using namespace std 为您所做的。这样,您只需输入cout。但这是very bad idea

【讨论】:

  • fwiw 我认为视频中的那个人错过了using namespace std; 的真正问题。他主要争论的是风格和可读性……
  • 为什么 cin 和 cout 会引起混淆,因此需要在命名空间中将它们分开?它们不只是为了一件事......写/读吗?
  • @Robert 假设你编写了自己的cout,因为你把它放在你的命名空间中你就有roberts::coutstd::cout,没有命名空间你将有coutcoutstd中的名字非常多,没有人能全部记住,没有命名空间就无法避免冲突
【解决方案4】:

图书馆

库中预先编写了部分代码,以便为您提供功能。可以是函数/重载运算符等形式。

有两种类型的库:

  1. 标准库,例如#include &lt;iostream&gt;,库名在尖括号中。

  2. 用户定义/制作,例如#include "randomLib.h" 库名用双引号括起来。

命名空间

当您的项目需要多个库时。两者都可能包含多个具有相同名称的方法(函数定义),或者单个库可能使用相同的函数名称但在不同的命名空间中。命名空间的存在是为了消除编译器和用户的混淆或歧义。

  1. 假设 lib 1 有 namespace abc{ foo(); } 而 lib 2 有 namespace def{ foo(); }

所以你会为你需要的功能做abc::foo()def::foo()。这里 abc/def 是 namespace:: 称为范围解析运算符,foo() 是您正在调用的方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-06-10
    • 1970-01-01
    • 2014-11-16
    • 2020-07-27
    • 1970-01-01
    • 2012-07-10
    • 2011-06-08
    • 1970-01-01
    相关资源
    最近更新 更多