עבור לתוכן

בעיה בinclude

Featured Replies

פורסם

שלום

אני עושה את המשחק דיגר, יש לי שני אובייקטים לוח ואוייב(בהמשך יוסף גם האובייקט של הדיגר)

בכל אופן ללוח יש שדה של אוייב והאובייקט של הלוח דואג לשם אותו עליו.

עכשיו באובייקט של האוייב יש את הפונקציה של התזוזה, עכשיו כדי הוא יוכל לזוז הוא צריך שתיהיה לו אינדיקציה לגבי מצב הלוח

האם יש לו קירות, יהלומים (וגם את הדיגר) עכשיו הבעיה שלי היא כזאת

בקובץ board.h יש לי אינקלוד לenemy.h אך מצד שני בקובץ enemy.h יש לי אינקלוד לboard.h

board.h

#ifndef BOARDH
#define BOARDH
#include "enemy.h"


class Board
{
public:
Board();

private:
Enemies *digger_Enemies ;
int numEnemies;
};
#endif

enemy.h


#ifndef ENEMYH
#define ENEMYH
#include "board.h"


class Enemy
{
public:
Enemy(Board currB);

private:


int EnemyRow , EnemyCol;
Board currentBoard;
};
#endif

תודה לעוזרים

פורסם

Forward declaration to the rescue!

קודם כל, לא אמרת מה השגיאה שלך. חוץ מזה, לא הגדרת מה זה Enemies (אני מניח שהתכוונת ל-Enemy?)

הפתרון הוא:

א. לדאוג ש-Enemy יחזיק מצביע ל-Board.

ב. במקום לעשות include, לעשות forward decleration, כלומר ב-enemy.h, לפני שאתה מגדיר את class Enemy, להוסיף את השורה:

class Board;

זה אומר לקומפיילר "קיים קלאס בשם Board, אבל אני עדיין לא אומר לך מהו", ואז זה מאפשר לקלאס להכיל מצביע אליו.

פורסם
  • מחבר

1) לגבי הEnemies זה אכן אמור להיות Enemy

2) אני לא יכול שיהיה לי שדה "רגיל" של לוח בEnemy , ואז בקריאה של הבנאי של ENEMY (זה יבוצע מאובייקט של הלוח) לשלוח לו את *this

או שבגלל שזה אובייקט(והוא יחסית גדול בהמשך יהיו עוד שדות) אני צריך מצביע

3) אם אני יוסיף את השורה איך הקומפילר ידע איפה קלאס הBoard נמצא?

תודה על התגובה המהירה

פורסם

2. לא רק שאתה לא יכול (מבחינת הקומפיילר), אלא גם עדיף שלא (מבחינה לוגית). אם Enemy מכיל בתוכו משתנה מטיפוס Board, אז הוא צריך להכיל עותק של הלוח. כלומר כל אובייקט מטיפוס Enemy יכיל עותק משלו של הלוח, וזה לא הגיוני בשום צורה. צריך להיות רק אובייקט אחד מטיפוס Board, וכל האובייקטים מטיפוס Enemy יחזיקו מצביע אליו.

3. סמוך עליו :) כשאתה משתמש ב-forward declaration, אז בזמן הקומפילציה הקומפילר לא באמת צריך לדעת איך נראה הקלאס Board, הוא רק צריך לדעת שהוא קיים. הוא למעשה מסמן אותו באופן מיוחד, שאומר "אני יודע שקיים סימבול שנקרא Board, אבל אני לא יודע מהו עדיין". אחרי הקומפילציה מתרחש שלב הקישור (link), שם הקומפיילר מקשר בין כל הסימבולים השונים - הוא מחפש בין כל הקבצים המקומפלים סימבול שנקרא Board, ושם אותו במקום המתאים. אם הוא לא מוצא כזה (נניח ששכחת לכלול את ההגדרה של Board בפרוייקט שלך) אז הוא יצעק.

פורסם
  • מחבר

הסתדרתי אם הבעיה הראשונה אבל עכשיו יש לי בעיה חדשה

כשאני רוצה להריץ מתוך הenemy את הפונקציה של BOARD ע"י המצביע הוא כותב לי

error c2027 ממה שהבנתי יש בעיה בהגדרה של הקלאס

ככה הגדרתי את השדה בBoard בתוך הEnemy

Board *boardPtr

ובקובץ enemy.cpp אני מנסה להריץ את השורה

boardPtr->draw()

פורסם

בקובץ enemy.cpp עשית אינקלוד ל-board.h?

תעלה את הקוד המלא.

פורסם
  • מחבר

אמרת לי שבמקום האינקלוד כותבים

class Board;

בכל אופן כרגע הקבצים נראים כך

board.h


#ifndef BOARDH
#define BOARDH
#include "enemy.h"


class Board
{
public:
Board();
void draw();

private:
Enemies *digger_Enemies ;
int numEnemies;
};
#endif

המימוש של draw זה הדפסות וזה עבד מקודם ככה שנראה לי שזה לא הבעיה בפונקציה הזו(אם יהיה צורך אני יעלה גם אותה)

enemy.h

#ifndef ENEMYH
#define ENEMYH


class Board;
class Enemy
{
public:
Enemy(Board *currB);
void f();
private:


int EnemyRow , EnemyCol;
Board *currentBoard;
};
#endif

[b]enemy.cpp[/b]

#include "enemy.h"

Enemy::Enemy(Board *currB)
{
currentBoard = currB;
}


void f()
{
currentBoard->draw(); //יש בעיה בשורה הזו בעיה שאובייקט הלוח לא מוגדר טוב
}

פורסם

בקובץ h אתה שם את class Board במקום האינקלוד.

בקובץ cpp עדיין צריך את האינקלוד.

שים לב להפרדה - בקובץ h, אתה מגדיר את Enemy כך שהוא רק צריך לדעת על קיומו של קלאס בשם Board, בלי לדעת איך הוא נראה ומה אפשר לעשות איתו. בקובץ cpp, אתה ממש משתמש בפונקציות של Board, ולכן אתה כן צריך לדעת איך הוא מתנהג, ולכן צריך לעשות שם אינקלוד.

בכל מקרה, עדיף להימנע כמה שאפשר מלעשות אינקלודים בקבצי h ולשים אותם בקבצי ה-cpp במקום (כמובן זה עניין של שיקול דעת).

פורסם
  • מחבר

השתגעתי קצת מכל האינקלודים, עשיתי אינקלוד לבורד בקובץ הH של enemy

וזה אכן מתקמפל מה שלא ברור לי עדיין בשביל מה אני צריך לעשות class Board; אם גם ככה אני יכתוב בקובץ ההדר או בקובץ הCPP

אינקלוד לboard.h

פורסם

זה הכל עניין של סדר.

בוא נניח שיש לנו שני קלאסים, A ו-B, שכל אחד מהם מכיל בתוכו מצביע לאחר.

A.h:


#ifndef A_H
#define A_H
class A {
public:
A();
void f();

private:
B* b;
};
#endif

B.h:


#ifndef B_H
#define B_H
class B {
public:
B();
void f();

private:
A* a;
};
#endif

ונניח שמוגדרים הקבצים הבאים שמממשים את הפונקציות של הקלאסים:

A.cpp:

#include "A.h"
#include "B.h"
A::A() {
b = new B();
}

void A::f() {
b->f();
}

B.cpp:

#include "B.h"
#include "A.h"
B::B() {
a = new A();
}

void B::f() {
// do something
}

עכשיו, אתה אומר לקומפיילר לקמפל. שים לב שקבצי ה-h לא מתקמפלים, אלא רק קבצי ה-cpp (קבצי ה-h מתקמפלים דרכם, בעקיפין, באמצעות ה-include).

הקומפיילר ניגש לקמפל את הקובץ A.cpp. קודם כל, הוא עושה include, כלומר הוא "מדביק" את כל הקוד שמופיע בקבצי h בתוך הקובץ. יוצא משהו כזה:

class A {
public:
A();
void f();

private:
B* b;
};

class B {
public:
B();
void f();

private:
A* a;
};

A::A() {
b = new B();
}

void A::f() {
b->f();
}

כיוון שב-A.cpp עשיתי קודם כל אינקלוד ל-A.h ואז ל-B.h, אז ההגדרה של A מופיעה קודם. הקומפיילר מתחיל לקמפל לפי הסדר. הוא מגיע לקלאס A, ושם הוא מגיע לשורה:

  B* b;

בעיה - הוא לא מכיר את הטיפוס B, ולכן לא יודע מה לעשות כאן, ולכן יזרוק שגיאה. מה הפתרון? להוסיף בתחילת A.h את השורה הבאה:

#include "B.h"

ואז ההגדרה של B תבוא לפני ההגדרה של A. אבל רגע.... אז הקוד (אחרי הכנסת האינקלודים) ייראה ככה:


class B {
public:
B();
void f();

private:
A* a;
};

class A {
public:
A();
void f();

private:
B* b;
};


A::A() {
b = new B();
}

void A::f() {
b->f();
}

ושוב, נתקענו - הקומפיילר מגיע לשורה

A* a;

ושוב לא יודע מה לעשות. אם נשים ב-B.h אינקלוד ל-A.h רק נחזור למצב הראשון.

לכן, הפתרון הוא forward declaration - לדאוג ש-A רק יידע שקיים קלאס בשם B (ולהיפך), ואז הבעיה תיפתר. נוסיף את השורה class A בתחילת B.h, ואת השורה class B בתחילת A.h, ואז אחרי האינקלודים הקוד ייראה ככה:


class B;

class A {
public:
A();
void f();

private:
B* b;
};

class A;
class B {
public:
B();
void f();

private:
A* a;
};

A::A() {
b = new B();
}

void A::f() {
b->f();
}

ועכשיו הכל טוב ויפה - כשהקומפיילר מגיע לקמפל את הקלאס A, הוא כבר יודע שקיים טיפוס שנקרא B, ולהיפך.

עכשיו, למה היינו צריכים לעשות ב-A.cpp אינקלוד גם ל-A.h וגם ל-B.h? נניח שהיינו עושים אינקלוד רק ל-A.h. אז הקוד היה נראה ככה:


class B;

class A {
public:
A();
void f();

private:
B* b;
};

A::A() {
b = new B();
}

void A::f() {
b->f();
}

יש כאן בעיה - הקומפיילר יודע שקיים קלאס שנקרא B, אבל הוא לא יודע איזה פונקציות יש לו. הקומפיילר לא מסוגל לקרוא ל-b->f, כי הוא לא יודע שבכלל ל-b יש פונקציה שנקראת f. לכן צריך לעשות אינקלוד ל-B.h בכל מקום שבו משתמשים בפונקציות של B.

נ.ב. מצטער על ההסבר הארוך, מקווה שעכשיו זה מובן יותר :)

פורסם
  • מחבר

דוגמא מעולה, הבנתי סוף סוף מה הולך שם תודה רבה שניצל :yelclap:

פורסם
  • מחבר


#ifndef BOARDH
#define BOARDH
#include "enemy.h"


class Board
{
public:
Board();
void draw();

private:
Enemies *digger_Enemies ;
int numEnemies;
};
#endif

יש לי עוד שאלה קצרה

איך אני מאתחל את השדה digger_Enemies* כאשר הבנאי של האוייב אמור לקבל מצביע ללוח

הבנאי של האוייב


Enemy(Board *currB);

ניסיתי אם הרבה צורות אבל שום דבר לא עובד

הגודל של המערך digger_Enemies אמור להיות בגודל numEnemies

פורסם

אפשר ליצור מערך דינמי רק של טיפוסים שיש להם בנאי דיפולטי (כזה שלא מקבל פרמטרים).

בשביל זה יש לך שתי אופציות:

תוסיף ל-Enemy בנאי דיפולטי, ופונקציית setBoard (שתקרא לה אחרי שתיצור את כל האובייקטים מטיפוס Enemy)

או שתשתמש במערך של מצביעים ל-Enemy, ואז תאתחל כל אחד מהם בנפרד ע"י new (וכמובן תדאג אחר כך למחוק כל אחד בנפרד ע"י delete, בנוסף למחיקת המערך).

(יש עוד פתרונות)

פורסם
  • מחבר

בהנחה שאני אשתמש בפתרון הראשון זה אמור להיות משהו כזה:


Board::Board()
{
digger_Enemies = new Enemy()[numEnemies];
for(int i=0;i<numEnemies;i++)
digger_Enemies[i].setBoard(this);
}

לא הבנתי כל כך את הפתרון השני אני מגדיר מערך של מצביעים לאובייקט מסוג אוייב Enemy **digger_Enemies ואז פשוט אני רושם


Board::Board()
{
digger_Enemies = new Enemy*[numEnemies];
for(int i=0;i<numEnemies;i++)
{
digger_Enemies[i] = new Enemy();
digger_Enemies[i].setBoard(this);
}
}

בנוגע להערה האחרונה שלך אתה מתכוון לדסטראקטור של הenemy, לשכתב אותו בגלל ההקצאות?

ואיזה עוד פתרונות יכולים לפתור את הבעיה?

פורסם

הפתרון הראשון שלך כמעט נכון (לא צריך סוגריים אחרי Enemy, וכשיוצרים אובייקט ב-new עם בנאי דיפולטי אז לא שמים סוגריים).

בחלק השני בלבלת את שני הפתרונות ביחד - אם אתה משתמש במצביעים, לא צריך בנאי דיפולטי ו-setBoard.

במקרה השני, איפה שאתה עושה delete[] digger_Enemies, אתה צריך לדאוג קודם לעשות delete לכל אחד מהאיברים של המערך בנפרד.

ארכיון

דיון זה הועבר לארכיון ולא ניתן להוסיף בו תגובות חדשות.

דיונים חדשים