【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

查看: 3356|回复: 3

[C/C++] 关于C++的程序书写规范

[复制链接]
发表于 2005-12-27 17:05:13 | 显示全部楼层 |阅读模式

刘振飞 liuzf@pku.org.cn

PKU-748-RIP 1997.08.31

一.题外话 1.关于面向对象(OO:Object-Oriented)的方法

OO方法是在结构化程序设计的基础上,进一步力图用更自然的方法来反映客观世界。数据是用来描述客观具体事物的,而处理的过程实质上是反映了客观事物的运动和相互作用。OO方法的目标就在于是软件开发的思维更加符合客观世界的思维。

OO的世界观:世界万物都处于不停的运动和发展中,数据和处理不可分割。

OO的方法论:用问题域的模型来模拟客观世界。

在OO的软件系统中,将数据结构和对数据的操作封装在一起,用对象 (Object)这一概念来反映客观事物的静态特性(Attribute)和动态特性 (Service)。OO系统是由对象,对象的状态变化及对象间相互作用构成的。

从“面向对象编程(OOP:Object-Oriented Programming)”到“面向对象的设计(OOD:Object-Oriented Design)”,再到“面向对象的分析 (OOA:Object-Oriented Analysis)”,OO方法是计算机科学的一次革命。

2.如何评价一个软件的优劣

最根本也是最重要的一条应该是软件的“稳定可靠”性,用户或许可以容忍你写的软件功能稍弱,外表不甚美观,但绝不会容忍程序动不动就死机!在你时刻关注着“稳定可靠”性之外,还应考虑以下几方面:

从产品角度

从开发角度

功能齐备

满足用户的功能需求

实现用户的所有功能

易学易用

用户稍加培训就可操作,并可以自己进一步摸索

方便的联机帮助

充分利用面向对象的思想

软件的文档齐全

界面漂亮

1.美观

2.亲切

1.充分利用开发环境提供的能力

2.书写风格要统一化

一个好的软件应该是这样的:它有着经过周密计划和审慎设计的完美的内部结构,同时又具有让人赏心悦目的精美的外观。这样的软件,你可以称它是一件美妙的工艺品,也可以称它是一部复杂而可靠的机器。

编写好的软件肯定离不了好的书写习惯,它有三方面的含义:

(1).安全: 你的代码完成所需的功能之余,不要产生负作用(要稳定可靠!)

(2).易读: 类、实例、成员变量、成员函数的命名最好让同事一目了然

(3).美观: 让同事看你的程序风格跟看自己的一样;统一就是美

以下的讨论都是基于这三条的。

 楼主| 发表于 2005-12-27 17:07:07 | 显示全部楼层
二.养成良好的编程习惯 1.禁止使用全局性的变量和函数的(当然CWinApp派生类的那个全局对象除外)

在面向对象的系统中,应该是所有的一切都是基于对象的。你原来习惯的全局函数完全可以放在一个类中,大不了作为一个静态成员函数。

2.局部变量一定要初始化

如果你声明一个变量,千万不要以为编译器会自动将之赋零值!你随手给它赋个初值并不麻烦,却会使程序更可靠,何乐而不为呢?

int iTemp = 0;

CRect rectTemp = CRect(0, 0, 0, 0);

3.成员函数的功能一定要单一;实现其功能时不要过分追求技巧,函数体也不能过长

功能单一才有利于公用。计算机是个笨蛋,老老实实的写法可能更适合它,也让别人易懂。你愿意看PageDown半天都找不着结尾的函数吗?把函数体的长度限制在200行之内吧!

交换两个整数i和j的值,方法二是不是比方法一(少用了一个变量)更简单易懂:

方法一:

i = i+j;

j = i-j;

i = i-j;

方法二:

int k = i;

i = j;

j = k;

4.自己定义的类(和界面有关或存储有关,或较为复杂的)尽可能从MFC的最基本类CObject派生出来,但一些基本的类应尽量简单,例如类似于CRect, CPoint之类的类。

