برنامج الألة الحاسبة في ++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 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 ) , تجد كائنات من التي تستخدم ف ي هذه البيئة , مثل الضواغط جاهزة لاستخدامك و كما يخبرك نظام التشغيل أيضا عندما ينقر بالفأرة علي مفتاح ما وييسر ذلك من البرمجة إلي قدر كبير .
تعليقات
إرسال تعليق