أنت هنا:برمجها»اللغة س»الدرس 9: المؤشرات
الدرس 9: المؤشرات الدرس 9: المؤشرات
قيم الموضوع
(1 تصويت)

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


سنتطرق في هذه الفقرة إلى :

  • عنونة المتغيرات
  • المؤشرات
  • المؤشرات والجداول
  • جدول المؤشرات
  • الحجز الحركي للذاكرة

1. عنونة المتغيرات

توجد عدة أنواع من أنظمة العنونة، وأهمها العنونة المباشرة والعنونة غير المباشرة.

1.1 العنونة المباشرة

عندما نُعَرف متغيرا فإن المترجم يقوم بحجز مكان له في الذاكرة، وللتعامل مع هذا المتغير الموجود حقيقة في الذاكرة نستعمل بطبيعة اسمه الذي عرفناه في البداية، وهذا ما يسمى بالعنونة المباشرة، وبالتالي نكتب :

العنونة المباشرة : الدخول إلى محتوى المتغير انطلاقا من اسمه.

مثال في الحياة اليومية : "دخول البيوت من أبوابها" ، ولا يقوم بها إلا  ذَوُو المروءة والأخلاق.

مثال :

 

1.2 العنونة غير المباشرة

في هذه الحالة يتم الدخول إلى محتوى متغير ما عن طريق متغير آخر نسميه مؤشرا يشير أو يحمل عنوان هذا المتغير.

مثال في الحياة اليومية : "دخول البيوت من نوافذها" ، ولا يقوم بها إلا  اللصوص.

مثال :  ليكن:

  1. A متغيرا يحتوي على القيمة 10

  2. P مؤشرا يحتوي على عنوان المتغير A .

يمكن تمثيل A و P  في الذاكرة على الشكل التالي :


2. المؤشرات

المؤشر: بكل بساطة هو متغير يحتوي على عنوان لمتغير آخر.

ليكن P يحمل عنوان المتغير A.

ونقول إذن :  P يشير إلى المتغير A.

 

2.1 العوامل المتحكمة بالمؤشرات

عند التعامل مع المؤشرات نحتاج إلى العوامل التالية :

  • & : عامل العنوان ===> ويستعمل للحصول على عنوان متغير.

  • *  : عامل المحتوى ===> ويستعمل للدخول إلى محتوى متغير.

كما نلاحظ بأن العامل & تستعمله الدالة ()scanf للحصول على عنوان متغير.

مثال :

int N;
printf("ًWrite a number : ");
scanf("%d", &N);

 2.2 كيف يمكن معرفة عنوان متغير ما في الذاكرة

نعتبر التعريف التالي :           int  A=10;

محتوى المتغير A هو 10 ،  لكن ما هو عنوانه في الذاكرة ؟

الحل 1 : استعمال العامل &

لمعرفة محتوى A نستعمل  A   بذاته

ولمعرفة عنوانه  نستعمل   A& ، ونكتب إذن :

int A=10;
printf("A = %d\n",A);
printf("Address of A = %X\n",&A);

ملاحظة : X%  ===>  تعني ظهور النتيجة بالنظام الستعشري، ويمكنك استعمال d% عوضها للإظهار  بالنظام العشري.

وخلاصة الأمر أن :

A    : تعبر عن محتوى A

A& : تعبر عن عنوان A

 

الحل 2 :استعمال المؤشر

أولا نقوم بتعريف مؤشر من نفس نوع المتغير A، ولأجل ذلك نكتب :     

int *P;
int A=10;
P = &A; /*نعطي لـ P عنوان A */

ليكن في العلم (  تذكر هذا جيدا):

  •  أن  P  هي بمثابة  A&  وكلاهما يعبر عن  عنوان  A
  • وأن P* هي بمثابة  A    وكلاهما يعبر عن  محتوى  A

وبالتالي نكتب أيضا :

int *P;
int A=10;
P = &A;
printf("A = %d\n",*P);
printf("Address of A = %X\n",P);

 

2.3 كيف يمكن تغيير محتوى متغير ما في الذاكرة

نعتبر التعريف التالي :           int  A=10;

محتوى المتغير A هو 10 ،  لكن كيف يمكن تغيير محتواه في الذاكرة ؟

الحل 1 : استعمال المتغير A ذاته

بما أن A ذاته يمثل محتوى A فيمكننا أن نكتب إذا :

int A=10; /* A= 10 */
printf("A = %d\n",A);
A=20; /* A = 20 */
printf("A = %d\n",A);
A+=5; /* A = 25 */
printf("A = %d\n",A);
scanf("%d",&A); /*إدخال قيمة جديدة*/
printf("A = %d\n",A);

الحل 2 :استعمال المؤشر

أولا نقوم بتعريف مؤشر من نفس نوع المتغير A، ولأجل ذلك نكتب :     

int *P;
int A=10;
P = &A; /*نعطي لـ P عنوان A */

ليكن في العلم  (تذكر هذا جيدا):

  •  أن  P  هي بمثابة A&  وكلاهما يعبر عن  عنوان  A
  • وأن P* هي بمثابة A     وكلاهما يعبر عن  محتوى  A

وبالتالي نكتب أيضا :

int *P;
int A=10;
P = &A;

printf("A = %d\n",*P);
*P=20; /* A= 20 */
printf("A = %d\n",*P);
(*P)+=5; /* A = 25 */
printf("A = %d\n",A);
scanf("%d",P); /*إدخال قيمة جديدة*/
printf("A = %d\n",A);

 

 خلاصة (تذكر هذا جيدا) : إن أي تغيير يطرأ على P* يطرأ أيضا على A لكن بشرط أن يكون P يحمل عنوان A.

 

