برنامج الألة الحاسبة في ++C

برنامج الألة الحاسبة

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

والوضع العملي أن يكون تشغيل الحاسبة عن طريق الفأرة , ولكن لغة السي ++ لا تتضمن دوال مكتبية لتشغيل الفأرة , وكتابة برنامج لتشغيل الفأرة سوف يجعل العمل معقدا بقدر كبير .

calculator

 

استخدام الحاسبة

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

ملفات البرنامج

يتكون البرنامج من ثلاثة ملفات , المفروض أن الملفين الأولين سوف نشتريها جاهزين , وأن الملف الثالث هو ما سنكتبه بأنفسنا . الملف الأول هو الملف التصديري calc.h ويحتوي علي إعلانات الفئات , والملف الثاني هو calc.cpp ويحتوي علي الدوال المنتمية . هذا الملف يوجد علي الصورة المصدرية , ولكنك لو اشتريته جاهزا فسوف يكون غالبا علي صورة هدف , بامتداد .obj أو lip والملف الثالث calc_app,cpp وهو الملف التطبيقي الذي يفترض أننا نكتبه بأنفسنا . ويجب عليك التأكد من أن هذه الملفات توجد جميعا تحت نفس الدليل وفي حالة بورلاند , تأكد من أنه تحت الدوس وليس EasyWin حيث نستخدم الدالة delay ودوال أخري مندوال الدوس , ولن تعمل هذه الدوال في بيئة الويندوز . أنشئ مشروعا باسم calc)app.prj وأضف إليه الملفات الثلاثة . بعد ذلك ترجم المشروع وشغله كما بينا في هذه الفصل .

وإليك البرامج الثلاثة :

calc.h


 


// calc.h


// header file for calc.cpp


// UCS Laboratories


 


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


#include <graphics.h>    // for graphics functions


#include <conio.h>       // for getche()


#include <dos.h>         // for delay(), sound(), etc.


#include <strstrea.h>    // for ostrstream class


#include <iomanip.h>     // for setiosflags()


#include <math.h>        // for atof()


 


class Window;                         // (needed for SCREEN)


Window* const SCREEN = (Window*)0;    // ultimate owner


 


enum buttonstatus { unpushed, pushed };


enum boolean { false, true };


 


class Window                 // parent class


   {


   protected:


      Window* ptrOwner;        // address of owner of this window


      int left, top, right, bot;  // outside edges of rectangle


      int delta;               // distance between borders


      int deltacolor;          // color between borders


      int centercolor;         // color within inside border


   


   public:                     // constructor: initialize window


      Window(Window* ptro, int l, int t, int r, int b,


         int dc, int cc);


      void Display(void);      // display the window


   };


 


class Border : public Window   // border


   {


   public:


      Border(Window* ptro, int l, int t, int r, int b,


         int dc=BLUE, int cc=DARKGRAY)


        : Window(ptro, l, t, r, b, dc, cc)


    { }


   };


 


class Button : public Window   // push button


   {


   private:


      char text[20];               // characters on button


      buttonstatus bstatus;        // pushed or unpushed


   public:


      Button(Window* ptro, int l, int t, int r, int b,


         int cc=BLACK, char* tx="")


        : Window(ptro, l, t, r, b, BLACK, cc)


     { strcpy(text, tx);  bstatus = unpushed; }


      void Click(void);            // click the button


      void Display(void);          // display the button


   };


 


class Output : public Window   // output window


   {


   public:


      Output(Window* ptro, int l, int t, int r, int b,


         int dc=BLUE, int cc=WHITE)


        : Window(ptro, l, t, r, b, dc, cc)


     { }


      void Text(char *);    // display text sent as string


      void Number(double);  // display number


   };






Calc.cpp




calc.cpp


 


// calc.cpp


// member functions for calc


// UCS Laboratories


 


#include "calc.h"            // calc header file


 


                             // constructor: initialize window


Window::Window(Window* ptro, int l, int t, int r, int b,


       int dc, int cc)


   {                              // set private data


   ptrOwner=ptro; left=l; top=t; right=r; bot=b;


   deltacolor=dc; centercolor=cc;


                                  // calculate delta


   delta = ((right-left)+(bot-top))/150 + 3;


   if( ptrOwner != SCREEN )       // if there is an owner,


      {                           // our coordinates


      left  += ptrOwner->left;    // start at owner's


      right += ptrOwner->left;    // upper left corner


      top   += ptrOwner->top;


      bot   += ptrOwner->top;


      }


   }


 


void Window::Display(void)   // display the window


   {


   setcolor(WHITE);


 


   int p[10];                      // draw outer rectangle and


   p[0]=left;  p[1]=top;           // fill it


   p[2]=right; p[3]=top;           // use fillpoly to clear


   p[4]=right; p[5]=bot;           // existing pattern


   p[6]=left;  p[7]=bot;           // (floodfill won't do this)


   p[8]=left;  p[9]=top;


   setfillstyle(SOLID_FILL, deltacolor);


   fillpoly(5, p);


                   // draw inner rectangle


   rectangle(left+delta+1, top+delta,


         right-delta-1, bot-delta);


                   // and fill it


   setfillstyle(SOLID_FILL, centercolor);


   floodfill(left+(right-left)/2, top+delta+1, WHITE );


   }


 


void Button::Click(void)       // click the button


   {


   bstatus = pushed;                    // push it


   Button::Display();                   // display it


   sound(500); delay(10); nosound();    // in beep


   delay(250);                          // wait 1/4 sec


   bstatus = unpushed;                  // unpush it


   Button::Display();                   // display it


   sound(400); delay(10); nosound();    // out beep


   }


 


void Button::Display(void)     // display the button


   {


   Window::Display();                   // display basic button


                    // charcter on button


   moveto(left+(right-left)/2+1, top+(bot-top)/2);


   settextjustify(CENTER_TEXT, CENTER_TEXT);


   settextstyle(SANS_SERIF_FONT, HORIZ_DIR, USER_CHAR_SIZE );


   setusercharsize(5, 8, 2, 3);         // 5/8 width, 2/3 height


   setcolor(WHITE);                     // always white on black


   outtext(text);                       // write the character


 


   moveto(left, top);                   // upper left diagonal


   lineto(left+delta, top+delta);


   moveto(right, top);                  // upper right diagonal


   lineto(right-delta, top+delta);


   moveto(left, bot);                   // lower left diagonal


   lineto(left+delta, bot-delta);


   moveto(right, bot);                  // lower right diagonal


   lineto(right-delta, bot-delta);


 


   setfillstyle(SOLID_FILL, LIGHTGRAY); // illuminated edge color


   if(bstatus==unpushed)                


      {                                 // shade top and left


      floodfill(left+(right-left)/2, top+1, WHITE);  // top


      floodfill(left+1, top+(bot-top)/2, WHITE);     // left


      }


   else  // pushed                      


      {                                 // shade bot and right


      floodfill(left+(right-left)/2, bot-1, WHITE);  // bot


      floodfill(right-1, top+(bot-top)/2, WHITE);    // right


      }


   }


 


void Output::Text(char *ptrstring)  // display text


   {


   Display();                       // clear output window


   moveto(right-delta, top+(bot-top)/2);


   settextjustify(RIGHT_TEXT, CENTER_TEXT);


   settextstyle(SANS_SERIF_FONT, HORIZ_DIR, USER_CHAR_SIZE );


   setusercharsize(5, 8, 1, 1);     // 5/8 width, 1/1 height


   setcolor(BLACK);                 // black text


   outtext(ptrstring);              // insert the text


   }


 


void Output::Number(double d)       // display number


   {


   char buffer[80];                 // set up text buffer


   ostrstream omem(buffer, 80);     // memory stream object


   omem << setiosflags(ios::fixed)  // format 123.00


        << setprecision(2)          // two digits to right of pt


        << setw(16)                 // field width 16


        << d;


   Output::Text(buffer);            // display string


   }






Calc_app.cpp




calc_app.cpp


 


// calc_app.cpp


// four-function calculator with 15 digits


// uses calc.cpp


// UCS Laboratories


 


#include "calc.h"                 // header file for calc.app


 


void main()


   {


   int driver, mode;


   driver = EGA;                  // set graphics driver


   mode = EGAHI;                  // and graphics mode


   initgraph(&driver, &mode, "\\tc\\bgi");


 


   // this section defines the various objects


                      // border and output windows


   Border border1(SCREEN,  240, 30, 480, 330);


   Output output1(&border1, 20, 20, 220,  60, BLUE, WHITE);


                      // buttons


   Button button0(&border1, 30, 230, 65, 265, BLACK, "0");


   Button button1(&border1, 30, 180, 65, 215, BLACK, "1");


   Button button2(&border1, 78, 180, 113, 215, BLACK, "2");


   Button button3(&border1, 127, 180, 162, 215, BLACK, "3");


   Button button4(&border1, 30, 130, 65, 165, BLACK, "4");


   Button button5(&border1, 78, 130, 113, 165, BLACK, "5");


   Button button6(&border1, 127, 130, 162, 165, BLACK, "6");


   Button button7(&border1, 30, 80, 65, 115, BLACK, "7");


   Button button8(&border1, 78, 80, 113, 115, BLACK, "8");


   Button button9(&border1, 127, 80, 162, 115, BLACK, "9");


   Button buttonDiv(&border1, 175, 80, 210, 115, BLUE, "/");


   Button buttonMul(&border1, 175, 130, 210, 165, BLUE, "*");


   Button buttonSub(&border1, 175, 180, 210, 215, BLUE, "-");


   Button buttonDot(&border1, 78, 230, 113, 265, BLACK, ".");


   Button buttonClr(&border1, 127, 230, 162, 265, RED, "clr");


   Button buttonAdd(&border1, 175, 230, 210, 265, BLUE, "+=");


 


   // this section displays the various objects


 


   border1.Display();             // border and output windows


   output1.Display();


                      // buttons


   button7.Display();    button8.Display();  button9.Display();


   button4.Display();    button5.Display();  button6.Display();


   button1.Display();    button2.Display();  button3.Display();


   buttonSub.Display();  buttonDiv.Display();


   buttonMul.Display();  buttonAdd.Display();


   button0.Display();  buttonDot.Display();  buttonClr.Display();


   output1.Number(0.0);           // display 0.0


 


   // this section handles the keyboard and activates display


 


   const char ESC=27;             // Escape key


   char dstring[80];              // string for display


   char tempbuf[80];              // temp string holder


   int numchars = 0;              // number of chars in dstring


   char ch;                       // character read from keyboard


   char oper;                     // operator: /, *, -, +


   boolean isfirst = true;        // first (not second) operator


   boolean chain = false;         // chaining to next number


   double number1, number2;       // first and second numbers


   double answer;                 // answer to arithmetic


 


   while( (ch=getch()) != ESC )   // quit program on Escape key


      {                           // is it a digit (or dot)


      if( (ch>='0' && ch<='9') || ch=='.' )


     {


     switch(ch)


        {                     // click the button


        case '0': button0.Click(); break;


        case '1': button1.Click(); break;


        case '2': button2.Click(); break;


        case '3': button3.Click(); break;


        case '4': button4.Click(); break;


        case '5': button5.Click(); break;


        case '6': button6.Click(); break;


        case '7': button7.Click(); break;


        case '8': button8.Click(); break;


        case '9': button9.Click(); break;


        case '.': buttonDot.Click(); break;


        }  // end switch


     dstring[numchars++] = ch;  // put char in buffer


     dstring[numchars] = '\0';  // put 0 at end of string


     if( atof( dstring) > 99999999999.99 ||  // if too big


          numchars > 11 )                // or too long


        {                            // beep


        delay(100); sound(200); delay(300); nosound();


        dstring[--numchars] = '\0';  // delete last char


        }


     output1.Text(dstring);     // send it to output window


     }  // end if (it's a digit)


 


                 // if it's a valid math operator


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


           ch=='=')


     {


     strcpy(tempbuf,dstring);   // save the input string


     numchars = 0;              // empty input buffer


     dstring[numchars] = '\0';


     output1.Text(dstring);     // display empty buffer


     if( isfirst )              // if first operator


        {                       // 1st time, n1 = answer


        number1 = (chain) ? answer : atof(tempbuf);


        isfirst = false;        // next op will be second


        switch(ch)              // store the operator


           {


           case '/':  buttonDiv.Click();  oper='/';  break;


           case '*':  buttonMul.Click();  oper='*';  break;


           case '-':  buttonSub.Click();  oper='-';  break;


           case '+':


           case '=':  buttonAdd.Click();  oper='+';  break;


           }  // end switch (ch)


        }  // end if (first number)


     else                         // second operator


        {                         //    (should be '=')


        buttonAdd.Click();        // assume it was '='


        number2 = atof(tempbuf);  // get second number


        switch(oper)              // do the action


           {


           case '/':  answer = number1 / number2;  break;


           case '*':  answer = number1 * number2;  break;


           case '-':  answer = number1 - number2;  break;


           case '+':  answer = number1 + number2;  break;


           }


        if(answer > 99999999999.99)  // if answer too big


           output1.Text("Overflow");   // to display


        else                       // otherwise


           {                       // answer ok


           output1.Number(answer); // display the answer


           number1 = answer;       


           }                       // set up to chain to


        isfirst = true;            // another 2nd operator


        chain = true;              // and another 2nd number


        }  // end else (second operator)


     }  // end else if (operator)


 


      else if( ch=='C' || ch=='c' )    // if it's Clear


     {


     buttonClr.Click();            // click the button


     isfirst = true;               // next number is first


     chain = false;                // not chaining


     numchars = 0;                 // empty input buffer


     dstring[numchars] = '\0';


     output1.Number(0.0);          // display 0.0


     }  // end else if (clear)


      else                             // it's a bad character


     {                             // beep


     delay(100); sound(200); delay(300); nosound();


     }  // end else (bad character)


      }  // end while


   closegraph();              // shut down graphics system


   getche();


   }  // end main()







الإعلان عن الفئات



يحتوي الملف calc.h علي الإعلان عن الفئات الخاصة بالبرنامج , ويجب أن يحتوي أي برنامج يستغل هذه الفئات هذا الملف بالأمر #include .



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



الفئة الأصلية Windows



تشتمل الفئة الأصلية Windows علي مواصفات الكائنات المذكورة في الفقرة السابقة . وتضم حدود المستطيل الخارجي , والمسافة الفاصلة بينه وبين المستطيل الداخلي , محددة بالبكسلات , وتسمي delta . كما تضم الفئة متغيرين يعبران عن لوني المسافة الفاصلة والمستطيل الداخلي .



المؤشر ptrOwner



في الأشكال الرسومية علي غرار شكل الحاسبة التي نتناولها في مثالنا هذا , تجد الشكل مكونا من عناصر ذات علاقات موضعية فيما بينها , فكل ضاغط يجاوره ضاغط أخر , ومن ثم فسوف يكون الأمر أيسر بمراحل لو أننا حددنا موضعا أساسيا , ثم حددنا بقية المواضع بالنسبة له , بدلا أن تحدد قيمتها المطلقة علي الشاشة . ومن المتعارف عليه أن يطلق علي الشكل المرجعي ] أي الذي به الموضع المنسوب إليه مواضع الأشكال الأخري [ لفظ ''Owner'' , ( كان بإمكاننا استخدام اللفظ ''Parent'' لولا خشية أن يتداخل هذا مع مفاهيم التوارث ) . وحين ننشئ نافذة ما , فإننا نرسل لها مؤشرا بالشكل الذي يعتبر مرجعا لها , لكي تحسب أوضاع عناصرها بالنسبة له , يعتبر المؤشر ptrOwner عن المرجع الذي تنسب إليه الأبعاد . ويمكن أن تكون نافذة مرجعا لنافذة , ثم تكون الأخيرة مرجعا لنافذة ثالثة , وهكذا



تجد أن الشكل المرجعي المبدئي هو الشاشة ذاتها , ونقطة الأصل فيها (0.0) , وإليها تنسب أوضاع النافذة الأولي , وبذلك تكون نقطة الأصل في هذه النافذة هي النقطة (10,10) . وحين تنسب النافذة الثانية للأولي , فإنها تنسب للنقطة (10,10) . ولهذا الأسلوب فائدة جمة في بيان الأوضاع النسبية للأشكال , مما يجعل البرمجة أيسر تصورا .



وللفئة الأساسية دالتان مبينتان في الملف calc.cpp بادئة ودالة إظهار . زفي البادئة تحسب المواضع الخاصة بالنافذة طبقا للشكل المرجعي لها , وتحسب المسافة delta بحيث لا تقل عن 3 بكسل (الثابت 150 قد تحدد عن بطريقة تجريبية ) . هذه المسافة تتناسب مع حجم النافذة 



وبالنسبة لدالة الإظهار , فقد روعي فيها أن يغطي المستطيل الخارجي ما تحته , ومن ثم فقد أختيرت الدالة fillpoly() , أما المستطيل فقد اختيرت له الدالة floodfill() وهي أيسر في التطبيق . ويشتق من الفئة Windows كافة الكائنات الأخري ؛ الإطار الخارجي , والضواغط , وشاشة الجهاز .



الفئة Border



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



الفئة Button



ترسم بهذه الفئة الضواغط , وتحتوي علي بيانين إضافيين , وضع الضاغط (مضغوط عليه أم لا ) , والبيان الخاص به (رقم , معامل , .... ) كما تحتوي علي ثلاثة دوال إضافية Click(), Display() والبادئة Button() وهي مماثلة لبادئة الفئة Border . الدالة Click() تظهر عملية الضغط علي المفتاح , معطية التأثير الصوتي والبصري لذلك . والمدة المقدرة للحفاظ علي الضاغط في وضع الانضغاط هي ربع ثنائية , يعود بعدها الضاغط لوضعه الأصلي .



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



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



الفئة Output



تختص هذه الفئة لإظهار نافذة شاشة الجهاز , حيث تظهر القيم المتعامل معها والنتيجة النهائية . وتضم الفئة دالتين خاصتين بها الأولي لإظهار البيانات النصية , والثانية لإظهار البيانات الرقمية . هذه الدالة الأخيرة تستخدم كائن التدفق ostrstream الذي يتعامل مع الذاكرة كما لو كانت ذاكرة مرحلية , ويضع فيها البيان الرقمي باعتباره نصا وليس رقما , ثم يستدعي الأولي لإظهاره (راجع المقالات السابقة , المثال ostrser.cpp) .



الملف calc_app.cpp



يضم هذا الملف البرنامج التطبيق المحتوي علي الدالة الأصلية main() , ويستخدم الملفين السابقين في تكوين الحاسبة , كما يحتوي أوامر التعامل مع لوحة المفاتيح للقيام بالعمليات المختلفة , ويكون ذلك عن طريق دوارة while كبيرة نقرا كل مفتاح عن طريق الدالة gatch() وتحتوي علي سلسلة من الشروط if…else if وتنتهي بالضرب علي مفتاح الخروج Esc .



إدخال الأرقام



إذا كان المحرف الداخل رقما أو كان النقطة العشرية , قامت الدالة click() الخاصة بالضاغط المقصود بإعطاء تأثير الضغط , ثم خزن الرقم المدخل في ذاكرة مرحلية تسمي dstring , ثم أضيف إليه المحرف الصفري تعبيرا عن نهاية العبارة النصية, وأظهر الرقم علي الشاشة باستخدام الدالة Text الخاصة بالفئة Output . وفي حالة كون العدد أكبر من 11 أو كانت الذاكرة ممتلئة , صدر صوت كإنذار بالخطأ , ثم محي الحرف من الذاكرة بتسجيل المحرف الصفري عليه .



إدخال معامل حسابي



إذا تم الضرب علي مفتاح خاص بمعامل حسابي , أفرغت الذاكرة dstring بعد تخزين محتوياتها مؤقتا وأزيل الرقم من علي الشاشة . ويعتمد ما يحدث بعد ذلك علي كون المعامل المدخل أول معامل , أم لا , وتقوم الراية isfirst بمراقبة هذه الحالة , فإذا كانت متحققة , نعلم أن المعامل يجب أن يكون أحد المعاملات الأربعة : + , - , * , عندئذ تخزن الرقم في المتغير number1 , وتظهر الضغط علي المفتاح الخاص بالعملية , ثم نخزن المعامل الي حين (سوف نتجاهل احتمال العمليات المتتابعة)



المعامل الثاني يجب أن يكون الخاص بالضاغط "=+" , لكي تظهر النتيجة (سوف يعمل هذا ضاغط هذا المعامل مهما كان المفتاح المضروب ) , عندئذ يخزن الرقم في المتغير number2 وتجري العملية التي خزن معاملها من قبل , وتخزن النتيجة في المتغير answer أما إذا كانت أكبر من اللازم ظهرت العبارة overflow .



ضاغط المحو



عند الضرب علي ضاغط المحو Clr تخلي الذاكرة استعدادا للعملية التالية .



الضرب علي مفتاح غير صحيح



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



تتابع العمليات



حين يريد المستخدم إجراء عملية ثانية , تؤخذ النتيجة المخزنة في المتغير answer لتخزن في المتغير number1 , وتستمر العمليات كما لو كان الرقم مدخلا من المستخدم .



ومن المهم ملاحظة أن هذا البرنامج , كشأن كافة البرامج الرسومية , لا يعمل علي استقلال , بل لابد من تواجد بيئة لغة السي ++ , لاحتياجه للدالة initgraph() المكتبية الخاصة بهذه اللغة . وسوف نقدم في الملحق ج كيفية تحويل البرنامج لبرنامج قائم بذاته



البرمجة باستخدام الويندوز .



في البرمجة باستخدام بيئة الويندوز (وكذلك Presentation Manager في نظام تشغيل (OS/2 ) , تجد كائنات من التي تستخدم ف ي هذه البيئة , مثل الضواغط جاهزة لاستخدامك و كما يخبرك نظام التشغيل أيضا عندما ينقر بالفأرة علي مفتاح ما وييسر ذلك من البرمجة إلي قدر كبير .

التسميات: