• c++ 的namespace及注意事项


    前文

    下文中的出现的"当前域"为"当前作用域"的简写
    namepsace在c++中是用来避免不同模块下相同名字冲突的一种关键字,本文粗略的介绍了一下namespace的使用以及需要注意的地方:

    • 1.可通过显示指定namespace,or使用using引入符号的方式, or使用using namepsace加载整个namespace的方式使用一个名称空间下的符号
    • 2.显示指定符号所属namespace的情况下,无论该表达式在哪里都是按照namespace绝对路径进行匹配而不是相对路径
    • 3.using 指令是存在作用域和指令顺序性的,允许同时使用多个namespace,同时使用多个namespace存在相同的符号并不会覆盖,但编译会报ambigious,可通过显示指定符号所属namespace解决
    • 4.using namespace相当于在当前域把namespace下的所有符号导入全局,如果当前域有局部变量,会出现局部变量覆盖全局变量的情况;
    • 5.直接使用using引入变量A,相当于在当前域涉及到的A都是这个引入的A,不允许覆盖,同名,重复,否则报错。
    • 6.对于匿名namespace, 编译器为每个不同编译单元(.o文件)生成的不同的namespace并使用它,可通过匿名空间实现类似于static的符号外部不可见性
    • 7.namespace在编译期完成后的本质就是给符号加前缀
    • 8.人生苦短,我还是显示指定吧

    正文

    使用namespace

    下文简单的创建了一个名为n1的namespace,在其中放入了一个变量number,并在main()中使用了它

    namespace n1{
        int number = 1;
    };
    
    int main(){
        //1.显式指出使用那个namespace下的number
        std::cout<<n1::number<<std::endl; 
        
        //2.引入n1::number后再使用它
        using n1::number;                
        std::cout<<number<<std::endl;
        return 0;
    }
    

    还有另一种方式获取到n1::number,可以通过using namespace加载整个namespace进来,但这种方式有其复杂性(后文详述),通常不推荐使用

    namespace n1{
        int number = 1;
    }
    
    int main(){
        using namespace n1;        //使用namespace n1
        std::cout<<number<<std::endl; 
    
        return 0;
    }
    

    符号的namepsace查找规则

    由于namespace是支持嵌套的,在显式指出符号所属的namespace的情况下,无论这个表达式放在什么地方,都是按照绝对路径从头开始匹配namespace,什么意思呢, 如下:

    namespace n1{
        void fn(){
            std::cout<<"1"; //这里是去找std::cout, 而不是找 n1::std::cout
        }
    }
    

    global namspace

    因为编译器为程序默认创建使用一个的global namespace(未显示指定在其余namespace下的定义生命变量都在global namespace下), 对于global namespace, 直接使用::, 前面什么都不需要加, 一般而言全局域的::是可以省略的,因为所有创建的东西都是在全局名称空间下,包括自己创建的namespace, 所以std::cout和::std::cout是一样的。

    int number = 0;
    
    int main(){
        std::cout<<::temp<<std::endl;
    return 0;
    }
    

    作用域

    下面的例子显示了 using 是有作用域的,和一般的变量的作用域规则一样

    namespace n1{
        int number = 1;
    }
    
    namespace n2{
        int number = 2;
    }
    
    int main(){
        {
            using  n1::number;
            std::cout<<number<<std::endl;     // 1 输出n1::number
            {
                using  n2::number;
                std::cout<<number<<std::endl; // 2 输出n2::number
            }
            std::cout<<number<<std::endl;     // 1 输出n1::number
        }
        return 0;
    }
    

    覆盖

    当使用using namespace的时候,就会使用一个namespace,同时支持多个namespace的使用,在同时使用多个namespace的情况下,是会存在同名符号冲突的情况的,如下:
    此时还是需要通过显示的指出所属namespace来解决

    int number = 0;         //global namespace
    
    namespace n1{
        int number = 1;      //n1 namespace
    }
    
    int main(){
        using namespace n1;
        std::cout<<number<<std::endl; //由于同时存在两个全局变量number,所以此处会报ambigious的错误
    return 0;
    }
    

    但如果此处using的变量则没有问题,其相当于在当前域引入了一个变量number

    int number = 0;
    
    namespace n1{
        int number = 1;
    }
    
    int main(){
        using n1::number;              //声明接下来要使用n1::number了,接下里的number都是它,相当于在当前域引入了一个变量number
        std::cout<<number<<std::endl; //没问题,输出1
    return 0;
    }
    

    当使用一个namespace的时候,就相当于在当前域把该namespace的变量都导入全局中了,如往常,局部变量会覆盖全局变量

    namespace n1{
        int number = 1;
    }
    
    int main(){
        int number = 0;
        using namespace n1;
        std::cout<<number<<std::endl;//输出的结果为0, 局部变量覆盖了n1::number
    return 0;
    }
    

    但是要注意,如果是引入变量到当前域,和之前所说的一样相当于定义一个变量,编译器严格控制了,不允许再定义相同的符号

    namespace n1{
        int number = 1;
    }
    
    int main(){
        using n1::number;    // error: redeclaration of ‘int number’
        int number = 0;
    return 0;
    }
    

    匿名namespace

    当定义一个名称空间时,可以忽略这个名称空间的名称,这种名称空间又被称为匿名namespace(匿名空间)

    namespace {
        int number;
    }
    

    编译器在内部会为这个名称空间生成一个唯一的名字,而且还会为这个匿名的名称空间生成一条using指令。所以上面的代码在效果上等同于:

    namespace __UNIQUE_NAME_ {
            int number;
    }
    using namespace __UNIQUE_NAME_;
    

    匿名空间在不同的编译单元下的__UNIQUE_NAME_ 是不同的,可以用来解决链接阶段不同编译单元(.o文件)的符号同名现象,如下

    **************f1.c***************
    namespace{
        int number;
    }
    void fn1(){}
    
    **************f2.c***************
    namespace{
        int number;
    }
    void fn2(){}
    
    **************main.c***************
    
    int main(){
    return 0;    
    }
    
    **************编译链接***************
     ⚡ root@acnszavl00033 � ~/temp/test g++ -c f1.c
     ⚡ root@acnszavl00033 � ~/temp/test g++ -c f2.c
     ⚡ root@acnszavl00033 � ~/temp/test g++ -o a.out main.c f1.o f2.o
    

    但如果去掉了f1.c和f2.c中的namespace就会报重定义的错,因为在链接阶段会发现有两个同名符号(详见编译链接(1)的2.1 关于链接);namespace在编译期后的本质,就是给不同的名称空间下的符号加上前缀, 而static将变量的可见性限制为.o文件内部(而不是源文件内部),因无法在编程时显示调用我要xxx.o文件的匿名空间从而使用到其符号,所以也常见google也使用匿名空间以替代static

    跨文件使用namespace

    有时候我们希望跨文件使用namespace,可以如下使用方式

    **************fn1.h***************
    namespace n1{
        extern int number;    //声明
    }
    **************fn1.c***************
    namespace n1{
       int number = 1;        //定义
    }
    
    **************main.c***************
    #include"fn1.h"
    #include<iostream>
    int main(){
        std::cout<<n1::number<<std::endl;
    return 0;
    }
    

    当我们使用g++ main.c fn1.c去编译的时候应该想到,其做了如下操作,

    g++ -c main.c //生成main.o
    g++ -c fn1.c  //生成fn1.o
    g++ main.o fn1.o
    

    因为我们知道namespace在编译期后的本质,就是给不同的名称空间下的符号加上前缀, 所以在链接阶段,要链接符号前缀相同,所以可以链接上

  • 相关阅读:
    jmeter压测学习42-逻辑控制器之交替控制器
    jmeter压测学习41-逻辑控制器之吞吐量控制器
    jmeter压测学习40-逻辑控制器之事务控制器
    jmeter压测学习39-获取post请求x-www-form-urlencoded格式的数据
    jmeter压测学习38-通过Jython调用Python脚本
    对微信小程序的生命周期进行扩展 – Typescript 篇
    在微信小程序开发中使用Typescript
    TCP长连接和短连接 Python代码
    jQuery Ajax编程
    Django 网页中文显示u开头的乱码
  • 原文地址:https://www.cnblogs.com/ishen/p/12078652.html
Copyright © 2020-2023  润新知