C / الأساسي: قراءة البرامج وبناؤها / الدرس 5
الدوال والعقود
استخدم الدوال لمنح الحسابات واجهات واضحة، وصرح بالشروط التي تسمح للمستدعي بالاعتماد على النتيجة.
في هذه الصفحة
أهداف الدرس
- فصل التصريحات والتعريفات والاستدعاءات.
- شرح المعاملات بالقيمة والقيم المعادة والنطاق المحلي.
- تصميم واجهة مفحوصة ذات إبلاغ صريح للفشل.
التصريح وعد
يحدد تصريح الدالة نوع الإرجاع وأنواع المعاملات المتاحة عند موضع الاستدعاء. ويضيف التعريف جسم الدالة. في C17 يصرح int f(void) بعدم وجود معاملات؛ ولا يقدم int f() معلومات النموذج الأولي نفسها. فضل النماذج الأولية الصريحة وضمن الترويسة المناسبة قبل الاستدعاء.
للدالة الجيدة مسؤولية مترابطة واحدة واسم يصف النتيجة أو الفعل. يشمل عقدها الشروط المسبقة والآثار واصطلاح الإرجاع وأية ملكية موارد. لا يضيف تعليق يقول «احسب القيمة» كثيرا؛ لكن شرح قبول عدد صفري وبقاء المدخل دون تغيير يمنع غموضا فعليا.
المعاملات تتلقى قيما
تمرر C المعاملات بالقيمة. يكون المعامل كائنا محليا منفصلا مهيأ بقيمة الوسيط، لذلك لا يغير تعديل معامل int متغير int لدى المستدعي. يعيد المثال الأول القيمة المحولة ويترك للمستدعي تخزينها أو تجاهلها. تمرر معاملات المؤشرات قيمة أيضا، لكن تلك القيمة قد تحدد كائنا آخر يسمح للدالة بتعديله.
يتحكم نطاق الكتلة المحلي في مواضع ظهور الاسم. وتتحكم مدة التخزين التلقائية في مدة وجود الكائن المحلي المعتاد. هذان مفهومان مختلفان: قد يتوقف ظهور اسم بينما يستمر الكائن في نطاق محيط. وتتجنب إعادة نتيجة حسابية عادية كشف تخزين محلي.
#include <stdio.h>
static int increased(int value)
{
return value + 1;
}
int main(void)
{
int original = 4;
int result = increased(original);
printf("%d %d\n", original, result);
return 0;
}
4 5
لهذا المدخل الصغير تعيد increased خمسة؛ ويبقى original أربعة لأن قيمته نُسخت.
الإبلاغ عن الفشل دون قيمة مزيفة
قد تكون إعادة عدد وحده ملتبسة عندما تكون كل القيم العددية بيانات صالحة. تستطيع الواجهة المفحوصة إعادة علم نجاح وتخزين النتيجة عبر مؤشر إخراج. صرح بأن كائن الإخراج يبقى دون تغيير عند الفشل، وضع الإسناد بعد نجاح كل الفحوص فقط.
تقبل الدالة التالية معاملات غير سالبة فقط ومؤشر إخراج غير فارغ. وتحرس الطرح والجمع بتسلسل يبقى ضمن مدى int. يبقى على المستدعي تقديم مؤشر إلى int حي قابل للكتابة؛ ففحص عدم مساواة المؤشر للصفري لا يثبت جميع جوانب صلاحيته. يقدم المثال صياغة المؤشر لغرض محدود وموثق.
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
static bool add_nonnegative(int a, int b, int *out)
{
if (out == NULL || a < 0 || b < 0) return false;
if (a > INT_MAX - b) return false;
*out = a + b;
return true;
}
int main(void)
{
int result = -1;
if (!add_nonnegative(12, 8, &result)) return 1;
printf("result=%d\n", result);
return 0;
}
result=20
تحدد &result كائن int الحي لدى المستدعي. ولا يُخزن المجموع المفحوص إلا عند النجاح.
إظهار الاعتمادات
يجعل تمرير البيانات المطلوبة كمعاملات الحساب أسهل للاختبار عادة من قراءة حالة عامة خفية. تمتلك الدوال static في نطاق الملف ارتباطا داخليا، فتكون أسماؤها خاصة بوحدة الترجمة. هذه أداة مفيدة للدوال المساعدة التنفيذية، وليست بديلا عن عقد محدد جيدا.
تجنب الدوال التي تعتمد نتيجتها بصورة مفاجئة على استدعاء سابق. يستمر الكائن المحلي static بين الاستدعاءات، وقد يناسب آلة حالات مقصودة لكنه يفاجئ داخل حساب يبدو نقيا. افصل الحساب عن الإدخال والإخراج لتستطيع الاختبارات توفير قيم معروفة دون محاكاة محادثة طرفية كاملة.
تحليل كل مسار إرجاع
يجب أن يقدم كل مسار في دالة غير void النتيجة الموعودة أو فشلا موثقا. راجع المدخلات الاستثنائية قبل الحساب المعتاد. تجعل الإرجاعات المبكرة الحالات المرفوضة أوضح غالبا، بشرط صحة تنظيف الموارد. وينبغي أن يصف عقد الدالة المعاملات التي يجوز أن تشير إلى الكائن نفسه عند وجود عدة مؤشرات.
يحتاج الاستدعاء الذاتي إلى حالة أساس وتقدم نحوها. لا تضمن C عمقا غير محدود ولا تحسين الاستدعاء الذيلي. وقد تكون الخوارزمية التكرارية ذات الحدود الصريحة أسهل للتبرير عند المدخلات الكبيرة. اختر التقسيم لتوضيح الثوابت والمسؤوليات، لا لزيادة عدد الدوال القصيرة.
الأخطاء الشائعة وإصلاحها
لا يغير تعديل المعامل كائن المستدعي.
- لماذا يحدث؟
- يحتوي المعامل قيمة منسوخة.
- كيف تصلحه؟
- أعد القيمة الجديدة أو استخدم مؤشر إخراج موثقا.
- كيف تتجنب تكراره؟
- ارسم كائنات المستدعي وكائنات المعاملات منفصلة.
يدمر الفشل النتيجة السابقة.
- لماذا يحدث؟
- أُسند الإخراج قبل اكتمال التحقق.
- كيف تصلحه؟
- أخر تعديل الإخراج حتى مسار النجاح.
- كيف تتجنب تكراره؟
- اختبر أن الفشل يحافظ على نتيجة مهيأة بقيمة مميزة.
يفتقر الاستدعاء إلى فحص الأنواع.
- لماذا يحدث؟
- استُخدمت قائمة معاملات فارغة بالأسلوب القديم في التصريح.
- كيف تصلحه؟
- صرح بأنواع المعاملات صراحة واستخدم void لغيابها في C17.
- كيف تتجنب تكراره؟
- ضمن الترويسة العامة لدى المستدعين وفي المصدر الذي يعرف الدالة.
اختبر فهمك
1. استدع add_nonnegative بمعامل سالب وresult مهيأ إلى 99. ماذا يحدث ولماذا؟
اعرض الحل المشروح
يعيد الحارس الأول false قبل حساب المجموع أو الإسناد عبر out. لذلك يبقى result بقيمة 99. يتبع هذا عقد بقاء الإخراج دون تغيير عند الفشل، وليس اصطلاحا مفروضا على كل دوال C. يجب أن يفحص الاختبار الإرجاع false وبقاء النتيجة معا، لأن فحص الإرجاع وحده يفوت كتابة مبكرة عرضية.
2. صمم عقد دالة تعيد الأكبر بين قيمتي int دون تعديل كائني المستدعي.
اعرض الحل المشروح
صرح int max_int(int a, int b)، دون شرط مسبق إضافي، وأعد a >= b ? a : b. لا تتجاوز المقارنة المدى، وكل مدخل قابل للتمثيل أصلا في int. الشرط اللاحق أن النتيجة تساوي أحد المدخلين ولا تقل عن أي منهما. اختبر التساوي والإشارتين المختلفتين وINT_MIN مع INT_MAX.
أهم ما تعلمته
- تشمل واجهة الدالة الأنواع والشروط المسبقة والآثار وسياسة الفشل.
- تمرر الوسائط بالقيمة، بما فيها قيم المؤشرات.
- اختبر مسارات الفشل واحفظ الحالة التي يعد بها العقد.