讲师中心 微信公众号
AI工具推荐 视频效率加速

正确的头文件礼仪

阿杰酱_9233

阿杰酱_9233

发布时间:2024-09-01 09:03:03

|

457人浏览过

|

来源于dev.to

转载

正确的头文件礼仪

介绍

任何使用 c 或 c++ 编程的人都知道,组成 api 的常量、宏、类型、结构(或类)和函数声明被放入 头文件 通常具有 .h (或有时为 c++ 的 .hpp)文件扩展名。

然而,许多解释都忽略了头文件中的代码应该如何组织,包括包含其他头文件的顺序。这对于帮助最大限度地提高编译速度和整体可维护性很重要。

c++20 添加了模块,但那是另一个故事了。 鉴于存在大量 c 和 c++20 之前的代码,头文件将继续存在一段时间。

包括警卫

基本的头文件如下:

// foo.h
#ifndef foo_h
#define foo_h

// ... declarations ...

#endif /* foo_h */

也就是说,所有声明都应该位于 include guard 中: #ifndef x, #define x, #endif /* x */ 序列,其中 x 是代码库中的唯一名称并派生从文件名。

包含防护的要点是,如果多次包含特定头文件,则编译器不会收到多个声明错误,因为预处理器将忽略防护中已定义的所有内容。

包含保护名称的命名法并不重要:只需选择一种不太可能与系统或第三方标头中使用的名称发生冲突的方法 - 并且保持一致。

#endif 之后的注释当然不是必需的,但为了可读性,最好总是重复 #ifndef(或 #ifdef 或 #if)中使用的条件。

然后在所有使用该标头的 .c(或 .cpp)文件中,只需 #include 它:

// foo.c
#include "foo.h"

// ... definitions ...

不幸的是,这通常是许多头文件解释停止的地方。有效地创建和使用头文件远不止这些。

自给自足的标头

在继续之前,我想定义头文件自给自足:

意味着什么
  • 自给自足的标头 是指如果将其自身包含到 .c(或 .cpp)文件中,则该文件将在编译时不会出现错误(具体来说,不会出现“未声明”错误)。

例如,一个简单的程序,例如:

#include "foo.h"

int main() {
}

只有当 foo.h 是自给自足的时候,编译才会没有错误。

在标头中包含其他标头

通常,头文件需要包含其他头文件,因为声明使用了其他头文件中的其他声明。

在头文件中:

  • 首先包含其他本地标头(如果有),然后是系统标头(如果有)。

例如:

// color.h
#ifndef cdecl_color_h
#define cdecl_color_h

#include "config.h"  // correct: #include local headers ...
#include "strbuf.h"
#include "util.h"

#include <stdio.h>   // ... before system headers.
// ...

#endif /* cdecl_color_h */

本地标头(用“”括起来的)(如果有的话)放在前面,然后是系统标头(用 <> 括起来的)(如果有的话)。

为什么?因为这有助于确保每个头文件都是自给自足的。 例如,如果您将系统标头放在前面:

#include <stdio.h>   // wrong: #include of system headers ...

#include "strbuf.h"  // ... before local headers.
// ...

那么 strbuf.h 中的声明就可以“意外”使用 stdio.h 中的声明(例如 file),而无需 strbuf.h 本身包括 stdio.h。

这将无限期地继续工作,但如果在某个时候您不再需要 color.h 中的 stdio.h 并因此删除 #include <stdio.h>,那么您将在 strbuf.h 中收到“未声明”错误。 直到此时,您永远不会注意到 strbuf.h 不是自给自足的。

一旦您注意到,它很容易修复,但最好首先通过始终在系统标头之前包含本地标头来避免该问题。

前向声明而不是包含

在 c 头文件中:

  • 如果您仅通过指针使用在另一个标头中声明的结构或联合类型,请前向声明该类型而不是包含其他标头。

例如,如果您的标头 print.h 使用标准标头 pwd.h 中声明的 passwd 结构,但仅通过指针(并且您不需要 pwd.h 中的任何其他内容),则前向声明 passwd 而不是包含 pwd。小时:

// print.h
struct passwd;  // instead of: #include <pwd.h>