利用MFC的CObject作为基类的好处是可以方便的利用它提供的成员函数,比如判断运行时刻类IsKindOf(...)之类的函数。当然如果你定义的类非常简单,就没有这个必要了。

5.尽可能的将自己定义的类的声明和定义分别放在同一个模块的头文件和实现文件中,象对话框由这种VC++自己生成的框架更是如此

假定你定义了一个用于寻找匹配文件的类CFindMatchedFile,那么在该类的声明和inline函数的定义放在头文件FindMatchedFile.h中,在FindMatchedFile.CPP中存放CFindMatchedFile的除inline函数之外的成员函数定义。这样可以增加各个类之间的独立性

6.除了循环变量i,j,k...之外,文件、类、(成员)变量、(成员)函数的命名要有意义,大小写相间,一目了然;宏定义和常量全要用大写

操作系统和编译器既然允许长的名字,我们干嘛还客气?

7.设计你的类时要尽量考虑到它的通用性

日积月累,你就会拥有自己的类库!

8.对自己定义的复杂类一定提供构造和析构函数,在构造函数中初始化所有的成员变量,在析构函数中删除可能申请的内存空间

不要让你的构造和析构函数空闲着,也不要让它们再干其余的额外工作

9.析构函数前一律加上virtual;所有虚函数前一律加上virtual

尽管virtual属性具有继承性,但显式的写上更明了。析构函数为virtual可以防止”memory leak”。下面的CSonClass是从CParentClass派生出来的:

CParentClass* pParentClass = new CSonClass;

...

delete pParentClass;

如果CSonClass的析构函数是virtual的,那么一切正常;否则最后一句话仅仅释放了ParentClass占用的空间,并没有释放CSonClass占用的空间

10.不需要修改类的成员变量的成员函数,将之声明为const

如此可以保证不出现不必要的修改。比如:

DWORD CBirthDay::GetBirthYear() const

{

return m_dwYear;

}

11.输入参数声明为const,如果成员函数不会修改该输入参数的内容的话;当需要输入参数是一个类的实例时,请使用引用,或者指针,但严禁直接传送实例,那样不仅效率低,而且易出错,不会带来任何好处

用类的实例作为输入参数将大大降低系统的效率,用引用是最好的方法:

CBirthDay::CBirthDay(cosnt CBirthDay& BirthDay)

{

m_dwYear = BirthDay.m_dwYear;

m_dwMonth = BirthDay.m_dwMonth;

m_dwDay = BirthDay.m_dwDay;

}

12.自己申请的内存Buffer释放后,将其指针置为NULL

使用指针之前先判断其有效性,用完就释放之:

DWORD* pdwMyBuffer = new DWORD[100];

if (pdwMyBuffer != NULL)

{

// 干你的工作

...

delete []pdwMyBuffer;

pdwMyBuffer = NULL;

}

13.对浮点数比较大小时不要使用==

本来应该相等的两个浮点数由于计算机内部表示的原因可能略有微小的误差,这时用==就会认为它们不等。应该使用两个浮点数之间的差异的绝对值小于某个可以接受的值来判断判断它们是否相等,比如用

if (fabs(fOldWidth-fNewWidth) < 0.000001)

来代替

if (fOldWidth == fNewWith)

14.对数组和缓冲区进行检查,防止越界,尤其是变长的情况下

你申请多少空间,就只能用多少。越界使用往往是造成程序无故突然退出的祸首。

15.可以使用ASSERT来提前发现你程序中潜在的错误

if语句是说可能存在不同的情况;而ASSERT语句是说肯定存在的情况,如果ASSERT失败,就说明在此之前已经发生了错误。ASSERT用在自己负责的模块内部很有用,可以提前发现一些不应发生的错误,提高程序的执行效率。

16.特别谨慎使用goto语句,最好别用它

尽管goto语句在某些特殊的情况下(比如编译器中)还很管用, 但它破坏了整个程序的结构,尤其使用goto嵌套时,更让人一头雾水(很久以前就有人提出取消它)。所以不到万不得已时刻不要用它,可以用break,continue之类的语句替代之。

17.所有成员函数尽量是单入口,单出口

也许多出口的程序写起来更简洁,意义也更明了。但出了问题调试时会很难定位,所以宁可多用一些BOOL变量,多加些判断,保证单出口:

BOOL CMyClass:oSomething(PVOID pvBuffer, ...)

{

if (pvBuffer == NULL)

return FALSE;

...

return TRUE;

}

可以这样写:

BOOL CMyClass:oSomething(PVOID pvBuffer, ...)

{

BOOL bRet = FALSE;

if (pvBuffer != NULL)

{

...

bRet = TRUE;

}

...

return bRet;

}

18.调用函数时要严格按照接口规范

严格按照函数的输入要求给它合适的参数

19.实现(成员)函数时务必要求输入参数是在要求范围之内

尤其你定义的(成员)函数给别人调用时,用if语句要比ASSERT更合适

20.调用函数后务必要判断其返回值的合法性,如果它的返回值不是void的话

不要假定函数调用永远是成功的,异常情况总会发生的,尽管可能性不大

21.在你劳神的地方请加上详细的注释说明。除了最简单的存取成员变量的Set_/Get_成员函数之外,其余大部分的函数写上注释是良好的习惯。尽量使你的程序让别人很容易看懂

太多的注释会使程序很难看,但一些复杂的算法和数据结构处还是要加上注释的,这样别人就容易看懂。否则时间长了,你自己都未必看明白了

22.自己做代码内部(单元)测试时,必须做到语句覆盖,并且特别要注意边界值的覆盖

要让每个语句都被执行过,并且边界值(最大和最小)也被测试过。你在程序中写的各种情况都可能在用户那里出现

23.代码写完后要尽可能多的做一些静态检查(Debug调试可是很费神费时的)。尤其是对算法和数据管理(比如对文件存取)部分

比如:

BOOL CMyClass::CheckValidParameters();

...

// 下面少加了函数调用的()

CMyClass MyClass;

if (MyClass.CheckValidParameters)

{

...

}

24.在涉及永久存储和必须确保数据结构的长度不变的情况下,一定要使用固定长度的数据类型,比如(我们自定义的)UINT8、UINT16、UINT32等等;在不关心数据长度的时候,提倡使用int和UINT,这样在操作系统升级的时候,你的程序的速度会随之提高

以后使用int和UINT时你得提醒自己它们的长度是不小于32位的!现在UINT和DWORD一样都是32位,保不定在Win64出来时UINT就成了64位而DWORD不变,这时使用UINT就会提高软件的速度。当然存储时必须使用固定长度的类型。

25.类型之间的转换一律使用显式转换

如:

WORD wTemp = 0;

LONG lTemp = 0;

...

wTemp = (WORD)lTemp;

...

lTemp = (LONG)wTemp;

26.类中的成员变量尽可能地不定义为public变量,而通过相应的Get_、Set_函数得到或设置相应变量的值。为将来的派生考虑,也应尽量使用protected而非private成员 27. BOOL变量一定要严格的赋TRUE或FALSE值;在if语句中,语义应写清楚

TRUE的定义是1,FALSE的定义是0。尽管任何一个非零值判断时被认为是“真”,但不是严格等于“TRUE”!你自己写程序时应该确认一个BOOL变量应该要么是TRUE,要么是FALSE。

int i = 10;

if (i)

{

}

应写为

int i = 10;

if (i != 0)

{

}

BOOL变量表达的是“逻辑值”,所以两个BOOL变量之间不要直接用==比较是否相等:

BOOL bTemp = TRUE;

if (bTemp == TRUE)

{

}

应写为

BOOL bTemp = TRUE;

if (bTemp)

