الدوال في البرمجة !

السلام عليكم ورحمة الله وبركاته

وقت القراءة: ≈ 15 دقيقة

المقدمة

الآن وصلنا للجزء الأهم في البرمجة وهي الدوال !

الدالة وتسمى بالإنجليزية Function أو Method تعتبر من أهم المفاهيم في البرمجة وهي تساعدنا في تنظيم البرنامج وتقسيمه إلى أجزاء صغيرة لتسهيل الفهم والتعديل وإعادة استخدام الكود

والدالة يمكنك تخيلها كما لو كانت مصنع صغير يستقبل بعض البيانات ويقوم بإنتاج ناتج معين وهذا الناتج يمكن استخدامه في أي مكان في البرنامج

                                                Function
                           +-----------------------------------------------+
                           |                                               |
 تستقبل بعض البيانات ----> |   تقوم بعمل بعض الأكواد والعمليات المختلفة    | ----> ترجع ناتج
                           |                                               |
                           +-----------------------------------------------+

فمثلًا نريد عمل برنامج بسيط يقوم بجمع عددين، الطريقة الأولى التي قد تتبادر إلى ذهنك هي كتابة الكود في المكان الذي تريد استخدامه فيه مباشرة هكذا

int x = 5;
int y = 10;

int sum = x + y;

cout << sum << '\n';

حسنًا هذا صحيح ولكن ماذا لو أردنا جمع عددين في أكثر من مكان في البرنامج ؟ ماذا ستفعل ؟ هل ستكتب الكود السابق في كل مكان ؟

// الآن نريد جمع عددين
int x = 5;
int y = 10;

int sum = x + y;

cout << sum << '\n';

// اكواد اخرى
// اكواد اخرى
// اكواد اخرى
// اكواد اخرى
// اكواد اخرى

// الآن نريد جمع عددين آخرين
// لنعيد كتابة الكود من جديد

int a = 20;
int b = 30;

int sum2 = a + b;

cout << sum2 << '\n';

هذا جيد ولكن هل ترى الكود يتكرر ؟ قمنا بجمع عددين في مكان ما في الكود ثم بعد الكثير من الأكواد قمنا بجمع عددين آخرين في مكان آخر وقمنا بإعادة كتابة نفس كود الجمع

ستقول لي الأمر بسيط جدًا وكل ما في الأمر هو كتابة سطرين فقط
سأقولك لك معك حق هنا نحن نتحدث عن كود بسيط جدًا كجمع رقمين لكن ماذا لو كان البرنامج كبيرًا ؟

هل ستقوم بكتابة نفس الكود في كل مكان ؟

// لدينا أراي ونريد جمع كل عناصرها

int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

int sum = 0;

for (int i = 0; i < 10; i++)
{
    sum += arr[i];
}

cout << sum << '\n';

// اكواد اخرى
// اكواد اخرى
// اكواد اخرى
// اكواد اخرى

// لدينا أراي أخرى ونريد جمع كل عناصرها

int arr2[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};

int sum2 = 0;

for (int i = 0; i < 10; i++)
{
    sum2 += arr2[i];
}

cout << sum2 << '\n';

// اكواد اخرى
// اكواد اخرى
// اكواد اخرى
// اكواد اخرى

// لدينا أراي أخرى ونريد جمع كل عناصرها

int arr3[] = {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000};

int sum3 = 0;

for (int i = 0; i < 10; i++)
{
    sum3 += arr3[i];
}

cout << sum3 << '\n';

هل ترى الكود يتكرر ؟ هل ترى العملية مملة ؟ هل ترى الكود غير منظم ؟
كل مرة نريد جمع عناصر أراي نقوم بكتابة نفس الكود من جديد
والكود ليس بسيطًا هنا وتخيل ماذا لو كان الكود كبيرًا جدًا ؟

هنا تأتي فائدة الدوال !

باستخدام الدوال يمكنك كتابة الكود مرة واحدة فقط واستخدامها في أي مكان في البرنامج بدون الحاجة لإعادة كتابته

                                                    Sum Array Function
                                    +-----------------------------------------------+
                                    |   int sum = 0                                 |
 تستقبل الأراي المراد جمعها ------> |    for (int i = 0; i < n; i++)                | ----> ترجع ناتج
                                    |        sum += arr[i];                         |
                                    +-----------------------------------------------+

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

وسنرى هذا بالتفصيل الآن

لكن أولًا كيف يمكننا إنشاء دالة ؟

كيفية إنشاء دالة

شكل الدالة يختلف من لغة برمجة لأخرى ولكن الفكرة هي نفسها
في الغالب يتكون شكل الدالة من الأتي

  1. اسم الدالة
  2. البيانات التي تستقبلها الدالة (إن وجدت)
  3. نوع البيانات التي ترجعها الدالة (إن وجدت)
  4. الكود الذي يتم تنفيذه عند استدعاء الدالة

في لغة الـ C++ يمكنك إنشاء دالة بهذا الشكل

void functionName()
{
    // الكود الذي سيتم تنفيذه عند استدعاء الدالة
}

هذه دالة بسيطة لا تستقبل أي بيانات ولا ترجع أي بيانات
كلمة void تعني أن الدالة لا ترجع أي بيانات وسنفهم ما معنى ذلك لاحقًا
واسم الدالة هو functionName ويمكنك تغييره إلى أي اسم تريده
والأقواس () تستخدم لوضع البيانات التي تستقبلها الدالة وسنفهم كيفية استخدامها لاحقًا
والأقواس {} تستخدم لوضع الكود الذي يتم تنفيذه عند استدعاء الدالة


بالطبع لا تحتاج لأن أذكرك أن شكل الدالة يختلف من لغة برمجة لأخرى ولكن الفكرة هي نفسها
أظن أننا رأينا أمثله على هذا في المقالات السابقة

لكن لأرضي فضولك هذا هو شكل الدالة في لغة الـ TypeScript

function functionName(): void {
  // الكود الذي سيتم تنفيذه عند استدعاء الدالة
}

لاحظ أن هناك تشابه كبير وهذا بسبب أن كل اللغات تتبع نفس المفاهيم الأساسية ونفس المبادئ


سنقوم الآن بإنشاء دالة تقوم بطباعة رسالة بسيطة

void printMessage()
{
    cout << "Hello From Function\n";
}

هذه الدالة تقوم هنا أنشأنا دالة تسمى printMessage وفي داخلها تقوم بطباعة رسالة بسيطة Hello From Function
وهذه الدالة لا تستقبل أي بيانات ولا ترجع أي بيانات فقط تقوم بطباعة جملة
لذلك استخدمنا كلمة void لتعريف أن الدالة لا ترجع أي بيانات

بعد ما قمنا بإنشاء الدالة يمكننا استدعائها ونستفيد منها ؟

كيفية استدعاء الدالة

حسنًا الآن قمنا بإنشاء دالة بسيطة تقوم بطباعة رسالة

void printMessage()
{
    cout << "Hello From Function\n";
}

وهي دالة لا تفعل شيء سوى طباعة رسالة فقط
ولكن كيف يمكننا استدعاء هذه الدالة ؟

ببساطة يمكنك استدعاء الدالة في أي مكان في الكود بكتابة اسم الدالة مع الأقواس ()

printMessage();

هذا كل شيء !

عندما تستدعي الدالة بهذه الطريقة سيتم تنفيذ الكود الذي بداخلها
بمعنى أنه عندما قمنا باستدعاء الدالة printMessage() هكذا سيتم تنفيذ ما بداخلها وهي طباعة الرسالة Hello From Function

يمكنك تخيل الأمر هكذا

                            printMessage الدالة
                            تقوم بطباعة رسالة
                +-----------------------------------------------+
                |                                               |
 لا تستقبل شيء  |       cout << "Hello From Function\n";        |  لا ترجع شيء
                |                                               |
                +-----------------------------------------------+
                                ٨
                                |
                                |  يتم الذهاب لمكان الدالة وتنفيذ الكود
نستدعي الدال                    |
printMessage(); ----------------+

