في C++، غالبًا ما تُفصل التصريحات عن التعريفات. تُجمع التصريحات في ما يُعرف بملفات الترويسة، بينما توضع التنفيذات المقابلة في ملفات المصدر. يمكنك أن تنظر إلى ملفات الترويسة على أنها API. سيخبرك ملف الترويسة بـ_ما_ يمكن لقاعدة الكود أن تقدمه، دون الخوض في تفاصيل كيف تفعل ذلك.
امتداد الملف الأكثر شيوعًا لملفات الترويسة هو .h.
تستخدم بعض المشاريع .hpp أو تتجاهل الامتداد تمامًا.
توجد التعريفات في ملف .cpp منفصل.
ولجمع الأجزاء معًا، يبدأ ملف المصدر بـ_تضمين_ ملف الترويسة المقابل.
إذا أردت كتابة مكتبة تُسمى "quick_math" تقدم دالة "super_root" ترغب في استخدامها كثيرًا، فستبدو الملفات هكذا:
// A file named quick_math.h
#pragma once
namespace quick_math {
double super_root(double x, int n);
}
// A file named quick_math.cpp
#include "quick_math.h"
#include <cmath>
double quick_math::super_root(double x, int n) {
while(n) { x = std::sqrt(x), --n;}
return x;
}
إذا احتجت إلى تضمين ملف ترويسة لا يتطلبه إلا التنفيذ، فإن سطر #include المقابل لا يلزم إلا في ملف المصدر.
وكل ما يُضمَّن في ملف الترويسة متاح أيضًا في ملف .cpp، مثل مكتبة string في المثال أدناه.
تنبيه: فاصلة المنقوطة ; مطلوبة بعد التصريح في ملف الترويسة، لكنها ليست مطلوبة بعد التعريف في ملف المصدر.
تبدأ العديد من تمارين C++ على Exercism بملفين شبه فارغين: ملف الترويسة وملف المصدر.
عليك أن تفحص ملف *_test.cpp لمعرفة أسماء الدوال المتوقعة وفضاءات أسمائها، كي تحل التمرين.
قد تصبح الأصناف معقدة جدًا، وقد يكون ارتباطها بتقسيم الترويسة/المصدر مربكًا. من التخطيطات الممكنة أن تُبقي كل تفاصيل التنفيذ في ملف المصدر، وكل التصريحات ومتغيرات الأعضاء في ملف الترويسة:
// A file named robot_flower.h
#if !defined(ROBOT_FLOWER_H)
#define ROBOT_FLOWER_H
#include <string>
namespace robots {
class Flower {
private:
bool needs_water{};
int size{};
std::string name{};
public:
Flower(std::string name, int size = 0);
void give_water();
std::string get_name();
int get_size();
void start_next_day();
};
}
#endif
// A file named robot_flower.cpp
#include "robot_flower.h"
robots::Flower::Flower(std::string name, int size) {this->name = "Robotica " + name; this->size = size;}
void robots::Flower::start_next_day() {if (!needs_water) ++size; needs_water = true;}
std::string robots::Flower::get_name() {return name;}
int robots::Flower::get_size() {return size;}
عندما يُستخدم ملف الترويسة كنظرة عامة على API، فهناك سيبحث الشخص عن معلومات مثل القيم الافتراضية.
لذلك تُعالَج القيمة الافتراضية للمعامل size في المُنشئ داخل ملف الترويسة لا في التنفيذ.
وتُسبق التعريفات في ملف المصدر بفضاء الأسماء robots ونوع الصنف Flower.
وهناك خيار تخطيط آخر هو مكتبة ترويسة فقط، لا تحتوي على ملف .cpp إطلاقًا:
// A file named robot_flower.h
#pragma once
#include <string>
namespace robots {
class Flower {
private:
bool needs_water{};
int size{};
std::string name{};
public:
Flower(std::string name, int size = 0) {this->name = "Robotica " + name; this->size = size;}
void give_water() {needs_water = false;}
std::string get_name() {return name;}
int get_size() {return size;}
void start_next_day() {if (!needs_water) ++size; needs_water = true;}
};
}
قد تستخدم المشاريع توليفات من هذه التخطيطات، وهناك نقاش كثير حول ما قد يكون الأنسب لكل حالة استخدام.
ربما لاحظت سطر #pragma once في ملف الترويسة أعلاه.
يُسمى هذا حارس تضمين، وهو يضمن تضمين محتوى الملف مرة واحدة فقط أثناء الترجمة لتجنب الأخطاء.
وهناك صيغة أخرى أكثر تعقيدًا لحارس التضمين تبدأ بـ#ifndef وتنتهي بـ#endif.
وهي تخدم الغرض نفسه، وقد ورد استخدامها في مثال الصنف Flower أعلاه.
تبدأ يومك الأول في شركة أسترالية تُدعى Doctor Data، متخصصة في استعادة المعلومات. لقد نجحت في مقابلة العمل بفضل معرفتك بلغة C++ ومسابر فون نويمان. وبما أنك رأيت الكثير من ملفات الاختبار، يريد مديرك الجديد منك إعادة إنشاء ملفات المصدر والرأس المناظرة من بعض كود الاختبار الذي استعادته الشركة مؤخرًا.
في هذا التمرين، ستُعيد إنشاء ملفات مفقودة. حاليًا، محتوى الملفات بيانات تالفة غير قابلة للاستخدام.
سير العمل في هذا التمرين المفاهيمي مشابه جدًا لبنية تمارين التدريب في Exercism. نص مقدمة التمرين جزء واحد فقط من المواصفات. ملف الاختبار هو دليلك النهائي لحل مشكلة معينة. بسبب طريقة عمل ترجمة C++، قد لا تظهر نتائج الاختبار في واجهة الويب حتى تنفّذ نسخة أولية بسيطة من كل صنف ودالة وتعداد تتطلبها الاختبارات. إذا واجهت أخطاء ترجمة، فقد تختفي بمجرد أن تنجز جميع المهام والاختبارات.
لديك أربع مهام، جميعها متعلقة باستعادة الكود المفقود في ملفات الرأس والمصدر.
تنظر إلى سطرين مستعادين من ملفات الاختبار:
heaven::Vessel bob{"Robert Johansson", 1};
heaven::Vessel will{"Riker", 2, star_map::System::BetaHydri};
تعرفت عينك الثاقبة فورًا على فضاء أسماء heaven.
ترى أيضًا أنه لا بد من وجود صنف يُدعى Vessel.
يبدو أن المُنشئ يمكن استدعاؤه بوسيطين أو ثلاثة.
يبدو أن الوسيط الأول من النوع string، والثاني عدد.
من الممكن تهيئة الصنف Vessel بوسيط ثالث.
يأتي الوسيط الثالث من فضاء الأسماء star_map.
إنه عنصر تعداد من النوع System.
بل حصلت حتى على أحد التعدادات: BetaHydri.
جهّز ملفات المصدر والرأس بالمعلومات التي اكتشفتها.
تحتاج إلى فضاءي أسماء: heaven وstar_map.
يحتوي فضاء الأسماء heaven على صنف Vessel، يمكن استدعاؤه بوسيطين أو ثلاثة.
يجب وضع تعداد System في فضاء الأسماء star_map، وله عنصر تعداد BetaHydri.
ابقَ متنبهًا لعناصر تعداد أخرى، حتى تتمكن من تحديث تعداد System باستمرار.
تكشف عن سطور أخرى من الاختبار:
heaven::Vessel bob{"Robert Johansson", 1};
REQUIRE(bob.current_system == star_map::System::Sol);
REQUIRE(bob.generation == 1);
heaven::Vessel bob5 = bob.replicate("Mario");
REQUIRE(bob5.current_system == star_map::System::Sol);
REQUIRE(bob5.generation == 2);
تكشف سطور الاختبار المكتشفة حديثًا عن دالة عضو أخرى في الصنف Vessel: replicate.
تستقبل هذه الدالة سلسلة نصية وتُرجع نسخة أخرى من Vessel.
تكوّن فكرة عن القيمة الافتراضية للوسيط الثالث للمُنشئ من المهمة السابقة.
ترى أيضًا متغيرَي عضو عامّين في الصنف Vessel، ومواصفات دالة العضو replicate.
أضف الدالة replicate ومتغيرَي العضو العامّين current_system وgeneration إلى ملفات الرأس والمصدر.
تجد بعض السطور المثيرة للاهتمام في ملفات الاختبار المستعادة:
heaven::Vessel bob6{"Homer", 3, star_map::System::EpsilonEridani};
REQUIRE(bob6.busters == 0);
bob6.make_buster();
REQUIRE(bob6.busters == 1);
bool success = bob6.shoot_buster();
REQUIRE(success);
REQUIRE(bob6.busters == 0);
success = bob6.shoot_buster();
REQUIRE_FALSE(success);
يبدو أن الصنف Vessel يمتلك متغير عضو busters، يمكن تغييره عبر دالتَي العضو make_buster وshoot_buster.
وإلى أن تظهر معلومات أخرى، تخمّن أن الدالة make_buster تُرجع void.
وبما أن هناك اختبارًا لقيمة إرجاع shoot_buster، تفترض أن الدالة تُرجع bool.
أضف الدالتين ومتغير العضو إلى الصنف Vessel.
استمر في البحث عن عناصر تعداد System أخرى.
أثناء تفقّدك لملفات الاختبار، تجد قسمين فقط لم يتم اكتشافهما بعد من الكود:
heaven::Vessel bob1{"Bob", 1, star_map::System::AlphaCentauri};
heaven::Vessel marv{"Marvin", 2, star_map::System::DeltaEridani};
heaven::Vessel milo{"Milo", 3, star_map::System::DeltaEridani};
heaven::Vessel howie{"Howard", 4, star_map::System::Omicron2Eridani};
REQUIRE("Bob" == heaven::get_older_bob(bob1, marv));
REQUIRE(heaven::in_the_same_system(marv, milo));
REQUIRE_FALSE(heaven::in_the_same_system(marv, howie));
ترى دالتين ليستا من أعضاء الصنف Vessel، لأنهما لا تُستدعيان عبر نسخة.
تقارن get_older_bob بين نسختين من Vessel وتُرجع string.
تقارن in_the_same_system بين نسختين من Vessel وتُرجع bool.
نفّذ الدالتين الأخيرتين المفقودتين من السطور المستعادة أعلاه.
سجّل في Exercism لتتعلّم وتتقن C++ عبر 19 مفهومًا100 تمرين، وإرشاد بشري حقيقي، وكل ذلك مجانًا.