我正在编写一个代码,里面有 5 个包含文件,我在这个文件中定义函数,然后意识到为什么我不应该将header files
所有函数分开,然后最后将它们包含在一个文件中。但是,我已经看到通常不会这样做。为什么不?这样做有什么特别的缺点吗?
3 回答
这不是一个真正的答案,因为这个问题有一个错误的假设:
但是,我已经看到通常不会这样做。
这不是真的。这是一种常见的做法。一个很好的例子是ffmpeg.h。标头是扩展库的前端。
编译时间长的论点是虚假的。今天的系统非常快。这仅对非常庞大的系统很重要,但我真的不认为您使用它们。我自己从来没有遇到过这样的系统。
编译时间不是执行时间。这是另一个误解。
为方便起见,ffmpeg.h 的整个代码:
#ifndef _INCLUDE_FFMPEG_H_
#define _INCLUDE_FFMPEG_H_
#ifdef HAVE_FFMPEG
#include <avformat.h>
#endif
#include <stdio.h>
#include <stdarg.h>
/* Define a codec name/identifier for timelapse videos, so that we can
* differentiate between normal mpeg1 videos and timelapse videos.
*/
#define TIMELAPSE_CODEC "mpeg1_tl"
struct ffmpeg {
#ifdef HAVE_FFMPEG
AVFormatContext *oc;
AVStream *video_st;
AVCodecContext *c;
AVFrame *picture; /* contains default image pointers */
uint8_t *video_outbuf;
int video_outbuf_size;
void *udata; /* U & V planes for greyscale images */
int vbr; /* variable bitrate setting */
char codec[20]; /* codec name */
#else
int dummy;
#endif
};
/* Initialize FFmpeg stuff. Needs to be called before ffmpeg_open. */
void ffmpeg_init(void);
/* Open an mpeg file. This is a generic interface for opening either an mpeg1 or
* an mpeg4 video. If non-standard mpeg1 isn't supported (FFmpeg build > 4680),
* calling this function with "mpeg1" as codec results in an error. To create a
* timelapse video, use TIMELAPSE_CODEC as codec name.
*/
struct ffmpeg *ffmpeg_open(
char *ffmpeg_video_codec,
char *filename,
unsigned char *y, /* YUV420 Y plane */
unsigned char *u, /* YUV420 U plane */
unsigned char *v, /* YUV420 V plane */
int width,
int height,
int rate, /* framerate, fps */
int bps, /* bitrate; bits per second */
int vbr /* variable bitrate */
);
/* Puts the image pointed to by the picture member of struct ffmpeg. */
void ffmpeg_put_image(struct ffmpeg *);
/* Puts the image defined by u, y and v (YUV420 format). */
void ffmpeg_put_other_image(
struct ffmpeg *ffmpeg,
unsigned char *y,
unsigned char *u,
unsigned char *v
);
/* Closes the mpeg file. */
void ffmpeg_close(struct ffmpeg *);
/*Deinterlace the image. */
void ffmpeg_deinterlace(unsigned char *, int, int);
/*Setup an avcodec log handler. */
void ffmpeg_avcodec_log(void *, int, const char *, va_list);
#endif /* _INCLUDE_FFMPEG_H_ */
有些人认为将函数放在单独的文件中并通过标头包含它们会增加项目的一些开销并增加编译(而不是执行)时间。尽管严格来说这是正确的,但实际上增加的编译时间可以忽略不计。
我对这个问题的看法更多地基于函数的目的。我反对为每个文件放置一个带有关联标题的函数,因为这很快就会变得一团糟。我不认为将每个人放在一个文件中也不是一个好方法(尽管出于不同的原因,维护起来也是一团糟)。
我的观点是,理想的权衡是查看函数的目的。您应该问问自己这些功能是否可以在其他地方使用。换句话说,这些函数是否可以作为其他程序中一系列常见任务的库?如果是,这些函数应该组合在一个文件中。使用与一般任务一样多的文件。例如,在一个文件中执行数值积分的所有函数,在另一个文件中处理文件 i/o 的所有函数,在第三个文件中处理字符串的所有函数等等。通过这种方式,您的库是一致的。
最后,我会将执行仅对特定程序有意义的任务的函数放入该main
函数的同一文件中。例如,任何旨在初始化一系列变量的函数。
但最重要的是,你应该把任何建议当作建议。归根结底,您应该采用使您(或您的团队)开发效率最高的方法。
如果您正在编写某种 API 或库并希望该库的用户可以轻松访问其中的函数,则应该只创建一个“超级包含头”。Windows OS API 是最明显的例子,其中一个#include 让您可以访问成千上万个函数。
但即使在编写此类库时,您也应该警惕“超级头文件”。您应该尽量避免它们的原因与程序设计有关。面向对象的设计要求你应该努力制作独立的、自主的模块,这些模块专注于自己的任务,而不知道或关心程序的其余部分。
该设计规则背后的基本原理是减少称为紧密耦合的现象,其中每个模块都严重依赖于其他模块。计算机科学研究(例如这项研究)表明,紧密耦合与复杂性相结合会导致更多的软件错误,以及更严重的错误。如果一个紧密耦合的程序在一个模块中出现错误,错误可能会在整个程序中升级并导致灾难。而松耦合的自治模块中的错误只会导致该特定模块失败。
每次包含头文件时,都会在程序和该头文件之间创建依赖关系。因此,尽管创建一个包含所有内容的文件很诱人,但您应该避免这样做,因为它会在项目中的所有模块之间产生紧密耦合。它还将模块暴露给彼此的全局名称空间,可能导致更多名称空间与相同的变量名称等冲突。
除了安全问题之外,紧密耦合在链接/构建程序时也非常烦人。在紧密耦合的情况下,如果某些完全不相关的东西(例如 GUI 库未链接),您的数据库模块突然无法工作。