c/c++命名空间

C++名字空间/C++命名空间

 0、序言

名字空间是C++提供的一种解决符号名字冲突的方法。

一个命令空间是一个作用域,在不同名字空间中命名相同的符号代表不同的实体。

通常,利用定义名字空间的办法,可以使模块划分更加方便,减少模块间的相互影响。

1、名字空间的成员

定义在名字空间中的实体称为名字空间的成员。

名字空间内的名字可以被该名字空间内的其他成员直接访问,名字空间外的代码必须指定该名字位于哪个名字空间。

一个名字空间可以包含多种类型的标识符,如下面所列:

变量、常量、函数、结构体/联合体/枚举、类、嵌套名字空间

名字空间成员的引用方法如下: 

namespace_name::member_name

  

2、定义名字空间

(1)、一个名字空间可以在两个地方被定义:在全局范围层次或者是在另一个名字空间中被定义(这样就形成一个嵌套名字空间),不能在函数和类的内部定义。

(2)、名字空间可以是不连续的,他是由所有分离定义的部分的总体构成的。一个名字空间可以分散多个文件中,不同的文件中名字空间的定义也是累积的。

     通常将名字空间的声明放到头文件中,实现放到源文件中。可以将不相关的成员放到不同的头文件中。

 (3)、命令空间的作用域不能以分号结尾。

3、嵌套名字空间(Nested Namespce)

3.1、普通嵌套名字空间(ordinary nested namespace)

 一个嵌套名字空间就是一个嵌套作用域,其作用域嵌套在包含他的名字空间中。

在外部引用嵌套空间中的成员时,使用下面的形式 

包含嵌套空间的名字空间的名字::嵌套空间的名字::嵌套空间的成员

 下面举例说明嵌套名字空间定义和使用 

复制代码

#include <iostream>

namespace MyOutNames
{
    int iVal1 = 100;
    int iVal2 = 200;

    namespace MyInnerNames //定义嵌套名字空间
    {
        int iVal3 = 300;
        int iVal4 = 400;
    }
}

int main(void)
{
    std::cout<<MyOutNames::iVal1<<std::endl;
    std::cout<<MyOutNames::iVal2<<std::endl;
    std::cout<<MyOutNames::MyInnerNames::iVal3<<std::endl;  //使用嵌套名字空间成员
    std::cout<<MyOutNames::MyInnerNames::iVal4<<std::endl;  //使用嵌套名字空间成员

    return 0;
} 

复制代码

3.2、内联嵌套名字空间(Inline Namespace C++11)

C++11中,新增inline namespace,指示命名空间中的名称同时是外层命名空间直接包含的名称。这便于命名空间的版本管理,减少冲突。

inline描述符使得内联命名空间中的声明看起来就好像是直接在外围的命名空间中进行声明的一样。(使用inline关键字定义的内联名字空间成为默认名字空间。)
inline描述符由命名空间的设计者放置,即命名空间的作者可以通过放置inline描述符来表示当前最新的命名空间是哪个.

复制代码

// file V98.hpp:
namespace V98
{
    void f(int);    // does something
    // ...
}

// file V99.hpp:
inline namespace V99
{
    void f(int);    // does something better than the V98 version
    void f(double); // new feature
    // ...
}

// file Mine.hpp:
namespace Mine
{
#include "V99.hpp"
#include "V98.hpp"
}

//file example.cpp
#include "Mine.hpp"
using namespace Mine;
// ...
V98::f(1);  // old version
V99::f(1);  // new version
f(1);        //default version

复制代码

4、全局名字空间(Global Namespce) 

定义在全局作用域的名字(在任意类、函数或命名空间外部声明的名字)是定义在全局命名空间中的。
全局命名空间是隐式声明的,存在于每个程序中。在全局作用域定义实体的每个文件将那些名字加到全局命名空间。
可以用作用域操作符引用全局命名空间的成员。因为全局命名空间是隐含的,它没有名字,
所以使用记号如下方法引用全局命名空间的成员。

::member_name

   

5、匿名名字空间(Unnamed Namespace)

 命名空间可以是未命名的,未命名的命名空间在定义时没有给定名字。其定义方法如下: 

namespace     //No name
{
    //members....
}

未命名的命名空间与其他命名空间不同,未命名的命名空间的定义局部于特定文件,从不跨越多个文本文件。
未命名的命名空间可以在给定文件中不连续,但不能跨越文件,每个文件有自己的未命名的命名空间。
未命名的命名空间用于声明局部于文件的实体。在未命名的命名空间中定义的变量在程序开始时创建,在程序结束之前一直存在。
未命名的命名空间中定义的名字可直接使用,因为没有命名空间名字来限定它们。

复制代码

#include <iostream>

namespace //unnamed namespace
{
    int count = 1;
}
using namespace std;

namespace //unnamed namespace
{
    void name_printf(void)
    {
        cout << "count = " << count << endl;
    }
}

int main(void)
{
    count = 3;     //直接使用
    name_printf(); //直接使用

    return 0;
}

复制代码

 未命名的命名空间中定义的名字只在包含该命名空间的文件中可见。

如果另一文件包含一个未命名的命名空间,两个命名空间不相关,可以定义相同的名字,而这些定义将引用不同的实体。
未命名的命名空间中成员的名字不能与全局作用域中定义的名字相同。例子如下,函数也是同样的道理。

复制代码

int i;  // global variable

namespace //unnamed namespace
{
    int i;
}

// error: reference to ‘i’ is ambiguous

复制代码

 像其他命名空间一样,未命名的命名空间也可以嵌套在另一命名空间内部。

如果未命名的命名空间是嵌套的,其中的名字按常规方法使用外围命名空间名字访问: 

复制代码

int i; //Global Variable

namespace local 
{
    namespace //unnamed namespace
    {
        int i; // ok: i defined in a nested unnamed namespace is distinct from global i
    }
}

local::i = 42;        

复制代码

 如果头文件定义了未命名的命名空间,那么,在每个包含该头文件的文件中,该命名空间中的名字将定义不同的局部实体。

未命名的命名空间取代文件中的静态声明

在标准 C++ 中引入命名空间之前,程序必须将名字声明为static,使它们的作用域约束于一个文件中。
文件中静态声明的方法是从 C 语言继承而来, C++ 不赞成文件静态声明,因为这可能在未来版本中不支持。

所以应该避免文件静态而使用未命名空间代替。

6、名字空间的别名

 可以给名字空间起一个别名,别名是已定义的名字空间的可替换的名称。

一个命名空间可以有许多别名,所有别名以及原来的命名空间名字都可以互换使用。 

通过下面的形式将别名指定给已定义的名字空间的名字,就可以创建一个名字空间的别名。

namespace 别名 = 已定义的名字空间名字;

 下面举例说明名字空间别名的定义和使用 

复制代码

#include <iostream>

namespace MyNames
{
    int iVal1 = 100;
    int iVal2 = 200;
}

namespace MyAlias = MyNames;  //别名定义

int main(void)
{
    std::cout<<MyAlias::iVal1<<std::endl; //别名使用
    std::cout<<MyAlias::iVal2<<std::endl; //别名使用

    return 0;
}

复制代码

7、using声明 和 using指示 

使用using声明 和 using指示 的好处就是可以使用使用名字空间中成员时,不必加上名字空间的作用域。

using std::cout; //using声明

using namespace std; //using指示

7.1、using声明(using declaration)

一个 using 声明一次只引入一个命名空间成员。

using 声明的作用域从 using 声明点开始,直到包含 using 声明的作用域的末尾,名字都是可见的。外部作用域中定义的同名实体被屏蔽。

using 声明可以出现在全局、局部、类的作用域 和 名字空间中。在类作用域中using声明只能引用基类成员。

复制代码

//using declaration in Global Scope
#include <iostream>
using std::cout;              //using声明 
using std::endl;              //using声明

