لماذا توجد الدوال
الدالة كتلة من التعليمات البرمجية لها اسم، يمكنك تشغيلها عند الطلب عبر استدعائها. فبدلًا من تكرار المنطق نفسه في عدة مواضع، تكتبه مرة واحدة وتمنحه اسمًا ثم تستدعي ذلك الاسم حيثما احتجت إليه. هذا يجعل البرامج أقصر وأسهل في القراءة وأسهل في الإصلاح؛ فأنت تُغيّر المنطق في مكان واحد ويحصل كل المُستدعِين على التحديث.
لقد كنت بالفعل تستدعي دالة طوال هذا الوقت: main. إنها نقطة الدخول التي يبدأ منها كل برنامج C++. الآن ستكتب دوالك الخاصة. الحلقات التي رأيتها سابقًا، مثل حلقة for المعتمدة على النطاق، غالبًا ما تعيش داخل الدوال كي تتمكن من إعادة استخدام قطعة كاملة من المنطق عبر اسمها.
تشريح الدالة
لكل دالة أربعة أجزاء: نوع الإرجاع والاسم وقائمة المعاملات بين قوسين والجسم بين قوسين معقوفين.
int add(int a, int b) { // نوع الإرجاع | الاسم | المعاملات
return a + b; // الجسم
}
intهو نوع الإرجاع، أي نوع القيمة التي تُعيدها الدالة.addهو الاسم الذي تستخدمه لاستدعائها.(int a, int b)هي المعاملات، أي المدخلات التي يوفّرها المُستدعِي.- يحتوي القوسان المعقوفان على الجسم، وهو التعليمات البرمجية التي تُنفَّذ عند استدعائها.
وها هي ضمن برنامج كامل. تعريف الدالة فوق main يعني أن main يستطيع رؤيتها عند استدعائها.
الاستدعاء add(2, 3) يُشغّل الدالة بقيمتي a = 2 وb = 3، ويتحوّل التعبير بأكمله ليصبح القيمة المُرجَعة. يمكنك تخزينها في متغير أو استخدامها مباشرةً داخل تعبير آخر، كما يفعل سطر cout الثاني.
إرجاع قيمة
تؤدي جملة return أمرين: تُعيد قيمة إلى المُستدعِي، وتُنهي الدالة فورًا. أي تعليمات تأتي بعد return لا تُنفَّذ؛ إذ يقفز التحكّم مباشرةً إلى الموضع الذي استُدعيت منه الدالة.
يجب أن يطابق نوع القيمة المُرجَعة نوع الإرجاع المُعلَن (أو أن يكون قابلًا للتحويل إليه). الدالة المُعلَنة بنوع int ينبغي أن تُرجع int؛ وعدم إرجاع أي شيء، أو الخروج من النهاية دون return، خطأ في أي دالة ليست من نوع void.
دوال void
ليست كل دالة تُنتِج قيمة. عندما تكتفي الدالة بأن تفعل شيئًا — تطبع مخرجات، تُحدّث حالة — يكون نوع إرجاعها void. يمكن لدالة void أن تستخدم return; مجرّدة للخروج مبكرًا، أو أن تستمر ببساطة حتى القوس المعقوف الختامي.
محاولة استخدام نتيجة دالة void — int x = greet("Ada"); — هي خطأ في الترجمة، لأنه لا توجد قيمة لإسنادها. ومن الأخطاء الشائعة كتابة return someValue; داخل دالة void؛ والمُترجِم يرفض ذلك أيضًا.
الإعلانات مقابل التعريفات
تقرأ C++ الملف من أعلى إلى أسفل، لذا يجب افتراضيًّا أن تظهر الدالة قبل التعليمات البرمجية التي تستدعيها. وعندما يكون هذا الترتيب غير عملي، تُقسّم الدالة إلى إعلان (يُسمّى أيضًا النموذج الأولي) وتعريف.
يذكر الإعلان توقيع الدالة وينتهي بفاصلة منقوطة، دون جسم. وهو يَعِد المُترجِم بأن "هذه الدالة موجودة؛ وهكذا تُستدعى". ويمكن للتعريف الكامل أن يأتي بعد ذلك، حتى بعد main.
بدون النموذج الأولي في السطر 4، سيصادف المُترجِم square(5) داخل main قبل أن يكون قد رأى square على الإطلاق، وسيفشل البناء. والنماذج الأولية هي أيضًا الطريقة التي تتيح بها ملفات الترويسة لكثير من الملفات المصدرية أن تتشارك الدوال نفسها. لاحظ أن أسماء المعاملات في الإعلان اختيارية: int square(int); تعمل بالكفاءة نفسها؛ فالأنواع وحدها هي ما يهمّ المُترجِم.
الأخطاء الشائعة
هناك بضعة مزالق توقع المبتدئين مرارًا وتكرارًا:
- الاستدعاء قبل الإعلان. إذا ظهر لك الخطأ "
addwas not declared in this scope"، فالدالة مُعرَّفة أسفل أول استدعاء لها وليس لها نموذج أولي. انقل التعريف إلى أعلى أو أضِف نموذجًا أوليًّا. - نسيان الإرجاع. الوصول إلى نهاية دالة ليست
voidدونreturnسلوك غير مُعرَّف؛ يتلقّى المُستدعِي قيمة عشوائية. ترجِم مع تفعيل التحذيرات (-Wall) وسيُشير المُترجِم إلى ذلك. - الخلط بين التعريف والاستدعاء. للتعريف جسم بين قوسين معقوفين وبلا فاصلة منقوطة ختامية. وللإعلان فاصلة منقوطة وبلا جسم. الخلط بينهما — مثل وضع فاصلة منقوطة مباشرةً بعد قائمة معاملات دالة كنت تنوي تعريفها — يُنتج أخطاءً محيّرة.
- تجاهل القيمة المُرجَعة.
add(2, 3);وحدها في سطرها تُترجَم، لكن المجموع المحسوب يُهمَل بصمت. تأكّد من أنك تستخدم فعلًا ما تُرجعه الدالة.
// تبدو كتعريف، لكن الفاصلة المنقوطة الشاردة ; تحوّلها إلى
// إعلان يتبعه كتلة متبقّية — وهو خطأ طباعي متكرر:
int triple(int n); // <- هذه الـ ; تُنهي الجملة
{
return n * 3; // n غير مُعرَّف هنا؛ هذه الكتلة صارت يتيمة الآن
}
التالي: معاملات الدوال
رأيت كيف تتلقّى الدوال المدخلات عبر قائمة معاملاتها، لكن الأمر أوسع من ذلك بكثير. تتعمّق الصفحة التالية في معاملات الدوال: التمرير بالقيمة مقابل التمرير بالمرجع، والوسائط الافتراضية، ومعاملات const، وكيف يؤثّر هذا الاختيار في قدرة دالتك على تغيير بيانات المُستدعِي.
الأسئلة الشائعة
كيف تكتب دالة في C++؟
امنحها نوع إرجاع واسمًا وقوسين للمعاملات وجسمًا بين قوسين معقوفين: int add(int a, int b) { return a + b; }. استدعِها باسمها مع الوسائط، مثل add(2, 3). إذا كانت الدالة لا تُرجع شيئًا، فاستخدم void كنوع للإرجاع.
ما الفرق بين إعلان الدالة وتعريفها في C++؟
الإعلان (أو النموذج الأولي) يُخبر المُترجِم باسم الدالة ونوع إرجاعها ومعاملاتها، وينتهي بفاصلة منقوطة: int add(int a, int b);. أما التعريف فيوفّر أيضًا الجسم بين القوسين المعقوفين. يمكنك الإعلان عن دالة قبل main ثم تعريفها لاحقًا؛ فالإعلان يتيح لك استدعاءها قبل ظهور التعريف.
ماذا يحدث إذا لم تُرجع دالة C++ قيمة؟
في دالة void لا يحدث شيء؛ إنها تنتهي ببساطة. أما في دالة ليست من نوع void، فالوصول إلى النهاية دون return هو سلوك غير مُعرَّف: يتلقّى المُستدعِي قيمة عشوائية وقد يتصرف البرنامج بشكل خاطئ. تُحذّر معظم المُترجِمات من ذلك؛ احرص دائمًا على إرجاع قيمة في كل مسار من مسارات أي دالة ليست void.