C++: تمرير معلمات الدوال
آخر تحديث: 2026-08-26
في الدرس 10 تعلمنا أساسيات الدوال وكيف يمكن للدوال استقبال معلمات.
لكن كيف تُمرَّر المعلمات إلى الدوال بالضبط؟ إذا تم تعديل معلمة داخل الدالة، هل يتغير المتغير الخارجي؟
هذا ما يغطيه هذا الدرس — طرق تمرير المعلمات.
1. التمرير بالقيمة
(1) 1.1 الاستخدام الأساسي
التمرير بالقيمة هو طريقة تمرير المعلمات الافتراضية في C++ — قيمة المعامل الفعلي تُنسخ إلى المعامل الشكلي.
#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 الاستخدام الأساسي
#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 الاستخدام الأساسي
#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: التمرير بالقيمة (يفشل)
#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;
}
الخرج:
x = 5, y = 3
الطريقة 2: التمرير بالمؤشر (يعمل)
#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: التمرير بالمرجع (يعمل، موصى به)
#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.
#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. المصفوفات كمعلمات
عند تمرير مصفوفة كمعلمة، تتحول إلى مؤشر (ستُغطى بالتفصيل لاحقًا).
#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: حساب القسمة والباقي (صعوبة ⭐⭐)
#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;
}
الخرج:
17 / 5 = 3 ... 2
▶ مثال 3: المعلمات الافتراضية (صعوبة ⭐)
#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;
}
الخرج:
Hello,Zhang San!
Good morning,Li Si!
Good evening,Wang Wu!
الخرج المتوقع:
Hello,Zhang San!
Good morning,Li Si!
Good evening,Wang Wu!
❓ أسئلة شائعة
س: ما الفرق الحقيقي بين المراجع والمؤشرات؟ ج: - المرجع هو اسم مستعار لمتغير؛ المؤشر هو متغير يخزن عنوانًا - المرجع يجب تهيئته؛ المؤشر يمكن أن يكون
nullptr- المرجع لا يمكن إعادة ربطه؛ المؤشر يمكن - فضّل المراجع ما لم تكن بحاجة لتمثيل "لا شيء" (استخدم مؤشرًا حينها) س: لماذا لا تحتاج لتحديد الطول عند تمرير مصفوفة كمعلمة؟ ج: لأن المصفوفات تتحول إلى مؤشرات.void foo(int arr)تكافئvoid foo(int* arr)— arr هو في الواقع مؤشر، وليس مصفوفة. س: أيهما أكثر كفاءة: التمرير بالقيمة، التمرير بالمرجع، أم التمرير بالمؤشر؟ ج: - الكائنات الصغيرة (مثلint): التمرير بالقيمة أكثر كفاءة (تكلفة نسخ صغيرة) - الكائنات الكبيرة (مثل المصفوفات الكبيرة، البنى الكبيرة): التمرير بمرجع const أكثر كفاءة (يتجنب النسخ) - عندما تحتاج لتعديل معلمة: التمرير بالمرجع أفضل (صياغة موجزة)
س: ما الأهم في معلمات الدوال؟ ج: افهم المفاهيم الأساسية أولًا، ثم عززها عبر أمثلة التطبيق.
س: كيف أمارس معلمات الدوال بكفاءة؟ ج: ابدأ بأمثلة بسيطة، زِد الصعوبة تدريجيًا، واختبر كودك دائمًا.
📖 ملخص
- التمرير بالقيمة: يصنع نسخة؛ التعديلات داخل الدالة لا تؤثر على الخارج
- التمرير بالمؤشر: يمرر عنوانًا؛ الدالة يمكنها التعديل على الخارج عبر المؤشر
- التمرير بالمرجع: يمرر اسمًا مستعارًا؛ التعديلات داخل الدالة تؤثر على الخارج (موصى به)
- مرجع const: لا يمكن تعديله، لكن يتجنب تكلفة النسخ (الأكثر استخدامًا)
- المصفوفات تتحول إلى مؤشرات عند تمريرها كمعلمات؛ معلمة طول إضافية مطلوبة
📝 تمارين
-
أساسي (صعوبة ⭐): اكتب دالة
void addTen(int& x)تضيف 10 إلى المتغير الممرر. اختبرها فيmain. -
متوسط (صعوبة ⭐⭐): اكتب دالة
void minMax(int arr, int length, int& min, int& max)تجد القيمتين الصغرى والعظمى في مصفوفة (باستخدام معلمات مرجعية لـ "إرجاعهما"). -
تحدي (صعوبة ⭐⭐⭐): اكتب برنامجًا ينفذ "ترتيب ثلاثة أعداد":
-
اكتب دالة
void sortThree(int& a, int& b, int& c)ترتب ثلاثة أعداد من الأصغر إلى الأكبر -
في
main، دع المستخدم يدخل ثلاثة أعداد، استدعِsortThree، ثم أخرج النتيجة المرتبة
- التمرير بالقيمة: ينسخ المعلمة؛ التعديلات داخل الدالة لا تؤثر على الخارج
- التمرير بالمرجع: يشترك في الذاكرة؛ التعديلات داخل الدالة تؤثر على الخارج
- المعلمات الافتراضية تذهب من اليمين إلى اليسار؛ يمكن حذفها عند الاستدعاء
- تحميل الدوال: نفس الاسم بمعلمات مختلفة؛ يُحدد وقت الترجمة
- مرجع const يحمي المعلمات من التعديل مع تجنب النسخ
9. 🚀 الخطوة التالية
الآن بعد أن تعلمت تمرير المعلمات، سنتعلم بعد ذلك التعاود (الدرس 12) — التقنية المذهلة للدالة التي تستدعي نفسها!