C++: تمرير معلمات الدوال

آخر تحديث: 2026-08-26

في الدرس 10 تعلمنا أساسيات الدوال وكيف يمكن للدوال استقبال معلمات.

لكن كيف تُمرَّر المعلمات إلى الدوال بالضبط؟ إذا تم تعديل معلمة داخل الدالة، هل يتغير المتغير الخارجي؟

هذا ما يغطيه هذا الدرس — طرق تمرير المعلمات.


1. التمرير بالقيمة

(1) 1.1 الاستخدام الأساسي

التمرير بالقيمة هو طريقة تمرير المعلمات الافتراضية في C++ — قيمة المعامل الفعلي تُنسخ إلى المعامل الشكلي.

TEXT 📖 للعرض فقط
#include <iostream>

void modifyValue(int x) { // x is a parameter (copy)
 x = 100; // Modifies the copy, doesn't affect the original variable
 std::cout << "x in function = " << x << std::endl;
}

int main() {
 int a = 5;
 modifyValue(a); // Pass by value: a's value is copied to x
 std::cout << "a in main = " << a << std::endl; // a is still 5
 return 0;
}

💡 نقطة مهمة: بالتمرير بالقيمة، تعديل المعامل الشكلي داخل الدالة لا يؤثر على المعامل الفعلي.

(2) 1.2 مزايا وعيوب التمرير بالقيمة

المزايا العيوب
آمن (الدالة لا تستطيع تعديل المعامل الفعلي) الكائنات الكبيرة مكلفة النسخ (مثل المصفوفات الكبيرة، البنى الكبيرة)
منطق واضح لا يمكن للدالة "إرجاع" قيم متعددة


2. التمرير بالمؤشر

إذا كنت تريد أن تُعدِّل الدالة متغيرًا خارجيًا، يمكنك تمرير عنوانه (مؤشر).

(1) 2.1 الاستخدام الأساسي

CPP
#include <iostream>

void modifyValue(int* x) { // x is a pointer
 *x = 100; // Dereference: modify the value the pointer points to
 std::cout << "*x in function = " << *x << std::endl;
}

int main() {
 int a = 5;
 modifyValue(&a); // Pass address: pass a's address to the function
 std::cout << "a in main = " << a << std::endl; // a is now 100
 return 0;
}

💡 نقطة مهمة: بالتمرير بالمؤشر، الدالة يمكنها تعديل قيمة المعامل الفعلي عبر المؤشر.

(2) 2.2 لماذا يمكنها التعديل؟

الخطوة الشرح
1 int a = 5; — تخصيص مساحة في الذاكرة وتخزين 5
2 modifyValue(&a); — تمرير عنوان a إلى الدالة
3 int* x يستقبل العنوان، مشيرًا إلى مساحة ذاكرة a
4 *x = 100; — تعديل قيمة a عبر المؤشر


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

أدخلت C++ المراجع — وهي "أسماء مستعارة" للمتغيرات، أكثر أمانًا وسهولة من المؤشرات.

(1) 3.1 الاستخدام الأساسي

TEXT 📖 للعرض فقط
#include <iostream>

void modifyValue(int& x) { // x is a reference (alias)
 x = 100; // Modifying x modifies the original variable
 std::cout << "x in function = " << x << std::endl;
}

int main() {
 int a = 5;
 modifyValue(a); // Pass by reference: no & needed, looks like pass by value
 std::cout << "a in main = " << a << std::endl; // a is now 100
 return 0;
}

💡 نقطة مهمة: بالتمرير بالمرجع، المعامل الشكلي هو اسم مستعار للمعامل الفعلي — تعديل المعامل الشكلي يُعدِّل المعامل الفعلي.

(2) 3.2 المرجع مقابل المؤشر

المقارنة المرجع المؤشر
الصياغة int& x = a; int* x = &a;
يمكن أن يكون فارغًا ❌ لا (يجب تهيئته) ✅ نعم (يمكن أن يكون nullptr)
يحتاج إلغاء إشارة ❌ لا (استخدم مباشرة) ✅ نعم (استخدم *x)
يمكن إعادة الربط ❌ لا (بمجرد الربط، لا يمكن تغييره) ✅ نعم (يمكن الإشارة إلى متغير آخر)
التوصية ⭐⭐⭐⭐⭐ (فضّل) ⭐⭐⭐ (استخدم عند الضرورة)


