سؤال

أنا معتاد على القيام بكل ما عندي من الترميز في ملف C واحد.ومع ذلك، فأنا أعمل على مشروع كبير بدرجة كافية بحيث يصبح القيام بذلك غير عملي.لقد قمت بتضمينها معًا ولكن واجهت حالات قمت فيها بتضمين بعض الملفات عدة مرات، وما إلى ذلك.لقد سمعت عن ملفات ‎.h، لكنني لست متأكدًا من وظيفتها (أو لماذا يكون وجود ملفين أفضل من ملف واحد).

ما هي الاستراتيجيات التي يجب أن أستخدمها لتنظيم الكود الخاص بي؟هل من الممكن فصل الوظائف "العامة" عن الوظائف "الخاصة" لملف معين؟

هذا السؤال عجل استفساري.لا يشير ملف Tea.h إلى ملف Tea.c.هل "يعرف" المترجم أن كل ملف ‎.h له ملف ‎.c مطابق؟

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

المحلول

يجب عليك اعتبار ملفات .h كـ ملفات الواجهة من ملف .c الخاص بك.يمثل كل ملف .c وحدة تحتوي على قدر معين من الوظائف.إذا تم استخدام الوظائف الموجودة في ملف .c بواسطة وحدات أخرى (أي.ملفات .c الأخرى) ضع النموذج الأولي للوظيفة في ملف الواجهة .h.من خلال تضمين ملف الواجهة في ملف .c للوحدات النمطية الأصلية وكل ملف .c آخر تحتاج إلى الوظيفة فيه، فإنك تجعل هذه الوظيفة متاحة للوحدات النمطية الأخرى.

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

وينطبق الشيء نفسه على المتغيرات المستخدمة عبر وحدات متعددة.يجب أن يدخلوا إلى ملف الرأس ويجب عليهم وضع علامة عليه بالكلمة الأساسية "extern" هناك.ملحوظة:بالنسبة للوظائف، تعد الكلمة الأساسية "extern" اختيارية.تعتبر الوظائف دائمًا "خارجية".

تساعد حراس التضمين في ملفات الرأس على عدم تضمين نفس ملف الرأس عدة مرات.

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

الوحدة 1.ج:

    #include "Module1.h"

    static void MyLocalFunction(void);
    static unsigned int MyLocalVariable;    
    unsigned int MyExternVariable;

    void MyExternFunction(void)
    {
        MyLocalVariable = 1u;       

        /* Do something */

        MyLocalFunction();
    }

    static void MyLocalFunction(void)
    {
      /* Do something */

      MyExternVariable = 2u;
    }

الوحدة 1.ح:

    #ifndef __MODULE1.H
    #define __MODULE1.H

    extern unsigned int MyExternVariable;

    void MyExternFunction(void);      

    #endif

الوحدة 2.ج

    #include "Module.1.h"

    static void MyLocalFunction(void);

    static void MyLocalFunction(void)
    {
      MyExternVariable = 1u;
      MyExternFunction();
    }

نصائح أخرى

حاول أن تجعل كل .c يركز على مجال معين من الوظائف.استخدم ملف .h المطابق للإعلان عن هذه الوظائف.

يجب أن يحتوي كل ملف ‎.h على "رأس" حول محتواه.على سبيل المثال:

#ifndef ACCOUNTS_H
#define ACCOUNTS_H
....
#endif

وبهذه الطريقة يمكنك تضمين "accounts.h" عدة مرات كما تريد، وستكون المرة الأولى التي يتم رؤيتها في وحدة تجميع معينة هي المرة الوحيدة التي تسحب محتواها فعليًا.

مترجم

يمكنك رؤية مثال على "وحدة" C في هذا الموضوع - لاحظ أن هناك ملفين - العنوان Tea.h، والكود Tea.c.أنت تعلن عن جميع التعريفات العامة والمتغيرات ونماذج الوظائف التي تريد أن تصل إليها البرامج الأخرى في الرأس.في مشروعك الرئيسي، ستقوم بتضمين هذا الكود ويمكنه الآن الوصول إلى وظائف ومتغيرات وحدة الشاي المذكورة في الرأس.

يصبح الأمر أكثر تعقيدًا قليلاً بعد ذلك.إذا كنت تستخدم Visual Studio والعديد من بيئات التطوير المتكاملة (IDEs) الأخرى التي تدير التصميم الخاص بك نيابةً عنك، فتجاهل هذا الجزء - فهو يهتم بتجميع الكائنات وربطها.

رابط

عندما تقوم بتجميع ملفين منفصلين بلغة C، يقوم المترجم بإنتاج ملفات كائنات فردية - بحيث يصبح main.c هو main.o، وtea.c يصبح Tea.o.تتمثل مهمة الرابط في النظر في جميع ملفات الكائنات (main.o وtea.o)، ومطابقة المراجع - لذلك عندما تستدعي دالة Tea في main، يقوم الرابط بتعديل هذا الاستدعاء بحيث يستدعي بالفعل اليمين وظيفة في الشاي.الرابط ينتج الملف القابل للتنفيذ.

هناك تعليمي عظيم يتعمق أكثر في هذا الموضوع، بما في ذلك النطاق والمشكلات الأخرى التي ستواجهها.

حظ سعيد!

-آدم

بضع قواعد بسيطة للبدء:

  1. ضع تلك الإعلانات التي تريد جعلها "عامة" في ملف الرأس لملف تنفيذ C الذي تقوم بإنشائه.
  2. فقط # تضمين ملفات الرأس في ملف C اللازمة لتنفيذ ملف C.
  3. قم بتضمين ملفات الرأس في ملف رأس فقط إذا كان ذلك مطلوبًا للإعلانات داخل ملف الرأس هذا.

  4. استخدم طريقة الحماية المضمنة التي وصفها Andrew أو استخدمها #براغما مرة واحدة إذا كان المترجم يدعمه (والذي يفعل نفس الشيء - وأحيانًا أكثر كفاءة)

للإجابة على سؤالك الإضافي:

هذاالسؤال عجل استفساري.لا يشير ملف tea.h إلى ملف tea.c.هل يعرف المترجم "أن كل ملف .H يحتوي على ملف .c المقابل؟

لا يهتم المترجم بشكل أساسي بملفات الرأس.يقوم كل استدعاء للمترجم بترجمة ملف مصدر (.c) إلى ملف كائن (.o).خلف الكواليس (أي.في ال make ملف أو ملف مشروع) يتم إنشاء سطر أوامر مكافئ لهذا:

compiler --options tea.c

الملف المصدر #includeجميع ملفات الرأس للموارد التي تشير إليها، وهذه هي الطريقة التي يعثر بها المترجم على ملفات الرأس.

(أنا أتطرق إلى بعض التفاصيل هنا.هناك الكثير لنتعلمه عن بناء مشاريع C.)

بالإضافة إلى الإجابات المقدمة أعلاه، هناك ميزة صغيرة لتقسيم التعليمات البرمجية الخاصة بك إلى وحدات (ملفات منفصلة) وهي أنه إذا كان عليك الحصول على أي متغيرات عامة، فيمكنك قصر نطاقها على وحدة واحدة عن طريق استخدام الكلمة الرئيسية ' ثابتة'.(يمكنك أيضًا تطبيق هذا على الوظائف).لاحظ أن هذا الاستخدام لـ "static" يختلف عن استخدامه داخل دالة.

يوضح سؤالك أنك لم تقم بالكثير من التطوير الجاد.الحالة المعتادة هي أن التعليمات البرمجية الخاصة بك ستكون بشكل عام كبيرة جدًا بحيث لا يمكن وضعها في ملف واحد.القاعدة الجيدة هي أنه يجب عليك تقسيم الوظيفة إلى وحدات منطقية (ملفات .c) ويجب ألا يحتوي كل ملف على أكثر مما يمكنك الاحتفاظ به بسهولة في رأسك في وقت واحد.

يتضمن منتج البرنامج بشكل عام المخرجات من العديد من ملفات .c المختلفة.الطريقة التي يتم بها ذلك عادةً هي أن المترجم ينتج عددًا من ملفات الكائنات (في ملفات ".o" الخاصة بأنظمة Unix، يقوم VC بإنشاء ملفات ‎.obj).الغرض من "الرابط" هو إنشاء ملفات الكائنات هذه في الإخراج (إما مكتبة مشتركة أو ملف قابل للتنفيذ).

بشكل عام، تحتوي ملفات التنفيذ (.c) على تعليمات برمجية فعلية قابلة للتنفيذ، بينما تحتوي ملفات الرأس (.h) على إعلانات الوظائف العامة في ملفات التنفيذ تلك.يمكنك بسهولة الحصول على ملفات رأس أكثر من ملفات التنفيذ، وفي بعض الأحيان يمكن أن تحتوي ملفات الرأس على تعليمات برمجية مضمّنة أيضًا.

من غير المعتاد بشكل عام أن تتضمن ملفات التنفيذ بعضها البعض.من الممارسات الجيدة التأكد من أن كل ملف تنفيذ يفصل اهتماماته عن الملفات الأخرى.

أنصحك بتنزيل مصدر Linux kernel وإلقاء نظرة عليه.إنه ضخم جدًا بالنسبة لبرنامج C، ولكنه منظم جيدًا في مجالات منفصلة من الوظائف.

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

على سبيل المثال، عندما #include <stdio.h>, ، وهذا يوفر النماذج الأولية لـ printf ووظائف الإدخال والإخراج الأخرى.عادةً ما يتم تحميل رموز هذه الوظائف بواسطة المترجم افتراضيًا.يمكنك الاطلاع على ملفات .h الخاصة بالنظام ضمن /usr/include إذا كنت مهتمًا بالمصطلحات العادية المرتبطة بهذه الملفات.

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

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