编程视界:C++命名空间

发布于:2025-03-13 ⋅ 阅读:(10) ⋅ 点赞:(0)

目录

命名空间

为什么要使用命名空间

什么是命名空间

命名空间的使用方式

关键点总结

命名空间的嵌套使用

匿名命名空间

跨模块调用问题

命名空间可以多次定义

总结


首先从C++的hello,world程序入手,来认识一下C++语言

#include <iostream>
using namespace std;

int main(int argc, char * argv[]){
 	cout << "hello,world" << endl;
	return 0;
}>

(1)iostream是C++的头文件,为什么没有后缀?—— 模板阶段再作讲解

(2)using namespace std是什么含义?—— 命名空间的使用

(3) cout << "hello,world" << endl; 实现了输出hello,world的功能,如何理解这行代码?—— cout的使用

命名空间

为什么要使用命名空间

一个大型的工程往往是由若干个人独立完成的,不同的人分别完成不同的部分,最后再组合成一个完整的程序。由于各个头文件是由不同的人设计的,有可能在不同的头文件中用了相同的名字来命名所定义的类或函数,这样在程序中就会出现名字冲突。不仅如此,有可能我们自己定义的名字会与C++库中的名字发生冲突。

名字冲突就是在同一个作用域中有两个或多个同名的实体,为了解决命名冲突 ,C++中引入了命名空间,所谓命名空间就是一个可以由用户自己定义的作用域,在不同的作用域中可以定义相同名字的变量,互不干扰,系统能够区分它们。

C语言中避免名字冲突,只能进行起名约定

int hw_cpp_tom_num = 100;
int wd_cpp_bob_num = 200;

什么是命名空间

命名空间又称为名字空间,是程序员命名的内存区域,程序员根据需要指定一些有名字的空间域,把一些全局实体分别存放到各个命名空间中,从而与其他全局实体分隔开。通俗的说,每个名字空间都是一个名字空间域,存放在名字空间域中的全局实体只在本空间域内有效。名字空间对全局实体加以域的限制,从而合理的解决命名冲突。

C++中定义命名空间的基本格式如下:

namespace wd
{
int val1 = 0;
char val2;
}// end of namespace wd

在声明一个命名空间时,大括号内不仅可以存放变量,还可以存放以下类型:

变量、常量、函数、结构体、引用、类、对象、模板、命名空间等,它们都称为实体

(1)请尝试定义命名空间,并在命名空间中定义实体。

(2)命名空间中的实体如何使用呢?

命名空间的使用方式

命名空间一共有三种使用方式,分别是using编译指令、作用域限定符、using声明机制。

  1. 作用域限定符

    每次要使用某个命名空间中的实体时,都直接加上作用域限定符::,例如:

    namespace wd
    {
    int number = 10;
    void display()
    {
    	//cout,endl都是std空间中的实体,所以都加上'std::'命名空间
    	std::cout << "wd::display()" << std::endl;
    }
    }//end of namespace wd
    
    int main(void)
    {
    	std::cout << "wd::number = " << wd::number << endl;
    	wd::display();
        return 0;
    }

    好处:准确,只要命名空间中确实有这个实体,就能够准确调用(访问)

    坏处:繁琐

  2. using编译指令

    我们接触的第一个C++程序基本上都是这样的,其中std代表的是标准命名空间。

    #include <iostream>
    using namespace std; //using编译指令
    
    int main(int argc, char * argv[]){
        cout << "hello,world" << endl;
        return 0;
    }

    其中第二行就使用了using编译指令。如果一个名称空间中有多个实体,使用using编译指令,就会把该空间中的所有实体一次性引入到程序之中;对于初学者来说,如果对一个命名空间中的实体并不熟悉时,直接使用这种方式,有可能还是会造成名字冲突的问题,而且出现错误之后,还不好查找错误的原因,比如下面的程序就会报错,当然该错误是人为造成的。

    #include <iostream>
    using namespace std;
    double cout()
    {
    return 1.1;
    }
    int main(void)
    {
    cout();
    return 0;
    }

    image-20240305113638435

  3. using声明机制

using声明机制的作用域是从using语句开始,到using所在的作用域结束。要注意,在同一作用域内用using声明的不同的命名空间的成员不能有同名的成员,否则会发生重定义。

作用域示例

namespace A {
    void foo() {}
    int x = 10;
}

void func1() {
    using A::foo; // 作用域开始
    foo();        // 正确:调用 A::foo()
} // 作用域结束(到 func1 末尾)

void func2() {
    // foo();     // 错误:此处无法使用 A::foo
}

重定义示例

namespace B {
    void foo() {}  // 与 A::foo 同名但实现不同
    int x = 20;    // 与 A::x 同名
}

// 全局作用域中的 using 声明
using A::x;     // 引入 A::x
using B::x;     // 错误:重定义!x 已存在(来自 A)

int main() {
    // x = 30;  // 若单独使用一个 using 声明,此处可访问
    return 0;
}

合法作用域示例

void safe_func1() {
    using A::foo;
    foo();  // 调用 A::foo
}

