开始做事,我首先作了如下函数型宏代码:

  Code:

#define TONY_FORMAT(nPrintLength,szBuf,nBufferSize,szFormat)   
{   
    va_list pArgList;   
    va_start (pArgList,szFormat);   
    nPrintLength+=Linux_Win_vsnprintf(szBuf+nPrintLength,   
        nBufferSize-nPrintLength,szFormat,pArgList);   
    va_end(pArgList);   
    if(nPrintLength>(nBufferSize-1)) nPrintLength=nBufferSize-1;   
    *(szBuf+nPrintLength)='';   
}

  这个宏有4个参数,我解释一下:

  nPrintLength:这个很重要,C的规约,处理变参的函数一般要返回一个int,表示变参展开后,真实的字节数,注意,这里没有包括字符串这个''的位宽,即仅仅是strlen的长度。很多时候,C语言程序员习惯于要采纳这个值参与后续计算,嗯,我们后面有这个例子,所以,外部传进来一个变量nPrintLength,是求这个值。

  这也看出来,函数型宏,全部相当于传址调用,可以直接修改外部的变量的值的。

  szBuf,nBufferSize:是希望把变参展开,填充到的缓冲区和缓冲区长度,我强调0bug编程,很多时候,外部传入一个缓冲区要求函数填充的时候,都必须给一个边界,避免内存写出界导致崩溃,这个nBufferSize是干这个的,内部的设计会保证不超过这个边界。

  szFormat:精华了哈,前面说那么麻烦的va_list传递变参模式,在此简化为直接把szFormat传进来好了。我认为这是这个设计漂亮的一点,大大简化了调用者的程序行为,再也不麻烦了,呵呵。

  ok,有了这个宏,我们来改写一下前面经典的SafePrintf看看:

  Code:

//安全的变参打印函数  
inline int SafePrintf(char* szBuf,int nBufSize,char* szFormat, ...)  
{  
    if(!szBuf) return 0;  
    if(!nBufSize) return 0;  
    if(!szFormat) return 0;  
    int nRet=0;  
    TONY_FORMAT(nRet,szBuf,nBufSize,szFormat);  
    return nRet;  
}

  大家注意到什么没有?SafePrintf里面复杂的逻辑不见了,全部被整合成为TONY_FORMAT这个函数宏的调用。

  嗯,考虑到很多时候,我们做Debug或者日志输出,需要打印的时候自动加上一个时间戳,因此,我又做了变参处理宏的时间戳版本:

  Code:

#define TONY_FORMAT_WITH_TIMESTAMP(nPrintLength,szBuf,nBufferSize,szFormat)   
{   
    time_t t;   
    struct tm *pTM=NULL;   
    time(&t);   
    pTM = localtime(&t);   
    nPrintLength+=SafePrintf(szBuf,nBufferSize,"[%s",asctime(pTM));   
    szBuf[nPrintLength-1]='';   
    nPrintLength--;   
    nPrintLength+=SafePrintf(szBuf+nPrintLength,nBufferSize-nPrintLength,"] ");   
    TONY_FORMAT(nPrintLength,szBuf,nBufferSize,szFormat);   
}

  大家注意没,这里面,TONY_FORMAT_WITH_TIMESTAMP马上在调用前面的SafePrintf,以及TONY_FORMAT。这是我做程序的习惯,每个模块写出来是要给人用的,自己往往是第一个用户,函数接口,api设计得好不好,自己一用知道,不好用调整,调整到自己爽为止。把自己站在用户的立场上,把程序调整到自己用起来都“爽”,你的程序能获得用户的好评。

  我一直说,“程序员的用户,不仅仅是终端用户,还包括和你自己一样的,甚至是你自己,程序员。”是这个意思,大家能理解吗?