المساعد الشخصي الرقمي

مشاهدة النسخة كاملة : :: مشروع لتعليم أساسيات ++C :: موضوع الدروس



Wolf Sniper
29-01-2005, 09:26 PM
السلام عليكم ...


ملاحظة / أرجو عدم الرد لحين وضع الدرس كاملاً لأنه طويل .


هذا الموضوع عبارة عن مشروع تعليمي يتضمن ما يلي :

1 – تعليم أساسيات لغة ++C ضمن مواضيع الفهرس الموجود .
2 – مشروع إنشاء كتاب إلكتروني و هو ملخص عن الدروس المعطاة ليكون مرجع للمتعلمين .
3 – إنشاء مشروع تخرج للذين تعلموا هذه اللغة ( فقط من قام بالتسجيل ) .


المشتركين في الدورة هم الأعضاء :

1- son of Qatar .
2 - habash1986 .
3 - Son Of UAE .
4 - SONIC4ANIME .
5 - asf4ever .
6 - ^عبدالعزيز..ع^ .
7 - kokekemo .
8 - UAE Naruto .
9 - عنان 2005 .
10- ستو كايبا .
11 - Imaj .
12 - banan 86 .

و العدد قابل للزيادة .


مزايا التسجيل :

1 – باستطاعتك أن تسألني أي سؤال حول الدروس أو إن كنت تريدني أن أشرح لك بعض الفقرات بأسلوب مختلف أو لكي أحل لك بعض المسائل .. أما غير المسجلين فلن أجيب عن أسئلتهم ( فاعذروني ) و لكن أرحب بآرائهم و بردودهم .
2 – ستـطالب بحـل بعض التمارين ( و ليس كلها بما تستطيعه ) و تقديمها في هذا الموضوع بعد كل درس .
3 – ستقوم بعمل مشروع تخرج بعد انتهاء الدروس ( أريد واحد فقط على الأقل من بين الجميع أن يعمله ) .. و سأمنحك شهادة خاصة باسم المنتدى ( سنتحدث عن ذلك لاحقاً ) .
4 – سأقوم مع المسجلين بعمل كتاب إلكتروني و مناقشته .. و سأقـوم أنا بكتابته و سـيكون بإسمهم جميعاً و باسم المنتدى .


و من أجل من فاته التسجيل في الموضوع السابق الذي كان مقدمة لهذه الدورة و يريد معرفة التفاصيل فليدخل إلى هذا الموضوع و ليسجل فيه :

الموضوع الافتتاحي (http://www.montada.com/showthread.php?t=342352)


ملاحظة / لا تقم بتسجيل قائمتك في هذا الموضوع لأننا خصصنا الموضوع السابق لذلك .. و اعلم أن القائمة ستتجدد مع كل تسجيل جديد .


:: فهرس المواضيع ::

1_ بنية اللغة ( مكونات اللغة ) :
- المتحولات .
- الأنواع .
- العمليات .
إلخ ...

2_ تعليمات التحكم :
- الشرط .
- التكرار .

3_ البرامج الجزئية ( التوابع ) .

4_ المصفوفات ( Arrays ) .

5_ البنى ( Struct ) .

6_ المؤشرات ( Pointers ) .

7_ البرمجة غرضية التوجه :
- مقدمة .
- الصفوف ( Classes ) .
- توابع الهدم و البناء .
- الوراثة ( Inheritance ) .
- التحميل الزائد .
- التوابع الصديقة .


:: قبل البدأ ::

----------------------------------------

:: ملاحظات هامة ::

1 – إذا كنت تريد حل أي مسألة ( برنامج ) فعليك أولاً بالتفكير المنطقي لتسلسل عمل البرنامج ( كم متغير نحتاج ، ما هي العمليات و الأوامر المستخدمة ، هل نحتاج إلى الكائنات أم لا ؟ ، ما هي التوابع المطلوب كتابتها ... إلخ ) .. و ستزداد مقدرتك على فهم تلك الأمور كلما حللت مسائل و أمثلة أكثر .

2 – لا تيأس من أول محاولة فشل لحل أي مسألة و حاول تطبيق ما تعلمته و أعد المحاولة و إن لم تعرف فحينها اسألني و إن شاء الله سأجيبك .. فلا أريد أن يسألني أحد و هو لم يحاول إلا مرة واحدة . آمل أن تستجيبوا لهذا الأمر . و كل هذا من أجل مصلحتكم فإن كانت بدايتكم سيئة فلن تتعلموا شيئاً بعد ذلك لأن المواضيع ستتشابك قليلاً .

3 – يجب عليك أن تطبق ( تكتب ) جميع ( أو معظم ) الأمثلة - التي سوف نحلها - عندك على الجهاز و إن لم تفعل ذلك فأنا أؤكد لك أنك لن تفهم شيئاً .. و أنا أقصد بذلك من يكتفي بالقراءة فقط .. فلن تكتسب المهارة التي نريدها إلا بكتابة أكبر عدد ممكن المسائل و البرامج .. و هذا الأمر يؤكده جميع مدرسي البرمجة .. و سيكون لدينا مثال عملي واحد على الأقل في كل درس .

4 – لا تخف من البرمجة و لا توهم نفسك بأنها صعبة فإن لازمك هذا التفكير فلن تتعلمها أبداً - لأنك بعد ذلك لن تفهم حتى أبسط الأمور بها - لأن عقلك قد أغلق عليها تماماً ( على فكرة هذا الأمر يعرفونه من يدرسون علم النفس وهو الحوار مع العقل ) .. فها أنا قد تعلمتها و قد تعلمها كثيرون غيري فبماذا تختلف عنا ؟

5 – إن جميع هذه تطبيقات التي سنكتبها هي عبارة عن تطبيقات وحدة التحكم ( Consol ) و هي شبيهة بتطبيقات الدوس .. و لتجربة الأمثلة عليك كتابة المثال في ملف مصدري Source File ( انظر فقرة الملفات المستخدمة ) و من ثم قم بترجمته و بنائه ( انظر فقرة الإختصارات ) .. حيث أن الأمثلة لن تعمل كتطبيق مستقل و لكن يجب تشغيلها من البرنامج Microsoft Visual C++ 6 أو Borland C++ Builder .. و سنستخدم نحن الأول .. و من أجل التوضيح عن كيفية واجهة الأمثلة فانظر إلى الصورة :


http://members.lycos.co.uk/wolfsniper84c/C/Consol.jpg

6 – ملاحظة خاصة / أشهد الله تعالى بأني بريء من أي استعمال لهذه اللغة غير شرعي لا يرضي الله قام به أحد المتعلمين أو القارئين لهذا الموضوع .. فأنا لا أعلم ما في أنفسكم .. فلا تأتني يوم القيامة و تقول لله بأني قد أغويتك أو كنت السبب في ذنبك .. و الله يعلم نيتي و هدفي من هذا الموضوع كله و هو على ما أقوله شهيد .. و أتمنى أن لا يتم استخدام هذا العلم في ذلك .. لأنها أداة قد تستخدم في الجانب الإيجابي أو السلبي .

7– أخيراً ... أشكر من قام بتعليمي هذه اللغة و ساهم في دعمي لتعلمها و لن أنسى موقفه معي ما حييت .


:: مصطلحات ::


يجب أن تعرف بعض الأمور قبل أن تبدأ :

1 – Compiler : و هو المترجم الذي يقوم بقراءة برنامجك و يحوله إلى اللغة التي يفهمها و يجب أن تعرف أن هذه العملية تتم سطراً سطراً .. أي أن المترجم إذا وجد خطأ في برنامجك فسيتوقف عند السطر الذي يحتوي هذا الخطأ .. و بإمكانك طبعاً معرفة الخطأ ( في الفقرة التالية ) .

2 – Linker : و هو الذي يقوم بوصل المكتبات أو الملفات .. إلخ ببرنامجك .. فأحياناً سوف تجد أن هناك خطأ في Linker فبذلك تعرف بسهولة أين الخطأ .


:: إختصارات ( أزرار ) ::


هذه بعض الإختصارات التي سنتعامل معها حين كتابة البرامج :

1 – F7 : لكي تقوم بعملية بناء التطبيق ( Build ) و لمعرفة أخطاء برنامجك .. حيث أن الأخطاء ستظهر في نافذة ( Output ) في الأسفل .. error لعدد الأخطاء التي وجدت و warning من أجل التنبيه باحتمال وقوع خطأ في حالة معينة .. فإن لم يجد المترجم أخطاء فستجد برنامجك ضمن مجلد Debug في الملف الذي أنشأت به مشروعك .

2 – F4 : لمعرفة الأخطاء التي ظهرت نتيجة عملية البناء ( Build ) .. و تستطيع الانتقال بين الأخطاء بالضغط أيضاً على F4 .

3 – Ctrl + F5 : لتشغيل التطبيق و تجربته و الوقوف عند آخر سطر بكتابة جملة ( Press Any Key to Continue ) .. و فائدتها أن ترى النتائج و ما ظهر على الشاشة .

4 – F5 : نفس الوظيفة السابقة لكن لن يتم الوقوف في النهاية و سوف يغلق التطبيق بعد الإنتهاء من ترجمته ( تنفيذه ) .

5 – F10 و F11 : من أجل عملية التنقيح و التتبع ( Debug ) .. فعند استخدامهما تستطيع تتبع قراءة برنامجك مع المترجم سطرا سطراً .. و عندها ترى كيفية الدخول إلى التوابع و معرفة قيم المتغيرات خطوة خطوة و لهذه العديد من النوافذ سنأخذ بعضاً منها فيما بعد .. و هذه الطريقة مفيدة جداً لمعرفة مكان الخطأ و متى أخذت المتغيرات قيماً خاطئة .. و ماهي القيم التي أخذتها هذه المتغيرات .


:: الملفات المستخدمة للكتابة ::


سنأخذ نوعين من الملفات و ستسطيع إنشاءهم من File >> New ثم اختر قائمة Files :

1 – Source File : و هي الملفات المصدرية ( الرئيسية ) الخاصة بكتابة برنامجك و سوف نكتب جميع الأوامر هنا ( مبدئياً ) ثم نتعلم كيفية استخدام Header File .

2 – Header File : هي ملفات رأسية لكتابة رؤوس التوابع و الكائنات و غير ذلك و يمكن أن نطلق عليها اسم المكتبات .. فهي تحتوي على أوامر و أجزاء معينة تستدعى من قبل Source File .. ستتوضح الفكرة أكثر عن كيفية إنشاء هذه الملفات مع الأمثلة و لكن مبدئياً عليك معرفة أنها عبارة عن مكتبات جاهزة سواءً أنت أنشأتها أم شركة Microsoft .


:: النوافذ ::

1 – WorkSpace : و هي موجودة على يسار الشاشة .. هذه النافذة تظهر لك قائمتين الأولى File View و الثانية Class View .. الأولى من أجل عرض جميع ملفات برنامجك من ملفات مصدرية ( cpp. ) أو رأسية ( h. ) أو أي ملفات أخرى كالمصادر أو الموارد ( Resource File ) .
أما القائمة الثانية فهي من أجل عرض جميع أجزاء برنامج من توابع .. و كائنات .. و أعضاء هذه الكائنات من توابع و بيانات ( متغيرات ) و غير ذلك .. و باستطاعتك الإنتقال فوراً لمكان التابع أو أي جزء من برنامجك بالضغط عليه فقط .

2 – Output : موجودة في الأسفل .. لمعرفة أخطاء برنامجك ( شرحنا فكرتها ) .

3 – Debug Windows : هذه النوافذ من أجل عملية التنقيح و التتبع .. سنأخذها لاحقاً .


----------------------------------------

Wolf Sniper
29-01-2005, 09:35 PM
الأن مرحباً بك في عالم البرمجة خذ نفساً عميقاً و استعن بالله ثم اقرأ بتركيز .


---------- ---------- ---------- الدرس الأول ---------- ---------- ----------

اليوم : السبت ... التاريخ : 29 / 1 / 2005


:: بنية و مكونات اللغة ::

1 – المعرّفات ( Identifier ) :

هي عبارة عن أسماء تعرفها و تضعها في برنامجك و قد تكون :
- متغيرات ( Variable ) .
- ثوابت ( Constant ) .
- أسماء لتوابع .
- أسماء كائنات ( Calsses ) .

و لكن هناك شروط لتسمية هذه الأشياء و هي كالتالي :
أ – أن يبدأ الاسم بحرف أبجدي أو ( _ ) و أن لا يبدأ برقم .
ب – أن لا توجد فراغات بين الاسم .
ج – أن يكون طوله كحد أقصى 64 حرف .
د – أن لا يحتوي على رموز أو كلمات خاصة باللغة مثل ( const , float ) و أعتقد أن عدد هذه الكلمات 26 .
هـ - يفضل استخدام أسماء تدل على وظيفة هذا الاسم .

2 – المتغيرات ( Variable ) :

هي أسماء تحجز لمواقع في الذاكرة .. تحمل قيمة يمكن أن تتغير أثناء عمل البرنامج . مثلاً / إذا عرفنا متغير A و أعطيناه قيمة في بداية البرنامج و لتكن 5 .. فإننا نستطيع تغيير هذه القيمة إلى 10 مثلاً في أي مكان من البرنامج .

قاعدة 1 / إذا كنت تريد أن تخزن ( تحفظ ) أي معلومة أو قيمة أو أي شيء و تريد إجراء عملية على هذه القيمة مثل جمع عددين فيجب أن تضع هذه القيم ضمن متغيرات بشرط أنك تريد تغيير قيمتها فيما بعد أما إذا كنت تريد أن تكون ثابتة فاستخدم الثوابت .. و لكن المتغيرات سوف تموت ( تفقد قيمتها ) عند انتهاء البرنامج أو في مواقع أخرى سنتحدث عنها فيما بعد .


أمثلة على المتغيرات /

int a;
float b5;
bool fine;

قاعدة 2 / لتعريف متغير .. نضع نوع المتغير أولاً ثم اسمه ثم الفاصلة المنقوطة ( انظر الفقرة رقم 4 لمعرفة الأنواع ) و من الممكن إعطاء قيمة ابتدائية أيضاً .. مثال :

int a = 5;
قاعدة 3 / يمكن تعريف عدة متغيرات معاً من نفس النوع بحيث تفصل بين كل متغير و أخر فاصلة .. مثال :

int a,b,c;

3 – الثوابت ( Constant ) :

نفس المتغيرات و لكن قيمتها لا تتغير طوال فترة عمل برنامجك .. و نضع كلمة const لتعريفها قبل نوع هذا الثابت .. و يجب أن تضع قيمة عند تعريف الثابت عن طريق علامة يساوي ( = ) .

أمثلة على الثوابت /

const int a = 5;
const float b = 5.5;


حيث أن a و b ثوابت .

4 – الأنواع ( Types ) :

هي عبارة عن المجال الذي سوف تأخذه القيم .. و كل نوع له مساحة معينة في الذاكرة .

قاعدة / لا تستخدم أنواع لأحجام كبيرة من القيم و البيانات من أجل بيانات صغيرة الحجم و لكن استخدم المناسب منها .. لأن ذلك يضعف برنامجك و يهدر الذاكرة .. حيث يجب عليك أن تتوقع أكبر قيمة ممكن أن يأخذها المتغير .

و هي كالتالي /

أنواع حرفية :
- char : لتخزين حرف واحد فقط .. و حجمه 1 بايت في الذاكرة على ما أعتقد .
- المصفوفات ذو النوع char : لتخزين سلسلة حرفية ( أكثر من حرف ) .. و حجمها في الذاكرة على حسب شكل و حجم المصفوفة ( سنأخذها فيما بعد ) .

أنواع رقمية ( صحيحة ) :
- int : لتخزين قيم عددية يصل أقصى عدد يمكن أن تحمله إلى 2.148.000.000 .
- long int : نفس int و لكن يأخذ قيم أكبر .
- unsigned int : نفس int و لكنه لا يأخذ قيم سالبة و لكن يأخذ قيم موجبة أكبر تصل إلى 4.300.000.000 .
و ممكن أن نضع أيضاً الكلمة unsigned قبل النوع long int ليصبح عدد صحيح طويل موجب .

أنواع رقمية ( حقيقية ) :
- float : لتخزين أعداد حقيقية ذات فواصل عشرية .
أمثلة على أعداد حقيقة / 2.5 .. 6.3 .. 9.0 .. و إذا لم يكن هناك عدد عشري فنضع صفر ليدل على أن العدد هو عدد حقيقي .
- double : نفس float و لكن يأخذ قيم أكبر .

أنواع منطقية :
- bool : و هو النوع الوحيد من أجل هذه القيم و يحمل قيمتين هما : true أو false .. و هذا النوع يفيدنا كثيراً في أوامر الشرط و من أجل عمليات الإختبار ( سنرى أمثلة توضح ذلك ) .

ملاحظة / هذه الأنواع هي أنواع جاهزة موجودة في اللغة و باستطاعتك وضع أنواع خاصة بك و ذلك عن طريق السجلات ( البنى struct ) و الكائنات .. انظر الفهرس .

5 – العمليات و المعاملات ( Operators ) :

- عمليات حسابية : ( * الضرب ) و ( - الطرح ) و ( / القسمة ) و (% باقي القسمة ) .


أمثلة /

n = a * b;
m = 9 – b;
h = 2.0 / f;
t = k % a;

ملاحظة / دائماً حاول استخدام هذه العمليات لأنواع متشابهة من المتغيرات تجنباً للأخطاء و انتبه إلى حجم الناتج و حدد نوعه على حسب هذا الناتج الذي تتوقعه .. و إذا كنت تريد تريد استخدام عملية باقي القسمة مثلاً لنوعين مختلفين فاستخدم التالي :
بفرض أن a عدد صحيح و b عدد حقيقي و c عدد حقيقي أيضاً .

c = (float)a % b;
لقد وضعنا النوع float بين قوسين قبل المتغير a .. و بذلك سيتعامل المترجم ( Compilor ) مع هذا المتحول على أنه من النوع float و لكن بشكل قسري و مؤقت و لن يتغير نوع هذا المتغير إلى float و لكن كما قلت سينظر إليه في هذا السطر فقط على أنه float .

الأن سنأخذ بعض المعاملات المهمة فقط و هي كالتالي :

pow(x, y)
sin(x)
cos(x)
abs(x)
sqrt(x)
حبث أن الأول للأس .. و الثاني لجيب الزاوية .. و الثالث لجيب تمام الزاوية .. و الرابع للقيمة المطلقة و الخامس للجذر التربيعي .. بحيث أن x هي القيمة التي نريد حساب عملية عليها .. و بالنسبة إلى المعامل pow فإن x تمثل الأساس و y تمثل الأس .


أمثلة /

y = pow( 5 , 3 ); // 125
z = sqrt( b ); // b = 9 z = 3
h = abs(-6); // h = 6

ملاحظة 1 / هذه المعاملات تحتاج إلى ضم المكتبة الخاصة بها و هي المكتبة math.h سنتعلم في نهاية هذا الدرس كيفية إضافة المكتبات إلى مشروعك .
ملاحظة 2 / من أجل ( // ) و ما بعدها .. تستخدم هذه العلامة من أجل التعليقات التي تحب أن تضيفها .. و فائدتها تكمن فيما إذا صممت برنامج و بعد مدة حاولت الرجوع إلى هذا البرنامج و قراءته فستفيدك هذه التعليقات التي أضفتها في فهم البرنامج أو حتى ممكن أن تستفيد منها في فهم برامج غيرك بحيث يوضح المبرمج بواسطتها هدف معين يريد الوصول إليه من تعليمة أو أمر معين .. و بالطبع وضعها اختياري ( انظر فقرة القواعد العامة ) .
ملاحظة 3 / يجب إسناد هذه المعاملات إلى متغيرات ففي الأمثلة السابقة سيخزن الناتج في هذه المتغيرات .. حيث أن كتابتها على سطر لوحدها سيسبب خطأ .. و سنعرف لماذا يجب إسناد هذه المعاملات إلى متغيرات عند دراستنا للتوابع التي تعيد قيمة .

اختصارات على العمليات :
1 – ( ++C ) : تعني أضف واحد على المتغير C .. ( هل عرفت من أين أتت تسمية اللغة .. حيث قاموا باستخدام هذا الرمز ليوضحوا أن هذه اللغة هي لغة مطورة عن لغة C ) .. فمثلاً إذا كانت قيمة C تساوي 10 فتصبح 11 .. و هذا الرمز يقابل العملية التالية :

c = c + 1;
2 – ( --D ) : تعني اطرح واحد من المتغير d .. و تقابل العملية التالية :

d = d – 1;
3 – إضافة أي عملية قبل رمز ( = ) :

أمثلة /

y += 2; // y = y + 2
y -= 5; // y = y – 5
y *= 6; // y = y * 6
y /= 9; // y = y / 9
y %= 8; // y = y % 8

و المثال الأخير يعني : قسّم قيمة y على 8 و ضع باقي القسمة في نفس المتغير y .. و هذا يعني أن قيمة y القديمة ستمسح و تخزن بدلاً منها قيمة باقي القسمة .
ملاحظات /
1- من الممكن أن تستخدم ما يلي : i++ أو ++i .. و الفرق سوف نأخذه في المثال العملي في نهاية هذا الدرس .
2- من الممكن أن تستخدم العملية c++ أو c-- في المتغيرات الحرفية char .. مثال :

char a = 'm';
a++;

عندها تصبح a تحمل الحرف n و هكذا .. و هذا يعني الانتقال إلى الحرف التالي .

- عمليات منطقية : و تستخدم في الشرط و هي كالتالي :
( ! ) للنفي و ( == ) للمساواة و ( =! ) لعدم المساواة و ( > و < ) لأكبر من و لأصغر من .. و ( >= و <= ) لأكبر من أو يساوي و لأصغر من أو يساوي .. و ( && ) تعني ( و ) .. ( || ) تعني ( أو ) .
أمثلة /

If ( x==5 )
If ( y!= 10 )
If( x==5 && y >= 6 )
If( x < 60 || y==20 )
If( !(x=10) ) // equal of : if( x!=10 )

قاعدة / الشرط لا ينتهي بفاصلة منقوطة .. و سنأخذ الشرط و التكرار في الدرس القادم إن شاء الله .

Wolf Sniper
29-01-2005, 09:53 PM
6 – الإسناد :

هو إعطاء قيمة لمتحول و بمعنى آخر هو تخزين قيمة معينة في موقع المتحول في الذاكرة .


أمثلة /

x = 10;
x = y; // x takes a value
x = (y+1) / b;

قاعدة / دائماً المتحول الذي على اليسار هو الذي يأخذ القيمة من الأخر .


أخطاء برمجية /

a = b+2 = c;

و لكن ممكن أن نضع :

int a,b;
int c = a = b = 4;

حيث سيأخذ كل من a و b و c القيمة 4 .

7 – القراءة و الكتابة ( cout , cin ) :

هذه الأوامر تحتاج لإضافة مكتبتها الخاصة و هي iostream.h .. و تلفظ i لوحدها و تعني Inbut و O لوحدها و تعني Output و من ثم Stream .. بالمختصر تعني قنوات الدخل و الخرج .. انظر المثال التطبيقي لمعرفة كيفية استخدام هذه المكتبة و كيفية ضمها إلى مشروعك .

- cout : من أجل الإظهار على الشاشة و تلفظ c ثم out .. و تستخدم لكتابة قيم المتغيرات على الشاشة أو أي قيم معينة أخرى .. و هي كالتالي :
1 – قيم حرفية أو رقمية .. مثال :

cout << 7;
cout << "Hello";
2 – قيم المتغيرات و المتحولات .. مثال :

cout << a;
cout << b;
3 – عبارة حسابية .. مثال :

cout << (a+b) / 2;
cout << a-b;
و لـ cout عدة قواعد :
1 – يجب استخدام التوجيه ( >> ) بعد كل عملية إخراج .. مثال :

cout << a << b << c;
2 – ممكن أن نستخدم الأمر endl و يعني ( End Line ) من أجل نزول سطر على الشاشة .. مثال :

cout << 5 << endl;
3 – ممكن أن نستخدم الأوامر التالية : "n\" و "t\" .. حيث أن الرمز الأول يؤدي نفس عمل endl أما الثاني فيعمل على الإزاحة إلى اليمين بمقدار 7 أحرف ( على الشاشة ) .
4- من أجل الفهم .. انظر إلى اتجاه هذا التوجيه .. إذا كان اتجاهه يساراً ( إلى الخارج ) فهذا يعني أن القيم التي على اليمين سوف تخرج على الشاشة .. أما في cin فسنستخدم التوجيه ( << ) و اتجاهه إلى اليمين ( إلى الداخل ) و هذا يعني أننا سوف ندخل قيم على المتحولات الموجودة على اليمين .. إن شاء الله تكون الفكرة وضحت .

- cin : من أجل إعطاء المستخدم ( و ليس المبرمج ) قيم لمتحولات معينة عن طريق شاشة الإظهار .. و تستخدم للمتحولات المعرفة فقط .. و تلفظ c لوحدها ثم in .. مثال :

cin >> a;
cin >> b >> c;

لنشرح المثال الثاني : سوف يتوقف البرنامج و يظهر وميض من أجل أن يدخل المستخدم قيمة .. و بفرض أن b و c متحولات عددية .. فإذا أدخلنا 4 ثم مسافة ثم 5 فستأخذ b القيمة 4 و c القيمة 5 .

ملاحظة / لا يمكن استخدام cin مع الثوابت .. لأنه كما قلنا أن الثوابت لا تتغير قيمتها أبداً طوال فترة عمل البرنامج .


:: قواعد عامة ::

قاعدة عامة 1 / أي سطر من الأوامر ( Code ) يجب أن ينتهي بفاصلة منقوطة ( ; حرف الكاف ) .. ما عدا بعض الأماكن ستعرفها في هذه الدروس ( أخذنا الشرط في هذا الدرس ) .. و هي تعني انتهاء سطر الأوامر .
قاعدة عامة 2 / نستخدم أقواس المجموعات ( } و { ) في عدة أماكن و هي كالتالي :
لتحديد بداية و نهاية كتلة معينة مثل التوابع و الكائنات و المرقمات ( enum ) .. و في if الشرطية إذا كنت تريد تنفيذ مجموعة من الأوامر عند تحقيق شرط معين .. و في إعطاء قيم ابتدائية للمصفوفات .. سنأخذ كل تلك الأمور .
قاعدة عامة 3 / الحاصرات الفردية ( ' حرف الطاء ) و المزدوجة ( " حرف الطاء أيضاً و لكن مع زر Shift ) .. هذه الحاصرات من أجل المتغيرات النصية ( الحرفية ) و ليست الرقمية فهناك فرق لذلك استخدم هذه الحاصرات دائماً و أبداً مع القيم الحرفية .. فإذا كنت تريد تخزين قيم حرفية (اسم مثلاً ) في مصفوفة ( سنأخذ هذا الموضوع لاحقاً ) و كان الاسم يحتوي على أكثر من حرف فاستخدم الحاصرات المزدوجة أما إذا كانت القيمة عبارة عن حرف واحد فاستخدم الحاصرات الفردية .. و بشكل عام يمكننا القول بأن هذه الحاصرات تستخدم في :
1 – إعطاء قيم حرفية ابتدائية أو عند إسناد هذه القيمة للمتغيرات الحرفية .. مثال :

char a = 'G';
char b;
b = 'H';

حيث أننا أعطينا المتغير a قيمة ابتدائية ( أي عند التعريف ) و هو الحرف G .. و قمنا بإسناد القيمة H للمتغير b .
2 – لكتابة جمل على الشاشة باستخدام قنوات الدخل و الخرج iostream.h .. مثال :

cout << "Hello";
أما لطباعة أرقام على الشاشة فنكتب الرقم بدون حاصرات :

cout << 4;
قاعدة عامة4 / ( // ) من أجل التعليقات .. فأي جملة تضعها قبل هذه العلامة فلن يتم قراءتها من قبل المترجم بل سيتجاهلها .. و هناك علامة أخرى و هي ( */ ) للبداية و ( /* ) للنهاية ..مثال:

/* int a;
a =5;*/

// int a;
قاعدة عامة 5 / لغة ++C تفرق بين الحرف الصغير و الكبير .. لذلك فالمتغير a يختلف عن المتغير A .. مع أنهما نفس الحرف و لكن الأول صغير و الثاني كبير .. و بالتالي هما متغيران مختلفان .. و على العموم حاول مبدئياً كتابة متغيراتك بالأحرف الصغيرة .

قاعدة عامة 6 / إن الكلمات الخاصة باللغة و عددها حوالي 26 كلمة و التي تظهر باللون الأزرق .. يجب أن تكون بالأحرف الصغيرة .. مثل / const , if , struct , class , #include , for , while , int , float , double و غيرها .

قاعدة عامة 7 / إذا كان سطر الأوامر الذي تكتبه طويلاً تستطيع أن تضعه على السطر التالي دون أية مشاكل أو أن تضع مسافات بين الأوامر و التوجيهات .. مثال :

cout <<b/a << "Hello"
<< z << 'h' << 45;

و لكن بشرط أن لا تفصل بين كلمات الأوامر و أسماء المتغيرات فمثلاً لو كان عندنا متغير student فلا يجوز أن تضع التالي :

int student;
stud ent = 5;

قاعدة عامة 8 / تستطيع أن تجمع بين أكثر من سطر و تضعهم في سطر واحد .. بحيث يفصل بين كل سطر فاصلة منقوطة .. مثال :

cout << "Enter number 1 : "; cin >> a;
قاعدة عامة 9 / هناك مسائل سوف تجد أكثر من طريقة لحلها .. لذلك استخدم الطريقة التي توفر أقل كمية من حجز المتغيرات في الذاكرة و أسرعها .

Wolf Sniper
29-01-2005, 09:57 PM
:: أمثلة تطبيقية ::


أخيراً دخلنا إلى القسم العملي .. سأضع هنا ثلاثة أمثلة حول ما أخذناه في القسم النظري .

ملاحظة / لتشغيل الأمثلة أنشىء ملف مصدري cpp. ثم اكتب المثال .. و بعد ذلك قم ببناء التطبيق بواسطة F7 و بعد الإنتهاء من عملية البناء و التأكد من عدم وجود أخطاء اضغط على Ctrl + F5 .. و إذا وجدت أية أخطاء فاضغط على F4 لمعرفتها و أخبرني بالأخطاء التي ظهرت عندك إن وجدت .. لأن الأمثلة صحيحة و قد جربتها .


المثال 1 /

سنقوم بجعل المستخدم يدخل عددين صحيحين و من ثم نقوم بطباعة مجموعهما .. لذلك دعونا نناقش متطلبات البرنامج ( يجب أن تفعل ذلك عند حل كل مثال أو أي مسألة طبعاً مع الوقت ستصبح هذه الأمور بديهيات ) :
1 - كم متغير نحتاج ؟ ثلاثة .. الأول من أجل تخزين العدد الأول و الثاني من أجل تخزين العدد الثاني و الثالث من أجل المجموع .
2 – ما هي الأوامر التي نحتاجها ؟ أوامر الدخل و الخرج فقط .
3 – ما هي المكتبات التي نحتاجها ؟ iostream.h لأننا نريد استخدام cout و cin .

#include <iostream.h>
void main()
{
int a, b, sum;
cout << "Enter Number 1 : ";
cin >> a;
cout << "Enter Number 2 : ";
cin >> b;
sum = a + b;
cout << sum << endl;
}

شرح المثال :

1 – في السطر الأول قمنا بإضافة المكتبة iostream.h إلى مشروعنا .. و ذلك بواسطة الأمر #include .. و العلامة # عبارة عن توجيه خاص بالمترجم و هو بذلك يفهم الأمر include .. و بشكل عام إذا كنت تريد إضافة أي مكتبة لمشروعك فاستخدم الأمر #include ( سنستخدم هذا الأمر كثيراً ) و بعد ذلك ضع اسم المكتبة بين علامتي ( < > ) .
قاعدة 1 / إذا كانت المكتبة من المكتبات الجاهزة فاستخدم الأقواس السابقة ( < > ) .. أما إذا كانت المكتبة أنت من قمت بكتابتها فهناك طريقتين لضمها سنأخذ واحدة و هي أن تضع المكتبة ( h. ) في نفس مجلد مشروعك يعني في نفس المكان الموجود فيه التطبيق ( exe. ) و من ثم ضع بدلاً من الأقواس ( < > ) ضع الحاصرات المزدوجة ( " " ) .. مثال :

#include "MyLib.h"
قاعدة 2 / لا توجد فاصلة منقوطة في نهاية سطر إضافة المكتبات .

2 – في السطر الثاني عرفنا تابع اسمه main ( سنأخذ التوابع فيما بعد ) و هو التابع الأساسي الذي سيبدأ تنفيذ البرنامج به .. و يحب أن يحتوي كل تطبيق على هذا التابع .. و لكن ما معنى الكلمة التي تسبق اسم التابع ؟ سنأخذها عند دراسة التوابع لذلك احفظها هكذا مبدئياً .
قاعدة 1 / يجب أن يحتوي كل مشروع على تابع main واحد فقط بحيث سيبدأ تنفيذ برنامجك من أول سطر فيه .
قاعدة 2 / يمكن أن يحتوي مشروعك على العديد من التوابع .
قاعدة 3 / لتعريف تابع نضع نوعه ( احفظ الأن void ) ثم اسم التابع ثم قوسين ( سنعرف ما فائدة هذين القوسين في درس التوابع ) .. و من ثم نضع أقواس المجموعات ( { } ) لتحدد جسم هذا التابع و لتحديد بدايته و نهايته .
قاعدة 4 / لا توجد فاصلة منقوطة في نهاية سطر تعريف التابع أو في نهاية أسطر أقواسه .

3 – في السطر الثالث عرفنا ثلاثة متغيرات صحيحة a لتخزين العدد الأول و b لتخزين العدد الثاني و sum للعدد الثالث .. و طبعاً تستطيع افتراض ما تريد من الأسماء و تخزين الأعداد بأي متغيرات تريد .. فمثلاً تستطيع جعل العدد الأول يخزن في المتغير b و العدد الثاني يخزن في المتغير a عن طريق التبديل بين a و b في أوامر الإدخال cin .

4 – بعد ذلك قمنا بكتابة جملة على الشاشة تخبر المستخدم بأننا نريد منه إدخال العدد الأول .. فعند إدخاله قيمة ستحفظ في المتغير a و ذلك بعد الضغط على زر Enter .. و من ثم سنطلب منه إدخال العدد الثاني و سنعطي هذه القيمة لـ b .

5 – فيما بعد أسندنا مجموع المتغيرين a و b إلى المتغير sum ( أي خزنا ناتج عملية الجمع في المتغير sum ) .

6 – بعد ذلك طبعنا ناتج عملية الجمع على الشاشة مع النزول سطر واحد في النهاية لكي تظهر جملة Press any Key to Continue على سطر لوحدها ( لأن هذه الجملة تظهر عند في نهاية تنفيذ التطبيق دائماً ) لذلك جرب عدم وضع endl في نهاية أمر الإخراج و انظر إلى النتيجة .


----------------------------------------


المثال 2 /

سنتعرف على أهمية استخدام الثوابت في المصفوفات و الحلقات في الدروس القادمة ..و الأن سنأخذ مثالاً على بعض المعاملات و الإختصارات .
في هذا المثال نريد من المستخدم أن يدخل عدد صحيح فنحسب له جذره و مربعه و قيمته المطلقة و كل جواب نريده على سطر .. فيكون مناقشة الحل كالتالي :
1 – كم متغير نحتاج ؟ واحد .. لماذا ؟ لأنه لا فائدة من تخزين كل عملية في متغير انظر الشرح لتعرف لمـاذا .
2 – ما هي الأوامر التي نحتاجها ؟ أوامر الدخل و الخرج بالإضافة إلى المعاملات الجاهزة للجذر التربيعي و التربيع و القيمة المطلقة .
ملاحظة مهمة / انظر إلى العملية التي تريدها ( الجذر التربيع مثلاً ) .. هل هي موجودة ضمن مكتبة جاهزة أم لا ؟ إذا كانت موجود فالحمدلله .. أما إذا لم تكن موجودة فيجب عليك كتابتها بنفسك عن طريق التوابع .. و نصيحتي لك أن لا تحاول أن تعيد كتابة معاملات أو عمليات جاهزة ضمن مكتبات اللغة لأنك ستضيع وقتك و ستكون النتيجة بمستوى أقل فعالية من العمليات و المكتبات الجاهزة .. إلا إذا كنت تريد كسب خبرة من كتابة هذه المعاملات و العمليات .. و سنتعلم كيفية كتابة بعض هذه المعاملات في موضوع التوابع إن شاء الله .
3 – ما هي المكتبات التي نحتاجها ؟ iostram.h لقنوات الدخل و الخرج .. math.h من أجل العمليات و المعاملات .

#include <iostream.h>
#include <math.h>
void main()
{
int a;
cout << "Enter One Number : ";
cin >> a;
cout << "Sqrt = " << sqrt(a) << endl
<< "Pow = " << pow(a, 2) << endl
<< "Abs = " << abs(a) << endl;
}


شرح المثال :

لماذا وضعنا متغير واحد و لم نضع أربعة ؟ ما دمنا قادرين على بناء هذا المثال بواسطة متغير واحد فما هو الأفضل حجز مكان في الذاكرة لأربع متغيرات أم لمتغير واحد ؟ ( انظر القاعدة العامة 9 ) .. لذلك تجنب دائماً ( إن استطعت ) الإكثار من تعريف المتغيرات التي لا فائدة منها .. لأنه إذا كان برنامجك كبيراً فسيصبح سيئاً و بطيئاً .. و نحن نهدف إلى سرعة التطبيق لا إلى تبطيئه .. و لكن بما أنكم ما زلتم في البداية فلا خوف في ذلك مبدئياً لأن أمثلتنا صغيرة الحجم .. نعود للشرح :
1 – قمنا أولاً بإضافة المكتبتين iostram.h و math.h إلى برنامجنا من أجل استخدام الأوامر التالية :

pow(x, y)
sqrt()
abs()
cin
cout

2 – وضعت بعد ذلك التابع الأساسي main .
3 – عرّفنا متغير من النوع الصحيح و هو الذي سيخزن الرقم الذي سيدخله المستخدم .
4 – طلبنا من المستخدم إدخال هذا العدد .. و من ثم وضعناه في المتغير a .
5 – قمنا بطباعة عبارة على الشاشة لكل عملية لكي توضح معنى الرقم الذي سوف يأتي بعدها .. و من ثم وضعنا العملية ( راجع فقرة قنوات الدخل و الخرج cout ) ثم نزلنا سطراً و هكذا .
ملاحظة 1 / انتبه إلى مكان الفاصلة المنقوطة في آخر عملية طباعة على الشاشة .. فقط في النهاية و هي تعني نهاية أمر الإخراج cout .. و لاحظ أننا نستطيع الكتابة على سطر آخر طالما أننا لم نضع الفاصلة المنقوطة أو نستطيع حتى المتابعة على نفس السطر و لكن سيصبح السطر طويلاً بذلك .
ملاحظة 2 / استخدمنا أمر cout واحد فقط .. فليس هناك فائدة من كتابتها أكثر من مرة من أجل كل عملية .
ملاحظة 3 / استخدمنا endl من أجل عملية تنظيم الإجابات بوضع كل جواب على سطر .


----------------------------------------


المثال 3 /

نريد من المستخدم إدخال حرف فنطبع له الحرف الذي يليه .. فتكون مناقشة الحل كالتالي :
1 – ماهي المتغيرات التي نحتاجها ؟ واحد فقط .. و هو متغير حرفي من النوع char .
2 – ما هي الأوامر التي نحتاجها ؟ قنوات الدخل و الخرج بالإضافة إلى بعض العمليات .
3 – ما هي المكتبات التي نحتاجها ؟ iostream.h فقط .

#include <iostream.h>
void main()
{
char a;
cout << "Enter a Letter : ";
cin >> a;
cout << ++a << endl;
}

شرح المثال :

سأشرح فقط ( a++ ) لأن بقية مشروحة في الأمثلة السابقة .
1 – قلنا بأن هذه العملية تعني أضف واحد إلى المتغير a إذا كان متغير عددي .. أما إذا كان متغير حرفي فستعطينا هذه العملية الحرف الذي يلي الحرف الذي يحتويه a .. و لكننا وضعنا علامتي ++ قبل المتغير و ليس بعده .. و عمل آخر سطر هو كالتالي :
أوجد الحرف الذي يلي حرف a و خزنه في a ثم اطبعه على الشاشة .. حيث أننا نقرأ من اليسار إلى اليمين .
2 - أما لوكانت العملية ++ بعد المتغير a فشرح آخر سطر هو كالتالي :
اطبع قيمة a ثم أوجد الحرف الذي يلي حرف a و خزنه أيضاً في a .

فلو فرضنا أن المستخدم أدخل الحرف L مثلاً .. ففي الحالة الأولى سيظهر على الشاشة الحرف M .. أما في الحالة الثانية فسيظهر الحرف L نفسه .. جرب العملية الثانية بنفسك و شاهد النتائج .


---------- ---------- ---------- نهاية الدرس الأول ---------- ---------- ----------

ملاحظة أخيرة / من أجل عملية تنظيم و ترتيب برنامجك .. قم ضمن أي كتلة ( جسم ) تابع أو ضمن جمل if الشرطية و غيرها .. بعمل إزاحة عن طريق الزر Tab قبل بداية أي أمر .. انظر الأمثلة .

في هذا الدرس لا يوجد واجب .. فقط قم بحل الأمثلة العملية التي كتبناها و حاول قراءة الدرس أكثر من مرة و بتركيز .. آمل أن يكون الدرس سهل و مفهوم .. و لمن عنده أي استفسار فأنا جاهز .. أريد تعليقاتكم على الدرس و رأيكم به و بصعوبته أو سهولته .. بالتوفيق .

سماح ريمي
02-02-2005, 06:19 PM
بسم الله الرحمان الرحيم

السلام عليكم ورحمة الله وبركاته

اخي العزيز

فكرة تعليم البرمجة منك والله فكرة هايلة وتستاهل عليها كل تقدير واحترام

خصوصا ان اغلب المشاركات تقتصر على التعريف السطحي للغة البرمجة ++C

والكثير من الاخوة الكرام يكتبو اي اشي ولا واحد فيهم بيديك الشرح التام لمختلف مراحل البرمجة ولا حتى الشرح المعمق لكل ما يجب تعلمه من اساسيات هذه اللغة

وانا رايحة اديك مثال على الحكي هيدا من خلال الدرس تبعك هيدا



#include <iostream.h>
void main()
{
int a, b, sum;
cout << "Enter Number 1 : ";
cin >> a;
cout << "Enter Number 2 : ";
cin >> b;
sum = a + b;
cout << sum << endl;

اخي الكريم انا بقدرلك الجهد الي انت تبذلو واتمنى ان ربنا يوفيك كل الجزاء عليه

وانا من خلال مدخلتي مش بحاول انقص من قيمة ما قمت به بالعكس لكن بحاول اثمن الي انت بتعلمو لاخوتي بالمنتدى

وعلى هذا الاساس انا احب اكون بجنبك وبجنب كل واحد يحاول يعمل عمل كريم مثل ما عملت انت

انا بلاحظ من خلال الدرس انك ابتديت بامر هام في تشكيل الدروس
لكن بشوف كمان انك مشرحتش بطريقة مفصلة مثلا كيفية تشكيل الكود هيدا

انا الان بحاول اترجم كتاب مبدع البرمجة بلغة ++C بجارن ستروي ستروب من الفرنسي للعربي

واكتشفت من خلال عملية الترجمة ان هيدا الكتاب فعلا بيفتحلك باب التعلم من اول نقطة

وبعدها بيتدرج بادخال على مراح من خلال الامثلة اساسيات هذه اللغة

وكمان لحظت ان الدروس بتعتمد على بعضها البعض وان الفهم بيتم بشكل هايل من خلال هذه المراحل والامثلة

ومن بين المراحل الي بيها يتم تشكيل برنامج بلغة ++ C هناك نمط البرمجة المرحلية ونمط البرمجة المحورية اي

Programmation procédurale et la Programmation moduler
نمط البرمجة المرحلية بيحاول يفهم المبتدى الاساسيات المعمول بيها من خلال البرنامج بتعريفات وبديهيات وشروح وامثلة عبر مراحل لهيدا السبب سميت نمط البرمجة المرحلية

اولا بيعرف الاصناف القاعدية Typ مثل
int
char
flaot
bool
وبيعرف كمان معاملات البرمجة Operateur مثل
==
=!
>
<
=>
=<
=
وبيحاول من خلال هذه التعريفات والعمليات المسموح بيها من خلال الدرس اعطاء مفهوم مبسط لمتعلم البرمجة
هذا المفهوم بيكون اولي وشبه سطحي لكن مفيدا جدا لان كل عنصر من الي سبق ذكره سيتم التطرق اليه بالتفصيل عبر مختلف مراحل الدروس والامثلة القادمة
وبهذا سيكون الرابط واضح لمفهوم ان البرمجة بلغة ++ C تمر عبر مراحل وكل مرحلة جديدة تضيف وتوسع مجال التحصيل للمرحلة التي جاءت قبلها
انا بوعدكم المرة القادمة اني بنزل بالمنتدى الفصل الاول من الترجمة الي عملتها لهذا الكتاب
واتمنى من كل قلبي انها رايحة تزيد في تبسيط تعلم هذه اللغة
كما اني بطلب من الاخ الكريم ادراج مساحة مخصصة لمناقشة الدروس واثرائها بالامثلة التطبيقية والحلول
قبل طرحها ونشرها بامنتدى هيك بيكون شكل تقديم الدروس احترافي اكثر منو عرض دروس مش راح يفهم المبتدى اشي
والله شاهد اني بحاول اساهم مع اخوتي بهيدا المنتدى تطوعا لوجهه وليس استعراض

وفي الايام القليلة القادمة انشاء الله بيكون اول عمل الي لذالكم الحين اطلب من الله تعالى التوفيق لكل طالب علم

السلام عليكم ورحمة اللع تعالى وبركاته

UAE Naruto
03-02-2005, 09:11 AM
::

::

السلام عليكم و الرحمة.....:girl:

ممنوع الرد فب هذا الموضوع و لو شو كان الرد!!!!!!

سيتم ابلاغ المشرف...و بسرعة هاي المرة

لأخوي SOU (لول عالسريع أكتب :09: ) أرجوك امسح ردي هذا و الرد إلي قبلي بسرعة

و سلامتكم.....:ciao:

::

::

Wolf Sniper
05-02-2005, 01:50 PM
بسم الله الرحمن الرحيم

:: مقدمة ::

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


---------- ---------- ---------- الدرس الثاني ---------- ---------- ----------
اليوم : السبت ... التاريخ : 5 / 2 / 2005

:: تعليمات التحكم ::

هناك خمسة أنواع لتعليمات التحكم .. و سنأخذها كلها و هي كالتالي :
1 – العبارة ( if ) .
2 – العبارة ( if/else ) .
3 – العبارة ( switch ) .
4 – العبارة ( for ) .
5 – العبارة ( while ) .
6 – العبارة ( do/while ) .

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

1 – العبارة ( if ) :

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

قاعدة /

if ( condition )
statement;

حيث أن condition هو الشـرط و يكون النـاتج من سطر if إمـا true فينفـذ التعليمـات أو false فيتجاهلها و ينتقل للسطر التالي .. و statement هي التعليمة أو التعليمات التي ستنفذ إذا ما تحقق الشرط فقط .


مثال 1 / لشرط ينفذ تعليمة واحدة :

if ( x==0 )
cout << "Error\n";

ملاحظة 1 / من أجل ( == ) راجع العمليات المنطقية في الدرس الأول .. لأنها تختلف عن عملية الإسناد التي تسند قيمة لمتغير .. فهنا لا يتم إسناد الصفر إلى x بل يتم إختبار قيمته هل هي الصفر أم لا .
ملاحظة 2 / إذا كانت قيمة x تساوي الصفر أصبح الناتج true و بالتالي ينفذ تعليمة cout .. أما لو كانت x لا تساوي الصفر أصبح ناتج الإختبار false و بالتالي لن يتم تنفيذ تعليمة الإخراج .
ملاحظة 3 / سطر if لا يحتوي على فاصلة منقوطة .


مثال 2 / لشرط ينفذ عدة تعليمات :
بفرض أن a هو البسط .. و b المقام

if ( b!=0 )
{
cout << "We can devide …\n";
cout << a/b << endl;
}

ملاحظة 1 / إذا أردنا تنفيذ أكثر من تعليمة إذا ما تحقق الشرط فيجب وضع أقواس الكتل ( { } ) .. و هذه الأقواس سنستخدمها كثيراً في مواضيع أخرى .. مثل المصفوفات و التوابع و السجلات و الكائنات .. إلخ .
ملاحظة 2 / في حالة القسمة على صفر .. تعرفون في الرياضيات أن القسمة على الصفر لا تجوز .. و بالتالي يجب معالجة هذا الأمر بواسطة عملية إختبار .. و إلا إن حدث و قسمت على صفر فسيحدث خطأ في برنامج و سيقفل .. حاول أن تقسم على الصفر و لاحظ النتيجة .

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

2 – العبارة ( if/else ) :

يتم إختبار شرط فإذا تحقق ( أي كان ناتج الإختبار true ) يتم تنفيذ مجموعة من التعليمات .. أما إذا لم يتحقق ( أي إذا كان ناتج الإختبار false ) فيتم تنفيذ مجموعة أخرى من التعليمات .

قاعدة /

if ( condition )
{
Statement;
}
else
{
Statement;
}

ملاحظة 1 / وضعنا الأقواس ( { } ) لأنه سيتم تنفيذ أكثر من تعليمة في كل من if و else .. أما لو كنا نريد تنفيذ تعليمة واحدة فقط فلا نستخدم الأقواس ( { } ) .
ملاحظة 2 / condition هو الشرط .. و statement التعليمات .
ملاحظة 3 / لا حظ أنه لا توجد أيضاً فاصلة منقوطة بعد else .


مثال /

if ( x==0 && b>=0 )
cout << "We can divide\n";
else
cout << "We can not divide\n";

ملاحظة 1 /إذا كان ناتج الإختبار true فيتم تنفيذ تعليمات if .. أما إذا كان ناتج الإختبار false فيتم تنفيذ تعليمات else .
ملاحظة 2 / من أجل ( && ) راجع العمليات المنطقية في الدرس الأول .. و من أجل ناتج عملية الإختبار تعامل هذه العلامة و العلامة ( || ) تماماً كما في الرياضيات ( التقاطع و الإتحاد ) :
1 – ( && ) وتعني and : إذا كان الطرف الأول و الثاني true فناتج الإختبار true فيتم تنفيذ التعليمات التي تحت if .. أما لو كان أحد طريفيها false أو كلاهما فسيكون ناتج الإختبار false و بالتالي سينتقل إلى تعليمات else .
2 – ( || ) و تعني or : إذا كان أحد الطرفين true فناتج الإختبار true فيتم تنفيذ تعليمات if .. أما لو كان كلا الطرفين false فناتج الإختبار false و بالتالي سيتم تنفيذ تعليمات else .

ملاحظة 3 / إذا كنت تريد استخدام شرط يحتوي على أكثر من عملية منطقية ( && أو || ) فحاول استخدام الأقواس من أجل التمييز و لأجل معرفة ناتج الإختبار ( هناك طريقة أخرى و هي أولوية العمليات ) .


مثال /
بفرض أن x تساوي 5 و b تساوي 10

if ( ( x>=0 && x<=10 ) || b<0 )

عندها يكون ناتج الإختبار true لأن :
1 – x تقع ضمن المجال 0 و 10 .. و بالتالي ناتج الطرف الأيسر لـ ( || ) يساوي true .
2 – b أكبر من الصفر و بالتالي الطرف الأيمن لـ ( || ) يساوي false .
3 – أحد الطرفين يساوي true و بالتالي ناتج الإختبار true .

للإستزادة :
أما لو أردنا معرفة أولويات العمليات المنطقية && و || .. فتعامل مع && مثل الضرب و || مثل الجمع .. و بالتالي سيتم اختبار المعاملات && أولاً مهما كان عددها ثم سيتم تنفيذ المعاملات || .


مثال /
بفرض أن x تساوي 5 و y تساوي 10 و z تساوي 100

if ( x>=0 && x<=10 || y<0 && z>100 )
عندها يكون ناتج الإختبار true لأن :
1 – x تقع ضمن المجال 0 و 10 .. و بالتالي ناتج الطرف الأيسر لـ ( || ) يساوي true .
2 – y أكبر من الصفر و z ليست أكبر من 100 بل تساوي 100 لأن هناك فرق .. و بالتالي ناتج الطرف الأيمن لـ ( || ) يساوي false .
3 – أحد الطرفين يساوي true و بالتالي ناتج الإختبار true .

من أجل تسهيل الفهم :
في هذا المثال تخيل أن الإختبار كله عبارة عن معادلة كثير حدود حيث أن الضرب أقوى من الجمع .. حيث أنك ستقوم بإيجاد ناتج كل حد ثم ستقوم بجمع تلك الحدود ( للتوضيح فقط ) .

قاعدة / من الممكن أن نضع أكثر من عبارة if داخل عبارة واحدة لـ if .. مثال :

if ( x==0 )
cout << "X = 0 \n";
else if ( x==1 )
cout << "X = 1 \n";
else if ( x>1 )
cout << "X > 1 \n";

و بصفة عامة نستطيع وضع عدة عبارات مداخلة مع بعضها البعض .. فمثلاً نستطيع وضع عبارة if ضمن عبارة for و هكذا ..

Wolf Sniper
05-02-2005, 02:41 PM
3 – العبارة ( switch ) :

يتم فيها التحقق من القيم التي يحتويها متغير ( متحول ) ما .. فإذا كانت إحدى القيم التي يحتويها ذلك المتحول موجودة ضمن القيم التي افترضناها يتم تنفيذ تعليمات معينة .. أما إذا لم توجد قيمة ذلك المتحول ضمن تلك القيم فيتم الإنتقال إلى العبارة default و ستنفذ تعليماتها .. أما إذا وجدت قيمته ضمن أحد القيم فلن يتم تنفيذ العبارة default .

قاعدة /

switch( Variable )
{
case Value1 : Statements;
case Value2 : Statements;
case Value3 : Statements;
default : statements;
}
1 – Variable : متحول ما مهما كان نوعه ( صحيح أو حقيقي أو منطقي ... ) و يوضع بين قوسين بعد العبارة switch .
2 – Value : قيمة ما نريد معرفة إذا كان المتغير يحتويها .. و توضع بعد العبارة case ( انتبه للقيم الحرفية بحيث يجب أن تضع الحاصرات إما المزدوجة أو الفردية .. راجع الدرس الأول ) .
3 – Statements : ممكن أن تكون تعليمة واحدة أو عدة تعليمات .
4 – default : يتم تنفيذها فقط عند عدم وجود أي قيمة من تلك القيم في المتحول .
5 – يجب وضع : الأقواس ( { } ) إذا أردنا التحقق من أكثر من قيمة .. أما لو كنا نريد التحقق من قيمة واحدة فممكن أن لا نضع تلك الأقواس ( مثل العبارة if ) .. و لكن إن تحققنا من قيمة واحدة و وضعنا أيضاً العبارة default فعندها أصبحت switch تحتوي على أكثر من تعليمة و بالتالي يجب وضع الأقواس ( { } ) .

ملاحظة هامة / إذا تحققت إحدى القيم فسيتم تنفيذ جميع التعليمات الموجودة ضمن تلك القيمة و القيم التي تلي تلك القيمة بالترتيب .. و إذا أردنا إلغاء تلك العملية فيجب علينا إضافة الأمر break .


مثال هام /
بفرض أننا جعلنا المستخدم يدخل قيمة لعدد صحيح ضمن المتحول n لتحديد أيام الأسبوع بحيث أن العدد 1 ليوم السبت و 2 للأحد ... إلخ :

switch( n )
{
case 1 : cout << "Sat\n"; break;
case 2 : cout << "Sun\n"; break;
case 3 : cout << "Mon\n"; break;
case 4 : cout << "Tue\n"; break;
case 5 : cout << "Wed\n"; break;
case 6 : cout << "Thu\n"; break;
case 7 : cout << "Fri\n"; break;

default : cout << "Error…\n";
}
ملاحظة / من أجل توضيح عمل break .. بفرض أن المستخدم أدخل الرقم 4 و بفرض أننا لم نكتب الأمر break عندها سيتم كتابة أيام Tue و Wed و Thu و Fri على الشاشة كما قلنا .. هنا تكمن أهمية هذا الأمر فهو يجعل المترجم يتجاهل باقي القيم التي تلي القيمة التي نريدها و يخرج إلى خارج كتلة ( عبارة ) switch و سيتم تنفيذ التعليمة التي تلي تلك البنية أو الكتلة.. وبالتالي ستظهر Tue فقط على الشاشة .

قاعدة / يستخدم الأمر break للخروج من الكتلة التي يوجد فيها فقط .. و يمكن أن يستخدم في أي من الأوامر و العبارات التي هي موضوع درسنا ما عدا البنية if .

هناك أمر آخر يقوم بعكس وظيفة break .. و هو الأمر continue :
يقوم هذا الأمر بالإنتقال إلى بداية الكتلة التي يوجد بها متجاهلاً باقي التعليمات التي توجد بعد هذا الأمر في تلك الكتلة ( البنية ) .. و يستخدم في العبارات : for و while و do/while فقط ( يعني في التكرار فقط ) .. سنأخذها في حلقة for في هذا الدرس .

4 – العبارة ( for ) :

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

قاعدة /

for( i=1; i<=10; i++)
{
Statements;
}
1 – يتم تقسيم ما بين القوسين التابعين لـ for إلى ثلاثة أقسام :

أ ) القسم الأول : يمثل القيمة الإبتدائية لبداية التكرار بحيث تمثل i أي متحول صحيح .. و ممكن أن تكون أي قيمة و يفضل أن نبدأ بالقيمة واحد .. بشرط أن يكون شكل هذا القسم كالتالي :

Variable = Value
Variable : متحول التكرار .. وممكن أن نعرفه في بداية البرنامج .. أو من الأفضل أن نقوم بالتالي :

for( int i=1; i<10; i++)
وبهذا الشكل كأنك عرفته في بداية برنامجك .

Value : القيمة الإبتدائية التي سيبدأ التكرار بالعد منها .. و ممكن أن تكون قيمة عددية أو متغير يحمل قيمة عددية .. حيث أن ( = ) هي عملية إسناد سيتم فيها إعطاء المتغير ( Variable ) قيمة ابتدائية .


مثال /
بفرض أن h متغير صحيح معرف من قبل و يحمل القيمة واحد

for( int i=h; i<10; i++ )
لاحظ أن بين كل قسم هناك فاصلة منقوطة .

ب ) القسم الثاني : يمثل شرط متابعة التكرار ( انتبه المتابعة و ليس التوقف ) .. بحيث فإذا تحقق يستمر التكرار مرة أخرى أما إذا لم يتحقق فيتوقف التكرار و يتم الخروج من حلقة for .. و يتم تنفيذ السطر الذي يلي تلك البنية .. و بالنسبة لكيفية كتابة هذا القسم فهو يشابه تماماً طريقة كتابة الشرط في عبارة if .

ج ) القسم الثالث : يمثل مقدار التغير الذي نريد أن نحدثه على متغير التكرار بحيث يتم مثلاً زيادته واحداً في كل عملية تكرار .. أو من الممكن أن نزيده اثنان أو من الممكن أن نطرح أيضاً و هكذا ..

2 - Statements و هي التعليمات التي سيتم تكرارها عدد من المرات .


شرح آخر مثال :

سيتم هنا تكرار مجموعة من التعليمات 9 مرات لأن القيمة الإبتدائية 1 و شرط الاستمرار ( أو القيمة النهائية ) 9 لأن i<10 و ليس i<=10 .. و أيضاً لأننا سنزيد هذا التكرار في كل مرة بمقدار واحد .. عند الإنتهاء من تنفيذ تعليمات for في المرة الأولى يتم زيادة المتغير i واحد و من ثم يختبر الشرط هل هو محقق أم لا ؟ فإذا تحقق يعود المترجم لتنفيذ أول تعليمة ضمن هذه الحلقة .. فتنفذ التعليمات مرة ثانية و عند الانتهاء من آخر تعليمة ضمن حلقة for يتم زيادة المتغير i واحد و يختبر الشرط مرة أخرى .. فإذا تحقق يعود المترجم إلى تنفيذ أول تعليمة ضمن هذه الحلقة ... و هكذا .. إلى أن ينتفي الشرط و تصبح قيمته false .
و من الممكن أن نطلق على كل عملية تكرار اسم دورة .. فهنا سيتم تنفيذ 9 دورات في حلقة for .

ملاحظة 1 / إذا كنت تريد تكرار تعليمة واحدة فلا تضع أقواس الكتل ( { } ) .. كما ذكرنا في في العبارات السابقة تماماً
ملاحظة 2 / ممكن أن تضع أكثر من متغير للتكرار و أكثر من شرط و أكثر من عملية على متغيرات التكرار.. بحيث تضيف فاصلة فقط ( , ) في كل قسم لكي تضيف متحول آخر أو عملية أخرى .. بينما في القسم الثاني تضيف ( && أو || ) .


مثال /

for( int i=1, j=5; i!=j || j >=0; i++, j-- )
هنا سيتم زيادة المتغير I واحد و سيتم إنقاص المتغير j واحد في كل دورة .. بالإضافة إلى أن لدينا متغيرين بقيمتين ابتدائيتين مختلفتين .
ملاحظة 1 / انظر إلى القسم الأول في آخر مثال .. فهو يشابه تماماً طريقة تعريف المتغيرات بقيم ابتدائية ( راجع الدرس الأول ) .. بحيث لا نضيف int قبل المتغير j فالأولى تكفي .
ملاحظة 2 / تسمى حلقة for بالتكرار المعدود .. أي التكرار الذي ينتهي بعد عدد محدد من المرات .. على عكس حلقات التكرار while و do/while و تسمى هذه الحلقات بالتكرار المشروط .. بحيث تنتهي تلك الحلقات بانتفاء الشرط و لا نعرف عدد المرات التي تم فيها التكرار .. و لكننا نستطيع معرفة عدد مرات التكرار بطريقة معينة سنأخذها في الدرس القادم إن شاء الله حين ندرس التكرار المشروط .

الأمر continue :قلنا بأن الأمر break يقوم بكسر الكتلة ( البنية ) الموجود فيها و الخروج منها .. أما الأمر continue ( الذي يستخدم في التكرار فقط ) يقوم بتجاهل التعليمات الموجودة الباقية و الإنتقال إلى التكرار ( الدورة ) التالية .. سأشرح طريقة عمله بواسطة هذا المثال :

for( int i=1; i<=5; i++ )
{
if( i==3 ) continue;
cout << "Montada\n";
}

هنا سيتم كتابة الكلمة Montada أربع مرات فقط بدلاً من خمسة .. لأننا وضعنا شرط يقول بأنه إذا وصل قيمة التكرار إلى 3 فانتقل إلى الدورة ( التكرار ) التالي أي التكرار رقم 4 .. و لن يتم كتابة تلك الكلمة في المرة الثالثة بسبب الأمر continue الذي تجاهل باقي أوامر الكتلة for في المرة الثالثة فقط .

ملاحظة / انظر إلى المثال السابق كيف وضعنا عبارة if داخل حلقة for .. فهذا ممكن كما قلنا و يمكنك أيضاً وضع عدة حلقات for داخل بعضها البعض .

Wolf Sniper
05-02-2005, 03:11 PM
:: أمثلة تطبيقية ::

سنأخذ في هذا الدرس أربع أمثلة .. الأول و الثاني و الثالث لعبارة if و الرابع للحلقة for .

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

مناقشة الحل :
1 – ما هي المتغيرات التي نحتاجها ؟ متغيرين صحيحين هما a للعدد للبسط و b للمقام .. و لا نحتاج إلى تعريف متغير للناتج ( انظر المثال ) .
2 – ما هي الأوامر التي نحتاجها ؟ أوامر الدخل و الخرج بالإضافة إلى عبارات الشرط if .
3 – ما هي المكتبات التي نحتاجها ؟ iostram.h فقط .

#include <iostream.h>
void main()
{
int a,b;
cout << "Enter Number 1 : "; cin >> a;
cout << "Enter Number 2 : "; cin >> b;
if( b!=0 )
cout << "Div = " << (float)a / (float)b << endl;
else
cout << "Error... Can not divide by 0\n";
}


شرح المثال :
1 – قمنا بتعريف متغيرين صحيحين للبسط و المقام .
2 – طلبنا من المستخدم إدخال العددين .
3 – قمنا بالتحقق من القيمة التي يحملها المتغير b هل تساوي الصفر أم لا .. فإذا لم تساوي الصفر قمنا بعملية القسمة .. أما إذا ساوت الصفر فنكتب على الشاشة أنه يوجد خطأ و لا يجوز القسمة على الصفر .
4 – بالنسبة لـ

(float)a / (float)b
المتغيرين a و b هما متغيرين صحيحين و ناتج قسمة عدد صحيح على عدد صحيح ( في ++C ) يجب أن تكون عدد صحيح لأن العددين صحيحين .. فمثلاً إذا قسمن العدد واحد على اثنان فالناتج سيكون 0.5 و لكن سوف يكتب المترجم صفر !!! لماذا ؟ لأن قسمة واحد على اثنان تساوي صفر و الباقي واحد .. و يمكن معالجة هذا الأمر بطريقتين :
أ ) أن نجعل المتغيرين من النوع الحقيقي float .
ب ) كما فعلنا في المثال : أن نضع قبل كل متغير في جملة الإخراج ( أو في عملية القسمة ) العبارة (float) .. بحيث سيتعامل المترجم مع هذين المتغيرين و كأنها حقيقيين ( في هذا السطر فقط ) بينما هم من النوع الصحيح int .


----------------------------------------

المثال 2 /
نريد الأن قراءة عددين من المستخدم و نكتب له العدد الأكبر .

مناقشة الحل :
1 – ما هي المتغيرات التي نحتاجها ؟ متغيرين صحيحين هما a للعدد الأول و b للعدد الثاني .
2 – ما هي الأوامر التي نحتاجها ؟ أوامر الدخل و الخرج بالإضافة إلى عبارات الشرط if .
3 – ما هي المكتبات التي نحتاجها ؟ iostram.h فقط .

#include <iostream.h>
void main()
{
int a,b;
cout << "Enter a , b : "; cin >> a >> b;
if( a>b )
cout << "Max = " << a << endl;
else if ( b>a )
cout << "Max = " << b << endl;
else
cout << "a = b = " << a << endl;
}

شرح المثال :
1 – عرفنا متغيرين صحيحين و طلبنا من المستخدم إدخالهما معاً و يستطيع ذلك بوضع الرقم الأول ثم يضغط إما على مسافة أو Enter و من ثم يدخل العدد الثاني .
2 – هناك ثلاثة حالات لهذه المسألة :
أ ) a أكبر من b : اختبرنا ذلك في عبارة if الأولى .. فإذا كان ذلك صحيحاً نكتب على الشاشة قيمة a .. أما لو كان ذلك خاطئاً فننتقل إلى الإختبار الثاني .
ب ) a أصغر من b : اختبرنا ذلك في عبارة else if الثانية .. و لكن لن يتم قراءة هذه العبارة إلى إذا كان نتيجة الإختبار الأول false .
ج ) a تساوي b : اختبرنا ذلك في عبارة else الأخيرة .. و لن يتم قراءة هذه العبارة إلا إذا كانت نتيجة الإختبار الأول و الثاني false .

ملاحظة هامة / كان باستطاعتنا بدلاً من استخدام else أن نكتب جميع عبارات الشرط على الشكل التالي :

#include <iostream.h>
void main()
{
int a,b;
cout << "Enter a , b : "; cin >> a >> b;
if( a>b )
cout << "Max = " << a << endl;
if ( b>a )
cout << "Max = " << b << endl;
if ( a==b )
cout << "a = b = " << a << endl;
}

و لكن بهذا الشكل سنقوم بعملية الإختبار ثلاث مرات دائماً عند تشغيل هذا التطبيق مهما كانت النتيجة .. بينما في الطريقة الأولى سنقوم بعملية الإختبار أول مرة فإذا تحقق لن نقوم بعد ذلك بالإختبار مرة ثانية أو ثالثة بسبب وجود else بحيث لن ينتقل المترجم إلى هذا الجزء إلا إذا كانت نتيجة الإختبار الأول false .
الطريقة الأولى أفضل من الثانية !!! تخيل أن برنامجك كبير جداً و تريد قدر الإمكان أن تلغي العمليات الزائدة التي لا ضرورة لها و ذلك بعد تحقيق الهدف المراد من التعليمات .. و هذه الطريقة تحقق ذلك .


----------------------------------------

Wolf Sniper
05-02-2005, 03:42 PM
المثال 3 /
هذه المرة نريد حل مثال أكبر و هو كالتالي .. نريد حل معادلة من الدرجة الثانية التي تأخذ الشكل :

Ax2 + bx + c = 0
مناقشة الحل :
1 – ما هي المتغيرات التي نحتاجها ؟ 5 متغيرات من النوع float لأن هذه المتغيرات من الممكن أن تحنوي على كسور و أرقام عشرية .. و هي كالتالي :
- a : من أجل معامل x مربع .
- b : من أجل معامل x .
- c : من أجل المعامل الثابت .
- delta : من أجل عملية حساب جذور الحل و هي تساوي : b*b -4*a*c .
- x1 : الجذر الأول ( الحل الأول ) و يمكن إيجاده من القانون :

( -b –sqrt(delta) ) / ( 2 * a )
- x2 : الجذر الثاني ( الحل الثاني ) و يمكن إيجاده من القانون :

( -b +sqrt(delta) ) / ( 2 * a )
2 – ما هي الأوامر التي نحتاجها ؟ أوامر الدخل و الخرج بالإضافة إلى عبارات الشرط if .. بالإضافة إلى معاملات المكتبة math.h من أجل الجذر التربيعي .
3 – ما هي المكتبات التي نحتاجها ؟ iostram.h و math.h .

#include <iostream.h>
#include <math.h>
void main()
{
float a,b,c,delta,x1,x2;

cout << "Enter a : "; cin >> a;
cout << "Enter b : "; cin >> b;
cout << "Enter c : "; cin >> c;

delta = b*b – 4*a*c;

if( delta < 0 )
cout << "No Solution\n";
else if( delta == 0 )
{
cout << "Double Solution\n";
x1 = -b / ( 2 * a );
cout << "X1 = X2 = " << x1 << endl;
}
else
{
cout << "2 Solution\n";
x1 = ( -b –sqrt(delta) ) / ( 2 * a );
x2 = ( -b +sqrt(delta) ) / ( 2 * a );
cout << "X1 = " << x1 << endl
<< "x2 = " << x2 << endl;
}
}


شرح المثال :

1 – قمنا أولاً بإضافة المكتبات اللازمة و من ثم عرفنا المتغيرات التي نريدها و طلبنا بعد ذلك من المستخدم إدخال القيم لكل من المعاملات a و b و c .

2 – قمنا بعد ذلك بحساب قيمة delta لأن الحل يبدأ من عندها فالحل له 3 حالات :

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

3 – بقية الشرح أصبح معروفاً لديكم .


----------------------------------------

المثال 4 /
هذا المثال لحلقة التكرار for .. نريد أن نكتب على الشاشة الأعداد الفردية الموجودة بين 1 و 100 مع مربعاتها بوضعها عن طريق وضع كل عدد مع مربعه على سطر .

مناقشة الحل :
1 – ما هي المتغيرات التي نحتاجها ؟ فقط متغير التكرار i ( انظر الحل ) .
2 – ما هي الأوامر التي نحتاجها ؟ أوامر الدخل و الخرج .. و حلقة التكرار for .. و استخدمنا حلقة for في هذا المثال لأننا نريد تكرار عملية الكتابة حوالي 50 مرة و ليس من المعقول أن نكتب 50 أمر كتابة على الشاشة ! .
3 – ما هي المكتبات التي نحتاجها ؟ iostram.h فقط .

#include <iostream.h>
void main()
{
for( int i=1; i<=100; i+=2)
cout << i << " " << i*i << endl;
}

شرح المثال :

1 – في الحلقة for .. قمنا بوضع المتغير i و هو المتغير الذي سيأخذ قيم الأعداد الفردية .. بحيث وضعنا له قيمة ابتدائية و هي الواحد لأن الأعداد الفردية بين 1 و 100 تبدأ من الواحد .. و قمنا بوضع شرط متابعة التكرار i<100 لأننا نريد الوصول إلى آخر عدد فردي و هو 99 .. و قمنا بإضافة 2 إلى المتغير i في كل تكرار لأننا لا نريد الأعداد الزوجية .. أما لو كنا نريد جميع الأعداد بين 1 و 100 فنضع العملية في القسم الثالث كالتالي ++i .
2 – في كل حلقة ( عملية تكرار ) سنكتب قيمة i .. و من ثم فراغ و من ثم مربع i .. و من ثم نقوم بالنزول إلى سطر جديد .


---------- ---------- ---------- نهاية الدرس الثاني ---------- ---------- ----------

حان وقت الجد ... الأن سأعطيكم 5 تمارين ( واجبات ) لتحلوها .. و سأعطي علامة على كل من يحل أكبر مجموعة من الأسئلة بحيث يمكنك اختيار السؤال الذي يعجبك لحله .. و التقسيم كالتالي :

1 – من يحل 5 أسئلة : فهو ممتاز و له مستقبل إن شاء الله في البرمجة .. و بالتالي أكون قد عرفت أنه فهم جميع النواحي التي ذكرناها .
2 – من يحل 4 أسئلة : فمستواه في البرمجة جيد جداً .. و لكنها خطوة ممتازة .. إلى الأمام .
3 – من يحل 3 أسئلة : فمستواه جيد .. و عليه بذل جهد أكبر قليلاً للوصول إلى المستوى المطلوب .
4 – من يحل سؤالين : فمستواه مقبول .. و يجب أن يحاول أن يبسط فكرة التمرين لكي يستطيع حله و عليه التفكير بمنطق حل المسائل أيضاً .
5 – من يحل سؤال واحد : لن أقول ضعيف و لكنها خطوة جيدة و يحتاج إلى تطبيق أكبر عدد ممكن من الأمثلة لكي يكتسب خبرة في فهم المسائل .


----------------------------------------

:: التمارين ::

1 – اكتب برنامجاً يطلب من المستخدم إدخال 3 أعداد صحيحة و من ثم يكتب له العدد الأكبر .
2 – اكتب برنامجاً يطلب من المستخدم إدخال عددين صحيحين و من ثم يكتب له فيما إذا كان الثاني قاسماً للأول .. ( بحيث أن القاسم هو العدد الذي يمكن القسمة عليه و يكون باقي القسمة عليه تساوي الصفر ) .
3 – اكتب برنامجاً يطلب من المستخدم إدخال عدد صحيح و من ثم يكتب له جميع قواسمه . ( استخدم حلقة التكرار for و عبارة الشرط if ) .
4 - اكتب برنامجاً يطلب من المستخدم إدخال n عدد صحيح و من ثم يكتب له قيمة n! ( العاملي ) .. استخدم حلقة التكرار for .

ملاحظة / قانون العاملي :

n! = n * (n-1) * (n-2) …
5 - اكتب برنامجاً يطلب من المستخدم إدخال عدد صحيح و من ثم نخبره بإن كان هذا العدد أولي أم لا . ( استخدم حلقة التكرار for و عبارة الشرط if ) .


----------------------------------------

أروني إبداعاتكم في البرمجة .. بالتوفيق .

IWANTYOURHELP
05-02-2005, 06:46 PM
السلام عليكم

اولا وقبل كل شيء شكرا لك اخي على هذا الجهد

الان ممكن تشرح سؤال 4 .... اكتب برنامجاً يطلب من المستخدم إدخال n عدد صحيح و من ثم يكتب له قيمة n! ( العاملي )
وما هو هذا العاملي

2- اخي انت في بعض الاسئله وضعت لنا استخدام if and for فهل نستطيع ان نستخد واحده فقط اما if او for

UAE Naruto
05-02-2005, 06:51 PM
السلام عليكم

اولا وقبل كل شيء شكرا لك اخي على هذا الجهد

الان ممكن تشرح سؤال 4 .... اكتب برنامجاً يطلب من المستخدم إدخال n عدد صحيح و من ثم يكتب له قيمة n! ( العاملي )
وما هو هذا العاملي

2- اخي انت في بعض الاسئله وضعت لنا استخدام if and for فهل نستطيع ان نستخد واحده فقط اما if او for

::

::

ممنوع الرد في هذا الموضوع!!!!!!!!

سيتم ابلاغ المشرف

::

::

Wolf Sniper
12-02-2005, 02:59 PM
السلام عليكم ...

:: مقدمة ::

أخذنا في الدرس الماضي عبارات الشرط و حلقة التكرار 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 و بالتالي سينفذ التعليمات التي بداخل الشرط .. و بالتالي هذه طريقة مباشرة بدلاً من كتابة :

if( x==true )
و أيضاً هناك طريقة مشابهة لإختبار القيمة false .. انظر المثال :

if( !x )
cout << "X = False " << endl;

لاحظ كيف وضعنا معامل النفي ( ! ) قبل المتحول x .. و هي مشابهة تماماً للعبارة التالية :

if( x==false )

الثانية :

تتعلق بالحلقة for .. بأننا نستطيع عدم كتابة أحد الأقسام الثلاثة المتعلقة بالقيمة الإبتدائية و النهائية و العملية على متغير التكرار .. و ممكن أيضاً أن نضع الحلقة فارغة هكذا :

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 أيضاً لم تحتوي على أقواس الكتل .. لأنها أيضاً احتوت على تعليمة واحدة فقط .


----------------------------------------

Wolf Sniper
12-02-2005, 03:16 PM
السؤال الرابع :
اكتب برنامجاً يطلب من المستخدم إدخال 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 ) .


مثال /

while( y<0 )
x++;

6 – العبارة ( do/while ) :

نفس العبارة while تماماً .. و لكنها ستنفذ التعليمات الموجودة مرة واحدة على الأقل ثم بعد ذلك تختبر تحقق الشرط .

قاعدة /

do
Statements;
while( Condition );


مثال /

do
x++;
while( y<0 );

ملاحظة 1 / لاحظ وجود الفاصلة المنقوطة في نهاية هذه العبارة .. أما في العبارة السابقة لم نضع فاصلة منقوطة بعد while و ذلك من أجل التمييز بين النوعين ( while ) و ( do/while ) .

ملاحظة 2 / يمكننا أيضاً في هاتين العبارتين استخدام أقواس الكتل ( { } ) إذا كنا نريد تكرار عدد من التعليمات و ليس فقط تعليمة واحدة .. كما تعلمنا سابقاً .


----------------------------------------

Wolf Sniper
14-02-2005, 12:01 AM
:: أمثلة تطبيقية ::

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


----------------------------------------

المثال 2 /

نريد من المستخدم إدخال 10 أعداد صحيحة ( int ) و من ثم نحسب له مجموع تلك الأعداد و المتوسط ( Average ) .

http://members.lycos.co.uk/wolfsniper84c/C/Sum.jpg

مناقشة الحل :

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 قيمة ابتدائية و هي الصفر لأنه سيحسب المجموع .. انظر كيفية استخدام هذا المتغير داخل الحلقة :

sum = sum + a;
هنا سنجمع في كل تكرار القيمة الجديدة للمتغير a و سنضيفها على sum و لكن ماذا بالنسبة لأول عملية تكرار ؟ ستكون sum تحمل قيمة لا معنى لها و بالتالي وضعنا لها قيمة ابتدائية و هي الصفر .

2 – قمنا بوضع حلقة ستقوم بتكرار مجموعة من التعليمات 10 مرات من أجل إدخال 10 أعداد .. و طلبنا من المستخدم إدخال عدد في كل مرة .. بحيث في كل دورة ستأخذ a قيمة جديدة ..

لاحظ أين قمنا بعملية حساب مجموع الأعداد :

sum = sum + a;
وضعناها داخل التكرار و ليس خارجه مثل المتوسط avr .. لأننا نريد حساب في كل دورة المجموع الجديد للأعداد .. بينما المتوسط يتم حسابه مرة واحدة و ذلك بعد حساب المجموع .

ملاحظة / المتوسط يساوي مجموع الأعداد على عدد تلك الأعداد .

3 – كتبنا المجموع .

4 – حسبنا المتوسط .. و لاحظ كيف وضعنا ( float ) قبل المتغير sum قسمة عدد صحيح ( int ) على عدد صحيح ( int ) ستعطي عدد صحيح لا يحتوي على كسور .. و نحن نريد العكس لذلك قمنا بإخبار المترجم بأن يتعامل مع المتغير sum بشكل مؤقت على أنه عدد حقيقي من النوع float .. و من ثم كتبنا ذلك المتوسط على الشاشة .


----------------------------------------

Wolf Sniper
14-02-2005, 12:15 AM
المثال 3 /

نريد من المستخدم إدخال 10 أعداد صحيحة ( int ) و من ثم نكتب له أكبر عدد و أصغر عدد من بين تلك الأعداد .

http://members.lycos.co.uk/wolfsniper84c/C/Max.jpg

مناقشة الحل :

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 من أجل النزول سطر في كل دورة .


----------------------------------------

المثال 5 /

الأن نريد رسم هذا الشكل :

http://members.lycos.co.uk/wolfsniper84c/C/seen_2.jpg

مناقشة الحل :
لاحظ هذا الشكل فهو يشابه الشكل السابق و لكن أصبح في السطر الأول نجمة واحدة فقط و الثانية نجمتان و هكذا .. لذلك انظر إلى العلاقة بين السطر و عدد النجوم .. نجد أن أنهما متساويان لذلك يجب وضع قيم السطر 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 بسبب تساوي رقم السطر مع رقم العمود .


----------------------------------------

Wolf Sniper
14-02-2005, 12:42 AM
المثال 6 /
نريد رسم مستطيل فارغ بحيث عدد الأسطر و الأعمدة تساوي 10 .. انظر الصورة :

http://members.lycos.co.uk/wolfsniper84c/C/seen_3.jpg

مناقشة الحل :

بنفس طريقة المثالين السابقين .. سنضع الحلقة الأولى من أجل التنقل بين الأسطر و الثانية من أجل التنقل بين الأعمدة .. و أيضاً سنستخدم الشرط لمعرفة أماكن رسم النجوم .. انظر الحل :


الحل /

#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 .. و بهذا الأسلوب تستطيع رسم أشكال مختلفة .


----------------------------------------

:: النافذة Watch ::

http://members.lycos.co.uk/wolfsniper84c/C/Watch.jpg


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

يمكنك استخدام هذه النافذة من خلال أوامر القائمة Debug و ستظهر تلك النافذة باستخادم أربع أوامر .. سنأخذ نحن أمرين :


الأول / باستخدام F10 / Step Over / من أجل الانتقال سطراً سطراً داخل برنامج أثناء عمله و بالتالي ستعرف كيف يقرأ المترجم برنامجك .. و لكنك بهذا الأمر لن تدخل إلى داخل التوابع عند استدعائها أو إلى الأماكن الأخرى التي ينتقل إليها المترجم .. بل ستكتفي بالتنقل ضمن التابع الذي يوجد به المترجم حالياً .


الثاني / باستخدام F11 / Step Into /
نفس الأمر السابق و لكنك ستدخل إلى كل التفاصيل و إلى داخل التوبع أو الأوامر التي وضعتها في برنامجك .. و يمكنك طبعاً استخدام كلا الأمرين معاً .. على حسب المكان الذي تريد الإنتقال إليه .. مثلاً إذا كنت تريد إلى تفاصيل تابع ما فاستخدم F11 عند سطر استدعائه .. أما لو كنت لا تريد ذلك فاستخدم F10 و بالتالي سيتم الإنتقال إلى السطر الذي يلي سطر استدعاء ذلك التابع ( ستتوضح الأمور حينما تجرب تلك الأوامر بنفسك ) .. و سنأخذ التوابع في الدرس القادم إن شاء الله .

ملاحظة 1 / لن تظهر النافذة Debug أو النافذة Watch إلا حين استخدام أحد الأمرين السابقين .. و ذلك بالضغط فوراً إما على F10 أو F11 .. و ستظهر في الأسفل على اليمين .. أما القائمة Debug فستظهر ضمن القوائم .


http://members.lycos.co.uk/wolfsniper84c/C/Debug.jpg

ملاحظة 2 / في النافذة Watch ضع اسم المتغير الذي تريد معرفة قيمه ضمن اللائحة Name و ستظهر قيمه ضمن اللائحة Value .

ملاحظة 3 / تستطيع الخروج من عملية إزالة الأخطاء Debug بالضغط على F7 و بعد ذلك اضغط Ok .


مثال /
حاول تجربة ما قلته في الأمثلة التي حللناها في هذا الدرس و شاهد قيم المتغيرات التي ستأخذها أثناء عمل البرنامج .. و أنصح أن تطبقوا ذلك على أمثلة الأشكال لمعرفة قيم i و j .. انظر الصورة :

http://members.lycos.co.uk/wolfsniper84c/C/Watch1.jpg


---------- ---------- ---------- نهاية الدرس الثالث ---------- ---------- ----------

حاول حل الأمثلة التي كتبناها اليوم على جهازك .. و في الدرس القادم إن شاء الله سنأخذ التوابع و ستشعر بأنك بدأت بدراسة البرمجة الحقيقة .. بالتوفيق .

Wolf Sniper
17-02-2005, 12:42 AM
السلام عليكم ...

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


---------- ---------- ---------- الدرس الرابع ---------- ---------- ----------
اليوم : الخميس ... التاريخ : 17 / 2 / 2005

:: البرامج الجزئية ( التوابع ) ::

:: مقدمة ::

كان أسلوبنا في حل المسائل السابقة هو أن نضع البرنامج كله ككتلة واحدة .. و في الحقيقة أن البرامج الحديثة اليوم لا تتبع هذا الأسلوب .. بل تأخذ تلك البرامج شكلاً آخر .. هو بتقسيم البرنامج ( مهما كان حجمه ) إلى أجزاء صغيرة تسمى الكائنات .. و لكننا نريد الأن التوابع التي هي جزء من هذه الكائنات .

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


أمثلة على التوابع :

1 – تابع لإيجاد متوسط مجموعة من الأعداد .
2 – تابع لإيجاد العدد الأكبر أو الأصغر من بين مجموعة من الأعداد .
3 – تابع لحل معادلة من الدرجة الثانية .. بحيث نعطيه البيانات اللازمة فيقوم بالعملية الخاصة به .
4 – تابع الإظهار ( الكتابة ) على الشاشة .
5 – تابع من أجل ترتيب مجموعة من الأعداد .. بحيث نعطيه تلك الأعداد فيعيدها لنا مرتبة .
6 – تابع إيجاد القيمة المطلقة و الجذر التربيعي لعدد ما .


فائدة التوابع :

1 – تقسيم البرنامج إلى مجموعة صغيرة مما يؤدي إلى سهولة تصحيح الأخطاء .. حيث أنك ستحتاج إلى تصحيح جسم ( كتلة ) ذلك التابع فقط .

2 – سهولة استخدام عملية ( وظيفة ) ما عدد كبير من المرات عن طريق استدعاء ذلك التابع بسطر واحد فقط .. فإذا كنا لا نستخدم التوابع فإننا سنضطر إلى كتابة قوانين تلك العملية في كل مرة نحتاج إلى تلك العملية .

3 – تستطيع استخدام التوابع التي كتبتها في برامج أخرى .. بحيث تضعها في مكتبة خاصة بك .. و من ثم تستدعيها عن طريق #include .

خلاصة / إن جميع المكتبات القياسية الموجودة في اللغة مثل :
1 – math.h .
2 – string.h .
3 – ctype.h .
و الكثير من المكتبات الأخرى هي في الحقيقة مجموعة من التوابع الجاهزة الخاصة بلغة السي++ .. فانظر مثلاً إلى هذه العملية التي أخذناها سابقاً :

sqrt();
هذه العملية من أجل إيجاد الجذر التربيعي لعدد ما .. و هي تابع من توابع المكتبة 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 ) و ليس في المتحول العام .

Wolf Sniper
17-02-2005, 12:56 AM
:: العملية الثنائية ( :: ) ::

تستخدم هذه العملية من أجل كسر القاعدة السابقة ( أولوية المتحول الخاص عند تشابه الأسماء ) .. و بالتالي عند استخدامها سيتم تجاهل المتحول الخاص و اعتبار المتحول العام هو المقصود .


يكتب هذا الرمز قبل اسم المتحول .. كالتالي :

::a

مثال / نفس المثال السابق مع بعض التعديل :

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 .

النتائج :

a = 2 b = 2 c = 31

شرح النتائج :

1 – أولاً نجد أن هناك ثلاثة متحولات عامة و هي a و b و c .

2 - نبدأ القراءة من التابع main .. فنجد ثلاثة عمليات إسناد ( إعطاء قيم ) بحيث أخذ a القيمة واحد .. و b أخذ القيمة 2 .. و c أخذ القيمة 3 ..

3 – بعد ذلك قمنا باستدعاء التابع f1 و بالتالي سينتقل المترجم إلى ذلك التابع لقراءته ( أي أنه سينتقل إلى جسم ذلك التابع ) .. في ذلك التابع : سنجد أن هناك متحولين ( من النوع الخاص ) يحملان نفس الاسم لمتحولين عامين و هما a و b .. و بالتالي سيتم تجاهل المتحولين العامين و استخدام هذه المتحولات الخاصة بهذا التابع .. حيث قام هذا التابع بإعطاء المتحولين a و b قيم معينة و هي متحولات خاصة به .. و لكنه أعطى المتحول العام c قيمة جديدة و هي 30 .. لذلك قيم المتغيرات العامة حتى الأن هي :

a = 1 b = 2 c = 30
و بهذا يكون المترجم قد نفذ هذا التابع .. و بعد ذلك سيرجع المترجم قراءة باقي برنامجك في التابع main .. أي إلى السطر الذي يلي مكان استدعاء التابع f1 .

4 – قمنا بعد ذلك باستدعاء التابع الثاني f2 .. و بالتالي سينتقل إليه المترجم لقراءته .. و نجد أن في ذلك التابع متحول خاص باسم b و يحمل نفس اسم المتحول العام .. لذلك سيتم تجاهل المتحول العام b في هذا التابع ..
نجد بعد ذلك أن هناك ثلاث عمليات إضافة واحد لكل متغير .. و الذي يهمنا هو المتحولين العامين a و c .. لأن b تم تجاهله كما قلنا .. و بالتالي ستصبح القيم كالتالي :

a = 2 b = 2 c = 31
بعد ذلك سيعود المترجم للسطر الذي يلي سطر استدعاء التابع f2 .

5 – قمنا بكتابة قيم المتحولات العامة .

ملاحظة / من أجل زيادة الفهم .. استخدم النافذة Watch من أجل معرفة قيمة المتحولات العامة التي ستأخذها أثناء استدعاء التوابع .. و استخدم F10 للمرور على استدعاء التابع دون الدخول إلى جسمه .. و استخدم F11 للدخول إلى التابع لمعرفة ما قام به من تغييرات على المتحولات ( راجع النافذة Watch في الدرس السابق ) .


:: المتحولات الوسيطة ( Parameters ) ::

هل تتذكر الأقواس الموجود بعد اسم التابع ؟ الأن سنعرف فائدتها .. تستخدم هذه الأقواس من أجل القيام بإدخال قيم يحتاجها التابع لكي يقوم بالعمليات المناسبة عليها .. كيف ؟ افرض أنك تريد كتابة تابع يحسب مجموع عددين .. و بالتالي يجب أن تعطيه العددين لكي يجمعه .. و من الخطأ أن نستخدم العددين كمتحولات عامة للأسباب المذكرة سابقاً ( بسبب تحميل الذاكرة ببيانات لا نحتاجها ربما إلا مرة واحدة و حيث أن المتحولات العامة ستبقى حتى نهاية برنامجك .. و بالتالي سيزداد حجم الذاكرة دون فائدة ) .. إذاً يجب أن نستخدم المتحولات الوسيطة من أجل إدخال هذين العددين إلى داخل التابع .

قاعدة / داخل الأقواس الخاصة بالتابع و التي توضع بعد اسمه مباشرة .. نضع نوع المتغير ثم اسمه كأننا نعرف أي متغير آخر .. و إذا كنت تريد إدخال أكثر من متغير إلى التابع فاستخدم الفاصلة ( , ) من أجل ذلك .. و لكن انتبه لا يجوز أن تضع الأتي :

…(int a,b)

بل عليك مع كل متغير وسيط أن تضع نوعه قبل اسم ذلك المتغير الوسيط .

ملاحظة 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 – أنواع المتحولات الوسيطة الفعلية يجب أن تكون متوافقة بالترتيب مع المتحولات الوسيطة الشكلية .

Wolf Sniper
17-02-2005, 01:33 AM
:: القيمة العائدة ( Return Value ) ::

تعلمنا حتى الأن كيف نقوم بكتابة التوابع و كيف نقوم بإدخال البيانات اللازمة للتابع لكي يجري عليها العمليات اللازمة .. و لكن افرض أننا نريد أن نرجع ناتج العمليات التي قام بها التابع إلى التابع الأخر الذي قام باستدعائه ؟ نستطيع ذلك عن طريق القيمة العائدة .. و على سبيل المثال :

في التابع السابق الذي يحسب متوسط عددين .. افرض أننا نريد من نجعل أي تابع آخر ( main مثلاً ) أن يستفيد من ذلك الناتج و يجري عليه عمليات أخرى .. لذلك لا يكفي في التابع avr أن ندخل عددين فقط بل يجب أن نعطي التوابع الأخرى ناتج عملية إيجاد المتوسط .

قاعدة 1 / هل تتذكر كيف شرحنا طريقة تعريف تابع ( الإعلان عن التوابع ) ؟ قلنا أنه يجب ذكر نوع التابع ثم اسمه ثم المتحولات الوسيطة ضمن قوسين ثم يأتي جسم ذلك التابع .. الأن حان الوقت لتغيير النوع void .. و لكن يجب أولاً توضيح بعض النقاط :

1 – التوابع التي لا تعيد قيمة : يكون نوعها void .

2 – التوابع التي تعيد قيمة : يمكن أن يكون نوعها بأي الأنواع المعرفة من قبل اللغة .. مثل : int و float و bool .. أو بأي من نوع من أنوع الكائنات أو السجلات ( سنأخذ ذلك لاحقاً ) .. و المهم في هذا الدرس الأنواع المعرفة مسبقاً .

قاعدة 2 / يجب استخدام التعليمة return مع التوابع التي تعيد قيمة .. و طريقة استخدامها هي كالتالي :

return Value;
حيث أن Value ممكن أن تكون أي قيمة أو ممكن أن تكون متحول ما .. و يشترط أن تلك القيمة مناسبة لنوع التابع .

قاعدة 3 / لا يمكن استدعاء التوابع التي تعيد قيمة بذكر اسمها فقط !!! بل يجب أن توضع إما كإسناد قيمة أو في تعليمات cout أو ... إلخ .. أمثلة :


بفرض أن لدينا التابع avr الذي يحسب المتوسط و يعيد الناتج .. و بفرض أن c هو متحول ما من النوع float :

c = avr( 2, 3 );
cout << avr( x, y ) << endl;

و بهذا تختلف عن التوابع التي من النوع void التي يمكن استدعاؤها يذكر اسمها فقط :

بفرض أن read تابع من النوع void .. و بالتالي يمكن استدعاؤه كالتالي:

read();


مثال / إيجاد متوسط عددين :

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 ) بالإضافة إلى الأمثلة التطبيقية للمعلومات الموجودة في هذا الدرس .. بالتوفيق .

Wolf Sniper
21-02-2005, 02:38 PM
---------- ---------- ---------- الدرس الخامس ---------- ---------- ----------
اليوم : الاثنين ... التاريخ : 21 / 2 / 2005

:: مقدمة ::

سنأخذ في هذا الدرس إن شاء الله موضوع العودية بالإضافة إلى بعض الأمثلة التطبيقية على التوابع .. و أيضاً سأتطرق إلى موضوع كيفية صناعة مكتبتنا الخاصة ذو الامتداد (h.) و وضع التوابع اللازمة بها .

ملاحظة قبل قراءة موضوع العودية / هذا الموضوع يعد من أصعب مواضيع البرمجة .. لذلك إذا شعرت أنك لم تفهم شيئاً فهذا طبيعي .. فالكل أصابه نفس الشيء حين سماع هذا المصطلح حتى أنا .. لذلك حاول قراءة الموضوع أكثر من مرة و صدقني ستشعر أن هذا الموضوع سيصبح سهلاً عليك إن شاء الله إذا قرأت شرحي بتركيز ( أقول بتركيز ) .. فإذا فهمت هذا الموضوع اعلم أنك تقدمت كثيراً في البرمجة و أن استيعابك لأسلوب كتابة الأوامر قد تطور كثيراً .. بالتوفيق .


سم بالله ثم اقرأ بتركيز و تمعن ..

:: العودية ( الاستدعاء الذاتي ) Recursion ::

و هي أن يقوم التابع باستدعاء نفسه .. و بالتالي سيستمر الاستدعاء إلى ما لا نهاية حتى يتم تحقيق شرط معين في الاستدعاء رقم n .. و عندها سيعود المترجم لتنفيذ التابع رقم n-1 و بعد ذلك إلى التابع رقم n-2 و هكذا إلى أن يصل المترجم للتابع الأول الذي قام باستدعاء نفسه .

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


و كمثال على العودية سنستخدم تابع يقوم بإيجاد العاملي لعدد n .. بحيث سندخل هذا العدد إلى التابع و سنعيد الناتج كقيمة معادة :

int fact( int n )
{
if( n==0 ) return 1;
else return ( n * fact( n-1 ) );
}

ملاحظة / اجعل هذا المثال كقاعدة لك مبدئياً عند استخدامك للعودية .

شرح المثال :
1 – يجب عليك أولاً معرفة الحالة النهائية التي سيقف عندها الاستدعاء المتكرر للتابع .. و في مثالنا حول العاملي سيتم الوقوف عند الوصول إلى ( صفر! ) و النتيجة ستكون هي الواحد ( لأن 0! تسوي واحد ) .. لذلك وضعنا شرط يقوم باختبار العملية السابقة .

2 - و إذا لم تكن n تساوي الصفر .. فسيتم استدعاء التابع لنفسه .. بحيث سنضع قانون العاملي و الذي يمثل العدد نفسه ضرب العدد ناقص واحد في كل مرة حتى نصل إلى العدد واحد .. و لكن لماذا وضعنا القانون بهذا الشكل :


n * fact( n-1 )
ملاحظة / يجب عليك معرفة أين ستقوم بعملية العودية ( استدعاء التابع لنفسه ) .. حيث هناك قانون آخر للعودية و هو كالتالي :

5! = 5 × 4 × 3 × 2 × 1 = 120

5! = 5 × 4! = 120
5! = 5 × 4 × 3! = 120
5! = 5 × 4 × 3 ×2! = 120
5! = 5 × 4 × 3 × 2 × 1! = 120
5! = 5 × 4 × 3 × 2 × 1 × 0! = 120


و بالتالي نستنتج القانون التالي :

n! = n * (n-1)!


و هذا ما يوضحه الحل الذي وضعناه .

و انظر إلى آخر حالة كيف أننا وصلنا إلى ( 0! ) .. حيث أننا نعرف مسبقاً أن ( 0! ) تساوي الواحد الصحيح .. لذلك تكون عادة حالة التوقف عن العودية عند الوصول إلى قيمة معروف جوابها مسبقاً ( مثل 0! ) و التي تساوي الواحد .

سنشرح الفكرة السابقة : بفرض أن المستخدم أدخل العدد 5 فستكون العملية كالتالي ( انظر الشكل ) :


http://members.lycos.co.uk/wolfsniper84c/C/Recursion.jpg


الاستدعاء الأول :

في هذا المثال .. سيكون لدينا 6 استدعاءات .. عند الاستدعاء الأول ( الذي قام التابع main باستدعائه ) ستكون قيمة المتحول الشكلي الأول 5 لأن المستخدم أدخل العدد 5 و قد أرسلنا هذا العدد إلى التابع fact .. و بالتالي سيتم اختبار الشرط فيما إذا كان العدد يساوي الصفر ( لأن جواب 0! نعرفه مسبقاً ) .. و لكنه لا يساويه .. لذلك سيستدعي التابع نفسه مرة أخر لحساب ( 5 * 4! ) .. و بالتالي سيتم إرسال القيمة n-1 كمتحول شكلي إلى الاستدعاء الثاني و هذه القيمة تساوي 4 .. و كأننا نطلب من الاستدعاء الثاني أن يحسب لنا ( 4! ) .


الاستدعاء الثاني :

كما قلنا فإن قيمة المتحول الشكلي n في هذا الاستدعاء ستكون 4 .. و بالتالي لا تساوي الصفر .. لذلك سيستدعي التابع نفسه مرة أخرى و لكنه سيرسل القيمة n-1 كمتحول شكلي إلى الاستدعاء الثالث بحيث ستكون القيمة 3 .. لأننا قلنا سابقاً بأن : ( n! = n * ( n-1 .. حيث أننا نطلب من الاستدعاء الثالث أن يحسب لنا ( 3! ) .. و هكذا بقية الاستدعاءات .


الاستدعاء الثالث :

قيمة المتحول الشكلي

( n ) = 3
القيمة المرسلة إلى الاستدعاء الرابع

( n-1 ) = 2


الاستدعاء الرابع :

قيمة المتحول الشكلي

( n ) = 2
القيمة المرسلة إلى الاستدعاء الرابع

( n-1 ) = 1


الاستدعاء الخامس :

قيمة المتحول الشكلي

( n ) = 1
القيمة المرسلة إلى الاستدعاء الرابع

( n-1 ) = 0


الاستدعاء السادس :

هنا ستكون قيمة ( n ) تساوي الصفر .. و بالتالي وصلنا إلى الحالة النهائية التي لا يوجد حل للعاملي بعدها .. لذلك سيعيد هذا التابع القيمة ( 1 ) لأن الشرط تحقق .. و لكنه لن يعيدها إلى الاستدعاء الأول بل إلى التابع الذي قام باستدعائه و هو الاستدعاء رقم 5 .. لذلك :
القيمة المعادة للاستدعاء الخامس = 1 .


في الاستدعاء الخامس :

الأن في الاستدعاء الخامس نحن موجودين ضمن الشرط في else ( لأننا كما قلنا أن n لا تساوي الصفر ) .. و بالتالي الأن أصبحت قيمة :


n * fact( n-1 )

معروفة و هي :

n = 1 .
fact( n-1 ) = 1 .. ( أوجدناها من الاستدعاء السادس الذي أعاد لنا تلك القيمة ) .

و الناتج سيكون الواحد .. و بالتالي ( بسبب وجود return ) سيعيد الاستدعاء الخامس ناتج عملية الضرب إلى الاستدعاء ( التابع ) رقم 4 .
القيمة المعادة للاستدعاء ( التابع ) رقم 4 = 1 .. ( لأن 1 × 1 = 1 ) .


في الاستدعاء الرابع :

تماماً كالاستدعاء رقم 5 .. سنكون بداخل else لأن n لا تساوي الصفر .

n = 2 .
fact( n-1 ) = 1 .. ( و هي القيمة المعادة من الاستدعاء الخامس ) .

و الناتج سيكون ( 2 ) .. و بالتالي ( بسبب وجود return ) سيعيد الاستدعاء الرابع ناتج عملية الضرب إلى الاستدعاء ( التابع ) رقم 3 .
القيمة المعادة للاستدعاء ( التابع ) رقم 3 = 2 .. ( لأن 2 × 1 = 2 ) .


في الاستدعاء الثالث :

تماماً كالبقية .. سنكون بداخل else لأن n لا تساوي الصفر .

n = 3 .
fact( n-1 ) = 2 .. ( و هي القيمة المعادة من الاستدعاء الرابع ) .

و الناتج سيكون ( 6 ) .. و بالتالي ( بسبب وجود return ) سيعيد الاستدعاء الثالث ناتج عملية الضرب إلى الاستدعاء ( التابع ) رقم 2 .
القيمة المعادة للاستدعاء ( التابع ) رقم 2 = 6 .. ( لأن 3 × 2 = 6 ) .


في الاستدعاء الثاني :

تماماً كالبقية .. سنكون بداخل else لأن n لا تساوي الصفر .

n = 4 .
fact( n-1 ) = 6 .. ( و هي القيمة المعادة من الاستدعاء الثالث ) .

و الناتج سيكون ( 24 ) .. و بالتالي ( بسبب وجود return ) سيعيد الاستدعاء الثاني ناتج عملية الضرب إلى الاستدعاء ( التابع ) رقم 1 .
القيمة المعادة للاستدعاء ( التابع ) رقم 1 = 24 .. ( لأن 4 × 6 = 24 ) .


في الاستدعاء الأول:

سنكون أيضاً بداخل else لأن n لا تساوي الصفر .

n = 5 .
fact( n-1 ) = 24 .. ( و هي القيمة المعادة من الاستدعاء الثاني ) .

و الناتج سيكون ( 120 ) لأن ( 24 × 5 = 120 ) .. و هكذا نكون قد وصلنا إلى الجواب الذي نريده .. و بالتالي سيعيد هذا التابع تلك القيمة إلى التابع main الذي قام باستدعائه لكي يقوم بكتابة الناتج على الشاشة أو لإجراء أية عمليات أخرى على الناتج .


خلاصة الحل :

1 – لاحظ كيف بدأنا بالاستدعاء الأول و انتهينا أيضاً به .
2 – عند القيام باستدعاء التابع لنفسه .. سيقف المترجم عند السطر الذي قمنا به بالعودية ( استدعاء التابع لنفسه ) .. و سيعود المترجم إلى نفس المكان حين إيجاد القيمة المعادة ( أو عند الانتهاء من التابع الذي قمنا باستدعائه ) و سيكمل ما تبقى من تعليمات موجودة في المكان الذي توقف به ( سنأخذ مثال على ذلك في هذا الدرس ) .


مساوئ العودية :

- إن الاستدعاء المتكرر للتوابع سيكون تنفيذه أبطأ من استخدام حلقات التكرار ( for و while ... إلخ ) .. و لكن هناك مسائل لا تحل إلا عن طريق العودية .. مثل عملية البحث في الملفات و التي ينفذها windows و طريقة عملها تشابه تماماً طريقة عمل العودية.. لذلك عليك معرفة متى تستخدم العودية و متى تستخدم حلقات التكرار ( على حسب المسألة ) .

Wolf Sniper
21-02-2005, 02:55 PM
:: أمثلة تطبيقية ::

سنأخذ عدة أمثلة على التوابع و لكننا سنبدأ بمثالين على العودية لتأكيد فكرة كيفية وقوف المترجم عند النقطة التي قام بالاستدعاء عندها و من ثم يكمل عمله مع باقي تعليمات الاستدعاء ( التابع ) نفسه عند إرجاع قيمة أو عند الانتهاء من التابع المستدعى .. و لكن سيكون التطبيق الأول كالمثال السابق حول العاملي .. و الثاني سيوضح تلك الفكرة .


المثال 1 /
نريد من المستخدم إدخال عددين بحيث يكون الأول هو الأساس و الثاني يكون هو الأس بحيث نوجد ناتج عملية الرفع إلى أس عن طريق العودية .. انظر الصورة :

http://members.lycos.co.uk/wolfsniper84c/C/Rec1.jpg


الحل /

#include <iostream.h>

int power( int, int );

void main()
{
int a,b;

cout << "Enter X : "; cin >> a;
cout << "Enter Y : "; cin >> b;

cout << "X Power Y = " << power( a, b ) << endl << endl;
}

int power( int x, int y)
{
if( y==0 ) return 1;
else return ( x * power( x, y-1 ) );
}

شرح الحل :

1 – استخدمنا الطريقة الثانية في كتابة التوابع ( راجع الدرس السابق ) بحيث وضعنا رأس التابع في الأعلى لكي يتعرف عليه المترجم إذا ما استخدمناه في التابع main .. ثم كتبنا جسم ذلك التابع أسفل التابع main .

2 – عرفنا متغيرين في التابع main : a من أجل الأساس .. و b من أجل الأس .. و بعد ذلك طلبنا من المستخدم إدخال تلك القيم .. و بعد ذلك استدعينا التابع power لحساب الناتج عن طريق إرسال a و b كمتحولات وسيطة .

3 – في التابع power .. سنستخدم نفس أسلوب المثال السابق .. و لكن هناك اختلافات بسيطة :

أولاً / ما هي القيمة النهائية ( القيمة المعروف ناتجها ) التي سيتوقف عندها الاستدعاء المتكرر للتابع ؟ الجواب هو عندما يكون الأس يساوي الصفر ! لماذا ؟ لأن أي عدد إذا كان أسه يساوي الصفر فالناتج هو الواحد .
ثانياً / كيف سنستدعي التابع مرة أخرى عن طريق العودية ؟ ما هو القانون ؟ الجواب هو :
لاحظ دائماً ما هي القيم التي تتغير في القانون .. انظر إلى الأمثلة التالية ( اقرأ من اليمين إلى اليسار ) :


.. القوة تعني الأس ..
4 قوة 4 = 256 .
4 قوة 4 = 4 × 4 قوة 3 .
4 قوة 4 = 4 قوة 2 × 4 قوة 2 .
4 قوة 4 = 4 قوة 3 × 4 .

4 قوة 4 = 4 قوة 4 × 4 قوة صفر .. و هكذا باقي الأرقام .

لذلك يمكن أن نستنتج القانون التالي :


x Power y = x * x Power ( y-1)
و هذا ما وضعناه في حل المثال .. و باقي الشرح يشابه تماماً شرح المثال السابق حول العاملي .. و بالتالي يجب عليك معرفة أين تضع مكان الاستدعاء التابع لنفسه و لاحظ كيف وضعناه نحن في الأمثلة السابقة .


----------------------------------------

المثال 2 /
نريد الأن أن نطلب من المستخدم إدخال جملة حرفية و من ثم نكتبها معكوسة .. بحيث تنتهي الجملة بإدخال النقطة .. انظر الصورة :

http://members.lycos.co.uk/wolfsniper84c/C/Rec2.jpg


الحل /

#include <iostream.h>

void read()
{
char x;

cin >> x;

if( x != '.' )
{
read();
cout << x;
}
}

void main()
{
cout << "Enter a Sentence : ";

read();

cout << endl << endl;
}

شرح الحل :

1 – أظن أن التابع main مفهوم .

2 – في التابع read .. سيمثل كل استدعاء ( تابع ) حرف من الجملة .. و بالتالي سنطلب في كل مرة من المستخدم إدخال حرف جديد طالما أن الحرف السابق ليس النقطة .. و سيكون كل حرف مخزن في المتغير x الخاص بتابع معين .. فكلما أدخل المستخدم حرف جديد لا يساوي النقطة قمنا باستدعاء تابع آخر ليخزن الحرف الجديد في المتحول x الخاص به .

3 – عند إدخال النقطة في نهاية الجملة .. سيكون آخر تابع يحمل قيمة النقطة و بذلك ينتهي هذا التابع دون كتابة تلك النقطة .. و بالتالي سيكون التابع قبل الأخير يحمل قيمة آخر حرف .. إذا سيتم كتابة هذا الحرف أولاً على السطر و بذلك ينتهي هذا التابع ..
و تستمر التوابع بعد ذلك بكتابة كل منها قيمة المتغير x ( الحرف ) الذي يحمله .. و بالتالي ستكتب الجملة معكوسة لأن كتابة المتغيرات x الخاصة بالتوابع ستكتب من آخر تابع إلى الأول و نحن نعرف أن الأحرف وضعت بشكل متتالي في التوابع نتيجة إدخال المستخدم في كل مرة حرف واحد .

و سبب ذلك أن المترجم يقف عند سطر استدعاء التابع و ينتقل إلى ذلك التابع .. و عند الانتهاء من هذا التابع يعود المترجم إلى السطر الذي يلي سطر الاستدعاء الخاص بالتابع .. و هكذا باقي التوابع .. انظر الصورة :


http://members.lycos.co.uk/wolfsniper84c/C/Rec3.jpg

هذه الصورة تمثل التوابع الثلاثة الأولى لكلمة Montada و بقية التوابع كهذه .. لاحظ كيف تم تسلسل إدخال الأحرف .. و أيضاً في النهاية كيف سيتم كتابة قيمة الحرف x في التابع رقم 3 و بذلك ينتهي عمله ثم سيتم سينتقل المترجم إلى التابع رقم 2 و لكن ليس إلى أوله بل إلى السطر الذي استدعينا فيه التابع رقم 3 .. و عندها سيتم كتابة قيمة x .. و عندها ينتهي عمل التابع رقم 2 .. و بالتالي سيعود المترجم إلى التابع رقم 1 .. و يكتب قيمة x و الذي تمثل أول حرف تم إدخاله و بالتالي سيكتب هذا الأحرف في النهاية .. و هذا يعني أن الجملة ستكتب معكوسة .


----------------------------------------

المثال 3 /
نريد من المستخدم إدخال عددين صحيحين و من ثم نعطي العدد الأكبر كقيمة عائدة عن طريق التابع bigger .. و إذا كان العددين متساويين فنعيد القيمة صفر .

http://members.lycos.co.uk/wolfsniper84c/C/Fun_max.jpg


الحل /

#include <iostream.h>

int bigger( int, int );

void main()
{
int a, b;

cout << "Enter Number 1 : "; cin >> a;
cout << "Enter Number 2 : "; cin >> b;

cout << "Max = " << bigger( a, b ) << endl;
}

int bigger( int a, int b )
{
if( a>b )
return a;
else if ( b>a )
return b;
else
return 0;
}

شرح المثال :

1 – استخدمنا الطريقة الثانية في تعريف التوابع كالمثال الأول .. بحيث عرفنا رأس التابع في بداية الصفحة لكي يتعرف عليه المترجم لأننا نريد استخدامه في التابع main و أيضاً لأننا سنضع هذا التابع ( bigger ) أسفل التابع main .

2 – في التابع main .. طلبنا من المستخدم إدخال العددين .. و من ثم استدعينا التابع bigger لكي يحسب العدد الأكبر عن طريق إرسال العددين كمتحولات وسيطة .. و من ثم سيعيد لنا العدد الأكبر .. حيث أن العددين بالنسبة للتابع bigger يعتبران المدخلات و العدد الأكبر يعتبر مخرجاته .

3 – في التابع bigger .. سنحل السؤال كما حللناه في الدروس السابقة .. فإذا كان العدد a أكبر من b فأرجع a كقيمة معادة .. أما إذا كان b أكبر من a فأرجع b .. أما إذا كان العددين متساويين فأرجع الصفر .


----------------------------------------

Wolf Sniper
21-02-2005, 03:22 PM
المثال 4 /
نريد من المستخدم إدخال عدد و من ثم نحسب له العاملي لهذا العدد عن طريق التابع fact .. و لكن ليس عن طريق العودية كما حللناه في بداية هذا الدرس بل عن طريق حلقة التكرار for ( نفس الحل الموجود في الدرس الثالث و لكن سنستخدم التوابع للحل ) .

http://members.lycos.co.uk/wolfsniper84c/C/Fact.jpg


الحل /

#include <iostream.h>

int fact( int );

void main()
{
int a;

cout << "Enter a Number : "; cin >> a;

cout << fact( a ) << endl;
}

int fact( int x )
{
int f=1;

for( int i=1; i<=x; i++ )
f *= i;

return f;
}

شرح المثال :

1 – أظن أن التابع main مفهوم .

2 – التابع fact مشروح فكرته في الدرس الثالث .. و لكننا هنا بدلاً من جعل I تتغير قيمتها تنازلياً .. جعلناها هنا تتغير تصاعدياً من الواحد و حتى العدد نفسه الذي أدخله المستخدم و الجواب هو نفسه .. و بعد ذلك سنعيد الناتج كقيمة عائدة إلى التابع main .


----------------------------------------

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

http://members.lycos.co.uk/wolfsniper84c/C/prime.jpg


الحل /

#include <iostream.h>

bool prime( int );

void main()
{
int a;

cout << "Enter a Number : "; cin >> a;

if( prime(a) ) cout << "Prime" << endl;
else cout << "not Prime" << endl;
}

bool prime( int x )
{
if( x==2 || x==3 ) return false;

for( int i=2; i<=x-1; i++ )
if( x%i == 0 ) return false;

return true;
}

شرح الحل :

1 – في التابع main .. قمنا بتعريف العدد الذي سيدخله المستخدم .. بحيث طلبنا منه ذلك .

2 – بعد ذلك قمنا باختبار فيما إذا كان هذا العدد أولي أم لا عن طريق استدعاء التابع prime مع إرسال العدد كمتحول وسيط لكي يختبر فيما إذا كان ذلك العدد كذلك .. فإذا كان أولي سنكتب جملة Prime أما إن لم يكن أولي فسنكتب له not Prime .. وحيث أننا نعرف بأن القيمة المعادة هي من النوع bool لذلك نستطيع وضع التابع داخل الشرط if .. بحيث سيقوم الإختبار أولاً باستدعاء التابع ثم سيختبر القيمة المعادة هل هي true أم false .. و لاحظ أيضاً كيف أننا لم نضع التالي :


if( prime(a)==true )

بل اكتفينا باستدعاء التابع .. لأن العبارتين متشابهتين ( اقرأ الملاحظة التالية ) .

ملاحظة مهمة / من أجل طريقة استخدام التوابع التي تعيد قيمة من النوع bool أو من أجل المتغيرات من ذلك النوع أيضاً و كيفية استخدامها مع الشرط if .. راجع أول الدرس الثالث .. و طريقة استخدام تلك التوابع هي بنفس طريقة استخدام المتغيرات في الشرط .. و لكن في التوابع من ذلك النوع عند استخدامها مع الشرط سيتم استدعاء التابع أولاً ثم سيتم اختبار القيمة المعادة منه .

3 – في التابع prime .. بنفس الحل الموجود في الدرس الثالث و لكننا سنطوره قليلاً .. حيث أن العددان 2 و 3 ليسا من الأعداد الأولية ( هكذا أتذكر ) .. و طريقة الحل هنا ستعتبر أن تلك الأعداد هي من الأعداد الأولية و هذا ما لا نريده .. لذلك وضعنا الشرط الأول لإختبار قيم x فوراً .. فإذا كانت تساوي إحدى العددين فسيتم إرجاع القيمة false للدلالة على أنهما ليسا من الأعداد الأولية .

و من ثم في الاختبار الثاني حسبنا عدد القواسم للعدد المدخل .. و لكننا لن نحسب كم هي بالضبط ! فإذا وجدنا واحد فقط فهذا يعني أنه ليس من الأعداد الأولية ( طبعاً لم نضع في الحلقة العدد 1 و العدد نفسه x لأن العدد الأولي دائماً يقبل القسمة على الواحد و على نفسه فليس هناك فائدة من إدخالهم في عملية الحساب ) .. فإذا وجدنا قاسماً واحداً فسنعيد القيمة false أما إذا لم نجد أي قاسم للعدد فهذا يعني أنه أولي و بالتالي سنعيد القيمة true التي تدل على أنه أولي .

ملاحظة مهمة / الأمر return ينهي عمل التابع .. و بالتالي عند استخدامه سيتم الخروج من التابع فوراً و عن طريقه سيتم إرجاع قيمة ما ( راجع الدرس السابق ) .


----------------------------------------

Wolf Sniper
21-02-2005, 03:42 PM
:: كيفية صناعة المكتبات الخاصة بنا ذو الامتداد (h.) ::

تحدثنا سابقاً حول الملفات التي نستطيع كتابة الأوامر فيها .. و لقد كتبنا جميع برامجنا و أمثلتنا على ملفات مصدرية Source File ذو الامتداد (cpp.) .. و لقد تحدثنا عن المكتبات ذو الامتداد (h.) و أنا تحتوي على مجموعة من الأوامر و التوابع و أشياء أخرى معرفة مسبقاً من قبل المستخدم .

لذلك افرض مثلاً أنك تريد صناعة مجموع من التوابع الجاهزة بحيث لا تعيد كتابتها ثانية في برامجك الأخرى و بالتالي ستوفر على نفسك عناء تلك الكتابة .

لذلك نستنتج أن هذه المكتبات مفيدة لكي يستخدمها المبرمج في برامج أخرى يقوم بكتابتها هو أو غيره و ستصبح هذه المكتبات كالمكتبات الجاهزة الأخرى مثل : math.h و ctype.h و مكتبات أخرى كثيرة .. و هناك أشخاص يقومون ببيع تلك المكتبات بأسعار خيالية و لكنها ليست على شكل ملفات رأسية (h.) كما سنتعلم الأن !!! حيث أنها عبارة عن مكتبات ربط ديناميكية و هو ما يسمى بملفات dll .. و لكن لا تخف فإن هذه المكتبات ما هي بالنهاية إلا ملفات رأسية (h.) و لذلك يجب أن نعرف الأن مبدئياً كيف يمكن أن نصنع مكتبة جزئية خاصة بنا من ذلك النوع من الملفات .

ملاحظة / راجع الدرس الأول لمزيد من المعلومات حول الملفات الرأسية (h.) .


أولاً / على ماذا تحتوي الملفات الرأسية (h.) ؟

قد تحتوي هذه الملفات على الأنواع التالية :

1 – توابع .
2 – كائنات .
3 – مؤشرات .
4 – سجلات من النوع struct .
5 – متغيرات .
إلخ ...

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


ثانياً / كيف نصنع ( ننشئ ) تلك الملفات ؟

تستطيع ذلك بنفس طريقة إضافة ملف مصدري (cpp.) .. إضغط على : File >> New و من ثم ضمن القائمة File اختر Header File .. و اضغط على Ok .. و بهذا تكون قد أنشأت ملف رأسي .


ثالثاًُ / ما هي طرق كتابة تلك الأنواع من الملفات ؟

في الحقيقة هناك طريقتين :

الأولى : نضع كل الأوامر مهما كانت ( كائنات .. توابع .. سجلات .. إلخ ) في هذه الملفات مع جميع أجزائها .. و بالتالي ستكون مكتبتنا الخاصة مكونة من ملف رأسي فقط .

الثانية : هي أن نضع رؤوس الأوامر فقط داخل الملف الرأسي ( كما استخدمنا الطريقة الثانية لكتابة التوابع ) و أجسام تلك الأوامر في ملف مصدري (cpp.) .. و هذه الطريقة هي الأفضل و هي الأكثر استخداماً و لكننا لن نتطرق إليها إلا عند الوصول إلى الكائنات .. لأننا سنضطر لذكرها .. لذلك اتركوا هذه الطريقة الأن و لنرجع إلى الطريقة الأولى .


رابعاً / الأن سنقوم بكتابة مكتبتنا الأولى .. لذلك قم بإنشاء ملف رأسي و سميه باسم Library1 .. و هذا الاسم سيكون اسم تلك المكتبة و التي ستحتوي على التوابع التالية :

1 – تابع إيجاد قيمة العاملي لعدد ما ( بطريقة التكرار و ليس العودية ) .
2 – تابع إيجاد القوة لعدد ما ( الأس و الأساس ) .
3 – تابع إيجاد الجملة المعكوسة للجملة التي أدخلها المستخدم .
4 – تابع إيجاد أي العددين أكبر .
5 – تابع معرفة فيما إذا كان العدد أولي أم لا .

و كل تلك التوابع قمنا بكتابتها في هذا الدرس .. و سنضيف على هذه المكتبة توابع أخرى إن شاء الله في الدروس القادمة .


خامساً / كتابة المكتبة ( Library1 ) :

المكتبة ( Library1 )


int fact( int x )
{
int f=1;

for( int i=1; i<=x; i++ )
f *= i;

return f;
}

int power( int x, int y)
{
if( y==0 ) return 1;
else return ( x * power( x, y-1 ) );
}

void read()
{
char x;

cin >> x;

if( x != '.' )
{
read();
cout << x;
}
}

int bigger( int a, int b )
{
if( a>b )
return a;
else if ( b>a )
return b;
else
return 0;
}

bool prime( int x )
{
if( x==2 || x==3 ) return false;

for( int i=2; i<=x-1; i++ )
if( x%i == 0 ) return false;

return true;
}

قمنا بوضع التوابع كما هي في الملف الرأسي Library1 .. و الحلول موجودة في هذا الدرس .

سادساً / كيف يمكن أن نضيف المكتبة ( Library1 ) إلى برنامجنا بعد أن كتبناها ؟

يمكننا ذلك عن طريق الأمر :

#include "Library.h"
هل عرفت فائدة الأمر ( include ) ؟ تستطيع بهذا الأمر ضم المكتبات التي تريدها إلى مشروعك .. و لكن لماذا وضعنا اسم المكتبة داخل علامتي التنصيص ( " " ) ؟ قلنا سابقاً في الدرس الأول أننا نستخدم علامتي التنصيص في هذا المكان لكي نخبر المترجم بأن المكتبة موجودة ضمن الملف ( المجلد ) الذي يوجد فيه تطبيقك ذو الامتداد exe .

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

هل عرفت أيضاً ما فائدة الـ Linker ؟ يقوم Linker بربط برنامجك بالمكتبات و الملفات المطلوبة و التي قمت بإضافتها إلى مشروعك ( راجع الدرس الأول ) .


سابعاً / الاستخدام الفعلي للمكتبة ( Library1 ) :

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


#include <iostream.h>
#include "Library1.h"

void main()
{
int a, b;

cout << "Enter Number 1 : "; cin >> a;
cout << "Enter Number 2 : "; cin >> b;

cout << "Max = " << bigger( a, b ) << endl;
}
لاحظ كيف أننا بهذا الشكل إختصرنا على أنفسنا عناء كتابة التابع bigger داخل هذا الملف المصدري .. و حيث أننا أيضاً نستطيع استخدام باقي التوابع المكتوبة في المكتبة ( Library1 ) و كأن تلك التوابع مكتوبة بداخل الملف الأساسي المصدري في هذا المثال .. و كل ذلك نتيجة إضافة تلك المكتبة عن طريق الأمر include و الذي جعل المترجم يقوم بتعريف تلك التوابع و كأنها في برنامجك .


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


---------- ---------- ---------- نهاية الدرس الخامس ---------- ---------- ----------

في النهاية .. أتمنى أن يكون هذا الدرس بداية طريقكم لفهم هذه اللغة و بداية لاحترافكم لها .. لأن هذه المواضيع مهمة و إذا لم تفهموها فلا أعتقد أنكم ستفهمون المواضيع القادمة .. لذلك أرجو من الجميع التركيز على كل ما سبق لكي تكون الدروس القادمة مفهومة لكم إن شاء الله .. بالتوفيق .

الطموحه
21-02-2005, 09:30 PM
السلام عليكم انا اعشق لغة c++ولكن مررت بوضوع جديد وهو class الذي خلبط علي الشغله وكرهني في لغة c++فيا ليت تقوموم يالشرح الكافي مع الاملثه لو سمحتم (تكفى ياwolf) ولكم الشكر الجزيل:vereymad:

UAE Naruto
22-02-2005, 04:17 PM
::

::

ممنوع الرد فب هذا الموضوع

سيتم ابلاغ المشرف

::

::

Wolf Sniper
25-03-2005, 09:52 PM
السلام عليكم ...

:: مقدمة ::

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

1 – المصفوفات ( Arrays ) .
2 – السجلات ( Structures ) .
3 – المؤشرات ( Pointers ) .

أما الجزء الأخير و هو البرمجة غرضية التوجه ( بجميع مواضيعه ) أعتبره ذو مستوى متقدم .. و بذلك نكون قد قسمنا الدروس إلى ثلاثة مستويات .. و هذا الأمر مهم برأيي .

ملاحظة / أريد أن أوضح نقطة مهمة .. أن الدروس التي أكتبها هي ليست مني 100% ؟؟! .. بل هي مجهود مشترك بيني و بين من قام بتعليمي تلك اللغة ( لن أذكر اسمه ) و أيضاً المرجع " كيف تبرمج بلغة السي++ " للدكتور صلاح الدوه جي .. و بذلك أكون أنا حلقة وصل بينكم و بينهم .. و لكن هذا لا يعني أني لا أكتب شيئاً في هذا الدروس بل على العكس .. الدروس مكتوبة 100% مني و لكن محتواها من الأمثلة و المعلومات و الشرح مجزء كالآتي :

1 – مني أنا 50% .. و شرح الأمثلة كلها كتبته بنفسي .. بالإضافة إلى أمثلة كثيرة و أمور أخرى كتبتها بنفسي .
2 – أستاذي 45% .. حيث أنني وضعت الكثير من التعاريف و الأمثلة و الحلول التي قام هو بإعطائها لي حين تعلمت لغة السي++ منه .
3 – المرجع 5% .. استفدت قليلاً من المرجع في وضع الدروس من هذا المرجع و هو مهم لذلك أنصح الجميع بالحصول عليه .

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

اليوم سنتكلم عن المصفوفات .. و سننتهي منها في هذا الدرس بشكل كامل إن شاء الله .. لذلك درس اليوم أيضاً مهم و سأحاول تبسيط الموضوع قدر الإمكان .. بسم الله نبدأ .


---------- ---------- ---------- الدرس السادس ---------- ---------- ----------
اليوم : السبت ... التاريخ : 26 / 3 / 2005

:: المصفوفات ( Arrays ) ::

تخيل أنك تريد مثلاً أن تعرّف ( تنشئ ) مجموعة كبيرة من المتغيرات من النوع الواحد ( int على سبيل المثال ) .. فإذا أردنا مثلاً تعريف 10 متغيرات من ذلك النوع فإننا سنكتب التالي عشر مرات لعشر متغيرات يختلفون في الاسم :


int a;
و بالتالي هذه الطريقة تضيع وقتنا و تدخلنا في متاهات نحن في غنا عنها .. إذاً نحتاج لتعلم شيء جديد يفيدنا في تنفيذ ذلك الأمر .. و هذا ما توفره لنا المصفوفات .

إذاً ما الفائدة من المصفوفات ؟ نستفيد منها بالتالي :

1 – تعريف مجموعة من المتغيرات من النوع الواحد بحيث يكون لتلك المتغيرات اسم مشترك بينها يمثل اسم المصفوفة .. بالإضافة إلى أننا نستطيع الوصول إلى أي من متغيرات المصفوفة عن طريق ما يسمى بالدليل ( Index ) .
2 – هناك مسائل و أمور لا تحل إلا عن طريق المصفوفات حيث أن لها استخدامات متعددة .
3 – أنك حينما تتعامل في برنامجك مع اسم المصفوفة فإنك تتعامل مع عنوانها في الذاكرة .. سنستفيد من هذه المعلومة في حل بعض المسائل .


:: تعريف ::

المصفوفة / هي معرّف ( Identifier ) مكونة من مجموعة من المتحولات كلها من نوع واحد .. و لتلك المتحولات اسم مشترك و يشار إلى كل قيمة منها ( متحول ) باسم المصفوفة مع الدليل الذي يدل على موقع القيمة ( المتحول ) في المصفوفة و يكون دليل العنصر الأول يساوي الصفر .


:: التصريح عن المصفوفات ::

بنفس طريقة تعريف المتحولات العادية و لكن مع إضافة شيء بسيط .. حيث يمكننا تعريف مصفوفة عن طريق تحديد نوع المصفوفة ثم اسمها .. ثم القوسين ( [] ) و نضع داخل القوسين عدد المتغيرات التي نريد وضعها في المصفوفة ( قلت العدد و ليس الدليل ) و بعد ذلك نضع الفاصلة المنقوطة .. و بذلك نكون قد عرّفنا مجموعة من المتغيرات من النوع الواحد .


مثال /

int a[10];
float a[5];
bool a[8];
ملاحظة 1 / في المثال الأول سيكون دليل العنصر الأول صفر و سيكون دليل العنصر الأخير 9 .
ملاحظة 2 / هذه المصفوفات تمثل أشعة ( مصفوفة أحادية البعد ) و هذا يعني أن أبعادها كالتالي :


N × 1
حيث n ممكن أن تكون أي عدد صحيح يمثل عدد أعمدة المصفوفة .. و الواحد طبعاً يمثل وجود سطر واحد فقط في تلك المصفوفة ( لذلك قلنا أن تلك المصفوفات أحادية الأبعاد ) .

ملاحظة 3 / سنأخذ المصفوفة ثنائية الأبعاد في الفقرات التالية .
ملاحظة 4 / يجب وضع حجم المصفوفة عند تعريفها .. بخلاف مصفوفة المؤشرات التي من الممكن تحديد حجمها أثناء تنفيذ البرنامج ( سنأخذها إن شاء الله ) .


:: إعطاء قيم ابتدائية للمصفوفة ::

أمثلة /

int a[3] = { 10, 20, 30 };
int a[3] = { 5. 2 };
int a[3] = { 5 };
int a[3] = { 0 };
في المثال الأول أعطينا المصفوفة a ثلاثة قيم قيم للعناصر الثلاثة التي تحتوي عليها و سيتم إسناد القيم بالترتيب .. فالقيمة عشرة سيأخذها العنصر الأول و القيمة عشرين سيأخذها العنصر الثاني و هكذا .. بينما في المثال الثاني أعطينا المصفوفة a قيمتان فقط و الثالثة ستعتبر صفر .. بينما في المثال الثالث أعطينا المصفوفة a قيمة واحدة و البقية ستعتبر أيضاً صفر .. أما في المثال الرابع ستعتبر قيم جميع العناصر أصفار .


مثال توضيحي :

http://members.lycos.co.uk/wolfsniper84c/C/table1.jpg

ملاحظة / قلنا سابقاً فائدة إعطاء قيم ابتدائية للمتحولات .. فإذا أردت مثلاً استخدام تلك المتحولات ( في الكتابة على الشاشة أو في أحد العمليات الرياضية ) و لم تكن تحتوي على قيم فلن تكون النتائج صحيحة .. و المصفوفات بنفس الأسلوب .. و لكي تفهم الفكرة حاول كتابة قيمة متحول على الشاشة لم يعطى له قيمة ( ليس منك و لا من المستخدم ) و شاهد النتيجة .. سترى قيمة لا معنى لها .


:: التعامل مع المصفوفة ::

هناك طريقتين .. الأولى إذا كنا نريد التعامل مع عنصر واحد فقط .. و الثانية إذا كنا نريد التعامل مع جميع عناصر المصفوفة أو مع بعضها .. و الطريقتان متشابهتان :

1 – التعامل مع عنصر واحد فقط :

نستطيع ذلك عن طريق استخدام رقم الدليل الخاص بالعنصر المطلوب .. مثال / بفرض لدينا مصفوفة a تحتوي على ثلاثة أعداد حقيقية :


float a[3];
a[0] = 5.0;

هنا أعطينا العنصر الأول من المصفوفة العدد 5 .. بالتالي تحتاج دائماً إلى الدليل لاستخدام عناصر المصفوفة في أي عملية مهما كانت ( إخراج على الشاشة أو إسناد أو عملية جمع ... إلخ ) .

2 – التعامل مع أكثر من عنصر :

نحتاج لذلك إلى حلقة تكرار for .. حيث أننا سنستخدم متحول التكرار كدليل للمصفوفة في كل دورة .. لأن هذا المتحول ستتغير قيمته في كل مرة .. بحيث نضع القيمة الابتدائية له تساوي الصفر ( لأن دليل أول عنصر يساوي الصفر ) و القيمة النهائية تساوي عدد عناصر المصفوفة ناقص واحد ( لأننا قلنا أن أول أول عنصر سيأخذ الدليل صفر و الأخير سيكون دليله عدد العناصر ناقصاً الواحد ) .. مثال / بفرض أن لدينا مصفوفة a تحتوي على ثلاثة أعداد صحيحة :


int a[3];
for( int i=0; i<3; i++ )
a[i] = 5;
ملاحظة / تخيل أنك استخدمت المصفوفة a السابقة عدداً كبيراً من المرات في برنامجك بواسطة حلقة التكرار for .. بالتالي ستضع القيمة الابتدائية لمتحول التكرار صفر و النهائية 2 .. و لكن افرض أنك قمت بتغيير حجم المصفوفة a فجعلتها تحتوي 5 عناصر بدلاً من 3 ؟ فستحتاج إلى تغيير الأرقام كلها التي وضعتها لمتحول التكرار في الحلقات for .. و لكن هناك حل جيد لهذه المشكلة و هي كالتالي :

نضع ثابت صحيح في أول البرنامج يمثل حجم المصفوفة a و لنفرض أنه m .. و نضع جميع القيم النهائية لمتحول التكرار في الحلقات for القيمة m .. و بالتالي إذا احتجت إلى تغيير حجم المصفوفة بعد ذلك فستغير رقماً واحداً فقط و هو قيمة m .. مثال :


const int m = 5;
float a[m];

for( int i=0; i<m; i++ )
a[i] = 5;

Wolf Sniper
25-03-2005, 10:11 PM
:: المصفوفة الحرفية ( char ) ::

أولاً / فائدة المصفوفة الحرفية :

للمصفوفة الحرفية عدة مزايا أهمها أننا نستطيع بها تخزين جملة حرفية كاملة بدلاً من حرف واحد بواسطة char ( اسم على سبيل المثال ) .. بالإضافة إلى أن لها مزايا عند استخدامها مع قنوات الدخل و الخرج ( cout, cin ) .


ثانياً / الفرق بين مفهوم الحرف و المحرف :

الحرف : نقصد به الحرف أبجدي حصراً .. سواءً كان باللغة العربية أو الأجنبية .
المحرف : ممكن أن يكون حرف أبجدي أو من الممكن أن يكون رمز أو علامة مثل ( $ ) .


ثالثاً / التصريح عن المصفوفة الحرفية :

بنفس طريقة التصريح عن المصفوفات الأخرى .. و ذلك بذكر نوع المصفوفة ( نريد هنا char ) ثم اسمها ثم حجمها و ذلك بوضع عدد عناصرها داخل القوسين ( [ ] ) .. مثال :


char name[20];


رابعاً / إعطاء قيم ابتدائية للمصفوفة الحرفية :

الطريقة الأولى :

char name[20] = { 'm','o','n','t','a','d','a','\0' };
بنفس طريقة إعطاء قيم ابتدائية للمصفوفة العددية .. ولكن القيم هنا هي أحرف و ليست أرقام لذلك وضعنا الحاصرة الأحادية .. و لكن ما فائدة آخر رمز ؟ هذا الرمز يسمى بالمحرف الصفري .. و هو مهم لأنه يدل على نهاية الجملة الحرفية في المصفوفة .. فإذا أردنا مثلاً كتابة تلك الكلمة ( montada ) على الشاشة فإن الحرف الأخير الذي ستتم كتابته هو الحرف ( a ) و لن يتم كتابة باقي قيم المصفوفة ( حيث أن حجمها يساوي 20 أي أنها تتسع لجملة طولها 20 محرف ) .. و ذلك بسبب وجود المحرف الصفري ( سنأخذ مثال على ذلك في الفقرة التالية ) .. و لكن يمكنك في هذه الطريقة ألا تكتب المحرف الصفري لأن المترجم سيضعه بشكل تلقائي بعد آخر حرف وضعته في المصفوفة و لكن يفضل أن تكتبه أنت لكي تفهم فائدته .


الطريقة الثانية :

char name[20] = "montada";
هذه الطريقة أبسط و أسرع .. و المترجم هنا أيضاً سيقوم بوضع المحرف الصفري بشكل تلقائي بعد آخر حرف تضعه .. و هذه الطريقة خاصة بالمصفوفة الحرفية فقط دون غيرها .


خامساً / قنوات الدخل و الخرج في المصفوفة الحرفية :

1 – قنوات الدخل ( cin ) :
تستطيع ذلك بواسطة استخدام اسم المصفوفة ( فقط دون القوسين طبعاً و دون استخدام الدليل ) .. و أيضاً دون الحاجة إلى استخدام حلقة for .. و هذه الطريقة أيضاً خاصة بالمصفوفة الحرفية فقط .. مثال / بفرض أن name هي مصفوفة حرفية تحتوي على 20 عنصر :


char name[20];
cin >> name;

2 – قنوات الخرج ( cout ) :
بنفس أسلوب cin .. حيث أننا نستخدم اسم المصفوفة الحرفية فقط ( دون استخدام الدليل ) و دون استخدام حلقة for .. مثال / بفرض أن name مصفوفة حرفية :


char name[20];
cout << name;

من الممكن توضيح عمل الطريقة السابقة كالآتي ( للتوضيح فقط ) :

int i=0;
while( name[i] != '\0' )
{
cout << name[i];
i++;
}


سادساً / معرفة عدد المحارف التي تحتوي عليها المصفوفة الحرفية و التي تسبق المحرف الصفري :

يمكننا ذلك عن طريق استخدام المعامل ( الدالة ) strlen الموجودة في المكتبة string.h .. بحيث نرسل المصفوفة الحرفية ( عن طريق اسمها فقط ) أو السلسلة الحرفية ( جملة حرفية موجودة داخل حاصرات مزدوجة " " ) إلى تلك الدالة كوسائط .. مثال :


#include <string.h>
#include <iostream.h>

void main()
{
cout << strlen("Montada") << endl;

char name[20] = "Montada";
cout << strlen(name);
}

ملاحظة / لن يتم احتساب المحرف الصفري في الدالة strlen و الموجودة في المكتبة string.h .

سابعاً / تطوير cin من أجل المصفوفة الحرفية :

قلنا سابقاً أنك إذا أردت أن تطلب من المستخدم إدخال قيم حرفية و ذلك بأن تضعها في مصفوفة من النوع char .. فإنك ستستخدم التالي / بفرض أن name مصفوفة حرفية :


cin >> name;
و لكن افرض أن المستخدم أدخل جملة و ليس كلمة .. بالتالي سيكون هناك فراغات بين كل كلمة .. و هذه المسألة تعتبر مشكلة ! لماذا ؟ لأن قناة الدخل ( cin ) لن تقرأ إلا الكلمة الأولى فقط .. لأنها سوف تقف عند المسافة فهي مبرمجة على أن تنهي القراءة عند وجود الفراغ .. و لحل هذه المشكلة يمكننا استخدام الآتي :


cin.getline( name, 20 );
حيث قمنا بإضافة المنهج ( الدالة ) getline إلى cin ( سنتحدث عن المناهج أو التوابع الخاصة عن الحديث حول الكائنات ) .. فبذلك سيتم قراءة السطر ككل دون الانتهاء عند الفراغ كالسابق .. و طبعاً عليناً إرسال المصفوفة الحرفية كوسيط بالإضافة إلى أقصى حد من عدد المحارف التي تريد أن تقف القراءة عندها و يفضل أن تضع حجم المصفوفة كحد أقصى .


:: المصفوفة ثنائية الأبعاد ::

أولاً / مقدمة :

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


ثانياً / التصريح عن المصفوفة ثنائية الأبعاد :

بنفس أسلوب المصفوفة أحادية البعد .. و لكننا كما قلنا يجب أن نحدد حجم البعد الثاني أيضاً .. بحيث يمثل البعد الأول عدد الأسطر و البعد الثاني عدد الأعمدة .. مثال :


int a[3][3];


ثالثاً / إعطاء قيم ابتدائية :

مثال 1 / بفرض أننا نريد تعريف مصفوفة تحتوي على سطرين و عمودين .. بحيث يكون في السطر الأول العددين 1 و 2 .. و في السطر الثاني العددين 3 و 4 :

int a[2][2] = { {1,2} , {3,4} };

مثال 2 / نريد تعريف مصفوفة تحتوية على سطرين و ثلاثة أعمدة و لكن قيمها كلها أصفار :

int a[2][3] = { 0 };

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

مثال توضيحي :

http://members.lycos.co.uk/wolfsniper84c/C/table2.jpg


رابعاً / المصفوفة ثنائية الأبعاد الحرفية :

تتمتع بنفس مزايا المصفوفة الحرفية أحادية البعد ما عدا ميزة الإدخال ( cin ) و الإخراج ( cout ) .. حيث أننا نحتاج إلى حلقتين for بدلاً من واحدة .. الحلقة الأولى من أجل عدد الأسطر و الثانية من أجل عدد الأعمدة .


مثال 1 / بفرض أننا نريد تعريف مصفوفة تحتوي على خمسة أسماء بحيث يمثل كل سطر اسم ما .. و طلبنا من المستخدم إدخال تلك الأسماء فسيكون الحل كالتالي :

char name[5][20];

for( int i=0; i<5; i++ )
for( int j=0; j<20; j++ )
cin >> name[i][j];
مثال 2 / أو من ممكن أن نستخدم الشكل التالي :


char name[5][20];

for( int i=0; i<5; i++ )
cin >> name[i];

يمكننا إعطاء قيم ابتدائية للمصفوفة السابقة على الشكل التالي :

char name[5][20] = { {"montada"} , {'m','o','n','t','a','d','a','\0'} };
حيث أنني وضعت اسمين فقط بطريقتين مختلفتين .. و هما صحيحتين على ألا تنسى في الطريقة الثانية المحرف الصفري كما قلنا سابقاُ .. و باقي الأسطر ستحتوي على الفراغ .. فإذا حاولنا كتابة المصفوفة السابقة على الشاشة فإننا سنكتب التالي :


for( int i=0; i<5; i++ )
{
for( int j=0; j<20; j++ )
cout >> name[i][j];
cout << endl;
}

استخدمنا cout << endl لكي نضع كل جملة على سطر .

Wolf Sniper
25-03-2005, 10:28 PM
:: استخدام المصفوفات في التوابع كمتحولات وسيطة ::

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


void f1( int s[20] )
{
}

و يمكننا أيضاً عدم وضع حجم المصفوفة .. مثال /

void f1( int s[ ] )
{
}
هذا إن كنا نستخدم الطريقة الأولى في كتابة التوابع .. أما لو استخدمنا الطريقة الثانية و هي كالتالي / بوضع رأس التابع فوق التابع الأساسي main و جسم التابع يكون أسفله :


void f1( int [ ] );

فإننا لن نحتاج إلى وضع حجم المصفوفة أو حتى اسمها في رأس التابع و لكن يجب وضع القوسين( [ ] ) ( ليس كالسابق كالمتغيرات العادية حيث كنا نكتفي بنوع المتغير فقط ) .. هذا الأمر فقط في المصفوفة أحادية البعد .. أما في المصفوفة ثنائية البعد فيجب تحديد حجم البعد الثاني فقط متجاهلين حجم البعد الأول .


مثال :

void f1( int [ ][20] );

أو حتى عند كتابة التابع نفسه :

void f2( int s[ ][20] )
{
}

قاعدة 1 / عند تعريف المصفوفات ( سواءً الأحادية أم الثنائية البعد ) كمتحولات وسيطة في التوابع :

1 - فإننا لن نحتاج إلى وضع اسم المصفوفة إذا ما عرفنا رأس التابع و لكن نحتاج إلى وضع حجم البعد الثاني لمصفوفة إذا كانت ثنائية الأبعاد .. و الأقواس ( [ ] ) طبعاً ضرورية .
2 – و لكننا نحتاج إلى وضع اسم المصفوفة عند وضع جسم التابع مع إمكانية تجاهل حجم البعد الأول فقط للمصفوفة ( قلت الحجم و ليس القوسين ( [ ] ) ) كما في الفقرة الأولى .

و لكن كيف يمكننا إرسال إحدى قيم ( عناصر ) المصفوفة ؟ أو حتى كيف يمكننا إرسال المصفوفة كلها إلى إحدى التوابع كمتحولات وسيطة فعلية ؟ ( راجع درس التوابع لمعرفة معنى متحولات وسيطة فعلية و شكلية ) ..

قاعدة 2 / يمكننا إرسال أحد عناصر المصفوفة عن طريق وضع اسم المصفوفة مع الدليل الخاص بذلك العنصر .. مثال / بفرض أن f1 تابع لا يعيد قيمة يحتاج إلى عدد صحيح كمدخلات له .. و s هي مصفوفة من النوع int :


f1( s[0] );

قاعدة 3 / أما إذا كنا نريد إرسال المصفوفة كلها إلى تابع ما .. فنضع اسم المصفوفة فقط دون حجمها و بدون الأقواس ( [ ] ) .. مثال / هنا التابع f1 يحتاج إلى مصفوفة كمدخلات و ليس إلى قيمة واحدة كالمثال السابق :


f1( s );

قاعدة 4 / إن نوع الاستدعاء في المصفوفات هو استدعاء بالمرجع ( حصراً ) و لا يمكن استدعاؤها بالقيمة ( هذا إن كنا أرسلنا المصفوفة كلها كمتحول فعلي إلى تابع آخر ) ..
و بالتالي إذا قمت بتغيير إحدى قيم المصفوفة في أحد التوابع فإن تلك القيمة ستتغير في المصفوفة الأصلية لأنها تستدعى بالمرجع بشكل تلقائي .. حيث أن اسمها يمثل عنوان أول عنصر ( لها ) في الذاكرة .. و قلنا سابقاً بأن معنى المرجعية ( Reference ) هو التعامل مع عنوان المتغير في الذاكرة .. و بالتالي يمكن أن يكون هناك أكثر من متغير يحملون نفس الموقع في الذاكرة ( متغير فعلي واحد مع عدة متغيرات شكلية ) .. فإذا تغير أحدهم يتغير الآخر ( راجع موضوع الاستدعاء بالقيمة و بالمرجع في درس التوابع ) .

قاعدة 5 / إن الاستدعاء لأحد قيم المصفوفة من الممكن أن يكون إما بالقيمة أو بالمرجع .. مثال لاستدعاء إحدى قيم المصفوفة بالقيمة / لبرنامج يقوم بطلب جملة من المستخدم و من ثم يكتب له أول حرف من هذه الجملة ( للتوضيح فقط ) :


#include <iostream.h>

void f1( char s )
{
cout << s << endl;
}

void main()
{
char name[20];

cin >> name;

f1(name[0]);
}


ملاحظة / إذا كنا نريد في المثال السابق أن نستدعي القيمة :


name[0]
بالمرجع فما علينا إلا أن نضيف الرمز ( & ) قبل المتحول الشكلي ( s ) الموجود في التابع ( f1 ) .


:: نظام الترميز ASCII ::

1 – مقدمة :

لماذا نتعلم نظام الآسكي ؟ في الحقيقة هذا النظام مفيد جداً لحل بعض المسائل .. فهذا النظام قام على أساس وضع لكل رقم أو حرف أو رمز ... إلخ رقماً معين ( شفرة ) يشير إلى ذلك المحرف أو الرقم .. بحيث يحتوي هذا النظام على 256 رقماً يشيرون إلى تلك الرموز و الأحرف و الأرقام .

2 – محتويات النظام ASCII :

يحتوي هذا النظام كما قلنا على عدة رموز ( سنأخذ المهمة فقط ):

أ ) من 0 إلى 32 : عبارة عن مجموعة من الأوامر و الأحـداث .. كحدث الضغط على زر Enter أو Space .
ب ) من 48 إلى 57 : الأرقام من الصفر و حتى التسعة .
ج ) من 65 إلى 90 : الأحرف الإنجليزية الكبيرة مثل : ( A, B, C ) .
د ) من 97 إلى 122 : الأحرف الإنجليزية الصغيرة مثل : ( a, b, c ) .

3 - استخدام نظام الترميز ASCII في السي++ :

يمكننا ذلك عن طريق المتغيرات من النوع int و char .. كيف ؟ و ذلك إما بوضع محرف في متغير عددي صحيح ( int ) أو بوضع عدد صحيح في متغير حرفي ( char ) .. انظر المثال التالي :


int a = 'a';

char b = 98;

في المثال الأول أعطينا المتغير العددي a قيمة حرفية من النوع char .. و بالتالي سيقوم المترجم بإيجاد الرمز الذي يشير إلى الحرف الصغير a في النظام ASCII .. و سوف يضع القيمة ( 97 ) في المتحول a .. لذلك إذا حاولت كتابة قيمة المتحول a على الشاشة فسوف يظهر الرقم 97 كما قلنا .

في المثال الثاني عكسنا العملية .. فقمنا بوضع قيمة عددية في متغير حرفي .. لذلك سيقوم المترجم بإيجاد القيمة التي يشير إليها الرمز 98 و هي الحرف b الصغير .. لذلك قم بكتابة قيمة كل من المتغيرين a و b على الشاشة ( cout ) و شاهد النتائج .

4 – الاستفادة من نظام الترميز ASCII :

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


----------------------------------------

Wolf Sniper
25-03-2005, 10:36 PM
:: أمثلة تطبيقية ::

سأقوم بوضع أمثلة تتدرج من السهل إلى الأصعب .. مع وضع برنامج تطبيقي في النهاية يشمل موضوع المصفوفات بشكل كامل .

ملاحظة / جميع هذه الأمثلة من أستاذي ( جزاه الله خيراً ) .. و ها أنا أضعها بين يديكم .

المثال 1 /

نريد من المستخدم أن يدخل خمسة أعداد صحيحة و من ثم نكتب له مجموع تلك الأعداد :

http://members.lycos.co.uk/wolfsniper84c/C/array1.jpg


الحل /

#include <iostream.h>

void main()
{
int sum = 0;
int a[5];

for( int i=0; i<5; i++ )
{
cout << "a[" << i << "] : ";
cin >> a[i];
sum += a[i];
}

cout << "Sum = " << sum << endl;
}

شرح الحل :

1 – قمنا بتعريف المتغير sum و الذي سيقوم بحساب مجموع الأعداد المدخلة .. و وضعنا له قيمة ابتدائية تساوي الصفر ( قلنا سابقاً فائدة ذلك ) .
2 – عرفنا مصفوفة أعداد صحيحة لكي يدخل المستخدم الأعداد فيها .. و هي تحتوي على خمسة مواقع في الذاكرة من النوع int .
3 – بعد ذلك قمنا بترتيب عملية الإدخال لكي يعرف المستخدم أي الأعداد سيدخل .. و ذلك عن طريق عملية الإخراج الأولى .. حيث أن المستخدم في كل عملية إدخال جديدة ( في كل دورة ) سيتم تخزين العدد المدخل في مكانه المناسب في المصفوفة a .. و بعدها أيضاً سيتم إضافة ذلك العدد إلى sum في كل مرة لكي يحتوي sum في النهاية على مجموع تلك الأعداد .
4 – في النهاية قمنا بكتابة ناتج عملية الجمع بواسطة المتحول sum .


----------------------------------------

المثال 2 /

نريد من المستخدم أن يدخل عدداً من الحروف و من ثم نكتب له كل حرف قام بكتابته على سطر :

http://members.lycos.co.uk/wolfsniper84c/C/array2.jpg

الحل /

#include <iostream.h>
#include <string.h>

void main()
{
char s[100];

cin >> s;

for( int i=0; i<strlen(s); i++ )
cout << s[i] << endl;
}

شرح الحل :

1 – قمنا بتعريف مصفوفة حرفية تتسع لمائة محرف و سميناها s ( المائة عدد افترضناها تستطيع وضع ما شئت ) .
2 – طلبنا من المستخدم إدخال تلك الأحرف .
3 – في الحلقة for .. قمنا بجعل الدليل i يبدأ من القيمة صفر ( لأن دليل أول عنصر في المصفوفة يساوي الصفر ) و ينتهي في آخر دورة بعدد محارف تلك المصفوفة و التي تسبق المحرف الصفري ( راجع فقرة معرفة عدد المحارف ) .. و لكن لماذا فعلنا هذا ؟ لأننا نريد كتابة الأحرف التي أدخلها المستخدم فقط و لا نريد جميع عناصر المصفوفة حيث أن المستخدم قد يدخل عدداً من الأحرف تقل عن المائة .
4 – في داخل الحلقة for قمنا في كل دورة بكتابة عنصر المصفوفة ذي الدليل i و انتقلنا إلى سطر جديد .. و هذه العملية ستتكرر على حسب عدد المحارف التي أدخلها المستخدم .


----------------------------------------

المثال 3 /

نريد من المستخدم إدخال جملة و من ثم نكتب له كل كلمة أدخلها على سطر :

http://members.lycos.co.uk/wolfsniper84c/C/array3.jpg

الحل /

#include <iostream.h>
#include <string.h>

void main()
{
char s[100];

cin.getline( s, 100 );

for( int i=0; i<strlen(s); i++ )
if( s[i] == ' ' )
cout << endl;
else
cout << s[i];

cout << endl;
}

شرح الحل :

1 – بنفس أسلوب المثال السابق و لكن مع بعض التطوير .. حيث قمنا باستخدام المنهج ( الدالة ) getline لكي نخزن في المصفوفة s جميع الكلمات و ليس الكلمة الأولى فقط حيث أننا سنقرأ أول مائة محرف ( قلنا أنه يفضل وضع حجم المصفوفة و الذي سنرسله مع المصفوفة s كوسائط إلى المنهج getline ) .. و حيث أن cin العادية ستقوم بقراءة أول كلمة فقط حيث أنها تنتهي القراءة عند الفراغ ( راجع فقرة تطوير قناة الدخل cin ) .
2 – في الحلقة for .. سنختبر في كل دورة عناصر المصفوفة عنصراً عنصراً .. فإذا كان هذا العنصر ( المحرف ) يساوي الفراغ فسنقوم بالنزول إلى سطر جديد .. أما إذا لم يكن كذلك فسنكتب له المحرف و بهذه الطريقة سيتم وضع كل كلمة على سطر و هذا هو المطلوب .
3 – في النهاية قمنا فقط بالنزول إلى سطر جديد من أجل ترتيب البرنامج .


----------------------------------------

المثال 4 /

نريد من المستخدم إدخال 10 أعداد صحيحة و من ثم نكتب له أكبر عدد و أصغر عدد و ذلك باستخدام المصفوفات :

http://members.lycos.co.uk/wolfsniper84c/C/array4.jpg

الحل /

#include <iostream.h>

void main()
{
int a[10], max, min;

cout << "a[0] = ";
cin >> a[0];

max = a[0];
min = a[0];

for( int i=1; i<10; i++ )
{
cout << "a[" << i << "] = ";
cin >> a[i];

if( a[i] > max ) max = a[i];
if( a[i] < min ) min = a[i];
}

cout << "Max = " << max << endl
<< "Min = " << min << endl;
}

شرح الحل :

هذا المثال يطابق تماماً المثال الثالث الموجود في الدرس الثالث .. لذلك لن أشرحه لكي لا أقوم بتكرار شيء موجود .


----------------------------------------

المثال 5 /

نريد من المستخدم إدخال جملة و من ثم نحسب له عدد كلمات تلك الجملة .. على أنه من الممكن أن يكون بين كل كلمتين أكثر من فراغ أو حتى من الممكن أن يكون هناك رمز ما مثل ( $ ) أو حتى رقم .. لذلك من الخطأ حساب عدد الفراغات .. و لكن ما الحل ؟

فكرة الحل / يمكننا حساب عدد كلمات جملة ما إذا عرفنا متى تنتهي الكلمة .. تنتهي الكلمة إذا وجدنا حرف في المصفوفة بعده فراغ و بالتالي يكون ذلك الحرف هو آخر حرف في الكلمة .. عندها يكون عندنا متحول يحسب لنا عدد الكلمات فنزيده واحد و هكذا ..


http://members.lycos.co.uk/wolfsniper84c/C/array5.jpg


الحل /
سنستخدم التوابع في هذا الحل :

#include <iostream.h>
#include <string.h>
#include <ctype.h>

int nword(char s[ ]);

void main()
{
char s[100];

cout << "Enter The Sentence : ";
cin.getline(s, 100);

cout << nword (s) << endl;
}

int nword(char s[100])
{
int c=0;

for( int i=0; i<strlen(s); i++ )
if( isalpha(s[i]) && !isalpha(s[i+1]) )
c++;

return c;
}

شرح الحل :

1 – ما فائدة التابع ( الدالة ) isalpha ؟ هذا التابع يقوم باختبار فيما إذا كانت القيمة المرسلة إليه حرفاً أبجدياً أم لا .. و هذا ما نحتاجه في سؤالنا هذا .. و هذا التابع موجود في المكتبة ctype.h .
2 – أظن أن التابع main مفهوم .. فقط قمنا بتعريف مصفوفة حرفية و من ثم طلبنا من المستخدم إدخال جملة و بعد ذلك استدعينا التابع nword و الذي سيحسب لنا عدد الكلمات للجملة المدخلة من قبل المستخدم .
3 – في التابع nword .. قمنا بأخذ المصفوفة من التابع main و أجرينا عليها الاختبار الذي ذكرناه في فكرة الحل .. قمنا باختبار جميع قيم المصفوفة فإذا وجدنا أن إحدى القيم هي حرف أبجدي و كان المحرف الذي يليه ليس حرفاً أبجدياً ( استخدمنا علامة النفي ! ) بالتالي يكون لدينا كلمة جديدة و عندها نزيد العداد ( c ) واحد .. و هكذا إلى نهاية المصفوفة .. و في النهاية سيقوم التابع nword بإرجاع قيمة عدد الكلمات إلى التابع main .


----------------------------------------

Wolf Sniper
27-03-2005, 03:38 PM
المثال 6 /

نريد من المستخدم إدخال جملة و من ثم نعطيه إحصائية عن الحروف أو الرموز أو الأرقام الموجودة في تلك الجملة :

http://members.lycos.co.uk/wolfsniper84c/C/array6.jpg


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

فكرة الحل / سنستخدم نظام الترميز ASCII لأنه سيسهل علينا اختبار جميع الرموز و الأحرف و الأرقام و التي من الممكن أن يدخلها المستخدم عن طريق لوحة المفاتيح .. و ذلك عن طريق الآتي :

1 - سنقوم بتعريف مصفوفة تحتوي على 256 عنصر من النوع int .. و جميع تلك المتغيرات عبارة عن عدادات إحصائية .. بحيث سيقوم العنصر رقم 97 بحساب عدد مرات تكرار الحرف a و العنصر رقم 98 بحساب عدد مرات تكرار الحرف b ... إلخ .. و هكذا .. و بالتالي قمنا بإنشاء مصفوفة تمثل نظام ASCII بنفس الترتيب للرموز من الصفر و حتى 256 .. حيث أن العنصر الأول يمثل الرمز الأول في ذلك النظام و العنصر الثاني يمثل الرمز الثاني ... إلخ .. و هكذا .
2 – سنقوم باستخدام طريقة إعطاء قيمة حرفية لمتغير عددي من النوع int و التي ذكرناها في فقرة نظام الترميز ASCII .


الحل /

#include <iostream.h>
#include <string.h>
#include <ctype.h>

void main()
{
int counter[256] = {0};
char s[100];

cout << "Enter a Sentence : ";
cin.getline(s, 100);

for( int i=0; i<strlen(s); i++ )
if( isalpha(s[i]) )
counter[ s[i] ]++;

for( i=0; i<256; i++ )
if( counter[i] != 0 )
cout << (char)i << " : " << counter[i] << endl;
}


ملاحظة / انظر إلى الحل كم هو قصير .. و افرض أننا لم نستخدم نظام الآسكي .. عندها سنحتاج ربما لصفحات لحل هذا السؤال .

شرح الحل :

1 – كما قلنا في فكرة الحل .. قمنا بتعريف مصفوفتين الأولى من أجل القيام بإحصائية حول عدد كل حرف أو رمز قام المستخدم بإدخاله ( طبعاً يجب أن نجعل جميع القيم فيها أصفار لأنها عدادات ) .. و الثانية من أجل الجملة التي سيدخلها المستخدم .
2 – طلبنا من المستخدم إدخال تلك الجملة .. و استخدمنا المنهج getline لأننا نريد إدخال جملة و ليس كلمة واحدة فقط .
3 – في الحلقة الأولى for .. قمنا باستخدمنا الشرط لاختبار فيما إذا كان المحرف هو حرف أم لا .. لأنه من الممكن أن يكون مسافة أو رمز .. و نحن نريد الأحرف الأبجدية فقط .. و داخل الشرط قمنا بحساب عدد مرات تكرار كل حرف من أحرف الجملة المدخلة .. و لكن كيف ؟ انظر أولاً إلى :


s[i]
قيمتها في أول دورة ستكون الحرف الأول و الذي أدخله المستخدم .. و لكن :


s[i]
وضعت في داخل القوسين ( [ ] ) المتعلقين بدليل المصفوفة counter .. و نحن نعلم جيداً أن الدليل دائماً و أبداً يجب أن يكون من النوع int حصراً .. و بالتالي سيقوم المترجم بإيجاد الرمز ( الرقم ) في النظام آسكي و الذي يقابل الحرف الأول من الجملة .. فإذا فرضنا أن ذلك الحرف كان a بالتالي سيكون دليل المصفوفة counter هو الرقم 97 لذلك سيتم زيادة العنصر رقم 97 في المصفوفة counter بالقيمة واحد ... و هكذا حتى نحسب جميع أحرف الجملة .
4 – في الحلقة الثانية .. سنقوم بكتابة قيم العدادات ( الإحصائية ) .. و لكننا لا نريد كتابة إلا عدد الأحرف التي أدخلها المستخدم و لا نريد كتابة باقي قيم المصفوفة counter لأن جميعها أصفار .. و بالتالي قمنا باختبار فيما إذا كانت قيمة كل عنصر لا تساوي صفر فإذا كانت كذلك فهذا يعني أن هناك إحصائية .. و بالتالي نقوم بكتابة تلك الإحصائية


counter[i]
و لكن لماذا وضعنا i (char) ؟ بنفس الأسلوب لكي يظهر الحرف الذي تكرر في الجملة و ليس رقمه ( رمزه ) في النظام آسكي ( راجع المثال التطبيقي الأول في الدرس الثاني لمعرفة فائدة استخدام تلك العمليات القسرية ) .

ملاحظة / في الحلقة الأولى وضعنا ( int i ) .. و لكننا لم نضع ذلك في الحلقة الثانية بل استخدمنا i فوراً ؟ لماذا ؟ لأننا في الحلقة الأولى قمنا بتعريف i لذلك من الخطأ تعريفها من جديد .. و نحن نعلم أنه يمكننا في لغة السي++ أن نصرح عن المتغيرات في أي مكان نريد .


----------------------------------------

المثال 7 /

نريد من المستخدم إدخال مصفوفة ثنائية مربعة الشكل و من ثم نكتب له مجموع القطر الأساسي و الثانوي لتلك المصفوفة :

http://members.lycos.co.uk/wolfsniper84c/C/array7.jpg


ملاحظة 1 / القطر الأساسي جميع عناصره تحقق المعادلة : i = j .. حيث أن ( i ) للسطر و ( j ) للعمود .
ملاحظة 2 / القطر الثانوي جميع عناصره تحقق المعادلة : i = n – 1 .. حيث أن ( n ) هي حجم المصفوفة المربعة .

الحل /
سنستخدم التوابع في هذا الحل :

#include <iostream.h>

const int n = 3;

void read(int a[][n]);
int sum_primary (int a[][n]);
int sum_secondary(int a[][n]);

void main()
{
int array[n][n];

read(array);
cout << "Primary = " << sum_primary(array) << endl;
cout << "Secondary = " << sum_secondary(array) << endl;
}

void read(int a[n][n])
{
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
{
cout << "array[" << i << ']' << '[' << j << "] = ";
cin >> a [i][j];
}
cout << endl;
}
}

int sum_primary(int a[n][n])
{
int c=0;

for(int i=0; i<n; i++)
c += a[i][i];

return c;
}

int sum_secondary(int a[n][n])
{
int c=0;

for(int i=0; i<n; i++)
c += a[i][n-i-1];

return c;
}


شرح الحل :

1 – في أول البرنامج قمنا بتعريف ثابت من النوع الصحيح ( int ) و الذي سيحدد حجم المصفوفة الثنائية .. و سنستخدمه أيضاً في الحلقات for و التي سنحتاجها لبعض العمليات على تلك المصفوفة .. و لقد افترضنا أن حجم المصفوفة هو 3×3 .

2 – قمنا بتعريف ثلاثة توابع :
الأول / read : من أجل إدخال المصفوفة .. و لقد أضفنا عليه بعض عمليات الإخراج من أجل تحسين مظهر البرنامج .. و هذا التابع من النوع void أي أنه لن يعيد قيمة و بالتالي يمكن استدعاءه بذكر اسمه فقط مع المتحولات الوسيطة التي يحتاجها .. و طبعاً يجب أن نرسل له المصفوفة الثنائية لكي يستطيع أن يتعامل معها .
الثاني / sum_primary : من أجل حساب مجموع القطر الرئيسي .. و قلنا أن القطر الرئيسي يمكن إيجاد عناصره عن طريق المعادلة i=j .. و هذا التابع هو من النوع int أي أنه سيعيد قيمة عددية صحيحة .. و بالتالي لا يمكن استدعاءه بذكر اسمه فقط مع القوسين ! بل يجب ذلك إما ضمن عملية إسناد أو إخراج ( cout ) ... إلخ ( راجع درس التوابع ) .. و أيضاً سيحتاج إلى المصفوفة لكي يستطيع أن يتعامل معها لهذا وضعنا مصفوفة ثنائية من نفس الحجم كمتحول وسيط .
الثالث / sum_secondary : من أجل حساب مجموع القطر الثانوي .. و بنفس أسلوب التابع السابق و لكن يمكن إيجاد عناصره عن طريق المعادلة التالية : i = n-1 .. و هذا يعني أن دليل أي عنصر من القطر الثانوي يحقق التالي : ( n – i – 1 ) .. و ذلك بعد أن أرسلنا i إلى طرف المعادلة الأيمن .

3 – في التابع main قمنا بتعريف المصفوفة الأساسية و التي سنجري عليها جميع العمليات المطلوبة .. و قمنا أيضاً باستدعاء التوابع الثلاثة المذكورة سابقاً .

4 – في التابع sum_primary : قمنا باستقبال المصفوفة array عن طريق المتحول الوسيط الشكلي ( المصفوفة ) :


a[n][n]
و هي بنفس الحجم 3×3 .. و أيضاً قمنا بتعريف المتغير c و الذي سيحسب لنا مجموع القطر الرئيسي .
الأن من أجل حساب ذلك المجموع .. ما الذي سنحتاجه ؟ في الحقيقة لن نحتاج إلا لحلقة for واحدة فقط .. و ذلك عن طريق استخدام دليل i الخاص بالحلقة لأننا قلنا أن القطر الرئيسي عناصره يمكن إيجادها عن طريق المعادلة i=j .. و بالتالي ليس هناك ضرورة إلى المتحول j لذلك لن نستخدمة حلقة أخرى .. و بالتالي في داخل الحلقة قمنا بجمع عناصر القطر الرئيسي ذو الدليل :


a[i][i]
و وضعنا المجموع في المتحول c .. و في النهاية قمنا بإرجاع قيمة c كقيمة معادة لكي يستفيد من هذه القيمة باقي التوابع و منهم بالتأكيد التابع الأساسي main .

ملاحظة / دائماً كنت أقول لكم حاولوا إيجاد العلاقة ( المعادلة ) بين السطر و العمود في كل مسألة تواجهكم مثل هذه المسألة و التي تتعلق بالمصفوفات .. و عندها سيكون حل السؤال بسيطاً و سهلاً .

5 – في التابع sum_secondary : بنفس أسلوب التابع السابق و لكن ستختلف معادلة البعد الثاني للمصفوفة في داخل الحلقة for .. فأول عنصر في القطر الثانوي سيكون سطره يساوي ( الصفر ) و عموده يساوي ( 2 ) .. و العنصر الثاني سيكون سطره ( 1 ) و عموده ( 1 ) .. و العنصر الثالث سيكون سطره ( 2 ) و عموده ( صفر ) .. و بالتالي ستكون معادلة دليل البعد الثاني :
(n-i-1) .. و هذا ما وضعناه في أدلة المصفوفة a .. و بالتالي يقوم المتغير c بحساب مجموع ذلك القطر و من ثم سنقوم بإرجاع قيمته إلى التابع main .

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


----------------------------------------

Wolf Sniper
27-03-2005, 04:01 PM
:: برنامج تطبيقي ::

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

http://members.lycos.co.uk/wolfsniper84c/C/program1.jpg


و فكرة البرنامج هي كالتالي : نريد من المستخدم إدخال ثلاثة أسماء لثلاثة طلاب بالإضافة إلى ثلاثة مواد يقومون بدراساتها و أيضاً علاماتهم في تلك المواد .. و بعد ذلك يمكن للمستخدم مشاهدة جدول كامل يحتوي على جميع البيانات التي قام المستخدم بإدخالها و هي على الشكل التالي :

http://members.lycos.co.uk/wolfsniper84c/C/program2.jpg


ولكن قبل ذلك علينا إدخال البيانات اللازمة .. في البداية ندخل الأسماء على الشكل التالي :

http://members.lycos.co.uk/wolfsniper84c/C/program3.jpg


و بعد ذلك ندخل أسماء المواد على الشكل التالي :

http://members.lycos.co.uk/wolfsniper84c/C/program4.jpg


و بعد ذلك ندخل علامات الطلاب في تلك المواد على الشكل التالي :

http://members.lycos.co.uk/wolfsniper84c/C/program5.jpg


و بعد ذلك نحسب معدل ( النسبة المئوية ) لكل طالب على الشكل التالي :

http://members.lycos.co.uk/wolfsniper84c/C/program6.jpg


الحل /


#include <iostream.h>
#include <ctype.h>

const int number = 3;

void readstudents(char [ ][10]);
void readmaterials(char [ ][10]);
void readmarks(char [ ][10], char [ ][10], int [ ][number]);
void averagestudents(char [ ][10], int [ ][number], float [ ]);
void printall(char [ ][10], char [ ][10], int [ ][number], float [ ]);

void main()
{
char students[number][10];
char materials[number][10];
int marks[number][number];
float average[number];

char choice;

do{
cout << "S. Read Students\n"
<< "M. Read Materials\n"
<< "K. Read Marks\n"
<< "P. Average Students\n"
<< "A. Print All\n"
<< "Q. Quit\n\n"
<< "Enter your choice : ";
cin >> choice;

switch( toupper(choice) )
{
case 'S' : readstudents(students); cout << "\n------------------------------\n"; break;
case 'M' : readmaterials(materials); cout << "\n------------------------------\n"; break;
case 'K' : readmarks(students, materials, marks); cout << "\n------------------------------\n"; break;
case 'A' : printall(students, materials, marks, average); cout << "\n------------------------------\n"; break;
case 'P' : averagestudents(students, marks, average); cout << "\n------------------------------\n"; break;
case 'Q' : return;

default : cout << "Error.. \n\n------------------------------\n\n";
}
}while( true );
}

void readstudents(char students[ ][10])
{
cout << endl;
for( int i=0; i<number; i++ )
{
cout << "Name of Student Number " << i << " : ";
cin >> students[i];
}
}

void readmaterials(char materials[ ][10])
{
cout << endl;
for( int i=0; i<number; i++ )
{
cout << "Name of Mterial Number " << i << " : ";
cin >> materials[i];
}
}

void readmarks(char students[ ][10], char materials[ ][10], int marks[ ][number])
{
cout << endl;
for( int i=0; i<number; i++ )
{
for( int j=0; j<number; j++ )
{
cout << "Mark of " << students[i] << " in " << materials[j] << " : ";
cin >> marks[i][j];
}
cout << endl;
}
}

void averagestudents(char students[ ][10], int marks[ ][number], float average[ ])
{
cout << endl;

for( int i=0; i<number; i++ )
{
int sum=0;

for( int j=0; j<number; j++ ) sum+=marks[i][j];

average[i] = (float)sum / (float)number;
}

for( i=0; i<number; i++ )
cout << "Average of Student " << students[i] << " = " << average[i] << endl;
}

void printall(char students[ ][10], char materials[ ][10], int marks[ ][number], float average[ ])
{
cout << "\n---------------\n\n";
for( int i=0; i<number; i++ )
{
cout << "Student : " << students[i] << endl
<< "Average = " << average[i] << endl << endl;
for( int j=0; j<number; j++ )
cout << "Mark of " << materials[j] << " = " << marks[i][j] << endl;
cout << "\n---------------\n\n";
}
}
شرح الحل :

1 – أولاً قمنا بتعريف الثابت number و الذي سنحتاجه من أجل حجم المصفوفات التي سنعرفها .

2 – لقد وضعنا كل عملية في تابع مستقل ( كعملية إدخال الأسماء ) .. و قمنا أيضاً بكتابة المصفوفات الوسيطة ( Parameter ) و التي سيحتاجها التابع لكي يقوم بوظيفته .. و طبعاً وضعنا نوع القيمة المعادة من النوع void لأننا لا نحتاج إلى إعادة قيمة .
ملاحظة / لاحظ كيف أننا لم نحدد حجم البعد الأول للمصفوفات ( في المصفوفات الوسيطة ) .. راجع فقرة استخدام المصفوفات كمتحولات وسيطة .

3 – في التابع الرئيسي main :
أ ) قمنا بتعريف المصفوفات المطلوبة و هي كالتالي :
students : مصفوفة ثنائية الأبعاد من النوع char .. و التي ستخزن أسماء الطلاب بحيث يكون كل اسم على سطر مستقل ضمن أسطرها على أن لا يتجاوز اسم الطالب عشرة أحرف .
materials : مصفوفة ثنائية الأبعاد من النوع char .. لتخزين أسماء المواد ( بنفس أسلوب المصفوفة students ) .
marks : مصفوفة ثنائية الأبعاد من النوع int .. لتخزين علامات الطلاب بحيث يمثل السطر الأول منها علامات الطالب الأول و السطر الثاني من أجل علامات الطالب الثاني و السطر الثالث من أجل علامات الطالب الثالث .. و يمثل العمود الأول من المصفوفة المادة الأولى .. و العمود الثاني المادة الثانية .. و العمود الثالث المادة الثالثة .. و بالتالي نستطيع بهذه المصفوفة تخزين علامات جميع الطلاب في جميع المواد .
average : مصفوفة أحادية البعد من النوع float .. من أجل تخزين معدل كل طالب بحيث تمثل القيمة الأولى معدل الطالب الأول .. و القيمة الثانية معدل الطالب الثاني .. و القيمة الثالثة معدل الطالب الثالث .

ب ) بعد ذلك قمنا بتعريف المتحول choice ذو النوع char .. و الذي سيخزن قيمة الحرف الذي سيدخله المستخدم و الذي يمثل اختصار لتنفيذ العملية المطلوبة منا .

ج ) استخدمنا التكرار ( do\while ) لأن برنامجنا سينفذ أكثر من أمر من بين الأوامر التي وضعناها حتى يضع المستخدم الحرف ( Q ) الذي سينهي البرنامج .

د ) في داخل التكرار ( do\while ) .. قمنا بكتابة الأوامر التي يستطيع المستخدم أن يقوم بها .. و بعد ذلك طلبنا منه أن يقوم بإدخال حرف و الذي سيدل على عملية ما من بين العمليات التي كتبناها .

هـ ) بعد ذلك استخدمنا العبارة switch لكي نضع الاحتمالات التي يمكن أن يحتوي عليها المتغير choice .. و لكن ما معنى الدالة toupper ؟ افرض أن المستخدم أدخل الحرف ( S ) كحرف صغير و الذي يدل على عملية إدخال أسماء الطلاب .. لذلك لن يقوم برنامجنا باستدعاء التابع readstudents لأننا قمنا بوضع في الحالة case الحرف ( S ) كحرف كبير .. لذلك تقوم الدالة toupper بتكبير حجم الحرف .. و بذلك نتعامل مع المتحول choice بالأحرف الكبيرة فقط .

و ) داخل switch .. قمنا بوضع جميع اختصارات الأوامر الموجودة و التي يمكن للمستخدم أن يقوم بها و في كل حالة قمنا باستدعاء التابع الخاص بالعملية المطلوبة .. مع إضافة سطر بعد استخدام كل عملية .. و إذا أدخل المستخدم الحرف ( Q ) فإن البرنامج سينتهي و ذلك لأننا استخدمنا return و التي تقوم بالخروج من التابع و إنهاء عمله كلياً و بما أننا في التابع main فسينهي البرنامج .. و إذا أدخل المستخدم حرف يختلف عن الأحرف التي وضعناها فسنكتب له رسالة Error و ذلك عن طريق default .

4 – بالنسبة للتوابع التي كتبناها .. أعتقد أنها سهلة و لا حاجة لشرحها فكل ما هو موجود فيها قد شرحناه سابقاً .. و إذا أراد أحد منكم أن أشرح له أحدها فلا بأس .. و لكن لاحظوا إلى أننا نريد أن نكتب التوابع بالشكل الموجود في الصور تماماً .


----------------------------------------

:: المكتبة Library2.h ::

سنقوم بإضافة التابع nword الموجود في المثال الخامس بالإضافة إلى التابعين sum_primary و sum_secondary الموجودين في المثال السابع إلى مكتبتنا الخاصة و سنسميها Library2 .. حيث أن الرقم يمثل كل إضافة جديدة للمكتبة .. المكتبة موجودة في المرفقات .

ملاحظة 1 / قمنا بإضافة المكتبتين string.h و ctype.h .. لأننا استخدمنا في المكتبة كلاً من :
- strlen .
- isalpha .

ملاحظة 2 / قمنا بتعريف الثابت n في بداية مكتبتنا لأننا سنستخدم هذا الثابت في التابعين sum_primary و التابع sum_secondary .

ملاحظة 3 / قمنا بتغيير اسم التابع read و الذي وضعناه في المكتبة Library1 إلى mirror_word .

ملاحظة 4 / لتجربة أحد توابع المكتبة Library2 .. قم بكتابة ملف مصدري ( cpp ) و قم بضم المكتبة إلى هذا الملف عن طريق :


#include "Library2.h"

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

---------- ---------- ---------- نهاية الدرس السادس ---------- ---------- ----------

في النهاية أرجو أن أكون قد وفقت في شرح هذا الدرس و أتمنى أن يكون مرجعاً لأي شخص يريد التعرف على مفهوم المصفوفات في السي++ .. في الدرس القادم إن شاء الله سنأخذ السجلات ( Structures ) و التي تعتبر بداية مهمة لمفهوم الكائنات .. بالتوفيق .

Wolf Sniper
10-04-2005, 01:05 AM
السلام عليكم ..

---------- ---------- ---------- الدرس السابع ---------- ---------- ----------
اليوم : السبت ... التاريخ : 9 / 4 / 2005

:: السجلات ( Structures ) ::

:: 1- مقدمة ::

ماذا نعني بالسجلات ؟ .. في قواعد البيانات كنا نطلق على اسم الصف باسم السجل بحيث يحتوي كل سجل من السجلات على مجموعة من الحقول في كل حقل نوع من البيانات قد يختلف من حقل إلى آخر .. و بالتالي السجلات في البرمجة تأخذ نفس الفكرة تماماً .. و لكن هذا لا يعني أنها قواعد بيانات .. بل ( السجلات في البرمجة ) هي عبارة عن كتلة تحتوي على مجموعة من الحقول .. و الحقول تحتوي على متغيرات .

و بالتالي يختلف السجل عن المصفوفة في كثير من الأمور منها :

1 – يشترط في المصفوفة أن تحتوي على متغيرات من نوع واحد فقط .. بينما في السجل لا يشترط ذلك .
2 – يمكننا التعامل مع أحد متغيرات المصفوفة عن طريق الدليل ( Index ) .. بينما في السجل نستطيع ذلك عن طريق النقطة ثم ذكر اسم الحقل ( سنأخذ الطريقة فيما بعد ) .
3 – بنية المصفوفة تختلف أصلاً عن بنية السجل .. فعندما تقوم بتعريف مصفوفة تكون قد وضعت مجموعة متغيرات من نفس النوع في برنامجك .. بينما إذا قمت بتعريف سجل فكأنك قمت بتعريف نوع ( Types ) جديد كالأنواع الأخرى المسبقة التعريف ( int, float, double إلخ ) .. و عن طريق هذا النوع الجديد تستطيع تعريف متحولات على أنها سجلات تحتوي على نفس الحقول التي عرفتها في ذلك النوع .
4 – هناك فروق أخرى و لكن ما سبق يكفي .


:: 2- تعريف ::

السجل / هو كتلة مركبة من متحولات فرعية تسمى حقولاً و يكون لهذه الكتلة اسم واحد مشترك و لكل حقل اسم خاص يشار إليه بـ :


Struct.Column
حيث أن :
Struct : اسم السجل .
Column : اسم الحقل .

لاحظ كيف استخدمنا أحد حقول ( متغيرات ) السجل .. فقط وضعنا اسم السجل ثم النقطة ثم اسم الحقل ( المتغير ) .


:: 3- فائدة استخدام السجلات ::

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

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

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


:: 4- التصريح عن السجلات ( تعريف السجلات ) ::

أولاً / تعريف النوع : نقوم بتعريف السجل .. و هذا السجل سيمثل نوع ( Type ) نستطيع به بعد ذلك أن نعرف أي متحول على أنه من هذا النوع .

قاعدة :


struct Name {
Type Column;
Type Column;
Type Column;
};

ملاحظة / انتبه إلى الفاصلة المنقوطة الموجودة في آخر تعريف السجل .

بحيث تمثل :
struct : هي الكلمة الخاصة بتعريف السجلات .
Name : اسم هذا السجل .
Type : نوع المتغير ( الحقل ) الذي نريد أن نضعه داخل هذا السجل ( int, float, double .. إلخ ) .. أو حتى من الممكن أن نضع نوع لسجل آخر .
Column : اسم المتغير ( الحقل ) الذي نريد أن نضعه داخل هذا السجل .


مثال / لسجل يحتوي على بيانات الطالب ( الاسم ، العمر ، الرقم ) :

struct student {
char name[20];
int age;
int number;
};

ثانياً / تعريف متحول : نقوم بتعريف متحول ما على أنه من النوع الذي قمنا بتعريفه سابقاً على أنه سجل ( Student على سبيل المثال ) .. و كأنك تقوم بتعريف متحول بشكل طبيعي على أنه من النوع int أو أي نوع آخر .


مثال / لمتحولات من النوع student :

student s1;
student s2;
student student1;

ملاحظة 1 / سيظهر النوع student باللون الأسود و ليس الأزرق لأننا قلنا سابقاً بأن الكلمات التي تلون باللون الأزرق هي الكلمات المحجوزة فقط مثل ( if .. switch .. int .. float .. break و غيرها ) .

ملاحظة 2 / يمكننا اختصار العمليتين السابقتين على الشكل التالي :


struct student {
char name[20];
int age;
int number;
}s;


:: 5- استخدام قنوات الدخل و الخرج ( cin, cout ) مع السجلات ::

لقراءة و طباعة ( cin, cout ) حقول السجل لا بد من استخدامها على شكل إفرادي ( كل حقل بشكل منفرد ) ..

مثال 1 / بفرض أن s هو سجل نوعه student المعرف في الفقرة السابقة :

cin >> s;
cin >> s.age;

المثال الأول خاطئ لأننا كما قلنا يجب استخدام كل حقل في السجل على حدة .. بينما المثال الثاني صحيح .

مثال 2 / سنقوم بكتابة النوع student مرة أخرى لكي نضيف عليه بعض الحقول :

#include <iostream.h>

struct student{
char name[15];
char phone[10];
char address[50];
int age;
int number;
};

void readinfo(student &);
void printinfo(student &);

void main()
{
student s;

readinfo(s);
cout << "--------------------\n";
printinfo(s);
}

void readinfo(student &s)
{
cout << "Name : "; cin >> s.name;
cout << "Phone : "; cin >> s.phone;
cout << "Address : "; cin >> s.address;
cout << "Age : "; cin >> s.age;
cout << "Number : "; cin >> s.number;
}

void printinfo(student &s)
{
cout << "Name : " << s.name << endl;
cout << "Phone : " << s.phone << endl;
cout << "Address : " << s.address << endl;
cout << "Age : " << s.age << endl;
cout << "Number : " << s.number << endl;
}

في هذا المثال قمنا بتعريف النوع student و الذي يحتوي على خمسة حقول .. الاسم و رقم الهاتف و العنوان و العمر و الرقم .. و استخدمنا تابعين .. الأول لقراءة السجل و الثاني لطبعاته على الشاشة .. و لكن لاحظ كيف أننا وضعنا نوع المتحول الوسيط في كلا التابعين من النوع student .. لأن كل تابع سيستقبل السجل المطلوب كمدخلات له و سيقوم بالعمليات المطلوبة .

ملاحظة / استخدمنا الاستدعاء بالمرجع مع السجل s .. لأننا كما قلنا سابقاً بأنه لا ينصح استدعاء السجلات و الكائنات بالقيمة !! لأنه بذلك سيقوم المترجم بنسخ ذلك السجل و وتخزين في المتحول الوسيط .. و بالتالي إذا كان لدينا سجل حجمه كبير فسيضيع جزء كبير من الذاكرة بلا فائدة فمن الخطأ أن ننسخ حقول ذلك السجل .. و عوضاً عن ذلك نستخدم الاستدعاء بالمرجع الذي يقوم بإعطاء عنوان السجل الأصلي في الذكرة إلى المتحول الوسيط .. و بالتالي إذا تغيرت إحدى حقول السجل داخل التابع فستتغير حتماً في السجل الأصلي و لن يتم نسخ حقول ذلك السجل .. و هذا الحل هو المتبع دائماً مع السجلات و الكائنات .

Wolf Sniper
10-04-2005, 01:14 AM
:: 6- استخدام عملية الإسناد مع الحقول ذات النوع char ::

في الحقيقة لا نستطيع وضع عملية الإسناد مع الحقول من ذلك النوع .. انظر المثال التالي :

void main()
{
student s;

s.name = "Montada";
}

هنا سوف يظهر خطأ ؟!! .. بحيث سيخبرك المترجم بأنه لا يستطيه وضع قيمة حرفية حجمها أقل من حجم الحقل الأساسي name الموجود في السجل student .. هنا لدينا 7 حروف ( مع المحرف الصفري ) و الحقل name يحمل 15 خانة ..


و لحل هذه المشكلة نستخدم الدالة ( التابع ) strcpy و تلفظ string copy .. و طريقة استخدامها على الشكل التالي :

Strcpy(s.name, "Montada");
هنا سيتم إسناد قيمة القسم الثاني ( Second Parameter ) إلى القسم الأول ( First Parameter ) .. و بالتالي نستنتج أن القيمة Montada ستخزن في الحقل name .. و نستنتج أيضاً أنه يجب علينا دائماً أن نضع في القسم الثاني متحول ما لكي يتم تخزين القيمة فيه .

ملاحظة / بالنسبة للحقول ذات الأنواع الأخرى ( int, float, bool .. إلخ ) .. يمكن القيام بعملية الإسناد بشكل طبيعي عليها .


:: 7- الحقول المركبة ( سجل كحقل من حقول سجل آخر ) ::

تفرض لو أننا نريد أن نجعل سجل داخل سجل آخر .. فهل هذا ممكن ؟ بالتأكيد نستطيع ذلك .. مثال / سنعرف سجلان جديدان هما : date و address .. و سنضعهما بداخل السجل student :


#include <iostream.h>

struct date{
int day,month,year;
};

struct address{
char street[15];
char city[15];
};

struct student{
char name[15];
char phone[10];
date bd;
address ad;
};

void readinfo(student &s)
{
cout << "Name : "; cin >> s.name;
cout << "Phone : "; cin >> s.phone;
cout << "Birthday : "; cin >> s.bd.day
>> s.bd.month
>> s.bd.year;
cout << "Address : "; cin >> s.ad.street
>> s.ad.city;
}

void printinfo(student &s)
{
cout << "Name : " << s.name << endl;
cout << "Phone : " << s.phone << endl;
cout << "Birthday : " << s.bd.day << " "
<< s.bd.month << " "
<< s.bd.year << endl;
cout << "Address : " << s.ad.street << " "
<< s.ad.city << endl;
}

void main()
{
student s;

readinfo(s);
cout << "--------------------\n";
printinfo(s);
}

فقط قمنا في هذا المثال ( كالمثال السابق ) بإدخال البيانات اللازمة عن طريق التابع readinfo و من ثم قمنا بطعاتها على الشاشة عن طريق التابع printinfo .. و المهم في هذا المثال هو كيفية تداخل السجلات بعضها ببعض .


:: 8- المصفوفات كحقول في السجلات ::

افرض الأن أننا نريد أن نضع مصفوفة داخل السجل student الذي كتبناه مسبقاً .. انظر المثال التالي / سنضع المصفوفة marks و التي تخزن علامات الطالب في داخل السجل student :


struct student{
char name[15];
char phone[10];
int age;
int marks[5];
};


و سيكون شكل هذا السجل ( student ) على النحو التالي :

http://members.lycos.co.uk/wolfsniper84cc/C/struct1.jpg


لذلك سنحتاج إلى حلقة for للتعامل مع المصفوفة marks .. انظر إلى كيفية عمل ذلك داخل التابع readinfo :

void readinfo(student &s)
{
cout << "Name : "; cin >> s.name;
cout << "Phone : "; cin >> s.phone;
cout << "Age : "; cin >> s.age;

cout << "Marks : ";

for(int i=0; i<5; i++)
cin >> s.marks[i];
}

ملاحظة / انظر إلى الحقلين name و phone .. أيضاً هما مصفوفتين و لكنهما لا يحتاجان إلى حلقة for لأنهما من النوع char ( راجع درس المصفوفات لمعرفة السبب ) .


:: 9- السجل كعنصر في مصفوفة ( مصفوفة سجلات ) ::

هذه المرة سنقوم بتعريف مصفوفة تحتوي كلها على سجلات من النوع student .. و طريقة عمل ذلك سنعرفها عن طريق المثال التالي / سنقوم بتعريف المصفوفة s و التي ستحتوي على 3 سجلات بحيث يكون لكل طالب سجل خاص فيه :


struct student{
char name[15];
char phone[10];
int age;
int mark[5];
};

void main()
{
student s[3];
}


و سيكون شكل هذه المصفوفة ( s ) على النحو التالي :

http://members.lycos.co.uk/wolfsniper84cc/C/struct2.jpg


بهذه الطريقة نكون قد عرفنا مصفوفة تحتوي على ثلاثة سجلات .. و لكن كيف يمكننا استخدام تلك السجلات ؟ نستطيع ذلك عن طريق التالي : مثال /

s[2].name = "Amer";
بهذه الطريقة قمنا بتغيير اسم الطالب الثالث .. و ذلك باستخدام القوسين ( [ ] ) بعد المتحول s و طبعاً مع تحديد الدليل المطلوب .. و بعد ذلك نستخدم الحقل الذي نريده مع ملاحظة استخدام النقطة دائماً لذلك .

سنكمل مثالنا السابق .. حيث أننا نريد إدخال بيانات ثلاثة طلاب و ليس طالب واحد فقط .. و بعد ذلك نقوم بكتابة تلك المعلومات على الشاشة كالأمثلة السابقة :


#include <iostream.h>

const int m=3;

struct student{
char name[15];
char phone[10];
int age;
int marks[5];
};

void readinfo(student &);
void printinfo(student &);

void main()
{
student s[m];

for(int i=0; i<m; i++)
{
readinfo(s[i]);
cout << endl;
}

cout << "--------------------\n";

for(i=0; i<m; i++)
{
printinfo(s[i]);
cout << endl;
}
}

void readinfo(student &s)
{
cout << "Name : "; cin >> s.name;
cout << "Phone : "; cin >> s.phone;
cout << "Age : "; cin >> s.age;

cout << "Marks : ";

for(int i=0; i<5; i++)
cin >> s.marks[i];
}

void printinfo(student &s)
{
cout << "Name : " << s.name << endl;
cout << "Phone : " << s.phone << endl;
cout << "Age : " << s.age << endl;

cout << "Marks : ";

for(int i=0; i<5; i++)
cout << s.marks[i] << " ";

cout << endl;
}

لاحظ التابع main كيف تغير ( بقية المثال كما هو ) .. أولاً عليك معرفة أن التابعين readinfo و printinfo يتعاملون مع سجل واحد فقط .. لذلك قمنا بوضع حلقتين for الأولى وظيفتها أن تقوم في كل دورة باستدعاء التابع readinfo لإدخال بيانات السجلات .. ففي الدورة الأولى سيتم إدخال بيانات السجل الأول و بعد الانتهاء من ذلك يتم الاستدعاء مرة أخرة لأدخال بيانات السجل الثاني و ذلك في الدورة الثانية .. و هكذا إلى أن ندخل بيانات جميع السجلات الموجودة .

و الحلقة الثانية تقوم بنفس العملية و بنفس الأسلوب و لكن لطباعة بيانات السجلات و ذلك باستخدام التابع printinfo في كل دورة لطباعة بيانات السجل المطلوب .. مع ملاحظة أن النزول سطر جديد في كلا الحلقتين من أجل الترتيب فقط .. و باقي المثال أعتقد أنه مفهوم لأننا شرحناه مسبقاً .. و بالنسبة لفائدة استخدام الثابت m فقم بمراجعة درس المصفوفات لمعرفة ذلك .


----------------------------------------

Wolf Sniper
10-04-2005, 01:40 AM
:: 10- برنامج تطبيقي ::

سنأخذ فقط مثال عملي واحد .. و هو عبارة عن برنامج يقوم بإدخال أسماء مدن معينة و درجات الحرارة في تلك المدن ( سنفترض وجود ثلاثة مدن فقط ) .. بالإضافة إلى إمكانية البحث ضمن تلك المدن عن مدينة معينة لعرض درجة الحرارة فيها .. و أيضاً مع إمكانية طباعة جميع المعلومات المدخلة .. و البرنامج على الشكل التالي :


http://members.lycos.co.uk/wolfsniper84cc/C/program21.jpg


ملاحظة / سنستخدم نفس طريقة عمل البرنامج التطبيقي الموجود في الدرس السابق ( درس المصفوفات ) بحيث سنضع قائمة بالأوامر الممكن استخدامها مع اختصاراتها ( انظر الصورة السابقة ) .


سنقوم بإدخال أسماء المدن على الشكل التالي :

http://members.lycos.co.uk/wolfsniper84cc/C/program22.jpg


و بعد ذلك سنقوم بإدخال درجات الحرارة على الشكل التالي :

http://members.lycos.co.uk/wolfsniper84cc/C/program23.jpg

نستطيع الأن البحث ضمن المدن المدخلة ضمن الأمر الثالث عن طريق إدخال اسم المدينة و من ثم سيعطينا البرنامج درجة حرارة تلك المدينة .. و إذا لم يجدها سيعطي خطأ بأن اسم المدينة غير موجود ضمن أسماء المدن المدخلة و ذلك على الشكل التالي :

http://members.lycos.co.uk/wolfsniper84cc/C/program24.jpg

و أيضاً يمكننا أن نطبع جميع المعلومات التي أدخلناها على الشكل التالي :

http://members.lycos.co.uk/wolfsniper84cc/C/program25.jpg

ملاحظات حول الحل :

1 – سنستخدم السجلات في هذا البرنامج .

2 – سنحتاج إلى مصفوفتين أحادية البعد فقط .. الأولى لتخزين أسماء المدن و الثانية لتخزين درجات الحرارة .. و سنضع هاتين المصفوفتين كحقول داخل السجل City و الذي سنقوم بتعريفه في أول البرنامج .

3 – سنضع كل أمر موجود في القائمة على شكل تابع ( دالة ) على الشكل التالي :
- التابع read_cities : من أجل إدخال أسماء المدن .
- التابع read_temp : من أجل إدخال درجات الحرارة الخاصة بكل مدينة .
- التابع search : من أجل البحث عن درجة حرارة مدينة معينة .
- التابع print : من أجل طباعة جميع المعلومات التي أدخلناها على الشاشة .

4 - كل تابع من التوابع السابقة سيحتاج إلى السجل كمدخلات له عن طريق استخدام ( Parameter ) لأن كل تابع سيقوم باستخدام ذلك السجل الذي أدخله المستخدم .. و السجل كما قلنا يحتوي اسم المدينة بالإضافة إلى درجة الحرارة فيها .

5 – سنفترض أننا نريد من المستخدم إدخال ثلاثة مدن فقط مع درجات الحرارة فيها .. لذلك سنحتاج إلى مصفوفة سجلات من النوع City حجمها 3 .. و تعريف هذه المصفوفة على الشكل التالي :


city c[3];
الحل :

#include <iostream.h>
#include <ctype.h>
#include <string.h>

const int m=3;

struct city{
char name[15];
int temp;
};

void read_cities(city [ ]);
void read_temp(city [ ]);
void search(city [ ]);
void print(city [ ]);

void main( )
{
city c[m];
char choice;

do{
cout << "C. Name of Cities\n"
<< "T. Enter Temeratures\n"
<< "S. Search For a City\n"
<< "P. Print All\n"
<< "Q. Quit\n\n"
<< "Enter your choice : ";
cin >> choice;

switch( toupper(choice) )
{
case 'C' : read_cities(c); cout << "\n------------------------------\n\n"; break;
case 'T' : read_temp(c); cout << "\n------------------------------\n\n"; break;
case 'S' : search(c); cout << "\n------------------------------\n\n"; break;
case 'P' : print(c); cout << "\n------------------------------\n\n"; break;
case 'Q' : return;

default : cout << "Error.. \n\n------------------------------\n\n";
}
}while( true );
}

void read_cities(city c[ ])
{
for(int i=0; i<m; i++)
{
cout << "Name of City " << i << " : ";
cin >> c[i].name;
}

for(i=0; i<m; i++)
for(int j=0; j<strlen(c[i].name); j++)
c[i].name[j] = toupper(c[i].name[j]);
}
void read_temp(city c[ ])
{
for(int i=0; i<m; i++)
{
cout << "Temperature of City " << c[i].name << " = ";
cin >> c[i].temp;
}
}
void search(city c[ ])
{
char word[15];

cout << "Enter a Name of City : ";
cin >> word;

for(int i=0; i<strlen(word); i++)
word[i] = toupper(word[i]);

for(i=0; i<m; i++)
if( strcmp( c[i].name, word) == 0 )
{
cout << "City : " << c[i].name << endl
<< "Temperature = " << c[i].temp << endl;
return;
}
cout << "There is No City Like This Name...\n";
}
void print(city c[ ])
{
for(int i=0; i<m; i++)
cout << "City : " << c[i].name << endl
<< "Temperature = " << c[i].temp << "\n\n";
}
شرح الحل :

1 – هذا البرنامج يشبه تماماً البرنامج الذي حللناه في درس المصفوفات .. لذلك سوف أشرح الإختلافات فقط و أهمها التابعين search و read_cities .

2 – في التابع search .. قمنا بتعريف مصفوفة حرفية و هذه المصفوفة ستخزن الكلمة التي سيدخلها المستخدم و التي سيتم البحث بها بين أسماء المدن .. و بعد ذلك طلبنا من المستخدم إدخال تلك الكلمة .. الأن علينا معرفة أننا نريد عمل مقارنة بين هذه الكلمة و بين أسماء المدن المدخلة فإذا وجدنا أن أحد تلك الأسماء يطابق تماماً تلك الكلمة نكون قد وجدنا المدينة المطلوبة .. و لكن يجب أن ننتبه لحجم الأحرف التي أدخلها المستخدم هل هي صغيرة أم كبيرة .. و بالتالي قمنا في التابع search بتكبير حجم أحرف المصفوفة word .. و أيضاً قمنا بنفس الأمر بالنسبة لأسماء المدن و ذلك في التابع read_cities و ذلك لكي يصبح التطابق ممكناً .. و لكن لاحظ أن التابع toupper يتعامل مع حرف واحد فقط لذلك استخدمنا حلقات for لتكبير حجم جميع أحرف المصفوفة المعنية .

3 – نعود للتابع search .. الأن علينا وضع التعليمات المناسبة لاختبار التطابق .. أولاً علينا معرفة أنه من أجل إجراء عملية مقارنة بين نصين يجب استخدام الدالة ( التابع ) strcmp .. و هذه الدالة تحتاج إلى وسيطين حرفين .. و هذه الدالة ستعيد إحدى ثلاثة قيم :
الصفر : و يعني تطابق الوسيطين .
+1 : و يعني اختلاف الوسيطين .. و هو يعني أن الوسيط الأول هو الأكبر ( من حيث الترتيب الأبجدي للأحرف ) .
-1 : و يعني أيضاً اختلاف الوسيطين .. و لكنه يدل على أن الوسيط الأول هو الأصغر .

و الحالة صفر هي التي نحتاجها .. و أيضاً لاحظ كيف استخدمنا الدالة strcmp داخل الشرط و بالتالي سيقوم المترجم باستدعاء تلك الدالة ثم سيختبر القيمة المعادة منها .. و لاحظ أيضاً أننا استخدمنا return و ذلك من أجل انهاء عملية البحث و الخروج من التابع فوراً .. و لكن إن لم نجد ذلك التطابق فسيتم طباعة الجملة الأخيرة على الشاشة بأنه لا توجد مدينة بذلك الاسم .

و لاحظ أيضاً كيف و ضعنا عملية اختبار التطابق داخل حلقة for و ذلك من أجل اختبار جميع السجلات الموجودة في المصفوفة c و المرسلة من التابع main .


----------------------------------------

:: المكتبة Library3.h ::

سنقوم بإضافة السجلات date و address و student إلى مكتبتنا الخاصة .. حيث كما قلنا سابقاً بأن الملفات من النوع ( h. ) يمكن وضع أي شيء فيها سواءً أكان متغيرات أم توابع أم سجلات أم كائنات .. إلخ .

و أنا أريد من تطوير تلك المكتبة ( Library.h ) أن نتعود على استخدام مفهوم المكتبات الخاصة و كيفية صنع تلك المكتبات .. و بالتالي مع الوقت سيصبح الأمر سهلاً .. و أيضاً لأننا سنحتاج إلى تلك الأنواع من الملفات ( h. ) في دروسنا حول الكائنات و الفئات .


ستكون تلك السجلات في المكتبة ( Library3.h ) على النحو التالي :

struct date{
int day,month,year;
};

struct address{
char street[15];
char city[15];
};

struct student{
char name[15];
char phone[10];
int age;
date birhday;
address ad;
};
المكتبة موجودة في المرفقات مع هذا الدرس .

ملاحظة / بالنسبة لاستخدام السجلات التي وضعناها في المكتبة ( Library3.h ) داخل ملف مصدري .. فبنفس الأسلوب .. فقط نقوم بضم المكتبة إلى مشروعنا عن طريق ( include# ) على الشكل التالي :

#include "Library3.h"
بشرط وضع تلك المكتبة بداخل المجلد الذي يحتوي على مشروعك .

---------- ---------- ---------- نهاية الدرس السابع ---------- ---------- ----------

بذلك نكون قد انتهينا من درس السجلات و هو من أحد الدروس المهمة و سنحتاج إلى هذا الدرس في موضوع الكائنات و الفئات .. و في الدرس القادم إن شاء الله سنتكلم عن المؤشرات .. بالتوفيق .

Wolf Sniper
26-04-2005, 08:06 PM
السلام عليكم ..

في هذا الدرس سنتطرق لموضوع المؤشرات و سنأخذ أهم ما يميزها من مواضيع .. و لكن قبل ذلك أريد أن أضيف فقرة صغيرة ( قد نسيتها ) في الدرس السابق .. و هي إعطاء قيم ابتدائية لحقول السجلات .. بالإضافة إلى موضوع آخر ليس له علاقة بالسجلات و هو موضوع الـ Scope .. و أيضاً سأضيف طريقة تشغيل تطبيقاتك خارج فيجوال سي++ 6 باستخدام Command Prompt .

لنبدأ بهذه المفاهيم أولاً ثم سندخل على موضوع المؤشرات


:: إعطاء قيم ابتدائية لحقول السجلات ::

القيم الابتدائية هنا ليس المقصود بها إعطاء قيم ابتدائية للحقول الموجودة في بنية ( جسم ) تعريف السجل .. بل المعنى هنا هو إعطاء قيم ابتدائية لحقول المتغيرات التي عرفناها على أنها سجلات .. فمن الخطأ إعطاء قيم ابتدائية لحقول السجل في بنية تعريفه .. انظر إلى هذا المثال /

struct date{
int day=1;
int month=12;
int year;
};


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

date x = {1, 12};
لاحظ هنا كيف قمنا بذلك .. بنفس أسلوب إعطاء قيم ابتدائية للمصفوفات .. هنا في هذا المثال قمنا بإعطاء حقول السجل x القيم : 1 و 12 على التوالي .. بذلك سيتم إعطاء القيمة ( 1 ) للحقل الأول و هو day و القيمة ( 12 ) للحقل الثاني و هو month .. و تماماً كالمصفوفات سيتم إعطاء باقي الحقول القيمة صفر .. لأننا لم نعط تلك الحقول قيماً ابتدائية و بالتالي سيضع المترجم لها القيمة صفر بشكل تلقائي .

ملاحظة / إذا كان الحقل من النوع char و لم نقم بوضع قيمة ابتدائية لذلك الحقل عند تعريف المتغير على أنه سجل .. فسيتم إعطاء ذلك الحقل القيمة ( فراغ ) .. أما إذا كان الحقل من النوع bool فسيتم إعطاءه القيمة صفر أيضاً !! لأننا قلنا سابقاً أن الصفر يدل على القيمة false و الواحد يدل على القيمة true و استخدام أي من القيم السابقة صحيح مع المتغيرات من النوع bool .


:: مفهوم الـ Scope .. أو ما يسمى بـ ( فترة حياة المتغير ) ::

و هو المكان الذي سنقوم به بتعريف المتغير هل هو خارج التوابع ( متغير عام ) أم داخل أحد التوابع ( متغير خاص ) أم داخل أحد الكتل الخاصة بالبنى التي درسناها ( كحلقة for أو حتى عبارة if ) ..

و نحن نريد هنا الحالة الثالثة .. و كمثال على ذلك انظر إلى البرنامج التالي :

int x,y;

cin >> x >> y;

if( y==0 )
cout << "Error..\n";
else
{
float c;
c = (float)x / (float)y;
cout << c << endl;
}

هذا المثال هو من أجل إدخال عددين و من ثم حساب ناتج قسمة العدد الأول على الثاني .. و استخدمنا عبارة if من أجل معالجة حالة القسمة على الصفر .. فإذا كان العدد الثاني يساوي صفراً نرفض العملية أما إذا كان العدد الثاني يحتوي على أي رقم غير الصفر فنقوم بالتالي :
نعرف متغير جديد اسمه c من أجل تخزين ناتج عملية القسمة و من ثم نقوم بعملية الإسناد من أجل تلك العملية و بعد ذلك نكتب الناتج على الشاشة ( من أجل العملية القسرية (float) راجع أمثلة الدرس الثاني ) ..

ما أريد قوله هنا أن المتغير c الجديد قد عرفناه في داخل البنية else و بالتالي عند انتهاء عمل البنية else يتم تحرير المتغير c من الذاكرة ( يتم إلغاؤه ) .. و بالتالي لا يمكن استخدام هذا المتغير خارج تلك البنية .. بنفس أسلوب المتغيرات الخاصة بالتوابع و التي قلنا عنها أنه لا يمكننا استخدامها خارج كتلة التابع .. و بذلك نصل إلى مفهوم الـ Scope .
و هذا المفهوم ينطبق أيضاً على جميع البنى و الكتل .. مثل ( for, if, while, switch .. إلخ ) .. و بالتالي نستطيع تلخيص هذه الفقرة على أن المتغيرات التي يتم التصريح عنها داخل أحد الكتل ( البنى ) تتصف بما يلي :
1 – يتم إنشاؤها عند الدخول إلى تلك الكتلة .
2 – يتم التخلص منها ( إلغاؤها من الذاكرة ) عند انتهاء عمل تلك الكتلة .
3 – من الفقرة ( 2 ) نستنتح أنه لا يمكن استخدام هذا المتغير خارج هذه الكتلة .


ملاحظة / كان باستطاعتنا داخل البنية ( else ) أن لا نعرف متغير جديد بحيث نقوم بكتابة ناتج القسمة على الفور و ذلك على الشكل التالي :

else
cout << (float)x / (float)y << endl;

و لكني استخدمت ذلك المتغير ( c ) من أجل شرح الفقرة التي نحن في صددها .


:: طريقة أخرى لتشغيل تطبيقاتك باستخدام Command Prompt أو ما يسمى MS-DOS Prompt ::

بكل بساطة تستطيع تشغيل الأمثلة و البرامج التي كتبناها سابقاً عن طريق استخدام Command Prompt .. و هو عبارة عن نافذة شبيهة تماماً بنافذة الدوس و لكنه يعمل على نظام الويندوز 32 بت ، و بالتالي هو ليس الدوس و لكنه يشببه تماماً فتستطيع به أن تستخدم جميع الأوامر التي كنت تستخدمها في الدوس مثل ( dir /p ) .

ملاحظة 1 / تستطيع تشغيل Command Prompt عن طريق التالي :

في ويندوز XP : اضغط على ( Start >> Programs >> Accessories >> Command Prompt ) .
في ويندوز ME : فقط الاسم سيختلف و هو MS-DOS Prompt و هو موجود بنفس المكان السابق في ويندوز XP .

ملاحظة 2 / البرامج التي نستطيع تشغيلها بواسطة Command Prompt يجب أن تكون امتدادها ( نوع الملف ) من النوع ( exe ) .. و نستطيع عمل هذه الملفات لبرامجنا عن طريق عملية بناء ( Build ) البرنامج .. و بها سيتم صنع ملف خاص لبرنامجك من النوع ( exe ) و سيتم وضعه داخل المجلد الخاص بمشروعك في الملف Debug .


سنفرض أننا نريد تشغيل البرنامج العملي الموجود في درس السجلات .. لذلك كل ما علينا فعله هو معرفة مكان وجوده في الهارد ديسك .. على سبيل المثال :

البرنامج اسمه Ex1 .. و هو موجود ضمن المجلد التالي ( E:\My Programs ) .. و بالتالي نستطيع تشغيله عن طريق كتابة اسم البرنامج فقط في داخل المجلد الموجود فيه .. انظر الصورة :

http://members.lycos.co.uk/wolfsniper84c/C/pointers1.gif

ملاحظة 3 / تستطيع طبعاً أن تشغل تطبيقاتك ذات الامتداد ( exe ) بالضغط عليها فوراً بواسطة الفأرة .. و لكن لن يتم التوقف عند الانتهاء من التطبيق لرؤية النتائج .. و بذلك أردت توضيح كيفية تشغيل التطبيقات بواسطة Command Prompt .


---------- ---------- ---------- الدرس الثامن ---------- ---------- ----------
اليوم : الثلاثاء ... التاريخ : 26 / 4 / 2005

:: المؤشرات ( Pointers ) ::

:: 1- مقدمة ::

تعتبر المؤشرات من أهم مزايا لغة السي++ .. حيث تفتقر العديد من اللغات إلى هذه الميزة و هي امكانية التحكم الغير مباشر بمكونات البرنامج ( المتغيرات ، المصفوفات ، السجلات .. إلخ ) باستخدام الذاكرة العشوائية ( RAM ) .. إذاً نستنتج من هذا الكلام أن المؤشرات تتيح لنا التعامل مع الذاكرة بشكل مباشر .. و بالتالي ستمتلك حرية أكبر في مجال استخدام الذاكرة من ناحية التحرير ( الإلغاء ) اليدوي للذاكرة .

إن للمؤشرات العديد من الامكانيات التي تزيد قدرة المبرمج على تحكمه ببرنامجه .. فنذكر على سبيل المثال :
1 – الحجز الديناميكي و التحرير الديناميكي ( الحجز و التحرير اليدوي للمتغيرات ) .
2 – التعامل بشكل غير مباشر مع البنى الأخرى عن طريق عناوينها في الذاكرة .. و بالتالي سنستفيد من ميزة الاستدعاء بالمرجع ( Reference ) و التي كنا قد استخدمناها في التوابع ( الدوال ) .
3 – المؤشر ذو النوع ( void ) و الذي يستطيع أن يحمل قيمة من أي نوع نريده .
4 – استخدام المؤشرات بدلاً من المصفوفات من أجل تحديد حجم ديناميكي للمصفوفة أثناء زمن التنفيذ .. و هذه الميزة تخالف مفهوم المصفوفة و التي تقوم على أساس تحديد حجمها في مرحلة ما قبل الترجمة ( ترجمة البرنامج بواسطة المترجم Compilor ) .
5 - المؤشرات على التوابع ( الدوال ) و فائدتها .
6 – هناك مزايا أخرى فنذكر مثلاً : القوائم المرتبطة و المكدسات و الأشجار و غيرها .. و لكننا لن نتطرق إلى هذه المواضيع .

خلاصة / إن موضوع المؤشرات مهم جداً لأي مبرمج .. لما لها من مزايا و أنها تزيد البرنامج مرونة أكبر و تزيد قدرة المبرمج على تحكمه ببرنامجه .. و بالتالي لا تتردد في استخدام المؤشرات إن احتجت لها .. و اعلم أن الكثير من البرامج تم برمجتها باستخدام المؤشرات حتى في توابع ( دوال ) المكتبات القياسية الموجودة في السي++ فأيضاً الكثير منها استخدمت فيه المؤشرات إن لم يكن كلها .

و لكن قبل الدخول الفعلي بالمؤشرات يجب أن نتحدث حول فكرة مهمة .. و هي أن لكل متغير أربع معلومات يجب علينا معرفتها و هي كالتالي :
1 – اسم المتغير .
2 – حجمه ( و يتحدد عن طريق النوع ) .
3 – قيمته ( نحددها نحن أو المستخدم ) .
4 – عنوانه في الذاكرة .. و هذا ما سنتعامل معه في هذا الدرس .

ملاحظة / هذه المعلومات الأربعة تنطبق أيضاً على الثوابت و المصفوفات و السجلات و الكائنات .


في الدروس السابقة تعلمنا كيف نحدد المعلومات الثلاثة الأولى .. فعلى سبيل المثال / إذا قمنا بتعريف متغير x على الشكل التالي :

int x=5;
نكون قد حددنا التالي :
1 – اسم المتغير : x .
2 – حجمه : 4 بايت ( لأن نوعه int ) .
3 – قيمته : العدد 5 .

و لكن عنوانه في الذاكرة سيحدده المترجم .. و نحن عن طريق المؤشرات سنتعامل مع عناوين المتغيرات .. و بالتالي نستنتج أن :

قاعدة / المتغيرات تحمل قيماً إما عددية أو حرفية أو منطقية .. أما المؤشرات فتحمل عناوين في الذاكرة فقط .

و لكن التعامل مع الذاكرة بهذه الطريقة المباشرة تحمل في طياتها بعضاً من المخاطرة .. و خاصة مع الاستخدام الخاطئ لها عن طريق المؤشرات .. فعلى سبيل المثال : إذا قمنا بالحجز اليدوي لمتغير في الذاكرة بواسطة مؤشر و لم نقم بتحرير ذلك المتغير ( إلغاؤه من الذاكرة ) .. فسيبقى ذلك المتغير محجوزاً حتى بعد انتهاء عمل البرنامج .. و بذلك نكون قد أهدرنا في الذاكرة بدلاً من أن نستفيد من مزايا المؤشرات في التحرير الديناميكي ( اليدوي ) و الذي وجد من أجل تحرير الذاكرة من المتغيرات التي انتهى عملها و ذلك بأن نلغيها من الذاكرة في المكان الذي نريده و أن لا نتقيد بمفهوم الـ Scope الذي تحدثنا عنه في البداية .

Wolf Sniper
27-04-2005, 02:11 PM
:: 2 - تعريف ::

المؤشر / هو متحول يحتوي على عنوان موقع في الذاكرة .. و هذا العنوان هو عنوان متحول آخر يحتوي على بيانات من نوع معين ( int, float .. إلخ ) .. و بالتالي المؤشر يشير إلى ذلك المتحول بطريقة غير مباشرة .. و بذلك فإن المؤشرات لا تحتوي إلا عناوين في الذاكرة تخص متحولات أخرى .


:: 3 - التصريح عن المؤشرات ::

نصرح عن المؤشرات تماماً كالمتغيرات و لكن مع إضافة رمز النجمة ( * ) قبل اسم المتغير .

قاعدة / نعرف المؤشرات عن طريق وضع نوع المؤشر ثم النجمة ثم اسمه و بعدها نضع الفاصلة المنقوطة .. و لاحظ أن نوع المؤشر يجب أن يطابق نوع القيمة التي سيشير إليها هذا المؤشر .. فمن الخطأ أن يؤشر مؤشر على قيمة من غير نوعه ( سنذكر ذلك لاحقاً ) .


أمثلة على المؤشرات :

int* ptr;
float* p1;
double* p2;


:: 4 – إعطاء قيم ابتدائية لمؤشر ::

ينصح دائماً بإعطاء قيم ابتدائية لأي مؤشر يتم تعريفه في البرنامج .. و ذلك لكي لا نحصل على نتائج غير مرغوب بها .. و يمكننا إعطاء ثلاثة قيم ابتدائية لمؤشر و هي كالتالي :
1 – الصفر : و هو يعني أن المؤشر لا يشير إلى أي قيمة .
2 – القيمة NULL : و هي تعني أيضاً أن المؤشر لا يشير إلى أي قيمة .. و لكن مع ملاحظة أن NULL هي بالأحرف الكبيرة .
3 – عنوان متحول آخر : و ذلك عن طريق إستخدام عنوان ذلك المتحول كقيمة ابتدائية للمؤشر .


أمثلة :

int* ptr1 = 0;
int* ptr2 = NULL;
int* ptr3 = &x;

ملاحظة / في المثال الثالث .. استخدمنا عنوان المتحول x كقيمة ابتدائية للمؤشر ptr3 .. و ذلك عن طريق الرمز ( & ) .. سنتعرف أكثر على معنى هذا الرمز و رموز أخرى في الفقرة التالية .


:: 5 – الرموز التي سنحتاجها أثناء التعامل مع المؤشرات ::

هناك رمزين مهمين يجب أن نعرفهما أثناء التعامل مع المؤشرات و هما كالتالي :
1 – الرمز ( * ) : لقد استخدمنا هذا الرمز سابقاً عند التصريح عن المؤشرات .. الأن سنستخدم هذا الرمز في أماكن أخرى في البرنامج و بالتحديد في عمليات الإسناد و في قنوات الدخل و الخرج ( cin, cout ) .

و معنى هذا الرمز هو : القيمة التي يشير إليها المؤشر ( اقرأها جيداً ) .

ألم نقل أن المؤشرات تحمل عناوين في الذاكرة تخص متحولات أخرى ؟ و أن المؤشرات تستخدم لكي تشير على قيم معينة مخزنة في تلك متحولات ؟ و بالتالي كيف يمكننا أن نحدد القيمة التي يشير إليها المؤشر ؟ يمكننا ذلك باستخدام هذا الرمز .

2 – الرمز ( & ) : يستخدم هذا الرمز لتحديد عنوان المتحولات في الذاكرة .. و بالتالي يجب استخدام هذا الرمز عند تحديد قيم المؤشرات لأن المؤشرات كما قلنا تحتوي على عناوين المتحولات في الذاكرة .

و قبل أن نأخذ مثال على استخدام الرمزين السابقين .. تذكر أن لكل متغير أربع معلومات ( راجع الفقرة الأولى ) .. و لكن للمؤشر أصبح لدينا خمس معلومات و هي كالتالي :
1 – اسم المؤشر .
2 – حجمه ( و يتحدد عن طريق النوع ) .
3 – قيمته ( و هي عنوان في الذاكرة يخص متحول آخر ) .
4 – عنوان هذا المؤشر في الذاكرة .
5 – القيمة التي يشير إليها هذا المؤشر .


مثال :

int x = 5;

int* ptr = &x;

cout << "*ptr = " << *ptr << endl << "x = " << x << "\n\n"
<< "ptr = " << ptr << endl << "&x = " << &x << "\n\n"
<< "&ptr = " << &ptr << "\n\n";

في هذا المثال .. قمنا بتعريف المتحول x و سنعطيه القيمة 5 كقيمة ابتدائية له .. و بعدها قمنا بتعريف المؤشر ptr و أعطينا عنوان المتحول x كقيمة ابتدائية له .. لاحظ كيف استخدمنا الرمز ( & ) و ذلك لكي نخزن عنوان المتحول x كقيمة في المؤشر ptr .. و بهذه الطريقة أصبح المؤشر ptr يشير إلى المتحول x .. لمزيد من التوضيح انظر الصورة التالية :


http://members.lycos.co.uk/wolfsniper84c/C/pointers2.gif

هذه الصورة تبين كيف أن المؤشر ptr يحتوي على عنوان المتغير x .. و لا تخلط بين مفهوم قيمة مؤشر و عنوان المؤشر فكلاهما مختلفان .. حيث أن قيمة مؤشر تحتوي على عنوان متحول آخر و في مثالنا هنا فإن قيمة المؤشر ptr أصبحت تساوي عنوان المتحول x .. و بالتالي فإن المعلومات الخاصة بالمؤشر ptr هي كالتالي :

1 – اسمه : ptr .
2 – حجمه : 4 بايت لأن نوعه int .
3 – قيمته : 101 و هي عنوان المتحول x .
4 – عنوانه في الذاكرة : 201 .
5 – القيمة التي يشير إليها : 5 و هي قيمة المتحول x .


حاول التفريق بين البيانات السابقة لأننا سنستخدمها بكثرة في باقي الدرس .

ملاحظة / العناوين 101 و 201 هي عناوين مفترضة قمت بوضعها من أجل التوضيح فقط .

من أجل بقية المثال .. قمنا بكتابة بعض القيم على الشاشة .. في السطر الأول كتبنا على الشاشة القيمة التي يشير إليها المؤشر ptr و بعدها وضعنا قيمة المتحول x و لاحظ كيف أنهما متساوين .

في السطر الثاني .. وضعنا قيمة المؤشر ptr و عنوان المتحول x و لاحظ أيضاً كيف أنهما متساويين .. و في السطر الثالث قمنا بوضع عنوان المؤشر ptr و لاحظ كيف أنه يختلف عن البيانات السابقة .. انظر الصورة لمعرفة نتائج هذا المثال :


http://members.lycos.co.uk/wolfsniper84c/C/pointers3.gif


:: 6 – استخدام Const مع المؤشرات و مفهوم المؤشر الثابت ::

ماذا نعني بالمؤشر الثابت ؟ تماماً كالثوابت .. عندما كنا نعرف ثابت ما .. لم نكن نستطيع تغيير قيمة ذلك الثابت أبداً و ذلك سواءً أكان ذلك خلال فترة عمل البرنامج أو في فترة ما قبل الترجمة .. و الأن مع المؤشرات نستطيع تثبيت إحدى قيمتين في المؤشر :

الأولى / قيمة المؤشر نفسه ( و هي عنوان المتحول الآخر ) .. و هو ما يسمى بالمؤشر الثابت .
الثانية / القيمة التي يشير إليها المؤشر ( و هي قيمة ذلك المتحول الذي يشير إليه المؤشر ) .. و هو ما يسمى بالمعطيات الثابتة .

و لكن ما فائدة تثبيت تلك القيم ؟ في الحقيقة إن التعامل مع المتحولات و المؤشرات الثابتة يعتبر أسرع و أفضل للاستخدام ( بالنسبة للمترجم ) من المتغيرات و المؤشرات العادية .. هذا أولاً .. و أيضاً نستخدم المؤشرات الثابتة من أجل دعم مبدأ الميزات الأقل و هو يقوم على الآتي :
أن تجعل جميع مكونات برنامجك ( من متحولات و مصفوفات و سجلات و كائنات .. إلخ ) تقوم بالوظيفة و الحاجة الموكلة إليها دون زيادة أو نقصان .
و في حديثنا حول المؤشرات الثابتة نكون قد دعمنا هذا المبدأ بجعلنا للمؤشر بأن يقوم بالعمل الذي يحتاجه فقط دون أن نعطيه مزيداً من المزايا .. كإمكانية تغيير قيمة ذلك المؤشر لكي يشير إلى متحول آخر أو إمكانية تغيير القيمة التي يشير إليها و هكذا ..


1 – تثبيت قيمة المؤشر لكي لا يشير إلى متحول آخر جديد ( مفهوم المؤشر الثابت ) :

الأن كيف يمكننا تثبيت القيمة الأولى ( قيمة المؤشر ) ؟ نستطيع ذلك عن طريق وضع const بعد رمز النجمة ( * ) و قبل اسم المتغير و ذلك في سطر تعريف ذلك المؤشر .. مثال :

int x=5;
int* const ptr=&x;
ملاحظة / يجب وضع قيمة ابتدائية للمؤشر الثابت .
بهذه الطريقة نكون قد ثبتنا قيمة المؤشر ptr بأن جعلنا هذا المؤشر يشير فقط إلى المتحول x .. و بالتالي لن نستطيع بعد ذلك أن نشير بهذه المؤشر إلى متحول آخر .. فعلى سبيل المثال : لو قمنا بتعريف متغير جديد اسمه ( y ) و حاولنا أن نضع عنوان هذا المتحول كقيمة للمؤشر ptr فسيحدث خطأ :


int y=6;
ptr=&y;

2 – تثبيت القيمة التي يشير إليها المؤشر ( مفهوم المعطيات الثابتة ) :

نستطيع ذلك عن طريق وضع const قبل سطر تعريف المؤشر .. مثال :

int x=5;
const int* ptr=&x;
عن طريق هذا المثال لن نستطيع تغيير القيمة ( 5 ) إلى أي قيمة أخرى .. لأننا قمنا بجعل تلك القيمة ثابتة بالنسبة للمؤشر ptr .. الأن سنحاول تغيير تلك القيمة و لكن سيظهر لنا خطأ .. على سبيل المثال : لو كتبنا التالي :


*ptr=6;

هنا سيحدث خطأ لأننا حاولنا تغيير القيمة التي يشير إليها ptr .. و لكنا لو كتبنا التالي فلن يحدث خطأ :

x=6;

هنا لن يحدث خطأ !!! لأن x ليس ثابت بل القيمة التي يشير إليها ptr هي الثابتة .. و لو حاولنا طباعة ( ptr* ) فسيكون الناتج يساوي 6 :

cout << *ptr << endl;


خلاصة / نستنتج من هذه الفقرة أن هناك أربع حالات بالنسبة لاستخدام const مع المؤشرات :

1 – مؤشر غير ثابت على معطيات غير ثابتة :

int* ptr;

2 – مؤشر ثابت على معطيات غير ثابتة :

int* const ptr=&x;

3 – مؤشر غير ثابت على معطيات ثابتة :

const int* ptr;

4 – مؤشر ثابت على معطيات ثابتة :

const int* const ptr=&x;

Wolf Sniper
27-04-2005, 02:54 PM
:: 7 – كيفية التعامل مع المؤشرات ::

الطريقة الأولى / استخدام عنوان متحول آخر للإشارة إلى محتوياته :

أخذنا في بداية هذا الدرس كيفية عمل ذلك عن طريق استخدام الرمز ( & ) .. و ذلك كالتالي :

int x=10;
int* ptr;
ptr=&x;

ملاحظة / يجب أن يكون نوع المؤشر مطابق لنوع المتحول الذي سيشير إليه ذلك المؤشر .

الأن لو أردنا طباعة القيمة ( 10 ) نستطيع ذلك بطريقتين .. إما عن طريق استخدام المتحول x أو عن طريق المؤشر ptr :

cout << x << endl;
cout << *ptr << endl;

وقلنا أن الرمز ( * ) يعني : القيمة التي يشير إليه المؤشر .. و بهذه الطريقة نستطيع استخدام المؤشر ptr بدلاً من المتحول x .. مع ملاحظة أنه إذا تغيرت ( *ptr ) فستتغير قيمة المتحول ( x ) و العكس صحيح .. فلو كتبنا التالي :

(*ptr)++;
cout << *ptr << endl << x << endl;

هنا سيتم زيادة القيمة 10 إلى 11 .. و بالتالي تغيرت قيمة x إلى 11 مع أننا استخدمنا المؤشر ptr لذلك .. لاحظ نتائج الطباعة حيث أنه سيتم طباعة العدد 11 مرتين .

ملاحظة 1 / العملية ( ++ ) لها أولية على الرمز ( * ) لذلك استخدمنا الأقواس لكي نزيد القيمة التي يشير إليها المؤشر واحد و ليس قيمة المؤشر .. فبدون استخدام الأقواس سيتم زيادة قيمة ptr واحد و بذلك فلن يشير هذا المؤشر إلى المتحول x لأنه أصبح لديه قيمة جديدة و هي عنوان جديد .. و بذلك خطورة كبيرة لأننا لا نعرف على ماذا يحتوي ذلك العنوان الجديد في الذاكرة .. و بالتالي سيحدث خطأ منطقي و ليس قواعدي .

ملاحظة 2 / تتطلب هذه الطريقة ( الطريقة الأولى ) وجود متحول آخر تم تعريفه مسبقاً في البرنامج و ذلك لكي يستطيع المؤشر أن يؤشر على ذلك المتحول .. و تعتبر الفكرة السابقة أحد عيوب هذه الطريقة .. على عكس الطريقة الثانية التي لا تحتاج إلى متحول آخر من أجل عملية التأشير .


الطريقة الثانية / استخدام المؤشر عن طريق الحجز الديناميكي و التحرير الديناميكي ( اليدوي ) و ذلك خلال زمن تنفيذ البرنامج :

أولاً : الحجز الديناميكي :

و يتم ذلك على خطوتين و هما كالتالي :
1 – حجز موقع جديد يتسع لقيمة معينة .. و هذا يعني باختصار : التصريح عن مؤشر جديد يتسع لتخزين القيمة التي نريدها .. و ذلك تماماً كالسابق .. مثال :


int* ptr;
2 – البحث عن موقع جديد فارغ في الذاكرة و من ثم وضع عنوان ذلك الموقع كقيمة في المؤشر الذي قمنا بتعريفه في الخطوة الأولى .. و ذلك عن طريق التالي :


Pointer = new Types;
حيث أن :
Pointer : هو المؤشر الذي قمنا بتعريفه في الخطوة الأولى .
new : هي كلمة محجوزة ( ستظهر باللون الأزرق ) .. وظيفتها هي إيجاد موقع جديد فارغ في الذاكرة حجمه يطابق حجم النوع الذي سيتم وضعه بعدها ( Types ) .
Types : أي نوع نريده سواءً أكان أحد الأنواع مسبقة التعريف مثل ( int,float,double .. إلخ ) أو أحد الأنواع المعرفة من قبل المستخدم كالسجلات و الكائنات .


و في مثالنا هنا نستطيع كتابة التالي :

ptr = new int;

ملاحظة / يمكن اختصار الخطوتين السابقتين على الشكل التالي :

int ptr = new int;

و لمزيد من التوضيح .. نفرض أن المؤشر ( ptr ) قد أخذ العنوان ( 320 ) و ذلك في الخطوة الأولى التي تم فيها تعريف ذلك المؤشر .. و لاحظ أيضاً كيف أنه في هذه الخطوة لم يأخذ قيمة بعد ( و هي عنوان موقع آخر في الذاكرة ) و هذا يعني أن المؤشر لا يشير إلى قيمة .. انظر الصورة :

http://members.lycos.co.uk/wolfsniper84c/C/pointers4.gif


و في الخطوة الثانية سيتم البحث عن موقع فارغ في الذاكرة ذو حجم معين ( 4 بايت قي مثالنا السابق من أجل النوع int ) .. و سيتم إعطاء عنوان ذلك الموقع للمؤشر المعرف قي الخطوة الأولى و سنفرض أن عنوان ذلك الموقع هو ( 120 ) .. انظر الصورة :

http://members.lycos.co.uk/wolfsniper84c/C/pointers5.gif


بهذه الطريقة أصبح المؤشر ( ptr ) يؤشر على موقع جديد عنوانه ( 120 ) .. و لكن لاحظ أن ذلك الموقع لا يحتوي على قيمة !!! إذا كيف نستطيع تخزين قيمة داخل ذلك الموقع ؟ ببساطة .. نستطيع ذلك عن طريق المؤشر ( prr ) و ذلك طبعاً باستخدام الرمز ( * ) .. مثال :

*ptr = 5;

تماماً كما كنا نفعل في السابق .. و لكن افرض أننا نريد إعطاء قيمة ابتدائية لذلك الموقع .. نستطيع فعل ذلك في الخطوة الثانية عن طريق وضع قوسين بعد النوع و وضع القيمة الابتدائية للموقع الجديد بين هذين القوسين .. مثال : في الخطوة الثانية /

ptr = new int(5);

هنا أصبح الموقع الجديد الذي يشير إليه المؤشر ( ptr ) يحتوي على القيمة ( 5 ) .. و انتبه إلى أن هذه القيمة هي ليست قيمة المؤشر ( ptr ) !!! بل كما قلنا هي قيمة الموقع الذي يشير إليه ذلك المؤشر .. انظر الصورة ( و ذلك مع افتراض نفس القيم السابقة للعناوين ) :

http://members.lycos.co.uk/wolfsniper84c/C/pointers6.gif


للتأكد سوف نقوم بطباعة قيمة المؤشر ( ptr ) و أيضاً القيمة التي يشير إليها المؤشر ( ptr* ) و شاهد كيف أن تلك القيم تختلف و كيف أن ( ptr* ) تساوي ( 5 ) .. و ذلك عن طريق كتابة التالي :

cout << ptr << endl << *ptr << endl;


http://members.lycos.co.uk/wolfsniper84c/C/pointers7.gif


ثانياً / التحرير ( الحذف أو الإلغاء ) الديناميكي :

بعدما قمنا بحجز موقع جديد في الذاكرة عن طريق الأمر ( new ) .. يجب علينا الأن أن نعرف كيف نقوم بعملية تحرير ( إلغاء ) ذلك الموقع من الذاكرة .. لأن كل عملية حجز يدوية يجب أن تقابلها عملية تحرير يدوية للذاكرة .. و ذلك لفتح المجال أمام متغيرات أخرى لكي تأخذ ذلك الموقع .. و لكي لا تحصل أخطاء غير مرغوب بها متعلقة بانتهاك للذاكرة العشوائية .. لذلك يجب علينا في هذه الحالة أن نقوم نحن بتحرير ذلك الموقع من الذاكرة بأنفسنا .. و نستطيع عمل ذلك في أي مكان نريد و كقاعدة عامة / قم بتحرير الموقع الذي قمت بإنشائه عن طريق الأمر ( new ) فور الانتهاء منه نهائياً بحيث لن تحتاج له بعد ذلك .. و إذا لم تستطع تقدير ذلك المكان قم بعملية التحرير في نهاية البرنامج ( في نهاية التابع main ) .. ( تم التصحيح هنا ) .


قاعدة / نستطيع القيام بعملية التحرير الديناميكي عن طريق كتابة الأمر ( delete ) و من ثم نضع اسم المؤشر و من ثم نضع الفاصلة المنقوطة :

delete Pointer;
حيث أن :
delete : كلمة محجوزة ستظهر باللون الأزرق .. و تستخدم من أجل عملية التحرير الديناميكي .
Pointer : اسم المؤشر الذي تريد أن تقوم بتحريره من الذاكرة .


مثال :

delete ptr;


ملاحظة / يجب عليك استخدام عملية حجز جديدة بعد أي عملية تحرير و ذلك إن أردت استخدام المؤشر مرة أخرى .. إذا نستنتج من ذلك أننا نستطيع أن نقوم بعملية الحجز و التحرير الديناميكي أكثر من مرة و ذلك لمؤشر واحد فقط .. ( تم التصحيح هنا ) .. مثال :

int* p;

p = new int(10);
cout << *p << endl;
delete p;

p = new int(20);
cout << *p << endl;
delete p;
هنا سيتم إنشاء موقع جديد يشير إليه المؤشر ( p ) و يحمل القيمة ( 10 ) .. و بعدها سنقوم بإلغاء ذلك الموقع مع ملاحظة أن العملية ( delete p ) لا تقوم بإلغاء المؤشر نفسه كما قلنا بل تلغي الموقع الذي يشير إليه ذلك المؤشر .. و بعدها قمنا بحجز موقع جديد آخر يختلف عنوانه عن العنوان السابق و وضعنا فيه القيمة ( 20 ) ..

الأن لاحظ كيف أنه بعملية التحرير سيلغى الموقع المحجوز و لن توجد القيمة ( 100 ) في ذلك الموقع .. انظر المثال التالي /

int* p;

p = new int(100);
delete p;

cout << *p << endl << p << endl;
و يمكننا تلخيص طريقة عمل الحجز الديناميكي و التحرير الديناميكي عن طريق مثالنا السابق على الشكل التالي :

int* ptr;
ptr = new int(5);

cout << *ptr << endl;
(*ptr)--;
cout << *ptr << endl;
*ptr = 3;
cout << *ptr << endl;

cin >> *ptr;
cout << *ptr << endl;

delete ptr;


ملاحظة / لاحظ كيف أن هذه الطريقة ( الحجز الديناميكي و التحرير الديناميكي ) لا تحتاج إلى وجود متحول آخر لكي يشير إليه المؤشر كما في الطريقة الأولى .. و ذلك بسبب استخدام الأمر ( new ) الذي يبحث عن موقع فارغ جديد لا يشغله أي متغير آخر .

al_yahmedi
31-05-2005, 10:45 AM
السلام عليكم ورحمة الله وبركاته
احببت موضوعك اخي العزيز.....وسوف اقوم جاهداً لكسب شيئا ولو قليل ...انا طالب في كليات ولجات للعلوم التطبيقية
الكلية معتمدة من معهد بيرلا بالهند
انا طالب سنة اولى... العائق الوحيد بالنسبة لي هي اللغة الأنجليزية ,كوني لم استطيع فهم الاستاذ بسبب اللغة و لغات البرمجة مثل:-
(السي / السي ++)
حاولت لكن من دون فائدة
عندي خلفيه في السي لكن سي++ صفر بالمائة يعني كأني طاولة في الصف وكل هذا الكلام غير صحيح لاني حاولت من دون جدوى و
يشرفني ان اكون احد من طلابك الفاشل الطموح في نفس الوقت .
al_yahmedi@hotmail.com

wabdulaziz
03-07-2005, 11:49 PM
مشكووور على هذا المجهود الرائع......:biggthump

UAE Naruto
04-07-2005, 02:08 AM
مشكووور على هذا المجهود الرائع......:biggthump


::

::

ممنوع الرد في هذا الموضوع!!!!!!!!!!!!!!!!!!!!!

::

::

ابو سن
08-07-2005, 02:20 PM
مجرد تنبيه !! اكثر الصور لا تعمل

glowing gold
12-07-2005, 04:10 AM
الصور لاتعمل ارجو ان يتم رفعها من جديد

Wolf Sniper
13-07-2005, 12:01 AM
يا إخوان أرجو عدم الرد في هذا الموضوع و ذلك لأننا خصصنا موضوع آخر للردود !!! و ذلك لترتيب الدروس بشكل متتالي و جيد .. ادخلوا هنا و اكتبوا أي سؤال أو أي استفسار تريدون موضوع الردود (http://www.montada.com/showthread.php?p=3739376#post3739376)

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

نور حياتي
14-02-2007, 01:45 PM
انا عضوه جديده وبحاجه الى تعلم هذه اللغه لاني طالبه في جامعه علوم الحاسبات واعاني منها جدا واتمنى الرد على سؤالي
اكتب برنامج لحساب عدد مراتب رقم مدخل
مع جزيل الشكر

d.a.m.h
14-02-2007, 03:26 PM
انا عضوه جديده وبحاجه الى تعلم هذه اللغه لاني طالبه في جامعه علوم الحاسبات واعاني منها جدا واتمنى الرد على سؤالي
اكتب برنامج لحساب عدد مراتب رقم مدخل
مع جزيل الشكر

-على فكرة رفع المواضيع القديمة ممنوع حسب قوانين المنتدى البند الثالث
http://www.montada.com/showthread.php?t=446138

-من الافضل التوجه لقسم البرمجة
http://www.montada.com/forumdisplay.php?f=461
ففيه اعضاء ممكن يساعدوكى لأن قسمنا هنا مخصص لبرمجة وتصميم الالعاب فقط

شكرا يا DAMH للمساعدة, الموضوع ممنوع من الرفع بعد الآن, و لكن ليس مغلق. - OctoBot