C++中使用namespace与作用域

C++中namespace的使用

概述

	C++中使用namespace定义作用域,所有变量都有作用域,以往C中定义一个变量,
	主要取决于定义的位置及修饰(如static),这样定义的作用域应用灵活度不高。
	C++兼容以往的做法,但更推荐使用namespace来定义作用域。
	且部分原来的C语法在C++已经被取消(如使用static定义局部静态变量已经被C++取消了,使用未命名空间取代),
	以下就针对namespace的相关语法和使用做详细分析。
	本文主要参考:C++ primer

namespace定义

namespace test{		//关键字namesapce + 名字
	class test_data {/* */}; 	//类
	int n = 1;					//变量(及初始化)
    extern float fExtVal;		//外部域声明
    int fn1(int x);				//函数(及定义)
    template <> struct hash;	//模版
    namspace other_name;	//其他名空间
}//这个地方不要;
//名空间定义包括两部分:关键字namespace和名字,名字后面是{},包括声明和定义。
//名空间包含的内容主要包括如上.

名空间实质

1:名空间最基本的作用还是定义作用域
2:名空间中的每个名字都必须表示该空间的唯一实体,
不同名空间作用域不同,所以可以使用同名成员。
3:名空间中成员可以被名空间中其他成员直接访问
4;名空间可以不连续,如namespace test{},如test存在,就是添加新成员,
如不存在,就是定义一个新的名空间,这样就可以将名空间独立接口和具体实现组成一个名空间。

名空间的使用注意细节

1:不用把#include放在名空间中,因为这会把头文件中的所有名字定义为该名空间内部成员。
2:程序中某些实体只能定义一次:非内联函数,静态数据成员,变量等
3:全局命名空间:以隐式方式声明,且所有程序有存在,名字被隐式添加到全局命名空间中,
因为全局作用域是隐式的,所以使用这样的形式 ::member_name
嵌套名空间
namespace test{
    namespace test-1{
        class cTest1{/* */};
        int a;
    }
    namespace test-2{
        class cTest1{/* */};
        class cTest3 : public cTest1 {/* */};
        //.....
    }
}
//如上,test分隔为两个,内层名空间隐藏外层名空间同名成员,如上的cTest1,
//嵌套的名空间只能使用内层的名字,外层的名空间要访问,必须添加名字限定符,如:test:test-1:cTest1
内联名空间
//不同处:内联名空间名字可直接被外层名空间直接使用,无需前缀
//文件intest.h
inline namespace InTestName{	//inline必须在名空间第一次出现的地方定义,后续可以不用
}
....
namespace InTestName{ 	//隐式内联
    class Quary{};
    //
}    
//文件comtest.h
namespace ComTestName{	//非内联空间
    class Quary{};
    class other{};
    //
}
//现假设一个名空间Maintest拥有两个
namespace Maintest{
    #include "intest.h"
    #include "comtest.h"
}
//因为InTestName是内联的,可以Maintest:Quary直接访问InTestName的成员,
//而如果访问ComTestName,必须要:Maintest:ComTestName:Quary
未命名空间
// namespace后没有名字,直接{}
// 拥有静态生命周期
// 可以在一个文件中不连续,但不能跨文件
// 成员可以直接使用(因为不知道名空间没有名字),也不能用作用域运算符
int i;
namespace{
    int i;	//二义性
}
// 也可以嵌套
namespace local{
    namespace{
        int i;
    }
}
//这是可以的,直接方位i就是全局的,要名空间的方式: local:i = 123;

名空间的声明与指示using

//using declaration(声明):语句一次引入名空间的一个成员,可以出现在全局/局部作用域,
//名空间作用域及类作用域,类作用域中,只能指向基类成员。
//using directive(指示):一个名空间,可出现全局/局部作用域和名空间,但不能在类作用域,
//使得某个特定名空间所有名字可见,之后就无需添加前缀了。
//using指示具备将名空间提升到包含名空间本身和using指示的最近的作用域的能力
namespace A{
    int i,j;
}
void f()
{
    using namespace A;	//把A的名空间注入到全局作用域中
    cout << i*j << endl;
}
//另外一个示例
namespace ntest{
    int i=1,j=10,k=100;
}
int j = 20;
void main()
{
    using namespace ntest;
    ++i;	//ntest::i设定为2;
    ++j;	//二义性,是全局的,还是ntest不明确
    ++::j;	//全局的,::j = 21
    ++ntest::j;	//
    int k=200;	//
    ++k;		//
}
//综上,使用using指示可能导致二义性错误,此类错误在使用冲突名字才发现,这是延后检测,
//意味着可能在特定库使用很久后才爆发冲突。
//故使用using申明效果更好,当日,using指示也不是无用,在名空间本身的实现文件中,使用using还是非常好的

成员查找顺序

/*当使用某个名字时,查找顺序如下:
1:首先在该成员中查找
2:其次在类中查找(包括基类)
3:最后在外层作用域中查找(可能就是外层名空间)
事例如下:*/
namespace A{
    int i;
    int k;
    class c1{
        public:
        	c1():i(0),j(0){};		//正确初始化c1类成员i,j
        	int f1() {return k;};	//返回A:k
        	int f2() {return h;};	//错误:h未定义
        	int f3();
        private:
        	int i;	//隐藏了A::i
        	int j;
    };
    int h = i;	//用A::i初始化
}
int A:c1:f3() {return h;} //正确,发回A::h
/*综上.成员的查找顺序如下:
 1:类中查找
 2:向上查找作用域
 3:名字必须先声明,后使用
 4:限定A:c1:f3()查找顺序:先查找函数f3作用域,之后查外层空间C1作用域,
 最后查A作用域以及包含f3定义的作用域
*/

成员查找范围

std::string s;
std::cin >> s;
//以上的>>实际对应操作符: operate>>(std::cin, s) , 此处是如何调用到了呢
/*
1:隐藏规则有个重要例外:给函数传递一个类类型兑现时,除常规作用域查找外,还会查找实参所属名空间
2:针对1分析,调用operate时,首先当前作用域查找该函数,没找到,之后在参数所属的std和string的空间查找,
最后就找到string里面的operate函数。
3:针对1,2,要使用非成员函数时,有3种方式:
	1:using std::operate>>;			//先声明后使用
	2:std::operate>>(std::cin,s);	//显示指定
	3: operate>>(std::cin, s);		//隐式指定,靠参数cin和s的名空间内的接口			
*/

随意重载的隐患

/* 如果代码中定义了一个标准库接口,在调用这个接口的时候,可能会:
1:基于重载规则,决定执行的版本
2:执行本地定义的,不执行标准库
*/
//应用和标准库的冲突大部分是无意的,如move接口,因此,最好使用带限定语的完整版本,如:
std::move // 而不是move

候选函数集

//上述,通过函数的实参所属的名空间,可以找到该函数名空间。这对于确定函数候选集有影响,
//我们将在每个实参类(及实参的基类)所属的名空间搜索候选函数,
//这个空间的所有与被调用函数同名的函数都添加到候选集中,如下例
namespace NS{
    class ctest{/* */};
    void display(const ctest& ) {/* */};
}
class test_item : public NS::ctest {/* */};
int main()
{
    test_item item;
    display(item);
    return 0;
}
//如上,display函数从哪里来呢?
//首先查找本地(main函数内及本文件),之后实参查item,item属于test_item,item就从test_item及
//基类中查找,同时,NS中的声明的display也添加到候选集中。

重载与using

//using声明,声明的是一个名字,不是一个特定函数,如下:
/*
	using NS::print(int);	//错误,不能指定参数
	using NS::print;		//ok,using只是声明个名字
*/ //当书写声明时,所有版本的函数都引入了当前作用域,故库作者如果选择性发布部分同名函数接口,
//可能导致外部调用异常(外部调用一个未显式提供的接口,结果也能用了)
//注意:如果using引入的函数和已有的同名且形参一样,using将引发错误

//using指示,将名空间成员提升到外层作用域中
namespace lib_us{
    extern void print(int);
    extern void print(double);
}
//普通声明
void print(const std::string &);
using namespace lib_us;	//此时print的候选集包括了如上的3个print

void foo(int a)
{
    print("test ok !!");//调用print(const std::string &)
    print(a);	//调用print(int)
}
//注意:如果此时namespace中的print版本和外部一样,并不出错,使用时指定名空间还是当前作用域就可以

跨越多个namespace函数集

//如果使用多个using指示,则每个名空间的名字都成为候选函数集的一部分,如:
namespace aa{
    int print(int);
}
namespace bb{
    double print(double);
}
using namespace aa;
using namespace bb;
long double print(long double);
int main()
{
	print(1);	//调用aa:print
    print(1.1);	//调用bb:print
    return 1;
}

你可能感兴趣的:(C/C++编程,c++,开发语言)