تخطَّ إلى المحتوى

الدوال في C: التعريف والنموذج والمؤشرات

الدرس 5 من 10 · C

البرامج الكبيرة لا تُكتب كتلة واحدة، بل تُقسَّم إلى دوال صغيرة، كل واحدة تؤدي مهمة واحدة واضحة. أنت تستخدم الدوال منذ أول درس: main دالة، و printf دالة. في هذا الدرس ستكتب دوالك الخاصة، وستتعرف على مفهوم من أشهر مفاهيم C وأهمها: المؤشرات، في أبسط صورها.

#include <stdio.h>
int add(int a, int b) {
return a + b;
}
void greet(void) {
printf("أهلاً بك في C!\n");
}
int main(void) {
greet();
printf("%d\n", add(3, 4));
return 0;
}
الناتج
أهلاً بك في C!
7
  • int قبل الاسم: نوع القيمة التي تُرجعها الدالة.
  • (int a, int b): المعاملات وأنواعها.
  • return: تُرجع القيمة وتُنهي الدالة.
  • void كنوع مرجع: الدالة لا تُرجع شيئاً. و void بين القوسين: لا تستقبل معاملات.

وحتى main تُرجع int: القيمة 0 في return 0; تخبر نظام التشغيل أن البرنامج انتهى بنجاح.

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

#include <stdio.h>
int square(int x); /* النموذج الأولي */
int isEven(int n);
int main(void) {
printf("%d\n", square(5));
printf("%d\n", isEven(4));
printf("%d\n", isEven(7));
return 0;
}
int square(int x) {
return x * x;
}
int isEven(int n) {
return n % 2 == 0;
}
الناتج
25
1
0

النموذج الأولي مثل فهرس الكتاب: يخبر المترجم مسبقاً بأسماء الدوال وما تستقبله وما تُرجعه، فيتعرف عليها عند استدعائها، ثم يجد تفاصيلها لاحقاً في الملف. وهذا يسمح لك بوضع main في الأعلى لتكون أول ما يقرؤه أي شخص يفتح الملف.

لاحظ أن isEven تُرجع 1 أو 0، فهكذا تمثّل C الصح والخطأ كما تعلمت في درس الشروط.

void tryToChange(int x) {
x = 100;
}
int main(void) {
int value = 5;
tryToChange(value);
printf("%d\n", value);
return 0;
}
الناتج
5

لم تتغير القيمة! لأن C ترسل للدالة نسخة من القيمة. الدالة غيّرت نسختها الخاصة، والمتغير الأصلي في main بقي كما هو. مثل أن تعطي صديقك صورة من مفتاح بيتك على ورقة: يستطيع الرسم عليها، لكنه لا يستطيع فتح بيتك بها.

المؤشرات: تعديل المتغير الأصلي

Section titled “المؤشرات: تعديل المتغير الأصلي”

ماذا لو أردت فعلاً أن تغيّر الدالة متغيراً في main؟ تعطيها عنوانه في الذاكرة بدلاً من نسخة من قيمته:

void addPoints(int *score, int bonus) {
*score = *score + bonus;
}
int main(void) {
int score = 10;
addPoints(&score, 5);
printf("%d\n", score);
return 0;
}
الناتج
15

ثلاثة رموز جديدة:

  • &score: «عنوان المتغير score في الذاكرة».
  • int *score: المعامل مؤشر (Pointer)، أي متغير يحمل عنواناً لمكان فيه int.
  • *score: «القيمة الموجودة عند هذا العنوان». فـ *score = ... تكتب في المتغير الأصلي نفسه.

تذكّر تشبيه الشارع من درس المتغيرات: الذاكرة صناديق مرقمة. بدلاً من أن تعطي الدالة نسخة مما في الصندوق، أعطيتها رقم الصندوق، فذهبت إليه وغيّرت ما بداخله.

C تأتي مع مكتبة رياضيات جاهزة:

#include <math.h>
printf("%.1f\n", sqrt(16.0));
printf("%.0f\n", pow(2, 10));
الناتج
4.0
1024

sqrt للجذر التربيعي، و pow للأس، وكلاهما يُرجع double. وعلى بعض الأنظمة مثل Linux، تحتاج إضافة -lm في أمر الترجمة: gcc main.c -o main -lm.

#include <stdio.h>
double priceAfterDiscount(double price, double percent);
int main(void) {
printf("%.1f\n", priceAfterDiscount(200, 10));
printf("%.1f\n", priceAfterDiscount(200, 25));
return 0;
}
double priceAfterDiscount(double price, double percent) {
return price - price * percent / 100;
}
الناتج المتوقع
180.0
150.0

تحدٍّ: اكتب دالة void swap(int *a, int *b) تبدّل قيمتي متغيرين في main. تلميح: تحتاج متغيراً مؤقتاً.

  • استدعاء دالة قبل تعريفها بدون نموذج أولي: تحذير أو خطأ implicit declaration of function. أضف النموذج في الأعلى.
  • نسيان & عند تمرير العنوان: addPoints(score, 5) ترسل القيمة بدل العنوان، والمترجم يحذرك.
  • نسيان return في دالة غير void: تُرجع قيمة عشوائية، والمترجم مع -Wall يحذرك.

سؤال

ما فائدة النموذج الأولي int square(int x); في أعلى الملف؟

سؤال

ماذا يعني &score في C؟

  • الدالة: نوع مرجع، واسم، ومعاملات بأنواعها، و void تعني لا شيء.
  • النموذج الأولي في أعلى الملف يسمح باستدعاء الدالة قبل تعريفها.
  • C ترسل نسخة من القيم، فالدالة لا تغيّر المتغير الأصلي.
  • المؤشرات: &x عنوان المتغير، و int *p مؤشر، و *p القيمة عند العنوان.
  • math.h فيها sqrt و pow، وقد تحتاج -lm عند الترجمة.