المسارات
/
C++
C++
/
التمارين
/
سيّد اللازانيا
سيّد اللازانيا

سيّد اللازانيا

تمرين تعلّمي

مقدمة

المصطلحات

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

// Function declaration:
bool                                              // Return type
admin_detected(string user, string password)      // Type signature
;                                                 // Don't forget the ';' for the declaration

// Function definition:
bool                                              // Return type
admin_detected                                    // Function name
(string user, string password)                    // Parameter list
{ return user == "admin" && password == "1234"; } // Function body
Advanced

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

مُحدِّد const

أحيانًا تريد التأكد من أن القيم لا يمكن تغييرها بعد تهيئتها. تستخدم C++ الكلمة المفتاحية const كمُحدِّد للثوابت.

const int number_of_dragon_balls{7};
number_of_dragon_balls--; // compilation error
Note

سترى الثوابت غالبًا مكتوبة بنمط UPPER_SNAKE_CASE. يُنصح بحصر هذا النمط على الماكرو، إن لم توجد اتفاقية أخرى.

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

يمكنك أيضًا استخدام const كمُحدِّد لمعاملات الدالة.

string guess_number(const int& secret, const int& guess) {
    if (secret < guess) return "lower.";
    if (secret > guess) return "higher.";
    return "exact!";
}

عندما تمرر مرجعًا const إلى الدالة، يمكنك أن تثق بأنه سيُترك دون تغيير. سترى غالبًا مراجع const لكائنات قد يكون نسخها مكلفًا، مثل السلاسل النصية الطويلة. ومن حالات الاستخدام الثالثة لمُحدِّد const دوال الأعضاء التي لا تغيّر نسخة الصنف.

class Stubborn {
    public:
    Stubborn(string reply) {
        response = reply;
    }
    string answer(const string& question) const {
        if (question.length() == 0) { return ""; }
        return response;
    }
    private:
    string response{};
};

تستخدم دالة العضو answer في الصنف Stubborn مرجعًا const string& كمعامل. يتجنب ذلك عملية نسخ من الكائن الأصلي الذي مُرّر إلى الدالة.

التحميل الزائد للدوال

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

ترويسة الدالة بدون نوع الإرجاع هي توقيع النوع الخاص بالدالة. أي تغيير في توقيع النوع ينتج عنه دالة جديدة.

يحتوي مثال play_sound على ستة تحميلات زائدة مختلفة لتلائم سيناريوهات مختلفة:

// different argument types:
void play_sound(char note);         // C, D, E, ..., B
void play_sound(string solfege);    // do, re, mi, ..., ti
void play_sound(int jianpu);        // 1, 2, 3, ..., 7

// different number of arguments:
void play_sound(string solfege, double duration);

// different qualifiers:
void play_sound(vector<string>& solfege);
void play_sound(const vector<string>& solfege);
Advanced

يُعرَّف توقيع النوع باسم الدالة وعدد المعاملات وأنواعها ومُحدِّداتها (وليس أسماءها). نوع الإرجاع ليس جزءًا من توقيع النوع بشكل صريح، وستحصل على أخطاء ترجمة إذا كان لديك دالتان تختلفان في نوع الإرجاع فقط. سيشتكي المترجم لأن الأمر غير واضح، أي الدالتين ينبغي استخدامها.

الوسائط الافتراضية

قد تصبح بعض الدوال طويلة جدًا، وقد تستخدم كثير من استدعاءاتها القيم نفسها لمعظم المعاملات. يمكن تجنب التكرار في تلك الاستدعاءات باستخدام الوسائط الافتراضية.

void record_new_horse_birth(string name, int weight, string color="brown-ish", string dam="Alruccaba", string sire="Poseidon");

record_new_horse_birth("Urban Sea", 130); // color will be brown, dam "Alruccabam", sire "Poseidon"
record_new_horse_birth("Highclere", 175, "off-white", "Fall Aspen");   // sire will be "Poseidon"

ولأن تصريح الدالة يُقرأ غالبًا قبل التعريف، فهو المكان الأفضل لضبط الوسائط الافتراضية. إذا كان لأحد المعاملات قيمة افتراضية في التصريح، فإن كل المعاملات الواقعة إلى يمينه تحتاج إلى قيمة افتراضية أيضًا. أحيانًا يمكن إعادة هيكلة التحميلات الزائدة المعقدة للدوال إلى دوال أقل مع وسائط افتراضية، لتحسين قابلية الصيانة.

التعليمات

في هذا التمرين ستكتب المزيد من الكود المتعلق بتحضير وطبخ اللازانيا الرائعة من كتاب الطبخ المفضل لديك.

لديك خمس مهام. المهمة الأولى مرتبطة بالطبخ نفسه، أما المهام الأربع الأخرى فتتعلق بالتحضير المثالي.

1. قدّر وقت التحضير

