Table-drive表驱动法--由税率问题引出
小窥模板元编程

表驱动法(Table-Driven )在程序中应用

皮贝贝 posted @ 2008年7月22日 02:23 in 模式之灾 with tags 表驱动 , 5745 阅读

上一节看到了表驱动法的优点,结构清晰明了,易于维护修改,现在在现实的程序中找几个例子看看吧!
1.Vim之可设置参数
1.1 构建表
    Vim是一个开源的软件,随着功能的不断增强,其源码量也在不断攀升,就来研究下官方能找到的最早源码版本1.14吧。
    Vim是可以自我定制,即你可以自己编写命令来控制,比如你可以设置一些系统提供的一些变量,如autoindent(ai),ignorecase(ic),number(nu)等等,像每一个参数都有一个缩写形式,所以我们猜测这可能是一个结构体,我们可以在param.h中找到这个结构体:
 

  1.    
  2. /**************
  3. * param:
  4. * 参数结构体:
  5. *                 全名
  6. *                 缩写
  7. *                 值:    long或char* 型别
  8. *                 标志位,含类型信息
  9. * */
  10. struct param{
  11.     char        *fullname;        /* 全名 */ 
  12.     char        *shortname;         /* 缩写 */
  13.     union{
  14.         long    intval;         /* intenger 参数值 */
  15.         char    *strval;        /* string 参数值 */
  16.     } val;
  17.     int         flags;
  18. };
  19.  


紧跟着的是标志位信息宏定义,有以下几种:P_BOOL, P_NUM, P_CHANGED,P_STRING,是按照状态标志位的形式来给出。

  1.  
  2. #define P_BOOL            0x01    /* 布尔型 */
  3. #define P_NUM            0x02    /* 数值型 */
  4. #define P_CHANGED        0x04    /* 改参数被更改 */
  5. #define P_STRING        0x08    /* 字符串型 */
  6.  


在这个文件中,有这样一个数组声明:

  1. extern struct param params[]


打开param.c,我们就可以找到表驱动法的踪影了:

  1.  
  2. struct param params[] =
  3. {
  4.         //参数全名    缩写    初始值    类型
  5.         {"autoindent",    "ai",    FALSE,    P_BOOL},
  6.         {"autowrite",    "aw",    FALSE,    P_BOOL},
  7.         {"backspace",    "bs",    FALSE,    P_BOOL},
  8.         {"backup",    "bk",    TRUE,    P_BOOL},
  9.         {"writeany",    "wa",    FALSE,    P_BOOL},
  10. // .......略
  11.         {"yankendofline", "ye", FALSE,    P_BOOL},
  12.         {NULL, NULL, 0, 0}        /* 末尾标记 */
  13. };



全局就是维护着这样一个表,来控制这Vim的状态信息。注意在表的末尾有一个空行,即{NULL,NULL, 0,
0},这是作为表尾标记出现,我们可以通过它来检测数组中元素个数。这一做法也经常出现在一些其他程序中。
而在main.c的usage()函数中,我们有看到另一种解决方案,即

[数组元素个数] = sizeof( [数组] ) / sizeof( [数组中每个元素] )

这两种方案都是解决表大小判断的。第一种方案,结构清晰,规整;第二种则巧妙。并且两种方案都无需在表中增删元素的情况下更改代码的其他部分。

1.2访问
与上述表紧密定义的是一组下标索引:

  1.  
  2. #define P_AI            0        /* auto-indent */
  3. #define P_AW            1        /* auto-write */
  4. #define P_BS            2        /* backspace over newlines in insert mode */
  5. #define P_BK            3        /* make backups when writing out files */
  6. // ....
  7.  


索引与表不可分割,改动一个另一个也得相应的改动,这一点必须注意。
接下来访问表的元素就可以定义成下面宏形式:

  1.  
  2. #define P(n)    ((params[n].val.intval))
  3. #define PS(n)    (params[n].val.strval)
  4.  


事实上,就是通过这中访问方式访问的:

  1.  
  2.     P(P_LI) = Rows = 24;
  3.  


2.MFC之消息映射
表只是一种形式,内部存放的东西可以是任何东西,当然也包括函数。我们通过MFC的消息机制,看下控件是如何与处理函数关联起来的。
通常在MFC类的声明中会出现这样一句:
 

  1.    DECLARE_MESSAGE_MAP()


相应地,在实现文件中有如下宏:

  1. BEGIN_MESSAGE_MAP(CtestDlg, CDialog)
  2.     ON_WM_SYSCOMMAND()
  3.     ON_WM_PAINT()
  4.     ON_WM_QUERYDRAGICON()
  5.     //}}AFX_MSG_MAP
  6.     ON_BN_CLICKED(IDOK, &CtestDlg::OnBnClickedOk)
  7.     ON_WM_COPYDATA()
  8. END_MESSAGE_MAP()



