مسیرها
/
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. مقدار رشته و سس لازم را حساب کنید

علاوه بر کنار گذاشتن زمان، می‌خواهید مطمئن شوید سس و رشته به اندازه‌ی کافی برای پختن لازانیای رؤیاهایتان دارید. برای هر لایه‌ی رشته در لازانیایتان به ۵۰ گرم رشته نیاز دارید. برای هر لایه‌ی سس در لازانیایتان به ۰٫۲ لیتر سس نیاز دارید.

تابع 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 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.