小何与小乌的故事

放假了,笔记暂缓

By robinhesky | 01月 23, 2008

放假了,好好过年!笔记就暂缓了!

Tags: 未分类 | No Comments »

thinking in java 第六、七章

By robinhesky | 01月 21, 2008

 1.类再生

Java引人注目的一项特性是代码的重复使用或者再生。但最具革命意义的是,除代码的复制和修改以外,我们还能做多得多的其他事情。”

在象C那样的程序化语言里,代码的重复使用早已可行,但效果不是特别显著。与Java的其他地方一样,这个方案解决的也是与类有关的问题。我们通过创建新类来重复使用代码,但却用不着重新创建,可以直接使用别人已建好并调试好的现成类。但这样做必须保证不会干扰原有的代码。

有两个达到这一目标的方法:

(1)在新类里简单地创建原有类的对象。我们把这种方法叫作“合成”,因为新类由现有类的对象合并而成。我们只是简单地重复利用代码的功能,而不是采用它的形式。

(2)创建一个新类,将其作为现有类的一个“类型”。我们可以原样采取现有类的形式,并在其中加入新代码,同时不会对现有的类产生影响。这种魔术般的行为叫作“继承”(Inheritance),涉及的大多数工作都是由编译器完成的

每种非基本类型的对象都有一个toString()方法。若编译器本来希望一个String,但却获得某个这样的对象,就会调用这个方法。所以在下面这个表达式中:
System.out.println(”source = ” + source) ;
编译器会发现我们试图向一个WaterSource添加一个String对象(”source =”)。这对它来说是不可接受的,因为我们只能将一个字串“添加”到另一个字串,所以它会说:“我要调用toString(),把source转换成字串!”经这样处理后,它就能编译两个字串,并将结果字串传递给一个System.out.println()。每次随同自己创建的一个类允许这种行为的时候,都只需要写一个toString()方法。

2.初始化的顺序

我个人的理解是,如果有派生的关系,先从根类开始初始化(其它类都由它派生出来),如果各个类中有static成员,那最初初始化的是static,再从根类开始初始化。而根类的初始化顺序应为先初始化成员,再调用构造函数进行初始化,其派生类也依从这样的顺序。而如果的确要实行清除操作,则先清除最底层的派生类,与初始化顺序刚好相反,这样做的理由时,再清除过程中派生类可能还用到基类的方法。

3.关于多态性

典型的例子如下:

public class PrivateOverride {
  //private static Test monitor = new Test();
  private void f(String s) {
    System.out.println(”private f()”+s);
  }
  public static void main(String[] args) {
   PrivateOverride po = new Derived();
    po.f(”哈哈”);
    /*monitor.expect(new String[] {
      “private f()”
    });*/
  }
}

class Derived extends PrivateOverride {
    private void f(String s) {
    System.out.println(”public f()”+s);
  }
} ///:~

编译不会报错,但结果不是输出:publicf()哈,因为再基类里的private方法表明它是final的,即是不会被重载的,这时侯,虽然f函数名称相同,但派生类中的f相当于以个全新的类,而基类中的f函数对派生类来说是不可见的,所以输出的为privatef)哈

再方法前加了private,static,final都可认为是早期绑定!

多态性”(Polymorphism)从另一个角度将接口从具体的实施细节中分离出来,亦即实现了“是什么”与“怎样做”两个模块的分离。利用多形性的概念,代码的组织以及可读性均能获得改善。此外,还能创建“易于扩展”的程序。无论在项目的创建过程中,还是在需要加入新特性的时候,它们都可以方便地“成长”。

3.方法调用的绑定

将一个方法调用同一个方法主体连接到一起就称为“绑定”(Binding)。若在程序运行以前执行绑定(由编译器和链接程序,如果有的话),就叫作“早期绑定”。大家以前或许从未听说过这个术语,因为它在任何程序化语言里都是不可能的。C编译器只有一种方法调用,那就是“早期绑定”。
后期绑定”,它意味着绑定在运行期间进行,以对象的类型为基础。后期绑定也叫作“动态绑定”或“运行期绑定”。若一种语言实现了后期绑定,同时必须提供一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。也就是说,编译器此时依然不知道对象的类型,但方法调用机制能自己去调查,找到正确的方法主体。不同的语言对后期绑定的实现方法是有所区别的。但我们至少可以这样认为:它们都要在对象中安插某些特殊类型的信息。
Java中绑定的所有方法都采用后期绑定技术,除非一个方法已被声明成final,private,static。这意味着我们通常不必决定是否应进行后期绑定——它是自动发生的。

4.抽象类和抽象方法

Java专门提供了一种机制,名为“抽象方法”。它属于一种不完整的方法,只含有一个声明,没有方法主体。下面是抽象方法声明时采用的语法:
abstract void X();
包含了抽象方法的一个类叫作“抽象类”。如果一个类里包含了一个或多个抽象方法,类就必须指定成abstract(抽象)。否则,编译器会向我们报告一条出错消息。
若一个抽象类是不完整的,那么一旦有人试图生成那个类的一个对象,编译器又会采取什么行动呢?由于不能安全地为一个抽象类创建属于它的对象,所以会从编译器那里获得一条出错提示。通过这种方法,编译器可保证抽象类的“纯洁性”,我们不必担心会误用它。
如果从一个抽象类继承,而且想生成新类型的一个对象,就必须为基础类中的所有抽象方法提供方法定义。如果不这样做(完全可以选择不做),则衍生类也会是抽象的,而且编译器会强迫我们用abstract关键字标志那个类的“抽象”本质。

深入浅出MFC-MFC六大关键技术笔记

By robinhesky | 01月 20, 2008

mfc类别阶层

上图为MFC各主要类的关系图

