一、程序风格:
   1、严格采用阶梯层次组织程序代码:
各层次缩进的分格采用VC的缺省风格,即每层次缩进为4格,括号位于下一行。要求相匹配的大括号在同一列,对继行则要求再缩进4格。例如:
    2、提示信息字符串的位置
在程序中需要给出的提示字符串,为了支持多种语言的开发,除了一些给调试用的临时信息外,其他所有的提示信息必须定义在资源中。
3、对变量的定义,尽量位于函数的开始位置。
二、命名规则:
1、变量名的命名规则
①、变量的命名规则要求用“匈牙利法则”。即开头字母用变量的类型,其余部分用变量的英文意思或其英文意思的缩写,尽量避免用中文的拼音,要求单词的第一个字母应大写。
即: 变量名=变量类型+变量的英文意思(或缩写)
对非通用的变量,在定义时加入注释说明,变量定义尽量可能放在函数的开始处。
见下表:
bool(BOOL)      用b开头        bIsParent
byte(BYTE)     用by开头      byFlag
short(int)    用n开头   nStepCount
long(LONG)      用l开头   lSum
char(CHAR)     用c开头   cCount
  float(FLOAT)    用f开头   fAvg
double(DOUBLE)  用d开头   dDeta
void(VOID)        用v开头        vVariant
unsigned int(WORD) 用w开头 wCount
unsigned long(DWORD) 用dw开头 dwBroad
HANDLE(HINSTANCE) 用h开头 hHandle
DWORD 用dw开头 dwWord
LPCSTR(LPCTSTR) 用str开头 strString
用0结尾的字符串         用sz开头           szFileName对未给出的变量类型要求提出并给出命名建议给技术委员会。②、指针变量命名的基本原则为:
对一重指针变量的基本原则为:
“p”+变量类型前缀+命名
如一个float*型应该表示为pfStat
对多重指针变量的基本规则为:
二重指针:  “pp”+变量类型前缀+命名
三重指针:  “ppp”+变量类型前缀+命名
......
③、全局变量用g_开头,如一个全局的长型变量定义为g_lFailCount,即:变量名=g_+变量类型+变量的英文意思(或缩写)
④、静态变量用s_开头,如一个静态的指针变量定义为s_plPerv_Inst,即: 变量名=s_+变量类型+变量的英文意思(或缩写)
⑤、成员变量用m_开头,如一个长型成员变量定义为m_lCount;即:变量名=m_+变量类型+变量的英文意思(或缩写)
⑥、对枚举类型(enum)中的变量,要求用枚举变量或其缩写做前缀。并且要求用大写。
如:enum cmEMDAYS
{
EMDAYS_MONDAY;
EMDAYS_TUESDAY;
……
};
⑦、对struct、union、class变量的命名要求定义的类型用大写。并要加上前缀,其内部变量的命名规则与变量命名规则一致。
结构一般用S开头
如:struct ScmNPoint
{
int nX;//点的X位置
int nY; //点的Y位置
};
联合体一般用U开头
如: union UcmLPoint
{
long lX;
long lY;
}
类一般用C开头
如:
class CcmFPoint
{
public:
float fPoint;
};
  对一般的结构应该定义为类模板,为以后的扩展性考虑
如:
template <class TYPE>
class  CcmTVector3d
{
public:
                       TYPE x,y,z;
};
⑧、对常量(包括错误的编码)命名,要求常量名用大写,常量名用英文表达其意思。
如:#define CM_FILE_NOT_FOUND   CMMAKEHR(0X20B)     其中CM表示类别。
⑨、对const 的变量要求在变量的命名规则前加入c_,即:c_+变量命名规则;例如:
    const char*  c_szFileName;
