C: المؤشرات المتقدمة
المؤشرات كأوراق العناوين — مؤشر إلى مؤشر هو ورقة عنوان كُتبت على ورقة عنوان أخرى. يبدو الأمر معقدًا، لكن في الواقع "رقم الخزانة حيث تخزن الطرد" يعمل بنفس الطريقة، طبقة فوق طبقة.
1. مؤشر إلى مؤشر
متغير المؤشر هو نفسه متغير — يعيش في الذاكرة وله عنوانه الخاص. عندما تستخدم مؤشرًا للإشارة إلى مؤشر آخر، تحصل على "مؤشر إلى مؤشر".
int x = 42;
int *p = &x;
int **pp = &p;
من خلال **pp يمكنك تعديل قيمة x بشكل غير مباشر:
**pp = 100;
printf("%d\n", x);
100
أشهر استخدام للمؤشر المزدوج هو "تعديل المؤشر نفسه داخل دالة"، مثل جعل دالة تُخصّص ذاكرة لمؤشر:
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;
}
hello
char *ptr، فإن الدالة تعدّل نسخة — المؤشر الأصلي في الخارج لن يتغير. لتعديل المؤشر نفسه، يجب تمرير عنوانه.
2. مصفوفة المؤشرات مقابل مؤشر المصفوفة
هذان المفهومان يُخلط بينهما بسهولة. المفتاح هو أسبقية * و [].
(1) مصفوفة المؤشرات
int *arr[4] — يرتبط بـ [] أولاً، لذا فهو مصفوفة عناصرها int *.
int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c};
for (int i = 0; i < 3; i++) {
printf("%d ", *arr[i]);
}
10 20 30
تطبيق نموذجي: مصفوفة سلاسل نصية.
const char *names[3] = {"Alice", "Bob", "Carol"};
for (int i = 0; i < 3; i++) {
printf("%s\n", names[i]);
}
Alice
Bob
Carol
(2) مؤشر المصفوفة
int (*p)[4] — يرتبط بـ * أولاً، لذا فهو مؤشر يشير إلى مصفوفة من 4 أعداد صحيحة. يُستخدم عادةً لتمرير المصفوفات ثنائية البُعد إلى الدوال.
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]);
7
▶ مثال
#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;
}
10 20 30 40
50 60 70 80
apple banana cherry
int *a[4] هي مصفوفة مؤشرات (مصفوفة من المؤشرات)، int (*a)[4] هو مؤشر مصفوفة (مؤشر إلى مصفوفة).
3. مؤشر الدالة والاستدعاء العكسي
اسم الدالة هو عنوان دخولها. يمكن لمؤشر الدالة تخزين هذا العنوان، مما يُمكّن "الاستدعاء المؤجل" أو "الاستدعاء العكسي".
(1) تعريف مؤشر الدالة
int (*pf)(int, int);
هذا يُعرّف pf كمؤشر إلى دالة تأخذ عددين صحيحين وتُرجع عددًا صحيحًا.
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;
}
8
6
(2) آلية الاستدعاء العكسي
تمرير مؤشر دالة كوسيط إلى دالة أخرى، بحيث يمكن للأخيرة "استدعائه عكسيًا" في الوقت المناسب.
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;
}
2 4 6 8
-2 -4 -6 -8
▶ مثال
#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;
}
3 7 19 42 55
55 42 19 7 3
qsort في المكتبة القياسية هي تطبيق كلاسيكي للاستدعاءات العكسية بمؤشرات الدوال — تحتاج فقط إلى تقديم قاعدة المقارنة، وخوارزمية الترتيب تتولى الباقي.
4. التوليفات الثلاث لـ const مع المؤشرات
لـ const مع المؤشرات ثلاثة مواضع، لكل منها دلالات مختلفة.
(1) مؤشر إلى const
const int *p;
int const *p;
لا يمكنك تعديل القيمة المُشار إليها عبر p، لكن p نفسه يمكنه الإشارة إلى مكان آخر.
int a = 10, b = 20;
const int *p = &a;
printf("%d\n", *p);
p = &b;
printf("%d\n", *p);
10
20
(2) مؤشر const
int * const p = &a;
لا يمكن لـ p تغيير ما يشير إليه، لكن يمكنك تعديل القيمة عبر p.
int a = 10;
int * const p = &a;
*p = 99;
printf("%d\n", a);
99
(3) مؤشر const إلى const
const int * const p = &a;
لا يمكن تغيير هدف المؤشر ولا القيمة المُشار إليها — القراءة فقط بأقصى درجات الصرامة.
const على يسار * أم يمينه. على اليسار يُعدّل البيانات؛ على اليمين يُعدّل المؤشر نفسه.
5. مؤشر void
void * هو "مؤشر عام" يمكنه الإشارة إلى أي نوع، لكن يجب تحويله قبل الاستخدام.
int a = 42;
double b = 3.14;
void *p;
p = &a;
printf("%d\n", *(int *)p);
p = &b;
printf("%.2f\n", *(double *)p);
42
3.14
استخدامات نموذجية لـ void *:
mallocتُرجعvoid *، والذي يمكن إسناده إلى أي نوع مؤشر- دالة المقارنة في
qsortتستخدم وسائطconst void * - تنفيذ هياكل بيانات عامة (مثل قائمة مرتبطة عامة)
void * — المترجم لا يعرف حجم البيانات التي يشير إليها. يجب تحويله أولاً إلى نوع مؤشر محدد.
❓ أسئلة شائعة
[] أولاً فهو مصفوفة (مصفوفة مؤشرات)؛ إذا ارتبط بـ * أولاً فهو مؤشر (مؤشر مصفوفة). استخدم الأقواس لتغيير الأسبقية.const int *p و int const *p متطابقان؟p+1 غير مُعرّفة. يجب تحويله أولاً إلى نوع محدد قبل إجراء الحسابات.📖 ملخص
- المؤشر المزدوج
int **ppيُستخدم لتعديل المؤشر نفسه داخل دالة - مصفوفة المؤشرات
int *a[N]هي مصفوفة من المؤشرات؛ مؤشر المصفوفةint (*p)[N]هو مؤشر إلى مصفوفة - مؤشر الدالة
int (*pf)(int,int)يخزن عنوان دالة، ومع الاستدعاءات العكسية يُتيح تصميمًا مرنًا constعلى يسار*يُعدّل البيانات؛ على يمين*يُعدّل المؤشر نفسهvoid *مؤشر عام — يجب تحويله قبل الاستخدام
📝 تمارين
- اكتب دالة
void swap_ptr(int **a, int **b)تبدّل ما يشير إليه مؤشران. تحقق في main أن كل مؤشر يشير إلى القيمة الأصلية للآخر بعد التبديل - أعلن عن مصفوفة مؤشرات دوال
int (*ops[4])(int,int)تحتوي على دوال الجمع والطرح والضرب والقسمة. استدعِ العملية المناسبة بناءً على فهرس يُدخله المستخدم - اكتب دالة طباعة عامة
void print_any(void *data, char type)حيث type يكون 'i' لـ int، و 'd' لـ double، و 's' لسلسلة نصية