C++学习笔记命名冲突和命名空间介绍

C++命名冲突和命名空间介绍

假设您是第一次开车去朋友家,给您的地址是 Mill City 的 245 Front Street。到达米尔城后,您拿出地图,却发现米尔城实际上有两条不同的前街,彼此隔着城镇!你会去哪一个?除非有一些额外的线索可以帮助您做出决定(例如,您记得他的房子在河边),否则您必须打电话给您的朋友并询问更多信息。因为这会令人困惑且效率低下(尤其是对于您的邮递员),所以在大多数国家/地区,城市内的所有街道名称和房屋地址都必须是唯一的。

类似地,C++ 要求所有标识符都没有歧义。如果两个相同的标识符以编译器或链接器无法区分的方式引入同一个程序,编译器或链接器将产生错误。此错误通常称为命名冲突(或命名冲突)。

命名冲突的一个例子

a.cpp:

#include <iostream>

void myFcn(int x)
{
    std::cout << x;
}

主.cpp:

#include <iostream>

void myFcn(int x)
{
    std::cout << 4 * x;
}

int main()
{
    return 0;
}

编译器在编译这个程序时,会独立编译a.cppmain.cpp,每个文件都编译没有问题。

但是,当链接器执行时,它会将a.cppmain.cpp 中的所有定义链接在一起,并发现函数myFcn 的冲突定义。然后链接器将因错误而中止。请注意,即使从未调用myFcn也会发生此错误!

大多数命名冲突发生在两种情况下:

  1. 一个函数(或全局变量)的两个(或多个)定义被引入到编译成同一个程序的单独文件中。这将导致链接器错误,如上所示。
  2. 一个函数(或全局变量)的两个(或多个)定义被引入到同一个文件中(通常通过 #include)。这将导致编译器错误。

随着程序变大并使用更多标识符,引入命名冲突的几率显着增加。好消息是 C++ 提供了大量的机制来避免命名冲突。局部作用域,防止在函数内部定义的局部变量相互冲突,就是这样一种机制。但是局部作用域不适用于函数名。那么我们如何避免函数名相互冲突呢?

什么是命名空间?

暂时回到我们的地址类比,拥有两条前街只是有问题,因为这些街道存在于同一个城市内。另一方面,如果您必须将邮件投递到两个地址,一个地址是 Mill City 的 Front Street 209,另一个地址是 Jonesville 的 Front Street 417,那么您将不会混淆该去哪里。换句话说,城市提供了分组,使我们能够消除可能相互冲突的地址的歧义。在这个类比中,命名空间就像城市一样。

一个命名空间是允许您为消除歧义的目的里面的声明它名字的区域。命名空间为其内部声明的名称提供了一个范围(称为命名空间范围)——这只是意味着在命名空间内声明的任何名称都不会被误认为是其他范围中的相同名称。

在命名空间中声明的名称不会被误认为是在另一个作用域中声明的相同名称。

在命名空间内,所有名称必须是唯一的,否则会导致命名冲突。

命名空间通常用于对大型项目中的相关标识符进行分组,以帮助确保它们不会无意中与其他标识符发生冲突。例如,如果您将所有数学函数放在名为math的命名空间中,那么您的数学函数将不会与math命名空间之外的同名函数发生冲突。

我们将在以后讨论如何创建自己的命名空间。

全局命名空间

在 C++ 中,任何未在类、函数或命名空间中定义的名称都被视为隐式定义的命名空间的一部分,称为全局命名空间(有时也称为全局作用域)。

在课程顶部的示例中,函数 main() 和 myFcn() 的两个版本都在全局命名空间中定义。示例中遇到的命名冲突是因为 myFcn() 的两个版本最终都在全局命名空间内,这违反了命名空间中所有名称必须唯一的规则。

标准命名空间

在最初设计 C++ 时,C++ 标准库中的所有标识符(包括 std::cin 和 std::cout)都可以在没有std:: 的情况下使用前缀(它们是全局命名空间的一部分)。但是,这意味着标准库中的任何标识符都可能与您为自己的标识符选择的任何名称(也在全局命名空间中定义)发生潜在冲突。当您#included 标准库中的新文件时,正在运行的代码可能会突然发生命名冲突。或者更糟的是,在一个 C++ 版本下编译的程序可能无法在 C++ 的未来版本下编译,因为引入标准库的新标识符可能与已经编写的代码存在命名冲突。因此,C++ 将标准库中的所有功能移动到名为“std”(standard 的缩写)的命名空间中。

事实证明,std::cout的名字并不是真正的std::cout。它实际上只是cout,而std是标识符cout所属的命名空间的名称。因为cout是在std命名空间中定义的,所以名称cout不会与我们在全局命名空间中创建的任何名为cout 的对象或函数冲突。

类似地,当访问在命名空间(例如std::cout)中定义的标识符时,您需要告诉编译器我们正在寻找在命名空间(std)中定义的标识符。

当您使用在命名空间(例如std命名空间)内定义的标识符时,您必须告诉编译器该标识符位于命名空间内。

有几种不同的方法可以做到这一点。

显式命名空间限定符 std::

最直接的方式告诉我们要使用的编译器COUTSTD命名空间是通过显式使用*的std ::*前缀。例如:

#include <iostream>

int main()
{
    std::cout << "Hello world!"; // when we say cout, we mean the cout defined in the std namespace
    return 0;
}

:: 符号是一个称为作用域解析运算符的运算符。:: 符号左侧的标识符标识了 :: 符号右侧的名称所在的命名空间。如果没有提供 :: 符号左侧的标识符,则假定为全局命名空间。

因此,当我们说std::cout 时,我们说的是“存在于命名空间std 中cout ”。

这是使用cout的最安全的方法,因为对于我们引用的coutstd命名空间中的那个*cout)*没有歧义。

最佳实践

使用显式命名空间前缀访问命名空间中定义的标识符。

使用命名空间 std(以及为什么要避免它)

另一种访问命名空间内标识符的方法是使用using 指令语句。这是我们带有using 指令的原始“Hello world”程序:

#include <iostream>

using namespace std; // this is a using directive telling the compiler to check the std namespace when resolving identifiers with no prefix

int main()
{
    cout << "Hello world!"; // cout has no prefix, so the compiler will check to see if cout is defined locally or in namespace std
    return 0;
}

一个使用指令告诉编译器试图解决一个没有命名空间前缀的标识符时检查指定的命名空间。所以在上面的例子中,当编译器去确定标识符cout是什么时,它会检查本地(它未定义的地方)和std命名空间(它将与std::cout匹配的地方)。

许多文本、教程,甚至一些编译器都推荐或使用程序顶部的using 指令。但是,以这种方式使用,这是一种不好的做法,并且非常不鼓励。

考虑以下程序:

#include <iostream> // imports the declaration of std::cout

using namespace std; // makes std::cout accessible as "cout"

int cout() // declares our own "cout" function
{
    return 5;
}

int main()
{
    cout << "Hello, world!"; // Compile error!  Which cout do we want here?  The one in the std namespace or the one we defined above?

    return 0;
}

上面的程序无法编译,因为编译器现在无法判断我们是想要我们定义的cout函数,还是在std命名空间内定义的cout

当以这种方式使用 using 指令时,我们定义的任何标识符都可能与std命名空间中的任何同名标识符冲突。更糟糕的是,虽然今天的标识符名称可能不会发生冲突,但它可能会与未来语言修订版中添加到 std 命名空间的新标识符发生冲突。这就是首先将标准库中的所有标识符移动到std命名空间的重点!

警告

避免在程序顶部或头文件中使用指令(例如using namespace std;)。它们违反了首先添加命名空间的原因。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值