🍅命名空间
2022-6-25
| 2023-8-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property

 
大的项目中通常需要用到第三方库,这些库通常都会定义一个名称空间,没有定义名称空间而直接使用的名字会带来名称空间污染,C 语言通过将库的名字作为函数等全局名字的前缀来避免此问题。C++则提供了名称空间,名称空间可以更有效的更好的管理名字。名称空间将全局名称空间进行切分,每个名称空间就一个作用域。
 

名称空间定义

名称空间中包含一系列的声明和定义,这些声明和定义必须是可以出现在全局作用域中的:类、变量(以及初始值)、函数以及其定义、模板和其它名称空间。
与任何名字一样,名称空间的名字必须在其作用域中是唯一的。名称空间可以定义在全局作用域中或者在别的名称空间中,但不能在函数或者类中。命名空间定义时最后的分号可有可无。
 
每个名称空间是一个作用域
每个名称空间中的每个名字都必须指向独一无二的实体,在不同的名称空间中的相同名字是不同的实体。同一个名称空间中的名字可以被直接访问(包括嵌套的作用域)。而在名称空间之外则需要用完全限定名来进行访问。
 
名称空间不需要连续
名称空间可以分离定义,在不同的文件或者在同一个文件的不同地方:
要么定义一个新的名称空间nsp ,要么给已经存在的名称空间增加内容。如果nsp之前没有定义过,那么就是创建一个新的名称空间。否则就将定义增加到已经存在的名称空间中去。
名称空间的这种用法主要用来适配类定义以及函数的定义。名称空间中定义类和声明函数是接口的一部分,将被放在头文件中。而名称空间的成员实现则被放在源文件中。通过将接口和实现分离,可以保证名称空间中的名字只被定义以此,但是可以被声明多次。 如果一个名称空间中包含了多个不相关的类型,应该使用分离的文件来书写这些名称空间和每个类型。
需要注意的是,#include 必须出现在所有的名称空间之前,否则就是将所有被包含的文件中的名字在我们的名称空间中再次定义一次。
 
定义名称空间成员
在同一个名称空间中的成员之间相互通过非限定名称进行引用,也可以在名称空间外面定义成员,定义需要指定名字是属于哪个名称空间的。
这个定义的声明必须存在于对应的名称空间中,与定义类的成员函数一样,函数体是在名称空间中的,所以可以不加限定地使用名称空间中的名字。
模板特例
模板特例需要放在与原始模板相同的作用域中,与别的名字一样,模板特例可以在作用域中声明,然后在外面进行定义。
全局名称空间
在全局作用域中定义的名字被放在全局名称空间(global namespace)中,全局名称空间是隐式定义的,并且存在于每一个程序中。每个文件中定义在全局作用域中的名字都被放到了全局名称空间中。
引用全局名称空间中的名字需要使用 ::member_name 的方式。
嵌套的名称空间
定义在别的名称空间中的名称空间就是嵌套名称空间。嵌套名称空间是一个嵌套作用域,其作用域被嵌套在另外一个名称空间中。规则与嵌套作用域一样,定义在内部名称空间中的名字会隐藏外部名称空间中的名字,外部名称空间想要访问嵌套名称空间中的名字必须通过完全限定名称进行访问。
 
inline名称空间
新标准中引入了一种新的嵌套名称空间,inline namespace。与常规的嵌套名称空间不同的是,inline名称空间中的名字可以被其直接包含的名称空间直接,而不需要加以限定:
inline名称空间多用于从应用的一个版本迁移到另一个版本。
 
unnamed名称空间
匿名名称空间(unnamed namespace)由关键字 namespace 后直接跟名称空间定义,在匿名名称空间中定义的名字是 static 的,当第一次使用时存在,并且在程序结束时销毁。
匿名名称空间不会跨越多个文件,每个文件有自己的匿名名称空间,它们可以定义同名的名字但是不是同一个实体。不要在头文件中定义匿名名称空间。
匿名名称空间中的名字可以直接被使用,它们的作用域在其直接外部名称空间,所以需要其中的名字没有冲突。如:
C++ 语言试图用匿名名称空间来替换 static 变量声明。
 

命名空间的使用

通过 using 声明(using declarations)、名称空间别名(namespace aliases)和 using 指令(using directives)来简化名称空间的使用。
 
名称空间别名
名称空间别名将一个较短的名字作为名称空间名字的别名:
名称空间别名可以表示一个嵌套的名称空间:
 
