C++中的命名空间、using用法、区域运算符(::)详解

先问你个问题哈,你是不是在写C++代码的时候,脊髓反射式的写一个using namespce std; 但其实你并不了解这句话什么意思?哈哈,如果你中枪了,那么你就更需要好好把它搞清楚了。

那么什么是命名空间呢?简单的来说,就是为了防止函数或者类名重名而采取的一种手段。举个例子来说,我写了一个用来表示二维向量的类,叫vector2d,然后棒槌也写了个表示二维向量的类,也叫vector2d,那我们俩的代码如果要放在一起用的话,会让人崩溃掉,因为要克服太多的重名问题。那么有人想了这么一种方法,所有我写的类名和函数名前加一个DB(表示逗比)前缀,而所有棒槌写的代码前都加一个BC前缀(表示棒槌),于是,我的DBVector2d和他写的BCVector2d就可以共存了,只要我们一直按照这种方式书写代码,就不用考虑重名的问题。其实,Objective-C就是这么干的,你可以看到在iOS的各种库中,就采用了两个大写字母前缀来表示这个类或这个函数属于哪个库中,这个其实就可以称作命名空间了。那么命名空间的含义就是,把这一个范围内的所有函数名和类名都包含到这个空间中,他们拥有相同的命名空间(刚才那个例子就是拥有相同的前缀了)。

然而在C++中你并不需要这么做,因为C++本身就提供了一种非常好的命名空间的书写方法,这就是namespace,还是用刚才那个例子来说话吧:


  1. namespace douBi {
  2. class vector2d {};
  3. }
  4. namespace bangChui {
  5. class vector2d {};
  6. }
  1. 那么,我们在命名空间之外如何来调用命名空间内部的类或函数呢?这就需要用到两个冒号,我们把它叫做区域运算符:
  2. int main(int argc, const char * argv[]) {
  3. douBi::vector2d *v1 = new douBi::vector2d();
  4. bangChui::vector2d *v2 = new bangChui::vector2d();
  5. return 0;
  6. }

其实我们可以看出来,这和加前缀的方法好像也差不多,不过区别就在于,这样写到命名空间中,命名空间的名字是强制性加到类名或函数名之前的,而之前那种采用前缀式的写法,其实是软约束,因为即便你没有按照正确的方式书写前缀,程序也可以正常运行。

值得一提的是,命名空间是可以嵌套的,例如:


  1. namespace ns1 {
  2. namespace ns2 {
  3. void func() {}
  4. }
  5. }
  6. int main(int argc, const char * argv[]) {
  7. ns1::ns2::func();
  8. return 0;
  9. }

那么,用了命名空间以后,是不是说我们就一定要在每一条语句前面加上长长的前缀呢?也未必,C++提供了一个语句,用于隐式声明命名空间。


  1. namespace ns1 {
  2. void func() {}
  3. }
  4. int main(int argc, const char * argv[]) {
  5. using ns1::func; // 这条语句表示,在当前代码块内,写func均代表ns1::func
  6. func(); // 这里其实是调用了ns1::func()
  7. return 0;
  8. }
  1. 但是要注意的是,如果用了using,你就要时刻注意你之后调用的到底是哪个命名空间下的,比如下面这个例子:
  2. namespace ns1 {
  3. void func() {}
  4. }
  5. void func() {}
  6. int main(int argc, const char * argv[]) {
  7. using ns1::func;
  8. func();
  9. return 0;
  10. }
  1. 主函数中到底调用的哪里的函数?答案是,因为用using ns1::func;语句,所以调用的是ns1中的函数。那我要想调用外面的那个函数怎么办呢?其实啊,在C++中,任何一个命名空间之外的部分,我们叫他“全局命名空间”,是的,你没有写到命名空间中的部分就是在这个全局命名空间下,其实你写的那个命名空间它也是全局命名空间中嵌套的那个命名空间,那么,我们如何在局部访问到全局命名空间中的内容呢?很简单,你只要记住,全局命名空间就是没有名字的命名空间即可,看下面的例子:
  2. namespace ns1 {
  3. void func() {}
  4. }
  5. void func() {}
  6. int main(int argc, const char * argv[]) {
  7. using ns1::func;
  8. func(); // ns1中的函数
  9. ::func(); // 全局中的函数
  10. return 0;
  11. }

这样做,就可以区分开了。其实using语句就是更改了代码的默认命名空间而已。

关于using的用法还值得一提的是,它还可以直接包含整个命名空间中的内容,比如:


  1. namespace ns1 {
  2. void func() {}
  3. void func2() {}
  4. }
  5. int main(int argc, const char * argv[]) {
  6. using namespace ns1;
  7. func(); // ns1::func();
  8. func2(); // ns2::func();
  9. return 0;
  10. }
  1. 但是这样写要注意的是,一定要避免冲突,比如下面的代码将会受到error:
  2. namespace ns1 {
  3. void func() {}
  4. void func2() {}
  5. }
  6. namespace ns2 {
  7. void func() {}
  8. void func3() {}
  9. }
  10. void fun2() {}
  11. int main(int argc, const char * argv[]) {
  12. using namespace ns1;
  13. using namespace ns2;
  14. func(); // 冲突报错(ns1和ns2冲突)
  15. func2(); // 冲突报错(ns1和全局冲突)
  16. func3(); // ns2::func3()
  17. return 0;
  18. }

所以,通常情况下来说,除了某个类的实现文件以外,仅仅是调用某个命名空间下的类或函数时,不建议使用using,而是应当显式的写出其命名空间,这样可以增强代码的可读性,大大降低错误几率。

关于using的用法还有一些很有趣的方式,我们也可以用到比较深入的API编写中,using的另一个用法是在某一个命名空间中借用其他命名空间中的内容,举个例子:


  1. namespace ns1 {
  2. void func() {}
  3. }
  4. namespace ns2 {
  5. using ns1::func; // 借用ns1::func()
  6. }
  7. int main(int argc, const char * argv[]) {
  8. ns2::func(); // 其实是调用了ns1::func()
  9. return 0;
  10. }
  1. 当然,也可以在命名空间中借用全局中的内容,例如:
  2. void func() {}
  3. namespace ns1 {
  4. using ::func;
  5. }
  6. int main(int argc, const char * argv[]) {
  7. ns1::func(); // 其实是调用了全局中的func()
  8. return 0;
  9. }
  1. 道理是相同的,就不再赘述了。不过,单纯的这样用实在是有点逗比了,这个一般是嵌套在预处理中的,用于在不同的环境下使用不同的命名空间中的函数,例如:
  2. void func() {}
  3. namespace ns1 {
  4. void func() {}
  5. }
  6. namespace ns2 {
  7. #ifdef kExp
  8. using ns1::func;
  9. #elifdef kExp2
  10. using ::func;
  11. #else
  12. void func() {}
  13. #endif
  14. }
  15. int main(int argc, const char * argv[]) {
  16. ns2::func(); // 会根据当前环境定义宏的不同来调用不同命名空间下的func()函数
  17. return 0;
  18. }

当然,这里面技巧相当多,具体怎么用到实战中,那就靠聪明的你啦!如果你有兴趣可以去研究下,头文件cstdio和头文件stdio.h究竟有什么不同,类似的例子还有很多,不过最明显的一个是cmath和math.h,这里面多了很多std命名空间下的重载函数。研究一下你就明白为什么在C++中推荐使用#include <cmath>而不是#include <math.h>了。

顺便提一下,区域运算符(::)并不仅仅用在命名空间中,如果一个变量或一个函数属于一个类而不是对象的话,也是需要用区域运算符来访问的,比如说:


  1. class CTest {
  2. public:
  3. static int men1; // 类成员
  4. static void func() {} // 类方法
  5. };
  6. int CTest::men1 = 5; // 通过区域运算符来初始化
  7. int main(int argc, const char * argv[]) {
  8. std::cout << CTest::men1 << std::endl; // 通过区域运算符来访问
  9. CTest::func(); // 通过区域运算符调用
  10. return 0;
  11. }

好啦!差不多就是这样喽!逗比老师今天就和大家逗比到这里哈!如果你有什么逗比的问题,欢迎留言,我们一起探讨研究。

C++中的命名空间、using用法、区域运算符(::)详解

相关推荐