【问题标题】:Headers and different cpp files标头和不同的 cpp 文件
【发布时间】:2012-10-03 23:21:58
【问题描述】:

我已经知道包含守卫,但这里有一些我想弄清楚的问题:

示例 1

Foo.h

int SumOfNums(int i, int j);

Foo.cpp

#include "Foo.h"
int SumOfNums(int i, int j){
   return i+j;
}

main.cpp

#include "Foo.h"
#include "Foo.h"

int main(){
    SumOfNumbs(5,10);
}

这将编译并运行正常。

示例 2

Foo.h

int SumOfNums(int i, int j);
int i;

Foo.cpp

#include "Foo.h"
int SumOfNums(int i, int j){
   return i+j;
}

main.cpp

#include "Foo.h"

int main(){
    SumOfNumbs(5,10);
}

根据编译器重新定义“i”。

示例 3

Foo.h

int SumOfNums(int i, int j);
enum FooBar{FOO, BAR};

Foo.cpp

#include "Foo.h"
int SumOfNums(int i, int j){
   return i+j;
}

main.cpp

#include "Foo.h"

int main(){
    SumOfNumbs(5,10);
}

这将编译并运行正常。

示例 4

Foo.h

int SumOfNums(int i, int j);
enum FooBar{FOO, BAR};

Foo.cpp

#include "Foo.h"
int SumOfNums(int i, int j){
   return i+j;
}

main.cpp

#include "Foo.h"
#include "Foo.h"
int main(){
    SumOfNumbs(5,10);
}

根据编译器重新定义FooBar。

总结一下:

示例 1 - 为什么 Foo.h 可以在 main.cpp 中包含两次,而没有包含保护?

示例 2 - int 变量与函数头有何不同?

示例 3 - 为什么 Foo.cpp 中有一个 FooBar 定义,而 main.cpp 中有一个 FooBar 定义时,链接器不报错?

示例 4 - 这与 示例 1 有什么区别?

【问题讨论】:

    标签: c++


    【解决方案1】:

    Ex1 - 为什么 Foo.h 可以在 main.cpp 中包含两次,而没有包含保护?

    因为在这种情况下,它只是声明一个函数。您可以拥有任意数量的声明。

    Ex2 - int 变量与函数头有何不同?

    这也是一个定义,因此打破了单一定义规则。

    Ex3 - 为什么 Foo.cpp 中有一个 FooBar 定义而 main.cpp 中有一个定义时,链接器不报错?

    跨翻译单元定义类型很好。

    Ex4 - 这和 Ex1 有什么区别?

    您在同一个翻译单元中定义了两次相同的类型 - 不允许。

    【讨论】:

    • 头文件中有常量怎么办?这不是像示例 2 那样吗?
    【解决方案2】:
    1. 多次声明一个函数并不是错误。

    2. SumOfNums 的函数声明只告诉编译器SumOfNums 存在于某处(但不在此处)。 i 的定义在全局区域中分配存储并为其命名。您有两个i定义,每个.cpp 文件中都有一个,因为包含定义的标头不止一次。

    3. 链接器永远不会看到enum FooBar。枚举中的值被编译器用作常量。

    4. 此示例包含enum FooBar 的声明,而示例 1 没有。编译器只希望看到给定enum 的声明一次。

    【讨论】:

    • 我确定您知道这一点,但您的 1. 意味着多次声明一个变量错误。
    • @LuchianGrigore:不,它没有,我的声明 1 没有提到变量。
    • 没错,它强调的是“功能”。 “多次声明不是错误”有什么问题?
    【解决方案3】:
    1. 因为函数声明可以重复,但类型定义等不能。
    2. 您在包含标题的每个翻译单元中定义变量i,但“一个定义规则”意味着您在程序中只能对变量进行一个定义。
    3. enum FooBar的定义纯粹是类型信息;标头既不定义也不声明该类型的变量。使用 <iostream> 等标准标头时,您会得到类似的行为。
    4. 这里的区别在于您尝试重复 enum FooBar 的类型定义(通过包含 Foo.h 两次)并且不允许类型重新定义,这是您应该使用标头保护的主要原因。

    【讨论】:

      【解决方案4】:

      一般的答案是您可以多次声明事物,但只能定义一次(这并不完全正确,很少有事物只能声明一次,例如模板上的默认参数)。当在翻译单元中看到多个定义时,编译器会抱怨,当在不同的翻译单元中看到事物的多个定义并且不能在不同的翻译单元中多次定义时,链接器会抱怨。例如,类型、模板和内联函数可以在每个翻译单元中定义一次而不会出现问题。普通函数只能在一个翻译单元中定义。

      在您的第一个示例中,您只需在标头中声明一个函数。您可以根据需要随时声明函数。不过,您只能定义一次。

      您的第二个示例包括变量i 的定义。编译此标头的每个翻译单元都将包含i 的定义。当链接器尝试构建东西时,它会检测到i 有两个定义,它会失败。包括守卫不会阻止这个问题,因为你他们只在一个翻译单元中工作。

      您的第三个示例只是在标头中声明了一个函数,还定义了一个enum。每个翻译单元中只能定义一次类型,但多个翻译单元都可以有一个类型的定义。原因很简单:类型不会创建任何代码,也不会为变量分配任何空间。

      您的第四个示例包括定义 enum 两次的标头,即翻译单元看到类型的重新定义并失败。如果你使用了 include 守卫,这个问题就会被发现。

      【讨论】:

        【解决方案5】:

        Ex1 - 可以,因为可以。即,这是合法的,语言中没有任何内容可以阻止您多次包含头文件。如果有的话,一开始就不需要包含警卫。这个Foo.h 中没有任何内容阻止它被包含两次。它只有一个函数原型,您可以根据需要包含任意多个函数原型的副本,只要它们都相同。

        Ex2 - 您会从 链接器 收到重复的符号消息,因为您将有两个名为 i 的全局变量。但这是链接器的事情,而不是编译器的事情。如果您实际上遇到了编译器错误,我想这可能是因为在Foo.cpp 中,在包含Foo.h 之后,您有一个全局变量i。但是,在SumOfNums 的主体内,您还使用i 作为函数参数。因此,在该函数内部,您无法访问全局 i。不过,并不是所有的 c++ 编译器都会抱怨这一点。有些人只会给你一个警告,然后继续他们愉快的编译工作。

        Ex3 - 因为FooBar 是一种类型,而不是变量。链接器符号仅为全局变量和函数实现生成(意味着具有主体的函数,而不仅仅是原型)。仅包含Foo.h 不会生成链接器符号,因此不存在链接器问题。

        Ex4 - 此示例中的 Foo.h 定义了一个类型,而示例 1 中的那个没有。因此,通过在同一个main.cpp 文件中包含两次Foo.h,您将两次注入相同的类型定义。那是违法的。如果你问我为什么这对于类型定义是非法的,但对于函数原型是合法的,我只能告诉你语言规范是这样写的。这背后可能有一个基本原理,但我不知道它是什么。

        【讨论】:

          猜你喜欢
          • 2013-10-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-02-12
          • 1970-01-01
          • 2017-08-18
          • 2023-03-20
          相关资源
          最近更新 更多