1.RTTI(执行时期类型识别)

以shape 为例,希望达到的技术效果如下,再程序执行期间可以识别对象的类型:
CSquare* pSquare = new CSquare;
cout << pSquare->IsKindOf(CSquare); // 应该获得1(TRUE)
cout << pSquare->IsKindOf(CRect); // 应该获得1(TRUE)
cout << pSquare->IsKindOf(CShape); // 应该获得1(TRUE)

以MFC 的类别阶层来说,希望:
CMyDoc* pMyDoc = new CMyDoc;
cout << pMyDoc->IsKindOf(CMyDoc); // 应该获得1(TRUE)
cout << pMyDoc->IsKindOf(CDocument); // 应该获得1(TRUE)
cout << pMyDoc->IsKindOf(CCmdTarget); // 应该获得1(TRUE)
cout << pMyDoc->IsKindOf(CWnd); // 应该获得0(FALSE)

要达到RTTI 的能力,我们(类别库的设计者)一定要在类别构造起来的时候,记录必要的信息,以建立型录。型录中的类别信息,最好以串行(linked list)方式串接起来,将来方便一一比对。我们这份「类别型录」的串行元素将以CRuntimeClass 描述之,那是一个结构。结构如下:

struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema; // schema number of the loaded class
CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
CRuntimeClass* m_pBaseClass;
// CRuntimeClass objects linked together in simple list
static CRuntimeClass* pFirstClass; // start of class list
CRuntimeClass* m_pNextClass; // linked list of registered classes

};

每一个类别都能拥有这样一个CRuntimeClass 成员变量,并且最好有一定的命名规则(例如在类别名称之前冠以”class” 作为它的名称),然后,经由某种手段将整个类别库构造好之后,「类别型录」能呈现类似这样的风貌:

类别型录表

上图的实现, 是通过以系列宏的声明实现的:

#define DECLARE_DYNAMIC(class_name) \
public: \
static CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const

但这样并没有把类型表连接起来,还要通通过下面的宏定义来实现:

#define IMPLEMENT_DYNAMIC(class_name, base_class_name) \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL)

#define _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name,wSchema,pfnNew) \
static char _lpsz##class_name[] = #class_name; \
CRuntimeClass class_name::class##class_name = { \
_lpsz##class_name, sizeof(class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL }; \
static AFX_CLASSINIT _init_##class_name(&class_name::class##class_name); \结构体的构造函数

CRuntimeClass* class_name::GetRuntimeClass() const \
{ return &class_name::class##class_name; } \

#define RUNTIME_CLASS(class_name) \
(&class_name::class##class_name)

而其中负责把各个类别连接起来的,主要靠下面这段宏程序:

struct AFX_CLASSINIT
{ AFX_CLASSINIT(CRuntimeClass* pNewClass); }

AFX_CLASSINIT::AFX_CLASSINIT(CRuntimeClass* pNewClass)
{
pNewClass->m_pNextClass = CRuntimeClass::pFirstClass;
CRuntimeClass::pFirstClass = pNewClass;

}

这样利用静态指针PFIRSTCLASS,就可以把各个类型别录表连接起来,每当创建一个CRUNTIMECLASS后,pfirstclass都指向当前结构体。

对于classcobject这个头部,因为它的基类为Null,故要特别设计

// in header file
class CObject
{
public:
virtual CRuntimeClass* GetRuntimeClass() const;

public:
static CRuntimeClass classCObject

};

// in implementation file
static char szCObject[] = “CObject”;
struct CRuntimeClass CObject::classCObject =
{ szCObject, sizeof(CObject), 0xffff, NULL, NULL };
static AFX_CLASSINIT _init_CObject(&CObject::classCObject);
CRuntimeClass* CObject::GetRuntimeClass() const
{
return &CObject::classCObject;

};

并且,CRuntimeClass 中的static 成员变量应该要初始化

CRuntimeClass* CRuntimeClass::pFirstClass = NULL

这样,整个类型别录表就建立起来了

那么类型识别就可以很容易实现:

为CObject 加上一个IsKindOf 函数,于是此函数将被所有类别继承。它将把
参数所指定的某个CRuntimeClass 对象拿来与类别型录中的元素一一比对。比
对成功(在型录中有发现),就传回TRUE,否则传回FALSE:
// in header file
class CObject
{
public:

BOOL IsKindOf(const CRuntimeClass* pClass) const;
};
// in implementation file
BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const
{
CRuntimeClass* pClassThis = GetRuntimeClass();
while (pClassThis != NULL)
{
if (pClassThis == pClass)
return TRUE;
pClassThis = pClassThis->m_pBaseClass;
}
return FALSE; // walked to the top, no match

}

2.Dynamic Creatiion(动态生成)

如果能够把类别的大小记录在类别型录中,把构造函数(注意,这里并非指C++ 构造式,而是指即将出现CRuntimeClass::CreateObject)也记录在类别型录中,当程序在执行时期获得一个类别名称,它就可以在「类别型录网」中找出对应的元素,然后调用其构造函数(这里并非指C++ 构造式),产生出对象。

于是CRuntimeClass有了变化

struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema; // schema number of the loaded class
CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
CRuntimeClass* m_pBaseClass;
CObject* CreateObject();
static CRuntimeClass* PASCAL Load();
// CRuntimeClass objects linked together in simple list
static CRuntimeClass* pFirstClass; // start of class list
CRuntimeClass* m_pNextClass; // linked list of registered classes

}

为了适应CRuntimeClass 中新增的成员变量, 再添两个宏如下:

#define DECLARE_DYNCREATE(class_name) \
DECLARE_DYNAMIC(class_name) \
static CObject* PASCAL CreateObject();

#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \
class_name::CreateObject)

