عند كتابة المشاريع في C ++ ، تكون إدارة تجزئة الملفات ضرورية للغاية. اليوم ، سيقدم لك محرر قناة Foxin Technology شرحًا مفصلاً لملفات الرأس وملفات المصدر في C ++. آمل أن يكون من المفيد لك أن تتعلم هذه المعرفة!
شرح مفصل لملفات الرأس وملفات المصدر في C ++
1. وضع تجميع C ++
بشكل عام ، في برنامج C ++ ، لا يوجد سوى نوعان من الملفات - ملف .cpp وملف .h. من بينها ، يسمى ملف .CPP ملف مصدر C ++ ، ويتم وضع رمز المصدر لـ C ++ فيه ؛ بينما يسمى ملف .H ملف رأس C ++ ، ويتم وضع رمز المصدر لـ C ++ فيه.
تدعم لغة C ++ "تجميع منفصل". بمعنى آخر ، يمكن تقسيم جميع محتويات البرنامج إلى أجزاء مختلفة ووضعها في ملفات .CPP مختلفة. الأشياء في ملف .cpp مستقل نسبيا. عند التجميع (تجميع) ، لا تحتاج إلى التواصل مع الملفات الأخرى. تحتاج فقط إلى الارتباط مع الملفات المستهدفة الأخرى بعد تجميعها في الملف الهدف. على سبيل المثال ، يتم تعريف الدالة العالمية "void a () {}" في الملف A.CPP ، ويجب استدعاء هذه الوظيفة في الملف B.CPP. ومع ذلك ، فإن الملفات A.CPP و B.CPP لا تحتاج إلى معرفة وجود بعضهما البعض ، ولكن يمكنها تجميعها بشكل منفصل. بعد تجميعها في الملف المستهدف ، ربطها ، ويمكن تشغيل البرنامج بأكمله.
كيف يتم تحقيق هذا؟ من منظور كتابة البرامج ، يكون الأمر بسيطًا للغاية. في الملف B.CPP ، قبل استدعاء وظيفة "void a ()" ، أعلن الوظيفة "void a () ؛" أولاً. وذلك لأن المترجم سيقوم بإنشاء جدول رمز عند تجميع B.CPP. سيتم تخزين رموز مثل "void a ()" التي لا يمكن رؤيتها في هذا الجدول. عند الارتباط مرة أخرى ، سيبحث المترجم عن تعريف هذا الرمز في ملفات الكائنات الأخرى. بمجرد العثور عليها ، يمكن إنشاء البرنامج بسلاسة.
لاحظ أن هناك مفهومين مذكوران هنا ، أحدهما "تعريف" والآخر هو "إعلان". ببساطة ، "التعريف" يعني وصف رمز بطريقة كاملة وكاملة: سواء كان متغيرًا أو وظيفة ، ما هو النوع الذي يعيده ، وما هي المعلمات التي يحتاجها ، إلخ. "الإعلان" يعلن عن وجود هذا الرمز ، أي ، يخبر المترجم بأن هذا الرمز محدد في ملفات أخرى. سأستخدمها أولاً. عند الارتباط ، انتقل إلى مكان آخر لمعرفة ما هو عليه. عند التعريف ، يجب عليك تحديد رمز (متغير أو وظيفة) تمامًا وفقًا لبناء C ++ ، وعند الإعلان ، تحتاج فقط إلى كتابة النموذج الأولي لهذا الرمز. تجدر الإشارة إلى أنه يمكن إعلان الرمز عدة مرات خلال البرنامج ، ولكن يجب تعريفه مرة واحدة فقط. فقط تخيل ، إذا كان هناك تعريفان مختلفان للرمز ، فمن يجب أن يستمع المترجم؟
تجلب هذه الآلية العديد من الفوائد لمبرمجي C ++ ، وتؤدي أيضًا إلى طريقة لكتابة البرامج. ضع في اعتبارك أنه إذا كانت هناك وظيفة شائعة الاستخدام "void f () {}" سيتم استدعاؤها في العديد من ملفات .cpp في البرنامج بأكمله ، فحيننا فقط بحاجة إلى تحديد هذه الوظيفة في ملف واحد وإعلان هذه الوظيفة في ملفات أخرى. من السهل التعامل مع الوظيفة ، وهذا يعني فقط جملة واحدة لإعلانها. ومع ذلك ، ماذا لو كان هناك الكثير من الوظائف ، مثل مجموعة من الوظائف الرياضية ، هناك المئات منها؟ هل يمكن لكل مبرمج أن يتأكد بدقة وتكتب جميع الوظائف في شكلها؟
2. ما هو ملف الرأس
من الواضح أن الجواب مستحيل. ولكن هناك طريقة بسيطة للغاية لمساعدة المبرمجين على حفظ مشكلة في تذكر العديد من النماذج الأولية للوظائف: يمكننا كتابة جميع بيانات الإعلان لمئات الوظائف أولاً ووضعها في ملف. عندما يحتاج المبرمج إليهم ، انسخ كل هذه الأشياء في رمز المصدر الخاص به.
هذه الطريقة ممكنة بالتأكيد ، لكنها لا تزال مزعجة للغاية ويبدو أنها خرقاء. لذلك ، يمكن أن يلعب ملف الرأس دوره. يحتوي ملف الرأس المزعوم بالفعل على نفس المحتوى مثل المحتوى في ملف .cpp ، وكلاهما هو رمز مصدر C ++. لكن ملف الرأس لا يحتاج إلى تجميع. وضعنا جميع إعلانات الوظيفة في ملف رأس. عندما يحتاج ملف مصدر .CPP إلى إدراجها ، يمكن تضمينها في ملف .CPP من خلال أمر ماكرو "#include" ، بحيث يتم دمج محتوياتها في ملف .cpp. عند تجميع ملف .cpp ، يتم تشغيل وظائف هذه الملفات .H.
دعونا نعطي مثالا. افترض أن جميع الوظائف الرياضية تحتوي على اثنين فقط: F1 و F2 ، ثم وضعنا تعريفاتهما في Math.cpp:
/ * math.cpp */double f1 () {// افعل شيئًا هنا ... return ؛} double f2 (double a) {// افعل شيئًا هنا ... إرجاع a * a ؛}/ * نهاية Math.cpp */ووضع إعلان وظائف "تلك" في ملف رأس Math.H:
/ * Math.H */Double F1 () ؛ مزدوج F2 (مزدوج) ؛/ * نهاية Math.H */
في ملف آخر main.cpp ، أريد أن أتصل بهاتين وظيفتين ، لذلك أحتاج فقط إلى تضمين ملف الرأس:
/ * main.cpp */#تشمل "math.h" main () {int number1 = f1 () ؛ int number2 = f2 (number1) ؛}/ * end of main.cpp */هذا برنامج كامل. تجدر الإشارة إلى أن ملف .h لا يلزم كتابته بعد أمر المترجم ، ولكن يجب العثور عليه في المكان الذي يمكن أن يجده فيه المترجم (على سبيل المثال ، في نفس الدليل مثل Main.cpp). يمكن تجميع Main.cpp و Math.cpp لإنشاء main.o و math.o على التوالي ، ثم ربط هذين ملف الكائن ، ويمكن تشغيل البرنامج.
3. #include
#Include هو أمر ماكرو من لغة C التي تعمل قبل تجميع المترجم ، أي عندما يكون مسبقًا. الغرض من #include هو تضمين محتوى الملف المكتوب بعده في الملف الحالي. تجدر الإشارة إلى أنه لا يوجد لديه وظائف أخرى أو وظائف فرعية. تتمثل وظيفتها في استبدال كل مكان يظهر فيه بمحتوى الملف الذي يكتب خلفه. استبدال النص البسيط ، لا شيء آخر. لذلك ، سيتم استبدال الجملة الأولى في ملف main.cpp (#include "Math.h") بمحتويات ملف Math.H قبل التجميع. أي عندما تكون عملية التجميع على وشك البدء ، تغير محتوى Main.CPP:
/ * ~ main.cpp */double f1 () ؛ double f2 (double) ؛ main () {int number1 = f1 () ؛ int number2 = f2 (number1) ؛}/ * end of ~ main.cpp */لا أكثر أو أقل ، فقط الحق. وبالمثل ، إذا استخدمنا وظائف F1 و F2 بالإضافة إلى Main.cpp ، فنحن بحاجة فقط إلى كتابة جملة #include "Math.h" قبل استخدام هاتين الوظيفتين.
4. ما ينبغي كتابته في ملف الرأس
من خلال المناقشة أعلاه ، يمكننا أن نفهم أن وظيفة ملف الرأس سيتم تضمينها في .CPPs الأخرى. إنهم أنفسهم لا يشاركون في التجميع ، ولكن في الواقع ، يتم تجميع محتوياتهم في ملفات .CPP متعددة. من خلال قاعدة "التعريف يمكن أن يكون مرة واحدة فقط" ، يمكننا بسهولة أن نستنتج أنه يجب وضع إعلانات المتغيرات والوظائف فقط في ملف الرأس ، ولا ينبغي وضع تعريفاتها. نظرًا لأنه سيتم بالفعل تقديم محتويات ملف الرأس في ملفات .CPP مختلفة متعددة ، وسيتم تجميعها جميعًا. من الجيد بالطبع وضع إعلان. إذا وضعت تعريفًا ، فهو يعادل تعريف رمز (متغير أو دالة) يظهر في ملفات متعددة. على الرغم من أن هذه التعريفات هي نفسها ، إلا أنه ليس من القانوني للمترجم أن يفعل ذلك.
لذلك ، هناك شيء واحد يجب أن تتذكره هو أنه في ملف رأس .H ، لا يمكن أن يكون هناك سوى متغيرات أو وظائف إعلانات ، ولا تضع تعريفات. وهذا هو ، جمل مثل: extern int a ؛ و void f () ؛ لا يمكن كتابتها إلا في ملف الرأس. هذه هي العبارات. إذا كتبت جملة مثل int a ؛ أو void f () {} ، ثم بمجرد تضمين ملف الرأس في ملفين أو أكثر. CPP ، سيقوم برنامج التحويل البرمجي على الفور بالإبلاغ عن خطأ. (حول Extern ، تمت مناقشته من قبل ، ولن تتم مناقشة الفرق بين التعريف والإعلان هنا.) ومع ذلك ، هناك ثلاثة استثناءات لهذه القاعدة.
1. يمكن كتابة تعريف كائن const في ملف الرأس. نظرًا لأن كائن Const العالمي لم يتم الإعلان عنه بواسطة Extern بشكل افتراضي ، فهو صالح فقط في الملف الحالي. اكتب مثل هذا الكائن في ملف رأس ، حتى لو تم تضمينه في ملفات .CPP أخرى متعددة ، يكون الكائن صالحًا فقط في الملف الذي يحتوي عليه وغير مرئي للملفات الأخرى ، لذلك لن يؤدي إلى تعريفات متعددة. في الوقت نفسه ، نظرًا لأن الكائنات الموجودة في ملفات .CPP هذه يتم تضمينها من ملف الرأس ، فإن هذا يضمن أن قيم كائنات const في ملفات .cpp هذه هي نفسها ، والتي يمكن قولها لقتل عصفورين بحجر واحد. وبالمثل ، يمكن أيضًا وضع تعريف الكائن الثابت في ملف الرأس.
2. يمكن كتابة تعريف الوظائف المضمنة في ملف الرأس. نظرًا لأن الوظيفة المضمنة تتطلب من التحويل البرمجي مضمّن وفقًا لتعريفه حيث يواجهه ، بدلاً من مجرد وظيفة عادية يمكن إعلانها أولاً ثم ربطها (لن يتم ربط الوظائف المضمنة) ، يحتاج المترجم إلى رؤية التعريف الكامل للوظيفة المضمنة أثناء التجميع. إذا كان لا يمكن تعريف وظيفة مضمنة إلا مرة واحدة مثل الوظيفة العادية ، فسيكون من الصعب القيام بذلك. لأنه على ما يرام في ملف ، يمكنني كتابة تعريف وظيفة الخط المضمّن في البداية ، حتى أتمكن من رؤية التعريف عند استخدامه لاحقًا ؛ ولكن ماذا لو استخدمت هذه الوظيفة في ملفات أخرى؟ لا يوجد حل جيد تقريبًا لهذا ، لذلك ينص C ++ على أنه يمكن تعريف الوظائف المضمنة عدة مرات في البرنامج. طالما تظهر الوظيفة المضمنة مرة واحدة فقط في ملف .cpp ، وفي جميع ملفات .cpp ، يكون تعريف هذه الوظيفة المضمنة هو نفسه ، ويمكن تجميعه. ثم من الواضح ، من الحكمة للغاية وضع تعريف الوظائف المضمنة في ملف رأس.
3. يمكن كتابة تعريف الفصل في ملف الرأس. لأنه عند إنشاء كائن فئة في برنامج ما ، لا يمكن للمترجم إلا أن يعرف كيف يجب وضع كائن هذه الفئة عندما يكون تعريف هذه الفئة مرئيًا تمامًا ، وبالتالي فإن متطلبات تعريف الفئة تكون في الأساس مثل الوظائف المضمنة. لذلك ، من الممارسات الجيدة وضع تعريف الفئة في ملف الرأس وتضمين ملف الرأس في ملف .cpp المستخدم في هذه الفئة. هنا ، تجدر الإشارة إلى أن تعريف الفصل يحتوي على أعضاء البيانات وأعضاء الوظيفة. لن يتم تعريف أعضاء البيانات حتى يتم إنشاء الكائن المحدد (المساحة المخصصة) ، ولكن يجب تحديد أعضاء الوظيفة من البداية ، وهو ما نسميه عادةً تنفيذ الفصل. بشكل عام ، يتمثل نهجنا في وضع تعريف الفئة في ملف الرأس ، ووضع رمز التنفيذ لعضو الوظيفة في ملف .cpp. هذا جيد وطريقة جيدة. ومع ذلك ، هناك طريقة أخرى. وهذا هو كتابة رمز التنفيذ مباشرة لأعضاء الوظيفة في تعريف الفصل. في فئات C ++ ، إذا تم تعريف أعضاء الوظيفة في هيئة تعريف الفئة ، فسوف يعتبر المترجم الوظيفة على أنها مضمنة. لذلك ، من القانوني كتابة تعريف أعضاء الوظيفة في هيئة تعريف الفصل ووضعهم معًا في ملف الرأس. لاحظ أنه من غير القانوني كتابة تعريف عضو الوظيفة في ملف الرأس لتعريف الفئة وليس في تعريف الفئة ، لأن عضو الوظيفة ليس مضمنًا في هذا الوقت. بمجرد تضمين ملف الرأس في ملفين أو أكثر ، يتم إعادة تعريف عضو الوظيفة هذا.
5. تدابير الحماية في ملفات الرأس
ضع في اعتبارك أنه إذا كان ملف الرأس يحتوي فقط على بيانات الإعلان ، فسيكون ذلك جيدًا إذا تم تضمينه في نفس ملف .cpp عدة مرات - لأن حدوث بيانات الإعلان غير مقيد. ومع ذلك ، فإن الاستثناءات الثلاثة في ملفات الرأس التي تمت مناقشتها أعلاه هي أيضًا استخدام شائع جدًا لملفات الرأس. بعد ذلك ، بمجرد ظهور أي من الاستثناءات الثلاثة أعلاه في ملف رأس ويتم تضمينه عدة مرات بواسطة .cpp ، ستكون المشكلة كبيرة. لأنه على الرغم من أن عناصر بناء الجملة في هذه الاستثناءات الثلاثة "يمكن تعريفها في ملفات مصدر متعددة" ، إلا أنه يمكن أن تظهر فقط مرة واحدة في ملف مصدر واحد ". تخيل أنه إذا كان AH يحتوي على تعريف الفئة A و BH يحتوي على تعريف الفئة B. نظرًا لأن تعريف الفئة B يعتمد على الفئة A ، فإن AH أيضًا في BH. يوجد الآن ملف مصدر يستخدم كل من الفئة A والفئة B ، لذلك يتضمن المبرمج كل من AH و BH في هذا الملف المصدر. في هذا الوقت ، تنشأ المشكلة: يظهر تعريف الفئة A مرتين في هذا الملف المصدر! لذلك لا يمكن تجميع البرنامج بأكمله. قد تعتقد أنه كان خطأ المبرمج - يجب أن يعلم أن BH يحتوي على AH - ولكن في الواقع لا ينبغي له ذلك.
يمكن أن يؤدي استخدام "#Define" مع التجميع الشرطي إلى حل هذه المشكلة بشكل جيد. في ملف الرأس ، يتم تعريف الاسم من خلال #Define ، و #ifndef ... #يتم تجميعه بشكل مشروط بحيث يمكن للمترجم أن يقرر ما إذا كان سيستمر في تجميع المحتوى اللاحق في الرأس بناءً على ما إذا كان الاسم محدد. على الرغم من أن هذه الطريقة بسيطة ، يجب أن تتذكر كتابتها عند كتابة ملف الرأس.
شكرا لك على قراءة هذا المقال. آمل أن يساعدك التفسير التفصيلي لملفات الرأس والملفات المصدر في C ++ في هذه المقالة. شكرا لك على دعمك من شبكة القناة التكنولوجية الجديدة!