依旧的博客

技术学习

C++博客 首页 新随笔 联系 聚合 管理
  17 Posts :: 1 Stories :: 2 Comments :: 0 Trackbacks

#

1. 冒泡排序

 思想:

  1. 从现有元素中取出最大的元素,移到相应的位置,直到所有元素都就位。
  2. 通过比较和交换逐步调整现有序列,最终找出最大元素并使其就位。

 设计:

  输入是待排数组及其长度,输出排序后的数组。
  在冒泡过程中对数组的有序情况进行检查,在数组已经有序时便结束算法。

代码:

void BubbleSort(int nArray[], int nLength)
{
     bool bSorted = false;
   
     if (nArray == NULL)
         throw -1;
   
     if (nLength < 2)
         return;
   
    for (int i = nLength; !bSorted && i > 1; i--)
    {
         bSorted = true;
       
         for (int j = 1; j < i; j++)
        {
             if (nArray[j] < nArray[j-1])
            {
                 int n;
                 n = nArray[j];
                 nArray[j] = nArray[j-1];
                 nArray[j-1] = n;
       
                 bSorted = false;
             }//if
         }
     }

}

2. 双向冒泡排序

void BiBubbleSort(int nArray[], int nLength)
{
    int  low, high;
 
    if (nArray == NULL)
       throw -1;

    if (nLength < 2)
       returnt;

    low = 0;
    high = nLength - 1;
    while (low < high)
   {
       int t;

       t = low;
       for (int i = low; i < high; i++)
       {
           if (nArray[i] > nArray[i+1])
          {
              int n;
              n = nArray[i];
              nArray[i] = nArray[i+1];
              nArray[i+1] = n;

              t = i + 1;
          }
       }
       high = t - 1;

      t = high;
      for (int j = high; j > low; j--)
      {
          if (nArray[j] < nArray[j-1])
          {
             int n;
             n = nArray[j];
             nArray[j] = nArray[j-1];
             nArray[j-1] = n;
            
             t = j - 1;
          }
      }

     low = t + 1;

  }//while

}

3. 快速排序

 思想:

 选一个枢轴元素,把待排序列划分成两段,前一段不大于枢轴, 后一段不小于枢轴。如果这两段分别有序,原序列也随之有序。通过划分,一个段的排序可以转化为两个子段的排序,即同样性质但较小规模的问题。当段的长度为1时,本身是有序的,转化可以终止。

设计:

用一个递归函数来实现快速排序算法,递归终止条件是段的长度小于等于1。
一次划分过程设计如下:取段的第一个元素为枢轴,从最后一个元素向前与枢轴比较,发现小于枢轴的元素时,与枢轴交换位置,从第二个元素向后与枢轴比较,这样两端是已完成划分的部分,中间是待划分的部分,枢轴始终处于中间部分的一端,比较从另一端向该端进行,发现分类不同的元素就同枢轴交换。随着比较和交换的进行,中间部分不断收缩(每次长度缩短1),当收缩到长度为1时,划分终止。

实现要点:

递归函数的参数是待排序列及前后边界。
划分过程需要用两个变量记录中间部分的边界。

代码:

void QuickSort(int nArray[], int low, int high)
{
     int pivot = nArray[low];
     int i = low,j = high;
   
     if (high < low)
           return;   
    
     while (i < j)
     {
          while (i < j && nArray[j] >= pivot) j--;
          if (i < j) 
               nArray[i++] = nArray[j];
 
          while (i < j && nArray[i] <= pivot) i++;
          if (i < j) 
               nArray[j--] = nArray[i];
     }
    
     nArray[i] = pivot;
   
     QuickSort(nArray, low, i - 1);
     QuickSort(nArray, i + 1, high);
}

测试要点:

  1. 递归终止条件。必须是high < low,而不能是 high == low。递归的终止是很重要的边界情况,在实现之前有一个概念上的终止条件,但在实现时处理必须准确。终止条件和递推方式有关,需要结合实际的递推方式来确定。
  2. 递归的递推方式。
  3. 分划的终止条件。分划过程在i == j时终止,虽然在比较的过程中可能进行交换,但是每次未分划部分的长度减1,用该长度控制分划的终止。
  4. 分划过程中改变方向时的交接。

