C++命名空间namespace

前言

先看一个例子

#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;
}

上面的代码会报错:找不到 coutendl,因为当前命名空间(全局命名空间,后面会介绍)没有它们的定义。
那么我们可以采用间接访问的方式:

#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. 使用建议

  1. 有的时候,我们会需要使用到全局变量,有的教科书上建议 “能不用全局变量就不用”。那么我们可以将 全局变量定义在一个命名空间中

既方便管理,又可避免因不恰当地使用导致的错误。

  1. 尽量不要再头文件中使用 “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**了解。


最后

本文中的大部分知识来源于官方、或者其他博主的文章,一小部分为自己的理解,因此可能有理解不当的情况。所以:

如果你在阅读中发现任何错误,欢迎指正,并留下你宝贵的意见。
如果本文对你有帮助,希望你能一键三连,你也可以向其他人分享本文。
转载请注明出处即可。

【往期内容】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值