标签:ams 大型 double 标准库 细节 地方 声明和定义 comment htm
任何实际程序都是有一些部分组成的。通过将程序进行模块化可以使我们的程序更加清晰,有助于多人合作和维护。
将一个程序进行模块化以后,当其中一个模块调用另一个模块时,它不需要知道其具体实现,只需要调用它提供的接口即可。因此一个模块应该是由两个部分组成:具体实现和提供给外部的接口。
命名空间相当于一个容器,它里面包含了逻辑结构上互相关联的一组类、模板、函数等。也就是说如果某些“对象”在逻辑上有关系,我们就可以将它们放到一个命名空间里用以和外界进行区分。命名空间一个显著的特点是命名空间内的变量(类等)名可以和命名空间以外的重名。这可以用来将不同人写的代码进行整合。
命名空间的使用格式如下:
namespace A
{
void Fun1(){...};
void Fun2(){...};
}
上面的组织形式我们将函数的具体实现和声明放到了一起,有时候我们并不想看到函数的具体实现,只希望能一眼看到的全部都是函数的接口界面。我们可以采用如下的方式将函数的界面和具体实现分开。
namespace A
{
void Fun1();
void FUn1();
}
void A::Fun1(){/*...*/}
void A::Fun2(){/*...*/}
void A:Fun3(); //错误,A里并没有Fun3()
命名空间是一个作用域,因此它具有普通作用域的规则。
double A::Fun()
{
using B::Fun1; //使用B命名空间的函数Fun1;
using C::Var1; //使用C命名空间的变量Var1;
void Fun1(Var1); //B::Fun1
}
namespace A
{
using namespace B; //使用命名空间B中所有实体
}
有时候我们同一个命名空间在面向不同的用户时,可能需要提供不同的界面。比如我们有一个命名空间里面定义了关于串口的一些实体。我们给一个开发中的程序提供的接口可能包括:打开串口,设置波特率,设置校验位等。但是面向一个最终用户时,我们可能只需要给他提供一个打开串口接口就够了。这便是使用多重界面的意义。
namespace A
{
void Fun1();
void Fun2();
void Fun3();
}
namespace A_Interface1
{
using A::Fun1;
}
namespace A
{
void Fun1();
void Fun2();
void Fun3();
}
namespace A_Interface1
{
void Fun1(){A::Fun1();}
}
这里为了书写方便将A_Interface1中Fun1的声明和定义放到一起了。上面这种界面实现形式保证了A_Interface1中的Fun1有一定的自主性,当A中的Fun1改变时,我们可以在A_Interface1中的Fun1进行进一步的调整,或者干脆不用A中的Fun1重新实现Fun1.这种界面处理方式去除了一定的耦合性,基本已经能够满足大部分需求了。
3.当然我们也可以不重新定义一个命名空间直接使用原来命名空间的名字,但是这样容易误导程序设计者以外的人(让看到这个接口的人以外该命名空间只有声明的这些实体)。
namespace A
{
void Fun1();
void Fun2();
void Fun3();
}
//使用接口的文件包含下面这个声明或其所在的文件
namespace A
{
void Fun1();
}
我们知道不同命名空间的变量名可以重复,这有助于第三方将两个不同人写的代码进行整合。有时候我们并不想我们的某些代码被其他人进行整合,但是也想利用命名空间的优势——可以让变量名重复。这时候使用无名命名空间就很有价值了:第一没有名字,其他地方无法引用进去;第二因为是命名空间它里面的变量可以和其他命名空间中变量的名字重复。
无名命名空间可以在本编译单元(所在文件)处调用,没有这一规则就永远都用不到了。需要注意的是,不同编译单元中的无名命名空间不同。
namespace A
{
class TypeA{...};
void Fun1(TypeA a){...};
}
void Fun(A::TypeA a)
{
Fun1(a); //可以,会在TypeA所在命名空间A找到Fun1
Fun1(2); //不行
}
当然,命名空间本身必须在作用域里,函数也必须在寻找和使用之前声明。
namespace A
{
class TypeA{...};
bool operator==(const A&,std::string&);
}
void Fun(A::TypeA a,std::string str)
{
if(a == str)
{...}
}
在上例中表达式a==str
中有两种数据类型,且这两种数据类型所在的命名空间A,std都定义了operator==(string的运算符的定义在std中),于是重载解析规则就会被调用。由于std::operator不以TypeA为参数,所有最后编译器会调用A::operator==.
3. 当一个类的成员调用一个函数时,编译器查找函数时偏向于在同一类和其基类中查找函数,而不是在被调用函数其他参数类型所在的命名空间查找。但这一规则对运算符(如+,-)查找并不适用。
我们在给命名空间取名字的时候,如果太短(比如上面的A)很可能出现冲突。起太长又太麻烦。这时候我们将长名字的命名空间在合适的地方取个别名可能会更好些。格式如下:
namespace A = LongNameNamespaceA;
这种替换的方式和C语言中的宏定义非常相似,因此别名另外一个特别用于的地方就是使代码对命名空间的依赖降低。比如我们有一个大型程序依赖于一个命名空间LibA,现在需要版本升级将所有引用LibA中的代码替换为LibA_Plus.如果使用起别名的方式,我们不用再代码中逐个查找将LibA::
替换为LibA_Plus
.而是简单的在命名空间的别名定义处稍作修改即可。
namespace A = LibA;
//替换为
namespace A = LibA_Plus;
但也需要注意过多的使用命名空间别名也容易造成混乱。
前面我们说了命名空间里可以包含其他的命名空间,下面给出命名空间组合时的一些具体细节。
namespace A
{
using namespace B;
}
namespace B
{
fill(char C);
}
A::fill(char C) //错误 A中并没有声明fill函数
{...}
namespace B
{
class String{...};
String operator+(const String&,const String&);
String operator+(const String&,const char*);
}
namespace A
{
using B::String;
using B::operator+; //使用B中所定义的全部(两个)+运算
}
namespace LibA
{
class String{...};
template<class T>class Vector{...};
//其他实体
}
namespace LibB
{
class String{...};
template<class T> class Vector{...};
//其他实体
}
namespace LibC
{
using namespace LibA;
using namespace LibB;
using LibA::String; //偏向使用LibA中的String
using LibB::Vector; //偏向使用LIbB中的Vector
//其他实体
}
template<class T>class A_Vector:public LibA::Vector<T>{...};
typedef LibB::String B_String;
//stdio.h
namespace std
{
int printf(const char*...);
}
using namespace std; //包含头文件后不需要再重复这句话了
//...
#include <cstdio> //对新库也进行包含
using std::printf;
C++中提供了新的头文件,头文件中没有使用using指令,这使不希望全部的标准库实体都隐式的使用成为了可能。
//cstdio.h
namespace std
{
int printf(const char* ...);
//...
}
//file1.c
namespace A
{
int a;
}
//其他代码
namespace A
{
int b;
}
//file2.h
namespace A
{
int c;
}
标签:ams 大型 double 标准库 细节 地方 声明和定义 comment htm
原文地址:http://www.cnblogs.com/mdumpling/p/7761671.html