名字,具有属性:'使用范围',表明名字可以在那些地方使用
实体,具有属性:'生存期',描述着实体何时被创建,何时被销毁;就像一个实实在在存在的物理实体..
程序中函数,变量,他们都可以看作实体
而模板函数,模板类,这些不应该看作实体,.因为他们只是一个概念,蓝图,公式...
通过定义/声明语句,可以将实体与名字关联起来,如:
int i=33; /* 通过定义语句,将名字i与int实体关联起来 */ extern int i; /* 通过声明语句,将名字i与int实体关联起来 */
定义了名字.使用范围,与实体.生存期,作用域分为局部作用域,全局作用域;
若在局部/全局作用域中声明/定义一个名字,则名字.使用范围为被声明/定义的位置,一直到作用域的结束;
若在局部作用域中定义一个实体,则该实体.生存期为[被定义的位置,作用域的结束];
若在全局作用域中定义一个实体,则该实体.生存期为[程序开始运行,程序运行结束];
查找规则定义了当遇到一个名字时,如何确定它所关联的实体.
通过外围作用域由内向外查找,外围作用域可能是一个或多个嵌套的命名空间;
只考虑在使用点之前定义/声明的名字;
namespace X{ int i=33; /* A */ int test( void ){ Println("%d",i); /** * 对于名字i的查找顺序:test函数形成的局部作用域->test函数所处的命名空间X->命名空间X所处的全局命名空间; * 并且注意'只考虑在使用点之前定义的名字',所以这里的名字i引用的是A处定义的int实体. */ int i=77;/* B */ Println("%d",i);/* 同样这里的名字i引用的是B处定义的实体 */ } }
即此时名字已被类名,或者命名空间名通过作用域运算符::限定.此时仅在指定的类,命名空间中确定名字关联的实体.
int main( int argc,char *argv[] ){ X::test(); /* 名字test被命名空间X限定,则只在命名空间X中查找名字test对应的实体 */ }
当以类对象,或者类类型指针调用函数时,并且函数名未被命名空间或类名限定,则会在定义这些类及其基类的命名空间中查找函数的定义/声明,从而确定函数名所关联的实体.
StartSpace(X) class Base{}; void test( const Base & ){ Println("HelloWorld"); } EndSpace StartSpace(Y) class D:public X::Base{}; EndSpace int main( int argc,char *argv[] ){ Y::D d; test(d); /** * 此时会在定义类D的命名空间Y,以及类D的基类Base所在的命名空间X中查找test的声明/定义. * 所以此时会查找到 X::test(const Base &); */ }
如果函数名已被命名空间或类名限定,则遵循'名字已被限定'规则.
当在类中进行友元函数/类声明时,若函数或类的声明不可见,则friend语句具有将该函数/类的声明放入外围作用域(即:定义类的作用域/命名空间)的效果.如:
namespace A{ class C{ friend void f( const C & ); }; } void f2(){ A::C obj; f(obj); /** * 因为函数f接受类类型引用形参并且以类类型对象调用,所以会在定义C的命名空间中查找名字f. * 又因为f经由friend隐式在命名空间A中声明,所以这里调用的是A::f(); */ }
就像地址空间,是C++源程序中所有合法名字的集合.
命名空间是一个工具,是用来划分'全局命名空间'.这样可以有效的避免名字冲突(即一个名字与多个实体关联).
全局命名空间也是一个命名空间!定义在全局作用域中的名字是定义在全局命名空间的,此时通过'::名字'访问.
与作用域的关系: 命名空间与作用域之间没有任何联系.命名空间只是一个工具.
namespace A{ int i=33; /** * 此时名字i的使用范围,同在全局作用域中声明i一样,只不过在A之外访问名字i需要添加限定符. * i所关联的实体也与在全局作用域中定义i一样,生存期:[程序开始,程序结束] */ }
不连续性: 命名空间可以是不连续的,如:
namespace XXX{ 声明. } 若命名空间XXX已经存在,则此时打开命名空间XXX,然后将'声明'放入命名空间中. 若命名空间XXX尚未存在,则此时创建一个新的命名空间.
嵌套性: 一个命名空间可以嵌套在另一个命名空间中定义,
语法: namespace{ /* 未命名的命名空间 */ }
语义: 定义只限当前文件访问的成员.就像C中的static.
不连续: 未命名的命名空间也是不连续的,只是不可以跨越文件.
/* file1.cc */ namespace{ int i=33; } /* 则i只限于file1中使用 */ /* file2.cc */ namespace{ int i=77 } /* 在连接时不会因为file1.cc中的i而产生重定义. */
访问: 未命名命名空间中定义的名字可以在定义该未命名命名空间的命名空间中找到,即:
/* file1.cc */ namespace { int i=33 } /* 未命名命名空间处在全局命名空间中,此时直接通过'i'来访问该实体 */ namespace X{ namespace { int j=33 } /* 未命名命名空间处在命名空间X中,此时直接通过'X::i'来访问该实体 */ } int k=77; namespace { int k=77; } Println("%d",k);/* 此时会造成二义性,因为无法确定k来自全局命名空间,还是来自未命名的命名空间. */
应该使用未命名命名空间来代替static.如:
#ifdef __cplusplus # define _LocalLeft namespace { # define _LocalRight } #else # define _LocalLeft static # define _LocalRight #endif /** 定义一个仅限于当前文件使用的成员 */ #define Local(var) _LocalLeft var ; _LocalRight /* 示例: Local(int i=33);则i不会被其他文件引用. */
在命名空间内部定义,此时不需要使用限定符来限定函数名,如:
/* file1.h */ namespace X{ void f(); }; /* file1.cc */ namespace X{ void f(){ ; } };
在命名空间外部定义.此时需要使用限定符,另外当编译器看到被命名空间名(如:X)限定的函数名后,就表明函数已经处于命名空间X的作用域中了,此时若函数的参数表与函数体内使用了X中的其他成员并不需要使用'X::'限定.
/* fiel1.h */ namespace X{ class C{}; C f(const C &); } /* file1.cc */ X::C X::f(const C &){ /* 此时返回类型仍需要使用'X::'限定符 */ return C(); }
只能在包含成员声明的命名空间中定义成员.如上,包含f()函数声明的命名空间有:命名空间X,全局命名空间.所以只能在X与全局命名空间中定义函数f().
语法: using 命名空间名::名字,示例: using std::endl;
语义: 对于指定的名字搜索,添加一个搜索范围,如:
namespace A{ int i=33; } //using A::i; int main( int argc,char *argv[] ){ Println("%d",i); /** * 此时根据查找规则,i的搜寻范围:main形成的局部作用域->main所在的全局命名空间. * 所以此时提示无法确定i所关联的实体. * 在使用 using A::i 之后,此时对i的搜寻范围是: * main形成的局部作用域->main所在的全局命名空间->命名空间A. * 所以此时可以确定i所关联的实体,即在命名空间A中定义的int实体 */ }
作用域: 出现在全局作用域/局部作用域中时,此时using声明的有效范围:[using声明所处位置,作用域结束]
当using 声明出现在类作用域时,'命名空间名'只能是该类的基类名之一,'成员名'也只能是该基类的成员之一.
当派生类继承基类时,由C++对象模型可知,派生类会继承基类的所有数据成员.对于基类的成员函数,派生类的继承情况可以总结为'对于基类的成员函数,若派生类中不存在同名函数则会被继承,否则不会被继承(此时需要using声明显式继承).'
class Base{ public: void print(){ Println("Hello"); } }; class D:public Base{ public: using Base::print; void print(int){ Println("D"); } }; /** * 因为D中已经存在print(int),所以D不会继承Base::print()函数. * 除非使用了using Base::print,如上 */
语法: using namespace 命名空间名.
作用域: 同using声明.
语法: namespace 命名空间1=命名空间2.命名空间2可以是嵌套的命名空间.