C++: حساب المؤشرات المتقدم
آخر تحديث: 2026-08-26
في الدرس 23، تعلمنا أساسيات حساب المؤشرات (
p + n،p - q).لكن هناك تفاصيل كثيرة أخرى في حساب المؤشرات — مثلًا، هل يمكن مقارنة المؤشرات؟ ما هو مؤشر
void*؟ ما هي المؤشرات إلى مؤشرات؟
1. مقارنة المؤشرات
(1) 1.1 هل يمكن مقارنة المؤشرات؟
نعم! لكن فقط المقارنات بين مؤشرات تشير إلى نفس المصفوفة لها معنى.
▶ مثال 2: تطبيق برمجي أساسي (صعوبة ⭐)
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p1 = &arr[0];
int* p2 = &arr[3];
if (p1 < p2) { // ✅ p1 pointed element is before p2
std::cout << "p1 is before p2" << std::endl;
}
if (p2 - p1 == 3) { // ✅ two pointers are 3 elements apart
std::cout << "p1 and p2 are 3 elements apart" << std::endl;
}
return 0;
}
الخرج:
p1 is before p2
p1 and p2 are 3 elements apart
💡 نقطة مهمة: مقارنة المؤشرات تُقارن قيم العناوين (أي مواقع العناصر في المصفوفة).
(2) 1.2 مقارنة تساوي المؤشرات
#include <iostream>
int main() {
int x = 5;
int* p1 = &x;
int* p2 = &x;
if (p1 == p2) { // ✅ two pointers point to the same variable
std::cout << "p1 and p2 point to the same variable" << std::endl;
}
int y = 10;
int* p3 = &x;
int* p4 = &y;
if (p3 != p4) { // ✅ two pointers point to different variables
std::cout << "p3 and p4 point to different variables" << std::endl;
}
return 0;
}
2. مؤشرات void*
(1) 2.1 ما هو مؤشر void*؟
void* هو مؤشر عام — يمكنه الإشارة إلى أي نوع من البيانات.
#include <iostream>
int main() {
int x = 5;
double y = 3.14;
char c = 'A';
void* p1 = &x; // ✅ void* can point to int
void* p2 = &y; // ✅ void* can point to double
void* p3 = &c; // ✅ void* can point to char
return 0;
}
💡 الخصائص:
void*لا يمكن تحويله (المترجم لا يعرف كم بايت يقرأ)void*لا يمكنه إجراء حساب المؤشرات (حجم الخطوة غير معروف)
(2) 2.2 استخدامات void*
الاستخدام 1: كتابة دوال عامة (ستتعلم لاحقًا أن std::memcpy تستخدم void*)
#include <iostream>
#include <cstring>
int main() {
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
// std::memcpy Use void* to accept any type's pointer
std::memcpy(arr2, arr1, 5 * sizeof(int));
for (int i = 0; i < 5; i++) {
std::cout << arr2[i] << " ";
}
std::cout << std::endl;
return 0;
}
الاستخدام 2: تخصيص الذاكرة الديناميكي (ستتعلم لاحقًا أن malloc تُرجع void*)
(3) 2.3 تحويل void*
لاستخدام القيمة التي يشير إليها void*، يجب تحويله إلى مؤشر من نوع محدد.
#include <iostream>
int main() {
int x = 5;
void* p = &x; // void* pointing to int
// ❌ Error: Cannot directly dereference void*
// std::cout << *p << std::endl;
// ✅ Correct: First convert to int*
int* intPtr = static_cast<int*>(p);
std::cout << *intPtr << std::endl; // Output 5
return 0;
}
💡 نصيحة: static_cast هو عامل تحويل النوع في C++ (ستُغطى لاحقًا)، وهو أكثر أمانًا من تحويل C القديم (int*)p.
3. المؤشرات متعددة المستويات (مؤشرات إلى مؤشرات)
(1) 3.1 ما هو المؤشر إلى مؤشر؟
المؤشر إلى مؤشر هو مؤشر يخزن عنوان مؤشر آخر (مثل الدمى المتداخلة).
#include <iostream>
int main() {
int x = 5;
int* p = &x; // p points to x
int** pp = &p; // pp points to p (pointer to pointer)
std::cout << "x = " << x << std::endl;
std::cout << "*p = " << *p << std::endl; // Dereference once: get x value
std::cout << "**pp = " << **pp << std::endl; // Dereference twice: get x value
return 0;
}
(2) 3.2 تعريف المؤشرات متعددة المستويات
| التعريف | المعنى |
|---|---|
int x; |
x هو int |
int* p; |
p هو int* (مؤشر إلى int) |
int** pp; |
pp هو int** (مؤشر إلى int*) |
int*** ppp; |
ppp هو int*** (مؤشر إلى int**) |
💡 كيفية القراءة: اقرأ من اليمين إلى اليسار.
int* p;→pهو مؤشر إلىintint** pp;→ppهو مؤشر إلى مؤشر إلىint
(3) 3.3 استخدامات المؤشرات متعددة المستويات
الاستخدام 1: مصفوفات ثنائية الأبعاد الديناميكية (ستُغطى لاحقًا)
// Dynamically allocate a 3x4 2D array
int** matrix = new int*[3]; // matrix is a pointer to pointer
for (int i = 0; i < 3; i++) {
matrix[i] = new int[4];
}
الاستخدام 2: دوال تحتاج لتعديل مؤشر (نادر، فقط كن على علم)
#include <iostream>
void allocateInt(int** ptr) {
*ptr = new int(5); // Modify *ptr (i.e., modify the original pointer)
}
int main() {
int* p = nullptr;
allocateInt(&p); // Pass the pointer address
std::cout << *p << std::endl; // Output 5
delete p; // Free memory
return 0;
}
4. المؤشرات مع const
(1) 4.1 أربع تركيبات
| التعريف | المعنى | يمكن تغيير المؤشر؟ | يمكن تغيير القيمة المُشار إليها؟ |
|---|---|---|---|
int* p; |
مؤشر عادي | ✅ نعم | ✅ نعم |
const int* p; |
مؤشر إلى int ثابت | ✅ نعم | ❌ لا |
int* const p = &x; |
مؤشر ثابت (يجب التهيئة) | ❌ لا | ✅ نعم |
const int* const p = &x; |
مؤشر ثابت إلى ثابت | ❌ لا | ❌ لا |
💡 طريقة تذكر: اقرأ من اليمين إلى اليسار.
const int* p;→pهو مؤشر إلىconst intint* const p = &x;→pهو مؤشرconstإلىint
(2) 4.2 مثال: const int* (الأكثر استخدامًا)
#include <iostream>
void printArray(const int* arr, int size) {
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// ❌ Cannot modify:arr[i] = 0; // Compile error
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
printArray(arr, 5);
return 0;
}
الخرج:
1 2 3 4 5
💡 نقطة مهمة: const int* arr هو نوع معلمة الدالة الأكثر استخدامًا — يعني "أنا أقرأ فقط، لا أعدّل."
5. تطبيق: تنفيذ نسخ سلسلة بالمؤشرات
▶ مثال 1: strcpy مكتوبة يدويًا (صعوبة ⭐⭐)
#include <iostream>
void myStrcpy(char* dest, const char* src) {
while (*src != '\0') {
*dest = *src; // Copy character
dest++; // Move pointer
src++;
}
*dest = '\0'; // Add null terminator
}
int main() {
char src[] = "Hello";
char dest[20];
myStrcpy(dest, src);
std::cout << "src = " << src << std::endl;
std::cout << "dest = " << dest << std::endl;
return 0;
}
الخرج:
src = Hello
dest = Hello
💡 نصيحة: هذا البرنامج يستخدم حساب المؤشرات والتحويل — إنه تطبيق ممتاز لفهم المؤشرات.
▶ مثال 3: اجتياز مصفوفة بالمؤشرات (صعوبة ⭐)
#include <iostream>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int* p = arr; // p points to first array element
std::cout << "Traversing array with pointers: " << std::endl;
for (int i = 0; i < 5; i++) {
std::cout << "*p = " << *p << std::endl;
p++; // Move pointer to next element
}
// pointer subtraction calculates number of elements
int* end = arr + 5;
std::cout << "Number of elements:" << end - arr << std::endl;
return 0;
}
الخرج:
Traversing array with pointers:
*p = 10
*p = 20
*p = 30
*p = 40
*p = 50
Number of elements:5
❓ أسئلة شائعة
س: لماذا لا يمكن تحويل void؟* ج: لأن
voidهو نوع بلا حجم — المترجم لا يعرف ما إذا كان سيقرأ 1 بايت،4بايت، أم8بايت.يجب تحويله إلى مؤشر من نوع محدد (مثل
int*،double*) حتى يعرف المترجم حجم الخطوة.
س: ما استخدامات المؤشرات متعددة المستويات؟ ج: في المشاريع الفعلية،
int**تُرى غالبًا في: > 1. مصفوفات ثنائية الأبعاد الديناميكية (مثلnew int*[3]) > 2. دوال تحتاج لتعديل المؤشر نفسه (مثلvoid foo(int** p)يمكنها تعديل ما يشير إليهp)المؤشرات فوق مستويين (
int***إلخ) غير موصى بها — صعبة الفهم جدًا.
س: هل
const int*وint const*متطابقان؟ ج: نعم! الشكلان التاليان متكافئان: > > const int p; // موصى به > int const p; // متكافئ، لكن أقل شيوعًا > > لكنint* const pمختلف — هذا مؤشرconst(لا يمكن تغيير ما يشير إليه).**
📖 ملخص
- المؤشرات يمكن مقارنتها (لها معنى فقط عند الإشارة لنفس المصفوفة)
void*هو مؤشر عام لا يمكن تحويله أو استخدامه في حساب المؤشرات- المؤشرات متعددة المستويات هي مؤشرات إلى مؤشرات (
int**) - المؤشرات + const لها أربع تركيبات؛ الأكثر استخدامًا هو
const int*(للقراءة فقط) - تنفيذ نسخ سلسلة بالمؤشرات تطبيق ممتاز لفهم المؤشرات
📝 تمارين
-
أساسي (صعوبة ⭐): عرّف متغير
intبقيمةx = 5، ثم عرّف مؤشرint*باسمpيشير إلىx، ثم عرّف مؤشرint**باسمppيشير إلىp. أخرجx،*p،**ppوتحقق من تساويها. -
متوسط (صعوبة ⭐⭐): اكتب دالة
void swap(int** a, int** b)تبدّل ما يشير إليه مؤشران.
Input: p1 points to x, p2 points to y
Call swap(&p1, &p2)
Output: p1 points to y, p2 points to x
- تحدي (صعوبة ⭐⭐⭐):
استخدم
void*وحساب المؤشرات لكتابة دالةmyMemcpyيدويًا: - نموذج الدالة:
void myMemcpy(void* dest, const void* src, size_t n) - الوظيفة: نسخ
nبايت منsrcإلىdest - تلميح: انسخ بايت ببايت (استخدم
char*لحساب المؤشرات)
- جمع/طرح الأعداد الصحيحة مع المؤشرات: إزاحة بحجم النوع المُشار إليه
- طرح المؤشرات: يعطي الفرق بعدد العناصر
- مقارنة المؤشرات: يمكن استخدامها لتحديد الترتيب
- مؤشر void*: بلا نوع، لا يمكن تحويله أو استخدامه في الحساب
- مؤشرات الدوال: تخزن عنوان دخول دالة
6. 🚀 الخطوة التالية
الآن بعد أن تعلمت مفاهيم المؤشرات المتقدمة، لنتابع إلى المراجع (الدرس 25) — "النسخة الأكثر أمانًا" من المؤشرات، ميزة مهمة في C++!