C/C++编程:命名空间

1060 篇文章 296 订阅

什么是namespaces

  • namespaces指的是标志符的某种可见范围。
  • namespaces是一个声明性区域,为内部的标志符提供一个访问
  • namespaces内的所有标识符彼此可见,没有任何限制。
  • namespaces具有扩展开放性,可以出现在任何模块文件中
    • 所以我们可以利用namespaces来定义一些组件,而他们可以散布于多个实质模块上
    • 这种组件最典型的例子是C++标准程序库。 它的所有标识符都定义在一个叫做std的namespaces中

怎么用

1、第一种方法:直接指定标识符

	std::cout << "hello namespaces" << std::endl;

2、第二种方法:使用using declaration

	using std::cout;
    using std::endl;

    cout << "hello namespaces" << endl;

3、第三种方法:使用using directive

  • 注意在头文件、模块或者程序库中,应该避免使用using directive。因为它让xxx内定义的所有标识符都曝光了
	using  namespace std;
	cout << "hello namespaces" << endl;

对于自定义命名空间,如何

namespace ContosoData {
    class ObjectManager{
    public:
        void DoSomething(){}
    };
    void func(ObjectManager){}
}

// 方法一:使用完全限定名
void func(){
    ContosoData::ObjectManager mgr;
    mgr.DoSomething();
    ContosoData::func(mgr);
}

// 方法二:使用using声明,以将一个标识符引入范围
void func1(){
    using ContosoData::ObjectManager;
    
    ObjectManager mgr;
    mgr.DoSomething();
    func(mgr);
}

// 方法三:使用using指令,以将命名空间中的所有内容引入范围
void func2(){
    using namespace ContosoData;
    
    ObjectManager mgr;
    mgr.DoSomething();
    func(mgr);
}

一般情况下,避免将 using 指令(比如using namespace ContosoData)放置在头文件 (*.h) 中,因为任何包含该标头的文件都会将命名空间中的所有内容引入范围,这将导致非常难以调试的名称隐藏和名称冲突问题。 在头文件中,始终使用完全限定名。 如果这些名称太长,可以使用命名空间别名将其缩短。

为什么要引入命名空间

解决命名冲突问题

当你采用不同的模块和程序库时,经常会出现冲突现象,这是因为不同的模块和程序库可能针对不同的对象使用相同的标志符。 namespaces(命名空间)可以解决这个问题

#include <iostream>
 
using namespace std;
namespace data
{
	int a;
	int b;
	int numa(int a, int b);   //函数声明
	int add(int a, int b)
	{
		return a - b;
	}
}
int add(int a, int b)
{
	return a + b;
}
//注意:必须在命名空间内部声明函数才能在外部定义这个函数实体,或者:“numa” : 不是“data”的成员	
int data::numa(int a, int b)  //::指明这个函数是命名空间里的函数实体
{
	return a * b;
}
int main()
{
	cout << add(10, 29) << endl; //39
	cout << data::add(10, 29) << endl;  //::指明使用命名空间里面的函数
	cout << data::numa(10, 29) << endl;
}

总结:

  • 命名空间里面可以有函数声明和函数实体
  • 可以用域操作符::指明使用命名空间里的函数
  • 命名空间里的add和add函数不在同一个空间里。因此可以重名
  • 一般情况,函数放在命名空间之外,命名空间内部放置声明
  • 必须在命名空间内部声明函数才能在外部定义这个函数实体,否则提示:“numa” : 不是“data”的成员
#include <iostream>
using namespace std;
namespace V2014
{
	void fun(int num)
	{
		cout << "int " << "V2014" << endl;
	}
}
namespace V2015
{
	void fun(int num)
	{
		cout << "int " << "V2014" << endl;
	}
	void fun(double num)  //重载
	{
		cout << "double " << "V2014" << endl;
	}
}
int main()
{
	V2014::fun(11);
	V2015::fun(11);
	V2015::fun(11.12);
}

总结:

  • 函数重载会根据数据类型的不同而选择不同的操作
  • namespace V2014是2014年写的函数,namespace V2015是2015年对这个功能的扩展
  • ::域操作符也会决定函数重载使用哪个函数

命名空间扩展

如果有新的需求又不打算对代码大改,可以对命名空间进行扩展

#include <iostream>

using namespace std;
namespace data
{
	int a;
	int b;
	int numa(int a, int b);   //函数声明
}

int data::numa(int a, int b)  //::指明这个函数是命名空间里的函数实体
{
	return a * b;
}

//如果有新的需求又不打算对代码大改,可以对命名空间进行扩展
namespace data
{
	int add(int a, int b);   //函数声明
}
int data::add(int a, int b)
{
	return a + b;
}
void main()
{

	cout << data::numa(10, 29) << endl;
	cout << data::add(10, 29) << endl;
	cin.get();
}

匿名或者未命名的命名空间

可以创建显式命名空间,但不为其提供一个名称:

namespace
{
    int MyFunc(){}
}

这称为未命名或匿名命名空间,当你想要使变量声明对其他文件中的代码不可见时,这会很有用, (也就是说,无需创建命名命名空间即可) 提供内部链接。同一文件中的所有代码都可以看到未命名的命名空间中的标识符,但这些标识符以及命名空间本身在该文件外部(或更准确地说,在翻译单元外部)不可见

嵌套命名空间

  • 普通嵌套命名空间可以用于封装不属于父命名空间的公共接口的一部分的内部实现详细信息
  • 普通的嵌套命名空间具有对其父级成员的非限定访问权限,但父成员不具有对嵌套命名空间 的非限定访问权限,除非它被声明为内联
namespace ContosoDataServer
{
    void Foo();

    namespace Details
    {
        int CountImpl;
        void Ban() { return Foo(); }
    }

    int Bar(){...};
    int Baz(int i) { return Details::CountImpl; }
}

内联命名空间

我们知道, 命名空间可以嵌套使用:

#include <iostream>


namespace Jim {
    namespace Basic {
        struct Knife{
            Knife(){printf("Knife in Basic");}
        };
        class CorkScrew{};
    };

    namespace  TookKit{
        template<typename T> class SwissArmyKnife{};
    };
    
    namespace Other{
      //  Knife a;
      struct Knife{
          Knife(){printf("Knife in Other");}
      };
      Knife c;        //"Knife in Other"
      Basic::Knife k; //"Knife in Basic"
    }
};


int main(){
    Jim::TookKit::SwissArmyKnife<Jim::Basic::Knife> swissArmyKnife;
}

但是这样可能会带来一个问题,类型声明Jim::TookKit::SwissArmyKnife<Jim::Basic::Knife>太长了,其实没有必要让用户看到子名字空间,因此可以像这样改写代码:

#include <iostream>
namespace Jim {
    namespace Basic {
        struct Knife{
            Knife(){printf("Knife in Basic");}
        };
        class CorkScrew{};
    };

    namespace  TookKit{
        template<typename T> class SwissArmyKnife{};
    };

    namespace Other{
      //  Knife a;
      struct Knife{
          Knife(){printf("Knife in Other");}
      };
      Knife c;        //"Knife in Other"
      Basic::Knife k; //"Knife in Basic"
    }

    using namespace Basic;
    using namespace TookKit;
};

// 这是LiLei再使用Jim的库
namespace Jim{
    template<> class SwissArmyKnife<Knife>{};  // 编译失败
};

using namespace Jim;
int main(){
    SwissArmyKnife<Knife> swissArmyKnife;
}

上面的改写简化了名字的编写。但是带来了一个新的问题:库的使用者觉得TookKit中的模板SwissArmyKnife不合用,因此决定特化一个SwissArmyKnife< Knife>的版本。这时,编译无法通过。这是由于C++不允许在不同的名字空间中对模板进行特化造成的。

为此,C++11中引入一个叫做内联的命名空间的新特性。内联命名空间允许在父名字空间中定义或者特化子名字空间的模板,即:

#include <iostream>
namespace Jim {
    namespace Basic {
        struct Knife{
            Knife(){printf("Knife in Basic");}
        };
        class CorkScrew{};
    };

    inline  namespace  TookKit{
        template<typename T> class SwissArmyKnife{};
    };

    namespace Other{
        //  Knife a;
        struct Knife{
            Knife(){printf("Knife in Other");}
        };
        Knife c;        //"Knife in Other"
        Basic::Knife k; //"Knife in Basic"
    }

    using namespace Basic;
    using namespace TookKit;
};

// 这是LiLei再使用Jim的库
namespace Jim{
    template<> class SwissArmyKnife<Knife>{};  //ok
};

using namespace Jim;
int main(){
    SwissArmyKnife<Knife> swissArmyKnife;
}

与普通嵌套命名空间不同,内联命名空间的成员会被视为父命名空间的成员。

  • 在默认情况下,外部代码如何绑定到内联命名空间:
//Header.h
#include <string>

namespace Test
{
    namespace old_ns
    {
        std::string Func() { return std::string("Hello from old"); }
    }

    inline namespace new_ns  // inline标示默认的版本
    {
        std::string Func() { return std::string("Hello from new"); }
    }
}

#include "header.h"
#include <string>
#include <iostream>

int main()
{
    using namespace Test;
    
    std::string s = Func();
    printf("%s", s.c_str()); // "Hello from new"
    return 0;
}
  • 演示如何在内联命名空间中声明的模板的父命名空间中声明专用化:
namespace Parent
{
    inline namespace new_ns
    {
         template <typename T>
         struct C
         {
             T member;
         };
    }
     template<>
     class C<int> {};
}

可以将内联命名空间用作版本控制机制,以管理对库的公共接口的更改。 例如,可以创建单个父命名空间,并将接口的每个版本封装到嵌套在父命名空间内的其自己的命名空间中。 保留最新或首选的版本的命名空间限定为内联,并因此以父命名空间的直接成员的形式公开。 调用 Parent::Class 的客户端代码将自动绑定到新代码。 通过使用指向包含该代码的嵌套命名空间的完全限定路径,选择使用较旧版本的客户端仍可以对其进行访问。

#include <iostream>
using namespace std;

namespace all
{
	namespace V2014
	{
		void fun(int num)
		{
			cout << "int " << "V2014" << endl;
		}
	}
}

namespace all
{
	inline namespace V2015   //默认的版本
	{
		void fun(int num)
		{
			cout << "int " << "V2015" << endl;
		}
		void fun(double num)  //重载
		{
			cout << "double " << "V2015" << endl;
		}
	}
}

int main()
{
	all::fun(11);   //int 2015
}

下面的示例演示一个接口的两个版本,每个版本位于一个嵌套命名空间中。 通过 v_10 接口对 v_20 命名空间进行了某些修改,且该命名空间被标记为内联。 使用新库并调用 Contoso::Funcs::Add 的客户端代码将调用 v_20 版本。 尝试调用 Contoso::Funcs::Divide 的代码现在将获取一个编译时错误。 如果它们确实需要该函数,则仍可以通过显式调用Contoso::v_10::Funcs::Divide访问 v_10 版本。

namespace Contoso
{
    namespace v_10
    {
        template <typename T>
        class Funcs
        {
        public:
            Funcs(void);
            T Add(T a, T b);
            T Subtract(T a, T b);
            T Multiply(T a, T b);
            T Divide(T a, T b);
        };
    }

    inline namespace v_20
    {
        template <typename T>
        class Funcs
        {
        public:
            Funcs(void);
            T Add(T a, T b);
            T Subtract(T a, T b);
            T Multiply(T a, T b);
            std::vector<double> Log(double);
            T Accumulate(std::vector<T> nums);
      };
    }
}

内联命名空间可以跟宏__cplusplus结合使用,编译器就可以自动根据编译器选择默认的版本了:

#include <iostream>
namespace Jim {
#if __cplusplus == 201103L
    inline 
#endif
    namespace cpp11{
        struct Knife{ Knife(){printf("Knife in C++11");}};
    };
#if __cplusplus < 201103L
    inline
#endif
    namespace oldcpp{
        struct Knife{ Knife(){printf("Knife aa C++11");}};
    };
};

内联命名空间会自动把内部的标识符放到外层作用域,比如:

namespace X {
inline namespace Y {
void foo();
}  // namespace Y
}  // namespace X

X::Y::foo() 与 X::foo() 彼此可代替。内联命名空间主要用来保持跨版本的 ABI 兼容性。内联命名空间只在大型版本控制里有用。

命名空间别名

命名空间名称必须是唯一的,这意味着通常它们不应太短。 如果名称的长度使代码难以读取,则可以创建一个命名空间别名作为实际名称的缩写。 例如:

namespace a_very_long_namespace_name { class Foo {}; }
namespace AVLNN = a_very_long_namespace_name;
void Bar(AVLNN::Foo foo){ }

声明命名空间和命名空间成员

通常情况下,在头文件中声明一个命名空间。如果函数实现位于一个单独的文件中,则限定函数名称:

//contosoData.h
#pragma once
namespace ContosoDataServer
{
    void Foo();
    int Bar();
}
//Contosodata 中的函数实现应使用完全限定的名称,即使你将 using 指令放在文件顶部:
#include "contosodata.h"
using namespace ContosoDataServer;

void ContosoDataServer::Foo() // use fully-qualified name here
{
   // no qualification needed for Bar()
   Bar();
}

int ContosoDataServer::Bar(){return 0;}

指定的命名空间的成员可以在定义的名称的显式限定所声明的命名空间的外部进行定义。 但是,定义必须出现在命名空间中的声明位置之后,该命名空间包含在声明的命名空间中。 例如:

// defining_namespace_members.cpp
// C2039 expected
namespace V {
    void f();
}

void V::f() { }        // ok
void V::g() { }        // C2039, g() is not yet a member of V

namespace V {
    void g();
}

using声明和using指示

using声明

声明的形式:

using namespace_name::name

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

using std::cout;
using std::vector;
int main()
{
    int x;
    cin>>x;//wrong
    std::cin>>x;//right
    cout<<x;//right
}

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

注意using声明出现的位置的影响,如例1和例2

--example 1--
#include<iostream>
using namespace std;
namespace Lib
{
    void print(int x)
    {
        cout<<"int"<<x<<endl;
    }
}
void print(double y){
        cout<<"double"<<y<<endl;
    }
int main()
{
    using Lib::print;//example 1 : main作用域中嵌套了Lib命名空间
    print(1.3);
    print(3);
    getchar();
    return 1;
}
//输出:
int 1
int 3
--example 2--
#include<iostream>
using namespace std;
namespace Lib
{
    void print(int x)
    {
        cout<<"int"<<x<<endl;
    }
}
void print(double y){
        cout<<"double"<<y<<endl;
    }
using Lib::print;//example 2 : main作用域中平行于Lib命名空间
int main()
{
    print(1.3);
    print(3);
    getchar();
    return 1;
}
//输出:
double 1.3
int 3

using指示

形式:

using namespace 命名空间名

using指示同using声明一样,可以使我们能够使用命名空间的简写形式,简写名字从using指示点开始,直到出现using指示的作用域的末尾。但不同的是using声明可以选择性的部分可见,但using指示使得特定命名空间名的所有可见。

例1:

namespace A
{
  int i,j;
}
void f(){
  using namespace A;//using指示
  cout<<i*j<<endl;
}

使用策略

  • 鼓励在 .cc 文件内使用匿名命名空间或 static 声明
    • 在 .cc 文件中定义一个不需要被外部引用的变量时,可以将它们放在匿名命名空间或声明为 static 。但是不要在 .h 文件中这么做。
    • 所有置于匿名命名空间的声明都具有内部链接性,函数和变量可以经由声明为 static 拥有内部链接性,这意味着你在这个文件中声明的这些标识符都不能在另一个文件中被访问。即使两个文件声明了完全一样名字的标识符,它们所指向的实体实际上是完全不同的。
    • 匿名命名空间的声明和具名的格式相同,在最后注释上 namespace :
namespace {
...
}  // namespace
  • 使用具名命名空间时, 其名称可基于项目名或相对路径.
  • 禁止使用 using 指示(using-directive)
  • 禁止使用内联命名空间(inline namespace)。
  • 不要在命名空间 std 内声明任何东西, 包括标准库的类前置声明.
    • 在 std 命名空间声明实体是未定义的行为, 会导致如不可移植. 声明标准库下的实体, 需要包含对应的头文件.
  • 不应该使用 using 指示 引入整个命名空间的标识符号。
/ 禁止 —— 污染命名空间
using namespace foo;
  • 使用时,请在命名空间的最后注释出命名空间的名字。
namespace X {
inline namespace Y {
void foo();
}  // namespace Y
}  // namespace X
// .h 文件
namespace mynamespace {

// 所有声明都置于命名空间中
// 注意不要使用缩进
class MyClass {
    public:
    ...
    void Foo();
};

} // namespace mynamespace
// .cc 文件
namespace mynamespace {

// 函数定义都置于命名空间中
void MyClass::Foo() {
    ...
}

} // namespace mynamespace
  • 不要在头文件中使用 命名空间别名 除非显式标记内部命名空间使用。因为任何在头文件中引入的命名空间都会成为公开API的一部分。
// 在 .cc 中使用别名缩短常用的命名空间
namespace baz = ::foo::bar::baz;
// 在 .h 中使用别名缩短常用的命名空间
namespace librarian {
namespace impl {  // 仅限内部使用
namespace sidetable = ::pipeline_diagnostics::sidetable;
}  // namespace impl

inline void my_inline_function() {
  // 限制在一个函数中的命名空间别名
  namespace baz = ::foo::bar::baz;
  ...
}
}  // namespace librarian
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值