白盒测试 [代码规范][C++] 三

函数

函数是程序执行的最小单位,任何一个有效的C/C++程序都少不了函数。

 

函数原型

函数原型的格式为:

 

[存储类] 返回值类型
[名空间或类::]函数名(参数列表) [const说明符] [异常过滤器]

例如:

static inline void
Function1(void)

int
CSem::Function2(IN const char* pcName) const throw(Exp)

其中:

  • 以 "[ ]" 括住的为可选项目。
  • 除了构造/析构函数及类型转换操作符外,"返回值类型" 和 "参数列表" 项不可省略(可以为 "void")。
  • "const说明符" 仅用于成员函数中 。
  • "存储类", "参数列表" 和 "异常过滤器" 的说明见下文 。

函数声明

函数声明的格式为:

 

//! 函数功能简单说明(可选)
函数原型;

例如:

//! 执行某某操作
static void
Function(void);

函数声明和其它代码间要有空行分割。

声明类的成员函数时,为了紧凑,返回值类型和函数名之间不用换行,也可以适当减少声明间的空行。

 

函数定义

函数定义使用如下格式:
 


函数原型
{
    // ...
}

对于返回值、参数意义都很明确简单函数(代码不超过20行),也可以使用单行函数头:

//! 函数实现功能
函数原型
{
    // ...
}

函数定义和其它代码之间至少分开2行空行。

 

参数描述宏

以下预定义宏对程序的编译没有任何影响,只为了增强对参数的理解:

 

说明

IN

输入参数

OUT

输出参数

DUMMY

哑元参数-不使用参数的值,仅为帮助函数重载解析等目的而设置的参数

OPTIONAL

可选参数-通常指可以为NULL的指针参数,带默认值的参数不需要这样标明

RESERVED

保留参数-这个参数当前未被支持,留待以后扩展;或者该参数为内部使用,用户无需关心

OWNER

获得参数的所有权,调用者不再负责销毁实参指定的对象;如果用来修饰返回值,则表示调用者获得返回值的所有权,并负责将其销毁

UNUSED

标明这个参数在此版本中已不再使用

CHANGED

参数类型或用途与较早版本相比发生了变化

ADDED

新增的参数

NOTE

需要注意的参数-参数意义发生变化或者与习惯用法不同

WRKBUF

工作缓冲区-为避免频繁分配临时资源而传入的临时工作区

其中:

  • 除了空参数 "void" 和哑元参数以外,每个参数左侧都必须有 "IN" 和/或 "OUT" 修饰。
  • 既输入又输出的参数应记为:"IN OUT",而不是 "OUT IN"。
  • IN/OUT的左侧还可以根据需要加入一个或多个上表中列出的其它宏 。

参数描述宏的使用思想是:只要一个描述宏可以用在指定参数上(即:对这个参数来说,用这个描述宏修饰它是贴切的),那么就应当使用它。

也就是说,应该把能用的描述宏都用上,以期尽量具体地描述一个参数 的作用和用法等信息。

 

参数列表

参数列表的格式为:

 

参数描述宏1 参数类型1 参数1, 参数描述宏2 参数类型2 参数2, ...

例如:

IN const int nCode, OUT string& nName

OWNER IN CDatabase* piDB, OPTIONAL IN OUT int* pnRecordCount = NULL

IN OUT string& stRuleList, RESERVED IN int nOperate = 0

...

其中:

存储类

"extern", "static", "inline" 等函数存储类说明应该在声明和定义中一致并且显式地使用。不允许隐式地使用一个类型声明,也不允许一个类型声明仅存在于函数的声明或定义中。

 

成员函数的存储类

由于C++语言的限制,类中成员函数的 "static", "virtual", "explicit" 等存储类说明不允许出现在函数定义中。

但是为了明确起见,这些存储类应以注释的形式在相应的成员定义中给出。

例如:

CThread::EXITCODE
CSrvCtl::CWrkTrd::Entry(void)
{
    // ...
}

inline void
stringEx::regex_free(IN OUT void*& pRegEx)
{
    // ...
}

特别地,为缩短声明的长度,"inline" 关键字可以在成员函数声明中省略。

 

默认参数

类似地,参数的默认值只能出现在函数声明中,但是为了明确起见,这些默认值应以注释的形式在定义中给出。

例如:

bool
stringEx::regex_find(OUT VREGEXRESULT& vResult,
                     IN  stringEx      stRegEx,
                     IN  size_t        nIndex        ,
                     IN  size_t        nStartPos     ,
                     IN  bool          bNoCase       ,
                     IN  bool          bNewLine      ,
                     IN  bool          bExtended     ,
                     IN  bool          bNotBOL       ,
                     IN  bool          bNotEOL       ,
                     IN  bool          bUsePerlStyle ) const
{
    // ...
}

 

异常过滤器

对于任何可能抛出异常的函数,必须在其声明和定义中显式地指定异常过滤器,并在过滤器中列举该函数可能抛出的异常。

例如:

int
Function(IN const char* pcName) throw(byExp, exception);

如果一个函数本身及其直接调用的函数不会显式抛出异常(没有指定异常过滤器),那么该函数可以省略异过滤器。

特别地:如果一个函数内部显式地捕获了任何可能的异常(例如:使用了 "catch (...)" ),并且保证不抛出任何异常,那么应该在其声明和定义中显式地指定一个空异常过滤器:"throw()"。

例如:

int
Function(IN const char* pcName) throw();

特别地:进程入口函数("main()")应当使用异常过滤器。

 

代码段注释

如果函数体中的代码较长,应该根据功能不同将其分段。代码段间以空行分离,并且每段代码都以代码段分割注释作为开始。

例如:

void
CXXX::Function(IN void* pmodAddr)
{
    if (NULL == pmodAddr)
    {
        return;
    }

    { CSessionLock iLock(mxLock);

        // =====================================================================
        // = 判断指定模块是不是刚刚被装入,由于在NT系列平台中,“A”系列函数都是
        // = 由“W”系列函数实现的。 所以可能会有一次LoadLibrary产生多次本函数调
        // = 用的情况。为了增加效率,特设此静态变量判断上次调用是否与本次相同。
        static PVOID pLastLoadedModule = NULL;
        if (pLastLoadedModule == pmodAddr)
        {
            return;  // 相同,忽略这次调用
        }
        pLastLoadedModule = pmodAddr;

        // =====================================================================
        // = 检查这个模块是否在旁路模块表中
        stringEx stModName;
        if (!BaiY_IMP::GetModuleNameByAddress(pmodAddr, stModName))
        {
            return;
        }
       
        if (CHookProc::sm_sstByPassModTbl.find(stModName)
            != CHookProc::sm_sstByPassModTbl.end())
        {
            return;
        }

        // =====================================================================
        // = 在这个模块中HOOK所有存在于HOOK函数表中的函数
        PROCTBL::iterator p = sm_iProcTbl.begin();
        for (; p!=sm_iProcTbl.end(); ++p)
        {
            p->HookOneModule(pmodAddr);
        }
    } // SessionLock
}

明显地,如果需要反复用到一段代码的话,这段代码就应当作为一个函数实现。

当一个函数过长(超过100行)或代码的意图不明确时,为了便于阅读和理解,也应当将其中的一些代码段实现为单独的函数。

特别地,对由于如加密及性能优化等特殊原因无法提取为一个单独函数的代码段,应当使用特别代码段注释显式分割。当然,类似情况应当尽量使用内联函数或编译器提供的强制性内联函数代替。

例如:

void
CXXX::Function(void)
{
    // ...

    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    // @@ 获取首网卡的 MAC 地址
    typedef CTmpHandle<IP_ADAPTER_INFO, FreeDeletor<IP_ADAPTER_INFO> >
            THADAPTERINFO;

    byteEx btAddr;
    THADAPTERINFO thAdapterInfo;
    thAdapterInfo = (IP_ADAPTER_INFO*) malloc(sizeof(IP_ADAPTER_INFO));

    ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);

    // Make an initial call to GetAdaptersInfo to get
    // the necessary size into the ulOutBufLen variable
    if (ERROR_SUCCESS != ::GetAdaptersInfo(thAdapterInfo, &ulOutBufLen))
    {
        thAdapterInfo = (IP_ADAPTER_INFO*) malloc(ulOutBufLen);
    }

    if (NO_ERROR != ::GetAdaptersInfo(thAdapterInfo, &ulOutBufLen))
    {
    #ifdef DEBUG
        CLog::DebugMsg(byT("lic verifier"), byT("failed verifying license"));
    #endif
        CProcess::Exit(-97);
    }
    btAddr.assign(thAdapterInfo->Address, thAdapterInfo->AddressLength);
    // @@ 获取首网卡的 MAC 地址
    // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

    // ...
}

 

调用系统API

所有系统API调用前都要加上全局名称解析符 "::"。

例如:

::MessageBoxA(NULL, gcErrorMsg, "!FATAL ERROR!", MB_ICONSTOP|MB_OK);

if (0 == ::GetTempFileName(m_basedir.c_str(), byT("bai"), 0, stR.ref()))
{
    // ...
}

 

让相同的代码只出现一次

为了使程序更容易调试、修改,尽量降低日后维护的复杂性,应该把需要在一个以上位置使用的代码段封装成函数。哪怕这段代码很短,为了以后维护方便着想,也应当将其封装为内联函数。

关于函数的例子,请参见:函数的风格与版式例子

关于函数的模板,请参见:函数模板

 

变量、常量

声明格式

变量、常量的声明格式如下:

 

[存储类] 类型 变量名;

其中:

  • 以 "[ ]" 括住的为可选项目。
  • "存储类" 的说明见下文

 

定义格式

变量、常量的定义格式如下:

 