هكذا سيتم طباعة الرسالة
`Hello From Function`

عندما نقوم باستدعاء الدالة printMessage() ستقوم لغة البرمجة بالذهاب لمكان الدالة وتنفيذ الكود الذي بداخلها وهو طباعة الرسالة Hello From Function

ويمكنك استدعاء الدالة في أي مكان في الكود وفي أي وقت تريده

void printMessage()
{
    cout << "Hello From Function\n";
}

printMessage();

// أكواد أخرى
// أكواد أخرى
// أكواد أخرى

printMessage();

// أكواد أخرى
// أكواد أخرى
// أكواد أخرى

printMessage();

لاحظ أننا قمنا بكتابة الدالة printMessage() ثلاث مرات وفي كل مرة يتم طباعة الرسالة Hello From Function

                            printMessage الدالة
                            تقوم بطباعة رسالة
                +-----------------------------------------------+
                |                                               |
 لا تستقبل شيء  |       cout << "Hello From Function\n";        |  لا ترجع شيء
                |                                               |
                +-----------------------------------------------+
                                ٨
                                |
                                |  يتم الذهاب لمكان الدالة وتنفيذ الكود
نستدعي الدال                    |
printMessage(); ----------------+
                                |
هكذا سيتم طباعة الرسالة         |
`Hello From Function`           |
                                |
                                |
نستدعي الدالة مرة أخرى          |
printMessage(); ----------------+
                                |
هكذا سيتم طباعة الرسالة مجددًا   |
`Hello From Function`           |
                                |
                                |
نستدعي الدالة مرة أخرى          |
printMessage(); ----------------+

هكذا سيتم طباعة الرسالة مجددًا
`Hello From Function`

لاحظ أنه في كل مرة نستدعي فيها الدالة printMessage() سيتم الذهاب لمكان الدالة وتنفيذ الكود الذي بداخلها في كل مرة

جعل الدالة تستقبل بيانات

الآن قمنا بإنشاء دالة بسيطة لكنها لم تكن تستقبل أي بيانات
الآن سننشيء دالة ولكن سنجعلها تستقبل بيانات

void printMessage(string message)
{
    cout << "Message: " << message << '\n';
}

الآن قمنا بتعديل الدالة وجعلناها تستقبل بيانات من نوع string
وتقوم بطباعة الرسالة التي تم إرسالها إليها
لاحظ أننا قمنا بوضع اسم المتغير داخل الأقواس () وهو message وقلنا أن نوعه string
أقواس الدالة تستخدم لتخبرنا عن البيانات التي تستقبلها الدالة وهذه البيانات تسمى المعاملات Parameters
وتستخدم فقط داخل الدالة

والآن يمكننا استدعاء الدالة وإرسال البيانات إليها

printMessage("Don't forget to pray your 5 prayers");

كهذا بكل بساطة قمنا باستدعاء الدالة printMessage وإرسال الرسالة Don't forget to pray your prayers إليها
الآن
والدالة ستقوم باستقبال الرسالة في متغير خاص بها هو message وثم ستقوم بتنفيذ الكود الذي بداخلها وهو طباعة الرسالة

                                            printMessage الدالة
                                            تقوم بطباعة رسالة
                                    +-----------------------------------------------+
                                    |                                               |
 string تستقبل قيمة من نوع  +-----> |    cout << "Message: " << message << '\n';    | لا ترجع شيء
    وتخزنه في متغير داخلها  |       |                                               |
              message يسمى  |       +-----------------------------------------------+
                            |
                            |
                            |
                            |
                            |
عندما نستدعي الدال          |
printMessage("Don't forget to pray your 5 prayers");

لاحظ شيء مهم جدًا هنا جملة Don't forget to pray your 5 prayers التي قمنا بإرسالها إلى الدالة
الدالة استقبلتها في متغير خاص بها هي فقط يسمى message وهذا المتغير يمكننا استخدامه داخل الدالة فقط
وهذا يعني أننا لا نستطيع استخدام المتغير message خارج الدالة printMessage

void printMessage(string message)
{
    cout << "Message: " << message << '\n';
}


cout << message; // ❌ أنا لم أرى هذا المتغير من قبل

عندما قمنا بمحاولة استخدام المتغير message خارج الدالة printMessage سيحدث خطأ
يقول لنا أنه لا يرى هذا المتغير لأن المتغير message لم يتم تعريفه في هذا النطاق وهو الـ Scope وهو المكان تنتمي إليه المتغيرات
بل تم تعريف المتغير message داخل الدالة printMessage فقط لذا فهو ينتمي إلى الدالة ويستخدم داخل الدالة فقط وليس خارجها


ملحوظة مهمة أخرى
لاحظ أن المتغير message نوعه string بمعنى أنك لا يمكنك إرسال أي نوع آخر إلى الدالة printMessage غير string

printMessage(5); // ❌
printMessage(5.5); // ❌
printMessage(true); // ❌
printMessage('A'); // ❌

وأيضًا إن استدعيت الدالة بدون إرسال أي بيانات سيحدث خطأ لأنك مجبر على أن ترسل شيء من نوع string

printMessage(); // ❌

طالما الدالة تريد بيانات فيجب أن تعطيها ما تريد سواء بكتابة القيمة مباشرةً أو بوضع متغير يحمل نفس نوع القيمة

printMessage("Hello"); // ✅

string message = "Hello";
printMessage(message); // ✅

لاحظ أننا قمنا بإرسال القيمة مباشرةً أو عن طريق متغير

قيم افتراضية للبيانات

في بعض الأحيان تحتاج لإعطاء قيمة افتراضية للبيانات التي تستقبلها الدالة
بحيث أننا إذا لم نرسل أي شيء لدالة عندما نستدعيها ستأخذ الدالة تلك القيمة الافتراضية

void printMessage(string message = "No Message")
{
    cout << "Message: " << message << '\n';
}

هنا قمنا بإعطاء قيمة افتراضية للمتغير message وهي No Message
هكذا يمكنك استدعاء الدالة بدون إرسال أي بيانات وستأخذ الدالة القيمة الافتراضية

printMessage(); // ✅ Message: No Message

وبالطبع يمكنك إرسال قيمة إلى الدالة وستأخذ الدالة تلك القيمة بدلًا من القيمة الافتراضية

printMessage("Hello Ahmed El-Tabarani"); // ✅ Message: Hello Ahmed El-Tabarani

جعل الدالة ترجع بيانات

حسنًا الآن وصلنا لجزء جديد وهو جعل الدالة ترجع بيانات
حتى الآن كل الدوال التي أنشأناها لا ترجع أي بيانات بل تقوم ببعض العمليات داخل الدالة أو تطبع شيء ما ولا ترجع شيء
وتذكر أن كلمة void الذي تكتبها قبل الدالة فائدتها أنها تقوم بتعريف أن الدالة لا ترجع أي بيانات

ولكن ماذا لو أردنا أن تقوم الدالة بعملية معينة ثم ترجع لنا ناتج هذه العملية ؟
في هذه الحالة بدلًا من كتابة void سنقوم بتحديد نوع البيانات التي سترجعها الدالة مثل int أو string أو bool أو أي نوع آخر

على سبيل المثال، لننشيء دالة تقوم بجمع عددين وترجع الناتج

int sum(int a, int b)
{
    int result = a + b;
}

هنا قمنا بإنشاء دالة تسمى sum تستقبل عددين int a و int b
وتقوم بجمع العددين وتخزين الناتج في متغير result
ولكن هنا هناك خطأ وهو أننا حددنا أن الدالة يجب أن ترجع int لكنها في المثال الذي كتبناه لا تقوم بإرجاع الناتج

بمعنى أن الدالة sum تقوم بجمع العددين وتخزين الناتج في متغير result ولكنها لا ترجع هذا الناتج بعد
لذا يجب أن نستخدم الكلمة المفتاحية return لإرجاع الناتج

int sum(int x, int y)
{
    int z = x + y;
    return z;  // هنا نقوم بإرجاع الناتج
}

