??xml version="1.0" encoding="utf-8" standalone="yes"?>
2. 用例之间的基本关pLQ泛化,包含和扩展?br />用例A到B的泛化关p表CA和B是具体与抽象的关pR?br />用例A到B的包含关p表CA使用?jin)B提供的功能?br />用例A到B的扩展关p表CA向B(ti)提供的可用的功能?br />但从A到B的包含关pd从B到A的扩展关pL不同的:(x)
A包含B说明B是从A中分解出来的公共行ؓ(f)QB自n是独立的Q但对于A来说是不可缺的一部分?br />B扩展A说明B是从A中分解出来的变(sh)行ؓ(f)Q必L定扩展点Q也是在基本用例中执行变(sh)行ؓ(f)的具体条件。B仅仅是A的补充,而不是不可缺的部分QB自n也不是独立的。A可以单独执行Q表C通常的情况,在特定的情况下,用B来补充它?br />抽象用例不能被实例化Q不能被实际执行Q它的作用在于更好地l织用例关系?br />
参考书Q?br />《UML用户指南?Grady BoochQJames RumbaughQIvar Jacobson?늻忠等?机械工业出版C?br />《统一软g开发过E?Ivar JacobsonQGrady BoochQJames Rumbaugh?周伯生等?机械工业出版C?br />
动态创建就是创建某U类型的对象Q具体类型在q行时确定,~译时可能不知道。比如运行时用户输入一个类型名Uͼ如果该类型是E序cd体系中的一员,则程序中能够创cd的对象。下面的代码是用MFC动态创建机制的一个简化的例子Q?/p>
CRuntimeClass* g_pFirstClass;
void func()
{
char szClassName[64];
CRuntimeClass* pClass;
CObject* pObject;
cout << "enter a class name... ";
cin >> szClassName;
for (pClass = g_pFirstClass; pClass != NULL; pClass = pClass->m_pNextClass)
{
if (strcmp(szClassName, pClass->m_lpszClassName) == 0)
pObject = pClass->CreateObject();
}
}
实现动态创建的思\是把动态的cd名称与程序类型体pM的每一个进行比较,与某个类型吻合时让该cd创徏自n的对象。这P支持动态创建的cd中的每一个类都要额外实现一些功能,卛_别一个名U是否与自n相符Q以?qing)创w的对象?/p>
判别一个名U是否与自n相符Q这是运行时c识别的内容Q所以MFC动态创建是在RTCI基础上实现的?/p>
RTCI是一个对象能够判定自己是否属于某U类型,该类型的名称在运行时定Q编译时可能不知道。从下面的例子很Ҏ(gu)理解RTCIQ?/p>
void Func()
{
char szClassName[64];
CDocument* pDoc = new CDocument;
cout << "enter a class name... ";
cin >> szClassName;
cout << pDoc->IsKindOf(szClassName); //是返?Q否q回0
}
有一炚w要说明的是,因ؓ(f)CDocumentz于CObjectQ所以IsKindOf对于CObject也要q回1。因为我们是从动态创建出发的Q所以如果是q样可能?x)有一点背d街但是RTCI明显和动态创建有密切联系QRTCI也可能有单独的h(hun)|所以先把RTCI实现h?/p>
实现RTCI的思\是让每一个类记录自n的类型信息,q提供IsKindOf(char*)函数q行所l类型与自ncd的比较,而且q要能访问基cȝcd信息Q进行比较,一直到根类。所以记录的cd信息要按l承关系qv来,每个cȝIsKindOf()q要调用基类的IsKindOf()。MFC把要记录的类型信息抽取到一个CRuntimeClassl构体中Q每个类中加入一个CRuntimeClass成员卛_?/p>
现在回到动态创建,在RTCI建立的数据结构基上将可实现它。动态创Z不同于IsKindOf()的角度用这一数据l构Q它要遍历所有类型的CRuntimeClass。那么仅仅有l承关系的类的CRuntimeClass相连q(sh)够,要把所有类的CRuntimeClassq成一个链表。其实动态创建ƈ不关?j)类间的l承关系Q它q等看待每个cR现在以CRuntimeClass为结Ҏ(gu)成一个纵横两个方向的链表QIsKindOf()和动态创建分别用它不同的侧面?/p>
序列化的概念是在文g中存储对象信息,q能Ҏ(gu)它恢复对象。对于文档视囄构的软gQ用户需要保存所~辑的文档和打开已编辑的文档Q这正是序列化的应用Q所以序列化是非帔R要的一U特性。在序列化恢复对象时Q就可以用到动态创建?/p>
使用MFC序列化的例子如下Q?/p>
void CMyDocument::Serialize(CArichive &ar)
{
if (ar.IsStoring())
{
ar << m_pMyClass; //CMyClass m_pMyClass;
}
else
{
ar >> m_pMyClass;
}
}
一个支持序列化的类提供Serialize(CArchive &)函数Q重?lt;<?gt;>操作。注意两者是不同的,在上例中QCMyDocumentcȝ信息q不被序列化Q而CMyClasscȝ信息被序列化。实际上一个序列化cȝ<<?gt;>操作Q其不涉?qing)类信息的部分是调用Serialize()完成的,它必d时实现这两者?/p>
按照MFC的要求,需要在支持序列化的cd义中使用DECLARE_SERIAL宏,在类实现中用IMPLEMENT_SERIAL宏。我们看一下这两个宏实C(jin)什么,
#define DECLARE_SERIAL(class_name) \
_DECLARE_DYNCREATE(class_name) \
AFX_API 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) \
AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name)); \
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
return ar; } \
主要是加入了(jin)?gt;>的重载,但是没有重蝲<<QMFC仅提供了(jin)CObject?lt;<的重载,如下Q?/p>
_AFX_INLINE CArchive& AFXAPI operator<<(CArchive& ar, const CObject* pOb)
{ ar.WriteObject(pOb); return ar; }
q是因ؓ(f)在序列化d写的时候,都需要具体类的CRuntimeClass信息。相应的GetRuntimeClass()是一个虚函数QCObject重蝲<<Q在写类信息时调用到该函敎ͼ׃虚函数机Ӟ写入的是具体cȝ信息。但是这里隐含着一个条Ӟ是调用<<和GetRuntimeClass()Ӟ具体cd象已l存在了(jin)Q而调?gt;>和读入类信息Ӟ该类对象q未被创建,所以无法利用这U机Ӟ只能在每个具体类中都重蝲一?gt;>。我觉得《深入解析MFC》对q个问题的解释不正确?/p>
q里有一个问题需要明一下,序列化ؓ(f)什么要写入cM息?一是它应该保存完整的能够独立恢复对象的信息Q二是在E序d对象Ӟ要把它的cM息和E序中期望的(所能处理的)cM息相比较Q进行检验?/p>
看IMPLEMENT_SERIAL宏对重蝲>>的实玎ͼ是提供一个期望的CRuntimeClassl构(用于(g)?Q委托CArchiveq行对象d。因对象旉先要跟文件打交道Q所以交lC(j)Archive处理Q随后把d的数据写入对象时QCArchive再调用具体类的Serialize()Q如此合作是十分恰当的。在q里QCArchiveq负责了(jin)d和检验类信息Q然后创建对象的q程。因Z斚w具体cd象还?sh)存在,另一斚wq些操作Ҏ(gu)有具体类都没有分别,应该提出来,在类U别实现或者让合作者实现。实际上QMFC先把q个q程交给C(j)Archive::ReadClass()Q后者又调用CRuntimeClass::Load()?nbsp;
对于序列化来_(d)搞清它的概念以后Q就是实现Serialzie()Q重?lt;<?gt;>。对<<?gt;>的重载涉?qing)很多工作,MFC已经帮我们实C(jin)Q我们也看见?jin)大概的设计Q主要是与CArchive分工合作Q其ơ是CRuntimeClass?/p>
现在看到CRuntimeClassl构体在MFC对RTCIQ动态创建和序列化的实现中都L(fng)重要的作用,重新认识一下这个数据结构很有必要?/p>
CRuntimeClass包含?jin)关于类的各U信息和有关操作。把cd(qing)其基cȝCRuntimeClassq成一个链表,可以很方便地实现RTCI的IsKindOf()Q把所有类的CRuntimeClassq成一个链表,再加上一个简单的CreateObject函数Q就可以对以Lcdq行动态创建的企图做出反应QCRuntimeClassq实C(jin)向文件读写类信息的Load()QStore()Q配合序列化的实现?/p>
在分析消息映和命o(h)传递机制之前,需要对WindowsE序模型有很好的理解?/p>
未完待箋(hu)...
参考:(x)
《深入解析MFC?中国?sh)力出版C?br>《深入浅出MFC?华中U大出版C?br>《WindowsE序设计?北大出版C?/p>
(tng)设计Q?br />
(tng) 输入是待排数l及(qing)光度,输出排序后的数组?br /> (tng) (tng)在冒泡过E中Ҏ(gu)l的有序情况q行(g)查,在数l已l有序时便结束算法?br />
代码Q?br />
void BubbleSort(int nArray[], int nLength)
{
(tng) (tng) (tng) (tng) bool bSorted = false;
(tng) (tng) (tng)
(tng) (tng) (tng) (tng) if (nArray == NULL)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) throw -1;
(tng) (tng) (tng)
(tng) (tng) (tng) (tng) if (nLength < 2)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng)return;
(tng) (tng) (tng)
(tng) (tng) (tng) for (int i = nLength; !bSorted && i > 1; i--)
(tng) (tng) (tng) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng)bSorted = true;
(tng) (tng) (tng) (tng) (tng) (tng) (tng)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) for (int j = 1; j < i; j++)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (nArray[j] < nArray[j-1])
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) int n;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) n = nArray[j];
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) nArray[j] = nArray[j-1];
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) nArray[j-1] = n;
(tng) (tng) (tng) (tng) (tng) (tng) (tng)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) bSorted = false;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) }//if
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng)}
}
2. 双向冒(chng)排序
void BiBubbleSort(int nArray[], int nLength)
{
(tng) (tng) (tng) (tng)int (tng) low, high;
(tng)
(tng) (tng) (tng) (tng)if (nArray == NULL)
(tng) (tng) (tng) (tng) (tng) (tng) (tng)throw -1;
(tng) (tng) (tng) (tng)if (nLength < 2)
(tng) (tng) (tng) (tng) (tng) (tng) (tng)returnt;
(tng) (tng) (tng) low = 0;
(tng) (tng) (tng) (tng)high = nLength - 1;
(tng) (tng) (tng) while (low < high)
(tng) (tng) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng)int t;
(tng) (tng) (tng) (tng) (tng) (tng) (tng)t = low;
(tng) (tng) (tng) (tng) (tng) (tng) (tng)for (int i = low; i < high; i++)
(tng) (tng) (tng) (tng) (tng) (tng) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (nArray[i] > nArray[i+1])
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) int n;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) n = nArray[i];
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) nArray[i] = nArray[i+1];
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) nArray[i+1] = n;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) t = i + 1;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) high = t - 1;
(tng) (tng) (tng) (tng) (tng) t = high;
(tng) (tng) (tng) (tng) (tng)for (int j = high; j > low; j--)
(tng) (tng) (tng) (tng) (tng) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (nArray[j] < nArray[j-1])
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) int n;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) n = nArray[j];
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) nArray[j] = nArray[j-1];
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) nArray[j-1] = n;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) t = j - 1;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) low = t + 1;
(tng) }//while
}
3. 快速排?br />
(tng)思想Q?br />
(tng)选一个枢轴元素,把待排序列划分成两段Q前一D不大于枢uQ?tng)后一D不于枢u。如果这两段分别有序Q原序列也随之有序。通过划分Q一个段的排序可以{化ؓ(f)两个子段的排序,卛_h质但较?yu)规模的问题。当D늚长度?Ӟ本n是有序的Q{化可以终止?br />
设计Q?br />
用一个递归函数来实现快速排序算法,递归l止条g是段的长度小于等??br />一ơ划分过E设计如下:(x)取段的第一个元素ؓ(f)枢uQ从最后一个元素向前与枢u比较Q发现小于枢轴的元素Ӟ与枢轴交换位|,从第二个元素向后与枢轴比较,q样两端是已完成划分的部分,中间是待划分的部分,枢u始终处于中间部分的一端,比较从另一端向该端q行Q发现分cM同的元素同枢u交换。随着比较和交换的q行Q中间部分不断收~?每次长度~短1)Q当收羃到长度ؓ(f)1Ӟ划分l止?br />
实现要点Q?br />
递归函数的参数是待排序列?qing)前后边界?br />划分q程需要用两个变量记录中间部分的边界?br />
代码Q?br />
void QuickSort(int nArray[], int low, int high)
{
(tng) (tng) (tng) (tng) int pivot = nArray[low];
(tng) (tng) (tng) (tng) int (tng)i = lowQj = high;
(tng) (tng) (tng)
(tng) (tng) (tng) (tng) if (high < low)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng)return; (tng) (tng) (tng)
(tng) (tng) (tng) (tng)
(tng) (tng) (tng) (tng) while (i < j)
(tng) (tng) (tng) (tng) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) while (i < (tng)j && nArray[j] >= pivot) j--;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (i < j) (tng)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) nArray[i++] = nArray[j];
(tng)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) while (i < (tng)j && nArray[i] <= pivot) i++;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (i < j) (tng)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) nArray[j--] = nArray[i];
(tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng)
(tng) (tng) (tng) (tng) nArray[i] = pivot;
(tng) (tng) (tng)
(tng) (tng) (tng) (tng) QuickSort(nArray, low, (tng)i - 1);
(tng) (tng) (tng) (tng) QuickSort(nArray, (tng)i + 1, high);
}
试要点Q?
法分析Q?br />
假设原序列有2n个元素,每次分划把一个段{分成两D,则经qnU递归法l止Q每一U递归的比较L为n, 所以QuickSort()的时间ؓ(f)O(nlog(n))Q这是^均情c(din)当原序列本w有序时QQuickSort()出现最坏情况,旉为O(n2)?/p>
COM作ؓ(f)一U二q制lg模型Q要求对象和客户可能分,它们的一切联p都通过接口q行。一个对象可以有多个接口Q那么,客户在获得第一个接口指针后Q应当可以从一个接口指针查询下一个接口指针,以保持对象的使用。客户应当可以通过接口理对象的生命期Q以l束对象的用。作ZU设计,COM规定从对象的一个接口可以查询它的所有接口,对象生命期管理的责Q分散到每个接?只要客户为每个接口进行生命期理Q就可以实现对象的生命期理)。在实现上,COM接口查询和生命期管理的责Q集中C个IUnknown接口Q所有接口都从IUnknownz。COM接口是从IUnknownz的接口?/p>
2. COM的面向对象特?/p>COM在二q制上提供了(jin)一UY件结构模型,q且带有面向对象的特征?br />
COM对象是有状态的Q数据和操作装在一赗COM接口和普通API函数的不同,在于COM对象是有状态的。比如一个宇宙飞船对?实现IMotion接口QIMotion包含void Fly(double dTime)和double GetPosition()函数)Q让它飞行一D|?通过IMotion接口调用Fly()函数)以后它的位置改变(sh)(jin)(在飞行前后调用GetPosition()得到不同l果)?/p>
同样的接口可以由不同的COM对象实现Q客L(fng)序用l一的方法进行处理,却可以得C同的l果。接口也可以zQ不同的子接口对基接口的函数有不同的实现?/p>
在这里解释一下MFC实现COM对象的机制。一个COM对象可以实现多个接口Q而这些接口都是IUnknown的子接口Q它们对QueryInterface(), (tng) AddRef(), (tng) Release()各有一份实C码,而在同一对象内,q三个函数的内容完全相同Q因此可以抽出来Q委z该对象。又׃对Q何COM对象QAddRef()和Release()的实现本质上也相同,因此可以q一步,抽取q两个函数及(qing)其操作的数据(m_Ref)Q放到CCmdTarget中去。QueryInterface()的情冉|所不同Q它操作的数据是依赖于具体COM对象的接口映表Q可以在把函数放qCCmdTarget的同Ӟ实现一个返回接口映表的虚函数QQueryInterface()调用此函数获得具体的接口映射表?/p>
COM对象可以用包容和聚合两种方式重用已有的COM对象?/p>
聚合方式实现重用比较复杂?br />
在实现对象聚合时Q要解决的一个主要问题是在接口查询上对用户保持透明。客户从暴露出来的内部对象接口进行查询,应当查到的是外部对象的接口。那么收到查询时Q内部对象的IUnknown应当d托外部对象的IUnknown。但是内部对象也可能不被用于聚合Q应该有一个正常的IUnknown。这样可以考虑把内部对象最初收到查询的IUnknown设成一个代理,它根据聚合与否把查询h转交l外部对象IUnknown或内部对象的正常IUnknownQ即内部对象实现两个IUnknownQ作Z理的委托I(y)Unknown和正常的非委托I(y)Unknown。内部对象还要知道外部对象IUnknownQƈ且能判别自n是否被聚合。可以在创徏内部对象时把外部对象IUnknown指针传给它,不是聚合时传递一个空指针Q这样内部对象就得到?jin)够信息?/p>
引用计数的管理也是一P内部对象的委托I(y)Unknown区别被聚合与否,调用外部对象IUnknown或自w的非委托I(y)Unknown?/p>
当然Q从外部对象接口要能查到内部对象接口。外部对象需要知道内部对象的IUnknownQ以查询所要暴露给客户E序的接口。这个IUnknown应当是内部对象的非委托I(y)Unknown?/p>
2NF?NF的涵义是Q键是关pȝ标识信息Q非d性是附属信息。如果附属信息对标识信息的依赖不够紧密,关系的语义单U性就差,从而容易出现各U更新异常?
如果q反2NFQ既存在非主属性对键的部分依赖Q会(x)有什么问题?例如关系模式SCGT(S#,C#,G,TN)QS#是学生号QC#是课E号QG是成l,TN是Q课教师姓名,假设每门译֏有一个教师?S#,C#)是键QC#->TN是非d性对键的部分依赖Q因为它的存在会(x)产生三种更新异常Q?). 不开评教师姓名无法插入Q?). 一门课的所有学生都退选,则Q课教师姓名无法保留;3). 一门课更换教师Ӟ必须寚w该评所有学生进行修攏V非d性对键的部分依赖反映?jin)附属信息和标识信息的缺乏整体一致性,所以会(x)产生以上问题?/p>
如果W合2NFQ但q反3NFQ即存在非主属性对键的传递依赖,?x)有什么问题?例如关系模式SDL(S#,DEPT,LOC)QS#是学生号QDEPT是所在系QLOC是系的办公地Q这里S#是键QS#->DEPTQDEPT-/>S#QDEPT->LOCQLOC传递依赖于S#Q因为它的存在会(x)产生三种更新异常Q?). 如果一个系新成立尚未招生,则无法插入;2). 如果一个系不再招生Q但仍ؓ(f)其他pd课,则现有学生毕业后Q系的信息无法保留;3). 一个系更换办公地时Q必d该系的所有学生进行修攏V非d性对键的传递依赖反映了(jin)附属信息和标识信息缺乏直接一致性,所以会(x)产生以上问题。缺乏直接一致不如缺乏整体一致那样严重,所以到?NF才排除?/p>
那么BCNF的涵义在哪里呢?
2NF?NF对一个关pL式中的非d性加以限Ӟ而忽略键之间的关pR如果一个主属性依赖含键不完全的属性组意味着什么呢Q可以证明,该依赖涉?qing)不止一个键Q其军_子有两种情况Q一U是部分键,一U是含部分键和键外的属性。第一U情况下存在一个键之外的属性对该键的部分依赖;W二U情况下Q取一个不含前qC属性的键,易知存在该属性对该键的传递依赖,即一个键外的属性对该键的传递依赖,排除q两U情况就得到BCNF。ؓ(f)什么要q样做呢Q因为有多个键的情况下,必须照顾每一个键Q如果键之外的属性和该键不能保持整体和直接的一_(d)也可能生更新异常。例如SCZ(S,C,Z)QSQCQZ分别表示街道Q城?jng),邮编Q关pL式上的依赖集为{SC->Z,Z->C}QSC和SZ都是键。如果插入一个城?jng)的总邮~,必须借助一个街道,删除q个街道Q城?jng)的总邮~也被删除,出现q种情况是因为C与SZ键缺乏整体一致性?br />
参考:(x)
王能斌《数据库pȝ教程??sh)子工业出版C?/p>
1. 客户-服务器通信中的基本问题
客户和服务器通信是ؓ(f)?jin)用服务,为此在传输机制的基础上设计协议,通过寚w信行ؓ(f)的规范,实现通信的目的,q解决传输中的问题?/p>
传输机制通常׃层协议提供,Ҏ(gu)不同的通信需要选择不同的下层协议,q是一个基本的问题。对应用协议来说Q可用的传输机制有可靠连接的字节类型和不可靠无q接的数据报cd?/p>
服务器处理大量客L(fng)hQ从而ƈ发是服务器的一个基本问题,如何处理q个问题?sh)取决于通信需要。处理方式上Q服务器可以是@环的或ƈ发的Qƈ发服务器有多U实现方?异步I/OQ多U程和多q程)?/p>
一件事情能无重复地q箋(hu)q行Q通常?x)获得更好的效率Q这要求M始终知道当前的状态。一ơ通信q程的连l性取决于通信双方Q它们都要知道通信q行的状态。这对客户一般不成问题,但服务器要和大量客户通信Q不一定能为每个客L(fng)每次通信保存状态。如果服务器是有状态的Q那么就更快地计响应,减少通信的数据量。但是传输和客户的故障有状态服务器面(f)很大问题Q当传输不可?报文重复Q丢失,乱序)Ӟ服务器维护的状态会(x)和客户失M_(d)一个不断崩溃重启的客户?x)造成状态信息不能发挥作用,而维护开销却极大增加?/p>
q就提出?jin)客?服务器通信中的三个基本问题Q它们的解决Ҏ(gu)都取决于实际需要,客户-服务器通信中有哪些情况的需要呢Q?/p>
通常Ҏ(gu)前两个基本问题把服务器实现分为四U类型,它们的适用范围如下Q?/p>
2. winsock基本函数的?/p>
winsock的基本函数有WSAStartup()QW(xu)SACleanup()Qsocket()Qclosesocket()Qbind()Qlisten()Qaccept()Q?connect()Qsend()和recv()?/p>
使用q些函数Q客L(fng)的大概算法是Q?/p>
服务器端的大概算法是Q?/p>
有以下几炚w要进一步说明,
1). 客户端调用connect()和服务器端调用accept()成功后将在客戯E和服务器进E之间徏立一个TCPq接。连接两端的每个套接字描q符都包含一个本地端点地址和一个远E端点地址。所以在使用q接套接字发送数据时不用指示目的地址?/p>
2). 多宿M机的IP地址选择问题。从上面的算法容易提?gu)L(fng)问题Qؓ(f)什么客L(fng)在用套接字时不l定端点地址Q通常的主机只有一个IPQ但是多宿主L有多个IP地址Q在q种情况下,客户端ؓ(f)套接字指定的IP可能与实际发送时l过的IP不符Q所以允许客L(fng)不指定套接字地址Q而由TCP/IP软g在实际发送时指定IPQ同旉择一个未用过的端口号Q这正是在connect()调用中完成的。那么服务器端就不存在同L(fng)情况吗?不是Q在它调用bind()时指定一个套接字地址Q其端口部分采用应用协议的熟知端口,而IP地址部分有着同样的问题。ؓ(f)此定义了(jin)一个代表统配地址的常量INADDR_ANYQ用它指CIP地址部分。实际用的IP仍然是由TCP/IP软g分配?/p>
3). TCPZ个连接的发送端和接收端各维护一个缓冲区。当发送端~冲区满的时候,send()调用?x)阻塞,在接收端~冲Zؓ(f)I的时候,recv()调用?x)阻塞。ؓ(f)什么要在通信q程和TCPq接之间l护一个间接层呢?可能是ؓ(f)?jin)在一端有多个q程要用信道的情况下,在多个进E之间进行信道分配的协调。比如在发送端Q信道传输数据时send()调用可以l箋(hu)执行Q多个进E的send()调用同缓冲区打交道,彼此影响不大Q因写缓冲区速度很快Q而信道同~冲区打交道Q这时可以对各进E的发送数据进行协调,实现公^的信道分配。另外,在TCP中有滑动H口概念Q是用于量控制的,前述~冲区和滑动H口有什么关p?我现在不太清楚?/p>
4). 套接字的关闭问题。在客户机和服务器通过TCPq接完成数据交换后,有一个安全关闭的问题。一斚wQ服务器不能关闭q接Q因为客h可能q有hQ另一斚wQ客h虽然知道何时不再hQ但是它不知道服务器的响应何时发送完Q因为有些应用协议的响应数据量不定。ؓ(f)此采用部分关闭的办法Q虽然连接是双向的,但是允许在一个方向上关闭它,当客L(fng)不再hӞ可以部分关闭q接Q服务器收C个信P如果响应发送完?jin),服务器就可以关闭q接Q此时连接被完全关闭
3. 套接字接口中的端点地址
端点地址用来表示通信的进E,是传输层协议?qing)其套接字接口中的重要概c(din)不同的协议族可以用不同方式表示端点地址Q一个协议族q可以有多个地址族,每个地址族的地址格式不同。TCP/IP只有一个地址族,它的端点地址包括一?2位IP地址和一?6位端口号。在协议族和地址族的基础上,套接字接口用更ؓ(f)具体的结构来表示端点地址?/p>
套接字是一U适用于多个协议族的接口,q允怸个协议族使用多个地址族。TCP/IP协议族及(qing)其唯一地址族的标识分别是PF_INET和AF_INET。由于套接字接口的通用性,它提供一个通用的地址l构Q其格式?地址族,该族中的套接字地址)。套接字作ؓ(f)一个接口标准,可以有不同实玎ͼ以下我们只讨论windows套接字?/p>
如下定义的sockaddr实现?jin)前q通用地址l构Q?/p>
// winsock2.h
struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};
sockaddr的通用性是相对的,某些地址族不适合q个l构?/p>
管sockaddr适合于TCP/IP协议族,但是winsockq定义了(jin)TCP/IP专用的地址格式Q?/p>
// winsock2.h
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
sin_family域取值恒为AF_INET。其中的in_addrl构表示IP地址Q定义如下,
//winsock2.h
struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
#define s_addr S_un.S_addr
/* can be used for most TCP & IP code */
#define s_host S_un.S_un_b.s_b2
/* host on imp */
#define s_net S_un.S_un_b.s_b1
/* network */
#define s_imp S_un.S_un_w.s_w2
/* imp */
#define s_impno S_un.S_un_b.s_b4
/* imp # */
#define s_lh S_un.S_un_b.s_b3
/* logical host */
};
Z(jin)保证软g的可UL性与可维护性,讉KTCP/IP的代码不应用sockaddr。只使用TCP/IP的应用程序可以只使用sockaddr_inQ而永q不用sockaddr?/p>
4. winsockE序实例
《vc6技术内q》的例程ex34a包括一个web服务器和三个客户Q服务器用winsock实现Q一个客L(fng)winsockQ另两个用wininet。我们以winsock实现的服务器和客户ؓ(f)例?/p>
CBlockingSocket对各接口函数q行装Q它们的调用可以统一报错。把错误(g)查和函数调用一起封装可以避免每ơ调用这些函数时都检错。ؓ(f)l一报错Q采用了(jin)异常机制Q在(g)出错误后抛出异常Q然后统一q行异常处理。异常机制我们可以把错误检查和错误处理分开Q检查必L分散的,但是处理可以适当集中Q代码化?/p>
CHttpBlockingSocketҎ(gu)接收http报文的特点对CBlockingSocketq行?jin)扩展。成员函数ReadHttpHeaderLine()可以从TCPq接中按行接收字W?它引入了(jin)一个缓冲区Q缓冲区中收到的字符构成一行后再输出。该~冲区的长度需要能够容Ux(chng)一行字W,溢出时报错。接收输?gu)的缓冲区也可能长度不Iq时接收的数据不一行,在调用ReadHttpHeaderLine()时要注意q一?QReadHttpResponse()用于接收首部行之后所有的响应Q当调用者提供的~冲Zx(chng)?x)报错。缓冲区不的情况都是在CBlockingSocket::Receive()函数中检到的,该函数调用以上层ơ中的代码按照正常情늼写?/p>
CSockAddr是一个与sockaddr_in同样用途的c,但是用法更方ѝwinsock函数使用的端点地址l构是sockaddrQsockaddr_in本n用来代替它,所以CSockAddr需要能够替代sockaddr。sockaddr可能用在传值或传址参数中,CSockAddr必须在逻辑上和存储上都和sockaddr有等h,q实现有兛_制类型{换。CSockAddrq实C(jin)和sockaddr, sockaddr_in互相转换的成员函敎ͼ因ؓ(f)一U结构很隑֜所有情况下都好用,新结构也需要和旧结构保持兼宏V?br>
本例中采用服务器关闭套接字的办法Q因为每ơ连接只处理一个请求?br>
参考:(x)
《用TCP/IPq行|际互联W三?windows套接字版)?清华出版C?/p>
《vc6技术内q?5th ed?希望?sh)子出版C?/p>
我们知道概念是h们在某个领域中实늻验的ȝQƈ可能发展为理论。概忉|以客观事物ؓ(f)基础Q但不是对客观事物的L反映。它来自于实践,所以包含主体因素,q是很重要的。实跉|概念的根本来源,理论上的需要对概念形成也有一些作用?/p>
软g中用的对象cM于领域中使用的概c(din)《UML与模式应用》中_(d)面向对象是按照概念而不是功能进行分解。ؓ(f)什么Y件要使用概念性元素呢Q因Zh的认识是概念性的Q而Y件由人来使用Qh来开发。ؓ(f)?jin)提供有良好概念性的用户接口QY件本w适宜采用概念性元素。由人来开发的软g则更需要采用概忉|元素,软g本n和领域实践都有巨大的复杂性,Z是按照功能性元素来思考的Q面向对象可以让开发h员用概念性元素思考,从而增加对复杂性的适应和控制能力?/p>
领域概念是对象的重要来源Q但是对象也形成于Y件开发的q程。一斚wQY件的使用没有改变领域实践的本质,臛_没有完全改变Q而概念反映了(jin)领域中已l成熟的认识Q所以是非常重要的指导和参考。另一斚wQ领域实는软gq行和由行确实非怸同,要求q行新的认识QY件本w的需要也?x)?jing)响到对象的Ş成?/p>
面向对象的基本特征反映着概念的基本特征?/p>
Stroustrup的《C++E序设计语言》中_(d)(x)
“cd该用于模拟程序员的和应用的世界里的那些概c(din)?..一个概念不?x)孤立地存在Q它M一些相关的概念共存Qƈ在与相关概念的相互关pM表现出它的大部分力量?..因ؓ(f)我们要用c表C概念,问题变成了(jin)如何去表C概念之间的关系。然而,我们无法在程序语a里表qCQ意的关系。即使能q样做,我们也未必想d它。我们的cd该定义得比日常概忉|H一些——而且也更_?#8221;
我们可以体会(x)q段话的深刻性?/p>