{

}

 楼主| 发表于 2005-12-27 17:07:49 | 显示全部楼层

28.定义宏时,参数使用扩号,结果也应扩起来 #define SUB(a,b) ((a)-(b))

以确保如下形式的调用不会出错:

3*SUB(3,4-5)

29.变量的长度一定要用sizeof来求例如int类型原来是2字节,而现在是4字节

30. 同一指针new和delete时用的类型一定要完全一致 char* p = new char[10];

int* p1 = (int*)p;

错误的删除方法:

delete []p1; // wrong

正确的删除方法:

delete [](char*)p1; // right

或者:

delete []p; // right

31.new一个实例和多个实例时的delete方法不一样 new char[n] 和 delete []对应。

new char和delete对应。

32.实现(成员)函数时,可能需要几个小功能模块。每个小模块之间最好用{}间隔起来,以保证每个小模块之间的局部变量互不干扰,尽量避免出现各个小模块之间公用的局部变量,因为上个模块可能会在局部变量中留下下个模块不希望的值 void CMyClass:oSomething(…)

{

int i = 0, j = 0, k = 0;

// 小功能模块1使用i, j, k

// 小功能模块2使用i, j

// 小功能模块3使用k

}

可以改为这样写:

void CMyClass:oSomething(…)

{

{

int i = 0, j = 0, k = 0;

// 小功能模块1使用i, j, k

}

{

int i = 0, j = 0;

// 小功能模块2使用i, j

}

{

int k = 0;

// 小功能模块3使用k

}

}

33.注意在C++中一个类的创建过程和删除过程的顺序刚好是相反的:其创建过程是:

1.按虚基类的出现顺序创建各个虚基类

2.按非虚基类的出现顺序创建各个非虚基类

3.按本类中非静态成员变量的出现顺序创建它们

4.调用本类的构造函数

其删除顺序刚好相反:

1.调用本类的析构函数

2.按本类中非静态成员变量的出现逆顺序删除它们

3.按非虚基类的出现逆顺序删除各个非虚基类

4.按虚基类的出现逆顺序删除各个虚基类

看看下面的例子:

class A

{

public:

inline A();

inline virutal ~A();

inline virtual void DoSomething();

};

inline A::A()

{

}

inline A::~A()

{

DoSomething();

}

inline A:oSomething()

{

printf(_T(”A:oSomething()\n”));

}

class B: public A

{

public:

inline virtual void DoSomething();

};

inline B:oSomething()

{

printf(_T(”B:oSomething()\n”));

}

void main()

{

B* pB = new B;

delete pB;

}

这个例子的输出结果是:

A:oSomething()

而不是

B:: DoSomething()

因为进入A::~A()时,B本身已经“终结”了。故此时调用DoSomething()只能是A:: DoSomething()。得出结论:凡在析构函数中调用的函数只能是(纯)自己的和基类的,别指望调用子类的虚函数。

34.写程序时要把编译选项的“警告级别(Warning Level)”置为最高级,并且最终编译结果不允许出现任何警告!因为任何警告都是错误可能的隐藏之地!

35.类的实现文件(.CPP)头上在#include之后要加上下面的语句,可以发现程序中的“Memory Leak”: #ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

 楼主| 发表于 2005-12-27 17:08:21 | 显示全部楼层
推荐一种程序的书写风格

每个人都有自己的书写习惯,所以统一起来很困难。但统一的书写习惯明显有好处,我们为什么看Microsoft的例子程序觉得很顺眼?因为看的多了,并且它的例子基本上保持了相同的书写风格,让你习以为常了。我们为什么不这样干呢?每个人都舍弃一些自己的习惯,同一小组的同事协商一个共同的风格,大家都照此写代码,以后交流起来会很亲切,整个程序就象一个人书写的。

1.空格和空行

缩进TAB键统一为4个空格

赋值语句(=)及逻辑判断符(> < != && ||...etc)左右各空一格

算术运算符左右不空格

if/while/switch之类的语句右边空一格

实现每个成员函数后加一个空行

每个成员函数内部各个子功能之间用一个空行

2.除了循环变量i,j,k...之外,文件、类、(成员)变量、(成员)函数的命名要有意义,大小写相间,一目了然;宏定义和常量全要用大写 3.类的命名和定义

类名前加前缀C,还可以加些别的适当的前缀,比如C748_,或者你负责部分的简写,比如CPSPNT_, CRIP_,等等;类的成员变量必须加前缀m_

类的书写顺序:

类名

{

public类型的成员变量

public类型的成员函数

protected类型的成员变量

protected类型的成员函数

private类型的成员变量

private类型的成员函数

};

把公用的成员放在最前面,可以方便的让使用你这个类的同事找到他需要的外部可以调用的公用成员函数(或许还有极少的成员变量),而他并不关心这个类的内部数据和详细实现。各种类型的成员中间可以按其功能属性分组,各组间用空行隔开。

类的声明和其inline函数(──不要在类的声明中直接定义,而放在类的声明之后)放在头文件(.h)中,在头文件中加上宏定义保证该类只被include一次;类的非inline成员函数放在同一个模块(.cpp)中实现。

比如你定义的一个类CMyClass,我们来看看它的头文件.h格式:

// MyClass.h : declarating the class CMyClass

//

#ifndef __MYCLASS_H__

#define __ MYCLASS_H__

CMyClass

{

...

};

// 下面定义CMyClass的所有inline函数

...

#endif // __MYCLASS_H__

下面是实现文件.cpp的格式:

// MyClass.cpp : defining the class CMyClass

//

#include “stdafx.h”

#include “MyClass.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

// 下面定义CMyClass的所有非inline函数

4变量的命名要遵循所谓的匈牙利命名法,即可由命名得知变量的类型和用途

下面是一些常见类型的缩写约定:

i -----> int

u -----> UINT

w -----> WORD

dw -----> DWORD

b -----> BOOL

by -----> BYTE

ch -----> CHAR

sz -----> CHAR ARRAY

l -----> LONG

f -----> float

d -----> double

q -----> SINT64

uq -----> UINT64

v -----> void

fn -----> function

h -----> HANDLE

rect -----> RECT, CRect

pt -----> POINT, CPoint

如果是指针,前面加上p;定义指针时*号紧跟在类型之后,比如:

DOWRD* pdwTemp = new DWORD;

CMyClass MyClassTemp;

CMyClass* pMyClassTemp = & MyClassTemp;

如果定义两个实例,可以这样合起来写:

CMyClass MyClassTemp1, MyClassTemp2;

但定义两个指针必须这样分开写:

CMyClass* pMyClassTemp1;

CMyClass* pMyClassTemp2;

5.各种C++语句的书写风格要统一 (1).顺序语句

dwParam1 = dwParam2+dwParam3;

int iHeight = 15, iWidth = 18;

int iRectArea = iHeight*iWidth;

(2).判断语句

<1>if-else

if ((dwParam1 > dwParam2) && (dwParam2 != 100))

{

}

else if (dwParam1 == 13)

{

}

<2>? :

BOOL bRet = (dwParam1 > dwParam2) ? TRUE : FALSE;

<3>swith-case

switch (dwParam1)

{

case 0:

TRACE(_T(“for case 0\n”);

break;

case 1:

// do something here...

break;

default:

// for all default case here

break;

)

(3).循环语句

<1>for

for (int i = 0; i < 100; i++)

{

// your loop body

}

<2>while

int i = 0;

while (i < 100)

{

// your loop body

i++;

}

<3>do...while

int i = 0;

do

{

// your loop body

i++;

} while (i < 100);

(4). 关于注释的写法:

不提倡使用/* 和 */来注释, 最好使用//来注释。如果是对某一段程序(算法/结构)的注释, 在程序头直接用//再空一格来进行说明,一行不要超过80字符

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-2-6 04:39

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表