算法分析:

假设原序列有2n个元素,每次分划把一个段等分成两段,则经过n级递归算法终止,每一级递归的比较总数为n, 所以QuickSort()的时间为O(nlog(n)),这是平均情况。当原序列本身有序时,QuickSort()出现最坏情况,时间为O(n2)。

posted @ 2006-05-09 16:37 依旧的博客 阅读(337) | 评论 (0)编辑 收藏

最原始的多线程通信机制是全局变量,但是两个线程对同一变量的并发操作可能是冲突的,所谓冲突是指一种并发调度不等价于任何串行调度。可以通过特定的系统调用保证线程对全局变量的操作具有原子性。这时全局变量成为一种可用的通信机制,根据通信的需要设计通信的协议,两个线程分别执行,就可以完成合作。比如辅线程重复某个过程直到主线程通知它终止,可以约定全局变量为特定值时辅线程终止,让辅线程在循环中检测全局变量,主线程设置全局变量通知辅线程终止。这种机制的缺点是接收通知的一方必须不断检测全局变量,事件机制由此进行了改进。接收方可以在事件上阻塞,等待特定的通知,等待的时间可以设定,如果设为0,则事件退化为全局变量。事件只有两种状态,有信号或无信号,这相对全局变量是一个限制。事件实现了单向的消息,这是最基本的机制。在实际通信活动中还有许多同步问题,每一方不但根据特定的消息采取相应的动作,还在收到消息后发出反馈消息。通信是一个交互和持续的过程。临界段机制就是为此而提供的。临界段的局限是只有两种状态:锁定和解锁,在很多的通信中,需要进一步量化的状态,这就产生了信号量机制。
posted @ 2006-05-05 00:15 依旧的博客 阅读(827) | 评论 (0)编辑 收藏

1. 接口

接口是一组函数的集合(更一般情况下,是一组函数和变量的集合),对象和客户(程序的两个不同部分)可通过它进行通信。接口有特定的内存结构,一个接口指针指向一个虚表(vtbl)指针,虚表是一个函数指针的数组,每项指向一个接口函数。

接口是概念性的程序元素,它具有继承和多态性。继承性是指子接口继承了基接口的所有函数,子接口可以转型为基接口。在实现上,子接口的虚表包括了基接口的虚表,子接口的虚表指针可以转型为基接口的虚表指针。多态性是指一个基接口的不同子接口可以有不同的行为。

2. COM接口(组件模型对接口的要求)

COM作为一种二进制组件模型,要求对象和客户尽可能分离,它们的一切联系都通过接口进行。一个对象可以有多个接口,那么,客户在获得第一个接口指针后,应当可以从一个接口指针查询下一个接口指针,以保持对象的使用。客户应当可以通过接口管理对象的生命期,以结束对象的使用。作为一种设计,COM规定从对象的一个接口可以查询它的所有接口,对象生命期管理的责任分散到每个接口(只要客户为每个接口进行生命期管理,就可以实现对象的生命期管理)。在实现上,COM将接口查询和生命期管理的责任集中到一个IUnknown接口,所有接口都从IUnknown派生。COM接口就是从IUnknown派生的接口。

2. COM的面向对象特征

COM在二进制上提供了一种软件结构模型,并且带有面向对象的特征。

  1. 封装

    COM对象是有状态的,数据和操作封装在一起。COM接口和普通API函数的不同,就在于COM对象是有状态的。比如一个宇宙飞船对象(实现IMotion接口,IMotion包含void Fly(double dTime)和double GetPosition()函数),让它飞行一段时间(通过IMotion接口调用Fly()函数)以后它的位置就改变了(在飞行前后调用GetPosition()得到不同结果)。

  2. 多态

    同样的接口可以由不同的COM对象实现,客户程序用统一的方法进行处理,却可以得到不同的结果。接口也可以派生,不同的子接口对基接口的函数有不同的实现。

    在这里解释一下MFC实现COM对象的机制。一个COM对象可以实现多个接口,而这些接口都是IUnknown的子接口,它们对QueryInterface(),  AddRef(),  Release()各有一份实现代码,而在同一对象内,这三个函数的内容完全相同,因此可以抽出来,委派给该对象。又由于对任何COM对象,AddRef()和Release()的实现本质上也相同,因此可以进一步,抽取这两个函数及其操作的数据(m_Ref),放到CCmdTarget中去。QueryInterface()的情况有所不同,它操作的数据是依赖于具体COM对象的接口映射表,可以在把函数放进CCmdTarget的同时,实现一个返回接口映射表的虚函数,QueryInterface()调用此函数获得具体的接口映射表。

  3. 重用

    COM对象可以用包容和聚合两种方式重用已有的COM对象。

    聚合方式实现重用比较复杂。

    在实现对象聚合时,要解决的一个主要问题是在接口查询上对用户保持透明。客户从暴露出来的内部对象接口进行查询,应当查到的是外部对象的接口。那么收到查询时,内部对象的IUnknown应当去委托外部对象的IUnknown。但是内部对象也可能不被用于聚合,应该有一个正常的IUnknown。这样可以考虑把内部对象最初收到查询的IUnknown设成一个代理,它根据聚合与否把查询请求转交给外部对象IUnknown或内部对象的正常IUnknown,即内部对象实现两个IUnknown,作为代理的委托IUnknown和正常的非委托IUnknown。内部对象还要知道外部对象IUnknown,并且能判别自身是否被聚合。可以在创建内部对象时把外部对象IUnknown指针传给它,不是聚合时传递一个空指针,这样内部对象就得到了足够信息。

    引用计数的管理也是一样,内部对象的委托IUnknown区别被聚合与否,调用外部对象IUnknown或自身的非委托IUnknown。

    当然,从外部对象接口要能查到内部对象接口。外部对象需要知道内部对象的IUnknown,以查询所要暴露给客户程序的接口。这个IUnknown应当是内部对象的非委托IUnknown。

posted @ 2006-05-03 21:54 依旧的博客 阅读(1109) | 评论 (0)编辑 收藏

如果有一个随机排列的整数表,怎样将它排序呢?这是生活中也经常碰到的问题。比如给一组牌排序,我们通常会怎样做呢?

不断从原序列中取出元素来排成一个新序列,在新序列形成的时候保证它有序,这就是插入排序的办法。插入排序需要有空间存放和操作新序列,这可以在原序列的空间中满足。插入排序需要大量的比较和移动,量级是O(n*n)。我们也可以每次从现有元素中取出最小元素,这样新序列排下来自然是有序的,这就是选择排序的办法。选择排序需要O(n*n)的比较,最坏最好情况下都一样。这是一个缺点,和与之对称的插入排序比较,选择排序对原序列的情况缺乏适应性,冒泡排序是对此的改进。冒泡排序也基本使用原序列的空间,每次在现有元素中进行比较以寻找最小元素,并通过交换逐步把它移到相应位置。冒泡排序在原序列的基础上逐步调整得到新序列,它可以更加适应原序列的情况,在最好情况下时间为O(n)。这三种排序是最基本的,其它方法都是从各种角度对它们进行改进。

三种基本排序都着眼于从原序列形成新序列的过程。这是最基本的,但还可以把整个过程用分治或渐进的思想来处理。具体的改进方法有多种,希尔排序,快速排序,归并排序等等,我们现在可以欣赏它们的思想,但是当初每种方法的发现都是重要的成果,需要对排序问题有扎实的认识,也需要创造的灵感。

希尔排序把原序列分组,每一组进行插入排序,从较小的分组开始逐渐扩大,直到整个序列作为一组。分组元素是间隔选取的,较小的分组排序完成后,整个序列就在一个较大的尺度上有序了,随着分组的扩大,序列在越来越小的尺度上有序,直到完全有序。希尔排序利用插入排序对乐观情况的适应性,自顶向下渐进处理,避免了直接在小尺度上进行处理的盲目性。

归并排序反映出一种自底向上的分治思想,其时间为O(n*log(n))。

快速排序采用了自顶向下的分治思想,其做法和归并排序有某种对称性。

基数排序也是自顶向下的分治思想,它从关键字本身衡量问题的解决程度。


posted @ 2006-05-03 17:40 依旧的博客 阅读(412) | 评论 (0)编辑 收藏

2NF以上的范式都是对关系上的依赖进行限制,其中最重要的是3NF和BCNF。

  •  BCNF:所有非平凡依赖都以超键为决定子。一个属性集只有包含了整个的键,才能决定集外的属性。
  • 3NF:其非平凡依赖X->A必须满足:X是超键,或者A是主属性。3NF比BCNF有所放松,允许含键不完全的属性集决定集外的属性,但必须是主属性。
  • 不符合3NF的情况有两种:
    1. 键的真子集决定非主属性,即非主属性对键的部分依赖;
    2. 既非超键也非键的真子集决定非主属性,由此将可证明,存在非主属性对键的传递依赖。
    如果一个关系不满足2但满足1,称此关系符合2NF。

2NF和3NF的涵义是:键是关系的标识信息,非主属性是附属信息。如果附属信息对标识信息的依赖不够紧密,关系的语义单纯性就差,从而容易出现各种更新异常。

如果违反2NF,既存在非主属性对键的部分依赖,会有什么问题?例如关系模式SCGT(S#,C#,G,TN),S#是学生号,C#是课程号,G是成绩,TN是任课教师姓名,假设每门课只有一个教师。(S#,C#)是键,C#->TN是非主属性对键的部分依赖,因为它的存在会产生三种更新异常:1). 不开课的教师姓名无法插入;2). 一门课的所有学生都退选,则任课教师姓名无法保留;3). 一门课更换教师时,必须对选该课的所有学生进行修改。非主属性对键的部分依赖反映了附属信息和标识信息的缺乏整体一致性,所以会产生以上问题。

如果符合2NF,但违反3NF,即存在非主属性对键的传递依赖,会有什么问题?例如关系模式SDL(S#,DEPT,LOC),S#是学生号,DEPT是所在系,LOC是系的办公地,这里S#是键,S#->DEPT,DEPT-/>S#,DEPT->LOC,LOC传递依赖于S#,因为它的存在会产生三种更新异常:1). 如果一个系新成立尚未招生,则无法插入;2). 如果一个系不再招生,但仍为其他系开课,则现有学生毕业后,系的信息无法保留;3). 一个系更换办公地时,必须对该系的所有学生进行修改。非主属性对键的传递依赖反映了附属信息和标识信息缺乏直接一致性,所以会产生以上问题。缺乏直接一致不如缺乏整体一致那样严重,所以到了3NF才排除。

那么BCNF的涵义在哪里呢?

2NF和3NF对一个关系模式中的非主属性加以限制,而忽略键之间的关系。如果一个主属性依赖含键不完全的属性组意味着什么呢?可以证明,该依赖涉及不止一个键,其决定子有两种情况,一种是部分键,一种是含部分键和键外的属性。第一种情况下存在一个键之外的属性对该键的部分依赖;第二种情况下,取一个不含前述主属性的键,易知存在该属性对该键的传递依赖,即一个键外的属性对该键的传递依赖,排除这两种情况就得到BCNF。为什么要这样做呢?因为有多个键的情况下,必须照顾每一个键,如果键之外的属性和该键不能保持整体和直接的一致,也可能产生更新异常。例如SCZ(S,C,Z),S,C,Z分别表示街道,城市,邮编,关系模式上的依赖集为{SC->Z,Z->C},SC和SZ都是键。如果插入一个城市的总邮编,必须借助一个街道,删除这个街道,城市的总邮编也被删除,出现这种情况是因为C与SZ键缺乏整体一致性。


参考:

王能斌《数据库系统教程》/电子工业出版社

posted @ 2006-05-02 17:15 依旧的博客 阅读(289) | 评论 (0)编辑 收藏

1. 客户-服务器通信中的基本问题

客户和服务器通信是为了使用服务,为此在传输机制的基础上设计协议,通过对通信行为的规范,实现通信的目的,并解决传输中的问题。

传输机制通常由下层协议提供,根据不同的通信需要选择不同的下层协议,这是一个基本的问题。对应用协议来说,可用的传输机制有可靠连接的字节流类型和不可靠无连接的数据报类型。

服务器处理大量客户的请求,从而并发是服务器的一个基本问题,如何处理这个问题也取决于通信需要。处理方式上,服务器可以是循环的或并发的,并发服务器有多种实现方式(异步I/O,多线程和多进程)。

一件事情能无重复地连续进行,通常会获得更好的效率,这要求主体始终知道当前的状态。一次通信过程的连续性取决于通信双方,它们都要知道通信进行的状态。这对客户一般不成问题,但服务器要和大量客户通信,不一定能为每个客户的每次通信保存状态。如果服务器是有状态的,那么就更快地计算响应,减少通信的数据量。但是传输和客户的故障使有状态服务器面临很大问题,当传输不可靠(报文重复,丢失,乱序)时,服务器维护的状态会和客户失去一致,一个不断崩溃重启的客户会造成状态信息不能发挥作用,而维护开销却极大增加。

这就提出了客户-服务器通信中的三个基本问题,它们的解决方案都取决于实际需要,客户-服务器通信中有哪些情况的需要呢?

  • 是否要求可靠传输;
  • 是否需要服务器进行大量处理。对循环服务器进行分析可以知道,需要大量处理的通信用循环方案可能会丢失请求,用并发方案还可以提高服务器资源利用率,改善性能,只要少量处理的通信则无法忍受开销大的解决方案。
  • 在局域网还是互联网环境下,局域网中传输很少出错,互联网环境则不然;


通常根据前两个基本问题把服务器实现分为四种类型,它们的适用范围如下:

  • 循环无连接服务器,少量处理的通信时,并且在局域网中或不要求可靠传输。这种做法主要是为了避免开销。
  • 循环连接服务器,较少用,主要是循环的方式不够高效,因为连接有一定开销,响应时间可能不低。在少量处理并要求可靠性的情况下使用。
  • 并发无连接服务器,很少用,因为要给每个请求开线程,开销太大。在不要求可靠性的情况下,如果线程开销远小于计算响应开销,或者并发可以让各请求的I/O并行,或者循环方案会丢失请求时可以考虑。
  • 并发连接服务器,常用。


2. winsock基本函数的使用

winsock的基本函数有WSAStartup(),WSACleanup(),socket(),closesocket(),bind(),listen(),accept(), connect(),send()和recv()。

使用这些函数,客户端的大概算法是,

  1.  调用WSAStartup()初始化winsock库。
  2.  调用socket()创建套接字,返回套接字描述符s。
  3.  指定远程套接字地址sa,对s调用connect(),向sa标识的服务进程请求连接。
  4.  连接成功后,对s调用send()发送请求,调用recv()接收响应,如此反复直到完成任务。
  5.  对s调用closesocket()关闭连接。
  6.  不再发起连接处理新的任务时,调用WSACleanup()释放winsock库。

服务器端的大概算法是,

  1. 调用WSAStartup()初始化winsock库。
  2. 调用socket()创建套接字,返回套接字描述符s。
  3. 对s调用bind(),将其绑定到本地的套接字sa。
  4. 调用listen(),将s置为被动模式。此时开始侦听客户端的连接请求,将其放入一个队列。
  5. 对s调用accept(),即从请求队列中取出一项,接受该连接后返回一个新的套接字描述符s',以及对应客户端的套接字地址sa'。
  6. 对s'调用recv()接收请求,调用send()发送响应,如此反复直到完成任务。
  7. 对s'调用closesocket()关闭该连接。
  8. 重复5到7的过程。
  9. 从8退出后,调用WSACleanup()释放winsock库。

有以下几点需要进一步说明,

1). 客户端调用connect()和服务器端调用accept()成功后将在客户进程和服务器进程之间建立一个TCP连接。连接两端的每个套接字描述符都包含一个本地端点地址和一个远程端点地址。所以在使用连接套接字发送数据时不用指示目的地址。

2). 多宿主主机的IP地址选择问题。从上面的算法容易提出这样的问题,为什么客户端在使用套接字时不绑定端点地址?通常的主机只有一个IP,但是多宿主主机有多个IP地址,在这种情况下,客户端为套接字指定的IP可能与实际发送时经过的IP不符,所以允许客户端不指定套接字地址,而由TCP/IP软件在实际发送时指定IP,同时选择一个未用过的端口号,这正是在connect()调用中完成的。那么服务器端就不存在同样的情况吗?不是,在它调用bind()时指定一个套接字地址,其端口部分采用应用协议的熟知端口,而IP地址部分有着同样的问题。为此定义了一个代表统配地址的常量INADDR_ANY,用它指示IP地址部分。实际使用的IP仍然是由TCP/IP软件分配。

