درس ۱۱ از ۴۹ · تابع‌ها

دیرکن و ویژگی‌های تابع

دیرکن کار پاکسازی را کنار کدی نگه می‌دارد که به آن نیاز دارد، تا با یک برگشت زودهنگام فراموش نشود. ویژگی‌های تابع هم به مترجم می‌گویند تابع چه رفتاری دارد و مترجم بعضی از آن‌ها را برایتان می‌سنجد.

دیرکن

دستوری که پس از دیرکن بیاید، همان‌جا اجرا نمی‌شود؛ وقتی بلوکِ دربرگیرنده‌اش تمام شود اجرا می‌شود. اگر چند دیرکن باشد، آخرین آن‌ها زودتر اجرا می‌شود، درست مثل وقتی که کارها را به ترتیب وارونه برمی‌گردانید. رایج‌ترین کاربردش آزادکردن چیزی درست کنار خطی است که آن را گرفته است.

defer.salam
تابع کار():
    سرچاپ "آغاز کار"
    دیرکن سرچاپ "پاکسازی نخست"
    دیرکن سرچاپ "پاکسازی دوم"
    سرچاپ "انجام کار"
پایان

تابع ریشه:
    کار()
    سرچاپ "پس از کار"
پایان
خروجیآغاز کار انجام کار پاکسازی دوم پاکسازی نخست پس از کار

همه‌ی راه‌های بیرون رفتن

دستور دیرکن‌شده از هر راهی که از بلوک بیرون بروید اجرا می‌شود: در پایان بلوک، با یک برگشت در میانه، یا با بشکن از یک حلقه. فایده‌ی اصلی‌اش همین است: تابعی که چند برگشت دارد یک دیرکن می‌خواهد، نه یک نسخه از پاکسازی پیش از هر برگشت.

defer-return.salam
تابع جستجو(فهرست: صحیح[]، خواسته: صحیح): صحیح:
    دیرکن سرچاپ "جستجوی"، خواسته، "تمام شد"
    هر ش، عضو در فهرست:
        اگر عضو == خواسته:
            برگشت ش
        پایان
    پایان
    برگشت -۱
پایان

تابع ریشه:
    کدها := [۴۰۴، ۲۰۰، ۵۰۰]
    سرچاپ "جایگاه:"، جستجو(کدها، ۲۰۰)
    سرچاپ "جایگاه:"، جستجو(کدها، ۳۰۱)
پایان
خروجیجستجوی 200 تمام شد جایگاه: 1 جستجوی 301 تمام شد جایگاه: -1

خط دیرکن‌شده پس از آن‌که نتیجه حساب شد و پیش از آن‌که به دست فراخواننده برسد اجرا می‌شود؛ برای همین پیش از جایگاه: چاپ شده است. در ریشه هم همین‌طور است: برگشت ۱ باز هم دیرکن‌های ریشه را اجرا می‌کند و به همین دلیل سیستم عامل.خروج آن‌جا مجاز نیست.

دیرکن مال بلوک خودش است

دیرکن در پایان بلوکی اجرا می‌شود که در آن نوشته شده، نه در پایان تابع. درون حلقه یعنی در پایان هر دور، و درون اگر یعنی در پایان همان شاخه.

defer-blocks.salam
تابع ریشه:
    تکرار ۳ در روز:
        دیرکن سرچاپ "  بستن روز"، روز
        سرچاپ "باز کردن روز"، روز
    پایان
    دیرکرد := ۲
    اگر دیرکرد > ۰:
        دیرکن سرچاپ "  گزارش فرستاده شد"
        سرچاپ "سفارش‌های دیرکرده:"، دیرکرد
    پایان
    سرچاپ "تمام"
پایان
خروجیباز کردن روز 0 بستن روز 0 باز کردن روز 1 بستن روز 1 باز کردن روز 2 بستن روز 2 سفارش‌های دیرکرده: 2 گزارش فرستاده شد تمام

پس چیزی که درون بدنه‌ی حلقه گرفته و همان‌جا دیرکن شده، پیش از شروع دور بعد آزاد می‌شود و معمولاً همین را می‌خواهید.

مقدارها کی خوانده می‌شوند

دستور دیرکن‌شده متغیرهایش را هنگام اجرا می‌خواند، نه وقتی به خط دیرکن می‌رسیم. اگر متغیر در این فاصله عوض شود، دیرکن مقدار تازه را می‌بیند. برای نگه‌داشتن مقدارِ لحظه‌ی دیرکن، اول آن را در متغیر دیگری کپی کنید.

defer-late.salam
تابع ریشه:
    ناپایا مرحله := "آغاز"
    دیرکن سرچاپ "دیرکن می‌بیند:"، مرحله
    نگه‌داشته := مرحله
    دیرکن سرچاپ "کپی نگه‌داشته:"، نگه‌داشته
    مرحله = "پایان کار"
    سرچاپ "مرحله‌ی کنونی:"، مرحله
پایان
خروجیمرحله‌ی کنونی: پایان کار کپی نگه‌داشته: آغاز دیرکن می‌بیند: پایان کار

آزادکردن مجموعه‌ها

وکتور و نگاشت حافظه می‌گیرند و با آزادکن() پس داده می‌شوند. درست در خط بعد از ساختنشان دیرکن نام.آزادکن() بنویسید تا همه‌ی مسیرهای بیرون رفتن از تابع، از جمله برگشت ۰ زودهنگامِ این مثال، آن را آزاد کنند.

defer-free.salam
تابع میانگین خوانش(خام: صحیح[]): صحیح:
    ناپایا معتبر := وکتور {} برگردان وکتور<صحیح>
    دیرکن معتبر.آزادکن()
    هر خ در خام:
        اگر خ >= ۰:
            معتبر.بیفزا(خ)
        پایان
    پایان
    اگر معتبر.طول() == ۰:
        برگشت ۰
    پایان
    ناپایا مجموع := ۰
    هر م در معتبر:
        مجموع += م
    پایان
    برگشت مجموع / معتبر.طول()
پایان

تابع ریشه:
    سرچاپ میانگین خوانش([۲۱، -۱، ۲۳، ۲۵، -۱])
    سرچاپ میانگین خوانش([-۱، -۱])
پایان
خروجی23 0

وکتور و نگاشت در درس مجموعه‌ها آمده‌اند و درس حافظه توضیح می‌دهد چه چیزهایی باید آزاد شوند و چرا.

بستن پرونده

پرونده‌ها هم با همین الگو بسته می‌شوند. بررسی کنید که باز کردن موفق بوده و بلافاصله بستن را دیرکن کنید؛ کدی که بعد می‌آید می‌تواند زود برگردد بی‌آن‌که پرونده باز بماند. این‌جا ریشه پاک کردن پرونده‌ی موقت را هم دیرکن کرده تا پس از پایان برنامه اثری از آن نماند.

defer-file.salam
واردسازی سیستم عامل

تابع ذخیره گزارش(مسیر: رشته، جمع: صحیح): منطقی:
    پ := سیستم عامل.بازکردن(مسیر، "w")
    اگر پ == پوچ:
        برگشت نادرست
    پایان
    دیرکن پ.ببند()
    پ.نوشتن("جمع: " + جمع + "\n")
    پ.نوشتن("وضعیت: پرداخت شده\n")
    برگشت درست
پایان

تابع ریشه:
    مسیر := سیستم عامل.پوشه موقت() + "/salam-report-fa.txt"
    اگر وارونه ذخیره گزارش(مسیر، ۲۴۵):
        نادرست‌سرچاپ "نوشتن ممکن نشد"، مسیر
        برگشت ۱
    پایان
    دیرکن سیستم عامل.حذف(مسیر)
    چاپ سیستم عامل.خواندن پرونده(مسیر)
پایان
خروجیجمع: 245 وضعیت: پرداخت شده

دیرکن می‌تواند تابعی را صدا بزند که مقداری برمی‌گرداند، مثل سیستم عامل.حذف؛ آن مقدار کنار گذاشته می‌شود. شکل بلوکی دیرکن: ... پایان وجود ندارد. اگر پاکسازی چند گام دارد، آن‌ها را در یک تابع بگذارید و فراخوانی آن تابع را دیرکن کنید.

ویژگی‌های تابع

این واژه‌ها پیش از تابع می‌آیند و چیزی درباره‌ی تابع می‌گویند. بعضی قول‌هایی‌اند که مترجم می‌سنجد و بعضی فقط پیشنهادند:

ویژگیمعناکاری که مترجم می‌کند
ناببی اثر جانبی: نتیجه فقط به ورودی‌ها بستگی داردچاپ کردن، نوشتن در متغیر سراسری یا صدا زدن تابع ناخالص را در آن نمی‌پذیرد
درخطبدنه در جای فراخوانی گذاشته شودآن را پیشنهاد می‌گیرد؛ برای تابع‌های کوچکی که بسیار صدا زده می‌شوند
نادرخطفراخوانی واقعی بماندآن را پیشنهاد می‌گیرد؛ هنگام اندازه‌گیری کارایی یا اشکال‌زدایی به کار می‌آید
نابرگشتتابع هرگز برنمی‌گردد، مثلاً برنامه را تمام می‌کندکد پس از فراخوانی آن را دسترس‌ناپذیر می‌داند
بی‌کارهتابع کهنه شده استدر هر فراخوانی هشدار می‌دهد (W013)
همگانیبیرون از بسته هم دیده می‌شوددرس بسته‌ها را ببینید
fn-mods.salam
ناب تابع بهای خالص(بها: صحیح، تخفیف: صحیح): صحیح:
    برگشت بها - بها * تخفیف / ۱۰۰
پایان

درخط تابع مربع(ع: صحیح): صحیح:
    برگشت ع * ع
پایان

نادرخط تابع خط رسید(نام: رشته، مبلغ: صحیح): رشته:
    برگشت نام + ": " + مبلغ
پایان

تابع ریشه:
    سرچاپ بهای خالص(۲۰۰، ۱۵)، مربع(۹)
    سرچاپ خط رسید("چای"، بهای خالص(۹۰، ۱۰))
پایان
خروجی170 81 چای: 81

اگر در بهای خالص یک سرچاپ بنویسید، ساخت متوقف می‌شود و مترجم می‌گوید تابع ناب نمی‌تواند چاپ را صدا بزند، چون اثر جانبی دارد. ارزش ناب در همین سنجش است: تابع ناب را می‌شود هر جا و هر چند بار که لازم است، بی‌خطر صدا زد.

fn-noret.salam
واردسازی سیستم عامل

نابرگشت تابع شکست(پیام: رشته):
    نادرست‌سرچاپ "خطا:"، پیام
    سیستم عامل.خروج(۲)
پایان

بی‌کاره تابع جمع قدیمی(الف: صحیح، ب: صحیح): صحیح:
    برگشت الف + ب
پایان

تابع بررسی سن(سن: صحیح): صحیح:
    اگر سن < ۰ یا سن > ۱۵۰:
        شکست("سن ناممکن " + سن)
    پایان
    برگشت سن
پایان

تابع ریشه:
    سرچاپ بررسی سن(۳۴)
    سرچاپ جمع قدیمی(۲، ۳)
پایان
خروجی34 5

اگر بررسی سن با سن منفی ۳ صدا زده شود، «خطا: سن ناممکن -3» چاپ می‌شود و برنامه با کد ۲ بیرون می‌رود. چون شکست نابرگشت است، مترجم می‌داند این فراخوانی هرگز برنمی‌گردد و دستوری که درست پس از آن نوشته شود را دسترس‌ناپذیر می‌داند و نمی‌پذیرد (E070). ولی خودِ بدنه را نمی‌سنجد: تابع نابرگشتی که برگردد برنامه را از کار می‌اندازد، پس مطمئن شوید همیشه با سیستم عامل.خروج یا فراخوانی دیگری که برنمی‌گردد تمام می‌شود. صدا زدن جمع قدیمی ساخته و اجرا می‌شود، فقط هشدار W013 می‌گیرد.

چند قاعده‌ی سرانگشتی

  • دیرکن را درست در خط بعد از گرفتن چیزی بنویسید که آزادش می‌کند.
  • دیرکن‌ها به ترتیب وارونه و در پایان بلوک خودشان اجرا می‌شوند.
  • دستور دیرکن‌شده مقدار متغیرها را در لحظه‌ی اجرا می‌بیند.
  • تابعی را که ناب است ناب علامت بزنید تا مترجم ناب نگهش دارد.
  • نابرگشت را فقط برای تابعی بنویسید که واقعاً برنامه را تمام می‌کند.