هكذا الآن قمنا بإرجاع الناتج باستخدام كلمة return

والآن يمكنك استدعاء الدالة وارسال العددين إليها

sum(5, 10);

حسنًا .. ممم ااا أين الناتج ؟
هنا نحن استدعينا الدالة sum وأرسلنا العددين 5 و 10 إليها والدالة قامت بالفعل بجمع العددين وارجعته
لكن نحن نحتاج لاستقبال هذا الناتج التي ترجعه الدالة

int result = sum(5, 10);
cout << result << '\n'; // 15

طالما الدالة ترجع ناتج يجب عليك تخزين هذا الناتج في متغير ثم استخدامه
وهذا ما فعلناه قمنا بتخزين الناتج الذي ترجعه الدالة في متغير result ثم طبعناه

أو يمكننا طباعة الناتج مباشرةً بدون تخزينه في متغير

cout << sum(5, 10) << '\n'; // 15

هكذا قمنا بطباعة الناتج مباشرةً بدون تخزينه في متغير

إليك شكل تخيلي لما يحدث

                                        Sum Function
                                    +-----------------------+
                            +-----> |   int z = x + y;      |
    int تستقبل عددين من نوع |       |   return z;           |
         وتخزنهم في متغيرات |       +-----------------------+
               x و y داخلها |                   |
                            |                   |
                            |                   |
                            |                   |
                            |                   | بعد ما تقوم الدالة بجمع العددين
                            |                   | int سترجع الناتج من نوع
int result = sum(5, 10); ---+                   |
      ٨                                         |
      |                                         |
      +-----------------------------------------+

عندما نستدعي الدالة sum ونرسل لها العددين 5 و 10 ستقوم الدالة بجمع العددين وتخزين الناتج في متغير z
ثم ستقوم بارجاع القيمة الموجودة في المتغير z
وهذه القيمة ستكون ناتج جمع العددين 5 و 10 وهي 15
ثم نحن استقبلنا تلك القيمة في متغير result وثم طبعناه


بالطبع بما أننا حددنا أن الدالة ترجع ناتج من نوع int فلا يمكننا أن نرجع شيء آخر غير int

int sum(int x, int y)
{
    return "Hello"; // ❌
}

هنا لا نستطيع أن نرجع string طالما حددنا أن الدالة ترجع int

int sum(int x, int y)
{
    return x + y; // ✅
}

وأيضًا علينا استقبال الدالة في متغير من نفس نوع القيمة التي ترجعها الدالة

string result = sum(5, 10); // ❌

هنا لا يمكننا أن نستقبل الناتج في متغير من نوع string طالما أن الدالة ترجع int

int result = sum(5, 10); // ✅

إذا كانت الدالة ترجع void فلا يمكننا استقبالها في متغير

void printMessage()
{
    cout << "Hello From Function\n";
}

int result = printMessage(); // ❌

هنا لا يمكننا أن نستقبل الدالة printMessage في متغير من نوع int طالما أن الدالة لا ترجع شيء هذا ما يعنيه void


لاحظ أن أي كود يأتي بعد كلمة return لن يتم تنفيذه
لأن الدالة تنتهي عندما تقوم بعمل return وترجع القيمة
لذا لا يمكنك وضع أي كود بعد return لأنه لن يتم تنفيذه

int sum(int x, int y)
{
    return x + y;
    // return لن يتم تنفيذ أي كود أخر بعد هذا السطر لأن الدالة انتهت عند
    int z = x + y;
    cout << z << '\n';
}

بالطبع يمكنك استخدام الدالة في أي وقت وفي أي مكان هذا هو الهدف منها

int result1 = sum(5, 10);
cout << result1 << '\n'; // 15

int result2 = sum(10, 20);
cout << result2 << '\n'; // 30

int result3 = sum(100, 200);
cout << result3 << '\n'; // 300

بالطبع كما قلنا أن عملية كجمع رقمين قد تكون بسيطة ولا تحتاج لأن تكون دالة
وأنت معك حق لكن هذا مثال بسيط لتوضيح كيفية إنشاء الدوال واستدعائها واستخدامها
لنجعل الأمور أكثر تعقيدًا الآن