[存储类] 类型 变量名 = 初始值;

其中:

  • 以 "[ ]" 括住的为可选项目。
  • "存储类" 的说明见下文

 

存储类

除 "auto" 类型以外,诸如 "extern", "static", "register", "volatile" 等存储类均不可省略,且必须在声明和定义中一致地使用(即:不允许仅在声明或定义中使用)。

 

成员变量的存储类

由于C++语言的限制,成员变量的 "static" 等存储类说明不允许出现在变量定义中。

但是为了明确起见,这些存储类应以注释的形式在定义中给出。

例如:

int CThread::sm_nPID = 0;

 

指针或引用类型的定义和声明

在声明和定义多个指针或引用变量/常量时,每个变量至少占一行。例如:

 

int* pn1,
   * pn2 = NULL,
   * pn3;

char* pc1;
char* pc2;
char* pc3;

// 错误的写法:
int* pn11, *pn12, *pn13;

 

常指针和指针常量

声明/定义一个常指针(指向常量的指针)时,"const" 关键字一律放在类型说明的左侧。

声明/定义一个指针常量(指针本身不能改变)时,"const" 关键字一律放在变量左侧、类型右侧。

例如:

const char* pc1;       // 常指针
char* const pc2;       // 指针常量
const char* const pc3; // 常指针常量

// 错误的写法:
char const* pc1;  // 与 const char* pc1 含义相同,但不允许这样写

 

全局变量、常量的注释

全局变量、常量的注释独占一行,并用 "//!" 开头。

例如:

//! 当前进程的ID
static int sg_nPID = 0;

//! 分割符
static const char* pcDTR = "\\/";

 

类型转换

禁止使用C风格的 "(类型)" 类型转换,应当优先使用C++的 "xxx_cast" 风格的类型转换。C++风格的类型转换可以提供丰富的含义和功能,以及更好的类型检查机制,这对代码的阅读、修改、除错和移植有很大的帮助。其中:

 

static_cast

static_cast 用于编译器认可的,安全的静态转换,比如将 "char" 转为 "int" 等等。该操作通常在编译时完成,但有可能调用用户定义的类型转换操作或非 explicit 的单参(或至少从第二个参数开始带缺省值的)构造函数 。

reinterpret_cast

reinterpret_cast 用于编译器不认可的,不安全的静态转换,比如将 "int*" 转为 "int" 等等。这种转换有可能产生可移植性方面的问题,该操作在编译时完成 。(注意:reinterpret_cast 比 C 风格的类型转换还要野蛮,它不进行任何地址对齐和调整,也不调用任何用户定义的类型转换操作)

const_cast

const_cast 用于将一个常量转化为相应类型的变量,比如将 "const char*" 转换成 "char*" 等等。这种转换可能伴随潜在的错误。该操作在编译时完成 。

dynamic_cast

dynamic_cast 是 C++ RTTI 机制的重要体现,用于在类层次结构中漫游。dynamic_cast 可以对指针和引用进行自由度很高的向上、向下和交叉转换。被正确使用的 dynamic_cast 操作将在运行时完成 。反之,若编辑器关闭了 RTTI 支持,或被转换的类层次结构中没有抽象类存在,则此操作在编译时完成(有些编译器会给出警告)。

此外,对于定义了单参构造函数或类型转换操作的类来说,应当优先使用构造函数风格的类型转换,如:'string("test")' 等等。

通常来说,"xxx_cast" 格式的转换与构造函数风格的类型转换之间,最大的区别在于:构造函数风格的转换经常会生成新的临时对象,可能伴随相当的时间和空间开销。而 "xxx_cast" 格式的转换只是告诉编译器,将指定内存中的数据当作另一种类型的数据看待,这些操作一般在编译时完成,不会对程序的运行产生额外开销。当然,"dynamic_cast" 和某些 "static_cast" 则例外。

参见:RTTI、虚函数和虚基类的开销分析和使用指导

 

枚举、联合、typedef

枚举、联合的定义格式

枚举、联合的定义格式为:

 

//! 说明(可选)
enum|union 名称
{
    内容  // 注释(可选)
};

例如:

//! 服务的状态
enum SRVSTATE
{
    SRV_INVALID  = 0,  // 无效(尚未启动)
    SRV_STARTING = 1,
    SRV_STARTED,
    SRV_PAUSING,
    SRV_PAUSED,
    SRV_STOPPING,
    SRV_STOPPED
};

//! 32位整数
union INT32
{
    unsigned char    cByte[4];
    unsigned short   nShort[2];
    unsigned long    nFull;
};

 

typedef的定义格式

typedef 的定义格式为:

 

//! 说明(可选)
typedef 原类型 类型别名;

例如:

//! 返回值类型
typedef int EXITCODE;

//! 字符串数组类型
typedef vector<string> VSTR;

 转载自:http://blog.sina.com.cn/s/articlelist_1154237180_1_1.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值