背景
声明区域(declaration region) :可以进行声明的区域。
全局变量的声明区是整个文件,函数内声明的变量的声明区是代码块。
潜在作用域(potential scope) :声明位置开始到声明区截止。
作用域(scope) :变量对程序的可见范围。
变量的作用域是潜在作用域的子集。
例如,在函数中声明的局部变量会隐藏同名的全局变量,导致全局变量的作用域缺少一部分。
名称空间(namespace) :每个声明区都可以声明名称,并且这些名称独立于其他声明区的名称。(例如:一个函数中的局部变量不会和另一个函数的局部变量发生冲突。)所以说,每个声明区就是一个名称空间。
C++允许我们自己创造名称空间。为了统一概念,我们不再把代码块当成名称空间,所以名称空间只有两种:全局名称空间 用户创造的名称空间
名称空间
我们创造两个名称空间Jack
和Jill
int Hill;namespace Jack{ double pail; void fetch () ; int pal; struct well {...}; } namespace Jill{ double bucket (double n) ; double fetch; int pal; struct Hill {...}; }
名称空间的法则
名称空间只有两种:全局名称空间 和 用户创造的名称空间
全局名称空间 是最基本的名称空间。全局名称空间就是整个文件。
名称空间要么是全局名称空间,要么就是嵌套在另一个名称空间中,但是不能位于代码块中。
任何名称空间中的名称不会和其他名称空间中的名称发生冲突 ,例如,Jack中的fetch不会和Jill中的fetch发生冲突,Jill中的Hill不会和全局变量Hill发生冲突。
默认情况下,名称空间中的名称具有外部链接性和静态持续性 。当然了,在全局名称空间中,我们有局部变量、全局变量、静态局部变量(内部链接性的静态变量),那么我们也可以在用户创造的名称空间中设置相应的变量。
用户创造的名称空间的一切声明和定义法则和全局名称空间一样 。例如,我们也可以像全局名称空间那样,给jack名称空间,添加变量,添加函数定义,等等所有操作
namespace Jack{ int extra; } namespace Jack{ void fetch () { std::cout<<"hello\n" ; } }
那么,既然名称空间这么多,我们如何在一个名称空间中使用其他名称空间中的名称呢?作用域解析符——::
,正如上面这段代码中所示,我们使用了std空间中的cout名称。
语法细谈
using声明
#include <iostream> namespace Jill{ double bucket (double n) ; double fetch=0 ; int pal; } double fetch=2.222 ;void foo () ;int main () { using Jill::fetch; foo (); std::cout<<"in main fetch : " <<fetch<<std::endl; std::cout<<"global fetch : " <<::fetch<<std::endl; } void foo () { using Jill::fetch; fetch=1.111 ; std::cout<<"in foo fetch : " <<fetch<<std::endl; }
in foo fetch : 1.111in main fetch : 1.111global fetch : 2.222
这里的using Jill::fetch;
就是我们所谓的 using声明
,他使得main函数中的一切fetch
名称都视为是Jill::fetch
。这里的fetch
会覆盖同名的全局变量 ,如果我们想要调用全局变量,只需要添加作用域解析符,::fetch
就是全局变量,我们还验证了一个法则,默认情况下,Jill名称空间中的名称的持续性都是静态的,因为在foo函数中对fetch
的修改,会影响main函数中的fetch
的值。
#include <iostream> namespace Jill{ double bucket (double n) ; double fetch=0 ; int pal; } using Jill::fetch;void foo () ;int main () { foo (); std::cout<<"in main fetch : " <<fetch<<std::endl; } void foo () { using Jill::fetch; fetch=1.111 ; std::cout<<"in foo fetch : " <<fetch<<std::endl; }
上面这段代码是正确的,我们把using声明
放到了全局名称空间中,这样子全局名称空间的main函数中使用的fetch
就是Jill::fetch
。
using编译指令
#include <iostream> using namespace std;int main () { cout<<"hello world!\n" ; }
上面这段代码就是最简单的C++程序,这里的#include<iostream>
会把头文件iostream
放到std名称空间中,using namespace std;
就是所谓的 using编译指令
,上面那段代码会使得 std名称空间的所有名称在全局名称空间中可用。
#include <iostream> int main () { using namespace std; cout<<"hello world!\n" ; }
有的程序员喜欢上面这段代码,因为他使得 std名称空间中的所有名称在main()函数中可用,这种写法更加安全。
using声明
使一个名称可用,而using编译指令
使得所有的名称都可用。
using编译指令
增加了名称冲突的可能性
namespace Jack{ double pail; void fetch () ; int pal; struct well {...}; } namespace Jill{ double bucket (double n) ; double fetch; int pal; struct Hill {...}; } using namespace Jack;using namespace Jill;
上面这段代码会报错,因为他会将Jack和Jill中的所有名称导出到全局名称空间,从而引发fetch
和pal
名称的冲突。
using声明
和using编译指令
的比较
假设名称空间和声明区域定义了相同的名称。如果使用using声明
,将名称空间中的名称导入声明区域,会导致两个名称发生冲突,从而报错。如果使用using编译指令
将名称空间中的所有名称导入声明区域,不会报错也不会发出警告,因为局部名称会隐藏名称空间中的名称。
#include <iostream> namespace Jill{ double bucket (double n) ; double fetch=0 ; int pal; } double fetch=1.11 ;void foo () ;int main () { using namespace Jill; char fetch[10 ]="hello" ; std::cout<<fetch<<std::endl; std::cout<<Jill::fetch<<std::endl; std::cout<<::fetch<<std::endl; foo (); } void foo () { std::cout<<"in foo fetch : " <<fetch<<std::endl; }
hello 0 1.11 in foo fetch : 1.11
仔细看上面这段代码,很神奇是不是,它不会报错,这里有3个fetch
:全局fetch
,Jill名称空间中的fetch
和局部变量fetch
,在mian函数中使用using namespace Jill;
不会报错,因为这里局部名称会隐藏名称空间名称和全局名称。如果把using namespace Jill;
改成using Jill::fetch
就会报错。
using声明
像是声明了一个名称,它会与同名的局部名称发生冲突。
using编译指令
更像是名称解析,它导入大量的名称,如果存在同名局部名称,则局部名称会覆盖名称空间名称。
using声明
比using编译指令
更安全
名称空间的嵌套
namespace Jill{ double bucket (double n) ; double fetch=0 ; int pal; } namespace elements{ namespace fire{ int flame; } } namespace myth{ using namespace elements; using std::cin; using std::cout; using Jill::fetch; }
名称空间中仍然可以创造名称空间,使用using声明和using编译指令。
using namespace myth;cin>>fetch; cin>>fire::flame;
而且using编译指令具有传递性,所以using namespace myth;
也会把elements名称空间中的所有名称导入全局名称空间。
所以cin>>fire::flame;
也是合法的。
也可以给名称空间创造别名
namespace favorite=elements::fire;
using favorite::flame;
未命名的名称空间
未命名的名称空间中的变量是静态全局变量的替代品;
#include <iostream> static int count;void foo () ;int main () { using namespace std; foo (); cout<<"now :" <<count<<endl; foo (); cout<<"now :" <<count<<endl; foo (); cout<<"now :" <<count<<endl; } void foo () { count++; }
上面这段代码等价于下面这段代码:
#include <iostream> namespace { int count; } void foo () ;int main () { using namespace std; foo (); cout<<"now :" <<count<<endl; foo (); cout<<"now :" <<count<<endl; foo (); cout<<"now :" <<count<<endl; } void foo () { count++; }
namespace{int count;}
就好像后面跟着一条using编译指令一样,由于名称空间没有名字,所以在其他文件中使用using编译指令或者using声明。
一些建议
名称空间的诞生是因为大型编程项目难于管理。如果你只写一些简单的程序请忽视下列建议。
使用已命名的名称空间中声明的变量代替全局变量
使用未命名的名称空间中声明的变量代替静态全局变量
将函数库类库放在一个名称空间中
少用using编译指令
避免在头文件中使用using编译指令。
导入名称时,首选using声明和作用域解析符
首选在局部使用using声明
一个例子
#include <string> #include <iostream> using std::cin;using std::cout;using std::endl;using std::string;namespace pers{ struct Person { string fname; string lname; }; void getPerson (Person&) ; void showPerson (const Person&) ; } namespace debts{ using namespace pers; struct Debt { Person name; double amount; }; void getDebt (Debt &) ; void showDebt (const Debt &) ; double sumDebts (const Debt ar[],int n) ; }
#include "名称空间6.h" namespace pers{ void getPerson (Person &rp) { cout<<"enter first name: " ; cin>>rp.fname; cout<<"enter last name: " ; cin>>rp.lname; } void showPerson (const Person& rp) { cout<<rp.fname<<" " <<rp.lname; } } namespace debts{ void getDebt (Debt &rd) { getPerson (rd.name); cout<<"enter debt: " ; cin>>rd.amount; } void showDebt (const Debt & rd) { showPerson (rd.name); cout<<": $" <<rd.amount<<endl; } double sumDebts (const Debt ar[],int n) { double total=0 ; for (int i=0 ;i<n;i++){ total+=ar[i].amount; } return total; } }
#include "名称空间6.h" void other () ;void another () ;int main () { using debts::Debt; using debts::showDebt; Debt golf={{"Benny" ,"Goatsniff" },120.0 }; showDebt (golf); other (); another (); } void other () { using namespace debts; Person dg={"Doodles" ,"Glister" }; showPerson (dg); cout<<endl; Debt zippy[3 ]; for (int i=0 ;i<3 ;i++){ getDebt (zippy[i]); } for (int i=0 ;i<3 ;i++) showDebt (zippy[i]); cout<<"Total debt: $" <<sumDebts (zippy,3 )<<endl; return ; } void another () { using pers::Person; Person collector={"Milo" ,"Rightshift" }; pers::showPerson (collector); cout<< endl; }
PS D:\study\c++> g++ -I .\path_to_c++\include\ -o test .\path_to_c++\名称空间6main.cpp .\path_to_c++\名称空间6.cpp PS D:\study\c++> .\test.exe Benny Goatsniff: $120 Doodles Glister enter first name: Ann enter last name: Cherry enter debt: 200 enter first name: Ban enter last name: Tank enter debt: 230 enter first name: Sam enter last name: Captain enter debt: 300 Ann Cherry: $200 Ban Tank: $230 Sam Captain: $300 Total debt: $730 Milo Rightshift