int main(void)
{
  cout<<"Hello World"<<endl;
  return 0;
}

复制代码

复制代码

//using declaration in Local Scope
#include <iostream>

void func(void)
{
    using std::cout;
    using std::endl;

    cout << "Using Declarations In Function"<<endl;
}

int main()
{
    func();
//  cout << "Hello" <<endl; //error: ‘cout’ and ‘endl’ were not declared in this scope
    return 0;
}

复制代码

复制代码

//using declaration in Class Scope
#include <stdio.h>

class B
{
public:
   void f(void) {printf("In B::f()\n");}
   void g(void) {printf("In B::g()\n");}
};

class C
{
public:
   void g() {printf("In C::g()\n");};
};

class D : public B
{
public:
   using B::f;      // OK: B is a base of D2
   using B::g;      // OK: B is a base of D2
// using C::g;      // error: C isn't a base of D2
};

int main(void)
{
   D MyD;
   MyD.f();
   MyD.g();
}

复制代码

复制代码

//using declaration in Namespce
#include <iostream>

namespace MyNames
{
    using std::string;
    using std::cout;
    using std::endl;

    string str;

    void func(void){cout << "Hello"<<endl;}
}

int main(void)
{
    MyNames::func();
    return 0;
}

复制代码

7.2、using指示(using directive)

using 指示使得整个名字空间中的成员都可见。

using 指示可以出现在全局、局部的作用域 和 名字空间中,不会出现在类的作用域中。

复制代码

//using directive in Global Scope
#include <iostream>
using namespace std;            //using指示

int main(void)
{
  cout<<"Hello World"<<endl;
  return 0;
}

复制代码

复制代码

//using directive in Local Scope
#include <iostream>

void func(void)
{
    using namespace std;
    cout << "Using Declarations In Function"<<endl;
}

int main()
{
    func();
//  cout << "Hello" <<endl; //error: ‘cout’ and ‘endl’ were not declared in this scope
    return 0;
}

复制代码

复制代码

//using declaration in Namespce
#include <iostream>

namespace MyNames
{
    using namespace std;

    string str;
    void func(void){cout << "Hello"<<endl;}
}

int main(void)
{
    MyNames::func();
//    cout<<"Hello"<<endl; //error: ‘cout’ and ‘endl’ were not declared in this scope
    return 0;
}

复制代码

  

7.3、避免使用using指示

using 指示注入来自一个命名空间的所有名字,这个方法看似简单,但是如果应用程序使用许多库,并且用 using 指示使得这些库中的名字可见,那么,全局命名空间污染问题就重新出现。

相对于依赖于 using 指示,对程序中使用的每个命名空间名字使用using 声明更好,这样做减少注入到命名空间中的名字数目,由 using 声明引起的二义性错误容易发现和修正。

8、综合应用举例

复制代码

////file : mynames.hpp
#ifndef MYNAMES__HPP_
#define MYNAMES__HPP_

namespace MyNames
{
    //Member:Variable
    extern int iVal;

    //Member:Class
    class MyString
    {
    public:
        MyString(const std::string&);
        void OutputString(void);
    private:
        std::string str;
    };

    //Member:Function
    void NormalFunc(void);

    //Member:Struct
    struct st_Names
    {
        char ch;
        int count;
    };

    //Member:Union
    union un_Names
    {
        char ch;
        int count;
    };

    //Member:enum
    enum en_Names
    {
        ZERO,
        ONE,
        TWO
    };
}

#endif /* MYNAMES__HPP_ */

//------------------------------------------------------------------------------------------------------------

//file : mynames.cpp
#include <iostream>
#include "mynames.hpp"

namespace MyNames
{
    int iVal = 100;

    MyString::MyString(const std::string& refstr)
    {
        str = refstr;
    }

    void MyString::OutputString(void)
    {
        std::cout << str << std::endl;
    }

    void NormalFunc(void)
    {
        std::cout << "NormalFunc" << std::endl;
    }
}

//-------------------------------------------------------------------------------------------------------------