3). TCP为一个连接的发送端和接收端各维护一个缓冲区。当发送端缓冲区满的时候,send()调用会阻塞,在接收端缓冲区为空的时候,recv()调用会阻塞。为什么要在通信进程和TCP连接之间维护一个间接层呢?可能是为了在一端有多个进程要使用信道的情况下,在多个进程之间进行信道分配的协调。比如在发送端,信道传输数据时send()调用可以继续执行,多个进程的send()调用同缓冲区打交道,彼此影响不大,因为读写缓冲区速度很快,而信道同缓冲区打交道,这时可以对各进程的发送数据进行协调,实现公平的信道分配。另外,在TCP中有滑动窗口概念,是用于流量控制的,前述缓冲区和滑动窗口有什么关系?我现在不太清楚。

4). 套接字的关闭问题。在客户机和服务器通过TCP连接完成数据交换后,有一个安全关闭的问题。一方面,服务器不能关闭连接,因为客户机可能还有请求,另一方面,客户机虽然知道何时不再请求,但是它不知道服务器的响应何时发送完,因为有些应用协议的响应数据量不确定。为此采用部分关闭的办法,虽然连接是双向的,但是允许在一个方向上关闭它,当客户端不再请求时,可以部分关闭连接,使服务器收到一个信号,如果响应发送完了,服务器就可以关闭连接,此时连接被完全关闭