2.4 أمثلة أخرى

مثال 1 : نعتبر التطبيق التالي (يمكننا التحكم في متغير انطلاقا من مؤشرين أو أكثر)

ملاحظة : الكتابتين التاليتين متكافئتين.

الكتابة الأولى

الكتابة الثانية

int *P,*T,A=10;
P = &A; /* *P=10 */ 
T = &A; /* *T=10 */


printf("A = %d\n", A); 
printf("A = %d\n",*P); 
printf("A = %d\n",*T); 
*P=20+*T ; /* A=*T =*P =30 */ 
printf("A = %d\n", A); 
printf("A = %d\n",*P); 
printf("A = %d\n",*T); 
scanf("%d",&A); /*إدخال قيمة جديدة*/ 
printf("A = %d\n", A); 
printf("A = %d\n",*P); 
printf("A = %d\n",*T);
int *P,*T,A=10;
P = &A; /* *P=10 */
T = P; /* *T=10 */ 


printf("A = %d\n", A);
printf("A = %d\n",*P);
printf("A = %d\n",*T);
*T=20+A ; /* A=*T =*P =30 */ 
printf("A = %d\n", A); 
printf("A = %d\n",*P); 
printf("A = %d\n",*T); 
scanf("%d",T); /*إدخال قيمة جديدة*/ 
printf("A = %d\n", A); 
printf("A = %d\n",*P); 
printf("A = %d\n",*T);

مثال 2 : نعتبر التطبيق التالي

ملاحظة : الكتابتين التاليتين متكافئتين.

الكتابة الأولى

الكتابة الثانية

int B=20,A=10;

printf("A = %d\n", A);
printf("B = %d\n", B);
B = A;
A = 20;
printf("A = %d\n", A);
printf("B = %d\n", B);

int *P,B=20,A=10;
P = &A; /* *P=10 */


printf("A = %d\n", A);
printf("B = %d\n", B);
B = *P;
*P = 20;
printf("A = %d\n", A);
printf("B = %d\n", B);

 

2.4 العمليات الحسابية باستعمال المؤشر

مثال : الكتابتين التالين متكافئتين

الكتابة الأولى

الكتابة الثانية

int *P,*T,A=10,B=20;
P = &A; /* *P=10 */
T = &B; /* *T=20 */

*P=15; /* A=15 */
*P=*T; /* A=20 */
*T=*T+*P; /* B=40 */
*P=*P-*T; /* A=-20 */
(*P)++; /* A=-19 */
(*T)--; /* B=39 */
*T+=10; /* B=49 */
*P-=10; /* A=-29 */
*P+=*T; /* A=20 */
*T-=*P; /* B=29 */
*T*=10; /* B=290 */
*P*=*T; /* A=5800*/
*T=*P**T; /* B=1682000*/

 int A=10,B=20;

 

A=15; /* A=15 */
A=B; /* A=20 */
B=B+A; /* B=40 */
A=A-B; /* A=-20 */
A++; /* A=-19 */
B--; /* B=39 */
B+=10; /* B=49 */
A-=10; /* A=-29 */
A+=B; /* A=20 */
B-=A; /* B=29 */
B*=10; /* B=290 */
A*=B; /* A=5800*/
B=A*B; /* B=1682000*/


التمرين 9.1

نعتبر الشفرة التالية:

main()
{
 int A = 1;
 int B = 2;
 int C = 3;
 int *P1, *P2;
 P1=&A;
 P2=&C;
 *P1=(*P2)++;
 P1=P2;
 P2=&B;
 *P1-=*P2;
 ++*P2;
 *P1*=*P2;
 A=++*P2**P1;
 P1=&A;
 *P2=*P1/=*P2;
return 0;
}

أكمل الجدول التالي من خلال الشفرة أعلاه :

  A B C P1 P2
الحالة البدئية 1 2 3 / /
P1=&A 1 2 3 &A /
P2=&C          
*P1=(*P2)++          
P1=P2          
P2=&B          
*P1-=*P2          
++*P2          
*P1*=*P2          
A=++*P2**P1          
P1=&A          
*P2=*P1/=*P2          

3. المؤشرات والجداول

في C هناك علاقة وطيدة بين المؤشرات والجداول، حيث أن جميع العمليات التي تجرى على الجداول هي نفسها التي تجرى على المؤشرات.

 

3.1. عنونة عناصر الجدول

نعتبر التعريف التالي :      int  TAB[10];

ليكن في العلم أن عنوان الجدول في الذاكرة هو عنوان أول عنصر فيه، ويمكننا القول بأن :

 (تذكر هذا جيدا)        &TAB[0]     تكافئ      TAB                                        

لنعتبر التعريف التالي :

int *P,TAB[10]={1,2,3,4,5,6,7,8,9,10};
P = TAB; /* P=&TAB[0];*/

 الكتابات التالية متكافئة :
