目录:
3.3 using声明机制
带命名空间的函数声明
命名空间扩展
1. 为什么要使用命名空间?
一个大型的工程往往是由若干个人独立完成的,不同的人分别完成不同的部分, 后再组合成一个完整的程序。由于各个头文件是由不同的人设计的,有可能在不同的头文件中用了相同的名字来命名所定义的类或函数,这样在程序中就会出现名字冲突。不仅如此,有可能我们自己定义的名字会与C++库中的名字发生冲突。
名字冲突就是在同一个作用域中有两个或多个同名的实体,为了解决命名冲突 , C++中引入了命名空间,所谓命名空间就是一个可以由用户自己定义的作用域,在不 同的作用域中可以定义相同名字的变量,互不干扰,系统能够区分它们。
2. 什么是命名空间?
命名空间又称为名字空间,是程序员命名的内存区域,程序员根据需要指定一些有名字的空间域,把一些全局实体分别存放到各个命名空间中,从而与其他全局实体分隔开。通俗的说,每个名字空间都是一个名字空间域,存放在名字空间域中的全局实体只在本空间域内有效。名字空间对全局实体加以域的限制,从而合理的解决命名冲突。
C++中定义命名空间的基本格式如下:
namespace sp1
{
int val1 = 0;
char val2;
} // end of namespace sp1
在声明一个命名空间时,大括号内不仅可以存放变量,还可以存放以下类型:
- 变量
- 常量
- 函数,可以是定义或声明
- 结构体
- 类
- 模板
- 命名空间,可以嵌套定义
//命名空间存放整变量,结构体,函数
namespace wd
{
int number = 0;
struct Foo
{
char ch;
int val;
};
void display();
} // end of namespace wd
定义在名称空间中的变量或者函数都称为实体,名称空间中的实体作用域是全局的, 并不意味着其可见域是全局的。
如果不使用作用域限定符和using机制,抛开名称空间嵌套和内部屏蔽的情况,实体的可见域是从实体创建到该名称空间结束。在名称空间外,该实体是不可见的。
3. 命名空间的使用方式
命名空间一共有三种使用方式,分别是 using编译指令、作用域限定符、using声明机制。
3.1 using编译指令
我们接触的第一个C++程序基本上都是这样的,其中std代表的是标准命名空间。
tips:
cout 标准输出
<< 输出流运算符
cin 标准输入
<< 输入流运算符
//using编译指令 打印 helloworld
#include <iostream>
// using编译指令,一次会将std里面的实体全部引出来
using namespace std;
int main()
{
cout << "Hello world" << endl;
//对输出流运算符进行重载
operator << (cout, "Hello world");
cout.operator << (endl);
operator << (cout, "Hello world").operator << (endl);
//cin标准输入
int number = 0;
cin >> number;
cout << "number = " << number << endl;
return 0;
}
其中第5行就使用了using编译指令。如果一个名称空间中有多个实体,使用using 编译指令,就会把该空间中的所有实体一次性引入到程序之中;对于初学者来说, 如果对一个命名空间中的实体并不熟悉时,直接使用这种方式,有可能还是会造成名字冲突的问题,而且出现错误之后,还不好查找错误的原因,比如下面的程序就会报错,当然该错误是人为造成的。
// using编译指令 函数调用
#include <iostream>
using namespace std;
double cout()
{
return 1.1;
}
int main(void)
{
cout();
return 0;
}
报错:
error_cout.cc:13:5: error: reference to ‘cout’ is ambiguous
自定义的函数名cout 与命名空间 std 里的实体 cout 有冲突,下一种方法可以解决这种问题
3.2 作用域限定符
第二种方式就是直接使用作用域限定符 :: 。每次要使用某个名称空间中的实体时, 都直接加上,例如:
#include <iostream>
namespace sp1
{
int num = 1;
void printNum()
{
std::cout << "sp1::printNum" << std::endl;
}
}
//我们自定义的函数变量名可以与命名空间里面的实体冲突
int cout(int x, int y)
{
std::cout << "x = " << x << ", y = " << y << std::endl;
std::cout << "int cout(int, int)" << std::endl;
return 0;
}
int main()
{
std::cout << sp1::num << std::endl;
sp1::printNum();
cout(3, 5);
return 0;
}
这种方式会显得比较冗余,所以还可以采用第三种使用方式。
3.3 using声明机制
using声明机制的作用域是从using语句开始,到using所在的作用域结束。要注意, 在同一作用域内用using声明的不同的命名空间的成员不能有同名的成员,否则会发生重定义。
#include <iostream>
//推荐使用方式,一次只引出一个实体
using std::cout;
using std::endl;
namespace sp1
{
int num = 1;
void printNum()
{
cout << "sp1::printNum" << endl;
}
}
using sp1::num;
using sp1::printNum;
int main()
{
//cout << sp1::num << endl;
cout << num << endl;
//sp1::printNum();
printNum();
return 0;
}
在这三种方式之中,我们推荐使用的就是第三种,需要哪个实体的时候就引入到程序中,不需要的实体就不引入,尽可能减小犯错误的概率。
还可以使用带命名空间的函数声明,防止在命名空间相互调用时产生未定义的错误。
//带命名空间的函数声明
#include <iostream>
using std::cout;
using std::endl;
//由于 sp1需要使用 sp2的实体,对 sp2进行声明
namespace sp2
{
int number;
void printsp1();
}
//使用 sp2空间的实体
namespace sp1
{
int num = 1;
void print1()
{
cout << "sp1::print1" << endl;
}
void printsp2()
{
cout << "sp1::printsp2" << endl;
sp2::printsp1();
}
} // end of namespace sp1
//使用 sp1空间的实体
namespace sp2
{
int num2 = 2;
void printsp1()
{
cout << "sp2::printsp1" << endl;
sp1::print1();
} // end of namespace sp2
}
int main()
{
cout << sp1::num << endl;
sp1::printsp2();
return 0;
}
打印
1
sp1::printsp2
sp2::printsp1
sp1::print1
命名空间是可以进行扩展的,
标准的命名空间,不建议进行扩展,可能导致冲突
namespace std
{
struct MyStruct
{
int val;
};
}
3.4 匿名命名空间
命名空间还可以不定义名字,不定义名字的命名空间称为匿名命名空间。
由于没有名字,该空间中的实体,其它文件无法引用,它只能在本文件的作用域内有效,它的作用域是从匿名命名空间声明开始到本文件结束。在本文件使用无名命名空间成员时不必用命名空间限定。其实匿名命名空间和static是同样的道理,都是只在本文件内有效,无法被其它文件引用。
namespace
{
int val1 = 10;
void func();
}//end of anonymous namespace
在匿名空间中创建的全局变量,具有全局生存期,却只能被本空间内的函数等访问,是static变量的有效替代手段。
3.5. 命名空间的嵌套及覆盖
#include <iostream>
using std::cout;
using std::endl;
//全局
int a_num = 100;
namespace sp1
{
int num = 1;
void printNum(int number)
{
cout << "形参 number = " << number << endl;
cout << "sp1::num = " << sp1::num << endl;
cout << "全局 a_num = " << ::a_num << endl; //匿名命名空间
}
//命名空间 sp1 内嵌套 sp2
namespace sp2
{
int num2 = 2;
void print2()
{
cout << "sp1::sp2::print2" << endl;
}
} // end of namespace sp1
} // end of namespace sp1
int main()
{
int val = 20;
sp1::printNum(val);
cout << endl;
cout << "sp1::sp2::num2 = " << sp1::sp2::num2 << endl;
sp1::sp2::print2();
return 0;
}
打印
形参 number = 20
sp1::num = 1
全局 a_num = 100
sp1::sp2::num2 = 2
sp1::sp2::print2
3.6. 对命名空间的思考和总结
下面引用当前流行的名称空间使用指导原则:
- 提倡在已命名的名称空间中定义变量,而不是直接定义外部全局变量或者静态全局变量。
- 如果开发了一个函数库或者类库,提倡将其放在一个名称空间中。
- 对于using 声明,首先将其作用域设置为局部而不是全局
- 不要在头文件中使用using编译指令,这样,使得可用名称变得模糊,容易出现二义性
- 包含头文件的顺序可能会影响程序的行为,如果非要使用using编译指令,建议放在所有 #include 预编译指令后。