在C ++中创建类时,我的标准库头放在哪里?

时间:2018-10-07 15:26:15

标签: c++ class header preprocessor

如果我错了,请纠正我,C ++标头开头的#ifndef内容特别有助于避免代码重复?

那么(根据最佳实践),我应该在哪里放置诸如之类的基本库头?

让我用一个例子来澄清这个问题

让我们说我有一个程序可以创建Employee类并存储有关它们的信息,例如它们的名称或其他名称

  1. 我有一个int main(),它使用std :: cout ALOT将信息显示到控制台。它甚至可以使用硬编码的字符串变量来将信息存储到一个类中(我知道硬编码是不好的,请耐心等待,这将有助于为我澄清事情)

  2. 我有一个名为Employee.h的标头,其中包含我的所有私有变量。在这种情况下,名称为employee ..并且还具有函数声明。假设它在其中一个构造函数中使用字符串数据类型。

  3. 一个确实进行设置和获取...的Employee.cpp。让我们假装它具有一些奇怪的函数来操纵Employee名称(它是一个字符串),并在其前面添加“ yolo”。凉。因此,此文件还需要访问

到目前为止:我的所有三个文件都想使用标头,只有main.cpp要使用标头。

我的问题:

好像标头可以放在Eployee标头中而不是main.cpp中,程序将编译并运行得很好...但是如果我将它放在main标头而不是Employee标头中,则编译器变得疯狂。那么我应该将其同时包含在main和header中还是仅包含在header中?

如果我创建一个名为Companies的第二类,该类也使用字符串库,此最佳实践是否会更改?

P.S。请问可以解释一下常见的预处理器如何链接东西。例如为什么当仅包含在main中但不包含标头时,计算机为什么会抛出错误……我认为这些东西都被链接在一起了……我傻瓜请解释一下< / p>

(我想我了解有关obj文件的所有庞然大物,但是预处理器和编译器如何知道在.cpp类和主.cpp类中都没有重复头代码?

感谢您的耐心阅读,我也非常感谢这种方式对像我这样的c ++新手的帮助。

1 个答案:

答案 0 :(得分:1)

根据C ++标准:

  

翻译单元是C ++中编译的基本单元。它   由单个源文件的内容以及   任何直接或间接包含的头文件,减去这些头文件   使用条件预处理语句忽略的行。

请注意,“间接包含”的措词。另一个标头中#include的任何标头都包含在#include该标头的任何源文件中。因此,如果您要求将其包含在标头中,即标头需要一个定义而不只是一个声明,请在其中#include

翻译单元生成方式的一小部分示例:最初我们有标题。让我们称之为stdfoo.h(这是一个示例,您应该避免使用std命名自己的标头,以免发生冲突。)

#ifndef STD_FOO_ // here is our header guard
#define STD_FOO_

typedef long howl_t;
void foo();

#endif // STD_FOO_ close the header guard at the end

让我们将其包含在我们的项目源main.cxx中:

#include <stdfoo.h>

int main {
    foo();
    return 0;
};

当我们编译它时,它将通过预处理器运行到翻译单元中。让我们看看生成的单元可能是什么样的:

typedef long howl_t;
void foo();

int main {
    foo();
    return 0;
};

#include指令在转换单元中扩展了stdfoo.h,因此编译器可以查看单个转换单元并生成对象。

让我们进行修改,并为main.cxx赋予标题main.h

#ifndef MAIN_H
#define MAIN_H

class BarkBark {
    BarkBark() {}
    void emit();
};

#endif // MAIN_H

在新的main.cxx中使用它:

#include "main.h"
#include <stdfoo.h>

int main {
    BarkBark woof;
    woof.emit();
    foo();
    return 0;
};

翻译单元如下:

class BarkBark {
    BarkBark() {}
    void emit();
};

typedef long howl_t;
void foo();

int main {
    BarkBark woof;
    woof.emit();
    foo();
    return 0;
};

现在说emit使用howl_t作为void emit(howl_t h)这样的参数,这将需要重新声明howl_t(一种简单的方法),或者将stdfoo.h包含在{{1 }}

main.h

该翻译单元的外观如何?

#ifndef MAIN_H
#define MAIN_H

#include <stdfoo.h>

class BarkBark {
    BarkBark() {}
    void emit(howl_t h);
};

#endif // MAIN_H

预处理器已将typedef long howl_t; void foo(); class BarkBark { BarkBark() {} void emit(); }; int main { howl_t aroooooo = 0; BarkBark woof; woof.emit(aroooooo); return 0; }; d标头中的#include展开。

请注意,由于预处理器处理标头保护,#include是否保留在#include <stdfoo.h>中,翻译单元看起来还是一样的,因此将第二个包含项简单地丢弃。

现在,就标准库头而言,并非所有标准库中的大多数都可以防止多重包含,因此您可以main.cxx随心所欲地频繁和频繁地进行操作,而不会产生不良影响。生成的翻译单元将只包含一次以进行编译。请注意,使用任何其他标头不能保证这一点,并且多次包含无保护的标头可能会产生非常模糊的外观错误,因为重复声明仅存在于翻译单元中。

因此,如果您的标头要求包含特定的类或函数,请在标头中#include来回答您的问题。此时,是否决定在源文件中也#include都是一种风格选择。有人说万一标题改变了,有人说不简化代码。该选择实际上取决于您。

如果另一个头文件中不需要头,则最好只在需要它的每个源文件中包含它,这样您的代码将产生较小的翻译单元以进行编译并减少名称空间污染。

但是!这不是一个硬性规定。有些使用不同的方法,将#include排列在可以使用它们或在多个源文件中使用某些标头的逻辑位置,以减少代码重复。就个人而言,我认为这种安排增加了技术负担,如果可以改进或重构代码,并且标头变得孤立的话。