提问人:Marius 提问时间:9/3/2008 最后编辑:Philipp Matthias SchäferMarius 更新时间:3/22/2021 访问量:208556
如何在 C++ 中正确使用命名空间?
How do you properly use namespaces in C++?
问:
我来自Java背景,使用包,而不是命名空间。我习惯于将协同工作以形成完整对象的类放入包中,然后稍后从该包中重用它们。但现在我正在使用 C++。
如何在 C++ 中使用命名空间?您是为整个应用程序创建单个命名空间,还是为主要组件创建命名空间?如果是这样,如何从其他命名空间中的类创建对象?
答:
命名空间本质上是包。它们可以像这样使用:
namespace MyNamespace
{
class MyClass
{
};
}
然后在代码中:
MyNamespace::MyClass* pClass = new MyNamespace::MyClass();
或者,如果要始终使用特定命名空间,可以执行以下操作:
using namespace MyNamespace;
MyClass* pClass = new MyClass();
编辑:按照 bernhardrusch 的说法,我倾向于根本不使用“using namespace x”语法,我通常在实例化对象时显式指定命名空间(即我展示的第一个示例)。
正如您在下面所问的,您可以根据需要使用任意数量的命名空间。
评论
std
using
std::cout
std::string
cout
std
using namespace FooBario;
using namespace X;
mylibrary::endl
一般来说,如果我认为可能与其他库存在函数或类型名称冲突,我会为代码正文创建一个命名空间。它还有助于品牌代码,ala boost:: 。
我更喜欢为应用程序使用顶级命名空间,为组件使用子命名空间。
使用其他命名空间中的类的方式与 java 中的方式非常相似。 您可以使用类似于“import PACKAGE”语句的“use NAMESPACE”,例如使用 std。或者将包指定为用“::”分隔的类的前缀,例如 std::string。这类似于 Java 中的“java.lang.String”。
@马吕斯
是的,您可以一次使用多个命名空间,例如:
using namespace boost;
using namespace std;
shared_ptr<int> p(new int(1)); // shared_ptr belongs to boost
cout << "cout belongs to std::" << endl; // cout and endl are in std
[2014 年 2 月 -- (真的有那么久吗?):这个特殊的例子现在是模棱两可的,正如 Joey 在下面指出的那样。Boost 和 std::现在各有一个shared_ptr。
评论
std
shared_ptr
boost
std
shared_ptr
在 Java 中:
package somepackage;
class SomeClass {}
在 C++ 中:
namespace somenamespace {
class SomeClass {}
}
使用它们,Java:
import somepackage;
和 C++:
using namespace somenamespace;
此外,全名是“somepackge”。SomeClass“用于 Java,”somenamespace::SomeClass“用于 C++。使用这些约定,您可以像在 Java 中习惯的那样进行组织,包括为命名空间创建匹配的文件夹名称。但是,文件夹>包和文件>类要求不存在,因此您可以根据包和命名空间独立命名文件夹和类。
为了避免说出所有内容,Mark Ingram 已经说过使用命名空间的小技巧:
避免头文件中的“using namespace”指令 - 这将为导入此头文件的程序的所有部分打开命名空间。在实现文件(*.cpp)中,这通常没有大问题 - 尽管我更喜欢在函数级别使用“using namespace”指令。
我认为命名空间主要用于避免命名冲突 - 不一定是用来组织代码结构。我会主要使用头文件/文件结构来组织C++程序。
有时,命名空间用于较大的 C++ 项目中,以隐藏实现细节。
using 指令的附加说明: 有些人更喜欢只对单个元素使用“using”:
using std::cout;
using std::endl;
评论
using std::cout;
是一个 using 声明
using std::cout, std::endl;
using std::cout, endl;
using namespace x
另请注意,您可以添加到命名空间。举个例子就更清楚了,我的意思是你可以有:
namespace MyNamespace
{
double square(double x) { return x * x; }
}
在文件中,并且square.h
namespace MyNamespace
{
double cube(double x) { return x * x * x; }
}
在文件中。这将定义单个命名空间(即,您可以跨多个文件定义单个命名空间)。cube.h
MyNamespace
请注意,C++ 中的命名空间实际上只是一个命名空间。它们不提供包在 Java 中所做的任何封装,因此您可能不会经常使用它们。
我使用 C++ 命名空间的方式与我在 C#、Perl 等中的方式相同。它只是标准库内容、第三方内容和我自己的代码之间的符号语义分离。我会将自己的应用程序放在一个命名空间中,然后将可重用的库组件放在另一个命名空间中进行分离。
java 和 C++ 的另一个区别是,在 C++ 中,命名空间层次结构不需要对文件系统布局进行调整。因此,我倾向于将整个可重用库放在单个命名空间中,并将库中的子系统放在子目录中:
#include "lib/module1.h"
#include "lib/module2.h"
lib::class1 *v = new lib::class1();
只有当存在名称冲突的可能性时,我才会将子系统放在嵌套的命名空间中。
您还可以包含“using namespace ...”例如,在函数内部:
void test(const std::string& s) {
using namespace std;
cout << s;
}
我见过的更大的C++项目几乎没有使用多个命名空间(例如boost库)。
实际上,boost使用大量的命名空间,通常boost的每个部分都有自己的内部工作命名空间,然后可能只将公共接口放在顶级命名空间boost中。
我个人认为,代码库越大,命名空间就越重要,即使在单个应用程序(或库)中也是如此。在工作中,我们将应用程序的每个模块放在自己的命名空间中。
我经常使用的命名空间的另一个用途(没有双关语)是匿名命名空间:
namespace {
const int CONSTANT = 42;
}
这与以下基本相同:
static const int CONSTANT = 42;
但是,使用匿名命名空间(而不是静态命名空间)是仅在 C++ 中的当前编译单元中可见代码和数据的推荐方法。
评论
const int CONSTANT = 42;
不要听每个人都告诉你命名空间只是命名空间。
它们很重要,因为编译器认为它们应用接口原则。基本上,可以用一个例子来解释:
namespace ns {
class A
{
};
void print(A a)
{
}
}
如果要打印 A 对象,代码将是这样的:
ns::A a;
print(a);
请注意,在调用函数时,我们没有显式提及命名空间。这是接口原则:C++ 将一个类型作为参数的函数视为该类型接口的一部分,因此无需指定命名空间,因为该参数已经暗示了命名空间。
那么为什么这个原则很重要呢?想象一下,类 A 作者没有为这个类提供 print() 函数。您必须自己提供一个。由于你是一个优秀的程序员,你将在你自己的命名空间中定义这个函数,或者可能在全局命名空间中定义这个函数。
namespace ns {
class A
{
};
}
void print(A a)
{
}
您的代码可以在任何您想要的地方开始调用 print(a) 函数。现在想象一下,多年后,作者决定提供一个比你的更好的 print() 函数,因为他知道他的类的内部结构,并且可以制作出比你的更好的版本。
然后C++作者决定使用他的print()函数版本,而不是另一个命名空间中提供的版本,以尊重接口原则。并且 print() 函数的这种“升级”应该尽可能简单,这意味着您不必更改对 print() 函数的每次调用。这就是为什么可以在 C++ 中调用“接口函数”(与类位于同一命名空间中的函数)的原因。
这就是为什么在使用C++命名空间时,您应该将命名空间视为“接口”,并牢记接口原则。
如果你想更好地解释这种行为,你可以参考Herb Sutter的《异常C++》一书
评论
文森特·罗伯特(Vincent Robert)在他的评论中是对的:如何在C++中正确使用命名空间?
使用命名空间
命名空间至少用于帮助避免名称冲突。在 Java 中,这是通过“org.domain”习语强制执行的(因为假设一个人不会使用他/她自己的域名以外的任何东西)。
在 C++ 中,您可以为模块中的所有代码提供命名空间。例如,对于模块 MyModule.dll,可以为其代码指定命名空间 MyModule。我在其他地方看到有人使用 MyCompany::MyProject::MyModule。我想这有点矫枉过正,但总而言之,这对我来说似乎是正确的。
使用“使用”
应非常谨慎地使用,因为它可以有效地将一个(或所有)符号从命名空间导入到当前命名空间中。
在头文件中这样做是邪恶的,因为你的标头会污染包括它在内的每个源(这让我想起了宏......),甚至在源文件中,函数范围之外的样式也不好,因为它会在全局范围内导入命名空间中的符号。
使用“使用”的最安全方法是导入选择符号:
void doSomething()
{
using std::string ; // string is now "imported", at least,
// until the end of the function
string a("Hello World!") ;
std::cout << a << std::endl ;
}
void doSomethingElse()
{
using namespace std ; // everything from std is now "imported", at least,
// until the end of the function
string a("Hello World!") ;
cout << a << endl ;
}
您将在教程或示例代码中看到很多“using namespace std ;”。原因是减少符号的数量以使阅读更容易,而不是因为这是一个好主意。
斯科特·迈耶斯(Scott Meyers)不鼓励使用“使用命名空间std”(我不记得确切是哪本书,但如果有必要,我可以找到它)。
命名空间组成
命名空间不仅仅是包。另一个例子可以在Bjarne Stroustrup的“C++编程语言”中找到。
在“特别版”中,在 8.2.8 命名空间组合中,他描述了如何将两个命名空间 AAA 和 BBB 合并到另一个名为 CCC 的命名空间中。因此,CCC 成为 AAA 和 BBB 的别名:
namespace AAA
{
void doSomething() ;
}
namespace BBB
{
void doSomethingElse() ;
}
namespace CCC
{
using namespace AAA ;
using namespace BBB ;
}
void doSomethingAgain()
{
CCC::doSomething() ;
CCC::doSomethingElse() ;
}
您甚至可以从不同的命名空间导入选择符号,以构建自己的自定义命名空间接口。我还没有找到它的实际用途,但从理论上讲,它很酷。
评论
我在其他答案中没有看到任何提及它,所以这是我的 2 加分:
在“使用命名空间”主题中,一个有用的语句是命名空间别名,它允许您“重命名”命名空间,通常为其提供更短的名称。例如,代替:
Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::TheClassName foo;
Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::AnotherClassName bar;
你可以写:
namespace Shorter = Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally;
Shorter::TheClassName foo;
Shorter::AnotherClassName bar;
标准 :: cout
这 prefix std:: 表示 名称 Cout 和 endl 是 在命名空间中定义 命名为 std。命名空间允许 我们避免无意中的碰撞 在我们定义的名称之间 以及这些相同名称的使用 在图书馆内。所有名称 由标准库定义 位于 stdnamespace 中。写作 std:: cout 使用 scope 运算符 (::运算符)说我们 想要使用名称 Cout 在 命名空间 std。 将显示一种更简单的方法 访问库中的名称。
评论