2、 函数的命名规范:
函数的命名应该尽量用英文表达出函数完成的功能。遵循动宾结构的命名法则,函数名中动词在前,并在命名前加入函数的前缀,函数名的长度不得少于8个字母。
例如:
long cmGetDeviceCount(……);
3、函数参数规范:
①、 参数名称的命名参照变量命名规范。
②、 为了提高程序的运行效率,减少参数占用的堆栈,传递大结构的参数,一律采用指针或引用方式传递。
③、 为了便于其他程序员识别某个指针参数是入口参数还是出口参数,同时便于编译器检查错误,应该在入口参数前加入const标志。如:
……cmCopyString(const char * c_szSource,  char * szDest)
4、引出函数规范:
对于从动态库引出作为二次开发函数公开的函数,为了能与其他函数以及Windows的函数区分,采用类别前缀+基本命名规则的方法命名。例如:在对动态库中引出的一个图象编辑的函数定义为 imgFunctionname(其中img为image缩写)。
现给出三种库的命名前缀:
①、 对通用函数库,采用cm为前缀。
②、 对三维函数库,采用vr为前缀。
③、 对图象函数库,采用img为前缀。
对宏定义,结果代码用同样的前缀。
5、文件名(包括动态库、组件、控件、工程文件等)的命名规范:
文件名的命名要求表达出文件的内容,要求文件名的长度不得少于5个字母,严禁使用象file1,myfile之类的文件名。
三、注释规范:
1、函数头的注释
对于函数,应该从“功能”,“参数”,“返回值”、“主要思路”、“调用方法”、“日期”六个方面用如下格式注释:
//程序说明开始
//================================================================//
//        功能:  从一个String 中删除另一个String。
// 参数:  strByDelete,strToDelete
// (入口) strByDelete:  被删除的字符串(原来的字符串)
// (出口) strToDelete:  要从上个字符串中删除的字符串。
// 返回: 找到并删除返回1,否则返回0。(对返回值有错误编码的要// 求列出错误编码)。
// 主要思路:本算法主要采用循环比较的方法来从strByDelete中找到
//   与strToDelete相匹配的字符串,对多匹配strByDelete
//   中有多个strToDelete子串)的情况没有处理。请参阅:
//   书名......
// 调用方法:......
// 日期:起始日期,如:2000/8/21.9:40--2000/8/23.21:45
//================================================================//
函数名(……)
//程序说明结束
①、 对于某些函数,其部分参数为传入值,而部分参数为传出值,所以对参数要详细说明该参数是入口参数,还是出口参数,对于某些意义不明确的参数还要做详细说明(例如:以角度作为参数时,要说明该角度参数是以弧度(PI),还是以度为单位),对既是入口又是出口的变量应该在入口和出口处同时标明。等等。
②、 函数的注释应该放置在函数的头文件中,在实现文件中的该函数的实现部分应该同时放置该注释。
③、 在注释中应该详细说明函数的主要实现思路、特别要注明自己的一些想法,如果有必要则应该写明对想法产生的来由。对一些模仿的函数应该注释上函数的出处。
④、 在注释中详细注明函数的适当调用方法,对于返回值的处理方法等。在注释中要强调调用时的危险方面,可能出错的地方。
⑤、 对日期的注释要求记录从开始写函数到结束函数的测试之间的日期。
⑥、 对函数注释开始到函数命名之间应该有一组用来标识的特殊字符串。
如果算法比较复杂,或算法中的变量定义与位置有关,则要求对变量的定义进行图解。对难以理解的算法能图解尽量图解。
2、变量的注释:
对于变量的注释紧跟在变量的后面说明变量的作用。原则上对于每个变量应该注释,但对于意义非常明显的变量,如:i,j等循环变量可以不注释。
例如: long lLineCount  //线的根数。
 3、文件的注释:
文件应该在文件开头加入以下注释:
/////////////////////////////////////////////////////////////////////
//       工程:  文件所在的项目名。
//    作者:**,修改者:**
//    描述:说明文件的功能。
//    主要函数:…………
//    版本: 说明文件的版本,完成日期。
//    修改: 说明对文件的修改内容、修改原因以及修改日期。
//    参考文献: ......
/////////////////////////////////////////////////////////////////////
为了头文件被重复包含要求对头文件进行定义如下:
#ifndef __FILENAME_H__
#define __FILENAME_H__
其中FILENAME为头文件的名字。
   4、其他注释:
在函数内我们不需要注释每一行语句。但必须在各功能模块的每一主要部分之前添加块注释,注释每一组语句,在循环、流程的各分支等,尽可能多加以注释。
其中的循环、条件、选择等位置必须注释。
对于前后顺序不能颠倒的情况,建议在注释中增加序号。
例如:
在其他顺序执行的程序中,每隔3—5行语句,必须加一个注释,注明这一段语句所组成的小模块的作用。对于自己的一些比较独特的思想要求在注释中标明。
四、程序健壮性:
1、函数的返回值规范:
对于函数的返回位置,尽量保持单一性,即一个函数尽量做到只有一个返回位置。(单入口单出口)。
要求大家统一函数的返回值,所有的函数的返回值都将以编码的方式返回。
例如编码定义如下:
#define CM_POINT_IS_NULL CMMAKEHR(0X200)
:
:
建议函数实现如下:
long 函数名(参数,……)
{
long  lResult; //保持错误号
lResult=CM_OK;
//如果参数有错误则返回错误号
if(参数==NULL)
{
lResult=CM_POINT_IS_NULL;
goto END;
}
……
END:
return  lResult;
}
2、关于goto的应用:
对goto语句的应用,我们要求尽量少用goto语句。对一定要用的地方要求只能向后转移。
3、资源变量的处理(资源变量是指消耗系统资源的变量):
对资源变量一定赋初值。分配的资源在用完后必须马上释放,并重新赋值。
4、对复杂的条件判断,为了程序的可读性,应该尽量使用括号。
例:if(((szFileName!=NULL)&&(lCount>=0)))||(bIsReaded==TRUE))
五、可移植性:
1、高质量的代码要求能够跨平台,所以我们的代码应该考虑到对不同的平台的支持,特别是对windows98和windowsnt的支持。
2、由于C语言的移植性比较好,所以对算法函数要求用C代码,不能用C++代码。
3、对不同的硬件与软件的函数要做不同的处理

解决方案 »

  1.   

    上海贝尔公司也有一篇类似的文章,感觉更好,更详细。贝尔公司的那篇文章还提到了内存、类、const等细节的比较好的处理方法。建议大家也看看那篇文章。
      

  2.   

    3、对变量的定义,尽量位于函数的开始位置。
    这一点不同的人想法不同。一般i,j,k之类的循环变量我总是在靠近使用点最近处定义。
      

  3.   

    不错
    不知道关于空格的小问题,需不需要列入?
    如:int* a; or int *a; //这是个人对指针的理解问题
    如:int a = 0; or int a=0;
    如:void proc1(int a; int b);
       void proc1(int a;int b);
       void proc1( int a; int b);
       ...
      

  4.   

    应该减少使用
    #define  AAAA    0x00FFFF
    替换成const或者内联函数
      

  5.   

    其实这些还是比较基本的,程序写的多了,大致上都是这样的风格。提几点意见:
    “3、对变量的定义,尽量位于函数的开始位置。”
    这点不太符合C++习惯,算法提倡用C写,但其他的如果用C++的话也这样规定,
    未免有点,看看EC,这样可能会导致不必要的开销。
    {}问题,{这个,跟在上一句空一格后面也无妨,看起来也还可以,代码行数也
    少一些。
    “函数名的长度不得少于8个字母”,这个是不是有点死板了,
    不会个个都能超过的吧。“对一般的结构应该定义为类模板,为以后的扩展性考虑”,
    这点是不是有点走的太远了?可能我自己不太喜欢用模板。
    但我总觉得凡结构必模板化的做法是不是有点感觉上的虚无缥缈?关于注释:如果你是用VC作为开发环境,那可能会好一点,因为VC里的
    注释是绿色的,呵呵,但如果跨了平台,unix下的字符环境可没有这
    花花绿绿的东西了,一眼看下去,都是被注释的东西,一个源文件号称
    上千行,哥几个当场就晕那了,呵呵。所以我觉得注释以简洁为主。
    函数的说明千万放在头文件里,放在源文件有时候会看的头大的。对于可移植性:
    第一点:C++的移植性不是太好的,高质量的代码不一定意味着可移植性好,
    讲究移植性的代码里很可能充斥着ifdef之类的东西,所以我觉得,移植性
    该考虑,但你既然用C/C++来开发,就在一定程度上保证不了移植性了。
    第二点:出色!
    第三点:有点牵强,不是高手,不是阅历丰富的高手很难做到。另外提一点,我觉得蛮重要的,
    一个函数的代码长度尽量控制在你常用的显示器屏幕的两倍以下。
    一眼看不到头的代码,我常常觉得恼火:)个人看法。
    控制办法一般是:拆开处理方法,做封装。如果实在不行的话:(看着办了。
      

  6.   

    fang_jb(_L2002_寂寞如雪)的分析极妙值得学习!
      

  7.   

    蹦蹦跳猜想:所有goto前跳都可改写为后跳.
    请给出反例,谢谢!
      

  8.   

    对啊,贝尔、林锐都是 sb,楼上的你最牛b了。
    大家别理他,他是刚从青山出来的。我们继续学习。
      

  9.   

    注意
    1,宏函数定义里都要给变量加括号
    2,注释太多有时的确不好,影响正常的阅读,尤其注释是英语很容易与代码混淆。
       建议注视尽量使用中文
    3,在条件判断中,如果待判断的变量本身就是BOOL、bool,建议仍然要完整的写出来 if (XX==TRUE), 不要写if (XX)
      

  10.   

    刚刚学来的:源代码是一种交流的方式,问题是,交流的目标是谁?编译器?不,源代码是一个程序员与其它程序员交流的一种媒介。它是一种对意图的表达,一种对期望结果的表达。我们必须努力使交流尽可能的简单和清楚。为了做到这一点,变量名应当反映变量所扮演的角色。而变量的确切类型相形之下居次要地位。一个象sz这样的变量名只能告诉你你所看到的是C风格的字符串。至于该字符串所扮演的角色,你无法通过它来了解。
      

  11.   

    city_tiger(都市老虎):1谦虚点;2提高素质;3晚婚晚育,治好口臭
      

  12.   

    city_tiger你以为自己很牛吗?
    如果你是编程的独行侠,你可以这么说
    但是在一个合作的开发团体,如果没有这种规定,肯定会延迟开发
    进度,影响调试,围棋是一个人下的,软件是一个团体开发的,
    独行侠是没法开发出大型软件的不过对中国,也许有点差别,你的观点也许会对一点,
    听说很多公司开发软件是用的拼音而不是英文,很奇怪
      

  13.   

    不错。
    不过我想提出以下意见:
      1.关于goto的应用:我觉得在任何情况下都可以不用!都可以用其它的办法来解决!
      2.关于条件的判断:可以使用 if( 常量 == 变量 )
                                    {
                                       //语句
                                    }
       这样使用可以在build时就可以检查出下面的错误:
             你的意图是:if ( i == 5 ) { ..... } 
             而你一不小心写成了:if( i = 5 ) {... } 而Build时又不会报错,这种错误是不容易检查出来的。
      如果你采用上面的书写形式的话,在Build时就可以知道错误。个人意见
      

  14.   

    3、对变量的定义,尽量位于函数的开始位置。
    //////////////////////////////////////////应尽量晚定义.
    同意,
    看看 Inside The C++ Object Model 
      

  15.   

    规范很好!但还不够。(对团队开发来说:)
    对C/C++这样的语言要有足够的约束,应该作到从行文风格上无法区分
    水平相当的的程序员写出的代码。这在整体上才是优秀的统一的代码。下面是一些我们这儿的细节规范:TAB设为四个空格宽度。相匹配的左右大括号在同一竖列中。不得将左大括号放于行末。分号与前面的语句间隔一个空格。运算符左右各加一个空格。一旦混合使用运算符时必须加括号。如:if( i == ( j + k ) )函数名与其后的圆括号间 不要 加空格。不允许在函数中途定义变量(针对C++)。
    (这跟大形势有些不同……但程序的清晰是第一位的。)变量采用匈牙利命名法。一行只写一个语句。尽量用 == 进行主判断。尽量避免对“非”条件的判断。不允许形如:if( !(...) ) 的写法。switch语句中case子句间要空行。
    我们的目标是写出最清晰的代码,而不是最巧妙的代码。
      

  16.   

    关于goto:
    在控制跳出循环的时候往往不用goto会使得代码过长,
    显得有些拖沓,简洁其实也会让别人很舒服的。“不允许在函数中途定义变量(针对C++)。”
    在函数开头的时候定义真的是一个老旧的规范了。
    代码比较长 ,要翻上几下的时候,你会发现在末端发现了一个记不太清楚的
    变量的时候往回翻是很累的事情,并且:
    如果你在用到这个变量之前发生了另一个异常或者产生了一个函数退出的
    条件,你的变量就显得一点用处都没有,这时候造成的问题就是一种资源上
    的浪费,也许现在的内存大,浪费一点没关系,但这对于用C++的人来说不是个
    应该的习惯。“我们的目标是写出最清晰的代码,而不是最巧妙的代码。”
    大部分时候清晰和巧妙是可以共存的,不是吗:)
      

  17.   

    关于移植性:
    如果只是win9x<==>winnt之间的,那要简单的多。但如果是windows和unix之间的,呵呵,在我看来,
    个人写的代码想要这样移植真的是会疯掉,简单的还好,
    win32的api和linux下的函数差别太大,比如遍历文件夹,
    在linux下可以用opendir和readdir之类的,在windows下没有这样的函数
    在比如stl,这个东西在两个平台下相差的不是一点半点,呵呵
    就是Redhat很SCO之间的差别都不小。除非自己实现链表:)
      

  18.   

    simon_zhang() thanks a lot 
    , 小东西害死人 ,谢谢你的指教!
      

  19.   

    阿fash,敬上。毕竟我见过的FISH肚子都是大大的!
      

  20.   

    我发现 JBuilder 中的例子中,换行时倒是缩进 2 个字符的....
    反正,规范不一定只有一种呀
      

  21.   

    PioneerMan ((拓荒者)) :
    能不能把您的完整规范给偶发一份,偶正需要这个呢。[email protected]
      

  22.   

    关于:
    “如:int* a; or int *a; //这是个人对指针的理解问题
    如:int a = 0; or int a=0;
    如:void proc1(int a; int b);
       void proc1(int a;int b);
       void proc1( int a; int b);
       ...

    我喜欢没有空格的样式,如   int a=0;
    因为空格太多,语句长的时候,一行显示不完,还要移动滚动条,太麻烦!
      

  23.   

    我不是用C++的,但我认为应该在过程的开头尽量的定义完所用到的变量。除了i,j,k之类的循环变量。至于一个过程太长,不方便找变量的时候,是不是应该拆分过程了?
      

  24.   

    Mark
    很好
    可以更多地参考MFC的写法
    代码是别人能看懂才算牛
      

  25.   

    PioneerMan ((拓荒者)) :
    俺也想要完整规范
    [email protected]
      

  26.   

    关于函数只有一个退出点,其实可以用异常机制来实现,__try,__finally配合__leave来实现这个很好,而且在VC环境下看真是赏心悦目。
      

  27.   

    unsigned int(WORD) 用w开头 wCount
    这有些不妥,UINT在win32下是32位,应为dw。
      

  28.   

    unsigned int(WORD) 用w开头 wCount
    有些不妥,UINT在win32下是32位的,不是16位
      

  29.   

    关于使用工具的建议:1, 用indent处理诸如缩进一类琐碎的细节问题。
    2, 用doxygen做程序中的嵌入文档关于变量声明的建议:1, 变量在使用的时候才声明,这是效率的需要。
    2, 在函数开头用注释的方法说明函数内部用到的变量,这是为了清晰。其他:1, C++的可移植性有那么糟糕吗?还是不会用啊?win32<->unix间移植,难道
    C++的程序还不如C程序?stl可以统一只使用StlPort。__try, __final这样的win32扩展,使用起来对程序本身很有利,但是对移植很不利。2, 矫枉容易过正,诸如函数名不得少于8个字符一类的规定,我觉得很不合理。函数名以能够恰当反应函数功能为宜,如果不足,可以通过函数定义前面的注释来说明。如果在没有code insight功能的环境下写过程序就能体会到,冗长的函数名是多恶心的一个东西,特别是Win32 API。
      

  30.   

    我是匈牙利命名法的坚决反对者。那个东东的本来目前是帮助程序员在旧的C编译器上减少出错机会。现代C++编译器本身就可以把类型匹配做到非常好,连有符号对无符号的赋值都会出警告。另外所谓用类型前缀方便阅读纯粹就是睁着眼睛说瞎话,lpszUsername好读还是username好读?没人会把username当成是int类型的吧。自从发明匈牙利命名法那小子(丫的博士论文)离开微软之后,连微软都闭口不提匈牙利命名法了。C#里有没有什么匈牙利命名法?没有!那是原Delphi的设计者搞出来的新东东。
    程序员也不能读死书,不要把上个世纪用来迁就原始C编译器的命名法带到二十一世纪了。
      

  31.   

    我觉得 .net文档里提到的规范也不错!
    我还要求我的手下猛写注释,注释一般要占80%以上。
    类、函数、方法的开始,写他们的总体目的;
    每个重要变量写他们的作用、用途;(如果一个变量担任了太多的责任,往往说明设计不完美、不精致)
    每小段代码又要写伪代码,一直写到不能用中文写为止;
    每次代码的修改都要在每处写注释,注明修改目的、修改范围、修改者、修改时间。
    为了质量,浪费点时间是值得的,磨刀不误砍柴工嘛!
    当然,我只是讲一下自己的习惯,供大家参考,并不想说这就是好习惯!
      

  32.   

    PioneerMan ((拓荒者)) :
    给俺也来一份完整规范
    [email protected]
      

  33.   

    这种东西很难找的啊有的话,给我也一份,[email protected]
      

  34.   

    有的话,给我也一份,[email protected]
      

  35.   

    世界上 C++ 代码的书写规范有千千万,很多人都有自己的习惯,也没有那份规范是完美的,防止四海皆准的。就连MS内部,个个开发组的代码规范都不一样。据说 Windows 组,MFC 组,Office 组,写的代码都不一样的。VC 组的人喜欢把左花括号放在单独的一行,而写 Server 的那些家伙很多喜欢经典的 K&R 方式。个人以为,这个东西不必要规定。源代码的规范,目的是为了交流与维护,上级不能把规范强加于下级,而应该是采取一种协商的方式,采取一种多数人统一的方式规范代码。当然如果组员水平实在....,属于连 padding 和 indention 都不懂做的,另当别论。不过这样水平的人,招他来做什么?
      

  36.   

    PioneerMan ((拓荒者)) :给我一份规范吧,我很需要,谢谢[email protected]
      

  37.   

    能给我一份吗,谢谢:[email protected]
      

  38.   

    匈牙利命名法早就过时了,还是用java风格吧
      

  39.   

    谢谢大家,大家多提建议,
    我们的目的是让别人能够在最少的时间内读懂我们的程序
    特别是一些比较难的算法
    还有不完善的地方,大家一起讨论.
    下面是一个示例:
    //
    //////////////////////////////////////////////////////////////////////
    //工程: FuncTemplate.cpp //
    //作者: ** //
    //修改者: **,*** //
    //描述: 用来处理对二叉树的一些算法,以及矩阵内存分配。
    //主要函数:cmWalkTreeUseCycle,cmInitMatrix
    //版本: FuncTemplate 1.0版。
    //完成日期:2000-8-26 //
    //修改日期: 2000-8-27,2001-12-21 //
    //参考文献: 图形程序开发人员指南(机械工业出版社)
    //////////////////////////////////////////////////////////////////////#define  STRICT
    #include "stdio.h"
    #include "stdlib.h"#ifdef __cplusplus
    extern "C" {
    #endif  /* __cplusplus */#include "makehresult.h"
    #include "memory.h"
    #include "functemplate.h"#define CM_MEM_POINTER_IS_NULL CMEMAKEHR(0X100) //表示指针不为空的错误
    #define CM_MEM_POINTER_IS_NOT_NULL CMEMAKEHR(0X101) //表示指针不为空的错误#define MAX_PUSHED_NODES 100  //定义最大压栈数量
    ////程序函数说明开始
    //==================================================================
    // 功能: 用循环来实现二叉树的左序遍历
    // 参数: cpNode
    //(入口) cpNode:  二叉树的入口地址,即根节点的地址。
    //(出口) 无。
    // 返回: long 的函数返回码,如果返回值为CM_OK,表成功遍历,返回
    // MS_POINT_IS_NULL表二叉树指针为空。
    // 调用方法:在调用此函数前必须先初始化二叉树
    // 思路: 如果正在访问某节点,如果该节点有左分枝,就先访问左分枝。
    // 接着访问该节点,如果该节点还有右分支。就再
    //       访问右分支。
    // 访问左分枝时,在该处栈中做标记,当处理完左分枝就访问此
    // 处。访问完每一个节点后,如果该节点没有右孩子,并且栈已经
    // 为空,那么对该节点的访问就完成,代码对每一个节点都重复以
    // 上操作。
    // 参阅: 图形程序开发人员指南(机械工业出版社)Page:927
    // 日期: 2000/8/26.9:40--2000/8/26.21:45
    //==================================================================//图解:
    //                   根节点
    //                      @
    //                    /   \
    //  /     \
    //   左孩子@       @右孩子
    //                 / \      / \
    //   /   \    /   \
    //               @     @   @    @
    //           左孩子 右孩子左孩子 右孩子HRESULT cmWalkTreeUseCycle(const NODE * cpNode)
    ////程序函数说明结束
    {
    HRESULT lResult;//用来保存返回的错误号。
    NODE * pNodeStack[MAX_PUSHED_NODES];//用来作为节点的堆栈。
    NODE ** ppNodeStack;//用来指示节点堆栈的指针。 lResult=CM_OK;
    //判断树是否为空。
    if(cpNode !=NULL)
    {
    pNodeStack[0]=NULL; //设置堆栈为空。
    ppNodeStack=pNodeStack+1;
    for(;;)
    {
    //如果当前的节点有左孩子,对当前点压栈。
    //并把当前点移到左孩子,开始遍历左子树,
    //如此,直到找到没有左孩子的节点。
    while (cpNode->pLeftChild!=NULL)
    {
    *ppNodeStack++=(NODE*)cpNode;
    cpNode=cpNode->pLeftChild;
    }

    //我们现在处于没有左孩子的节点,所以访问
    //节点,如果有右子树,然后访问右子树。或
    //后入的节点。重复节点的出栈直到我们找到
    //有右子树的节点,或所有节点出栈
    for(;;)
    {
    //访问节点(调用别的函数,没有实现)
    cmVisitNode(cpNode); //如果节点有右孩子,使该孩子成为当前节
    //点并开始遍历其子树,否则回朔访问节点
    //直到我们发现一个有右子树的节点,或所
    //有的入栈点已经被访问。
    if(cpNode->pRightChild!=NULL)
    {
    cpNode=cpNode->pRightChild;
    break;
    } //出栈下一个节点,我们可以访问它,并判
    //断是否有右子树。
    if((cpNode=*(--ppNodeStack))==NULL)
    {
    //栈为空并且当前节点没有右子树,完
    //成遍历。
    lResult =CM_OK;
    goto END;
    }
    }
    }
    }
    //如果指针为空则设置返回值信息为MS_POINT_IS_NULL。
    lResult=CM_POINTER_IS_NULL;
    END:
    return lResult;
    }
    ////程序函数说明开始
    //==================================================================
    // 功能: 对矩阵进行分配内存
    // 参数: cwRowSize,cwColSize,ppplMatrix
    //(入口) cwRowSize:矩阵的行数; 
    // cwColSize:矩阵的列数; 
    // ppplMatrix:要分配的矩阵的地址;
    //(出口) ppplMatrix:分配的矩阵的地址;
    // 返回: long 的错误号,如果返回值为CM_OK,表分配成功,返回
    // CM_POINTER_IS_NOT_NULL表矩阵的入口地址值不为空。
    // CM_MEM_ALLOC_FAILED 系统的内存不足
    // 调用方法:在调用此函数前必须先对传入值赋空。对传入值作引用。
    // 如:定义: long ** pplMatrix;对pplMatrix赋空,即:
    // pplMatrix=NULL; 调用为:cmInitMatrix(10,10,&pplMatrix); 
    // 思路: 先对每一行分配内存,再对每一行对应的列分配内存。
    // 参阅: 无
    // 修改人:
    // 日期: 2000/8/29.9:40--2000/8/29.16:45
    //==================================================================
    HRESULT cmInitMatrix(const size_t cwRowSize,const size_t cwColSize,long *** ppplMatrix)
    ////程序函数说明结束
    {
    HRESULT lResult; //存储返回值。
    lResult=CM_OK; WORD  wI; //循环变量

    //要求对传入值进行初始化NULL.
    if(**ppplMatrix!=NULL)
    {
    lResult=CM_POINTER_IS_NOT_NULL;
    goto END;
    }
    //对矩阵的行进行分配内存
    **ppplMatrix=(long*)malloc(cwRowSize*sizeof(long*));
    //如果分配失败则返回。
    if((**ppplMatrix)==NULL)
    {
    lResult=CM_MEM_ALLOC_FAILED;
    goto END;
    }

    //对每一行所拥有的列数进行分配内存。
    for(wI=0;wI<cwRowSize;wI++)
    {
    *ppplMatrix=(long**)malloc(cwColSize* sizeof(long));
    //如果分配失败则返回错误。
    if(*ppplMatrix==NULL)
    {
    lResult=CM_MEM_ALLOC_FAILED;
    goto END;
    }
    //对内存置为零
    memset(*ppplMatrix,0,cwColSize*sizeof(long));
    }
    END:
    //对错误进行处理。
    if(FAILED(lResult))
    //如果分配不成功则释放内存。
    if(**ppplMatrix!=NULL)
    {
    //对每一行内存进行释放。
    for(wI=0;wI<cwRowSize;wI++)
    {
    if(*ppplMatrix!=NULL)
    {
    free(*ppplMatrix);
    *ppplMatrix=NULL;
    }
    }
    //对列的内存进行释放。
    free(**ppplMatrix);
    **ppplMatrix=NULL;
    }
    return lResult;
    }#ifdef __cplusplus
    }
    #endif  /* __cplusplus */