C++编码规范指南

来源:互联网 发布:php整数保留两位小数 编辑:程序博客网 时间:2024/05/23 01:22

原文见:http://www.codeproject.com/KB/cpp/cppstyle.aspx

目录

前言

命名规范

Tab键

空格,大括号和圆括号

防止重复包含

类设计

正确使用常量

函数返回值

杂项

面向对象技巧


前言

为了编写能被其他开发人员理解的代码,你应该遵守一定的编码规范而不是使用你自己发明的独特风格。包括命名规范(就是你如何给你的变量和函数命名),代码和类的布局(包括tab键,空格,括号的布置),必要的const等等。要理解本文你需要有一定的C++和面向对象基础。除了本文外,网上还有一些出色的在线资源可以参考:


命名规范

当你开始写程序是你遇到的第一件事就是如何给你的变量,对象以及函数命名。不要低估命名规范的重要性,合适的命名可以让代码具有“自文档”性,方便他人理解。除非你故意要让你的代码晦涩难懂,否则你应该遵守以下指导。

使用能明确描述一个变量或对象的名字。函数名应该包含一个表示函数行为的动词。

Linux风格(使用下划线分隔单词,全小写命名)

int some_variable;
float bar_weight;
unsigned int users_number;
bool is_engine_started;
double circle_area;
double m_circle_area; //类的私有变量, 增加 m_ 前缀
void* psome_void_pointer;
const int USERS_NUMBER;

int i, j, n, m, tmp; //本地循环变量, 临时变量

namespace mynamespace;

vector users;
vector user_names;

class SomeClass;

int do_work(int time_of_day);
float calculate_radius(const Circle& rcircle);
int start_io_manager();
int open_dvd_player();

windos风格(MFC程序,增加匈牙利式前缀表示变量类型)

INT nSomeVariable;
FLOAT fBarWeight;
DWORD dwUsersNumber;
BOOL bIsEngineStarted;
DOUBLE dCircleArea;
DOUBLE m_dCircleArea;  //类私有变量,增加m_前缀

PVOID pSomeVoidPointer;

const INT USERS_NUMBER;
int i, j, n, m, tmp;  //本地循环变量,临时变量

namespace MyNameSpace;

vector<int> nUsers;
vector<char *> pszUserNames;
class CSomeClass;

INT DoWork(INT nTimeOfDay);

FLOAT CalculateRadius(const& Circle rCircle);
INT StartIOManager();
INT OpenDvdPlayer(); //如果缩写超过两个字母,则不要全大写

.NET平台(匈牙利命名法,去掉前缀)

int someVariable;
float barWeight;
unsigned int usersNumber;
bool isEngineStarted;
double circleArea;
double circleArea; //表示类私有变量,以this->circleArea形式使用

void^ someVoidPointer;

const int UsersNumber;

int i, j, n, m, tmp;  //本地循环变量,临时变量

namespace MyNameSpace;

array<int> users;
array<String> userNames;

class SomeClass;

int DoWork(int timeOfDay);
float CalculateRadius(const& Circle circle);
int StartIOManager();
int OpenDvdPlayer();  //如果缩写超过两个字母,则不要全大写

Tab键,制表符

Tab键用8个空格,所以缩进也是8个字符,这样是为了清除地表示一个代码块的开始和结束,同时如果缩进很宽的话,你会更容易理解缩进是如何起作用的。还有一个好处就是当你的函数嵌套太深的时候会及时提示你。

//不好的缩进
void FaceDetector::estimate_motion_percent(const vec2Dc* search_mask)
{
  if (search_mask == 0) { m_motion_amount=-1.0f; }
  else
    {
     unsigned int motion_pixels=0;
     unsigned int total_pixels=0;
      for (unsigned int y = get_dy(); y < search_mask->height()-get_dy(); y++)
      {
       for (unsigned int x=get_dx(); x < search_mask->width()-get_dx(); x++)
       {
           total_pixels++;
           if ((*search_mask)(y,x)==1) motion_pixels++;
       }
      }
    m_motion_amount = float(motion_pixels)/float(total_pixels);
   }
}

//好的缩进
void FaceDetector::estimate_motion_percent(const vec2Dc* search_mask)
{
        if (search_mask == 0)
                m_motion_amount = -1.0f;
        else {
                unsigned int motion_pixels = 0;
                unsigned int total_pixels = 0;
                for (unsigned int y = get_dy(); y <
                           search_mask->height() - get_dy(); y++) {
                        for (unsigned int x = get_dx(); x <
                                 search_mask->width() - get_dx(); x++) {
                                total_pixels++;
                                if ((*search_mask)(y, x) == 1)
                                        motion_pixels++;
                        }
                }
                m_motion_amount = float(motion_pixels) / float(total_pixels);
        }
}

空格,大括号和圆括号

在函数和类定义的时候,把大括号另起一行会让代码看起来更清晰:

void some_function(int param)
{
        //函数体
}

class SomeClass
{
        //类实现
};
但是对于if,for,while等语句的括号就有一些其他的形式:

if(some_condition)
{
        //...
}

if( some_condition )
{
        //...
}

if (some_condition) {
        //...
}
首选形式应该是最后一种,由K&R提出的。把开括号放在行末闭括号放在行首,目的是为了减少空行的数量。

数学表达式中的空格应该按如下形式:

float a, b, c, d;
int e, f, g, h;

a = (b + d) / (c * d);

e = f - ((g & h) >> 10);    //好的
e =f-( (g&h ) >>10);        //不好的

防止重复包含

用宏把你的头文件内容包装起来防止被重复包含:

#ifndef Foo_h
#define Foo_h

// ... Foo.h 文件内容

#endif

类设计

当你声明一个类时,要记住如果你没有提供这些函数的定义,C++编译器会自动帮你提供:

构造函数
拷贝构造函数
赋值操作符
取地址操作符(const的和非const的)
析构函数

//声明一个类
class SomeClass
{
};

//你将自动拥有这些函数
class SomeClass
{
public:
        SomeClass() { }                     //构造函数
        ~SomeClass() { }                    //析构函数
        SomeClass(const SomeClass &rhs);    //拷贝构造函数
        SomeClass& operator=(const SomeClass& rhs); //赋值操作符
        SomeClass* operator&();             //取地址操作符
        const SomeClass* operator&() const; //const取地址操作符
};
如果你的类在构造函数中分配了内存并且在析构函数中才释放这些内存而你又不想提供拷贝构造函数和赋值操作符的话,把拷贝构造函数和赋值操作符声明为私有的(并且不要实现它们),这样可以防止意外地复制对象导致内存被重复释放。
按照public,protected, private的顺序声明类成员。这样类的使用者能够立即看到需要使用的公共接口。

#ifndef ClassName_h
#define ClassName_h

class ClassName
{
public:
    ClassName();
    ClassName(const ClassName& classname);
    virtual ~ClassName();

// Operators
    const ClassName& operator=(const ClassName& classname);

// Operations
    int do_work();
    int start_engine();
    int fire_nuke();       

// Access
    inline unsigned int get_users_count() const;       

// Inquiry               
    inline bool is_engine_started() const;

protected:
    //protected functions for descendant classes
      
private:
    //private data and functions
   
    unsigned int m_users_count;
    bool m_is_engine_started;

};

// Inlines
inline unsigned int SomeClass::get_users_count() const
{
    return m_users_count;
}

inline bool SomeClass::is_engine_started() const
{
    return m_is_engine_started;
}

#endif ClassName_h

把内联函数放在头文件中,并且要放在类定义外面,保持类定义的简洁。

正确使用const

正确使用const是指使用const关键字明确地声明并防止改变常量数据或对象。查找因const引起的bug是非常困难地,所以要在一开始就正确使用它。
你可以在数据或类成员函数的声明中使用const
const声明
const int max_array_size = 1024;
max_array_size++;    //编译错误

int a;
int & ra1 = &a;
const int& ra2 = &a;
a = 10;
ra1 = 15;    //正确
ra2 = 20;    //编译错误
// psour指向的数据不能被改变
int copy(char* pdest, const char* psour)
{
   //把psour所指的数据拷贝到pdest指向的内存中
}
把一个成员函数声明为const表示这是个“只读取”函数,也就是说调用这个函数不会修改它所属的对象。

class Foo
{
    //...
    void set(int x, int val);
    int get(int x) const;
    //...
};

const Foo* pobj1;        //禁止修改pobj1所指的对象
int i = pobj1->get();    //对于const指针所指的对象,你只能调用它的const函数
pobj1->set(0, 10);       //编译错误
不过你可以把一个成员变量声明为 mutable 这样就可以在const成员函数中修改它。


class SomeClass
{
public:
    void some_function() const;
private:
    mutable int a;
};

void SomeClass::some_function() const
{
    a++;    //你可以在const成员函数中修改a
}
-You can prevent a pointer once assigned to be reassigned to point to another place. This is similar in behaviour to a reference declaration.
你可以防止一个已经赋值的指针被重新赋值指向另外一个地方,类似于引用。

Foo foo1, foo2;
int data1[100];
int data2[100];

Foo* const pfoo;
int* const pdata;

pdata = data1;
pfoo = &foo1;

*pdata = 100;        //可以改变数据
pfoo->set(0, 10);    //可以修改对象

pdata = data2;       //编译错误
pfoo = &foo2;        //不可以对指针重新赋值

你也可以同时声明一个const对象和const指针:
//既不能修改pfoo指向的Foo对象,也不能把pfoo重新指向另外一个对象
const Foo* const pfoo;
const int* const pdata;
最后,如果你的类有提供()或[]操作符,则也提供它们的const版本,让"只读"对象也能调用。

class SomeClass
{
    ...
    inline int& operator()(int x);     
    inline int operator()(int x) const;
    inline int& operator[](int x);     
    inline int operator[](int x) const;
    ...
};
现在你可以使用它的普通版本和const版本了:

SomeClass a;
const SomeClass b;

int i = a(0);
int j = b(0);
a(1) = b(2);
b(3) = a(0);    //编译错误,b不能被改变

函数返回值

函数返回各种类型的返回值。常见的一种就是返回一个表示函数成功与否的值。这种返回值可以描述成整型的错误码(负数 = 失败, 0 = 成功) 或者是“是否成功的”布尔值(0 = 失败, 非零 = 成功)。

如果函数名是一个命令式词组,那么这个函数应该返回错误码,否则如果函数名是一个谓词,那么应该使用“是否成功的”布尔值。

int start_engine();           //0 = 成功, 负数 = 错误码
bool is_engine_started();     //true = 成功, false = 错误

杂项
把 & 和 * 跟类型放在一起,不要跟变量放在一起。这样让变量类型看起来更显眼一些。

//好的
int& rval;       //整形引用
float* pdata;    //浮点型指针

//不好的
int &rval;
float *pdata;
把非负变量定义成 unsigned 类型(例如:使用 unsigned int 表示数组长度而不是用 int 表示数组长度)。这样有助于避免意外地被赋值一个负数。

引用是一个类似于 int * const p 的指针,一旦赋值,就不能重新赋值。

int var1 = 100;
int var2 = 200;

int& ref = &var1;
ref = &var2;        //非法操作
这样声明一个 const 的Foo类型的指针的指针:
const Foo* const* ppfoo

总是在函数声明中提供形式参数:
void set_point(int, int);       //迷惑, 参数是什么?
void set_point(int x, int y);   //正确的声明
指针跟0的比较(译注:是指测试指针是否等于0,不是测试是否空指针,空指针应该用NULL):
void some_function(char* pname)
{
    //不好的
    if (!pname) {
            //...
    } 
    //有时会出错, 也许 NULL != 0
    if (pname == NULL) {
            //...
    }
    
    //好的
    if (pname == 0) {
            //...
    }
}
类对象做函数参数时,选用传指针或者引用。传值调用时会产生一个这个对象的拷贝,如果对象占用大量内存,这样会带来不良的后果。

//避免这样做, 应该传指针或者引用
BigObject modify_it(BigObject big_object)
{
    //会产生一个big_object的本地拷贝
    ...
    return big_object;
}

//良好的习惯
void modify_it(BigObject& big_object)
{
    //no copy is created, you can modify the object as usually
    //不会发生复制,你可以照常修改big_object
}

面向对象技巧

永远不要返回类的私有数据的指针或者非const引用。不要打破封装原则。

class SomeClass
{
public:
    SomeObject* get_object() { return &m_object; }
    //不要打破封装原则,这样m_object可能在类的外部被修改。

    const SomeObject& get_object() { return m_object; }
    //这样m_object就不会在外部被意外修改(译注:这种方式也不推荐,如果m_object是一个文件描述符,这样可能在类的外面被关闭)
private:
    SomeObject* m_object;
};
总是把公共类的析构函数声明为virtual。如果你从某个没有 virtual 析构函数的类继承了子类,然后你把指向子类的指针转换成指向父类的指针,这时对这个父类类型的指针调用delete则只会调用父类的析构函数:

class Base
{
    ...
    ~Base();    //这里应该声明为 virtual ~Base();
    ...
};

class Derived : Base
{
     ...
};

...
Derived* pderived = new Derived();
Base* pbase = (Base *)pderived;
...

delete pbase;    //这里只会调用 ~Base(),(译注:不会调用子类的析构函数,可能导致资源泄漏)
...
使用友员。如果你正在开发一个库,你不想你的库用户使用某个类,但是你的公共类又需要访问这个类的私有成员时,提供一个公共访问接口是多余的(译注:作者意思是既然这个类不给库用户使用,公共接口就没必要了,持保留意见)。把你的公共类声明为这个类的友员,这样你就可以访问他的私有数据了。

class MainClass
{
    ...
    void some_function();
    ...
    HelperClass* phelper_class;
    ...
};

void some_function()
{
    ...
    int i = phelper_class->m_variable;    //你可以在MainClass中访问HelperClass的私有成员了
    phelper_class->m_variable = 10;
    ...
}

class HelperClass
{
    friend class MainClass;
    ...
private:
    int m_variable;
    ...
};

原创粉丝点击