这几样东西,是关联消息映射的。内部是如何工作,看看宏的实现。
2.1 声明
   

  1. DECLARE_MESSAGE_MAP()


看起来是声明消息映射,转到定义一看究竟吧。

  1.  
  2. #define DECLARE_MESSAGE_MAP() \
  3.     protected: \
  4.         static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
  5.         virtual const AFX_MSGMAP* GetMessageMap() const; \
  6.        


看起来,这个宏定义就是声明了两个函数,一个静态const型,一个虚的const型,都是返回一个AFX_MSGMAP型的指针,这是一个什么结构体,继续搜索:

  1.  
  2. struct AFX_MSGMAP
  3. {
  4.     const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
  5. //指向基类的结构体指针
  6.     const AFX_MSGMAP_ENTRY* lpEntries;           
  7. };



AFX_MSGMAP_ENTRY是下列结构:

  1.  
  2. struct AFX_MSGMAP_ENTRY
  3. {
  4.     UINT nMessage;   // windows message 
  5.     UINT nCode;      // control code or WM_NOTIFY code
  6.     UINT nID;        // control ID (or 0 for windows messages)
  7.     UINT nLastID;    // used for entries specifying a range of control id's
  8.     UINT_PTR nSig;       // signature type (action) or pointer to message#
  9.     AFX_PMSG pfn;    // routine to call (or special value)  指向回调函数
  10. };



这个结构体才是真正的桥接,把windows消息和消息处理函数关联了起来,只要通过访问结构体,就可以获得相关联的信息。

2.2实现
看实现文件中还有这几个宏:

  1.  
  2. BEGIN_MESSAGE_MAP(CtestDlg, CDialog)
  3.     ON_WM_SYSCOMMAND()
  4.     ON_WM_PAINT()
  5.     ON_WM_QUERYDRAGICON()
  6.     //}}AFX_MSG_MAP
  7.     ON_BN_CLICKED(IDOK, &CtestDlg::OnBnClickedOk)
  8.     ON_WM_COPYDATA()
  9. END_MESSAGE_MAP()



看第一个:

 

  1.  
  2. #define BEGIN_MESSAGE_MAP(theClass, baseClass) \
  3.     PTM_WARNING_DISABLE \
  4.     const AFX_MSGMAP* theClass::GetMessageMap() const \
  5.         { return GetThisMessageMap(); } \
  6.     const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
  7.     { \
  8.         typedef theClass ThisClass;                           \
  9.         typedef baseClass TheBaseClass;                       \
  10.         static const AFX_MSGMAP_ENTRY _messageEntries[] =  \
  11.         {


有两个函数,这两个函数都是声明文件中的DECLARE_MESSAGE_MAP()宏声明的两个函数。第一个是接口函数,第二个是实现函数,
我们看到实现函数中维护着一个静态const表,是AFX_MSGMAP_ENTRY数组,接下来的ON类宏都是一个数组元素,如:

  1.  
  2. #define ON_WM_PAINT() \
  3.     { WM_PAINT, 0, 0, 0, AfxSig_vv, \
  4.         (AFX_PMSG)(AFX_PMSGW) \
  5.         (static_cast< void (AFX_MSG_CALL CWnd::*)(void) > ( &ThisClass :: OnPaint)) },


        
而与控件相关联的宏一般都是一个中间接口宏,如

  1.  
  2. #define ON_BN_CLICKED(id, memberFxn) \
  3.     ON_CONTROL(BN_CLICKED, id, memberFxn)
  4.    


  1.  
  2. #define ON_CONTROL(wNotifyCode, id, memberFxn) \
  3.     { WM_COMMAND, (WORD)wNotifyCode, (WORD)id, (WORD)id, AfxSigCmd_v, \
  4.         (static_cast< AFX_PMSG > (memberFxn)) },
  5.  


而最末一个宏:

  1.  
  2. #define END_MESSAGE_MAP() \
  3.         {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
  4.     }; \
  5.         static const AFX_MSGMAP messageMap = \
  6.         { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
  7.         return &messageMap; \
  8.     }                                  \
  9.     PTM_WARNING_RESTORE


    
在数组表的末尾又看到了末尾标记:

  1.  
  2.         {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \      // AfxSig_end是0
  3.     };


    
这样一个表的内容就构建完了。由此出发又进一步构造了AFX_MSGMAP型的消息映射messageMap,第一个参数指向基类的此结构体,
第二个参数就是本类刚刚定义的消息映射表入口。实现函数GetThisMessageMap()返回的就是这个AFX_MSGMAP型的消息映射messageMap
结构体,由此你可以关联基类或本类的消息函数了。

  • 无匹配

登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter