مشاركة: :: مشروع لتعليم أساسيات ++C :: موضوع الدروس
السلام عليكم
اولا وقبل كل شيء شكرا لك اخي على هذا الجهد
الان ممكن تشرح سؤال 4 .... اكتب برنامجاً يطلب من المستخدم إدخال n عدد صحيح و من ثم يكتب له قيمة n! ( العاملي )
وما هو هذا العاملي
2- اخي انت في بعض الاسئله وضعت لنا استخدام if and for فهل نستطيع ان نستخد واحده فقط اما if او for
مشاركة: :: مشروع لتعليم أساسيات ++C :: موضوع الدروس
اقتباس:
المشاركة الأصلية كتبت بواسطة IWANTYOURHELP
السلام عليكم
اولا وقبل كل شيء شكرا لك اخي على هذا الجهد
الان ممكن تشرح سؤال 4 .... اكتب برنامجاً يطلب من المستخدم إدخال n عدد صحيح و من ثم يكتب له قيمة n! ( العاملي )
وما هو هذا العاملي
2- اخي انت في بعض الاسئله وضعت لنا استخدام if and for فهل نستطيع ان نستخد واحده فقط اما if او for
::
::
ممنوع الرد في هذا الموضوع!!!!!!!!
سيتم ابلاغ المشرف
::
::
مشاركة: :: مشروع لتعليم أساسيات ++C :: موضوع الدروس
السلام عليكم ...
:: مقدمة ::
أخذنا في الدرس الماضي عبارات الشرط و حلقة التكرار for .. اليوم سنأخذ ما تبقى من تلك العبارات و هي كالتالي :
5 – العبارة ( while ) .
6 – العبارة ( do/while ) .
و لكن قبل ذلك سنأخذ بعض الملاحظات .. و سنحل التمارين التي أعطينكم إياها في الدرس الماضي .. و أيضاً سنأخذ النافذة Watch التابعة لنوافذ تصحيح الأخطاء Debug Window .. حيث أن هذه النافذة نستفيد منها في معرفة القيم التي تأخذها المتغيرات مهما كان نوعها .
---------- ---------- ---------- الدرس الثالث ---------- ---------- ----------
اليوم : السبت ... التاريخ : 12 / 2 / 2005
:: تابع تعليمات التحكم ::
قبل إكمال الموضوع .. هناك فكرتين يجب أن أذكرهما :
الأولى :
تتعلق بالمتغيرات المنطقية bool و كيفية استخدامها في عبارة الشرط if :
سنأخذ على الفور مثالاً يوضح الفكرة التي أريدها :
مثال /
بفرض أن x متغير منطقي من النوع bool
كود:
bool x = true;
if( x )
cout << "X = True " << endl;
لاحظوا كيف و ضعنا الشرط .. في الشرط نستطيع وضع المتغيرات من النوع bool بهذا الشكل و سيفهم المترجم أنك تريد إختبار في ما إذا كانت x تساوي true .
و لكن الشرح الصحيح هو أن المترجم سيرى أن قيمة المتغير x تساوي true و بالتالي نتيجة الإختبار أصبحت true و بالتالي سينفذ التعليمات التي بداخل الشرط .. و بالتالي هذه طريقة مباشرة بدلاً من كتابة :
و أيضاً هناك طريقة مشابهة لإختبار القيمة false .. انظر المثال :
كود:
if( !x )
cout << "X = False " << endl;
لاحظ كيف وضعنا معامل النفي ( ! ) قبل المتحول x .. و هي مشابهة تماماً للعبارة التالية :
الثانية :
تتعلق بالحلقة for .. بأننا نستطيع عدم كتابة أحد الأقسام الثلاثة المتعلقة بالقيمة الإبتدائية و النهائية و العملية على متغير التكرار .. و ممكن أيضاً أن نضع الحلقة فارغة هكذا :
و بالتالي ستصبح الحلقة مفتوحة و لن يتم الخروج منها .. و لكننا نستطيع ذلك عن طريق الأمر break كما تعلمنا .
----------------------------------------
:: حل التمارين ::
السؤال الأول :
اكتب برنامجاً يطلب من المستخدم إدخال 3 أعداد صحيحة و من ثم يكتب له العدد الأكبر .
الحل /
كود:
#include <iostream.h>
void main()
{
int a,b,c,max;
cout << "Enter a , b , c : ";
cin >> a >> b >> c;
max = a;
if( b > max )
max = b;
if( c > max )
max = c ;
cout << "Max = " << max << endl;
}
شرح الحل :
1 – قمنا بتعريف أربع متغيرات من النوع int : ثلاثة من أجل الأعداد و الرابع الذي سيحمل قيمة العدد الأكبر .
2 – قمنا بطلب تلك الأعداد من المستخدم .
3 – يجب أن نفترض أن عدد ما هو العدد الأكبر .. ثم نقوم بتعديل تلك القيمة إذا وجدنا أن الأعداد الأخرى هي الأكبر .
حيث افترضنا أن a هو العدد الأكبر .. ثم اختبرنا فيما إذا كان b هو الأكبر فإذا كان كذلك قمنا بتغيير max .. و بعد ذلك اختبرنا فيما إذا كان c هو الأكبر .. و بعد ذلك كتبنا الناتج .
ملاحظة / هناك حلول كثيرة لهذا السؤال .. و حلول الأعضاء صحيحة و لكن أرى أن هذا الحل بسيط و سهل الفهم ..
----------------------------------------
السؤال الثاني :
اكتب برنامجاً يطلب من المستخدم إدخال عددين صحيحين و من ثم يكتب له فيما إذا كان الثاني قاسماً للأول .
الحل /
كود:
#include <iostream.h>
void main()
{
int a,b;
cout << "Enter a , b : ";
cin >> a >> b;
if( a % b == 0 )
cout << "b is dividable " << endl;
else
cout << "b is not dividable " << endl;
}
شرح الحل :
1 – قمنا بتعريف متغيرين صحيحين من أجل العددين الذين سنختبرهما .
2 – طلبنا القيم من المستخدم .
3 – نريد أن نعرف متى يكون العدد الثاني قاسماً للأول ؟ يكون كذلك إذا كان باقي قسمة الأول على الثاني تساوي الصفر .. مثال : العدد 6 و 2 حيث أن 2 هو قاسم للعدد 6 لأن باقي قسمة 6 على 2 تساوي الصفر .. و هكذا .
لذلك قمنا بإختبار تلك العملية بواسطة عبارة الشرط if .. و من ثم كتبنا الإجابة .
----------------------------------------
السؤال الثالث :
اكتب برنامجاً يطلب من المستخدم إدخال عدد صحيح و من ثم يكتب له جميع قواسمه .
الحل /
كود:
#include <iostream.h>
void main()
{
int n;
cout << "Enter a Number : ";
cin >> n;
for( int i=1; i<=n; i++ )
if( n % i == 0 )
cout << i << endl;
}
شرح الحل :
1 – قمنا بتعريف المتغير n الذي سنوجد له قواسمه كلها .
2 – طلبنا القيمة من المستخدم .
3 – هذا السؤال هو نفس السؤال السابق .. و لكننا هنا نريد إيجاد جميع الأعداد التي تعد قاسماً للعدد n .. إذاً نحتاج إلى حلقة for بحيث نضع القيمة الإبتدائية واحد و النهائية هي العدد نفسه n .. حيث أن i ستأخذ في كل دورة أحد الأعداد التي بين 1 و n و بعد ذلك سنختبر نفس الإختبار في السؤال السابق .. و بذلك نكون قد اختبرنا جميع الأعداد التي نريدها .. بحيث نكتب قيمة i إذا كان باقي قسمة n على i تساوي الصفر .
ملاحظة مهمة / انظر إلى حلقة for كيف أنها لم تحتوي على الأقواس ( { } ) .. و ذلك لأنها تحتوي على تعليمة واحدة فقط و هي if .. و العبارة if أيضاً لم تحتوي على أقواس الكتل .. لأنها أيضاً احتوت على تعليمة واحدة فقط .
----------------------------------------
مشاركة: :: مشروع لتعليم أساسيات ++C :: موضوع الدروس
السؤال الرابع :
اكتب برنامجاً يطلب من المستخدم إدخال n عدد صحيح و من ثم يكتب له قيمة n! ( العاملي ) .
ملاحظة / قانون العاملي :
كود:
n! = n * (n-1) * (n-2) * (n-(n-1))
أمثلة على العاملي /
6! = 6 × 5 × 4 × 3 × 2 × 1 = 720
4! = 4 × 3 × 2 × 1 = 24
2! = 2 × 1 = 2
1! = 1
0! = 1
الحل :
كود:
#include <iostream.h>
void main()
{
int n,y;
cout << "Enter a Number : ";
cin >> n;
y = n;
for( int i = n-1; i>1; i-- )
y = y * i;
cout << "Y! = " << y << endl;
}
شرح الحل :
1 – عرفنا متغيرين صحيحين : n للعدد الذي الذي سيدخله المستخدم .. و y من أجل حساب العاملي .
2 – طلبنا قيمة العدد الذي سنحسب له العاملي .
3 – انسى التعليمة : y=n .. و انظر إلى الحلقة for و ذلك بفرض أن المستخدم أدخل العدد 5 :
في العاملي يوجد تسلسل في الأعداد لذلك احتجنا إلى الحلقة for .. بحيث وضعنا القيمة الإبتدائية هي العدد n-1 و ستكون في هذا الإفتراض 4 .. و النهائية 2 دائماً لأننا لا نحتاج إلى الواحد لأنه عنصر محايد و الضرب به لا يغير النتيجة .
و من ثم وضعنا القانون الذي سيحسب لنا القيمة .. لذلك وضعنا قبل الحلقة for التعليمة y=n .. حيث أن y ستأخذ أولاً قيمة العدد الذي سيدخله المستخدم و من ثم ستتغير قيمتها في داخل الحلقة .
بحيث ستأخذ y في أول دورة القيمة 5 × 4 .
في الدورة الثانية 20 × 3 .
في الدورة الثالثة 60 × 2 = 120 .. و هكذا .
و هذا ما يحقق : 5 × 4 × 3 × 2 × 1 = 120 .
ملاحظة هامة / إذا لم نضع للمتغير y قيمة ابتدائية .. فستكون نتيجة التعليمة y = y * i قيم لا معنى لها !!! لأن المترجم سيعطي قيمة عشوائية لا معنى لها لأي متغير تم تعريفه و لم يعطى له قيمة ابتدائية .. و بالتالي ستظهر أخطاء في النتائج .
----------------------------------------
السؤال الخامس :
اكتب برنامجاً يطلب من المستخدم إدخال عدد صحيح و من ثم نخبره بإن كان هذا العدد أولي أم لا .
ملاحظة / العدد يكون أولي إذا كان عدد قواسمه اثنان فقط هما العدد نفسه و الواحد .. لذلك سنحسب عدد القواسم .
الحل /
كود:
#include <iostream.h>
void main()
{
int n;
int y=0;
cout << "Enter a Number : ";
cin >> n;
for( int i=2; i<=n-1; i++ )
if( n % i == 0 )
y++;
if( y > 0 )
cout << "not Prime" << endl;
else
cout << "Prime" << endl;
}
شرح الحل :
1 – عرفنا متغيرين صحيحين : n من أجل العدد الذي سيدخله المستخدم .. y من أجل حساب عدد القواسم .
2 – طلبنا قيمة العدد لاختبار فيما إذا كان أولي أم لا .
3 – اتبعنا نفس أسلوب السؤال الثالث .. و لكننا هنا لا نريد كتابة القواسم .. فقط نريد عدها .. و غيرنا أيضاً القيمة الإبتدائية و النهائية للتكرار لأننا لا نريد اختبار العدد نفسه و العدد واحد لأننا نعرف بأنهما قواسم للعدد بشكل مسبق .
فإذا كان باقي قسمة العدد على i تساوي الصفر فهذا يعني أنه قاسم له لذلك نزيد y بمقدار واحد .. و هكذا حتى نختبر جميع القيم .
4 – بعد ذلك نختبر فيما إذا كانت y أكبر من الصفر فهذا يعني أننا وجدنا قواسم للعدد و بالتالي نكتب أن العدد ليس أولي .. أم إذا كانت y تساوي الصفر و هذا يعني أننا لم نجد قواسم للعدد و بالتالي نكتب أن العدد أولي .
----------------------------------------
:: تابع تعليمات التحكم ::
الأن لنعود إلى إلى درسنا .. سنأخذ باقي العبارات و التي تسمى ( بالتكرار المشروط ) و هي كالتالي :
5 – العبارة ( while ) :
تقوم هذه العبارة بتكرار مجموعة من التعليمات عدد غير معروف من المرات طالما أن الشرط محقق .. و بهذا تختلف عن الحلقة for في أننا لا تعرف عدد المرات التي سيتكرر في التنفيذ .
قاعدة /
كود:
while( Condition )
Statements;
حيث أن :
Condition : هو الشرط .. يكتب تماماً مثل العبارة if .
Statements : التعليمات المراد تكرارها طالما أن الشرط محقق ( نتيجته true ) .
مثال /
6 – العبارة ( do/while ) :
نفس العبارة while تماماً .. و لكنها ستنفذ التعليمات الموجودة مرة واحدة على الأقل ثم بعد ذلك تختبر تحقق الشرط .
قاعدة /
كود:
do
Statements;
while( Condition );
مثال /
كود:
do
x++;
while( y<0 );
ملاحظة 1 / لاحظ وجود الفاصلة المنقوطة في نهاية هذه العبارة .. أما في العبارة السابقة لم نضع فاصلة منقوطة بعد while و ذلك من أجل التمييز بين النوعين ( while ) و ( do/while ) .
ملاحظة 2 / يمكننا أيضاً في هاتين العبارتين استخدام أقواس الكتل ( { } ) إذا كنا نريد تكرار عدد من التعليمات و ليس فقط تعليمة واحدة .. كما تعلمنا سابقاً .
----------------------------------------
مشاركة: :: مشروع لتعليم أساسيات ++C :: موضوع الدروس
:: أمثلة تطبيقية ::
في هذا الدرس أريد أن أحل أكبر عدد ممكن من الأسئلة .. و بالتالي سنأخذ بعض الأمثلة على العبارتين ( while ) و ( do/while ) .. و من ثم سنأخذ أمثلة عامة عما تعلمناه سابقاً .
المثال 1 /
نريد أن نطلب من المستخدم إدخال جملة بشرط أن تنتهي بنقطة ( لكي نعرف أن الجملة انتهت ) و من ثم نكتب له عدد حروف تلك الجملة .
http://members.lycos.co.uk/wolfsniper84c/C/Sentence.jpg
لاحظ أننا لا نعرف مسبقاً عدد الحروف التي سيدخلها المستخدم لذلك سنستخدم الحلقة while .. و لكننا نستطيع ذلك بواسطة الحلقة for بحيث نضع الحلقة مفتوحة ( انظر إلى أول هذا الدرس ) .. لذلك سأضع الحلين .
الطريقة الأولى / استخدام while و do/while :
مناقشة الحل :
1 – نريد متحول من النوع char و الذي سيحتوي على الحروف التي سيدخلها المستخدم .. و لكن انتبه هذا النوع سيحتوي على حرف واحد فقط في كل مرة .. و إذا كنا نريد أن نجعل هذا المتحول يحتوي على جملة كاملة فيجب أن نستخدم المصفوفات ( سنأخذها إن شاء الله ) .
2 – نريد متحول من النوع int ليحسب لنا عدد الحروف التي أدخلها المستخدم .. و سنزيد هذا المتحول عند كل عملية إدخال للحروف .. حيث سنضع cin داخل حلقة تكرار .
3 – ما هو شرط المتابعة في عملية التكرار ؟ تنتهي الجملة ( من السؤال ) بالنقطة لذلك طالما أن الحرف لا يساوي النقطة فسنجعل المستخدم يدخل حرف آخر .
الحل 1 / استخدام do/while :
كود:
#include <iostream.h>
void main()
{
char c;
int i=0;
cout << "Enter a Sentence : ";
do
{
cin >> c;
i++;
}
while( c != '.' );
cout << --i << endl;
}
شرح الحل :
1 – عرفنا متغيرين : الأول حرفي من النوع char من أجل حروف الجملة التي سيدخلها المستخدم .. و الثاني من أجل حساب عدد هذه الحروف .
قاعدة / عندما تريد أن تحسب عدد مرات إدخال حرف أو عدد مرات التكرار أو عدد أي شيء تريده .. فيجب أن تعطي قيمة ابتدائية دائماً لهذا العداد و يفضل أن تكون الصفر .
2 – كتبنا على الشاشة طلب إدخال الجملة .
3 – لاحظ أين وضعنا cin بداخل التكرار .. لأننا نريد أن نحسب عدد الحروف المدخلة لذلك يجب وضع الأمر cin داخل التكرار .. بحيث عند إدخال الحرف الأول سنزيد i بمقدار واحد .. و لكن لاحظ أيضاً أن عمل cin ينتهي في كل تكرار بإدخال حرف واحد لأن المتغير الحرفي c هو لحرف واحد .. بينما إذا المتغير c يستطيع أن يحمل أكثر من حرف ( جملة ) فهذا الحل سيكون خطأ .. لأن في كل تكرار لن يكون عدد الحروف المدخلة هي حرف واحد فقط بل من الممكن أن يكون في تكرار عبارة عن جملة .. و بالتالي حلنا هكذا يكون قاصر .. دعونا الأن نعود لهذا السؤال .
4 – في كل دورة سيختبر الشرط فيما إذا كان الحرف المدخل هو النقطة أم لا .. فإذا كان كذلك فسيتم الخروج من الحلقة .. أما إذا تحقق الشرط فسيتم التكرار مرة أخرى .
5 – بعد ذلك قمنا بكتابة عدد الحروف المدخلة .. و لكن سنكتب عدد الحروف ناقصاً واحد ! لأننا قمنا بعد النقطة كحرف .
ملاحظة / سيتم تنفيذ تعليمات الكتلة do مرة واحدة أولاً ثم سيتم التحقق من الشرط .. على عكس العبارة while التي ستختبر الشرط أولاً ثم تقوم بالتكرار .
الحل 2 / استخدام while :
كود:
#include <iostream.h>
void main()
{
char c = 'a';
int i = 0;
cout << "Enter a Sentence : ";
while( c != '.' )
{
cin >> c;
i++;
}
cout << --i << endl;
}
شرح الحل :
الحل هو تماماً مثل الحل السابق .. و لكن أضفنا شيئاً صغيراً .. قبل شرحه انظر إلى العبارة while .. ستقوم هذه العبارة باختبار المتغير الحرفي c أولاً و من ثم نطلب من المستخدم إدخال حرف .. و بالتالي فإن المتغير c لم يحتوي على أي قيمة فمن الخطأ أن يتم الإختبار على متغير لم يحتوي على قيمة .. لذلك وضعنا قيمة ابتدائية افتراضية للمتغير c و هي الحرف a على سبيل المثال .
قاعدة / لا تقم بأي عملية مهما كانت ( جمع ، طرح ، إختبار ... ) على متغيرات لم تعطى لها قيم بعد .. لأنها عند تعريفها ستأخذ قيماً لا معنى لها .. ( كما قلنا سابقاً ) .
في المثال السابق لم نضع قيمة ابتدائية للمتغير c لأنه سيأخذ قيمة ابتدائية قبل الإختبار و هي الحرف الأول من الجملة لأننا كما قلنا أن do/while ستنفذ التعليمات مرة واحدة قبل الإختبار .. لذلك عليك معرفة متى تستخدم الطريقتين .
الطريقة الثانية / استخدام for :
قلنا أن في هذا السؤال لا نعرف مسبقاً عدد الحروف المدخلة لذلك من الصعب استخدام for و لكننا نستطيع ذلك .
الحل /
كود:
#include <iostream.h>
void main()
{
char c;
int i = 0;
cout << "Enter a Sentence : ";
for( ; ; )
{
cin >> c;
i++;
if( c == '.' )
break;
}
cout << --i << endl;
}
شرح الحل :
لاحظ كيف استخدمنا break داخل الشرط if .. فإذا تحقق الشرط فسيتم الخروج من الحلقة .. و باقي الشرح هو نفسه .
مناقشة الحل :
1 – نريد متحول واحد فقط من أجل الأعداد العشرة و هو a .. و نوعه ( int ) .
2 – و نريد أيضاً متغير صحيح لحساب مجموع الأعداد sum .. و آخر للمتوسط avr .. بحيث سيكون المتوسط من النوع الحقيقي ( float ) .. لأن المتوسط قد يحتوي على أعداد عشرية بسبب عملية القسمة .
ملاحظة / نستطيع إيجاد المتوسط دون الحاجة إلى المتغير avr .. عن طريق كتابة قانونه فوراً في تعليمة cout .. و لكني سأستخدمه لكي يكون المثال واضحاً .
3 – سنستخدم حلقة for من أجل إدخال 10 أعداد لأنه ليس من العقول أن نكتب 10 مرات cin ! .. و أيضاً استخدمنا for و لم نستخدم while لأننا عرفنا مسبقاً عدد مرات التكرار و هي 10 .
الحل /
كود:
#include <iostream.h>
void main()
{
int a, sum=0;
float avr;
for( int i=1; i<=10; i++ )
{
cout << "Enter a Number " << i << " : ";
cin >> a;
sum = sum + a;
}
cout << "Sum = " << sum << endl;
avr = (float)sum / 10;
cout << "Avr = " << avr << endl;
}
شرح الحل :
1 – عرفنا المتغيرات المطلوبة .. و أعطينا المتغير sum قيمة ابتدائية و هي الصفر لأنه سيحسب المجموع .. انظر كيفية استخدام هذا المتغير داخل الحلقة :
هنا سنجمع في كل تكرار القيمة الجديدة للمتغير a و سنضيفها على sum و لكن ماذا بالنسبة لأول عملية تكرار ؟ ستكون sum تحمل قيمة لا معنى لها و بالتالي وضعنا لها قيمة ابتدائية و هي الصفر .
2 – قمنا بوضع حلقة ستقوم بتكرار مجموعة من التعليمات 10 مرات من أجل إدخال 10 أعداد .. و طلبنا من المستخدم إدخال عدد في كل مرة .. بحيث في كل دورة ستأخذ a قيمة جديدة ..
لاحظ أين قمنا بعملية حساب مجموع الأعداد :
وضعناها داخل التكرار و ليس خارجه مثل المتوسط avr .. لأننا نريد حساب في كل دورة المجموع الجديد للأعداد .. بينما المتوسط يتم حسابه مرة واحدة و ذلك بعد حساب المجموع .
ملاحظة / المتوسط يساوي مجموع الأعداد على عدد تلك الأعداد .
3 – كتبنا المجموع .
4 – حسبنا المتوسط .. و لاحظ كيف وضعنا ( float ) قبل المتغير sum قسمة عدد صحيح ( int ) على عدد صحيح ( int ) ستعطي عدد صحيح لا يحتوي على كسور .. و نحن نريد العكس لذلك قمنا بإخبار المترجم بأن يتعامل مع المتغير sum بشكل مؤقت على أنه عدد حقيقي من النوع float .. و من ثم كتبنا ذلك المتوسط على الشاشة .
----------------------------------------
مشاركة: :: مشروع لتعليم أساسيات ++C :: موضوع الدروس
مناقشة الحل :
1 – نريد ثلاثة متغيرات صحيحة ( int ) :
A : من أجل الأعداد التي سيدخلها المستخدم .
Max : من أجل أكبر عدد .
Min : من أجل أصغر عدد .
2 – سنستخدم الحلقة for لأننا عرفنا مسبقاً عدد الأعداد التي نريد اختبارها .
الحل /
كود:
#include <iostream.h>
void main()
{
int a, max, min;
cout << "Enter the Number 1 : ";
cin >> a;
max = a;
min = a;
for( int i=2; i<=10; i++ )
{
cout << "Enter the Number " << i << " : ";
cin >> a;
if( a > max ) max = a;
if( a < min ) min = a;
}
cout << "Max = " << max << endl;
cout << "Min = " << min << endl;
}
شرح الحل :
1 – قمنا بتعريف المتغيرات المطلوبة من النوع int .
2 – سأشرح فكرة السؤال ثم سأخبركم لماذا قمت بذلك :
أولاً / طلبنا من المستخدم العدد الأول و من ثم جعلنا قيم min و max تساوي ذلك العدد .. و بعد ذلك بقي لدينا 9 أعداد لم يدخلها المستخدم بعد لذلك نضع حلقة تبدأ من 2 و تنتهي بـ 10 .. لأن العدد الأول قد حصلنا عليه ..
ثانياً / عند إدخال كل عدد من الأعداد سنختبر فيما إذا كان هذا العدد أكبر من max ( افترضنا أن العدد الأول هو الأكبر ) فإذا كان كذلك فهذا يعني أن هذا العدد هو الأكبر و بالتالي نجعل max تساوي ذلك العدد .. و نختبر أيضاً فيما إذا كان هذا العدد أصغر من min ( افترضنا أيضاً أن العدد الأول هو الأصغر ) فإذا كان كذلك فهذا يعني أن هذا العدد هو الأصغر و بالتالي نجعل قيمة min تساوي هذا العدد .
ثالثاً / نكتب قيمة العدد الأكبر و الأصغر .
ملاحظة / لا يمكن أن نستخدم else هنا .. لأنه إذا لم يكن a أكبر من max فهذا لا يعني أنه أصغر من min .
لماذا لم نضع إدخال العدد الأول داخل الحلقة ؟ قلت سابقاً أنه لا يجوز أن نقوم بأي عملية أو اختبار على متحول ما لا يحمل قيمة .. و بالتالي إذا وضعنا إدخال العدد الأول داخل الحلقة for .. فلن نستطيع إعطاء قيمة أولية للمتغيرين max و min .. حاول أن تضع إدخال العدد الأول داخل الحلقة و شاهد الأخطاء التي ستظهر .
----------------------------------------
الأن سنقوم بحل بعض الأمثلة الجميلة المتعلقة برسم أشكال على الشاشة بواسطة الرمز ( * ) .. و سنحاول رسم بعض هذه الأشكال .. و كمثال على ذلك انظر الصورة :
http://members.lycos.co.uk/wolfsniper84c/C/seen_1.jpg
هذه الأشكال تعتمد بشكل كبير على الحلقات for و بالتالي إذا استطعتم حلها و فهم كيفية رسم هذه الأشكال فستعرفون أنكم قد أتقنتم كل ما تعلمناه حتى الأن حول التكرار .
ملاحظة / هذه الأشكال وجدتها كأمثلة موجودة في أحد الكتب .. و هو باسم "كيف تبرمج بلغة السي++" للدكتور صلاح الدوه جي .. و هذا الكتاب هو لدار لشعاع الموجودة في سوريا .. و هو أفضل كتاب موجود في سوريا يشرح السي++ بشكل كامل لأنه ليس كتاب بل مرجع كامل .. لذلك من استطاع الحصول عليه فلا يتردد بشرائه .
المثال 4 /
نريد رسم الشكل السابق .. لذلك سنستخدم for كما قلنا .. بحيث في السطر الأول يوجد 5 نجوم و الثاني 4 و الثالث 3 و الرابع 2 و الخامس نجمة واحدة فقط .
مناقشة الحل :
1 – نحتاج إلى حلقتين for متداخلتين .. لماذا ؟ لأن هناك عملتي تكرار :
الأولى / من أجل الكتابة على كل 5 أسطر .
الثانية / من أجل كتابة مجموعة من النجوم على كل سطر .
2 – سنستخدم متغير التكرار I من أجل السطر ( من أجل التنقل بين الأسطر ) .. و المتغير j من أجل العمود ( المتمثل في كتابة النجوم كما ذكرنا ) .
الحل /
كود:
#include <iostream.h>
void main()
{
for( int i=5; i>=1; i-- )
{
for( int j=1; j<=i; j++ )
cout << "*";
cout << endl;
}
}
شرح الحل :
1 – قلنا بأن الحلقة الأولى من أجل التنقل بين الأسطر .. حيث أنه في كل دورة سيتم كتابة مجموعة من النجوم .. و بالتالي عرفنا الأن فائدة الحلقة الأولى .. و نحن نحتاج إلى 5 أسطر .. و لكن لماذا وضعنا التكرار بشكل تنازلي من 5 إلى الواحد و ليس العكس ؟ انظر الفقرة التالية .
2 – الحلقة الثانية من أجل كتابة النجوم .. و لكن كيف يمكننا أن نكتب تلك النجوم ؟ لاحظ العلاقة بين عدد النجوم و السطر .. في التكرار الأول سنكتب 5 نجوم و بالتالي يجب أن تكون القيمة النهائية للحلقة الثانية 5 و لكننا سنجد أن الأمر خاطئ إذا كتبنا 5 لأننا بذلك سنكتب 5 نجوم في كل سطر .. لذلك يجب أن نعتمد في الحلقة الثانية على قيم i المتمثلة في السطر .. هل عرفت الأن لماذا وضعنا i شكل تنازلي .
ففي الدورة الثانية ( السطر الثاني ) ستكون قيمة i تساوي 4 و بالتالي سنكتب 4 نجوم .. و في الدورة الثالثة ( السطر الثالث ) ستكون i تساوي 3 و بالتالي سنكتب 3 نجوم .. و هكذا .
لاحظ أيضاً أننا وضعنا الأمر : cout << endl من أجل النزول سطر في كل دورة .
مناقشة الحل :
لاحظ هذا الشكل فهو يشابه الشكل السابق و لكن أصبح في السطر الأول نجمة واحدة فقط و الثانية نجمتان و هكذا .. لذلك انظر إلى العلاقة بين السطر و عدد النجوم .. نجد أن أنهما متساويان لذلك يجب وضع قيم السطر i بنفس قيم العمود j .
الحل /
كود:
#include <iostream.h>
void main()
{
for( int i=1; i<=5; i++ )
{
for( int j=1; j<=i; j++ )
cout << "*";
cout << endl;
}
}
شرح الحل :
هذا الحل هو نفس الحل السابق .. و لكن غيرنا قيم i بحيث أصبحت تتزايد من الواحد إلى 5 .. و قلنا سبب ذلك لأننا نريد أن تكون القيمة النهائية لـ j مساوية لـ i بسبب تساوي رقم السطر مع رقم العمود .
----------------------------------------
مشاركة: :: مشروع لتعليم أساسيات ++C :: موضوع الدروس
مناقشة الحل :
بنفس طريقة المثالين السابقين .. سنضع الحلقة الأولى من أجل التنقل بين الأسطر و الثانية من أجل التنقل بين الأعمدة .. و أيضاً سنستخدم الشرط لمعرفة أماكن رسم النجوم .. انظر الحل :
الحل /
كود:
#include <iostream.h>
void main()
{
for( int i=1; i<=10; i++ )
{
for( int j=1; j<=10; j++ )
if( i==1 || i==10 || j==1 || j==10 )
cout << "*";
else
cout << " ";
cout << endl;
}
}
شرح الحل :
بنفس الأسلوب .. و لكننا أضفنا هنا شرط .. سيفيدنا هذا الشرط في تحديد أماكن رسم النجوم .. انظر إليه جيداً ..
لقد جعلنا النجوم ترسم في السطر الأول و العاشر .. و في العمود الأول و العاشر .. كيف ذلك ؟ قلنا أن في الحلقات المتداخلة ( مثل ما هو مذكور ) ستكون i للسطر و j للعمود .. لذلك اختبرنا فيما إذا كانت I تساوي الواحد أو عشرة .. و ذلك ينطبق على j أيضاً .. فإذا تحقق الشرط نرسم نجمة و إذا لم يتحقق نرسم فراغ ( لأننا نريد المستطيل فارغ ) .
ملاحظة مهمة / الشرط يتبع الحلقة الثانية الخاصة بـ j و لا تتبع الحلقة الأولى .. و لم نضع أقواس للحلقة الثانية لأنها تحتوي على تعليمة واحد و هي if .. و حيث أن else تتبع if .
قاعدة / باستطاعتك عن طريق هذا المثال رسم عدة أشكال عن طريق تحديد رقم الأسطر و الأعمدة التي تريد رسم النجوم فيها .. و ذلك بتغيير الشرط فقط .. مثلاً :
1 – لرسم الحرف N : يكون الشرط ..
كود:
if( j==1 || j==10 || i==j )
نحدد العمود الأول و العاشر .. بالإضافة إلى المكان الذي يتساوى فيه I مع j لأننا نريد رسم مستقيم مائل و يمكن ذلك عندما يكون رقم السطر يساوي رقم العمود .
ملاحظة / حاول دائماً معرفة العلاقة بين السطر و العمود .. لكي تعرف مكان رسم النجوم .
2 – لرسم الحرف Z : يكون الشرط ..
كود:
if( i==1 || i==10 || i+j==11 )
نحدد السطر الأول و العاشر .. بالإضافة إلى المكان الذي يكون مجموع السطر مع العمود يساوي 11 .. لاحظ العلاقة بين السطر و العمود :
في السطر الأول سيكون موقع النجمة في العمود العاشر .. و في السطر الثاني سيكون موقع النجمة في السطر التاسع و في السطر الثالث سيكون موقع النجمة في العمود الثامن .. إلخ . لاحظ كيف أن المجموع دائماً يساوي 11 .. و بهذا الأسلوب تستطيع رسم أشكال مختلفة .
تستخدم هذه النافذة من أجل معرفة القيم التي تأخذها المتغيرات التي قمت بتعريفها في برنامج .. و ذلك أثناء عمل البرنامج .. و بالتالي تستطيع معرفة الأخطاء أو أماكن الضعف في برنامج .
يمكنك استخدام هذه النافذة من خلال أوامر القائمة Debug و ستظهر تلك النافذة باستخادم أربع أوامر .. سنأخذ نحن أمرين :
الأول / باستخدام F10 / Step Over /
من أجل الانتقال سطراً سطراً داخل برنامج أثناء عمله و بالتالي ستعرف كيف يقرأ المترجم برنامجك .. و لكنك بهذا الأمر لن تدخل إلى داخل التوابع عند استدعائها أو إلى الأماكن الأخرى التي ينتقل إليها المترجم .. بل ستكتفي بالتنقل ضمن التابع الذي يوجد به المترجم حالياً .
الثاني / باستخدام F11 / Step Into /
نفس الأمر السابق و لكنك ستدخل إلى كل التفاصيل و إلى داخل التوبع أو الأوامر التي وضعتها في برنامجك .. و يمكنك طبعاً استخدام كلا الأمرين معاً .. على حسب المكان الذي تريد الإنتقال إليه .. مثلاً إذا كنت تريد إلى تفاصيل تابع ما فاستخدم F11 عند سطر استدعائه .. أما لو كنت لا تريد ذلك فاستخدم F10 و بالتالي سيتم الإنتقال إلى السطر الذي يلي سطر استدعاء ذلك التابع ( ستتوضح الأمور حينما تجرب تلك الأوامر بنفسك ) .. و سنأخذ التوابع في الدرس القادم إن شاء الله .
ملاحظة 1 / لن تظهر النافذة Debug أو النافذة Watch إلا حين استخدام أحد الأمرين السابقين .. و ذلك بالضغط فوراً إما على F10 أو F11 .. و ستظهر في الأسفل على اليمين .. أما القائمة Debug فستظهر ضمن القوائم .
ملاحظة 2 / في النافذة Watch ضع اسم المتغير الذي تريد معرفة قيمه ضمن اللائحة Name و ستظهر قيمه ضمن اللائحة Value .
ملاحظة 3 / تستطيع الخروج من عملية إزالة الأخطاء Debug بالضغط على F7 و بعد ذلك اضغط Ok .
---------- ---------- ---------- نهاية الدرس الثالث ---------- ---------- ----------
حاول حل الأمثلة التي كتبناها اليوم على جهازك .. و في الدرس القادم إن شاء الله سنأخذ التوابع و ستشعر بأنك بدأت بدراسة البرمجة الحقيقة .. بالتوفيق .
مشاركة: :: مشروع لتعليم أساسيات ++C :: موضوع الدروس
السلام عليكم ...
سنأخذ في هذا الدرس موضوع التوابع بالإضافة إلى الأمثلة المتعلقة بالموضوع .. و أريد أن أخبركم بأن درس اليوم مهم جداً و هو بداية إتقانكم للبرمجة .. و لكن قبل البدأ .. أريد أن أشكر مرة أخرى الدكتور الذي علمني هذه اللغة و أقول أن الأمثلة و التعاريف الموجودة في هذا الدرس هي من أفكاره .. و لكني أضيف عليها بأسلوبي الخاص بعضاً من الأمور و الأمثلة و الشرح ليسهل الفهم أكثر .. بالتوفيق .
---------- ---------- ---------- الدرس الرابع ---------- ---------- ----------
اليوم : الخميس ... التاريخ : 17 / 2 / 2005
:: البرامج الجزئية ( التوابع ) ::
:: مقدمة ::
كان أسلوبنا في حل المسائل السابقة هو أن نضع البرنامج كله ككتلة واحدة .. و في الحقيقة أن البرامج الحديثة اليوم لا تتبع هذا الأسلوب .. بل تأخذ تلك البرامج شكلاً آخر .. هو بتقسيم البرنامج ( مهما كان حجمه ) إلى أجزاء صغيرة تسمى الكائنات .. و لكننا نريد الأن التوابع التي هي جزء من هذه الكائنات .
إن شكل البرنامج الذي كنا كنبته هو غير عملي .. و ستدخل في متاهات كبيرة عند قراءتك له إذا كان كبيراً .. لذلك نستطيع استخدام التوابع مبدئياً ( بعد ذلك سنستخدم الكائنات ) لتقسيم برنامجنا إلى مجموعة من العمليات و الوظائف بحيث يكون لكل عملية أو وظيفة تابع خاص بها .
أمثلة على التوابع :
1 – تابع لإيجاد متوسط مجموعة من الأعداد .
2 – تابع لإيجاد العدد الأكبر أو الأصغر من بين مجموعة من الأعداد .
3 – تابع لحل معادلة من الدرجة الثانية .. بحيث نعطيه البيانات اللازمة فيقوم بالعملية الخاصة به .
4 – تابع الإظهار ( الكتابة ) على الشاشة .
5 – تابع من أجل ترتيب مجموعة من الأعداد .. بحيث نعطيه تلك الأعداد فيعيدها لنا مرتبة .
6 – تابع إيجاد القيمة المطلقة و الجذر التربيعي لعدد ما .
فائدة التوابع :
1 – تقسيم البرنامج إلى مجموعة صغيرة مما يؤدي إلى سهولة تصحيح الأخطاء .. حيث أنك ستحتاج إلى تصحيح جسم ( كتلة ) ذلك التابع فقط .
2 – سهولة استخدام عملية ( وظيفة ) ما عدد كبير من المرات عن طريق استدعاء ذلك التابع بسطر واحد فقط .. فإذا كنا لا نستخدم التوابع فإننا سنضطر إلى كتابة قوانين تلك العملية في كل مرة نحتاج إلى تلك العملية .
3 – تستطيع استخدام التوابع التي كتبتها في برامج أخرى .. بحيث تضعها في مكتبة خاصة بك .. و من ثم تستدعيها عن طريق #include .
خلاصة / إن جميع المكتبات القياسية الموجودة في اللغة مثل :
1 – math.h .
2 – string.h .
3 – ctype.h .
و الكثير من المكتبات الأخرى هي في الحقيقة مجموعة من التوابع الجاهزة الخاصة بلغة السي++ .. فانظر مثلاً إلى هذه العملية التي أخذناها سابقاً :
هذه العملية من أجل إيجاد الجذر التربيعي لعدد ما .. و هي تابع من توابع المكتبة math.h بحيث ندخل له عدد ما عن طريق وضعه داخل القوسين و من ثم سيعيد لنا الجذر التربيعي .
:: تعريف ::
البرنامج الجزئي ( التابع ) : هو كتلة برمجية مستقلة لها بداية و نهاية و اسم يستدعى به .. و يخصص لمعالجة جزء من مسألة كبرى .
:: الإعلان عن التوابع ::
يتم الإعلان عن تابع عن طريق ذكر نوعه ثم اسمه ثم وضع القوسين ( سنأخذ فائدتهما في هذا الدرس ) و بعد ذلك نضع جسم ذلك التابع .
كود:
void Name()
{
Function body
}
void : تمثل أي نوع من الأنواع التي أخذناها .. مثل : int أو float أو ... إلخ .. و لكننا مبدئياً سنستعمل void .
Name : من أجل اسم التابع .
Function body : جسم التابع الذي يحتوي على التعليمات الخاصة به .
ملاحظة / انظر إلى شكل التوابع .. هل عرفت الأن لماذا التابع main كبقية التوابع .. و لكن يختلف عنهم أن المترجم سيبدأ قراءة برنامجك منه .
مثال / حيث أن a و b متغيرات عددية عامة ( سأشرح معنى عامة و خاصة في هذا الدرس ) من النوع int :
كود:
void read_num()
{
cin >> a;
cin >> b;
}
:: استدعاء التوابع ::
يتم استدعاء التوابع ( أي طلب عملها ) بكتابة اسمها فقط متبوعاً بالقوسين و ما بداخل هذين القوسين من متغيرات وسيطة ( إن وجدت ) في أي مكان من برنامجك ( وطبعاً لا تنسى الفاصلة المنقوطة ) .. بشرط أن يكون ذلك الاستدعاء إما بداخل التابع main أو في تابع من التوابع الأخرى .. لأننا نستطيع استدعاء تابع داخل تابع آخر .
فعلى سبيل المثال : افرض أننا وضعنا تابعين .. الأول لإيجاد مجموع الأعداد و الثاني لحساب المتوسط .. و بالتالي يمكن أن يستفيد التابع الثاني من الأول بأن يستدعيه أثناء عمله ليطلب من ذلك التابع إيجاد مجموع تلك الأعداد .. لأن قاونون المتوسط هو : مجموع الأعداد على عددها .
مثال / لبرنامج يقوم بطلب إدخال عددين من المستخدم و من ثم يقوم بكتابتهما مرة أخرى على الشاشة .
ملاحظة / بالنسبة لتعريف المتحولات العامة a و b سيتم شرحمها في الفقرة التالية .
كود:
int a, b;
void read_num()
{
cin >> a;
cin >> b;
}
void print_num()
{
cout << a << endl;
cout << b << endl;
}
void main()
{
read_num();
print_num();
}
ملاحظة مهمة / يجب وضع القوسين دائماً عند استدعاء التابع .. و ذلك للتمييز بين التابع و المتحول .
:: المتحول العام و الخاص ::
1 – المتحول العام :
- يعلن عنه في بداية البرنامج و خارج كل التوابع ( حتى التابع main ) .
- يحجز مكانه في الذاكرة عند بداية البرنامج .. و يحرر ( يلغى أو يفقد قيمته ) عند انتهاء البرنامج .
- يمكن استخدامه في أي مكان من برنامجك ( في كل التوابع ) .
2 – المتحول الخاص :
- يعلن عنه داخل التابع .
- يحجز مكانه في الذاكرة عند الدخول إلى التابع ( استدعائه ) .. و يحرر عند انتهاء استدعاء ذلك التابع .
- يمكن استخدامه فقط داخل التابع الذي تم تعريفه فيه .. و بالتالي لا يمكن استخدامه في التوابع الأخرى أو حتى في التابع main .
ملاحظة / حاول التقليل من المتحولات العامة قدر الإمكان .. لماذا ؟ لأن هذه المتحولات ستبقى طوال فترة برنامجك ( كما ذكرنا ) و يمكن أن يكون استخدامها لفترة قصيرة فقط .. لذلك سيتم تحميل الذاكرة ببيانات لا نحتاجها .. و نحن نهدف إلى تخزين القيم المهمة فقط في الذاكرة و البقية يجب التخلص منه .
ملاحظة / يمكن أن يكون هناك متحول خاص يحمل نفس اسم متحول عام آخر .. و لكن إذا قمنا باستخدام ذلك الاسم ( داخل التابع الذي تم تعريف ذلك المتحول الخاص فيه ) فسيعتبر المترجم أنك تقصد المتحول الخاص .. و هذا يقودنا إلى قاعدة .
قاعدة / المتحول الخاص له أولية في التعامل من المتحول العام عند تشابه أسماء المتغيرات العامة و الخاصة .
مثال /
كود:
int a;
void r()
{
int a;
cin >> a;
}
هنا سيتم إدخال عدد من المستخدم و سيتم تخزينه في المتحول الخاص ( الموجود داخل التابع r ) و ليس في المتحول العام .
مشاركة: :: مشروع لتعليم أساسيات ++C :: موضوع الدروس
:: العملية الثنائية ( :: ) ::
تستخدم هذه العملية من أجل كسر القاعدة السابقة ( أولوية المتحول الخاص عند تشابه الأسماء ) .. و بالتالي عند استخدامها سيتم تجاهل المتحول الخاص و اعتبار المتحول العام هو المقصود .
يكتب هذا الرمز قبل اسم المتحول .. كالتالي :
مثال / نفس المثال السابق مع بعض التعديل :
كود:
int a;
void r()
{
int a;
cin >> ::a;
}
في هذا المثال سيتم إدخال عدد صحيح و تخزينه في المتحول العام و ليس الخاص .
تمرين على المتحولات العامة و الخاصة /
اكتب قيم المتحولات العامة النهائية و التي ستظهر في نهاية البرنامج .
كود:
int a, b, c;
void f1()
{
int a,b;
a = 10; b = 20; c = 30;
}
void f2()
{
int b=0;
a++; b++; c++;
}
void main()
{
a = 1; b=2; c=3;
f1();
f2();
cout << a << endl << b << endl << c << endl;
}
ملاحظة / عند قراءتك لأي برنامج ابدأ دائماً بالتابع main .
النتائج :
شرح النتائج :
1 – أولاً نجد أن هناك ثلاثة متحولات عامة و هي a و b و c .
2 - نبدأ القراءة من التابع main .. فنجد ثلاثة عمليات إسناد ( إعطاء قيم ) بحيث أخذ a القيمة واحد .. و b أخذ القيمة 2 .. و c أخذ القيمة 3 ..
3 – بعد ذلك قمنا باستدعاء التابع f1 و بالتالي سينتقل المترجم إلى ذلك التابع لقراءته ( أي أنه سينتقل إلى جسم ذلك التابع ) .. في ذلك التابع : سنجد أن هناك متحولين ( من النوع الخاص ) يحملان نفس الاسم لمتحولين عامين و هما a و b .. و بالتالي سيتم تجاهل المتحولين العامين و استخدام هذه المتحولات الخاصة بهذا التابع .. حيث قام هذا التابع بإعطاء المتحولين a و b قيم معينة و هي متحولات خاصة به .. و لكنه أعطى المتحول العام c قيمة جديدة و هي 30 .. لذلك قيم المتغيرات العامة حتى الأن هي :
و بهذا يكون المترجم قد نفذ هذا التابع .. و بعد ذلك سيرجع المترجم قراءة باقي برنامجك في التابع main .. أي إلى السطر الذي يلي مكان استدعاء التابع f1 .
4 – قمنا بعد ذلك باستدعاء التابع الثاني f2 .. و بالتالي سينتقل إليه المترجم لقراءته .. و نجد أن في ذلك التابع متحول خاص باسم b و يحمل نفس اسم المتحول العام .. لذلك سيتم تجاهل المتحول العام b في هذا التابع ..
نجد بعد ذلك أن هناك ثلاث عمليات إضافة واحد لكل متغير .. و الذي يهمنا هو المتحولين العامين a و c .. لأن b تم تجاهله كما قلنا .. و بالتالي ستصبح القيم كالتالي :
بعد ذلك سيعود المترجم للسطر الذي يلي سطر استدعاء التابع f2 .
5 – قمنا بكتابة قيم المتحولات العامة .
ملاحظة / من أجل زيادة الفهم .. استخدم النافذة Watch من أجل معرفة قيمة المتحولات العامة التي ستأخذها أثناء استدعاء التوابع .. و استخدم F10 للمرور على استدعاء التابع دون الدخول إلى جسمه .. و استخدم F11 للدخول إلى التابع لمعرفة ما قام به من تغييرات على المتحولات ( راجع النافذة Watch في الدرس السابق ) .
:: المتحولات الوسيطة ( Parameters ) ::
هل تتذكر الأقواس الموجود بعد اسم التابع ؟ الأن سنعرف فائدتها .. تستخدم هذه الأقواس من أجل القيام بإدخال قيم يحتاجها التابع لكي يقوم بالعمليات المناسبة عليها .. كيف ؟ افرض أنك تريد كتابة تابع يحسب مجموع عددين .. و بالتالي يجب أن تعطيه العددين لكي يجمعه .. و من الخطأ أن نستخدم العددين كمتحولات عامة للأسباب المذكرة سابقاً ( بسبب تحميل الذاكرة ببيانات لا نحتاجها ربما إلا مرة واحدة و حيث أن المتحولات العامة ستبقى حتى نهاية برنامجك .. و بالتالي سيزداد حجم الذاكرة دون فائدة ) .. إذاً يجب أن نستخدم المتحولات الوسيطة من أجل إدخال هذين العددين إلى داخل التابع .
قاعدة / داخل الأقواس الخاصة بالتابع و التي توضع بعد اسمه مباشرة .. نضع نوع المتغير ثم اسمه كأننا نعرف أي متغير آخر .. و إذا كنت تريد إدخال أكثر من متغير إلى التابع فاستخدم الفاصلة ( , ) من أجل ذلك .. و لكن انتبه لا يجوز أن تضع الأتي :
بل عليك مع كل متغير وسيط أن تضع نوعه قبل اسم ذلك المتغير الوسيط .
ملاحظة 1 / تستخدم المتحولات الوسيطة تماماً كالمتحولات الأخرى و لكن يتم التخلص منها ( تحريرها من الذاكرة ) عند الانتهاء من تنفيذ التابع ( كالمتحولات الخاصة ) .. و لكن تختل عن المتحولات الخاصة في مهمتها في أنها تقوم بإدخال القيم المهمة من خارج جسم هذا التابع إلى داخله لكي يقوم بالعمليات المناسبة على تلك القيم .. لأننا كما قلنا سابقاً أن المتحولات الخاصة لا يمكن رؤيتها أو استخدامها إلا من قبل التابع الذي عرفت فيه .. و بالتالي لا يمكن استخدامها من قبل التوابع الأخرى إلا عن طريق إرسالها كمتحولات وسيطة .
ملاحظة 2 / المتحولات المعرفة داخل التابع main هي أيضاً متحولات خاصة بهذا التابع و ليست عامة .. لأن التابع main هو كباقي التوابع ( كما قلنا ) و لكنه يتميز عنهم بأن برنامجك يبدأ التنفيذ عنده أولاً .
مثال /
كود:
void avr(int a, int b)
{
int c = ( a+b ) / 2;
cout << c << endl;
}
void main()
{
int x, y;
x = 10; y = 20;
avr( x, y );
}
شرح المثال :
في هذا المثال قمنا بتعريف عددين صحيحين و هما x و y .. و من ثم أعطيناهم قيم معينة .. و بعد ذلك قمنا بإرسال هذين العددين للتابع avr لكي يحسب المتوسط و من ثم يكتب الناتج .
ملاحظة 1 / المتحولات الوسيطة الموجودة في تعريف التابع .. تسمى بالمتحولات الوسيطة الشكلية .. بينما المتحولات الموجودة بين القوسين عند استدعاء التابع تسمى بالمتحولات الفعلية ( لأنها هي التي ستجرى عليها العمليات ) .
ملاحظة 2 / لا يشترط تشابه أسماء المتحولات الفعلية مع المتحولات الشكلية .
قواعد :
1 – عدد المتحولات الوسيطة الشكلية يجب أن يساوي عدد المتحولات الوسيطة الفعلية .
2 – أنواع المتحولات الوسيطة الفعلية يجب أن تكون متوافقة بالترتيب مع المتحولات الوسيطة الشكلية .
مشاركة: :: مشروع لتعليم أساسيات ++C :: موضوع الدروس
:: القيمة العائدة ( Return Value ) ::
تعلمنا حتى الأن كيف نقوم بكتابة التوابع و كيف نقوم بإدخال البيانات اللازمة للتابع لكي يجري عليها العمليات اللازمة .. و لكن افرض أننا نريد أن نرجع ناتج العمليات التي قام بها التابع إلى التابع الأخر الذي قام باستدعائه ؟ نستطيع ذلك عن طريق القيمة العائدة .. و على سبيل المثال :
في التابع السابق الذي يحسب متوسط عددين .. افرض أننا نريد من نجعل أي تابع آخر ( main مثلاً ) أن يستفيد من ذلك الناتج و يجري عليه عمليات أخرى .. لذلك لا يكفي في التابع avr أن ندخل عددين فقط بل يجب أن نعطي التوابع الأخرى ناتج عملية إيجاد المتوسط .
قاعدة 1 / هل تتذكر كيف شرحنا طريقة تعريف تابع ( الإعلان عن التوابع ) ؟ قلنا أنه يجب ذكر نوع التابع ثم اسمه ثم المتحولات الوسيطة ضمن قوسين ثم يأتي جسم ذلك التابع .. الأن حان الوقت لتغيير النوع void .. و لكن يجب أولاً توضيح بعض النقاط :
1 – التوابع التي لا تعيد قيمة : يكون نوعها void .
2 – التوابع التي تعيد قيمة : يمكن أن يكون نوعها بأي الأنواع المعرفة من قبل اللغة .. مثل : int و float و bool .. أو بأي من نوع من أنوع الكائنات أو السجلات ( سنأخذ ذلك لاحقاً ) .. و المهم في هذا الدرس الأنواع المعرفة مسبقاً .
قاعدة 2 / يجب استخدام التعليمة return مع التوابع التي تعيد قيمة .. و طريقة استخدامها هي كالتالي :
حيث أن Value ممكن أن تكون أي قيمة أو ممكن أن تكون متحول ما .. و يشترط أن تلك القيمة مناسبة لنوع التابع .
قاعدة 3 / لا يمكن استدعاء التوابع التي تعيد قيمة بذكر اسمها فقط !!! بل يجب أن توضع إما كإسناد قيمة أو في تعليمات cout أو ... إلخ .. أمثلة :
بفرض أن لدينا التابع avr الذي يحسب المتوسط و يعيد الناتج .. و بفرض أن c هو متحول ما من النوع float :
كود:
c = avr( 2, 3 );
cout << avr( x, y ) << endl;
و بهذا تختلف عن التوابع التي من النوع void التي يمكن استدعاؤها يذكر اسمها فقط :
بفرض أن read تابع من النوع void .. و بالتالي يمكن استدعاؤه كالتالي:
مثال / إيجاد متوسط عددين :
كود:
float avr( float a, float b )
{
float c;
c = ( a+b ) / 2;
return c;
}
void main()
{
float x, y;
cin >> x >> y;
cout << avr( x, y ) << endl;
}
و من الممكن أيضاً كتابة التابع avr بشكل آخر .. بحيث لا نستخدم متحول جديد لإرجاع القيمة :
كود:
float avr( float a, float b )
{
return ( a+b ) / 2;
}
ملاحظة / هل عرفت الأن أن التابع الذي كتبناه ( avr ) يشابه تماماً توابع أخرى موجودة في المكتبات القياسية للغة ؟ انظر كيف استخدمنا التابع avr .. و كيف استخدمنا التابع sqrt الموجود في المكتبة math.h :
كود:
cout << sqrt(9) << endl;
وبذلك تستطيع وضع مكتباتك الخاصة و لكن سنتطرق إلى الطريقة فيما بعد .
:: أماكن كتابة التوابع ::
في الأمثلة السابقة قمنا بكتابة التوابع فوق التابع الأساسي main .. و لذلك لكي يتعرف عليها برنامج حين استدعائها .. بينما إذا وضعناها أسفل التابع main فسيحدث خطأ .. لأنك قمت باستدعاء تابع لم يتم التعرف عليه بعد .. و كما قلنا فإن نمط البرمجة يتم سطراً سطراً من أول سطر في برنامجك إلى آخر سطر و لكن التنفيذ يتم من التابع main .. لذلك إذا استخدمت تابع و لم تقم قبل ذلك بتعريفه فسيحدث خطأ .
و لحل هذه المشكلة ( بأننا نريد وضع التوابع أسفل التابع main ) .. نضع فقط في أول البرنامج رؤوس تعريفات تلك التوابع .. بحيث تضع نوع التابع ثم اسمه ثم أنواع متحولاته الوسيطة فقط ( دون ذكر أسمائها ) مع كتابة الفاصلة المنقوطة في النهاية .. و دون كتابة جسم ذلك التابع .
ملاحظة / إذا وضعت أسماء المتحولات الوسيطة فلا بأس و لكن المترجم سيتجاهلها .
مثال /
كود:
int f1();
bool f2( int, int);
void f3(float);
void main()
{
}
int f1()
{
return 0;
}
bool f2(int a, int b)
{
return true;
}
void f3(float a)
{
}
ملاحظة 1 / لا يشترط التوافق في ترتيب ذكر رؤوس التوابع مع أجسامها في الأسفل .
ملاحظة 2 / قلنا أنه لا يشترط وضع أسماء المتحولات الوسيطة .. و لكن يجب ذكرها في أجسام التوابع .
ملاحظة 3 / بهذه الطريقة إذا كنت قد كتبت عدد كبير من التوابع .. فستجد أن التابع main موجود في الأعلى دائماً و بالتالي ستسهل على نفسك عناء البحث عنه .
:: الاستدعاء بالقيمة ( by Value ) و الاستدعاء بالمرجع ( by Reference ) ::
إذا أردنا أن ندخل مجموعة من القيم إلى داخل تابع ما .. كنا نستخدم لذلك المتحولات الوسيطة .. و عند استدعاء التابع كنا نرسل القيم التي نريدها عن طريق المتحولات الوسيطة الفعلية ( راجع فقرة المتحولات الوسيطة ) .. و لكن ما معنى الاستدعاء بالقيمة و الاستدعاء بالمرجع ؟
الاستدعاء بالقيمة ( by Value ) :
و هو الوضع الافتراضي لإرسال القيم للتوابع عن طريق المتحولات الوسيطة .. و لكن ما معناها ؟ انظر الاستدعاء التالي للتابع avr و الذي يحسب متوسط عددين ( لا يعيد قيمة ) و بفرض أن x و y هما العددين الذين سنحسب متوسطهما :
كود:
void avr(int a, int b)
{
int c = ( a+b ) / 2;
cout << c << endl;
}
void main()
{
int x, y;
x = 10; y = 20;
avr( x, y );
}
عند الاستدعاء .. سيتم إجراء نسخة عن كل من x و y و إرسالها إلى المتحولات الوسيطة a و b الخاصين بالتابع avr .. و بالتالي لن يتأثر المتغيرين x و y بنتائج عمليات التابع ( أي لن تتغير قيمتهما حتى بعد الانتهاء من التابع ) لأن المترجم قام بنسخ قيم x و y و وضعها في المتحولين الوسيطين a و b .. و بالتالي نستنتج أيضاً أنه سيتم تحميل الذاكرة بقيمتين مشابهتين تماماً لقيم x و y .
و لكن افرض مثلاً أنك تريد إرسال بيانات كبيرة ( كالكائنات أو السجلات مثلاً ) عبر تلك المتحولات الوسيطة .. فسيتم بهذه الطريقة إجراء نسخة أخرى لتلك القيم .. و بهذا سيزداد العبء على الذاكرة و سيزداد حجمها .. و لكن ما الحل لمعالجة هذه المشكلة ؟ الحل هو عن طريق الاستدعاء بالمرجع .. و تفيدنا تلك الطريقة أيضاً في إرجاع أكثر من قيمة بدلاً من قيمة واحدة فقط ( انظر الفقرة التالية ) .
الاستدعاء بالمرجع ( by Reference ) :
يختلف الاستدعاء بالمرجع عن الاستدعاء بالقيمة بأنه هذا الاستدعاء ( بالمرجع ) سيقوم بالتعامل مع عنوان المتحول الوسيط الفعلي في الذاكرة .. و بالتالي إذا حدثت أي تغييرات على المتحول الوسيط الشكلي فإن هذا التغير سيطرأ أيضاً على المتحول الوسيط الفعلي .. أي أن قيمة المتحول الوسيط الشكلي ستعطى للمتحول الوسيط الفعلي . انظروا المثال .
قاعدة / يمكنك استخدام الاستدعاء بالمرجع عن طريق وضع العلامة ( & ) قبل اسم المتحول الوسيط الشكلي الموجود في تعريف التابع .
كود:
void f1( int &a )
{
}
مثال / لتابع ندخل عليه عدد ما فيعطينا العدد الذي يليه :
كود:
void num( int &a )
{
a++;
}
void main()
{
int x;
cin >> x;
num( x );
cout << x << endl;
}
ملاحظة / لاحظ أننا لم نستخدم التوابع التي تعيد قيمة لإخراج الناتج .. بل استخدمنا الاستدعاء بالمرجع .
شرح المثال :
في هذا المثال .. سيدخل المستخدم عدد ما ( x ) ثم سنرسل هذا العدد كمرجع إلى التابع num .. و بهذه الطريقة سيتم إخراج الناتج عن طريق ( x ) أيضاً .. و لكن لن يتم هنا إجراء نسخة مطابقة للمتغير x بل سيتم تعديلها فقط عن طريق المتحول الشكلي a .. فإذا تغيرت a تتغير x و كأن a هي اسم آخر لـ x أي يتم التعامل معه و كأنه المتحول x .
و أريد التأكيد على معنى المرجعية .. قلنا فيما سبق أن المتغيرات تستخدم لتخزين قيم في الذاكرة و بالتالي لكل متغير في الذاكرة معلومتين : الأولى و هي القيمة التي يحملها .. و الثانية هي عنوانه في الذاكرة ( مكان وجوده ) .. و بالتالي عند استخدام الاستدعاء بالمرجع نكون قد وضعنا متغير آخر ( الشكلي ) يشير إلى نفس القيمة المرسلة فإذا تغيرت قيمة أحد المتغيرين ( الفعلي و الشكلي ) يكون قد تغير الأخر و كأننا وضعنا عنوانين في الذاكرة يشيران إلى نفس القيمة .. و هذا يعني أنه لن يتم نسخ قيمة المتحول الفعلي كما هو الحال في الاستدعاء بالقيمة .. حيث يتم في ذلك الاستدعاء إجراء نسخة كاملة عن المتحول الفعلي و وضعها في المتحول الشكلي بحيث يكون لها عنوان مختلف و قيمة مستقلة عن المتحول الفعلي .. و بالتالي إذا تغير أحدهم لن يتغير الأخر .
و في هذا المثال .. إذا فرضنا أن المستخدم أدخل العدد 5 فسيظهر على الشاشة العدد 6 .. و هكذا .
قاعدة 1 / من أجل القيم صغيرة الحجم يمكنك إرسال تلك القيم إلى التوابع باستخدام الاستدعاء بالقيمة .. بينما ينصح باستخدام الاستدعاء بالمرجع بالنسبة للقيمة الكبيرة .. و أيضاً من أجل إرجاع أكثر من قيمة ( أي تكون مخرجات التابع أكثر من قيمة ) .
قاعدة 2 / عليك معرفة متى تستخدم الاستدعاء بالقيمة و الاستدعاء بالمرجع .. حسب وظيفة التابع و على حسب العمليات التي سيقوم .
---------- ---------- ---------- نهاية الدرس الرابع ---------- ---------- ----------
في الدرس القادم إن شاء الله سنأخذ موضوع العودية ( Recursion ) بالإضافة إلى الأمثلة التطبيقية للمعلومات الموجودة في هذا الدرس .. بالتوفيق .