zoukankan      html  css  js  c++  java
  • C++笔记008:C++对C的扩展——命名空间 namespace基础


    原创笔记,转载请注明出处!

    点击【关注】,关注也是一种美德~


     

    第一, 命名空间的意义

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

    我认识两位叫“A”的朋友,一位是我同学,一位是我同事,两个人的联系方式在手机中保存的时候需要备注一下“同学A”、“同事A”,在名字前面加上额外的信息加以区分,虽然有时候脑袋会不清醒,打电话会打错……

    同样的情况也出现在C++编程中。例如,我自己写一个显示函数show(),在另外一个可用的库中也有一个显示函数show(),二者函数名相同。这样我在调用函数时,编译器就无法判断我是用的是哪一个show()函数。

    因此引入命名空间(namespace)这个概念,专门用于解决上面的问题,就像在“A”这个名字前面加上额外的附加信息一样(额外的附加信息…..这句是不是病句),命名空间可以用来作为附加信息来区分不同库中相同名称的函数、类、变量等。本质上,命名空间就是定义了一个范围。

    再说几句,我们之前看到的代码都有这条语句:using namespace std;即使用命名空间std,规定该文件中使用的标准库函数都是在标准命名空间std中定义的。

    为什么需要将这些函数、类等定义在命名空间中呢?这其实是为了避免变量或函数重名的问题。一个项目往往由多个工程师开发完成,有可能出现全局变量或函数重名的现象,而如果每个人都定义了自己的命名空间就可以解决这个问题,即使重名,只要分属不同的命名空间就不会引起问题。

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

    全局变量的作用域是整个程序,在同一作用域中不应有两个或多个同名的实体(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<<a; //得到a的值为3

    第二,命名空间的定义

    命名空间的定义使用关键字namespace,后跟命名空间的名称,如下所示:

    namespace  A

    {

    void  Fun1(){...};

    void  Fun2(){...};

    }

    上面的组织形式我们将函数的具体实现和声明放到了一起,有时候我们并不想看到函数的具体实现,只希望能一眼看到的全部都是函数的接口界面。我们可以采用如下的方式将函数的界面和具体实现分开。

    namespace  A

    {

        int a;

    double b;

    void  Fun1();

    void  Fun2();

    }

    void A::Fun1(){/*...*/}

    void A::Fun2(){/*...*/}

    1、如果一个函数的定义没有在其对应的命名空间里,必须要使用作用域解析符::来指定函数的命名空间。

    2、不可以在命名空间以外定义一个命名空间中不存在的新成员。例如:

    void A::Fun3(); //错误,A里并没有Fun3()

    3、一个良好的程序应该将程序中的所有实体(变量,类,函数)都放到某个命名空间里。当然除了main()函数之外。

     

    命名空间内部不仅可以声明或定义变量,对于其它能在命名空间以外声明或定义的实体,同样也都能在命名空间内部进行声明或定义,例如变量的声明或定义、函数的声明或定义、typedef等都可以出现在命名空间中。

    namespace 是定义命名空间所必须写的关键字,A是用户自己指定的命名空间的名字(可以用任意的合法标识符),在花括号内是声明块,在其中声明的实体称为命名空间成员(namespace member)。现在命名空间成员包括变量a和b,注意二者仍然是全局变量,仅仅是把它们隐藏在指定的命名空间中而已。如果在程序中要使用变量a和b,必须加上命名空间名和作用域分辨符“::”,如A:a,A::b。这种用法称为命名空间限定(qualified),这些名字(如A::a)称为被限定名 (qualified name)。C++中命名空间的作用类似于操作系统中的目录和文件的关系,由于文件很多,不便管理,而且容易重名,于是人们设立若干子目录,把文件分别放到不同的子目录中,不同子目录中的文件可以同名。调用文件时应指出文件路径。 

    命名空间的作用:是建立一些互相分隔的作用域,把一些全局实体分隔开来。以免产生老师点名叫李华时,3个同名的学生都站起来应答,这就是名字冲突,因为他们无法辨别老师想叫的是哪一个李华,同名者无法互相区分。为了避免同名混淆,学校把3个同名的学生分在3个班。这样,在小班点名叫李华时,只会有一个人应答。也就是说,在该班的范围(即班作用域)内名字是惟一的。如果在全校集合时校长点名,需要在全校范围内找这个学生,就需要考虑作用域问题。如果校长叫李华,全校学生中又会有3人一齐喊“到”,因为在同一作用域中存在3个同名学生。为了在全校范围内区分这3名学生,校长必须在名字前加上班号,如高三甲班的李华,或高三乙班的李华,即加上班名限定。这样就不致产生混淆。 

    可以根据需要设置许多个命名空间,每个命名空间名代表一个不同的命名空间域,不同的命名空间不能同名。这样,可以把不同的库中的实体放到不同的命名空间中,或者说,用不同的命名空间把不同的实体隐蔽起来。过去我们用的全局变量可以理解为全局命名空间,独立于所有有名的命名空间之外,它是不需要用 namespace声明的,实际上是由系统隐式声明的,存在于每个程序之中。 

    在声明一个命名空间时,花括号内不仅可以包括变量,而且还可以包括以下类型: 
    ·变量(可以带有初始化); 
    ·常量; 
    ·数(可以是定义或声明); 
    ·结构体; 
    ·类; 
    ·模板; 
    ·命名空间(在一个命名空间中又定义一个命名空间,即嵌套的命名空间)。 

    例如 
    namespace nsl 
    {

    const int RATE=0.08; //常量 
    double pay; //变量 
    double tax() //函数 
    {

    return a*RATE;


    namespace  ns2    //嵌套的命名空间 
    {

    int age;

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

    cout<<nsl::RATE<<endl;

    cout<<nsl::pay<<endl;

    cout<<nsl::tax()<<endl;

    cout<<nsl::ns2::age<<endl;  //需要指定外层的和内层的命名空间名

       可以看到命名空间的声明方法和使用方法与类差不多。但它们之间有一点差别:在声明类时在右花括号的后面有一分号,而在定义命名空间时,花括号的后面没有分号。

    第三,命名空间使用

    第一种:命名空间::标识符名称

    为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称,我们看到,使用“命名空间::标识符名称”的方式就可以访问命名空间中的变量或函数了,而且即使是重复命名也可以正确访问。

    让我们来看看命名空间如何为变量或函数等实体定义范围:

    定义两个命名空间,在使用函数func()时,用first_space::func()和second_space::func()来指明使用的是哪个func函数,cout和endl属于std命名空间。

     

    #include<iostream>

    //using namespace std;

     

    //第一个命名空间

    namespace first_space

    {

    void func()

    {

    std::cout<<"Inside first_space"<<std::endl;

    }

    }

    //第二个命名空间

    namespace second_space

    {

    void func()

    {

    std::cout<<"Inside second_space"<<std::endl;

    }

    }

     

    int main()

    {

    //调用第一个命名空间中的函数

    first_space::func();

    //调用第二个命名空间中的函数

    second_space::func();

     

    system("pause");

    return 0;

    }

    执行结果

    Inside first_space

    Inside second_space

    请按任意键继续. . .

     

    第二种:using指令之声明整个命名空间

    可以使用using namespace *;指令,这里的*可以表示任何命名空间,其作用是释放命名空间*中的变量或函数等,使之在被访问时可以不必加“命名空间::”,访问方法与一般的变量或函数无异。这样在使用命名空间中的变量、函数、类等时就不需要在前面加上命名空间的名称,这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。

    #include<iostream>

    //using namespace std;

    //第一个命名空间

    namespace first_space

    {

    void func()

    {

    std::cout<<"Inside first_space"<<std::endl;

    }

    }

    //第二个命名空间

    namespace second_space

    {

    void func()

    {

    std::cout<<"Inside second_space"<<std::endl;

    }

    }

    using namespace first_space;

    int main()

    {

    //调用第一个命名空间中的函数

    func();

     

    system("pause");

    return 0;

    }

     

    在main函数上面已经用using namespace *;指令释放了first_space命名空间中的函数,因此main函数中func()函数就知道使用哪个命名空间中的func()函数了。

    运行结果:

    Inside first_space

    请按任意键继续. . .

     

    using namespace *;会给我们书写程序带来方便,但也要慎用,如果释放了多个命名空间中的东西后,它们又可能会引起命名冲突。下面演示同时释放first_space和second_space命名空间的情况。

    #include <iostream>  

    using namespace std;  

      

    // 命名空间 first_space

    namespace first_space  

    {  

        char *Url = "Autocodes";  

    }  

      

    // 命名空间 second_space  

    namespace second_space  

    {  

        char *Url = "Autocodes Autocodes";  

    }  

      

    // 释放命名空间 first_space 和 second_space  

    using namespace first_space;  

    using namespace second_space;  

      

    int main()  

    {  

        cout << Url << endl;

     

    system("pause");

        return 0;  

    }

    上例中,编译器会提示编译错误,因为它不知道该访问哪个命名空间中的字符串变量Url。这时要想正确访问,还需在Url前面加上命名空间修饰。

      而如果main函数中又定义了一个局部变量Url呢?

    #include <iostream>  

    using namespace std;  

      

    // 命名空间 first_space

    namespace first_space  

    {  

        char *Url = "Autocodes";  

    }  

      

    // 命名空间 second_space  

    namespace second_space  

    {  

        char *Url = "Autocodes Autocodes";  

    }  

      

    // 释放命名空间 first_space 和 second_space  

    using namespace first_space;  

    using namespace second_space;  

      

    int main()  

    {  

    char *Url = "Autocodes Autocodes Autocodes";  

        cout << Url << endl;

     

    system("pause");

        return 0;  

    }

    运行后我们发现,Url访问正确,可见,这种情况下,编译器优先访问局部变量。

     

    第三种:using指令之声明命名空间特定的项目

    using不仅仅可以用于声明整个命名空间,也可以针对命名空间中的一个名称。

    using指令也可以用来指定命名空间中的特定项目。例如,如果只打算使用命名空间中的cout,可以使用如下语句:

    using std::cout;

    随后的代码中,在使用cout时就可以不用加上命名空间名称作为前缀,但是命名空间std中的其他项目仍然需要加上命名空间名称作为前缀,如下所示:

    #include <iostream> 

    using std::cout;

    int main () 

    cout << "std::endl is used with std!" << std::endl;

    return 0;

    }

     

    using 指令引入的名称遵循正常的范围规则。名称从使用 using 指令开始是可见的,直到该范围结束。此时,在范围以外定义的同名实体是隐藏的。

    第四不连续的命名空间

    命名空间可以定义在几个不同的部分中,因此命名空间是由几个单独定义的部分组成的。一个命名空间的各个组成部分可以分散在多个文件中。

    所以,如果命名空间中的某个组成部分需要请求定义在另一个文件中的名称,则仍然需要声明该名称。下面的命名空间定义可以是定义一个新的命名空间,也可以是为已有的命名空间增加新的元素:

    namespace  namespace_name  

    {

    //代码声明

    }

    第五嵌套的命名空间

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

    namespace  namespace_name1

    {

    //代码声明

    namespace  namespace_name2

    {

    //代码声明

    }

    }

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

    //访问namespace_name2中的成员

    using  namespace  namespace_name1::namespace_name2;

    //访问namespace_name1中的成员

    using  namespace  namespace_name1;

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

     

     

     

     

     

    第六,多重界面

    有时候我们同一个命名空间在面向不同的用户时,可能需要提供不同的界面。比如我们有一个命名空间里面定义了关于串口的一些实体。我们给一个开发中的程序提供的接口可能包括:打开串口,设置波特率,设置校验位等。但是面向一个最终用户时,我们可能只需要给他提供一个打开串口接口就够了。这便是使用多重界面的意义。

    1、实现多重界面的方法有很多,首先可能想到的是使用不同的命名空间。

    namespace A

    {

    void Fun1();

    void Fun2();

    void Fun3();

    }

    namespace A_Interface1

    {

    using A::Fun1;

    }

    2、上面界面实现的过程中,A_Interface1和A有着非常强的关联,修改A中的Fun1会使A_Interface1中的Fun1也修改。有时候我们可能不需要这么强的关联性,让A_Interface1中的函数有着一定的可控性,可以使用下面界面实现的方式。

    namespace A

    {

    void Fun1();

    void Fun2();

    void Fun3();

    }

    namespace A_Interface1

    {

    void Fun1()

    {

    A::Fun1();

    }

    }

     

    第七,无名命名空间

    我们知道不同命名空间的变量名可以重复,这有助于第三方将两个不同人写的代码进行整合。有时候我们并不想我们的某些代码被其他人进行整合,但是也想利用命名空间的优势——可以让变量名重复。这时候使用无名命名空间就很有价值了:第一没有名字,其他地方无法引用进去;第二因为是命名空间它里面的变量可以和其他命名空间中变量的名字重复。

    无名命名空间可以在本编译单元(所在文件)处调用,没有这一规则就永远都用不到了。需要注意的是,不同编译单元中的无名命名空间不同。

    第八,命名空间的别名

    我们在给命名空间取名字的时候,如果太短(比如上面的A)很可能出现冲突。名太长又太麻烦。这时候我们将长名字的命名空间在合适的地方取个别名可能会更好些。格式如下:

    namespace A = LongNameNamespaceA;

    这种替换的方式和C语言中的宏定义非常相似,因此别名另外一个特别用于的地方就是使代码对命名空间的依赖降低。比如我们有一个大型程序依赖于一个命名空间LibA,现在需要版本升级将所有引用LibA中的代码替换为LibA_Plus.如果使用起别名的方式,我们不用再代码中逐个查找将LibA::替换为LibA_Plus.而是简单的在命名空间的别名定义处稍作修改即可。

    namespace A = LibA;

    //替换为

     namespace A = LibA_Plus;

    但也需要注意过多的使用命名空间别名也容易造成混乱。

    再如 :

    namespace Television     //声明命名空间,名为Television 
    { … } 

    可以用一个较短而易记的别名代替它。如: 
    namespace TV=Television; //别名TV与原名Television等价 

    也可以说,别名TV指向原名Television,在原来出现Television的位置都可以无条件地用TV来代替。


    原创笔记,转载请注明出处!

    更多精彩请关注微信公众号:依法编程


     

  • 相关阅读:
    【c语言趣味编程100例】爱因斯坦数学题
    【c语言趣味编程100例】求车速
    【c语言】sizeof和strlen函数区别
    Spiral Matrix I, II
    Trapping Rain Water
    Word Ladder**
    Minimum Size Subarray Sum
    Longest Substrings Without Repeating Characters
    Palindrome Linked List
    Container With Most Water
  • 原文地址:https://www.cnblogs.com/tyyhmtyyhm/p/8971906.html
Copyright © 2011-2022 走看看