الدوال

ما هي الدالة؟

الدالة هي مجموعة من الأوامر مجمعة تحت اسم معين. يمكن استدعاء هذا الاسم لتنفيذ الأوامر عند الحاجة.

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

  1. اسم الدالة : الاسم الذي سيتم استخدامه لاستدعاء الدالة.
  2. محتوى الدالة : المكان الذي تكتب فيه أوامر الدالة.
  3. المعاملات : القيم التي تُمرر إلى الدالة عند استدعائها. يمكن أن تحتوي الدالة على أي عدد من المعاملات (بما في ذلك صفر).
  4. نوع القيمة المرجعة : الدوال يمكن أن تُرجع قيمة إلى الموضع الذي استدعيت منها. نوع القيمة المرجعة يحدد نوع هذه القيمة المرجعة. إذا لم تُرجع الدالة أي شيء، يجب أن يكون نوعها void.

صيغة الدوال

طريقة كتابة دالة

تُكتب الدوال عادةً خارج الدالة main() بالصورة التالية:

return_type function_name(parameter1, parameter2, ... , parameterN) {
    // محتوى الدالة
}
العنصر الشرح
return_type نوع القيمة التي سترجعها الدالة (مثل int, long long, string). إذا كان void، فلن تُرجع الدالة أي قيمة.
function_name الاسم الذي تستدعي به الدالة.
parameter1, parameter2, ... , parameterN معاملات الدالة.

طريقة استدعاء دالة

لاستدعاء دالة نستخدم الصيغة التالية:

function_name(value1, value2, ... , valueN);

مثال

مثال على برنامج يحتوي على دالة لا تُرجع أي قيمة (return_type = void) ولا تحتوي على معاملات هو كالتالي:

#include<iostream>
using namespace std;
 
void f() { //  وليس لديها معاملات (void) ولا ترجع أي قيمة f دالة باسم
    cout << "Hello Function" << endl;
}
 
int main() { // يبدأ البرنامج من هنا
    f();
    cout << "Hello Main" << endl;
    f();
}

الناتج سيكون:

Hello Function
Hello Main
Hello Function

هذا البرنامج يحتوي على دالتين: f() و main(). تنفذ C++ main() فقط، ومن داخلها يتم استدعاء باقي الدوال. أولاً يتم استدعاء f() من main()، فتبدأ بتنفيذ الأمر cout << "Hello Function"<< endl; عند الانتهاء من f()، يعود التنفيذ إلى main() ويستمر ببتنفيذ الأمر cout << "Hello Main" << endl; وثم تقوم باستدعاء f() مرة أخرى.

المعاملات

تعريف دالة بمعاملات

المعاملات هي متغيرات تستقبل قيمة عند استدعاء الدالة. تُعرف بالشكل التالي: parameter_type parameter_name. مثال:

void multiply(int x, int y) {
    cout << x * y << endl;
}

هنا قمنا بتعريف دالة multiply() التي تستقبل معاملين (x و y) وتطبع حاصل ضربهما.

تمرير المعاملات

لاستدعاء multiply() نضع القيم التي نريد تمريرها داخل الأقواس عند الاستدعاء. يجب أن يتطابق ترتيب القيم الممررة مع ترتيب المعاملات في التعريف. مثال لطباعة حاصل ضرب 5 و 3:

multiply(5, 3);

هنا تم استدعاء الدالة بحيث x = 5 و y = 3.

flowchart BT
    subgraph استدعاء
        F["multiply(5, 3)"]
    end

    subgraph الدالة
        M["int multiply(int x, int y)"]
        X["x = 5"]
        Y["y = 3"]
    end

    F --> X
    F --> Y
    X --> M
    Y --> M

تمرير المعاملات بالمرجع

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

لتمرير المعامل بالمرجع نضف & قبل اسم المعامل. مثال:

void addOneByValue(int x) {
    x = x + 1;
}
void addOneByReference(int &x) {
    x = x + 1;
}

مثال يستخدم كلا الطريقتين:

#include<iostream>
using namespace std;

void addOneByReference(int &x) {
    x = x + 1;
}

void addOneByValue(int x) {
    x = x + 1;
}

int main() {
    int n = 5;
    addOneByValue(n); // لن يتغير nو ،n سينسخ قيمة x 
    cout << n << endl;
    addOneByReference(n); // x يتغير بتغير nو ،n سيصبح x 
    cout << n << endl;
}

الناتج سيكون:

5
6

عند استدعاء addOneByValue(n); تم نسخ قيمة n إلى x، فالتعديل على x لم يغير قيمة n. وعند استدعاء addOneByReference(n); أصبح x هو n نفسه، فالتعديل على x غيّر قيمة n.

القيم المرجعة

بعد انتهاء أي دالة، يمكنها إرجاع قيمة للموضع التي استدعيت فيه بكتابة return. لكتابة دالة ترجع قيمة معينة يجب تحديد نوع القيمة المرجعة قبل اسم الدالة. مثال:

#include<iostream>
using namespace std;

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

int main() {
    int result = add(5, 3); // هي 8 result أصبحت قيمة
}
الجزء الشرح
int قبل add الدالة ترجع عدداً من نوع int.
return a + b تُرجع ناتج الجمع إلى مكان الاستدعاء.
int result = add(5, 3) تخزّن القيمة المرجعة في المتغير result.

sequenceDiagram
    participant main()
    participant Add()

    main()->>Add():  add(5, 3) استدعاء
    Add()->>Add():  a + b حساب
    Add()-->>main(): return 8
    main()->>main(): result = 8

Noteملحوظة

بعد return a + b; تنتهي الدالة فوراً، وأي تعليمات بعدها لن تُنفذ.

الاستدعاء في الدوال

استدعاء دوال أخرى

يمكن استدعاء دالة داخل دالة أخرى. عند انتهاء الدالة المستدعاة يعود التنفيذ إلى الدالة التي استدعتها. مثال:

#include<bits/stdc++.h>
using namespace std;

void m() {
    cout << "third" << endl;
}

void f() {
    cout << "second" << endl;
    m();
    cout << "fourth" << endl;
}

int main() {
    cout << "first" << endl;
    f();
    cout << "fifth" << endl;
}

الناتج:

first
second
third
fourth
fifth

البرنامج يبدأ بتنفيذ main() التي تنفذ cout << "first" << endl; ثم تستدعي f()، داخل f() يتم تنفيذ cout << "second" << endl; ثم يتم استدعاء m()، داخل m() يتم تنفيذ cout << "third" << endl; ثم تنتهي الدالة وتعود إلى f() لتنفيذ cout << "fourth" << endl; ، ثم تنتهي f() وتعود إلى main() لتنفيذ cout << "fifth" << endl;.

sequenceDiagram
    participant main()
    participant f()
    participant m()

    main()->>main(): cout << "first" << endl;
    main()->>f():  f() استدعاء
    f()->>f(): cout << "second" << endl;
    f()->>m():  m() استدعاء;
    m()->>m(): cout << "third" << endl;
    m()-->>f(): return
    f()->>f(): cout << "fourth" << endl;
    f()-->>main(): return
    main()->>main(): cout << "fifth" << endl;

الدوال العودية

الدوال العَودية (أو دوال الاستدعاء الذاتي) هي دوال تستدعي نفسها أثناء تنفيذها. مثال: حساب المضروب n! ويحسب كالتالي.

n! = 1 * 2 * 3 .... * (n - 1) * n

إذن فإن مضروب الرقم 4 (!4) هو :

24 = 4 * 3 * 2 * 1 = !4
Noteملحوظة

مضروب الرقم n هو حاصل ضرب مضروب n - 1 بn (أي n! = (n - 1)! * n).

#include<bits/stdc++.h>
using namespace std;

int factorial(int x) {
    if (x <= 1) {
        return 1;
    } else {
        return factorial(x - 1) * x;
    }
}

int main() {
    int n;
    cin >> n;
    cout << factorial(n) << endl;
}

في هذا البرنامج قمنا بكتابة دالة factorial() التي تستدعي نفسها لحساب مضروب رقم معين. إذا أدخلنا الرقم 3، سيتم استدعاء factorial(3) التي تستدعي factorial(2) والتي تقوم باستدعاء factorial(1). factorial(1) تُرجع 1 إلى الموضع الذي استدعيت فيه (factorial(2)). factorial(2) تُرجع factorial(1) * 2 = 1 * 2 = 2 إلى factorial(3)، وأخيراً تُرجع factorial(3) القيمة factorial(2) * 3 = 2 * 3 = 6 إلى main() لتُطبع.

Noteملحوظة

الشرط if (x <= 1) ضروري جداً لتجنب استدعاء الدالة لنفسها إلى ما لا نهاية.

sequenceDiagram
    participant ()main
    participant f(3)
    participant f(2)
    participant f(1)


    
    ()main->>f(3): استدعاء f(3)
    f(3)->>f(2): استدعاء f(2)
    f(2)->>f(1): استدعاء f(1)
    f(1)-->>f(2): return 1
    f(2)-->>f(3): return 1 * 2
    f(3)-->>()main: return 2 * 3