您的位置:首页 > 编程语言 > C语言/C++

The C++ Programming Language 第八章 笔记

2005-04-05 14:30 471 查看
The C++ Programming Language 第八章 笔记
名字空间和异常
james chen
050320
8.1 模块化和界面 暂略过
*********************
8.2 名字空间
*********************
名字空间是一种描述逻辑分组的机制。也就是说,如果一些声明按照某种准则在逻辑上属同一类,则可以将他们放入同一个名字空间,以表明这个事实。如菜单之edit:
namespace Edit
{
void copy(char){}
void cut(char){}
void paste(char){}
void delete(char){}
}
namespace File
{
void open();
void save();
void exit();
}
对于名字空间内成员的定义,一般来说应放在名字空间外与其声明分开:
namespace Edit
{
void copy(char);
void cut(char);
void paste(char);
void delete(char);
}
void Edit::copy(char x){}
void Edit::cut(char x){}
void Edit::paste(char x){}
void Edit::delete(char x){}
将声明与定义分离后,用户看到的只是声明,而函数体将被放在什么地方,用户则无需太多关心。
请注意,每个函数只能一个声明和定义。
名字空间成员必须在空间内声明,不能在名字空间外引入新成员:
void Edit::search(char); //错,必须在namespace里声明。
名字空间成员函数的定义与其声明必须遵循函数准则:
void Edit::cpoy(char x){} //错,Edit中没有cpoy()
void Edit::cut(int a){} //错,要求形参为char
一个namespace也就是一个作用域。常规的局部作用域、全局作用域和类也是namespace。
理想的情况是,程序里的每个实体都属于某个可以识别的逻辑单位,所以,一个好程序里的每个声明都应该位于某个名字空间里,以此指明它在程序中所扮演的角色。

8.2.1 带限定词的名字
因为名字空间是作用域,所以普通的作用域规则也对名字空间成立。因此,如果一个名字先前已在本名字空间里或者其外围作用域里声明过,它就可以直接使用了,不必再进一步为它操心,也可以使用来自另一个名字空间的名字,但需要用该名字所属的名字空间作为限定词。如:
void Edit::copy(char x)
{
cut(x); //不需要限定词,因为cut和copy同处于Edit名字空间
File::open(); //open是File中的,所以要限定词
}

8.2.2 使用声明
当某个名字在它自己的名字空间之外频繁使用时,每次使用都得加上限定词,这样做的确很讨厌,如
void Edit::copy(char x)
{
cut(x);
File::open();
File::open();
File::open();
File::open();
.....
}
反复的书写限定词很让人讨厌,这些多余的东西可以通过一个“使用声明”而清除掉,如:
void Edit::copy(char x)
{
using File::open; //使用来自File的open成员
cut(x);
open();
open();
open();
open();
}
如果a namespace使用了b namespace中的成员,我们可以把有关的使用声明放在a的定义里,如:
namespace Edit
{
void copy(char);
void cut(char);
void paste(char);
void delete(char);
using File::open; //使用File中的open成员
}
void Edit::copy(char x)
{
cut(x);
open();
}
这样一来,我们可以完全把open当作Edit的内部成员使用,而无须关系它是不是来自其它成员。

8.2.3 使用指令
一个“使用指令”可以将一个namespace的所有名字变成可用的,在用户看来,几乎就和它本身的名字一样。如:
namespace Edit
{
void copy(char);
void cut(char);
void paste(char);
void delete(char);
using namespace File; //使用来自File中的所有名字都可用
}
void Edit::copy(char x)
{
cut(x);
open();
exit();
}

8.2.4 多重界面
接口(界面)的作用就是尽可能减小程序不同部分之间的相互依赖。最小的接口将会使程序易于理解,有很好的数据隐蔽性质,容易修改,也编译得更快。
namespace Edit
{
void copy(char*);
void cut(char*);
}
namespace File
{
void open();
void save();
}
namespace EF_interface
{
using Edit::copy;
using Edit::cut;
using File::open;
using File::save;
}
我们在EF_interface中使用Edit/File成员,再将其开放给用户,用户通过EF_interface间接使用Edit/File成员。当然也可以在EF_interface中新增成员,在成员定义再使用Edit/File成员,如:
namespace EF_interface
{
void copy(char*);
void cut(char*);
void open();
void save();
}
void EF_interface::copy(char* a){Edit::copy(a);}
void EF_interface::cut(char* a){Edit::cut(a);}
void EF_interface::open(){File::open();}
void EF_interface::save(){File::save();}
间接使用接口的另一种方式。
晕,这一节不大明白。。。。
8.2.5 避免名字冲突
名字空间就是为了表示逻辑结构。最简单的这类结构就是分清楚由一个人写的代码与另一个人写的代码。
当我们从相互独立的部分组合起一个程序,在这些单独模块中若有相同名字时,就产生冲突,考虑:
//my.h
char f(char);
int f(int);
//your.h
char f(char);
int f(int);
有了这些定义以后,第三方就很难同时使用my.h和your.h。一个很明显的解决办法就是将每组声明包含在各自的名字空间中:
//my.h
namespace My{
char f(char);
int g(int);
}
//your.h
namespace Your{
char f(char);
int g(int);
}
现在就可以在第三方中通过使用声明、使用指令来使用my.h和your.h中的声明,如:
#include "my.h"
#include "your.h"
using My::f;
using Your::g;
void main()
{
char a=f('a'); //使用my.h中的f;
char b=Your::f('a'); //使用Your.h中的f
}
也可以把先using namespace My,要用Your空间成员时,再用限定词Your::来使用。。

8.2.5.1 无名名字空间
namespace
{
int a;
void f();
}
相当于:
namespace $$$
{
int a;
void f();
}

8.2.6 名字查找
一个取T类型参数的函数常常与T类型本身定义在同一个名字空间里。因此,如果在使用一个函数的环境中无法找到它,我们就去查看它的参数所在的名字空间,如:
namespace AA
{
class Date{};
bool operator==(const Date&,const std::string&);
std::string format(const Date&);
}
void f(AA::Date d,int i)
{
std:string s=format(d); //OK,AA::format(d)
std:string t=format(i); //错,在作用域里没有format(int)
}
与显式地使用限定比,这个查找规则能使程序员节省许多输入,而且又不会以“使用指令”那样的方式污染名字空间。这个规则对于运算符的运算对象和模板参数特别有用,因为在那里使用显式限定是非常麻烦的。
请注意,名字空间本身必须在作用域里,而函数也必须在它被寻找和使用之前声明。
一个函数形参可以来自多个名字空间,如:
void f(AA::Date d,std::string s)
{
if(d==s)
{...}
else if(d=="august 4,1914")
{...}
}
对于这种情况,我们将在调用的作用域里,在每个参数(包括每个参数的类和基类)的名字空间里查找函数,而后对找到的所有函数执行普通的重载解析规则。
对于上面的d==s,我们将在f()的作用域里,在名字空间std和AA中查找最区域d==s的运算符,在std中存在一个operatror==,但它不以Date和string为参数,而AA的operator==以Date,string为参数,所以就用AA::operator==。
当一个类的成员调用一个命名函数时,函数查找时应当偏向于同一个类及其基类的其他成员,而不是基于其他参数的类型可能发现的函数。

8.2.7 名字空间别名
如果用户给他们的名字空间取很短的名字,不同名字空间的名字也可能出现冲突:
namespace A{ //A太短,易引起冲突
class String{};
}
A::String s1="asdfasf";
A::String s2="kjhlegd";
然而长名字在实际代码中用起来又不是很方便:
namespace Akfsdlflksldflksdflj{ //又太长,使用起来不方便
class String{};
}
Akfsdlflksldflksdflj::String s3="asdfasf";
Akfsdlflksldflksdflj::String s4="kjhlegd";
这种两难境地通过为长名字取别名的方式来解决:
namespace ASA=Akfsdlflksldflksdflj; //替长名字取别名,嘿,有点引用的感觉
ASA::String s3="asdfasf";
ASA::String s4="kjhlegd";
名字空间别名也使用户能够引用“某一个库”,并通过惟一的一个声明来定义那个库到底是什么:
namespace Lib=Foundation_library_v2r11;
Lib::set s;
Lib::string s5="sdfsfsf";
使用Lib而不是Foundation_library_v2r11,这使得程序极大简化,且代码维护也变得简单,当需要更新到新版时,只要将别名处重新定义即可,而不需要到处修改。
当然,过多的使用别名也会引用混乱。

8.2.8 名字空间组合
我们常常需要从现存的界面出发组合出新的界面,如:
namespace His_string{
class String{/*....*/};
String operator+(const String&,const String&);
String operator+(const String&,const char*);
void fill(char);
//.....
}
namespace Her_vector{
template<class T>class Vector{/*...*/};
//....
}
namespace My_lib{
using namespace His_string;
using namespace Her_vector;
void my_fct(String&);
}
有了这些,我们就可以在My_lib的基础上写程序了:
void f()
{
My_lib::String s="Byosdf"; //这里使用的是My_lib::His_string::String
//...
}
using namespace My_lib;
void g(Vector<String>& vs) //等同于void g(Vector<My_lib::His_string::String>& vs)
{
//...
my_fct(vs[5]); //调用的是My_lib::my_fct(vs[5])
//...
}
如果显式限定的名字(如String)在所说的名字空间里没有声明,编译器就会去查看"使用指令"的名字空间(如His_string)。
按理想情况一个名字空间应该:
[1]描述了一个具有逻辑统一性的特征集合。
[2]不为用户提供对无关特征的访问。
[3]不给用户强加任何明显的记述负担。

8.2.8.1 选择
有时我们只是想从一个名字空间里选用几个名字,如:
namespace His_string{
class String{/*...*/};
String operator+(const String&,const String&);
String operator+(const String&,const char*);
}
但是,除非我是His_String的设计者或者维护者,否则这一做法很快就会陷入混乱。任何“真正的”Hit_String定义的修改都不会在这个声明中反映出来。通过“使用声明”来做,可以使从名字空间里选择一些特征的事变得更加明确:
namespace My_String{
using His_String::String;
using His_String::operator+; //使用任何+
}
“使用声明”将具有所指定的名字的每个声明都带入作用域。特别地,通过一个“使用声明”就可以将一个重载函数的所有变形都带进来。
在这种方式下,如果His_String的维护者给String增加了一个成员函数,或者拼接了运算符增加了一个重载版本,这种修改将自变成My_String的用户可用的东西。相反,如果某些特征被删除,对My_String的影响也将被编译器检查出来。

8.2.8.2 组合与选择
将组合(使用指令)和选择(使用声明)结合起来可获得更多的灵活性。考虑:
namespace His_lib{
class String{/*...*/};
template<class T>class Vector{/*...*/};
//...
}
namespace Her_lib{
class String{/*...*/};
template<class T>class Vector{/*...*/};
//...
}
namespace My_lib{
using namespace His_lib; //使用来自His_lib的所有东西
using namespace Her_lib; //使用来自Her_lib的所有东西
using His_lib::String; //使用来自His_lib的String
using Her_lib::Vector; //使用来自Her_lib的Vector
template<class T>class List{/*...*/}; //新增
//...
}
在查看一个名字空间时,其中显式声明的名字(包括作用声明)优先于在其他作用域中的那些通过“使用指令”才能访问的名字。这样在,My_lib的用户将会看到,对于String和Vector名字冲突的解析分别偏向于His_lib::String和Her_Vector。List总是指My_lib::List,而不管在His_string和Her_Vector中是否存在List,因为重载。
一般来说,在将一个名字引进一个新的名字空间时,最好让其保持不变,这样的话我们就不用记住两个名字。当然,这也不是绝对的,有时候用新名字将更好:
namespace lib2{
using namespace His_lib;
using namespace Her_lib;
using His_lib::String;
using Her_lib::Vector;
typedef Her_lib::String Her_string; //重命名
//......
}

8.2.9 名字空间和老代码 过
8.2.9.1 名字空间和c 过
8.2.9.2 名字空间和重载
重载可以跨名字空间工作。对于我们能以最小的代价将现存的库修改为使用名字空间的东西而言,这特征是必不可少的:
//老的a.h
void f(int);
//老的b.h
void f(char);
//
老的user.c
#include "a.h"
#include "b.h"
void g()
{f('a');} //调用b.h中的f()
将其升级到使用名字空间的版本,无须修改实际代码:
//新的a.h
namespace A{
void f(int);}
//新的b.h
namespace B{
void f(char);}
//
新的user.c
#include "a.h"
#include "b.h"
using namespace A;
using namespace B;
void g()
{f('a');} //调用b.h中的f()

8.2.9.3 名字空间是开放的
名字空间是开放的,也就是说,你可以通过多个名字空间声明给它加入名字。例如:
namespace A{
int f(); //现在A有成员f()
}
namespace A{
int g(); //现在则有f()和g()
}
通过这种方式,我们就能支持一个名字空间中放入几个大的程序片段,其方式就像老的库和应用存在于同一个全局名字空间里那样。为做到这些,我们必须允许一个名字的定义分布到多个头文件和源代码里。
//my.h //有两个函数,g()和f()
void f();
#include<stdio.h>
int g();
//....
重写:
//my.h //haha namespace中有g(),f()
namespace Haha{
void f();
}
#include<stdio.h>
namespace Haha{
int g();
}
//....
在写新代码时,名字空间要尽量的小,而不是将代码的主要片段都塞进同一个名字空间。
在定义一个名字空间里已经声明过的成员时,更安全的方式是采用Haha::语法形式,而不是重新打开Haha。如:
void Haha::ff() //错误,在Haha里没有void ff();
{}
编译器能捕捉到这个错误,然而,由于在一个空间里可以定义新函数,编译器就无法报错!!如:
namespace Haha{
void ff() //哎,ff被作为新成员写入Haha
{/*.....*/}
}
编译器并不知道是写错了,我们的本意是定义void f(),现在函数没定义好,还画蛇添足。。

*********************
8.3 异常
*********************
当一个程序由一些相互分离的模块组成时,特别是当这些模块来自某些独立开发的库时,错误处理的工作就需要分成两个相互独立的部分:
[1]一方报告出那些无法在局部解决的错误。
[2]另一方处理那些在其他地方检查出的错误。

晕,看得稀里糊涂的,以后再来看吧。
8.4 忠告
[1]用名字空间表示逻辑结构。
[2]将每个非局部的名字放入某个名字空间里,除了main()之外。
[3]名字空间的设计应该让你能很方便地使用它,而又不会意外地访问了其他的无关空间。
[4]避免对名字空间使用很短的名字。
[5]如有需要,通过名字空间别名去缓和名字空间名的影响。
[6]避免给你的名字空间的用户添加太大的记法负担。
[7]在定义名字空间的成员时使用namespace::member的形式。
[8]只在转换时,或者在局部作用域里,才用using namespace。
[9]利用异常去松驰“错误”处理代码和正常处理代码之间的联系。
[10]采用用户定义类型作为异常,不用内部类型。
[11]当局部控制结构足以应付问题时,不要用异常。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: