C++名字空间详解

1.名字空间的由来

名字空间(namespace)是由标准C++引入的,是一种新的作用域级别。原来C++标识符的作用域分为三级:代码块({…}和函数体)、类域和全局作用域。如今,在类作用域和全局作用域之间,C++标准又添加了名字空间域这一个作用域级别。

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

2.名字空间的作用

名字空间的作用主要是为了解决日益严重的名称冲突问题。随着可重用代码的增多,各种不同的代码体系中的标识符之间同名的情况就会显著增多。解决的办法就是将不同的代码库放到不同的名字空间中。

访问一个具体的标识符的时候,可以使用如下形式:space_name::identifier。即用作用域指示符“::”将名字空间的名称和该空间下的标识符连接起来,这要,即使使用同名的标识符,由于它们处于不同的名字空间,也不会发生冲突。

有两种形式的命名空间——有名的和无名的。 
定义格式为:

有名的命名空间:
       namespace 命名空间名 {
              声明序列可选
       }
匿名的命名空间:
       namespace {
              声明序列可选
       }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3.名字空间的注意要点

(1)一个名字空间可以在多个头文件或源文件中实现,成为分段定义。如果想在当前文件访问定义在另一个文件中的同名名字空间内的成员变量,需要在当前文件的名字空间内部进行申明。如标准C++库中的所有组件都是在一个被称为std的名字空间中声明和定义的。这些组件当然分散在不同的头文件和源文件中。

(2)名字空间内部可以定义类型、函数、变量等内容,但名字空间不能定义在类和函数的内部。

(3)在一个名字空间中可以自由地访问另一个名字空间的内容,因为名字空间并没有保护级别的限制。

(4)虽然经常可以见到using namespace std;这样的用法,我们也可以用同样的方法将名字空间中的内容一次性“引入”到当前的名字空间中来,但这并不是一个值得推荐的用法。因为这样做的相当于取消了名字空间的定义,使发生名称冲突的机会增多。所以,用using单独引入需要的内容,这样会更有针对性。例如,要使用标准输入对象,只需用using std::cin;就可以了。

(5)不能在名字空间的定义中声明另一个嵌套的子命名空间,只能在命名空间中定义子命名空间。

(6)名字空间的成员,可以在命名空间的内部定义,也可以在名字空间的外部定义,但是要在名字空间进行声明。 
命名空间成员的外部定义的格式为:

扫描二维码关注公众号,回复: 64064 查看本文章
名字空间名::成员名 ……
  • 1

(7)名字空间在进行分段定义时,不能定义同名的变量,否则连接出现重定义错误。因为名字空间不同于类,具有外部连接的特性。由于外部连接特性,请不要将名字空间定义在头文件,因为当被不同的源文件包含时,会出现重定义的错误。

结合以上几点,观察如下程序。

//main.cpp
#include <iostream>

namespace myspace1{
    extern int gvar;//内部声明
    extern int otherVar; //另一个文件中同名名字空间中定义
    using std::cout;
    using std::endl;
    class myclass{
    public:
        void print(){
            cout<<"in space1,gvar="<<gvar<<endl;
        }
    };
}


namespace myspace2{
    using std::cout;
    using std::endl;
    int i=5;
    class myclass{
    public:
        void print(){
            cout<<"in space2"<<endl;
        }
    };
    namespace nestedspace{
        void ExternFunc();//内部声明
    }
}

//外部定义
int myspace1::gvar=1;
void myspace2::nestedspace::ExternFunc()
{
    cout<<"in nestedspace"<<endl;
}

int main(int argc,char* argv[])
{
    myspace1::myclass obj1;
    obj1.print();
    myspace2::myclass obj2;
    obj2.print();
    myspace2::nestedspace::ExternFunc();
    std::cout<<myspace1::otherVar<<std::endl;

    getchar();
}

//sp2.cpp
namespace myspace1{
    int otherVar=3;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

程序输出结果是: 
这里写图片描述

(8)为了避免命名空间的名字与其他的命名空间同名,可以用较长的标识符作为命名空间的名字。但是书写较长的命名空间名时,有些冗余,因此,我们可以在特定的上下文环境中给命名空间起一个相对简单的别名。 
参考如下程序。

namespace MyNewlyCreatedSpace{
    void show(){
        std::cout<<"a function within a namespace"<<std::endl;
    }
}

int main(int argc,char* argv[])
{
    namespace sp=MyNewlyCreatedSpace;
    sp::show();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4.匿名名字空间

4.1与static关键字的共同作用

匿名名字空间提供了类似在全局函数前加 static 修饰带来的限制作用域的功能。它的这种特性可以被用在struct和class上, 而普通的static却不能。比如,在两个源文件中定义了相同的全局变量(或函数),就会发生重定义的错误。如果将它们声明为全局静态变量(函数)就可以避免重定义错误。在C++中,除了可以使用static关键字避免全局变量(函数)的重定义错误,还可以通过匿名名字空间的方式实现。参考如下代码。

//main.cpp
#include <iostream>
using namespace std;

namespace{
    double dvar=1.8;
}
void show1(){
    cout<<"dvar:"<<dvar<<endl;
}

int main(int argc,char* argv[])
{
    void show2();
    show1();
    show2();
}

//a.cpp
#include <iostream>
using namespace std;

double dvar=2.8;
void show2(){
    cout<<"dvar:"<<dvar<<endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

程序输出: 
这里写图片描述

未命名的名字空间中定义的变量(或函数)只在包含该名字空间的文件中可见,但其中的变量的生存期却从程序开始到程序结束。如果有多个文件包含未命名的名字空间,这些名字空间是不相关的,即使这些名字空间中定义了同名的变量(函数),这些标识符也代表不同的对象。

4.2与static的不同

通过匿名名字空间,同样实现了对不同源文件中同名全局变量(函数)的保护,使它们不至于发生冲一定冲突。在这一点上,匿名名字空间和static的作用是相同的。

但是,用static修饰的变量(函数)具有内部连接特性,而具有内部连接特性的变量(函数)是不能用来实例化一个模板的。参考如下程序。

#include <iostream>
using namespace std;

template <char*p> class Example{
public:
    void display(){
        cout<<*p<<endl;
    }
};

static  char c='a';
int main(int argc,char* argv[])
{

    Example<&c> a; //编译出错
    a.display();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

此程序无法通过编译,因为静态变量c不具有外部连接特性,因此不是真正的“全局”变量。而类模板的非类型参数要求是编译时常量表达式,或者是指针类型的参数要求指针指向的对象具有外部连接性。具体要求,参见C++标准关于模板非类型参数的要求:

14.3.2 Template non-type arguments [temp.arg.nontype] 
1 A template-argument for a non-type, non-template template-parameter shall be one of: 
— an integral constant-expression of integral or enumeration type; or 
— the name of a non-type template-parameter; or 
— the address of an object or function with external linkage, including function templates and function 
template-ids but excluding non-static class members, expressed as & id-expression where the & is 
optional if the name refers to a function or array, or if the corresponding template-parameter is a reference; 
or 
— a pointer to member expressed as described in 5.3.1 .

C++11标准文档的下载见C++11标准文档下载,或者到官网下载:ISO相关标准官网

为了实现既能保护全局变量(函数)不受重定义错误的干扰,能够使它们具有外部连接特性的目的,必须使用匿名名字空间机制。同样是上面的这个程序,将char c=’a’;至于匿名空间进行定义,即可通过编译并运行。读者可自行考证。

通过以上程序,可以看出匿名名字空间与static的区别:包含在匿名名字空间中的全局变量(函数)具有外部连接特性,而用static修饰的全局变量具有内部连接特性,不能用来实例化模板的非类型参数。


猜你喜欢

转载自blog.csdn.net/qq_38028821/article/details/80079109
今日推荐