4. مقارنة طرق التمرير الثلاث

▶ مثال 1: تبديل متغيرين (صعوبة ⭐⭐)

الطريقة 1: التمرير بالقيمة (يفشل)

CPP
#include <iostream>

void swapFailed(int a, int b) { // ❌ Pass by value: modifies copies
 int temp = a;
 a = b;
 b = temp;
}

int main() {
 int x = 3, y = 5;
 swapFailed(x, y);
 std::cout << "x = " << x << ", y = " << y << std::endl; // x=3, y=5 (not swapped)
 return 0;
}
▶ جرّب الكود

الخرج:

TEXT 📖 للعرض فقط
x = 5, y = 3

الطريقة 2: التمرير بالمؤشر (يعمل)

TEXT 📖 للعرض فقط
#include <iostream>

void swapPointer(int* a, int* b) { // ✅ Pass by pointer
 int temp = *a;
 *a = *b;
 *b = temp;
}

int main() {
 int x = 3, y = 5;
 swapPointer(&x, &y);
 std::cout << "x = " << x << ", y = " << y << std::endl; // x=5, y=3 (swapped)
 return 0;
}

الطريقة 3: التمرير بالمرجع (يعمل، موصى به)

CPP
#include <iostream>

void swapReference(int& a, int& b) { // ✅ Pass by reference (recommended)
 int temp = a;
 a = b;
 b = temp;
}

int main() {
 int x = 3, y = 5;
 swapReference(x, y); // Looks like pass by value, but is pass by reference
 std::cout << "x = " << x << ", y = " << y << std::endl; // x=5, y=3 (swapped)
 return 0;
}


5. متى تستخدم أي طريقة تمرير؟

السيناريو الطريقة الموصى بها السبب
الدالة لا تحتاج لتعديل المعلمة التمرير بالقيمة أو التمرير بمرجع const آمن وواضح
الدالة تحتاج لتعديل المعلمة التمرير بالمرجع صياغة موجزة، آمن
تحتاج "إرجاع" قيم متعددة التمرير بالمرجع الدالة يمكنها تعديل معلمات مرجعية متعددة
المعلمة كائن كبير (مصفوفة، بنية) التمرير بمرجع const تجنب تكلفة النسخ
تحتاج تمثيل "لا شيء" (مثل فشل البحث) التمرير بالمؤشر المؤشر يمكن أن يكون nullptr

💡 القاعدة الذهبية: فضّل التمرير بالمرجع، استخدم التمرير بالمؤشر عند الضرورة، والتمرير بالقيمة للكائنات الصغيرة.



6. المرجع const

إذا كنت لا تريد الدالة أن تُعدِّل المعلمة لكنك تريد تجنب تكلفة النسخ، استخدم مرجع const.

TEXT 📖 للعرض فقط
#include <iostream>
#include <string>

void printName(const std::string& name) { // const reference: can't modify name
 std::cout << "Name: " << name << std::endl;
 // name = "Alice"; // ❌ Error: can't modify a const reference
}

int main() {
 std::string myName = "MOTO";
 printName(myName); // No need to copy the entire string — efficient
 return 0;
}

💡 نقطة مهمة: const reference هي إحدى طرق تمرير المعلمات الأكثر استخدامًا في C++، توازن بين الأمان والكفاءة.



7. المصفوفات كمعلمات

عند تمرير مصفوفة كمعلمة، تتحول إلى مؤشر (ستُغطى بالتفصيل لاحقًا).

CPP
#include <iostream>

// Array as parameter, need to pass the length
void printArray(int arr, int length) {
 for (int i = 0; i < length; i++) {
 std::cout << arr[i] << " ";
 }
 std::cout << std::endl;
}

int main() {
 int myArray[5] = {1, 2, 3, 4, 5};
 printArray(myArray, 5); // Pass array name (which is the address of the first element)
 return 0;
}

💡 نصيحة: عند تمرير مصفوفة كمعلمة، الدالة لا تعرف طول المصفوفة — تحتاج لتمرير معلمة طول إضافية.



8. تطبيق: إرجاع قيم متعددة

▶ مثال 2: حساب القسمة والباقي (صعوبة ⭐⭐)

TEXT 📖 للعرض فقط
#include <iostream>

// Use reference parameters to "return" multiple values
void divide(int a, int b, int& quotient, int& remainder) {
 quotient = a / b;
 remainder = a % b;
}

int main() {
 int a = 17, b = 5;
 int q, r;
 
 divide(a, b, q, r); // q and r will be modified
 
 std::cout << a << " / " << b << " = " << q << " ... " << r << std::endl;
 // Output: 17 / 5 = 3 ... 2
 
 return 0;
}

الخرج:

TEXT 📖 للعرض فقط
17 / 5 = 3 ... 2

▶ مثال 3: المعلمات الافتراضية (صعوبة ⭐)

CPP
#include <iostream>
#include <string>

// Function with default parameters
void greet(const std::string& name, const std::string& greeting = "Hello") {
    std::cout << greeting << "," << name << "!" << std::endl;
}

int main() {
    greet("Zhang San");              // Use default parameter
    greet("Li Si", "Good morning");     // Specify parameter
    greet("Wang Wu", "Good evening");     // Specify parameter
    return 0;
}
▶ جرّب الكود

الخرج:

TEXT 📖 للعرض فقط
Hello,Zhang San!
Good morning,Li Si!
Good evening,Wang Wu!

الخرج المتوقع:

TEXT 📖 للعرض فقط
Hello,Zhang San!
Good morning,Li Si!
Good evening,Wang Wu!

❓ أسئلة شائعة

س: ما الفرق الحقيقي بين المراجع والمؤشرات؟ ج: - المرجع هو اسم مستعار لمتغير؛ المؤشر هو متغير يخزن عنوانًا - المرجع يجب تهيئته؛ المؤشر يمكن أن يكون nullptr - المرجع لا يمكن إعادة ربطه؛ المؤشر يمكن - فضّل المراجع ما لم تكن بحاجة لتمثيل "لا شيء" (استخدم مؤشرًا حينها) س: لماذا لا تحتاج لتحديد الطول عند تمرير مصفوفة كمعلمة؟ ج: لأن المصفوفات تتحول إلى مؤشرات. void foo(int arr) تكافئ void foo(int* arr) — arr هو في الواقع مؤشر، وليس مصفوفة. س: أيهما أكثر كفاءة: التمرير بالقيمة، التمرير بالمرجع، أم التمرير بالمؤشر؟ ج: - الكائنات الصغيرة (مثل int): التمرير بالقيمة أكثر كفاءة (تكلفة نسخ صغيرة) - الكائنات الكبيرة (مثل المصفوفات الكبيرة، البنى الكبيرة): التمرير بمرجع const أكثر كفاءة (يتجنب النسخ) - عندما تحتاج لتعديل معلمة: التمرير بالمرجع أفضل (صياغة موجزة)

س: ما الأهم في معلمات الدوال؟ ج: افهم المفاهيم الأساسية أولًا، ثم عززها عبر أمثلة التطبيق.

س: كيف أمارس معلمات الدوال بكفاءة؟ ج: ابدأ بأمثلة بسيطة، زِد الصعوبة تدريجيًا، واختبر كودك دائمًا.

📖 ملخص


📝 تمارين

  1. أساسي (صعوبة ⭐): اكتب دالة void addTen(int& x) تضيف 10 إلى المتغير الممرر. اختبرها في main.

  2. متوسط (صعوبة ⭐⭐): اكتب دالة void minMax(int arr, int length, int& min, int& max) تجد القيمتين الصغرى والعظمى في مصفوفة (باستخدام معلمات مرجعية لـ "إرجاعهما").

  3. تحدي (صعوبة ⭐⭐⭐): اكتب برنامجًا ينفذ "ترتيب ثلاثة أعداد":

  4. اكتب دالة void sortThree(int& a, int& b, int& c) ترتب ثلاثة أعداد من الأصغر إلى الأكبر

  5. في main، دع المستخدم يدخل ثلاثة أعداد، استدعِ sortThree، ثم أخرج النتيجة المرتبة


9. 🚀 الخطوة التالية

الآن بعد أن تعلمت تمرير المعلمات، سنتعلم بعد ذلك التعاود (الدرس 12) — التقنية المذهلة للدالة التي تستدعي نفسها!

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%