解决
真正帮助我的是,我可以在.cpp文件中#include标头而不会导致重新定义的错误。
我是C ++的新手,但我在C#和Java方面有一些编程经验,所以我可能会遗漏一些C ++独有的基础知识。
问题是我真的不知道什么是错的,我会粘贴一些代码来试图解释这个问题。
我有三个类,GameEvents,Physics和GameObject。我有每个标题。 GameEvents有一个Physics和一个GameObjects列表。 Physics有一个GameObjects列表。
我想要实现的是我希望GameObject能够访问或拥有一个Physics对象。
如果我只是在GameObject中#include“Physics.h”我得到了 “错误C2111:'ClassXXX':'类'类型redtifinition”我明白了。 这就是我认为#include-guard有帮助所以我在Physics.h中添加了一个包含守卫,因为那是我想要包含两次的标题。
这就是它的外观
#ifndef PHYSICS_H
#define PHYSICS_H
#include "GameObject.h"
#include <list>
class Physics
{
private:
double gravity;
list<GameObject*> objects;
list<GameObject*>::iterator i;
public:
Physics(void);
void ApplyPhysics(GameObject*);
void UpdatePhysics(int);
bool RectangleIntersect(SDL_Rect, SDL_Rect);
Vector2X CheckCollisions(Vector2X, GameObject*);
};
#endif // PHYSICS_H
但如果我在GameObject.h中#include“Physics.h”就像这样:
#include "Texture2D.h"
#include "Vector2X.h"
#include <SDL.h>
#include "Physics.h"
class GameObject
{
private:
SDL_Rect collisionBox;
public:
Texture2D texture;
Vector2X position;
double gravityForce;
int weight;
bool isOnGround;
GameObject(void);
GameObject(Texture2D, Vector2X, int);
void UpdateObject(int);
void Draw(SDL_Surface*);
void SetPosition(Vector2X);
SDL_Rect GetCollisionBox();
};
我遇到了多个不明白为什么会出现的问题。 如果我不#include“Physics.h”我的代码运行得很好。
我非常感谢任何帮助。
答案 0 :(得分:114)
预处理器是一个程序,它接受你的程序,进行一些更改(例如包括文件(#include),宏扩展(#define),基本上所有以#
开头的)并给出“干净” “结果给编译器。
预处理器在看到#include
时会这样工作:
当你写:
#include "some_file"
some_file
的内容几乎完全粘贴到包含它的文件中。现在,如果你有:
a.h:
class A { int a; };
和
b.h:
#include "a.h"
class B { int b; };
和
main.cpp:
#include "a.h"
#include "b.h"
你得到:
main.cpp:
class A { int a; }; // From #include "a.h"
class A { int a; }; // From #include "b.h"
class B { int b; }; // From #include "b.h"
现在您可以看到A
是如何重新定义的。
当你写警卫时,他们会变成这样:
a.h:
#ifndef A_H
#define A_H
class A { int a; };
#endif
b.h:
#ifndef B_H
#define B_H
#include "a.h"
class B { int b; };
#endif
现在让我们来看看main中的#include
将如何展开(这与前一种情况完全一样:复制粘贴)
main.cpp:
// From #include "a.h"
#ifndef A_H
#define A_H
class A { int a; };
#endif
// From #include "b.h"
#ifndef B_H
#define B_H
#ifndef A_H // From
#define A_H // #include "a.h"
class A { int a; }; // inside
#endif // "b.h"
class B { int b; };
#endif
现在让我们按照预处理程序查看“真正的”代码。我会一行一行地去:
// From #include "a.h"
注释。忽视!继续:
#ifndef A_H
是否定义了A_H
?没有!然后继续:
#define A_H
现在确定A_H
已定义。继续:
class A { int a; };
这不是预处理器的东西,所以请保留它。继续:
#endif
之前的if
已在此处完成。继续:
// From #include "b.h"
注释。忽视!继续:
#ifndef B_H
是否定义了B_H
?没有!然后继续:
#define B_H
现在确定B_H
已定义。继续:
#ifndef A_H // From
是否定义了A_H
?是!然后忽略直到相应的#endif
:
#define A_H // #include "a.h"
忽略
class A { int a; }; // inside
忽略
#endif // "b.h"
之前的if
已在此处完成。继续:
class B { int b; };
这不是预处理器的东西,所以请保留它。继续:
#endif
之前的if
已在此处完成。
也就是说,在预处理器完成文件之后,这就是编译器看到的内容:
main.cpp
class A { int a; };
class B { int b; };
正如您所看到的,任何可以在同一文件中两次获得#include
d的内容,无论是直接还是间接都需要加以保护。由于.h
文件总是很可能被包含两次,所以如果你保护所有你的.h文件就好了。
P.S。请注意,您还有循环#include
s。想象一下预处理器将Physics.h的代码复制粘贴到GameObject.h中,它看到有一个#include "GameObject.h"
,这意味着将GameObject.h
复制到自身。当你复制时,你再次获得#include "Pysics.h"
并且你永远陷入了一个循环。编译器阻止了这一点,但这意味着你的#include
已经完成了一半。
在说明如何解决这个问题之前,你应该知道另一件事。
如果你有:
#include "b.h"
class A
{
B b;
};
然后编译器需要知道关于b
的所有内容,最重要的是,知道它有哪些变量等等,以便它知道应该在b
中代替A
的字节数
但是,如果你有:
class A
{
B *b;
};
然后编译器实际上不需要知道B
的任何内容(因为指针,无论类型是否具有相同的大小)。关于B
,唯一需要了解的是它存在!
所以你做了一个叫做“前瞻性声明”的事情:
class B; // This line just says B exists
class A
{
B *b;
};
这与您在头文件中执行的许多其他操作非常相似,例如:
int function(int x); // This is forward declaration
class A
{
public:
void do_something(); // This is forward declaration
}
答案 1 :(得分:5)
您在此处有循环引用:Physics.h
包括GameObject.h
,其中包含Physics.h
。您的班级Physics
使用GameObject*
(指针)类型,因此您无需在GameObject.h
中加入Physics.h
,只需使用转发声明 - 而不是
#include "GameObject.h"
把
class GameObject;
此外,在每个头文件中放置警卫。
答案 2 :(得分:4)
问题在于,您的GameObject.h
没有警卫,所以#include "GameObject.h"
Physics.h
GameObject.h
Physics.h
包括{{1}}时,{{1}}。{ p>
答案 3 :(得分:4)
在所有*.h
或*.hh
标头文件中添加包含警戒(除非您有特殊原因不这样做)。
要了解正在发生的事情,请尝试获取源代码的预处理形式。对于GCC,它类似于g++ -Wall -C -E yourcode.cc > yourcode.i
(我不知道微软编译器如何做到这一点)。您还可以询问包含哪些文件,GCC为g++ -Wall -H -c yourcode.cc
答案 4 :(得分:4)
首先你需要在游戏对象上加入警卫,但这不是真正的问题
如果还有其他内容包括physics.h,那么physics.h包含gameobject.h,你可以得到类似的内容:
class GameObject {
...
};
#include physics.h
class Physics {
...
};
并且#include physics.h由于包含警卫而被丢弃,并且在物理声明之前你最终会得到GameObject的声明。
但如果你想让GameObject拥有一个指向物理学的指针,这就是一个问题,因为必须首先声明htat物理。
要解决这个循环,你可以转发声明一个类,但前提是你只是在下面的声明中使用它作为指针或引用,即:
#ifndef PHYSICS_H
#define PHYSICS_H
// no need for this now #include "GameObject.h"
#include <list>
class GameObject;
class Physics
{
private:
list<GameObject*> objects;
list<GameObject*>::iterator i;
public:
void ApplyPhysics(GameObject*);
Vector2X CheckCollisions(Vector2X, GameObject*);
};
#endif // PHYSICS_H
答案 5 :(得分:3)
在 ALL 头文件中使用包含警戒。由于您使用的是Visual Studio,因此可以使用#pragma once
作为所有标头中的第一个预处理器定义。
但我建议使用经典方法:
#ifndef CLASS_NAME_H_
#define CLASS_NAME_H_
// Header code here
#endif //CLASS_NAME_H_
关于forward declaration的第二次阅读并应用它。
答案 6 :(得分:0)
标头保护的目标是避免多次包含同一文件。 但是可以改善当前在C ++中使用的标头防护。当前的警卫是:
var firms = _context.Firms.AsQueryable();
firms = firms
.SelectMany(x => x.Categories)
.Where(x => x.CategoryId == param.CategoryId)
.Select(x => x.Firm)
.Include(x => x.Categories).ThenInclude(x => x.Category)
.Include(x => x.Services).ThenInclude(x => x.Service);
;
我的新警卫建议是:
#ifndef AAA_H
#define AAA_H
class AAA
{ /* ... */ };
#endif
这解决了当AAA类需要BBB类声明而BBB类需要AAA类声明时出现的烦人问题,通常是因为从一个类到另一个类有交叉的指针:
#ifndef AAA_H
#define AAA_H
class AAA
{ /* ... */ };
#else
class AAA; // Forward declaration
#endif
我希望将其包含在从模板自动生成代码的IDE中。
答案 7 :(得分:0)
“ #pragma一旦” :::具有与标头防护相同的目的,并具有更短,更不易出错的优点。
许多编译器使用#pragma指令支持更简单的备用表头防护形式: “ #pragma一次” //您的代码在这里
但是,#pragma曾经不是C ++语言的正式组成部分,并且并非所有编译器都支持它(尽管大多数现代编译器都支持)。
出于兼容性目的,人们建议坚持使用传统的标题保护器。它们工作量不大,而且可以保证所有兼容的编译器都支持它们。