void print_passwd( struct passwd *pw );

为什么? 当您只需要一个声明时,它节省了预处理器必须打开 pwd.h 的时间以及编译器必须解析整个文件的时间。 对于大型 c 或 c++ 代码库,时间会增加。

c++ 的等效指南类似,但包括类和引用:

  • 如果您仅通过指针或引用使用在另一个标头中声明的结构、联合或类类型,请前向声明该类型,而不是包含其他标头。

包括一切必要的东西

在头文件中:

  • 您必须包含它需要自给自足的所有其他标头(或前向声明)。

永远不要强迫您的标头的用户必须在您的标头之前包含一些其他标头,以便编译时不会出现错误。

bsd 派生的操作系统历来倾向于违反此准则。 这样做的理由是,这是帮助最大化编译速度的另一种方法。 它通过强迫你成为人类包括守卫来做到这一点。

例如:

#include <sys/types.h>
#include <pwd.h>        // needs <sys/types.h>
#include <unistd.h>     // needs <sys/types.h> too

pwd.h 和 unistd.h 各自执行#include <sys/types.h>,而是依赖您自己执行包含操作。

C++ 算法竞赛自动化测试数据生成与校验框架
C++ 算法竞赛自动化测试数据生成与校验框架

根据原题生成新题面、验证器及完整测试数据,自动套用 testlib 模板,用于用户要求生成测试数据时。

下载

这有什么帮助? 它消除了预处理器必须打开 sys/types.h、读取文件、遇到包含防护并忽略其余内容(如果之前已见过该防护)的步骤(如 unistd.h 的情况) .

因此,虽然它确实有帮助,但代价是它迫使用户必须记住手动包含文件,这可能会导致不必要的包含,从而减慢编译速度。 例如,如果在某个时候您删除了 pwd.h 和 unistd.h 的包含内容,则可能会导致不再需要 sys/types.h,但您可能会忘记删除它。

与计算机科学中的许多其他事物一样,这是一种权衡。 bsd 派生的操作系统已经放弃了这种做法,并使标头自给自足。

子目录

大型代码库通常将代码划分到子目录中,每个子目录包含一组相关文件。 对于 #include "...",预处理器仅在当前目录中查找,不在其子目录中查找。

要在子目录中使用标头,有两种选择:

  1. 使用引号之间的子目录名称;或:
  2. 告诉编译器也查看子目录。

第一个示例是:

#include "subsystem/out_q.h"

执行第二个操作是特定于编译器的,但对于基于 unix 的编译器(例如 gcc 和 clang),您通常会添加 -isubsystem 形式的命令行选项,将子系统添加到编译器包含路径.

这两种方法都可以,但如果您采用第二种方法,头文件名必须在整个代码库中是唯一的。 如果不同子目录中的两个标头具有相同的名称,则包含其中一个标头将仅包含编译器包含路径中较早的标头。

仅大小写差异

另一件事不要做的是:

  • 不要有名称不同的文件仅大小写不同。

例如,不有out_q.h 和 out_q.h。为什么不呢?

  • 很容易写错。
  • 在不区分大小写但保留大小写的文件系统(例如 apfs 和 hfs+)上,此类文件被视为相同文件。

对于第二个问题,这可能意味着即使您包含 out_q.h,如果 out_q.h 在包含路径中排在第一位,您最终也可能会包含 out_q.h。

切勿使用../

你必须永远不要做的一件事是:

  • 切勿在包含路径中使用 ../,例如:
#include "../subsystem/out_q.h"

为什么不呢?

  • 代码库的构建过程可能使用符号链接,并且..可能最终相对于解析路径,而不是原始路径,因此您结束的目录up 包括 from 可能不是您想象的那样。 这可能会导致难以诊断的错误。

  • 如果您的代码库架构良好,代码不应该具有循环依赖关系 - 并且包含路径将被适当设置以防止这种情况。

对于第二个,如果您尝试包含subsystem/out_q.h 并得到“没有这样的文件”,则意味着您不应该包含您的文件中的该文件正在努力,因为这会产生循环依赖。 使用 ../ 只是为了让你的代码编译破坏了这个有意的限制。

循环依赖通常很糟糕,因为它们可能会导致静态初始化顺序惨败。

在 .c 或 .cpp 文件中包含标头

对于 .c(或 .cpp)文件,在头文件中包含标头的所有准则也适用,但需要进行一项调整以包含本地标头:

  • 对于给定的 .c(或 .cpp)文件,例如 foo.c,首先包含其相应的标头 foo.h。

为什么?这确保了 foo.h 是自给自足的。

结论

正确的头文件规范有助于最大限度地提高编译速度和整体可维护性。总结一下:

  • 自给自足的标头 是一个如果将其自身包含到 .c(或 .cpp)文件中,则该文件将编译而不会出现错误(具体来说,没有“未声明”)错误)。

  • 在头文件中,使用包含防护。

  • 在头文件中,首先包含其他本地标头(如果有),然后包含系统标头(如果有)。

  • 如果您使用仅通过指针(或 c++ 中的引用)在另一个标头中声明的结构或联合(或 c++ 中的类)类型,请前向声明该类型,而不是包含其他标头。

  • 对于标头,您必须包含它需要自给自足的所有其他标头(或前向声明)。

  • 不要有名称不同的文件仅大小写不同。

  • 切勿在包含路径中使用 ../。

  • 对于给定的 .c(或 .cpp)文件,例如 foo.c,首先包含其相应的标头 foo.h。

负责任地包含。

热门AI工具

更多
豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

Lovart
Lovart Hot

一款面向视觉设计创作的AI设计平台,可通过智能体和画布工作流辅助制作海报、Logo、网页、PPT及其他视觉内容。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

LibLibAI
LibLibAI Hot

一款AI视频创作工具,主要用于国内领先的AI创意平台,以海量模型、低门槛操作与“创作-分享-商业化”生态,让小白与专业创作者都能高效实现图文乃至视频创意表达,适合需要提升相关任务效率的用户。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

相关专题

更多
python中print函数的用法
python中print函数的用法

python中print函数的语法是“print(value1, value2, ..., sep=' ', end=' ', file=sys.stdout, flush=False)”。本专题为大家提供print相关的文章、下载、课程内容,供大家免费下载体验。

2320

2023.09.27

python print用法与作用
python print用法与作用

本专题整合了python print的用法、作用、函数功能相关内容,阅读专题下面的文章了解更多详细教程。

229

2026.02.03

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

5764

2023.10.24

typedef和define区别
typedef和define区别

typedef和define区别在类型检查、作用范围、可读性、错误处理和内存占用等。本专题为大家提供typedef和define相关的文章、下载、课程内容,供大家免费下载体验。

313

2023.09.26

define的用法
define的用法

define用法:1、定义常量;2、定义函数宏:3、定义条件编译;4、定义多行宏。更多关于define的用法的内容,大家可以阅读本专题下的文章。

619

2023.10.11

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

299

2026.04.23

unix和linux的区别
unix和linux的区别

unix和linux的区别包括发展历史、开源性、发行版本、内核、文件系统、应用程序兼容性和用户界面等。本专题为大家提供unix和linux相关的文章、下载、课程内容,供大家免费下载体验。

2313

2023.09.22

Buffalo框架数据库开发全教程
Buffalo框架数据库开发全教程

本专题围绕Buffalo框架数据库开发,讲解database.yml多环境配置、soda与fizz迁移生成回滚、模型结构体标签、增删改查与条件查询、一对多与多对多关联、数据校验、回调钩子、事务处理及原生SQL执行能力。

40

2026.09.23

Buffalo框架路由与请求处理实操指南
Buffalo框架路由与请求处理实操指南

本专题讲解Buffalo框架路由与请求处理机制,涵盖路由注册与分组、资源路由、Handler编写规范、Context上下文方法、参数绑定、中间件编写挂载、Session与Cookie读写、Flash消息及错误页面定制方法。

20

2026.09.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Conan 2 Essentials 免费课程
Conan 2 Essentials 免费课程

共0课时 | 0人学习

CMake 与 Conan 集成实践
CMake 与 Conan 集成实践

共0课时 | 0人学习

Conan 2 高级依赖模型介绍
Conan 2 高级依赖模型介绍

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn