C++: تمرين: المؤشرات والمراجع الشامل
آخر تحديث: 2026-08-26
الدروس السابقة غطت المؤشرات والمراجع بشكل منفصل، لكن في البرامج الحقيقية تُستخدم معًا.
في هذا الدرس، سنتدرب على جعل المؤشرات والمراجع يعملان معًا من خلال عدة أمثلة شاملة.
1. مثال 1: عكس سلسلة باستخدام المؤشرات*
▶ مثال 1: عكس سلسلة (الصعوبة ⭐⭐)
#include <iostream>
#include <cstring>
void reverseString(char* str) {
int len = std::strlen(str);
char* left = str;
char* right = str + len - 1;
while (left < right) {
// تبديل الأحرف التي يشير إليها left وright
char temp = *left;
*left = *right;
*right = temp;
left++;
right--;
}
}
int main() {
char str[] = "Hello, World!";
std::cout << "Before reversal: " << str << std::endl;
reverseString(str);
std::cout << "After reversal: " << str << std::endl;
return 0;
}
الخرج:
Before reversal:
After reversal:
2. مثال 2: مصفوفة بنى ديناميكية*
▶ مثال 2: تخصيص مصفوفة طلاب ديناميكيًا (الصعوبة ⭐⭐⭐)
#include <iostream>
#include <string>
struct Student {
std::string name;
int age;
double score;
};
int main() {
int n;
std::cout << "Enter number of students: ";
std::cin >> n;
// تخصيص مصفوفة طلاب ديناميكيًا
Student* students = new Student[n];
// إدخال معلومات الطلاب
for (int i = 0; i < n; i++) {
std::cout << "Enter name for student " << i + 1 << ": ";
std::cin.ignore();
std::getline(std::cin, students[i].name);
std::cout << "Enter age: ";
std::cin >> students[i].age;
std::cout << "Enter score: ";
std::cin >> students[i].score;
}
// إخراج جميع معلومات الطلاب
std::cout << "\n========== Student List ==========\n";
for (int i = 0; i < n; i++) {
std::cout << i + 1 << ". " << students[i].name
<< ", Age: " << students[i].age
<< ", Score: " << students[i].score << std::endl;
}
// تحرير الذاكرة
delete[] students;
return 0;
}
الخرج:
Enter number of students:
Enter name for student :
Enter age:
Enter score:
========== Student List ==========
.
3. مثال 3: أساسيات القائمة المرتبطة (اختياري)*
(1) 3.1 ما هي القائمة المرتبطة؟
القائمة المرتبطة هي بنية بيانات ديناميكية — كل عنصر (عقدة) يخزن بيانات ومؤشر للعقدة التالية.
| التشبيه من الواقع | المكافئ البرمجي |
|---|---|
| صيد الكنز (كل دليل يشير لموقع الدليل التالي) | قائمة مرتبطة |
▶ مثال 3: قائمة مرتبطة بسيطة (الصعوبة ⭐⭐⭐)
#include <iostream>
struct Node {
int data;
Node* next;
};
int main() {
// إنشاء عقد
Node* head = new Node{1, nullptr};
Node* second = new Node{2, nullptr};
Node* third = new Node{3, nullptr};
// ربط العقد
head->next = second;
second->next = third;
// اجتياز القائمة المرتبطة
Node* current = head;
while (current != nullptr) {
std::cout << current->data << " ";
current = current->next;
}
std::cout << std::endl;
// تحرير الذاكرة
delete head;
delete second;
delete third;
return 0;
}
الخرج:
💡 نصيحة: القوائم المرتبطة موضوع مهم في هياكل البيانات — هذه مجرد مقدمة مختصرة. ستتعلم المزيد من هياكل البيانات لاحقًا.
4. تمرين تطبيقي: دفتر العناوين (نسخة الذاكرة الديناميكية)*
▶ مثال 4: دفتر عناوين ديناميكي (الصعوبة ⭐⭐⭐)
#include <iostream>
#include <string>
#include <cstring>
struct Contact {
char name[50];
char phone[20];
Contact* next; // مؤشر القائمة المرتبطة
};
Contact* head = nullptr; // مؤشر رأس القائمة المرتبطة
void addContact(const std::string& name, const std::string& phone) {
Contact* newContact = new Contact;
std::strcpy(newContact->name, name.c_str());
std::strcpy(newContact->phone, phone.c_str());
newContact->next = head; // العقدة الجديدة تشير للرأس الأصلي
head = newContact; // تحديث الرأس
}
void printContacts() {
if (head == nullptr) {
std::cout << "Address book is empty!" << std::endl;
return;
}
std::cout << "\n========== Address Book ==========\n";
Contact* current = head;
int index = 1;
while (current != nullptr) {
std::cout << index << ". " << current->name
<< " - " << current->phone << std::endl;
current = current->next;
index++;
}
}
int main() {
addContact("MOTO", "13800138000");
addContact("Alice", "13900139000");
addContact("Bob", "13700137000");
printContacts();
// تحرير الذاكرة (اختياري)
// ...
return 0;
}
الخرج:
Address book is empty!
========== Address Book ==========
.
❓ أسئلة شائعة
س: متى أستخدم مؤشرات مقابل مراجع؟ ج:> - إذا كان المعامل قد يُعدّل ويحتاج لتمثيل "لا شيء" (مثلاً: فشل البحث) → مؤشر (
nullptr) > - إذا كان المعامل قد يُعدّل لكنه دائمًا له قيمة → مرجع > - إذا كان المعامل لا يحتاج تعديل → مرجع ثابت (موصى به) > - إذا كان تخصيص الذاكرة الديناميكية مطلوبًا → مؤشر (new) س: لماذا القوائم المرتبطة أفضل من المصفوفات؟ ج:> - المصفوفة: حجم ثابت، الإدراج/الحذف يتطلب إزاحة جميع العناصر اللاحقة (بطيء) > - القائمة المرتبطة: حجم ديناميكي، الإدراج/الحذف يتطلب تغيير المؤشرات فقط (سريع)لكن القوائم المرتبطة لها أيضًا عيوب: لا وصول عشوائي (لإيجاد العنصر i يجب الاجتياز من الرأس).
س: كيف أتجنب تسرب الذاكرة؟ ج:> 1. تأكد من اقتران
newوdelete> 2. عيّن المؤشر إلىnullptrفورًا بعد التحرير > 3. استخدم المؤشرات الذكية (std::unique_ptr،std::shared_ptr، ستُغطى لاحقًا)
س: ما الأهم في التمرين مع المؤشرات؟ ج: افهم المفاهيم الأساسية أولاً، ثم عززها من خلال أمثلة عملية.
📖 ملخص
- المؤشرات والمراجع يمكن خلطهما — المراجع تجعل الكود موجزًا، المؤشرات تتعامل مع الذاكرة الديناميكية
- المؤشرات يمكنها تنفيذ هياكل بيانات ديناميكية (مثلاً: القوائم المرتبطة، الأشجار، الرسوم البيانية)
- بعد تخصيص الذاكرة ديناميكيًا، تذكر دائمًا تحريرها (
delete) - القوائم المرتبطة هي أساس هياكل البيانات الديناميكية
📝 تمارين
-
أساسي (الصعوبة ⭐): اكتب دالة
void swap(int* a, int* b)تبدل قيم متغيرين باستخدام المؤشرات. -
متوسط (الصعوبة ⭐⭐): اكتب برنامجًا يخصص مصفوفة
intديناميكيًا، يتيح للمستخدم إدخال حجم المصفوفة ومحتوياتها، ثم يعكس المصفوفة ويُخرجها. -
متقدم (الصعوبة ⭐⭐⭐): وسّع برنامج "دفتر العناوين الديناميكي":
- أضف ميزة "حذف جهة اتصال"
- أضف ميزة "بحث عن جهة اتصال"
- حرر جميع الذاكرة المخصصة ديناميكيًا عند انتهاء البرنامج
- معاملات الدوال بالمؤشر: تعديل قيم المتغيرات الخارجية
- المصفوفات الديناميكية: تحديد الحجم وقت التشغيل، تحرير عند الانتهاء
- الدوال التي تُرجع مؤشرات يجب أن تضمن عدم تحرير الذاكرة مبكرًا
- صيغة التمرير بالمرجع أوجز من المؤشرات
- فحوصات nullptr تمنع إلغاء الإشارة لمؤشرات فارغة
5. 🚀 الخطوات التالية*
تهانينا! لقد أكملت جميع دروس المرحلة 4 (المؤشرات والمراجع). بعد ذلك، ندخل المرحلة 5 (البرمجة كائنية التوجه) — تعلم أقوى ميزة في C++، فهم الأصناف، الكائنات، الوراثة، وتعدد الأشكال!