void safe_func2() {
    using B::foo;
    foo();  // 调用 B::foo(不同作用域,无冲突)
}
关键点总结
  1. 作用域限定using 声明的作用域从声明处开始,到当前代码块({})结束

  2. 冲突规则:同一作用域内不允许通过 using 引入同名标识符

  3. 隔离方案:通过函数/代码块划分作用域可避免冲突

  4. using namespace 区别using 声明精确引入单个标识符,using namespace 会污染当前作用域的所有标识符

实际开发中建议:

  • 优先在函数内局部使用 using 声明

  • 避免在头文件的全局作用域使用 using

  • 对频繁使用的标识符,使用 namespace alias(如 namespace fs = std::filesystem;

#include <iostream>
using std::cout;
using std::endl;
namespace wd
{
int number = 10;
void display()
{
cout << "wd::display()" << endl;
}
}//end of namespace wd
using wd::number;
using wd::display;
int main(void)
{
    cout << "wd::number = " << number << endl;
	wd::display();
    return 0;
}

在这三种方式之中,我们推荐使用的就是第三种,需要哪个实体的时候就引入到程序中,不需要的实体就不引入,尽可能减小犯错误的概率。

image-20240305113925045

命名空间的嵌套使用

类似于文件夹下还可以建立文件夹,命名空间中还可以定义命名空间。那么内层命名空间中的实体如何访问呢?尝试一下

image-20240305114217487

image-20240305114537247

匿名命名空间

命名空间还可以不定义名字,不定义名字的命名空间称为匿名命名空间(简称匿名空间),其定义方式如下:

namespace {
int val1 = 10;
void func();
}//end of anonymous namespace

image-20240305115202613

使用匿名空间中实体时,可以直接使用,也可以加上作用域限定符(没有空间名),但是如果匿名空间中定义了和全局位置中同名的实体,会有冲突,即使使用::作用域限定符也无法访问到匿名空间中重名的实体,只能访问到全局的实体。

在C++代码中可以直接使用一些C语言的函数,就是通过匿名空间实现(体现了C++对C的兼容性),在本文件使用匿名命名空间的实体时不必用命名空间限定。

image-20240305120025058

image-20240305120642759

printf本身可以直接用,和C语言中的效果一致。但是经过匿名空间改写后,效果不一样了 —— 不要随意改写

匿名空间注意事项:

(1)匿名空间不要定义与全局空间中同名的实体;

(2)匿名空间中支持改写兼容C语言的函数,但是最好不要改写;

(3)匿名空间中的实体不能跨模块调用。

补充:匿名空间和有名空间(具名空间)统称为命名空间(名称空间)。

跨模块调用问题

一个.c/.cc/*.cpp的文件可以称为一个模块。

(1)全局变量和函数是可以跨模块调用的

externA.cc

image-20240305144421267

externB.cc

image-20240305144326193

对externA.cc和externB.cc联合编译,实现跨模块调用

(2)有名命名空间中的实体可以跨模块调用

image-20240305181047048

命名空间中的实体跨模块调用时,要在新的源文件中再次定义同名的命名空间,进行联合编译时,这两次定义被认为是同一个命名空间。

使用规则:如果要同时从全局位置和命名空间中外部引入实体,要么让它们不要重名,要么在使用时采取作用域限定的方式。

using wd2 :: num 放在test2外面就发生冲突,使用using namespace wd2 也会发生冲突,看似不在一个作用域也会冲突,所以就用上面的解决办法。

(3)静态变量和函数只能在本模块内部使用

image-20240305151609213

(4)匿名空间的实体只能在本模块内部使用

匿名空间中的实体只能在本文件的作用域内有效,它的作用域是从匿名命名空间声明开始到本文件结束。

image-20240305153745838

补充:extern外部引入的方式适合管理较小的代码组织,用什么就引入什么,但是如果跨模块调用的关系不清晰,很容易出错;

include头文件的方式在代码组织上更清晰,但是会一次引入全部内容,相较而言效率比较低。

命名空间可以多次定义

函数可以声明多次,但是只能定义一次;命名空间可以多次定义。

image-20240305152144711

在同一个源文件中可以多次定义同名的命名空间,被认为是同一个命名空间,所以不能进行重复定义。

image-20240305152606533

在命名空间中可以声明实体、定义实体,但是不能使用实体。使用命名空间中的实体一定在命名空间之外,可以理解为命名空间只是用来存放实体。

总结

命名空间的作用:

  1. 避免命名冲突:命名空间提供了一种将全局作用域划分成更小的作用域的机制,用于避免不同的代码中可能发生的命名冲突问题;

  2. 组织代码:将相关的实体放到同一个命名空间;

  3. 版本控制:不同版本的代码放到不同的命名空间中;

    总之,需要用到代码分隔的情况就可以考虑使用命名空间。

还有一个隐藏的好处:声明主权。

下面引用当前流行的命名空间使用指导原则:

  1. 提倡在已命名的名称空间中定义变量,而不是直接定义外部全局变量或者静态全局变量。

  2. 如果开发了一个函数库或者类库,提倡将其放在一个命名空间中。

  3. 对于using 声明,首先将其作用域设置为局部而不是全局。

  4. 不要在头文件中使用using编译指令,这样,使得可用名称变得模糊,容易出现二义性。

  5. 包含头文件的顺序可能会影响程序的行为,如果非要使用using编译指令,建议放在所有#include预编译指令后。


网站公告

今日签到

点亮在社区的每一天
去签到