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

الدوال في C++‎: المراجع والقيم الافتراضية و Lambda

الدرس 5 من 11 · C++

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

#include <iostream>
using namespace std;
void greet() {
cout << "أهلاً بك في C++!" << endl;
}
int add(int a, int b) {
return a + b;
}
int main() {
greet();
cout << add(3, 4) << endl;
return 0;
}
الناتج
أهلاً بك في C++!
7
  • نوع القيمة المرجعة قبل الاسم، و void تعني «لا تُرجع شيئاً».
  • المعاملات بأنواعها بين القوسين، و return تُرجع القيمة وتُنهي الدالة.
  • كما في C، إذا عرّفت الدالة بعد main، تحتاج نموذجاً أولياً (Prototype) قبل main. لذلك وضعنا الدوال في الأعلى هنا.

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

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

add(3, 4) تستدعي النسخة الأولى وتُرجع 7، و add(2.5, 1.5) تستدعي الثانية وتُرجع 4. المترجم يختار حسب أنواع ما ترسله. (لاحظ أن cout تطبع 4 وليس 4.0، لأنها تحذف الأصفار غير الضرورية.)

string makeTea(int sugar = 2) {
return "شاي بـ " + to_string(sugar) + " ملاعق سكر";
}
cout << makeTea() << endl;
cout << makeTea(0) << endl;
الناتج
شاي بـ 2 ملاعق سكر
شاي بـ 0 ملاعق سكر

المعاملات ذات القيم الافتراضية يجب أن تكون في آخر القائمة.

التمرير بالقيمة والتمرير بالمرجع

Section titled “التمرير بالقيمة والتمرير بالمرجع”

هذه أهم فكرة في الدرس:

void tryToChange(int x) {
x = 100;
}
void addPoints(int& score, int bonus) {
score += bonus;
}
int main() {
int value = 5;
tryToChange(value);
cout << value << endl;
int score = 10;
addPoints(score, 5);
cout << score << endl;
return 0;
}
الناتج
5
15
  • التمرير بالقيمة int x: الدالة تستقبل نسخة. تعديلها لا يؤثر في الأصل، لذلك بقيت value تساوي 5.
  • التمرير بالمرجع int& score: الدالة تستقبل المتغير الأصلي نفسه باسم آخر. تعديله يغيّر الأصل، فأصبحت score تساوي 15.

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

في C كنت تحتاج المؤشرات و & و * لتحقيق هذا. في C++‎ المرجع أبسط: تكتب & في المعامل فقط، وتستخدم المتغير داخل الدالة كأي متغير عادي.

ماذا لو كان المعامل نصاً طويلاً أو قائمة كبيرة؟ نسخه بطيء. وتمريره بالمرجع يعرّضه للتعديل بالخطأ. الحل: مرجع ثابت:

void printName(const string& name) {
cout << "الاسم: " << name << endl;
}

printName("منى") تطبع «الاسم: منى». الدالة تقرأ الأصل دون نسخ، و const يمنعها من تعديله. هذا هو الأسلوب المعتاد لتمرير النصوص والكائنات الكبيرة في C++‎.

يمكنك تعريف دالة صغيرة داخل دالة أخرى، وتخزينها في متغير:

auto square = [](int x) { return x * x; };
cout << square(6) << endl;
الناتج
36
  • [] بداية الـ Lambda. (وما بداخل القوسين المربعين يحدد المتغيرات الخارجية التي تستطيع رؤيتها، وهو موضوع متقدم.)
  • (int x) المعاملات، و { ... } الكود.

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

#include <iostream>
using namespace std;
double priceAfterDiscount(double price, double percent = 10) {
return price - price * percent / 100;
}
int main() {
cout << priceAfterDiscount(200) << endl;
cout << priceAfterDiscount(200, 25) << endl;
return 0;
}
الناتج المتوقع
180
150

تحدٍّ: اكتب void swapValues(int& a, int& b) تبدّل قيمتي متغيرين، وقارنها بنسخة C التي تحتاج مؤشرات.

  • التوقع أن التمرير بالقيمة يغيّر الأصل: استخدم & إذا أردت التعديل.
  • تمرير قيمة ثابتة لمرجع غير ثابت: addPoints(10, 5) خطأ، لأن المرجع يحتاج متغيراً حقيقياً يمكن تعديله.
  • قيمة افتراضية في غير آخر المعاملات: void f(int a = 1, int b) خطأ ترجمة.

سؤال

ما قيمة x بعد استدعاء void inc(int& n) { n++; } بـ inc(x) إذا كانت x تساوي 7؟

سؤال

ما الطريقة المفضلة لتمرير string كبير لدالة تقرؤه فقط؟

  • الدوال: نوع مرجع، واسم، ومعاملات، و void لا تُرجع شيئاً.
  • Overloading: نفس الاسم بمعاملات مختلفة، والقيم الافتراضية في آخر المعاملات.
  • بالقيمة T نسخة، وبالمرجع T& الأصل نفسه، وبالمرجع الثابت const T& قراءة بلا نسخ.
  • المراجع في C++‎ أبسط من المؤشرات في C لتعديل المتغيرات.
  • Lambda دالة صغيرة مجهولة [](int x) { ... }.