C++ 命名空间

在前面编写程序时,每次使用 std::cout、std::cin 或 std::endl,我们都在前面固定写了一个 std::。可能不少小伙伴心里一直好奇:这个 std 到底是什么,中间为什么还要写两个冒号 :: 呢?

这其实就是 C++ 中的命名空间(namespace)。简单来说,命名空间就是给变量、函数或类型等名字划分的一个 “所属区域”,专门用来防止不同地方起相同的名字时发生冲突,如下图所示。

C++ 命名空间划分所属区域防止同名冲突

为什么需要命名空间?

我们先来看一个常见的开发场景:假设在编写一个程序时,“学校模块” 想定义一个名为 score 的变量来表示考试分数,而 “游戏模块” 也想定义一个名为 score 的变量来表示游戏积分。

如果我们把这两个同名的变量直接放在同一个区域里定义,编译器马上就会报错:

int score = 95;
int score = 100;    // 重复定义同名变量

这并不是 score 这个标识符本身违反了命名规则,而是当同一个范围内出现两个一模一样的名字时,编译器分不清后面要用的到底是哪一个 score。

这就像一所学校里有两个都叫 “张伟” 的学生。如果把他们分在同一个班,老师上课点名喊 “张伟”,两个人就不知道喊的是谁。如果把他们分别安排在 “一班” 和 “二班”,点名时喊 “一班的张伟” 和 “二班的张伟”,就不会搞混了,如下图所示。

同班同名混淆与分班区分点名的对比

C++ 的命名空间干的就是给名字 “分班” 的事:我们可以把学校模块的名字放进 school 命名空间,把游戏模块的名字放进 game 命名空间。这样一来,就算两边都叫 score,它们也属于不同的区域,互不干扰。

C++ 命名空间的定义与访问

在 C++ 中,使用命名空间来管理名字只需要掌握两步:一是使用 namespace 定义命名空间,二是使用双冒号 :: 访问里面的名称。

1. 使用 namespace 定义命名空间

我们可以使用 namespace 关键字来创建一个命名空间,把需要隔离的变量、函数或类型放进花括号 {} 内部即可。

语法:

namespace 命名空间名 {
    // 定义变量、函数或类型
}

说明:

namespace 是 C++ 关键字,后面的 “命名空间名” 由我们自己起,遵循普通标识符的命名规则。需要注意,命名空间末尾的右花括号 } 后面不需要加分号。

同时还要留意一点:以前我们写的变量都放在 main() 函数的花括号里面,而命名空间代表的是全局层面的独立区域,因此必须定义在 main() 等函数的外部。下面这种把命名空间写进函数内部的做法是错误的:

int main() {
    // 不能在函数内部定义命名空间
    namespace school {
        int score = 95;
    }
    return 0;
}

2. 使用 :: 访问命名空间成员

命名空间定义好之后,想要使用里面的变量或工具,我们需要使用两个连续的英文冒号 :: 来指明它属于哪个命名空间。这两个冒号 :: 叫做 “作用域限定符”。

语法:

命名空间名::名称

说明:

命名空间名::名称 的意思就是 “某某命名空间里的某某名称”。比如 school::score 表示 school 命名空间里的 score,而 game::score 则表示 game 命名空间里的 score。

示例 1:访问不同命名空间的同名变量

#include <iostream>

namespace school {
    int score = 95;
}

namespace game {
    int score = 100;
}

int main() {
    std::cout << school::score << '\n';
    std::cout << game::score << '\n';
    return 0;
}

运行结果如下。

95
100

分析:

在这个例子中,我们在 main() 外部定义了 school 和 game 两个命名空间,并在里面各自定义了变量 score。输出时通过 school::score 和 game::score 指明所属区域,编译器就能准确区分,不会发生冲突。

命名空间里面的普通变量同样支持修改,比如在 main() 中执行 school::score = 98; 就能更新 school 里的分数,而 game::score 依然保持 100 不变。

C++ 标准库命名空间 std

弄懂了 school::score 之后,再回过头看平时用于输出的 std::cout 就很好理解了:std 是英文 “standard(标准)” 的缩写,它是 C++ 标准库专属的命名空间名称。

无论是读取键盘输入的 std::cin,还是控制换行的 std::endl、控制小数位数的 std::fixed,C++ 标准库自带的工具都统一放在 std 这个官方命名空间里。因此,std::cout 说白了就是 “去 std 命名空间里找到并使用 cout”。

可能有些小伙伴会问:“既然在代码第一行已经写了 #include <iostream>,为什么后面使用 cout 时还得再写 std:: 呢?” 实际上,它们各自负责的是两件不同的事:

  • #include <iostream>:相当于把标准输入输出的 “工具箱” 搬进当前代码文件,让编译器知道有 cout、cin 这些工具的存在。
  • std::cout:相当于打开贴有 std 标签的工具箱,从里面拿出 cout 来使用,指明这个名字归属于 std 命名空间。

注意 std 是 C++ 标准库专用的官方命名空间。平时写代码时,我们只需要通过 std:: 去使用里面的标准库工具即可,千万不要自己写一个 namespace std 把自定义变量或函数塞进去。

使用 using 简化访问

如果某个命名空间里的工具使用频率非常高,希望省去每次都写前缀的麻烦,C++ 提供了以下 2 种简化写法:一种是只引入单个名称的 using 声明,另一种是一次性公开整个命名空间的 using namespace 指令。

1. 引入单个名称:using 声明(推荐)

如果我们只希望省略某一个常用名称前面的 std::,比如省掉 cout 前面的前缀,可以使用 using 声明把它单独引入到当前函数中。

语法:

using 命名空间名::名称;

说明:

这条语句相当于一张定向通行证:点名引入了哪个名称,哪个名称后面就可以省掉命名空间前缀。没有点名的其他工具,依然需要保留完整的前缀。

示例 2:单独引入 std::cout

#include <iostream>

int main() {
    using std::cout;
    cout << "欢迎来到绿叶网!" << std::endl;
    return 0;
}

运行结果如下。

欢迎来到绿叶网!

分析:

using std::cout; 放在 main() 函数内部,作用是告诉编译器:在当前 main() 函数里,只要直接写 cout,指的就是 std::cout,因此下一行直接写 cout 就能正常输出。

同时小伙伴们可以留意行尾的 std::endl:因为我们只引入了 std::cout,没有引入 endl,所以使用 endl 时仍然需要保留完整的 std::endl。这种 “用谁引谁” 的方式既省事又安全,不会把不需要的名字混进来。

2. 引入整个空间:using namespace(了解)

大家在阅读一些传统教材或早期代码时,经常会在文件开头看到 using namespace std; 这行代码。

语法:

using namespace 命名空间名;

说明:

这叫做 using 编译指令。与前面只引入一个名字的 using std::cout; 不同,using namespace std; 会把整个 std 命名空间里的所有名字全部公开出来,让后面的代码可以直接写 cout、cin、endl 等所有标准库名称。

示例 3:引入整个 std 命名空间

#include <iostream>
using namespace std;

int main() {
    cout << "欢迎来到绿叶网!" << endl;
    return 0;
}

运行结果如下。

欢迎来到绿叶网!

分析:

在文件开头写上 using namespace std; 之后,main() 函数里的 cout 和 endl 都直接省掉了 std:: 前缀。

既然 using namespace 写起来这么简短,为什么咱们这个教程不直接写它,反而坚持写 std::cout 呢?因为命名空间本来就是为了防重名而设计的,一旦把所有名字全部公开,遇到同名变量时又会发生冲突。请看下面例子。

示例 4:全局公开引发重名冲突

#include <iostream>

namespace first {
    int value = 10;
}

namespace second {
    int value = 20;
}

using namespace first;
using namespace second;

int main() {
    std::cout << value << '\n';
    return 0;
}

运行结果如下。

(报错)error: reference to 'value' is ambiguous

分析:

first 和 second 两个命名空间里都定义了 value,外部同时使用两条 using namespace 把它们的名字全部暴露了出来。当 main() 访问 value 时,编译器无法确定用哪一个,直接报 “对 value 的引用不明确” 的错误。

同理,std 标准库里包含 count、max、min、distance 等大量常见英文单词。如果在文件开头写 using namespace std;,我们自己起的名字很容易与标准库发生冲突。因此在实际开发中,建议大家坚持显式写上 std::,或在函数内部按需使用 using std::cout;。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号