c++名称空间

在 c++ 中,名称可以是变量、函数、结构、枚举、类以及类和结构的成员

假设这样一种情况,当一个班上有两个名叫 WYB 的学生时,为了明确区分它们,我们在使用名字之外,不得不使用一些额外的信息,比如他们的家庭住址,或者他们的其他身份等等。

同样,当随着项目的增大,名称相互冲突的可能性也将增加。使用多个厂商的类库是,可能会导致命名冲突,这样,编译器就无法判断用户想要使用哪个。

例如:两个库可能都定义了名为 HASH 和 TREE 的类,但定义的方式不兼容,用户可能希望使用一个库的 HASH 类,而使用另一个库的 TREE 类。这种冲突被称为命名冲突问题。

因此,引入了命名空间(namespace)这个概念,专门用于解决上面的问题,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。

A 有关概念

声明区域:可以在其中进行声明的区域。例如在函数外声明的变量,是全局变量,其声明区域为其声明所在的文件。

潜在作用域:从声明的位置开始,到其声明区域的结尾。

 

 

 

作用域:变量对于程序而言可见(可以调用)的范围。

变量并非在其潜在作用域内的任何位置都是可见的,当变量间出现重名的情况下,作用域小的会屏蔽作用域大的。例如:

#include <iostream>
using namespace std;

// 全局变量声明
int g = 20;
int main ()
{
    // 局部变量声明
    int g = 10;
    cout << g;
    return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

 

B 定义命名空间

命名空间的定义使用关键字 namespace,后跟命名空间的名称,基本格式如下:

namespace namespace_name{
     //代码声明
}

例如:

#include<bits/stdc++.h>
using namespace std;

namespace WYB{
    double pail;   //变量声明
    void fetch(int,int);  //函数原型
    int girlfriend;//变量声明
    struct parents{//结构声明
        //...
    };
     void print(){
         printf("Inside namespace_WYB\n");
     } 
}

namespace SEN{ 
    void pail(int,double);//函数原型 
    double fetch;         //变量声明
    int boyfriend;        //变量声明 
    void print(){
        printf("Inside namespace_SEN\n");
    }
}
int main(){
    WYB::print();
    SEN::print();
    return 0;
}

运行,得到:

上述代码说明了两个问题:

  1. 任何名称空间中的名称都不会与其他名称空间中的名称发生冲突。即,WYB 中的 fetch函数 可以与 SEN 中的 fetch 变量共存。名称空间中的声明和定义规则同全局声明和定义规则相同。
  2. 可以通过作用域解析符 :: 来使用名称空间中的名称

另外,名称空间是开放的,即可以吧名称加入到已有的名称空间中。例如,下列语句将名称 waste 添加到 SEN 已有的名称列表中:

namespace SEN{
    char * waste(const char *);
}

同样,原来的 WYB 名称空间为 fecth() 函数提供了原型,可以在该文件后面(或另外一个文件中)再次使用 WYB 名称空间来提供该函数原型的函数体:

namespace WYB{
    void fetch(int a,int b){
        if(b==0){
              throw "Division by zero condition!";
        }
    }
} 

Tips:

未限定名称(unqualified name):未被装饰的名称,如 pail

限定的名称(qualified name):包含名称空间的名称,如 WYB:pail

 

C using声明和编译指令

真是麻烦!每次使用名称的时候都要对它进行限定

平时没有自己定义名称空间的时候为什么不用加 :: 呢?

原因是这样一条语句:

using namespace std;

(std 即 c++ 标准库)

我们并不希望每次使用名称时都对它进行限定,因此C++提供了两种机制(using声明和using编译指令)来简化对名称空间中名称的使用。using声明使特定的标识符可用,而using编译指令使整个名称空间可用。

举例:

int main(){
using
namespace SEN; print(); using WYB::print;
print();
}

得到:

值得注意的是:(下列语句仍在main()中)

using WYB::print; 
print();
using namespace SEN; print();

其中using namespace 是using编译指令

using ... 是using声明

对于using声明

将特定的名称添加到它所属的声明区域中,例如main()中的using声明 WYB::print将print添加到main()定义的声明区域中。完成该声明后,便可以使用名称print代替WYB::print。

char fetch;
int
main(){ using SEN::fetch;//put fetch into local namespace
   double fetch;//Error!Already have a local fetch
cin>>fetch;//read a value into SEN::fetch cin>>::fetch;//read a value into global fetch return 0; }

上面的代码说明,由于using声明将名称添加到局部声明区域中,因此这个示例避免了将另一个局部变量也命名为fetch。另外,和其他局部变量一样,fetch也将覆盖同名的全局变量。

在函数的外面使用using声明时,将把名称添加到全局名称空间中。

对于using编译指令

using编译指令由名称空间名和它前面的关键字using namespace组成,它使该名称空间中的所有名称都可用,而不需要域名解析符,我们经常将这种格式用于名称空间std

Tips:有关using编译指令和using声明,需要记住的一点是,他们增加了名称冲突的可能性,但使用作用域解析符却不会出现这样的二义性问题,编译器将不会允许这样的情况。

二者之比较

使用 using 编译指令导入一个名称空间中所有的名称与使用多个using 声明是不一样的,而更像是大量
使用作用域解析运算符。

使用 using 声明时,就好像声明了相应的名称一样。

如果某个名称已经在函数中声明了,则不能用using声明导入相同的名称。然而,使用using编译指令时,将进行名称解析,就像在包含 using 声明和名称空间本身的最小声明区域中声明了名称一样。

在下面的示例中,名称空间为全局的。

如果使用 using 编译指令导入一个已经在函数中声明的名称,则局部名称将隐藏名称空间名,就像隐藏同
名的全局变量一样。不过仍可以像下面的示例中那样使用作用域解析运算符:

namespace Jill{
    double bucket(double n){...}
    double fetch;
    struct Hill{...};
} 
char fetch;//global namespace
int main(){
    using namespace Jill;  //import all namespaces names
    Hill Thrill;           //create a type Jill::Hill structure
    double water=bucket(2);//use Jill::bucket();
    double fetch;          //not an error;hides Jill::fetch
    cin>>fetch;            //read a value into the local fetch
    cin>>::fetch;          //read a value into global fetch
    cin>>Jill::fetch;      //read a value into Jill::fetch
}
int foom(){
    Hill top;        //Error
    Jill::Hill crest;//vaild
}

在main( )中,名称 Jill::fetch 被放在局部名称空间中,但其作用域不是局部的,因此不会覆盖全局的
fetch.

然而,局部声明的fetch将隐藏Jill:fetch和全局fetch。.

然而,如果使用作用域解析运算符,则后两个fetch变量都是可用的。

需要指出的另一点是,虽然函数中的 using 编译指令将名称空间的名称视为在函数之外声明的,但它

不会使得该文件中的其他函数能够使用这些名称。因此,foom()函数不能使用未限定的标识符Hill

Tips:假设名称空间和声明区域定义了相同的名称。如果试图使用 using声明将名称空间的名称导入该声明区域,则这两个名称会发生冲突,从而出错。

如果使用 using 编译指令将该名称空间的名称导入该声明区域,则局部版本将隐藏名称空间版本。

一般说来,使用using声明比使用using编译指令更安全,这是由于它只导入指定的名称。如果该名称
与局部名称发生冲突,编译器将发出指示。using编译指令导入所有名称,包括可能并不需要的名称。如果
与局部名称发生冲突,则局部名称将覆盖名称空间版本,而编译器并不会发出警告。另外,名称空间的开
放性意味着名称空间的名称可能分散在多个地方,这使得难以准确知道添加了哪些名称。

 

D 名称空间嵌套

命名空间可以嵌套,您可以在一个命名空间中定义另一个命名空间,如下所示:

namespace namespace_name1 {
   // 代码声明
   namespace namespace_name2 {
      // 代码声明
   }
}

可以通过使用 :: 运算符来访问嵌套的命名空间中的成员:

// 访问 namespace_name2 中的成员
using namespace namespace_name1::namespace_name2;
 
// 访问 namespace:name1 中的成员
using namespace namespace_name1;

在上面的语句中,如果使用的是 namespace_name1,那么在该范围内 namespace_name2 中的元素也是可用的,如下所示:

#include <iostream>
using namespace std;
 
// 第一个命名空间
namespace first_space{
   void func(){
      cout << "Inside first_space" << endl;
   }
   // 第二个命名空间
   namespace second_space{
      void func(){
         cout << "Inside second_space" << endl;
      }
   }
}
using namespace first_space::second_space;
int main ()
{
 
   // 调用第二个命名空间中的函数
   func();
   
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

 

E 引用

《C++ Primer Plus》 by Stephen Prata

C++ 命名空间 by 菜鸟教程

 

posted @ 2020-12-06 13:31  _Famiglistimo  阅读(184)  评论(0编辑  收藏  举报