استخدام الدوال في برامج أكبر

الآن بعد أن فهمنا كيفية إنشاء الدوال واستدعائها واستخدامها، يمكننا استخدامها في برامج أكبر وأكثر تعقيدًا
على سبيل المثال، سننشئ دالة تقوم بحساب مجموع عناصر أراي وترجع الناتج

int sumArray(int arr[], int n)
{
    int sum = 0;
    for (int i = 0; i < n; i++)
    {
        sum += arr[i];
    }
    return sum;
}

هنا لاحظ أننا قمنا بإنشاء دالة تسمى sumArray تستقبل أراي arr[] من نوع int
وأيضًا تستقبل طول الأراي n من نوع int أيضًا

وتقوم الدالة بحساب مجموع عناصر الأراي وترجع الناتج كما ترى
هذه عملية كبيرة وليست ببسيطة ولذلك يمكننا استخدام الدالة لتسهيل الأمور

ويمكننا استدعاء الدالة واستخدامها في أي مكان في الكود

int arr1[] = {1, 2, 3, 4, 5};
int sum1 = sumArray(arr1, 5);
cout << sum1 << '\n'; // 15

int arr2[] = {10, 20, 30, 40, 50};
int sum2 = sumArray(arr2, 5);
cout << sum2 << '\n'; // 150

int arr3[] = {100, 200, 300, 400, 500};
int sum3 = sumArray(arr3, 5);
cout << sum3 << '\n'; // 1500

الآن يمكنك رؤية فائدة الدوال في تسهيل الكود وتنظيمه وإعادة استخدامه بدون الحاجة لإعادة كتابة نفس الكود مرة أخرى
هذا هو جمال الدوال في البرمجة!


مثال آخر، سننشئ دالة تقوم بالبحث عن عنصر معين في أراي وترجع لنا الـ index الذي يوجد فيه العنصر
وإذا لم يكن العنصر داخل الأراي يمكننا ارجاع -1 على سبيل المثال ليمثل أن العنصر غير موجود

int search(int arr[], int n, int target)
{
    for (int i = 0; i < n; i++)
    {
        if (arr[i] == target)
        {
            return i;
        }
    }
    return -1; // To indicate that the target not found
}

هنا قمنا بإنشاء دالة تسمى search تستقبل أراي arr[] من نوع int وطول الأراي n والعنصر المراد البحث عنه target
وتقوم الدالة بالبحث عن العنصر الذي نريده وإذا وجدته ترجع الـ index الخاص به وإذا لم تجده ترجع -1'
يمكنك أن تستبدل -1 بأي شيء تريده لتعبر عن أن العنصر غير موجود

ويمكننا استدعاء الدالة واستخدامها في أي مكان في الكود كما قلنا

int arr[5] = {76, 21, 13, 144, 57};
int target = 144;
int index = search(arr, 5, target);

if (index != -1)
{
    cout << "The target found at index " << index << '\n';
}
else
{
    cout << "The target not found\n";
}

هكذا قمنا بالبحث عن العنصر 144 داخل الأراي وإذا وجدناه سنطبع The target found at index 3 وإذا لم نجده سنطبع The target not found

ما الفرق بين by-reference و by-value

هنا مبدأ مهم جدًا في البرمجة في تعاملها مع الدوال وهو مبدأ by-reference و by-value
بمعنى هل نرسل نسخة من قيمة المتغير إلى الدالة أم نرسل المتغير نفسه إلى الدالة

سأوضح السؤال أكثر بمثال

لنفترض أن لدينا دالة تقوم بزيادة قيمة المتغير بمقدار واحد

int increment(int n)
{
    n++;
    return n;
}

هنا قمنا بإنشاء دالة تسمى increment تستقبل متغير n وتقوم بزيادة قيمته بمقدار واحد
ثم ترجع الناتج

الآن يمكننا استدعاء الدالة واستخدامها بهذا الشكل كما تعودنا

cout << increment(5); // 6

هنا قمنا بإرسال القيمة 5 إلى الدالة increment وقمنا بزيادة القيمة بمقدار واحد وطبعنا الناتج

أين السؤال ؟

السؤال في المثال التالي، لنفترض أن لدينا متغير يدعى age ونريد زيادة قيمته بمقدار واحد

int age = 20;

cout << increment(age); // 21

هنا قمنا بإرسال المتغير age إلى الدالة increment وقمنا بزيادة قيمته بمقدار واحد وطبعنا الناتج
السؤال هنا، الآن قيمة المتغير age تم تعديل قيمتها داخل الأراي فقط وليس خارجها
فهل قيمة المتغير age التي خارج الدالة هل تأثرت بالتعديل الذي حدث داخل الدالة ؟

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

int age = 20;

cout << increment(age); // 21
cout << age; // 20

هنا قمنا بطباعة قيمة المتغير age بعد استدعاء الدالة ولم نجد أي تغيير في قيمة المتغير age بل ظلت قيمتها 20 كما هي
هذا ما نسميه بالـ pass by-value أي أننا نرسل نسخة من قيمة المتغير إلى الدالة وليس المتغير نفسه


لكن هناك مبدأ آخر يسمى pass by-reference وهو عكس pass by-value
في pass by-reference يتم إرسال المتغير نفسه إلى الدالة وليس نسخة من قيمته
بالتالي أي تعديل يحدث على المتغير داخل الدالة سيؤثر على المتغير خارج الدالة

لنقم بإعادة كتابة الدالة increment ونجعلها pass by-reference

int increment(int &n)
{
    n++;
    return n;
}

في لغة الـ C++ يتم تحديد pass by-reference بوضع & قبل اسم المتغير
وهو معناها أننا نرسل المتغير نفسه إلى الدالة وليس نسخة من قيمته

الآن يمكننا استدعاء الدالة وإرسال المتغير age إليها

int age = 20;

cout << increment(age); // 21
cout << age; // 21

هنا قمنا بطباعة قيمة المتغير age بعد استدعاء الدالة ووجدنا أن قيمة المتغير age تغيرت إلى 21
لأننا قمنا بإرسال المتغير نفسه إلى الدالة وليس نسخة من قيمته فالتعديل الذي حدث داخل الدالة أثر على المتغير خارج الدالة

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

           age
        +--------+
        |   25   |
        +--------+
      0x7ffd1afe0a9c

هنا عندما نرسل المتغير age إلى الدالة increment بطريقة pass by-reference
فأننا نرسل عنوان المتغير الأصلي في الذاكرة وليس نسخة من قيمته

                                        increment Function
         [0x7ffd1afe0a9c] <---> n   +-----------------------+
         نرسل عنوان المتغير +-----> |   n++;                |
                            |       |   return n;           |
                            |       +-----------------------+
           age              |    أصبح يشاور على نفس العنوان n هكذا المتغير
        +--------+          |    age سيؤثر على المتغير n بالتالي أي تعديل يحدث على المتغير
        |   25   |          |
        +--------+          |
      0x7ffd1afe0a9c -------+

هكذا عندما نرسل المتغير age إلى الدالة increment بطريقة pass by-reference
يتم جعلت المتغير n الذي تستخدمه الدالة لاستقبال البيانات أصبح يشاور على نفس العنوان في الذاكرة الذي يشاور عليه المتغير age
بالتالي أي تعديل يحدث على المتغير n سيؤثر على المتغير age لأنهما يشاوران على نفس العنوان في الذاكرة

هذا هو باختصار مبدأ pass by-reference


أما في pass by-value فكما قلنا أننا نرسل نسخة من قيمة المتغير إلى الدالة وليس المتغير نفسه

دعونا نرى شكل تخيلي لأنني أعرف أنك تحب أن ذلك

                                                increment Function
                            +------+        +-----------------------+
   تم إنشاء نسخة من القيمة  |  25  | -----> |   n++;                |
           وارسالها للدالة  +------+        |   return n;           |
                                ٨           +-----------------------+
           age                  |
        +--------+              |   لم يتم ارسال عنوان المتغير بل تم إرسال نسخة من قيمته
        |   25   | -------------+   age لن يؤثر على المتغير n بالتالي أي تعديل يحدث على المتغير
        +--------+
      0x7ffd1afe0a9c

هكذا عندما نرسل المتغير age إلى الدالة increment بطريقة pass by-value
يتم إنشاء نسخة من قيمة المتغير age وترسل هذه النسخة إلى الدالة وليس المتغير نفسه
بالتالي أي تعديل يحدث على المتغير n داخل الدالة لن يؤثر على المتغير age لأنهما لا يشاوران على نفس العنوان في الذاكرة

وهذا ببساطة كان مبدأ pass by-value


بالطبع كل لغة برمجة تدعم مبدأ pass by-reference و pass by-value
وفي لغة الـ C++ يمكنك استخدام pass by-reference بوضع & قبل اسم المتغير في الدالة
أما pass by-value فهو الافتراضي ولا تحتاج لكتابة شيء

ستجد الأمر مشابه في باقي اللغات البرمجية

دوال جاهزة في لغات البرمجة

في لغات البرمجة توجد العديد من الدوال الجاهزة التي تقوم بالعديد من العمليات الشائعة بمعنى أن لغات البرمجة توفر لك مجموعة من الدوال الجاهزة التي يمكنك استخدامها بدون الحاجة لكتابتها من الصفر لكنها كتبت بالفعل داخل اللغة التي تستخدمها

على سبيل المثال، في لغة الـ C++ توجد دالة تسمى max تقوم بإرجاع العدد الأكبر من بين عددين

int maxNumber = max(10, 20);
cout << maxNumber << '\n'; // 20

هنا قمنا باستخدام الدالة max التي تقوم بإرجاع العدد الأكبر من بين العددين 10 و 20 وهو 20
وهذه الدالة موجودة بالفعل في لغة الـ C++ ويمكنك استخدامها بدون الحاجة لكتابتها من الصفر

بالطبع إذا كانت اللغة التي تستعملها لا تحتوي على هذه الدوال يمكنك كتابتها بنفسك

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

لكن طالما الدالة موجودة بالفعل في اللغة لماذا تكتبها من الصفر ؟
إلا إذا كنت تريد ان تمرن تفكيرك وتحاول كتابتها بنفسك لتعرف كيف تعمل وتفهمها أكثر

بالطبع كل لغة تحتوي على عدد قد لا يحصى من الدوال الجاهزة التي يمكنك استخدامها
كل مع عليك فقط هو معرفة اللغة وتعلمها واستكشاف تلك الدوال


بعض الدوال الجاهزة الشائعة في لغة الـ C++

وكذلك في لغات مثل JavaScript و Python و Java وغيرها توجد العديد من الدوال الجاهزة التي يمكنك استخدامها

الخاتمة

الدوال هي أحد أهم المفاهيم في البرمجة وتستخدم لتنظيم الكود وتسهيله وإعادة استخدامه
باستخدام الدوال يمكنك كتابة الكود مرة واحدة واستخدامه في أي مكان في البرنامج بدون الحاجة لإعادة كتابته

استخدامك للدوال يجعل الكود أكثر تنظيمًا وأكثر قابلية للقراءة والفهم والتعديل عليه
لذا عندما تلاحظ أنك تقوم بكتابة نفس الكود مرارًا وتكرارًا فهذا يعني أنك بحاجة لاستخدام الدوال
لكي تضع الكود الذي يتكرر داخل دالة وتستقبل البيانات التي تريدها وترجع لك الناتج


بالطبع عالم الدوال واسع جدًا وتسمع على مصطلحات مثل Callback Functions و Pure Functions و Higher-Order Functions
Recursion و Anonymous Functions وغيرها الكثير وتسمع عن اسلوب برمجي يسمى Functional Programming
وهو أسلوب برمجي وعالم واسح يحتوى على العديد من المفاهيم والأساليب والتقنيات


يوجد مقالة هنا في المدونة عن الـ Recursion Function يمكنك قراءتها من هنا كودك بـ Recursion نفسه ؟