先讲一个笑话:
同时学习两年 Java的程序员在一起讨论的是面向对象和设计模式,而同时学习两年 C++的程序员,在一起讨论的是 template和各种语言规范到底怎么回事情。
下面就从公开的资料中撸一撸如何写好一个c++类,从头文件(.h)需要包含的文件开始:
1、#define保护:
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_
2、包含头文件的顺序
如果dir/foo.cc的主要功能是实现和测试dir2/foo2.h中的内容,可以这样安排:
(1)dir2/foo2.h (2)C系统文件(3)C++系统文件(4)其他库头文件(5)本项目头文件
3、命名空间
在.cc文件中,通常鼓励匿名名称空间。当需要命名一个名称空间时,可以基于项目或者它的路径。不要使用using指令。
加上命名空间,有利于避免命名冲突。4、定义类名
类型名以大写字母开头,每个单词的首字母大写,中间没有下划线。所有类型——类、结构体、类型定义(typedef)和枚举——都符合统一的命名约定。
5、继承
组合通常比继承更合适。使用继承时,一般为公有继承。多重实现继承通常罕有其用,一般是禁止使用,Java和C#中是没有多重继承的。
6、接口
作为接口的类可以但不必以Interface后缀结束。
定义:
满足以下条件的类被称为纯接口:
l 只有公共的纯虚函数(“=0”)和静态方法(见下文,析构函数例外);
l 只能有静态数据成员;
l 没有构造函数定义。即使有构造函数,也仅是默认构造函数且被声明为protected;
l 如果是子类,只能继承自满足以上条件且以Interface后缀结束的类;
在《Effective c++》中又区分了接口继承和实现继承,接口继承就是纯接口。7、类格式
成员按public、protected和private的次序定义。
公共部分位于保护部分前,保护部分位于私有部分前,如果某个部分空,则忽略它。
在每个部分,请按照下面的次序来声明成员:
l 类型定义(Typedefs)和枚举(Enums);
l 常量(staticconst数据成员);
l 构造函数;
l 析构函数;
l 类方法,包括静态方法
l 数据成员(静态常量除外);
8、成员变量、成员函数命名规则
成员变量要与函数的局部变量分开标示,如可以在成员变量前加上m_或者_,变量的名称尽量把变量的类型包含进去。
成员变量一般用名词或者名词短语,成员函数一般用动词或者动词短语。
9、构造函数
通常认为构造函数仅仅完成成员变量的初始化。其他复杂的初始化工作则交给Init()函数。
如果类定义了成员变量且没有其他构造函数,应定义默认构造函数,这样可以确保新建对象的内部状态一致和有效。否则,编译器将会不安全地初始化类。
最好在每一个只有一个参数的构造函数前用 explicit 进行限制。在必要时才提供复制构造函数和赋值运算符。否则,使用DISALLOW_COPY_AND_ASSIGN来禁用它们。
不要在构造函数中调用virtual函数。
10、析构函数
在析构函数中释放资源。
不要让析构函数抛出异常,也不要在析构函数中调用virtual函数。
如果类被继承了,需要在析构函数前加上virtual。
11、运算符重载
只有在很罕见的情况下才会用到运算符重载。
12、成员变量都应该是private
隐藏类的数据,不要直接的将数据供别人调用,而应该用接口。
13、尽量使用前置声明
当一个前置声明足够时,不要使用#include。
如何在不访问其定义的情况下使用类Foo呢?
(1) 声明数据成员Foo* 或者Foo&;
(2) 声明以Foo为参数,和/或返回值的函数。(有一个例外:如果参数是Foo或者const Foo&,有一个隐式单参数构造函数,这种情况下我们需要引入完全定义来支持自动类型转换)。
(3) 声明静态的Foo数据成员,这是因为静态数据成员在类定义外定义。
14、多使用const
const可以施加于任何作用域的对象、函数参数、函数返回类型、成员函数本体。
15、成员函数
(1)函数的形参从左至右,一般是先是输入参数,最后是输出参数
(2)输入参数使用const + & 的方式