C: الدوال — الأساسيات
الدوال كالوصفات في المطبخ — أعطها مكونات (معاملات)، تتبع خطوات لمعالجتها، وتقدّم طبقًا (قيمة مرجعة). تغليف المنطق المتكرر في دوال يبقي الكود نظيفًا وفعّالًا.
1. تعريف الدالة
يتكون تعريف الدالة من أربعة أجزاء: نوع القيمة المرجعة، واسم الدالة، وقائمة المعاملات، وجسم الدالة.
int add(int a, int b) {
return a + b;
}
int: نوع القيمة المرجعة، يحدد نوع نتيجة الدالةadd: اسم الدالة، يُستخدم عند الاستدعاء(int a, int b): قائمة المعاملات، تستقبل بيانات من المستدعي{ return a + b; }: جسم الدالة، ينفذ المنطق الفعلي
إذا لم تكن هناك معاملات، اكتب void في قائمة المعاملات:
int get_value(void) {
return 42;
}
2. استدعاء الدالة
لاستدعاء دالة، أتبع اسم الدالة بأقواس ومرّر الوسائط:
int result = add(3, 5);
printf("%d\n", result);
عند الاستدعاء، يقفز البرنامج إلى جسم الدالة، وينفذه، ويعود إلى موقع الاستدعاء عند مصادفة return أو الوصول إلى نهاية الدالة.
يمكن أن تتداخل الدوال — يمكن أن تكون القيمة المرجعة لدالة ما وسيطًا لدالة أخرى:
int x = add(add(1, 2), add(3, 4));
هذا يكافئ add(3, 7)، والنتيجة 10.
3. المعاملات والقيم المرجعة
(1) المعاملات مقابل الوسائط
المتغيرات داخل أقواس تعريف الدالة تُسمّى معاملات (معاملات شكلية). القيم الممررة عند الاستدعاء تُسمّى وسائط (معاملات فعلية).
int square(int n) {
return n * n;
}
int main(void) {
int x = 5;
int y = square(x);
return 0;
}
n هو معامل، و x هو وسيط. المعاملات متغيرات محلية داخل الدالة؛ تُنسخ قيمة الوسيط إلى المعامل — هذا هو "التمرير بالقيمة". تعديل المعامل داخل الدالة لا يؤثر على الوسيط.
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. يجب أن يتطابق نوع القيمة المرجعة مع تصريح الدالة:
double circle_area(double r) {
return 3.14159 * r * r;
}
يمكن أن تُرجع الدالة تعبيرًا مباشرة دون تخزينه في متغير أولًا. بمجرد تنفيذ return، تنتهي الدالة فورًا؛ لا يُنفَّذ الكود اللاحق:
int max(int a, int b) {
if (a > b) {
return a;
}
return b;
}
4. تصريح الدالة (النموذج الأولي)
إذا عُرّفت الدالة بعد نقطة استدعائها، فلن يتعرف المترجم على اسم الدالة وسينتج خطأ أو سيفترض ضمنيًا أنها تُرجع int. الحل هو تصريح نموذج الدالة قبل الاستدعاء:
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;
}
التصريح يتضمن الرأس فقط متبوعًا بفاصلة منقوطة؛ جسم الدالة غير مطلوب. يمكن حذف أسماء المعاملات:
int add(int, int);
لكن الاحتفاظ بأسماء المعاملات يحسّن القراءة ويُنصح به.
5. الدوال void
الدوال التي لا تحتاج لإرجاع قيمة تستخدم void كنوع للقيمة المرجعة:
void print_line(int len) {
int i;
for (i = 0; i < len; i++) {
printf("-");
}
printf("\n");
}
في دالة void، يمكن حذف return، أو استخدام return; للخروج المبكر:
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 بالنوع الصحيح.
▶ مثال
تحديد ما إذا كان عدد صحيح زوجيًا:
#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;
}
3 is odd
4 is even
7 is odd
8 is even
11 is odd
16 is even
6. جملة return بالتفصيل
يمكن أن تحتوي الدالة على عدة جمل return، لكن واحدة فقط تُنفَّذ في كل استدعاء. لـ return تأثيران:
- إرجاع قيمة إلى المستدعي
- إنهاء تنفيذ الدالة فورًا
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. الدوال متعددة المعاملات
يمكن أن تحتوي الدالة على عدة معاملات، تفصلها فواصل. يجب أن يتطابق عدد الوسائط وأنواعها مع المعاملات تمامًا:
double bmi(double weight, double height) {
return weight / (height * height);
}
تُطابَق الوسائط مع المعاملات حسب الموضع:
double result = bmi(70.0, 1.75);
▶ مثال
حساب عدد الأيام بين تاريخين (نسخة مبسّطة، متجاهلة السنوات الكبيسة واختلاف الأشهر، فقط لعرض الدوال متعددة المعاملات):
#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;
}
106 days apart
❓ أسئلة شائعة
return; في دالة void؟break في الحلقة.📖 ملخص
- تتكون الدالة من نوع القيمة المرجعة واسم الدالة وقائمة المعاملات وجسم الدالة
- تُنسخ الوسائط إلى المعاملات (التمرير بالقيمة)؛ تعديل المعاملات داخل الدالة لا يؤثر على الوسائط
- يجب تصريح الدالة أو تعريفها قبل استدعائها؛ تصريح النموذج كافٍ
- دوال void لا تُرجع قيمة؛ استخدم
return;للخروج المبكر - جملة return تنهي الدالة فورًا وترسل قيمة إلى المستدعي
📝 تمارين
- اكتب دالة
int max3(int a, int b, int c)تُرجع أكبر ثلاثة أعداد صحيحة. - اكتب دالة
void swap_wrong(int a, int b)تحاول مبادلة قيمتين. تحقق في main إن نجحت، واشرح السبب. - اكتب دالة
int digit_sum(int n)تحسب مجموع أرقام عدد صحيح موجب (مثلًا: 123 تُرجع 6). اختبرها في main.