3. 套接字接口中的端点地址

端点地址用来表示通信的进程,是传输层协议及其套接字接口中的重要概念。不同的协议族可以用不同方式表示端点地址,一个协议族还可以有多个地址族,每个地址族的地址格式不同。TCP/IP只有一个地址族,它的端点地址包括一个32位IP地址和一个16位端口号。在协议族和地址族的基础上,套接字接口用更为具体的结构来表示端点地址。

套接字是一种适用于多个协议族的接口,并允许一个协议族使用多个地址族。TCP/IP协议族及其唯一地址族的标识分别是PF_INET和AF_INET。由于套接字接口的通用性,它提供一个通用的地址结构,其格式为(地址族,该族中的套接字地址)。套接字作为一个接口标准,可以有不同实现,以下我们只讨论windows套接字。

如下定义的sockaddr实现了前述通用地址结构,

// winsock2.h

struct sockaddr {
        u_short sa_family;              /* address family */
        char    sa_data[14];            /* up to 14 bytes of direct address */
};

sockaddr的通用性是相对的,某些地址族不适合这个结构。

尽管sockaddr适合于TCP/IP协议族,但是winsock还定义了TCP/IP专用的地址格式,

// 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_addr结构表示IP地址,定义如下,

//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 */
};

为了保证软件的可移植性与可维护性,访问TCP/IP的代码不应使用sockaddr。只使用TCP/IP的应用程序可以只使用sockaddr_in,而永远不用sockaddr。

4. winsock程序实例

《vc6技术内幕》的例程ex34a包括一个web服务器和三个客户,服务器用winsock实现,一个客户用winsock,另两个用wininet。我们以winsock实现的服务器和客户为例。

CBlockingSocket对各接口函数进行封装,使它们的调用可以统一报错。把错误检查和函数调用一起封装可以避免每次调用这些函数时都检错。为统一报错,采用了异常机制,在检出错误后抛出异常,然后统一进行异常处理。异常机制使我们可以把错误检查和错误处理分开,检查必须是分散的,但是处理可以适当集中,使代码简化。

CHttpBlockingSocket根据接收http报文的特点对CBlockingSocket进行了扩展。成员函数ReadHttpHeaderLine()可以从TCP连接中按行接收字符(它引入了一个缓冲区,缓冲区中收到的字符构成一行后再输出。该缓冲区的长度需要能够容纳每一行字符,溢出时报错。接收输出行的缓冲区也可能长度不足,这时接收的数据不足一行,在调用ReadHttpHeaderLine()时要注意这一点),ReadHttpResponse()用于接收首部行之后所有的响应,当调用者提供的缓冲区不足时会报错。缓冲区不足的情况都是在CBlockingSocket::Receive()函数中检测到的,该函数调用以上层次中的代码按照正常情况编写。

