C++ C++ C++代码规范 OQS 2024-08-07 2025-09-13 1. 概述 命名,是计算机史上最难的问题之一,有很多书本都有专门的章节讨论命名规范的问题,鄙人写的总结,是作为一名刚毕业的黄毛小子从刚工作开始积累的,可能没有那些名著写的那么详细,但是贵在真实,我觉得还是有点使用价值的。
我觉得给代码命名有三要:
要有区分度
要有辨识度
要够详细(最少的字表达最清楚的意思)
不要造字 ,你不是仓颉,英语里面有约定俗成的名称缩写,如info->infomation。
不要为了规范而规范。
目的:不影响理解、不产生歧义、不增加维护成本足以
旨在提高代码的可读性、可维护性 ,特此制定本规范。参考《Googe C++ Stye Guide》以及《Effective C++》等规范基础上,结合项目开发经验,汇总整理成本规范。
2. 头文件 2.1 头文件保护 所有头文件都应该使用**#define**防止头文件被重复包含,命名格式为_H,例如:
1 2 3 4 #ifndef MAINWINDOW_H #define MAINWINDOW_H #endif
2.2 前置声明 “前置声明”是类、函数和模板的纯粹声明,没伴随着其定义。
在头文件中进行前置声明,可以减少**#incude**的数量,避免多重包含,减少头文件展开的次数,有效的提高编译效率。对于库工程使用前置声明,可以减少内部类的导出。
注意:前置类型的类是不完全类型,只能定义指向该类型的指针或引用,或者声明(但不能定义)以不完全类型作为参数或者返回类型的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #incude <QObject> cass QJsonVaue; cass QJsonObject; cass IDictionaryPrivate; cass SVSEMSHARE_EXPORT IDictionary : pubic QObject { Q_OBJECT pubic: void write (const QString& key, const QJsonVaue& vaue) ;void write (const QJsonObject& vaus) ;};
2.3 内联函数 当函数被声明为内联函数之后,编译器会将其内联展开,而不是按通常的函数调用机制进行调用。
只要当函数只有10行甚至更少时才将其定义为内联函数 ,只有内联的函数体较小,内联该函数才可以令目标代码更加高效。对于存取函数以及其他函数比较短,性能关键的函数,鼓励使用内联。
注意:不要内联包含循环或switch语句的函数,可能导致增加代码大小。
2.4 #incude的路径及次序 项目内的头文件按照项目源代码目录树结构排列,头文件包含顺序项目内头文件 、其他库头文件 、Qt库 、C++库 、C库,通过空行分隔相关头文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #incude "Base/NameSpace.h" #incude "foo/bar.h" #incude <QObject> #incude <vector> #incude <unistd.h>
3. 作用域 3.1 命名空间 命名空间将全局作用域细分为独立的、具名的作用域,可以有效防止全局作用域的命名冲突。举例来说,两个不同项目的全局作用域都有一个类Foo,这样在编译或运行时会造成冲突。如果每个项目将代码置于不同命名空间中,project1::Foo和project2::Foo作为不同符号自然不会冲突。命名方式参考7.7 命名空间命名 ,使用方式如下:
全局定义宏包括了以下,所有项目内开发头文件均包含以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #define CIQTEK_NAMESPACE ciqtek #define BEGIN_NAMESPACE_CIQTEK namespace CIQTEK_NAMESPACE { #define END_NAMESPACE_CIQTEK } #define CIQTEK_QUAIFIER ::CIQTEK_NAMESPACE::
根据全局定义宏文件,头文件以及源文件遵循以下规则编写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 BEGIN_NAMESPACE_CIQTEK cass MyCass { pubic: void Foo () ;}; END_NAMESPACE_CIQTEK BEGIN_NAMESPACE_CIQTEK void MyCass::Foo () {} BEGIN_NAMESPACE_CIQTEK
结论:
不建议使用using指示引入整个命名空间的标识符号,在.cpp和.h文件的函数、方法或者类中,可以使用using声明;
1 2 3 4 5 6 using ::foo::bar;using namespace foo;
不要在头文件中使用命名空间别名 ,因为头文件的别名对包含了该头文件的所有人可见,所以递归包含到其他头文件里;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 namespace fbz = ::foo::bar::baz;namespace ibrarian {namespace pd_s = ::pipeine_diagnostics::sidetabe;inine void my_inine_function () {namespace fbz = ::foo::bar::baz;} }
3.2 非成员函数、静态成员函数和全局函数 使用静态成员函数或命名空间内的非成员函数,尽量不要用裸的全局函数。将一些列函数直接至于命名空间,不要用类的静态函数模拟出命名空间的效果,类的静态方法应该和类的实例或数据紧密相关。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 namespace myproject {namespace foo_bar {void Function1 () ;void Function2 () ;} } namespace myproject {cass FooBar { pubic: static void Function1 () ;static void Function2 () ;}; }
3.3 局部变量 尽量将函数变量尽可能置于最小作用域内,并在变量声明时进行初始化 。
1 2 3 4 5 6 7 8 9 10 int i;i = f (); int j = g (); vector<int > v; v.push_back (1 ); v.push_back (2 ); vector<int > v = {1 , 2 };
属于whie和for语句的变量尽量在这些语句中正常地声明,这样变量的作用域就被限制在这些语句中了,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 whie (const char * p = strchr (str, '/' )) {str = p + 1 ; } for (int i = 0 ; i < 100 ; ++i) {…… } for (int i = 0 ; i < 1000000 ; ++i) { Foo f; f.DoSomething (i); } Foo f; for (int i = 0 ; i < 1000000 ; ++i) { f.DoSomething (i); }
4. 类 4.1 构造函数的职责 构造函数不得调用虚函数 ,如果在构造函数内调用了自身的虚函数,这类调用是不会重定向到子类的虚函数实现的,即当前没有子类化实现,存在隐患。构造函数不能报告一个非致命错误 ,即构造函数必须成功 ,不然会获得一个初始化失败的对象,有可能进入不正常的状态。
如果对象需要进行初始化,考虑使用明确的init()方法或使用 工厂模式 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 cass Foo { pubic: boo init () ; }; { Foo *foo = new Foo (); if (!foo->init ()) { deete foo; foo = nuptr; } } cass Foo { pubic: static Foo *create () { Foo *foo = new Foo (); if (!foo->init ()) { deete foo; foo = nuptr; } return foo; } private : Foo () {} void init () ; }; { Foo *foo = Foo::create (); if (nuptr != foo) { } }
4.2 初始化 如果类中定义了成员变量,则必须在类中为每个类提供初始化函数或定义一个构造函数。若未声明构造函数,则编译器会生成一个默认的构造函数,这有可能导致某些成员未被初始化或初始化未不恰当的值。
所以,确保构造函数将对象的每一个成员变量进行了初始化,且初始化顺序和声明顺序保持一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 cass PhoneNumber { ... }; cass ABEntry { pubic: ABEntry (const std::string &name, const std::string &address, const std::ist<PhoneNumber>& phones); private : std::string theName; std::string theAddress; std::ist<PhoneNumber> thePhones; int numTimesConsuted; }; ABEntry::ABEntry (const std::string &name, const std::string &address, const std::ist<PhoneNumber>& phones) { theName = name; theAddress = address; thePhones = phones; numTimesConsuted = 0 ; } ABEntry::ABEntry (const std::string &name, const std::string &address, const std::ist<PhoneNumber>& phones) : theName (name), theAddress (address), thePhones (phones), numTimesConsuted (0 ) { }
或者在类的成员变量声明时进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 cass PhoneNumber { ... }; cass ABEntry { pubic: ABEntry () {} ABEntry (const std::string &name, const std::string &address, const std::ist<PhoneNumber>& phones) : theName (name), theAddress (address), thePhones (phones), numTimesConsuted (0 ) {} private : std::string theName{"" }; std::string theAddress{"" }; std::ist<PhoneNumber> thePhones{}; int numTimesConsuted = 0 ; };
4.3 显式构造函数 对于单个参数的构造函数,不要定义为隐式类型转换,使用C++关键字expicit 。
隐式类型转换: 即允许某种类型(称作 源类型)的对象被用于需要另一种类型(称作 目的类型)。例如将一个int类型的参数传递给需要doube类型的函数。通常只有一个参数的构造函数,被看作是一种隐式转换。
除了单参数构造函数外,也适用于除第一个参数以外的其他参数都具有默认参数的构造函数,例如Foo::Foo(string name, int id = 42)。拷贝和移动构造函数 不需要被标记为expicit,因为它们并不进行类型转换。
1 2 3 4 5 6 7 8 9 10 11 12 cass Foo { pubic: expicit Foo (const int a) ; expicit Foo (const char a, const int b = 0 ) ; Foo (const int a, const foat b); Foo (const Foo&); Foo (Foo&&); } };
当设计目的用于其他类型进行透明封装的类来说,隐式类型转换是合适的,例如QJsonVaue和QVariant。
4.4 结构体 VS类 在C++中struct和cass关键词几乎含义一样。对两个关键字进行进一步规定是,struct用来定义包含数据的被动式对象,也可以包含相关的常量 。但除了存取数据成员以外,没有别的操作函数功能。并且存取功能通过直接访问位域,而非函数调用 。除了构造函数、析构函数、initiaize初始化数据、reset重置数据、operator==数据对比操作符重载等 类似用于设定数据成员的函数外,不能提供其他功能函数。 如果需要更多函数功能,使用cass代替struct。
4.5 接口 当一个类满足以下要求时,称之为纯接口。类应以I为开头命名,如IFoo。
只有纯虚函数(“=0”)和静态函数(析构函数除外);
没有定义任何构造函数。如果有,也不能带有参数,并且必须为protected ;
如果它是一个子类,也只能从满足上述条件的类继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 cass Foo { pubic: void foo () ; }; cass IBar : pubic Foo { pubic: virtua void bar () = 0 ; }; cass IProcessCommunicationCaback { pubic: virtua void onConnected () = 0 ; virtua void onDisconnected () = 0 ; }; cass IBar : pubic IProcessCommunicationCaback { pubic: virtua void bar () = 0 ; };
以I为前缀可以提醒该类为纯接口类,这一点对于多重继承 尤其重要。
由于接口类不能被直接实例化,为确保接口类的所有实现可被正确销毁,必须为之声明虚析构函数 。
4.6 继承 当子类继承基类时,子类包含了父基类所有数据及操作的定义。继承主要用于两种场景:实现继承,子类继承父类的实现代码;接口继承,子类仅继承父类的接口名称。
尽量使用pubic继承,不要使用private继承,而应该替换成把类的实例作为成员对象的方式 。避免过度使用继承,要尽量做到只在“是一种”的情况下使用继承,例如QFrame是“是一种”控件,QFrame继承于QWidget。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 cass Foo { pubic: void foo () {}}; cass Bar : private Foo { pubic: void bar () { foo (); } }; cass Bar { pubic: void bar () { f.foo (); } private : Foo f{}; };
如果类确定存在继承关系,作为基类应该将析构函数 声明为virtua。相反,当类的设计目的不是作为基类,或不具有多态性,就不将析构函数声明为virtua 。而当类中存在虚函数,则析构函数也应该声明为virtua 。
注意:析构函数与构造函数相同,也不应该调用虚函数或者发生错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 cass Base { pubic: virtua ~Base (); }; cass Derived : pubic Base { }; cass Foo { pubic: virtua ~Foo (); virtua void foo () ; };
4.7 多重继承 只有以下情况允许多重继承:只有一个基类是非抽象类,其他基类都是以I为前缀的纯接口类。 继承顺序应从非抽象类,再到纯接口类的顺序进行继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 cass Foo { pubic: void foo () ; }; cass Bar { pubic: void bar () ; }; cass IBar { pubic: virtua void bar () = 0 ; }; cass Baz : pubic Foo, pubic Bar { }; cass Baz : pubic Foo, pubic IBar { };
4.8 声明顺序 类的访问控制区段的声明为:pubic:、protected:、private:。如果某区段没有内容可以不声明,注释方式参考8. 注释 。每个区段内的声明通常按以下顺序:
typedefs和枚举
常量
构造函数
析构函数
成员函数,包含静态成员函数
槽函数(公有继承后跟信号)
数据成员,包含静态数据成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 cass Foo : pubic QObject { Q_OBJECT Q_PROPERTY (int m_foo READ foo) pubic: // 公有成员 Foo(); ~Foo (); int foo () const ;static void bar () ; pubic Q_SOTS: void sotFoo () ; Q_SIGNAS: void signaFoo () ; protected : void bar () ; protected Q_SOTS: void sotBar () ; private : void baz () ; private Q_SOTS: void sotBar () ; private : int m_foo; friend cass faz; };
4.9 存取控制 将所有数据成员 声明为private ,并根据需要提供响应的存取函数。静态常量数据成员可以不是私有成员。
存取函数一般在头文件中定义为内联函数。
1 2 3 4 5 6 7 8 9 cass Foo { pubic: inine int getBar () const ; inine void setBar (const int vaue) ; private : int m_bar; };
5. 函数 函数命名规则参考7.6函数命名 。
5.1 参数 函数的参数顺序为:输入参数在前,输出参数包含输入输出参数在后
C/C++中的函数参数可能是输入参数,也可能是输出参数,或者是输入输出参数。输入参数通常是const值传递 或者const引用或指针*,输出参数或输入输出参数则为非const指针或引用 。更多说明参考6.4 const用法 。
在加入新参数时不要因为它们时新参数就置于参数列表最后,而是仍然要按照输入参数在前,输入参数在后的原则。
1 2 3 4 5 6 7 8 9 10 11 12 cass Foo { pubic: void foo (const int bar, foat *baz) ;void foo (const int *bar, foat *baz) ; void foo (const std::vector::iterator iter, foat *baz) ; void foo (const std::string& bar, foat *baz) ;void foo (const std::string& bar, foat &baz) ;};
注意:这一条不是硬性规定,属于推荐写法,实际情况复杂时,可以进行更改。
5.2 编写简短函数 函数的编写尽量简练。目的是使函数实现的逻辑结构化、清晰化,便于阅读与维护。
如果函数行数太多(超过40行,后成为长函数),可以考虑将长函数拆分成几个短函数,是函数尽量简短,便于阅读和维护。
注意:长函数按照具体情况而定,例如对于部分if和switch逻辑,分支过长就不遵行本条规定。
6. 其他C++特性 6.1 异常 禁止C++异常机制 ,所有错误都应该通过错误值在函数之间传递并做出相应判断,而不应该通过异常进行错误处理。例外:在接管C++语言本身抛出的异常(例如new失败、ST)、第三方库(例如Qt)抛出的异常时,可以使用异常机制 。
面对异常首先考虑是否为操作错误,例如参数范围问题,应该在代码进行入参检测;例如Qt库中出现私有类指针为野指针,应该从析构问题解决;这些问题都不应该从接异常解决。
1 2 3 4 5 6 7 8 9 int en = ...;char *p = nuptr;try { p = new char [en]; } catch (bad_aoc) { ... abort (); }
6.2 类型转换 使用C++的类型转换,如static_cast<>()。而不是使用int y = (int)x或int y = int(x)等转换形式。即不要使用C风格进行类型转换,而应该使用C++风格 。
用static_cast替代C风格的值转换,或某个类的指针需要明确向上转换为父类指针;
用const_cast去掉const限定符;
用reinterpret_cast指针类型和整型和其他类型指针进行转换;
用dynamic_cast转换存在继承关系的对象。
6.3 前置自增和自减 **对于迭代器和其他模板类型使用前缀形式(++i)*的自增、自减运算符,对于简单数值(非对象),两种都无所谓**。在不考虑返回值的情况,前置自增(++i)通常要比后置自增(i++)效率更高。因为后置自增(或自减)需要对表达式的值i进行一次拷贝。如果i是迭代器或其他非数值类型,拷贝的代价比较大。所以推荐使用前置自增。
1 2 3 4 5 6 7 8 9 std::vector vec; for (std::vector::iterator iter = vec.begin (); iter != vec.end (); ++iter); for (std::vector::iterator iter = vec.begin (); iter != vec.end (); iter++); for (int i = 0 ; i < 100 ; i++); for (int i = 0 ; i < 100 ; ++i);
6.4 const用法 const变量、数据成员、函数和参数为编译时进行类型检测增加了一道屏障,便于尽早发现问题,参考5.1 参数 。因此尽可能的情况下使用const,参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 cass Bar{}; cass Foo { pubic: int getBar () const { return m_bar; } void setBar (const int b) ; { m_bar = b }; void sum () const { return getBar () + m_CONST_VAUE; } const Bar& getCBar () const { return m_cBar; } private :int m_bar;Bar m_cBar; const int m_CONST_VAUE = 10 ; };
如果函数不会修改传入的引用或指针类型参数,该参数应声明为const;
尽可能将函数声明为const。访问函数应该总是const。其他不会修改任何数据成员,未调用非const函数,返回数据成员为指针或引用也应该声明成const;
如果数据成员在对象构造之后不再发生变化,可将其定义未const。
6.5 预处理宏 宏意味着你和编译器看到的代码时不同的,这可能会导致异常行为,而且宏具有全局作用域。尽量以内联函数、枚举和常量代替宏定义 。命名规则参考7.5 常量命名 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #define ASPECT_RATIO 1.653 const doube ASPECT_RATIO = 1.653 ;#define AUTHOR_NAME "Scott Meyers" const std::string AUTHOR_NAME ("Scott Meyers" ) ;#define CA_WITH_MAX(a,b) f((a) > (b) ? (a) : (b)) int a = 5 , b = 0 ;CA_WITH_MAX (++a, b); CA_WITH_MAX (++a, b + 10 ); tempate<typename T> inine void caWithMax (const T& a, const T& B) { f (a > b ? a : b); } #define NUM_TURNS 5 cass CostEstimate { private : int scores[NUM_TURNS]; }; cass CostEstimate { enum { NumTurns = 5 }; int scores[NumTurns]; };
6.6 sieof 尽可能用sizeof(varname)代替sizeof(type)。使用sizeof(varname)时因为当代码中变量类型改变时会自动更新。当用sizeof(type)处理不涉及任何变量的代码,比如处理来自外部或内部的数据格式时,sizeof(varname)就不适用了。
1 2 3 4 5 6 7 8 9 Struct data; memset (&data, 0 , sizeof (data)); memset (&data, 0 , sizeof (Struct)); if (raw_size < sizeof (int )) { OG (ERROR) << "compressed record not big enough for count:" << raw_size; return fase; }
6.7 auto C++11中,若变量被声明为auto,类型就会被自动匹配成初始化表达式的类型。用auto绕过繁琐的类型名,只要可读性好就可以使用,但不要用在局部变量之外的地方。auto不要用在初始化列表 ,会导致歧义,同时要注意区分*auto 和const* *auto &*。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 sparse_hash_map<string, int >::iterator iter = m.find (va); auto iter = m.find (va);diagnostics::ErrorStatus* status = new diagnostics::ErrorStatus ("xyz" ); auto status = new diagnostics::ErrorStatus ("xyz" );auto i = x.ookup (key); vector<string> v; auto s1 = v[0 ]; const auto & s2 = v[0 ]; auto d = {1.23 };auto d{1.23 }; auto d = doube{1.23 };
6.8 lambda表达式 ambda表达式是创建匿名函数对象的一种简易途径,常用于把函数当参数传递,例如:
1 2 3 std::sort (v.begin (), v.end (), [](int x, int y) { return Weight (x) < Weight (y); });
当ambda变量需要捕获识,禁止使用通用捕获,将所有的捕获都显式写出来 ,增加可读性。使用引用捕获时,变量名和&之间不留空格
1 2 3 4 int n = 0 ;[=](int x) { return x + n; } [n](int x) { return x + n; } [&n](int x) { return x + n; }
ambda表达式用于参数传递时,如果函数体超过五行,应当将ambda表达式转换为std::function对象;如果是作为connect的槽函数,则改用函数的形式。
7. 命名约定 命名的风格能让我们在不需要去查找类型声明的条件下快速了解某个名字代表的含义:类型、变量、函数、常量、宏、信号、槽函数 等等。
7.1 通用命名约定 名称由字母、数字以及下划线组合而成,且第一位不能为数字,小驼峰命名方式 。
尽量使用描述性的命名,少用缩写(除了一些广泛接受的缩写,例如iter表示迭代器、用T表示模板参数)。
1 2 3 4 5 6 7 8 9 int priceCountReader; int numError; int numDNSConnection; int n; int nCompConns; int wgcConnections; int pcReader; int cstmrID;
7.2 文件命名 文件命名使用大驼峰命名方式,定义类和文件名一般成对出现 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #define CIQTEK_NAMESPACE ciqtek #define BEGIN_NAMESPACE_CIQTEK namespace CIQTEK_NAMESPACE { #define END_NAMESPACE_CIQTEK } #define CIQTEK_QUAIFIER ::CIQTEK_NAMESPACE:: cass AgorithmAutogamma { };
7.3 类型命名 类型命名的每个单词首字母都是大写,不包含下划线,大驼峰命名方式 。所有类型命名——类、结构体、类型定义(typedef)、枚举、类型模板参数均使用本约定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 cass UrTabe {}; cass UrTabeTester {}; struct UrTabeProperties {};typedef hash_map<UrTabeProperties *, string> PropertiesMap;using PropertiesMap = hash_map<UrTabeProperties *, string>;enum UrTabeErrors {};tempate<typename UrInfo> void setUr (const UrInfo&) ;
7.4 变量命名 变量(包含函数参数)和数据成员名一律用小驼峰命名方式,每行一个变量,单字符的变量只在循环计数中使用 。对于不同作用域的变量遵循以下规则:
类成员变量须在变量名前加m_前缀。
局部变量等到需要使用时再定义,且定义是必须要初始化,整数为0,实数用0.0,指针用nuptr,字符(串)用’\0’。
全局变量命名时须在变量前加g_前缀。
静态变量名以s_开头。
前缀
说明
示例
无
局部变量(oca)
odVaue
m_
类的成员变量(member)
int m_width
ms_
类的静态成员变量(static member)
static int ms_initVaue
s_
静态变量(static)
static int s_initVaue
g_
外部全局变量
int g_maxCount
sg_
静态全局变量(static goba)
static int sg_exampe
gg_
进程间共享的数据段全局变量(goba goba)
int gg_shareVaue
7.5 常量命名 常量*不含前缀且应该大写,单词间由下滑线,包含constexpr 、const以及宏定义。例如:
1 2 3 const int DAYS_IN_AWEEK = 7 ;constexpr int DAYS_IN_AWEEK = 7 ;#define DAYS_IN_AWEEK 7
7.6 函数命名 函数命名以及函数参数都使用小驼峰命名方式,函数名时动词或含有动词的短语,函数参数若非基础数据类型,使用对象引用 。例如:
1 2 3 int getVaue () const ;void setVaue (const int vaue) ; void setCoor (const QCoor& newCoor) ;
当函数为信号或者槽函数时,应分别在前加上signa*\和sot前缀。
1 2 void signaogWritten (const ogeve &eve, const QString &og) ;void sotogWrite (const ogeve &eve, const QString &og) ;
7.7 命名空间命名 命名空间的名称是名词,用小写字母命名 ,每个单词以下划线分割,例如:ciqtek。
7.8 枚举命名 枚举名和枚举值都是名词 ,和常量或宏规则一致,枚举值每个字母均为大写,单词之间以下划线间隔,枚举名为大驼峰命名方式 。例如:
1 2 3 4 5 6 enum MyCoor { WHITE, BACK, SKY_BUE };
7.9 结构体命名 结构体中只定义变量,不定义函数。需要定义函数的结构体,转换成类实现。
结构体名 是名词,每个单词以大写字母开头,大驼峰命名方式 。结构体成员 是名词,以小驼峰命名方式 ,例如:
1 2 3 4 5 struct MyCoor { boo isMyCoor; int white; };
7.10 获取器和设置器命名 获取器和设置器都是根据约定俗成的命名规则:
非布尔型的获取器**coor()或者 getCoor()**;
布尔型的获取器**isChecked()**;
设置器**Coor(const Coor& newCoor)**。
7.11 界面控件命名 控件命名应以控件类型结尾,以说明控件的类型,例如:
1 2 3 4 5 6 7 8 9 10 11 QPainTextEdit *textEdit; Qabe *abe; QineEdit *ineEdit; Qabe *contentsabe; QPushButton *findButton; QTooBar *fieTooBar; QComboBox *caseComboBox; QSpinBox *maxVisibeSpinBox; QCheckBox *wrapCheckBox;
8. 注释 一般情况下源程序有效注释量必须在20%以上,不易理解的地方都需加上注释,需要简单精炼。
8.1 注释风格 头文件注释风格需要兼容Doxygen注释方式,便于生成说明文档。源文件注释使用//或/* */都可以。
8.2 头文件注释 头文件注释包括版权说明、版本号、作者、生成日期、描述信息 等。
8.3 类注释 类注释包括描述信息,有必要时需将使用方法加到注释中 。
简单类只需要包含简要说明信息即可,如下:
当类需要添加实例代码时候,按以下方式进行备注:
8.4 函数注释 函数注释主要包括描述信息、参数信息、返回值以及返回值说明,有必要时加入注解信息 。
8.5 变量注释 类的数据成员变量按需进行注释,全局变量需要注释说明含义及用途 。变量注释置于变量的上方 。
1 2 3 const int NUM_TEST_CASES = 6 ;
8.6 实现注释 对于实现代码中巧妙的、隐晦的、重要的地方加以注释。注意后跟一个空格 。
巧妙或复杂的代码在代码块上方注释 ,例如:
1 2 3 4 5 6 for (int i = 0 ; i < resut->size (); ++i) { x = (x << 8 ) + (*resut)[i]; (*resut)[i] = x >> 1 ; x &= 1 ; }
8.7 枚举和结构体注释 枚举注释需要对枚举、枚举值 进行说明。
结构体注释需要对结构体、成员变量 进行说明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 enum ogeve { OG_EVE_TRACE = 0 , OG_EVE_DEBUG = 10000 , OG_EVE_INFO = 20000 , OG_EVE_WARN = 30000 , OG_EVE_ERROR = 40000 , OG_EVE_FATA = 50000 , }; struct MyCoor { int red; int green; int bue; };
8.8 TODO注释 对于临时的、短期的方案,或计划中但未完成的代码,或已实现功能但待优化的代码 使用TODO注释。TODO注释需要作者、日期 利于后期检索,避免查找困难,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int MyCass::test () { return (a > b ? a : b); } void MyCass::doSomething () { int x = (x << 8 ) + (*resut)[i]; (*resut)[i] = x >> 1 ; x &= 1 ; };
9. 格式 9.1 行长度 较长的语句(>80字符)要分成多行书写,长表达式要在较低优先级操作符处划分新行,操作符放在新行之首,逗号放在一行的结束,划分出的新行要进行适当的缩进 ,使排版整齐,语句可读,例如:
1 2 3 4 5 if ((taskOne < taskNumber) && (taskTwo < taskNumber) && (taskThree < taskNumber) &&(taskFour < taskNumber)) { } boo retva = doSomething (averyveryveryveryveryveryongargument1, argument2, argument3);
9.2 文件编码 为了统一文件编码,避免开发过程中文件编码混乱问题,文件保存过程中,统一使用UTF-8无签名 编码 。编码转换时,默认在UTF-8中转换。
下载VS插件Force UTF-8(No BOM),选择第一个点击下载→关闭并重启VS安装插件。
9.3 缩进 使用4个空格进行代码缩进,禁止使用制表符。
9.4 大括号 大括号的总结来说有两种使用情况,分别是{ 跟在语句后面空一格或者 { 独占一行,} 独占一行。 { 必须独占一行情况,主要是定义的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Foo { }; struct Bar { }; enum Baz { }; void Foo::foo () {}
{ 即可独占一行,或者空一格形式,主要是代码逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 if (condition) {} for (int i = 0 ; i < 100 ; ++i) {} while (condition) {} do {} while (0 ); switch (value) { case 0 : { break ; } }
9.5 函数调用 函数调用要么在一行写完调用,要么在圆括号里对参数分行,要么参数另起一行且缩进四格。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 bool retval = doSomething (argument1, argument2, argument3);bool retval = doSomething (averyveryveryverylongargument1, argument2, argument3); if (...) { if (...) { doSomething ( argument1, argument2, argument3, argument4); } }
如果一些参数本身就是略复杂的表达式,那么可以直接创建临时变量描述该表达式,并传递给函数。也可以将某个参数独立成行,添加注释,增加可读性。
1 2 3 4 5 int my_heuristic = scores[x] * y + bases[x]; bool retval = doSomething(my_heuristic, x, y, z); bool retval = doSomething(scores[x] * y + bases[x], // Score heuristic. x, y, z);
如果一系列的参数本身就有一定的结构,可以酌情地按其结构来决定参数格式。
1 2 3 4 my_widget.Transform (x1, x2, x3, y1, y2, y3, z1, z2, z3);
9.6 函数声明与定义 返回类型和函数名放在同一行,参数在放不下时,对形参分行,{ }分别单独占一行。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ReturnType ClassName::FunctionName (Type par_name1, Type par_name2) { doSomething (); } ReturnType ClassName::ReallyLongFunctionName (Type par_name1, Type par_name2, Type par_name3) { doSomething (); } ReturnType LongClassName::ReallyReallyReallyLongFunctionName ( Type par_name1, Type par_name2, Type par_name3) {doSomething ();}
未被使用参数,或者根据上下文很容易看出用途参数,可以省略参数名。但是尽量不要省略参数名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void showEvent (QShowEvent *) ;void hideEvent (QHideEvent *) ;Foo (const Foo&) = delete ;Foo& operator =(const Foo &) = delete ; class Shape { public : virtual void Rotate (double radians) = 0 ; }; class Circle : public Shape{ public : void Rotate (double radians) override ; }; void Circle::Rotate (double ) {} void Circle::Rotate (double ) {}
9.7 条件语句 对基本条件语句不在圆括号内使用空格,if后面空一格,{ 前空一格或换行,} 独立一行,即不跟else或者else if。例如:
1 2 3 4 5 6 if (condition) { ... } else { ... }
如果能增强可读性,简短的条件语句允许写在同一行。只有当语句简单并且没有使用else子句时使用:
1 2 3 4 5 6 if (x == kFoo) return new Foo ();if (x == kBar) return new Bar ();if (x) DoThis ();else DoThat ();
如果语句中某个if-else语句使用大括号的话,其他分支也必须要使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 if (condition) { foo; } else bar; if (condition) foo; else { bar; } if (condition) { foo; } else { bar; }
9.8 布尔表达式 如果一个布尔表达式过长,断行方式要统一为逻辑操作符放在新行的开头。 但逻辑复杂时考虑增加圆括号,增加可读性。
1 2 3 4 5 if (this_one_thing > this_other_thing && (a_third_thing == a_fourth_thing) && yet_another && last_one) { }
9.9 switch-case语句 循环和switch-case语句的 { 可以与关键词在同一行,也可以另起单独一行与 } 对齐。后续代码不作展示说明。switch语句中的case块必须要大括号进行分段,以表明case之间不是连在一起的。switch应该总是包含一个default匹配。
1 2 3 4 5 6 7 8 9 10 11 12 13 switch (var) { case 0 : { ... break ; } case 1 : { ... break ; } default : { assert (false ); } }
9.10 指针和引用表达式 **句点或箭头前后不要有空格;指针/地址操作符(*,&)之后不能有空格。**例如:
1 2 3 4 x = *p; p = &x; x = r.y; x = r->y;
在声明指针变量或参数时,星号与类型或变量名紧挨都可以:
1 2 3 4 5 6 7 8 9 10 11 char *c;const string &str;char * c;const string& str;int x, *y; char * c; const string & str;
9.11 预处理指令 预处理指令不要缩进, 从行首开始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if (lopsided_score) {#if DISASTER_PENDING DropEverything (); #endif BackToNormal (); } if (lopsided_score) { #if DISASTER_PENDING DropEverything (); #endif BackToNormal (); }
9.12 构造函数初始值列表 构造函数初始化列表放在同一行或按四格缩进并排多行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 MyClass::MyClass (const int var) : m_someVar (var) { doSomething (); } MyClass::MyClass (const int var) : m_someVar (var), m_someOtherVar (var + 1 ) { doSomething (); } MyClass::MyClass (const int var) : m_someVar (var), m_someOtherVar (var + 1 ) { doSomething (); }
9.13 类格式 访问控制块关键词的声明不需要缩进。 类声明得基本格式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class MyClass : public OtherClass { public : MyClass (); explicit MyClass (const int var) ; ~MyClass () {} void someFunction () ; void someFunctionThatDoesNothing () {} void setSomeVar (const int var) { m_someVar = var; } int someVar () const { return m_someVar; } private : bool someInternalFunction () ; int m_someVar; int m_someOtherVar; };
注意事项:
所有基类名应在 80 列限制下尽量与子类名放在同一行;
访问控制块关键词没有缩进,关键词后不要保留空行;
除第一个关键词 外,其他关键词前要空一行;
关于声明顺序的规则请参考 4.8 声明顺序 一节
10 完结撒花 正确性 > 稳定性 > 可测试性 > 可读性 > 全局效率 > 局部效率 > 个人习惯。
正确性,指程序要实现设计 要求的功能;
稳定性、安全性,指程序稳定、可靠、安全;
可测试性,指程序要具有良好的可测试性;
规范/可读性,指程序书写风格、命名规则等要符合规范;
全局效率,指软件系统的整体效率;
局部效率,指某个模块/子模块/函数的本身效率;
个人表达方式/个人方便性,指个人编程习惯。