目录
前言
先看一个例子
#include <iostream>
class A { };
class A { };
int main()
{
A a;
return 0;
}
编译上述代码,你会得到一个报错:A 重复定义。对此,你可能觉得:谁闲着没事敲这种代码(我就是-_-,开个玩笑)——当然,遇见这种情况,你会选择重新命名,比如:A1、A2。但是如果其名称不方便修改或者无法修改:
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
byte b;
return 0;
}
此时会报错:byte 不明确
因为 <windows.h>、<iostream> 中都各自定义了 byte,编译器不知道使用哪个
那么我们该怎么办?跟它死杠?怎么可能。
对此,C++ 提出了 命名空间 的概念,引入 namespace 关键字,用于解决 命名冲突 的问题。
下面我将详细介绍 namespace:
namespace
1. 如何理解
举一个例子:上学时候,老师都喜欢点名回答问题。假如班上有3个 “张三”,那该怎么区分他们?
当然不谈长相,仅看名字。要不然你觉得让编译器叫 “那个胖子张三”“那个丑鬼张三”?
我们可以给他们分组:a、b、c组。他们各自记住自己的组别,那么点名就可以叫 “a组的张三” … …
namespace 也是这样类似的思想。
简单地说:
namespace 相当于给 标识符(id) 声明了作用域
,使得声明的 标识符 只能在特定的范围内起作用。
由此用来解决命名冲突的问题。
2. 如何定义
语法:namespace 名称 { … }
namespace A {
using line_t = long long;
int a = 1;
void fic() { }
class A { };
}
- 通常不能在 { } 定义 “不能用 id 标识的语句”
比如你可在 { } 中定义 变量、函数、类型、命名空间 等 (变量可由变量名、函数由函数名 … 来标识)
namespace A {
cout << "hello"; // 错误
int a; // 正确
}
- 可以多层嵌套
比如 A班级 有 a、b、c 三个组,b组中还分有 b1 小组… …
namespace A {
namespace a {}
namespace b {
namespace b1 {}
}
namespace c {}
}
- 同一命名空间可在同一作用域多次定义,会自动被合并成一个
因此要注意 id 重复定义问题
namespace A {
int a;
}
namespace A {
int b;
}
// ****** 相当于 ******
namespace A{
int a;
int b;
}
- 命名空间不能定义在局部作用域
简单说就是只能定义在函数外
namespace A {} // 正确
int main()
{
namespace B {} // 报错
}
3. 如何使用
在说之前有必要说说作用域:
C++的作用域遵循:内层可以访问外层,外层不能访问内层,同一作用域可以访问已经声明的 id
namespace 所定义命名空间也可以看做作用域,其遵循:
内层可以直接访问外层,外层不能直接访问内层,同层可以直接访问已经声明的 id;相互之间都能间接访问已经声明的 id
namespace outer {
int a;
namespace inner {
int b;
// 正确:内层定义 fic() 可以直接访问外层定义的 a
void fic() { cout << a; }
}
// 错误:外层的定义 fic() 不能直接访问内层定义的 b
void fic() { cout << b; }
}
那么我们怎么访问,也就是间接访问呢?
1. 作用域限定符 " :: "
语法: 命名空间名::id
比如 A班的 a组 的 “张三”
namespace A {
namespace a {
string name = "张三";
// ***** 1 *****
void fic1() { name; }
}
// ***** 2 *****
void fic2() { a::name; }
}
// **** 3 ****
A::a::name;
// **** 4 ****
void fic()
{
A::a::name;
}
-
第一个
因为 fic1() 定义在 a 中,所以可以直接访问 a 中定义 name
-
第二个
fic2() 定义在 A 中,在 a 的外层,因此不能直接访问 name,需要间接访问;同时由于它在 A 中,因此只需要:a::name 即可(当然也可以 A :: a :: name)
-
第三、四个
都是在 A 外,因此只能间接访问
2. using 关键字
语法:using namespace 命名空间 ;
其作用是告诉编译器,当你看到一个 id 时,如果在当前命名空间找不到,就去 using 指定的命名空间 中找。
比如 C++ 我们使用的 cout、endl 等都被定义在 std 这个命名空间中
#include <iostream>
int main()
{
cout << "hello" << endl;
return 0;
}
上面的代码会报错:找不到 cout、endl,因为当前命名空间(全局命名空间,后面会介绍)没有它们的定义。
那么我们可以采用间接访问的方式:
#include <iostream>
int main()
{
std::cout << "hello" << std::endl;
return 0;
}
但是这样比较麻烦,如果要使用 std 中定义的变量或者函数等,每次都要加上 std::,那么可以如下:
#include <iostream>
using namespace std;
int main()
{
cout << "hello" << endl;
return 0;
}
如此,编译器在当前命名空间找不到时,就去 std 命名空间找。
这也可以看出,using namespace xxx; 可以在命名空间中使用
- 命名空间也可以是 A::a 这种形式。
namespace std{ // 最终会和 c++ 的 std 合并为一个
namespace A {
namespace a{}
}
// *****************
using namespace A::a;
}
4. 特殊命名空间(了解)
1. 全局命名空间(global namespace)
未显式指定在 namespace 中定义的变量等都在 global namespace 中。
编译器为程序默认创建一个的 global namespace。对于 global namespace,访问其中的元素可以直接使用 ::(前面什么都不需要加), 一般而言 :: 是也可以直接省略的
比如 std 也是在 global namespace 中,而 cout 在 std 中,那么我们可以有两种等价的方式使用 cout:
- std::cout
- ::std::cout
#include <iostream>
int g; // 没有显式指定在 namespace 中,因此直接在 global namespace 下
int main()
{
std::cout << ::g << ::std::endl;
}
2. 无名(匿名)命名空间
简言之没有指定名字的命名空间
namespace { // 没有名字
// ...
}
其作用是:
将其中定义的变量等限定在此文件(在哪个文件写的就是哪个文件)中,仅此文件能使用,而其他文件无法使用,相当于 static
5. 使用建议
- 有的时候,我们会需要使用到全局变量,有的教科书上建议 “能不用全局变量就不用”。那么我们可以将 全局变量定义在一个命名空间中。
既方便管理,又可避免因不恰当地使用导致的错误。
- 尽量不要再头文件中使用 “using namespace xxx;”。
- 引入 namespace 的目的就是为了防止命名冲突。如果你在头文件中使用,那么就会使得这个 using 指定的命名空间形同虚设。除非你确定使用了不会造成命名冲突。
- 比如 前言 中提到的 byte 不明确 的例子,因为 windows.h 头文件 中定义了 byte,而 c++ 的 iostream 头文件 的 std 中也定义了 byte,由于使用了 “using namespace std;”,使得编译器找到了两个 byte,不知道用哪一个。
6. 其他用法
- 起别名
namespace ID = 命名空间 ;
有时候命名空间可能很长,不方便书写,这时我们可以给它起别名,比如:
namespace std {
namespace A {
namespace a {
string name = "张三";
}
}
}
// *******************
namespace AA = std::A::a;
// 使用别名访问 name
AA::name; // 相当于 std::A::a::name
- 声明与定义语句分离
为了方便项目管理,常常采用分文件的写法:在头文件中写声明,源文件中写定义,借助 同一命名空间在同一作用域多次定义,会自动被合并成一个 这一特性,可以如下编写代码:
// 头文件:add.h
namespace std {
int add(int x, int y);
}
// *************************
// 源文件:add.cpp
namespace std {
int add(int x, int y) {
// ...
}
}
// 也可以这么写
// 源文件:add.cpp
int std::add(int x, int y){
// ...
}
在写的过程中你会发现一个重复性的工作:需要两次写声明函数,并进行一些增删,比如:
// 头文件
namespace std {
void fic(const string& s = "");
}
// 源文件
namespace std {
// 删除参数的默认值,加上 { }
void fic(const string& s) {
// ...
}
}
声明语句少时没多大感觉,但较多时就比较麻烦,这里推荐一个作者自己写的命令行小工具(我称为 header_to_file),能够 读取头文件中的声明语句,自动生成定义语句,并输出到源文件中,避免重复性工作,有兴趣的前往**header_to_file**了解。
最后
本文中的大部分知识来源于官方、或者其他博主的文章,一小部分为自己的理解,因此可能有理解不当的情况。所以:
如果你在阅读中发现任何错误,欢迎指正,并留下你宝贵的意见。
如果本文对你有帮助,希望你能一键三连,你也可以向其他人分享本文。
转载请注明出处即可。