للتعبير عن المحتوى     للتعبير عن العنوان
(P+0)* <=> (TAB+0)* <=> P[0] <=> TAB[0]
(P+1)* <=> (TAB+1)* <=> P[1] <=> TAB[1]
(P+2)* <=> (TAB+2)* <=> P[2] <=> TAB[2]
(P+3)* <=> (TAB+3)* <=> P[3] <=> TAB[3]
(P+4)* <=> (TAB+4)* <=> P[4] <=> TAB[4]
(P+5)* <=> (TAB+5)* <=> P[5] <=> TAB[5]
(P+6)* <=> (TAB+6)* <=> P[6] <=> TAB[6]
(P+7)* <=> (TAB+7)* <=> P[7] <=> TAB[7]
(P+8)* <=> (TAB+8)* <=> P[8] <=> TAB[8]
(P+9)* <=> (TAB+9)* <=> P[9] <=> TAB[9]
   
(P+0) <=> (TAB+0) <=> &TAB[0]
(P+1) <=> (TAB+1) <=> &TAB[1]
(P+2) <=> (TAB+2) <=> &TAB[2]
(P+3) <=> (TAB+3) <=> &TAB[3]
(P+4) <=> (TAB+4) <=> &TAB[4]
(P+5) <=> (TAB+5) <=> &TAB[5]
(P+6) <=> (TAB+6) <=> &TAB[6]
(P+7) <=> (TAB+7) <=> &TAB[7]
(P+8) <=> (TAB+8) <=> &TAB[8]
(P+9) <=> (TAB+9) <=> &TAB[9]

حسنا، كيف نتحكم في الجدول باستعمال المؤشر ؟

من أجل هذا نعتبر المثال التالي : (الكتابتين التاليتين متكافئتين)

الكتابة الأولى

الكتابة الثانية

int i,TAB[10]={1,2,3,4,5,6,7,8,9,10};

for(i=0;i<10;i++)
  scanf("%d",&TAB[i]);
for(i=0;i<10;i++)
  printf(" %d",TAB[i]);

int i,*P,TAB[10]={1,2,3,4,5,6,7,8,9,10};
P = TAB; /* P=&TAB[0];*/

for(i=0;i<10;i++)
  scanf("%d",(P+i));
for(i=0;i<10;i++)
  printf(" %d",*(P+i));

خلاصة :

إذا كان TAB جدولا فإن:

  1. TAB  تعبر عن  عنوان العنصر [0]TAB
  2. TAB + i   تعبر عن  عنوان العنصر TAB[i]
  3. (TAB + i)*   تعبر عن  محتوى العنصر TAB[i]

وكذلك :

  1. [0]TAB&  تعبر عن  عنوان العنصر [0]TAB
  2. &TAB[i]   تعبر عن  عنوان العنصر TAB[i]
  3. TAB[i]   تعبر عن  محتوى العنصر TAB[i]

إذا كان مؤشرا P يشير للجدول TAB فإن:

  1. تعبر عن  عنوان العنصر [0]TAB
  2. P+ i   تعبر عن  عنوان العنصر TAB[i]
  3. (P + i)*   تعبر عن  محتوى العنصر TAB[i]
  4. P[i]   تعبر عن  محتوى العنصر TAB[i]

التمرين 9.2

أنجز شفرة تمكن من قراءة جدولين A و B وكذلك بعديهما N و M من خلال لوحة المفاتيح ، قم بعد ذلك بإضافة عناصر B بآخر A . استعمل المؤشر في كل حالة ممكنة.


3.1. العمليات الحسابية

3.1.1 إعطاء محتوى مؤشر لمؤشر آخر

مثال : 

float *P1,*P2;
int *P3;

P1=P2;        /*هذه الكتابة صحيحة لأنهمـــــا من نفس النوع*/
P1=P3;        /*هذه الكتابة خاطئة لأنهما ليس من نفس النوع*/


3.1.2 الزيادة والنقصان عند المؤشرات

إذا كان P مؤشرا يشير إلى العنصر   فإن :  TAB[i]

P++;  P يشير إلى  A[i+1]
P+=n; P يشير إلى A[i+n] 
P--; P يشير إلى A[i-1]   
P-=n; P يشير إلى A[i-n]   

مجال الزيادة والنقصان :لا يجب أن تتعدى أقصى رتبة للجدول أو أقل رتبة التي هي 0.

مثال :

  int A[10];
  int *P;
/* آخر عنصر -> مقبول */ P = A+9;
/* آخر عنصر + 1 -> مقبول */ P = A+10;
/* أول عنصر + 2 -> غير مقبول */ P = A+11;
/* أول عنصر - 1 -> غير مقبول */ P = A-1;

 

3.1.3 عملية الطرح بين مؤشرين

إذا كان P1 و P2 مؤشرين على نفس الجدول فإن نتيجة العملية P2-P1 تكون :

- سالبة
إذا كان P1 قبل P2
- صفرا
إذا كان P1 = P2
- موجبة
إذا كان P2 قبل P1
- غير محددة
إذا كان P1 و P2 مؤشرين على جدولين مختلفين

كما أنه بالإمكان المقارنة بين مؤشرين بالعوامل الطبيعية للمقارنة.


التمرين 9.3

لماذا عمل مطوري C على جعل مؤشرات أول عنصر في الجدول ولا تتواجد في آخر الجدول؟ أعط مثالا.


التمرين 9.4

ليكن P مؤشر 'يشير' إلى الجدول A :

int A[] = {12, 23, 34, 45, 56, 67, 78, 89, 90};
int *P;
P = A;

ما هي قيم أو عناوين الإنشاءات التالية:

  1. *P+2

  2. *(P+2)

  3. &P+1

  4. &A[4]-3

  5. A+3

  6. &A[7]-P

  7. P+(*P-10

  8. *(P+*(P+8)-A[7])


التمرين 9.5

أنجز شفرة تمكن من قراءة عدد صحيح X  وجدول A من نوع int  من  لوحة المفاتيح ، قم بعد ذلك بإزالة كل مثيلات X في الجدول A  .الشفرة تستعمل مؤشرين P1 و P2 للمرور في الجدول.


التمرين 9.6

أنجز شفرة تمكن من تبديل عناصر جدول A من نوع int  في الاتجاه المعاكس. الشفرة تستعمل مؤشرين P1 و P2  ومتغير AIDE للمساعدة في التبديل.


 
 3.2. المؤشرات والمتسلسلات الحرفية

معلوم أن المتسلسلات عبارة عن جداول ذات النوع char، لهذا فإن تعامل المؤشرات مع المتسلسلات شبيه بتعاملها مع الجداول.

لمناقشة المتسلسلات من خلال المؤشرات نعتبر الأمثلة التالية :

 

3.2.1  مثال أول "استعمال الجدول"

char ch[10]="salam";

هذه المتسلسلة تمثل في الذاكرة بهذا الشكل :

                  '\0' 'm' 'a' 'l' 'a' 's'
العناوين 003A 0039 0038 0037 0036 0035 0034 0033 0032 0031

كما نلاحظ أن هذه المتسلسلة تحجز 10 خانات أي 10 أثمان في الذاكرة، رغم أنه لدينا فقط 6 خانات مستعملة للكلمة "salam

وإذا أردنا كتابة الجملة "salam 3alaikom" في المتسلسلة ch فإنها لن تستطيع استيعابها لأنها متكونة من 15 حرفا،وهذا يعتبر من مساوئ استعمال الجداول لتمثيل الجمل.

ملاحظة : يمكننا أن نستعمل مؤشرا من نفس نوع المتسلسلة ch يعني النوع char ونتحكم فيها من خلال هذا المؤشر كما تطرقنا في الصفحة السابقة.

 

3.2.2  مثال ثان "استعمال المؤشر"

char *ch="salam";

تكافئ

char *ch;
ch="salam";

هذه المتسلسلة تمثل في الذاكرة بهذا الشكل :

  '\0' 'm' 'a' 'l' 'a' 's'
العناوين 0036 0035 0034 0033 0032 0031

كما نلاحظ أن هذه المتسلسلة تحجز 6 خانات أي 6 أثمان في الذاكرة فقط وذلك حسب طول المتسلسلة.

وإذا أردنا كتابة الجملة "sisio 3alaikom" في المتسلسلة ch فإننا نكتب بكل بساطة :    ch="sisio 3alaikom";

وبالتالي تصبح المتسلسلة تحجز 15 خانة أي 15 ثمنا في الذاكرة، أما كلمة "salam" فقد تم حذفها.

 

3.2.3  مثال ثالث "الفرق بين استعمال المؤشر واستعمال الجدول"

المثال الأول : استعمال الجداول

char A[45] = "Petite chaîne";
char B[45] = "Deuxième chaîne un peu plus longue";
char C[30];
A = B; /* !!! مستحيل -> خطأ*/
C = "Bonjour !"; /* !!! مستحيل -> خطأ */

خلاصة : في هذا المثال

  1.  لا يجب أن نعطي B لـ A مباشرة، وإذا أردنا ذلك سنستعمل الدالة ()strcpy
  2. لا يجب أن نعطي جملة لـ C مباشرة، وإذا أردنا ذلك سنستعمل الدالة ()strcpy

المثال الثاني : استعمال المؤشرات

char *A = "Petite chaîne";
char *B = "Deuxième chaîne un peu plus longue";
A = B;

  في هذه الحالة أصبح A و B مؤشرين على نفس المتسلسلة.

3.2.4  مثال رابع "مناقشة مثال الدالة strcpy"

تقوم الدالة بنسخ متسلسلةCH2  في أخرى CH1 ، حيث CH1 و CH2 عبارة عن مؤشرين تستعملهما الدالة strcpy للدخول إليها.

أول تقريب للدالة strcpy

void strcpy(char *CH1, char *CH2)

  int I; 
  I=0; 
  while ((CH1[I]=CH2[I]) != '\0') 
   I++; 
}

ثاني تقريب للدالة strcpy

void strcpy(char *CH1, char *CH2)

  int I; 
  I=0; 
  while ((CH1[I]=CH2[I]) != '\0') 
   I++; 
}

 ثالث تقريب للدالة strcpy

void strcpy(char *CH1, char *CH2) 

  while ((*CH1=*CH2) != '\0') 
  { 
   CH1++; 
   CH2++; 
  } 
}

رابع تقريب للدالة strcpy

void strcpy(char *CH1, char *CH2)
{
  while (*CH1++ = *CH2++);
}
 

التمرين 9.7

أنجز شفرة تمكن من قراءة جدولين A و B وكذلك بعديهما N و M من خلال لوحة المفاتيح ، قم بعد ذلك بإضافة عناصر B بآخر A . استعمل مؤشرين PB و PA لأجل التحويل وإظهار الجدول الناتج A على الشاشة.


التمرين 9.8

أنجز بطريقتين مختلفتين  شفرة تمكن من التحقق بدون استعمال دوال <string.h> ، إذا كانت الجملة أو الكلمة CH المطلوب إدخالها من لوحة المفاتيح معكوسة.

أ) استعمل فقط الجدول.

ب) استعمل المؤشرات مكان الرموز الرقمية.

تذكير : الكلمة أو الجملة المعكوسة : عندما يكون نصفها الأخير مشابه لنصفها الأول لكن في الاتجاه المعاكس.

أمثلة

PIERRE

 

ليست معكوسة

OTTO

 

معكوسة

23432

 

معكوسة

محمد

 

ليست معكوسة

باب

 

معكوسة


التمرين 9.9

أنجز شفرة تمكن من قراءة متسلسلة حرفية CH وحدد طولها بمساعدة مؤشرP ،الشفرة لا تستعمل أبدا المتغيرات الرقمية.أظهر النتائج على الشاشة.


التمرين 9.10

أنجز شفرة تمكن من قراءة جملة CH وحدد عدد الكلمات الموجودة فيها.استعمل مؤشرP ، ومتغير منطقي، والدالة isspace  ومتغير رقمي N الذي يحتوي على عدد الكلمات. أظهر النتائج على الشاشة.


التمرين 9.11

أنجز شفرة تمكن من قراءة متسلسلة حرفية CH  المطلوب إدخالها من لوحة المفاتيح، أحسب لكل حرف من الحروف الأعجمية بدون اعتبار هل هي كبيرة أم صغيرة عدد المرات التي تكرر فيها داخل CH . استعمل جدول ABC ذي البعد 26 لحفظ نتيجة كل حرف ، ومؤشر PCH للمرور في CH  ومؤشر PABC  للمرور في ABC .أظهرفقط عدد التكرار لكل حرف على الشاشة.

مثال :

Write a line of text (max. 100 characters) :
Jeanne
The sentence "Jeanne" has :
1 times of the alphabet 'A'
2 times of the alphabet 'E'
1 times of the alphabet 'J'
3 times of the alphabet 'N'


التمرين 9.12

أنجز شفرة تمكن من قراءة حرف C و جملة CH وحدد عدد المرات التي تكرر فيها C داخل CH ، بعد ذلك احذف جميع الحروف المماثلة لـ C في CH .استعمل مؤشر والدالة strcpy للمساعدة. أظهر النتائج على الشاشة.


التمرين 9.13

أنجز شفرة تمكن من قراءة  جملتين CH1 و CH2 ،أزل  جميع حروفالجملة CH1 من المتسلسلة الحرفية CH2.استعمل مؤشرين P1 و P2 ومتغير منطقي TROUVE  والدالة strcpy للمساعدة. أظهر النتائج على الشاشة.

أمثلة:

Bonjour

Bravo

==>

njou

Bonjour

bravo

==>

Bnjou

abacab

aa

==>

bcab

 


التمرين 9.14

أنجز شفرة تمكن من قراءة  جملتين CH1 و CH2 ،أزل  الجملة CH1 من المتسلسلة الحرفية  CH2 في المرة الأولى فقط.استعمل فقط المؤشر ومتغير منطقي TROUVE  والدالة strcpy للمساعدة. أظهر النتائج على الشاشة.

أمثلة:

Alphonse

phon

==>

Alse

totalement

t

==>

otalement

abacab

aa

==>

abacab

 


3.3. المؤشرات والمصفوفات

لنأخذ مثالا : نعتبر M مصفوفة

int M[4][10] = {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
                 {10,11,12,13,14,15,16,17,18,19},
                 {20,21,22,23,24,25,26,27,28,29},
                 {30,31,32,33,34,35,36,37,38,39}};

M : هو اسم المصفوفة، وهو أيضا عنوان أول عنصر فيه حيث يشير (مفاجأة) إلى السطر الأول أي إلى الجدول M[0]   الذي يحتوي على العناصر التالية : {0,1,2,3,4,5,6,7,8,9}.

M+1 : هو عنوان ثاني عنصر في المصفوفة حيث يشير إلى السطر الثاني أي إلى الجدول M[1]   الذي يحتوي على العناصر التالية : {10,11,12,13,14,15,16,17,18,19}.

وهكذا ...

شرح وتفصيل:

في الحقيقة أو في الواقع : إن المصفوفات التي نقول أنها جداول ذات بعدين (هذا في التمثيل الرياضي فقط)، ليست إلا جداول أحادية البعد في الذاكرة حيث أن كل عنصر فيها يمثل مجموعة من العناصر نسميها سطرا أو جدولا. حيث أن أول عنصر في المصفوفة M هو المتجهة {0,1,2,3,4,5,6,7,8,9}, وثاني عنصر هو المتجهة {10,11,12,13,14,15,16,17,18,19} وهكذا...

وعموما نقول أن (M+i) عنوان العنصر أو الجدول M[I]

مسألة : كيف يمكن أن نَدْخُلَ أو أن نستعمل عنصر المصفوفة M (أي M[0][0], M[0][1], ... , M[3][9] ) بمساعدة المؤشر؟

مناقشة الحل :

هناك حل، وهو تحويل قيم المصفوفة M (التي هي في الأصل مؤشرا على جدول ذي النوع int ) إلى مؤشر على int. ومن أجل هذا نكتب :

int M[4][10] = {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
                {10,11,12,13,14,15,16,17,18,19},
                {20,21,22,23,24,25,26,27,28,29},
                {30,31,32,33,34,35,36,37,38,39}};
int *P;
P = M; /* تحويل تلقائي */

العملية P = M; هي عبارة عن تحويل تلقائي للعنوان  &M[0] إلى العنوان  &M[0][0] (لاحظ أن العنوان المحول بقي هو نفسه لكن طبيعة المؤشر هي التي تبدلت.)

في الحقيقة هذا الحل ليس حلا كافيا مائة في المائة.

الحل النهائي :

في حالة الجداول ذات بعدين يجب أن يكون هناك تحويلا إجباري :

int M[4][10] = {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
                {10,11,12,13,14,15,16,17,18,19},
                {20,21,22,23,24,25,26,27,28,29},
                {30,31,32,33,34,35,36,37,38,39}};
int *P;
P = (int *)M; /* تحويل إجباري */

في هذه الحالة أصبح لدينا مؤشرا على كل عناصر المصفوفة، وبالتالي يمكن أن نستعمل المصفوفة M كجدول أحادي البعد من خلال      P. أي أن أصبح جدولا أحادي البعد رتبته هي 10×4.

مثال : لنحسب مجموع عناصر المصفوفة M باستعمال المؤشر P.

int M[4][10] = {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
                {10,11,12,13,14,15,16,17,18,19},
                {20,21,22,23,24,25,26,27,28,29},
                {30,31,32,33,34,35,36,37,38,39}};
int *P;
int I, SOM;
P = (int*)M;
SOM = 0;
for (I=0; I<40; I++) /* 40 هي عدد عناصر المصفوفة كلها */
  SOM += *(P+I);

 مثال آخر : نعتبر مصفوفة A ذات 3 أسطر و 4 أعمدة، ونريد استعمال سطرين وعمودين فقط :

int A[3][4];
A[0][0]=1;
A[0][1]=2;
A[1][0]=10;
A[1][1]=20;

 في الذاكرة، هذه العناصر تُحجز كما يلي :

 وتذكر جيدا أن عنوان العنصر  A[I][J] يحسب بهذه العلاقة :  A + I*4 + J .


التمرين 9.15

أنجز شفرة تمكن من قراءة  مصفوفة A  ذات البعدين N و M من لوحة المفاتيح. استعمل المؤشر في كل مرة ممكنة وأظهر النتائج على الشاشة كما يلي :

أ) المصفوفة A

ب) مقلوب A

ج) ناقش A كأنها جدول أحادي البعد.


التمرين 9.16

أنجز شفرة تمكن من قراءة  مصفوفة A  ذات البعدين N و M  والمصفوفة B  ذات البعدين M و P  من لوحة المفاتيح.

أنجز عملية ضرب A في B  والنتيجة تحتفظ في المصفوفة C . استعمل المؤشر في كل مرة ممكنة وأظهر النتائج على الشاشة.


التمرين 9.17

أنجز شفرة تمكن من قراءة 5  كلمات (50 حرفا كأقصى حد) حيث يتم تحفيظهم في جدول المتسلسلات الحرفية TABCH . اقلب ترتب حروف الكلمات الخمس بمساعدة مؤشرين P1 وP2 .أظهر النتيجة.


 

ملاحظة : إذا كان مفهوم المؤشر مازال جديدا لديك لحد الآن، فمن المفضل أن لا تزعج نفسك بالمقطعين 4 و5 لهذه الفقرة ، وتابع القراءة للفقرتين الآتيتين، وما إن تمكنت من خلالهما من فهم عمل المؤشرات فارجع إلى هنا لتكمل ما تم نقصانه.


4. جدول المؤشرات

 

4.1 التعريف

إذا أردنا استعمال مؤشرات كثيرة، في هذه الحالة من المفضل أن نعرف جدولا للمؤشرات.

أمثلة للتعريف :

char *JOUR[7];
double *A[10];
int *tab[20];

 ملاحظة : تستعمل غالبا جداول المؤشرات لاقتصاد حجم الذاكرة عندما نستعمل جدول المتسلسلات الحرفية.

 

4.2 أين تتجلى أهمية جدول المؤشرات

نعتبر المثال التالي:"استعمال جدول المتسلسلات الحرفية"

char JOUR[7][9]= {"lundi", "mardi", "mercredi","jeudi",

                   "vendredi", "samedi","dimanche"};

الحجز في الذاكرة :

من خلال هذا المثال نلاحظ أن الجدول JOUR يحجز  7*9*1 = 63  ثمن في الذاكرة.

لإظهار الحروف الأولى للأيام فقط نكتب :

for(I=0; I<7; I++)
    printf("%c ", JOUR[I][0]);

ولإظهار جميع الأيام على الشاشة نكتب :

for(I=0; I<7; I++)
    printf("%s\n", JOUR[I]);

 نعتبر المثال التالي:"استعمال جدول المؤشرات"

char *JOUR[] = {"dimanche", "lundi", "mardi",
                "mercredi", "jeudi", "vendredi",
                "samedi"};

في هذه الحالة عرفنا جدولا ذو 7 مؤشرات على char. وكل مؤشر يحمل عنوان متسلسلة واحدة من بين 7 متسلسلات.

 الحجز في الذاكرة :

من خلال هذا المثال نلاحظ أن الجدول JOUR يحجز  52 ثمنا في الذاكرة.

لإظهار الحروف الأولى للأيام فقط نكتب :

for(I=0; I<7; I++)
   printf("%c ", *JOUR[I]);

ولإظهار جميع الأيام على الشاشة نكتب :

for(I=0; I<7; I++)
   printf("%s\n", JOUR[I]);

العبارة  JOUR[I]+J  ترمز إلى الحرف ذي الموقع J في الجملة I . وبالتالي يمكننا إظهار الحرف الثالث لكل يوم في الأسبوع :

int I;
for (I=0; i<7; I++) printf("%c\n",*(JOUR[I]+2));

خلاصة :

  1. الحجز في الذاكرة باستعمال جدول المؤشرات اقتصادي من استعمال جدول المتسلسلات (52 < 63)

  2. كما أن معالجة المتسلسلات باستعمال جدول المؤشرات شبيه تقريبا باستعمال جدول المتسلسلات (كإظهار الحروف الأولى ..)

  3. نعتبر D جدولا للمؤشرات، نكتب إذا :int *D[];

D[i]

 يرمز إلى عنوان أول العنصر i

D[i]+j

 يرمز إلى عنوان العنصر j في العنصر i

*(D[i]+j)

 يرمز إلى محتوى العنصر j في العنصر i



التمرين 9.18

نعتبر التعريفات أو المعطيات التالية :

char *NOM1[] = {"Mohammed", "Mustafa", "Omar","Othman", "Zineb" };    
char NOM2[][16] = {"Mohammed", "Mustafa", "Omar", "Othman", "Zineb" };

أ) أنجز خطاطة يدويا  تبين في كيفية تحفيظ المتغيرين NOM1 و NOM2 .

ب) نفترض أنك تريد إنجاز خوارزم أو شفرة لترتيب الكلمات قاموسيا بالنسبة للمتغيرين NOM1 و NOM2 ، علما أنك استعملت نفس طريقة الترتيب لكل منهما ، من هي في رأيك الشفرة الأسرع : الشفرة المخصصة لـ NOM1 أم  الشفرة المخصصة لـ NOM


التمرين 9.19

أنجز شفرة تمكن من قراءة اليوم والشهر والسنة من لوحة المفاتيح ، وأظهر التاريخ بالفرنسية والألمانية. استعمل جدولين للمؤشرات MFRAN  و MDEUT يحتويان في البداية على أسماء الشهور بالنسبة للغتين. أول جزء في كل جدول يحتوي على رسالة أو جملة تعبر عن الخطأ ليتم إظهارها في حالة إن كان هناك خطأ.

أمثلة :

  Introduisez la date: 1 4 1993
  Luxembourg, le 1er avril 1993 
  Luxemburg, den 1. April 1993  
  Introduisez la date: 2 4 1993
  Luxembourg, le 2 avril 1993   
Luxemburg, den 2. April 1993  


5.الحجز الحركي للذاكرة

لقد رأينا فقط كيف نتعامل مع المؤشرات حيث نقتصد أثناء التعريف أو التهيئة في حجم الذاكرة، لكن ماذا لو أردنا حجز أثمان أخرى أو تحريرها، في هذه الحالة نتكلم عن الحجز الحركي للذاكرة.

 

5.1 التعريف الطبيعي للمعطيات

وهو أن كل متغير يحتاج إلى عدد محدد من الأثمان في الذاكرة، ويتم تحديد ذلك أثناء التعريف.

مثال:

float A, B, C; /* حجز 12 ثمنا */
short D[10][20]; /* حجز 400 ثمن */
char E[] = {"salam"}; /* حجز 6 أثمان */
char F[][10] = {"un", "deux", "trois", "quatre"};
/* حجز 40 ثمنا */

المؤشر :

في هذه الحالة عدد الأثمان المحجوزة للمؤشر تتعلق بنوع الحاسوب وكذلك بنوع الذاكرة المختارة، حيث أن كل هذا يكون معروفا لذا المترجم C.
 ولنفترض أن المؤشر يحتاج لـ
p ثمن في الذاكرة (في DOS : تكون p=2 أو p=4)

مثال:

double *G; /*حجز p ثمن */
char *H; /* حجز p ثمن */
float *I[10]; /* حجز 10*p ثمن */

أمثلة أخرى :

char *J = "Bonjour !"; /* حجز p+10 ثمن */
float *K[] = {"un", "deux", "trois", "quatre"};
/* حجز 4*p+3+5+6+7 ثمن */

 

5.2 الحجز الحركي

مشكل:

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

مثال :

 نريد قراءة 10 جملة من لوحة المفاتيح ونقوم بحفظها في جدول المؤشرات من نوع char. نكتب إذا :

char *TEXTE[10];

نحتاج إذا لـ 10*p من أجل هذه العشر المؤشرات. وهذا العدد معروف في البداية والأثمان تحجز في هذه الحالة تلقائيا. لكن المشكل هو أننا لا نعرف عدد الأثمان التي يجب حجزها في الذاكرة أثناء التنفيذ للجمل المطلوب إدخالها من طرف المستعمل...

الحجز الحركي :

الحجز في الذاكرة لهذه الجمل يمكن أن يكون فقط أثناء التنفيذ . نتكلم إذن عن الحجز الحركي للذاكرة.

5.3 الدالة malloc والعامل sizeof

تنتمي الدالة malloc إلى المكتبة <stdlib> وهي تساعدنا في حجز الأثمان للمعطيات حركيا أثناء تنفيذ المشروع.

مثال : malloc(N) ==>  يتم حجز N ثمن مباشرة.

مثال : لنفترض أننا نريد حجز جزء من الذاكرة لنص يتكون من 4000 حرف. نقوم في هذه الحالة بتعريف مؤشر T من نوع char.ونكتب إذن :

char *T;
T = malloc(4000);

هنا يتم حجز 4000 ثمن، لكن إن لم يوجد مكان في الذاكرة ، تكون نتيجة T هي 0.

كما أننا حجزنا 4000 ثمن لـ 4000 حرف لأن النوع char يحجز لكل حرف ثمنا واحدا فقط.

لكن إن لم نكن نعرف حجم نوع الذي نريد استعماله، ماذا سنفعل في هذه الحالة ؟

 هنا يبرز الدور الكبير الذي تلعبه الدالة sizeof التي تقوم بمعرفة حجم كل نوع.

sizeof(متغير) ==>  تكون نتيجتها هو مقدار المتغير.

sizeof(ثابتة)  ==>  تكون نتيجتها هو مقدار الثابتة.

sizeof(نوع)  ==>  تكون نتيجتها هو مقدار النوع.

مثال :

short A[10];
char B[5][10];

تكون النتيجة :

sizeof(A)

تُرْجٍع 20 ثمنا

sizeof(B)

تُرْجٍع 50 ثمنا

sizeof(4.25)

تُرْجٍع 8 أثمان

sizeof("salamor !")

تُرْجٍع 10 أثمان

sizeof(float)

تُرْجٍع 4 أثمان

sizeof(double)

تُرْجٍع 8 أثمان

مثال آخر : نريد أن نحجز في الذاكرة لـ X قيمة من نوع int ، العدد X يتم قراءته من لوحة المفاتيح :

int X;
int *PNum;
printf("Write a number :");
scanf("%d", &X);
PNum = malloc(X*sizeof(int));

 

5.4 الدالة exit

تستعمل هذه الدالة للخروج من المشروع أثناء التنفيذ لعدة أسباب منها عدم وجود مكان في الذاكرة أثناء الحجز.

تنتمي هذه الدالة إلى المكتبة <stdlib>.

مثال : المشروع التالي يمكن من قراءة 10 جمل من لوحة المفاتيح، حيث يبحث في الذاكرة على الأمكنة الفارغة والقادرة على استيعاب هذه الجمل. في حالة عدم وجود مكان للحجز فيه يظهر المشروع رسالة تعبر عن وجود خطا معين ثم يتم الخروج من التنفيذ مباشرة.

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

main()
{
 char INTRO[500];
 char *TEXTE[10];
 int I;
 

 for (I=0; I<10; I++)
 {
  gets(INTRO);
  /* الحجز في الذاكرة */
  TEXTE[I] = malloc(strlen(INTRO)+1);
  /* هل يوجد مكان فارغ في الذاكرة */
  if (TEXTE[I])
   /* نسخ الجملة الممثلة */
   strcpy(TEXTE[I], INTRO);
  else
  {
   /* وإلا فاخرج من البرنامج مع */
   /* ذكر رسالة خطأ بأنه لا يوجد مكان إضافي في الذاكرة */

   printf("ERROR: There is no enough memory space \n");
   exit(-1);
  }
 }
 return 0;
}


التمرين 9.20

أنجز شفرة تمكن من قراءة 10  جمل (200 حرف كأقصى حد) حيث يتم تحفيظهم حركيا في الذاكرة باستعمال جدول المؤشرات  . اقلب ترتيب حروف الجمل العشر بتغيير المؤشرات .أظهر النتيجة على الشاشة.


 

5.5 الدالة free

بعد الحجز الذي قمنا به نحتاج بطبيعة الحال إلى تحرير الأماكن أو الأثمان التي استعملناها، في هذه الحالة يبرز دور الدالة free التي تقوم بهذا العمل،

ملاحظة :  تنتمي الدالة free إلى المكتبة <stdlib>

مثال :

  char *p;

  p=malloc(20*sizeof(char));

  ....

  free(p)


التمرين 9.21

أنجز شفرة تمكن من قراءة 10  جمل (200 حرف كأقصى حد) حيث يتم تحفيظهم في جدول المؤشرات MOT. امسح الجمل العشر لكن حسب ترتبها قاموسيا لتحرر مكانها في الذاكرة .أظهر النتيجة في كل عملية مسح حيث يتم انتظار المستعمل على أن يضغط على المفتاح 'ENTER'.


التمرين 9.22

أنجز شفرة تمكن من قراءة 10  كلمات (50 حرفا كأقصى حد) حيث يتم تحفيظهم في جدول المؤشرات MOT. انسخ أو انقل الكلمات العشر حسب ترتبها قاموسيا لتضعها في جملة واحدة PHRASE . احجز مكانا يكفي لـ PHRASE في الذاكرة قبل عملية نسخ أو نقل الكلمات. حرر مكان كل كلمة بعد نقلها أو نسخها في PHRASE .استعمل دوال <string.h>.


التمرين 9.23

أنجز شفرة تمكن من قراءة 10  كلمات (50 حرفا كأقصى حد) حيث يتم تحفيظهم في جدول المؤشرات MOT.  احجز مكانا يكفي لكل كلمة حركيا في الذاكرة. رتب الكلمات قاموسيا بالطريقة التي تناسبك، لكن باستعمال المؤشر.


 تأليف

المؤلف الأصلي: فرديريك فابر (Frédéric FABER)

البريد الإلكترونيعنوان البريد الإلكتروني هذا محمي من روبوتات السبام. يجب عليك تفعيل الجافاسكربت لرؤيته.

الموقع الإلكتروني http://www.ltam.lu/cours-c

ترجمة بتصرف: محمد عبد الرحمان


 

أضف تعليقا


إصنعها يريد أن يتأكد أنك لست روبوتا، لذلك أحسب ما يلي:

كود امني
تحديث