//file : example.cpp
#include <iostream>
#include "mynames.hpp"

namespace Name = MyNames;

using namespace Name;

int main(void)
{

    std::cout<<iVal<<std::endl;
    std::cout<<Name::iVal<<std::endl;
    std::cout<<MyNames::iVal<<std::endl;

    MyNames::MyString mystr("Hello");
    mystr.OutputString();
    MyNames::NormalFunc();

    MyNames::st_Names myst;
    myst.count = 0;

    MyNames::en_Names myen;
    myen = MyNames::ZERO;

    MyNames::un_Names myun;
    myun.count = 1;

    return 0;
}

一、 为什么需要命名空间(问题提出)

命名空间是ANSIC++引入的可以由用户命名的作用域,用来处理程序中 常见的同名冲突。

在 C语言中定义了3个层次的作用域,即文件(编译单元)、函数和复合语句。C++又引入了类作用域,类是出现在文件内的。在不同的作用域中可以定义相同名字的变量,互不于扰,系统能够区别它们。

1、全局变量的作用域是整个程序,在同一作用域中不应有两个或多个同名的实体(enuty),包括变量、函数和类等。

例:如果在文件中定义了两个类,在这两个类中可以有同名的函数。在引用时,为了区别,应该加上类名作为限定: 
class A //声明A类 
{ public: 
void funl();//声明A类中的funl函数 
private: 
int i; }; 
void A::funl() //定义A类中的funl函数 
{…………}

class B //声明B类
{ public:
    void funl(); //B类中也有funl函数
    void fun2(); };
void B::funl() //定义B类中的funl函数
{ …………}

这样不会发生混淆。 
在 文件中可以定义全局变量(global variable),它的作用域是整个程序。如果在文件A中定义了一个变量a int a=3; 
在文件B中可以再定义一个变量a int a=5; 
在分别对文件A和文件B进行编译时不会有问题。但是,如果一个程序包括文件A和文件B,那么在进行连接时,会报告出错,因为在同一个程序中有两个同名的变量,认为是对变量的重复定义。 
可 以通过extern声明同一程序中的两个文件中的同名变量是同一个变量。如果在文件B中有以下声明: 
extem int a; 
表示文件B中的变量a是在其他文件中已定义的变量。由于有此声明,在程序编译和连接后,文件A的变量a的作用域扩展到了文件B。如果在文件B中不再对a赋值,则在文件B中用以下语句输出的是文件A中变量a的值: cout<

二、 什么是命名空间(解 决方案)

命名空间:实际上就是一个由程序设计者命名的内存区域,程序设计者可以根据需要指定一些有名字的空间域,把一些全局实体分别放在各个命名空间中,从而与其他全局实体分隔开来。 
如: namespace ns1 //指定命名中间nsl 
{ int a; 
double b; } 
namespace 是定义命名空间所必须写的关键字,nsl 是用户自己指定的命名空间的名字(可 以用任意的合法标识符,这里用ns1是因为ns是namespace的缩写,含义请楚),在花括号内是声明块,在其中声明的实体称为命名空间成员(namespace member)。现在命名空间成员包括变量a和b,注意a和b仍然是全局变量,仅仅是把它们隐藏在指定的命名空间中而已。如果在程序中要使用变量a和b,必须加上命名空间名和作用域分辨符“::”,如nsl::a,nsl::b。这种用法称为命名空间限定(qualified),这些名字(如nsl::a)称为被限定名 (qualified name)。C++中命名空间的作用类似于操作系统中的目录和文件的关系,由于文件很多,不便管理,而且容易重名,于是人们设立若干子目录,把文件分别放到不同的子目录中,不同子目录中的文件可以同名。调用文件时应指出文件路径。 
命名空间的作用:是建立一些互相分隔的作用域,把一些全局实体分隔开来。以免产生老点名叫李相国时,3个人都站起来应答,这就是名字冲突,因为他们无法辨别老师想叫的是哪一个李相国,同名者无法互相区分。为了避免同名混淆,学校把3个同名的学生分在3个班。这样,在小班点名叫李相国时,只会有一个人应答。也就是说,在该班的范围(即班作用域)内名字是惟一的。如果在全校集合时校长点名,需要在全校范围内找这个学生,就需要考虑作用域问题。如果校长叫李相国,全校学生中又会有3人一齐喊“到”,因为在同一作用域中存在3个同名学生。为了在全校范围内区分这3名学生,校长必须在名字前加上班号,如高三甲班的李相国,或高三乙班的李相国,即加上班名限定。这样就不致产生混淆。 
可以根据需要设置许多个命名空间,每个命名空间名代表一个不同的命名空间域,不同的命名空间不能同名。这样,可以把不同的库中的实体放到不同的命名空间中,或者说,用不同的命名空间把不同的实体隐蔽起来。过去我们用的全局变量可以理解为全局命名空间,独立于所有有名的命名空间之外,它是不需要用 namespace声明的,实际上是由系统隐式声明的,存在于每个程序之中。 
在声明一个命名空间时,花括号内不仅可以包括变量,而且还可以包括以下类型: 
·变量(可以带有初始化); 
·常量; 
·数(可以是定义或声明); 
·结构体; 
·类; 
·模板; 
·命名空间(在一个命名空间中又定义一个命名空间,即嵌套的命名空间)。 
例如 
namespace nsl 
{ const int RATE=0.08; //常量 
doublepay; //变量 
doubletax() //函数 
{return a*RATE;} 
namespacens2 //嵌套的命名空间 
{int age;} 

如果想输出命名空间nsl中成员的数据,可以采用下面的方法: 
cout<

三、 使用命名空间解决名字冲突(使用指南)

有了以上的基础后,就可以利用命名空间来解决名字冲突问题。现在,对例4程序进行修改,使之能正确运行。 
例5 利用命名空间来解决例4程序名字冲突问题。 
修改两个头文件,把在头文件中声明的类分别放在两个不同的命名空间中。 
//例8.5中的头文件1,文件名为header1.h 
using namespace std; 
#include 
#include 
namespace ns1 //声明命名空间ns1 
{ class Student //在命名空间nsl内声明Student类 
{ public: 
Student(int n,string nam,int a) 
{ num=n;name=nam;age=a;} 
void get_data(); 
private: 
int num; 
string name; 
int age; }; 
void Student::get_data() //定义成员函数 
{ cout<

四、 使用命名空间成员的方法

从上面的介绍可以知道,在引用命名空间成员时,要用命名空间名和作用域分辨符对命名空间成员进行限定,以区别不同的命名空间中的同名标识符。即: 
命名空间名::命名空间成员名 
这种方法是有效的,能保证所引用的实体有惟一的名字。但是如果命名空间名字比较长,尤其在有命名空间嵌套的情况下,为引用一个实体,需要写很长的名字。在一个程序中可能要多次引用命名空间成员,就会感到很不方便。 
1 、使用命名空间别名 
可以为命名空间起一个别名(namespace alias),用来代替较长的命名空间名。如 
namespace Television //声明命名空间,名为Television 
{ … } 
可以用一个较短而易记的别名代替它。如: 
namespace TV=Television; //别名TV与原名Television等价 
也可以说,别名TV指向原名Television,在原来出现Television的位置都可以无条件地用TV来代替。 
2、使用using命名空间成员名 
using后面的命名空间成员名必须是由命名空间限定的名字。例如: 
using nsl::Student; 
以上语句声明:在本作用域(using语句所在的作用域)中会用到命名空间ns1中的成员Student,在本作用域中如果使用该命名空间成员时,不必再用命名空间限定。例如在用上面的using声明后,在其后程序中出现的Student就是隐含地指nsl::Student。 
using声明的有效范围是从using语句开始到using所在的作用域结束。如果在以上的using语句之后有以下语句: 
Student studl(101,”Wang”,18); //此处的Student相当于ns1::Student 
上面的语句相当于 
nsl::Student studl(101,”Wang”,18); 
又如 
using nsl::fun; //声明其后出现的fun是属于命名空间nsl中的fun 
cout<

五、 无名的命名空间

以上介绍的是有名字的命名空间,C++还允许使用没有名字的命名空间,如在文件A中声明了以下的无名命名空间: 
namespace //命名空间没有名字 
{ void fun( ) //定 义命名空间成员 
{ cout<<”OK.”<

六、标准命名空间std

为了解决C++标准库中的标识符与程序中的全局标识符之间以及不同库中的标识符之间的同名冲突,应该将不同库的标识符在不同的命名空间中定义(或声明)。标准C++库的所有的标识符都是在一个名为std的命名空间中定义的,或者说标准头文件(如iostream)中函数、类、对象和类模板是在命名空间 std中定义的。std是standard(标准)的缩写,表示这是存放标准库的有关内容的命名空间,含义请楚,不必死记。 
这样,在程序中用到C++标准库时,需要使用std作为限定。如 
std::cout<<”OK.”<

七、 使用早期的函数库

C语言程序中各种功能基本上都是由函数来实现的,在C语言的发展过程中建立了功能丰富的函数库,C++从C语言继承了这份宝贵的财富。在C++程序中可以使用C语言的函数库。 
如果要用函数库中的函数,就必须在程序文件中包含有关的头文件,在不同的头文件中,包含了不同的函数的声明。 
在C++中使用这些 头文件有两种方法。 
1、用C语言的传统方法 
头文件名包括后缀.h,如stdio.h,math.h等。由于C语言没有命名空间,头文件并不存放在命名空间中,因此在C++程序文件中如果用到带后缀.h的头文件时,不必用命名空间。只需在文件中包含所用的头文件即可。如 
#include 
2、用C++的新方法 
C++标准要求系统提供的头文件不包括后缀.h,例如iostream、string。为了表示与C 语言的头文件有联系又有区别,C++所用的头文件名是在C语言的相应的头文件名(但不包括后缀.h)之前加一字母c。例如,C语言中有关输入与输出的头文件名为stdio.h在C++中相应的头文件名为cstdio。C语言中的头文件math.h,在C++中相应的头文什名为cmath。C语言中的头文件 string.h在C++中相应的头文件名为cstring。注意在C++中,头文件cstnng和头文件strmg不是同一个文件。前者提供C语言中对字符串处理的有关函数(如strcmp,ctrcpy)的声明,后者提供C++中对字符串处理的新功能。 
此外,由于这些函数都是在命名空间std中声明的,因此在程序中要对命名空间std作声明。如: 
#include 
#include 
using namespace std; 
目前所用的大多数C++编译系统既保留了c的用法,又提供丁C++的新方法。下面两种用法等价,可以任选。 
C传 统方法 C++新方法 
#include #include 
#include #include 
#include #include 
using namespace std; 
可以使用传统的c方法,但应当提倡使用C++的新方法。


C++另外有一种匿名的命名空间,来保证生成的符号是局部的,这样对于匿名空间中的变量等,外部都是不可见的.

//test3.cpp

static void bar(){}

namespace //匿名的命名空间
{
    float bar2;
    int foo;
}

//test4.cpp
extern int foo;
extern void bar();
extern float bar2; 
int main()
{
bar();                    //外部的bar()被声明为static,这里链接不到符号.不能访问
bar2 = 0.1f;          //外部的匿名空间哩,这里也不能访问.
foo = 0xFF;
return 0;
};//如果将test4的目标和test3的目标进行链接,实际上是找不到这些符号的.链接会失败.

匿名的命名空间是C++的特性,相对于C的static声明来说,可以在匿名的空间里面声明很多变量和函数,这样可以省去了对每个变量和函数添加static声明.
实质上匿名空间的功能跟static声明是一样的.

复制代码

发布了22 篇原创文章 · 获赞 6 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/baidu_18891025/article/details/82377374