雨疏风骤

编程/音乐/护肤/美食

  VC知识库BLOG :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 登录 ::
  14 随笔 :: 0 文章 :: 1555 评论 :: 9 Trackbacks
<2005年3月>
272812345
6789101112
13141516171819
20212223242526
272829303112
3456789

留言簿(112)

随笔分类

随笔档案

文章档案

相册

走过路过不要错过^_^

搜索

最新评论

阅读排行榜

评论排行榜

2005年3月17日 #

C语言中对时间和日期的处理

Chuck Allison

Chuck Allison是盐湖城圣Latter Day教堂总部下耶稣教堂家族历史研究处的软件体系设计师。他拥有数学学士和数学硕士学位。他从1975年起开始编程,从1984年起他开始从事c语言的教学和开发。他目前的兴趣是面向对象的技术及其教育。他是X3J16ANSI C ++标准化委员会的一员。发送e-mailallison@decus.org,或者拨打电话到(801)240-4510均可以与他取得联系。

大部分的操作系统有办法得到当前的日期和时间。通过定义在time.h的库函数,ANSI C能以许多不同的形式得到这个信息。函数time返回一个类型为time_t的值(通常为long),该函数在运行期间对当前的日期和时间进行编码。然后你可以将这个返回值传递给其他能对该值进行解码和格式化的函数。

Listing 1中的程序使用函数timelocaltimestrftime以不同的形式输出当前的日期和时间。函数localtime把已经编码的时间解码成如下的struct

struct tm
{
   int tm_sec;     /* (0 - 61) */
   int tm_min;     /* (0 - 59) */
   int tm_hour;    /* (0 - 23) */
   int tm_mday;    /* (1 - 31) */
   int tm_mon;     /* (0 - 11) */
   int tm_year;    /* past 1900 */
   int tm_wday;    /* (0 - 6) */
   int tm_yday;    /* (0 - 365) */
   int tm_isdst;   /* daylight savings flag */
};

每次当你调用localtime的时候,它会重写一个静态的结构并返回该结构的地址(因此同一时刻在一个程序中只能取得一个这样的结构,而不能做明显的拷贝)。函数ctime返回一个指向静态字符串的指针,该字符串以标准的格式包含了完整的时间和日期。strftime根据用户的指定格式格式化字符串(例如,%A代表一周中每一天的名称)。Table 1列出了格式描述符的完整列表。

时间/日期运算

通过改变tm结构里的值,可对时间/日期进行运算。Listing 2中的程序展示了如何计算将来某天的日期和以秒为单位所计算出的程序执行时间。注意函数time的语法(参数time_t由地址传入,并非作为函数的返回值)。函数mktime改变tm结构的值,以便日期和时间在一个合适的范围内,之后day-of-week (tm_wday)和day-of-year (tm_yday)域进行相应的更新。mktimetm结构中日期和时间的值置于合适的范围之内,相应的更新day of week (tm-wday)和day of year (tm-yday)的值。这种情况发生在当一个日期超出了你的实现能够支持的范围的时候。例如,我的MS-DOS的编译器不能编码1970年1月份之前的日期。函数asctime返回tm参数所描述时间的标准字符串(因此ctime (&tval)与asctime (localtime(&tval)是相等的)。函数difftime返回用秒做单位的两个time_t的差。

如果需要处理超出系统范围的日期,或者需要计算两个日期的间隔又不是用秒来做单位,那你需要设计自己的date编码。Listing 3Listing 5中的应用程序通过使用一个简单的month-day-year结构,展示了确定两个日期间隔的年数、月份数和天数的技术。日期的相减就像你在小学里做的减法那样(例如,首先进行天数的相减,如果需要就向月份数借位,以此类推)。注意跳过的年份都被计算进去了。为了简略起见,date_interval函数假设日期都是有效的,并且第一个日期在第二个日期之前。函数返回一个指向静态Date结构的指针,该结构包含了我们想要的答案。

文件时间/日期戳

大多数操作系统为文件维护时间/日期戳。至少你能得知一个文件最后被修改的时间。(常用的make工具使用这一信息来决定一个文件是否需要被重新编译,或者一个应用程序是否需要被重新连接)。由于文件系统在不同平台上有所不同,没有什么通用的函数得到一个文件的时间/日期戳,因此ANSI 标准没有定义这样的函数。然而,大多数流行的操作系统(包括MS-DOS和VAX/VMS)提供了UNIX函数stat,该函数返回相关的文件信息,包括用time_t表示的最后修改时间。

Listing 6中的程序使用statdifftime来确定是否time1.ctime2.c更新(例如,是否最近被修改过)。

如果你需要更新一个文件的时间/日期戳到当前时间,可简单的重写文件的第一个字节。虽然实际内容并未改变,但你的文件系统会认为文件已经被改变了,并且会相应的更新时间/日期戳。(知道你的文件系统!在VAX/VMS下,当你得到一个文件的新版本的时候,旧的版本仍会被保留)。这种技术叫做“‘touching’一个文件”。Listing 7touch的实现在指定文件不存在的时候会创建一个新文件。注意文件以“binary”模式打开(在打开模式字符串中由字符b决定—在将来的专栏中我会详细讨论文件处理的问题)。

1:strftime的格式描述符

Code  Sample Output
---------------------------------------------
%a    Wed
%A    Wednesday
%b    Oct
%B    October
%c    Wed Oct 07 13:24:27 1992
%d    07    (day of month [01-31])
%H    13    (hour in [00-23])
%I    01    (hour in [01-12])
%j    281   (day of year [001-366])
%m    10    (month [01-12])
%M    24    (minute [00-59])
%p    PM
%S    27    (second [00-59] )
%U    40    (Sunday week of year [00-52])
%w    3     (day of week [0-6])
%W    40    (Monday week of year [00-52])
%x    Wed Oct 7, 1992
%X    13:24:27
%y    92
%Y    1992
%Z    EDT   (daylight savings indicator)

Listing 1 time1.c — 采用不同格式输出当前的日期和时间

#include <stdio.h>
#include <time.h>
 
#define BUFSIZE 128
 
main()
{
   time_t tval;
   struct tm *now;
   char buf[BUFSIZE];
   char *fancy_format =
     "Or getting really fancy:\n"
     "%A, %B %d, day %j of %Y.\n"
     "The time is %I:%M %p.";
 
   /* Get current date and time */
   tval = time(NULL);
   now = localtime(&tval);
   printf("The current date and time:\n"
         "%d/%02d/%02d %d:%02d:%02d\n\n",
     now->tm_mon+1, now->tm_mday, now->tm_year,
     now->tm_hour, now->tm_min, now->tm_sec);
   printf("Or in default system format:\n%s\n",
         ctime(&tval));
   strftime(buf,sizeof buf,fancy_format,now);
   puts(buf);
 
   return 0;
}
 
/*  Output
The current date and time:
10/06/92 12:58:00
 
Or in default system format:
Tue Oct 06 12:58:00 1992
 
Or getting really fancy:
Tuesday, October 06, day 280 of 1992.
The time is 12:58 PM.
*/
 
/* End of File */

Listing 2 time2.c —展示如何计算将来某一天的日期以及以秒为单位计算出的执行时间

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
main()
{
   time_t start, stop;
   struct tm *now;
   int ndays;
 
   /* Get current date and time */
   time(&start);
   now = localtime(&start);
 
   /* Enter an interval in days */
   fputs("How many days from now? ",stderr);
   if (scanf("%d",&ndays) !=1)
      return EXIT_FAILURE;
   now->tm_mday += ndays;
   if (mktime(now) != -1)
      printf("New date: %s",asctime(now));
   else
      puts("Sorry. Can't encode your date.");
 
   /* Calculate elapsed time */
   time(&stop);
   printf("Elapsed program time in seconds: %f\n",
     difftime(stop,start));
 
   return EXIT_SUCCESS;
}
 
/* Output
How many days from now? 45
New date: Fri Nov 20 12:40:32 1992
Elapsed program time in seconds: 1.000000
*/
 
/* End of File */

Listing 3 date.h — 一个简单的日期结构

struct Date
{
   int day;
   int month;
   int year;
};
typedef struct Date Date;
 
Date* date_interval(const Date *, const Date *);
/* End of File */

Listing 4 date_int.c — 计算两个日期的间隔

/* date_int.c: Compute duration between two dates */
 
#include "date.h"
 
#define isleap(y) \
 ((y)%4 == 0 && (y)%100 != 0 || (y)%400 == 0)
 
static int Dtab [2][13] =
{
  {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
  {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
 
Date *date_interval(const Date *d1, const Date *d2)
{
   static Date result;
   int months, days, years, prev_month;
 
   /* Compute the interval - assume d1 precedes d2 */
   years = d2->year - d1->year;
   months = d2->month - d1->month;
   days = d2->day - d1->day;
 
   /* Do obvious corrections (days before months!)
    *
    * This is a loop in case the previous month is
    * February, and days < -28.
    */
   prev_month = d2->month - 1;
   while (days < 0)
   {
      /* Borrow from the previous month */
      if (prev_month == 0)
         prev_month = 12;
      --months;
      days += Dtab[isleap(d2->year)][prev_month--];
   }
 
   if (months < 0)
   {
      /* Borrow from the previous year */
      --years;
      months += 12;
   }
 
   /* Prepare output */
   result.month = months;
   result.day = days;
   result.year = years;
   return &result;
}
/* End of File */

Listing 5 tdate.c — 举例说明日期间隔函数的使用

/* tdate.c: Test date_interval() */
 
#include <stdio.h>
#include <stdlib.h>
#include "date.h"
 
main()
{
   Date d1, d2, *result;
   int nargs;
 
   /* Read in two dates - assume 1st precedes 2nd */
   fputs("Enter a date, MM/DD/YY> ",stderr);
   nargs = scanf("%d/%d/%d%*c", &d1.month,
     &d1.day, &d1.year);
   if (nargs != 3)
      return EXIT_FAILURE;
 
   fputs("Enter a later date, MM/DD/YY> ",stderr);
   nargs = scanf("%d/%d/%d%*c", &d2.month,
     &d2.day, &d2.year);
   if (nargs != 3)
      return EXIT_FAILURE;
 
   /* Compute interval in years, months, and days */
   result = date_interval(&d1, &d2);
   printf("years: %d, months: %d, days: %d\n",
      result->year, result->month, result->day);
   return EXIT_SUCCESS;
 
}
/* Sample Execution:
Enter a date, MM/DD/YY> 10/1/51
Enter a later date, MM/DD/YY> 10/6/92
years: 41, months: 0, days: 5 */
/* End of File */

Listing 6 ftime.c — 确定是否time1.c比time2.c更新

/* ftime.c: Compare file time stamps */
 
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
 
main()
{
   struct stat fs1, fs2;
 
   if (stat("time1.c",&fs1) == 0 &&
      stat("time2.c",&fs2) == 0)
   {
      double interval =
        difftime(fs2.st_mtime,fs1.st_mtime);
 
      printf("time1.c %s newer than time2.c\n",
        (interval < 0.0) ? "is" : "is not");
      return EXIT_SUCCESS;
   }
   else
      return EXIT_FAILURE;
}
/* Output
time1.c is not newer than time2.c */
/* End of File */

Listing 7 touch.c —通过覆盖旧文件或者创建一个新的文件来更新时间戳

/* touch.c: Update a file's time stamp */
 
#include <stdio.h>
 
void touch(char *fname)
{
   FILE *f = fopen(fname,"r+b");
   if (f != NULL)
   {
      char c = getc(f);
      rewind(f);
      putc(c,f);
   }
   else
      fopen(fname,"wb");
 
   fclose(f);
}
 
/* End of File */

 

发表于 2005-03-17 12:34 燕七 阅读(11665) | 评论 (322)编辑 收藏

 

Duane Burton Sr. Technical Marketing Engineer
Intel Corporation

Jim Huang
Sr. Technical Marketing Engineer
Intel Corporation

2002年六月

应用于
Microsoft® Windows® XP

摘要: 本文介绍了怎样利用实时通信(RTC)应用编程接口(API)来实现音视频会议、应用程序共享、白板、简单的点对点聊天和音视频调节向导。RTC API提供了卓越的基于PC的通信上的革新,这可应用于所有基于Microsoft Windows XP应用程序。

下载 RTCSampleCode.zip.

目录

绪论
RTC
客户端接口
初始化
会话
处理RTC
事件
创建会话
处理实时流会话事件
关闭
会话
平台性能
结论
参考资料

绪论

微软的实时通信(RTC)应用编程接口(API)提供了显著的基于PC的通信上的革新—即时消息、音视频会议和应用程序共享/合作,这可应用于所有基于Microsoft® Windows® XP的应用程序。

使用RTCAPI来进行通信是一个非常简单的过程。
1
、客户端应用程序确定参与通信的平台的性能。
2
、应用程序选择通信中首选的视音频设备。
3
、应用程序初始化会话。
4
RTC层调整数据的获取、压缩和传输,这使得应用程序不用负责这一任务。使用哪一种音视频的编码解码器 由通信双方的连接质量决定。
5
参与会话的应用程序接受、解压并重放被传输的数据。

插图见连接:
图一、音视频会议的界面

本文描述了怎样为一个应用程序添加PC-to-PCRTC基本能力;我们假定你对使用COM对象开发Windows应用程序已经很熟悉。本文所讨论的源代码可在本文开始所给出的连接里获得。我们以后将会讨论PC-to-Phone、现场能力和XML配置。

例子代码展示了使用实时通信API实现音视频会议、应用程序共享、白板、简单的点对点聊天和音视频调节向导的基本要素。其他RTC 支持但本文没有讨论的特征有回波抵消(AEC)、前向错误校验(FEC)、 带宽估计、动态抖动缓冲管理、自动增益控制(AGC)和服务质量(QC)控制算法。在《Microsoft Windows的实时通信客户端的媒体支持》中描述了上述特征。

RTC客户端接口

所需头文件: rtccore.h

你的应用程序需要通过CoCreateInstance()来获得RTC接口CLSID_RTCClientGUID = {7a42ea29-a2b7-40c4-b091-f6f024aa89be})作为参数。一旦获得了接口,用Initialize()来初始化COM对象,以确定该平台的通信能力。

   // Initialize the RTC COM object

   hr = CoCreateInstance (CLSID_RTCClient, NULL,

      CLSCTX_INPROC_SERVER, IID_IRTCClient,

      (LPVOID *)&m_pClient);     

 

      // Initialize the client interface

hr = m_pClient->Initialize();

选择通信能力

下一步是选择首选的通信类型和相关设备(摄像机、麦克风等)。缺省配置是激活所有通信类型。如果会话的参与者能共享应用程序、传送即时消息和音视频会议,那么这些通信类型自动被激活。如果某一参与者不支持某种通信类型,那么所有的参与者都不能激活该类型。

   m_pClient->SetPreferredMediaTypes ( RTCMT_ALL, VARIANT_TRUE );

会议参与者的平台性能和可用带宽决定了使用哪一种codec

·                     视频:Windows RTC 客户端支持分辨率为QCIF176×144)的H.261H.263 codecs 这些比特率可变的codecs6125 Kbps传送视频数据。使用IRTCClient方法中的put_MaxBitRateput_TemporalSpatialTradeOff有可能会影响到视频传送的空间和瞬时清晰度。

·                     音频:Windows RTC客户端支持许多音频codec。音频codec由连接的两端共同决定。下表列出了所支持的音频codec

CODEC

取样率(kHz)

比特率(Kbps)

帧长 (msec)

G.711

8

64

20

G.722.1

16

24

20

G.723

8

6.4

30, 60, or 90

GSM

8

13

20

DVI4

8

32

20

SIREN

16

16

20, or 40

调整通信设备

选择了首选的通信类型和相关设备之后,调整通信设备。RTCAPI提供了向导对摄像机和麦克风进行调整。使用RTCClient的方法InvokeTuningWizard()可调整它们的设置。

插图见连接:
2:摄像机调节向导

3:麦克风调节向导

初始化会话

在应用程序与其他参与者连接之前,它必须能够处理会话中的RTC事件。PC-to-PC的通信中,应用程序捕获即时消息事件、音量事件、媒体事件、客户端消息事件和会话状态改变事件。下述代码展示了怎样创建一个事件过滤器来捕获RTC客户端的特定事件。

lEventMask设置了一组应用程序感兴趣的事件。(为获得全部的事件列表,可在MSDN站点搜索RTC_EVENT,这样可以获得关于每一事件的更多信息。CRTCEvents 类在客户端之间分配事件。 RTCEvents 对象在应用程序和 IRTCEventNotification接口之间创建接口。 所有的RTC事件由RTCEvents 类处理。

    // Set the event filter to listen for RTC events

    // Using RTCEF_ALL will listen for all events

    // For the sample application, we will demonstrate how to set the

    // event listener for a limited set of events.

    long lEventMask = RTCEF_SESSION_STATE_CHANGE |

                 RTCEF_MESSAGING |

                 RTCEF_MEDIA |

                 RTCEF_INTENSITY |

                 RTCEF_CLIENT;

 

    hr = m_pClient->put_EventFilter( lEventMask );

 

    // Create the event sink object

    m_pEvents = new CRTCEvents;

 

    // initialize the event handler

    hr = m_pEvents->Advise( m_pClient, m_hWnd );

 

    // Set the listen mode for RTC client

    // RTCLM_BOTH opens the standard SIP port 5060, as well as

    // a dynamic port.

    hr = m_pClient->put_ListenForIncomingSessions(RTCLM_BOTH);

音视频的媒体类型可在会话过程中添加或删除,因此客户端必须能监听这些类型的事件。在“处理实时流会话事件”这一节中可获得关于状态改变和事件处理的更多信息。

处理RTC事件

一旦事件处理者IRTCEventNotification接收器中进行了注册,接收和处理RTC事件就变得相当的容易了。当例子程序接收到RTC事件时,应用程序的事件处理者就对应用程序的消息处理者发一个消息。OnRTCEvent() 处理应用程序接收到的所有事件。

OnRTCEvent(UINT message, WPARAM wParam, LPARAM lParam)

{

 

    // Based on the RTC_EVENT type, query for the

    // appropriate event interface and call a helper

    // method to handle the event

    switch ( wParam )

    {

       ….

                ….

       ….

        case RTCE_MEDIA:

            {

                IRTCMediaEvent * pEvent = NULL;

 

                hr = pDisp->QueryInterface( IID_IRTCMediaEvent,

                                            (void **)&pEvent );

 

                if (SUCCEEDED(hr))

                {

                    OnRTCMediaEvent(pEvent);

                    SAFE_RELEASE(pEvent);

                }

            }

            break;

                ….

                ….

       ….

}

创建一个会话

当你在RTC中发起一个呼叫之前,你必须创建并且初始化一个会话。 然后你可以输入参与者的IP地址来发起一个呼叫。可可能通过属于一个e-mail 地址或者一个电话号码来激活一个会话。然而,这一功能需要一个SIP注册服务器,对它的讨论超出了本文的范围。参阅MSDN可获得关于SIP注册服务器的更多信息。

RTC目前还不支持多方视频通话,因此应用程序在初始化一个新会话之前,必须保证没有视频会议在进行。在它第一个发布版本中,Windows RTC客户端只支持多方电话会议,并不支持多方音视频会话和视频会议。

为与另一台PC通话,确定RTC会话类型并且使用IRTCSession接口创建一个同类型的会话。下列代码展示了如何创建会话。

HRESULT CAVDConfDlg::MakeCall(RTC_SESSION_TYPE enType, BSTR bstrURI)

{

    ...

 

    // Create the session

    IRTCSession * pSession = NULL;

 

    hr = m_pClient->CreateSession(enType, NULL, NULL, 0, &pSession);

 

    // Add the participant to the session

    hr = pSession->AddParticipant(bstrURI, NULL, &m_Participant);

 

    ...

    return S_OK;

}

处理实时流会话事件

根据不同的会话类型,存在媒体事件、音量事件、即时消息事件和会话状态改变事件。

媒体事件

处理媒体事件需要得到媒体类型、事件类型和原因,然后发送消息给会话窗口。应用程序可以使用get_MediaType()从视频、音频、T120和实时传输协议(RTP)事件中接收消息。例子程序展示了如何获得媒体事件并将其发送给媒体对话框去处理。

void CAVDConfDlg::OnRTCMediaEvent(IRTCMediaEvent *pEvent)

{

    ...

 

    hr = pEvent->get_MediaType(&lMediaType);

 

    hr = pEvent->get_EventType(&enType);          

   

    hr = pEvent->get_EventReason(&enReason);

 

    if ((m_AVDlg) && (m_AVDlg.GetState () != RTCSS_IDLE))

    {

        // Deliver the media state to the session window

        m_AVDlg.DeliverMedia(lMediaType, enType, enReason);

    }

}

音量事件

当扬声器或者麦克风的音量水平发生变化时产生音量事件。应用程序可使用get_Direction()函数获得发生改变的音频设备。一旦确定了设备,应用程序可获得设备的属性并处理改变。应用程序可通过slider控件来显示音量的改变,或者显示给用户一个音量表。

void CAVDConfDlg::OnRTCIntensityEvent(IRTCIntensityEvent *pEvent)

{

    ...

 

    hr = pEvent->get_Direction(&enDevice);

 

    hr = pEvent->get_Level(&lLevel);

 

    hr = pEvent->get_Min(&lMin);

 

    hr = pEvent->get_Max(&lMax);

 

    if (m_AVDlg.GetState () != RTCSS_IDLE)

    {

        // Deliver the intensity state to the session window

        m_AVDlg.DeliverIntensity(enDevice, lLevel);

    }

}

即时消息事件

使用IRTCMessagingEvent可在会话参与者中传递即时消息。当一个消息事件产生时,应用程序必须获得会话和事件类型,得到相关会话中的参与者信息,以便能将消息传递给适当一方。事件处理者也能处理会话状态的改变。

HRESULT CAVDConfDlg::OnRTCMessagingEvent(IRTCMessagingEvent *pEvent)

{

    ...

 

    hr = pEvent->get_Session(&pSession);

 

    hr = pEvent->get_EventType(&enType);

 

    hr = pEvent->get_Participant(&pParticipant);

 

    if (enType == RTCMSET_MESSAGE)

    {

        hr = pEvent->get_MessageHeader(&bstrContentType);

 

        hr = pEvent->get_Message(&bstrMessage);

 

        // Deliver the message to the session window

        if (m_cMessageDlg)

      m_cMessageDlg.DeliverMessage(pParticipant, bstrContentType,

         bstrMessage);

 

    }

    else if (enType == RTCMSET_STATUS)

    {

        hr = pEvent->get_UserStatus(&enStatus);

 

        // Deliver the user status to the session window

        m_cMessageDlg.DeliverUserStatus(pParticipant, enStatus);

    }

   return S_OK;

}

会话状态改变事件

会话状态改变事件的处理过程与其他RTC事件相同。会话状态的改变包括建立一个新的音/视频会话,或者通知客户端一个到来的即时消息。下列例子展示了当请求会话时所作的处理;客户端通过一阵铃声被通知,请求被应答,然后会话开始。

Void CAVDConfDlg::OnRTCSessionStateChangeEvent(IRTCSessionStateChangeEvent

   *pEvent)

{

    ...

 

    hr = pEvent->get_State(&enState);

 

    hr = pEvent->get_Session(&pSession);

 

    switch ( enState )

   {

   case RTCSS_INCOMING:

        {

      ...

 

            // This event is called when an incoming call occurs

            RTC_SESSION_TYPE enType;

 

            hr = pSession->get_Type(&enType);

 

            // Ring the bell

            m_pClient->PlayRing(RTCRT_PHONE, VARIANT_TRUE);

 

            // Accept the session

            hr = pSession->Answer();

         }

     }

    ...

}

应用程序共享

开启T120应用程序共享非常容易,只需调用IRTCClient 接口的StartT120Applet 方法。

hr = m_pClient->StartT120Applet ( RTCTA_APPSHARING );

白板支持

在应用程序中支持白板,需要调用StartT120Applet 方法,使用 RTCTA_WHITEBOARD 枚举作为参数。

hr = m_pClient->StartT120Applet ( RTCTA_WHITEBOARD );

关闭会话

要关闭一个会话,所有正在运行T120的应用程序必须被关闭。然后RTC 客户端接口调用ShutDown()并完成关闭会话的过程。

平台性能

使用RTC进行通信需要处理器具有适当的性能。下列例子中,一个1 GHzPentium® III处理器和一个2.2 GHzPentium 4处理器用于确定当使用RTC特征时处理器的利用率。下表描述了使用本文介绍的RTC特征时处理器的利用率。

任务

P4 处理器 at 2.2-GHz (% CPU 占用率n)1

P III 处理器 at 1.0-GHz (% CPU 占用率)2

音视频会议

9%

22%

添加程序共享(共享IE浏览器)

10%

35%

增加白板

12%

37%

增加即时

12%

37%

1 P4处理器配置:Intel®主板D850MV256MB PC800 RDRAM,主板集成声卡, nVidia* GeForce*2 Ultra; Windows XP专业版

2 P III处理器配置:Intel 主板VC820; 256MB PC133 SDRAM, nVidia* GeForce*2 Ultra, Creative* Sound Blaster* Live*, Windows XP 专业版

结论

通过使用实时通信客户端API,在Windows XP下开发通信工具已变得相当简单。开发者可迅速设计、配置和开发他们的应用程序。现有的音视频会议应用程序可通过添加RTC的丰富的通信特征而获益匪浅。使用RTC API进行开发的程序可从一个统一的通信协议中获益。这提高了你的程序与其他文本消息和音视频会议程序的互相合作的能力。将RTC APIIntel的处理器以及Microsoft Windows XP相结合,你正在传达着一种创新的通信体验。

参考资料

Intel Developer Services

Media Support in the Microsoft Windows Real-time Communications Client

Microsoft Platform SDK: Real-Time Communications Client

发表于 2005-03-17 12:19 燕七 阅读(7267) | 评论 (72)编辑 收藏

Windows实时通信技术的应用

Tom Fout

Microsoft Corporation

January 15, 2002

摘要:Windows XP RTC的客户端API使得开发人员能够在自己的应用程序中实现实时通信。本文介绍了RTC技术和应用这一技术所必须的组件。

 

目录:绪论

RTC-Enabled 应用

RTC-Enabled应用场合

RTC客户端应用程序接口

RTC对象

Profiles and Provisioning

客户端事件

RTC接口

更多信息

 

绪论

今天,我们可以采取很多方法和我们的伙伴、客户、支持者、家庭和朋友进行交流。E-mail已经成为一个普遍深入的交流方式,但是它的实时性不强。电话被证明是一种好的交流方式并且具有实时性,但是它也有局限性,比如只有声音的交流,并且它缺乏一种机制来使我们知道是否我们交流的另一方已经准备好接听我们的电话。

 

走进实时通信

Real-Time Communications (RTC)提供了丰富的与现场信息相结合的通信和协作特征,使你能够知道在何时何地找到你的联系人。RTC的许多特征以另外的形式或者在其他的应用中有所体现,但是这些特征从未被结合为一个单独的实现。

例如,Instant Messaging (IM)即时消息是发展最快的Internet通信机制之一,因为它实现简单并且效率高。IM具有实时性,而且具有现场能力,使你知道何时能找到你的联系人。但是它不具备另外一些特征,例如音频和视频会议以及在线合作等。

Microsoft® Windows® XPMicrosoft® Windows Messenger中包含了所有的实时通信功能。Windows Messenger使你能与你的客户和同事通过使用文本、音频(语音电话可以在PCPCPC到电话、电话到电话之间建立)、视频、共享白板和应用程序的方式进行交流。你可以建立一个联系人清单,通过现场信息得知你的联系人何时在线何时可以联系得到。并且,这些能力并非只限于Windows Messenger,通过使用RTC客户端API可用来丰富其他的应用程序。

 

RTC-Enabled应用程序

Windows Messenger客户端支持Windows XPRTC客户端组件。这一客户端在一个单独的应用程序中体现了所有RTC能力。然而,在许多情况下,因为通信的负担很重,往往需要一个第二方程序。

RTC基于网络技术和协议标准,这使得其他的设备和应用程序能使用这一技术与Windows Messenger进行通信。

Windows XPRTC客户端的实现也提供了一个开放的应用程序接口,因此,基于Windows平台的应用程序可增加RTC功能。例如,一个基于web的购买程序可包含一个即时求助按钮。点击该按钮之后,应用程序就会打开一个在客户与技术支持人员之间建立的IM会话。

 

RTC-Enabled应用场合

Windows Messenger在一个单独的应用中包含了RTC的许多功能。这一解决方案有许多用处并且在许多场合可能是最好的解决方案。很多情况下,在你的应用程序中包含这些功能会使事情变得更容易,更完善,更棒。

下面是一些将RTC功能包含进应用程序的例子。使用这一技术的其他例子还有很多。

客户关系管理——桌面远程帮助

    Jim想在在线书店购买一本书,但是他的信用卡好像有点问题。通过点击网页上的一个按钮,他立即与Amazon(书店的名字)的代表建立了IM会话。通过即时消息和应用程序的共享,代表发现了并解决了问题。这本书也被定购了。若没有这种实时帮助的能力,Jim很可能就放弃了这次购买,这笔生意也就丢掉了。

知识管理——团队和项目的通信站点

    产品研发团队的站点有责任的分工(例如客户机或者服务器团队)。通过浏览不同的责任区域,用户能够找到一个列出了团队所有成员以及他们在场与否的面板。通过点击相关的连接,用户能够立即看到团队里的成员。这种即时知识的出现减少了E-mail和电话的数量,减少了失败。

电子商务——合作伙伴/供应商的通信

一个庞大的制造厂商有很多供应商,这些供应商连接到不同的外部网络中。不采用发送电子邮件等方式来讨论订单,他们可以使用RTC的客户端API开发一个应用程序,用该程序传达特定信息。例如,制造商可以使用IM发一个1000个器具订单,并且能够立即确认订单已经传送到了供应商一方并且进入了供应商的系统。随着订单的增长,订单发送的状况可以在需要的时候随时取得。

系统警告——发送至用户PC机桌面或者移动设施

一个公司已经开发了一个E-mail服务器。为了使技术支持的电话能减少,他们开发了一个小的应用程序,这个程序能通知所有某一特定的E-mail服务器用户(使用IM)说这个服务器将要暂停以便进行例行的维护保养。即使E-mail服务器已经不能上线,用户也能收到消息,使得技术支持人员得以清静。

游戏

大多数的多人游戏都包括了和对手以及同伴聊天的机制。但是你能想象在你将要结束你的朋友的游戏的时候看到他的脸么?

其他用处

    在应用程序中RTC的功能还有很多其他潜在的用处。通过将RTC集成到现存的商业软件中可以实现即时通信和协作,这导致了生产力的提高,为很多行业和部门,包括金融、制造业、卫生保健和人力资源等带来了好处。

RTC客户端应用程序接口

为了获得与在Windows Messenger中实现的同样的RTC客户端功能,Windows XP通过RTC客户端API开放了RTC。这一API使应用程序能够创建从PC-PC, PC-phone或者 phone-phone的呼叫。应用程序能够增添在Internet或者Intranet上创建IM会话的功能。在PC机用户之间,语音和视频呼叫都可以建立。应用程序能获得并显示一系列联系人的现场信息。通过增加应用程序和白板的共享的功能,可以加强双方的合作。

谁可以使用这些接口呢?内部的开发团体可以使用这些API增加内部工具的功能。软件公司可以使用RTC APIRTC功能集成到自己的应用程序中,或者建立用于出售的专门软件,或者用来满足客户的需要。电话服务提供者可为住宅或者公司用户建立应用程序。想要出售RTC服务的电信提供者可通过使用这些APIs为他们的客户建立客户端应用程序。可使用C++Microsoft® Visual Basic®建立应用程序。

 

RTC对象

RTC的基本编码模型是COM。在RTC中用于通信的对象分别是Client客户端、Session会话对象和Participant参加者对象。插图见连接:

1.RTC客户端COM对象

Client对象:Client对象实现了IRTCClient接口,并且为会话建立了允许的类型和参数,比如首选设备、媒体类型和比特率和其他的媒体属性,比如音量和回波抵消。这个接口也被用于创建会话对象。

Session对象: Session对象通过IRTCClient::CreateSession方法创建,被用于管理一个会话。这个对象实现了IRTCSession接口,这一接口用于初始化、回复或者终止一个会议,增加参加者和其他的会议管理。这个对象支持IM 和其他的会话类型。Session对象可用于,例如,创建一个与你的朋友的IM会议,或者开始一个与Web站点代表的音频呼叫。

Participant对象:Participant对象由IRTCSession::AddParticipant方法创建,包含了与会议参加者有关的所有方法。它包括了参加者的姓名和当前状态。IRTCParticipant接口 由该对象实现。

现场信息由Buddy Watcher对象管理。这些对象提供接口,用于管理得到的与联系人和现场成员有关的信息。

Buddy对象:Buddy对象用于管理、设置和取回与联系人相关的信息。Buddy object is created 当通过IRTCClientPresence::AddBuddy方法添加了一个联系的时候,一个Buddy对象被创建(IRTCClientPresenceClient 对象实现)。诸如联系人的姓名和状态的信息可由IRTCBuddy接口获得。

Watcher对象:Watcher用于获取一个watcher的信息, watcher把你添加为联系人。这个对象也可使用AddWatcher方法通过IRTCClientPresence接口添加。Watcher的状态和其他信息使用在IRTCWatcher接口中实现的方法获得。

构造文档(profile)和设置

一个构造文档(profile)对象用于维护客户端的设置。一个构造文档(profile)对象包括了客户端的显示、用户姓名、支持的会议类型和客户端RTC提供者的信息。这一信息可以获得网络资源的情况,并且能用于设置呼叫或者处理出席信息。构造文档对象(profile)由IRTCClientProvisioning接口(在Client对象中实现)创建。

客户端创建了一个构造文档来存储信息,用于客户进入网络上的服务。一个客户端应用程序必须创建一个XML文档来保存客户端的配置。个别的构造文档(profile)可使用设不同的提供者、代理服务器或者网关提供给客户端不同类型的服务。

初始化RTC之后,客户端应用程序创建并且激活一个构造文档(profile)对象。

客户端事件

RTC的客户端API允许通过不同的客户端事件通知应用程序,包括消息事件、会话状态变化、参与者状态变化、出席状态变化、设备变化和网络质量变化等。在具体实现中,应用程序通过IRTCClien接口设置一个事件过滤器,并且在应用程序A中注册一个IRTCEventNotification事件接口。当感兴趣的事件发生时,将事件类型和对象提供给AA的事件函数进行处理。

 

RTC接口

上文所讨论的RTC对象的几个接口已经实现了。这些接口可用于添加期望的RTC功能到应用程序中。RTC的接口和相关方法在SDK中有详细的文档描述。

 

更多信息

要想获得关于XP的最新信息,请参阅Windows XP home page

要想获得关于RTC客户端API的详细信息,请参考SDK里的Real-Time Communications (RTC) Client documentation

发表于 2005-03-17 12:17 燕七 阅读(3632) | 评论 (13)编辑 收藏

Chuck Allison是盐湖城圣Latter Day教堂总部下耶稣教堂家族历史研究处的软件体系设计师。他拥有数学学士和数学硕士学位。他从1975年起开始编程,从1984年起他开始从事c语言的教学和开发。他目前的兴趣是面向对象的技术及其教育。他是X3J16ANSI C ++标准化委员会的一员。发送e-mailallison@decus.org,或者拨打电话到(801)240-4510均可以与他取得联系。

 

在上个月的封装中我提出了一个简单的C++日期类的雏形。为了提供一个能够计算两个日期的间隔的函数,这个类举例说明了C++的下列特征:

 

· 内联函数

· 引用

· 构造函数

· 对私有数据成员的访问控制

 

在这个月的部分里我将增加相关的运算符、输入/输出操作和得到当前日期的能力。它们示范了下列特征:

 

·  运算符重载

· 

·  友元函数

·  静态成员

 

当使用日期的时候你经常需要确定某一日期是否在另一日期之前。我将为日期类增加下面这个成员函数(参见 Listing 1):

int compare(const Date& d2) const;

Date::compare类似于strcmp如果当前对象(*this)d2之前,它返回一个负整数;如果这两个日期相同,则返回0;否则返回一个正整数(参见 Listing 2 中的函数实现和 Listing 3 中的示例程序)。就像你们都很熟悉的C标准库中的qsort一样,你也可以使用Date::compare来对日期进行排序,就好像你使用strcmp对字符串进行排序一样。下面是一个可传递给qsort的比较函数(下个月的代码封装将包括qsort):

#include "date.h"
int datecmp(const void *p1, const void *p2)
{
   const Date
   *d1p = (const Date *) p1,
   *d2p = (const Date *) p2;
   return d1p->compare(*d2p);
}

运算符重载

大多数时候,拥有相关的运算符是更方便的,例如:

if (d1 < d2)
  // do something appropriate..

使用Date::compare来添加一个“小于”运算符是非常容易的——只要在类的定义里插入下面这个内联成员函数就可以了:

int operator<(const Date& d2) const
{return compare(d2) < 0};
每一个表达式:d1 < d2出现的地方,都会被编译器翻译成函数调用的形式:
d1.operator<(d2)
 
Listing 4 中类的定义中拥有六个相关的操作符,Listing 5中展示了更新之后的示范程序。
 
既然函数Date::interval 的功能类似减法(它给出两个日期的差),把它重命名为Date::operator-就是件很自然的事情了。在做这个事情之前,我们仔细研究一下下列语句的语音:
a = b - c;
无论变量是什么类型,下述语句总是成立的:
 
a 是一个由减法产生的明确的对象,并且 b - c == - (c - b) 
 

我们使用下列约定俗成的习惯,即一个正的日期对象的所有数据成员都是正的,反之亦然(不允许符号的混合)。在Listing 7中我用Date::operator- (const Date&)代替了Date::interval前者为每一个数据成员增加了正确的符号并且返回重新构造过的类的对象。Listing 6中重新定义的类中还包括了一个一元的“-”运算符函数,它的名字还是Date::operator-,但是没有任何参数。编译器将把下列的语句

d1 - d2;
-d1;

分别替换为:

d1.operator-(d2); // Calls Date::operator-(const Date&)
d1.operator-();   // Calls Date::operator-()

Listing 8中有一个使用了新的成员函数的简单示例程序。

输入输出流

正如我以前所说的一样,一个日期类的对象应该具有和系统内建类型一致的外观和感觉——输入/输出支持。C++提供了能够处理标准类型的的输入输出操作的流的对象。例如,下列程序

#include <iostream.h>
 
main()
{
  int i;
  cout << "Enter an integer: ";
  cin >> i;
  cout << "You typed " << i << endl;
  return 0;
}
的输出结果为:
Enter an integer: 5
You typed 5
 
  coutC++流库中提供的output流(类ostreom)而cinC++流库中提供的input流(类istreom),它们分别与标准输出和标准输入相关。当编译器看到下面的表达式:
cout << "Enter an integer: "
它将用如下语句代替:
cout.operator<<("Enter an integer: ")
上述语句调用了成员函数ostream::operator<<(const char *)。同样的,表达式
cout << i调用了函数ostream::-operator<<(int)endl是一个特殊的流指示,它输出一个换行符并清空输出缓冲区。Output行可以连在一起:
cout << "You typed " << i
因为ostream::operator<<返回一个到stream自身的引用。上述语句变成了
(cout.operator<<("You typed ")).operator<<(i)
为了适应Date对象的输出,你需要一个全局函数,该函数将要输出的内容发送给一个给定的输出流,并且返回到那个流的引用:
ostream& operator<<(ostream& os, const Date& d)
{
   os << d.get_month() << '/'
   << d.get_day() << '/'
   << d.get_year();
   return os;
}
这当然不能是一个成员函数,因为流(并非正在被输出的对象)总是出现在流插入符号的左边。
 
友元
为了提高效率,通常会赋予operator<<进入到一个对象的私有数据成员的权限(大多数的类的实现都提供了相关的I/O操作符,因此在这种情况下打破封装的边界似乎是比较安全的)。为了穿破对私有数据成员访问的限制,你需要在类的声明中加入如下语句,把operator<<声明为Date的友元:
friend ostream& operator<<(ostream&, const Date&);
Listing 9中展示了新的类的声明,并且包括了输入函数operator>>的声明。Listing 10中展示了这些函数的实现,Listing 11中有一个简单的示例程序。
 
静态成员
C++中的类定义了一个作用域。这就是为什么函数Date::compare不会和一个叫做compare的全局函数发生冲突的原因(即使它们参数和返回值的类型都相同)。现在考虑实现文件中的一个数组dtab[]dtab的静态存储类型使它对文件来说是private的。但是它实际上属于整个类,而不是这个文件。如果我想要传递Date的成员函数到多个文件中,我不得不将需要访问dtab的函数传递到同一个文件中。
一个更好的办法是使dtab成为类的静态成员。静态成员属于整个类,而不是一个单独的对象。这意味着只有一个dtab的拷贝存在,它被所有类的对象所共享。使函数isleap成为static则允许你不需要和一个对象相关就能调用它,比如,你只需要这样写:
isleap(y);
而不需要这样写:
d.isleap(y);
 
要想使isleap对任何调用者都可用,使它为public,用如下方式调用:
Date::isleap(y);
 
最后,我将重新定义缺省构造函数,用当前日期初始化类的对象。最后的类的定义、实现和示例程序分别参见Listing 12 - Listing 14
 
总结
  在上面两个部分中,我试图说明C++是如何支持数据抽象——使用者的产物——自定义的数据类型。构造函数使得当你声明一个对象的时候能够自动对它进行初始化。你可以通过声明类的成员为private来保护它们不受到无意中的访问。重载公用的运算符可以使得你的对象看起来跟系统内建的数据类型很相似——这增加了可读性和可维护性。
 

Listing 1 介绍日期比较函数

// date4.h
 
class Date
{
   int month;
   int day;
   int year;
 
public:
   // Constructors
   Date()
     {month = day = year= 0;}
   Date(int m, int d, int y)
     {month = m; day = d; year = y;}
 
   // Accessor Functions
   int get_month() const
     {return month;}
   int get_day() const
     {return day;}
   int get_year() const
     {return year;}
 
   Date * interval(const Date&) const;
   int compare(const Date&) const;
};
 
// End of File

Listing 2 间隔和比较成员的实现

// date4.cpp
 
#include "date4.h"
 
inline int isleap(int y)
  {return y%4 == 0 && y%100 != 0 || y%400 == 0;}
 
static int dtab[2][13] =
{
  {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
  {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
 
Date * Date::interval(const Date& d2) const
{
   static Date result;
   int months, days, years, prev_month;
 
   // Compute the interval - assume d1 precedes d2
   years = d2.year - year;
   months = d2.month - month;
   days = d2.day - day;
 
   // Do obvious corrections (days before months!)
   //
   // This is a loop in case the previous month is
   // February, and days < -28.
   prev_month = d2.month - 1;
   while (days < 0)
   {
      // Borrow from the previous month
      if (prev_month == 0)
         prev_month = 12;
      -months;
      days += dtab[isleap(d2.year)][prev_month-];
   }
 
   if (months < 0)
   {
      // Borrow from the previous year
      -years;
      months += 12;
   }
 
   // Prepare output
   result.month = months;
   result.day = days;
   result.year = years;
   return &result;
}
 
int Date::compare(const Date& d2) const
{
   int months, days, years, order;
 
   years = year - d2.year;
   months = month - d2.month;
   days = day - d2.day;
 
   // return <0, 0, or >0, like strcmp()
   if (years == 0 && months == 0 && days == 0)
      return 0;
   else if (years == 0 && months == 0)
      return days;
   else if (years == 0)
      return months;
   else
      return years;
}
// End of File

Listing 3 日期比较成员函数的测试

// tdate4.cpp
 
#include <stdio.h>
#include "date4.h"
 
void compare_dates(const Date& d1, const Date& d2)
{
   int compval = d1.compare(d2);
   char *compstr - (compval < 0) ? "precedes" :
     ((compval > 0) ? "follows" : "equals"};
 
   printf("%d/%d/%d %s %d/%d/%d\n",
     d1.get_month(),d1.get_day(0),d1.get_year(),
     compstr,
     d2.get_month(),d2.get_day(),d2.get_year());
}
main()
{
   Date d1(1,1,1970);
   compare dates(d1,Date(10,1,1951));
   compare_dates{d1,Date(1,1,1970));
   compare_dates(d1,Date(12,31,1992));
   return 0;
}
 
/* OUTPUT
 
1/1/1970 follows 10/1/1951
1/1/1970 equals 1/1/1970
1/1/1970 precedes 12/31/1992
*/
 
// End of File

Listing 4 Date class定义相关的运算符

// date5.h
 
class Date
{
   int month;
   int day;
   int year;
 
public:
   // Constructors
   Date()
     {month = day = year = 0;}
   Date(int m, int d, int y)
     {month = m; day = d; year = y;}
 
   // Accessor Functions
   int get_month() const
     {return month;}
   int get_day() const
     {return day;}
   int get_year() const
     {return year;}
 
   Date * interval(const Date&) const;
   int compare(const Date&) const;
 
   // Relational operators
   int operator<(const Date& d2) const
     {return compare(d2) < 0;}
   int operator<=(const Date& d2) const
     {return compare(d2) <= 0;}
   int operator>(const Date& d2) const
     {return compare(d2) > 0;}
   int operator>=(const Date& d2) const
     {return compare(d2) >= 0;}
   int operator!=(const Date& d2) const
     {return compare(d2) != 0;}
   int operator!=(const Date& d2) const
     {return compare(d2) !=0;}
};
 
// End of File

Listing 5 使用日期类里相关的运算符

// tdate5.cpp
 
#include <stdio.h>
#include <stdlib.h>
#include "date5.h"
 
void compare_dates(const Date& d1, const Date& d2)
{
   char *compstr = (d1 < d2) ? "precedes" :
     ((d1 > d2) ? "follows" : "equals");
 
   printf("%d/%d/%d %s %d/%d/%d\n",
     d1.get_month(),d1.get_day(),d1.get_year(),
     compstr,
     d2.get_month(),d2.get_day(),d2.get_year());
}
 
main()
{
   Date d1(1,1,1970);
   compare_dates(d1,Date(10,1,1951));
   compare_dates(d1,Date(1,1,1970));
   compare_dates(d1,Date(12,31,1992));
   return 0;
}
 
/* OUTPUT
 
1/1/1970 follows 10/1/1951
1/1/1970 equals 1/1/1970
1/1/1970 precedes 12/31/1992
*/
 
// End of File

Listing 6 Date类添加二元和一元的“-“运算符

// date6.h
 
class Date
{
   int month;
   int day;
   int year;
 
public:
   // Constructors
   Date()
     {month = day = year = 0;}
   Date(int m, int d, int y)
     {month = m; day = d; year = y;}
 
   // Accessor Functions
   int get_month() const
     {return month;}
   int get_day() const
     {return day;}
   int get_year() const
     {return year;}
 
   Date operator-(const Date& d2) const;
   Date& operator-()
     {month = -month; day = -day; year = -year;
      return *this;}
 
   int compare(const Date&) const;
 
   // Relational operators
   int operator<(const Date& d2) const
     {return compare(d2) < 0;}
   int operator<=(const Date& d2) const
     {return compare(d2) <= 0;}
   int operator>(const Date& d2) const
     {return compare(d2) > 0;}
   int operator>=(const Date& d2) const
     {return compare(d2) >= 0;}
   int operator==(const Date& d2) const
     {return compare(d2) == 0;}
   int operator!=(const Date& d2) const
     {return compare(d2) != 0;}
};
 
// End of File

Listing 7 二元“-”运算符的实现

// date6.cpp
#include <assert.h>
#include "date6.h"
 
inline int isleap(int y)
  {return y%4 == 0 && y%100 != 0 || y%400 == 0;}
 
static int dtab[2][13] =
{
  {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
  {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
 
Date Date::operator-(const Date& d2) const
{
   int months, days, years, prev_month, order;
   const Date * first, * last;
 
   // Must know which date is first
   if (compare(d2) <= 0)
   {
      // this <= d2
      order = -1;
      first = this;
      last = &d2;
   }
   else
   {
      order = 1;
      first = &d2;
      last = this;
   }
 
   // Compute the interval; first <= last
   years = last->year - first->year;
   months = last->month - first->month;
   days = last->day - first->day;
   assert(years >= 0 && months >= 0 && days >= 0);
 
   // Do obvious corrections (days before months!)
   // This is a loop in case the previous month is
   // February, and days < -28.
   prev_month = last->month - 1;
   while (days < 0)
   {
      // Borrow from the previous month
      if (prev_month == 0)
         prev_month = 12;
      --months;
      days += dtab[isleap(last->year)][prey_month--];
   }
 
   if {months < 0)
   {
      // Borrow from the previous year
      --years;
      months += 12;
   }
 
   // Return a date object with the interval
   if (order == -1)
      return Date(-months,-days,-years);
   else
      return Date(months, days, years);
}
 
int Date::compare(const Date& d2) const
{
    // same as in Listing 2
}
// End of File

Listing 8 两个日期相减

// tdate6.cpp:
 
#include <stdio.h>
#include "date6.h"
 
main()
{
   Date d1(1,1,1970), d2(12,8,1992);
   Date result = d1 - d2;
   printf("years: %d, months: %d, days: %d\n",
      result.get_year(),
      result.get_month(),
      result.get_day());
   result = d2 - d1;
   printf("years: %d, months: %d, days: %d\n",
      result.get_year(),
      result.get_month(),
      result.get_day());
   int test = d1 - d2 == -(d2 - d1);
   printf("d1 - d2 == -(d2 - d1)? %s\n",
     test ? "yes" : "no");
   return 0;
}
 
/* OUTPUT
 
years: -22, months: -11, days: -7
years: 22, months: 11, days: 7
d1 - d2 == -(d2 - d1)? yes
*/
 
// End of File

Listing 9 Date类增加输入输出流

// date7.h
 
class ostream;
 
class Date
{
   int month;
   int day;
   int year;
 
public:
   // Constructors
   Date()
     {month = day = year = 0;}
   Date(int m, int d, int y)
     {month = m; day = d; year = y;}
 
   // Accessor Functions
   int get_month() const
     {return month;}
   int get_day() const
     {return day;}
   int get_year() const
     {return year;}
 
   Date operator-(const Date& d2) const;
   Date& operator-()
     {month= -month; day = -day; year = -year;
      return *this;}
 
   int compare(const Date&) const;
 
   // Relational operators
   int operator<(const Date& d2) const
     {return compare(d2) < 0;}
   int operator<=(const Date& d2) const
     {return compare(d2) <= 0;)
   int operator>(const Date& d2) const
     {return compare(d2) > 0;}
   int operator>=(const Date& d2) const
     {return compare(d2) >= 0;}
   int operator==(const Date& d2) const
     {return compare(d2) == 0;}
   int operator!=(const Date& d2) const
     {return compare(d2) != 0)
 
   // I/O operators
   friend ostream& operator<<(ostream&, const Date&);
   friend istream& operator>>(istream&, Date&);
};
 
// End of File

Listing 10 实现Date类的stream I/O函数

#include <iostream.h>
#include "date7.h"
 
ostream& operator<<(ostream& os, const Date& d)
{
   os << d.month << '/' << d.day << '/' << d.year;
   return os;
}
 
istream& operator>>(istream& is, Date& d)
{
   char slash;
   is >> d.month >> slash >> d.day >> slash >> d.year;
   return is;
}
 
// End of File

Listing 11 举例说明Date 类对象的stream I/O

// tdate7.cpp:
 
#include <iostream.h>
#include "date7.h"
 
main()
{
   Date d1, d2;
   cout << "Enter a date: ";
   cin >> d1;
   cout << "Enter another date: ";
   cin >> d2;
   cout << "d1 - d2 = "<< d1 - d2 << endl;
   cout << "d2 - d1 = "<< d2 - d1 << endl;
   return 0;
}
 
/* OUTPUT
 
Enter a date: 10/1/1951
Enter another date: 5/1/1954
d1 - d2 = -7/0/-2
d2 - d1 = 7/0/2
*/
 
// End of File
 

Listing 12 定义静态成员

// date8.h
 
// Forward declarations
class istream;
class ostream;
 
class Date
{
   int month;
   int day;
   int year;
 
   static int dtab[2][13];
 
public:
   // Constructors
   Date();         // Get today's date (see .cpp file)
   Date(int m, int d, int y)
     {month = m; day = d; year = y;}
 
   // Accessor Functions
   int get_month() const
     {return month;}
   int get_day() const
     {return day;}
   int get_year() const
     {return year;}
 
   Date operator-(const Date& d2) const;
   Date& operator-()
     {month = -month; day = -day; year = -year;
      return *this;}
 
   int compare(const Date&) const;
 
   // Relational operators
   int operator<(const Date& d2) const
     {return compare{d2) < 0;}
   int operator<=(const Date& d2) const
     {return compare(d2) <= 0;}
   int operator>(const Date& d2) const
     {return compare(d2) > 0;}
   int operator>=(const Date& d2) const
     {return compare(d2) >= 0;}
   int operator==(const Date& d2) const
     {return compare(d2) == 0;}
   int operator!=(const Date& d2) const
     {return compare(d2) != 0;}
 
   // Stream I/O operators
   friend ostream& operator<<(ostream&, const Date&);
   friend istream& operator>>(istream&, Date&);
 
   static int isleap(int y)
     {return y%4 == 0 && y%100 != 0 || y%400 == 0;}
};
 
// End of File
 

Listing 13 Date类的最终实现

// date8.cpp
 
#include <iostream.h>
#include <time.h>
#include <assert.h>
#include "date8.h"
 
// Must initialize statics outside the class definition
int Date::dtab[2][13] =
{
  {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
  {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
 
Date Date::operator-(const Date& d2) const
{
   int months, days, years, prev_month, order;
   const Date * first, * last;
 
   // Must know which date is first
   if (compare(d2) <= 0)
   {
      // this <= d2
      order= -1;
      first = this;
      last = &d2;
   }
   else
   {
      order = 1;
      first = &d2;
      last = this;
   }
 
   // Compute the interval; first <= last
   years = last->year - first->year;
   months = last->month - first->month;
   days = last->day - first->day;
   assert(years >= 0 && months >= 0 && days >= 0);
 
   // Do obvious corrections (days before months!)
   //
   // This is a loop in case the previous month is
   // February, and days < -28.
   prev_month = last->month - 1;
   while (days < 0)
   {
      // Borrow from the previous month
      if (prev_month == 0)
         prev_month = 12;
      -months;
      days += dtab[isleap(last->year)][prev_month-];
   }
 
   if (months < 0)
   {
      // Borrow from the previous year
      -years;
      months += 12;
   }
 
   // Return a date object with the interval
   if (order == 1)
      return Date(-months,-days,-years);
   else
      return Date(months,days,years);
}
 
int Date::compare(const Date& d2) const
{
   int months, days, years, order;
 
   years = year - d2.year;
   months = month - d2.month;
   days = day - d2.day;
 
   // return <0, 0, or >0, like strcmp()
   if (years == 0 && months == 0 && days == 0)
      return 0;
   else if (years == 0 && months == 0)
      return days;
   else if (years == 0)
      return months;
   else
      return years;
}
 
ostream& operator<<(ostream& os, const Date& d)
{
   os << d.month << '/' << d.day << '/' << d.year;
   return os;
}
 
istream& operator>>(istream& is, Date& d)
{
   char slash;
   is >> d.month >> slash >> d.day >> slash >> d.year;
   return is;
}
 
Date::Date()
(
   // Get today's date
   time_t tval = time(0);
   struct tm *tmp= localtime(&tval);
 
   month = tmp->tm_mon+1;
   day = tmp->tm_mday;
   year = tmp->tm_year + 1900;
}
// End of File
 

Listing 14 得到今天的日期

// tdate8.cpp:
 
#include <iostream.h>
#include "date8.h"
 
main()
{
   Date today, d2;
   cout << "Today's date is "<< today << endl;
   cout << "Enter another date: ";
   cin >> d2;
   cout << "today - d2 = "<< today - d2 << endl;
   cout << "d2 - today = "<< d2 - today << endl;
   return 0;
}
 
/* OUTPUT
Today's date is 12/12/1992
Enter another date: 1/1/1970
today - d2 = 11/11/22
d2 - today = -11/-11/-22
*/
// End of File