C: الدوال — الأساسيات

الدوال كالوصفات في المطبخ — أعطها مكونات (معاملات)، تتبع خطوات لمعالجتها، وتقدّم طبقًا (قيمة مرجعة). تغليف المنطق المتكرر في دوال يبقي الكود نظيفًا وفعّالًا.

1. تعريف الدالة

يتكون تعريف الدالة من أربعة أجزاء: نوع القيمة المرجعة، واسم الدالة، وقائمة المعاملات، وجسم الدالة.

C
int add(int a, int b) {
    return a + b;
}

إذا لم تكن هناك معاملات، اكتب void في قائمة المعاملات:

C
int get_value(void) {
    return 42;
}

2. استدعاء الدالة

لاستدعاء دالة، أتبع اسم الدالة بأقواس ومرّر الوسائط:

TEXT 📖 للعرض فقط
int result = add(3, 5);
printf("%d\n", result);

عند الاستدعاء، يقفز البرنامج إلى جسم الدالة، وينفذه، ويعود إلى موقع الاستدعاء عند مصادفة return أو الوصول إلى نهاية الدالة.

يمكن أن تتداخل الدوال — يمكن أن تكون القيمة المرجعة لدالة ما وسيطًا لدالة أخرى:

C
int x = add(add(1, 2), add(3, 4));

هذا يكافئ add(3, 7)، والنتيجة 10.


3. المعاملات والقيم المرجعة

(1) المعاملات مقابل الوسائط

المتغيرات داخل أقواس تعريف الدالة تُسمّى معاملات (معاملات شكلية). القيم الممررة عند الاستدعاء تُسمّى وسائط (معاملات فعلية).

C
int square(int n) {
    return n * n;
}

int main(void) {
    int x = 5;
    int y = square(x);
    return 0;
}

n هو معامل، و x هو وسيط. المعاملات متغيرات محلية داخل الدالة؛ تُنسخ قيمة الوسيط إلى المعامل — هذا هو "التمرير بالقيمة". تعديل المعامل داخل الدالة لا يؤثر على الوسيط.

C
void try_change(int n) {
    n = 100;
}

int main(void) {
    int x = 5;
    try_change(x);
    printf("%d\n", x);
    return 0;
}

المخرجات لا تزال 5، لأن n هو مجرد نسخة من x.

(2) القيم المرجعة

تُرجع الدالة نتيجة إلى المستدعي عبر جملة return. يجب أن يتطابق نوع القيمة المرجعة مع تصريح الدالة:

C
double circle_area(double r) {
    return 3.14159 * r * r;
}

يمكن أن تُرجع الدالة تعبيرًا مباشرة دون تخزينه في متغير أولًا. بمجرد تنفيذ return، تنتهي الدالة فورًا؛ لا يُنفَّذ الكود اللاحق:

C
int max(int a, int b) {
    if (a > b) {
        return a;
    }
    return b;
}

4. تصريح الدالة (النموذج الأولي)

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

C
int add(int a, int b);

int main(void) {
    int result = add(3, 5);
    return 0;
}

int add(int a, int b) {
    return a + b;
}

التصريح يتضمن الرأس فقط متبوعًا بفاصلة منقوطة؛ جسم الدالة غير مطلوب. يمكن حذف أسماء المعاملات:

C
int add(int, int);

لكن الاحتفاظ بأسماء المعاملات يحسّن القراءة ويُنصح به.

💡 نصيحة: في المشاريع الفعلية، تُوضع تصاريح الدوال عادةً في ملفات الرأس (.h)، وتعريفات الدوال في ملفات المصدر (.c).


5. الدوال void

الدوال التي لا تحتاج لإرجاع قيمة تستخدم void كنوع للقيمة المرجعة:

C
void print_line(int len) {
    int i;
    for (i = 0; i < len; i++) {
        printf("-");
    }
    printf("\n");
}

في دالة void، يمكن حذف return، أو استخدام return; للخروج المبكر:

C
void greet(int hour) {
    if (hour < 0 || hour > 23) {
        return;
    }
    if (hour < 12) {
        printf("Good morning\n");
    } else {
        printf("Good afternoon\n");
    }
}
⚠️ ملاحظة: دالة void لا يمكنها إرجاع قيمة؛ return 5; سيسبب خطأ تجميع. وبالمثل، يجب أن يحتوي كل مسار تنفيذ في دالة غير void على return بالنوع الصحيح.

▶ مثال

تحديد ما إذا كان عدد صحيح زوجيًا:

C
#include <stdio.h>

int is_even(int n) {
    return n % 2 == 0;
}

int main(void) {
    int nums[] = {3, 4, 7, 8, 11, 16};
    int i;
    int len = sizeof(nums) / sizeof(nums[0]);

    for (i = 0; i < len; i++) {
        printf("%d is %s\n", nums[i], is_even(nums[i]) ? "even" : "odd");
    }

    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
3 is odd
4 is even
7 is odd
8 is even
11 is odd
16 is even

6. جملة return بالتفصيل

يمكن أن تحتوي الدالة على عدة جمل return، لكن واحدة فقط تُنفَّذ في كل استدعاء. لـ return تأثيران:

  1. إرجاع قيمة إلى المستدعي
  2. إنهاء تنفيذ الدالة فورًا
C
const char *grade(int score) {
    if (score >= 90) return "Excellent";
    if (score >= 80) return "Good";
    if (score >= 60) return "Pass";
    return "Fail";
}

تُنسخ القيمة المرجعة إلى موقع الاستدعاء. للأنواع الأساسية (int و double وغيرها)، تكلفة النسخ ضئيلة. بعد إرجاع الدالة، لم تعد متغيراتها المحلية موجودة، لذا يجب ألا تُرجع مؤشرًا إلى متغير محلي.


7. الدوال متعددة المعاملات

يمكن أن تحتوي الدالة على عدة معاملات، تفصلها فواصل. يجب أن يتطابق عدد الوسائط وأنواعها مع المعاملات تمامًا:

C
double bmi(double weight, double height) {
    return weight / (height * height);
}

تُطابَق الوسائط مع المعاملات حسب الموضع:

C
double result = bmi(70.0, 1.75);
⚠️ ملاحظة: عندما لا تتطابق أنواع الوسائط والمعاملات، قد يجري المترجم تحويلًا ضمنيًا أو يُصدر تحذيرًا. من الأفضل الحفاظ على اتساق الأنواع.

▶ مثال

حساب عدد الأيام بين تاريخين (نسخة مبسّطة، متجاهلة السنوات الكبيسة واختلاف الأشهر، فقط لعرض الدوال متعددة المعاملات):

C
#include <stdio.h>

int day_of_year(int year, int month, int day) {
    int days_in_month[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int total = 0;
    int m;

    for (m = 1; m < month; m++) {
        total += days_in_month[m];
    }
    total += day;

    return total;
}

int days_between(int y1, int m1, int d1, int y2, int m2, int d2) {
    int first = day_of_year(y1, m1, d1);
    int second = day_of_year(y2, m2, d2);
    int diff = second - first;
    if (diff < 0) diff = -diff;
    return diff;
}

int main(void) {
    int d = days_between(2025, 3, 1, 2025, 6, 15);
    printf("%d days apart\n", d);
    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
106 days apart

❓ أسئلة شائعة

س هل يمكن أن يكون للمعامل نفس اسم الوسيط؟
ج نعم، إنها متغيرات في نطاقات مختلفة ولا تؤثر على بعضها. لكن لتجنب الخلط، يُنصح باستخدام أسماء مختلفة.
س هل يمكن حذف نوع القيمة المرجعة لدالة؟
ج في C89، حذف نوع القيمة المرجعة يجعله int افتراضيًا. يتطلب C99 وما بعده نوعًا صريحًا. تحديد نوع القيمة المرجعة دائمًا عادة حسنة.
س ما فائدة return; في دالة void؟
ج تُستخدم للخروج المبكر من الدالة، متجاوزةً المنطق اللاحق. تشبه break في الحلقة.
س هل يمكن لدالة إرجاع عدة قيم؟
ج دالة C لا يمكنها إرجاع قيمة واحدة فقط. لإرجاع عدة نتائج، استخدم معاملات مؤشرات أو بنى أو مصفوفات.

📖 ملخص

📝 تمارين

  1. اكتب دالة int max3(int a, int b, int c) تُرجع أكبر ثلاثة أعداد صحيحة.
  2. اكتب دالة void swap_wrong(int a, int b) تحاول مبادلة قيمتين. تحقق في main إن نجحت، واشرح السبب.
  3. اكتب دالة int digit_sum(int n) تحسب مجموع أرقام عدد صحيح موجب (مثلًا: 123 تُرجع 6). اختبرها في main.
Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%