背景

声明区域(declaration region):可以进行声明的区域。
全局变量的声明区是整个文件,函数内声明的变量的声明区是代码块。

潜在作用域(potential scope):声明位置开始到声明区截止。
作用域(scope):变量对程序的可见范围。
变量的作用域是潜在作用域的子集。
例如,在函数中声明的局部变量会隐藏同名的全局变量,导致全局变量的作用域缺少一部分。

名称空间(namespace):每个声明区都可以声明名称,并且这些名称独立于其他声明区的名称。(例如:一个函数中的局部变量不会和另一个函数的局部变量发生冲突。)所以说,每个声明区就是一个名称空间。
C++允许我们自己创造名称空间。为了统一概念,我们不再把代码块当成名称空间,所以名称空间只有两种:全局名称空间 用户创造的名称空间

名称空间

我们创造两个名称空间JackJill

int Hill;
namespace Jack{
double pail;
void fetch();
int pal;
struct well{...};
}
namespace Jill{
double bucket(double n);
double fetch;
int pal;
struct Hill{...};
}

名称空间的法则

  1. 名称空间只有两种:全局名称空间 和 用户创造的名称空间
  2. 全局名称空间是最基本的名称空间。全局名称空间就是整个文件。
  1. 名称空间要么是全局名称空间,要么就是嵌套在另一个名称空间中,但是不能位于代码块中。
  1. 任何名称空间中的名称不会和其他名称空间中的名称发生冲突,例如,Jack中的fetch不会和Jill中的fetch发生冲突,Jill中的Hill不会和全局变量Hill发生冲突。
  2. 默认情况下,名称空间中的名称具有外部链接性和静态持续性。当然了,在全局名称空间中,我们有局部变量、全局变量、静态局部变量(内部链接性的静态变量),那么我们也可以在用户创造的名称空间中设置相应的变量。
  3. 用户创造的名称空间的一切声明和定义法则和全局名称空间一样。例如,我们也可以像全局名称空间那样,给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.111
in main fetch : 1.111
global 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中的所有名称导出到全局名称空间,从而引发fetchpal名称的冲突。

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++;
}
now :1
now :2
now :3

上面这段代码等价于下面这段代码:

#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声明。

一些建议

名称空间的诞生是因为大型编程项目难于管理。如果你只写一些简单的程序请忽视下列建议。

  1. 使用已命名的名称空间中声明的变量代替全局变量
  2. 使用未命名的名称空间中声明的变量代替静态全局变量
  3. 将函数库类库放在一个名称空间中
  4. 少用using编译指令
  5. 避免在头文件中使用using编译指令。
  6. 导入名称时,首选using声明和作用域解析符
  7. 首选在局部使用using声明

一个例子

//名称空间6.h
#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);
}
//名称空间6.cpp
#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 pers
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;
}
}

//名称空间6main.cpp
#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