CSockAddr是一个与sockaddr_in同样用途的类,但是用法更方便。winsock函数使用的端点地址结构是sockaddr,sockaddr_in本身用来代替它,所以CSockAddr需要能够替代sockaddr。sockaddr可能用在传值或传址参数中,CSockAddr必须在逻辑上和存储上都和sockaddr有等价性,并实现有关强制类型转换。CSockAddr还实现了和sockaddr, sockaddr_in互相转换的成员函数,因为一种结构很难在所有情况下都好用,新结构也需要和旧结构保持兼容。

 

本例中采用服务器关闭套接字的办法,因为每次连接只处理一个请求。

参考:

《用TCP/IP进行网际互联第三卷(windows套接字版)》/清华出版社

《vc6技术内幕 5th ed》/希望电子出版社

posted @ 2006-05-02 12:46 依旧的博客 阅读(2237) | 评论 (0)编辑 收藏

软件中的对象同领域中的概念有着密切的关系。

我们知道概念是人们在某个领域中实践经验的总结,并可能发展为理论。概念是以客观事物为基础,但不是对客观事物的刻板反映。它来自于实践,所以包含主体因素,这是很重要的。实践是概念的根本来源,理论上的需要对概念形成也有一些作用。

软件中使用的对象类似于领域中使用的概念。《UML与模式应用》中说,面向对象就是按照概念而不是功能进行分解。为什么软件要使用概念性元素呢?因为人的认识是概念性的,而软件由人来使用,人来开发。为了提供有良好概念性的用户接口,软件本身适宜采用概念性元素。由人来开发的软件则更需要采用概念性元素,软件本身和领域实践都有巨大的复杂性,人不是按照功能性元素来思考的,面向对象可以让开发人员用概念性元素思考,从而增加对复杂性的适应和控制能力。

领域概念是对象的重要来源,但是对象也形成于软件开发的过程。一方面,软件的使用没有改变领域实践的本质,至少没有完全改变,而概念反映了领域中已经成熟的认识,所以是非常重要的指导和参考。另一方面,领域实践由软件进行和由人进行确实非常不同,要求进行新的认识,软件本身的需要也会影响到对象的形成。

面向对象的基本特征反映着概念的基本特征。

  1. 封装:概念不是静态的,它的属性和操作不可分。
  2. 通信:概念不是孤立的,概念的属性可以其他概念构成,也可以在属性中引用其他概念,概念的操作过程中会用到其它的概念。
  3. 抽象:从具体概念可以进一步产生抽象概念。
  4. 多态:抽象概念有着多样的具体表现。

Stroustrup的《C++程序设计语言》中说:

“类应该用于模拟程序员的和应用的世界里的那些概念。...一个概念不会孤立地存在,它总与一些相关的概念共存,并在与相关概念的相互关系中表现出它的大部分力量。...因为我们要用类表示概念,问题就变成了如何去表示概念之间的关系。然而,我们无法在程序语言里表述任意的关系。即使能这样做,我们也未必想去做它。我们的类应该定义得比日常概念更窄一些——而且也更精确。”

我们可以体会这段话的深刻性。

  1. 类用来定义概念。首先,概念不是静态的,它的属性和操作不可分,封装是面向对象的第一个特征。然后,概念不是孤立的,一个概念总是与相关概念共存,最基本的关系有两对:封装和通信,抽象和多态。前者反映了分工与合作关系,后者反映了抽象与具体关系。类是面向对象的核心,从探讨类的作用出发,就引出了面向对象的四个基本特征。
  2. 类所定义的概念不但来自应用领域,也来自程序员引入的东西。
  3. “我们无法在程序语言里表述任意的关系。即使能这样做,我们也未必想去做它。”
posted @ 2006-05-02 12:44 依旧的博客 阅读(238) | 评论 (0)编辑 收藏

仅列出标题
共2页: 1 2