مؤشرات لمؤشرات في ++C

مؤشرات لمؤشرات

المثال التالي ينشئ مصفوفة لمؤشرات لكائنات ويبين كيف ترتب هذه المؤشرات بناء علي بيانات تلك الكائنات , ويتضمن ذلك فكرة مؤشرات تشير لمؤشرات وقد يظهر أيضا لماذا يفقد الناس النوم حين يفكروا في المؤشرات .

وتقوم فكرة البرنامج علي إنشاء مصفوفة مؤشرات لكائنات من فئة class علي غرار ما فعلناه في البرنامج ptrobjs.cpp , ولكنا سوف نمضي لأبعد من ذلك بأن نطعم الدالتين order() و bsort() بأفكار من البرنامج ptrsort.cpp حتي يمكننا ترتيب الأسماء . وإليك البرنامج :

persort.cpp


 


// persort.cpp


// sorts person objects using array of pointers


// UCS Laboratories


 


#include <iostream.h>


#include <string.h>               // for strcmp()


#include <conio.h>


 


class person                      // class of persons


   {


   protected:


      char name[40];              // person's name


   public:


      void setName()              // set the name


     { cout << "Enter name: "; cin >> name; }


      void printName()            // display the name


     { cout << endl << name; }


      char* getName()             // return the name


     { return name; }


   };


 