从宏的定义可以很清楚看出,拥有动态生成(Dynamic Creation)能力的类别库,
必然亦拥有执行时期类型识别(RTTI)能力,因为_DYNCREATE 宏涵盖了_DYNAMIC
宏。

现在,我们开始仿真动态生成。首先在main 函数中加上这一段码:

void main()
{

//Test Dynamic Creation
CRuntimeClass* pClassRef;
CObject* pOb;
while(1)
{

if ((pClassRef = CRuntimeClass::Load()) == NULL)
break;
pOb = pClassRef->CreateObject();
if (pOb != NULL)
pOb->SayHello();
}
}
CRuntimeClass::CreateObject 和 CRuntimeClass::Load 如㆘:
// in implementation file
CObject* CRuntimeClass::CreateObject()
{
if (m_pfnCreateObject == NULL)
{
TRACE1(”Error: Trying to create object which is not ”
“DECLARE_DYNCREATE \nor DECLARE_SERIAL: %hs.\n”,
m_lpszClassName);
return NULL;
}
CObject* pObject = NULL;
pObject = (*m_pfnCreateObject)();
return pObject;
}
CRuntimeClass* PASCAL CRuntimeClass::Load()
{
char szClassName[64];
CRuntimeClass* pClass;
// JJHOU : instead of Load from file, we Load from cin.
cout << “enter a class name… “;
cin >> szClassName;
for (pClass = pFirstClass; pClass != NULL; pClass = pClass->m_pNextClass)
{
if (strcmp(szClassName, pClass->m_lpszClassName) == 0)
return pClass;
}
TRACE1(”Error: Class not found: %s \n”, szClassName);
return NULL; // not found

}

3.Persistence(永续生存机制)

对象导向有一个术语:Persistence,意思就是把对象永久保留下来,一般是把对象写到文件里去。

MFC 有一套Serialize 机制,目的在于把档名的选择、文件的开关、缓冲区的建立、资
料的读写、萃取运算子(>>)和嵌入运算子(<<)的多载(overload)、对象的动态生成

都包装起来。
上述Serialize 的各部份工作,除了资料的读写和对象的动态生成,其余都是支节。动态
生成的技术已经解决,让我们集中火力,分析资料的读写动作。

06 00 ;CObList elements count
07 00 ;class name string length
43 53 74 72 6F 6B 65 ;”CStroke”
02 00 ;DWordArray size
28 00 13 00 ;point
28 00 13 00 ;point
0A 00 ;class name string length
43 52 65 63 74 61 6E 67 6C 65 ;”CRectangle”
11 00 22 00 33 00 44 00 ;CRect
07 00 ;class name string length
43 43 69 72 63 6C 65 ;”CCircle”
55 00 66 00 77 00 ;CPoint & radius
07 00 ;class name string length
43 53 74 72 6F 6B 65 ;”CStroke”
02 00 ;DWordArray size
28 00 35 00 ;point
28 00 35 00 ;point
0A 00 ;class name string length
43 52 65 63 74 61 6E 67 6C 65 ;”CRectangle”
11 00 22 00 33 00 44 00 ;CRect
07 00 ;class name string length
43 43 69 72 63 6C 65 ;”CCircle”
55 00 66 00 77 00 ;CPoint & radius

我们可以在每次记录对象内容的时候,先写入一个代码,表示此对象之类别是否曾在档案中记录过了。如果是新类别,乖乖地记录其类别名称;如果是旧类别,则以代码表示。这样可以节省文件大小以及程序用于解析的时间。

还有一个问题。文件的「版本」如何控制?旧版程序读取新版文件,新版程序读取旧版文件,都可能出状况。为了防弊,最好把版本号码记录上去。最好是每个类别有自己的版本号码

20 03 84 03 ;Document Size
06 00 ;CObList elements count
FF FF ;new class tag
02 00 ;schema
07 00 ;class name string length
43 53 74 72 6F 6B 65 ;”CStroke”
02 00 ;DWordArray size
28 00 13 00 ;point
28 00 13 00 ;point
FF FF ;new class tag
01 00 ;schema
0A 00 ;class name string length
43 52 65 63 74 61 6E 67 6C 65 ;”CRectangle”
11 00 22 00 33 00 44 00 ;CRect
FF FF ;new class tag
01 00 ;schema
07 00 ;class name string length
43 43 69 72 63 6C 65 ;”CCircle”
55 00 66 00 77 00 ;CPoint & radius
01 80 ;old class tag
02 00 ;DWordArray size
28 00 35 00 ;point
28 00 35 00 ;point
03 80 ;old class tag
11 00 22 00 33 00 44 00 ;CRect
05 80 ;old class tag
55 00 66 00 77 00 ;CPoint & radius

类别之能够进行文件读写动作,前提是拥有动态生成的能力,所以,MFC 设计了两个宏
DECLARE_SERIAL 和IMPLEMENT_SERIAL:

#define DECLARE_SERIAL(class_name) \
DECLARE_DYNCREATE(class_name) \
friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb);
#define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
class_name::CreateObject) \
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
return ar; } \

为了在每一个对象被处理(读或写)之前,能够处理琐屑的工作,诸如判断是否第一次
出现、记录版本号码、记录文件名等工作,CRuntimeClass 需要两个函数Load 和Store

struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema; // schema number of the loaded class
CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
CRuntimeClass* m_pBaseClass;
CObject* CreateObject();
void Store(CArchive& ar) const;
static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);
// CRuntimeClass objects linked together in simple list
static CRuntimeClass* pFirstClass; // start of class list
CRuntimeClass* m_pNextClass; // linked list of registered classes

};

// Runtime class serialization code
CRuntimeClass* PASCAL CRuntimeClass::Load(CArchive& ar, UINT* pwSchemaNum)
{
WORD nLen;
char szClassName[64];
CRuntimeClass* pClass;
ar >> (WORD&)(*pwSchemaNum) >> nLen;
if (nLen >= sizeof(szClassName) || ar.Read(szClassName, nLen) != nLen)
return NULL;
szClassName[nLen] = ”;
for (pClass = pFirstClass; pClass != NULL; pClass = pClass->m_pNextClass)
{
if (lstrcmp(szClassName, pClass->m_lpszClassName) == 0)
return pClass;
}
return NULL; // not found
}
void CRuntimeClass::Store(CArchive& ar) const
// stores a runtime class description
{
WORD nLen = (WORD)lstrlenA(m_lpszClassName);
ar << (WORD)m_wSchema << nLen;
ar.Write(m_lpszClassName, nLen*sizeof(char));
}

4.Message Mapping(消息映射)

但是,MFC 之中用来处理消息的C++ 类别,并不呈单鞭发展。作为application framework
的重要架构之一的document/view,也具有处理消息的能力(你现在可能还不清楚什么是
document/view,没有关系)。因此,消息藉以攀爬的路线应该有横流的机会,如下图所示:

消息路由

为了尽量降低对正常(一般)类别声明和定义的影响,我们希望,最好能够像RTTI 和
Dynamic Creation 一样,用一两个宏就完成这巨大蜘蛛网的构造。最好能够像
DECLARE_DYNAMIC 和IMPLEMENT_DYNAMIC 宏那么方便。

struct AFX_MSGMAP
{
AFX_MSGMAP* pBaseMessageMap;
AFX_MSGMAP_ENTRY* lpEntries;
};
struct AFX_MSGMAP_ENTRY // MFC 4.0 format
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id’s
UINT nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
typedef void (CCmdTarget::*AFX_PMSG)(void);

然后我们定义一个宏:#define DECLARE_MESSAGE_MAP() \
static AFX_MSGMAP_ENTRY _messageEntries[]; \
static AFX_MSGMAP messageMap; \
virtual AFX_MSGMAP* GetMessageMap() const

于是,DECLARE_MESSAGE_MAP 就相当于声明了这样一个数据结构:

messagemap

于是,DECLARE_MESSAGE_MAP 就相当于声明了这样一个数据结构:
这个数据结构的内容填塞工作由三个宏完成: #define BEGIN_MESSAGE_MAP(theClass, baseClass) \
AFX_MSGMAP* theClass::GetMessageMap() const \
{ return &theClass::messageMap; } \
AFX_MSGMAP theClass::messageMap = \
{ &(baseClass::messageMap), \
(AFX_MSGMAP_ENTRY*) &(theClass::_messageEntries) }; \
AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
{
#define ON_COMMAND(id, memberFxn) \
{ WM_COMMAND, 0, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)memberFxn },
#define END_MESSAGE_MAP() \
{ 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
};
enum AfxSig
{
AfxSig_end = 0, // [marks end of message map]
AfxSig_vv,
};

消息流动图

5.Command Routing(命令绕行)

如果是一般的Windows 消息(WM_xxx),一定是由衍生类别流向基础类别,
没有旁流的可能。
如果是命令消息WM_COMMAND,就有奇特的路线了:

书上给出了个仿真例子,要理解它,必须理解虚函数!代码就不写了。

这并是我对这一章重要的地方的笔记,有些部分还不是很懂(特别是串行化部分) 。希望在以后几章的学习中,能加深对这几个重要技术的理解。

看完了深入浅出C++第三章(MFC六大关键技术之模拟),thinking in java第六章的3/4!

By robinhesky | 01月 19, 2008

最近比较累,也晚了,要写的也比较多,就改天补上吧!

thinking in java 第五章 隐藏具体实现

By robinhesky | 01月 18, 2008

进行面向对象的设计时,一项基本的考虑是:如何将发生变化的东西与保持不变的东西分隔开

为解决这个问题,Java推出了“访问指示符”的概念,允许库创建者声明哪些东西是客户程序员可以使用的,哪些是不可使用的。这种访问控制的级别在“最大访问”和“最小访问”的范围之间,分别包括:public,protected,包访问权限(没有关键词),private。

1.库单元

我们用import关键字导入一个完整的库时,就会获得“包”(Package)。例如:
import java.util.*;

之所以要进行这样的导入,是为了提供一种特殊的机制,以便管理“命名空间”(Name Space)。我们所有类成员的名字相互间都会隔离起来。位于类A内的一个方法f()不会与位于类B内的、拥有相同“签名”(自变量列表)的f()发生冲突

若在一个文件的开头使用下述代码:
package mypackage;
那么package语句必须作为文件的第一个非注释语句出现。该语句的作用是指出这个编译单元属于名为mypackage的一个库的一部分。或者换句话说,它表明这个编译单元内的public类名位于mypackage这个名字的下面。如果其他人想使用这个名字,要么指出完整的名字,要么与mypackage联合使用import关键字(使用前面给出的选项)。注意根据Java包(封装)的约定,名字内的所有字母都应小写,甚至那些中间单词亦要如此。

2.创建独一无二的域名

由于一个包永远不会真的“封装”到单独一个文件里面,它可由多个.class文件构成,所以局面可能稍微有些混乱。为避免这个问题,最合理的一种做法就是将某个特定包使用的所有.class文件都置入单个目录里。也就是说,我们要利用操作系统的分级文件结构避免出现混乱局面。这正是Java所采取的方法。
它同时也解决了另两个问题:创建独一无二的包名以及找出那些可能深藏于目录结构某处的类。正如我们在第2章讲述的那样,为达到这个目的,需要将.class文件的位置路径编码到package的名字里。但根据约定,编译器强迫package名的第一部分是类创建者的因特网域名。由于因特网域名肯定是独一无二的(由InterNIC保证——注释②,它控制着域名的分配),所以假如按这一约定行事,package的名称就肯定不会重复,所以永远不会遇到名称冲突的问题。换句话说,除非将自己的域名转让给其他人,而且对方也按照相同的路径名编写Java代码,否则名字的冲突是永远不会出现的。

这个技巧的另一部分是将package名解析成自己机器上的一个目录。这样一来,Java程序运行并需要装载.class文件的时候(这是动态进行的,在程序需要创建属于那个类的一个对象,或者首次访问那个类的一个static成员时),它就可以找到.class文件驻留的那个目录。
Java解释器的工作程序如下:首先,它找到环境变量CLASSPATH(将Java或者具有Java解释能力的工具——如浏览器——安装到机器中时,通过操作系统进行设定)。CLASSPATH包含了一个或多个目录,它们作为一种特殊的“根”使用,从这里展开对.class文件的搜索。从那个根开始,解释器会寻找包名,并将每个点号(句点)替换成一个斜杠,从而生成从CLASSPATH根开始的一个路径名(所以package foo.bar.baz会变成foo\bar\baz或者foo/bar/baz;具体是正斜杠还是反斜杠由操作系统决定)。随后将它们连接到一起,成为CLASSPATH内的各个条目(入口)。以后搜索.class文件时,就可从这些地方开始查找与准备创建的类名对应的名字。此外,它也会搜索一些标准目录——这些目录与Java解释器驻留的地方有关。

对于如下的包名,编译器是这样做的:

package com.bruceeckel.util;

这两个文件都置于我自己系统的一个子目录中:
C:\DOC\JavaT\com\bruceeckel\util
若通过它往回走,就会发现包名com.bruceeckel.util,但路径的第一部分又是什么呢?这是由CLASSPATH环境变量决定的。在我的机器上,它是:
CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT
可以看出,CLASSPATH里能包含大量备用的搜索路径。然而,使用JAR文件时要注意一个问题:必须将JAR文件的名字置于类路径里,而不仅仅是它所在的路径。所以对一个名为grape.jar的JAR文件来说,我们的类路径需要包括:
CLASSPATH=.;D:\JAVA\LIB;C:\flavors\grape.jar

3.包访问权限

如果根本不指定访问指示符,这意味着当前包内的其他所有类都能访问成员,但对包外的所有类来说,这些成员却是“私有”(Private)的,外界不得访问。

为获得对一个访问权限,唯一的方法就是:
(1) 使成员成为“public”(公共的)。这样所有人从任何地方都可以访问它。
(2) 变成一个“友好”成员,方法是舍弃所有访问指示符,并将其类置于相同的包内。这样一来,其他类就可以访问成员。
(3) 正如以后引入“继承”概念后大家会知道的那样,一个继承的类既可以访问一个protected成员,也可以访问一个public成员(但不可访问private成员)。只有在两个类位于相同的包内时,它才可以访问友好成员。但现在不必关心这方面的问题。
(4) 提供“访问器/变化器”方法(亦称为“获取/设置”方法),以便读取和修改值。这是OOP环境中最正规的一种方法,也是Java Beans的基础——具体情况会在第13章介绍。

下面举书上的两段程序加深对包访问权限的理解:

//: Cookie.java
// Creates a library
package c05.dessert;

public class Cookie {
  public Cookie() {
   System.out.println("Cookie constructor");
  }
  void foo() { System.out.println("foo"); }
} ///:~
 //: Dinner.java
// Uses the library
import c05.dessert.*;

public class Dinner {
  public Dinner() {
   System.out.println("Dinner constructor");
  }
  public static void main(String[] args) {
    Cookie x = new Cookie();
    //! x.foo(); // Can't access
  }

} ///:~

以上程序可以创建一个Cookie对象,因为它的构建器是public的,而且类也是public的(公共类的概念稍后还会进行更详细的讲述)。然而,foo()成员不可在Dinner.java内访问,因为foo()只有在dessert包内才是“友好”的。

4.protected–包访问权限的一种(友好的一种)

为了便于理解,举例如下:

import c05.dessert.*;

