C: المؤشرات المتقدمة

المؤشرات كأوراق العناوين — مؤشر إلى مؤشر هو ورقة عنوان كُتبت على ورقة عنوان أخرى. يبدو الأمر معقدًا، لكن في الواقع "رقم الخزانة حيث تخزن الطرد" يعمل بنفس الطريقة، طبقة فوق طبقة.

1. مؤشر إلى مؤشر

متغير المؤشر هو نفسه متغير — يعيش في الذاكرة وله عنوانه الخاص. عندما تستخدم مؤشرًا للإشارة إلى مؤشر آخر، تحصل على "مؤشر إلى مؤشر".

C
int x = 42;
int *p = &x;
int **pp = &p;

من خلال **pp يمكنك تعديل قيمة x بشكل غير مباشر:

C
**pp = 100;
printf("%d\n", x);
TEXT 📖 للعرض فقط
100

أشهر استخدام للمؤشر المزدوج هو "تعديل المؤشر نفسه داخل دالة"، مثل جعل دالة تُخصّص ذاكرة لمؤشر:

TEXT 📖 للعرض فقط
void alloc_buf(char **ptr, int size) {
    *ptr = (char *)malloc(size);
}

int main(void) {
    char *buf = NULL;
    alloc_buf(&buf, 128);
    if (buf) {
        strcpy(buf, "hello");
        printf("%s\n", buf);
        free(buf);
    }
    return 0;
}
TEXT 📖 للعرض فقط
hello
💡 نصيحة: إذا مررت مؤشرًا واحدًا فقط char *ptr، فإن الدالة تعدّل نسخة — المؤشر الأصلي في الخارج لن يتغير. لتعديل المؤشر نفسه، يجب تمرير عنوانه.


2. مصفوفة المؤشرات مقابل مؤشر المصفوفة

هذان المفهومان يُخلط بينهما بسهولة. المفتاح هو أسبقية * و [].

(1) مصفوفة المؤشرات

int *arr[4] — يرتبط بـ [] أولاً، لذا فهو مصفوفة عناصرها int *.

C
int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c};
for (int i = 0; i < 3; i++) {
    printf("%d ", *arr[i]);
}
TEXT 📖 للعرض فقط
10 20 30

تطبيق نموذجي: مصفوفة سلاسل نصية.

C
const char *names[3] = {"Alice", "Bob", "Carol"};
for (int i = 0; i < 3; i++) {
    printf("%s\n", names[i]);
}
TEXT 📖 للعرض فقط
Alice
Bob
Carol

(2) مؤشر المصفوفة

int (*p)[4] — يرتبط بـ * أولاً، لذا فهو مؤشر يشير إلى مصفوفة من 4 أعداد صحيحة. يُستخدم عادةً لتمرير المصفوفات ثنائية البُعد إلى الدوال.

C
int matrix[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};
int (*p)[4] = matrix;
printf("%d\n", p[1][2]);
TEXT 📖 للعرض فقط
7

▶ مثال

C
#include <stdio.h>

void print_matrix(int (*m)[4], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%3d", m[i][j]);
        }
        printf("\n");
    }
}

int main(void) {
    int matrix[2][4] = {
        {10, 20, 30, 40},
        {50, 60, 70, 80}
    };
    print_matrix(matrix, 2);

    const char *fruits[3] = {"apple", "banana", "cherry"};
    for (int i = 0; i < 3; i++) {
        printf("%s ", fruits[i]);
    }
    printf("\n");
    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
 10 20 30 40
 50 60 70 80
apple banana cherry
⚠️ ملاحظة: طريقة سهلة للتذكر: int *a[4] هي مصفوفة مؤشرات (مصفوفة من المؤشرات)، int (*a)[4] هو مؤشر مصفوفة (مؤشر إلى مصفوفة).


3. مؤشر الدالة والاستدعاء العكسي

اسم الدالة هو عنوان دخولها. يمكن لمؤشر الدالة تخزين هذا العنوان، مما يُمكّن "الاستدعاء المؤجل" أو "الاستدعاء العكسي".

(1) تعريف مؤشر الدالة

C
int (*pf)(int, int);

هذا يُعرّف pf كمؤشر إلى دالة تأخذ عددين صحيحين وتُرجع عددًا صحيحًا.

C
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

int main(void) {
    int (*pf)(int, int) = add;
    printf("%d\n", pf(3, 5));
    pf = sub;
    printf("%d\n", pf(10, 4));
    return 0;
}
TEXT 📖 للعرض فقط
8
6

(2) آلية الاستدعاء العكسي

تمرير مؤشر دالة كوسيط إلى دالة أخرى، بحيث يمكن للأخيرة "استدعائه عكسيًا" في الوقت المناسب.

C
void process(int *arr, int len, int (*transform)(int)) {
    for (int i = 0; i < len; i++) {
        arr[i] = transform(arr[i]);
    }
}

int double_it(int n) { return n * 2; }
int negate(int n) { return -n; }

int main(void) {
    int data[4] = {1, 2, 3, 4};
    process(data, 4, double_it);
    for (int i = 0; i < 4; i++) printf("%d ", data[i]);

    printf("\n");
    process(data, 4, negate);
    for (int i = 0; i < 4; i++) printf("%d ", data[i]);
    return 0;
}
TEXT 📖 للعرض فقط
2 4 6 8
-2 -4 -6 -8

▶ مثال

C
#include <stdio.h>
#include <stdlib.h>

int cmp_asc(const void *a, const void *b) {
    return *(int *)a - *(int *)b;
}

int cmp_desc(const void *a, const void *b) {
    return *(int *)b - *(int *)a;
}

void sort_and_print(int *arr, int n, int (*cmp)(const void *, const void *)) {
    qsort(arr, n, sizeof(int), cmp);
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(void) {
    int nums[5] = {42, 7, 19, 3, 55};
    sort_and_print(nums, 5, cmp_asc);

    int nums2[5] = {42, 7, 19, 3, 55};
    sort_and_print(nums2, 5, cmp_desc);
    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
3 7 19 42 55
55 42 19 7 3
🔥 خطأ شائع: دالة qsort في المكتبة القياسية هي تطبيق كلاسيكي للاستدعاءات العكسية بمؤشرات الدوال — تحتاج فقط إلى تقديم قاعدة المقارنة، وخوارزمية الترتيب تتولى الباقي.


4. التوليفات الثلاث لـ const مع المؤشرات

لـ const مع المؤشرات ثلاثة مواضع، لكل منها دلالات مختلفة.

(1) مؤشر إلى const

C
const int *p;
int const *p;

لا يمكنك تعديل القيمة المُشار إليها عبر p، لكن p نفسه يمكنه الإشارة إلى مكان آخر.

C
int a = 10, b = 20;
const int *p = &a;
printf("%d\n", *p);
p = &b;
printf("%d\n", *p);
TEXT 📖 للعرض فقط
10
20

(2) مؤشر const

C
int * const p = &a;

لا يمكن لـ p تغيير ما يشير إليه، لكن يمكنك تعديل القيمة عبر p.

C
int a = 10;
int * const p = &a;
*p = 99;
printf("%d\n", a);
TEXT 📖 للعرض فقط
99

(3) مؤشر const إلى const

C
const int * const p = &a;

لا يمكن تغيير هدف المؤشر ولا القيمة المُشار إليها — القراءة فقط بأقصى درجات الصرامة.

💡 نصيحة: خدعة سريعة: انظر هل const على يسار * أم يمينه. على اليسار يُعدّل البيانات؛ على اليمين يُعدّل المؤشر نفسه.


5. مؤشر void

void * هو "مؤشر عام" يمكنه الإشارة إلى أي نوع، لكن يجب تحويله قبل الاستخدام.

C
int a = 42;
double b = 3.14;
void *p;

p = &a;
printf("%d\n", *(int *)p);

p = &b;
printf("%.2f\n", *(double *)p);
TEXT 📖 للعرض فقط
42
3.14

استخدامات نموذجية لـ void *:

⚠️ ملاحظة: لا يمكنك إلغاء الإشارة لمؤشر void * — المترجم لا يعرف حجم البيانات التي يشير إليها. يجب تحويله أولاً إلى نوع مؤشر محدد.


❓ أسئلة شائعة

س كيف أميّز بسرعة بين مصفوفة المؤشرات ومؤشر المصفوفة؟
ج انظر ما الذي يرتبط به اسم المتغير أولاً — إذا ارتبط بـ [] أولاً فهو مصفوفة (مصفوفة مؤشرات)؛ إذا ارتبط بـ * أولاً فهو مؤشر (مؤشر مصفوفة). استخدم الأقواس لتغيير الأسبقية.
س ما الفرق بين مؤشر الدالة واسم الدالة؟
ج اسم الدالة هو عنوانها — وهو ثابت. مؤشر الدالة متغير يمكنه تخزين عناوين دوال مختلفة، مما يُمكّن تبديل هدف الاستدعاء وقت التشغيل.
س هل const int *p و int const *p متطابقان؟
ج نعم، متطابقان تمامًا. كلاهما يعني مؤشرًا إلى const int — لا يمكنك تعديل البيانات عبر p، لكن p نفسه يمكنه الإشارة إلى مكان آخر.
س هل يمكن إجراء حسابات المؤشرات على مؤشر void؟
ج لا. المترجم لا يعرف حجم البيانات التي يشير إليها مؤشر void، لذا خطوة p+1 غير مُعرّفة. يجب تحويله أولاً إلى نوع محدد قبل إجراء الحسابات.

📖 ملخص

📝 تمارين

  1. اكتب دالة void swap_ptr(int **a, int **b) تبدّل ما يشير إليه مؤشران. تحقق في main أن كل مؤشر يشير إلى القيمة الأصلية للآخر بعد التبديل
  2. أعلن عن مصفوفة مؤشرات دوال int (*ops[4])(int,int) تحتوي على دوال الجمع والطرح والضرب والقسمة. استدعِ العملية المناسبة بناءً على فهرس يُدخله المستخدم
  3. اكتب دالة طباعة عامة void print_any(void *data, char type) حيث type يكون 'i' لـ int، و 'd' لـ double، و 's' لسلسلة نصية
Web-Tutorial.com

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

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

100%