using声明与using指令
  • using声明在一次引入一个名称空间的成员。由using声明引入的名字遵循常规的作用域规则:从引入的地方可见直到作用域的结尾处结束。外部作用域中的相同名字被隐藏。在内部将其嵌套的作用域中可以不加限定的访问该名字,出了作用域就需要使用完全限定名字。
    • using声明可以出现在全局、局部、名称空间和类作用域中。如果类作用域中则 using 声明只能针对基类成员。
  • using指示可以让名称空间中的所有名字都不加限定的进行访问。using指令的形式是using namespace NAMESPACE, using指令只能出现在全局、局部和名称空间作用域中,不能出现在类作用域中。
避免使用using指示,原因:因为只要一条using指示,命名空间内的所有成员都变得可见,这会与其作用域的其它名称产生冲突,造成很严重的污染问题。并且会产生很多的二义性。建议,尽量使用using声明。
using指令将所有的名字提升到最近的名称空间中,这个名称空间同时包含using指令后的名称空间以及using指令本身所在的名称空间,以下就是错误的:
以允许blip中的名字与全局名称空间中的名字一样,但是如果想要引用这些名字的话就需要明确限定说明使用的是哪个名字,否则就会产生名字二义性。
 
头文件和using声明或using指令
头文件中至应该包含接口部分的名字,不应该包含任何实现部分的名字。因而,头文件不应该在函数或者名称空间外使用using声明或using指令。应该尽可能少的使用using指令,而在需要的时候使用using声明。
 

类、名称空间和作用域

名称空间中的名字查找一样是从内部作用域往外面不停查找,并且只查找外部作用域在前面声明的名字。名称空间中的类的成员函数中的名字先从成员函数中查找,再从类中查找,然后从所在的外围作用域中查找,最后才是从定义所在的地方进行查找。如:
这里需要注意的是 A::h 是在 f2 后定义的,所有无法通过编译,而f3是在A::h 后定义的,所以可以访问。
 
由实参决定的查找(Argument-Dependent Lookup)和类类型参数
当传递一个类类型对象给函数时,编译器将在正常的作用域查找之外从实参的类定义的名称空间中查找。这个规则将会运用于类类型的引用和指针实参。如:std::string s; std::cin >> s; 在调用 >> 就不需要指定 std 名称空间,将会自动从 cin 或者 s 的名称空间从查找函数。
这个规则的意义在于不需要为概念上是类的接口,但不是类的成员函数,在使用时不需要单独的 using 声明。
 
查找 std::move 和 std::forward
由于 std::move 和 std::forward 的参数是右值引用,所以是可以匹配任何参数的。这样如何应用程序定义了别的 move 的话就会产生名字冲突(name collision)。所以在使用时尽可能地使用 std 进行限定。
 
友元声明和由实参决定的查找
如果一个未声明的类或函数第一次出现在友元声明中将被认为是定义在最接近的外围名称空间中,这与由实参决定的名称查找会产生意想不到的结果。如:
通过由实参决定的名称查找可以调用 f ,如:
由于 f 的参数是类类型,而 f 是隐式声明在名称空间 A 中,所以 f 将被找到并被被调用。
 

重载和名称空间

由实参决定的名称查找和重载
带有类类型实参的函数查找函数名字时同时将在每个实参所在类及其基类的名称空间中查找此函数名字。这个规则同时会影响重载候选集。每个实参定义所在的名称空间都会被查找,所有这里面的同名函数都会被添加到候选集中。即便是这些函数在调用点看不到也会被添加到候选集中。如:
以上调用能够通过的原因是displayBulk_item的基类所在的名称空间中进行查找同名函数。
 
重载和using声明
using声明导入的是整个名字,而不是特定的函数,该函数的所有版本都被引入到当前作用域中。using声明引入的函数可以对当前作用域中的同名函数进重载,如果using声明的一个函数与作用域的一个函数同名且参数列表相同,将发生错误。
 
 
重载和using指示
using指示将空间内的所有函数都加载到重载集合中。如果名称空间中的成员与当前作用域中的名字同名,名称空间中的名字被添加到重载集合中。
using声明不同的是:using指示引入一个与作用域内函数名且参数列表相同的函数不会发生错误,只要在调用时指定希望调用名称空间中的,还是当前作用域中的。
 
在多个 using 指令之间重载
如果一次性出现多个using指令,那么每个名称空间中的名字都会变成候选集中的一员。
 
 
 
  • C++
  • 随机数不可移植的特性
    目录