سؤال

لدي ملفان رئيسيان يتلخصان في:

شجرة.ح:

#include "element.h"

typedef struct tree_
{
    struct *tree_ first_child;
    struct *tree_ next_sibling;
    int tag;
    element *obj;
    ....
} tree;

والعنصر ح:

#include "tree.h"

typedef struct element_
{
    tree *tree_parent;
    char *name;
    ...
} element;

تكمن المشكلة في أن كلاهما يشير إلى الآخر، لذا تحتاج الشجرة إلى عنصر ليتم تضمينه، ويحتاج العنصر إلى شجرة ليتم تضمينه.

هذا لا يعمل لأنه لتحديد بنية "الشجرة"، يجب أن تكون بنية العنصر معروفة بالفعل، ولكن لتحديد بنية العنصر، يجب أن تكون بنية الشجرة معروفة.

كيفية حل هذه الأنواع من الحلقات (أعتقد أن هذا قد يكون له علاقة بـ "الإعلان الأمامي"؟)؟

هل كانت مفيدة؟

المحلول

أعتقد أن المشكلة هنا ليست في غياب الحماية ولكن في حقيقة أن الهيكلين يحتاجان إلى بعضهما البعض في تعريفهما.لذلك فهو نوع يحدد مشكلة هان والبيضة.

طريقة حل هذه المشكلات في C أو C++ هي إجراء إعلانات أمامية على النوع.إذا أخبرت المترجم أن العنصر عبارة عن بنية من نوع ما، فسيكون المترجم قادرًا على إنشاء مؤشر إليه.

على سبيل المثال

داخل الشجرة.ح:

// tell the compiler that element is a structure typedef:
typedef struct element_ element;

typedef struct tree_ tree;
struct tree_
{
    tree *first_child;
    tree *next_sibling;
    int tag;

    // now you can declare pointers to the structure.
    element *obj;
};

بهذه الطريقة لن تضطر إلى تضمين element.h داخل Tree.h بعد الآن.

يجب عليك أيضًا وضع حراس التضمين حول ملفات الرأس الخاصة بك أيضًا.

نصائح أخرى

الملاحظة الحاسمة هنا هي أن العنصر لا يحتاج إلى معرفة بنية الشجرة، لأنه يحمل فقط مؤشرًا إليها.الشيء نفسه بالنسبة للشجرة.كل ما يحتاج الجميع إلى معرفته هو أن هناك نوعًا يحمل الاسم ذي الصلة، وليس ما بداخله.

لذلك في Tree.h، بدلاً من:

#include "element.h"

يفعل:

typedef struct element_ element;

هذا "يعلن" عن النوعين "element" و"struct element_" (يقول إنهما موجودان)، لكنه لا "يحددهما" (يذكر ما هما).كل ما تحتاجه لتخزين مؤشر إلى blah هو الإعلان عن blah، وليس تعريفه.فقط إذا كنت تريد احترامه (على سبيل المثال، قراءة الأعضاء)، فأنت بحاجة إلى التعريف.يجب أن يقوم الكود الموجود في ملف ".c" الخاص بك بذلك، لكن في هذه الحالة لا تقوم رؤوسك بذلك.

يقوم بعض الأشخاص بإنشاء ملف رأس واحد يعلن عن جميع الأنواع الموجودة في مجموعة من الرؤوس، ثم يتضمن كل رأس ذلك، بدلاً من تحديد الأنواع التي يحتاجها بالفعل.هذا ليس ضروريًا ولا غبيًا تمامًا.

الإجابات المتعلقة بالحراس المتضمنين خاطئة - إنها فكرة جيدة بشكل عام، ويجب أن تقرأ عنها وتحصل على بعضها، ولكنها لا تحل مشكلتك على وجه الخصوص.

الإجابة الصحيحة هي استخدام تضمين الحراس واستخدام الإعلانات الأمامية.

تشمل الحرس

/* begin foo.h */
#ifndef _FOO_H
#define _FOO_H

// Your code here

#endif
/* end foo.h */

يدعم Visual C++ أيضًا #pragma مرة واحدة.إنه توجيه غير قياسي للمعالج المسبق.في مقابل إمكانية نقل المترجم، يمكنك تقليل احتمالية تضارب أسماء المعالجات المسبقة وزيادة إمكانية القراءة.

التصريحات إلى الأمام

إلى الأمام أعلن عن الهياكل الخاصة بك.إذا لم تكن هناك حاجة واضحة لأعضاء البنية أو الفئة، فيمكنك الإعلان عن وجودهم في بداية ملف الرأس.

struct tree;    /* element.h */
struct element; /* tree.h    */

أقرأ عن التصريحات إلى الأمام.

أي.


// tree.h:
#ifndef TREE_H
#define TREE_H
struct element;
struct tree
{
    struct element *obj;
    ....
};

#endif

// element.h:
#ifndef ELEMENT_H
#define ELEMENT_H
struct tree;
struct element
{
    struct tree *tree_parent;
    ...
};
#endif

يعتبر تضمين الحراس مفيدًا، لكنه لا يعالج مشكلة الملصق وهي الاعتماد المتكرر على بنيتين للبيانات.

الحل هنا هو إعلان الشجرة و/أو العنصر كمؤشرات للبنيات داخل ملف الرأس، لذلك لا تحتاج إلى تضمين .h

شيء مثل:

struct element_;
typedef struct element_ element;

في الجزء العلوي من Tree.h يجب أن يكون كافيًا لإزالة الحاجة إلى تضمين element.h

مع إعلان جزئي مثل هذا، يمكنك فقط القيام بالأشياء باستخدام مؤشرات العناصر التي لا تتطلب من المترجم معرفة أي شيء عن التخطيط.

IMHO أفضل طريقة هي تجنب مثل هذه الحلقات لأنها علامة على الانقلاب الجسدي الذي يجب تجنبه.

مثلا (على ما أذكر) "الاستدلال على التصميم الشيئي" الغرض هو تجنب تضمين الحراس لأنها تخفي فقط التبعية الدورية (المادية).

هناك طريقة أخرى تتمثل في الإعلان المسبق عن الهياكل مثل هذا:

element.h:
struct tree_;
struct element_
  {
    struct tree_ *tree_parent;
    char *name;
  };

شجرة.ح:عنصر الهيكل_؛struct tree_ {struct tree_* first_child ؛شجرة البناء_* next_sibling;علامة كثافة العمليات؛struct element_ *obj;};

الإعلان الأمامي هو الطريقة التي يمكنك من خلالها ضمان وجود نوع من الهيكل الذي سيتم تحديده لاحقًا.

لا أحب التصريحات الأمامية لأنها زائدة عن الحاجة وعربات التي تجرها الدواب.إذا كنت تريد أن تكون جميع إعلاناتك في نفس المكان، فيجب عليك استخدام ملفات التضمين وملفات الرأس مع حراس التضمين.

يجب أن تفكر في التضمين كنسخ ولصق، عندما يعثر معالج لغة البرمجة المسبق على سطر #include، فإنه يضع محتوى myheader.h بالكامل في نفس الموقع الذي تم العثور فيه على سطر #include.

حسنًا، إذا كتبت تضمين الحراس، فسيتم لصق كود myheader.h مرة واحدة فقط حيث تم العثور على أول #include.

إذا كان برنامجك يجمع مع عدة ملفات كائنات واستمرت المشكلة، فيجب عليك استخدام الإعلانات الأمامية بين ملفات الكائنات (يشبه استخدام extern) للاحتفاظ فقط بتعريف النوع لجميع ملفات الكائنات (يمزج المترجم جميع الإعلانات في نفس الجدول ويجب أن تكون المعرفات كن فريدا).

الحل البسيط هو عدم وجود ملفات رأس منفصلة.بعد كل شيء، إذا كانا يعتمدان على بعضهما البعض فلن تستخدم أحدهما دون الآخر، فلماذا تفصلهما؟يمكن أن يكون لديك ملفات .c منفصلة يستخدم كلاهما نفس الرأس ولكنهما يوفران وظائف أكثر تركيزًا.

أعلم أن هذا لا يجيب على سؤال حول كيفية استخدام جميع العناصر الفاخرة بشكل صحيح، ولكنني وجدت ذلك مفيدًا عندما كنت أبحث عن حل سريع لمشكلة مماثلة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top