بالنسبة للالزانيا التالية التي ستحضّرها، تريد التأكد من أنك خصصت وقتًا كافيًا لتستمتع بالطبخ. لقد خططت بالفعل للطبقات التي ستتكون منها اللازانيا. الآن تريد تقدير المدة التي سيستغرقها التحضير بناءً على ذلك.

نفّذ دالة preparationTime تقبل متجهًا من الطبقات بصيغة string ومتوسط وقت التحضير لكل طبقة بالدقائق بصيغة int. يجب أن تُرجع الدالة تقدير الوقت الإجمالي للتحضير بناءً على عدد الطبقات بصيغة int. إذا لم يُمرَّر متوسط وقت التحضير، فستُستخدم القيمة الافتراضية 2.

std::vector<std::string> layers{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"};
preparationTime(layers, 3)
// => 18
preparationTime(layers)
// => 12

2. احسب كميات المعكرونة والصلصة اللازمة

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

عرّف الدالة quantities التي تأخذ متجهًا من الطبقات كمعامل بصيغة string. ستحدد الدالة بعد ذلك كمية المعكرونة والصلصة اللازمة لإعداد وجبتك. يجب أن تكون النتيجة على هيئة بنية struct باسم amount، وهي معرّفة لك بالفعل. تحتوي amount على متغيرين عضوين، أحدهما لوزن noodles بصيغة int والآخر لحجم sauce بصيغة double.

struct amount { int noodles; double sauce; };

amount needed{quantities({"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"})};
// needed.noodles => 100
// needed.sauce => 0.4

3. أضف المكوّن السري

منذ فترة زرت صديقًا وتناولت اللازانيا عنده. كانت رائعة وكان فيها شيء مميز. أرسل لك الصديق قائمة المكونات وأخبرك أن العنصر الأخير في القائمة هو "المكوّن السري" الذي جعل الوجبة مميزة إلى هذا الحد. الآن تريد إضافة هذا المكوّن السري إلى وصفتك أيضًا.

اكتب دالة addSecretIngredient تقبل معاملين من النوع vector of strings. المعامل الأول مرجع إلى مكونات وصفتك، والمعامل الثاني وهو متجه من السلاسل النصية يحتوي على المكونات التي أرسلها إليك صديقك. العنصر الأخير في قائمة مكوناتك هو دائمًا "?". يجب أن تستبدل الدالة هذا العنصر بالعنصر الأخير من قائمة صديقك. ملاحظة: لا تُرجع addSecretIngredient أي شيء، بل يجب أن تعدّل قائمة مكوناتك مباشرة. يمكن أن تكون قائمة مكونات صديقك مرجعًا أيضًا (لتحسين الأداء)، لكن يجب ألا تُعدّل. تأكد من أن القائمة لن تُعدّل باستخدام مرجع const.

std::vector<std::string> friendsList{"noodles", "sauce", "mozzarella", "kampot pepper"};
std::vector<std::string> myList{"noodles", "meat", "sauce", "mozzarella", "?"};

addSecretIngredient(myList, friendsList);
// myList => {"noodles", "meat", "sauce", "mozzarella", "kampot pepper"}

4. عدّل مقادير الوصفة

المقادير المذكورة في كتاب الطبخ لا تكفي إلا لحصتين من اللازانيا. وبما أنك تريد الطبخ لعدد أكبر من الأشخاص في المرة القادمة، فإنك تريد حساب المقادير لأعداد مختلفة من الحصص.

نفّذ دالة scaleRecipe تأخذ معاملين.

  • متجه من المقادير بصيغة double اللازمة لحصتين.
  • عدد الحصص التي تريد طبخها.

يجب أن تُرجع الدالة متجهًا جديدًا بالمقادير اللازمة لعدد الحصص المطلوب. لكنك تريد الحفاظ على الوصفة الأصلية. هذا يعني أنه يجب ألا يُعدّل الوسيط quantities في هذه الدالة.

std::vector<double> quantities{1.2, 3.6, 10.5};
scaleRecipe(quantities, 4);
// => { 2.4, 7.2, 21 }

5. اكشف سر العائلة

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

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

إسهام عمّة جدّك الكبرى يضيف طبقة من التعقيد تريد استخدامها في وصفتك. نفّذ نسخة أخرى من دالة addSecretIngredient تقبل وسيطين: مرجعًا إلى متجه يمثل قائمة مكوناتك، وسلسلة نصية واحدة تمثل الهدية الغامضة من عمّة جدّك الكبرى.

std::vector<std::string> myList{"noodles", "meat", "sauce", "mozzarella", "?"};
std::string secretIngredient{"spice melange"};
addSecretIngredient(myList, secretIngredient);
// myList => {"noodles", "meat", "sauce", "mozzarella", "spice melange"}

حظًا موفقًا، أيها الطاهي الشجاع!

تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
C++ Exercism

مستعد لبدء سيّد اللازانيا؟

سجّل في Exercism لتتعلّم وتتقن C++ عبر 19 مفهومًا100 تمرين، وإرشاد بشري حقيقي، وكل ذلك مجانًا.