1

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

示例 1

Foo.h

int SumOfNums(int i, int j);

Foo.cpp

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

主文件

#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;
}

主文件

#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;
}

主文件

#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;
}

主文件

#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 中有一个定义时,为什么链接器不报错?

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

4

5 回答 5

4
  1. 多次声明一个函数不是错误。

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

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

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

于 2012-10-03T23:26:07.497 回答
4

Ex1 - 为什么 Foo.h 可以在 main.cpp 中包含两次,当没有包含守卫时?

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

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

这也是一个定义,因此违反了一个定义规则。

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

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

Ex4 - 这和 Ex1 有什么区别?

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

于 2012-10-03T23:29:18.743 回答
3
  1. 因为函数声明可以重复,但类型定义等不能。
  2. i在包含标题的每个翻译单元中定义变量,但“一个定义规则”意味着您在程序中只能有一个变量定义。
  3. 的定义enum FooBar纯粹是类型信息;标头既不定义也不声明该类型的变量。您会得到与标准标题类似的行为,例如<iostream>.
  4. 这里的区别在于您尝试重复enum FooBar(通过包含Foo.h两次)的类型定义并且不允许类型重新定义,这是您应该使用标头保护的主要原因。
于 2012-10-03T23:26:56.373 回答
2

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

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

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

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

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

于 2012-10-03T23:33:00.423 回答
0

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

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

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

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

于 2012-10-03T23:40:11.957 回答