• 【C++】用命名空间避免命名冲突


    • 🌸博主主页:@釉色清风
    • 🌸文章专栏:C++
    • 🌸今日语录:如果神明还不帮你,说明他相信你。
    • 🪷文章简介:这篇文章是结合谭浩强老师的书以及自己的理解,同时加入了一些例子,方便理解。希望对大家有帮助。

    🌼名字冲突问题

    在实际生活中,一个大型的项目往往不是一个人独立完成的,而是由若干个人合作完成的,不同的人分别完成不同的部分,最后组合成一个完整的程序。

    假如不同的人分别定义了类,放在不同的头文件中,在主文件(包含主函数的文件)需要用这些类时,就用#include指令将这些头文件包含进来。

    由于各头文件是由不同的人设计的,有可能在不同的头文件中用了相同的名字来命名所定义的类或函数。这样在程序中就会出现名字冲突

    🌻例子

    程序员甲在头文件header1.h中定义了类Student和函数fun:

    #pragma once
    //header1.h
    #include 
    #include
    #include 
    using namespace std;
    class Student //声明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<< num <<"" << name << "" << age << endl;
    }
    //定义全局函数(即外部函数)
    double fun(double a, double b)
    {
        return sqrt(a + b);
    }
    
    • 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

    在main函数所在的主文件中包含文件header1.h:

    #include "header1.h"
    int main()
    {
        Student stud1(101,"Wang",18);
        stud1.get_data;
        cout<<fun(5,3)<<endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行结果如下:

    如果程序员乙写了头文件head2.h,在其中定义了其他类以外,还定义了类Student和函数fun,但其内容与头文件header1.h中的Student和函数fun有所不同。

    //header2.h
    #pragma once
    #include 
    #include
    #include
    using namespace std;
    class Student
    {
    public:
        Student(int n, string nam, char s)//参数与header1.h中不同
        {
            num = n;
            name = nam;
            sex = s;
                
        }
        void get_data();
    private:
        int num;
        string name;
        char sex;
    };
    
    
    //成员函数定义
    void Student::get_data()
    {
        cout << num << "" << name << "" << sex << endl;
    }
    //定义全局函数
    double fun(double a, double b)
    {
        return sqrt(a - b);//返回值与header1中的fun函数不同
    }
    ......
    //头文件2可能还有其他的内容
    
    • 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

    假如主程序员在其程序中要用到header1.h中的Student和函数fun,因而在程序中包含了头文件header1.h,同时又要用到头文件header2.h中的一些内容,但是不知道此时header2.h中包含了与header1.h同名单内容不同的Student类和fun函数,因而又在头文件中包含了头文件header2.h。

    主文件如下:

    //main file
    #include 
    #include "header1.h"
    #include "header2.h"
    
    int main()
    {
        Student stud1(101,"Wang",18);
        stud1.get_data;
        cout<<fun(5,3)<<endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这时,程序编译就会出现错误。

    🌻名字冲突

    因为在预编译后,头文件中的内容取代了对应的#include指令,这样就在同一个程序文件中出现了两个Student类和两个fun函数,显然是重复定义,这就是名字冲突。
    名字冲突,即在同一个作用域中含有两个或多个同名的实体。
    不仅如此,在程序中往往还需要引用一些库,包括C++编译系统提供的库、由软件开发商提供的库或者用户自己开发的库,为此需要包含有关的头文件。如果在这些库中包含有与程序的全局实体同名的实体,或者不同的库中有相同的实体名,则在编译时就会出现名字冲突。有人称之为全局命名空间污染

    为了避免这类问题的出现,人们提出了许多方法,例如:将实体的名字写得长一点(包含十几个或几十个字母和字符);把名字搞得特殊一些,包括一些特殊的字符;由编译系统提供的内部全局标识符都用下划线作为前缀,如_complex(),以避免与用户的实体同名;由软件开发商提供的尸体的名字用特定的字符作为前缀等。但是这样的效果并不理想,而且增加了阅读程序的难度,即可读性降低了。

    C语言和早期的C++语言没有提供有效的机制来解决这个问题,没有库的提供者能够建立自己的命名空间。人们希望ANSI C++ 标准库能够解决这个问题,提供一种机制、一种工具,使由库的设计者命名的全局标识符能够和程序的全局实体名以及其他类的全局标识符区别开来。

    🌼命名空间

    为了解决这个问题,ANSI C++增加了命名空间。
    所谓命命名空间,就是一个由程序设计者命名的内存区域。程序设计者可以根据需要指定一些有名字的空间域,把一些全局实体分别放在各个命名空间中,从而与其他全局实体分离开来。
    我的理解就是原来我们的全局实体变量都是“暴露出来”的,然后命名空间的作用就是分别个你需要的全局实体“围起来”,像栅栏一样,每一块区域都有一个专属于自己的名字。然后在编译阶段,就像是好多个“围起来的栅栏”,且各不相同。

    命名空间的作用是建立一些互相分隔的作用域,把一些全局实体分隔开来,以免产生名字冲突。

    这里有一段很形象的描述,可以帮助大家更好地理解:
    例如,某中学高三年级有3个叫李相国的学生,如果都在同一班,那么老师点名叫李相国时,3个人都站起来应答,这就是名字冲突。因为他们无法辨别老师想叫的是哪一个李相国?同名者无法互相区分。为了避免同名混淆,学校把3个同名的学生分在3个班。这样,在小班点名叫李相国时,只会有一个人应答。也就是说,在该班的范围内,即班作用域内名字是唯一的。如果在全校集合时校长点名,需要在全校范围内找这个学生,要考虑的作用域是全校范围。如果校长叫李相国,全校学生中又会有3个人一齐喊“到”,因为在同一作用域中存在3个同名学生。为了在全校范围内区分这3名学生,即加上班名限定。这样就不会产生混淆。

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

    🌻命名空间的类型

    在声明一个命名空间时,花括号内不仅可以包括变量,而且还可以包括一下类型:

    • 变量(可以带有初始化);
    • 常量;
    • 函数(可以是定义或声明);
    • 结构体;
    • 类;
    • 模板;
    • 命名空间(在一个命名空间中又定义了一个命名空间,即嵌套的命名空间)。

    🌻命名空间的成员

    例如:

    namespace ns1 //指定命名空间ns1
    {
        int a;
        double b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • ns1是命名空间的名字。
    • 在花括号内,声明的实体即为命名空间的成员,包括全局变量a和b。
    • 使用a和b,需要加上命名空间和作用域分辨符"::",如ns1::a,ns1::b。
    • 需要注意的是,a和b仍然是全局变量,仅仅是把他们放在了命名空间中而已。

    🌻命名空间的使用

    举例如下:

    namespace ns1
    {
        const int RATE=0.08;//常量
        double pay;//变量
        double tax()//函数
        {
            return a*RATE;
        }
        namespace ns2 //嵌套命名空间int age;}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    输出命名空间中ns1中成员的数据:

    cout<<ns1::RATE<<endl;
    cout<<ns1::pay<<endl;
    cout<<ns1::tax()<<end;
    cout<<ns1::ns2::age<<endl;
    
    • 1
    • 2
    • 3
    • 4

    🌼使用命名空间解决名字冲突

    声明命名空间ns1,并在命名空间ns1中声明Student类和定义成员函数、定义fun函数。

    #pragma once
    //header1.h
    #include 
    #include
    #include 
    using namespace std;
    namespace ns1 {
        class Student //声明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 << num << "" << name << "" << age << endl;
        }
        //定义全局函数(即外部函数)
        double fun(double a, double b)
        {
            return sqrt(a + b);
        }
    }
    
    • 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

    在header2.h中,声明命名空间ns2,并在命名空间ns2中定义Student类和 成员函数以及fun函数。

    using namespace std;
    namespace ns2
    {
        class Student
        {
        public:
            Student(int n, string nam, char s)//参数与header1.h中不同
            {
                num = n;
                name = nam;
                sex = s;
    
            }
            void get_data();
        private:
            int num;
            string name;
            char sex;
        };
    
    
        //成员函数定义
        void Student::get_data()
        {
            cout << num << "" << name << "" << sex << endl;
        }
        //定义全局函数
        double fun(double a, double b)
        {
            return sqrt(a - b);//返回值与header1中的fun函数不同
        }
    }
    
    • 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

    主函数如下:

    #include "header1.h";
    #include "header2.h";
    int main()
    {
        ns1::Student stud1(101, "Wang", 18);//用命名空间ns1中的Student类定义stud1
        stud1.get_data();
        cout <<ns1:: fun(5, 3) << endl;//调用命名空间ns1中的fun函数
        ns2::Student stud2(102, "Li", 'f');//用命名空间ns2中的Student类定义stud2
        stud2.get_data();
        cout << ns2::fun(5, 3) << endl;//调用命名空间ns2中的fun函数
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行结果如下:

  • 相关阅读:
    内存池中的嵌入式指针
    进程地址空间
    网络代理的多重应用与安全保障
    深入Spring Boot :整合Redis详解
    C++可调用对象的绑定器和包装器
    原生php 实现redis登录五次被禁,隔天再登陆
    高薪程序员&面试题精讲系列140之你熟悉分布式事务吗--下篇?有哪些分布式事务实现方案?
    powershell批量修改后缀名
    Python 数独求解器
    谈谈前端npm install速度慢的那些事
  • 原文地址:https://blog.csdn.net/2302_76305195/article/details/136389951