void main()


   {


   void bsort(person**, int);     // prototype


   person* persPtr[100];          // array of pointers to persons


   int n = 0;                     // number of persons in array


   char choice;                   // input char


   


   do                             // put persons in array


      {


      persPtr[n] = new person;    // make new object


      persPtr[n]->setName();      // set person's name


      n++;                        // count new person


      cout << "Enter another (y/n)? "; // enter another


      cin >> choice;              //    person?


      }


   while( choice=='y' );          // quit on 'n'


 


   cout << "\nUnsorted list:";


   for(int j=0; j<n; j++)         // print unsorted list


      { persPtr[j]->printName(); }     


 


   bsort(persPtr, n);             // sort pointers


 


   cout << "\nSorted list:";


   for(j=0; j<n; j++)             // print sorted list


      { persPtr[j]->printName(); }     


   getche(0;


   }  // end main()


 


void bsort(person** pp, int n)    // sort pointers to persons


   {                              


   void order(person**, person**);  // prototype


   int j, k;                      // indexes to array


 


   for(j=0; j<n-1; j++)          // outer loop


      for(k=j+1; k<n; k++)       // inner loop starts at outer


     order(pp+j, pp+k);      // order the pointer contents


   }


 


void order(person** pp1, person** pp2)  // orders two pointers


   {                                    // if 1st larger than 2nd,


   if( strcmp((*pp1)->getName(), (*pp2)->getName()) > 0)


      {


      person* tempptr = *pp1;           // swap the pointers


      *pp1 = *pp2;


      *pp2 = tempptr;


      }


   }




ترتيب المؤشرات



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



Object-Oriented Programming in C   _Page_0503_Image_0001



شكل ترتيب مصفوفة مؤشرات



نوع البيانات person**



تضمنت كلتا الدالتين bsprt(), order(0 نوعا من البيانات هو person** ما معني النجمتين هنا ؟ هذه المعاملات تستخدم لإمرار مؤشرات أو في حالة الدالة order(0 عناوين عناصر المصفوفة , ولو كانت عناصر المصفوفة من النوع person لكان عنوانها من النوع person* أم وأن عناصر المصفوفة المصفوفة هي ذاتها مؤشرات للنوع person فإنها تكون من النوع person* وتكون عناوينها من النوع person** (أنظر الشكل ) ويستبين الأمر بمقارنة الدالتين bsort(), order() في البرنامجين .



وحيث إن persPtr هي مصفوفة تحوي مؤشرات , فإن التعبير



persPtr[j]->printName()



يثير الدالة printName() في الكائنات التي تشير إليه المؤشرات .



Object-Oriented Programming in C   _Page_0504_Image_0001



مقارنة العبارات النصية



استخدمنا الدالة المكتبية strcmp() لمقارنة عبارتين نصيتين وهذه الدالة تقارن العبارات بحسب تسلسلها الأبجدي , وتعيد أحد القيم التالي 0<: إذا كانت s1 تالية لــ s2 .



وقد وصلنا للعبارات بالأمر :



(*pp1)->getName();



حيث إن المعامل pp1 هو مؤشر لمؤشر , فالمؤشر السهمي يعطينا المحتوي لمستوي واحد ومن ثم كان الاحتياج للنجمة لننتقل للمستوي الثاني .



ويمكن تصور مؤشر لمؤشر مؤشر وهكذا بحسب ما تقتضيه الظروف من تعقيد .



برنامج التحليل اللغوي



نقدم في ختام موضوع المؤشرات برنامجا من نوع برامج التحليل اللغوي parsing , وهي من البرامج الشائعة التي يمكن أن تبين إن كانت عبارة ما تنتمي للغة (برمجية) أم لا وسوف يكون مثالنا منصبا علي تحليل التعابير الرياضية مثل 1-3*2+3/6.



ويتمثل عمل البرنامج في أن يدخل المستخدم تعبيرا ما , ويقوم البرنامج بفحص المحارف الداخلة واحدا وراء الأخر , ليقرر معناه في سياق التعابير الرياضية ويخرج الناتج ] لاحظ أننا نعامل الأعداد هنا كمحارف وليس كأعداد[ مثلا 7 . وللتسهيل , سوف نقصر برنامجنا علي أعداد من رقم واحد , وبدون استخدام الأقواس .



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



ولدينا بعد ذلك فئة من نوع express (اختصار لــــــــــ expression ) تمثل تعبيرا رياضيا كاملا ودوال هذه الفئة تسمح للمستخدم أن يدخل تعبيرا وتحلله وتعيد النتيجة .



تحليل التعابير الرياضية



إليك طريقة تحليل التعابير الرياضية : نبدأ من اليسار ونتفحص كل محرف بالترتيب ويكون إما رقما (بين 0 و 9) أو معاملا رياضيا (+, - , * أو /) .



إذا كان المحرف المدخل رقما يدخل في المكدس , كما ندخل إليه أول معامل رياضي نقابله . الفكرة هي كيف نعمل المحارف التالية . لاحظ أننا لا يمكننا أن ننفذ المعامل الجاري , حيث لم ننته بعد من قراءة الرقم التالي . إن مقابلة المعامل تعني فقط إنه يمكننا أن ننفذ المعامل السابق , المحفوظ في المكدس . معني ذلك لو كان لدينا في المكدس 2+3 فعلينا أن ننتظر المعامل التالي قبل أن ننفذ هذا المعامل .



علي ذلك فكلما رأينا أن المحرف المدخل هو معامل (عدا الأول منها) , نخرج العدد السابق (3 في المثال المعطي) , والمعامل السابق عليه (+) من المكدس , و نضعهما في المتغيرات lastval و laststop . واخيرا نخرج الرقم الأخير (2) ونجري العملية نخرج الناتج (5) هل يمكننا دائما تنفيذ المعامل السابق ؟ كلا لأن عمليات الضرب والقسمة لها أولوية في التنفيذ عن عمليات الجمع والطرح ففي تعبير مثل 2/4+3 لا يمكننا أن ننفذ عملية الجمع قبل أن ننتهي من عملية القسمة . ولذا فعندما نقابل معامل القسمة نعيد للمكدس رقم 2 وعلامة + حتي ننهي القسمة .



ومن جهة أخري لو كان المعامل الجاري هو + أو - , نعلم أننا دائما يمكننا تنفيذ المعامل السابق بمعني أننا حين نري تعبيرا مثل 6+5-4 معلم أنه من الممكن تنفذ عملية الطرح وحين نقابل تعبيرا مثل 3-2/6 نعلم أنه بإمكاننا تنفيذ عملية القسمة ويبين الجدول أربعة إحتمالات .



جدول المعاملات والتحليل اللغوي























































المعامل السابق



المعامل الحالي



مثال



التصرف



+ أو -



* أو /



3+4/



ضع المعامل والرقم السابقين في المكدس (+,4)



* أو /



* أو /



9/3*



نفذ العملية , أدخل الناتج (3)



+ أو -



+ أو -



6+3-



نفذ العملية , أدخل الناتج (9)



* أو /



+ أو -



8/2-



نفذ العملية , أدخل الناتج (4)





وتقوم الدالة parse() بهذه العملية مقتفية أثر المحارف المدخلة وتنفذ من العمليات ما يمكنها ومع ذلك فأمامنا مهمة أخري سوف يتضمن المكدس إما رقما أو تسلسلا من رقم – معامل – رقم . وبتتبع المكدس هبوطا , يمكننا تنفيذ هذه التعابير , واخيرا , يتبقي رقم واحد هو قيمة التعبير الأصلي . وتقوم الدالة solve() بهذه المهمة , تأخذ طريقها في المكدس , وتقوم الدالة solve() بسحبها منه .



 





parse.cpp


 


// parse.cpp


// evaluates arithmetic expressions composed of 1-digit numbers


// UCS Laboratories


 


#include <iostream.h>


#include <string.h>                  // for strlen(), etc


#include <process.h>                 // for exit()


#include <conio.h>


 


const int LEN = 80;    // length of expressions, in characters


const int MAX = 40;    // size of stack


 


class Stack


   {


   private:


      char st[MAX];                  // stack: array of chars


      int top;                       // number of top of stack


   public:


      Stack()                        // constructor


     { top = 0; }


      void push(char var)            // put char on stack


     { st[++top] = var; }


      char pop()                     // take char off stack


     { return st[top--]; }


      int gettop()                   // get top of stack


         { return top; }


   };


 


class express                        // expression class


   {


   private:


      Stack s;                       // stack for analysis


      char* pStr;                    // pointer to input string


      int len;                       // length of input string


   public:


      express(char* ptr)             // constructor


         {


         pStr = ptr;                 // set pointer to string


         len = strlen(pStr);         // set length


         }


      void parse();                  // parse the input string


      int solve();                   // evaluate the stack


   };


 


void express::parse()                // add items to stack


   {


   char ch;                          // char from input string


   char lastval;                     // last value


   char lastop;                      // last operator


 


   for(int j=0; j<len; j++)          // for each input character


      {


      ch = pStr[j];                  // get next character


 


      if(ch>='0' && ch<='9')         // if it's a digit,


         s.push(ch-'0');             // save numerical value


                                     // if it's operator


      else if(ch=='+' || ch=='-' || ch=='*' || ch=='/')


         {


         if(s.gettop()==1)           // if it's first operator


            s.push(ch);              // put on stack


         else                        // not first operator


            {


            lastval = s.pop();       // get previous digit


            lastop = s.pop();        // get previous operator


            // if this is * or / AND last operator was + or -


            if( (ch=='*' || ch=='/') &&


                (lastop=='+' || lastop=='-') )


               {


               s.push(lastop);       // restore last two pops


               s.push(lastval);


               }


            else                     // in all other cases


               {


               switch(lastop)        // do last operation


                  {                  // push result on stack


                  case '+': s.push(s.pop() + lastval); break;


                  case '-': s.push(s.pop() - lastval); break;


                  case '*': s.push(s.pop() * lastval); break;


                  case '/': s.push(s.pop() / lastval); break;


                  default:  cout << "\nUnknown oper"; exit(1);


                  }  // end switch


               }  // end else, in all other cases


            s.push(ch);              // put current op on stack


            }  // end else, not first operator


         }  // end else if, it's an operator


      else                           // not a known character


         { cout << "\nUnknown input character"; exit(1); }


      }  // end for


   }  // end parse()


 


int express::solve()                 // remove items from stack


   {


   char lastval;                     // previous value


 


   while(s.gettop() > 1)


      {


      lastval = s.pop();             // get previous value


      switch( s.pop() )              // get previous operator


         {                           // do operation, push answer


         case '+': s.push(s.pop() + lastval); break;


         case '-': s.push(s.pop() - lastval); break;


         case '*': s.push(s.pop() * lastval); break;


         case '/': s.push(s.pop() / lastval); break;


         default:  cout << "\nUnknown operator"; exit(1);


         }  // end switch


      }  // end while


   return int( s.pop() );            // last item on stack is ans


   }  // end solve()


 


void main()


   {


   char ans;                         // 'y' or 'n'


   char string[LEN];                 // input string from user


 


   do


      {


      cout << "\nEnter an arithmetic expression"


              "\nof the form 2+3*4/3-2."


              "\nNo number may have more than one digit."


              "\nDon't use any spaces or parentheses."


              "\nExpresssion: ";


      cin >> string;                        // input from user


      express* eptr = new express(string);  // make expression


      eptr->parse();                        // parse it


      cout << "\nThe numerical value is: " 


           << eptr->solve();                // solve it


      delete eptr;                          // delete expression


      cout << "\nDo another (Enter y or n)? ";


      cin >> ans;


      } while(ans == 'y');


   getche();


   }





إنه برنامج طويل بعض الشئ ولكنه يبين مدي فائدة المكدس , كما يبين استخدام المؤشرات بطرق متعددة ومدي فائدتها في التعامل مع العبارات النصية كمصفوفات محارف .



تصحيح برامج المؤشرات



يمكن أن تكون المؤشرات مصدرا للعديد من الأخطاء البرمجية خاصة حين يساء وضع قيم فيها فتشير إلي مواضع غير المطلوبة يعلم الله ما هي فقد تكون لبرامج أخري فتفسدها , أو حتي خاصة بنظام التشغيل فتؤثر علي عمل الجهاز .



ومن الأخطاء الشائعة أن يشير المؤشر للعنوان "0" , ويحدث ذلك حين يعرف المؤشر مثلا تعريفا خارجها حيث إن المتغيرات الخارجية تستهل بقيمة صفرية دائما ( وهو نفس الشئ بالنسبة لبيانات الفئات ) . وإليك برنامجا صغيرا يبين مثل هذا الموقف :



Int* intptr;



Void main()



{



*intptr = 37;



}



تجد في هذا البرنامج محاولة لوضع القيمة 37 في العنوان "0" . في هذه الحالة تظهر رسالة خطأ ''Null pointer assignment'' , وحينئذ تعرف أن خطأ ما قد في استهلال المؤشر .



وفي البرامج المعقدة لن يكون الأمر سهلا وأحد الوسائل لذلك مراقبة العنوان "0" خلال تتبع البرنامج خطوة خطوة فحين تتغير قيمة ما في هذا العنوان , تعرف أي عبارة مسئولة عن الخطأ ولكنك لا تستطيع مجرد وضع العبارة 0* في شاشة المراقبة بل عليك استخدام الصيغة التالية : *(char *)0, 4m والتي تحدد القيمة "0" قسريا بأنها pointe-to-char أما 4m فتعني إظهار أربعة بايتات من الذاكرة وتكون عادة بالقيمة "صفر" وأي تغير في قيمة بايت منها يكشف عن سبب المشكلة .






التسميات: