梳理总结了有关C++中名称空间的知识
例程:名称空间示例(见最后)
名称:变量名、函数名、枚举、类、结构以及类和结构的成员名 等等
名称空间(层次):是为了更好地控制名称的作用域,使同一区域或不同区域中(同名)名称能相互独立不冲突
名称空间问题:来自不同类库的同名名称的冲突问题
注意区分:名称名 和 名称空间名、using声明 和 using编译指令
名称空间涉及概念:名称空间 vs 声明区域 vs 潜在作用域 vs 作用域(即对程序有效可见域,有效可用域)
- (被嵌套的同名名称会使之前的名称隐藏不可见,被嵌套的区域对于被隐藏的那个名称是非有效作用域,但该区域是它的潜在作用域)
- (每个声明区域声明的名称彼此独立,不过声明区域是有限的且没有层次对各个名称进行区分的,容易引发同名名称冲突问题或者导致覆盖隐藏某些名称)
- 名称空间是一种特殊的声明区域(C++特性),是一种独立于程序本身的声明区域的层次扩展,各个名称空间中声明的名称相互独立不冲突,并且可以在程序中的任何位置被使用(被调用),而且可以在同一个区域中使用(调用)来自不同名称空间(不同层次)的同名名称 且不冲突
- 两个特殊的名称空间:global namespace(全局声明区域,该区域默认为global名称空间,该区域声明的名称可以在主程序(main函数)中直接使用,不需要在主程序中再次进行using声明或using编译指令) 和 local namespace(代码块区域,其实就是函数中正常的声明区域,是不可定义的名称空间,可以通过using将该空间区域的名称链接到其它名称空间的名称上,并可以在该区域内控制该区域内同名名称之间的冲突问题和覆盖隐藏过程;另外,任何一个代码块都有一个local namespace相对应)
- 作用域(和local namespace相关) < 潜在作用域 < 声明区域(其中全局声明区域默认为global namespace) < 名称空间
名称空间的定义:namespace关键字(注意:比类和结构的声明最后少一个分号,因为它俩是类型,名称空间是域)
- 名称空间中名称的声明和定义规则同全局声明区域名称的声明和定义规则相同,名称空间名需使用关键字namespace标识(类似声明类使用关键字class),但是global namespace和local namespace不使用关键字namespace
- 名称空间可以外部全局定义,也可以在某名称空间中嵌套定义(可以用using编译指令把某整个名称空间嵌套添加到另一个名称空间中,类似类的继承过程),但名称空间不能位于代码块中定义
- 默认情况下,在名称空间中声明的名称的链接性为外部的(除非它引用了常量);未命名的名称空间(只有关键字namespace没有名称空间名)的名称的链接性为所属文件的内部,它可以是内部静态变量的替代品
- 名称空间是开放的,即可以在其它地方把名称添加到已有的名称空间中,即在不同位置对同一个名称空间的定义可以拼接组合,也可以利用using声明把其它名称空间的某个名称添加到另一个名称空间中(有点类似友元)
- 在名称空间中声明的函数名的作用域为整个名称空间,因此该函数的定义和声明必须位于同一个名称空间中;注意,main()的声明和定义在global namespace中
- namespace关键字可以给名称空间名创建新的别名,从而简化对嵌套的名称空间的使用,如namespace C=A::B;其中B是嵌套在名称空间A中的名称空间名
- C++将标准类库或标准函数库放在名称空间std中,没有“.h”后缀的头文件都进行了这方面的过渡
名称空间中的名称的访问:使用名称空间名限定的名称(使用作用域解析运算符::)或使用using声明(后接名称名)或使用using编译指令(后接namespace和名称空间名)
- 限定的名称 vs 未限定的名称(其中global namespace名称空间是“无名”的,直接使用::后接具体名称名)
- 在某名称空间中的代码块中的local namespace可以直接使用该名称空间中的名称,不需要再次使用::或using,比如在main()中直接使用global namespace中的名称;注意,为什么main()中可以直接使用global namespace中的名称,而不用再使用::或using,是因为main()的声明和定义是属于global namespace的
- using声明使特定名称空间的特定的名称可用,using编译指令使整个名称空间中的全部名称可用
- using声明将特定的名称添加到当前语句所属的声明区域(local namespace)中,从而将local空间的名称与添加的那个名称空间的同名名称链接起来,这时候在这个区域中使用该名称时就可以省略其原来名称空间的限定
- 在全局声明区域(或其它名称空间中)使用using声明调用某名称空间的名称时,会将该名称添加到全局名称空间global namespace(或其它名称空间)中去
- 在任何local namespace中使用using编译指令时,会将引进的名称空间的所有同名名称全部链接到当前的local namespace中去,且局部可用,同样这时候在这个区域中使用该名称时就可以省略其原来名称空间的限定
- 在全局声明区域(或其它名称空间中)使用using编译指令调用某整个名称空间时,会将该名称空间所有名称全部添加到全局名称空间global namespace(或其它名称空间)中去,且全局可用
- (“添加”和“链接”其实是同一个意思,这里是为了更形象的区分在global namespace和local namespace中using的使用效果,因为local namespace是不可以定义的名称空间;注意,在local namespace中同一个名称的链接using声明只能使用一次,否则会产生二义性,但是可以使用带作用域解析的名称空间的限定名称,让多个同名的名称在local namespace中共存 且不冲突)
使用using声明和using编译指令的主要区别:
- using声明后接 作用域解析运算符解析的名称名,如using std::cout; using编译指令后接 关键字namespace+名称空间名,如using namespace std;
- using声明在local namespace中的名称的链接性是比较彻底的,再次声明新的同名名称会产生二义性;而using编译指令实际上是添加了一个局部名称空间,在local namespace中使用时的名称链接性并没有使用using声明的名称链接性彻底,并不会覆盖全局名称空间中声明的的名称(有可能会产生二义性),甚至会被新声明的局部同名名称覆盖隐藏,换句话讲,函数中的using编译指令将名称空间的名称视为在函数之外声明的,函数体是名称空间的局部代码块,所以可以被新声明的局部同名名称覆盖隐藏,尽管此时被覆盖隐藏但依然可以在这个块中通过作用域解析运算符对其进行访问,实现同名名称的共存
- 从名称空间的开放性和编译器的警告提示上来说,使用using声明或作用域解析运算符更安全;另外,声明为局部比全局更安全
- 一般做法是,在每个函数(包括main()函数)的代码块开头使用using编译指令引入该函数需要的名称空间,让该名称空间在该函数中可用,但只能保证该名称空间的名称在该函数内部可以使用,不保证在其它之外的函数可用
- 在使用using编译指令时,名称空间之间的嵌套性是可传递的,类似于类的继承过程,也可以看成是名称空间开放性的另一种表达:级联开放性
- 在使用using声明函数名称时,并不描述函数的返回类型和特征标,只给出函数名名称,如果函数被重载了多种版本,则一个using声明将导入所有的版本
- 另外,不要在头文件中使用using编译指令,因为这样不容易知道要让哪些名称可用,并且会受到包含头文件顺序的影响;using编译指令的使用应放在所有预处理器编译指令#include之后,应先添加 <头文件> 再添加 "头文件"
例程:名称空间示例
// 头文件namesp.h
#include<string>
namespace pers
{
struct Person
{
std::string fname; // 需要使用std名称空间的作用域解析,或者使用using
std::string lname;
};
void getPerson(Person &); // 结构最好使用引用做参数
void showPerson(const Person &);
}
namespace debts
{
using namespace pers; // using编译指令,在debts中嵌套(添加)名称空间pers,从而在debts中可以使用pers中的名称
struct Debt
{
Person name;
double amount;
};
void getDebt(Debt &);
void showDebt(const Debt &);
double sumDebt(const Debt ar[],int n);
}
// 源代码文件namesp.cpp
#include<iostream>
#include"namesp.h"
namespace pers // 名称空间的开放性,函数需要在同一个名称空间中声明和定义
{
using std::cout; // 不在头文件中使用using,这里使用的using声明添加的名称空间std的具体名称
using std::cin;
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.lname << ", " << rp.fname;
}
}
namespace debts
{
void getDebt(Debt &rd)
{
getPerson(rd.name);
cout << "Enter debt : "; // 在名称空间pers中已使用using声明添加了cout/cin,所以不用再次使用using声明,可传递性
cin >> rd.amount;
}
void showDebt(const Debt &rd)
{
showPerson(rd.name);
cout << " : $" << rd.amount << std::endl; // 在名称空间pers中没有添加名称endl
}
double sumDebt(const Debt ar[],int n)
{
double sum = 0.0;
for (int i = 0; i < n; i++)
sum += ar[i].amount;
return sum;
}
}
// 源代码主文件namespace.cpp
#include<iostream>
#include"namesp.h"
void other(); // 这里就是global namespace,它是“无名”的,不是未命名的,没有使用namespace关键字
void another();
int main() // main()也在global namespace中
{
using debts::Debt; // 主函数中除了全局名称空间之外,只使用了这两个名称,注意第二个名称是个函数名
using debts::showDebt; // 也可以不使用using声明,则在使用该名称时使用作用域解析运算符
Debt golf = { {"Benny","Goatsniff"}, 120.0 };
showDebt(golf);
other(); // 直接使用global namespace中的函数名称,不必使用using或::
another();
return 0;
}
void other()
{
using std::cout;
using std::endl;
using namespace debts; // 这个指令使得debts空间能用于该函数,并可以被该函数中的局部同名变量覆盖隐藏,不安全
Person dg = {"Doodles","Glister"}; // using编译指令的可传递性
showPerson(dg);
cout << endl;
Debt zippy[3];
int i;
for (i = 0; i < 3; i++)
getDebt(zippy[i]);
for (i = 0; i < 3; i++)
showDebt(zippy[i]);
cout << endl << "Total debt : $" << sumDebt(zippy,3) << endl << endl;
return;
}
void another()
{
using pers::Person;
Person collector = {"Milo","Rightshift"};
pers::showPerson(collector);
std::cout << std::endl;
return;
}
本文总结自《C++ primer plus》(第六版中文版)第九章:名称空间