public class ChocolateChip extends Cookie {

public ChocolateChip() { System.out.println( “ChocolateChip constructor”); }

public static void main(String[] args) {

ChocolateChip x = new ChocolateChip();

//! x.foo(); // Can’t access foo } } ///:~

对于继承,值得注意的一件有趣的事情是倘若方法foo()存在于类Cookie中,那么它也会存在于从Cookie继承的所有类中。但由于foo()在外部的包里是“友好”的,所以我们不能使用它。当然,亦可将其变成public。但这样一来,由于所有人都能自由访问它,所以可能并非我们所希望的局面。若象下面这样修改类Cookie:

public class Cookie {
  public Cookie() {
    System.out.println("Cookie constructor");
  }
  protected void foo() {
    System.out.println("foo");
  }
}

那么仍然能在包dessert里“友好”地访问foo(),但从Cookie继承的其他东西亦可自由地访问它。然而,它并非公共的(public)。

由上可见,java中的protected与C++的protected是由很大区别的,再c++中,如果再成员前加了关键词protected,那么由类生成的对象不能直接方问该成员(以a.member的形式访问,其中a为对象,member为protected),必须通过成员函数来访问,继承类也可以通过这样的方法访问,而java可以以a.member的形式访问。另外,再java中protected还提供包访问权限。

5.类访问

注意不可将类设成private(那样会使除类之外的其他东西都不能访问它),也不能设成protected。

若构造器是私有的,该如何构造对象呢,示例如下:

//: Lunch.java
// Demonstrates class access specifiers.
// Make a class effectively private
// with private constructors:

class Soup {
  private Soup() {}
  // (1) Allow creation via static method:
  public static Soup makeSoup() {
    return new Soup();
  }
  // (2) Create a static object and
  // return a reference upon request.
  // (The "Singleton" pattern):
  private static Soup ps1 = new Soup();
  public static Soup access() {
    return ps1;
  }
  public void f() {}
}

class Sandwich { // Uses Lunch
  void f() { new Lunch(); }
}

// Only one public class allowed per file:
public class Lunch {
  void test() {
    // Can't do this! Private constructor:
    //! Soup priv1 = new Soup();
    Soup priv2 = Soup.makeSoup();
    Sandwich f1 = new Sandwich();
    Soup.access().f();
  }
} ///:~

第一个选择,我们可创建一个static方法,再通过它创建一个新的Soup,然后返回指向它的一个句柄。如果想在返回之前对Soup进行一些额外的操作,或者想了解准备创建多少个Soup对象(可能是为了限制它们的个数),这种方案无疑是特别有用的。
第二个选择是采用“设计方案”(Design Pattern)技术,本书后面会对此进行详细介绍。通常方案叫作“独子”,因为它仅允许创建一个对象。类Soup的对象被创建成Soup的一个static private成员,所以有一个而且只能有一个。除非通过public方法access(),否则根本无法访问它。

Linux主要文件学习笔记1

By robinhesky | 01月 16, 2008

字符设备和块设备文件。

字符设备是指设备发送和接收数据以字符的形式进行,没有缓冲;而块设备则以整个数据缓冲区的形式进行。字符设备的驱动相对比较简单。用ls -l 查文件,根据开头的字母可以来判断,以b开头的说明是块设备(如硬盘),以c开头的为字符设备文件(如打印机,终端,/dev/null)

管道设备文件

从字面上理解,管道设备文件就是FIF0文件(先进先出)。管道设备文件从一头流进,从另一头流出。可以用管道文件实现分区的镜像 。用ls -l 命令查,以p开头的就是管道文件。mknod命令可用来建立字符,块,管道设备文件。如

mknod 管道文件名 p

链接文件
linux存在软链接和硬链接
软链接又叫作符号链接,这个文件包含了被链接文件的路径名,可以是任意文件和目录, 可以链接不同文件系统的文件,类似于WINDOWS中的快捷方式,但由不是。链接文件还可以链接不存在的文件,这就产生一个断链的问题。链接文件还可以

链接自己,这就产生了个循环链接的问题,应尽量避免。删除链接文件时,只删除链接文件本身,而不删除文件。可用以下命令创建符号链接:

ln -s 源文件 链接文件

硬链接相当于给已经存在的 文件的别名。硬链接的命令是:

ln -d exsiting file new file

硬链接有两个限制

1.不能给目录文件创建硬链接

2.不能在不同系统文间间创建硬链接

删除硬链接文件的源文件时,只删除文件本身,而不删除硬链接文件,而且还保留了原来的内容。这时系统就忘了它是一个硬链接文件,而把它看成普通文件看待。

 

thinking in java 第四章 初始化与删除续

By robinhesky | 01月 16, 2008

5.非静态实例的初始化

针对每个对象的非静态变量的初始化,它看起来与静态初始化从句极其相似,只是static关键字从里面消失了。为支持对“匿名内部类”的初始化,必须采用这一语法格式。

6.数组初始化

在C中初始化数组极易出错,而且相当麻烦。C++通过“集合初始化”使其更安全。Java则没有象C++那样的“集合”概念,因为Java中的所有东西都是对象。但它确实有自己的数组,通过数组初始化来提供支持。
数组代表一系列对象或者基本数据类型,所有相同的类型都封装到一起——采用一个统一的标识符名称。数组的定义和使用是通过方括号索引运算符进行的([])。为定义一个数组,只需在类型名后简单地跟随一对空方括号即可:
int[] al;
也可以将方括号置于标识符后面,获得完全一致的结果:
int al[];

基本数据类型的数组元素会自动初始化成“空”值(对于数值,空值就是零;对于char,它是null;而对于boolean,它却是false)。若操作的是一个非基本类型对象的数组,那么无论如何都要使用new,而且应该如下样式赋值,

Integer[] a = new Integer[pRand(20)];
prt(”length of a = ” + a.length);
for(int i = 0; i < a.length; i++) {
a[i] = new Integer(pRand(500));
prt(”a[" + i + "] = ” + a[i]);
}

7.多维数组的赋值

int[][][] a3 = new int[pRand(7)][][];
for(int i = 0; i < a3.length; i++) {
a3[i] = new int[pRand(5)][];
for(int j = 0; j < a3[i].length; j++)
a3[i][j] = new int[pRand(5)];
}

对于第一个new创建的数组,它的第一个元素的长度是随机的,其他元素的长度则没有定义。for循环内的第二个new则会填写元素,但保持第三个索引的未定状态——直到碰到第三个new。
根据输出结果,大家可以看到:假若没有明确指定初始化值,数组值就会自动初始化成零。

Integer[][] a5;
a5 = new Integer[3][];
for(int i = 0; i < a5.length; i++) {
a5[i] = new Integer[3];
for(int j = 0; j < a5[i].length; j++)
a5[i][j] = new Integer(i*j);
}

thinking in java 第四章 初始化与删除

By robinhesky | 01月 16, 2008

1.以构造器(constructor)确保初始化

2.方法重载(method overloading)

每个重载的方法都有一个独一无二的参数列表。重载的方法就靠不同的参数列表来区分。以返回值是不能区分重载的方法的,以参数的顺序来区分不同的重载方法也是不可取的,容易导致代码混乱,不可维护。

3.缺省构造器(无参构造器)

如果没有写构造器,编译器会自动为程序生成一个构造器。如果你写了构造器,编译器 将不在为程序自动生成一个编译器。构造器里调用构造器,可以通过this实现。但在构造器里只能调用一次构造器,且只能放在最前处。ge collection

4.清除(clean up),终结(finalization),垃圾回收(garbage collection)

有时可能发现一个对象的存储空间永远都不会释放,因为自己的程序永远都接近于用光空间的临界点。若程序执行结束,而且垃圾收集器一直都没有释放我们创建的任何对象的存储空间,则随着程序的退出,那些资源会返回给操作系统。这是一件好事情,因为垃圾收集本身也要消耗一些开销。如永远都不用它,那么永远也不用支出这部分开销。

垃圾收集并不等于“破坏”!

若能时刻牢记这一点,踩到陷阱的可能性就会大大减少。它意味着在我们不再需要一个对象之前,有些行动是必须采取的,而且必须由自己来采取这些行动。Java并未提供“破坏器”或者类似的概念,所以必须创建一个原始的方法,用它来进行这种清除。例如,假设在对象创建过程中,它会将自己描绘到屏幕上。如果不从屏幕明确删除它的图像,那么它可能永远都不会被清除。若在finalize()里置入某种删除机制,那么假设对象被当作垃圾收掉了,图像首先会将自身从屏幕上移去。但若未被收掉,图像就会保留下来。所以要记住的第二个重点是:

我们的对象可能不会当作垃圾被收掉!

垃圾收集只跟内存有关!

垃圾收集器存在的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾收集有关的任何活动来说,其中最值得注意的是finalize()方法,它们也必须同内存以及它的回收有关。
但这是否意味着假如对象包含了其他对象,finalize()就应该明确释放那些对象呢?答案是否定的——垃圾收集器会负责释放所有对象占据的内存,无论这些对象是如何创建的。它将对finalize()的需求限制到特殊的情况。在这种情况下,我们的对象可采用与创建对象时不同的方法分配一些存储空间。但大家或许会注意到,Java中的所有东西都是对象,所以这到底是怎么一回事呢?
之所以要使用finalize(),看起来似乎是由于有时需要采取与Java的普通方法不同的一种方法,通过分配内存来做一些具有C风格的事情。这主要可以通过“固有方法”来进行,它是从Java里调用非Java方法的一种方式(固有方法的问题在附录A讨论)。C和C++是目前唯一获得固有方法支持的语言。但由于它们能调用通过其他语言编写的子程序,所以能够有效地调用任何东西。在非Java代码内部,也许能调用C的malloc()系列函数,用它分配存储空间。而且除非调用了free(),否则存储空间不会得到释放,从而造成内存“漏洞”的出现。当然,free()是一个C和C++函数,所以我们需要在finalize()内部的一个固有方法中调用它。

5.成员初始化

一个类的所有基本类型数据成员都会保证获得一个初始值。

在一个类的内部定义一个对象句柄时,如果不将其初始化成新对象,那个句柄就会获得一个空值。

6.构造器初始化

可以在运行期调用方法和采取行动,从而“现场”决定初始化值。但要注意这样一件事情:不可妨碍自动初始化的进行。

对于所有基本类型以及对象句柄,这种情况都是成立的。

7.初始化顺序

在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。即使变量定义大量遍布于方法定义的中间,那些变量仍会在调用任何方法之前得到初始化——甚至在构造器调用之前。

8.静态数据的初始化

若数据是静态的(static),那么同样的事情就会发生;如果它属于一个基本类型(主类型),而且未对其初始化,就会自动获得自己的标准基本类型初始值;如果它是指向一个对象的句柄,那么除非新建一个对象,并将句柄同它连接起来,否则就会得到一个空值(NULL)。

9.初始化顺序的总结

初始化的顺序是首先static(如果它们尚未由前一次对象创建过程初始化),接着是非static对象。大家可从输出结果中找到相应的证据。
在这里有必要总结一下对象的创建过程。请考虑一个名为Dog的类:
(1) 类型为Dog的一个对象首次创建时,或者Dog类的static方法/static字段首次访问时,Java解释器必须找到Dog.class(在事先设好的类路径里搜索)。
(2) 找到Dog.class后(它会创建一个Class对象,这将在后面学到),它的所有static初始化模块都会运行。因此,static初始化仅发生一次——在Class对象首次载入的时候。
(3) 创建一个new Dog()时,Dog对象的构建进程首先会在内存堆(Heap)里为一个Dog对象分配足够多的存储空间。
(4) 这种存储空间会清为零,将Dog中的所有基本类型设为它们的默认值(零用于数字,以及boolean和char的等价设定)。
(5) 进行字段定义时发生的所有初始化都会执行。
(6) 执行构建器。

10.非静态实例初始化

还没有看,明天总结。

linux实用培训教程笔记1(by 红联)

By robinhesky | 01月 14, 2008

1.linux的7个运行级别

LINUX系统中,共有7个运行级别,含义如下:
0 停机。不要把系统的默认级别设置为0,否则系统不能正常启动。
1 但用户模式。用于root用户对系统进行维护,不允许其他用户使用主机。
2 多用户模式。在该模式下不能使用NFS。
3 完全多用户模式。主机作为服务器时通常在该模式下。
4 未分配使用。
5 图形登陆的多用户模式。用户在该模式下可以进行图形界面的登陆。
6 重新启动。不要把系统的默认级别设置为6,否则不能正常启动。

2.Linux的命令组成

Linux命令组成:shell内部命令+shell外部命令
shell内部命令:最简单最常用的命令,在shell启动时进入内存
shell外部命令:独立的可执行程序。是一些使用工具程序

3.Shell命令基本规则

Shell命令的一般格式如下:命令名【选项】【参数1】【参数2】…【选项】是对命令的特别定义,以减
号(-)开始,多个选项可以用一个减号(-)连起来,如ls -l -a 与ls -la 相同。【参数】提供命令运行的信息,或者是
命令执行过程中所使用的文件名。

使用分号(;)可以将两个命令隔开,这样可以实现一行中输入多个命令。命令的执行顺序和输入的顺序相同

4. 常用的Shell命令-目录和文件操作

重命名文件
mv [源文件名] [目标文件名]
例:mv /etc/rc.d/rc3.d/K50xinetd /etc/rc.d/rc3.d/S50xinetd

删除文件
rm [文件名]
不需确认地删除多个文件
rm -f [带通配符的文件名]
硬链接文件。不能对目录文件做硬链接,不能在不同的文件系统之间做硬链接。
ln [源文件名] [目标文件名]
例:mv /etc/rc.d/rc3.d/K50xinetd /etc/rc.d/rc3.d/S50xinetd
软链接文件。也就是符号链接。可用此法创建文件的快捷方式。
ln -s [源文件或文件夹名] [目标名]
按文件名查找文件。
find / -name nametofind -print
改变文件所有者。
例:chown workman:workgroup 文件名
改变文件访问权限
例:chmod -R 755 /usr/local/LumaQQ
查看一个文件有多少行
wc -l usr.bin
查看一个文件有多少字节
wc -c usr.bin
查看文本文件的内容
cat usr.bin

5 .管道

将一个程序的标准输出写道一个文件中去,再将这个文件的内容作为另一个命令的标准输入,等效于通过
临时文件将两个命令结合起来。这种情况很普遍,需要Linux系统提供一种功能:它不需要或不必使用临时文件
,就能将两条命令结合在一起。这种功能就是管道。管道的操作符是一个竖杠“|”。管道是可以嵌套使用的,
因此可以把多个命令结合在一起。接上例,如果执行下面的命令将直接返回/usr/bin 中的文件列表的行数,而不
是列表的内容。
ls /usr/bin | wc -l

6.命令补全

在送入命令的任何时刻,可以按<Tab>键,当这样做时,系统将试图补全此时已输入的命令。如果已经输
入的字符串不足以唯一地确定它应该使用的命令,系统将发出警告声。再次按<Tab>键,系统则会给出可用来
用来补全的字符串清单。使用命令补全功能,可以提高使用长命令或操作较长名字的文件或文件夹的都是非常
有意义的。

7.启动与关闭shell(现在终于知道怎么回到图形界面了!)

在启动Linux桌面系统后,Shell已经在后台运行起来了,但并没有显示出来。如果想让它显示出来,按如下
的组合键就可以:<Ctrl> + <Alt> + <F2>组合键中的F2可以替换为F3、F4、F5、F6。如果要回到图形界面,则按
如下组合键:<Ctrl> + <Alt> + <F7>。

8 .输出重定向

在默认的情况下,Linux从键盘接受输入,并将命令的输出送到屏幕。在有时候,这样做并不方便。比如
,在一个目录里有很多文件,如果只用简单的ls命令,在屏幕上显示的输出结果可能上千行!为了得到我们需
要的信息。我们或许需要把这些结果存储到一个文件中然后再查看这个文件,这就要用到系统的输出重定向功
能。输出重定向的操作符为>或>>。单个大于号(>)后面紧跟文件名。
如果指定的文件不存在,将建立这一文件。如果指定的文件存在,则文件原有的内容将被覆盖。如果使用两个
大于号(>>)则会把输出内容追加到原来文件里面 。

9.几种常见shell简介

Linux系统提供多种不同的Shell以供选择。常用的有Bourne Shell(简称sh)、C-Shelll(简称csh)、Korn
Shell(简称ksh)和Bourne Again Shell (简称bash)。

(1)Bourne Shell是AT&T Bell实验室的 Steven Bourne为AT&T的Unix开发的,它是Unix的默认Shell,也是其
它Shell的开发基础。Bourne Shell在编程方面相当优秀,但在处理与用户的交互方面不如其它几种Shell。

(2)C Shell是加州伯克利大学的Bill Joy为BSD Unix开发的,与sh不同,它的语法与C语言很相似。它提供
了Bourne Shell所不能处理的用户交互特征,如命令补全、命令别名、历史命令替换等。但是,C Shell
与BourneShell并不兼容。

(3)Korn Shell是AT&T Bell实验室的David Korn开发的,它集合了C Shell和Bourne Shell的优点,并且与Bourne
Shell向下完全兼容。Korn Shell的效率很高,其命令交互界面和编程交互界面都很好。

(4)Bourne Again Shell (即bash)是自由软件基金会(GNU)开发的一个Shell,它是Linux系统中一个默认的Shell
。Bash不但与Bourne Shell兼容,还继承了C Shell、Korn Shell等优点。

thinking in java 第三章-程序控制流

By robinhesky | 01月 13, 2008

今天终于顺利的完成了thinking in java第三章,看了位操作符,移位操作符,三元操作符,逗号操作符,字符操作符,此外讨论了使用操作符时程序员易犯的错误,对于while等控制语句,由于在java里只能用boolean来比较,而不像C或C++那样可以拿数值量来当boolean,这样有效的避免了类似while(x=y),while(x|y)等错误的发生。在java中c++中sizeof,因为sizeof是考虑c的移植问题而产生的,不同的机子数据类型的存储位数可能不一样,但java没有这个问题,也就不需要了。另外一个比较有意思的地方是,java使用了标签,类似于goto语句,但java中的标签主要用来跳出多重循环,有严格的限制。此外文章里总结了操作符的使用,给出了个很好的总结各种基本数据类型使用的程序。提到了优先级。总体今天比较轻松。

« Previous Entries