برنامج الألة الحاسبة في ++C
برنامج الألة الحاسبة
بعد أن عرفت كيف ننشئ برنامجا ذا ملفات متعددة , نقدم لك برنامجا طموحا مبنيا علي هذا الأسلوب , يخرج لك حاسبة إلكترونية تقوم بالعمليات الحسابية الأربع .
والوضع العملي أن يكون تشغيل الحاسبة عن طريق الفأرة , ولكن لغة السي ++ لا تتضمن دوال مكتبية لتشغيل الفأرة , وكتابة برنامج لتشغيل الفأرة سوف يجعل العمل معقدا بقدر كبير .
استخدام الحاسبة
نقوم في برنامجنا بإظهار حالة الضغط علي ضواغط الحاسبة , كما تجدها في البرامج التجارية , وسوف نحقق ذلك بحيلة تعتمد علي تغيير ألوان حواف الضواغط . وسوف نستخدم ألوانا مختلفة لتحسين تصميم البرنامج , فضواغط الأرقام باللون الأسود , وضواغط المعاملات باللون الأزرق . وسوف تكون علامة = و + علي نفس الضاغط , كما سيميز الضاغط 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 windowint 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 windowWindow::Window(Window* ptro, int l, int t, int r, int b,
int dc, int cc)
{ // set private dataptrOwner=ptro; left=l; top=t; right=r; bot=b;
deltacolor=dc; centercolor=cc;
// calculate deltadelta = ((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 cornertop += 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 rectanglerectangle(left+delta+1, top+delta,
right-delta-1, bot-delta);
// and fill itsetfillstyle(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 buttonmoveto(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 diagonallineto(left+delta, top+delta);
moveto(right, top); // upper right diagonallineto(right-delta, top+delta);
moveto(left, bot); // lower left diagonallineto(left+delta, bot-delta);
moveto(right, bot); // lower right diagonallineto(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 windowmoveto(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 objectomem << 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 windowsBorder 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 windowsoutput1.Display();
// buttonsbutton7.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 displayconst 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 buttoncase '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 bufferdstring[numchars] = '\0'; // put 0 at end of string
if( atof( dstring) > 99999999999.99 || // if too big
numchars > 11 ) // or too long { // beepdelay(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 operatorelse 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 bufferif( isfirst ) // if first operator
{ // 1st time, n1 = answernumber1 = (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 numberswitch(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 answernumber1 = answer;
} // set up to chain toisfirst = 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 buttonisfirst = 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
{ // beepdelay(100); sound(200); delay(300); nosound();
} // end else (bad character) } // end while closegraph(); // shut down graphics systemgetche();
} // 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 ) , تجد كائنات من التي تستخدم ف ي هذه البيئة , مثل الضواغط جاهزة لاستخدامك و كما يخبرك نظام التشغيل أيضا عندما ينقر بالفأرة علي مفتاح ما وييسر ذلك من البرمجة إلي قدر كبير .
تعليقات
إرسال تعليق