The words you are searching are inside this book. To get more targeted content, please make full-text search by clicking here.

ترجمة لدرس تعلّم البرمجة بلغة السي الخاص بموقع OpenClassrooms

Discover the best professional documents and content resources in AnyFlip Document Base.
Search
Published by Hamza Abbad, 2017-08-03 11:01:25

تعلّم البرمجة بلغة C

ترجمة لدرس تعلّم البرمجة بلغة السي الخاص بموقع OpenClassrooms

‫التعدادات‬

‫;‪1 typedef enum Volume Volume‬‬
‫‪2 enum Volume‬‬
‫{‪3‬‬
‫‪4 LOW, MEDIUM, HIGH‬‬
‫;} ‪5‬‬

‫تلاحظ أننا نستعمل ‪ typedef‬هنا أيضا‪ ،‬مثلما رأينا لحد الآن‪.‬‬

‫لـكي نقوم بتعر يف تعداد نستعمل الكلمة المفتاحية ‪ . enum‬اسم التعداد هنا هو ‪ . Volume‬إن ّه نوع‬
‫مخ ّصص قمنا بتعر يفه يمكن له أن يأخذ واحدة من الثلاث قيم ال ّتي وضعناها ‪ :‬إما ‪ LOW‬أو ‪ MEDIUM‬أو‬

‫‪. HIGH‬‬

‫يمكننا إذن أن نعر ّف متغيرا اسمه ‪ music‬من نوع ‪ Volume‬لتخزين مستوى صوت الموسيقى‪.‬‬
‫يمكننا تهيئة الموسيقى على المستوى ‪: MEDIUM‬‬

‫;‪1 Volume music = MEDIUM‬‬

‫يمكننا لاحقا ًفي الشفرة‪ ،‬أن نغيّر قيمة مستوى الصوت و وضعها إمّا على ‪ HIGH‬أو على ‪. LOW‬‬

‫ارفاق قيم التعداد بأعداد‬

‫قد لاحظت أن ّي كتبت القيم الممكنة بأحرف كبيرة‪ .‬هذا يفترض به أن يذكّرك بالثوابت و ‪ ، #define‬أليس‬
‫كذلك ؟‬

‫في الواقع‪ ،‬إ ّن هذا مشابه كثيرا و لـكن ّه ليس نفس الشيء‪.‬‬
‫المترجم يقوم تلقائيا بارفاق قيم التعداد بأعداد موافقة لها‪.‬‬

‫في حالة تعدادنا ‪ LOW ، Volume‬سيتم ارفاقها بالقيمة ‪ MEDIUM ،0‬بالقيمة ‪ ،1‬و ‪ HIGH‬بالقيمة ‪.2‬‬
‫الإرفاق يتم ّ تلقائيّا انطلاقا من ‪.0‬‬

‫خلافا لـ ‪ ، #define‬فالمترجم هو من يرفق ‪ MEDIUM‬بـ‪ 1‬مثلا‪ ،‬وليس المعالج القبلي‪ .‬لـك ّن هذا سيكون‬
‫تقريبا مكافئا له‪.‬‬

‫بطبيعة الحال‪ ،‬عندما هي ّئنا المتغيّر ‪ music‬على ‪ ، MEDIUM‬فإنّنا قد وضعنا القيمة ‪ 1‬في خانة الذاكرة الموافقة‪.‬‬

‫عملي ّا‪ ،‬هل يهمّنا أن نعرف أ ّن ‪ MEDIUM‬تساوي ‪ HIGH ،‬تساوي ‪ ،‬إلخ‪ .‬؟‬

‫لا‪ .‬فهذا حقيقة لا يعنينا‪ .‬المترجم هو من سيقوم تلقائيّا بإرفاق العدد المناسب إلى ك ّل قيمة‪ .‬بفضل هذا‪،‬‬
‫ليس عليك سوى كتابة ‪:‬‬

‫‪201‬‬

‫الفصل ب‪ .6.‬أنشئ أنواع متغيرات خا ّصة بك‬

‫)‪1 if (music == MEDIUM‬‬
‫{‪2‬‬
‫‪3 // Play music with medium volume‬‬
‫}‪4‬‬

‫لا يه ّم ما هي قيمة ‪ ، MEDIUM‬ستترك المترجم يهتم ّ بالأعداد‪.‬‬

‫الفائدة من ك ّل هذا ؟ هي أنها تجعل الشفرة قابلة للقراءة جي ّدا‪ .‬في الواقع‪ ،‬أ ّي شخص يمكنه بسهولة قراءة‬
‫‪ if‬السابق )نفهم جي ّدا أ ّن الشرط يعني ”إن كانت الموسيقى بمستوى صوت متو ّسط”(‪.‬‬

‫ارفاق قيمة محددة‬

‫حالي ّا‪ ،‬كان المترجم هو من يقر ّر إرفاق العدد ‪ 0‬ثم ‪ 1،2،3‬بالترتيب‪.‬‬
‫من الممكن طلب إرفاق قيمة مح ّددة لك ّل عنصر من التعداد‪.‬‬

‫ما الفائدة التي يمكن تحصيلها من هذا ؟ حسنا فلنفرض أن ّه في حاسوبك‪ ،‬الصوت يتم تحديده بقيمة بين ‪0‬‬
‫و ‪ = 0) 100‬لا صوت‪ 100% = 100 ،‬من الصوت(‪ ،‬فسيكون من الجيد ارفاق قيمة مح ّددة بك ّل عنصر ‪:‬‬
‫;‪1 typedef enum Volume Volume‬‬
‫‪2 enum Volume‬‬
‫{‪3‬‬
‫‪4 LOW = 10, MEDIUM = 50, HIGH = 100‬‬
‫;} ‪5‬‬

‫هنا‪ ،‬المستوى ‪ LOW‬يوافق ‪ 10%‬من المستوى‪ ،‬المستوى ‪ MEDIUM‬يوافق ‪ ،50%‬إلخ‪.‬‬
‫يمكننا بسهولة إضافة بعض القيم الأخر مثل ‪ . MUTE‬نرفق في هذه الحالة ‪ MUTE‬بالقيمة‪ ! 0 ...‬لقد فهمت‪.‬‬

‫مل ّخص‬

‫• الهيكل هو نوع بيانات مخ ّصص يمكنك إنشائه و استخدامه في برامجك‪ .‬يجب عليك تعر يفه‪ ،‬عكس الأنواع‬
‫القاعدي ّة مثل ‪ int‬و ‪ double‬ال ّتي نجدها في ك ّل البرامج‪.‬‬

‫• الهيكل يتكوّن من ”متغيّرات داخلي ّة” تكون عادة من أنواع قاعدي ّة مثل ‪ int‬و ‪ ، double‬و أيضا‬
‫من الجداول‪.‬‬

‫• نستطيع الوصول إلى أحد مركّبات الهيكل بفصل اسم المتغيّر و المر ّكب بنقطة ‪. player.firstName :‬‬

‫• إذا كن ّا نتعامل مع مؤشر نحو هيكل و أردنا الوصول إلى أحد مركّباته‪ ،‬نستخدم السهم بدل النقطة ‪:‬‬
‫‪. playerPointer->firstName‬‬

‫• التعداد هو نوع مخ ّصص يمكنه فقط أخذ إحدى القيم المسبقة التعر يف ‪ MEDIUM ، LOW :‬أو ‪HIGH‬‬
‫مثلا‪.‬‬

‫‪202‬‬

‫الفصل ب‪7.‬‬

‫قراءة و كتابة الملفات‬

‫المشكل مع استعمال المتغيّرات‪ ،‬هو أنها موجودة فقط في الذاكرة العشوائية ‪ .RAM‬بخروجنا من البرنامج‪،‬‬
‫ك ّل المتغيّرات يتم حذفها من الذاكرة و لن يصبح ممكنا إستعادة قيمها‪ .‬كيف يمكننا إذن أن نحتفظ بأحسن‬
‫العلامات التي تح ّصلنا عليها في لعبة ؟ كيف يمكننا إنشاء محرر نصوص إذا كان ك ّل الن ّص المكتوب يختفي بمجر ّد‬

‫إيقاف البرنامج ؟‬
‫لحسن الح ّظ يمكننا القراءة من الملفا ّت و كذا الكتابة فيها في لغة ‪ .C‬هذه المل ّفات ُمخز ّنة في القرص الصلب‬
‫)‪ (Hard disk‬الخاص بالحاسوب ‪ :‬الشيء الإيجابيّ إذن هو أنها تبقى محفوظة‪ ،‬حت ّى عند إيقاف البرنامج أو‬

‫الحاسوب‪.‬‬
‫للقراءة من الملفات و الكتابة فيها‪ ،‬سنحتاج إلى استعمال ك ّل ما درسناه حت ّى الآن ‪ :‬المؤشرات‪ ،‬الهياكل‪،‬‬

‫السلاسل المحرفي ّة‪ ،‬الخ‪.‬‬

‫ب‪ 1.7.‬فتح و غلق ملف‬

‫للقراءة و الكتابة في المل ّفات‪ ،‬سنستعمل دوالا ًمعر ّفة في المكتبة ‪ stdio‬التي استعملناها سابقا ً‪.‬‬
‫نعم‪ ،‬هذه المكتبة تحتوي على الدالتين ‪ scanf‬و ‪ printf‬اللتان نعرفهما جي ّدا ! لـكن ليس هذا فحسب ‪:‬‬

‫يوجد بها الـكثير من الدوال الأخرى‪ ،‬خصوصا التي تعمل على الملفات‪.‬‬

‫كل المكتبات التي استعملناها حت ّى الآن ) ‪، math.h ، stdio.h ، stdlib.h‬‬
‫‪ (... string.h‬تش ّكل ما نسميه بالمكتبات القياسية )‪ ،(standard libraries‬و هي مكتبات تأتي‬
‫تلقائيا مع البيئة التطوير ية التي تستخدمها و لديها الميزة في أ ّنها تعمل على كل أنظمة التشغيل‪ .‬بالإمكان‬
‫استعمالها في أ ّي مكان‪ ،‬سواء كنت في ‪ ،Windows‬أو ‪ GNU/Linux‬أو ‪ Mac‬أو غير ذلك‪ .‬المكتبات‬
‫القياسي ّة ليست كثيرة و لا تم ّكننا من القيام بأكثر من بعض الأمور الأساسي ّة‪ ،‬كما فعلنا لغاية الآن‪.‬‬
‫للحصول على وظائف أكثر تق ّدما‪ ،‬كفتح النوافذ‪ ،‬يجب تحميل و ٺثبيت مكتبات جديدة‪ .‬سنرى ذلك‬

‫قريبا !‬

‫‪203‬‬

‫الفصل ب‪ .7.‬قراءة و كتابة الملفات‬

‫تأكّد إذن‪ ،‬للبدأ‪ ،‬أن تقوم بتضمين المكتبتين ‪ stdio.h‬و ‪ stdlib.h‬على الأقل أعلى ملفكم ‪: .c‬‬
‫>‪1 #include <stdlib.h‬‬
‫>‪2 #include <stdio.h‬‬

‫هاتان المكتبتان ضروريتان و أساسي ّتان لدرجة أن ّي أنصحك بتضمينهما في ك ّل البرامج التي تكتبها في المستقبل‪،‬‬
‫أيّا كانت‪.‬‬

‫حسنا ًو بعدما قمنا بتضمين المكتبتين‪ ،‬يمكننا أن ننطلق في بالأمور الج ّدي ّة‪ .‬إليك الخطوات التي يجب إتّباعها‬
‫دائما ًحينما تريد العمل على ملف‪ ،‬سواء للقراءة منه أو للكتابة فيه ‪:‬‬

‫• نقوم بمناداة دالة فتح الملف ‪ fopen‬التي تقوم بإرجاع مؤش ّر نحو هذا الملف‪.‬‬
‫• نتأكّد من نجاح عملي ّة الفتح )أي إن كان المل ّف موجودا( باختبار قيمة المؤشر الذي أرجعته الدالة‪ .‬فإن‬
‫كان المؤشر يساوي ‪ ، NULL‬فهذا يعني أ ّن فتح الملف لم ينجح‪ ،‬في هذه الحالة لا يمكننا الإكمال )يجب‬

‫أن نظهر رسالة خطا(‪.‬‬
‫• إذا تم الفتح بنجاح )أي أن قيمة المؤشر تختلف عن ‪ ،( NULL‬سنستمتع بالكتابة على الملف أو القراءة‬

‫منه‪ ،‬و ذلك باستخدام دوال سنراها لاحقا ً‪.‬‬
‫• بمجر ّد أن ننهي العمل على الملف‪ ،‬يجب تذكّر ”غلقه” باستعمال الدالة ‪. fclose‬‬

‫سنتعلّم كخطوة أولى كيف نستخدم ‪ fopen‬و ‪ ، fclose‬حينما ٺتعلّم هذا‪ ،‬سنتعلّم كيف نقرأ محتواه و نكتب‬
‫ن ّصا فيه‪.‬‬

‫‪ : fopen‬فتح ملف‬

‫في فصل السلاسل المحرفي ّة‪ ،‬كنا نستعين بنماذج الدوال مثل ”دليل استخدام”‪ .‬هذا ما يفعله المبرمجون غالبا ‪:‬‬
‫يقرؤون نموذج دالة و يفهمون كيف يستخدمونها‪ .‬مع ذلك‪ ،‬أعلم أنّنا بحاجة إلى بعض الشروحات البسيطة !‬

‫لهذا فلنرى قليلا ًنموذج ‪: fopen‬‬
‫;)‪1 FILE* fopen(const char* fileName, const char* openMode‬‬

‫هذه الدالة تنتظر معاملين ‪:‬‬

‫• اسم الملف الذي نريد فتحه‪.‬‬
‫• أسلوب فتح الملف‪ ،‬أي دلالة تذكر ما ال ّذي تريد فعله ‪ :‬القراءة من الملف‪ ،‬أو الكتابة فيه‪ ،‬أو كليهما‪.‬‬

‫‪204‬‬

‫فتح و غلق ملف‬

‫هذه الدالة ترجع‪ ...‬مؤش ّرا على ‪ ! FILE‬إن ّه مؤش ّر على هيكل من نوع ‪ . FILE‬هذا الهيكل متواجد في‬
‫المكتبة ‪ . stdio.h‬يمكنك فتح الملف لترى مما يتكوّن النوع ‪ ، FILE‬لـكن هذا ليس ما يهمّنا‪.‬‬

‫لـكن ل ِم َ اسم الهيكل كله بأحرف كبيرة؟ اعتقدت أن الأسماء بالأحرف الـكبيرة حجزناها للثوابت و‬
‫لـ ‪ #define‬؟‬

‫هذه ”القاعدة”‪ ،‬أنا من قمت بتحديدها )و كثير من المبرمجـين يتيعونها(‪ ،‬و لـكنّها لم تكن أبدا مفروضة‪ .‬و‬
‫يبدو أ ّن من برمجوا ‪ stdio.h‬لا يتبعون نفس القواعد !‬

‫هذا لا يجب أن يشوّشك كثيرا‪ .‬سوف ترى أ ّن المكتبات ال ّتي سندرسها لاحقا ٺتب ّع نفس القواعد التي أتّبعها‪،‬‬
‫أي أن اسم الهيكل يبتدئ فقط بحرف واحد كبير‪.‬‬

‫لنعد إلى دالتنا ‪ ، fopen‬إنها تقوم بارجاع *‪ . FILE‬إنه من المهم ج ّدا استرجاع هذا المؤش ّر كي نتم ّكن‬

‫لاحقا ًمن القراءة و الكتابة في الملف‪ .‬و لهذا سنقوم بإنشاء مؤش ّر على ‪ ، FILE‬في بداية دالتنا ) ‪ main‬مثلا(‬
‫‪:‬‬

‫‪1‬‬ ‫)][‪int main(int argc, char *argv‬‬
‫‪2‬‬
‫{‬

‫‪3‬‬ ‫;‪FILE* file = NULL‬‬
‫‪4‬‬
‫;‪return 0‬‬

‫}‪5‬‬

‫لقد هي ّأنا المؤش ّر على ‪ NULL‬من البداية‪ .‬أذكّرك بأ ّن هذه قاعدة أساسي ّة أن تهي ّأ ك ّل المؤش ّرات على ‪NULL‬‬
‫إ ّن لم تكن لديك قيمة أخرى لإعطائها‪ .‬إن لم تفعل ذلك‪ ،‬فأنت تزيد كثيرا خطر وجود أخطاء لاحقا‪.‬‬

‫إنه ليس ضرور يا ًأن تكتب ‪ ، struct FILE* file = NULL‬لأن منشئي ‪ stdio.h‬قد وضعوا‬
‫‪ typedef‬كما عل ّمتك منذ م ّدة قصيرة‪ .‬لاحظ أن شكل الهيكل قد يتغيّر من نظام تشغيل إلى آخر‬
‫)لا تملك بالضرورة نفس المركّبات في كل الأنظمة(‪ .‬لهذا فلن نع ّدل محتوى ‪ FILE‬مباشرة )لا نقوم‬

‫بـ ‪ file.element‬مثلا(‪ .‬بل سنكتفي باستدعاء دوال‪ ،‬ٺتعامل مع ‪ FILE‬نيابة عنا ً‪.‬‬

‫الآن سنقوم بمناداة الدالة ‪ ، fopen‬و استرجاع القيمة ال ّتي تعيدها في المؤشر ‪ . file‬و لـكن قبل هذا‬
‫يجب أن اشرح لك كيف تستخدم المعامل الثاني ‪ . openMode‬في الواقع‪ ،‬هناك شفرة تد ّل للحاسوب على أنك‬

‫تريد أن تفتح الملف بوضع القراءة فقط‪ ،‬الكتابة فقط أو الاثنين معا ً‪.‬‬
‫هذه هي أوضاع فتح الملف المختلفة ‪:‬‬

‫• ”‪ : ”r‬قراءة فقط )‪ .(read only‬يمكنك قراءة محتوى الملف‪ ،‬و لـكن لا يمكنك الكتابة فيه‪ .‬يجب أن‬
‫يكون الملف موجودا ً من قبل‪.‬‬

‫‪205‬‬

‫الفصل ب‪ .7.‬قراءة و كتابة الملفات‬

‫• ”‪ : ”w‬كتابة فقط )‪ .(write only‬يمكنك الكتابة في الملف‪ ،‬لـكن لا يمكنك قراءة محتواه‪ .‬إذا لم يكن‬
‫الملف موجودا ً من قبل‪ ،‬فإنه سيتم إنشاؤه‪.‬‬

‫• ”‪ : ”a‬إلحاق )‪ .(append‬يمكنك الكتابة في الملف‪ ،‬إنطلاقا من نهايته‪ .‬إن لم يكن الملف موجودا ً‪ ،‬فسيتم‬
‫إنشاؤه‪.‬‬

‫• ”‪ : ”r+‬قراءة و كتابة )‪ .(read and write‬يمكنك القراءة من الملف و الكتابة فيه‪ .‬يجب أن يكون‬
‫الملف موجودا ً من قبل‪.‬‬

‫• ”‪ : ”w+‬قراءة و كتابة مع مسح المحتوى أ ّولا‪ .‬سيتم تفر يغ الملف من محتواه أولا ً‪ ،‬ثم بإمكانك الكتابة فيه‬
‫و قراءة محتواه بعد ذلك‪ .‬إن لم يكن الملف موجودا ً من قبل‪ ،‬سيتم إنشاؤه‪.‬‬

‫• ”‪ ”a+‬إلحاق مع القراءة ‪ /‬الكتابة في آخر الملف‪ .‬يمكنك القراءة و الكتابة إنطلاقا من نهاية الملف‪ .‬إن لم‬
‫يكن موجودا ً‪ ،‬سيتم إنشاؤه‪.‬‬

‫لمعلوماتك‪ ،‬أنا عرضت لك بعضا من أوضاع فتح ملف‪ .‬في الحقيقة‪ ،‬يوجد ضعفها ! من أجل كل‬
‫وضع رأيناه هنا‪ ،‬إن أضفت ”‪ ”b‬بعد المحرف الأول ) ”‪، ”wb+” ، ”rb+” ، ”ab” ، ”wb” ، ”rb‬‬
‫”‪ ،( ”ab+‬فإن الملف سيتم فتحه بالوضع الثنائي )‪ .(binary‬هذا وضع خاص قليلا ًفلن ندرسه هنا‪ .‬في الواقع‬
‫وضع النص يخت ّص بتخزين‪ ...‬النص‪ ،‬تماما كما يوحي الاسم )فقط المحارف القابلة للعرض(‪ .‬أما الوضع الثنائي‪،‬‬
‫يسمح بتخزين المعلومات بايتا بايتا )‪) (Byte by byte‬أرقام بشكل أساسي(‪ .‬هذا مختلف كثيرا‪ .‬على أي حال‬

‫فطر يقة العمل هي تقريبا نفس ال ّتي سنراها هنا‪.‬‬

‫شخصيا ً‪ ،‬أستعمل كثيرا ً الأوضاع ‪) ”r” :‬قراءة(‪) ”w” ،‬كتابة(‪) ”r+” ،‬قراءة و كتابة في آن واحد(‪.‬‬
‫وضع ”‪ ”w+‬خطر قليلا ًلأنه يقوم بمسح محتوى الملف مباشرة‪ ،‬بدون أن يطلب التأكيد قبل القيام بذلك‪ .‬إن‬
‫هذا الوضع ليس مفيدا ً إلا إذا أردنا أن نعيد تهيئة الملف أ ّولا‪ .‬وضع الإلحاق ) ”‪ ( ”a‬يمكنه أن يفيد في بعض‬

‫الحالات‪ ،‬إذا كنت تريد إضافة معلومات إلى نهاية الملف‪.‬‬

‫إن كنت تريد قراءة مل ّف‪ ،‬فمن المستحسن وضع ”‪ . ”r‬بالطبع‪ ،‬الوضع ”‪ ”r+‬يعمل أيضا‪ ،‬لـكن‬
‫بوضع ”‪ ”r‬فأنت تضمن أ ّن المل ّف لا يمكن تعديله‪ ،‬هذا نوع من الحماية‪.‬‬

‫إن كتبت دالة ً ‪) loadLevel‬لتحميل مستوى في لعبة مثلا(‪ ،‬الوضع ”‪ ”r‬كا ٍف‪ ،‬أما إن أردت أن‬
‫كتابة دالةٍ ‪) saveLevel‬لحفظ المستوى( فستستعمل الوضع ”‪. ”w‬‬

‫الشفرة التالية ستفتح الملف ‪ test.txt‬في وضع ”‪) ”r+‬قراءة و كتابة( ‪:‬‬
‫)][‪1 int main(int argc, char *argv‬‬
‫{‪2‬‬
‫;‪3 FILE* file = NULL‬‬

‫‪206‬‬

‫فتح و غلق ملف‬

‫‪4‬‬ ‫;)”‪file = fopen(”test.txt”, ”r+‬‬
‫‪5‬‬ ‫;‪return 0‬‬
‫}‪6‬‬

‫المؤش ّر ‪ file‬يصبح إذن مؤشرا ً على الملف ‪. test.txt‬‬

‫أين يجب أن يكون الملف ‪ test.txt‬؟‬

‫يجب أن يكون في نفس المجل ّد الذي يتواجد به الملف التنفيذي ) ‪.( .exe‬‬
‫من أجل متطل ّبات هذا الفصل‪ ،‬أطلب منك أن تقوم بإنشاء ملف ‪ test.txt‬في نفس المساؤ الذي به‬

‫‪ ، .exe‬مثلما أفعل أنا )الشكل الموالي(‪.‬‬

‫كما ترى فأنا أستعمل حالي ّا بيئة التطوير ‪ Code::Blocks‬الأمر الذي يفسّر وجود ملف المشروع بصيغة‬
‫‪) .cbp‬في مكان الصيغة ‪ .sln‬إن كنت تستعمل ‪ Visual C++‬مثلا ً(‪ .‬باختصار‪ ،‬الأمر المهم هو أن‬

‫برنامجي ) ‪ ( tests.exe‬موجود في نفس مجل ّد الملف الذي نريد قرائته أو كتابته ) ‪.( test.txt‬‬

‫هل يجب أن يكون الملف بصيغة ‪ .txt‬؟‬
‫لا‪ ،‬الأمر يعود إليك في اختيار صيغة الملف عندما تفتحه‪ .‬أي أنه بإمكانك أن تخـترع صيغتك الخا ّصة‬

‫‪ .level‬لحفظ مستو يات ألعابك مثلا ً‪.‬‬
‫هل من الواجب أن يكون الملف الذي نريد فتحه في نفس دليل الملف التنفيذي ؟‬

‫لا أيضا‪ .‬يمكنه أن يكون داخل مجل ّد بذات الدليل ‪:‬‬

‫‪207‬‬

‫الفصل ب‪ .7.‬قراءة و كتابة الملفات‬

‫;)”‪1 file = fopen(”directory/test.txt”, ”r+‬‬

‫هنا‪ ،‬الملف ‪ test.txt‬في مجل ّد داخليّ اسمه ‪ . directory‬هذه الطر يقة التي نسميها المسار النسبي‬
‫عملي ّة أكثر‪ .‬هكذا‪ ،‬يمكن للبرنامج أن يعمل أينما كان مثب ّتا‪.‬‬

‫من الممكن أيضا فتح مل ّف أينما كان في القرص الصلب‪ .‬في هذه الحالة يجب كتابة المسار الكامل )ما نسميه‬
‫المسار المطلق( ‪:‬‬

‫;)”‪1 file = fopen(”C:\\Program Files\\Notepad++\\readme.txt”, ”r+‬‬

‫هذه الشفرة تفتح الملف ‪ readme.txt‬الموجود بـ ‪. C:\Program Files\Notepad++‬‬

‫تعمّدت استعمال شرطتبن خلفي ّتين \ كما تلاحظ‪ .‬في الواقع‪ ،‬إن كتبت اشارة واحدة‪ ،‬سيعتقد‬
‫الحاسوب أنني أريد أن استخدم رمزا خاصا )مثل الـ ‪ \n‬أو الـ ‪ .( \t‬لكتابة شرطة خلفي ّة في سلسلة‪،‬‬

‫يجب كتابتها إذن م ّرتين ! هكذا يمكن أن يفهم أن ّك تريد استخدام الرمز \ ‪.‬‬

‫المشكل مع المسارات المطلقة‪ ،‬هو أنها لا تعمل إلا مع نظام معي ّن‪ ،‬فهي ليست حل ّا محمولا إذن‪ .‬أي أنه لو‬
‫كنت تعمل على ‪ GNU/Linux‬لكان عليك كتابة مسار كهذا مثلا ‪:‬‬

‫;)”‪1 file = fopen(”/home/mateo/directory/readme.txt”, ”r+‬‬

‫لهذا فأنا أنصحك بكتابة مسارات نسبية‪ .‬لا تستعمل المسارات المطلقة إلا في حالة كان البرنامج مخصص‬
‫لنظام تشغيل معي ّن‪ ،‬ليع ّدل على ملف معي ّن في القرص الصلب‪.‬‬

‫اختبار فتح ملف‬

‫المؤش ّر ‪ file‬يجب أن يحوي عنوان الهيكل من نوع ‪ ، FILE‬و الذي نستعمله كواصف )‪(descriptor‬‬
‫للملف‪ .‬هذا الواصف تم تحميله من أجلك في الذاكرة من طرف الدالة ‪ . fopen‬بعد هذا‪ ،‬هناك احتمالان ‪:‬‬

‫• إمّا أن تنجح عملية الفتح‪ ،‬فسنتمكن من المواصلة )أي البدء في القراءة و الكتابة في الملف(‪.‬‬
‫• إمّا ألّا تنجح لأن الملف ليس موجودا ً أو أنه مستخدم من طرف برنامج آخر‪ .‬في هذه الحالة‪ ،‬سنتوقف عن‬

‫العمل على الملف‪.‬‬

‫مباشرة بعد فتح الملف‪ ،‬يجب التأكد ما إن تمت العملية بنجاح‪ ،‬أم لا‪ .‬هذا أمر بسيط ‪ :‬إذا كانت قيمة‬
‫المؤشر تساوي ‪ ، NULL‬فإن الفتح قد فشل‪ .‬إن كانت قيمته تساوي شيئا غير ‪ ، NULL‬فقد تم الفتح بنجاح‪.‬‬
‫سنتبع إذن هذا المخطط التالي ‪:‬‬

‫‪208‬‬

‫فتح و غلق ملف‬

‫‪1‬‬ ‫)][‪int main(int argc, char *argv‬‬
‫‪2‬‬
‫{‬

‫;‪3 FILE* file = NULL‬‬
‫;)”‪4 file = fopen(”test.txt”, ”r+‬‬

‫)‪5 if (file != NULL‬‬

‫{‪6‬‬

‫‪7 // We can read or write in the file‬‬

‫}‪8‬‬

‫‪9 else‬‬

‫{ ‪10‬‬

‫‪11 // We display an error message if we want‬‬

‫;)”‪12 printf(”Can’t open the file test.txt‬‬

‫} ‪13‬‬

‫;‪14 return 0‬‬

‫} ‪15‬‬

‫إفعل هذا دائما عند فتح أي ملف‪ .‬إن لم تفعل و الملف غير موجود‪ ،‬فأنت تخاطر بتوق ّف البرنامج بعدها‪.‬‬

‫‪ : fclose‬غلق الملف‬

‫إذا نجحت عملية فتح الملف‪ ،‬يمكننا القراءة و الكتابة فيه )سنرى كيف نفعل هذا لاحقا ً(‪.‬‬
‫ما إن نكمل العمل على الملف‪ ،‬يجب علينا ”غلقه”‪ .‬نستعمل من أحل هذا الدالة ‪ fclose‬التي تقوم بتحرير‬

‫الذاكرة‪ .‬يعني أن ّه سيتم حذف الملف المحمّل في الذاكرة العشوائية‪.‬‬

‫نموذج الدالة ‪:‬‬

‫;)‪1 int fclose(FILE* pointerOnFile‬‬

‫هذه الدالة تأخذ معاملا واحدا ‪ :‬المؤشر نحو الملف‪.‬‬
‫تقوم بإرجاع ‪ ، int‬و الذي يأخذ القيم ‪:‬‬

‫• ‪ : 0‬إذا نجح غلق الملف‪.‬‬
‫• ‪ : EOF‬إذا فشل الغلق‪ EOF .‬هي عبارة عن ‪ #define‬موجودة في ‪ stdio.h‬و هي توافق‬
‫عددا ً خاصا ً‪ ،‬ي ُستعمل للقول أنه حصل خطأ‪ ،‬أو أننا وصلنا إلى نهاية الملف‪ .‬في حالتنا هذه‪ ،‬هذا يعني‬

‫حدوث خطأ‪.‬‬

‫في غالب الأحيان‪ ،‬تنجح عملية غلق الملف ‪ :‬هذا ما يدفعني إلى عدم اختبار إن كانت ‪ fclose‬قد عملت‪.‬‬
‫رغم هذا‪ ،‬يمكنك فعل ذلك إن أردت‪.‬‬

‫لإغلاق الملف‪ ،‬نكتب إذن ‪:‬‬

‫‪209‬‬

‫الفصل ب‪ .7.‬قراءة و كتابة الملفات‬

‫;)‪1 fclose(file‬‬

‫في النهاية‪ ،‬المخ ّطط الذي نت ّبعه لفتح و غلق ملف سيكون كالتالي ‪:‬‬
‫)][‪1 int main(int argc, char *argv‬‬
‫{‪2‬‬
‫;‪3 FILE* file = NULL‬‬
‫;)”‪4 file = fopen(”test.txt”, ”r+‬‬
‫)‪5 if (file != NULL‬‬
‫{‪6‬‬
‫‪7 // We read and we write in the file‬‬
‫‪8 // ...‬‬
‫‪9 fclose(file); // We close the opened file‬‬
‫} ‪10‬‬
‫;‪11 return 0‬‬
‫} ‪12‬‬

‫لم استعمل ‪ else‬لأظهر رسالة خطأ في حال لم ينجح الفتح‪ ،‬يمكنك فعل ذلك إن أردت‪.‬‬

‫يجب دائما التفكير في غلق الملف الذي فتحته بمجر ّد الإنتهاء من العمل عليه‪ .‬هذا سيسمح بتحرير الذاكرة‪.‬‬
‫إن نسيت تحرير الذاكرة‪ ،‬قد يأخذ برنامجك حجما كبيرا ً من الذاكرة بدون أن يستخدمه‪ .‬في مثال صغير كهذا‬

‫الأمر غير خطير‪ ،‬لـكن مع برنامج كبير‪ ،‬مرحبا ًبالمشاكل !‬

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

‫غالب الأحيان‪ ،‬يكون السبب واحدا أو إثنين من الأمور ”الثانو ية” مثل نسيان ‪. fclose‬‬

‫ب‪ 2.7.‬طرق مختلفة للقراءة و الكتابة في الملفات‬

‫و الآن مادمنا تعل ّمنا كيف نفتح و نغلق ملفا‪ ،‬لم يبق سوى أن نضيف الشفرة ال ّتي تقوم بالقراءة و الكتابة‬
‫عليه‪.‬‬

‫سنبدأ برؤ ية كيفي ّة الكتابة في مل ّف )الأمر الأبسط قليلا(‪ ،‬ثم ّ نمر ّ يعدها إلى كيفي ّة القراءة من مل ّف‪.‬‬

‫الكتابة في ملف‬

‫توجد الـكثير من الدوال التي تسمح بالكتابة في ملف‪ .‬يبقى عليك أن تختار أيها الأنسب لك لتستخدمها‪ .‬هذه‬
‫الثلاث دوال ال ّتي سنتعل ّمها ‪:‬‬

‫• ‪ : fputc‬تكتب حرفا في المل ّف )حرف واحد في المرة(‪.‬‬

‫• ‪ : fputs‬تكتب سلسلة محرفي ّة في الملف‪.‬‬

‫• ‪ : fprintf‬تكتب سلسلة ”من ّسقة ً” في الملف‪ ،‬طر يقة عملها مطابقة تقريبا للدالة ‪. printf‬‬

‫‪210‬‬

‫طرق مختلفة للقراءة و الكتابة في الملفات‬

‫‪fputc‬‬

‫هذه الدالة تكتب حرفا واحدا في المر ّة في الملف‪ .‬نموذجها ‪:‬‬
‫;)‪1 int fputc(int character, FILE* pointerOnFile‬‬

‫و هي تأخذ معاملين ‪:‬‬

‫• المحرف الذي يجب كتابته )من نوع ‪ ، int‬مثلما قلت فاستعماله يعود تقريبا ً إلى استعمال ‪ ، char‬إلا‬
‫أن عدد المحارف الممكن استعمالها هنا أكبر(‪ .‬يمكنك إذن أن تكتب مباشرة ’‪ ’A‬كمثال‪.‬‬

‫• المؤش ّر نحو الملف الذي نريد أن نكتب فيه‪ .‬في مثالنا‪ ،‬المؤشّر اسمه ‪ . file‬استعمال المؤش ّر في ك ّل مرة‬
‫يساعدنا لأنه بإمكاننا أن نفتح العديد من الملفات في آن واحد‪ ،‬و نقرأ و نكتب في ك ّل واحد من هذه‬

‫المل ّفات‪ .‬لست مح ّددا بفتح مل ّف واحد في المر ّة‪.‬‬

‫الدالة تقوم بإرجاع ‪ ، int‬و هو رمز الخطأ‪ .‬هذا الـ ‪ int‬يساوي ‪ EOF‬إذا فشلت الكتابة‪ ،‬و إلّا فسيأخذ‬
‫قيمة أخرى‪.‬‬

‫بما أ ّن المل ّف قد تم ّ فتحه بنجاح‪ ،‬فليس من عادتي إختبار إن كانت ك ّل واحدة من ‪ fputc‬قد نجحت‪ ،‬ولـكن‬
‫يمكنك فعل ذلك إن أردت‪.‬‬

‫الشفرة التالية تسمح بكتابة الحرف ’‪ ’A‬في الملف ‪) test.txt‬إذا كان موجودا ً من قبل فإنه سيتم‬

‫استبداله‪ ،‬أمّا إن لم يكن موجودا ً سيتم إنشاؤه(‪ .‬الشفرة تحتوي كل الخطوات التي تكلّمنا عنها سابقا ً ‪ :‬فتح‬
‫الملف‪ ،‬اختبار الفتح‪ ،‬الكتابة و الغلق ‪:‬‬

‫)][‪1 int main(int argc, char *argv‬‬
‫{‪2‬‬
‫;‪3 FILE* file = NULL‬‬
‫;)”‪4 file = fopen(”test.txt”, ”w‬‬
‫)‪5 if (file != NULL‬‬
‫{‪6‬‬
‫‪7 fputc(’A’, file); // Write the character A‬‬
‫;)‪8 fclose(file‬‬
‫}‪9‬‬
‫;‪10 return 0‬‬
‫} ‪11‬‬

‫افتح بنفسك الملف ‪ . test.txt‬ماذا ترى ؟‬
‫إن هذا سحر ّي‪ ،‬الملف يحتوي الآن على الحرف ’‪ ’A‬كما ترى في الشكل التالي ‪:‬‬

‫‪211‬‬

‫الفصل ب‪ .7.‬قراءة و كتابة الملفات‬

‫‪fputs‬‬

‫هذه الدالة شبيهة جدا ًبالدالة ‪ ، fputc‬إلا أنها تسمح بكتابة سلسلة محرفي ّة كاملة‪ ،‬و هذا عادة أحسن من الكتابة‬

‫حرفا ًحرفا ً‪.‬‬
‫لـكن ‪ fputc‬تبقى ضرور ي ّة حينما نحتاج إلى الكتابة محرفا بمحرف‪ ،‬و هذا يحدث كثيرا‪.‬‬

‫نموذج الدالة ‪:‬‬

‫;)‪1 char* fputs(const char* string, FILE* pointerOnFile‬‬

‫المعاملان سهلا الفهم ‪:‬‬

‫• ‪ : string‬السلسلة ال ّتي نريد كتابتها‪ .‬تلاحظ أن النوع هنا هو *‪ : const char‬إضافة الكلمة‬
‫‪ const‬في النموذج تشير إلى أن السلسة ال ّتي سنعطيها للدالة ت ُفترض ثابتة‪ .‬أي أ ّن الدالة لن تقوم بتغييرها‪.‬‬
‫هذا أمر منطقي عندما نفكّر فيه ‪ fputs :‬يجب أن تقرأ السلسلة بدون تعديلها‪ .‬هذه إذن معلومة لـكم‬

‫)و حماية( أ ّن سلسلتكم لن يتم ّ إدخال أي ّة تعديلات عليها‪.‬‬

‫• ‪ : pointerOnFile‬مثل ‪ fputc ،‬تحتاج هذه الدالة إلى مؤشر من نوع *‪ FILE‬نحو الملف ال ّذي‬
‫فتحته‪.‬‬

‫الدال ّة تعيد القيمة ‪ EOF ،‬في حالة وجود خطأ‪ ،‬و إلّا‪ ،‬فهذا يعني أ ّنها عملت على ما يرام‪ .‬و هنا أيضا‪ ،‬لن‬
‫أقوم عادة باختبار القيمة التي ترجعها الدالة‪.‬‬

‫فلنجرب كتابة سلسلة في ملف ‪:‬‬

‫‪212‬‬

‫طرق مختلفة للقراءة و الكتابة في الملفات‬

‫‪1‬‬ ‫)][‪int main(int argc, char *argv‬‬
‫‪2‬‬
‫{‬

‫;‪3 FILE* file = NULL‬‬
‫;)”‪4 file = fopen(”test.txt”, ”w‬‬

‫)‪5 if (file != NULL‬‬

‫{‪6‬‬

‫;)‪7 fputs(”Hello my friends\nHow are you ?”, file‬‬

‫;)‪8 fclose(file‬‬

‫}‪9‬‬

‫;‪10 return 0‬‬

‫} ‪11‬‬

‫الشكل التالي يظهر الملف بعد التعديل عليه من طرف البرنامج ‪:‬‬

‫‪fprintf‬‬

‫إليك نوعا ً آخرا ً من الدالة ‪ . printf‬هذه تستخدم للكتابة في ملف‪ .‬هذه الدالة تستعمل بنفس الطر يقة التي‬
‫نستعمل بها ‪ ، printf‬إلا أنه يجب إعطاؤها المؤشر نحو ‪ FILE‬كمعامل أ ّول‪.‬‬
‫الشفرة التالية تطلب من المستخدم إدخال عمره‪ ،‬ثم ّ تقوم بكتابته في الملف ‪:‬‬

‫)][‪1 int main(int argc, char *argv‬‬
‫{‪2‬‬
‫;‪3 FILE* file = NULL‬‬

‫‪213‬‬

‫‪4‬‬ ‫الفصل ب‪ .7.‬قراءة و كتابة الملفات‬
‫‪5‬‬
‫‪6‬‬ ‫;‪int age = 0‬‬
‫‪7‬‬ ‫;)”‪file = fopen(”test.txt”, ”w‬‬
‫‪8‬‬ ‫)‪if (file != NULL‬‬
‫‪9‬‬ ‫{‬
‫‪10‬‬
‫‪11‬‬ ‫‪// Request the age‬‬
‫‪12‬‬ ‫;)” ? ‪printf(”How old are you‬‬
‫;)‪scanf(”%d”, &age‬‬
‫‪13‬‬ ‫‪// Write the age on the file‬‬
‫‪14‬‬ ‫‪fprintf(file , ”The mister who uses the PC has %d‬‬
‫‪15‬‬
‫} ‪16‬‬ ‫;)‪years”, age‬‬
‫;)‪fclose(file‬‬
‫}‬
‫;‪return 0‬‬

‫يمكنك إذا إعادة استعمال ما تعرفه عن ‪ printf‬للكتابة في ملف ! لهذا السبب أنا غالبا ما استعمل‬
‫‪ fprintf‬للكتابة في المل ّفات‪.‬‬

‫القراءة من ملف‬

‫لدينا أيضا ًثلاث دوال للقراءة من ملف‪ ،‬اسمها مختلف قليلا ًفقط عن دوال الكتابة ‪:‬‬

‫• ‪ : fgetc‬قراءة محرف‪.‬‬
‫• ‪ : fgets‬قراءة سلسلة محرفي ّة‪.‬‬
‫• ‪ : fscanf‬قراءة سلسلة من ّسقة‪.‬‬

‫سأسرع قليلا ًفي شرح هذه الدوال ‪ :‬إذا كنت قد فهمت ما كتبته من قبل‪ ،‬فلن تجد أي صعوبة مع هذه‬
‫الدوال‪.‬‬

‫‪fgetc‬‬

‫أولا ً‪ ،‬النموذج ‪:‬‬

‫;)‪1 int fgetc(FILE* pointerOnFile‬‬
‫‪214‬‬

‫طرق مختلفة للقراءة و الكتابة في الملفات‬

‫هذه الدالة تقوم بإرجاع ‪ : int‬إنه المحرف الذي تم ّت قراءته‪ .‬إذا لم تقرأ أ ّي محرف‪ ،‬فستعيد القيمة‬
‫‪. EOF‬‬

‫لـكن كيف لنا أن نعرف المحرف الذي نقرؤه ؟ ماذا لو أردنا قراءة المحرف الثالث و أيضا العاشر‪ ،‬كيف‬
‫نفعل هذا ؟‬

‫في الواقع‪ ،‬في ك ّل مرة تقرأ فيها مل ّفا‪ ،‬فهناك ”مؤشر” )‪) (cursor‬مثل المؤش ّر الذي يغمز في محرر النصوص(‬

‫يتحر ّك في ك ّل مرة‪ .‬و هذا المؤشّر افتراضي طبعا ً‪ ،‬لن تتمكن من رؤيته على الشاشة‪ .‬و هو يشير إلى أين وصلنا في‬
‫قراءة الملف‪.‬‬

‫سنتعلم لاحقا ًكيف نعرف الوضعية التي وصل إليها المؤشّر بالضبط و أيضا كيف نحر ّكه من مكانه )و ذلك‬
‫لـكي نقوم بتحر يكه إلى بداية الملف مثلا‪ ،‬أو إلى مكان محرف مح ّدد‪ ،‬كالمحرف العاشر(‪.‬‬

‫‪ fgetc‬تقوم بتحر يك المؤشر بمحرف واحد في ك ّل مرة تقرؤ فيها واحدا‪ .‬أي أنك إن استدعيت ‪fgetc‬‬

‫مرة ثانية‪ ،‬فستقرأ المحرف الثاني‪ ،‬ثم الثالث و هكذا‪ .‬و بهذا يمكنك استعمال حلقة تكرار ية لقراءة محارف الملف‬
‫واحدا واحدا‪.‬‬

‫سنقوم بكتابة شفرة تقرأ ك ّل محارف الملف واحدا واحدا و في ك ّل م ّرة تكتبها على الشاشة‪ .‬الحلقة ستتوقف‬
‫حينما تعيد ‪ fgetc‬القيمة ‪) EOF‬و ال ّتي تعني ”‪ ”End Of File‬أي ”نهاية الملف”(‪.‬‬

‫‪1‬‬ ‫)][‪int main(int argc, char *argv‬‬
‫‪2‬‬
‫{‬

‫‪3‬‬ ‫;‪FILE* file = NULL‬‬
‫‪4‬‬
‫;‪int currentCharacter = 0‬‬

‫;)”‪5 file = fopen(”test.txt”, ”r‬‬

‫)‪6 if (file != NULL‬‬

‫{‪7‬‬

‫‪8 // A loop to read the characters one by one‬‬

‫‪9 do‬‬

‫{ ‪10‬‬

‫‪11 currentCharacter = fgetc(file); // Read the‬‬

‫‪character‬‬

‫‪12 printf(”%c”, currentCharacter); // Display it‬‬
‫‪13 } while (currentCharacter != EOF); // Continue while‬‬

‫‪14‬‬ ‫)‪fgets didn’t return EOF (End Of File‬‬
‫‪15‬‬ ‫;)‪fclose(file‬‬
‫‪16‬‬ ‫}‬
‫} ‪17‬‬ ‫;‪return 0‬‬

‫الـكونسول ستقوم بإظهار محتوى الملف كاملا ً‪ ،‬مثلا ‪:‬‬

‫! ‪Hello, I’m the content of the file test.txt‬‬

‫‪215‬‬

‫الفصل ب‪ .7.‬قراءة و كتابة الملفات‬

‫‪fgets‬‬

‫هذه الدالة تقوم بقراءة سلسلة من ملف‪ .‬هذا يجنبك قراءة ك ّل محارف الملف واحدا واحدا‪ .‬الدالة تقرأ على‬
‫الأكثر سطرا ً واحدا ً )ٺتوقف عند ملاقاة أول ‪ ،( \n‬إن أردت قراءة العديد من الأسطر‪ ،‬فعليك استعمال‬

‫حلقة‪.‬‬

‫هذا نموذج الدالة ‪:‬‬

‫;)‪1 char* fgets(char* string, int nbOfCharsToRead, FILE* pointerOnFile‬‬

‫هذه الدالة ٺتطلب معاملا خا ّصا نوعا ًما‪ ،‬و لـكن ّه سيكون عملي ّا ج ّدا ‪ :‬عدد المحارف التي نريد قراءتها‪ .‬هذا‬
‫ما يطلب من الدالة ‪ fgets‬التوقف عن قراءة السطر إذا كان يحوي أكثر من ‪ X‬من المحارف‪ .‬الفائدة ‪ :‬هذا‬
‫يسمح لنا بضمان عدم حدوث تجاوز في الذاكرة ! في الواقع‪ ،‬إذا كان حجم السطر أكبر من أن تسعه السلسلة‬
‫المحرفي ّة‪ ،‬فمن الممكن أن تقرأ عددا من المحارف أكثر مم ّا يسمح به المكان المتوف ّر‪ ،‬و هذا قد يسبب تع ّطل البرنامج‪.‬‬

‫سنتعلّم كيف نقرأ سطرا ً واحدا ً باستخدام ‪) ، fgets‬ثم بعدها سنرى كيفية قراءة ملف كامل(‪.‬‬

‫لهذا فسنقوم بتعر يف سلسلة محرفي ّة كبيرة كفاية لتخزين السطر المراد قراءته )على الأقل نتمن ّى ذلك‪ ،‬لا‬
‫يمكننا أن نكون متأكّدين ‪ .(100%‬سترى فائدة استخدام الـ ‪ #define‬في تعر يف حجم جدول ‪:‬‬

‫‪1 #define MAX_SIZE 1000 // A table of size 1000‬‬

‫‪2‬‬ ‫)][‪int main(int argc, char *argv‬‬
‫‪3‬‬
‫{‬

‫‪4‬‬ ‫;‪FILE* file = NULL‬‬
‫‪5‬‬
‫‪char string[MAX_SIZE] = ””; // Empty string of size MAX_SIZE‬‬

‫;)”‪6 file = fopen(”test.txt”, ”r‬‬

‫)‪7 if (file != NULL‬‬

‫{‪8‬‬

‫‪9 fgets(string, MAX_SIZE, file); // Read at maximum MAX_SIZE‬‬

‫‪10‬‬ ‫”‪characters from the file, store them in ”string‬‬
‫‪11‬‬ ‫‪printf(”%s”, string); // Display the string‬‬
‫‪12‬‬ ‫;)‪fclose(file‬‬
‫‪13‬‬ ‫}‬
‫} ‪14‬‬ ‫;‪return 0‬‬

‫النتيجة هي نفسها النتيجة السابقة‪ ،‬مع العلم أ ّن المحتوى ي ُكتب في الـكونسول ‪:‬‬

‫! ‪Hello, I’m the content of the file test.txt‬‬

‫الفرق هو أننا هنا لم نستعمل حلقة تكرار ية‪ .‬نقوم بإسترجاع محتوى الملف كاملا في م ّرة‪.‬‬

‫أنت تلاحظ بك ّل تأكيد الآن فائدة استعمال ‪ #define‬في شفرتك لتعر يف الحجم الأقصى لجدول مثلا ً‪.‬‬
‫في الواقع‪ MAX_SIZE ،‬مستعمل في مكانين مختلفين في الشفرة ‪:‬‬

‫‪216‬‬

‫طرق مختلفة للقراءة و الكتابة في الملفات‬

‫• المرة الأولى لتعر يف حجم الجدول الذي نريد إنشاءه‪.‬‬
‫• م ّرة اخرى في الـ ‪ fgets‬لنقوم بتحديد عدد المحارف التي نقرؤها‪.‬‬

‫الفائدة هنا‪ ،‬هي أن ّه في حال ما وجدت أن السلسلة المحرفي ّة غير كبيرة كفاية لقراءة الملف‪ ،‬فلن يكون عليك‬

‫سوى تعديل سطر الـ ‪ #define‬و إعادة الترجمة‪ .‬هذا سيجن ّبك البحث عن ك ّل مكان من الشفرة وضعت فيه‬
‫حجم الجدول‪ .‬المعالج القبلي سيقوم باستبدال كل تكرار لـ ‪ MAX_SIZE‬بالقيمة الجديدة‪.‬‬

‫كما قلت فإن ‪ fgets‬تقرأ على الأكثر سطرا ً واحدا في المر ّة‪ .‬ٺتوقف عن قراءة السطر عندما تتجاوز عدد‬
‫المحارف الذي سمحت لها بقراءتها‪.‬‬

‫نعم و لـكن ‪ :‬حالي ّا‪ ،‬نحن لا نجيد سوى قراءة سطر واحد باستخدام ‪ . fgets‬كيف لنا أن نقرأ كل‬
‫الملف ؟ الجواب بسيط ‪ :‬بحلقة تكرار ية !‬

‫الدالة ‪ fgets‬تعيد ‪ NULL‬في حالة لم تستطع قراءة ما طلبته منها‪.‬‬
‫أي أن الحلقة يجب أن تنتهي بمجر ّد أن تعيد ‪ fgets‬القيمة ‪. NULL‬‬

‫ليس علينا سوى استعمال الحلقة ‪ while‬لـكي نقوم بالتكرار ما دامت ‪ fgets‬لم ترجع ‪: NULL‬‬

‫‪1 #define MAX_SIZE 1000‬‬
‫)][‪2 int main(int argc, char *argv‬‬
‫{‪3‬‬
‫;‪4 FILE* file = NULL‬‬
‫;”” = ]‪5 char string[MAX_SIZE‬‬
‫;)”‪6 file = fopen(”test.txt”, ”r‬‬
‫)‪7 if (file != NULL‬‬
‫{‪8‬‬
‫‪9 while (fgets(string, MAX_SIZE, file) != NULL) // Read the‬‬

‫)‪file while there’s no error (NULL‬‬

‫{ ‪10‬‬
‫‪11 printf(”%s”, string); // Display the string that we’ve‬‬

‫‪12‬‬ ‫‪read‬‬
‫‪13‬‬ ‫}‬
‫‪14‬‬ ‫;)‪fclose(file‬‬
‫‪15‬‬ ‫}‬
‫} ‪16‬‬ ‫;‪return 0‬‬

‫هذه الشفرة تقوم بقراءة الملف سطرا ً سطرا ً و إظهار الأسطر‪.‬‬

‫السطر الأكثر لفتا ًللانتباه في الشفرة هو ‪:‬‬
‫)‪1 while (fgets(string, MAX_SIZE, file) != NULL‬‬

‫سطر الـ ‪ while‬يقوم بأمرين ‪ :‬قراءة سطر من الملف و التأكد أن ‪ fgets‬لم ت ُع ِد ‪ . NULL‬يمكن ترجمة‬
‫هذا كالتالي ‪” :‬إقرأ سطرا ً جديدا ً ما دمنا لم نصل إلى نهاية الملف”‪.‬‬

‫‪217‬‬

‫الفصل ب‪ .7.‬قراءة و كتابة الملفات‬

‫‪fscanf‬‬

‫مبدأ هذه الدالة مشابه تماما ًلمبدأ نظيرتها ‪ ، scanf‬هنا أيضا‪.‬‬
‫هذه الدال ّة تقوم بقراءة مل ّف تمت كتابته بشكل مح ّدد‪.‬‬

‫لنفترض أن الملف يحتوي على ثلاثة أعداد مفصولة بفراغ‪ ،‬و هي مثلا أكبر ثلاثة نقاط تم التحصل عليها في‬
‫لعبتك ‪. 15 20 30 :‬‬

‫أنت تريد أن تسترجع ك ّل واحد من هذه الأعداد في متغير من نوع ‪. int‬‬
‫الدالة ‪ fscanf‬ستسمح لك بالقيام بهذا بشكل سر يع‪.‬‬

‫‪1‬‬ ‫)][‪int main(int argc, char *argv‬‬
‫‪2‬‬
‫{‬

‫‪3‬‬ ‫;‪FILE* file = NULL‬‬
‫‪4‬‬
‫‪int score[3] = {0}; // Table of the 3 best scores‬‬

‫;)”‪5 file = fopen(”test.txt”, ”r‬‬

‫)‪6 if (file != NULL‬‬

‫{‪7‬‬

‫;)]‪8 fscanf(file, ”%d %d %d”, &score[0], &score[1],&score[2‬‬

‫‪9 printf(”The best scores are : %d, %d and %d”, score[0], score[1],‬‬

‫;)]‪score[2‬‬

‫;)‪10 fclose(file‬‬
‫} ‪11‬‬
‫;‪12 return 0‬‬
‫} ‪13‬‬

‫‪The best scores are : 15, 20 and 30‬‬

‫كما ترى‪ ،‬فالدالة ‪ fscanf‬تنتظر ثلاث أعداد مفصولة بفراغ ) ”‪ .( ”%d %d %d‬ستقوم بتخزينهم في‬
‫جدولنا ذو الخانات الثلاث‪.‬‬

‫نقوم لاحقا ًبإظهار ك ّل القيم المسترجعة‪.‬‬

‫حت ّى الآن‪ ،‬لم استعمل سوى رمز ‪ %d‬واحد في الدالة ‪. scanf‬‬
‫اليوم اكتشف َت بأنه بإمكانك أن تستعمل العديد منها‪ .‬إذا كان الملف مكتوبا بطر يقة مح ّددة جي ّدا‪ ،‬فهذا‬

‫يسمح لك بالإسراع لاسترجاع ك ّل واحدة من هذه القيم‪.‬‬

‫ب‪ 3.7.‬التحرك داخل ملف‬

‫كنت قد كلّمتك عن وجود ”مؤش ّر” افتراضي )‪ (virtual cursor‬قبل قليل‪ .‬سنقوم الآن بدراسته بشكل‬
‫أكثر تفصيلا ً‪.‬‬

‫‪218‬‬

‫التحرك داخل ملف‬

‫في ك ّل مرة تفتح فيها ملفا‪ ،‬فهناك مؤشّر يشير إلى وضعيتك في الملف‪ .‬و لتتخي ّله تماما مثل مؤشر محرر‬
‫النصوص‪ .‬يد ّل على الكان ال ّذي أنت فيه من الملف‪ ،‬أي أين ستقوم بالكتابة‪.‬‬

‫كتلخيص‪ ،‬نظام المؤشر يسمح لك بالكتابة و القراءة في وضعية محددة من الملف‪.‬‬
‫توجد ثلاث دوال لتتعرف عليها ‪:‬‬

‫• ‪ : ftell‬تدل ّنا على الوضعية التي نحن بها حاليا ًفي الملف‪.‬‬
‫• ‪ : fseek‬ت ُموضع المؤش ّر في مكان محدد‪.‬‬

‫• ‪ : rewind‬تقوم بإرجاع المؤش ّر إلى بداية الملف )هذا مكافئ للطلب من الدالة ‪ fseek‬أن تموضع‬
‫المؤشّر في البداية(‪.‬‬

‫‪ : ftell‬الموضع في الملف‬

‫هذه الدالة بسيطة الاستعمال ج ّدا‪ .‬تعيد الموضع الذي يتواجد به المؤشّر حاليا بنوع ‪: long‬‬
‫;)‪1 long ftell(FILE* pointerOnFile‬‬

‫العدد ال ّذي يتم ارجاعه يد ّل على موضع المؤشر في الملف‪.‬‬

‫‪ : fseek‬التموضع داخل الملف‬

‫نموذج ‪ fseek‬هو التالي ‪:‬‬
‫;)‪1 int fseek(FILE* pointerOnFile, long deplacement, int origin‬‬

‫الدال ّة ‪ fseek‬تسمح بتحر يك المؤش ّر بـعدد من المحارف )يد ّل عليها ‪ ( deplacement‬إنطلاقا من الموضع‬
‫ال ّذي يد ّل عليه ‪. origin‬‬

‫• العدد ‪ deplacement‬يمكن له أن يكون عددا ً موجبا ً)للتقدم إلى الأمام(‪ ،‬معدوما )= ‪ (0‬أو سالبا ً‬
‫)للرجوع إلى الخلف(‪.‬‬

‫• أمّا بالنسبة للعدد ‪ origin‬فهو يأخذ إحدى القيم التالية ‪:‬‬
‫– ‪ SEEK_SET‬تعني بداية الملف‪.‬‬

‫– ‪ SEEK_CUR‬تعني الموضع الحالي نفسه‪.‬‬
‫– ‪ SEEK_END‬تعني نهاية الملف‪.‬‬

‫‪219‬‬

‫الفصل ب‪ .7.‬قراءة و كتابة الملفات‬

‫إليك بعض الأمثلة لـكي تفهم جي ّدا كيف ٺتلاعب بـ ‪ deplacement‬و ‪: origin‬‬

‫• هذه الشفرة تضع المؤشر محرفين بعد بداية الملف ‪:‬‬

‫;)‪1 fseek(file, 2, SEEK_SET‬‬

‫• هذه الشفرة تضع المؤشّر أربع محارف قبل الوضعية الحالية ‪:‬‬
‫;)‪1 fseek(file, −4, SEEK_CUR‬‬

‫لاحظ أن قيمة ‪ deplacement‬سالبة لأننا نتحر ّك إلى الوراء‪.‬‬

‫• الشفرة التالية تضع المؤشّر في نهاية الملف ‪:‬‬

‫;)‪1 fseek(file, 0, SEEK_END‬‬

‫إذا كتبت‪ ،‬بعد القيام بـ ‪ fseek‬تحر ّكك إلى نهاية الملف‪ ،‬فذلك سيضيف معلومات إلى نهاية الملف‬
‫)الملف سيتم ّ إكماله(‪.‬‬

‫بالمقابل‪ ،‬إذا وضعت المؤشّر في بداية الملف وكتبت‪ ،‬فهذا سيستبدل الن ّص الموجود هناك‪ .‬لا توجد طر يقة‬
‫لـ”إدراج” نص في ملف‪ .‬إلا إن قمت بنفسك ببرمجة دالة تقرأ المحارف لتتذكّرها قبل إستبدالها !‬

‫لـكن كيف لي أن أعرف أ ّي موضع يجب أن أذهب إليه للقراءة و الكتابة في الملف ؟‬

‫هذا يعود إليك‪ .‬إن كان ملفا ًقمت أنت بكتابته‪ ،‬فأنت تعرف كيف تم ّ بناءه‪ .‬أنت تعرف أين تذهب للبحث‬
‫عن المعلومة ‪ :‬مثلا‪ ،‬أحسن النتائج المسجلة في اللعبة في الموضع ‪ ،0‬أسماء آخر اللاعبين في الموضع ‪ ،50‬الخ‪.‬‬

‫سنقوم بعمل تطبيقي لاحقا ً حيث ستفهم‪ ،‬إذا لم تكن قد فهمت بالفعل الآن‪ ،‬كيف نذهب للبحث عن‬
‫معلومة تهمّنا‪ .‬لا تنس بأن ّك أنت من يعر ّف كيفي ّة بناءه‪ .‬إذن عليك أن تقول ‪” :‬أضع نتيجة أحسن لاعب في‬

‫السطر الأ ّول‪ ،‬الخاصة بثاني أحسن لاعب في السطر الثاني‪ ،‬إلخ‪”.‬‬

‫الدالة ‪ fseek‬قد ٺتعامل بشكل غريب مع الملفات المفتوحة بوضع النص )‪ .(Text mode‬عادة‪ ،‬نحن‬
‫نستعملها أكثر مع الملفات المفتوحة بالوضع الثنائي )‪ .(Binary mode‬عند القراءة و الكتابة في ملف‬
‫بوضع الن ّص‪ ،‬فإنّنا عادة ما نفعل ذلك محرفا ً محرفا ً‪ .‬الشيء الوحيد الذي نسمح به غالبا في وضع الن ّص‬

‫مع ‪ fseek‬هو العودة إلى البداية أو التموضع في نهاية الملف فقط‪.‬‬

‫‪ : rewind‬الرجوع إلى البداية‬

‫هذه الدالة مكافئة لاستخدام ‪ fseek‬لإرجاعنا إلى الموضع ‪ 0‬في الملف ‪:‬‬
‫;)‪1 void rewind(FILE* pointerOnFile‬‬

‫طر يقة الاستعمال بسيطة كالنموذج‪ .‬أنت لست بحاجة إلى شرح إضافيّ‪.‬‬

‫‪220‬‬

‫إعادة تسميه و حذف ملف‬

‫ب‪ 4.7.‬إعادة تسميه و حذف ملف‬

‫ننهي هذا الفصل بن ُع ُومة عن طر يق دراسة دالتين بسيطتين للغاية ‪:‬‬

‫• ‪ : rename‬إعادة تسمية ملف‪.‬‬
‫• ‪ : remove‬حذف ملف‪.‬‬

‫الشيء الخا ّص في هاتين الدالتين هو أنهما لا تحتاجان مؤشرا ً نحو الملف لـكي تعملا‪ .‬يكفيهما فقط اسم الملف‬
‫المراد حذفه أو تغيير اسمه‪.‬‬

‫‪ : rename‬إعادة تسمية ملف‬

‫إليك نموذج هذه الدالة ‪:‬‬
‫;)‪1 int rename(const char* oldName, const char* newName‬‬

‫الدالة تعيد ‪ 0‬إذا نجحت في اعادة التسمية‪ ،‬و إلّا فستعيد قيمة مختلفة عن ‪ .0‬هل من اللازم أن أعطيك‬
‫مثالا ؟ إليك واحدا ‪:‬‬

‫)][‪1 int main(int argc, char *argv‬‬
‫{‪2‬‬
‫;)”‪3 rename(”test.txt”, ”test_rename.txt‬‬
‫;‪4 return 0‬‬
‫}‪5‬‬

‫‪ : remove‬حذف ملف‬

‫هذه الدالة تقوم بحذف ملف دون ترك أي أثر ‪:‬‬
‫;)‪1 int remove(const char* fileToDelete‬‬

‫كن حذرا جدا ً عند استعمالك لهذه الدالة ! هي تحذف الملف بدون أن تطلب منك أ ّي تأكيد ! الملف‬
‫لن يوضع في سلة المحذوفات‪ ،‬بل يسحذف حرفي ّا من القرص الصلب‪ .‬لن يمكنك استعادة مل ّف محذوف‬

‫بهذه الطر يقة )إلّا باستعمال أدوات خا ّصة باسترجاع المل ّفات‪ ،‬لـك ّن هذه العملية قد تكون طو يلة‪ ،‬مع ّقدة‬
‫و قد لا تنجح(‪.‬‬

‫هذه الدالة مناسبة لإنهاء الفصل‪ ،‬فلم أعد في حاجة إلى الملف ‪ ، test.txt‬يمكنني الآن حذفه ‪:‬‬

‫‪1‬‬ ‫)][‪int main(int argc, char *argv‬‬
‫‪2‬‬
‫{‬

‫;)”‪3 remove(”test.txt‬‬

‫;‪4 return 0‬‬

‫}‪5‬‬

‫‪221‬‬

‫الفصل ب‪ .7.‬قراءة و كتابة الملفات‬
‫‪222‬‬

‫الفصل ب‪8.‬‬

‫الحجز الح ّي للذاكرة )‪Dynamic memory‬‬
‫‪(allocation‬‬

‫كل المتغيّرات التي أنشأناها لحد الآن تم ّ إنشاؤها تلقائيّا من طرف المترجم الخا ّص بلغة ‪ .C‬لقد كانت الطر يقة‬
‫البسيطة‪ .‬رغم ذلك‪ ،‬توجد طر يقة يدو ية أكثر لإنشاء متغيّرات و نسمّيها بالحجز الح ّي )‪.(Dynamic allocation‬‬

‫من بين فوائد الحجز الح ّي هو السماح لبرنامج بحجز مكان لازم لتخزين جدول في الذاكرة لا ي ُعرف حجمه‬
‫قبل بداية الترجمة‪ .‬في الواقع‪ ،‬حت ّى الآن‪ ،‬كان حجم جداولنا ثابتا ًفي الشفرة المصدر ي ّة‪ .‬بعد قراءة هذا الفصل‪،‬‬

‫ستستطيع إنشاء جداول بطر يقة أكثر مرونة !‬
‫من الضروري أن ٺتقن التعامل مع المؤشرات لتتم ّكن من قراءة هذا الفصل ! إن كانت لديك بعض‬

‫الشكوك حول المؤشرات‪ ،‬أنصحك بالذهاب لإعادة قراءة الفصل الموافق قبل البدأ‪.‬‬
‫عندما نقوم بالتصريح عن متغيّر‪ ،‬فإننا نقول أننا طلبنا حجز مكان في الذاكرة ‪:‬‬

‫;‪1 int myNumber = 0‬‬

‫عندما يصل المترجم إلى سطر مشابه للسطر السابق‪ ،‬يقوم بالأمور التالية ‪:‬‬
‫• يقوم البرنامج بطلب إذن من نظام التشغيل )‪ (...Mac OS ،GNU/Linux ،Windows‬ليحجز شيئا من‬

‫الذاكرة‪.‬‬
‫• يستجيب نظام التشغيل بإعطاء البرنامج عنوان الخانة حيث يمكنه تخزين المتغيّر )يعطيه العنوان ال ّذي حجزه‬

‫له(‪.‬‬
‫• عندما تنتهي الدال ّة‪ ،‬المتغيّر يتم حذفه من الذاكرة‪ .‬برنامجك يقول لنظام التشغيل ‪” :‬أنا لم أعد بحاجة إلى‬
‫المكان في الذاكرة ال ّذي حجزته في ذلك العنوان‪ ،‬شكرا ! التاريخ لا يح ّدد إن كان البرنامج قد قال فعلا‬

‫”شكرا” لنظام التشغيل‪ ،‬لـك ّن هذا في مصلحته لأ ّن نظام التشغيل هو ال ّذي يتحكم في الذاكرة !‬

‫‪223‬‬

‫الفصل ب‪ .8.‬الحجز الح ّي للذاكرة )‪(Dynamic memory allocation‬‬

‫لحد الآن كل الأمور كانت تلقائيّة‪ .‬عندما نصرّح عن متغير فإن نظام التشغيل يتم ّ استدعاءه تلقائيا ً من‬
‫طرف البرنامج‪ .‬ما رأيك إذا بفعل هذا بطر يقة يدو ية ؟ ليس لأننا نريد أن نستمتع بفعل شيء مع ّقد‪ ،‬بل لأننا‬

‫أحيانا نظطر ّ لفعل ذلك !‬
‫في هذا الفصل سنقوم بـ ‪:‬‬

‫• دراسة كيف تعمل الذاكرة )نعم‪ ،‬م ّرة أخرى !( لنعرف ما الحجم الذي يحجزه كل متغيّر حسب نوعه‪.‬‬
‫• ثم ّ ندخل في موضوعنا الأساسي ‪ :‬سنرى كيف نطلب من نظام التشغيل يدويّا أن يحجز لنا مكانا في‬

‫الذاكرة‪ .‬هذا ما سنسميه الحجز الح ّي للذاكرة‪.‬‬

‫• و أخيرا ً‪ ،‬سنكتشف الفائدة من القيام بالحجز الح ّي بتعلّم إنشاء جدول ذو حجم غير معروف إلّا عند اشتغال‬
‫البرنامج‪.‬‬

‫ب‪ 1.8.‬حجم المتغيرات‬

‫بحسب نوع المتغير التي نريد إنشاءه ) ‪ (... float ، char ، int‬فنحن نحتاج إلى حجم معي ّن من‬
‫الذاكرة‪.‬‬

‫في الواقع‪ ،‬لتخزين عدد من ‪ −128‬إلى ‪ ( char ) 127‬لن نحتاج إلا إلى بايت واحد من الذاكرة‪ .‬هذا حجم‬
‫صغير للغاية‪.‬‬

‫بالمقابل‪ int ،‬يحجز عادة حوالي ‪ 4‬بايتات من الذاكرة‪ .‬بينما ‪ double‬يحجز ‪ 8‬بايتات‪.‬‬

‫المشكل هو ‪ ...‬أن هذا ليس دائما صحيحا‪ .‬هذا يعتمد على الأجهزة ‪ :‬فقد يكون ‪ int‬يحجز ‪ 8‬بايتات‪.‬‬
‫من يعلم ؟‬

‫هدفنا هنا أن نتعر ّف كم يحجز ك ّل نوع من حجم في الذاكرة على حاسوبك‪.‬‬

‫توجد وسيلة سهلة ج ّدا لمعرفة هذا ‪ :‬استعمال العامل )(‪. sizeof‬‬
‫على عكس الظاهر‪ ،‬فهو ليس دالة‪ ،‬بل عبارة عن إحدى الوظائف الأساسية من لغة الـ‪ ،C‬يجب عليك فقط أن‬

‫تضع بين القوسين النوع الذي تريد تحليله‪.‬‬
‫لمعرفة حجم ‪ ، int‬يجب كتابة التالي ‪:‬‬

‫)‪1 sizeof(int‬‬

‫عند الترجمة‪ ،‬سيتم استبدال هذه الشفرة بعدد ‪ :‬عدد البايتات ال ّتي يحجزها ‪ int‬في الذاكرة‪ .‬بالنسبة لي‪،‬‬
‫)‪ sizeof(int‬تساوي ‪ ،4‬و هذا يعني أ ّن ‪ int‬يأخذ ‪ 4‬بايتات‪ .‬بالنسبة لك‪ ،‬ستكون نفس القيمة على‬

‫الأرجح‪ ،‬لـكنّها ليست قاعدة‪ .‬جرّب لترى‪ ،‬بعرض القيمة عن طر يق ‪ printf‬مثلا ‪:‬‬

‫‪224‬‬

‫حجم المتغيرات‬

‫;))‪1 printf(”char : %d bytes\n”, sizeof(char‬‬
‫;))‪2 printf(”int : %d bytes\n”, sizeof(int‬‬
‫;))‪3 printf(”long : %d bytes\n”, sizeof(long‬‬
‫))‪4 printf(”double : %d bytes\n”, sizeof(double‬‬

‫بالنسبة لي ‪ ،‬هذا يظهر على الشاشة ‪:‬‬

‫‪1 char : 1 bytes‬‬
‫‪2 int : 4 bytes‬‬
‫‪3 long : 4 bytes‬‬
‫‪4 double : 8 bytes‬‬

‫لم أختبر كل الأنواع ال ّتي نعرفها‪ ،‬أتركك لتجر ّب أحجام الأنواع الأخرى‪.‬‬

‫أنت تلاحظ أن ‪ int‬و ‪ long‬يحجزان نفس الحجم من الذاكرة‪ .‬إنشاء ‪ long‬يعود تماما إلى إنشاء‬
‫‪ ، int‬هذا يأخذ ‪ 4‬بايتات من الذاكرة‪.‬‬

‫في الواقع‪ ،‬النوع ‪ long‬هو مكافئ لنوع نسميه ‪ ، long int‬و الذي هو مكافئ لنوع‪ int ...‬نفسه‪.‬‬
‫باختصار‪ ،‬فإن هذه أسماء كثيرة مختلفة لأجل أشياء ليست بالـكبيرة‪ ،‬في النهاية ! امتلاك أنواع مختلفة‬
‫كثيرة كان أمرا مهمّا في الوقت الذي لم ّ تكن الحواسيب تملك كثيرا من ذاكرة‪ .‬كنا نبحث دائما لاستخدام‬

‫الح ّد الأدنى من الذاكرة باستخدام النوع المناسب‪.‬‬
‫اليوم‪ ،‬هذا لم يعد مفيدا كثيرا لأ ّن ذاكرة الحاسوب صارت كبيرة ج ّدا‪ .‬بالمقابل‪ ،‬هذه الأنواع لا تزال‬
‫مفيدة إذا كنت تنشئ برامج للأنظمة المضمّنة )‪ (Embedded systems‬حيث الذاكرة المتوف ّرة أقل‪.‬‬

‫أظن مثلا في البرامج المو ّجهة للهواتف المحمولة‪ ،‬الألي ّات‪ ،‬إلخ‪.‬‬

‫هل بإمكاننا أن ن ُظهر حجم نوع مخ ّصص قمنا نحن بإنشائه )هيكل( ؟‬

‫نعم ! )(‪ sizeof‬تعمل مع الهياكل أيضا !‬

‫; ‪1 typedef struct Coordinates Coordinates‬‬

‫‪2 struct Coordinates‬‬

‫{‪3‬‬

‫;‪4 int x‬‬

‫;‪5 int y‬‬

‫;} ‪6‬‬

‫‪7‬‬ ‫)][‪int main(int argc, char *argv‬‬
‫‪8‬‬
‫{‬

‫;))‪9 printf(”Coordinates : %d bytes\n”, sizeof(Coordinates‬‬

‫;‪10 return 0‬‬

‫} ‪11‬‬

‫‪Coordinates : 8 bytes‬‬

‫‪225‬‬

‫الفصل ب‪ .8.‬الحجز الح ّي للذاكرة )‪(Dynamic memory allocation‬‬
‫كلما احتوى الهيكل من مركّبات كلّما أخذ حجما أكثر من الذاكرة‪ .‬الأمر منطقي تماما‪ ،‬أليس كذلك ؟‬

‫طر يقة أخرى للنظر إلى الذاكرة‬

‫لحد الآن‪ ،‬كل المخططات التي ق ّدمتها لك عن الذاكرة لم تكن دقيقة‪ .‬سنجعلها أخيرا دقيقة حقا و صحيحة بما‬
‫أننا تعل ّمنا الآن كم يأخذ كل نوع من حجم بالذاكرة‪.‬‬

‫إن صرّحنا عن متغير من نوع ‪: int‬‬

‫;‪1 int number = 18‬‬

‫و )‪ sizeof(int‬يعطينا ‪ 4‬بايت على حاسوبنا‪ ،‬هذا يعني أن المتغير يحجز ‪ 4‬بايت في الذاكرة !‬
‫لنفترض أن المتغير ‪ number‬محجوز بالعنوان ‪ 1600‬من الذاكرة‪ .‬سيكون لدينا إذا المخطط التالي للذاكرة ‪:‬‬

‫هنا‪ ،‬يمكننا فعلا ًأن نرى بأن المتغير ‪ number‬من النوع ‪ int‬يحجز ‪ 4‬بايت من الذاكرة‪ .‬فهو يبدأ من‬
‫العنوان ‪ 1600‬و ينتهي عند العنوان ‪ ،1603‬المتغير القادم لن يتم تخزينه إلا إبتداء ً من العنوان ‪! 1604‬‬

‫إن جربنا نفس الشيء مع ‪ ، char‬فالمتغير لن يأخذ سوى بايت واحد في الذاكرة )الشكل التالي( ‪:‬‬

‫‪226‬‬

‫حجم المتغيرات‬

‫تخي ّل الآن جدولا من ‪! int‬‬
‫كل ”خانة” من الجدول ستحجز ‪ 4‬بايت‪ .‬إن كان الجدول يحوي مثلا ً‪ 100‬خانة ‪:‬‬
‫;]‪1 int table[100‬‬

‫سنحجز إذن ‪ 100 ∗ 4 = 400‬بايت في الذاكرة‪.‬‬

‫ماذا لو كان الجدول فارغا ً‪ ،‬هل سيحجز بايت ؟‬

‫نعم بالطبع ! فالمكان في الذاكرة قد تم ّ حجزه‪ ،‬و لا يملك أي برنامج الح ّق في استخدام هذه الخانات )غير هذا‬
‫البرنامج(‪ .‬بمجر ّد التصريح عن متغيّر‪ ،‬سيأخذ مكانه مباشرة المكان في الذاكرة‪.‬‬

‫لاحظ لو أننا ننشئ جدولا من نوع ‪: Coordinates‬‬

‫;]‪1 Coordinates table[100‬‬

‫سيستخدم هذه المر ّة ‪ 8 ∗ 100 = 800‬بايت‪.‬‬
‫من المه ّم الفهم الجي ّد لهذه الحسابات البسيطة لنواصل بقي ّة الفصل‪.‬‬

‫‪227‬‬

‫الفصل ب‪ .8.‬الحجز الح ّي للذاكرة )‪(Dynamic memory allocation‬‬

‫ب‪ 2.8.‬الحجز الح ّي للذاكرة‬

‫فلندخل إلى صلب الموضوع‪ .‬سأذكّرك بهدفنا ‪ :‬تعلّم كيفي ّة طلب الذاكرة يدو يا ً‪.‬‬
‫سنحتاج إلى تضمين المكتبة ‪ . stdlib.h‬إن كنت قد اتّبعت نصائحي‪ ،‬فقد ضم ّنتها في ك ّل برامجك‪ .‬هذه‬

‫المكتبة تحتوي على دال ّتين سنحتاج إليهما ‪:‬‬
‫• ‪ ”Memory ALLOcation”) malloc‬بمعنى ”حجز الذاكرة”( ‪ :‬تطلب الإذن من نظام التشغيل لاستخدام‬

‫الذاكرة‪.‬‬
‫• ‪) free‬تحرير( ‪ :‬تسمح للإشارة لنظام التشغيل بأننا لم نعد بحاجة إلى الذاكرة ال ّتي طلبناها‪ .‬المكان في‬

‫الذاكرة تم ّ تحريره‪ ،‬يستطيع برنامج آخر الآن استخدامها عند الحاجة‪.‬‬

‫عندما تقوم بحجز يدوي للذاكرة‪ ،‬فعليك اتباع الخطوات التالية ‪:‬‬

‫‪ .1‬استدعاء ‪ malloc‬من أجل طلب الذاكرة‪.‬‬
‫‪ .2‬اختبار القيمة التي تم ارجاعها من طرف ‪ malloc‬لمعرفة ما إن نجح نظام التشغيل في حجز الذاكرة‪.‬‬
‫‪ .3‬ما إن ننتهي من استخدام الذاكرة‪ ،‬يجب علينا تحريرها باستعمال ‪ . free‬إن لم نفعل هذا‪ ،‬فسنتعر ّض‬
‫لتسريبات ذاكرة‪ ،‬أي أ ّن البرنامج يخاطر بحجز كثير من الذاكرة مع أن ّه ليس بحاجة إلى ك ّل هذا المكان‪.‬‬

‫هل تذكّرك هذه الخطوات الثلاث بفصل الملفات ؟ نعم يجب أن تفعل ! المبدأ واحد تماما ‪ :‬نحجز‪ ،‬نختبر‬
‫إن نجح الحجز‪ ،‬ثم ّ نحرر عندما ننتهي من الاستعمال‪.‬‬

‫‪ malloc‬لنطلب الإذن لحجز الذاكرة‬

‫نموذج الدالة ‪ malloc‬هزليّ ج ّدا‪ ،‬سترى ‪:‬‬
‫;)‪1 void* malloc(size_t numberOfNecessaryBytes‬‬

‫الدالة تأخذ معاملا واحدا ‪ :‬عدد البايتات ال ّتي يجب حجزها‪ .‬هكذا‪ ،‬يكفي أن كتابة )‪ sizeof(int‬لحجز‬
‫مكان من أجل تخزين ‪. int‬‬

‫و لـك ّن الشيء الذي يثير الفضول‪ ،‬هو القيمة التي ترجعها الدالة ‪ :‬إ ّنها تعيد ‪ ! void* ...‬إذا لازلت ٺتذكّر‬
‫فصل الدوال‪ ،‬كنت قد قلت لك بأن الكلمة ‪ void‬تعني ”الفراغ” و نستعملها لنشير إلى أن الدالة لا ت ُعيد أية‬

‫قيمة‪.‬‬

‫‪228‬‬

‫الحجز الح ّي للذاكرة‬

‫إذن هنا‪ ،‬لدينا دالة ت ُعيد ‪” ...‬مؤشّرا ً نحو فراغ” ؟ هذه نكتة جيدة !‬
‫يبدو أن هؤلاء المبرمجـين لديهم ح ّس فكاهي متطوّر‪.‬‬

‫كن متأكّدا‪ ،‬يوجد سبب‪ .‬في الحقيقة‪ ،‬هذه الدالة تعيد عنوان الخانة التي حجزها نظام التشغيل من أجل‬
‫متغيّرك‪ .‬إن استطاع النظام إيجاد مكان لك في العنوان ‪ ،1600‬فالدالة ستعيد مؤش ّرا يحوي العنوان ‪.1600‬‬

‫المشكل هو أن الدالة ‪ malloc‬لا تعرف نوع المتغير التي نريد إنشاءه‪ .‬في الواقع‪ ،‬أنت لا تعطيها سوى‬
‫معامل واحد ‪ :‬عدد البايتات في الذاكرة ال ّتي تحتاجها‪ .‬فإذا طلبت ‪ 4‬بايت‪ ،‬فهذا يمكن أن يعني ‪ int‬أو ربما‬

‫‪ long‬مثلا !‬

‫بما أ ّن ‪ malloc‬لا تعرف أ ّي نوع يجب عليها أن تعيد‪ ،‬فهي تعيد النوع *‪ . void‬سيكون مؤش ّرا نحو أ ّي‬
‫نوع كان‪ .‬يمكننا أن نقول أن ّه مؤشّر جامع‪.‬‬

‫لننتقل إلى التطبيق‪.‬‬
‫إذا كنت أريد الاستمتاع بإنشاء متغير من نوع ‪ int‬يدويّا في الذاكرة‪ ،‬يجب أن أشير للـ ‪ malloc‬أنني أحتاج‬

‫إلى )‪ sizeof(int‬بايت في الذاكرة‪.‬‬
‫أسترجع قيمة ‪ malloc‬في مؤشر على ‪: int‬‬

‫‪1‬‬ ‫‪int* allocatedMemory = NULL; // Create a pointer on int‬‬
‫‪2‬‬
‫‪allocatedMemory = malloc(sizeof(int)); // The function malloc puts‬‬

‫‪the allocated address in the pointer.‬‬

‫في نهاية هذه الشفرة‪ allocatedMemory ،‬هو مؤش ّر يحتوي على عنوان حجزه نظام التشغيل لك‪ ،‬لنقل‬
‫مثلا القيمة ‪ 1600‬للاكمال من مخططاتي السابقة‪.‬‬

‫اختبار المؤش ّر‬

‫الدالة ‪ malloc‬أعادت في المتغير ‪ allocatedMemory‬عنوان الخانة التي تم حجزها بالذاكرة‪ .‬هناك‬
‫احتمالان ‪:‬‬

‫• إذا نجح الحجز‪ ،‬فالمؤشّر سيحتوي عنوانا‪.‬‬
‫• إذا فشل الحجز‪ ،‬فالمؤش ّر سيحتوي العنوان ‪. NULL‬‬

‫إنه من النادر أن تفشل عملية حجز الذاكرة‪ ،‬لـكن هذا ممكن‪ .‬تخي ّل أنك تطلب حجز ‪ Go 34‬من الذاكرة‬
‫العشوائية‪ ،‬في هذه الحالة‪ ،‬ستفشل عملية الحجز على أغلب الظن‪.‬‬

‫من المستحسن دائما ًأن نختبر ما إن تمت العملية بنجاح‪ .‬سنفعل هذا ‪ :‬إن فشل الحجز‪ ،‬فهذا يعني أن المساحة‬
‫الحر ّة من الذاكرة العشوائية لم تكن كافية )هذه حالة حرجة(‪ .‬في حالة كهذه‪ ،‬يجب إيقاف البرنامج فورا لأن ّه‪،‬‬

‫على أية حال‪ ،‬لن يكون قادرا ً على الاستمرار بشكل عاد ّي‪.‬‬

‫‪229‬‬

‫الفصل ب‪ .8.‬الحجز الح ّي للذاكرة )‪(Dynamic memory allocation‬‬

‫سنستعمل دالة قياسي ّة لم يسبق لنا رؤيتها حت ّى الآن ‪ . exit() :‬هذه الأخيرة توقف البرنامج فورا‪.‬‬
‫إ ّنها تأخذ معاملا ‪ :‬القيمة ال ّتي يجب إعادتها من طرف البرنامج )هذا في الحقيقة يوافق الـ ‪ return‬الخاص‬

‫بالـ ‪.( main‬‬

‫‪1‬‬ ‫)][‪int main(int argc, char *argv‬‬
‫‪2‬‬
‫{‬

‫;‪3 int* allocatedMemory = NULL‬‬
‫;))‪4 allocatedMemory = malloc(sizeof(int‬‬

‫‪5 if (allocatedMemory == NULL) // If the allocation has failed‬‬

‫{‪6‬‬

‫‪7 exit(0); // Stop the program‬‬

‫}‪8‬‬

‫‪9 // Else, we can continue the program normally.‬‬

‫;‪10 return 0‬‬

‫} ‪11‬‬

‫إذا كان المؤشر مختلفا عن ‪ ، NULL‬يمكن للبرنامج أن يواصل العمل‪ ،‬و إلا فيجب إظهار رسالة خطأ أو‬
‫حت ّى إنهاء البرنامج لأن ّه لن يتم ّكن من الاستمرار بشكل صحيح إن ل ّم يكن هناك مكان في الذاكرة‪.‬‬

‫‪ : free‬تحرير الذاكرة‬

‫مثلما استعملنا الدالة ‪ fclose‬لنغلق ملفا ً لم نعد في حاجة إليه‪ ،‬سنستعمل الدالة ‪ free‬من أجل تحرير‬
‫الذاكرة التي لم نعد بحاجة إليها‪.‬‬

‫;)‪1 void free(void* pointer‬‬

‫الدالة ‪ free‬بحاجة فقط إلى عنوان الذاكرة المراد تحريرها‪ .‬سنرسل لها إذن مؤش ّرنا‪ ،‬أي ‪allocatedMemory‬‬
‫في مثالنا‪.‬‬

‫إليكم المخطط الكامل و النهائي‪ ،‬مشابه بشكل كبير لما رأينا في الفصل الخاص بالملفات ‪:‬‬

‫)][‪1 int main(int argc, char *argv‬‬
‫{‪2‬‬

‫‪3‬‬ ‫;‪int* allocatedMemory = NULL‬‬
‫‪4‬‬
‫;))‪allocatedMemory = malloc(sizeof(int‬‬

‫‪5 if (allocatedMemory == NULL) // Verify if the memory has been‬‬

‫‪allocated‬‬

‫{‪6‬‬
‫! ‪7 exit(0); // Error: Stop everything‬‬
‫}‪8‬‬
‫‪9 // We can use the memory here‬‬
‫‪10 free(allocatedMemory); // No need for the memory anymore,‬‬

‫‪11‬‬ ‫‪free it.‬‬
‫} ‪12‬‬ ‫;‪return 0‬‬

‫‪230‬‬

‫الحجز الح ّي للذاكرة‬

‫مثال استخدام واقعي‬

‫سنبرمج شيئا درسناه منذ زمن طو يل ‪ :‬الطلب من المستخدم تزويدنا بع ُمره ثم ّ عرضه له‪ .‬الشيء المختلف عم ّا كن ّا‬
‫نفعله سابقا هو أن المتغيّر هنا سيتم ّ حجزه يدو يا )نقول أيضا حيويّا( و ليس تلقائيّا كالسابق‪ .‬إذن نعم‪ ،‬في المر ّة‬

‫الأولى‪ ،‬الشفرة أصعب قليلا‪ .‬لـكن قم بالمجهود اللازم لفهمها جي ّدا‪ ،‬هذا ضرور ّي ‪:‬‬

‫‪1‬‬ ‫)][‪int main(int argc, char *argv‬‬
‫‪2‬‬
‫{‬

‫‪3‬‬ ‫;‪int* allocatedMemory = NULL‬‬
‫‪4‬‬
‫‪allocatedMemory = malloc(sizeof(int)); // Allocation of the‬‬

‫‪5‬‬ ‫‪memory‬‬
‫‪6‬‬ ‫)‪if (allocatedMemory == NULL‬‬
‫‪7‬‬ ‫{‬
‫‪8‬‬
‫‪9‬‬ ‫;)‪exit(0‬‬
‫‪10‬‬ ‫}‬
‫‪11‬‬ ‫‪// Using the memory‬‬
‫‪12‬‬ ‫;)” ? ‪printf(”How old are you‬‬
‫‪13‬‬ ‫;)‪scanf(”%d”, allocatedMemory‬‬
‫‪14‬‬
‫} ‪15‬‬ ‫;)‪printf(”You are %d years old\n”, *allocatedMemory‬‬

‫‪free(allocatedMemory); // Freeing the memory‬‬
‫;‪return 0‬‬

‫‪How old are you ? 31‬‬
‫‪You are 31 years old‬‬

‫حذار ‪ :‬بما أن ‪ allocatedMemory‬هو مؤش ّر‪ ،‬فلا نستعمله بنفس الطر يقة التي نستعمل بها‬
‫متغيّرا حقيقي ّا‪ .‬للحصول على قيمة المتغير يجب وضع نجمة أمامه ‪) *allocatedMemory :‬لاحظ‬
‫‪ .( printf‬بينما للحصول على العنوان‪ ،‬يكفي فقط أن كتابة اسم المؤش ّر ‪allocatedMemory‬‬

‫)لاحظ ‪.( scanf‬‬
‫ك ّل هذا تم شرحه في فصل المؤش ّرات‪ .‬رغم ذلك‪ ،‬أعرف أن هذا سيأخذ وقتا و من الممكن أن تخلط‬

‫بينهما‪ .‬إن كانت هذه حالتك‪ ،‬فعليك بإعادة قراءة فصل المؤشّرات‪ ،‬فهو أساسي‪.‬‬

‫لنعد إلى الشفرة‪ .‬لقد قمنا بحجز حيّ لمتغيّر من نوع ‪ . int‬في النهاية‪ ،‬ما كتبناه يعود تماما لاستخدام الطر يقة‬
‫”التلقائيّة” ال ّتي نعرفها الآن جي ّدا ‪:‬‬

‫‪1‬‬ ‫)][‪int main(int argc, char *argv‬‬
‫‪2‬‬
‫{‬

‫( ‪3 int myVariable = 0; // Allocation of the memory‬‬

‫)‪automatically‬‬

‫‪4 // Using the memory‬‬
‫;)” ? ‪5 printf(”How old are you‬‬
‫;)‪6 scanf(”%d”, &myVariable‬‬

‫‪231‬‬

‫الفصل ب‪ .8.‬الحجز الح ّي للذاكرة )‪(Dynamic memory allocation‬‬
‫;)‪7 printf(”You are %d years old\n”, myVariable‬‬
‫;‪8 return 0‬‬
‫)‪9 } // Freeing the memory (automatically at the end of the function‬‬

‫‪How old are you ? 31‬‬
‫‪You are 31 years old‬‬

‫كملخص‪ ،‬لدينا طر يقتان لإنشاء متغير‪ ،‬أي لحجز الذاكرة‪ .‬إمّا أن نقوم بذلك ‪:‬‬

‫• تلقائيّا ‪ :‬هي الطر يقة ال ّتي تعرفها و التي استعملناها لغاية الآن‪.‬‬
‫• يدو يا )حيويّا( ‪ :‬هي الطر يقة ال ّتي أعل ّمكم إيّاها في هذا الفصل‪.‬‬

‫أنا أجد أن الطر يقة الحي ّة مع ّقدة و بلا فائدة !‬

‫أكثر تعقيدا‪ ...‬بالتأكيد‪ .‬لـكن بدون فائدة‪ ،‬لا ! أحيانا نكون مجـبرين على حجز الذاكرة يدويّا كما سنرى‬
‫الآن‪.‬‬

‫ب‪ 3.8.‬الحجز الح ّي لجدول‬

‫لحد الآن استعملنا الحجز الحي لإنشاء متغيّر صغير‪ .‬عادة لا نستخدم الحجز الح ّي في هذا‪ .‬نستخدم الطر يقة‬
‫التلقائيّة ال ّتي هي أبسط‪.‬‬

‫متى نحتاج للحجز الح ّي‪ ،‬ٺتسائلون ؟ أكثر شيء‪ ،‬نستخدمه لإنشاء جدول لا نعرف حجمه قبل تشغيل البرنامج‪.‬‬
‫لنتخيل مثلا برنامجا ًيقوم بتخزين أعمار أصدقاء المستخدم في جدول‪ ،‬يمكنك فعل ذلك هكذا ‪:‬‬

‫;]‪1 int friendsAge[15‬‬

‫لـكن من قال أنه لديه ‪ 15‬صديق ؟ ربما لديه أكثر من هذا ! عندما تكتب الشفرة المصدر ي ّة‪ ،‬لا يمكنك‬
‫معرفة حجم الجدول قبل التشغيل‪ ،‬عندما تطلب من المستعمل إدخال عدد الأصدقاء‪.‬‬

‫فائدة الحجز الح ّي موجودة هنا ‪ :‬تطلب من المستخدم إدخال عدد الأصدقاء‪ ،‬ثم تنشئ جدولا لديه تماما الحجم‬
‫اللازم )لا أصغر و لا أكبر(‪ .‬إن كان للمستخدم ‪ 15‬صديقا‪ ،‬فسننشئ جدولا من ‪ ، int 15‬إن كان لديه‬

‫‪ ،28‬فسننشئ جدولا من ‪ ، int 28‬إلخ‪.‬‬
‫كما عل ّمتكم‪ ،‬من الممنوع في لغة الـ‪ C‬إنشاء جدول بتحديد حجمه باستخدام متغيّر ‪:‬‬
‫;]‪1 int friends[friendsNumber‬‬

‫‪232‬‬

‫الحجز الح ّي لجدول‬

! ‫ من المنصوح به عدم استخدامها‬،‫هذه الشفرة قد تعمل في بعض المترجمات لـكن في حالات معي ّنة‬

‫ و هذا بفضل‬. friendsNumber ‫ هي أن ّه يمكننا من إنشاء جدول حجمه تماما المتغيّر‬،‫فائدة الحجز الح ّي‬
! ‫شفرة تعمل في ك ّل مكان‬

: ‫ بايت في الذاكرة‬friendsNumber * sizeof(int) ‫ حجز‬malloc ‫سنطلب من‬
1 friends = malloc(friendsNumber * sizeof(int));

! ‫ حجمه بوافق تماما عدد الأصدقاء‬int ‫هذه الشفرة تسمح بإنشاء جدول من نوع‬
: ‫هذا ما يقوم به البرنامج بالترتيب‬

.‫ نطلب من المستخدم كم من صديق لديه‬.1
.( malloc ‫ ذو حجم يساوي عدد أصدقائه )باستخدام‬int ‫ إنشاء جدول من‬.2
.‫ و نقوم بتخزينها في الجدول‬،‫ نطلب كم عمر ك ّل واحد من أصدقائه واحدا واحدا‬.3

.‫ نظهر أعمار الأصدقاء محتوى الجدول لنتأكد بأننا خزّننا ك ّل شيء‬.4
. free ‫ نقوم بتحرير بالدالة‬،‫ و بما أننا لسنا بحاجة إلى الجدول الذي يحوي أعمار الأصدقاء‬،‫ في النهاية‬.5

1 int main(int argc, char *argv[])
2
{

3 int friendsNumber = 0, i = 0;

4 int* friendsAge = NULL; // We use it after calling malloc
5 // Request for friends number

6 printf(”How many friends do you have ? ”);

7 scanf(”%d”, &friendsNumber);

8 if (friendsNumber > 0) // The user must have at least one friend (

or i will be upset :p)

9{
10 friendsAge = malloc(friendsNumber * sizeof(int)); // Allocate the

memory for the table

11 if (friendsAge == NULL) // Verify the allocation
12 {
13 exit(0); // Stop everything
14 }
15 // Request for the ages one by one
16 for (i = 0 ; i < friendsNumber ; i++)
17 {
18 printf(”How old is the friend number %d ? ”, i + 1);
19 scanf(”%d”, &friendsAge[i]);
20 }
21 // Display the stored ages
22 printf(”\n\nYour friends have the next ages :\n”);

233

‫الفصل ب‪ .8.‬الحجز الح ّي للذاكرة )‪(Dynamic memory allocation‬‬

‫)‪23 for (i = 0 ; i < friendsNumber ; i++‬‬
‫{ ‪24‬‬
‫;)]‪25 printf(”%d years\n”, friendsAge[i‬‬
‫} ‪26‬‬
‫‪27 // Free the memory‬‬
‫;)‪28 free(friendsAge‬‬
‫} ‪29‬‬
‫;‪30 return 0‬‬
‫} ‪31‬‬

‫‪How many friends do you have ? 5‬‬
‫‪How old is the friend number 1 ? 16‬‬
‫‪How old is the friend number 2 ? 18‬‬
‫‪How old is the friend number 3 ? 20‬‬
‫‪How old is the friend number 4 ? 26‬‬
‫‪How old is the friend number 5 ? 27‬‬
‫‪Your friends have the next ages :‬‬
‫‪16 years‬‬
‫‪18 years‬‬
‫‪20 years‬‬
‫‪26 years‬‬
‫‪27 years‬‬

‫هذا البرنامج عديم الفائدة ‪ :‬يطلب الأعمار ثم ّ يعرضها بعد ذلك‪ .‬لقد اخنرن فعل هذا لأن ّه مثال ”بسيط” )هذا‬
‫إن فهمت ‪.( malloc‬‬

‫أؤكّد لك ‪ :‬في بقي ّة هذا الكتاب ستكون لنا فرص لاستخدام ‪ malloc‬في أمور أكثر إفادة من تخزين‬
‫أعمار الأصدقاء !‬

‫مل ّخص‬

‫• كل متغير يحجز مكانا مختلفا في الذاكرة و هذا حسب نوعه‪.‬‬

‫• يمكننا معرفة عدد البايتات التي يشغلها كل نوع باستعمال العامل )(‪. sizeof‬‬

‫• الحجز الح ّي هو عبارة عن حجز يدوي لمكان في الذاكرة من أجل متغير أو من جدول‪.‬‬

‫• الحجز الح ّي يتم باستعمال الدالة )(‪ malloc‬و يجب خا ّصة عدم نسيان تحرير الذاكرة باستعمال )(‪free‬‬
‫بمجر ّد الانهاء من الاستخدام‪.‬‬

‫• الحجز الح ّي يسمح بشكل أساس ّي بتعر يف جدول حجمه يتم ّ تعيينه بمتغيّر في حين تشغيل البرنامج‪.‬‬

‫‪234‬‬

‫الفصل ب‪9.‬‬

‫برمجة لعبة الـ‪Pendu‬‬

‫أكرر دائما ‪ :‬التطبيق شيء ضرور ّي‪ .‬هو ضرور ّي لك لأنك اكتشفت كثيرا من المفاهيم النظر ية و‪ ،‬أيّا‬
‫كان ما تقول‪ ،‬لن تفهمها ح ّقا بدون تطبيق‪.‬‬

‫في هذا العمل التطبيقي‪ ،‬أقترح عليك إنشاء لعبة الـ‪ .Pendu‬و هي لعبة حروف تقليدي ّة يتم ّ فيها تخمين كلمة‬
‫سر ي ّة حرفا بحرف‪ .‬و الـ‪ Pendu‬سيكون إذن لعبة في الـكونسول بلغة ‪.C‬‬

‫الهدف هو جعلك تستخدم ك ّل ما تعل ّمته حت ّى الآن ‪ :‬المؤشرات‪ ،‬السلاسل المحرفي ّة‪ ،‬الملفات‪ ،‬الجداول‪...‬‬
‫باختصار‪ ،‬الأشياء الجي ّدة فقط !‬

‫ب‪ 1.9.‬التعليمات‬

‫سأقوم بشرح قواعد الـ‪ Pendu‬الواجب إنشاءه‪ .‬سأعطيك هنا التعليمات‪ ،‬أي سأشرح لك بدق ّة كيف يجب‬
‫أن تعمل اللعبة التي ست ُنشئها‪.‬‬

‫أعتقد أن الجميع يعرف الـ‪ ،Pendu‬أليس كذلك ؟ هي ّا‪ ،‬تذكير صغير لا يمكن أن يحدث ضررا ‪ :‬هدف‬
‫الـ‪ Pendu‬هو إيجاد الكلمة المخب ّأة في أق ّل من عشر محاولات )يمكنك تغيير العدد الأقصى لتغيير صعوبة اللعبة‪،‬‬

‫بالطبع !(‪.‬‬

‫سر يان الجولة‬

‫فلنفترض أن الكلمة المخب ّأة هي ‪.RED‬‬
‫ستقوم باقتراح حرف على الحاسوب‪ ،‬مثلا الحرف ‪ .A‬سيتأكّد الحاسوب ما إن كان هذا الحرف موجودا ً في‬

‫الكلمة المخفي ّة‪.‬‬

‫تذكّر ‪ :‬هناك دالة جاهزة في ‪ string.h‬تقوم بالبحث عن حرف في كلمة ! و بالطبع أنت لست مجـبرا ً‬
‫على استخدامها )شخصي ّا‪ ،‬أنا لم أفعل(‪.‬‬

‫إنطلاقا ًمن هنا‪ ،‬يوجد احتمالان ‪:‬‬

‫‪235‬‬

‫الفصل ب‪ .9.‬برمجة لعبة الـ‪Pendu‬‬
‫• الحرف موجود بالفعل في الكلمة ‪ :‬سنكشف مكان الحرف في الكلمة‪.‬‬
‫• الحرف غير موجود في الكلمة )هذا هو الحال هنا‪ ،‬لأن ‪ A‬ليس موجودا ًفي الكلمة ‪ : (RED‬سنخبر اللاعب‬
‫بأن الحرف هذا غير موجود في الكلمة‪ ،‬و سننقص عدد المحاولات المتب ّقية‪ .‬عندما لا ٺتبق أية محاولة )‪0‬‬

‫محاولة(‪ ،‬ستنتهي اللعبة و سنخسر‪.‬‬

‫في لعبة ‪” Pendu‬حقيقة”‪ ،‬يفترض وجود شخص يتأ ّسف في ك ّل م ّره نخطئ فيها‪ .‬في الـكونسول‪ ،‬سيكون‬
‫من الصعب كثيرا رسم شخص يتأ ّسف بواسطة لاشيء غير النص‪ ،‬لذا سنكتفي بعرض جملة بسيطة مثل‬

‫”بقي لك ‪ X‬محاولات قبل الموت الأكيد”‪.‬‬

‫فلنفرض الآن أن اللاعب أدخل الحرف ‪ .D‬هذا الحرف موجود في الكلمة المخفي ّة‪ ،‬لهذا لن نقوم بإنقاص‬
‫عدد المحاولات المتب ّقية للاعب‪ .‬سنقوم بإظهار الكلمة مع الحروف ال ّتي تم إيجادها‪ ،‬أي شيء كهذا ‪:‬‬

‫‪Secret word : **D‬‬

‫إذا أدخل اللاعب فيما بعد الحرف ‪ ،R‬و بما أن ّه موجود في الكلمة‪ ،‬سنضيف الحرف إلى قائمة الحروف التي‬
‫تم إيجادها و يتم إظهار الكلمة مع الحروف ال ّتي تم ّ اكتشافها ‪:‬‬

‫‪Secret word : R*D‬‬

‫حالة وجود حرف مكرر‬

‫في بعض الكلمات‪ ،‬يمكن أن نجد حرفا ًمكررا ً مرتين أو ثلاث‪ ،‬أو رب ّما أكثر !‬
‫مثلا ‪ :‬يوجد إثنان من ‪ Z‬في كلمة ‪ ،PUZZLE‬و كذلك يوجد ثلاثة ‪ E‬في كلمة ‪.ELEMENT‬‬
‫ماذا علينا أن نفعل في حالة كهذه ؟ قواعد ‪ Pendu‬واضحة ‪ :‬إذا أدخل اللاعب الحرف ‪ ،E‬ك ّل حروف ‪E‬‬

‫في كلمة ‪ ELEMENT‬يجب أن تظهر دفعة واحدة ‪:‬‬
‫**‪Secret word : E*E*E‬‬

‫يعني أنه ليس على اللاعب أن يدخل ‪ 3‬مرات الحرف ‪ E‬ليتم إكتشاف كل تكرار له في الكلمة‪.‬‬

‫مثال عن جولة كاملة‬

‫هذا ما ستبدو عليه جولة كاملة في الـكونسول عند انتهاء البرنامج ‪:‬‬

‫‪236‬‬

‫التعليمات‬

Welcome !
You have 10 remaining tries

What’s the secret word ? ****

Suggest a letter : B
You have 9 remaining tries

What’s the secret word ? ****

Suggest a letter : F
You have 9 remaining tries

What’s the secret word ? F***

Suggest a letter : D
You have 9 remaining tries

What’s the secret word ? F**D

Suggest a letter : O
You win ! The secret word is : FOOD

‫قراءة حرف من الـكونسول‬

.‫قراءة حرف من الـكونسول هي أكثر تعقيدا ً مم ّا تبدو‬
: ‫ يفترض أن ّك تفكّر في‬،‫ لاسترجاع محرف‬،‫بديهي ّا‬

1 scanf(”%c”, &myLetter);

‫ )متغيّر من نوع‬myLetter ‫ و الذي سنقوم بتخزينه في‬،ً ‫ تعني أننا ننتظر محرفا‬%c .‫ هذا جي ّد‬،‫و تماما‬
.( char

: ‫ يمكنك تجريب الشفرة التالية‬.‫ م ّرة اخرى‬scanf ‫ ما دمنا لم نقم بـ‬...ً ‫كل شيء يعمل جيدا‬

1 int main(int argc, char* argv[])
2
{

3 char myLetter = 0;

4 scanf(”%c”, &myLetter);

5 printf(”%c”, myLetter);

6 scanf(”%c”, &myLetter);

7 printf(”%c”, myLetter);

8 return 0;

9}

.‫ و ذلك لمر ّتين‬،‫يفترض بهذه الشفرة أن تطلب حرفا ًو تظهره‬
‫ فهو لا يطلب منك‬،‫ البرنامج يتوق ّف مباشرة بعدها‬...‫ و لـكن‬،‫ نعم‬،‫ ما الذي يحصل ؟ تدخل حرفا‬.‫جرّب‬

.‫ الثانية‬scanf ‫المحرف الثاني ! و كأنه تم تجاهل‬

‫ما الذي حصل ؟‬

237

‫الفصل ب‪ .9.‬برمجة لعبة الـ‪Pendu‬‬

‫في الواقع‪ ،‬حينما تدخل نصا ً في الـكونسول‪ ،‬فإن كل ما قمت بإدخاله يتم ّ تخزينه في الذاكرة‪ ،‬بما في ذلك‬
‫الزر ‪.( \n ) Enter‬‬

‫لذلك‪ ،‬في أ ّول م ّرة تدخل فيها حرفا )‪ A‬مثلا ً( ثم ّ تضغط على ‪ Enter‬فإن الحرف ‪ A‬هو من يتم إعادته من‬
‫طرف ‪ . scanf‬بينما في المر ّة الثانية‪ scanf ،‬سيعيد ‪ \n‬الموافق لـ‪ Enter‬ال ّذي أدخلته سابقا !‬

‫لتجنب هذا‪ ،‬من الأحسن أن نكتب بأنفسنا دالتنا الخا ّصة الصغيرة )(‪: readCharacter‬‬
‫)(‪1 char readCharacter‬‬
‫{‪2‬‬
‫;‪3 char character = 0‬‬
‫‪4 character = getchar(); // Read the first character‬‬
‫‪5 character = toupper(character); // Convert the character to‬‬

‫‪uppercase‬‬

‫)‪6 // Read other characters until reaching \n (to erase them‬‬
‫; )’‪7 while (getchar() != ’\n‬‬
‫‪8 return character; // Return the first character that have been read‬‬
‫}‪9‬‬

‫هذه الدالة تستخدم )(‪ getchar‬ال ّتي هي دالة من ‪ stdio‬و هذا يعود تماما ًإلى كتابة‬
‫;)‪ . scanf(”%c”, &letter‬الدالة )(‪ getchar‬تقوم بإرجاع المحرف الذي قام اللاعب بإدخاله‪.‬‬

‫بعد ذلك‪ ،‬أستعمل أيضا ًالدالة القياسي ّة التي لم تسنح لنا فرصة تعل ّمها في كتابنا ‪ . toupper() :‬هذه الدال ّة‬
‫تحوّل الحرف المعطى إلى كبير )‪ .(Uppercase‬هكّذا‪ ،‬اللعبة ستعمل حتى إن أدخل اللاعب حروفا ً صغيرة‪.‬‬

‫يجب تضمين ‪ ctype.h‬لتستطيع استخدام هذه الدالة )لا تنس ذلك !(‪.‬‬

‫تأتي بعد ذلك المرحلة الأكثر أهمية ‪ :‬و هي أن نقوم بمسح المحارف التي يمكن أن نكون قد أدخلناها‪ .‬في‬
‫الواقع‪ ،‬بإعادة استدعاء ‪ getchar‬نحصل على المحرف الثاني ال ّذي تم ّ إدخاله )مثلا ‪.( \n‬‬

‫ما أقوم به بسيط و يأخذ سطرا واحدا ‪ :‬أستدعي الدالة ‪ getchar‬في حلقة تكرار ية حتى الوصول إلى ‪. \n‬‬
‫ٺتوقف الحلقة إذن‪ ،‬و هذا يعني أننا ”قرأنا” ك ّل المحارف الأخرى‪ ،‬سيتم ّ إذن إفراغها من الذاكرة‪ .‬نقول أنّنا‬

‫نفرغ المتغير المؤقت )‪.(Buffer‬‬

‫لماذا توجد فاصلة منقوطة في نهاية الـ ‪ while‬و لماذا لا نرى أية حاضنة ؟‬

‫في الواقع‪ ،‬استعملت حلقة تكرار ية لا تحتوي على تعليمات )التعليمة الوحيدة‪ ،‬هي ‪ getchar‬داخل‬
‫القوسين(‪ .‬الحاضنتان ليستا ضروريّتين نظرا لأنه ليس لدينا ما نفعله غير ‪ . getchar‬لهذا أضع فاصلة منقوطة‬
‫لتعو يض الحاضنتين‪ .‬هذه الفاصلة المنقوطة تعني ”لا تفعل شيئا ً في ك ّل دورة للحلقة”‪ .‬هذا أمر غريب قليلا‪،‬‬

‫لـكنها تقني ّة يجب معرفتها‪ ،‬تقني ّة يستعملها المبرمجون لانشاء حلقات بسيطة و قصيرة‪.‬‬

‫اعلم أ ّن الـ ‪ while‬كان بالإمكان كتابتها هكذا ‪:‬‬

‫‪238‬‬

‫التعليمات‬

‫)’‪1 while (getchar() != ’\n‬‬
‫{‪2‬‬
‫‪3‬‬
‫}‪4‬‬

‫لا يوجد شيء داخل الحاضنتين‪ ،‬إ ّنها تطوّعي ّة‪ ،‬نظرا لأن ّه ليس هناك شيء آخر لفعله‪ .‬تقني ّتي ال ّتي تقتضي‬
‫وضع فاصلة منقوطة فقط أبسط من تلك الخا ّصة بالحاضنتين‪.‬‬

‫أخيرا‪ ،‬تقوم الدالة ‪ readCharacter‬بإرجاع المحرف الأ ّول الذي قمنا بقراءته ‪ :‬المتغي ّر ‪. character‬‬

‫خلاصة القول‪ ،‬في شفرتك‪ ،‬لا تستعمل ‪:‬‬

‫;)‪1 scanf(”%c”, &myLetter‬‬

‫و إنما استعمل بدل ذلك دال ّتنا الرائعة ‪:‬‬

‫;)(‪1 myLetter = readCharacter‬‬

‫قاموس الكلمات‬

‫لتجربة أولية للشفرة الخاصة بك‪ ،‬أطلب منك أن تقوم بتثبيت الكلمة السر ي ّة مباشرة في الشفرة‪ .‬أكتب مثلا ‪:‬‬
‫;”‪1 char secretWord[] = ”RED‬‬

‫طبعا ستبقى الكلمة السر ي ّة نفسها دائما إن تركناها هكذا‪ ،‬هذا ليس ممتعا‪ .‬لـكني طلبت منك فعل ذلك لـكي‬
‫لا تخلط المشاكل‪ .‬في الواقع‪ ،‬عندما تعمل لعبة ‪ Pendu‬جي ّدا )و فقط ابتداء من هذه اللحظة(‪ ،‬يمكنك البدء‬

‫بالطور الثاني ‪ :‬إنشاء قاموس الكلمات‪.‬‬

‫ما هو هذا ”قاموس الكلمات” ؟‬

‫هو ملف يحتوي كثيرا من الكلمات للعبتك ‪ .Pendu‬يجب أن تكون كل كلمة على سطر‪ .‬مثلا ‪:‬‬

‫‪HOUSE‬‬
‫‪BLUE‬‬
‫‪AIRPLANE‬‬
‫‪XYLOPHONE‬‬
‫‪BEE‬‬
‫‪BUILDING‬‬
‫‪WEIGHT‬‬
‫‪SNOW‬‬
‫‪ZERO‬‬

‫‪239‬‬

‫الفصل ب‪ .9.‬برمجة لعبة الـ‪Pendu‬‬

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

‫‪.Pendu‬‬

‫ستلاحظ أنني منذ البداية تعمّدت كتابة ك ّل الكلمات بالحروف الـكبيرة‪ .‬في الواقع‪ ،‬في الـ‪ Pendu‬لا‬
‫يتم التمييز بين الحروف الـكبيرة و الحروف الصغيرة‪ ،‬و لهذا فمن المستحسن أن نقول منذ البداية ‪” :‬كل‬
‫حروف كلمات اللعبة كبيرة”‪ .‬عليك أن تنب ّه اللاعب‪ ،‬في دليل استخدام اللعبة مثلا‪ ،‬أنه يفترض به‬

‫إدخال حروف كبيرة لا صغيرة‪.‬‬
‫بالمقابل‪ ،‬نتعمّد تجنب العلامات الصوتية )‪ (accents‬لتبسيط اللعبة )إن بدأنا اختبار ‪ ...ë ،ê ،è ،é‬فلن‬

‫ننتهي أبدا ً !(‪ .‬عليك إذن أن تكتب كلماتك كل ّها بحروف كبيرة و بدون علامات صوتيّة‪.‬‬

‫المشكل الذي سيحدث لك سر يعا هو أنه عليك معرفة عدد الكلمات الموجودة في القاموس‪ .‬في الواقع‪،‬‬
‫إن أردت إختيار كلمة عشوائية‪ ،‬يجب أن يتم أخذ عدد بين ‪ 0‬و ‪ ،X‬و أنت لا تعرف في بادئ الأمر كم من‬

‫الكلمات يحتوي الملف‪.‬‬

‫لح ّل هذا المشكل‪ ،‬يوجد حل ّان‪ .‬يمكنك أن تشير في السطر الأول من المل ّف إلى عدد الكلمات ال ّتي يحويها ‪:‬‬

‫‪3‬‬
‫‪HOUSE‬‬
‫‪BLUE‬‬
‫‪AIRPLANE‬‬

‫إلا أن هذه الطر يقة مملة‪ ،‬لأنه يجب إعادة حساب عدد الكلمات يدو يا في ك ّل م ّرة تضيف فيها كلمة )أو‬
‫إضافة ‪ 1‬إلى هذا العدد إن كنت ماكرا بدل إعادة الحساب‪ ،‬لـكنّها تبقى طر يقة بدائيّة قليلا(‪ .‬لهذا‪ ،‬أقترح‬
‫عليك أن تع ّد تلقائيّا عدد الكلمات عن طر يق قراءة الملف م ّرة أولى باستخدام برنامجك‪ .‬معرفة كم يوجد من‬

‫كلمات أمر بسيط ‪ :‬عليك ع ّد الـ ‪) \n‬العودة إلى السطر( في الملف‪.‬‬

‫حينما تقرأ المل ّف في م ّرة أولى لع ّد ‪ ، \n‬فعليك القيام بـ ‪ rewind‬للعودة إلى البداية‪ .‬لن يكون عليك إذن‬
‫سوى أخذ عدد عشوائيّ بين عدد الكلمات ال ّتي عددتها‪ ،‬ثم ّ عليك تخزين هذه الكلمة في سلسلة محرفي ّة في الذاكرة‪.‬‬

‫سأتركك قليلا لتفكّر في ك ّل هذا‪ ،‬لن أساعدك أكثر‪ ،‬و إلّا فلن يكون عملا تطبيقيا ! و اعلم بأن ك ّل المعارف‬
‫ال ّتي تحتاجها موجودة في الفصول السابقة‪ ،‬فأنت قادر تماما على إنشاء هذه اللعبة‪ .‬إنه يتطل ّب منك بعض الوقت‬
‫و هو أق ّل سهولة مم ّا يبدو عليه‪ ،‬و لـكن إذا ن ّظمت الأمور جي ّدا )بإنشاء قدر كاف من الدوال( سوف تصل‪.‬‬

‫بالتوفيق !‬

‫ب‪ 2.9.‬التصحيح )‪ : 1‬شفرة اللعبة(‬

‫‪240‬‬

‫التصحيح )‪ : 1‬شفرة اللعبة(‬

‫بقراءتك لهذه السطور‪ ،‬يعني أنك قد أكملت البرنامج‪ ،‬أو أنك لم تستطع إكماله‪.‬‬

‫لقد استغرقت شخصي ّا وقتا أكبر مم ّا كنت أعتقد في إنشاء هذه اللعبة البسيطة للغاية‪ .‬هكذا دائما ‪ :‬نقول ”هذا‬
‫بسيط”‪ ،‬لـكن في الحقيقة توجد الـكثير من الحالات لدراستها‪.‬‬

‫رغم ذلك أصرّ على القول بأنك قادر على فعل هذا‪ .‬يلزمك فقط بعض الوقت )بضع دقائق‪ ،‬بضع ساعات‬
‫بضع أيام ؟(‪ ،‬لـكن ّنا لم نكن أبدا في سباق‪ .‬أنا أف ّضل أن تأخذ كثيرا من الوقت للوصول إلى الحل على ألّا‬

‫تجر ّب سوى ‪ 5‬دقائق و ترى التصحيح‪.‬‬

‫لا تعتقد أن ّي كتبت البرنامج من المحاولة الأولى‪ .‬أنا أيضا‪ ،‬كنت أعمل خطوة بخطوة‪ .‬بدأت بشيء بسيط‬
‫ج ّدا‪ ،‬ثم ّ شيئا فشيئا ح ّسنت الشفرة للوصول إلى النتيجة النهائيّة‪.‬‬

‫قمت بع ّدة أخطاء أثناء كتابة الشفرة ‪ :‬نسيت في لحظة ما تهيئة متغير بشكل صحيح‪ ،‬نسيت كتابة نموذج دالة و‬
‫كذلك حذف متغير لم يعد مفيدا في شفرتي‪ .‬و حتى أن ّي ‪-‬أعترف‪ -‬نسيت فاصلة منقوطة سخيفة في لحظة ما عند‬

‫نهاية تعليمة‪.‬‬
‫لماذا أقول كل هذا ؟ لـكي أخبرك أن ّني لست معصوما من الأخطاء و أن ّي أواجه تقريبا نفس المشاكل مثلك‬

‫)”أ ّيها البرنامج البائس‪ ،‬هل ستعمل أم لا !؟”(‪.‬‬

‫سأعرض عليك الح ّل على جزئين‪.‬‬
‫• أ ّولا سأر يك كيف أنشأت شفرة اللعبة نفسها‪ ،‬بتثبيت الكلمة المخفي ّة مباشرة في الشفرة‪ .‬إخترت الكلمة‬

‫‪ YELLOW‬لأ ّنها تسمح باختبار ما إن كنت تعاملت جي ّدا مع المحارف المتكر ّرة‪.‬‬

‫• يعد ذلك‪ ،‬سأر يك كيف أضفت العمل بقاموس الكلمات لأخذ كلمة سرّية عشوائيّة لللاعب‪.‬‬

‫بالطبع‪ ،‬يمكنني أن أر يك الشفرة دفعة واحدة و لـكن‪ ...‬سيكون هذا كثيرا في م ّرة واحدة‪ ،‬و البعض لن‬
‫تكون لديه الشجاعة لمحاولة فهم الشفرة‪.‬‬

‫سأحاول أن أشرح لك خطوة بخطوة طر يقة عملي‪ .‬تذكّر أ ّن ما يهم‪ ،‬ليس النتيجة‪ ،‬و إن ّما طر يقة التفكير‪.‬‬

‫تحليل الدالة ‪main‬‬

‫مثلما يعلم الجميع‪ ،‬ك ّل شيء يبدأ بـ ‪ . main‬بجب ألا ننسى تضمين المكتبات ‪ stdlib ، stdio‬و ‪ctype‬‬

‫)من أجل الدالة ‪ ( toupper‬ال ّتي سنحتاج إليها أيضا ‪:‬‬

‫>‪1 #include <stdio.h‬‬

‫>‪2 #include <stdlib.h‬‬

‫>‪3 #include <ctype.h‬‬

‫‪4‬‬

‫‪5‬‬ ‫)][‪int main(int argc, char* argv‬‬
‫‪6‬‬
‫{‬

‫;‪7 return 0‬‬

‫}‪8‬‬

‫‪241‬‬

‫الفصل ب‪ .9.‬برمجة لعبة الـ‪Pendu‬‬

‫حسنا‪ ،‬لح ّد الآن يجب على الجميع أن يتابعوا‪.‬‬
‫الدالة ‪ main‬ستش ّكل معظم اللعبة و ستقوم باستدعاء بعض الدوال حينما تحتاج إليها‪.‬‬

‫فلنبدأ بتعر يف المتغيرات الضرور ي ّة‪ .‬كن متأكّدا‪ ،‬لم أفكّر في ك ّل هذه المتغيرات من الوهلة الأولى‪ ،‬و لقد‬
‫كان هناك أق ّل من هذا العدد في أ ّول م ّرة كتبت فيها الشفرة !‬

‫>‪1 #include <stdio.h‬‬
‫>‪2 #include <stdlib.h‬‬
‫>‪3 #include <ctype.h‬‬
‫)][‪4 int main(int argc, char* argv‬‬
‫{‪5‬‬
‫‪6 char letter = 0; // Stores the letter suggested by the user‬‬
‫‪7 char secretWord[] = ”YELLOW”; // The word that the user must find‬‬
‫‪8 int foundLetter[3] = {0}; // Boolean table. Each cell corresponds‬‬

‫‪to a letter in the secret word. 0 = letter not found, 1 = letter‬‬
‫‪found‬‬

‫)‪9 int remainingTries = 10; // Counting the remaining tries (0 = dead‬‬
‫‪10 int i = 0; // A little variable to browse the table‬‬
‫;‪11 return 0‬‬
‫} ‪12‬‬

‫لقد كتبت بمحض إرادتي تصريح ك ّل متغير على سطر و وضعت كثيرا من التعليقات لشرح دور كل متغير‪.‬‬
‫عملي ّا‪ ،‬لست مضطر ّا على وضع ك ّل هذه التعليقات كما وضع الـكثير من التصريحات في نفس السطر‪.‬‬

‫أعتقد أن أغلب المتغيرات تبدوا منطقية ‪ :‬المتغير ‪ letter‬يخز ّن الحرف الذي يدخله المستخدم في‬
‫ك ّل مرة‪ secretWord ،‬يحوي الكلمة الواجب اكتشافها‪ remainingTries .‬يحتوي عدد المحاولات‬
‫المتب ّقية‪ ،‬إلخ‪ .‬المتغيّر ‪ i‬هو متغير صغير استعمله كي أتص ّفح الجدول مستعملا الحلقة ‪ . for‬فهو ليس مهمّا‬

‫ج ّدا لـكن ّه ضرور ّي إذا أردنا القيام بحلقات‪.‬‬

‫و أخيرا ً المتغير ال ّذي يجب التفكير فيه‪ ،‬و الذي سيُمث ّل الفرق‪ ،‬إن ّه عبارة عن جدول من القيم المنطقية‬
‫‪ . foundLetter‬ستلاحظ بأن ّي جعلت حجم الجدول يساوي عدد حروف الكلمة السر ي ّة )‪ .(6‬هذا ليس‬

‫أمرا ً عشوائيا ً‪ :‬إذ أن ك ّل خانة من جدول القيم المنطقية تمث ّل حرفا ًمن الكلمة السر ية‪ .‬هكذا‪ ،‬الخانة الأولى تمث ّل‬
‫الحرف الأ ّول‪ ،‬الثانية الحرف الثاني‪ ،‬إلخ‪.‬‬

‫ك ّل خانات الجدول مهي ّئة في البداية على ‪ ،0‬و التي تعني ”الحرف لم يتم إيجاده بعد”‪ .‬بتق ّدم اللعبة‪ ،‬الجدول سيتم ّ‬
‫تعديله‪ .‬من أجل ك ّل حرف تم ّ إيجاده من الكلمة‪ ،‬الخانة التي توافقها من ‪ foundLetter‬ستأخذ ‪.1‬‬

‫مثلا‪ ،‬إذا كان في مرحلة من الجولة‪ ،‬لدينا العرض ‪ ،Y*LL*W‬فإن جدول الـ ‪ int‬سيحوي القيم ‪101101 :‬‬
‫)‪ 1‬لكل حرف تم ّ إيجاده(‪.‬‬

‫هذه الطر يقة تسهّل علينا معرفة متى يربح اللاعب ‪ :‬يكفي التح ّقق من أن جميع خانات الجدول لا تحوي سوى‬
‫‪.1‬‬

‫في الحالة الأخرى‪ ،‬سيخسر اللاعب إذا وصل الع ّداد ‪ remainingTries‬إلى ‪.0‬‬

‫فلننتقل إلى التالي ‪:‬‬

‫‪242‬‬

‫التصحيح )‪ : 1‬شفرة اللعبة(‬

‫;)”‪1 printf(”Welcome !\n\n‬‬

‫هذه رسالة ترحيب‪ ،‬لا يوجد أي شيء مثير فيها‪ .‬بالمقابل‪ ،‬الحلقة الرئيسي ّة هي الأكثر أهمي ّة ‪:‬‬

‫))‪1 while (remainingTries > 0 && !win(foundLetter‬‬
‫{‪2‬‬

‫اللعبة تستمر ّ مادام قد بقي بعض المحاولات ) ‪ ( remainingTries > 0‬و اللاعب لم يربح‪.‬‬
‫إذا لم تبق له أية محاولة‪ ،‬فهذا يعني أنه فشل‪ .‬إن ربح‪ ،‬فهذا يعني‪ ...‬أن ّه ربح‪.‬في كلتا الحالتين‪ ،‬يجب إيقاف اللعبة‪،‬‬

‫أي إيقاف الحلقة التي تطلب قراءة حرف في ك ّل مرة‪.‬‬

‫‪ win‬هي دالة تقوم بتحليل الجدول ‪ . foundLetter‬تقوم بإعادة ”صحيح” )‪ (1‬إذا كان اللاعب قد‬
‫ربح )أي أن الجدول ‪ foundLetter‬لا يحمل سوى ‪” ،(1‬خطأ” )‪ (0‬إن كان لم يربح بعد‪ .‬لن أشرح لك‬

‫الآن عمل الدالة بشكل مفصل‪ ،‬سنرى ذلك لاحقا ً‪ .‬حالي ّا‪ ،‬يجب عليك فقط معرفة ما تفعله‪.‬‬

‫باقي الشفرة ‪:‬‬

‫;)‪1 printf(”\n\nYou have %d remaining tries”, remainingTries‬‬
‫;)” ? ‪2 printf(”\nWhat’s the secret word‬‬
‫)‪3 for (i = 0 ; i < 3 ; i++‬‬
‫{‪4‬‬
‫‪5 if (foundLetter[i]) // If the letter n° i has been found‬‬
‫‪6 printf(”%c”, secretWord[i]); // Display it‬‬
‫‪7 else‬‬
‫‪8 printf(”*”); // Else, display * for the letters that are not‬‬

‫‪found‬‬

‫}‪9‬‬

‫نقوم في كل مرة بإظهار عدد المحاولات المتب ّقية و كذا الكلمة السر ي ّة )مخفي ّة بـ* بالنسبة للحروف التي لم يتم ّ‬
‫إ يجادها(‪.‬‬

‫يتم إظهار الكلمة السر ي ّة المخفي ّة بـ* بفضل حلقة ‪ for‬حيث أننا نحل ّل ك ّل حرف لنرى إن تم ّ إيجاده‬
‫) )]‪ .( if(foundLetter[i‬إن كان الشرط محققا ً‪ ،‬سنظهر الحرف‪ ،‬و إلا سنظهر * لإخفاءه‪.‬‬

‫الآن بعدما أظهرنا ما يجب‪ ،‬سنطلب من اللاعب أن يدخل حرفا ًجديدا ً ‪:‬‬

‫;)” ‪1 printf(”\nSuggest a letter :‬‬
‫;)(‪2 letter = readCharacter‬‬

‫أستدعي دالتنا )(‪ . readCharacter‬هذه الدالة تقرأ الحرف الأول الذي تم ّ إدخاله‪ ،‬تجعله كبيرا ً ثم‬
‫تفر ّغ المتغير المؤق ّت‪ ،‬أي أ ّنها تمسح بقي ّة الحروف التي يمكن أن تبقى في الذاكرة‪.‬‬

‫‪243‬‬

‫الفصل ب‪ .9.‬برمجة لعبة الـ‪Pendu‬‬

‫‪1 // if it’s NOT the right letter‬‬
‫))‪2 if (!findLetter(letter, secretWord, foundLetter‬‬
‫{‪3‬‬
‫‪4 remainingTries−−; // Decrement the remaining tries‬‬
‫}‪5‬‬
‫}‪6‬‬

‫نختبر ما إن كان الحرف الذي تم ّ إدخاله موجودا في ‪ . secretWord‬نستدعي لأجل هذا دال ّة أنشأناها‬
‫تسمّى ‪ . findLetter‬سنرى بعد قليل شفرة هذه الدال ّة‪.‬‬

‫حالي ّا‪ ،‬ك ّل ما يجب أن تعرفه‪ ،‬هو أ ّن هذه الدال ّة تعيد ”صحيح” إن كان الحرف موجودا في الكلمة‪” ،‬خطأ” إن‬
‫لم تجده‪.‬‬

‫كما تلاحظ فالـ ‪ if‬يبدأ بعلامة تع ّجب ! و التي تعني ”لا”‪ .‬الشرط ي ُقرأ إذن بهذه الطر يقة ‪” :‬إذا لم يتم‬
‫إيجاد الحرف”‪.‬‬

‫ماذا نفعل في حالة عدم إيجاد الحرف ؟ نقوم بتقليل عدد المحاولات المتبقية‪.‬‬

‫لاحظ أيضا ًأن الدالة ‪ findLetter‬تقوم بتحديث قيم الجدول ‪ . foundLetter‬تقوم بوضع ‪ 1‬في‬
‫الخانات الموافقة للحروف التي تم ّ إيجادها‪.‬‬

‫الحلقة الرئيسية في اللعبة ٺتوقف هنا‪ .‬لهذا فسنعيد من بداية الحلقة و نختبر ما إن كان قد بقي شيء من‬
‫المحاولات للعب و اللاعب لم بربح بعد‪.‬‬

‫عند الخروج من الحلقة الرئيسي ّة‪ ،‬لا يبقى سوى إظهار إن كان اللاعب قد نجح في اللعبة أو خسر قبل إنهاء‬
‫البرنامج ‪:‬‬

‫))‪1 if (win(foundLetter‬‬
‫;) ‪2 printf(”\n\nYou win! the secret word is : %s”, secretWord‬‬
‫‪3 else‬‬
‫) ‪4 printf(”\n\nYou lose ! the secret word is : %s”, secretWord‬‬

‫;‬

‫;‪5 return 0‬‬
‫}‪6‬‬

‫سنستدعي الدالة ‪ win‬لنرى ما إن كان اللاعب قد ربح‪ .‬إن كانت هذه هي الحالة‪ ،‬نقوم بإظهار الرسالة‬
‫”ربح !”‪ ،‬و إلّا‪ ،‬فقد انتهت فرص اللعب‪ ،‬فقد خسر‪.‬‬

‫تحليل الدالة ‪win‬‬

‫فلنرى الآن الشفرة الخاصة بالدالة ‪: win‬‬

‫‪244‬‬

‫التصحيح )‪ : 1‬شفرة اللعبة(‬

‫)][‪1 int win(int foundLetter‬‬
‫{‪2‬‬
‫;‪3 int i = 0‬‬
‫;‪4 int playerWins = 1‬‬
‫)‪5 for (i = 0 ; i < 3 ; i++‬‬
‫{‪6‬‬
‫)‪7 if (foundLetter[i] == 0‬‬
‫;‪8 playerWins = 0‬‬
‫}‪9‬‬
‫;‪10 return playerWins‬‬
‫} ‪11‬‬

‫هذه الدالة تأخذ جدول القيم المنطقي ّة ‪ foundLetter‬كمعامل‪ .‬تعيد قيمة منطقية ‪” :‬صحيح” إذا ربح‬

‫اللاعب و ”خطأ” إذا خسِر‪.‬‬

‫الشفرة الخاصة بهذه الدالة بسيطة‪ ،‬بفترض بك فهمها‪ .‬نتص ّفح ‪ foundLetter‬و نختبر ما إن كانت‬
‫إحدى خانات الجدول تحوي ”خطأ” )‪ .(0‬إن كان هناك حرف واحد لم يتم ّ إيجاده فلقد خسر اللاعب ‪:‬‬

‫سيتم وضع ”خطأ” )‪ (0‬في المتغير المنطقي ‪ . playerWins‬و إلّا‪ ،‬إن تم ّ إيجاد كل الحروف‪ ،‬فالمتغيّر المنطقي‬
‫سيكون ”صحيح” )‪ (1‬و الدالة تعيد ”صحيح”‪.‬‬

‫تحليل الدالة ‪findLetter‬‬

‫لهذه الدالة مهمّتان ‪:‬‬

‫• إرجاع متغير منطقي يشير ما إن كان الحرف موجودا ً في الكلمة السر ية‪.‬‬

‫• تحديث )على ‪ (1‬خانات الجدول ‪ foundLetter‬في المواضع الموافقة للحرف الذي تم ّ إيجاده‪.‬‬

‫)][‪1 int findLetter(char letter, char secretWord[], int foundLetter‬‬
‫{‪2‬‬
‫;‪3 int i = 0‬‬
‫;‪4 int rightLetter = 0‬‬
‫‪5 // Search for the letter in the table foundLetter‬‬
‫)‪6 for (i = 0 ; secretWord[i] != ’\0’ ; i++‬‬
‫{‪7‬‬
‫‪8 if (letter == secretWord[i]) // If it exists‬‬
‫{‪9‬‬
‫‪10 rightLetter = 1; // Memorize that it was the right one‬‬
‫‪11 foundLetter[i] = 1; // Put the correspondent value to 1 in the‬‬

‫‪table‬‬

‫} ‪12‬‬
‫} ‪13‬‬
‫; ‪14 return rightLetter‬‬
‫} ‪15‬‬

‫‪245‬‬

‫الفصل ب‪ .9.‬برمجة لعبة الـ‪Pendu‬‬

‫نتص ّفح إذن السلسلة المحرفي ّة ‪ secretWord‬محرفا ً محرفا ً‪ .‬في ك ّل م ّرة‪ ،‬نختبر ما إن كان الحرف الذي‬
‫اقترحه اللاعب حرف من الكلمة‪ ،‬سيتم القيام بأمرين ‪:‬‬

‫• تعديل المتغير المنطقي ‪ ، rightLetter‬إلى ‪ ،1‬لـكي تعيد الدال ّة ‪ 1‬لأن الحرف متواجد بالفعل في‬
‫‪. secretWord‬‬

‫• تحديث الجدول ‪ foundLetter‬على الموضع الحالي للإشارة إلى أ ّن هذا الحرف قد تم ّ إيجاده‪.‬‬

‫الشيء الجي ّد في هذه الطر يقة‪ ،‬هو أننا سنتص ّفح ك ّل الجدول )لا نتوقف عند أول حرف تم إيجاده(‪ .‬هذا‬
‫سيسمح لنا بتحديث الجدول ‪ foundLetter‬بشكل صحيح‪ ،‬في الحالة التي تحتوي فيها الكلمة حرفا ًمكررا ً ع ّدة‬

‫م ّرات‪ ،‬مثل حالة ‪ L‬في ‪.YELLOW‬‬

‫ب‪ 3.9.‬التصحيح )‪ : 2‬استعمال قاموس الكلمات(‬

‫لقد قمنا بجولة حول الوضائف الأساسي ّة لبرنامجنا‪ .‬إن ّه يحتوي على ك ّل ماهو ضروري لإدارة جولة في اللعبة‪،‬‬
‫لـكنه لا يعرف كيف يختار كلمة عشوائية من قاموس كلمات‪ .‬لم أضع لك شفرة المرحلة الأولى كل ّها لأنها‬

‫كانت ستأخذ حجما ًكبيرا ً كما ستكون تكرارا مع الشفرة المصدر ي ّة النهائيّة ال ّتي ستراها فيما بعد‪.‬‬

‫قبل الذهاب بعيدا‪ ،‬الشيء الأ ّول الواجب فعله هو إنشاء قاموس الكلمات‪ .‬و حتى إن كان قصيرا ً فهذا‬
‫ليس سي ّئا‪ ،‬سيكون مناسبا للاختبارات‪.‬‬

‫سأقوم إذن بإنشاء ملف ‪ dico.txt‬في نفس دليل مشروعي‪ .‬حالي ّا‪ ،‬سأضع فيه الكلمات التالية ‪:‬‬
‫‪1 HOUSE‬‬
‫‪2 BLUE‬‬
‫‪3 AIRPLANE‬‬
‫‪4 XYLOPHONE‬‬
‫‪5 BEE‬‬
‫‪6 BUILDING‬‬
‫‪7 WEIGHT‬‬
‫‪8 SNOW‬‬
‫‪9 ZERO‬‬

‫ما إن أنتهي من كتابة البرنامج‪ ،‬سأعود بالطبع إلى هذا القاموس و أملؤه بالـكثير من الكلمات الغريبة‬
‫كـ‪ XYLOPHONE‬و المطوّلة كـ‪ .ANTIDISESTABLISHMENTARIANISM‬لـكن حالي ّا‪ ،‬لنعد إلى كتابة التعليمات‪.‬‬

‫تحضير الملفات الجديدة‬

‫قراءة ”‪ ”dico‬سيأخذ الـكثير من الأسطر )على الأقل‪ ،‬لد ّي إحساس قبليّ بذلك(‪ .‬لهذا فسآخذ الاحتياطات‬
‫بإضافة ملف آخر إلى مشروعي ‪) dico.c‬الذي سيتك ّفل بقراءة ‪ .(dico‬كما سن َقُوم بإنشاء ‪ dico.h‬ال ّذي‬

‫يحوي نماذج الدوال الموجودة في ‪. dico.c‬‬

‫‪246‬‬

‫التصحيح )‪ : 2‬استعمال قاموس الكلمات(‬

‫في ‪ dico.c‬سأبدأ بتضمين المكتبات ال ّتي أنا في حاجة إليها بالإضافة إلى ‪ . dico.h‬أ ّولا‪ ،‬كالعادة‪،‬‬

‫سأحتاج إلى ‪ stdio.h‬و ‪ stdlib.h‬هنا‪ .‬بالإضافة إلى هذا‪ ،‬يجب عليّ أن أقوم بسحب عشوائي لعدد‬

‫من القاموس‪ ،‬سأقوم إذن بتضمين ‪ time.h‬مثلما فعلنا سابقا ً من أجل العمل التطبيقي الأول ”أكثر أو‬

‫أقل”‪.‬سأحتاج أيضا إلى تضمين ‪ string.h‬من أجل استعمال ‪ strlen‬في نهاية الدال ّة‪.‬‬

‫>‪1 #include <stdio.h‬‬
‫>‪2 #include <stdlib.h‬‬
‫>‪3 #include <time.h‬‬
‫>‪4 #include <string.h‬‬
‫‪5‬‬
‫”‪6 #include ”dico.h‬‬

‫الدالة ‪findWord‬‬

‫هذه الدالة تأخذ معاملا واحدا ‪ :‬مؤشرا ً نحو الذاكرة حيث يمكن كتابة الكلمة‪ .‬هذا المؤشّر يتم تزويدنا به عن‬
‫طر يق ‪. main‬‬

‫الدالة ستعيد ‪ int‬و سيكون قيمة منطقية ‪ = 1 :‬تم ّ ك ّل شيء على مايرام‪ = 0 ،‬كان هناك خطأ ما‪.‬‬

‫هذه بداية الدالة ‪:‬‬

‫‪1‬‬ ‫)‪int findWord(char *chosenWord‬‬
‫‪2‬‬
‫{‬

‫‪3 FILE* dico = NULL; // The pointer of the file‬‬
‫;‪4 int wordsNumber = 0, chosenWordNumber = 0, i = 0‬‬

‫;‪5 int readCharacter = 0‬‬

‫أع ّرف بعض المتغيرات ال ّتي ستكون ضرور ية لي‪ .‬مثل الـ ‪ ، main‬لم يخطر ببالي وضعها كل ّها من البداية‪،‬‬
‫يوجد بالتأكيد من قمت بإضافتها لاحقا حينما عرفت أنني بحاجة إليها‪.‬‬

‫أسماء الكلمات تعبّر عن نفسها‪ .‬لدينا المؤش ّر على القاموس ‪ dico‬و الذي سيمكننا من قراءة ‪، dico.txt‬‬

‫متغيرات مؤق ّتة ستخز ّن المحارف‪ ،‬إلخ‪ .‬لاحظ أنني هنا استعملت ‪ int‬لتخزين محرف ) ‪( characterRead‬‬

‫لأ ّن الدالة ‪ fgetc‬ال ّتي سأستخدمها تعيد ‪ . int‬فمن الأفضل إذن تخزين النتيجة في ‪. int‬‬

‫فلنمر إلى التالي ‪:‬‬

‫‪1 dico = fopen(”dico.txt”, ”r”); // Open the dictionary in read mode‬‬

‫‪only‬‬

‫‪2 // Check if it’s open without a problem‬‬
‫‪3 if (dico == NULL) // If there’s a problem‬‬
‫{‪4‬‬
‫;)”‪5 printf(”\nImpossible to load words dictionary‬‬
‫‪6 return 0; // Return a zero to say that the function failed‬‬
‫‪7 // The function stops after reading the instruction return‬‬
‫}‪8‬‬

‫‪247‬‬

‫الفصل ب‪ .9.‬برمجة لعبة الـ‪Pendu‬‬

‫ليس لد ّي الـكثير لأضيفه هنا‪ .‬أفتح الملف ‪ dico.txt‬بوضع قراءة فقط ) ”‪ ( ”r‬و تأكّد إن نجحت‬
‫عن طر يق إختبار إذا كان ‪ dico‬يحمل القيمة ‪ NULL‬فإن عملية فتح الملف قد فشلت )مل ّف غير موجود أو‬

‫مفتوح من طرف برنامج آخر(‪ .‬في هذه الحالة سنظهر رسالة خطأ و نقوم بـ ‪. return 0‬‬

‫لماذا تضع ‪ return‬هنا ؟ في الحقيقة‪ ،‬التعليمة ‪ return‬تضع نهاية للدالة‪ .‬إذا لم يتم فتح القاموس‪،‬‬
‫فستتوقف الدالة و لن يذهب الحاسوب إلى أبعد من ذلك‪ .‬إعادة ‪ 0‬تشير للـ ‪ main‬أ ّن الدال ّة قد فشلت‪.‬‬

‫في ما يلي من الدال ّة نفترض أن فتح الملف نجح‪.‬‬

‫)‪1 // Count the number of words in the file (Just counting the \n signs‬‬
‫‪2 do‬‬
‫{‪3‬‬
‫;)‪4 characterRead = fgetc(dico‬‬
‫)’‪5 if (characterRead == ’\n‬‬
‫;‪6 wordsNumber++‬‬
‫;)‪7 } while(characterRead != EOF‬‬

‫هنا‪ ،‬نتص ّفح ك ّل الملف دفعة واحدة باستعمال ‪) fgetc‬محرفا بمحرف(‪ .‬نع ّد الـ ‪ \n‬التي نجدها‪ .‬أي أنه‬
‫في ك ّل مرة نلتقي بـ ‪ \n‬نزيد قيمة المتغير ‪. wordsNumber‬‬

‫بفضل هذه الشفرة سنتحصل في المتغير ‪ wordsNumber‬على عدد الكلمات الموجودة في الملف‪ .‬تذكّر بأن‬
‫الملف يحتوي على كلمة في كل سطر‪.‬‬

‫‪1 chosenWordNumber = aleatoryNumber(wordsNumber); // Take a word by‬‬

‫‪hazard‬‬

‫هنا أستدعي دال ّة من إنشائي تختار لي عددا عشوائيا بين ‪ 1‬و ‪) wordsNumber‬المعامل الذي ترسله‬
‫للدالة(‪.‬‬

‫إنها دالة بسيطة وضعتها أيضا ً في الملف ‪) dico.c‬سأشرحها بشكل مو ّسع لاحقا ً(‪ .‬باختصار‪ ،‬تقوم بإرجاع‬
‫عدد )يوافق رقم سطر الكلمة في الملف( عشوائيّ يتم تخزينه في ‪. chosenWordNumber‬‬

‫‪1 // Start reading the file from the beginning and stop when finding‬‬

‫‪the right word‬‬

‫;)‪2 rewind(dico‬‬
‫)‪3 while (chosenWordNumber > 0‬‬
‫{‪4‬‬
‫;)‪5 characterRead = fgetc(dico‬‬
‫)’‪6 if (characterRead == ’\n‬‬
‫;‪7 chosenWordNumber −−‬‬
‫}‪8‬‬

‫و الآن و نحن نملك رقم الكلمة التي سنختارها‪ ،‬سنعود إلى بداية الملف باستدعاء )(‪ ، rewind‬و سنتص ّفح‬
‫الملف محرفا بمحرف لنحسب عدد ‪ . \n‬هذه المر ّة‪ ،‬سنقوم بانقاص قيمة ‪ . chosenWordNumber‬إن اخترنا‬

‫مثلا الكلمة رقم ‪ ،5‬في ك ّل ادخال سيتم ّ إنقاص المتغير ‪ chosenWordNumber‬بواحد‪.‬‬

‫‪248‬‬

‫التصحيح )‪ : 2‬استعمال قاموس الكلمات(‬

‫سيأخذ إذن القيم ‪ 4‬ثم ‪ 3‬ثم ‪ 2‬ثم ‪ 1‬ثم ‪.0‬‬
‫عندما يصل المتغير إلى ‪ ،0‬نخرج من الـ ‪ ، while‬لأن الشرط ‪ chosenWordNumber > 0‬لم يعد محققا‪.‬‬

‫هذا الجزء من الشفرة‪ ،‬و الذي يجب عليك فهمه حتما‪ ،‬سير يك كيفية تص ّفح الملف للوصول إلى المكان‬
‫المراد‪ .‬الأمر ليس مع ّقدا و لـكنه ليس ”بديهيا” أيضا ً‪ .‬كن متأكّدا من فهم ما أقوم بفعله هنا‪.‬‬

‫الآن‪ ،‬يفترض أن نملك مؤشّرا متموضعا تماما ً قبل الكلمة السر ي ّة ال ّتي يجب إيجادها‪ .‬سنقوم بتخزينها في‬
‫‪) chosenWordNumber‬المعامل الذي تستقبله الدالة( بفضل ‪ fgets‬بسيط يقوم بقراءة الكلمة ‪:‬‬

‫‪1‬‬ ‫‪/* The cursor of the file is placed in the best place.‬‬
‫‪2‬‬ ‫‪Nothing is needed more than an fgets that will read the line */‬‬
‫‪3‬‬
‫;)‪fgets(chosenWord, 100, dico‬‬

‫‪4 // We erase the \n at the end of the word‬‬

‫;’‪5 chosenWord[strlen(chosenWord) − 1] = ’\0‬‬

‫نحن نطلب من ‪ fgets‬ألا تقرأ أكثر من ‪ 100‬محرف )هذا هو حجم الجدول ‪ ، chosenWord‬الذي‬
‫قمنا بتعر يفه في الـ ‪ .( main‬تذكّر أ ّن ‪ fgets‬تقرأ سطرا كاملا‪ ،‬بما في ذلك ‪ . \n‬بما أننا لا نريد إبقاء الـ ‪\n‬‬

‫في الكلمة النهائية‪ ،‬نحذفها باستبدالها بـ ‪ . \0‬لهذا تأثير القيام بقطع الكلمة قبل ‪. \n‬‬

‫و ها نحن ذا ! لقد خزّنا الكلمة السر ية في الذاكرة عند عنوان ‪. chosenWord‬‬

‫لم يتب ّق سوى غلق الملف‪ ،‬و إعادة القيمة ‪ 1‬لتتوقف الدالة و تشير إلى أن كل شيء على ما ي ُرام ‪:‬‬

‫;)‪1 fclose(dico‬‬
‫‪2 return 1; // Everything is okay, return 1‬‬
‫}‪3‬‬

‫انتهينا من الدالة ‪! findWord‬‬

‫الدالة ‪aleatoryNumber‬‬

‫هذه الدالة التي وعدتكم بشرحها سابقا ً‪ .‬نختار عددا ً عشوائيا و نعيده ‪:‬‬

‫)‪1 int aleatoryNumber(int maxNumber‬‬
‫{‪2‬‬
‫;))‪3 srand(time(NULL‬‬
‫;)‪4 return (rand() % maxNumber‬‬
‫}‪5‬‬

‫السطر الأ ّول يهي ّؤ مول ّد القيم العشوائية‪ ،‬مثلما تعل ّمنا فعل ذلك في العمل التطبيقي الأ ّول ”أكثر أو أقل”‪ .‬أما‬
‫السطر الثاني فيقوم باختيار عدد عشوائي بين ‪ 0‬و ‪ maxNumber‬و يعيده‪ .‬يمكنك ملاحظة أنني قمت بك ّل‬

‫ذلك في سطر واحد‪ ،‬هذا ممكن بك ّل تأكيد‪ ،‬رغم أن ّه قد يبدو أحيانا أقل قابلي ّة للقراءة‪.‬‬

‫‪249‬‬

Pendu‫ برمجة لعبة الـ‬.9.‫الفصل ب‬

dico.h ‫الملف‬

‫ التي كنت قد طلبت منك‬#ifndef ‫يمكنك أن تلاحظ ”الحماية” التي تق ّدمها‬.‫يحتوي نماذج الدوال فقط‬
: (‫ )راجع درس توجيهات المعالج في حالة الحاجة‬.h ‫تضمينها في ك ّل ملفاتك ذات الإمتداد‬

1 #ifndef DEF_DICO

2 #define DEF_DICO

3 int findWord(char *chosenWord);
4
int aleatoryNumber(int maxNumber);

5 #endif

dico.c ‫الملف‬

: ‫ كاملا‬dico.c ‫هذا هو الملف‬

1 #include <stdio.h>

2 #include <stdlib.h>

3 #include <time.h>

4 #include <string.h>

5 #include ”dico.h”

6 int findWord(char *chosenWord)
7{

8 FILE* dico = NULL; // The pointer on the file
9
int wordsNumber = 0, chosenWordNumber = 0, i = 0;

10 int characterRead = 0;

11 dico = fopen(”dico.txt”, ”r”); // Open the dictionary in read

mode only

12 // Check if ’its open without a problem
13 if (dico == NULL) // If there’s a problem
14 {
15 printf(”\nImpossible to load words dictionary”);
16 return 0; // Return a zero to say that the function failed
17 // The function stops after reading the instruction return
18 }
19 // Count the number of words in the file (just count the \n

characters)

20 do
21 {
22 characterRead = fgetc(dico);
23 if (characterRead == ’\n’)
24 wordsNumber++;
25 }
26 chosenWordNumber = aleatoryNumber(wordsNumber); // Take a word by

hazard

27 // Start reading the file from the beginning and stop after

finding the right word

28 rewind(dico);
29 while (chosenWordNumber > 0)

250


Click to View FlipBook Version