C/C++编程规范——头文件

在选择编程规范时,我首选google,其次是华为与微软,最后根据自身的一些实际情况进行调整。以下内容摘自google的c/c++编程规范。
——————————————————————————
通常,每一个.cc 文件(C++的源文件)都有一个对应的.h 文件(头文件),也有一些例外,如单元测试代码和只包含 main()的.cc 文件。
正确使用头文件可令代码在可读性、文件大小和性能上大为改观。下面的规则将引导你规避使用头文件时的各种麻烦。

1. #define 的保护
所有头文件都应该使用#define 防止头文件被多重包含(multiple inclusion),命名格式应当是:

<PROJECT>_<PATH>_<FILE>_H_

为保证唯一性,头文件的命名应基于其所在项目源代码树的全路径。例如,项目 foo 中的头文件 foo/src/bar/baz.h 按如下方式保护:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

2. 头文件依赖
使用前置声明(forward declarations)尽量减少.h 文件中#include 的数量。
当一个头文件被包含的同时也引入了一项新的依赖(dependency),只要该头文件被修改,代码就要重新编译。如果你的头文件包含了其他头文件,这些头文件的任何改变也将导致那些包含了你的头文件的代码重新编译。因此,我们宁可尽量少包含头文件,尤其是那些包含在其他头文件中的。
使用前置声明可以显著减少需要包含的头文件数量。举例说明:头文件中用到类 File,但不需要访问 File 的声明,则头文件中只需前置声明 class File;无需#include
"file/base/file.h"。
在头文件如何做到使用类 Foo 而无需访问类的定义?
1) 将数据成员类型声明为 Foo *或 Foo &;
2) 参数、返回值类型为 Foo 的函数只是声明(但不定义实现);
3) 静态数据成员的类型可以被声明为 Foo,因为静态数据成员的定义在类定义之外。另一方面,如果你的类是 Foo 的子类,或者含有类型为 Foo 的非静态数据成员,则必须为之包含头文件。
有时,使用指针成员(pointer members,如果是 scoped_ptr 更好)替代对象成员(object members)的确更有意义。然而,这样的做法会降低代码可读性及执行效率。如果仅仅为了少包含头文件,还是不要这样替代的好。
当然,.cc 文件无论如何都需要所使用类的定义部分,自然也就会包含若干头文件。
译者注:能依赖声明的就不要依赖定义。

3. 内联函数
只有当函数只有 10 行甚至更少时才会将其定义为内联函数(inline function)。
定义(Definition):当函数被声明为内联函数之后,编译器可能会将其内联展开,无需按通常的函数调用机制调用内联函数。
优点:当函数体比较小的时候,内联该函数可以令目标代码更加高效。对于存取函数(accessor、mutator)以及其他一些比较短的关键执行函数。
缺点:滥用内联将导致程序变慢,内联有可能是目标代码量或增或减,这取决于被内联的函数的大小。内联较短小的存取函数通常会减少代码量,但内联一个很大的函数(译者注:如果编译器允许的话)将戏剧性的增加代码量。在现代处理器上,由于更好的利用指令缓存(instruction cache),小巧的代码往往执行更快。
结论:一个比较得当的处理规则是,不要内联超过 10 行的函数。对于析构函数应慎重对待,析构函数往往比其表面看起来要长,因为有一些隐式成员和基类析构函数(如果有的话)被调用!
另一有用的处理规则:内联那些包含循环或 switch 语句的函数是得不偿失的,除非在大多数情况下,这些循环或 switch 语句从不执行。
重要的是,虚函数和递归函数即使被声明为内联的也不一定就是内联函数。通常,递归函数不应该被声明为内联的(译者注:递归调用堆栈的展开并不像循环那么简单,比如递归层数在编译时可能是未知的,大多数编译器都不支持内联递归函数)。析构函数内联的主要原因是其定义在类的定义中,为了方便抑或是对其行为给出文档。

4. -inl.h 文件
复杂的内联函数的定义,应放在后缀名为-inl.h 的头文件中。
在头文件中给出内联函数的定义,可令编译器将其在调用处内联展开。然而,实现代码应完全放到.cc 文件中,我们不希望.h 文件中出现太多实现代码,除非这样做在可读性和效率上有明显优势。
如果内联函数的定义比较短小、逻辑比较简单,其实现代码可以放在.h 文件中。例如,存取函数的实现理所当然都放在类定义中。出于实现和调用的方便,较复杂的内联函数也可以放到.h 文件中,如果你觉得这样会使头文件显得笨重,还可以将其分离到单独的-inl.h 中。
这样即把实现和类定义分离开来,当需要时包含实现所在的-inl.h 即可。
-inl.h 文件还可用于函数模板的定义,从而使得模板定义可读性增强。
要提醒的一点是,-inl.h 和其他头文件一样,也需要#define 保护。

5. 函数参数顺序(Function Parameter Ordering)
定义函数时,参数顺序为:输入参数在前,输出参数在后。
C/C++函数参数分为输入参数和输出参数两种,有时输入参数也会输出(译者注:值被修改时)。输入参数一般传值或常数引用(const references),输出参数或输入/输出参数为非常数指针(non-const pointers)。对参数排序时,将所有输入参数置于输出参数之前。不要仅仅因为是新添加的参数,就将其置于最后,而应该依然置于输出参数之前。
这一点并不是必须遵循的规则,输入/输出两用参数(通常是类/结构体变量)混在其中,会使得规则难以遵循。

6. 包含文件的名称及次序
将包含次序标准化可增强可读性、避免隐藏依赖(hidden dependencies,译者注:隐藏依赖主要是指包含的文件中编译时),次序如下:C 库 、C++库、其他库的.h、项目内的.h。
项目内头文件应按照项目源代码目录树结构排列,并且避免使用 UNIX 文件路径.(当前目录)和..(父目录)。例如,google-awesome-project/src/base/logging.h 应像这样
被包含:
#include "base/logging.h"
dir/foo.cc 的主要作用是执行或测试 dir2/foo2.h 的功能,foo.cc 中包含头文件的次序如
下:
dir2/foo2.h(优先位置,详情如下)
C 系统文件
C++系统文件
其他库头文件
本项目内头文件
这种排序方式可有效减少隐藏依赖,我们希望每一个头文件独立编译。最简单的实现方式是将其作为第一个.h 文件包含在对应的.cc 中。
dir/foo.cc 和 dir2/foo2.h 通常位于相同目录下(像 base/basictypes_unittest.cc 和base/basictypes.h),但也可在不同目录下。
相同目录下头文件按字母序是不错的选择。
举例来说,google-awesome-project/src/foo/internal/fooserver.cc 的包含次序如下:

#include "foo/public/fooserver.h" // 优先位置
#include <sys/types.h>
#include <unistd.h>
#include <hash_map>
#include <vector>
#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/public/bar.h"

————————————————————————————
我把博客当作笔记本用,里面的内容会随着实际情况而发生变化。

原文地址:http://blog.51cto.com/12441164/2063146

时间: 2024-10-02 20:35:46

C/C++编程规范——头文件的相关文章

Windows客户端C/C++编程规范“建议”——文件

7 文件 7.1 正确使用#include 等级:[推荐] 说明:#include <>和#include ""导致编译器在搜索文件时,搜索的路径顺序不同.所以需要正确使用#include,以避免包含错了头文件. 语法形式 操作 带引号的形式 预处理器按以下顺序搜索包含文件: 在包含 #include 语句的文件所在的同一目录中. 在当前打开的包含文件的目录中,采用与打开它们的顺序相反的顺序. 搜索从父包含文件的目录中开始进行,然后继续向上到任何祖父包含文件的目录. 跟随每

linux网络编程常用头文件

sys/types.h:数据类型定义 sys/socket.h:提供socket函数及数据结构 netinet/in.h:定义数据结构sockaddr_in arpa/inet.h:提供IP地址转换函数 netdb.h:提供设置及获取域名的函数 sys/ioctl.h:提供对I/O控制的函数 sys/poll.h:提供socket等待测试机制的函数 其他在网络程序中常见的头文件 unistd.h:提供通用的文件.目录.程序及进程操作的函数 errno.h:提供错误号errno的定义,用于错误处理

C++编程常用头文件及其包含函数汇总

1. #include<iostream>是标准的C++头文件,任何符合标准的C++开发环境都有这个头文件. 当使用<iostream.h>时,相当于在c中调用库函数,使用的是全局命名空间,也就是早期的c++实现:当使用<iostream>的时候,该头文件没有定义全局命名空间,必须使用namespace std:这样才能正确使用cout.   2.#include <cstdlib>是C++里面的一个常用函数库, 等价于C中的<stdlib.h>

单片机中用c编程时头文件reg51.h及reg52.h解析

我们在用c语言编程是往往第一行就是reg51.h或者其他的自定义头文件,我们怎么样来理解呢? 1)“文件包含”处理. 程序的第一行是一个“文件包含”处理. 所谓“文件包含”是指一个文件将另外一个文件的内容全部包含进来.程序中包含REG51.h 文件的目的是为了要使用P1 (还有其他更多的符号)这个符号,即通知C 编译器,程序中所写的P1 是指80C51 单片机的P1 端口而不是其它变量.这是如何做到的呢? 打开reg51.h 可以看到这样的一些内容: (此文件一般在C:/KEIL/C51/INC

Windows客户端C/C++编程规范“建议”——前言

前言 工作中接触了很多编程规范.其中最有意思的是,公司最近发布了一版C/C++编程规范,然后我看到该规范的最后一段时,有这么一句:"该规范不适用于Windows平台开发".看来这份规范是由做其他平台开发的同学制定的.那么做Windows开发的人都去哪儿了?后来由于工作需要,项目组需要我制定一份编程规范.这也是我这系列博客的由来.(转载请指明出于breaksoftware的csdn博客) 说到"规范"",可能没多少人喜欢这样的东西.相信很多工程师和我一样,都

WindowsclientC/C++编程规范“建议”——前言

前言 工作中接触了非常多编程规范.当中最有意思的是,公司近期公布了一版C/C++编程规范,然后我看到该规范的最后一段时,有这么一句:"该规范不适用于Windows平台开发".看来这份规范是由做其它平台开发的同学制定的.那么做Windows开发的人都去哪儿了?后来因为工作须要,项目组须要我制定一份编程规范.这也是我这系列博客的由来.(转载请指明出于breaksoftware的csdn博客) 说到"规范"",可能没多少人喜欢这种东西.相信非常多project师

google C++编程风格指南之头文件的包含顺序

google C++编程风格对头文件的包含顺序作出如下指示: (1)为了加强可读性和避免隐含依赖,应使用下面的顺序:C标准库.C++标准库.其它库的头文件.你自己工程的头文件.不过这里最先包含的是首选的头文件,即例如a.cpp文件中应该优先包含a.h.首选的头文件是为了减少隐藏依赖,同时确保头文件和实现文件是匹配的.具体的例子是:假如你有一个cc文件(linux平台的cpp文件后缀为cc)是google-awesome-project/src/foo/internal/fooserver.cc,

arpa/inet.h所引起的Segmentation fault及网络编程常见的头文件

最近在学习Linux网络编程方面的知识,感觉还是有些困难.主要是对协议过程的理解,还有socket的API的理解不够深刻.今天复习编写了一个TCP的服务端和客户端的程序实现client.c从命令行参数中获得一个字符串发给服务器,然后接收服务器返回的已处理的字符串并打印. server.c 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h>

stm32 io操作 头文件规范

在stm32众多项目开发中,有太多的对io进行操作,若置1或清0,使用官方库提供的函数,固然方便,规范,但是需要包含标准的库,尺寸较大,还得处理不同版本兼容问题,包括io初始化也太繁琐,于是操作原子等例程进行精简, 初始化如下,变得如此简单:适用于stm32f和stm32L void Init_Io(void){ JTAG_Set(SWD_ENABLE); //开启SWD RCC->APB2ENR|=1<<6;//先使能外设PORTE时钟 RCC->APB2ENR|=1<&l