1.命名空间
一个命名空间的定义包含两部分:首先是关键字namespace,随后是命名空间的名字。在命名空间后面是一系列由花括号括起来的声明和定义。主要包括:类,变量,函数(及定义),模板和其他命名空间。
namespece Test{class Mytest{};int a = 0;....}//命名空间结束后无须分号
注意:
每个命名空间都是一个作用域:命名空间中的每个名字都必须表示该空间中的唯一实体。因为不同命名空间的作用域不同,所以在不同命名空间内可以有相同名字的成员。
//定义在命名空间中的名字可以被该命名空间内的其他成员直接访问,也可以被这些成员内嵌作用域中的任何单位访问。位于该命名空间之外的代码必须明确指出所用的名字属于哪个命名空间。namespace Test{//Test已存在,现在是打开int k = a;}int b = Test::a;//指明名字空间Test::Mytest Test::operator+(Mytest &obj1, Mytest &obj2);//operator在MyTest类中声明为友元
2.命名空间可以是不连续的:命名空间可以定义在几个不同的部分
//如果Test是第一次定义,则定义了一个新的命名空间。如果Test已经存在,则打开已经存在的命名空间定义,并添加一些新成员声明namespece Test{//声明...}
命名空间的一部分成员的作用是定义类,以及声明作为类接口的函数及对象,则这些成员应该置于头文件中,这些头文件将包含在使用这些成员的文件中
命名空间成员的定义部分则置于另外的源文件中
注意#include通常不放在命名空间内部
模板特例化必须定义在原始模板所属的命名空间中,只要在命名空间中声明了特例化,就能在命名空间外部定义了
namespace Test{template
3.全局命名空间
全局作用域中定义的名字都被隐式的添加到全局命名空间中了。通常使用::运算符访问,因为全局作用域是隐式的所以没有名字
int val = ::a; //表示使用全局作用域中a的值初始化val;
4.嵌套的命名空间
指定义在其他命名空间中的命名空间
内存命名空间声明的名字会隐藏外层命名空间声明的同名成员,在嵌套的命名空间中定义的名字只在内层命名空间中有效,外层命名空间中的代码想要访问它必须在名字前面添加限定符
namespace db{namespace tdb{}namespace sdb{ int a = 10;}}int val = db::sdb::a;
5.内联命名空间
内联名字空间,可以被外层命名空间直接引用。访问内联空间内声明的名字时,无须添加该命名空间的前缀
namespace SSTT{int a = 0;int b = 10;inline TT{aee = b;bee = 88;}}//可以直接访问SSTT::TT的成员,无须添加前缀std::cout << SSTT::bee << std::endl;namespace ST{int c = 88;}//可以直接访问ST的成员,无须前缀std::cout << c << std::endl;
6.未命名的命名空间
定义在未命名的名字空间中的名字可以直接使用。如果未命名的命名空间定义在文件的最外层作用域中,则该命名空间中的名字一定要与全局作用域中的名字有所区别
int i;namespace {int i;}i = 10; //二义性,在给i赋值时,编译器不知道使用哪个inamespace db{namespace{int i;}}db::i = 10; //这样就可以和全局作用域区别
7.using声明
用using给命名空间中某个成员取别名,一个只引入一个成员
//db是一个命名空间,Qlib是db下的命名空间,Test是Qlib空间内定义的类using Test = db::Qlib::Test;Test test;
8.using指示
using指示以关键字using开始,后面是关键字namespace以及命名空间的名字。
区别于using声明,无法控制哪些名字是可见的,所有的名字在引用处都是可见的。
namespace db{int i = 10;int b = 20;int c = 30;}int i = 88;int main(void){using namespace db; //using指示std::cout << i << std::endl;//二义性,全局i或者db下的i?std::cout << db::i << std::endl; //db下的istd::cout << ::i << std::endl; //全局iint c = 66; //局部c隐藏了db下的c++c;//局部c+1;}
通常情况下,头文件最多只能在它的函数或者命名空间内使用using指示或者声明。头文件如果在顶层作用域中使用using,将会在所有使用了该头文件的文件中注入名字。
9.类,命名空间与作用域
对于名字空间内部的名字查找遵循常规查找规则:即由内向外一次查找每个外层作用域。外层作用域也可能是一个或多个嵌套的命名空间,直到最外层的全局命名空间也查找过。
namespace A{int i;namespace B{int i ; //隐藏了A::iint j;int f1(){int j;//隐藏了A::B::j;return i;//返回B:i;} }//命名空间B结束,B中定义的名字不再可见int f2(){return j;//错误:j没有被定义}int j = i;//用A::i进行初始化}
对于位于命名空间中的类来说,常规的查找规则仍然使用:当成员函数使用某个名字时,首先在给成员中进行查找,然后再类中查找(包括几类),接着在外层作用域中查找。
namespace A{int i;int k;class C{public:C():i(0), j(10){}int f1() { return k; } //返回A::Kint f2() { return h; } //错误h未定义int f3();private:int i;//在C中隐藏了A::iint j;}int h = i;}int A::C::f3(){ return h; }//正确,返回A::h
类内部出现的成员函数定义,总是向上查找作用域;名字必须先声明后使用,f2的return试图使用A::h,但此时h尚未定义。所以f2找不到定义在后面的h。
因为f3的定义位于A::h之后,所以f3对于h的使用是合法的
10.实参相关的查找与类类型形参
std::string s;std::cin >> s;
//该调用等价于 operator >> (std::cin, s);
operator>>函数定义在标准库string中,string又定义在命名空间std中。但是我们不用std::限定符和using声明就可以调用operator>>
对于命名空间中名字的隐藏规则来说有一个重要的例外,它使得我们可以直接访问输出运算符。这个例外是:
当我们给函数传递一个类类型的对象是,除了在常规的作用域查找外还会查找实参类所属的命名空间。(这个例外对于传递类的引用或者指针调用同样有效)
在上面的例子中,编译器发现对operator>>的调用时,首先在当前作用域中查找合适的函数,接着查找输出语句的外层作用域。因为>>表达式的形参是类类型的,所以编译器还会查找cin和s的类所属的命名空间。编译器查找定义了istream和string的命名空间std,在std中编译器找到了string的输出运算符函数
11.友元声明与实参相关的查找
当类声明了一个友元时,该友元声明并没有是的友元本身可见。
然而,一个另外的未声明的类或者函数如果第一次出现在友元声明中,则我们认为它是最近的外层命名空间的成员。
namespace SSTT{class Test {//两个友元,在友元声明之外没有其他的声明//这些函数隐式的成为命名空间A的成员friend void print_(const Test& obj);//根据实参相关的查找规则可以被找到friend void pt();//除非另有声明,否则不会被找到public:Test() : val(88) {}private:int val;};SSTT::Test test;print_(test);//通过SSTT::Test中的友元声明找到A::print_
12.using声明与重载
using声明的是一个名字,而非一个特定的函数
using Test::print; using Test::print(int); //错误,不能指定形参列表
一个using声明引入的函数将重载该声明语句所属作用域中已有的其他同名函数。如果using 声明出现在局部作用域中,则引入的名字将隐藏外层作用域的相关声明。如果using声明所在的作用域已经有一个函数与引入的函数同名且形参列表相同,则using声明将引发错误。using 声明将为引入的名字添加额外的重在实例
13.using指示与重载
using指示将命名空间的成员提升到外层作用域中,如果命名空间的某个函数与该命名空间所属作用域的函数同名,则命名空间的函数将被添加到重载集合中。
与using声明不同的是,对于using指示来说,引入一个与已有函数形参列表完全相同的函数并不会产生错误。使用时只要指明调用的是哪个作用版本的函数即可
如果存在多个using指示,则来自每个命名空间的名字都会成为候选函数集的一部分