0% found this document useful (0 votes)
409 views490 pages

Lear C

Uploaded by

abowqleh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
409 views490 pages

Lear C

Uploaded by

abowqleh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 490

‫تَعَ� ّ َ� ال�َ� َ‬

‫��َ�‬ ‫‪Mathieu‬‬

‫بِلُغَ� ‪C‬‬
‫‪Nebra‬‬

‫‪C‬‬
‫ه�ا ال���ب‬
‫�� �� ِ�� َ� �‬
‫�‬
‫�� � � ‪OpenClassrooms.com‬‬
‫�‬ ‫�� �‬
‫��� � ��� �‬ ‫ﺗ َﺮﺟﻤﺔ‬
‫ُﻣﺮاﺟ َﻌﺔ و إِﻋﺪاد �� ���� �� ����‬
‫ﺗ َﺼﻤﻴﻢ اﻟﻐﻼف ٔ���� � ���� �� ��‬
‫‪Mathieu Nebra‬‬

‫تَع َل ّم البَرْ َ‬
‫مج َة بلُغ َة‬
‫الـ‪C‬‬
‫عدن بلواضح‬ ‫تَرْجَم َة‬

‫جع َة و إعْد َاد حمزة عب ّاد‬


‫م َُرا َ‬

‫أحمد زبوشي‬ ‫تَصميم الغلاف‬


‫تنز يل المشروع‬

‫تم ّت إنشاء هذا الكتاب بلغة التوصيف ‪LATEX‬و ترجمته بمترجم ‪ .XELATEX‬يمكن الحصول على الشفرة المصدر ي ّة الخاصة به‬
‫عن طر يق استنساخ مستودع ‪ GitHub‬التالي ‪:‬‬

‫‪https://github.com/Hamza5/Learn-to-program-with-C_AR‬‬

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

‫إذا كنت من مستخدمي ‪ ،GitHub‬يمكنك التبليغ عن الأخطاء ال ّتي قد تجدها في الكتاب عن طر يق فتح ‪ issue‬في هذا‬
‫المستودع و كتابة تفاصيل الخطأ )الفصل‪ ،‬القسم‪ ،‬الفقرة‪ ،‬رقم الصفحة و التصحيح الموافق إن أمكن(؛ أو عن طر يق القيام‬
‫بـ‪pull request‬‬ ‫بـ‪ fork‬لإنشاء نسخة مطابقة كمستودعك الخاص‪ ،‬ثم إدخال التعديلات المرادة‪ .‬بعد ذلك‪ ،‬يمكنك القيام‬
‫إلى المستودع الأصلي‪ .‬في حالة ما كان التعديل جي ّدا‪ ،‬سأقوم بدمجه في المستودع‪.‬‬

‫الترخيص‬

‫محتوى هذا الكتاب مرخّص تحت بنود رخصة المشاع الإبداعي‪ ،‬نسب المصنف ‪ -‬غير تجاري ‪ -‬الترخيص بالمثل‪ ،‬النسخة‬
‫الثانية )‪ ،(CC-BY-NC-SA 2.0‬تماما مثل ترخيص الدرس الأصلي المتوف ّر في موقع ‪.OpenClassrooms‬‬

‫‪https://creativecommons.org/licenses/by-nc-sa/2.0/‬‬

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

‫إن التحر ّر الفكري في بداية القرن العشرين أدّى إلى توسّ ع في البحوث العلمية التي شملت كل الميادين لاسيّما التكنولوجية‬
‫منها كعلوم الحاسوب‪ .‬هذه الأخيرة أعقبتها ثورة في لغات البرمجة التي تعتبر ركيزة أساسية تقوم عليها البرامج‪ .‬من بين‬
‫‪BCPL‬‬ ‫هذه اللغات نجد لغة الـ‪ ،C‬إذ تعتبر من أقوى لغات البرمجة و أكثرها شيوعاً‪ ،‬فهي مستلهمة من طرف لغتي ‪ B‬و‬
‫حيث تم ّ تطويرها في عام ‪ 1972‬من طرف ‪ Ken Thompson‬و ‪ ،Dennis Ritchie‬و في ظرف سنة واحدة توسّ عت لتكون‬
‫عِـماد نظام التشغيل ‪ UNIX‬بنسبة ‪ 90%‬ثم تم توز يعها في العام المـ ُوالي رسميا ًعبر الجامعات لتصبح بذلك لغة برمجة عالمية‪.‬‬
‫و اشتهرت لغة الـ‪ C‬كونـ ُها لغة عالية المستوى‪ ،‬لها م ُترجم سر يع و فع ّال‪ .‬كما أنها لغة برمجية نقّالة‪ ،‬هذا يعني أن أي برنامج‬
‫يحـترم المعيار ‪ AINSI‬يمكن أن يتم ّ تشغيله على أي ّة منصّ ة تحتوي على مترجم ‪ C‬دون أي ّة تخصيصات‪.‬‬

‫ل مبتدئ لتعل ّم لغة الـ‪ C‬خطوة بخطوة بدء ً من الأساسيات وصولا ً إلى تطوير ألعاب‬ ‫يعتبر هذا الكتاب بوابة سهلة لك ّ‬
‫ثنائية الأبعاد و التحكّم في هياكل البيانات الأكثر تعقيداً‪ .‬الكتاب مرفق بجملة من التمارين و الأعمال التطبيقية المحلولة التي‬
‫تساعد على هضم المفاهيم المكتسبة و تطبيقها على أيّ مشكل برمجي مهما كان نوعه‪ .‬و لأن الـكثير من لغات البرمجة تعتمد‬
‫أساسا ًعلى الـ‪ C‬كالـ‪ Java‬و الـ‪ C++‬و الـ‪) C#‬لغات برمجية غرضية التوجّه( و حتى ‪) PHP‬لغة لبرمجة المواقع( فإن تعل ّم لغة‬
‫حب العمل و الشغف المفاتيح الرئيسية للنجاح و الوصول إلى‬
‫ّ‬ ‫الـ‪ C‬سيساعد على تعل ّم أي ّة لغة برمجية كانت‪ .‬تبقى الإرادة و‬
‫الاحترافية‪.‬‬

‫عدن بلواضح‬

‫الجزائر‬
‫في ‪ 24‬ذو القعدة ‪1438‬‬
‫الموافق لـ‪ 17‬أوت ‪2017‬‬

‫‪1‬‬
‫تقديم‬

‫‪2‬‬
‫مقدّمة‬

‫حب تعل ّم البرمجة لـكن لا تعرف من أين تبدأ ؟ هذه الدروس لتعليم لغة الـ‪ C‬للمبتدئين قد جُعلت خصّ يصا ًمن أجلك !‬
‫ت ّ‬

‫لغة الـ‪ C‬هي لغة لا مفر ّ منها‪ُ ،‬أستلهم َت منها العديد من اللغات الأخرى‪ .‬تم ّ اختراعها في السبعينات و لا تزال مستعملة‬
‫لح ّد الآن في البرمجة النظامية و عالم الروبوتات‪ .‬تعتبر لغة الـ‪ C‬لغة معقّدة‪ ،‬لـكن إن استطعت تعلّمها ستكو ّن لك قاعدة‬
‫برمجية صلبة !‬
‫ل ما‬
‫في هذه الدروس‪ ،‬ستبدأ باكتشاف مبدأ عمل الذاكرة‪ ،‬المتغيرات‪ ،‬الشروط و الحلقات‪ .‬ثم ستقوم باستعمال ك ّ‬
‫تعلّمته في إنشاء واجهات رسومية بالاستعانة بالمكتبة ‪) SDL‬ألعاب فيديو‪ ،‬تسجيلات صوتية ‪ .( ...‬أخيراً‪ ،‬ستتعل ّم كيف‬
‫ٺتعامل مع هياكل البيانات الأكثر شيوعا ً من أجل تنظيم المعلومات في الذاكرة ‪ :‬قوائم متسلسلة‪ ،‬مكدّسات‪ ،‬طوابير‪،‬‬
‫جداول تجزئة ‪...‬‬
‫التحق بي في هذه الدروس من أجل اكتشاف البرمجة بلغة الـ‪! C‬‬

‫بعض الإنجازات ال ّتي سنقوم بها في هذا الكتاب‬

‫‪Mathieu Nebra‬‬
‫مؤسس مشارك لموقع‬
‫‪OpenClassrooms‬‬

‫‪3‬‬
‫مقدّمة‬

‫‪4‬‬
‫جدول المحتو يات‬

‫‪1‬‬ ‫تقديم‬

‫‪3‬‬ ‫مقدّمة‬

‫‪5‬‬ ‫جدول المحتو يات‬

‫‪7‬‬ ‫الـ‪C‬‬ ‫أساسي ّات البرمجة بلغة‬ ‫ا‬

‫‪9‬‬ ‫‪ 1‬قلت برمجة ؟‬


‫‪9‬‬ ‫‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬ ‫‪ 1.1‬ما هي البرمجة ؟‬

‫‪10‬‬ ‫‪. . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬ ‫‪ 2.1‬البرمجة‪ ،‬بأي لغة يا ترى ؟‬


‫‪10‬‬ ‫‪ 3.1‬قليل من المفردات ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪12‬‬ ‫‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬ ‫‪ 4.1‬لماذا نختار تعل ّم ‪C‬؟‬

‫‪13‬‬ ‫‪ 5.1‬هل البرمجة صعبة ؟ ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪15‬‬ ‫‪ 2‬الحصول على الأدوات اللازمة‬

‫‪15‬‬ ‫‪ 1.2‬الأدوات اللازمة للمبرمج ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪17‬‬ ‫‪. . . . . . . . . . . . . . . . . (Windows, Mac OS X, GNU/Linux) Code::Blocks 2.2‬‬


‫‪21‬‬ ‫‪ Windows) Visual C++ 3.2‬فقط( ‪. . . . . . . . . . . . . . . . . . . . . . . . . .‬‬
‫‪26‬‬ ‫‪ Mac OS X) Xcode 4.2‬فقط( ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪28‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪29‬‬ ‫‪ 3‬برنامجك الأوّل‬

‫‪29‬‬ ‫‪ 1.3‬كونسول أو نافذة ؟ ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪5‬‬
‫جدول المحتو يات‬

‫‪31‬‬ ‫‪ 2.3‬الح ّد الأدنى من الشفرة المصدر ية ‪. . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪35‬‬ ‫‪ 3.3‬كتابة رسالة على الشاشة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪39‬‬ ‫‪ 4.3‬التعليقات‪ ،‬مهمّة جدا ! ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪40‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪43‬‬ ‫‪ 4‬عالم المتغي ّرات‬

‫‪43‬‬ ‫‪ 1.4‬أمر متعلق بالذاكرة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪47‬‬ ‫‪ 2.4‬التصريح عن متغير ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪53‬‬ ‫‪ 3.4‬عرض محتوى متغير ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪55‬‬ ‫‪ 4.4‬استرجاع إدخال ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪57‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪59‬‬ ‫‪ 5‬حسابات سهلة‬

‫‪59‬‬ ‫‪ 1.5‬الحسابات القاعدية ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪63‬‬ ‫‪ 2.5‬الاختصارات ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪65‬‬ ‫‪ 3.5‬المكتبة الر ياضياتي ّة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪68‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪69‬‬ ‫‪ 6‬الشروط )‪(Conditions‬‬

‫‪69‬‬ ‫‪ 1.6‬الشرط ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . else ... if‬‬

‫‪75‬‬ ‫‪ 2.6‬المتغيرات المنطقية )‪ ،(Booleans‬أساس الشروط ‪. . . . . . . . . . . . . . . . . . .‬‬

‫‪78‬‬ ‫‪ 3.6‬الشرط ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . switch‬‬

‫‪81‬‬ ‫‪ 4.6‬الثلاثيات ‪ :‬الشروط المختصرة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪82‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪85‬‬ ‫‪ 7‬الحلقات التكرار ية )‪(Loops‬‬

‫‪85‬‬ ‫‪ 1.7‬ماهي الـحلقة ؟ ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪86‬‬ ‫‪ 2.7‬الحلقة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . while‬‬

‫‪89‬‬ ‫‪ 3.7‬الحلقة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . do ... while‬‬

‫‪89‬‬ ‫‪ 4.7‬الحلقة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . for‬‬

‫‪6‬‬
‫جدول المحتو يات‬

‫‪90‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪91‬‬ ‫‪ 8‬عمل تطبيقي ‪” :‬أكثر أو أقل”‪ ،‬لعبتك الأولى‬

‫‪91‬‬ ‫‪ 1.8‬تجهيزات و نصائح ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪94‬‬ ‫‪ 2.8‬التصحيح ! ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪96‬‬ ‫أفكار للتحسين ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪99‬‬ ‫‪ 9‬الدوال )‪(Functions‬‬

‫‪99‬‬ ‫‪ 1.9‬إنشاء و استدعاء دالة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪107‬‬ ‫‪ 2.9‬أمثلة للفهم الجي ّد ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪111‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪113‬‬ ‫الـ‪C‬‬ ‫تقنيات متقدّمة في لغة‬ ‫ب‬

‫‪115‬‬ ‫‪ 10‬البرمجة المجز ّأة )‪(Modular Programming‬‬

‫‪115‬‬ ‫‪ 1.10‬النماذج )‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . (Prototypes‬‬

‫‪117‬‬ ‫‪ 2.10‬الملفات الرأسية )‪. . . . . . . . . . . . . . . . . . . . . . . . . . . (Headers‬‬

‫‪122‬‬ ‫‪ 3.10‬الـترجمة المنفصلة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪124‬‬ ‫‪ 4.10‬نطاق الدوال و المتغيرات ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪128‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪129‬‬ ‫‪ 11‬المؤش ّرات )‪(Pointers‬‬

‫‪129‬‬ ‫‪ 1.11‬مشكل مضجر بالفعل ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪131‬‬ ‫‪ 2.11‬الذاكرة‪ ،‬مسألة عنوان ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪134‬‬ ‫‪ 3.11‬استعمال المؤشرات ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪138‬‬ ‫‪ 4.11‬إرسال مؤش ّر إلى دالة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪141‬‬ ‫‪ 5.11‬من الذي قال ”مشكل مضجر بالفعل” ؟ ‪. . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪142‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪7‬‬
‫جدول المحتو يات‬

‫‪143‬‬ ‫‪ 12‬الجداول )‪(Arrays‬‬

‫‪143‬‬ ‫‪ 1.12‬الجداول في الذاكرة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪144‬‬ ‫‪ 2.12‬تعر يف جدول ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪146‬‬ ‫‪ 3.12‬تصفح جدول ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪149‬‬ ‫‪ 4.12‬تمرير جدول لدالة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪151‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪153‬‬ ‫‪ 13‬السلاسل المحرفي ّة )‪(Strings‬‬

‫‪153‬‬ ‫‪ 1.13‬النوع ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . char‬‬

‫‪155‬‬ ‫‪ 2.13‬السلاسل المحرفي ّة هي جداول من نوع ‪. . . . . . . . . . . . . . . . . . . . . char‬‬

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

‫‪168‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪169‬‬ ‫‪ 14‬المعالج القبلي )‪(Preprocessor‬‬

‫‪169‬‬ ‫‪ 1.14‬الـ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . include‬‬

‫‪171‬‬ ‫‪ 2.14‬الـ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . define‬‬

‫‪175‬‬ ‫‪ 3.14‬الماكرو )‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . (Macro‬‬

‫‪177‬‬ ‫‪ 4.14‬الشروط ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪180‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪181‬‬ ‫‪ 15‬أنشئ أنواع متغيرات خاصّة بك‬

‫‪181‬‬ ‫‪ 1.15‬تعر يف هيكل ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪183‬‬ ‫‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬ ‫‪ 2.15‬استعمال هيكل‬

‫‪187‬‬ ‫‪ 3.15‬مؤش ّر نحو هيكل ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪190‬‬ ‫‪ 4.15‬التعدادات ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪191‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

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

‫‪193‬‬ ‫‪ 1.16‬فتح و غلق ملف ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪8‬‬
‫جدول المحتو يات‬

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

‫‪207‬‬ ‫‪ 3.16‬التحرك داخل ملف ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

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

‫‪211‬‬ ‫ي للذاكرة )‪(Dynamic memory allocation‬‬


‫‪ 17‬الحجز الح ّ‬

‫‪212‬‬ ‫‪ 1.17‬حجم المتغيرات ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪215‬‬ ‫ي للذاكرة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬


‫‪ 2.17‬الحجز الح ّ‬

‫‪219‬‬ ‫ي لجدول ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬


‫‪ 3.17‬الحجز الح ّ‬

‫‪221‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

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

‫‪223‬‬ ‫‪ 1.18‬التعليمات ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

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

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

‫‪242‬‬ ‫أفكار للتحسين ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪245‬‬ ‫نص بشكل أكثر أمانا‬


‫‪ 19‬إدخال ّ‬

‫‪245‬‬ ‫‪ 1.19‬حدود الدالة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . scanf‬‬

‫‪248‬‬ ‫‪ 2.19‬استرجاع سلسلة محارف ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪254‬‬ ‫‪ 3.19‬تحو يل سلسلة محرفي ّة إلى عدد ‪. . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪256‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪257‬‬ ‫‪SDL‬‬ ‫إنشاء ألعاب ‪ 2D‬في‬ ‫ج‬

‫‪259‬‬ ‫الـ‪SDL‬‬ ‫‪ 20‬ٺثبيت‬

‫‪260‬‬ ‫‪ 1.20‬لماذا نختار الـ‪ SDL‬؟ ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪263‬‬ ‫‪ 2.20‬تنز يل الـ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SDL‬‬

‫‪264‬‬ ‫‪ 3.20‬إنشاء مشروع ‪. . . . . . . . . . . . . . . . . . . . . . . . . . Windows : SDL‬‬

‫‪278‬‬ ‫‪ 4.20‬إنشاء مشروع ‪. . . . . . . . . . . . . . . . . . . . . . (Xcode) Mac OS : SDL‬‬

‫‪9‬‬
‫جدول المحتو يات‬

‫‪282‬‬ ‫‪ 5.20‬إنشاء مشروع ‪. . . . . . . . . . . . . . . . . . . . . . . . . GNU/Linux : SDL‬‬

‫‪283‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪285‬‬ ‫‪ 21‬إنشاء نافذة و مساحات‬

‫‪285‬‬ ‫‪ 1.21‬تحميل و إيقاف الـ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SDL‬‬

‫‪289‬‬ ‫‪ 2.21‬فتح نافذة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪295‬‬ ‫‪ 3.21‬التعامل مع المساحات ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪305‬‬ ‫‪ 4.21‬تمرين ‪ :‬إنشاء تدرّج لونيّ ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪309‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪311‬‬ ‫‪ 22‬إظهار صور‬

‫‪311‬‬ ‫‪ 1.22‬تحميل صورة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . BMP‬‬

‫‪315‬‬ ‫‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬ ‫‪ 2.22‬التحكم في الشفافية‬

‫‪320‬‬ ‫‪ 3.22‬تحميل صيغ صور أخرى باستعمال الـ‪. . . . . . . . . . . . . . . . . . . SDL_Image‬‬

‫‪324‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪327‬‬ ‫‪ 23‬معالجة الأحداث )‪(Event handling‬‬

‫‪327‬‬ ‫‪ 1.23‬مبدأ عمل الأحداث ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪332‬‬ ‫‪ 2.23‬لوحة المفاتيح ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪334‬‬ ‫‪ 3.23‬تمرين ‪ :‬تحر يك ‪ Zozor‬بواسطة لوحة المفاتيح ‪. . . . . . . . . . . . . . . . . . . . .‬‬

‫‪343‬‬ ‫‪ 4.23‬الفأرة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪348‬‬ ‫‪ 5.23‬أحداث النافذة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪351‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪353‬‬ ‫‪Mario Sokoban‬‬ ‫‪ 24‬عمل تطبيقي ‪:‬‬

‫‪353‬‬ ‫‪ 1.24‬مواصفات ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sokoban‬‬

‫‪357‬‬ ‫‪ 2.24‬الدالة ‪ main‬و الثوابت ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪361‬‬ ‫‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬ ‫‪ 3.24‬اللعبة‬

‫‪375‬‬ ‫‪ 4.24‬تحميل و حِفظ المستو يات ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪10‬‬
‫جدول المحتو يات‬

‫‪378‬‬ ‫‪ 5.24‬م ُنـشئ المستو يات ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪384‬‬ ‫ملخّ ص و تحسينات ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪387‬‬ ‫‪ 25‬تحكّم في الوقت !‬

‫‪387‬‬ ‫‪ 1.25‬الـ‪ Delay‬و الـ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ticks‬‬

‫‪396‬‬ ‫‪ 2.25‬المُؤقـ ِتات )‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . (Timers‬‬

‫‪399‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪401‬‬ ‫‪SDL_ttf‬‬ ‫‪ 26‬كتابة نصوص باستعمال‬

‫‪401‬‬ ‫‪ 1.26‬تسطيب ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SDL_ttf‬‬

‫‪404‬‬ ‫‪ 2.26‬تحميل ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SDL_ttf‬‬

‫‪407‬‬ ‫‪ 3.26‬الطرق المختلفة للكتابة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪415‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪417‬‬ ‫بـ‪FMOD‬‬ ‫‪ 27‬تشغيل الصوت‬

‫‪417‬‬ ‫‪ 1.27‬ٺثبيت ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . FMOD‬‬

‫‪420‬‬ ‫‪ 2.27‬تهيئة و تحرير غرض نظامي ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪422‬‬ ‫‪ 3.27‬الأصوات القصيرة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪428‬‬ ‫‪ 4.27‬الموسيقى )‪. . . . . . . . . . . . . . . . . . . . . . . . . . (OGG ،MP3 ،WAV‬‬

‫‪434‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪435‬‬ ‫‪ 28‬عمل تطبيقي ‪ :‬الإظهار الطيفي للصوت‬

‫‪436‬‬ ‫‪ 1.28‬التعليمات ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪441‬‬ ‫‪ 2.28‬التصحيح ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪445‬‬ ‫أفكار للتحسين ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪447‬‬ ‫هياكل البيانات‬ ‫د‬

‫‪449‬‬ ‫‪ 29‬القوائم المتسلسلة )‪(Linked lists‬‬

‫‪449‬‬ ‫‪ 1.29‬تمثيل قائمة متسلسلة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪11‬‬
‫جدول المحتو يات‬

‫‪450‬‬ ‫‪ 2.29‬بناء قائمة متسلسلة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪452‬‬ ‫‪ 3.29‬دوال معالجة القوائم المتسلسلة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪457‬‬ ‫‪ 4.29‬اذهب بعيدا ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪458‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪459‬‬ ‫‪ 30‬المكدّسات و الطوابير )‪(Stacks and Queues‬‬

‫‪459‬‬ ‫‪ 1.30‬المكدّسات )‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . (Stacks‬‬

‫‪465‬‬ ‫‪ 2.30‬الطوابير )‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . (Queues‬‬

‫‪468‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪471‬‬ ‫‪ 31‬جداول التجزئة )‪(Hash tables‬‬

‫‪471‬‬ ‫‪. . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬ ‫‪ 1.31‬لماذا نستعمل جدول تجزئة ؟‬

‫‪472‬‬ ‫‪ 2.31‬ماهي جداول التجزئة ؟ ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪474‬‬ ‫‪ 3.31‬كتابة دالة تجزئة ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪475‬‬ ‫‪ 4.31‬معالجة التصادمات )‪. . . . . . . . . . . . . . . . . . . . (Collisions management‬‬

‫‪477‬‬ ‫ملخّ ص ‪. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .‬‬

‫‪479‬‬ ‫خاتمة‬

‫‪12‬‬
‫الجزء ا‬

‫الـ‪C‬‬ ‫أساسي ّات البرمجة بلغة‬

‫‪13‬‬
‫الفصل ‪1‬‬

‫قلت برمجة ؟‬

‫ما هي البرمجة ؟‬ ‫‪1.1‬‬

‫؟‬
‫ما الذي تعنيه كلمة ”بَرْمَج َ”؟‬

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

‫• الآلة الحاسبة تعتبر برنامجاً‪.‬‬

‫• معالج النصوص يعتبر برنامجا ًأيضاً‪.‬‬

‫• وكذلك برنامج المحادثة‪.‬‬

‫• ألعاب الفيديو هي برامج كذلك‪.‬‬

‫‪joe87‬‬ ‫نسخة عن لعبة ‪ MetalSlug‬الشهيرة تم إنشاؤها من طرف العضو‬

‫‪15‬‬
‫الفصل ‪ .1‬قلت برمجة ؟‬

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

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

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

‫بالطبع هذا ليس شيئا م ُبهراً‪ ،‬ولـكن يجب علينا أن نبدأ من هنا؛ وشيئا فشيئا يمكنك أن تنشئ برامج معقّدة أكثر‪.‬‬
‫فالهدف من هذا الكتاب هو أن أعرفك على كل ما يتعلق بهذه اللغة‪.‬‬

‫‪ 2.1‬البرمجة‪ ،‬بأي لغة يا ترى ؟‬

‫حاسوبك هو آلة غريبة جداً‪ ،‬هذا أقل ما يمكن أن نقوله عنه‪ .‬يمكننا أن نخاطبه فقط بالصفر والواحد‪ ،‬فمثلا إذا‬
‫طلبنا منه حساب ‪ 3+5‬فيمكن لهذا أن يعطينا نتيجة كالتالي )هذه ليست ترجمة دقيقة ولـكنها تشبه ما يحدث بالفعل(‪:‬‬
‫‪0010110110010011010011110‬‬

‫ما ت َر َاه هنا يسمى اللغة الثنائية )‪ (Binary language‬أو لغة الآلة )‪ ،(Machine language‬وحاسوبك لا يفهم سوى‬
‫هذه اللغة‪ ،‬وكما تلاحظ‪ ،‬هذه اللغة غير مفهومة على الإطلاق !‬

‫مشكلتنا الآن ‪:‬‬

‫؟‬
‫كيف يمكننا التعامل مع حاسوب لا يفهم سوى اللغة الثنائية ؟‬

‫حاسوبك لا يتحدث الإنجليز ية‪ ،‬ولا العربية‪ ،‬ولا أي لغة غير هذه اللغة‪ ،‬ولـكنها صعبة جدا لدرجة أن حتى أكبر خبراء‬
‫جم َ إلى اللغة الثنائية‪ ،‬لـكن الشيء‬
‫الحاسوب لا يستخدمونها‪ .‬لهذا قام بعض مهندسي الحواسيب باختراع لغات يمكن أن تُت َر َ‬
‫الأصعب هو إنشاء البرامج ال ّتي تقوم بهذه الترجمة‪ .‬ولحسن الحظ فقد قاموا بهذا العمل نيابة عنا‪ .‬هذه البرامج تقوم بترجمة‬
‫الأوامر ال ّتي تكتبها )مثلا ‪ُ ” :‬أحسب ‪ (”3+5‬إلى شيء يشبه هذا ‪. 0010110110010011010011110 :‬‬

‫هذا المخطط يلخص ما كنت أشرح ‪:‬‬

‫قليل من المفردات‬ ‫‪3.1‬‬

‫‪16‬‬
‫‪ .3.1‬قليل من المفردات‬

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

‫نعود إلى الحديث عن المخطط السابق‪ .‬في المستطيل الأول قلت أن ”برنامجك مكتوب بلغة م ُب َ َ ّ‬
‫سطة”‪ ،‬في الواقع هذا النوع‬
‫من اللغات يُعرف باسم لغات البرمجة عالية المستوى )‪ .(High-level programming languages‬هناك مستو يات عديدة‬
‫من لغات البرمجة‪ ،‬وكلما كان مستوى اللغة أعلى كانت أقرب إلى اللغة الحقيقية وكان استخدامها أسهل‪ .‬إذن‪ ،‬اللغات عالية‬
‫المستوى سهلة الاستخدام لـكنها ٺتضمن بعض السلبي ّات سوف نتعر ّف عليها لاحقا‪.‬‬
‫توجد العديد من لغات البرمجة‪ ،‬وهي متفاوتة المستوى‪ ،‬منها ‪:‬‬
‫‪C‬‬ ‫•‬
‫‪C++‬‬ ‫•‬
‫‪Java‬‬ ‫•‬
‫‪Visual Basic‬‬ ‫•‬
‫‪Delphi‬‬ ‫•‬
‫• و العديد غيرها‬

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

‫مصطلح آخر يجب تذك ّره هو الشفرة المصدر ية )‪ ،(Source code‬وهي ببساطة الشفرة الخاصة ببرنامجك الذي تكتبه‬
‫بلغة عالية المستوى والذي يتم ترجمته فيما بعد إلى اللغة الثنائية‪.‬‬

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

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

‫أخيرا‪ ،‬البرنامج الثنائي المنشئ بواسطة المترجم يسمى الملف القابل للتنفيذ أو التنفيذي )‪ .(Executable‬لهذا السبب‬
‫تملك البرامج )على الأقل برامج ‪ (Windows‬الامتداد ‪ .exe‬والذي هو اختصار كلمة ‪.EXEcutable‬‬
‫نعود إلى مخططنا السابق‪ ،‬وهذه المرة سنستخدم المصطلحات الصحيحة ‪:‬‬

‫‪17‬‬
‫الفصل ‪ .1‬قلت برمجة ؟‬

‫لماذا نختار تعل ّم ‪C‬؟‬ ‫‪4.1‬‬

‫كما قلت سابقا‪ ،‬يوجد كثير من اللغات عالية المستوى‪ ،‬فلماذا ينبغي علينا أن نبدأ بإحداها على وجه الخصوص ؟ سؤال‬
‫عظيم !‬

‫على أية حال يجب علينا أن نختار بأي لغة سنبدأ البرمجة عاجلا أم آجلا‪ ،‬وبالتالي لديك الخيار في البدء بـ ‪:‬‬

‫• لغة ذات مستوى عالي جدّا ‪ :‬وتكون سهلة جدّا أوعامة‪ ،‬نذكر من بينها ‪ ،Visual Basic ،Ruby ،Python‬وغيرها‪.‬‬
‫هذه اللغات تسمح بكتابة برامج بشكل أسرع‪ .‬عامّة تحتاج لأن ت ُرفق معها ملفات مُسَاع ِدة لـكي تعمل )كَمُفَس ِّرٍ مثلا(‪.‬‬

‫• لغة ذات مستوى منخفض قليلا ‪ :‬هي أكثر صعوبة نوعا ما‪ ،‬ولـكن مع لغة مثل ‪ C‬سوف ٺتعلم كثيرا عن البرمجة‬
‫وحول طر يقة عمل حاسوبك‪ .‬ستكون بعد ذلك قادرا ً على تعل ّم لغة برمجة أخرى إن أردت وبكل يُسْرٍ‪.‬‬

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

‫م‬
‫بعض لغات البرمجة موجّهة أكثر للشبكة العنكبوتية )‪ (Web‬مثل ‪ PHP‬أكثر منها لإنشاء البرامج المعلوماتية‪.‬‬

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

‫؟‬
‫ما هو الفرق بين ‪ C‬و ‪ C++‬؟‬

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

‫• في البداية‪ ،‬عندما كانت الحواسيب تَزِنُ أطنانا وتشغل مكانا ق َ ْدرُه ُ حجم منزلك‪ ،‬تم ّ اختراع لغة برمجة تسمّى ‪.Algol‬‬

‫• بعدها تطو ّرت الأمور أكثر واخت ُرع َت لغة برمجة جديدة عُر ِف َْت باس ْ ِم ‪ CPL‬وال ّتي تطو ّرت فيما بعد إلى لغة ‪ BCPL‬ثم‬
‫أخذت إسم اللغة ‪.B‬‬

‫ي الزمن توصّل الخـبراء إلى ابتكار اللغة ‪ C‬وقد تم ّ إدخال بعض التعديلات عليها إلّا أنها لا تزال من أحد‬
‫• مع مض ّ‬
‫اللغات الأكثر استخداما اليوم‪.‬‬

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

‫‪18‬‬
‫‪ .5.1‬هل البرمجة صعبة ؟‬

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

‫ولو اعتُبرت ‪ C++‬تطويرا لـ‪ C‬فإن هذا لا يعني أنه يجب استخدام ‪ C++‬فقط لإنشاء البرامج‪ .‬لغة ‪ C‬ليست لغة عجوزا‬
‫‪Mac‬‬ ‫منسي ّة‪ ،‬بالعكس هي مستخدمة بكثرة اليوم‪ .‬بل إنها أساس أنظمة التشغيل الـكبيرة مثل ‪) Unix‬ومنه ‪ GNU/Linux‬و‬
‫‪ (OS‬و ‪.Windows‬‬

‫هل البرمجة صعبة ؟‬ ‫‪5.1‬‬

‫هذا سؤال يعذّب روح كل من يريد تعل ّم البرمجة ! هل يجب أن تكون أستاذ ر ياضيات كبير درس ‪ 10‬سنوات من‬
‫التعليم العالي حت ّى تبدأ البرمجة ؟‬

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

‫• الجمع‬
‫• الطرح‬
‫• الضرب‬
‫• القسمة‬

‫هذا ليس مخيفا ! سوف أشرح لك في فصل لاحق كيف يقوم الحاسوب بهذه العمليات الأساسية في برامجك‪.‬‬

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

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

‫؟‬
‫إذن أين هو الفخ ؟ وأين تكمن الصعوبة ؟‬

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

‫• الصبر ‪ :‬البرنامج لا يعمل عادة من أوّل محاولة‪ ،‬يجب أن تكون مثابراً‪.‬‬

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

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

‫‪19‬‬
‫الفصل ‪ .1‬قلت برمجة ؟‬

‫‪20‬‬
‫الفصل ‪2‬‬

‫الحصول على الأدوات اللازمة‬

‫بعد تجاوزنا لفصل تمهيدي مليئ بالثرثرة سوف نبدأ بالدخول في صلب الموضوع‪ .‬سوف نجيب عن السؤال التالي ‪” :‬ما‬
‫هي البرامج التي نحتاج إليها للبدء في البرمجة ؟”‪.‬‬

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

‫اغتنم الفرصة ! في الفصل التالي سنبدأ حقّا في البرمجة و لن يكون هناك وقت للقيلولة !‬

‫الأدوات اللازمة للمبرمج‬ ‫‪1.2‬‬

‫إذن ما هي الأدوات التي نحتاج إليها ؟ إذا تابعت الفصل السابق جي ّدا‪ ،‬فستعرف واحدا على الأقل !‬

‫هل تعلم عم ّا أتحدّث ؟ حقّا لا ؟‬

‫حسنا‪ ،‬نحن نتحدّث عن المترجم الذي يمكّن من ترجمة لغة الـ‪ C‬إلى اللغة الثنائي ّة !‬

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

‫ما الذي نحتاج إليه أيضا ؟ لن أتركك تخم ّن كثيرا و سأعطيك القائمة ‪:‬‬

‫• محر ّر نصوص )‪ (Text Editor‬لكتابة الشفرة المصدر ية الخاصّة بالبرنامح‪ .‬نظر ي ّا برنامج تحرير نصوص بسيط مثل‬
‫‪ Notepad‬على ‪ Windows‬أو ‪ vi‬على ‪ Unix‬يكفي‪ ،‬لـكن من الأحسن استخدام محر ّر نصوص ذكيّ يقوم بتلوين الشفرة‬
‫المصدر ية لـكي يسهّل عليك العمل‪.‬‬

‫• مترجم لتحو يل الشفرة المصدر ية إلى ملف ثنائي‪.‬‬

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

‫‪21‬‬
‫الفصل ‪ .2‬الحصول على الأدوات اللازمة‬

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

‫من الآن لدينا خياران ‪:‬‬

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

‫• أو أن تحصل على برنامج ”ثلاثة في واحد” يتضمّن محر ّر النصوص و المترجم و المنقّح‪ .‬هذا النوع من البرامج يعرف‬
‫باسم ”بيئات التطوير المتكاملة” )‪ (Integrated Development Environments‬و تسمّى اختصارا ‪.IDE‬‬

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

‫اختيار البيئة الخاصة بك‬

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

‫ل مبتدئ‬
‫• أحد هذه البيئات ال ّتي أفضّ لها هو ‪ .Code::Blocks‬هو مج ّاني و يعمل على أغلب أنظمة التشغيل‪ .‬أنصح ك ّ‬
‫أن يختاره للبدء )و في ما بعد أيضا إذا شعرت أن ّه يلائمك جي ّدا !(‪.‬‬
‫يعمل على أنظمة التشغيل ‪ Mac OS ،Windows‬و ‪.GNU/Linux‬‬

‫• الأكثر شهرة على ‪ Windows‬هو الّذي أنشأته ‪ ،Microsoft‬إن ّه ‪ .Visual C++‬هو برنامج مدفوع )و باهظ الثمن( لـكن‬
‫‪Visual C++‬‬ ‫حظ توجد نسخة مجانية منه تسمّى ‪) Visual Studio Express‬أنا أستخدم النسخة القديمة‬
‫لحسن ال ّ‬
‫‪ Express‬في هذا الكتاب(‪ .‬و هي ممتازة جدّا )بينها و بين النسخة المدفوعة فوارق طفيفة(‪ .‬إنه برنامج كامل و يملك‬
‫منقّحا قو ي ّا‪.‬‬
‫يعمل على ‪ Windows‬فقط‪.‬‬

‫• على ‪ Mac OS X‬يمكنك استخدام ‪ Xcode‬الّذي يفترض أن يكون متوف ّرا على قرص ٺثبيت النظام‪ .‬يناسب كثيرا‬
‫مبرمجي ‪.Mac‬‬
‫يعمل على ‪ Mac OS X‬فقط‪.‬‬

‫م‬
‫ملاحظة لمستخدمي ‪ : GNU/Linux‬يوجد العديد من البيئات لهذا النظام‪ ،‬و لـكن المبرمجـين المحترفين قد يفضّ لون‬
‫تجاوز البيئات و القيام بالترجمة ”يدو ي ّا”‪ ،‬و هو شيء أصعب قليلا‪ .‬نحن سنبدأ باستخدام بيئات تطوير ية‪ .‬لذلك‬
‫أنصحك بثبيت ‪ Code::Blocks‬إن كنت على ‪ GNU/Linux‬لـكي تتمكن من متابعة شروحاتي‪.‬‬

‫‪22‬‬
‫‪(Windows, Mac OS X, GNU/Linux) Code::Blocks .2.2‬‬

‫؟‬
‫ل بيئات التطوير هذه؟‬
‫من هي البيئة الأفضل من بين ك ّ‬

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

‫في هذا الكتاب سوف أستخدم ‪ .Code::Blocks‬فإن أردت الحصول على نفس لقطات الشاشة خاصّتي‪ ،‬خصوصا لـكي‬
‫لا تضيع في البداية‪ ،‬أنصحك بِد َايَة ً بتثبيت ‪.Code::Blocks‬‬

‫‪(Windows, Mac OS X, GNU/Linux) Code::Blocks‬‬ ‫‪2.2‬‬

‫‪ Code::Blocks‬هي بيئة تطوير متكاملة حرّة و مجاني ّة‪ ،‬متوف ّرة للـ‪ Mac ،Windows‬و ‪.GNU/Linux‬‬

‫حالي ّا ‪ Code::Blocks‬متوف ّر بالإنجليز ي ّة فقط‪ .‬لـكن هذا ليس أمرا يدعوك إلى تجن ّب استخدامه ! فنحن قلّما نحتاج إلى‬
‫العمل بقوائم واجهته‪ ،‬فلغة ‪ C‬هي ال ّتي تهمّنا‪.‬‬

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

‫‪Code::Blocks‬‬ ‫تنز يل‬

‫‪Code::Blocks‬‬ ‫توجّه إلى صفحة تنز يل‬

‫‪http://www.codeblocks.org/downloads/binaries‬‬

‫ثم ّ نز ّل الملف الذي يناسب نظامك ‪:‬‬

‫‪mingw‬‬ ‫• إذا كنت تستخدم ‪ ،Windows‬اذهب إلى القسم ”‪ ”Windows‬في أسفل الصفحة‪ .‬نز ّل البرنامج الّذي يحوي‬
‫في اسمه )مثلا ‪ .( codeblocks-10.05mingw-setup.exe :‬النسخة الأخرى لا تحوي مترجما‪ ،‬لن تتمكن‬
‫في حال استخدمتها من ترجمة برامجك !‬

‫• إذا كنت تستخدم ‪ ،GNU/Linux‬اختر الحزمة ال ّتي تناسب توز يعتك‪.‬‬

‫• إذا كنت تستخدم ‪ ،Mac‬اختر الملف الأحدث في القائمة‪ ،‬مثلا ‪. codeblocks-10.05-p2-mac.zip :‬‬

‫‪x‬‬
‫أقول و أكر ّر ‪ :‬إذا كنت تستخدم ‪ Windows‬فيجب عليك تنز يل النسخة ال ّتي يتضم َن اسمها كلمة ‪ mingw‬لأن ّه إذا‬
‫اخترت النسخة الخاطئة فلن تتمكّن من ترجمة برامجك فيما بعد !‬

‫التثبيت بسيط و سر يع‪ .‬أترك جميع الخيارات كما هي و شغ ّل البرنامج‪ .‬سوف تظهر لك نافذة شبيهة بهذه ‪:‬‬

‫‪23‬‬
‫الفصل ‪ .2‬الحصول على الأدوات اللازمة‬

‫نمي ّز أربعة أقسام رئيسية في واجهة البرنامج‪ ،‬و هي مرقّمة في الصورة ‪:‬‬

‫‪ .1‬شر يط الأدوات )‪ : (Toolbar‬يحتوي على كثير من الأزرار و لـكن ّنا سوف نستخدم بعضها فقط باستمرار‪ ،‬سأعود‬
‫للحديث عن هذا فيما بعد‪.‬‬

‫ل الملفات المصدر ي ّة المتعلقة بالبرنامج الذي تعمل عليه‪.‬‬


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

‫‪ .3‬المنطقة الرئيسية ‪ :‬هنا المساحة التي تكتب فيها الشفرة المصدر ية الخاصة ببرنامجك بلغة الـ‪.C‬‬

‫‪ .4‬منطقة الإشعار ‪ :‬و يدعوها البعض ”منطقة الموت”‪ ،‬هنا تُعْر َُض أخطاء الترجمة إذا كانت شفرة البرنامج تحوي خطأ ً‬
‫ما‪ .‬هذا الشيء يحدث كثيرا !‬

‫ما يهمّنا الآن هو قسم محدد من شر يط الأدوات‪ .‬تجد فيه الأزرار التالية )بهذا الترتيب( ‪، Execute ، Compile :‬‬
‫‪ Recompile everything ، Compile & Execute‬تذك ّرهم جي ّدا لأننا سنستخدمهم بانتظام‪.‬‬

‫ل واحد من هذه الأزرار ‪:‬‬


‫و هذا شرح عمل ك ّ‬

‫‪24‬‬
‫‪(Windows, Mac OS X, GNU/Linux) Code::Blocks .2.2‬‬

‫• ‪ : Compile‬كل ملفّات الشفرة المصدر ية ال ّتي كتبتها يتم ارسالها إلى المترجم الّذي يقوم بإنشاء الملف التنفيذي في‬
‫حالة عدم وجود أخطاء‪ .‬أمّا في حالة العثور على أخطاء )و هذا سيحدث عاجلا أم آجلا !( فلن يتم ّ إنشاؤه بل‬
‫يعرض رسائل خطأ في منطقة الإشعار‪.‬‬

‫• ‪ : Execute‬يقوم بتشغيل آخر ملف تنفيذي تم ّت ترجمته‪ .‬يعني أنك ستستخدم هذا الزر لاختبار برامجك ال ّتي أنشأتها‪.‬‬
‫طبعا يجب عليك ترجمة البرنامج قبل تشغيله‪ .‬يمكننا أيضا استخدام الزرّ الثالث‪.‬‬

‫• ‪ : Compile & Execute‬لا يجب أن تكون عبقر يا لـكي تعرف أن ّه ليس إلا مجموع الزرّين السابقين‪ .‬في الواقع هذا‬
‫هو الزرّ الّذي ستستخدمه أكثر‪ .‬في حالة ما إذا حدثت أخطاء في الترجمة لن يتم ّ تشغيل البرنامج بل ست ُعرض قائمة‬
‫جميلة من الأخطاء التي يجب عليك تصحيحها أوّلا !‬

‫• ‪ : Recompile everything‬عندما نستخدم ‪ Compile‬يقوم ‪ Code::Blocks‬في الحقيقة بترجمة الملفات ال ّتي عدّلتها‬
‫فقط‪ .‬أحيانا ‪-‬فقط أحيانا‪ -‬قد تحتاج إلى إعادة ترجمة جميع الملفّات‪ .‬سنتحدّث لاحقا عن فائدة هذا الزر و أيضا‬
‫عن كيفية عمل الترجمة بمزيد من التفصيل‪ .‬حاليا لـكي لا تختلط الأمور عليك يمكنك أن تعتبر أن ّه غير مهمّ‪.‬‬

‫م‬
‫أنصحك باستخدام اختصارات لوحة المفاتيح بدلا من الضغط على الأزرار‪ ،‬لأن ّه شيء نكر ّره كثيرا‪ .‬تذك ّر خصوصا‬
‫أن ّه يمكنك الضغط على ‪ F9‬بدل الزر ‪. Compile & Execute‬‬

‫إنشاء مشروع جديد‬

‫لإنشاء مشروع جديد إذهب إلى قائمة ‪ File‬ثم ّ ‪ New‬ثم ّ ‪ . Project‬من النافذة ال ّتي تظهر اختر ‪. Console application‬‬

‫‪25‬‬
‫الفصل ‪ .2‬الحصول على الأدوات اللازمة‬

‫م‬
‫كما ت َر َى‪ Code::Blocks ،‬يقترح عليك إنشاء عدد معتبر من أنواع البرامج ال ّتي تستخدم مكتبات )‪ (Libraries‬معروفة‬
‫مثل ‪ SDL‬للـ‪ 2D‬و ‪ OpenGL‬للـ‪ 3D‬و ‪ Qt‬و ‪ wxWidgets‬لإنشاء النوافذ الرسومي ّة‪ .‬حالي ّا‪ ،‬هذه الأيقونات ليست‬
‫ن المكتبات السابقة غير مثب ّتة على حاسوبك لهذا لا يمكنك أن تجعلها تعمل‪ .‬سوف نعود لهذه‬ ‫سوى للزينة لأ ّ‬
‫الأنواع الأخرى لاحقا‪ .‬في هذه الأثناء لا يمكننا سوى أن نستخدم الـ‪ Console‬لأن ّك لا تملك بعد المستوى اللازم‬
‫لانشاء أنواع أخرى من البرامج‪.‬‬

‫أنقر على ‪ Go‬لانشاء المشروع الجديد‪ .‬ثم أنقر على ‪ Next‬فالصفحة الأولى ليس مهمّة‪ .‬بعدها سيأتيك اختيار بين‬
‫لغتي الـ‪ C‬أو الـ‪ ،C++‬اختر الـ‪.C‬‬

‫سيُطْلَبُ منك الآن إدخال اسم المشروع‪ ،‬و كذلك مسار المجلّد الذي تختاره لحفظ الملفّات فيه‪.‬‬

‫‪26‬‬
‫‪ Windows) Visual C++ .3.2‬فقط(‬

‫آخر خطوة تُطلب منك هي ‪ ،‬كيف ينبغي أن يترجم البرنامج‪ ،‬يمكنك ترك الخيارات على حالها‪ ،‬لن يكون لهذا أي تأثير‬
‫على ما سنقوم به الآن )تأكّد أن إحدى الخانتين ”‪ ”Release‬أو ”‪ ”Debug‬تكون محدّدة على الأقل(‪.‬‬

‫إضغط على ‪ ، Finish‬إنتهى !‬


‫لقد قام ‪ Code::Blocks‬بإنشاء المشروع الأوّل و ملئه ببعض الشفرة المصدر ية‪.‬‬

‫في الخانة الخاصة بالمشار يع على اليسار‪ ،‬قم بتوسيعها بالضغط على ’ ‪ ’ +‬لـكي تظهر قائمة الملفات في المشروع‪ .‬سيكون‬
‫ل شيء !‬
‫لديك على الأقل ملف يسمّى ‪ . main.c‬هذا هو ك ّ‬

‫‪ Windows) Visual C++‬فقط(‬ ‫‪3.2‬‬

‫بعض التذكيرات حول ‪: Visual C++‬‬

‫• إنها البيئة التطوير ية الخاصة بـ‪.Microsoft‬‬

‫• برنامج مدفوع في الأصل‪ ،‬لـكن توجد نسخة مج ّانية منه تسمّى ‪.Visual C++ Express‬‬

‫• تمكّن من البرمجة باستخدام كلتا اللغتين ‪ C‬و ‪) C++‬و ليس فقط ‪ C++‬كما يوحي الاسم(‪.‬‬

‫طبعا ستقوم بتحميل النسخة المجانية ‪) Visual C++ Express‬احذر‪ ،‬هو غير متوافق مع ‪ Windows 7‬إلّا بداية من النسخة‬
‫‪: (2010‬‬

‫‪27‬‬
‫الفصل ‪ .2‬الحصول على الأدوات اللازمة‬

‫؟‬
‫ما الفرق بين هذه النسخة و النسخة ”الحقيقي ّة” ؟‬

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

‫للتنز يل‪ ،‬زر موقع ‪.Visual C++‬‬

‫‪https://msdn.microsoft.com/fr-fr/express/aa975050.aspx‬‬

‫و اختر تنز يل ‪ Community 2015‬و اختر لغتك المفضّ لة‪.‬‬

‫التثبيت‬

‫التثبيت سهل‪ .‬سوف يقوم البرنامج بتحميل آخر نسخة من الأنترنت تلقائيا‪.‬‬
‫أنصحك بترك الخيارات كما هي‪.‬‬

‫بعد ذلك سيطلب منك التسجيل في غضون ‪ 30‬يوما‪ .‬لا تقلق‪ ،‬إنه سر يع و مجاني لـكن يجب القيام بذلك‪.‬‬

‫اضغط على الرابط المُعطى لك‪ ،‬ستدخل موقع ‪ .Microsoft‬سج ّل دخولك باستخدام ‪) Windows Live ID‬المكافئ لحساب‬
‫‪ Hotmail‬أو ‪ (MSN‬أو قم بإنشاء واحد إذا لم يكن لديك‪ ،‬ثم أجب بعد ذلك على الأسئلة‪.‬‬

‫سيتم إعطاؤك في النهاية مفتاح تفعيل‪ .‬انسخ هذا المفتاح في القائمة ? ثم ”تسجيل المنتج”‪.‬‬

‫‪28‬‬
‫‪ Windows) Visual C++ .3.2‬فقط(‬

‫إنشاء مشروع جديد‬

‫لإنشاء مشروع جديد‪ ،‬إذهب إلى قائمة ”ملف” ) ‪ ( File‬ثم ّ ”جديد” ) ‪ ( New‬ثم ”مشروع” ) ‪ .( Project‬اختر‬
‫‪ Win32‬في العمود الأيسر ثم ّ ‪ . Win32 Console Application‬ثم ّ أ ْدخِل اسم مشروعك‪.‬‬

‫وافق‪ ،‬ستظهر لك نافذة جديدة‪.‬‬

‫هذه النافذة لا تحوي أيّ شيء مهمّ‪ ،‬تابع فقط‪.‬‬

‫‪29‬‬
‫الفصل ‪ .2‬الحصول على الأدوات اللازمة‬

‫اختر ‪ Console application‬و تأكّد من إنشاء مشروع فارغ عن طر يق تحديد ‪ ، Empty project‬ثم اضغط‬
‫على ”إنهاء” ) ‪.( Finish‬‬

‫إضافة ملف مصدري جديد‬

‫مشروعك فارغ لح ّد الآن‪ .‬لإضافة ملف مصدري‪ ،‬اضغط باليمين على ”الملفات المصدر ية” ) ‪ ( Source files‬الموجود‬
‫على اليسار‪ ،‬ثم ّ اختر ”إضافة” ) ‪ ( Add‬ثم ّ ”عنصر جديد” ) ‪.( New element‬‬

‫اختر ‪ Visual C++‬على اليسار ثم ّ ‪) C++ File‬أعلم ُ أنّنا لا ندرس ‪ C++‬و لـكن ليس لهذا أهمي ّة هنا(‪ .‬أ ْدخِل‬
‫‪main.c‬‬ ‫اسم الملف ‪:‬‬

‫‪30‬‬
‫‪ Windows) Visual C++ .3.2‬فقط(‬

‫ملف فارغ‪ .‬أنصحك بحفظه بسرعة باسم ‪. main.c‬‬


‫ثم اضغط على ”إضافة” ) ‪ .( Add‬سيتم إنشاء ّ‬

‫انتهى‪ ،‬يمكنك الآن أن تبدأ في كتابة الشفرة‪.‬‬

‫النافذة الرئيسي ّة‬

‫م أقسام النافذة الرئيسي ّة في ‪.Visual C++ Express‬‬


‫لنرى ما هي أه ّ‬

‫ل جزء‪.‬‬
‫هذه النافذة تشبه مثيلتها في ‪ .Code::Blocks‬و لـكن رغم ذلك سوف نعيد رؤ ية معنى ك ّ‬

‫‪31‬‬
‫الفصل ‪ .2‬الحصول على الأدوات اللازمة‬

‫‪ .1‬شر يط الأدوات ‪ :‬فيه أزرار اعتيادية‪ .‬لـكن كما ترى لا يوجد أيّ زرّ للترجمة‪ .‬يمكنك إضافته عن طر يق النقر باليمين‬
‫على هذا الشر يط و اختيار ”تنقيح” ) ‪ ( Debug‬و ”توليد” ) ‪ ( Generate‬من القائمة‪.‬‬

‫ل هذه الأزرار لديها ما يكافئها في القوائم ‪ Debug‬و ‪ . Generate‬استخدام ‪ Generate‬ينشئ الملف‬


‫ك ّ‬
‫التنفيذي )أي أنها تعني الترجمة(‪ .‬إذا استخدمت ‪ Debug / Execute‬فسوف يقترح عليك الترجمة قبل التشغيل‪.‬‬
‫إختصارات لوحة المفاتيح ‪ F7 :‬لتوليد المشروع و ‪ F5‬لتشغيله‪.‬‬

‫‪ .2‬هذه المساحة ج ّد مهمّة‪ ،‬إذ أنها تحتوي على الملفات الخاصة بمشروعك‪ .‬أنقر على ”مستكشف الحلول” ) ‪( Solution explorer‬‬
‫في الأسفل إن لم يكن ف ُع ِل من قبل‪ .‬سوف ترى أن ّه قد تم ّ إنشاء مجلّدات لفصل أنواع الملفّات المختلفة )مصدر ي ّة‪،‬‬
‫رأسي ّة و موارد(‪ .‬سنتعرف لاحقا على مختلف أنواع الملفات التي تكو ّن المشروع‪.‬‬

‫‪ .3‬المساحة الرئيسية ‪ :‬التي نعدّل فيها الملفّات المصدر ي ّة‪.‬‬

‫أكملنا جولتنا في ‪ .Visual C++‬يمكنك إلقاء نظرة على الخيارات إن أردت لـكن لا تأخذ من وقتك ثلاث ساعات‬
‫هناك ! لأنه يوجد كثير منها‪.‬‬

‫‪ Mac OS X) Xcode‬فقط(‬ ‫‪4.2‬‬

‫هناك الـكثير من البيئات التطوير ية المتوافقة مع ‪ Mac‬على غرار ‪ Code::Blocks‬طبعا‪.‬‬


‫سأقدم لك البيئة الأكثر شهرة في الماك و هي ‪.Xcode‬‬

‫‪ ،Xcode‬أين أنت ؟‬

‫أغلب مستخدمي ‪ Mac OS X‬ليسوا مبرمجـين‪ .‬لقد فهمت ‪ Apple‬هذا‪ ،‬لذلك لم ٺثب ّته افتراضيا مع النظام‪.‬‬
‫ل شيء جاهز‪ Xcode .‬متوف ّر على ‪ .MacAppStore‬ابدأ بأخذه من هناك‪.‬‬
‫ل من يريد أن يبرمج‪ ،‬ك ّ‬
‫لحسن الحظ‪ ،‬لك ّ‬

‫أنصحك أيضا بإلقاء نظرة على الموقع الخاص بالمطو ّرين لـ‪.Apple‬‬

‫‪https://developer.apple.com/‬‬

‫سوف تجد هناك كمّا هائلا من من المعلومات المهمّة للتطوير على ‪ .Mac‬يمكنك منه تحميل العديد من البرامج للتطوير‪.‬‬
‫لا تتردّد في التسجيل في ‪ ،(Apple Development Connection) ADC‬إنه مجاني و يساعدك على ٺتبع كل ما هو جديد‪.‬‬

‫‪Xcode‬‬ ‫تشغيل‬

‫أوّل شيء يمكننا فعله هو إنشاء مشروع جديد‪ ،‬فلنبدأ بهذا‪ .‬إذهب إلى ”ملف ” ) ‪ ( File‬ثم ّ ”مشروع جديد”‬
‫) ‪ .( New Project‬ستفتح لك نافذة اختيار المشروع‪.‬‬

‫‪32‬‬
‫‪ Mac OS X) Xcode .4.2‬فقط(‬

‫اختر ‪ Application‬من اليسار ثم ّ ‪ . Command Line Tool‬اضغط بعدها على ‪ . Next‬سوف يُطلب منكم‬
‫ل مشروع يجب أن يحفظ منذ البداية( و اسمه‪ .‬ضعه في المجلّد الّذي تريد‪.‬‬
‫بعدها حفظ مشروعك )ك ّ‬

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

‫نافذة التطوير‬

‫في ‪ ،Xcode‬عندما تختار ‪ main.c‬تظهر لك نافذة شبيهة بهذه ‪:‬‬

‫الواجهة مقسمة إلى أربعة أقسام‪ ،‬مرقمة هنا من ‪ 1‬إلى ‪: 4‬‬

‫‪ .1‬الجزء الأوّل هو شر يط الأزرار في الأعلى‪ .‬أه ّم زرّ فيه هو ”تشغيل” ) ‪ ( Run‬وظيفته تشغيل البرنامج‪.‬‬

‫‪33‬‬
‫الفصل ‪ .2‬الحصول على الأدوات اللازمة‬

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

‫‪ .3‬الجزء الثالث ٺتغي ّر وظيفته حسب ما قمت بتحديده في الجزء الأيسر‪ .‬و هنا يعرض محتوى الملف ‪. main.c‬‬

‫‪ .4‬أخيرا‪ ،‬الجزء الرابع يُظهر نتائج تشغيل البرنامج على الشاشة عندما تقوم بتشغيل البرنامج‪.‬‬

‫ملف جديد‬
‫إضافة ّ‬

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

‫ملف جديد‪ ،‬اذهب إلى قائمة ‪ File‬ثم ّ ‪ . New File‬سيطلب منك إدخال نوع الملف الذي تريد إنشاءه‪.‬‬
‫لإنشاء ّ‬
‫توجّه إلى قائمة ‪ Mac OS X‬و اختر ‪ C and C++‬ثم ّ ‪ . C File‬لاحظ هذه الصورة‪.‬‬

‫يجب عليك إعطاء اسم لملفّك الجديد‪ .‬امتداده يجب أن يبقى ‪ . .c‬أحيانا ‪-‬كما سنرى لاحقا‪ -‬يجب عليك أيضا إنشاء‬
‫ملفّات بامتداد ‪ . .h‬الخانة ‪ Also create file.h‬مخصّ صة لهذا الغرض‪ .‬حالي ّا هذا الخيار لا يهمّنا‪.‬‬

‫ملف آخر غير الملف ‪ ، main.c‬هنيئا لك فقد أصبحت‬


‫ٌ‬ ‫انقر بعدها على ‪ . Finish‬انتهى ! أصبح في مشروعك‬
‫الآن جاهزا ً للبرمجة على الـ ‪. Mac‬‬

‫ملخّ ص‬

‫• المبرمجون يحتاجون إلى ثلاثة أدوات ‪ :‬محر ّر نصوص‪ ،‬مترجم و منقّح‪.‬‬

‫• من الممكن ٺثبيت هذه الأدوات منفصلة‪ ،‬لـكن ّه من المعتاد البوم الحصول على ح ُزمة ٍ ثلاثة‪-‬في‪-‬واحد نسميها بيئة‬
‫التطوير المتكاملة‪.‬‬
‫• ‪ ،Visual C++ ،Code::Blocks‬و ‪ Xcode‬تع ّد من بين بيئات التطوير الأكثر شهرة‪.‬‬

‫‪34‬‬
‫الفصل ‪3‬‬

‫برنامجك الأوّل‬

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

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

‫كونسول أو نافذة ؟‬ ‫‪1.3‬‬

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

‫يوجد نوعان من البرامج‪ ،‬لا أكثر ‪:‬‬

‫• ‪،‬برامج بنوافذ‬

‫• برامج تعمل في الـكونسول‪.‬‬

‫البرامج ال ّتي تملك نوافذ‬

‫هي البرامج التي نعرفها جميعا‪ .‬هذا مثال على برنامج من نوع نافذة‪ ،‬مثل الرسام‪.‬‬

‫‪35‬‬
‫الفصل ‪ .3‬برنامجك الأوّل‬

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

‫؟‬
‫لـكن ماذا يعنى برنامج ‪ Console‬؟‬

‫البرامج ال ّتي تعمل في الـكونسول‬

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

‫مر الزمن بسرعة وزادت شعبية الويندوز نظرا ً لبساطته إلى أن نسي كثير من الناس ما هي الـكونسول‪.‬‬
‫ّ‬
‫لديّ خبر جي ّد لك ! الـكونسول لم تمت بعد ! في الواقع‪ GNU/Linux ،‬قد أعاد الـكونسول إلى الحياة‪ .‬هذه صورة‬
‫لـكونسول على ‪.GNU/Linux‬‬

‫‪36‬‬
‫‪ .2.3‬الح ّد الأدنى من الشفرة المصدر ية‬

‫مرعب ! صحيح ؟ لـكن على الأقل عرفت ما هي الـكونسول‪ ،‬وهذه بعض الملاحظات ‪:‬‬

‫ل شيء بالأبيض والأسود كما تتخي ّل‪.‬‬


‫• اليوم‪ ،‬يمكننا عرض الألوان في الـكونسول‪ .‬ليس ك ّ‬

‫• الـكونسول هو الأسهل من ناحية البرمجة بالنسبة للمبتدئين‪.‬‬

‫• أداة عالية الإمكاني ّات إذا عرفنا كيف نستخدمه‪.‬‬

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

‫؟‬
‫وفي الويندوز ألا توجد ‪ Console‬؟‬

‫بلى‪ ،‬لـكنّها مخفي ّة لو صح القول‪ .‬يمكنك فتحها بالذهاب إلى ”إبدأ” ) ‪ ( Start‬ثم ّ ”ملحقات” ) ‪( Accessories‬‬
‫ثم ّ ”موجه الأوامر” ) ‪ ( Command prompt‬أو بالذهاب إلى ”إبدأ” ثم ّ ”تشغيل” ) ‪ ( Run‬واكتب فيها ‪ cmd‬واضغط‬
‫على ”موافق”‪.‬‬

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

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

‫‪ 2.3‬الح ّد الأدنى من الشفرة المصدر ية‬

‫خاص لـكنّها ضرور ي ّة‪.‬‬


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

‫أطلب من البيئة التطوير ية الخاصة بك تزويدك بالحد الأدنى من الشفرة المصدر ية‬

‫لقد لاحظت أن طر يقة إنشاء مشروع جديد تختلف من بيئة تطوير ية إلى أخرى‪ .‬إليك تذكيرا ً بسيطا ‪ :‬في برنامج‬
‫‪) Code::Blocks‬الذي سنستخدمه في هذا الكتاب(‪ ،‬عليك التوجه نحو ‪ File‬ثم ّ ‪ New‬ثم ّ ‪ Project‬ثم تختار‬
‫‪ Console Application‬وبعدها اللغة ‪ .C‬سيولّد لك الحد الأدنى من الشفرة المصدر ية ‪ C‬التي تحتاجها‪ .‬ها هي ‪:‬‬

‫‪37‬‬
‫الفصل ‪ .3‬برنامجك الأوّل‬

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


‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬
‫‪4‬‬ ‫)(‪int main‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;)”‪printf(”Hello world!\n‬‬
‫‪7‬‬ ‫;‪return 0‬‬
‫‪8‬‬ ‫}‬
‫‪9‬‬

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

‫ن السطر ‪:‬‬
‫علما ًأ ّ‬

‫)(‪1 int main‬‬

‫‪ ...‬بإمكانه أن يُكتب كالتالي ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬

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

‫إذا كنت تستخدم بيئة تطوير ية أخرى فقم بنسخ هذه الشفرة المصدر ية وألصقها في الملف ‪ main.c‬ليكون لديكم‬
‫نفس الشفرة‪.‬‬

‫مرة‪.‬‬
‫ل ّ‬‫أخيرا‪ ،‬قم بحفظ عملك في المشروع‪ .‬أعلم أننا لم نقم بشيء حت ّى الآن لـكن من الجي ّد التعو ّد على الحفظ في ك ّ‬

‫تحليل أسطر الشفرة المصدر ية السابقة‬

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

‫فلنبدأ بأوّل سطرين ‪:‬‬

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


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

‫‪38‬‬
‫‪ .2.3‬الح ّد الأدنى من الشفرة المصدر ية‬

‫هذان السطران يبدآن بعلامة ‪ . #‬وهي أسطر خاصّة تُعرف باسم توجيهات المعالج القبلي )‪.(Preprocessor directives‬‬
‫اسم معقّد‪ ،‬أليس كذلك ؟ هذه الأسطر تتم ّ قراءتها من طرف البرنامج المسمّى بالمعالج القبلي‪ ،‬وهو برنامج يتم ّ تشغيله في بداية‬
‫الترجمة‪.‬‬

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

‫؟‬
‫حسنا لـكن ماذا يعنيه هذان السطران ؟ أريد أن أعرف !‬

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

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

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

‫نمر للتالي‪ ،‬باقي الأسطر ‪:‬‬

‫)(‪1 int main‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;)”‪printf(”Hello world!\n‬‬
‫‪4‬‬ ‫;‪return 0‬‬
‫} ‪5‬‬

‫ما تراه هنا هو ما نسميه بـالتابع أو الدالّة )‪ .(Function‬البرنامج في لغة ‪ C‬يتكو ّن من مجموعة دوال‪ .‬حالي ّا برنامجنا لا‬
‫يحوي سوى دالّة واحدة‪.‬‬

‫الدالّة تمكّننا من تجميع مجموعة من الأوامر‪ .‬الغرض من تجميع الأوامر هو جعلها تقوم بوظيفة ما‪ .‬مثلا يمكننا إنشاء دالّة‬
‫باسم ‪ open_file‬وجعلها تحتوي التعليمات التي تشرح للحاسوب كيفي ّة فتح ملف‪.‬‬

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

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

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


‫;‪2 return 0‬‬

‫‪39‬‬
‫الفصل ‪ .3‬برنامجك الأوّل‬

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

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

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

‫‪x‬‬
‫ل تعليمة بفاصلة منقوطة ” ; ” ‪ .‬بهذا يمكن التفر يق بين ما إذا كانت هذه تعليمة‬
‫هامّ جدّا ‪ :‬لا ب ّد أن تنتهي ك ّ‬
‫أم لا‪ .‬إذا نسيت وضع فاصلة منقوطة نهاية تعليمة ما‪ ،‬فلن تتم ّ ترجمة برنامجك‪.‬‬

‫السطر الأول ‪ printf(”Hello world!n”); :‬يطلب إظهار الرسالة ”!‪ ”Hello world‬على الشاشة‪ .‬عندما يصل‬
‫برنامجك إلى هذا السطر‪ ،‬فسوف يقوم بعرض هذه الرسالة ثم ّ المرور إلى التعليمة التالية‪.‬‬

‫ن الدالّة ‪ main‬قد انتهت و تطلب منه إعادة ‪.0‬‬


‫التعليمة التالية هي ;‪ return 0‬و هي تخـبرنا أ ّ‬

‫؟‬
‫لماذا يقوم برنامجي بإعادة العدد ‪ 0‬؟‬

‫ل شيء سار على ما يرام‪ .‬عملي ّا‪0 ،‬‬


‫نك ّ‬
‫ل برنامج عندما ينتهي ي ُرجع قيمة معينة‪ .‬على سبيل المثال‪ ،‬ليقول أ ّ‬
‫في الواقع‪ ،‬ك ّ‬
‫ل على حدوث خطأ‪ .‬في أغلب الأحيان هذه القيمة لا تُستخدم‬
‫ل قيمة أخرى تد ّ‬
‫ل شيء سار على ما يرام‪ ،‬و ك ّ‬
‫نك ّ‬
‫يعني أ ّ‬
‫‪ ،‬لـكن يجب رغم ذلك استعمالها‪.‬‬
‫كان يمكن أن يعمل برنامجك بدون ‪ ، return 0‬لـكن يمكننا القول أن وضعها يعتبر أمرا ً أكثر نظافة و أكثر جدّية‪.‬‬

‫إلى هنا نكون قد فصّ لنا قليلا في عمل هذه الشفرة المصدر ية‪.‬‬

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

‫م المصطلحات ال ّتي تعلّمناها في هذا الفصل‪.‬‬


‫إليك ما يلي ‪ :‬بما أنني في حال جي ّدة‪ ،‬سأقوم بوضع مخطّط يض ّ‬

‫‪40‬‬
‫‪ .3.3‬كتابة رسالة على الشاشة‬

‫لنجر ّب برنامجنا‬

‫ل ما سنقوم به الآن هو ترجمة المشروع ثم ّ تشغيله )اضغط على ‪ Build & Run‬إذا كنت على ‪.(Code::Blocks‬‬
‫ك ّ‬
‫سيطلب منك حفظ مشروعك إذا لم تقم بذلك من قبل‪.‬‬

‫‪x‬‬
‫إن لم تنجح الترجمة و ظهر لك خطأ مثل ‪:‬‬
‫‪”My-program - Release” uses an invalid compiler. Skipping...‬‬
‫‪ Nothing to be done...‬فهذا يعني أن ّك نزلت نسخة ‪ Code::Blocks‬دون ‪) mingw‬المترجم(‪ ،‬عد و‬
‫نز ّل النسخة التي تحتوي على ‪. mingw‬‬

‫بعد ب ُرهة‪ ،‬يظهر برنامجك كما في الصورة ‪:‬‬

‫البرنامج يُظهر ”!‪) ”Hello world‬في السطر الأوّل(‪.‬‬


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

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

‫كتابة رسالة على الشاشة‬ ‫‪3.3‬‬

‫من الآن سنقوم بإدخال التعديلات على الشفرة المصدر ية السابقة‪ .‬مهمّتك‪ ،‬إن قبلتها ‪ :‬عرض رسالة ”‪ ”Bonjour‬على‬
‫الشاشة‪.‬‬

‫؟‬
‫كيف يمكنني اختيار النص الّذي سيظهر على الشاشة ؟‬

‫‪41‬‬
‫الفصل ‪ .3‬برنامجك الأوّل‬

‫الأمر بسيط جدا‪ ،‬إذا بدأت من الشفرة التي رأيناها سابقاً‪ ،‬فسيكون عليك استبدال ”!‪ ”Hello world‬بـ”‪ ”Bonjour‬في‬
‫السطر الذي يستدعي ‪. printf‬‬

‫كما قلت من قبل‪ printf ،‬هي تعليمة وهي تعطي أمرا ً للحاسوب ‪” :‬قم بعرض هذه الرسالة على الشاشة”‪.‬‬
‫يجب أن تعرف أيضا أن ‪ printf‬هي دالّة كُت ِب َت من قبل من طرف مبرمجـين قبلك‪.‬‬

‫؟‬
‫أين توجد هذه الدالّة ؟ أنا لا أرى سوى الدالّة ‪! main‬‬

‫هل تذكر هذين السطرين ؟‬


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

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

‫لنقل مرحبا للسي ّد‬

‫في دالّتنا ‪ main‬نستدعي الدالّة ‪ . printf‬أي أن لدينا دالّة تستدعي أخرى )هنا ‪ main‬تستدعي ‪ .( printf‬سترى‬
‫أن هذا ما يحدث دائما في لغة ‪ : C‬دالّة تحتوي تعليمات تستدعي دوال أخرى‪ ،‬وهكذا‪.‬‬

‫إذن‪ ،‬لاستدعاء دالّة يكفي كتابة اسمها متبوعا بقوسين‪ ،‬ثم فاصلة منقوطة‪.‬‬
‫;)(‪1 printf‬‬

‫‪printf‬‬ ‫هذا جيد‪ ،‬لـكنه غير كاف‪ .‬يجب أن نُعلم البرنامج بما يجب أن يكتبه في الشاشة‪ .‬لفعل هذا يجب أن نعطي‬
‫النص المطلوب عرضه‪ .‬لفعل هذا نقوم بوضع النص داخل علامات الإقتباس المزدوجة بين القوسين‪.‬‬
‫في حالتنا هذه سنكتب تماما ‪:‬‬
‫;)”‪1 printf(”Bonjour‬‬

‫ل على نهاية التعليمة‪.‬‬


‫آمل ألا تكون قد نسيت رمز الفاصلة المنقوطة في النهاية‪ ،‬وأذك ّرك أنّها مهمّة جدا لأنّها تد ّ‬
‫هذه هي الشفرة المصدر ية التي يجب أن تحصل عليها ‪:‬‬
‫‪1‬‬ ‫>‪#include <stdio.h‬‬
‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬
‫‪4‬‬ ‫)(‪int main‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;)”‪printf(”Bonjour‬‬
‫‪7‬‬ ‫;‪return 0‬‬
‫‪8‬‬ ‫}‬

‫‪42‬‬
‫‪ .3.3‬كتابة رسالة على الشاشة‬

‫لدينا إذن تعليمتان تطلبان من الحاسوب القيام بهذين الأمرين بهذا الترتيب ‪:‬‬

‫‪ .1‬عرض ”‪ ”Bonjour‬على الشاشة‪.‬‬

‫‪ .2‬نهاية الدالّة ‪ ، main‬إعادة ‪ .0‬البرنامج يتوق ّف‪.‬‬

‫هذا ما يظهر على شاشتك ‪:‬‬

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

‫)‪Special‬‬ ‫ولـكن ضغط المفتاح ‪ Enter‬في الشفرة المصدر ية لن يعمل كما ٺتوقع‪ ،‬لهذا يجب استخدام المحارف الخاصّة‬
‫‪.(characters‬‬

‫المحارف الخاصّة‬

‫المحارف أو الرموز الخاصّة هي محارف تمكّن من تعر يف عودة إلى السطر‪ ،‬جدولة‪ ،‬إلخ‪.‬‬
‫من السهل التعر ّف عليها‪ ،‬فهي مكو ّنة من محرفين‪ .‬الأوّل هو الشَرْطَة ُ المائلة الخلفية )\( )‪ (Backslash‬والثاني يكون رقما‬
‫أو حرفا‪ .‬إليك محرفين خاصّين قد تحتاجهما كثيرا ‪:‬‬

‫• ‪ : \n‬العودة إلى السطر‪.‬‬


‫• ‪ : \t‬الجدولة )فراغ كبير في نفس السطر(‪.‬‬

‫‪Bonjour‬‬ ‫في حالتنا هذه‪ ،‬يكفي أن نكتب ‪ \n‬لإنشاء العودة إلى السطر‪ .‬إذن‪ ،‬إذا أردنا أن نضع عودة إلى السطر بعد‬
‫‪ ،‬فيكفي أن نكتب ‪:‬‬

‫;)”‪1 printf(”Bonjour\n‬‬

‫‪43‬‬
‫الفصل ‪ .3‬برنامجك الأوّل‬

‫ن عليه كتابة ”‪ ”Bonjour‬و يعود إلى السطر‪.‬‬


‫وسيفهم حاسوبك أ ّ‬

‫م‬
‫ل ما تكتبه بعد ‪ \n‬سيوضع في السطر الجديد‪ .‬يمكنك إذن التدرّب‬
‫يمكنك الكتابة بعد ‪ \n‬بدون أي ّة مشكلة‪ .‬ك ّ‬
‫;)”‪printf(”Good morning\nGood bye\n‬‬ ‫على كتابة ‪:‬‬
‫و سيتم ّ عرض ”‪ ”Good morning‬على السطر الأوّل و ”‪ ”Good bye‬على السطر الثاني‪.‬‬

‫‪Gérard‬‬ ‫متلازمة‬

‫؟‬
‫ن حرف ‪ é‬لا‬
‫مرحبا‪ ،‬اسمي ‪ Gérard‬و قد حاولت تعديل برنامجك ليقول ”‪ ،”Bonjour Gérard‬و لـكن ّي ألاحظ أ ّ‬
‫يظهر بشكل جي ّد ‪ ...‬مالّذي عليّ فعله ؟‬

‫أوّلا‪ ،‬مرحبا بك ‪ . Gérard‬هذا سؤال جي ّد‪ .‬لـكن لديّ خبر سيّء لك‪ .‬الـكونسول الخاصة بـ‪ Windows‬لا تمكّن من‬
‫عرض الحروف ال ّتي تحوي علامات النطق الصوتي مثل ‪ ، é‬خلافا لـكونسول ‪ GNU/Linux‬التي تفعل‪ .‬لديّ حل ّان لهذه‬
‫المشكلة ‪:‬‬

‫ل جذريّ بعض الشيء‪ .‬أحتاج إلى درس كامل لأعلّمك كيف تعمل على‬
‫• استخدم ‪ . GNU/Linux‬هذا ح ّ‬
‫‪ . GNU/Linux‬إذا لم يكن لديك المستوى‪ ،‬إنس هذا الخيار حالي ّا‪.‬‬

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

‫لكيلا تنزعج‪ ،‬يمكنك الكتابة دون استخدام الحروف التي تملك علامات النطق الصوتي ‪:‬‬
‫;)”‪1 printf(”Bonjour Gerard\n‬‬

‫نشكر صديقنا ‪ Gérard‬لتنبيهنا على هذه المشكلة !‬

‫‪44‬‬
‫‪ .4.3‬التعليقات‪ ،‬مهمّة جدا !‬

‫التعليقات‪ ،‬مهمّة جدا !‬ ‫‪4.3‬‬

‫أعرفك على التعليقات )‪ . (Comments‬أي ّا كانت لغة‬


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

‫ولـكن ما الذي يعنيه ”التعليق”؟‬


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

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

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

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

‫توجد طر يقتان لإضافة تعليق‪ .‬وهذا يعتمد على طول التعليق المراد إدراجه ‪:‬‬

‫• إذا كان تعليقك قصيرا ‪ :‬فيمكن كتابته على سطر واحد‪ ،‬ولا يحتوي سوى كلمات قليلة‪ .‬في هذه الحالة‪ ،‬عليك كتابة‬
‫شرطتين مائلتين ) ‪ ( //‬متبوعين بتعليقك‪ .‬على سبيل المثال ‪:‬‬
‫‪1 // This is a comment.‬‬

‫ن بهذه الطر يقة يمكننا‬


‫بإمكانك إضافة تعليق وحده على السطر‪ ،‬أو على يمين تعليمة معينة‪ .‬وهذا أمر مه ّم جدّا‪ ،‬لأ ّ‬
‫تحديد ما الذي يعنيه السطر الّذي كُتب بجانبه‪ .‬مثال ‪:‬‬
‫‪1 printf(”Bonjour”); // This instruction displays ’Bonjour’ on the screen‬‬

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

‫– لبدء التعليق ‪ :‬أكتب شرطة مائلة متبوعة بنجمة ) *‪.( /‬‬


‫– لإنهاء التعليق ‪ :‬أكتب نجمة متبوعة بشرطة مائلة ) ‪.( */‬‬

‫يمكنك كتابة هذا على سبيل المثال ‪:‬‬


‫‪1 /� This is‬‬
‫‪2 a comment‬‬
‫‪3 written on several lines �/‬‬

‫فلنعد إلى الشفرة المصدر ية التي تُظهر ”‪ ”Bonjour‬على الشاشة ونضيف إليها بعض التعليقات للتدرّب ‪:‬‬

‫‪45‬‬
‫الفصل ‪ .3‬برنامجك الأوّل‬

‫‪1‬‬ ‫�‪/‬‬
‫‪2‬‬ ‫‪Below, the directives of preprocessor.‬‬
‫‪3‬‬ ‫‪These lines allow you to add files to your program,‬‬
‫‪4‬‬ ‫‪files that we call libraries. Thanks to these libraries, we are ready to use‬‬
‫‪functions for display.‬‬
‫‪5‬‬ ‫‪for example, a message on screen.‬‬
‫‪6‬‬ ‫‪�/‬‬
‫‪7‬‬
‫‪8‬‬ ‫>‪#include <stdio.h‬‬
‫‪9‬‬ ‫>‪#include <stdlib.h‬‬
‫‪10‬‬
‫‪11‬‬ ‫�‪/‬‬
‫‪12‬‬ ‫‪Following, you have the principal function of the program, called main.‬‬
‫‪13‬‬ ‫‪All programs start with this function.‬‬
‫‪14‬‬ ‫‪Here, all what does my function is displaying ”Bonjour” on the screen.‬‬
‫‪15‬‬ ‫‪�/‬‬
‫‪16‬‬
‫‪17‬‬ ‫)(‪int main‬‬
‫‪18‬‬ ‫{‬
‫‪19‬‬ ‫‪printf(”Bonjour”); // This instruction displays ’Bonjour’ on the screen‬‬
‫‪20‬‬ ‫;‪return 0‬‬ ‫‪// The program returns 0 then it stops.‬‬
‫‪21‬‬ ‫}‬

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

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

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

‫’إذا قرأت التعليقات الموجودة في برنامج و لم تفهم مبدأ عمله‪ ،‬قم برميه !’‬

‫ملخّ ص‬

‫• البرامج يمكنها التفاعل مع المستخدم عن طر يق الـكونسول أو عن طر يق النافذة‪.‬‬

‫‪46‬‬
‫ملخّ ص‬

‫ن هذه قد تكون غير محبوبة لدى المبتدئ‪ ،‬فهذا‬


‫• من السهل على المبرمج في برامجه الأولى استخدام الـكونسول‪ ،‬رغم أ ّ‬
‫لا يمنع من استخدام النوافذ في الجزء الثالث من هذا الكتاب‪.‬‬

‫• البرنامج يتكو ّن من تعليمات تنتهي دائما بفاصلة منقوطة‪.‬‬

‫• الدالة ‪) main‬التي تعني الرئيسي ّة( هي الدالة ال ّتي يبدأ بها تنفيذ البرنامج‪ .‬إنّها الدالة الوحيدة الإجبار ية في البرنامج‪،‬‬
‫لا يمكن لأي برنامج أن يُترجم بدونها‪.‬‬

‫• ‪ printf‬هي دالة تمكننا من عرض رسالة على الشاشة‪.‬‬

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

‫‪47‬‬
‫الفصل ‪ .3‬برنامجك الأوّل‬

‫‪48‬‬
‫الفصل ‪4‬‬

‫عالم المتغي ّرات‬

‫نص على الشاشة‪ .‬جيد‪ ،‬لـكنّ هذا ليس شيئا مهماً‪ .‬هذا لأنك لا تعرف بعد ما يدعى بـالمتغي ّرات‬
‫تعلّمت كيفية إظهار ّ‬
‫)‪ (Variables‬في البرمجة‪.‬‬

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

‫أمر متعلق بالذاكرة‬ ‫‪1.4‬‬

‫ما سأعلمك في هذا الفصل هو أمر له علاقة مباشرة بذاكرة حاسوبك‪.‬‬

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

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

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

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

‫الأنواع المختلفة من الذاكرة‬

‫كي أوضح لك الصورة أكثر‪ ،‬إليك أنواع الذاكرة الموجودة في الحاسوب‪ ،‬من الأسرع إلى الأبطأ ‪:‬‬

‫‪ .1‬السجلات )‪ : (Registers‬ذاكرة سر يعة جدّا‪ ،‬موجودة داخل المعالج‪.‬‬

‫‪ .2‬ذاكرة التخبئة )‪ : (Cache memory‬تمثل همزة وصل بين السجلات والذاكرة الحية‪.‬‬

‫‪49‬‬
‫الفصل ‪ .4‬عالم المتغي ّرات‬

‫‪ .3‬ذاكرة الوصول العشوائي )‪ : (Random access memory‬و هي الذاكرة التي نستخدمها كثيرا‪ ،‬وتدعى اختصارا‬
‫‪.RAM‬‬

‫‪ .4‬القرص الصلب )‪ : (Hard disk‬والذي تعرفه بالطبع‪ ،‬نستعمله لحفظ الملفات‪.‬‬

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

‫م‬
‫عندما أقول ذاكرة بطيئة فهذا بالنسبة لحاسوبك‪ ،‬ففي نظر الحاسوب استغراق ‪ 8‬ميلي ثانية للوصول إلى القرص‬
‫الصلب يعتبر زمنا طو يلا جدّا !‬

‫ما الذي يجب أن أتذكره من كل هذا ؟‬


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

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

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

‫لحسن الحظ‪ ،‬عند إعادة تشغيل الحاسوب‪ ،‬يقوم القرص الصلب بتذكيرها بمحتواها‪.‬‬

‫صورة لذاكرة الوصول العشوائي‬

‫نظرا لأننا سنستعمل ذاكرة الوصول العشوائي خلال لحظات‪ ،‬فمن الأفضل أن أريها لك )مؤطرة بالأحمر(‪:‬‬

‫‪50‬‬
‫‪ .1.4‬أمر متعلق بالذاكرة‬

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

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

‫مخطط ذاكرة الوصول العشوائي‬

‫عرض المزيد من الصور لن يفيدك كثيرا‪ ،‬لـكن يجب عليك فهم كيف تعمل من الداخل‪ ،‬لذلك سأقدم لك هذا المخطط‬
‫البسيط الذي يمثل هندسة ذاكرة الوصول العشوائي ‪:‬‬

‫‪51‬‬
‫الفصل ‪ .4‬عالم المتغي ّرات‬

‫كما ترى‪ ،‬يمكننا أن نميز عمودين ‪:‬‬

‫• هناك العناوين ‪ :‬هي أعداد تسمح للحاسوب بتحديد موضع القيم في الـ‪ . RAM‬نبدأ بالعنوان ‪ 0‬وننتهي بالعنوان‬
‫‪ 3,448,765,900,126‬وبعض الأجزاء‪ .‬لا أعلم بالضبط كم عدد العناوين الموجودة في الـ‪ ، RAM‬لـكني أعرف‬
‫أنها كثيرة جدا‪ .‬إضافة إلى ذلك‪ ،‬هذا أمر يتعلق بكمية الذاكرة الموجودة في جهازك‪ ،‬فكلما زادت الذاكرة زادت‬
‫معها العناوين وصار بإمكاننا تخزين معلومات أكثر‪.‬‬
‫• عند كل عنوان يمكننا تخزين قيمة )عدد(‪ .‬حاسوبك يقوم بتخزين هذه الأعداد في ذاكرة الوصول العشوائي لـكي‬
‫يتمكن من تذكرها‪ .‬ولا يمكننا تخزين سوى عدد واحد عند كل عنوان‪.‬‬

‫لا يمكن للذاكرة الحية تخزين شيء سوى الأعداد‪.‬‬

‫؟‬
‫لـكن كيف يمكننا تخزين الكلمات ؟‬

‫سؤال جيد‪ .‬في الواقع حتى الحروف ليست سوى أعدادا في نظر الحاسوب ! الجملة هي مجرد ٺتابع لأعداد‪.‬‬
‫يوجد جدول يوافق بين الأعداد والحروف‪ ،‬جدول يقول مثلا بأن العدد ‪ 67‬يوافق الحرف ‪ .Y‬لن أدخل في التفاصيل‬
‫أكثر‪ ،‬ستكون لنا فرصة للرجوع إلى هذا لاحقا‪.‬‬

‫فلنعد إلى مخططنا‪ ،‬الأمور بسيطة جدا ‪ :‬إذا أراد الحاسوب تذكر العدد ‪) 5‬الذي قد يمثل عدد الأرواح المتبقية لشخصية‬
‫في لعبة( فسوف يضعه في مكان ما في الذاكرة أين يتوفر مكان شاغر و يحفظ العنوان الموافق )مثلا ‪.(3,062,199,902‬‬
‫لاحقا‪ ،‬عندما يريد معرفة هذا العدد فسيذهب إلى خانة الذاكرة التي تحمل العنوان رقم ‪ 3,062,199,902‬وسيجد القيمة‬
‫‪.5‬‬
‫هذه آلية عمل الذاكرة بشكل عام‪ .‬قد يكون الأمر لا زال غامضا في ذهنك حاليا )ما فائدة تخزين عدد إن كان علينا‬
‫تذكر عنوانه بدلا من ذلك ؟( لـكن كل شيء سيتضح مع بقية الفصول‪ ،‬أنا أعدك !‬

‫‪52‬‬
‫‪ .2.4‬التصريح عن متغير‬

‫التصريح عن متغير‬ ‫‪2.4‬‬

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

‫إذن‪ ،‬ما هو المتغير )‪ (Variable‬؟‬


‫إنه معلومة صغيرة نخزنها مؤقتا في الذاكرة الحية‪ .‬ببساطة يمكننا القول إن المتغير هو قيمة يمكن أن ٺتغير أثناء اشتغال‬
‫البرنامج‪ .‬مثلا عددنا ‪ 5‬الذي ذكرناه سابقا يمكن أن يتناقص بمرور الزمن‪ .‬إذا وصل إلى العدد ‪ 0‬فسنعرف أن اللاعب قد‬
‫خسر‪.‬‬

‫ل مكان‪.‬‬
‫في برامجنا سيكون هناك الـكثير من المتغيرات‪ .‬ستراها في ك ّ‬

‫في لغة الـ‪ ،C‬المتغير يتميز بشيئين ‪:‬‬

‫• قيمة ‪ :‬هو العدد الذي يحو يه‪ 5 ،‬مثلا‪.‬‬

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

‫إعطاء اسم للمتغير‬

‫في لغة البرمجة ‪ C‬كل متغير يجب أن يملك اسما خاصا به‪ .‬ومن أجل متغيرنا الذي يحوي عدد الأرواح المتبقية للاعب‬
‫يمكننا أن نسميه ”‪ ”Number of lives‬أو شيء من هذا القبيل‪.‬‬

‫للأسف توجد بعض الشروط‪ ،‬لا يمكنك تسمية المتغير كيفما شئت ‪:‬‬

‫• لا يجب أن يحتوي الاسم سوى على الحروف الصغيرة والـكبيرة والأرقام ) ‪.( abcABC012‬‬

‫• يجب أن يبدأ الاسم بحرف‪.‬‬

‫• المسافات ممنوعة‪ .‬بدلا من ذلك يمكننا استخدام الحرف المعروف باسم ‪ .( _ ) underscore‬إنه الحرف الخاص‬
‫الوحيد غير الحروف والأرقام الذي يمكن استعماله في اسم متغير‪.‬‬

‫• لا يمكنك استخدام حروف غير الحروف الإنجليز ية‪.‬‬

‫وأخيرا يجب أن تعرف أن لغة ‪ C‬تفر ّق بين الحروف الصغيرة والـكبيرة‪ .‬ولثقافتك‪ ،‬نقول إن ‪ C‬حساسة لحالة الحروف‬
‫)‪ .(Case sensitive‬كمثال‪ ،‬الأسماء ‪ width‬أو ‪ WIDTH‬أو ‪ WiDth‬تعتبر أسماء متغيرات مختلفة‪ ،‬حتى لو كانت تعني‬
‫لنا الأمر نفسه‪.‬‬

‫هذه أمثلة عن أسماء متغيرات صالحة ‪. phoneNumber ، phone_number ، surname ، name ، numberOfLives :‬‬

‫لكل مبرمج طر يقة خاصة في كتابة أسماء المتغيرات‪ .‬خلال هذا الفصل سأر يك طر يقتي ‪:‬‬

‫• أبدأ دائما بحرف صغير‪.‬‬

‫‪53‬‬
‫الفصل ‪ .4‬عالم المتغي ّرات‬

‫ل كلمة‪.‬‬
‫• إن كان في الاسم أكثر من كلمة أضع حرف كبيرا في بداية ك ّ‬

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

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

‫أنواع المتغيرات‬

‫حاسوبنا كما نعلم ليس سوى آلة كبيرة جدا للحساب‪ .‬لا يجيد التعامل سوى مع الأعداد‪ .‬لـكن يوجد أنواع كثيرة من‬
‫الأعداد ‪:‬‬

‫• الأعداد الصحيحة الموجبة )الطبيعية( مثل ‪.7650 ،398 ،45:‬‬

‫• الأعداد العشر ية‪ ،‬أي التي تحوي فاصلة عشر ية ‪.9810.7 ،1.7741 ،75.909 :‬‬

‫• الأعداد الصحيحة السالبة ‪.−916 ،−87 :‬‬

‫• الأعداد العشر ية السالبة ‪.−100.11 ،−76.9 :‬‬

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

‫عندما تصرّح عن متغي ّر فسيكون عليك تحديد نوعه‪ .‬إليك أنواع المتغيرات الأساسية في لغة ‪: C‬‬

‫الحد الأقصى‬ ‫الحد الأدنى‬ ‫النوع‬


‫‪127‬‬ ‫‪−128‬‬ ‫‪signed char‬‬
‫‪32, 767‬‬ ‫‪−32, 768‬‬ ‫‪int‬‬
‫‪2147483647‬‬ ‫‪−2147483648‬‬ ‫‪long‬‬
‫‪1 × 1037 − 1‬‬ ‫‪−1 × 1037‬‬ ‫‪float‬‬
‫‪1 × 1037 − 1‬‬ ‫‪−1 × 1037‬‬ ‫‪double‬‬

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

‫‪54‬‬
‫‪ .2.4‬التصريح عن متغير‬

‫م‬
‫للعلم أن ّي لم أعرض جميع الأنواع هنا‪ ،‬بل الأساسية منها فقط‪.‬‬

‫الأنواع الثلاثة الأولى ) ‪ ( long ، int ، char‬تسمح يتخزين الأعداد الصحيحة )‪.( ... ،4 ،3 ،2 ،1‬‬
‫النوعان الأخيران ) ‪ ( double ، float‬يسمحان بتخزين الأعداد العشر ية )‪.( ... 16.911 ،13.8‬‬

‫سترى أنّنا نتعامل من الأعداد الصحيحة معظم الوقت لأنّها سهلة الإستخدام‪.‬‬

‫‪x‬‬
‫احذر في الأعداد العشر ية من استخدام الفاصلة‪ ،‬حاسوبك لا يستخدم سوى النقطة‪ .‬لذلك لا تكتب ‪ 54, 9‬بدل‬
‫‪! 54.9‬‬

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

‫من ‪ 0‬إلى ‪255‬‬ ‫‪unsigned char‬‬


‫من ‪ 0‬إلى ‪65, 535‬‬ ‫‪unsigned int‬‬
‫من ‪ 0‬إلى ‪4, 294, 967, 295‬‬ ‫‪unsigned int‬‬

‫كما ترى‪ ،‬مشكلة الأنواع عديمة الإشارة هي عدم القدرة على تخزين الأعداد السالبة‪ ،‬لـكن الشيء الإ يجابي هي أنّها توف ّر‬
‫ل نوع موافق )مثلا ‪ signed char‬يتوق ّف عند ‪ ،127‬بينما ‪ unsigned char‬يمتد إلى‬
‫لنا ضعف حجم التخزين لك ّ‬
‫‪.(255‬‬

‫م‬
‫‪unsigned‬‬ ‫ن النوع ‪ char‬قد تم ّ إدراجه إمّا مع الكلمة المفتاحية ‪ ، signed‬أو مع الكلمة المفتاحية‬
‫تلاحظ أ ّ‬
‫لـكن لا يوضع وحده أبدا‪ .‬السبب بسيط ‪ :‬هذا النوع يمكن أن يكون بإشارة أو بدون إشارة حسب الحواسيب‪.‬‬
‫لذلك أنصحكم بتحديد أي واحد منهما تريدون حسب نوع القيمة المراد تخزينها‪.‬‬

‫؟‬
‫لم َِاذا توجد ثلاثة أنواع من المتغيرات الصحيحة ؟ ألا يكفي نوع واحد ؟‬

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

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

‫باختصار‪ ،‬نقوم بالتفر يق خاصّة بين الأعداد الصحيحة و العشر ي ّة‬

‫• بالنسبة للأعداد الصحيحة‪ ،‬نستعمل عادة ‪. int‬‬

‫• بالنسبة للأعداد العشر ي ّة نستعمل عادة ‪. double‬‬

‫‪55‬‬
‫الفصل ‪ .4‬عالم المتغي ّرات‬

‫التصريح عن متغي ّر‬

‫ها قد بدأنا‪ .‬الآن سنقوم بإنشاء برنامج كونسول نسميه ”‪.”variables‬‬


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

‫التصريح سيكون سهلا الآن‪ ،‬يجب علينا فقط أن نت ّبع الترتيب التالي ‪:‬‬

‫‪ .1‬نحدّد نوع المتغي ّر المراد إنشائه‪.‬‬

‫‪ .2‬نترك فراغا‪.‬‬

‫‪ .3‬نحدد اسم المتغي ّر‪.‬‬

‫‪ .4‬و أخيرا‪ ،‬يجب ألا ّ ننسى أبدا وضع الفاصلة المنقوطة‪.‬‬

‫مثلا‪ ،‬إذا أردنا إنشاء المتغي ّر ‪ numberOfLives‬من نوع ‪ int‬سنكتب السطر التالي ‪:‬‬

‫;‪1 int numberOfLives‬‬

‫هذا كل ما في الأمر‪ ،‬وإليك بعض الأمثلة الغبي ّة لملاحظة الشكل ‪:‬‬

‫;‪1 int mathMark‬‬


‫;‪2 double moneySumReceived‬‬
‫;‪3 unsigned int numberOfReadersWhoAreReadingALongVariableName‬‬

‫حسنا‪ ،‬أعتقد أن ّك فهمت المبدأ الآن !‬

‫ما فعلناه للتو يسمّى تصر يحا عن متغي ّر )‪) (Declaring a variable‬هذا مصطلح يجب حفظه(‪ .‬يمكنك وضع التصر يحات‬
‫في بدايات الدوال‪ .‬و بما أن لدينا دالّة وحيدة فقط )الدالة ‪ ( main‬فسنصرّح المتغي ّر هكذا ‪:‬‬

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


‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬
‫‪4‬‬ ‫)(‪int main(int argc, char �argv[]) // Equivalent to int main‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;‪int numberOfLives‬‬
‫‪7‬‬
‫‪8‬‬ ‫;‪return 0‬‬
‫‪9‬‬ ‫}‬

‫إن قمتم بتشغيل البرنامج فستلاحظ بدهشة ‪ ...‬أن ّه لا يقوم بشيء‪.‬‬

‫‪56‬‬
‫‪ .2.4‬التصريح عن متغير‬

‫بعض التوضيحات‬

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

‫لذلك كن متيقنا ًأنه سوف يتم إنشاء المتغيرات بنجاح‪.‬‬

‫م‬
‫معلومة صغيرة ‪ :‬إن كان لديك عدد من المتغي ّرات بنفس النوع تريد التصريح عنها‪ ،‬فمن غير الضروري‬
‫يكفي فصل أسماء المتغي ّرات عن بعضها بفواصل على نفس السطر‪ ،‬مثلا ‪:‬‬ ‫ل متغي ّر‪.‬‬
‫كتابة سطر لك ّ‬
‫هذا ينشئ ثلاثة متغي ّرات أسماؤها على التوالي ‪:‬‬ ‫;‪. int numberOfLives, level, playerAge‬‬
‫‪ level ، numberOfLives‬و ‪. playerAge‬‬

‫و الآن يجب علينا إعطاء قيمة للمتغي ّر الذي أنشأناه‪.‬‬

‫إسناد قيمة إلى متغي ّر‬

‫هذا أمر سهل للغاية فإن أردنا أن نعطي قيمة لمتغيرنا ‪ numberOfLives‬فيكمننا ببساطة كتابة هذا ‪:‬‬

‫;‪1 numberOfLives = 5‬‬

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

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


‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬
‫‪4‬‬ ‫)(‪int main(int argc, char �argv[]) // Equivalent to int main‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;‪int numberOfLives‬‬
‫‪7‬‬ ‫;‪numberOfLives = 5‬‬
‫‪8‬‬
‫‪9‬‬ ‫;‪return 0‬‬
‫‪10‬‬ ‫}‬

‫ل شيء يحدث في الذاكرة‪.‬‬


‫هنا أيضا لا شيء يُعرض على الشاشة‪ ،‬ك ّ‬
‫في أعماق الحاسوب هناك خانة ذاكرة تأخذ القيمة ‪ .5‬أليس شيئا رائعا ؟‬

‫يمكننا أن نستمتع بتغيير القيمة ‪:‬‬

‫‪57‬‬
‫الفصل ‪ .4‬عالم المتغي ّرات‬

‫‪1‬‬ ‫;‪int numberOfLives‬‬


‫‪2‬‬ ‫;‪numberOfLives = 5‬‬
‫‪3‬‬ ‫;‪numberOfLives = 4‬‬
‫‪4‬‬ ‫;‪numberOfLives = 3‬‬

‫ل من رمشة عين يأخذ المتغي ّر‬


‫ن الحاسوب سر يع جدّا ففي أق ّ‬
‫في هذا المثال‪ ،‬المتغير سيأخذ القيمة ‪ 5‬ثم ‪ 4‬ثم ‪ .3‬و بما أ ّ‬
‫القيم ‪ 5‬ثم ّ ‪ 4‬ثم ّ ‪ 3‬ثم ّ ينتهي البرنامج‪.‬‬

‫قيمة متغي ّر جديد‬

‫م يجب أن أطرحه عليك ‪:‬‬


‫هناك سؤال مه ّ‬

‫؟‬
‫عند التصريح عن متغي ّر‪ ،‬أيّ قيمة تكون فيه ؟‬

‫عندما يقرأ الحاسوب هذا السطر ‪:‬‬

‫;‪1 int numberOfLives‬‬

‫فسيحجز مكانا في الذاكرة لهذا المتغي ّر‪ ،‬لـكن ما هي قيمته في هذه اللحظة ؟‬
‫هل يملك قيمة افتراضي ّة ؟ )‪ 0‬مثلا(‪.‬‬

‫الجواب هو لا‪ ،‬لا و لا ثم ّ لا ! لا توجد قيمة افتراضي ّة‪ .‬المكان محجوز لـكنّ القيمة لا ٺتغي ّر‪ .‬لا يتم ّ حذف ما يوجد في‬
‫خانة الذاكرة‪ .‬ستكون قيمة متغي ّرك هي نفس القيمة التي كانت موجودة من قبل في تلك الخانة‪ ،‬و يمكن أن تكون أيّ‬
‫شيء !‬

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

‫;‪1 int numberOfLives = 5‬‬

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

‫‪58‬‬
‫‪ .3.4‬عرض محتوى متغير‬

‫الثوابت‬

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

‫ن قيمتها تبقى ثابتة‪.‬‬


‫هذا النوع الخاص من المتغي ّرات يسمى الثوابت )‪ ،(Constants‬طبعا هذا لأ ّ‬

‫للتصريح عن ثابت‪ ،‬تكفي إضافة كلمة ‪ const‬قبل نوع المتغي ّر‪ .‬و لـكن يجب إعطاء الثابت قيمة بمجر ّد التصريح عنه‪،‬‬
‫لأن ّه لاحقا سيكون الوقت متأخّرا و لن نتمكّن من تغيير قيمته‪.‬‬
‫مثال على التصريح بثابت ‪:‬‬
‫;‪1 const int INITIAL_NUMBER_OF_LIVES = 5‬‬

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

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

‫أخطاء الترجمة تعرض أسفل الشاشة )في المكان الذي أسميه ”منطقة الموت” هل ٺتذكر ؟(‪ ،‬في هذه الحالة سيقوم‬
‫المترجم بعرض رسالة تشبه التالي ‪:‬‬
‫’‪[Warning] assignment of read-only variable ’INITIAL_NUMBER_OF_LIVES‬‬

‫‪ 3.4‬عرض محتوى متغير‬

‫نحن نعرف كيف نعرض نصّ ا على الشاشة باستخدام الدالة ‪. printf‬‬
‫الآن سنتعلم كيف نعرض القيمة الخاصة بالمتغير باسخدام نفس الدالة‪.‬‬

‫سنستخدم الدالة ‪ printf‬بنفس الطر يقة‪ ،‬باستثناء أننا سنقوم بإضافة رمز خاص في المكان الّذي نريد عرض قيمة‬
‫المتغي ّر فيه‪ .‬مثلا ‪:‬‬
‫;)”‪1 printf(”You have %d lives left‬‬

‫هذا الرمز الخاص ما هو إلّا ‪ %‬متبوعا بمحرف ) ’‪ ’d‬في هذا المثال(‪ .‬هذا الحرف يبېّن ما الذي سنقوم بعرضه‪.‬‬
‫’‪ ’d‬تعني أننا سنعرض قيمة متغي ّر من نوع ‪. int‬‬
‫توجد حروف أخرى عديدة‪ ،‬لـكن من أجل التبسيط فسنكتفي بهذه ‪:‬‬

‫النوع المنتظر‬ ‫الشكل‬


‫‪int‬‬ ‫‪%d‬‬
‫‪long‬‬ ‫‪%ld‬‬
‫‪float‬‬ ‫‪%f‬‬
‫‪double‬‬ ‫‪%f‬‬

‫‪59‬‬
‫الفصل ‪ .4‬عالم المتغي ّرات‬

‫م‬
‫ن الشكل المستخدم لعرض ‪ float‬و ‪ double‬هو نفسه‪.‬‬
‫لاحظ أ ّ‬

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

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

‫;)‪1 printf(”You have %d lives left”, numberOfLives‬‬

‫‪ %d‬سيتم ّ استبداله بقيمة المتغي ّر المكتوب بعد الفاصلة‪ ،‬أي ‪. numberOfLives‬‬


‫فلنجر ّب هذا في برنامج ‪:‬‬

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


‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬
‫‪4‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫‪int numberOfLives = 5; // The player has 5 lives in the beginning.‬‬
‫‪7‬‬
‫‪8‬‬ ‫;)‪printf(”You have %d lives left\n”, numberOfLives‬‬
‫‪9‬‬ ‫‪printf(”���� B A M ����\n”); // A big blow on his head.‬‬
‫‪10‬‬ ‫‪numberOfLives = 4; // Life lost‬‬
‫‪11‬‬ ‫;)‪printf(”Sorry, only %d lives are remaining now !\n\n”, numberOfLives‬‬
‫‪12‬‬
‫‪13‬‬ ‫;‪return 0‬‬
‫‪14‬‬ ‫}‬

‫يمكن أن يكون البرنامج قاعدة للعبة فيديو )ينقصه الخيال فقط(‪.‬‬


‫هذا البرنامج يعرض على الشاشة ‪:‬‬

‫‪You have 5 lives left‬‬


‫���� ‪���� B A M‬‬
‫! ‪Sorry, only 4 lives are remaining now‬‬

‫يجب أن تفهم ما الذي يحدث في البرنامج ‪:‬‬

‫‪ .1‬في البداية يملك اللاعب ‪ 5‬أرواح‪ ،‬نعرض هذا في ‪. printf‬‬

‫‪ .2‬بعدها يتلقى اللاعب ضربة على رأسه )في مكان ‪.(BAM‬‬

‫‪ .3‬بعدها لا يبقى له سوى ‪ 4‬أرواح‪ ،‬نعرض هذا أيضا باستخدام ‪. printf‬‬

‫كان ذلك سهلا !‬

‫‪60‬‬
‫‪ .4.4‬استرجاع إدخال‬

‫عرض عدّة متغي ّرات باستخدام ‪ printf‬واحدة‬

‫يمكن عرض قيم متغيرات عديدة بنفس الدالة ‪ . printf‬يكفي فقط أن تضع ‪ %d‬أو ‪ %f‬في الأمكنة المناسبة‪ ،‬ثم‬
‫تحديد المتغي ّرات الموافقة بنفس الترتيب‪ ،‬مفصولة عن بعضها البعض بفواصل‪.‬‬

‫مثال ‪:‬‬

‫‪1 printf(”You have %d lives and you are in the level n° %d”, numberOfLives, level‬‬
‫;)‬

‫!‬
‫‪%d‬‬ ‫يجب عليك تحديد المتغي ّرات بالترتيب الصحيح‪ %d ،‬الأولى توافق المتغي ّر الأوّل ) ‪ ( numberOfLives‬و‬
‫الثانية توافق المتغي ّر الثاني ) ‪ .( level‬إذا أخطأت في الترتيب‪ ،‬فلن يكون للجملة أيّ معنى‪.‬‬

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

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪int numberOfLives = 5, level = 1‬‬
‫‪4‬‬
‫‪5‬‬ ‫‪printf(”You have %d lives and you are in the level n° %d”, numberOfLives,‬‬
‫;)‪level‬‬
‫‪6‬‬
‫‪7‬‬ ‫;‪return 0‬‬
‫} ‪8‬‬

‫و هذا يعرض لك ‪:‬‬

‫‪You have 5 lives and you are in the level n° 1‬‬

‫استرجاع إدخال‬ ‫‪4.4‬‬

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

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

‫هذا ما سنفعله على سبيل المثال ‪:‬‬

‫‪61‬‬
‫الفصل ‪ .4‬عالم المتغي ّرات‬

‫;‪1 int age = 0‬‬


‫;)‪2 scanf(”%d”, &age‬‬

‫علينا وضع ‪ %d‬بين مزدوجتين‪ .‬كما يجب وضع & أمام اسم المتغي ّر الذي سيستقبل القيمة‪.‬‬

‫؟‬
‫و لـكن لماذا نضع & أمام اسم المتغي ّر ؟‬

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

‫‪x‬‬
‫احذر ! يوجد اختلاف صغير في الشكل بين ‪ printf‬و ‪ ! scanf‬لاسترجاع ‪ float‬يجب استخدام الشكل‬
‫”‪ ، ”%f‬أما من أجل النوع ‪ double‬فيتم استخدام ”‪. ”%lf‬‬

‫;‪1 double weight = 0‬‬


‫;)‪2 scanf(”%lf”, &weight‬‬

‫فلنعد إلى برنامجنا‪ .‬عندما يصل هذا الأخير إلى ‪ ، scanf‬يتوقف مؤق ّتا منتظرا المستخدم لـكي يدخل عددا‪ .‬هذا العدد‬
‫يتم حفظه في المتغير ‪ . age‬إليك برنامجا يطلب عمر المستخدم ثم يقوم بعرضه ‪:‬‬
‫)][‪1 int main(int argc, char � argv‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;‪int age = 0‬‬
‫‪4‬‬
‫‪5‬‬ ‫;)” ? ‪printf(”How old are you‬‬
‫‪6‬‬ ‫;)‪scanf(”%d”, &age‬‬
‫‪7‬‬ ‫;)‪printf(”Ah ! so you are %d years old !\n\n”, age‬‬
‫‪8‬‬
‫‪9‬‬ ‫;‪return 0‬‬
‫} ‪10‬‬

‫و هذا ما يعرضه ‪:‬‬


‫‪How old are you ? 20‬‬
‫! ‪Ah ! so you are 20 years old‬‬

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

‫حسنا‪ ،‬لقد فهمت الأساس‪ .‬بفضل الدالة ‪ scanf‬يمكننا البدء في التفاعل مع المستخدم‪.‬‬

‫ليكن في علمك أنه لا مانع من إدخال أي شيء غير عدد صحيح ‪:‬‬

‫• إن قمت بإدخال عدد عشري‪ ،‬مثلا ‪ ،2.9‬فسيتم قطعه تلقائي ّا‪ .‬في هذه الحالة سيتم حفظ العدد ‪ 2‬في المتغي ّر‪.‬‬

‫‪62‬‬
‫ملخّ ص‬

‫• إن قمت بكتابة أحرف عشوائية‪ ،‬مثلا ‪ ،éèdyf :‬فلن ٺتغي ّر قيمة المتغي ّر‪ .‬و الشيء الجي ّد في هذه الحالة هو أننا قمنا بتهيئة‬
‫قيمة المتغير على ‪ .0‬لذلك سيعرض البرنامج ‪ ”Ah ! so you are 0 years old” :‬إن لم يتم الأمر بشكل صحيح‪ .‬لو لم‬
‫نقم بتهيئة المتغي ّر فسيعرض البرنامج عددا عشوائيا !‬

‫ملخّ ص‬

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

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

‫• في الشفرة المصدر ية‪ ،‬المتغي ّرات هي البيانات المحفوظة مؤق ّتا في الذاكرة الحي ّة‪ .‬قيمة هذه البيانات يمكن أن ٺتغي ّر‬
‫أثناء تشغيل البرنامج‪.‬‬

‫• بالمقابل‪ ،‬نطلق اسم الثوابت على بيانات محفوظة في الذاكرة الحي ّة‪ .‬قيمة هذه البيانات لا يمكن أن ٺتغي ّر‪.‬‬

‫• توجد أنواع عديدة من المتغي ّرات‪ ،‬تختلف في حجم الذاكرة التي تشغله‪ .‬بعض الأنواع مثل ‪ int‬تستخدم لتخزين‬
‫عدد صحيح‪ ،‬و أخرى مثل ‪ double‬تخز ّن أعدادا عشر ي ّة‪.‬‬

‫• الدالة ‪ scanf‬تمكّننا من طلب عدد من المستخدم‪.‬‬

‫‪63‬‬
‫الفصل ‪ .4‬عالم المتغي ّرات‬

‫‪64‬‬
‫الفصل ‪5‬‬

‫حسابات سهلة‬

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

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

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

‫الحسابات القاعدية‬ ‫‪1.5‬‬

‫بالرغم من قدرة الجهاز الواسعة إلا أنه في الأساس يتعمد في حساباته على عمليات بسيطة للغاية و هي ‪:‬‬

‫• الجمع‪،‬‬

‫• الطرح‪،‬‬

‫• القسمة‪،‬‬
‫• الضرب‪،‬‬

‫• الترديد )‪) (Modulo‬سأشرح لاحقا ما الّذي يعنيه إذا لم تكن تعرفه الآن(‪.‬‬

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

‫لنبدأ بالعملية الأسهل و هي الجمع‪.‬‬


‫طبعا في الجمع نحتاج الرمز ‪.+‬‬
‫يجب وضع نتيجة الجمع في متغير و لهذا سنقوم بإنشاء متغير اسمه مثلا ‪ result‬من نوع ‪ int‬و نقوم بالحساب ‪:‬‬

‫‪65‬‬
‫الفصل ‪ .5‬حسابات سهلة‬

‫‪1‬‬ ‫;‪int result = 0‬‬


‫‪2‬‬ ‫;‪result = 5 + 3‬‬

‫لا يجب أن تكون محـترفا في الحساب الذهني لتعرف أن النتيجة ستكون ‪ 8‬بعد تشغيل البرنامج‪.‬‬
‫بالطبع البرنامج لن يظهر أية نتيجة باستعمال هذه الشفرة المصدر ية‪ .‬إذا أردت معرفة محتوى المتغير ‪ result‬عليك‬
‫باستعمال الدالة ‪ printf‬التي تجيد كيفية استخدامها جيدا ً الآن ‪:‬‬

‫‪1‬‬ ‫= ‪printf(”5 + 3‬‬ ‫;)‪%d”, result‬‬

‫و هذا ما سيظهر على الشاشة ‪:‬‬

‫‪5 + 3 = 8‬‬

‫و هكذا ننهى عملية الجمع بسهولة‪.‬‬


‫الأمر مماثل بالنسبة للعمليات الأخرى‪ ،‬نحتاج تغيير الرمز ليس إلا ‪:‬‬

‫الرمز‬ ‫العملي ّة‬


‫‪+‬‬ ‫الجمع‬
‫‪-‬‬ ‫الطرح‬
‫*‬ ‫الضرب‬
‫‪/‬‬ ‫القسمة‬
‫‪%‬‬ ‫الترديد‬

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

‫القسمة‬

‫ينجز الحاسوب عملية القسمة بشكل طبيعيّ عندما لا يوجد أي باق‪ .‬مثلا العملية ‪ 6 / 2‬تعطينا النتيجة ‪ ،3‬النتيجة صحيحة‪.‬‬
‫حت ّى الآن‪ ،‬لا مشكلة‪.‬‬

‫ق مثل ‪ ... 5 / 2‬نتوقع أن النتيجة ستكون ‪ ،2.5‬و لـكن أنظر إلى ما تعطيه‬
‫لـكن لو نأخذ الآن عملية قسمة ببا ٍ‬
‫الشفرة ‪:‬‬

‫;‪1 int result = 0‬‬


‫;‪2 result = 5 / 2‬‬
‫;)‪3 printf(”5 / 2 = %d”, result‬‬

‫‪5 / 2 = 2‬‬

‫‪66‬‬
‫‪ .1.5‬الحسابات القاعدية‬

‫هناك مشكل كبير‪ ،‬فنحن نتوق ّع أن نحصل على القيمة ‪ ،2.5‬لـكن الحاسوب أعطى القيمة ‪! 2‬‬

‫هل يا ترى أجهزتنا غبية لهذه الدرجة ؟‬


‫في الواقع‪ ،‬يقوم الجهاز بعملية قسمة صحيحة )إقليدية( أي أنه يحتفظ بالجزء الصحيح فقط الذي هو ‪.2‬‬

‫؟‬
‫‪double‬‬ ‫هه أنا أعرف السبب ! لأن المتغير ‪ result‬الذي استخدمناه هو من نوع ‪ ! int‬لو استخدمنا النوع‬
‫لاستطاع تخزين العدد العشري !‬

‫لا‪ ،‬ليس هذا هو السبب ! جرب نفس الشفرة بتغيير نوع النتيجة إلى ‪ double‬و ستجد بأننا نتحصّ ل على نفس النتيجة‬
‫‪ 2‬لأن طرفا العملية من نوع ‪ int‬فإن الحاسوب سيعيد نتيجة من نوع ‪. int‬‬

‫إن أردنا أن يظهر لنا الجهاز القيمة الصحيحة‪ ،‬يجب أن نغير العددين ‪ 2‬و ‪ 5‬إلى عددين عشر يين كالتالي ‪ 2.0 :‬و‬
‫‪)5.0‬قيمتهما هي نفسها لـكن الجهاز سيعتبرهما عددين عشر يين‪ ،‬و بالتالي هو يظن بأنه يقوم بقسمة عددين عشر يين( ‪:‬‬

‫‪1‬‬ ‫;‪double result = 0‬‬


‫‪2‬‬ ‫;‪result = 5.0 / 2.0‬‬
‫‪3‬‬ ‫;)‪printf(”5 / 2 = %f”, result‬‬

‫‪5 / 2 = 2.500000‬‬

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

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

‫• ‪،5/2 = 2‬‬
‫• ‪،10/3 = 3‬‬
‫• ‪.4/5 = 0‬‬

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

‫إن أردت الحصول على نتيجة عشر ي ّة‪ ،‬فيجب أن يكون حدّا العملية عشر يّين ‪:‬‬

‫• ‪،5.0/2.0 = 2.5‬‬
‫• ‪،10.0/3.0 = 3.33333‬‬
‫• ‪.4.0/5.0 = 0.8‬‬

‫يمكن القول أن الجهاز يطرح على نفسه السؤال ‪” :‬كم يوجد من ‪ 2‬في العدد ‪ 5‬؟” طبعا يوجد ‪ 2‬فقط‪.‬‬

‫و لـكن أين الباقي من العملية ؟ لأنني لما أقول ‪ 5‬هي أثنين من ‪ ، 2‬يبقى ‪ 1‬طبعا‪ ،‬كيف لنا أن نسترجعه ؟‬
‫هنا يتدخل الترديد الذي كلمتك عنه‪.‬‬

‫‪67‬‬
‫الفصل ‪ .5‬حسابات سهلة‬

‫الترديد‬

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

‫كما قلت لك الترديد يمثل بالرمز ‪. %‬‬


‫إليكم بعض الأمثلة ‪:‬‬

‫• ‪،5%2 = 1‬‬
‫• ‪،14%3 = 2‬‬
‫• ‪.4%2 = 0‬‬

‫الترديد ‪ 5 % 2‬هو باقي العملية ‪ 5 / 2‬مما يعني أن الجهاز يقوم بالعملية ‪ 5 = 2 * 2 + 1‬حيث أن ‪ 1‬هو‬
‫الباقي و الذي يقوم بإرجاعه الترديد‪.‬‬

‫نفس الشيء بالنسبة للعملية ‪ ، 14 % 3‬العملية هي ‪) 14 = 3 * 4 + 2‬الترديد يعطي القيمة ‪ .(2‬أخيرا‪ ،‬من‬
‫أجل ‪ ، 4 % 2‬القسمة تامة‪ ،‬فلا يوجد باقي‪ ،‬لهذا يعطي الترديد القيمة ‪.0‬‬

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

‫ل عمليات الحساب القاعدية و تخلصنا من درس الر ياضيات !‬


‫لدي خبر جيد آخر‪ ،‬و هو أننا أتممنا ك ّ‬

‫عمليات على المتغيرات‬

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

‫‪1‬‬ ‫;‪result = number1 + number2‬‬

‫هذا السطر يعمل على جمع المتغيرين ‪ number1‬و ‪ number2‬ثم يخزن النتيجة في المتغير ‪. result‬‬

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

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

‫حاول كتابة هذا البرنامج البسيط‪ ،‬إن ّه سهل و سيكون تدريبا لك !‬

‫إليك الجواب ‪:‬‬

‫‪68‬‬
‫‪ .2.5‬الاختصارات‬

‫)][‪1 int main(int argc, char � argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪int result = 0, number1 = 0, number2 = 0‬‬
‫‪4‬‬
‫‪5‬‬ ‫‪// We request the two numbers from the user :‬‬
‫‪6‬‬
‫‪7‬‬ ‫;)” ‪printf(”Enter the first number :‬‬
‫‪8‬‬ ‫;)‪scanf(”%d”, &number1‬‬
‫‪9‬‬ ‫;)” ‪printf(”Enter the second number :‬‬
‫‪10‬‬ ‫;)‪scanf(”%d”, &number2‬‬
‫‪11‬‬
‫‪12‬‬ ‫‪// We calculate the result :‬‬
‫‪13‬‬
‫‪14‬‬ ‫;‪result = number1 + number2‬‬
‫‪15‬‬
‫‪16‬‬ ‫‪// We display the result on the screen :‬‬
‫‪17‬‬
‫‪18‬‬ ‫;)‪printf(”%d + %d = %d\n”, number1, number2, result‬‬
‫‪19‬‬
‫‪20‬‬ ‫;‪return 0‬‬
‫} ‪21‬‬

‫‪Enter the first number : 30‬‬


‫‪Enter the second number : 25‬‬
‫‪30 + 25 = 55‬‬

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

‫يمكنك التجريب باستخدام أعداد أخرى )يجب ألا تتجاوز الحد الأقصى لتحمّل نوع الـ ‪ ( int‬و سيقوم الحاسوب‬
‫بالحساب بشكل سر يع جدا ً لا يتجاوز بعض أجزاء من المليار من الثانية !‬

‫أنصحك أيضا ًبتجريب العمليات الأخرى )الطرح‪ ،‬القسمة و الضرب( لـكي ٺتدرب‪ .‬لن يكون هذا متعبا إلّا بقدر تغيير‬
‫إشارة أو اثنتين‪ .‬يمكنك أيضا ًإضافة متغير ثالث و جمع ثلاثة متغيرات دفعة واحدة‪ .‬سيشتغل البرنامج دون مشاكل ‪:‬‬
‫;‪1 result = number1 + number2 + number3‬‬

‫‪ 2.5‬الاختصارات‬

‫ل العمليات الموجودة ! بهذه العمليات البسيطة‬


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

‫توجد طرق في لغة الـ‪ C‬تسمح لنا باختصار كتابة بعض العمليات‪ .‬لماذا نستعمل هذه الإختصارات ؟ لأننا نحتاج في‬
‫غالب الأحيان من كتابة عمليات مكر ّرة‪ .‬ستفهم ما أريد قوله حينما ترون ما نسمّيه بالز يادة‪.‬‬

‫‪69‬‬
‫الفصل ‪ .5‬حسابات سهلة‬

‫الز يادة )‪(Incrementation‬‬

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

‫نفترض أن لديك متغيرا يحمل اسم ‪ ، number‬هل تعرف كيف تضيف له ‪ 1‬دون أن تعرف محتواه ؟‬

‫إليك ما يجب عليك فعله ‪:‬‬


‫;‪1 number = number + 1‬‬

‫ما الذي يحصل هنا ؟ نقوم بالحساب ‪ number + 1‬ثم نخزن الناتج في المتغير ‪ ! number‬و منه فإن كان المتغير‬
‫يحمل القيمة ‪ 4‬فهو بعد العملية يحمل القيمة ‪ .5‬لو أنه كان يحمل القيمة ‪ ،8‬فهو الآن يحمل القيمة ‪ ،9‬إلخ‪.‬‬

‫هذه العملية ٺتكرر كثيرا‪ .‬و بما أن المبرمج شخص كسول‪ ،‬سيتعبه أمر كتابة اسم المتغير مرتين في نفس التعليمة )نعم‬
‫هذا أمر متعب !(‪ .‬لهذا تم اختراع اختصار لهذه العملية بما نسميه بـالز يادة )‪ (Incrementation‬التعليمة أسفله تعطي تماما‬
‫نفس نتيجة التعليمة السابقة ‪:‬‬
‫‪1‬‬ ‫;‪number++‬‬

‫هذا السطر له نفس وظيفة السطر السابق الذي كتبناه قبل قليل‪ ،‬أليس مختصرا و قصيرا ؟ إنه يعني ”إضافة ‪ 1‬لمتغير”‪.‬‬
‫يكفي إذا أن نرفق باسم المتغير ‪ number‬الاشارة ‪ +‬مرتين‪ ،‬مع عدم نسيان الفاصلة المنقوطة الخاصة بنهاية التعليمة‪.‬‬

‫هذه العملية ستساعدنا كثيرا مستقبلا لأننا سنضطر للقيام بعملية الز يادة كثيرا‪.‬‬

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

‫الإنقاص )‪(Decrementation‬‬

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

‫إليكم كيف ننقص ‪ 1‬من متغير بالشكل ”الطو يل” ‪:‬‬


‫‪1‬‬ ‫‪number = number‬‬ ‫;‪− 1‬‬

‫و في شكلها المختصر ‪:‬‬


‫‪1‬‬ ‫;‪number−−‬‬

‫ربّما كان بإمكانك تخمين ذلك وحدك ! بدل وضع إشارة ‪ ++‬نضع إشارة ‪ . --‬إذا كان محتوى المتغير هو ‪ 5‬فسيصبح‬
‫بعد الإنقاص يساوي ‪.4‬‬

‫‪70‬‬
‫‪ .3.5‬المكتبة الر ياضياتي ّة‬

‫الاختصارات الأخرى‬

‫توجد اختصارات أخرى تعمل بنفس المنطلق‪ .‬هذه الاختصارات تصلح لكل العمليات القاعدية ‪. % / * - + :‬‬
‫هي تساعدنا على تجنب تكرار نفس اسم المتغير في نفس التعليمة‪.‬‬
‫لضرب محتوى المتغير في ‪ 2‬مثلا نقوم بالتالي ‪:‬‬
‫‪1‬‬ ‫;‪number = number � 2‬‬

‫و بالشكل المختصر ‪:‬‬


‫‪1‬‬ ‫;‪number �= 2‬‬

‫بالطبع إن كان للمتغير القيمة ‪ 5‬قبل إجراء العملية فسيحمل الآن ‪ 10‬بعد هذه التعليمة‪.‬‬
‫بالنسبة لباقي العمليات القاعدية‪ ،‬فالمبدأ نفسه‪ .‬إليك برنامجا صغيرا كمثال ‪:‬‬
‫‪1‬‬ ‫= ‪int number‬‬ ‫;‪2‬‬
‫‪2‬‬ ‫;‪number += 4‬‬ ‫‪//‬‬ ‫‪number = 6‬‬ ‫‪...‬‬
‫‪3‬‬ ‫;‪number −= 3‬‬ ‫‪//‬‬ ‫‪... number‬‬ ‫‪= 3‬‬
‫‪4‬‬ ‫;‪number �= 5‬‬ ‫‪//‬‬ ‫‪... number‬‬ ‫‪= 15‬‬
‫‪5‬‬ ‫;‪number /= 3‬‬ ‫‪//‬‬ ‫‪... number‬‬ ‫‪= 5‬‬
‫‪6‬‬ ‫;‪number %= 3‬‬ ‫‪//‬‬ ‫‪... number‬‬ ‫)‪= 2 (because 5 = 1 � 3 + 2‬‬

‫)لا ٺتذمر فبعض الحسابات الذهني ّة لن تقتل شخصا ً!(‬

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

‫تذك ّر أيضا ًأن عملية الز يادة هي الاختصار الأكثر استعمالاً‪.‬‬

‫المكتبة الر ياضياتي ّة‬ ‫‪3.5‬‬

‫في لغة الـ‪ C‬هناك دائما ما نسميه بالمكتبات القياسية )‪ ،(Standard libraries‬و هي المكتبات التي تستخدم على الدوام‪.‬‬
‫إنّها مكتبات قاعدية تستخدم كثيرا‪.‬‬

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

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


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

‫أعطيك مثالا‪ ،‬لغة الـ‪ C‬لا تحتوي على عملية الأس ! كيف نحسب المربع ؟ يمكنك كتابة العملية ‪ 52‬في برنامجك لـكن‬
‫الجهاز لن يفهمها أبدا ً لأن ّه لا يعرف مالّذي تعنيه هذه‪ ،‬إلا إن قمت بشرح العملية له باستخدام المكتبة الر ياضياتي ّة !‬

‫يمكننا الاستعانة بالدوال الجاهزة في المكتبة الر ياضياتي ّة‪ ،‬لـكن لا تنس كتابة توجيهات المعالج القبلي الخاصة بها في بداية‬
‫كل برنامج ‪:‬‬

‫‪71‬‬
‫الفصل ‪ .5‬حسابات سهلة‬

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

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

‫لديّ ني ّة في عرضها لك الآن‪.‬‬


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

‫‪fabs‬‬

‫هذه الدالة تحسب القيمة المطلقة للرقم‪ ،‬و نرمز لها بالشكل التالي ‪.|x| :‬‬
‫القيمة المطلقة لعدد هو قيمته الموجبة ‪:‬‬

‫• إعطاء العدد ‪ −52‬للدالة يجعلها ترجع القيمة ‪،52‬‬


‫• إعطاء العدد ‪ 52‬للدالة يجعلها تعيد ‪.52‬‬

‫باختصار‪ ،‬تعيد دائما العدد الموجب الموافق لما أعطيته لها‪.‬‬


‫;‪double absolute = 0, number = −27‬‬
‫‪absolute = fabs(number); // absolute = 27‬‬

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

‫م‬
‫هناك دالة اخرى مماثلة لـ ‪ fabs‬تسمى ‪ ، abs‬نجدها في ‪. stdlib.h‬‬
‫إنّها تعمل بنفس طر يقة الأولى إلا أنها تعمل مع الأعداد الصحيحة‪ ،‬فهي تعيد ‪. int‬‬

‫‪ceil‬‬

‫هذه الدالّة تعطي أول عدد طبيعي بعد العدد العشري الذي نعطيه لها‪ .‬يمكن القول أنها تدوّر العدد دائما إلى العدد الذي‬
‫يعلوه في الجزء الصحيح‪.‬‬
‫لو نعطيها مثلا العدد ‪ 26.512‬فستعطينا العدد ‪.27‬‬
‫الدالة تعمل بنفس الطر يقة و تعيد ‪ double‬أيضا ‪:‬‬
‫;‪1 double above = 0, number = 52.71‬‬
‫‪2 above = ceil(number); // Above = 53‬‬

‫‪72‬‬
‫‪ .3.5‬المكتبة الر ياضياتي ّة‬

‫‪floor‬‬

‫هذه عكس السابقة‪ ،‬تعيد العدد الأقل مباشرة في الجزء الصحيح‪.‬‬


‫إذا أعطيتها مثلا العدد ‪ 37.91‬تعطيني العدد ‪ ،37‬يعني الجزء الصحيح‪.‬‬

‫‪pow‬‬

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

‫;)‪1 pow(number, power‬‬

‫كمثال ‪ 2” :‬قوة ‪) ”3‬ال ّتي نكتبها عادة ‪ 23‬على الحاسوب( هو الحساب ‪ 2 ∗ 2 ∗ 2‬الّذي يعطي النتيجة ‪: 8‬‬

‫;‪1 double result = 0, number = 2‬‬


‫‪2 result = pow(number, 3); // result = 2ˆ3 = 8‬‬

‫‪sqrt‬‬

‫هذه الدالة تحسب الجذر التربيعي لعدد‪ ،‬تعيد ‪. double‬‬

‫;‪1 double result = 0, number = 100‬‬


‫‪2 result = sqrt(number); // Result = 10‬‬

‫‪tan ،cos ،sin‬‬

‫إنّها الدوال المثلثية الثلاث الشهيرة‪.‬‬


‫طر يقة عملها هي نفسها‪ ،‬تعيد ‪. double‬‬

‫هذه الدوال تأخذ قيما بـالراديان )‪.(Radians‬‬

‫‪atan ،acos ،asin‬‬

‫و هي الدوال ‪ ،arctan ،arccos ،arcsin‬دوال مثلثية أخرى‪.‬‬


‫تُستخدم بنفس الطر يقة و تعيد ‪ double‬أيضا‪.‬‬

‫‪exp‬‬

‫هذه الدالة تحسب قيمة الدالة الأسي ّة ذات الأساس ‪ e‬لعدد معين‪.‬‬
‫تعيد ‪. double‬‬

‫‪73‬‬
‫الفصل ‪ .5‬حسابات سهلة‬

‫‪log‬‬

‫هذه الدالة تحسب اللوغار يتم النيبيري لعدد معين‪) .‬الّذي نرمز له أيضا بـ”‪(”ln‬‬

‫‪log10‬‬

‫هذه الدالة تحسب اللوغار يتم ذو الأساس ‪ 10‬لعدد‪.‬‬

‫ملخّ ص‬

‫• الحاسوب ما هو سوى آلة حاسبة كبيرة ‪ :‬كل ما يجيد فعله هو القيام بالعملي ّات‪.‬‬

‫• العمليات التي يجيدها الحاسوب قاعدية جدا ‪ :‬الضرب‪ ،‬القسمة‪ ،‬الجمع‪ ،‬الطرح و الترديد )باقي القسمة(‪.‬‬

‫• بالإمكان إجراء عمليات على المتغيرات‪ ،‬الحاسوب سر يع جدا ً في هذا النوع من العمليات‪.‬‬

‫• الز يادة )‪ (Incrementation‬هي عملية إضافة الرقم ‪ 1‬إلى متغير‪ .‬نكتبها ‪. variable++‬‬

‫• الإنقاص )‪ (Decrementation‬هي عملية طرح الرقم ‪ 1‬من متغير‪ .‬نكتبها ‪. variable--‬‬

‫• لز يادة عدد العمليات التي يمكن للحاسوب القيام بها‪ ،‬نستعمل المكتبة الر ياضي ّاتي ّة )أي‬
‫>‪( #include <math.h‬‬

‫كالأس و الجذر و اللوغار يثم و غيرها‪.‬‬


‫ّ‬ ‫• تحتوي هذه المكتبة على دوال ر ياضي ّاتي ّة متقدّمة‬

‫‪74‬‬
‫الفصل ‪6‬‬

‫الشروط )‪(Conditions‬‬

‫لقد رأينا فيما سبق بأن هناك العديد من لغات البرمجة‪ .‬بعضها متشابه ‪ :‬الـكثير منها مستلهم من الـ‪.C‬‬

‫في الواقع‪ ،‬لغة الـ‪ُ C‬أنشئت منذ زمن طو يل‪ ،‬و هذا جعلها نموذجا لل ّغات الجديدة‪.‬‬

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

‫‪else ... if‬‬ ‫الشرط‬ ‫‪1.6‬‬

‫الشروط تسمح لنا بأن نقوم باختبارات على المتغيرات‪ .‬مثلا يمكننا القول‪” ،‬إن كان المتغي ّر ‪ machine‬يساوي ‪،50‬‬
‫يجب أن نقوم بكذا و كذا‪ .‬و لـكن من المؤسف عدم إمكانية اختبار سوى المساواة ! يجب أيضا اختبار ما إن كان المتغي ّر‪،‬‬
‫أقل من ‪ ،50‬أقل أو يساوي ‪ ،50‬أكبر‪ ،‬أكبر أو يساوي ‪ ...‬لا تقلق‪ ،‬في الـ‪ C‬كل شيء مُع َد !‬

‫لدراسة الشرط ‪ if ... else‬يجب أن نتبع المخطط التالي ‪:‬‬

‫• نتعلم بعض الرموز قبل البدأ‪،‬‬

‫• الشرط ‪، if‬‬

‫• الشرط ‪، else‬‬

‫• الشرط ‪، else if‬‬


‫• كثير من الشروط في مرة واحدة‪،‬‬

‫• بعض الأخطاء لنتجنبها‪.‬‬

‫قبل أن نرى كيف نكتب شرطا من النوع ‪ if ... else‬في الـ‪ ،C‬يجب أن تعرف ثلاثة رموز أساسية‪ .‬هذه‬
‫الرموز ضرور ية لإنشاء الشروط‪.‬‬

‫‪75‬‬
‫الفصل ‪ .6‬الشروط )‪(Conditions‬‬

‫بعض الرموز للتعل ّم‬

‫الجدول التالي يحوي رموز لغة الـ‪ C‬ال ّتي يجب حفظها عن ظهر قلب ‪:‬‬

‫المعنى‬ ‫الرمز‬
‫يساوي‬ ‫==‬

‫أكبر‬ ‫>‬

‫أصغر‬ ‫<‬

‫أصغر أو يساوي‬ ‫=<‬

‫أكبر أو يساوي‬ ‫=>‬

‫لا يساوي‬ ‫=!‬

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

‫‪ if‬بسيط‬

‫فلنبدأ‪ ،‬لنقم باختبار بسيط‪ ،‬يقول للحاسوب ‪ :‬إن كان المتغير يساوي كذا فلنقم بكذا‪.‬‬

‫في الإنجليز ي ّة‪ ،‬كلمة ”إذا” تُت َرجم إلى ‪ . if‬و هذا ما الّذي نستخدمه في لغة الـ‪ C‬لإنشاء اختبار‪.‬‬
‫أكتب إذن ‪ if‬ثم الأقواس و في داخلها الشرط‪.‬‬

‫ل ما يوجد بين الحاضنتين سيتم ّ تشغيله فقط في حالة تحقق الشرط‪.‬‬


‫بعد ذلك افتح حاضنة { و اغلقها لاحقا } ‪ .‬ك ّ‬

‫هذا يعطينا إذن ‪:‬‬


‫)‪1 if (/� Your condition �/‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫‪// Instructions to be executed‬‬
‫} ‪4‬‬

‫في مكان التعليق ”‪ ”Your condition‬سنكتب شرطا للتحقّق من متغي ّر‪.‬‬


‫كمثال سنحاول أن نقوم بختبار متغي ّر ‪ age‬لاحتواء العمر‪ .‬سنختبر ما إن كان المستعمل راشدا أم قاصرا استنادا إلى إذا‬
‫ما كان عمره يساوي أو أكبر من ‪: 18‬‬
‫)‪1 if (age >= 18‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;)”! ‪printf(”You are major‬‬
‫} ‪4‬‬

‫‪76‬‬
‫‪else ... if‬‬ ‫‪ .1.6‬الشرط‬

‫=> يعني ”أكبر أو يساوي”‪ ،‬كما رأينا في الجدول السابق‪.‬‬

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

‫جرّب هذه الشفرة‬

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

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

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


‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;‪int age = 20‬‬
‫‪6‬‬
‫‪7‬‬ ‫)‪if (age >= 18‬‬
‫‪8‬‬ ‫{‬
‫‪9‬‬ ‫;)”‪printf(”You are major !\n‬‬
‫‪10‬‬ ‫}‬
‫‪11‬‬ ‫;‪return 0‬‬
‫‪12‬‬ ‫}‬

‫هنا‪ age ،‬يساوي ‪ 20‬إذن سيتم عرض ”! ‪.”You are major‬‬


‫جرّب تغيير القيمة إلى ‪ 15‬مثلا‪ ،‬سيصبح الشرط خاطئا ًو بالتالي لن يُعرض شيء هذه المر ّة‪.‬‬

‫استخدم هذه الشفرة لتجريب الأمثلة اللاحقة في هذا الفصل‪.‬‬

‫مسألة نظافة‬

‫ل شيء على نفس السطر‪ .‬مثال ‪:‬‬


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

‫} ;)”! ‪1 if (age >= 18) { printf(”You are major‬‬

‫و على الرغم من أن ّه ممكن‪ ،‬فهذا أمر غير منصوح به مطلقا‪.‬‬


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

‫‪77‬‬
‫الفصل ‪ .6‬الشروط )‪(Conditions‬‬

‫حاول عرض شفرتك بنفس طر يقتي ‪ :‬حاضنة على سطر‪ ،‬ثم ّ التعليمات )مسبوقة بجدولة )‪ ،((tabulation‬ثم ّ حاضنة‬
‫الإغلاق على سطر آخر‪.‬‬

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

‫الـ‪ else‬لـكي نقول ‪ :‬و إلا‬

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

‫هذا يشبه ما يلي ‪ :‬إن كان المتغير يساوي كذا فلنقم بكذا‪ ،‬و إلا فلنقم بكذا‪.‬‬

‫يكفي أن نضيف ‪ else‬بعد الحاضنة الأخيرة لأوامر الـ ‪ ، if‬مثلا ‪:‬‬

‫‪1‬‬ ‫)‪if (age >= 18‬‬


‫‪2‬‬ ‫{‬
‫‪3‬‬ ‫;)”! ‪printf(”You are major‬‬
‫‪4‬‬ ‫}‬
‫‪5‬‬ ‫{ ‪else‬‬
‫‪6‬‬ ‫;)”! ‪printf(”You are minor‬‬
‫‪7‬‬ ‫}‬

‫الأمر سهل ‪ :‬إن كان المتغي ّر ‪ age‬أكبر من أو يساوي ‪ 18‬سنكتب على الشاشة ”! ‪ ،”You are major‬و إن لم يكن‬
‫الأمر كذلك فسنكتب ”! ‪.”You are minor‬‬

‫‪ if else‬لـكي نقول ‪ :‬و إلا فإذا‬

‫سنرى كيف نقوم بـ”إذا” و ”إلّا”‪ .‬يمكن أيضا ًالقيام بـ”و إلا ّ فإذا” للقيام باختبار آخر في حالة ما إذا لم ينجح الأوّل‪ .‬هذا‬
‫الاختبار يوضع بين ‪ if‬و ‪. else‬‬

‫يمكن ترجمة ذلك بما يلي ‪ :‬إن كان المتغير يساوي كذا‪ ،‬فافعل كذا و إلا فإن كان يساوي كذا‪ ،‬فافعل كذا و إلا‬
‫)جميع الحالات المتبقية(‪ ،‬افعل كذا‪.‬‬

‫الترجمة بلغة ‪: C‬‬

‫)‪1 if (age >= 18‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;)”! ‪printf(”You are major‬‬
‫} ‪4‬‬

‫‪78‬‬
‫‪else ... if‬‬ ‫‪ .1.6‬الشرط‬

‫‪5‬‬ ‫)‪else if (age > 4‬‬


‫‪6‬‬ ‫{‬
‫‪7‬‬ ‫;)”‪printf(”Well you’re not too young anyway...‬‬
‫‪8‬‬ ‫}‬
‫‪9‬‬ ‫‪else‬‬
‫‪10‬‬ ‫{‬
‫‪11‬‬ ‫;)”‪printf(”Aga gaa aga gaaa‬‬
‫‪12‬‬ ‫! ‪// Baby language, you can’t understand‬‬
‫‪13‬‬ ‫}‬

‫الجهاز يقوم بالاختبارات التالية بالترتيب ‪:‬‬

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

‫• إن لم يكن الشرط الأول محققا )يعني أننا في الـ ‪ ( else if‬سيختبر الشرط الثاني‪ ،‬إن كان هذا الأخير محققا فإنه‬
‫سينفذ التعليمات الموجودة بين الحاضنتين الثانيتين‪.‬‬
‫• في حالة ما لم يكن الشرط الأول محققا و لا حتى الثاني‪ ،‬فإنه سينفذ التعليمات الموجودة بين الحاضنتين الأخيرتين‪.‬‬

‫م‬
‫الـ ‪ else‬و الـ ‪ else if‬ليسلا ضرور يّين‪ .‬لإنشاء شرط‪ ،‬فقط ‪ if‬هو الضروري )هذا منطقي‪ ،‬و إلّا فكيف‬
‫سيكون هناك شرط !(‪.‬‬

‫لا حظ أن ّه يمكننا أن نستعمل الـكم ّ الّذي نريده من الـ ‪. else if‬‬

‫مرة واحدة‬
‫عدّة شروط في ّ‬

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

‫المعنى‬ ‫الرمز‬
‫و‬ ‫&&‬

‫أو‬ ‫||‬
‫لا )عكس الشرط(‬ ‫!‬

‫الاختبار بالواو )‪(and‬‬

‫لنختبر إن كان العمر في نفس الوقت أكبر من ‪ 18‬و أقل من ‪ 25‬يجب كتابة ‪:‬‬
‫)‪1 if (age > 18 && age < 25‬‬

‫&& يعنيان ”و”‪ .‬بالعربية هذا يعني ‪” :‬إذا كان العمر أكبر من ‪ 18‬و إذا كان العمر أصغر من ‪.”25‬‬

‫‪79‬‬
‫الفصل ‪ .6‬الشروط )‪(Conditions‬‬

‫الاختبار بالأو )‪(or‬‬

‫لاستخدام ”أو”‪ ،‬يجب كتابة الرمزين || ‪ .‬لكتابة الرمز | ‪ ،‬نضغط في لوحة المفاتيح على ‪) 6 + Alt Gr‬باعتبار أن‬
‫تخطيط لوحة المفاتيح ‪ AZERTY‬فرنسي(‪.‬‬

‫فلنعتبر برنامجا بسيطا‪ ،‬يختبر ما إن كان الشخص قادرا ً على فتح حساب بنكي أو لا‪ .‬فلـكي يكون قادرا ً على ذلك يجب‬
‫أن لا يكون شاب ّا كثيرا )فلنقل مثلا‪ ،‬ليس تحت ‪ 30‬سنة( أو لديه الـكثير من المال ‪:‬‬
‫‪1‬‬ ‫)‪if (age > 30 || argent > 100000‬‬
‫‪2‬‬ ‫{‬
‫‪3‬‬ ‫;)”! ‪printf(”Welcome to PicsouBank‬‬
‫‪4‬‬ ‫}‬
‫‪5‬‬ ‫‪else‬‬
‫‪6‬‬ ‫{‬
‫‪7‬‬ ‫;)”! ‪printf(”Get out of my sight, miserable‬‬
‫‪8‬‬ ‫}‬

‫الاختبار سيكون ناجحا فقط إذا كان الشخص بعمر أكبر من ‪ 30‬سنة‪ ،‬أو على الأقل يملك مبلغا ًأكبر من ‪100000‬‬
‫دينار مثلاً‪.‬‬

‫الإختبار بـلا )‪(not‬‬

‫الرمز الموافق لهذا الاختبار هو علامة التعجّ ب )!(‪ .‬في علوم الحاسوب‪ ،‬علامة التعجّ ب تعني ”لا”‪ .‬يجب وضع هذه‬
‫العلامة من أجل عكس الشرط لقول ”إن لم يكن هذا صحيحا” ‪:‬‬
‫))‪1 if (!(age < 18‬‬

‫يمكن أن نترجم هذا إلى ”إن كان الشخص غير قاصر”‪ .‬لو حذفنا ! فسيعكس المعنى ‪” :‬إن كان الشخص قاصرا”‪.‬‬

‫بعض الأخطاء التي يرتكبها المبتدؤون‬

‫==‬ ‫لا تنس الإشارتين‬

‫مثلما قلت سابقا‪ ،‬لـكي نختبر ما إن كان العمر يساوي ‪ 18‬نكتب ‪:‬‬
‫)‪1 if (age == 18‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;)”! ‪printf(”You have just become major‬‬
‫} ‪4‬‬

‫لا تنس وضع علامتي ”يساوي” داخل ‪ ، if‬هكذا ‪. == :‬‬

‫إن وضعت علامة = واحدة فإن المتغير ‪ age‬سيأخذ القيمة ‪) 18‬مثلما تعلمنا ذلك سابقا في فصل المتغيرات(‪ .‬نحن‬
‫نريد أن نختبر قيمة المتغير و ليس تغييرها فاحذر ! الـكثير يقع في هذا الخطأ و بالتأكيد فبرامجهم لن تعمل بالشكل المطلوب‬
‫!‬

‫‪80‬‬
‫‪ .2.6‬المتغيرات المنطقية )‪ ،(Booleans‬أساس الشروط‬

‫الفاصلة المنقوطة الزائدة‬

‫بعض المبتدئين يقومون بإضافة فاصلة منقوطة في نهاية سطر الـ ‪ ، if‬و لـكن الـ ‪ if‬هو شرط و لا نضع فاصلة منقوطة‬
‫إلّا في نهاية تعليمة‪ .‬الشفرة التالية لن تعمل كما هو متوق ّع لأن ّه يوجد ; في نهاية الشرط‪.‬‬
‫‪1 if (age == 18); // Note the semicolon that mustn’t be here‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;)”! ‪printf(”You are just major‬‬
‫} ‪4‬‬

‫المتغيرات المنطقية )‪ ،(Booleans‬أساس الشروط‬ ‫‪2.6‬‬

‫سندخل الآن في تفاصيل عمل شرط من نوع ‪. if... else‬‬


‫في الشروط نعتمد كثيرا ً على نوع آخر من أنواع البيانات نسميه الـ‪.boolean‬‬

‫بعض الأمثلة البسيطة للفهم‬

‫سنبدأ ببعض التجارب قبل أن نقدّم هذا المفهوم‪ .‬إليك شفرة مصدر ي ّة بسيطة جدّا أقترح عليك تجريبها ‪:‬‬
‫‪1‬‬ ‫;)‪if (1‬‬
‫‪2‬‬ ‫{‬
‫‪3‬‬ ‫;)”! ‪printf(”It is true‬‬
‫‪4‬‬ ‫}‬
‫‪5‬‬ ‫‪else‬‬
‫‪6‬‬ ‫{‬
‫‪7‬‬ ‫;)”! ‪printf(”It is false‬‬
‫‪8‬‬ ‫}‬

‫النتيجة ‪:‬‬
‫! ‪It is true‬‬

‫؟‬
‫لـكن‪ ،‬لم نضع شرطا داخل ‪ ، if‬هذا عدد فقط‪ .‬مالّذي يعنيه ؟ لا معنى لهذا‪.‬‬

‫بلى‪ ،‬ستفهم‪ .‬استبدل الواحد بالـصفر ‪:‬‬


‫‪1‬‬ ‫;)‪if (0‬‬
‫‪2‬‬ ‫{‬
‫‪3‬‬ ‫;)”! ‪printf(”It is true‬‬
‫‪4‬‬ ‫}‬
‫‪5‬‬ ‫‪else‬‬
‫‪6‬‬ ‫{‬
‫‪7‬‬ ‫;)”! ‪printf(”It is false‬‬
‫‪8‬‬ ‫}‬

‫‪81‬‬
‫الفصل ‪ .6‬الشروط )‪(Conditions‬‬

‫النتيجة ‪:‬‬

‫! ‪It is false‬‬

‫حاول الآن استبدال الصفر بأي عدد صحيح‪ ،‬مثل ‪ ،−36 ،−10 ،226 ،15 ،4‬إلخ‪ .‬النتيجة دائما ‪.”It is true !” :‬‬

‫إذا وضعنا ‪ ،0‬الاختبار سيعتبر خاطئا‪ ،‬أمّا إن وضعنا ‪ 1‬أو أيّ عدد آخر‪ ،‬فالاختبار سيكون صحيحا‪.‬‬ ‫ملخّ ص اختباراتنا ‪:‬‬

‫الشرح واجب‬

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

‫مثلا بالنسبة لهذا الشرط الّذي هو ‪: age >= 18‬‬

‫)‪1 if (age >= 18‬‬

‫لنفرض أن ‪ age‬كان ‪ .23‬إذن‪ ،‬الشرط صحيح‪ ،‬و الحاسوب ”سيستبدل” بطر يقة ما ‪ age >= 18‬بـ‪.1‬‬
‫بعد ذلك‪ ،‬سيحصل الحاسوب )في رأسه( على )‪ . if (1‬عندما يكون ‪ ،1‬كما رأينا‪ ،‬فسيعتبره صحيحا‪.‬‬

‫بالمثل‪ ،‬إذا كان الشرط خاطئا‪ ،‬فسيستبدل ‪ age >= 18‬بـ‪ ،0‬فالشرط خاطئ‪ ،‬و الحاسوب سيقرأ تعليمات ‪. else‬‬

‫فلنجرب مع متغير‬

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

‫‪1‬‬ ‫;‪int age = 20‬‬


‫‪2‬‬ ‫;‪int major = 0‬‬
‫‪3‬‬
‫‪4‬‬ ‫;‪major = age >= 18‬‬
‫‪5‬‬ ‫;)‪printf(”Major equals : %d\n”, major‬‬

‫كما تلاحظ فإن الشرط ‪ age >= 18‬أعطى ‪ 1‬و بالتالي فإن المتغير ‪ major‬أخذ القيمة ‪ 1‬يعني صحيح‪ .‬يمكنك‬
‫التأكد بالـ ‪. printf‬‬

‫قم بنفس الاختبار مع وضع ‪ . age == 10‬هذه المر ّة‪ major ،‬سيكون ‪.0‬‬

‫‪82‬‬
‫‪ .2.6‬المتغيرات المنطقية )‪ ،(Booleans‬أساس الشروط‬

‫المتغير ‪ major‬متغير منطقي‬

‫تذك ّر هذا جي ّدا ‪ :‬نقول عن متغي ّر نجعله يأخذ القيم ‪ 1‬و ‪ 0‬أن ّه متغي ّر منطقيّ )‪.(boolean‬‬

‫و هذا كما يلي ‪:‬‬

‫• ‪ = 0‬خطأ‪،‬‬
‫• ‪ = 1‬صحيح‪.‬‬

‫ل الأعداد الأخرى تعني صحيح )كما رأينا سابقا(‪ .‬و لـكن من أجل تبسيط الأمور‪ ،‬لن‬
‫في الواقع ‪ 0‬يمث ّل خطأ‪ ،‬و ك ّ‬
‫نستخدم سوى ‪ 0‬و ‪ 1‬لقول إن كان الشرط صحيحا أو خاطئا‪.‬‬

‫في لغة ‪ C‬لا يوجد نوع ‪. boolean‬‬


‫على الرغم من ذلك‪ ،‬يمكننا تغطية ذلك باستعمال النوع ‪. int‬‬

‫المتغيرات المنطقية في الشروط‬

‫عادة‪ ،‬نقوم باختبار ‪ if‬على متغي ّر منطقيّ ‪:‬‬

‫‪1‬‬ ‫;‪int major = 1‬‬


‫‪2‬‬ ‫)‪if (major‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;)”! ‪printf(”You are major‬‬
‫‪5‬‬ ‫}‬
‫‪6‬‬ ‫{ ‪else‬‬
‫‪7‬‬ ‫;)”! ‪printf(”You are minor‬‬
‫‪8‬‬ ‫}‬

‫بما أن المتغير ‪ major‬يساوي ‪ 1‬فإن الشرط محقق و بالتالي سيظهر على الشاشة ‪.”You are major !” :‬‬

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

‫يمكننا أيضا أن نجد شفرة كالتالي ‪:‬‬

‫)‪1 if (major && boy‬‬

‫يعني ”إن كان الشخص راشدا و ذكراً”‪ .‬في هذه الحالة‪ ،‬المتغير ‪ boy‬أيضا هو متغير منطقي‪ ،‬يساوي ‪ 1‬إذا كان‬
‫الشخص ولدا ً و ‪ 0‬إذا كان بنتاً‪ .‬أعتقد أنك فهمت المقصود‪.‬‬

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

‫‪83‬‬
‫الفصل ‪ .6‬الشروط )‪(Conditions‬‬

‫؟‬
‫سؤال صغير ‪ :‬إن كتبنا ‪ if (major == 1) :‬فسيعمل‪ ،‬أليس كذلك ؟‬

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

‫إذا كان متغي ّرك يحمل عددا )مثل العمر(‪ ،‬قم باختبار من الشكل )‪. if (variable == 1‬‬ ‫تذك ّر إذن ‪:‬‬

‫بالمقابل‪،‬إذا كان المتغي ّر منطقيا ّ‪) ،‬أي إمّا ‪ 0‬أو ‪ 1‬لقول صحيح أو خطأ(‪ ،‬قم باختبار من الشكل )‪. if (variable‬‬

‫‪switch‬‬ ‫‪ 3.6‬الشرط‬

‫الشرط ‪ if ... else‬الّذي رأيناه‪ ،‬هو أكثر أنواع الشروط استخداما‪.‬‬


‫في الواقع‪ ،‬لا يوجد ‪ 36‬طر يقة لكتابة شرط في الـ‪ .C‬الـ ‪ if ... else‬يُمكّن من التعامل مع ك ّ‬
‫ل الحالات‪.‬‬

‫مع ذلك‪ if ... else ،‬يبدو قليلا ‪ ...‬تكرار ي ّا‪ .‬فلنَر َ هذا المثال ‪:‬‬

‫‪1‬‬ ‫)‪if (age == 2‬‬


‫‪2‬‬ ‫{‬
‫‪3‬‬ ‫;)”! ‪printf(”Hello baby‬‬
‫‪4‬‬ ‫}‬
‫‪5‬‬ ‫)‪else if (age == 6‬‬
‫‪6‬‬ ‫{‬
‫‪7‬‬ ‫;)”! ‪printf(”Hello kid‬‬
‫‪8‬‬ ‫}‬
‫‪9‬‬ ‫)‪else if (age == 12‬‬
‫‪10‬‬ ‫{‬
‫‪11‬‬ ‫;)”! ‪printf(”Hello young‬‬
‫‪12‬‬ ‫}‬
‫‪13‬‬ ‫)‪else if (age == 16‬‬
‫‪14‬‬ ‫{‬
‫‪15‬‬ ‫;)”! ‪printf(”Hello teenager‬‬
‫‪16‬‬ ‫}‬
‫‪17‬‬ ‫)‪else if (age == 18‬‬
‫‪18‬‬ ‫{‬
‫‪19‬‬ ‫;)”! ‪printf(”Hello adult‬‬
‫‪20‬‬ ‫}‬
‫‪21‬‬ ‫)‪else if (age == 68‬‬
‫‪22‬‬ ‫{‬
‫‪23‬‬ ‫;)”! ‪printf(”Hello grandpa‬‬
‫‪24‬‬ ‫}‬
‫‪25‬‬ ‫‪else‬‬
‫‪26‬‬ ‫{‬
‫‪27‬‬ ‫;)”‪printf(”I have no words ready for your age‬‬
‫‪28‬‬ ‫}‬

‫‪84‬‬
‫‪switch‬‬ ‫‪ .3.6‬الشرط‬

‫‪switch‬‬ ‫بناء‬

‫المبرمجون يكرهون الأشياء التكرار ي ّة‪ ،‬لقد رأينا ذلك من قبل‪.‬‬

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

‫)‪1 switch (age‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪case 2 :‬‬
‫‪4‬‬ ‫;)”! ‪printf(”Hello baby‬‬
‫‪5‬‬ ‫;‪break‬‬
‫‪6‬‬
‫‪7‬‬ ‫‪case 6 :‬‬
‫‪8‬‬ ‫;)”! ‪printf(”Hello kid‬‬
‫‪9‬‬ ‫;‪break‬‬
‫‪10‬‬
‫‪11‬‬ ‫‪case 12 :‬‬
‫‪12‬‬ ‫;)”! ‪printf(”Hello young‬‬
‫‪13‬‬ ‫;‪break‬‬
‫‪14‬‬
‫‪15‬‬ ‫‪case 16 :‬‬
‫‪16‬‬ ‫;)”! ‪printf(”Hello teenager‬‬
‫‪17‬‬ ‫;‪break‬‬
‫‪18‬‬
‫‪19‬‬ ‫‪case 18 :‬‬
‫‪20‬‬ ‫;)”! ‪printf(”Hello adult‬‬
‫‪21‬‬ ‫;‪break‬‬
‫‪22‬‬
‫‪23‬‬ ‫‪case 68 :‬‬
‫‪24‬‬ ‫;)”! ‪printf(”Hello grandpa‬‬
‫‪25‬‬ ‫;‪break‬‬
‫‪26‬‬
‫‪27‬‬ ‫‪default :‬‬
‫‪28‬‬ ‫;)”‪printf(”I have no words ready for your age‬‬
‫} ‪29‬‬

‫استلهم من مثالي لإنشاء ‪ switch‬الخاصة بك‪ .‬نستخدمها نادرا‪ ،‬لـكنّها عملي ّة كثيرا لأنّها تجعلنا نكتب قدرا أقل‬
‫)قليلا( من الشفرة‪.‬‬

‫الفكرة هي أن نكتب )‪ switch (myVariable‬لـكي نقول ‪ :‬سنقوم باختبار حول قيمة المتغير ‪. myVariable‬‬
‫ثم نقوم بفتح حاضنتين نغلقهما في الأسفل‪.‬‬

‫بعد ذلك‪ ،‬في داخل الحاضنتين‪ ،‬ٺتعامل مع كل الحالات ‪... ، case 45 ، case 5 ، case 4 ، case 2 :‬‬

‫يجب عليك في نهاية كل حالة‪ ،‬أن تضع التعليمة ;‪ . break‬إن لم تفعل فإن الحاسوب سينتقل لقراءة التعليمات‬
‫الموالية التي هي من المفروض محجوزة للحالات الأخرى ! أي أن التعليمة ;‪ break‬تجـبر الحاسوب على الخروج من‬
‫الحاضنتين‪.‬‬

‫‪85‬‬
‫الفصل ‪ .6‬الشروط )‪(Conditions‬‬

‫في النهاية‪ default ،‬هي مثابة الـ ‪ else‬الذي تعرفه جيدا ً الآن‪ .‬أي أنه إن لم يساوي المتغير أي من الحالات‬
‫المذكورة فإن الحاسوب يقوم بتشغيل الحالة ‪. default‬‬

‫الـ‪switch‬‬ ‫التحكم بـقائمة بواسطة‬

‫الـ ‪ switch‬يُستخدم بكثرة لإنشاء قوائم في الـكونسول‪.‬‬


‫أعتقد أن ّه الوقت المناسب لبعض التطبيق !‬

‫إلى العمل !‬

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

‫=== ‪=== Menu‬‬


‫‪1. Royal Cheese‬‬
‫‪2. Mc Deluxe‬‬
‫‪3. Mc Bacon‬‬
‫‪4. Big Mac‬‬
‫? ‪Your choice‬‬

‫ستقوم بإظهار القائمة بالإستعانة بـ ‪ printf‬و ستستعمل ‪ scanf‬لاسترجاع اختيار المستعمل في‬ ‫مهمّتك )إن قبلتها( ‪:‬‬
‫متغي ّر ‪ choice‬و من ثم ّ تستعمل ‪ switch‬لتختبر الإختيار الذي قام به المستعمل‪ .‬و في النهاية حسب الحالة ستقول‬
‫للمستعمل ماذا اختار‪ ،‬هل اختار ”‪ ”Big Mac‬أو ”‪ ”Mc Bacon‬مثلا‪.‬‬

‫هي ّا‪ ،‬إلى العمل !‬

‫تصحيح‬

‫هذا هو التصحيح )أتمن ّى أن ّك قد وجدته بنفسك !( ‪:‬‬

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


‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;‪int choiceMenu‬‬
‫‪6‬‬
‫‪7‬‬ ‫;)”‪printf(”=== Menu ===\n\n‬‬
‫‪8‬‬ ‫;)”‪printf(”1. Royal Cheese\n‬‬
‫‪9‬‬ ‫;)”‪printf(”2. Mc Deluxe\n‬‬
‫‪10‬‬ ‫;)”‪printf(”3. Mc Bacon\n‬‬
‫‪11‬‬ ‫;)”‪printf(”4. Big Mac\n‬‬

‫‪86‬‬
‫ الشروط المختصرة‬: ‫ الثلاثيات‬.4.6

12 printf(”\nYour choice ? ”);


13 scanf(”%d”, &choiceMenu);
14
15 printf(”\n”);
16
17 switch (choiceMenu)
18 {
19 case 1:
20 printf(”You have chosen the Royal Cheese. Good choice !”);
21 break;
22 case 2:
23 printf(”You have chosen the Mc Deluxe. Berk, too much sauce...”
);
24 break;
25 case 3:
26 printf(”You have chosen the Mc Bacon. Well, it goes ;o)”);
27 break;
28 case 4:
29 printf(”You have chosen the Big Mac. You must be very hungry!”)
;
30 break;
31 default:
32 printf(”You haven’t specified a correct number. You shall not
eat anything!”);
33 break;
34 }
35
36 printf(”\n\n”);
37
38 return 0;
39 }

! ‫و هذا هو العمل‬

ّ ‫ عندما تبرمج يجب عليك التفكير في ك‬،‫ ! في الواقع‬switch ‫ في نهاية الـ‬default ‫أتمنى أنك لم تنس الـ‬
،‫ل الحالات‬
.‫ و هذا ليس ما كنت تنتظره‬،”‫ أو حت ّى ”مرحبا‬10 ‫ فسيأتيك دائما أحمق ليكتب‬،4 ‫ و‬1 ‫فحت ّى لو طلبت عددا بين‬

‫ يجب عليك اختبار حالة‬.‫ لأن ّه يمكنه إدخال أيّ شيء‬،‫ عليك دائما أن تكون حذرا و لا ٺثق في المستخدم‬،‫باختصار‬
. if ‫ في الشروط التي تنشئها بالـ‬else ‫ أيضا ًأو‬default

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

‫ الشروط المختصرة‬: ‫الثلاثيات‬ 4.6

87
‫الفصل ‪ .6‬الشروط )‪(Conditions‬‬

‫توجد طر يقة أخرى لإنشاء الشروط‪ ،‬أكثر ندرة‪.‬‬

‫هذا ما يعرف بـالعبارات الثلاثي ّة‪.‬‬


‫في الواقع‪ ،‬هي تشبه ‪ ، if ... else‬باستثناء أنها تكتب على سطر واحد !‬

‫مرتين ‪ :‬الأولى باستخدام‬


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

‫شرط ‪ else ... if‬معروف‬

‫ن لدينا متغي ّرا منطقي ّا ‪ major‬يأخذ القيمة ‪ 1‬إن كان الشخص راشدا ً و ‪ 0‬إن كان قاصراً‪.‬‬
‫فلنفرض أ ّ‬
‫نريد تغيير قيمة المتغي ّر ‪ age‬حسب المتغي ّر المنطقي‪ ،‬نضع فيها ‪ 18‬إن كان راشدا و ‪ 17‬إن كان قاصرا‪ .‬أوافقك الرأي‬
‫على أن ّه مثال غبيّ‪ ،‬و لـكن ّه يسمح لنا بفهم آلية عمل العبارات الثلاثي ّة‪.‬‬

‫هكذا يكون باستعمال الـ ‪: if ... else‬‬

‫)‪1 if(major‬‬
‫‪2‬‬ ‫;‪age = 18‬‬
‫‪3 else‬‬
‫‪4‬‬ ‫;‪age = 17‬‬

‫م‬
‫تلاحظ أنني قمت بنزع الحاضنتين لأنه لا توجد سوى تعليمة واحدة داخل ‪ if‬و أخرى داخل ‪ ، else‬كما قد‬
‫شرحت لك سابقا‪.‬‬

‫نفس الشرط بالثلاثي ّة‬

‫هذه الشفرة تقوم تماما بنفس عمل الشفرة السابقة‪ ،‬لـكنّها مكتوبة بالشكل الثلاثي ‪:‬‬

‫;‪1 age = (major)? 18 : 17‬‬

‫الثلاثيات تسمح‪ ،‬بسطر واحد‪ ،‬بتغيير قيمة متغي ّر حسب شرط معيّن‪ .‬هنا‪ ،‬الشرط هو ‪ major‬ببساطة‪ ،‬و لـكن كان‬
‫بالإمكان وضع أي شرط مهما كان طوله‪ .‬تريد مثالا آخرا ؟‬
‫;‪. authorization = (age >= 18) ? 1 : 0‬‬

‫علامة الإستفهام تمكّن من قول ”هل هو راشد ؟”‪ .‬إن كان الأمر كذلك فنضع القيمة ‪ 18‬في ‪ ، age‬و إلّا )النقطتان‬
‫تعنيان ‪ else‬هنا( نضع القيمة ‪.17‬‬

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

‫‪88‬‬
‫ملخّ ص‬

‫ملخّ ص‬

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

‫• الكلمات المفتاحية ‪ else if ، else ، if‬تعني ‪-‬على التوالي‪ -‬إذا‪ ،‬و إلا ‪ ،‬و إلا فإذا‪ .‬يمكننا استعمال‬
‫‪ else if‬بالقدر الذي نريد‪.‬‬

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

‫• الـ ‪ switch‬هي بديل للـ ‪ ، if‬تستخدم عندما نريد دراسة حالات قيمة متغي ّر ما‪ .‬يسمح بجعل الشفرة أكثر‬
‫وضوحا ً إذا كنت تريد اختبار عدد معتبر من الحالات‪ .‬إذا كنت تستخدم كثيرا من ‪ else if‬فهذه عادة ما‬
‫تكون إشارة إلى أن ‪ switch‬تكون أحسن في جعل الشفرة أسهل للقراءة‪.‬‬

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

‫‪89‬‬
‫الفصل ‪ .6‬الشروط )‪(Conditions‬‬

‫‪90‬‬
‫الفصل ‪7‬‬

‫الحلقات التكرار ية )‪(Loops‬‬

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

‫استرخ ‪ :‬هذا الفصل سيكون سهلاً‪ .‬لقد تعرفنا سابقا على ما تعنيه المتغي ّرات المنطقية )‪ (booleans‬و الشروط‬
‫)‪ (conditions‬في الفصل السابق‪ ،‬و بذلك كنا قد تخلصنا من عمل كبير‪ .‬من الآن فصاعدا ً ستكون الأمور سلسة أكثر و‬
‫لن يكون في العمل التطبيقي القادم الـكثير من المشاكل‪.‬‬

‫فلننتهز الفرصة‪ ،‬لأننا لن نتأخر في الدخول في الجزء الثاني من الكتاب‪ .‬سيكون من الجي ّد لك أن تنتبه !‬

‫ماهي الـحلقة ؟‬ ‫‪1.7‬‬

‫كما قلت سابقا ً‪ :‬هي عبارة عن تعليمة تسمح لنا بتكرار نفس التعليمات عدة مرات‪.‬‬

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

‫‪while‬‬ ‫•‬
‫‪do ... while‬‬ ‫•‬
‫‪for‬‬ ‫•‬

‫في جميع الحالات يبقى المخطط نفسه ‪:‬‬

‫و هذا ما سيحصل بالترتيب ‪:‬‬

‫‪91‬‬
‫الفصل ‪ .7‬الحلقات التكرار ية )‪(Loops‬‬

‫‪ .1‬الجهاز يقرأ التعليمات من الأعلى إلى الأسفل كالعادة‪.‬‬

‫‪ .2‬ما إن يصل لنهاية الحلقة يتوجه نحو التعليمة الأولى‪.‬‬

‫‪ .3‬يعيد بعدها قراءة التعليمات كلها من الأعلى إلى الأسفل‪.‬‬

‫‪ .4‬يصل لنهاية الحلقة و يعاود الرجوع للأول من جديد و هكذا ‪...‬‬

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

‫و هنا نجد ‪ ...‬الشروط ! فعندما ننشئ حلقة نقوم دائما بتعر يف شرطها‪ .‬هذا الشرط يعني ”كر ّر الحلقة دون توقف‬
‫مادام هذا الشرط صحيحا”‪.‬‬

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

‫‪while‬‬ ‫الحلقة‬ ‫‪2.7‬‬

‫هكذا نشكل حلقة ‪: while‬‬

‫)‪1 while (/� Condition �/‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪// The instructions that we want to repeat‬‬
‫} ‪4‬‬

‫لا يوجد أبسط من هذا‪ .‬الكلمة ‪ while‬تعني ”مادام” ‪ ،‬لذا نقول للجهاز‪ :‬مادام الشرط صحيحا‪ ،‬كرر التعليمات‬
‫المتواجدة بين الحاضنتين‪.‬‬

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

‫‪1‬‬ ‫;‪int entredNumber = 0‬‬


‫‪2‬‬ ‫)‪while (entredNumber != 47‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;)” ! ‪printf(”Enter the number 47‬‬
‫‪5‬‬ ‫;)‪scanf(”%d”, &entredNumber‬‬
‫‪6‬‬ ‫}‬

‫أنظر إلى الاختبار الذي قمت به‪ ،‬للعلم أنني تعمدت الخطأ ثلاث مرات ‪:‬‬

‫‪Enter‬‬ ‫‪the‬‬ ‫‪number‬‬ ‫‪47‬‬ ‫!‬ ‫‪10‬‬


‫‪Enter‬‬ ‫‪the‬‬ ‫‪number‬‬ ‫‪47‬‬ ‫!‬ ‫‪27‬‬
‫‪Enter‬‬ ‫‪the‬‬ ‫‪number‬‬ ‫‪47‬‬ ‫!‬ ‫‪40‬‬
‫‪Enter‬‬ ‫‪the‬‬ ‫‪number‬‬ ‫‪47‬‬ ‫!‬ ‫‪47‬‬

‫‪92‬‬
‫‪while‬‬ ‫‪ .2.7‬الحلقة‬

‫يتوقف البرنامج بعد إدخال العدد ‪.47‬‬


‫هذه الحلقة ‪ while‬ستتكرر مادام المستعمل لم يدخل العدد ‪ ،47‬لا يوجد أسهل من هذا‪.‬‬

‫الآن لنجعل الأمر ممتعا ًأكثر ‪ :‬نريد من الحلقة أن ٺتوقف بعد عدد معين من التكرارات‪.‬‬
‫لهذا سنستعين بمتغير ‪ counter‬الذي سيأخذ القيمة ‪ 0‬في بداية البرنامج ثم نقوم بز يادته‪ ،‬هل ٺتذكر ما قلناه في الفصل‬
‫السابق حول الز يادة )‪ (incrementation‬؟ التي تنص على إضافة ‪ 1‬لمتغير حينما نكتب ‪. variable++‬‬

‫إقرأ جيدا الشفرة المصدر ية التالية و حاول التمعن فيها و فهمها ‪:‬‬

‫‪1‬‬ ‫;‪int counter = 0‬‬


‫‪2‬‬ ‫)‪while (counter < 10‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;)”‪printf(”Hello !\n‬‬
‫‪5‬‬ ‫;‪counter++‬‬
‫‪6‬‬ ‫}‬

‫النتيجة ‪:‬‬

‫‪Hello‬‬ ‫!‬
‫‪Hello‬‬ ‫!‬
‫‪Hello‬‬ ‫!‬
‫‪Hello‬‬ ‫!‬
‫‪Hello‬‬ ‫!‬
‫‪Hello‬‬ ‫!‬
‫‪Hello‬‬ ‫!‬
‫‪Hello‬‬ ‫!‬
‫‪Hello‬‬ ‫!‬
‫‪Hello‬‬ ‫!‬

‫البرنامج يكرر عشر مرات العبارة ”! ‪”Hello‬‬

‫؟‬
‫كيف يعمل هذا بالتحديد ؟‬

‫‪ .1‬في البداية لدينا متغير ‪ counter‬مهي ّأ على القيمة الإبتدائية ‪.0‬‬

‫‪ .2‬الحلقة ‪ while‬تأمر بالتكرار مادامت قيمة المتغير ‪ counter‬أصغر من ‪ .10‬بما أن قيمة المتغير ‪ counter‬هي‬
‫‪ 0‬في البداية‪ ،‬فإننا ندخل في الحلقة لأن الشرط محقق‪.‬‬

‫‪ .3‬نقوم بإظهار الرسالة ”! ‪ ”Hello‬على الشاشة باستخدام الدالة ‪. printf‬‬

‫‪ .4‬نقوم بز يادة قيمة المتغير ‪ counter‬بفضل التعليمة ;‪ . counter++‬كان المتغير ‪ counter‬يحمل القيمة ‪،0‬‬
‫أمّا الآن فهو يحمل القيمة ‪.1‬‬

‫‪ .5‬نصل لنهاية الحلقة )حاضنة الإغلاق( ‪ :‬نعيد العملية من جديد و نتأكد ما إن كان الشرط محققا ًأي ما إن كانت‬
‫قيمة المتغير أصغر من ‪ 10‬؟ في هذه الحالة نعم لأن المتغير ‪ counter‬يحمل القيمة ‪ 1‬و هي أصغر من ‪ 10‬إذا سنمر ّ‬
‫بنفس التعليمة داخل الحاضنتين‪.‬‬

‫‪93‬‬
‫الفصل ‪ .7‬الحلقات التكرار ية )‪(Loops‬‬

‫و هكذا دواليك‪ counter ،‬تصبح ‪ 9 ،8 ، ... ،3 ،2 ،1‬ثم ّ ‪ .10‬حينها يصيح الشرط ‪ counter < 10‬غير‬
‫محقق‪ ،‬و عندها نخرج من الحلقة‪.‬‬

‫ل مرة بواحد‪ .‬يمكننا التأكّد بـ ‪: printf‬‬


‫و يمكننا ملاحظة أن قيمة المتغير ‪ counter‬تزيد في ك ّ‬
‫‪1‬‬ ‫;‪int counter = 0‬‬
‫‪2‬‬ ‫)‪while (counter < 10‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;)‪printf(”counter = %d\n”, counter‬‬
‫‪5‬‬ ‫;‪counter++‬‬
‫‪6‬‬ ‫}‬

‫‪counter‬‬ ‫=‬ ‫‪0‬‬


‫‪counter‬‬ ‫=‬ ‫‪1‬‬
‫‪counter‬‬ ‫=‬ ‫‪2‬‬
‫‪counter‬‬ ‫=‬ ‫‪3‬‬
‫‪counter‬‬ ‫=‬ ‫‪4‬‬
‫‪counter‬‬ ‫=‬ ‫‪5‬‬
‫‪counter‬‬ ‫=‬ ‫‪6‬‬
‫‪counter‬‬ ‫=‬ ‫‪7‬‬
‫‪counter‬‬ ‫=‬ ‫‪8‬‬
‫‪counter‬‬ ‫=‬ ‫‪9‬‬

‫ل شيء !‬
‫إن كنت قد فهمت المثال السابق فقد فهمت ك ّ‬
‫يمكنك الاستمتاع بتجربة أعداد أكبر من ‪ ) 10‬مثلا ‪ 100‬أو أي عدد آخر(‪ .‬كان هذا سيفيدني كثيرا في صغري لكتابة‬
‫مرة‪.‬‬
‫العقوبات ال ّتي كان يجب عليّ تكرارها مائة ّ‬

‫احذر من الحلقات غير المنتهية !‬

‫عندما تنشئ حلقة فـتأكّد دائما من جعلها قادرة على التوقف في لحظة معينة ! إن كان الشرط محققا ً دائماً‪ ،‬فلن يتوقف‬
‫البرنامج أبدا ً ! و هذا مثال على حلقة غير منتهية ‪:‬‬
‫)‪1 while (1‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;)”‪printf(”Infinite loop\n‬‬
‫} ‪4‬‬

‫تذكر ما قلناه بخصوص القيم المنطقية )‪ : (booleans‬فصحيح = ‪ 1‬و خاطئ = ‪ .0‬هنا‪ ،‬الشرط محقق دائما ًو بهذا فإن‬
‫البرنامج سيستمر ّ في كتابة العبارة ”‪ ”Infinite loop‬بدون توق ّف !‬

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

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

‫‪94‬‬
‫‪do ... while‬‬ ‫‪ .3.7‬الحلقة‬

‫‪do ... while‬‬ ‫‪ 3.7‬الحلقة‬

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

‫‪1‬‬ ‫;‪int counter = 0‬‬


‫‪2‬‬ ‫‪do‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;)”‪printf(”Hello !\n‬‬
‫‪5‬‬ ‫;‪counter++‬‬
‫‪6‬‬ ‫;)‪} while (counter < 10‬‬

‫ما الذي تغير ؟‬


‫هذا أمر بسيط فالحلقة ‪ while‬يمكن أن لا يتم تشغيلها أبدا ً إذا كان الشرط غير محقق منذ البداية‪ .‬فمثلا لو وضعنا القيمة‬
‫‪ 50‬كقيمة ابتدائية للمتغير ‪ counter‬فإن الشرط لن يكون محققا ًو لذلك لا ندخل في الحلقة أبدا‪.‬‬
‫بالنسبة للحلقة ‪ do ... while‬فالأمر مختلف ‪ :‬هذه الحلقة ٺتكرر على الأقل مرة واحدة‪ .‬في الواقع‪ ،‬اختبار الشرط يتم‬
‫في نهاية الحلقة كما يمكنك الملاحظة‪ .‬فلو أعطينا القيمة الإبتدائية ‪ 50‬للمتغير ‪ counter‬فإن التعليمة بين الحاضنتين سيتم‬
‫تشغيلها مرة واحدة‪.‬‬

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

‫!‬
‫هناك استثناء في الحلقة ‪ do ... while‬يغفل عنه الـكثير من المبتدئين‪ ،‬و هو وجود فاصلة منقوطة في نهاية‬
‫الحلقة ! لا تنس وضع واحدة بعد الـ ‪ while‬و إلا ستظهر لك أخطاء أثناء الترجمة !‬

‫‪for‬‬ ‫‪ 4.7‬الحلقة‬

‫نظر ياً‪ ،‬الحلقة ‪ while‬تلبي رغبتنا في أي نوع كان من التكرارات‪.‬‬


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

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

‫و كما قلت لك فالحلقة ‪ for‬ماهي إلا عبارة عن طر يقة أخرى لكتابة الحلقة ‪ while‬التي رأيناها قبل قليل‪.‬‬
‫من المثال السابق لدينا ‪:‬‬

‫‪1‬‬ ‫;‪int counter = 0‬‬


‫‪2‬‬ ‫)‪while (counter < 10‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;)”‪printf(”Hello !\n‬‬
‫‪5‬‬ ‫;‪counter++‬‬
‫‪6‬‬ ‫}‬

‫‪95‬‬
‫الفصل ‪ .7‬الحلقات التكرار ية )‪(Loops‬‬

‫الشفرة المصدر ية المكافئة باستعمال الحلقة ‪: for‬‬

‫‪1‬‬ ‫;‪int counter‬‬


‫‪2‬‬ ‫)‪for (counter = 0; counter < 10; counter++‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;)”‪printf(”Hello !\n‬‬
‫‪5‬‬ ‫}‬

‫ما الاختلاف ؟‬

‫• تلاحظ أننا لم نهي ّئ المتغير ‪ counter‬على قيمة ابتدائية تساوي ‪ 0‬عند إنشائه )لـكن ّه كان بإمكاننا فعل ذلك(‪.‬‬

‫• هناك الـكثير من التفاصيل داخل القوسين بعد ‪ ، for‬سنفصل ذلك لاحقاً‪.‬‬


‫• لا توجد التعليمة ;‪ counter++‬في الحلقة‪.‬‬

‫فلنهتم بما يوجد بين القوسين فهو كل ما يهم في الحلقة ‪ . for‬هناك ثلاث تعليمات مختلفة نفصل فيما بينها بفواصل‬
‫منقوطة‪.‬‬

‫• الأولى هي التهيئة )‪ : (initialisation‬هذه التعليمة تعمل على تحضير المتغير ‪ . counter‬في حالتنا‪ ،‬تهيئته بالقيمة‬
‫‪.0‬‬
‫• الثانية هي الشرط )‪ : (condition‬كما رأينا في الحلقة ‪ while‬فإن الشرط يخـبرنا ما إن كان يجب تكرار الحلقة أم‬
‫لا‪ .‬مادام الشرط محققا ًفإن الحلقة تستمر في تكرار التعليمات‪.‬‬

‫• أخيرا‪ ،‬الز يادة )‪ : (incrementation‬هذه التعليمة يتم تنفيذها بعد كل التعليمات أي في نهاية الدورة الواحدة و هي‬
‫ل مرة‪ .‬في غالب الأحيان نقوم بالز يادة‪ ،‬لـكن يمكننا أن نستعمل الإنقاص‬
‫تقوم بتعديل قيمة المتغير ‪ counter‬في ك ّ‬
‫)مثلا ‪ ،( counter--‬كما يمكننا القيام بعملي ّات أخرى‪ ،‬مثلا ز يادة قيمة العداد بـ‪.( counter+=2 ) 2‬‬

‫في النهاية‪ ،‬نلاحظ أن حلقة ‪ for‬ماهي سوى حلقة كتابة مختصرة‪ .‬تعل ّم كيفية استعمالها لأنك ستحتاجها بكثرة !‬

‫ملخّ ص‬

‫• الحلقات هي تعليمات تسمح لنا بتكرار سلسسلة من التعليمات عدة مرات‪.‬‬

‫ل منها يُستعمل في الحالة ال ّتي يكون فيها‬


‫• توجد كثير من الحلقات ‪ for ، while :‬و ‪ . do ... while‬ك ّ‬
‫مناسبا أكثر‪.‬‬

‫• الحلقة ‪ for‬هي التي سنستعملها بكثرة في التطبيقات‪ .‬نقوم فيها غالبا ًبز يادات أو إنقاصات للمتغيرات‪.‬‬

‫‪96‬‬
‫الفصل ‪8‬‬

‫عمل تطبيقي ‪” :‬أكثر أو أقل”‪ ،‬لعبتك الأولى‬

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

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

‫تجهيزات و نصائح‬ ‫‪1.8‬‬

‫مبدأ عمل البرنامج‬

‫ل شيء‪ ،‬سأشرح عمل برنامجنا‪ .‬إن ّه لعبة صغيرة نسمّيها ”أكثر أو أقل”‪.‬‬
‫قبل ك ّ‬

‫مبدأ عمل اللعبة هو التالي‪.‬‬

‫‪ .1‬الجهاز يختار عشوائيا عددا من ‪ 1‬إلى ‪.100‬‬

‫‪ .2‬يطلب منك أن تخمن عددا و بالتالي ستختار بدورك عددا من ‪ 1‬إلى ‪.100‬‬

‫‪ .3‬يقوم الجهاز بمقارنة العدد الذي كتبته بالعدد ”الغامض” الّذي حصل عليه عشوائي ّا‪ ،‬ثم يقول لك ما إن كان العدد‬
‫الغامض أصغر أو أكبر من العدد الذي اخترته أنت‪.‬‬
‫‪ .4‬ثم يقوم الجهاز بإعادة طلب العدد منك‪.‬‬

‫‪ .5‬ثم يقول لك ما إن كان العدد الغامض أصغر أو أكبر من العدد الذي اخترته أنت‪.‬‬

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

‫و الهدف من اللعبة هو أن تجد العدد الغامض في أقل عدد ممكن من المحاولات بالطبع‪.‬‬

‫و هذه ”لقطة شاشة” مما يجب أن تكون عليه اللعبة في طور التنفيذ ‪:‬‬

‫‪97‬‬
‫الفصل ‪ .8‬عمل تطبيقي ‪” :‬أكثر أو أقل”‪ ،‬لعبتك الأولى‬

‫‪What’s the‬‬ ‫‪number ? 50‬‬


‫! ‪Greater‬‬
‫‪What’s the‬‬ ‫‪number ? 75‬‬
‫! ‪Greater‬‬
‫‪What’s the‬‬ ‫‪number ? 85‬‬
‫! ‪Lesser‬‬
‫‪What’s the‬‬ ‫‪number ? 80‬‬
‫! ‪Lesser‬‬
‫‪What’s the‬‬ ‫‪number ? 78‬‬
‫! ‪Greater‬‬
‫‪What’s the‬‬ ‫‪number ? 79‬‬
‫‪Bravo, you‬‬ ‫!!! ‪have found the mysterious number‬‬

‫اختيار عدد عشوائي‬

‫؟‬
‫لـكن كيف يختار الجهاز عددا عشوائيا ً؟ أنا لا أجيد فعل هذا !‬

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

‫في الواقع‪ ،‬لـ”محاولة” الحصول على عدد عشوائيّ‪ ،‬يجب القيام بحسابات معقّدة للحاسوب‪ ،‬و هذا ما يعود في النهاية إلى‬
‫القيام بحسابات !‬

‫لـكي نفعل هذا‪ ،‬نميز حلين‪.‬‬

‫• إما أن نطلب من المستعمل أن يقوم باختيار عددٍ عشوائي في بداية اللعبة بواسطة الدالة ‪ . scanf‬هذا يستلزم‬
‫لاعبين ‪ :‬واحد يقوم بإدخال العدد الغامض و الآخر يحاول تخمينه بعد ذلك‪.‬‬

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

‫طبعا سنقوم بالاختيار الثاني و لـكن إن أردت تجريب الحل الأول فيما بعد فلا شيء يمنعك‪.‬‬

‫لاختيار عدد عشوائي نستعمل الدالة ‪ . rand‬و هي دالة تسمح باختيار عدد بطر يقة عشوائية‪ .‬لـكن ّنا نريد عددا ً بين‬
‫‪ 1‬و ‪ 100‬مثلا )لأننا إن لم نعرف الحدود فستصبح اللعبة معقّدة جدّا(‪.‬‬

‫للقيام بذلك‪ ،‬سنستخدم العبارة التالية )لا يمكنني أن أطلب منك تخمينها هي أيضا ً!( ‪:‬‬

‫;))‪1 srand(time(NULL‬‬
‫;‪2 mysteriousNumber = (rand() % (MAX − MIN + 1)) + MIN‬‬

‫‪98‬‬
‫‪ .1.8‬تجهيزات و نصائح‬

‫السطر الأول )الذي بـه ‪ ( srand‬يسمح بتهيئة مولَد الأرقام العشوائية‪ .‬نعم الأمر صعب قليلا ً كما أخبرتك‪.‬‬
‫‪ mysteriousNumber‬هو متغير سيحمل العدد العشوائي المختار‪.‬‬

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

‫‪ MAX‬و ‪ MIN‬هما ثابتان‪ ،‬الأول هو العدد الأقصى )‪ (100‬و الثاني هو العدد الأدنى )‪ ،(1‬و أنصحك بتعر يف‬
‫الثابتين في بداية البرنامج هكذا ‪:‬‬

‫;‪1 const int MAX = 100, MIN = 1‬‬

‫تضمين المكتبات اللازمة‬

‫كي يشتغل برنامجك بشكل صحيح‪ ،‬يجب أن تقوم بتضمين المكتبات ‪ stdlib‬و ‪ stdio‬و ‪) time‬الأخيرة تستعمل‬
‫من أجل الأعداد العشوائية(‪.‬‬
‫يجب إذن أن يبدأ برنامجك بالتالي ‪:‬‬

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


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

‫يكفي شرحا !‬

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

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

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

‫حان وقت العمل ! بالتوفيق !‬

‫‪99‬‬
‫ لعبتك الأولى‬،”‫ ”أكثر أو أقل‬: ‫ عمل تطبيقي‬.8 ‫الفصل‬

! ‫التصحيح‬ 2.8

.‫توقف ! من الآن سأجمع الأوراق‬

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

”‫تصحيح ”أكثر أو أقل‬

: ‫هذا هو التصحيح الذي أقترحه‬

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <time.h>
4 int main ( int argc, char�� argv )
5 {
6 int mysteriousNumber = 0, inputNumber = 0;
7 const int MAX = 100, MIN = 1;
8 // Generation of random number
9 srand(time(NULL));
10 mysteriousNumber = (rand() % (MAX − MIN + 1)) + MIN;
11
12 do
13 {
14 // We request the number
15 printf(”What’s the number? ”);
16 scanf(”%d”, &inputNumber );
17 // We compare between inputNumber and mysteriousNumber
18 if (mysteriousNumber > inputNumber )
19 printf(”Greater !\n\n”);
20 else if (mysteriousNumber < inputNumber )
21 printf(”Lesser !\n\n”);
22 else
23 printf (”Bravo, you have found the mysterious number !!!\n\n”);
24 } while (inputNumber != mysteriousNumber);
25 return 0;
26 }

‫الملف التنفيذي و الشفرة المصدر ية‬

: ‫ اللعبة و الشفرة المصدر ية متوف ّران للتنز يل من هنا‬،‫لمن يريد ذلك‬

https://openclassrooms.com/uploads/fr/ftp/mateo21/plusoumoins.zip (7 Ko)

100
‫‪ .2.8‬التصحيح !‬

‫م‬
‫الملف التنفيذي ) ‪ ( .exe‬مترجم للويندوز‪ ،‬لذا إن كنت تستخدم نظام تشغيل آخر فيجب عليك إعادة ترجمة‬
‫البرنامج ليشتغل عندك‪.‬‬

‫يوجد مجلّدان‪ ،‬واحد فيه الملف التنفيذي )مترجم للويندوز‪ ،‬أكر ّر( و آخر فيه ملفات الشفرة المصدر ية‪.‬‬

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

‫الشرح‬

‫سأشرح الآن الشفرة المصدر ي ّة الخاصة بي بدء ً من الأعلى ‪:‬‬

‫توجيهات المعالج القبلي‬

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

‫المتغيرات‬

‫لم نحتج الـكثير منها‪.‬‬


‫ن لـكي نسمح للجهاز بتوليد عدد عشوائي‬
‫احتجنا فقط واحدا للعدد الذي يدخله المستعمل ) ‪ ( inputNumber‬و ثا ٍ‬
‫) ‪.( mysteriousNumber‬‬

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

‫الحلقة‬

‫لقد اخترت الحلقة ‪ . do ... while‬نظر ي ّا‪ ،‬حلقة ‪ while‬بسيطة يمكنها أن تقوم بالمهمة أيضا‪ ،‬لـكن ّي وجدت بأن‬
‫استخدام ‪ do ... while‬منطقيّ أكثر‪.‬‬

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

‫ل دورة من الحلقة نطلب من المستعمل إدخال عدد‪ .‬نقوم بتخزين القيمة في المتغير ‪. inputNumber‬‬
‫في ك ّ‬
‫بعدها‪ ،‬نقارن المتغير السابق بالمتغير ‪ . mysteriousNumber‬هنا نجد ثلاثة احتمالات ‪:‬‬

‫‪101‬‬
‫الفصل ‪ .8‬عمل تطبيقي ‪” :‬أكثر أو أقل”‪ ،‬لعبتك الأولى‬

‫• يكون العدد الغامض أكبر من العدد الذي أدخله المستعمل و بالتالي تظهر على الشاشة العبارة ”! ‪.”Greater‬‬

‫• يكون العدد الغامض أصغر من العدد الذي أدخله المستعمل و بالتالي تظهر على الشاشة العبارة ”! ‪.”Lesser‬‬

‫• يكون العدد الغامض لا أكبر و لا أصغر من الّذي أدخله المستعمل‪ ،‬أي أن ّه مسا ٍو بالضرورة ! و بالتالي تظهر على‬
‫الشاشة العبارة ”! ‪.”Bravo, you have found it‬‬

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

‫أفكار للتحسين‬

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

‫لقد عرفت أن ّه لديّ خيال واسع‪ ،‬و حتى بكونه برنامجا ًسهلا ًفأنا أملك أفكارا لتطويره !‬

‫لـكن احذر هذه المرة فلن أعطيك التصحيح‪ ،‬عليك أن ٺتدب ّر أمرك بنفسك ! فإن كانت لديك حقّا مشاكل‪ ،‬فيمكنك‬
‫ز يارة منتديات ‪ OpenClassrooms‬من أجل طلب المساعدة من الآخرين‪.‬‬

‫ل مرة عندما لا يجد المستعمل الرقم الغامض‪ .‬بمعنى‬


‫• ضع عدادا للمحاولات‪ ،‬و هو متغير نقوم بز يادة قيمته في ك ّ‬
‫آخر في كل مرة يعود فيها البرنامج لتشغيل الحلقة‪ ،‬و عند انتهاء اللعبة تقوم بإظهار عدد المحاولات للاعب هكذا مثلا‬
‫‪.”Bravo ! you have found in 3 tries only !” :‬‬

‫• في هذا البرنامج‪ ،‬عندما يجد اللاعب العدد الغامض يتوقف البرنامج‪ ،‬لماذا لا نجعل البرنامج يسأله ما إن أراد جولة‬
‫ثانية ؟‬
‫إذا أردت تطبيق الفكرة فيجب عليك وضع حلقة تشمل تقريبا كل برنامجك‪ .‬و هذه الحلقة ٺتكرر مادام اللاعب لم‬
‫يطلب إيقاف البرنامج‪ ،‬و أنصحك بإضافة متغير منطقي اسمه مثلا ‪ continuePlaying‬ذي قيمة ابتدائية مساو ية‬
‫لـ‪ .1‬إذا طلب اللاعب إيقاف البرنامج‪ ،‬تُغي ّر القيمة لـ‪ 0‬و يتوقف البرنامج‪.‬‬

‫• أنشئ وضع لاعبين ! يعني بالإضافة إلى وضع اللاعب الواحد‪ ،‬ضع إمكانية اشتراك لاعبين !‬
‫لهذا عليك بصنع قائمة )‪ (menu‬في البداية لتطلب من المستخدم اختيار لاعب واحد أو لاعبين إثنين‪ .‬الفرق‬
‫بين الوضعين هو توليد العدد الغامض‪ ،‬ففي الحالة الأولى نستعمل الدالة ‪ rand‬و في الحالة الثانية نستعمل الدالة‬
‫‪! scanf‬‬
‫• إنشاء مستو يات مختلفة لصعوبة اللعبة‪ .‬يمكنك وضع قائمة في البداية ليختار المستخدم فيه المستوى و هذا مثال ‪:‬‬

‫– ‪ = 1‬بين ‪ 1‬و ‪،100‬‬


‫– ‪ = 2‬بين ‪ 1‬و ‪،1000‬‬
‫– ‪ = 3‬بين ‪ 1‬و ‪.10000‬‬

‫‪102‬‬
‫أفكار للتحسين‬

‫لفعل هذا يجب التعديل على الثابت ‪ . MAX‬بطبيعة الحال لا يمكننا أن نسميه ثابتا إن كانت قيمته ٺتغير أثناء البرنامج‬
‫! قم بتغيير اسم هذا المتغير إلى ‪) maximumNumber‬و لا تنس نزع الكلمة المفتاحية ‪ const‬لأنه ببقاءها يبقى‬
‫ثابتا !(‪ .‬ٺتغير قيمة هذه المتغير حسب المستوى المختار‪.‬‬

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

‫‪103‬‬
‫الفصل ‪ .8‬عمل تطبيقي ‪” :‬أكثر أو أقل”‪ ،‬لعبتك الأولى‬

‫‪104‬‬
‫الفصل ‪9‬‬

‫الدوال )‪(Functions‬‬

‫ننتهي من الجزء الأول من الكتاب )المبادئ( بهذها المفهوم المهم الذي يتكلّم عن الدوال في لغة ‪ .C‬كل البرامج في لغة‬
‫‪ C‬ترتكز على المبدأ الذي سأشرحه في هذا الفصل‪.‬‬

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

‫إنشاء و استدعاء دالة‬ ‫‪1.9‬‬

‫رأينا في الفصول السابقة بأن برامج الـ‪ C‬تبدأ بدالة تدعى ‪ . main‬لقد أعطيتك مخططا ًتلخيصي ّا ًلتذكيرك ببعض الكلمات‬
‫المفتاحية ‪:‬‬

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

‫لقد قلت لك بأن البرنامج في الـ‪ C‬يبدأ بالدالة ‪ . main‬أؤكد لك‪ ،‬هذا صحيح ! إلا أننا في هذه الحالة بقينا داخل الدالة‬
‫‪ main‬و لم نخرج أبدا ً منها‪ .‬أعد قراءة الشفرات المصدر ية في الدروس السابقة و سترى ‪ :‬لقد بقينا دائما داخل الحاضنتين‬
‫الخاصتين بالدالة الرئيسية‪.‬‬

‫؟‬
‫حسنا‪ ،‬هل من السيء فعل ذلك ؟‬

‫‪105‬‬
‫الفصل ‪ .9‬الدوال )‪(Functions‬‬

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

‫سنتعل ّم الآن كيف ننظّم عملنا‪ .‬سنبدأ بتقسيم برامجنا إلى قطع صغيرة )تذك ّر فكرة ‪ Lego‬ال ّتي حدّثتك عنها قبل قليل(‪.‬‬
‫ل قطعة تسمّى دالّة‪.‬‬
‫ك ّ‬

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

‫حينما نقوم باستدعاء دالة‪ ،‬نمر ّ بثلاثة خطوات‪.‬‬

‫‪ .1‬الإدخال ‪ :‬نقوم بإدخال المعلومات إلى الدالة )بإعطائها معلومات تعمل عليها(‪.‬‬

‫‪ .2‬الحسابات ‪ :‬تقوم الدالة بعمل حسابات على المعلومات التي تم ادخالها‪.‬‬

‫‪ .3‬الإخراج ‪ :‬بعد أن تنتهي الدالة من الحسابات تعطينا النتيجة على شكل قيمة الإخراج أو الإرجاع‪.‬‬

‫يمكن أن نتصو ّر دالة تسمى ‪ triple‬تضرب العدد في ‪ .3‬بالطبع الدوال في الغالب هي أكثر تعقيدا ً من هذا‪.‬‬

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

‫ُأحْلُم قليلا ً ‪ :‬لاحقاً‪ ،‬سننشئ مثلا دالة اسمها ‪ showWindow‬تقوم بفتح نافذة في الشاشة‪ .‬ما إن نكتب الدالة‬
‫)المرحلة الأصعب(‪ ،‬لن يتبقّ لنا سوى القول ”أيتها الدالة ‪ ، showWindow‬أظهري لي النافذة !”‪ .‬يمكننا أيضا ًكتابة دالة‬
‫‪ moveCharacter‬تهدف إلى تحر يك شخصية ما في اللعبة‪ ،‬إلخ‪.‬‬

‫‪106‬‬
‫‪ .1.9‬إنشاء و استدعاء دالة‬

‫مخطط دالة‬

‫لقد تكو ّنت لديك فكرة على الطر يقة التي تعمل بها الدالة ‪. main‬‬
‫و مع ذلك يجب أن أر يك كيف نقوم بإنشاء دالة‪.‬‬

‫الشفرة المصدر ية التالية تمث ّل دالة تخطيطياً‪ .‬هذا نموذج للحفظ ‪:‬‬

‫)‪1 type functionName(parameters‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪// We write the instructions here‬‬
‫} ‪4‬‬

‫أنت تعرف شكل الدالة ‪. main‬‬


‫إليك ما عليك فهمه بخصوص المخطط‪.‬‬

‫• ‪) type‬نوع قيمة الإخراج( ‪ :‬هو نوع الدالة‪ .‬مثل المتغيرات‪ ،‬للدوال أنواعها الخاصة‪ .‬هذا النوع يعتمد على القيمة‬
‫التي ترجعها الدالة ‪ :‬إن كانت الدالة ترجع عددا ً عشر ياً‪ ،‬فسنضع بالتأكيد الكلمة المفتاحية ‪ ، double‬أما إن‬
‫كانت ترجع عددا ً صحيحاً‪ ،‬سنضع النوع ‪ int‬أو ‪ long‬مثلا‪ .‬و لـكن يمكن أيضا ًإنشاء دوال لا ترجع أي شيء‬
‫! هناك إذا نوعان من الدوال ‪:‬‬

‫– دوال ترجع قيمة ‪ :‬تعطيها أحد الأنواع ال ّتي نعرفها كـ ‪ int‬أو ‪ char‬أو ‪ ، double‬إلخ‪.‬‬

‫– دوال لا ترجع أية قيمة ‪ :‬نعطيها نوعا خاصا يدعى ‪) void‬و الذي يعني الفراغ(‪.‬‬

‫– ‪ : functionName‬هو اسم الدالة‪ .‬يمكنك أن تسمي الدالة مثلما تريد لطالما تحـترم القواعد التي ٺتبعها في‬
‫تسمية المتغيرات )لا للأحرف التي تحتوي على العلامات الصوتية )‪ ،(accents‬لا فراغات‪ ،‬إلخ(‪.‬‬

‫– ‪) : parameters‬هي قيم الإدخال( ‪ :‬داخل قوسين‪ ،‬يمكنك أن تبعث معاملات للدالة‪ .‬هي القيم التي‬
‫ستعمل بها الدالة‪.‬‬
‫م‬
‫يمكنك أن تبعث القدر الذي تريد من المعاملات‪ .‬كما يمكنك ألا تبعث أية معامل‪ ،‬و لـكن نادرا ً ما‬
‫يُستخدم هذا‪.‬‬

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

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

‫الدالة إذا هي عبارة عن آلية ٺتلقّى قيم إدخال )المعاملات( و ترجع قيمة إخراج‪.‬‬

‫‪107‬‬
‫الفصل ‪ .9‬الدوال )‪(Functions‬‬

‫إنشاء دالة‬

‫فلنرى مثالا تطبيقي ّا دون مزيد من التأخير ‪ :‬الدالة ‪ triple‬التي حدثتك عنها منذ قليل‪ .‬فلنقل أن هذه الدالة ٺتلقّى‬
‫عددا ً صحيحا ًمن نوع ‪ int‬و أنها ت ُرجع عددا ً صحيحا ًأيضا ًمن نوع ‪ . int‬هذه الدالة تضرب العدد الذي نعطيها في ‪: 3‬‬

‫)‪1 int triple (int number‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪int result = 0‬‬
‫‪4‬‬ ‫‪result = number � 3; // We multiply the input number by 3‬‬
‫‪5‬‬ ‫‪return result; // We return the result as an output value‬‬
‫} ‪6‬‬

‫م للغاية ‪ :‬كما ترى‪ ،‬الدالة من نوع ‪ . int‬فهي مجـبرة على أن ترجع قيمة من نوع ‪. int‬‬
‫هاهي أول دالة لك ! شيء مه ّ‬

‫داخل القوسين‪ ،‬نجد المتغيرات التي ٺتلقّاها الدالة‪ .‬الدالة ‪ triple‬ٺتلقّى متغيرا من نوع ‪ int‬يسمى ‪. number‬‬

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

‫;‪1 return result‬‬

‫هذه الشفرة المصدر ية تعني للدالة ‪” :‬توق ّفي و أرجعي العدد ‪ .” result‬يجب أن يكون هذا المتغير ‪ result‬من‬
‫نوع ‪ ، int‬لأن الدالة تقوم بإرجاع قيمة من نوع ‪ int‬كما قلت في الأعلى‪.‬‬

‫صرّحت عن )= أنشأت( المتغير ‪ result‬في الدالة ‪ . triple‬هذا يعني أنه لا يستخدم إلا داخل هذه الدالة و‬
‫ليس داخل أخرى كالـدالة ‪ main‬مثلا‪ .‬أي أنه متغير خاص بالدالة ‪. triple‬‬

‫لـكن هل هذه هي الطر يقة الأقصر لكتابة الدالة ‪ triple‬؟‬


‫لا‪ ،‬يمكننا كتابة محتوى الدالة في سطر واحد كالتالي ‪:‬‬

‫)‪1 int triple (int number‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪return number � 3‬‬
‫} ‪4‬‬

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

‫العديد من المعاملات‪ ،‬لا معاملات‬

‫العديد من المعاملات‬

‫الدالة ‪ triple‬تحوي معاملا ًواحد‪ ،‬لـكن من الممكن إنشاء دوال تقبل العديد من المعاملات‪.‬‬
‫مثلا‪ ،‬دالة ‪ addition‬لجمع عددين ‪ a‬و ‪: b‬‬

‫‪108‬‬
‫‪ .1.9‬إنشاء و استدعاء دالة‬

‫)‪1 int addition (int a, int b‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪return a + b‬‬
‫} ‪4‬‬

‫يكفي تفر يق المعاملات بفاصلة كما ترى‪.‬‬

‫لا معاملات‬

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

‫تخيل دالة ‪ hello‬تقوم بإظهار الرسالة ”‪ ”Hello‬على الشاشة‪:‬‬

‫)( ‪1 void hello‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;)”‪printf(”Hello‬‬
‫} ‪4‬‬

‫لم أضع أي شيء داخل الأقواس لأن الدالة لا ٺتلقّى أي معامل‪.‬‬


‫بالإضافة إلى ذلك‪ ،‬استعملت النوع ‪ void‬الذي كل ّمتك عنه أعلاه‪.‬‬

‫بالفعل‪ ،‬كما ترى فالدالة لا تحتاج إلى التعليمة ‪ return‬لأنها لا ترجع أي شيء‪ .‬الدالة التي لا ترجع أي شيء هي‬
‫دالة من النوع ‪. void‬‬

‫استدعاء دالة‬

‫سنقوم الآن بتجريب الشفرة المصدر ية للتمر ّن قليلا ًمع ما تعلّمناه‪.‬‬


‫سنستعمل الدالة ‪ triple‬لضرب عدد في ‪.3‬‬

‫لحد الآن‪ ،‬أطلب منك كتابة الدالة ‪ triple‬قبل الدالة ‪ . main‬فإذا وضعتها بعدها‪ ،‬فلن يشتغل البرنامج‪ .‬سأشرح‬
‫لك هذا لاحقاً‪.‬‬

‫إليك الشفرة المصدر ية التالية ‪:‬‬

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


‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬ ‫)‪int triple(int number‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;‪return 3 � number‬‬
‫‪6‬‬ ‫}‬
‫‪7‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪8‬‬ ‫{‬

‫‪109‬‬
‫الفصل ‪ .9‬الدوال )‪(Functions‬‬

‫‪9‬‬ ‫;‪int inputNumber = 0, tripleNumber = 0‬‬


‫‪10‬‬
‫‪11‬‬ ‫;)” ‪printf(”Enter a number...‬‬
‫‪12‬‬ ‫;)‪scanf(”%d”, &inputNumber‬‬
‫‪13‬‬
‫‪14‬‬ ‫;)‪tripleNumber = triple(inputNumber‬‬
‫‪15‬‬ ‫;)‪printf(”The number’s triple = %d\n”, tripleNumber‬‬
‫‪16‬‬
‫‪17‬‬ ‫;‪return 0‬‬
‫} ‪18‬‬

‫يبدأ البرنامج بالدالة ‪ main‬كما تعلم‪.‬‬


‫نطلب من المستعمل إدخال عدد‪ .‬نبعث هذا العدد كإدخال للدالة ‪ ، triple‬ثم نسترجع النتيجة في المتغير ‪. tripleNumber‬‬
‫أنظر بشكل خاص السطر التالي‪:‬‬
‫;)‪1 tripleNumber = triple(inputNumber‬‬

‫داخل القوسين‪ ،‬نبعث المتغير كمدخل للدالة ‪ ، triple‬إنه العدد الذي ستعمل به الدالة‪ .‬هذه الدالة تقوم بإرجاع‬
‫قيمة‪ ،‬هذه القيمة نسترجعها في المتغير ‪ . tripleNumber‬نأمر إذا الحاسوب في هذا السطر ‪” :‬اُطلب من الدالة‬
‫‪ triple‬ضرب العدد ‪ inputNumber‬في ‪ 3‬و تخزين النتيجة في المتغير ‪.” tripleNumber‬‬

‫نفس الشرح على شكل مخطط‬

‫ألازالت لديك صعوبات في فهم المبدأ الذي تعمل به الدالة ؟‬


‫لا تقلق ! أنا متأكد أنك ستفهم بالمخططات‪.‬‬

‫هذه الشفرة المصدر ية التي تحتوي على تعليقات سَت ُر يك في أي ترتيب يتم تنفيذ التعليمات‪ .‬إبدأ إذا بقراءة السطر‬
‫المرق ّم ‪ 1‬ثم ‪ ،2‬إلخ )أعتقد أنك فهمت( ‪:‬‬
‫‪1‬‬ ‫>‪#include <stdio.h‬‬
‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬ ‫‪int triple(int number) // 6‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫‪return 3 � number; // 7‬‬
‫‪6‬‬ ‫}‬
‫‪7‬‬ ‫‪int main(int argc, char �argv[]) // 1‬‬
‫‪8‬‬ ‫{‬
‫‪9‬‬ ‫‪int inputNumber = 0, tripleNumber = 0; // 2‬‬
‫‪10‬‬
‫‪11‬‬ ‫‪printf(”Enter a number... ”); // 3‬‬
‫‪12‬‬ ‫‪scanf(”%d”, &inputNumber); // 4‬‬
‫‪13‬‬
‫‪14‬‬ ‫‪tripleNumber = triple(inputNumber); // 5‬‬
‫‪15‬‬ ‫‪printf(”The number’s triple = %d\n”, tripleNumber); // 8‬‬
‫‪16‬‬
‫‪17‬‬ ‫‪return 0; // 9‬‬
‫‪18‬‬ ‫}‬

‫‪110‬‬
‫‪ .1.9‬إنشاء و استدعاء دالة‬

‫إليك ما يحدث سطرا ً بسطر ‪:‬‬

‫‪ .1‬يبدأ البرنامج من الدالة ‪. main‬‬

‫‪ .2‬يقرأ التعليمات في الدالة واحدة تلو الأخرى بالترتيب‪.‬‬

‫‪ .3‬يقرأ التعليمة الخاصة بالدالة ‪. printf‬‬

‫‪ .4‬يقرأ أيضا ًالتعليمة الخاصة بالدالة ‪. scanf‬‬

‫‪ .5‬يقرأ التعليمة ‪ ...‬آه نحن نستدعي الدالة ‪ ، triple‬يجب إذا أن نقفز إلى أول سطر من محتوى هذه الدالة في‬
‫الأعلى‪.‬‬

‫‪ .6‬نقفز إلى الدالة ‪ triple‬ثم نقوم باسترجاع المعامل ‪. number‬‬

‫‪ .7‬نقوم بالحسابات و ننتهي من الدالة‪ .‬الكلمة المفتاحية ‪ return‬تعني أن الدالة قد انتهت و تسمح بتحديد النتيجة التي‬
‫سترجعها الدالة‪.‬‬

‫‪ .8‬نرجع للدالة ‪ main‬إلى التعليمة الموالية‪.‬‬

‫‪ .9‬نصل إلى ‪ return‬و منه تنتهي الدالة ‪ main‬و ينتهي البرنامج‪.‬‬

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

‫ليس الأمر نفسه بالنسبة لكل الدوال‪ .‬أحياناً‪ ،‬لا تأخذ الدالة أية معامل كإدخال‪ ،‬و بالعكس أحيانا ً تأخذ‬ ‫ملاحظة ‪:‬‬
‫الـكثير من المعاملات كإدخال )لقد شرحت لك هذا سابقاً(‪.‬‬
‫أيضاً‪ ،‬يمكن لدالة أن ترجع قيمة كما يمكنها ألا ترجع أي شيء )و في هذه الحالة لا يكون هناك ‪.( return‬‬

‫‪111‬‬
‫الفصل ‪ .9‬الدوال )‪(Functions‬‬

‫فلنجرب هذا البرنامج‬

‫هذا مثال عن تنفيذ البرنامج ‪:‬‬


‫‪Enter a number... 10‬‬
‫‪The number’s triple = 30‬‬

‫م‬
‫لست مضطرا ً إلى أن تخزن النتيجة في متغير ! يمكنك أن تعطي النتيجة المُرجعة من طرف الدالة ‪ triple‬إلى‬
‫دالة أخرى و كأن التعليمة )‪ triple(inputNumber‬في ح ّد ذاتها متغير‪.‬‬

‫لاحظ هذا جيداً‪ ،‬هي نفس الشفرة المصدر ية لـكن هناك تغيير آخر على مستوى ‪ . printf‬بالإضافة إلى ذلك‪ ،‬لم‬
‫نقم بالتصريح عن المتغير ‪ tripleNumber‬و الذي لا يفيدنا في أي شيء الآن ‪:‬‬
‫‪1‬‬ ‫>‪#include <stdio.h‬‬
‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬ ‫)‪int triple(int number‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;‪return 3 � nombre‬‬
‫‪6‬‬ ‫}‬
‫‪7‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪8‬‬ ‫{‬
‫‪9‬‬ ‫‪int inputNumber = 0, tripleNumber = �0‬‬
‫‪10‬‬
‫‪11‬‬ ‫;)” ‪printf(”Enter a number...‬‬
‫‪12‬‬ ‫;)‪scanf(”%d”, &inputNumber‬‬
‫‪13‬‬ ‫‪// The result returned by the function is directly sent to printf‬‬
‫‪without being saved in a variable‬‬
‫‪14‬‬ ‫;))‪printf(”The number’s triple = %d\n”, triple(inputNumber‬‬
‫‪15‬‬
‫‪16‬‬ ‫;‪return 0‬‬
‫} ‪17‬‬

‫كما ترى‪ ،‬استدعاء الدالة ‪ triple‬يتم داخل الدالة ‪. printf‬‬


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

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

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

‫‪112‬‬
‫‪ .2.9‬أمثلة للفهم الجي ّد‬

‫أمثلة للفهم الجي ّد‬ ‫‪2.9‬‬

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

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

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

‫التحو يل من الأورو الى الفرنك‬

‫سنبدأ بدالة مشابهة كثيرا ً للدالة ‪ ، triple‬لـكنها تحمل أهمية لابأس بها هذه المرة ‪ :‬دالة تحو ّل من الأورو إلى الفرنك‪.‬‬
‫لمن لا يعرف فإن ‪ 1‬أورو = ‪ 6,55957‬فرنك‪.‬‬
‫سننشئ دالة نسميها ‪. conversion‬‬
‫هذه الدالة تأخذ متغيرا كإدخال من نوع ‪ double‬لأننا سنتعامل بالضرورة مع أعداد عشر ية‪ .‬إقرأها بتمع ّن ‪:‬‬
‫‪1‬‬ ‫)‪double conversion(double euros‬‬
‫‪2‬‬ ‫{‬
‫‪3‬‬ ‫;‪double francs = 0‬‬
‫‪4‬‬ ‫;‪francs = 6.55957�euros‬‬
‫‪5‬‬ ‫;‪return francs‬‬
‫‪6‬‬ ‫}‬
‫‪7‬‬ ‫)][‪int main(int argc, char � argv‬‬
‫‪8‬‬ ‫{‬
‫‪9‬‬ ‫;))‪printf(”10 euros = %fF\n”, conversion(10‬‬
‫‪10‬‬ ‫;))‪printf(”50 euros = %fF\n”, conversion(50‬‬
‫‪11‬‬ ‫;))‪printf(”100 euros = %fF\n”, conversion(100‬‬
‫‪12‬‬ ‫;))‪printf(”200 euros = %fF\n”, conversion(200‬‬
‫‪13‬‬ ‫;‪return 0‬‬
‫‪14‬‬ ‫}‬

‫‪10 euros = 65.595700F‬‬


‫‪50 euros = 327.978500F‬‬
‫‪100 euros = 655.957000F‬‬
‫‪200 euros = 1311.914000F‬‬

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

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

‫‪113‬‬
‫الفصل ‪ .9‬الدوال )‪(Functions‬‬

‫اكتب دالة ثانية )دائما ً قبل الدالة ‪ ( main‬تقوم بالعملية العكسية أي تحول من الفرنك إلى الأورو‪ .‬لن‬ ‫حان دورك !‬

‫يكون الأمر صعباً‪ .‬هناك إشارة عملية ٺتغير ليس إلا‪.‬‬

‫العقوبة‬

‫سنهتم الآن بدالة لا تقوم بإرجاع أي شيء )لا وجود للإخراج(‪.‬‬


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

‫‪1‬‬ ‫)‪void punishment(int numberOfLines‬‬


‫‪2‬‬ ‫{‬
‫‪3‬‬ ‫;‪int i‬‬
‫‪4‬‬ ‫{)‪for (i=0; i<numberOfLines; i++‬‬
‫‪5‬‬ ‫;)”‪printf(”I won’t misbehave in class again\n‬‬
‫‪6‬‬ ‫}‬
‫‪7‬‬ ‫}‬
‫‪8‬‬ ‫)][‪int main(int argc, char � argv‬‬
‫‪9‬‬ ‫{‬
‫‪10‬‬ ‫;)‪punishment(10‬‬
‫‪11‬‬ ‫;‪return 0‬‬
‫‪12‬‬ ‫}‬

‫‪I‬‬ ‫‪won’t‬‬ ‫‪misbehave‬‬ ‫‪in‬‬ ‫‪class‬‬ ‫‪again‬‬


‫‪I‬‬ ‫‪won’t‬‬ ‫‪misbehave‬‬ ‫‪in‬‬ ‫‪class‬‬ ‫‪again‬‬
‫‪I‬‬ ‫‪won’t‬‬ ‫‪misbehave‬‬ ‫‪in‬‬ ‫‪class‬‬ ‫‪again‬‬
‫‪I‬‬ ‫‪won’t‬‬ ‫‪misbehave‬‬ ‫‪in‬‬ ‫‪class‬‬ ‫‪again‬‬
‫‪I‬‬ ‫‪won’t‬‬ ‫‪misbehave‬‬ ‫‪in‬‬ ‫‪class‬‬ ‫‪again‬‬
‫‪I‬‬ ‫‪won’t‬‬ ‫‪misbehave‬‬ ‫‪in‬‬ ‫‪class‬‬ ‫‪again‬‬
‫‪I‬‬ ‫‪won’t‬‬ ‫‪misbehave‬‬ ‫‪in‬‬ ‫‪class‬‬ ‫‪again‬‬
‫‪I‬‬ ‫‪won’t‬‬ ‫‪misbehave‬‬ ‫‪in‬‬ ‫‪class‬‬ ‫‪again‬‬
‫‪I‬‬ ‫‪won’t‬‬ ‫‪misbehave‬‬ ‫‪in‬‬ ‫‪class‬‬ ‫‪again‬‬
‫‪I‬‬ ‫‪won’t‬‬ ‫‪misbehave‬‬ ‫‪in‬‬ ‫‪class‬‬ ‫‪again‬‬

‫هنا نتكلم عن دالة لا ت ُرجع أية قيمة‪ .‬تكتفي هذه الدالة بالقيام بمهمة )هنا‪ ،‬إظهار النص على الشاشة(‪.‬‬
‫الدالة التي لا ترجع أيه قيمة هي دالة من نوع ‪ ، void‬غير هذا لا يوجد شيء مختلف‪.‬‬

‫سيكون من الممتع أن نكتب دالة ‪ punishment‬ٺتلائم مع أيّ عقوبة‪ ،‬فتقوم بإظهار النص الذي نريده نحن على‬
‫الشاشة‪ .‬نبعث لها معاملين ‪ :‬النص الذي نريد و عدد المرات التي نريد أن يتم إظهاره‪ .‬المشكل هو أننا لا نجيد بعد التعامل‬
‫مع النصوص في الـ‪) C‬إذا كنت لم تنتبه فأذكرك أنه لح ّد الآن لم نتعامل سوى مع متغيرات تحمل قيما عددي ّة داخلها منذ‬
‫بداية الكتاب !(‪ .‬و بهذا الصدد‪ ،‬أخبرك أننا لن نتأخر حتى نتعل ّم كيفية التعامل مع النصوص‪ .‬فعل ذلك معقّد قليلا ًو لا‬
‫يصلح أن نبدأ به في أول الكتاب !‬

‫‪114‬‬
‫ أمثلة للفهم الجي ّد‬.2.9

‫مساحة مستطيل‬

.‫ العرض × الطول‬: ‫من السهل حساب مساحة المستطيل‬


.‫ و تقوم بإرجاع المساحة‬.‫ الطول و العرض‬: ‫ تأخذ معاملين‬rectangleSurface ‫الدالة التي سنكتبها‬

1 double rectangleSurface(double width, double height)


2 {
3 return width � height;
4 }
5 int main(int argc, char � argv[])
6 {
7 printf(”Width = 5 and height = 10. Surface = %f\n”, rectangleSurface
(5,10));
8 printf(”Width = 2.5 and height = 3.5. Surface = %f\n”, rectangleSurface
(2.5,3.5));
9 printf(”Width = 4.2 and height = 9.7. Surface = %f\n”, rectangleSurface
(4.2,9.7));
10 return 0;
11 }

Width = 5 and height = 10. Surface = 50.000000


Width = 2.5 and height = 3.5. Surface = 8.750000
Width = 4.2 and height = 9.7. Surface = 40.740000

‫؟‬
‫ عرض و مساحة المستطيل داخل الدالة ؟‬،‫هل يمكننا أن نظهر مباشرة طول‬

: ‫ ستكتفي بإظهار ما حسبته‬.‫بالطبع ! في هذه الحالة لن ترجع الدالة أي شيء‬

1 void rectangleSurface(double width, double height)


2 {
3 double surface;
4 surface = width � height;
5 printf(”Width = %f and height = %f. Surface = %f\n”, width, height,
surface);
6 }
7 int main(int argc, char � argv[])
8 {
9 rectangleSurface(5,10);
10 rectangleSurface(2.5,3.5);
11 rectangleSurface(4.2,9.7);
12 return 0;
13 }

‫ هذه فقط طر يقة‬.‫ يعرض نفس الرسالة السابقة‬rectangleSurface ‫ في داخل الدالة‬printf ‫ الـ‬،‫كما ترى‬
.‫مختلفة لفعل نفس الشيء‬

115
(Functions) ‫ الدوال‬.9 ‫الفصل‬

‫القائمة‬

‫ تكتفي هذه الدالة بإظهار قائمة و‬.‫ لا تأخذ أي معامل كإدخال‬menu ‫ سننشئ دالة‬.‫هذه الشفرة أكثر أهمية و واقعي ّة‬
.‫ الدالة تقوم بإرجاع اختيار المستعمل‬.‫تطلب من المستعمل اختيار ما يريد‬

1 int menu()
2 {
3 int choice = 0;
4
5 while (choice < 1 || choice > 4)
6 {
7 printf(”Menu :\n”);
8 printf(”1 : Royal Fried Rise\n”);
9 printf(”2 : Noodle Basil \n”);
10 printf(”3 : Pattaya Paradise \n”);
11 printf(”4 : Spice Nitan Spicy...\n”);
12 printf(”Your choice? ”);
13 scanf(”%d”, &choice);
14 }
15
16 return choice;
17 }
18 int main(int argc, char � argv[])
19 {
20 switch (menu())
21 {
22 case 1:
23 printf(”You have chosen Royal Fried Rise\n”);
24 break;
25 case 2:
26 printf(”You have chosen Noodle Basil \n”);
27 break;
28 case 3:
29 printf(”You have chosen Pattaya Paradise \n”);
30 break;
31 case 4:
32 printf(”You have chosen Spice Nitan Spicy\n”);
33 break;
34 }
35 return 0;
36 }

‫ بإظهار القائمة طالما لم يقم المستعمل‬menu ‫ تقوم الدالة‬: (‫اغتنمت الفرصة لتحسين القائمة )مقارنة بما نفعله عادة‬
! ‫ لا يمكن أن تقوم الدالة بإرجاع قيمة لا تظهر على القائمة‬،‫ فهكذا‬.4 ‫ و‬1 ‫بإدخال رقم محصور بين‬

‫ فإنّها ستعيد خيار المستخدم‬، menu ‫ بمجر ّد أن تنتهي‬. switch(menu()) ‫ تلاحظ أننا استعملنا‬، main ‫في الـ‬
.‫ إنها طر يقة سر يعة و عملي ّة‬. switch ‫مباشرة في‬

116
‫ملخّ ص‬

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

‫ملخّ ص‬

‫• يمكن لدالة أن تستدعي دالة أخرى‪ .‬و لهذا فالـ ‪ main‬يمكنها استدعاء دوال جاهزة كـ ‪ printf‬و ‪ scanf‬و‬
‫كذلك دوالا ننشؤها بأنفسنا‪.‬‬

‫• ٺتلقّى الدالة كإدخال متغي ّرات نسميها معاملات‪.‬‬

‫• تقوم الدالة ببعض العملي ّات على هذه المعاملات و ن ُرجع عادة قيمة بالاستعانة بالتعليمة ‪. return‬‬

‫‪117‬‬
‫الفصل ‪ .9‬الدوال )‪(Functions‬‬

‫‪118‬‬
‫الجزء ب‬

‫الـ‪C‬‬ ‫تقنيات متقدّمة في لغة‬

‫‪119‬‬
‫الفصل ‪10‬‬

‫البرمجة المجز ّأة )‪(Modular Programming‬‬

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

‫لح ّد الآن عملنا في ملف واحد سم ّيناه ‪ . main.c‬كان أمرا ً مقبولا ً لح ّد الآن لأن برامجنا كانت صغيرة‪ ،‬لـكنها ستصبح‬
‫في القريب العاجل مركّ بة من عشرات‪ ،‬لن أقول من مئات الدوال‪ ،‬و إن كنت تريد وضعها كل ّها في نفس الملف‪ ،‬فإن‬
‫هذا الأخير سيصبح ضخما ً جداً‪ .‬لهذا السبب تم اختراع ما نسمّيه بالبرمجة المجز ّأة‪ .‬المبدأ سهل ‪ :‬بدل أن نضع كل الشفرة‬
‫المصدر ية في ملف واحد ‪ ، main.c‬سنقوم بتفر يقها إلى عدة ملفات‪.‬‬

‫النماذج )‪(Prototypes‬‬ ‫‪1.10‬‬

‫لح ّد الآن‪ ،‬كنت عندما تنشئ دالة‪ ،‬أطلب منك وضعها قبل الدالة الرئيسية ‪ . main‬لماذا ؟‬

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

‫؟‬
‫لـكنه تصميم سيّء نوعا ًما‪ ،‬أليس كذلك ؟‬

‫ل المشكل‪.‬‬
‫أنا متفق معك ! لـكن انتبه المبرمجون لهذه النقطة قبلك و عملوا على ح ّ‬

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

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

‫‪rectangleSurface‬‬ ‫تأمل في السطر الأول من دالتنا‬

‫‪121‬‬
‫الفصل ‪ .10‬البرمجة المجز ّأة )‪(Modular Programming‬‬

‫)‪1 double rectangleSurface(double width, double height‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪return width � height‬‬
‫} ‪4‬‬

‫قم بنسخ السطر الأول ) ‪ ( double rectangleSurface...‬المتواجد أعلى الشفرة المصدر ية )مباشرة بعد‬
‫تعليمات التضمين ‪ .( #include‬أضف فاصلة منقوطة في نهاية هذا السطر‪.‬‬
‫و هكذا يمكنك أن تضع الدالة الخاصة بك ‪ rectangleSurface‬بعد الدالة ‪ main‬ان أردت !‬

‫هذا ما يجب أن تكون عليه الشفرة المصدر ية ‪:‬‬


‫‪1‬‬ ‫>‪#include <stdio.h‬‬
‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬ ‫‪// The next line represents the prototype of the function rectangleSurface :‬‬
‫‪4‬‬ ‫;)‪double rectangleSurface(double width, double height‬‬
‫‪5‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪6‬‬ ‫{‬
‫‪7‬‬ ‫‪printf(”width = 5 and height = 10. Surface = %f\n”, rectangleSurface(5,‬‬
‫;))‪10‬‬
‫‪8‬‬ ‫‪printf(”width = 2.5 and height = 3.5. Surface = %f\n”, rectangleSurface‬‬
‫;))‪(2.5, 3.5‬‬
‫‪9‬‬ ‫‪printf(”width = 4.2 and height = 9.7. Surface = %f\n”, rectangleSurface‬‬
‫;))‪(4.2, 9.7‬‬
‫‪10‬‬
‫‪11‬‬ ‫;‪return 0‬‬
‫‪12‬‬ ‫}‬
‫‪13‬‬ ‫‪// Now, we can put our function wherever we want in the source code :‬‬
‫‪14‬‬ ‫) ‪double rectangleSurface(double width , double height‬‬
‫‪15‬‬ ‫{‬
‫‪16‬‬ ‫; ‪return width � height‬‬
‫‪17‬‬ ‫}‬

‫الشيء الذي تغي ّر هنا هو إضافة النموذج أعلى الشفرة المصدر ية‪.‬‬
‫النموذج هو عبارة عن إشارة للجهاز‪ ،‬يوحي إليه بوجود دالة تسمى ‪ rectangleSurface‬و التي تأخذ معاملات إدخال‬
‫معينة و ت ُرجِـع مخرجا من نوع أنت من تحدده‪ .‬هذا يساعد الجهاز على تنظيم نفسه‪.‬‬

‫بفضل ذلك السطر‪ ،‬يمكنك الآن وضع دوالك في أي ترتيب كان دون أي تفكير زائد‪.‬‬

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

‫كما ترى‪ ،‬الدالة ‪ main‬لا تملك أي نموذج‪ ،‬و كمعلومة فهي الوحيدة التي لا تملك نموذجا ً! لأن الجهاز يعرفها )فهي‬
‫نفسها مكررة في جميع البرامج(‪.‬‬

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

‫‪122‬‬
‫‪ .2.10‬الملفات الرأسية )‪(Headers‬‬

‫يمكننا أن نكتب ببساطة ‪:‬‬

‫;)‪1 double rectangleSurface (double, double‬‬

‫ل ما عليك فعله هو نسخ و لصق‬


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

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

‫الملفات الرأسية )‪(Headers‬‬ ‫‪2.10‬‬

‫مصدري واحد في مشروعنا و هو الذي كنّا نسمّيه ‪. main.c‬‬


‫ٍ‬ ‫ملف‬
‫ٍ‬ ‫لح ّد الآن لا نملك غير‬

‫عدة ملفات في مشروع واحد‬

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

‫؟‬
‫عفوا ‪ ...‬ماهو المشروع ؟‬

‫لا ! هل نسيت بسرعة ؟ سأعيد الشرح لأنه من اللازم أن نت ّفق على هذا المصطلح‪.‬‬

‫المشروع هو مجموع الملفات المصدر ية الخاصة ببرنامجك‪ .‬لحد الآن برنامجنا لم ٺتكون إلا من ملف واحد‪ .‬و يمكنك‬
‫التحقق من هذا بالنظر في البيئة التطوير ية الخاصة بك‪ ،‬غالبا ما يظهر المشروع في القائمة على اليسار )الصورة الموالية( ‪:‬‬

‫كما يمكنك رؤيته في يسار الصورة‪ ،‬هذا المشروع ليس مكو ّنا إلا من الملف ‪. main.c‬‬

‫اسمح لي الآن أن ُأرِ ي َ َ‬


‫ك صورة لمشروع حقيقي ستقوم به في وقت لاحق من الكتاب ‪ :‬لعبة ‪: Sokoban‬‬

‫‪123‬‬
‫الفصل ‪ .10‬البرمجة المجز ّأة )‪(Modular Programming‬‬

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

‫؟‬
‫لـكن لم يجب عليّ إنشاء ملفات عديدة ؟ و كم من ملف يجب علىّ أن أنشئ في مشروعي ؟‬

‫هذا يبقى اختيارك أنت‪ ،‬في الغالب نجمع في نفس الملف المصدري الدوال التي تشترك في الموضوع الذي تعالجه‪ .‬و هكذا‬
‫ل الدوال الخاصة ببناء المستوى‪ ،‬و في الملف ‪ jeu.c‬قمت بتجميع الدوال الخاصة‬
‫ففي الملف ‪ editeur.c‬جمعت ك ّ‬
‫باللعبة نفسها و هكذا ‪...‬‬

‫‪.h‬‬ ‫الملفات ‪ .c‬و‬

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

‫• ملفات ذات الإمتداد ‪ : .c‬الملفات المصدر ية‪ ،‬تحتوي الدوال نفسها‪.‬‬

‫• ملفات ذات الإمتداد ‪ : .h‬تسمى الملفات الرأسية و هي تحتوي النماذج الخاصة بالدوال‪.‬‬

‫عموما‪ ،‬إنه لمن النادر وضع نماذج في الملفات من صيغة ‪ .c‬مثلما فعلنا للتو ّ في الـملف ‪) main.c‬إلا إذا كان‬
‫برنامجك صغيرا(‪.‬‬

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

‫• هناك ‪) editeur.c‬الشفرة الخاصة بالدوال( و ‪) editeur.h‬ملف النماذج الخاصة بالدوال(‪.‬‬

‫• هناك ‪ jeu.c‬و ‪. jeu.h‬‬


‫• إلخ‪.‬‬

‫‪124‬‬
‫‪ .2.10‬الملفات الرأسية )‪(Headers‬‬

‫؟‬
‫لـكن كيف يعرف الحاسوب بأن نماذج الدوال موجودة في ملف آخر خارج الملف ‪ .c‬؟‬

‫يجب عليك تضمين الملف الرأسي ‪ . .h‬مستعينا ًبتوجهات المعالج القبلي‪.‬‬


‫كن مستعدا ً لأن ّي سأعطيك الـكثير من المعلومات في وقت قصير‪.‬‬

‫كيف نقوم بتضمين ملف رأسي ؟ أنت تجيد فعل ذلك لأنك قمت بذلك من قبل‪.‬‬

‫أنظر مثالا ًمن بداية الملف ‪: jeu.c‬‬

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


‫‪2‬‬ ‫>‪#include <stdio.h‬‬
‫‪3‬‬ ‫”‪#include ”jeu.h‬‬
‫‪4‬‬ ‫)‪void play(SDL_Surface� screen‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫‪// ...‬‬

‫التضمين يتم عن طر يق توجيهات المعالج القبلي ‪ #include‬التي يجدر بك أن تكون قد تعلّمتها من قبل‪.‬‬
‫تمعن في التالي ‪:‬‬

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


‫>‪2 #include <stdio.h‬‬
‫‪3 #include ”jeu.h” // We include jeu.h‬‬

‫قمنا بتضمين ثلاثة ملفات من صيغة ‪ .h‬و هي ‪ stdlib ، stdio :‬و ‪. jeu‬‬
‫لاحظ الفرق ‪ :‬الملفات التي قمت بإنشائها ووضعها في الـمجلّد الخاص بمشروعك يجب أن تكون مضمّنة بإشارات الاقتباس‬
‫) ”‪ ( ”jeu.h‬بينما ملفات المكتبات )التي توجد عادة في البيئة التطوير ية الخاصة بك( تكون مضمّنة بعلامات الترتيب‬
‫) >‪.( <stdio.h‬‬

‫تستعمل إذا ‪:‬‬

‫• علامتي الترتيب > < ‪ :‬لتضمين الملفات المتواجدة في المجلّد ‪ include‬الخاص بالبيئة التطوير ية‪.‬‬

‫• علامتي الاقتباس ” ” ‪ :‬لتضمين الملفات المتواجدة في مجلّد المشروع )و غالبا بجانب الملفات ‪.( .c‬‬

‫الأمر ‪ #include‬يطلب إدخال محتوى ملف معيّن في الملف ‪ .c‬فهي تعليمة تقول ‪”:‬أدخل الملف ‪ jeu.h‬هنا”‬
‫مثلا ‪.‬‬

‫؟‬
‫و في الملف ‪ jeu.h‬ماذا نجد ؟‬

‫لا نجد إلا نماذج خاصة بدوال الملف ‪! jeu.c‬‬

‫‪125‬‬
‫الفصل ‪ .10‬البرمجة المجز ّأة )‪(Modular Programming‬‬

‫;)‪1 void play(SDL_Surface� screen‬‬


‫;)‪2 void movePlayer(int map[][NB_BLOCS_HEIGHT], SDL_Rect �pos, int direction‬‬
‫;)‪3 void moveBox(int �firstBox, int �secondeBox‬‬

‫هكذا يعمل المشروع الحقيقي !‬

‫؟‬
‫ما الهدف من وضع نماذج في ملفات من نوع ‪ .h‬؟‬

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

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

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

‫م‬
‫عندما تستدعي دالة متواجدة في الملف ‪ functions.c‬إنطلاقا من الملف ‪ main.c‬سيكون‬
‫عليك تضمين النماذج الخاصة بالملف ‪ functions.c‬في الملف ‪ main.c‬يجب إذن وضع‬
‫”‪ #include ”functions.h‬في أعلى الـملف ‪. main.c‬‬
‫ل مرة تستدعي الدالة ‪ X‬في ملف‪ ،‬يجب عليك إدراج نموذج هذه الدالة في ملفك” هذا‬
‫تذكر هذه القاعدة ‪” :‬في ك ّ‬
‫ما يسمح للـمترجم بمعرفة ما إن كنت قد استدعيتها بشكل صحيح‪.‬‬

‫؟‬
‫كيف أقوم بإضافة ملفات ‪ .c‬و ‪ .h‬إلى مشروعي ؟‬

‫‪Source File‬‬ ‫هذا راجع للـبيئة التطوير ية التي تستخدمها‪ .‬لـكن المبدأ هو نفسه في جميع البرامج ‪/ New / File :‬‬
‫هذا يسمح بإنشاء ملف جديد فارغ‪ .‬هذا الملف ليس حاليا من النوع ‪ .c‬ولا ‪ .h‬أنت من يحدد ذلك أثناء عملية حفظ‬
‫الملف‪ .‬قم إذن بحفظه )حت ّى و إن كان لا يزال فارغا !( و هنا يطلب منكم إدخال اسم للملف‪ ،‬يمكنك هنا اختيار صيغة‬
‫الملف ‪:‬‬

‫• إذا سميته ‪ file.c‬فسيكون بامتداد ‪. .c‬‬

‫• إذا سميته ‪ file.h‬فسيكون بامتداد ‪. .h‬‬

‫‪126‬‬
‫‪ .2.10‬الملفات الرأسية )‪(Headers‬‬

‫هذا سهل‪ .‬قم بحفظ الملف في المجلّد أين ٺتواجد باقي الملفات الخاصة بمشروعك )نفس المجلّد أين يتواجد الملف‬
‫‪ .( main.c‬عموما كل ملفات المشروع تقوم بحفظها في نفس المجلّد سواء كانت ذات صيغة ‪ .c‬أو ‪. .h‬‬

‫مجلّد المشروع في النهاية سيكون مثل هذا ‪:‬‬

‫الملف الذي أنشأته محفوظ لـكن لم تتم إضافته إلى مشروعك بعد !‬
‫لإضافته قم بالنقر يمينا على القائمة أيسر الشاشة )الخاصة بملفات المشروع( و اختر ‪ Add files‬كالتالي ‪:‬‬

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

‫الـ‪ include‬الخاصّة بالمكتبات النموذجية‬

‫ن لديك سؤالا يدور في رأسك الآن‪...‬‬


‫يفترض أ ّ‬
‫إذا ضم ّنا الملفات ‪ stdio.h‬و ‪ stdlib.h‬فهذا يعني أنهما موجودان في مكان ما و يمكننا البحث عنهما‪ ،‬أليس‬

‫‪127‬‬
‫الفصل ‪ .10‬البرمجة المجز ّأة )‪(Modular Programming‬‬

‫كذلك ؟‬

‫نعم بالطبع !‬
‫يفترض أنهما مسطبان في المكان الذي ٺتواجد به البيئة التطوير ية الخاصة بك‪ ،‬بالنسبة للبيئة ‪ Code::Blocks‬أجدهم هنا ‪:‬‬

‫‪C:\Program Files\CodeBlocks\MinGW\include‬‬

‫على العموم يجب البحث عن مجلد يحمل اسم ‪. include‬‬


‫بداخله تجد كمّا هائلا من الملفات‪ ،‬و هي ملفات رأسية ) ‪ ( .h‬خاصة بمكتبات نموذجية أي مكتبات متوفرة في كل‬
‫‪stdlib.h‬‬ ‫مكان )سواء في ‪ Windows‬أو ‪ Mac OS X‬أو ‪ ،( ...GNU/Linux‬و ستجد داخلها الملفات ‪ stdio.h‬و‬
‫مع ملفّات أخرى‪.‬‬

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

‫؟‬
‫حسناً‪ ،‬الآن عرفت أين أجد نماذج الدوال النموذجية لـكن ألا يمكنني رؤ ية الشفرة المصدر ية الخاصة بالدوال ؟‬
‫أين هي الملفات ‪ .c‬؟‬

‫إنها غير موجودة أساسا ‪ ،‬لأنها مترجمة )إلى ملفات ثنائية )‪ ،(binary files‬يعني إلى لغة الحاسوب(‪ .‬و لهذا فإنه من‬
‫المستحيل أن تقرأها‪.‬‬

‫يمكنك إ يجاد الملفات المترجمة في المجلّد المسمى ‪) lib‬و الذي هو اختصار لكلمة ‪ library‬أي مكتبة(‪ ،‬بالنسبة‬
‫لي هي موجودة في المسار ‪:‬‬

‫‪C:\Program Files\CodeBlocks\MinGW\lib‬‬

‫ملفات المكتبات المترجمة لها الصيغة ‪ .a‬في البيئة ‪ Code::Blocks‬و التي تستخدم ‪ mingw‬كمترجم‪ .‬و لها صيغة‬
‫‪ . .lib‬في برنامج ‪ Visual C++‬الذي يستخدم المترجم ‪ . Visual‬لا تحاولوا قراءتها لأنها غير قابلة للقراءة من طرف‬
‫إنسان عادي‪.‬‬

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

‫الـترجمة المنفصلة‬ ‫‪3.10‬‬

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

‫سأعطيك الآن مخططا مفصلا عنها و من المستحسن أن تحفظه عن ظهر قلب ‪:‬‬

‫‪128‬‬
‫‪ .3.10‬الـترجمة المنفصلة‬

‫هذا مخطط حقيقي عم ّا يجري بالضبط أثناء التجميع و سأشرحه لك ‪:‬‬

‫‪ .1‬المعالج القبلي ‪ :‬المعالج القبلي هو برنامج ينطلق قبل عملية الترجمة و هو مخصص للقيام بتشغيل تعليمات نطلبها منه عن‬
‫طر يق ما سميناه بتوجيهات المعالج القبلي‪ ،‬و هي الأسطر الشهيرة التي تبدأ يإشارة ‪. #‬‬

‫لحد الآن توجيهة المعالج القبلي الوحيدة ال ّتي نعرفها هي ‪ #include‬و ال ّتي تسمح بإدراج ملف في ملف آخر‪.‬‬
‫طبعا للمعالج القبلي مهام أخرى سنتعرف إليها لاحقا لـكن ما يهمنا الآن هو ما أعطيتك إياه‪ .‬المعالج القبلي يقوم إذن‬
‫بـ”استبدال” أسطر ‪ #include‬بملفات أخرى نحددها‪ ،‬فهو يضمّن داخل كل الملفات ‪ .c‬الملفات ‪ .h‬التي‬
‫نعينها و نطلب منه تضمينها في السابقة‪.‬‬

‫‪ .2‬الترجمة ‪ :‬هذه الخطوة المهمة التي تسمح بتحو يل ملفاتك إلى شفرات ثنائية مفهومة للحاسوب‪ .‬فالمترجم ييقوم بتجميع‬
‫الملفات ‪ .c‬واحدا بواحدا حتى ينهيها جميعها‪ ،‬و لضمان ذلك يجب أن تكون كل الملفات موجودة في المشروع‬
‫)بحيث تظهر في القائمة اليسار ية(‪.‬‬
‫سيقوم المترجم بتوليد ملف ‪ .o‬أو ‪ .obj‬و هذا راجع لنوع المترجم و هي ملفات ثنائية مؤقتة‪ ،‬و على أي حال‬
‫تحذف هذه الملفات في نهاية الـترجمة و لـكن بتعديل الخيارات يمكنك الإبقاء عليها لـكن لـكن يكون هناك من داع‪.‬‬

‫‪ .3‬إنشاء الروابط ‪ :‬محرر الروابط )‪ (Linker‬هو برنامج يعمل على جمع الملفات الثنائية من نوع ‪ .o‬في ملف واحد كبير‬
‫‪ :‬الملف التنفيذي النهائي ! هذا الملف يحمل الصيغة ‪ .exe‬في الويندوز‪ .‬إن كنت تملك نظام تشغيل آخر فسيأخذ‬
‫الصيغة المناسبة له‪.‬‬

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

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

‫لا يزال المخطط أعلاه غير كامل‪ ،‬إذ أن المكتبات لم تظهر فيه ! إذن كيف تحدث العملية عندما نستخدم مكتبات‬
‫برمجية ؟‬

‫تبقى بداية المخطط هي نفسها‪ ،‬لـكن يقوم محرر الروابط بأعمال أخرى‪ ،‬سيقوم بتجميع ملفاتك ‪) .o‬المؤقتة( مع مكتبات‬
‫جاهزة تحتاجها ) ‪ .a‬أو ‪ .lib‬وفقا للمترجم( ‪:‬‬

‫‪129‬‬
‫الفصل ‪ .10‬البرمجة المجز ّأة )‪(Modular Programming‬‬

‫هكذا ننتهي و يكون مخططنا هذه المرة كاملا‪ ،‬ملفاتك من المكتبات ‪) .a‬أو ‪ ( .lib‬يتم تجميعها في الملف التنفيذي‬
‫مع الملفات ‪. .o‬‬

‫فبهذه الطر يقة نتحصل في النهاية على برنامج كامل ‪ 100%‬و الذي يحتوي كل التعليمات اللازمة للجهاز لتشرح له كيف‬
‫يعرض نصّ ا !‬
‫كمثال‪ ،‬الدالة ‪ printf‬توجد في ملف ‪ .a‬و طبعا سيتم تجميعها مع الشفرة المصدر ية الخاصّة بنا في الملف التنفيذي‪.‬‬

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

‫نطاق الدوال و المتغيرات‬ ‫‪4.10‬‬

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

‫المتغيرات الخاصة بدالة‬

‫عندما تصرّح عن متغير في داخل دالة يتم حذف هذا المتغير من الذاكرة مع نهاية الدالة‪.‬‬

‫)‪1 int triple(int number‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪int result = 0; // The variable result is created in the memory‬‬
‫‪4‬‬ ‫;‪result = 3 � number‬‬
‫‪5‬‬ ‫;‪return result‬‬
‫‪6 } // The function finished, the variable result is destroyed‬‬

‫‪130‬‬
‫‪ .4.10‬نطاق الدوال و المتغيرات‬

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

‫‪1‬‬ ‫;)‪int triple(int number‬‬


‫‪2‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;))‪printf(”The triple of 15 = %d\n”, triple(15‬‬
‫‪5‬‬ ‫‪printf(”The triple of 15 = %d”, result); // Error‬‬
‫‪6‬‬ ‫;‪return 0‬‬
‫‪7‬‬ ‫}‬
‫‪8‬‬
‫‪9‬‬ ‫)‪int triple(int number‬‬
‫‪10‬‬ ‫{‬
‫‪11‬‬ ‫;‪int result = 0‬‬
‫‪12‬‬ ‫;‪result = 3 � number‬‬
‫‪13‬‬ ‫;‪return result‬‬
‫‪14‬‬ ‫}‬

‫‪triple‬‬ ‫في الدالة الرئيسية أحاول الوصول إلى المتغير ‪ result‬و بما أن هذا المتغير تم التصريح عنه داخل الدالة‬
‫فطبعا لا يمكنني الوصول إليه من خلال الدالة ‪! main‬‬

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

‫المتغيرات الشاملة )‪ : (Global variables‬فلتتجن ّبها‬

‫ل الملفات‬
‫متغير شامل قابل للوصول إليه من خلال ك ّ‬

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

‫ل الدوال‪ ،‬يعني في أعلى الملف‪ ،‬و عموما بعد أسطر‬


‫للتصريح عن متغير شامل )‪ ،(Global‬يجب أن تقوم بذلك خارج ك ّ‬
‫الـ ‪. #include‬‬

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


‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬
‫‪4‬‬ ‫‪int result = 0; // Declaration of a global variable‬‬
‫‪5‬‬ ‫‪void triple(int number ); // Prototype of the function‬‬
‫‪6‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪7‬‬ ‫{‬
‫‪8‬‬ ‫‪triple(15); // We call the function triple which is going to modify the‬‬
‫‪variable result‬‬

‫‪131‬‬
‫الفصل ‪ .10‬البرمجة المجز ّأة )‪(Modular Programming‬‬

‫‪9‬‬ ‫‪printf(”The triple of 15 = %d\n”, result); // We can access to the‬‬


‫‪variable result‬‬
‫‪10‬‬ ‫;‪return 0‬‬
‫‪11‬‬ ‫}‬
‫‪12‬‬
‫‪13‬‬ ‫)‪void triple(int number‬‬
‫‪14‬‬ ‫{‬
‫‪15‬‬ ‫;‪result = 3 � number‬‬
‫‪16‬‬ ‫}‬

‫في هذا المثال‪ ،‬الدالة ‪ triple‬لا ت ُرجع أي شيء ) ‪ .( void‬إنها تقوم بتعديل قيمة المتغير الشامل ‪ result‬التي‬
‫يمكن للدالة ‪ main‬أن تسترجعه‪.‬‬

‫ل دوال‬
‫المتغير ‪ result‬يمكن الوصول إليه من خلال كل الملفات في المشروع و منه يمكننا استدعاؤها من خلال ك ّ‬
‫البرنامج‪.‬‬

‫!‬
‫هذا شيء يجب ألا يتواجد في برامج الـ‪ C‬الخاصة بك‪ .‬من المستحسن استعمال التعليمة ‪ return‬لإرجاع النتيجة‬
‫بدل التعديل عليه كمتغير شامل‪.‬‬

‫متغير شامل قابل للوصول إليه من خلال ملف واحد‬

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

‫لإنشاء متغير شامل مرئي في ملف واحد نستعمل الكلمة المفتاحية ‪ static‬قبله ‪:‬‬

‫;‪1 static int result = 0‬‬

‫متغير ساكن )‪ (static‬بالنسبة لدالة‬

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

‫)‪1 int triple(int number‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪static int result = 0; // The first time when the variable is created‬‬
‫‪4‬‬ ‫;‪result = 3 � number‬‬
‫‪5‬‬ ‫;‪return result‬‬
‫‪6 } // When we exit the function, the variable is not destroyed‬‬

‫‪132‬‬
‫‪ .4.10‬نطاق الدوال و المتغيرات‬

‫ماذا يعني هذا بالضبط ؟‬


‫يعني أنه يمكننا استدعاء الدالة لاحقا و يبقى المتغير ‪ result‬محتفظا بنفس القيمة الاخيرة‪.‬‬

‫و هذا مثال آخر للفهم أكثر ‪:‬‬

‫‪1‬‬ ‫;)(‪int increment‬‬


‫‪2‬‬
‫‪3‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;))(‪printf(”%d\n”, increment‬‬
‫‪6‬‬ ‫;))(‪printf(”%d\n”, increment‬‬
‫‪7‬‬ ‫;))(‪printf(”%d\n”, increment‬‬
‫‪8‬‬ ‫;))(‪printf(”%d\n”, increment‬‬
‫‪9‬‬
‫‪10‬‬ ‫;‪return 0‬‬
‫‪11‬‬ ‫}‬
‫‪12‬‬
‫‪13‬‬ ‫)(‪int increment‬‬
‫‪14‬‬ ‫{‬
‫‪15‬‬ ‫;‪static int number= 0‬‬
‫‪16‬‬
‫‪17‬‬ ‫;‪number++‬‬
‫‪18‬‬ ‫;‪return number‬‬
‫‪19‬‬ ‫}‬

‫‪1‬‬
‫‪2‬‬
‫‪3‬‬
‫‪4‬‬

‫هنا‪ ،‬في المرة الأولى التي نطلب فيها الدالة ‪ ، increment‬يتم إنشاء المتغير ‪ . number‬ثم نقوم بز يادة ‪ 1‬إلى قيمته‪.‬‬
‫و ما إن تنتهي الدالة لا يمسح المتغير‪.‬‬

‫عندما نطلب الدالة للمرة الثانية‪ ،‬يتم ببساطة قفز السطر الخاص بالتصريح بالمتغير‪ ،‬و لا نقوم بإعادة إنشاء المتغير بل فقط‬
‫نعيد استعمال المتغير الذي أنشأناه سابقا‪ .‬عندما يأخذ المتغير القيمة ‪ ،1‬تصبح قيمته ‪ 2‬ثم ‪ 3‬ثم ‪ ... 4‬إلخ‪.‬‬

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

‫الدوال المحلية لملف‬

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

‫قد تحتاج أحيانا ً إلى إنشاء دوال يمكن الوصول إليها فقط في الملفات التي ٺتواجد بها‪ ،‬و للقيام بذلك‪ ،‬أضف الكلمة‬
‫المفتاحي ّة ‪ static‬قبل الدالة كالتالي ‪:‬‬

‫‪133‬‬
‫الفصل ‪ .10‬البرمجة المجز ّأة )‪(Modular Programming‬‬

‫)‪1 static int triple(int number‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪// Instructions‬‬
‫} ‪4‬‬

‫و لا تنس النموذج أيضا ‪:‬‬


‫;)‪1 static int triple(int number‬‬

‫إنتهى ! دالتك الساكنة ‪ triple‬لا يمكن استدعاؤها إلا من داخل دوال تنتمى لنفس الملف )مثلا ‪.( main.c‬‬
‫إذا حاولت استدعاء الدالة ‪ triple‬من خلال دالة أخرى من ملف آخر )مثلا ‪ ،( display.c‬لن يشتغل البرنامج‬
‫لأن ‪ triple‬وقتها لن تكون مرئية‪.‬‬

‫ل شيء يتعل ّق بنطاق المتغيرات ‪:‬‬


‫لنلخص ك ّ‬

‫• المتغير الذي يتم التصريح عنه داخل دالة يتم حذفه في نهاية الدالة مباشرة ‪ .‬لا يمكن أن يكون مرئيا إلا داخل هذه‬
‫الدالة‪.‬‬
‫• المتغير الذي يتم التصريح عنه في داخل دالة بالكلمة المفتاحية ‪ ، static‬لا يحذف في نهاية الدالة لـكنه يحتفظ‬
‫بقيمته ما دام البرنامج يعمل‪.‬‬

‫ل ملفات‬
‫ل الدوال و في ك ّ‬
‫• المتغير الذي يتم التصريح عنه خارج الدوال يسمى متغيرا شاملا‪ ،‬يكون مرئيا في ك ّ‬
‫المشروع‪.‬‬

‫• المتغير الشامل المصرّح عنه بالكلمة المفتاحية ‪ static‬مرئي فقط في الملف الّذي يتواجد به‪ ،‬و لا يكون مرئيا في‬
‫دوال باقي الملفات‪.‬‬

‫و بالمثل‪ ،‬هذه هي النطاقات الممكنة للدوال ‪:‬‬

‫• الدالة ال ّتي يتم التصريح عنها عاديا ًتكون مرئية في كل ملفات المشروع‪ ،‬يمكننا طلبها إذا من أي ملف كان‪.‬‬

‫• إذا أردنا أن تكون الدالة مرئية فقط في الملف الذي ٺتواجد به فيجب إضافة الكلمة المفتاحي ّة ‪ static‬قبلها‪.‬‬

‫ملخّ ص‬

‫• البرنامج يحتوي العديد من الملفات ‪ . .c‬كقاعدة عامة‪ ،‬لكل ملف ‪ .c‬ملف مرافق له يحمل الإمتداد ‪) .h‬الذي‬
‫يعني ملفّا رأسي ّا )‪ .((Header‬الـ ‪ .c‬يحوي الدوال بينما ‪ .h‬يحوي النماذج )‪ ،(Prototypes‬أي توقيعات هذه‬
‫الدوال‪.‬‬
‫• يتم تضمين محتوى الملفات ‪ .h‬في أعلى الملفات ‪ .c‬بالاستعانة ببرنامج يسمّى المعالج القبلي‪.‬‬

‫• يتم تحو يل الملفات ‪ .c‬إلى ملفات ثنائية ‪ .o‬من طرف المترجم‪.‬‬

‫• يتم تجميع الملفات ‪ .o‬في ملف تنفيذي ) ‪ ( .exe‬من طرف محرر الروابط )‪.(Linker‬‬

‫• المتغير الّذي يتم التصريح عنه داخل دالة غير قابل للوصول إلا من داخل هذه الدالة‪ .‬نتكلّم هنا عن نطاق المتغيرات‪.‬‬

‫‪134‬‬
‫الفصل ‪11‬‬

‫المؤش ّرات )‪(Pointers‬‬

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

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

‫مشكل مضجر بالفعل‬ ‫‪1.11‬‬

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

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

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

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

‫)(‪1 int function‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪return value‬‬
‫} ‪4‬‬

‫إذا استخدمنا ‪ int‬ترجع لنا قيمة من نوع ‪) int‬بفضل التعليمة ‪.( return‬‬

‫يمكننا أيضا كتابة دالة لا ت ُرجع أية قيمة باستخدام الكلمة المفتاحية ‪. void‬‬

‫‪135‬‬
(Pointers) ‫ المؤش ّرات‬.11 ‫الفصل‬

1 void function()
2 {
3
4 }

. return ‫ هذا أمر مستحيل لأنه لا يمكننا استعمال تعليمتي‬... ‫لـكن إرجاع قيمتين مختلفتين في نفس الوقت‬

‫ تقوم الدالة بإرجاع عدد الساعات و الدقائق المواقفة‬.‫لنفرض أنني أريد كتابة دالة أعطيها كمدخل عددا من الدقائق‬
.‫لها‬

.‫ دقيقة‬45 ‫ ساعة و‬0 ‫ الدالة ترجع‬45 ‫• إذا أعطيت القيمة‬

.‫ دقائق‬0 ‫ ساعة و‬1 ‫ الدالة ترجع القيمة‬60 ‫• إذا أعطيت القيمة‬

.‫ دقيقة‬30 ‫ ساعة و‬1 ‫ الدالة ترجع القيمة‬90 ‫• إذا أعطيت القيمة‬

: ‫لنكن مجانين و لنجر ّب ذلك‬

1 #include <stdio.h>
2 #include <stdlib.h>
3
4 /� I put the prototype at the top.
5 Because it’s a short code, I don’t put it in a .h file.
6 In a real program I would have put the prototype
7 in a separate .h file of course �/
8
9 void minutesDevision(int hours, int minutes);
10
11 int main(int argc, char �argv[])
12 {
13 int hours = 0, minutes = 90;
14
15 /� We have a variable ”minutes” equals to 90.
16 after calling the function, I want from the variable“
17 hours” to take the value 1 and from my variable
18 ”minutes” to take the value 30 �/
19
20 minutesDivision(hours, minutes);
21 printf(”%d hours and %d minutes”, hours, minutes);
22 return 0;
23 }
24
25 void minutesDevision(int hours, int minutes)
26 {
27 hours = minutes / 60; // 90 / 60 = 1
28 minutes = minutes % 60; // 90 % 60 = 30
29 }

136
‫‪ .2.11‬الذاكرة‪ ،‬مسألة عنوان‬

‫النتيجة ‪:‬‬

‫‪0 hours and 90 minutes‬‬

‫لم تشتغل ! ما الأمر يا ترى ؟ في الواقع‪ ،‬عندما نبعث متغيرا إلى دالة‪ ،‬يتم إنشاء نسخة من المتغير‪ ،‬و لهذا فالمتغير‬
‫‪ hours‬في الدالة ‪ minutesDevision‬هو ليس نفسه الذي في الدالة ‪ ! main‬إنه فقط نسخة !‬

‫الدالة ‪ minutesDevision‬تقوم بعملها‪ .‬ففي داخلها المتغيران ‪ hours‬و ‪ minutes‬يحملان القيمتين الصحيحتين‬
‫‪ 1 :‬و ‪.30‬‬

‫لـكن بعد ذلك‪ ،‬ٺتوقف الدالة مباشرة عند الوصول إلى الحاضنة الغالقة‪ ،‬مثلما تعلمنا سابقا ‪ :‬المتغيرات الخاصة بدالة‬
‫يتم حذفها مباشرة عند انتهاء الدالة‪ .‬إذن النسخ عن المتغيرات ‪ minutes‬و ‪ hours‬تُمسح‪ .‬نرجع بعد ذلك للدالة‬
‫‪ . main‬و التي فيها متغيراتنا ‪ minutes‬و ‪ hours‬تحملان القيمتين ‪ 0‬و ‪ .90‬لقد فشلنا !‬

‫م‬
‫لاحظ إذن‪ ،‬بما أن الدالة تقوم بنسخ المتغيرات التي نعطيها لها‪ ،‬لست مضطرا ً لستمية متغيراتك بنفس الأسماء التي‬
‫تحملها في الـدالة الرئيسية ‪ . main‬و بالتالي يمكنك ببساطة كتابة ‪:‬‬
‫)‪void minutesDivision(int h, int m‬‬
‫‪ h‬للساعات و ‪ m‬للدقائق‪.‬‬
‫إن كانت متغيراتك لم تسمّى بنفس الطر يقة في الدالة و في ‪ main‬فهذا لا يطرح أيّ مشكل !‬

‫‪return‬‬ ‫ل الاتجاهات‪ .‬يمكنك محاولة بعث قيمة باستخدام الدالة )باستخدام‬


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

‫حسنا ًالمشكل لازال مطروحاً‪ ،‬كيف يمكننا حلّه باستخدام المؤشرات ؟‬

‫الذاكرة‪ ،‬مسألة عنوان‬ ‫‪2.11‬‬

‫تذكير بالمكتسبات القبلي ّة‬

‫سأعود بك قليلا ًإلى الوراء‪ ،‬هل ٺتذكر فصل المتغيرات ؟‬

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

‫‪137‬‬
‫الفصل ‪ .11‬المؤش ّرات )‪(Pointers‬‬

‫هكذا نقوم تقريبا ًبتمثيل الذاكرة الحية )‪ (RAM‬الخاصة بالحاسوب‪.‬‬

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

‫ل عنوان يمكننا تخزين عدد واحد فقط‪ .‬لا يمكننا تخزين عددين في نفس العنوان‪.‬‬
‫في ك ّ‬

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

‫عنوان و قيمة‬

‫حينما تنشئ متغيرا ً ‪ age‬من نوع ‪ int‬مثلا‪ ،‬بكتابة ‪:‬‬

‫;‪1 int age = 10‬‬

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

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

‫‪138‬‬
‫‪ .2.11‬الذاكرة‪ ،‬مسألة عنوان‬

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

‫لنعد للمتغير ‪ . age‬تم تخزين القيمة ‪ 10‬في مكان ما من الذاكرة‪ ،‬لنقل مثلا ًفي العنوان رقم ‪.4655‬‬
‫ل مرة‬
‫ما يحدث )و هذا دور المترجم( هو أن الكلمة ‪ age‬يتم تعو يضها بالعنوان ‪ 4655‬لحظة التنفيذ‪ .‬مما يعني أنه في ك ّ‬
‫قمت فيها بكتابة الكلمة ‪ age‬في الشفرة المصدر ية‪ ،‬يتم تعوضيها بـ‪ ،4655‬و بهذا يرى الجهاز إلى أي عنوان في الذاكرة عليه‬
‫ل فخر بأن المتغير ‪ age‬يحتوي القيمة ‪.10‬‬
‫الذهاب‪ .‬و منه يجيب بك ّ‬

‫ل بساطة أن نكتب الكلمة ‪ age‬في الشفرة المصدر ية‪ .‬إذا أردنا‬


‫نحن نعرف إذا كيف نسترجع قيمة متغير‪ ،‬يكفي بك ّ‬
‫إظهار السنّ ‪ ،‬يمكننا استعمال الدالة ‪: printf‬‬

‫;)‪1 printf(”The value of variable age is : %d”, age‬‬

‫النتيجة على الشاشة ‪:‬‬

‫‪The value of variable age is : 10‬‬

‫لا شيء جديد لح ّد الآن‪.‬‬

‫الخـبر المثير لليوم‬

‫أنت تعرف كيف تظهر قيمة متغير‪ ،‬لـكن هل تعرف أنه بإمكاننا أيضا إظهار عنوانه ؟‬

‫لـكي نُظهر عنوان متغير‪ ،‬نستعمل الإشارة ‪) %p‬الحرف ‪ p‬مأخوذ من الكلمة ‪ (Pointer‬في الدالة ‪ . printf‬أي أننا‬
‫لن نبعث للدالة ‪ printf‬المتغير في ح ّد ذاته لـكن نبعث لها عنوانه‪ .‬و لفعل هذا‪ ،‬يجب عليك استعمال الإشارة & أمام‬
‫المتغير ‪ age‬كما طلبت منك أن تفعل مع الدالة ‪ scanf‬من قبل دون أن أشرح لك لماذا‪.‬‬

‫أكتب إذا‪:‬‬

‫;)‪1 printf(”The address of the variable age is : %p”, &age‬‬

‫النتيجة ‪:‬‬

‫‪The address of the variable age is : 0023FF74‬‬

‫ما تراه هنا هو عنوان المتغير ‪ age‬في اللحظة التي طلبتُ فيها تنفيذ البرنامج من طرف حاسوبي‪ .‬نعم نعم‪0023FF74 ،‬‬
‫هو رقم‪ ،‬هو فقط مكتوب في النظام الست عشري )‪ (Hexadecimal‬عوض النظام العشري الذي تعو ّدنا عليه‪ .‬لو تقوم‬
‫بتعو يض ‪ %p‬بـ ‪ %d‬فإنه سيظهر لك رقما عشر يا كما تعو ّدنا‪.‬‬

‫‪139‬‬
‫الفصل ‪ .11‬المؤش ّرات )‪(Pointers‬‬

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

‫ل هذا ؟ أريدك أن ٺتذك ّر التالي ‪:‬‬


‫إلى أين أريد الوصول بك ّ‬

‫• ‪ : age‬تعني قيمة المتغير‪.‬‬

‫• ‪ : &age‬تعني عنوان المتغير‪.‬‬

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

‫استعمال المؤشرات‬ ‫‪3.11‬‬

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

‫؟‬
‫لـكن ‪ ...‬العناوين هي أعداد أيضاً‪ ،‬أليس كذلك ؟ هذا يعني أننا سنخزن أعدادا ً دائما !‬

‫هذا صحيح‪ ،‬لـكن لهذه الأعداد معنى آخر ‪ :‬هي تشير إلى عنوان متغير آخر في الذاكرة‪.‬‬

‫إنشاء مؤش ّر‬

‫لإنشاء متغير من نوع مؤش ّر‪ ،‬يجب علينا أن نضيف الرمز * أمام إسم المتغير ‪:‬‬

‫;‪1 int �myPointer‬‬

‫م‬
‫لاحظ أنه يمكننا أيضا أن نكتب ;‪ int* myPointer‬لهذا نفس المعنى‪ .‬لـكن الطر يقة الأولى هي المفضّ لة‪.‬‬
‫في الواقع‪ ،‬إن كنت تريد التصريح عن العديد من المؤشرات في نفس السطر‪ ،‬سيكون عليك أن تعيد كتابة النجمة‬
‫;‪int *pointer1, *pointer2, *pointer3‬‬ ‫أمام كل اسم ‪:‬‬

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

‫‪140‬‬
‫‪ .3.11‬استعمال المؤشرات‬

‫;‪1 int �myPointer = NULL‬‬

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

‫ما الذي يحصل ؟ ستقوم هذه الشفرة المصدر ية بحجز خانة في الذاكرة كما لو أننا أنشأنا متغيرا ً عادياً‪ .‬الشيء الذي يتغير‬
‫هو أن المؤشر سيحتوي عنوانا‪ .‬عنوان متغير آخر‪.‬‬

‫لم لا عنوان المتغير ‪ age‬؟ أنت تعرف الآن كيف تشير إلى عنوان متغير في مكان قيمته )باستعمال الرمز & (‪ ،‬هيا‬
‫بنا إذن ! هذا ما عليك كتابته ‪:‬‬

‫;‪1 int age = 10‬‬


‫;‪2 int �PointerOnAge = &age‬‬

‫السطر الأول يعني ‪” :‬أنشئ متغيرا من نوع ‪ int‬يحمل القيمة ‪ .”10‬السطر الثاني يعني ”أنشئ متغيرا ً من نوع مؤش ّر‬
‫قيمته هي عنوان المتغير ‪.” age‬‬

‫يقوم إذا السطر الثاني بمهمّتين معاً‪ .‬لـكي لا تختلط عليك الأمور‪ ،‬إعلم أنه يمكننا تقسيم السطر إلى سطرين ‪:‬‬

‫;‪1 int age = 10‬‬


‫”‪2 int �PointerOnAge; // 1) Means ”I create the pointer‬‬
‫‪3 PointerOnAge = &age; // 2) Means the pointer ”PointerOnAge contains the address‬‬
‫”‪of age‬‬

‫يمكنك الملاحظة أنه لا يوجد في لغة الـ‪ C‬نوع نسميه ‪ pointer‬كالنوع ‪ int‬و ‪ . double‬أي أنه لا يمكننا أن‬
‫نكتب ‪:‬‬

‫;‪1 pointer PointerOnAge‬‬

‫في مكان هذا‪ ،‬نستعمل الرمز * ‪ ،‬و لـكن نستمر في كتابة ‪ . int‬ماذا يعني هذا ؟ في الواقع يجب أن نشير إلى نوع‬
‫المتغير الذي سيحوي عنوانه المؤشر‪ .‬بما أن المؤشر ‪ PointerOnAge‬سيحتوي عنوان المتغير ‪) age‬الذي هو من نوع‬
‫‪ ،( int‬إذا فالمؤشر يجب أن يكون من نوع *‪ ! int‬إذا كان المتغير من نوع ‪ double‬فإنه يجب عليّ أن أكتب‬
‫‪. double *myPointer‬‬

‫إصطلاح ‪ :‬نقول بأن المؤش ّر ‪ PointerOnAge‬يؤش ّر على المتغير ‪. age‬‬

‫المخطط التالي يلخّ ص ما يحصل في الذاكرة ‪:‬‬

‫‪141‬‬
‫الفصل ‪ .11‬المؤش ّرات )‪(Pointers‬‬

‫‪PointerOnAge‬‬ ‫في هذا المخطط‪ ،‬تم تعو يض المتغير ‪ age‬بالعنوان ‪) 177450‬أنت ترى بأن قيمته هي ‪ ،(10‬و المؤش ّر‬
‫تم تعو يضه بالعنوان ‪) 3‬هذه محض صدفة(‪.‬‬

‫حينما يتم إنشاء المؤشر‪ ،‬يقوم نظام التشغيل بحجز خانة في الذاكرة كما فعل مع المتغير ‪ . age‬الشيء المختلف هنا هو‬
‫أن المتغير ‪ PointerOnAge‬له معنى آخر‪ .‬أنظر للمخطط جيدا ً ‪ :‬قيمته هي عنوان المتغير ‪. age‬‬

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

‫؟‬
‫و ‪ ...‬ما هي فائدة هذا ؟‬

‫هذا لا يقوم بتحو يل الحاسوب إلى آلة صنع القهوة‪ ،‬طبعا‪ .‬لـكن الآن لدينا المؤشر ‪ PointerOnAge‬يحتوي عنوان‬
‫المتغير ‪. age‬‬

‫فلنحاول رؤ ية ما يحتو يه المؤشر بالإستعانة بالدالة ‪: printf‬‬


‫;‪1 int age = 10‬‬
‫;‪2 int �PointerOnAge = &age‬‬
‫;)‪3 printf(”%d”, PointerOnAge‬‬

‫‪177450‬‬

‫هذا ليس مفاجئاً‪ ،‬نحن نطلب قيمة ‪ PointerOnAge‬و قيمته هي عنوان المتغير ‪) age‬أي ‪.(177450‬‬
‫ماذا نفعل لـكي نطلب قيمة المتغير المتواجدة في العنوان الذي يشير إليه المؤش ّر ‪ PointerOnAge‬؟ يجب أن نضع الرمز‬
‫* أمام إسم المؤش ّر ‪:‬‬

‫‪142‬‬
‫‪ .3.11‬استعمال المؤشرات‬

‫;‪1 int age = 10‬‬


‫;‪2 int �PointerOnAge = &age‬‬
‫;)‪3 printf(”%d, �PointerOnAge‬‬

‫‪10‬‬

‫ها قد وصلنا ! بوضع الرمز * أمام إسم المؤش ّر‪ ،‬يمكننا الوصول إلى قيمة المتغير ‪. age‬‬

‫لو استعملنا الرمز & أمام اسم المؤش ّر‪ ،‬سنتحصل على العنوان الذي يتواجد به المؤش ّر )هنا الرقم ‪.(3‬‬

‫؟‬
‫ماذا نربح هنا ؟ لقد نجحنا في تعقيد الأمور لا أكثر‪ .‬لم نكن نحتاج إلى مؤش ّر لنظهر قيمة المتغير ‪! age‬‬

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

‫المهم هو أن تفهم المبدأ الآن و بعده ستتوضح الأمور لوحدها رويدا ً رويداً‪.‬‬

‫ما يجب تذك ّره‬

‫هذا ما يفترض أن تكون قد فهمته و يجب عليك تذكره لباقي الفصل ‪:‬‬

‫• بالنسبة لمتغير كالمتغير ‪: age‬‬

‫– ‪ age‬تعني‪” :‬أريد قيمة المتغير ‪” age‬‬

‫– ‪ &age‬تعني‪” :‬أريد العنوان الذي يتواجد به المتغير ‪” age‬‬

‫• بالنسبة لمؤش ّر كالمؤشر ‪: PointerOnAge‬‬

‫– ‪ PointerOnAge‬تعني‪” :‬أريد قيمة ‪) ” PointerOnAge‬هذه القيمة هي عنوان(‪.‬‬

‫– ‪ *PointerOnAge‬تعني ‪” :‬أريد قيمة المتغير المتواجد في العنوان الذي يحتو يه المؤشر ‪.” PointerOnAge‬‬

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

‫‪143‬‬
‫الفصل ‪ .11‬المؤش ّرات )‪(Pointers‬‬

‫!‬
‫حينما تصرّح عن مؤش ّر‪ ،‬فّإن النجمة‬ ‫احذر في عدم الخلط بين مختلف تفسيرات النجمة !‬
‫;‪ int *PointerOnAge‬بينما لما نكتبها بجانب اسم المؤشر‬ ‫تشير إلى أننا بصدد إنشاء مؤشر ‪:‬‬
‫;)‪ printf(”%d”, *PointerOnAge‬فهذا يعني ‪ :‬أريد قيمة المتغير التي يشير إليها المؤشر‬ ‫بكتابة ‪:‬‬
‫‪. PointerOnAge‬‬

‫م أن ٺتعلّمها عن ظهر قلب‪ .‬لا تتردّد في قراءة و إعادة قراءة ما‬


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

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

‫إرسال مؤش ّر إلى دالة‬ ‫‪4.11‬‬

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

‫كيف يعمل هذا ؟ هناك الـكثير من الطرق لفعل هذا‪ .‬إليك مثالا ً‪:‬‬

‫‪144‬‬
‫‪ .4.11‬إرسال مؤش ّر إلى دالة‬

‫;)‪1 void triplePointer(int �numberPointer‬‬


‫‪2‬‬
‫)][‪3 int main(int argc, char �argv‬‬
‫{ ‪4‬‬
‫‪5‬‬ ‫;‪int number = 5‬‬
‫‪6‬‬
‫‪7‬‬ ‫‪triplePointer(&number); // We send the address of number to the‬‬
‫‪function‬‬
‫‪8‬‬ ‫‪printf(”%d”, number); // We display the variable number. The function‬‬
‫‪has directly modified its value because it knows the address of the‬‬
‫‪variable‬‬
‫‪9‬‬
‫‪10‬‬ ‫;‪return 0‬‬
‫‪11‬‬ ‫}‬
‫‪12‬‬
‫‪13‬‬ ‫)‪void triplePointeur(int �numberPointer‬‬
‫‪14‬‬ ‫{‬
‫‪15‬‬ ‫‪�numberPointer �= 3; // We multiply by 3 the value of number‬‬
‫‪16‬‬ ‫}‬

‫‪15‬‬

‫الدالة ‪ triplePointer‬تأخذ معاملا من نوع *‪) int‬أي مؤش ّر على ‪.( int‬‬
‫إليك ما يحدث بالترتيب‪ ،‬إنطلاقا ًمن بداية الدالة ‪: main‬‬

‫‪ .1‬يتم إنشاء متغير ‪ number‬في الدالة ‪ ، main‬نُسن ِد له القيمة ‪ ،5‬أنت تعرف هذا‪.‬‬

‫‪ .2‬نستدعي الدالة ‪ triplePointer‬و نرسل لها كمعامل عنوان المتغير ‪. number‬‬

‫‪ .3‬ٺتلقّى الدالة ‪ triplePointer‬المتغير في المؤشر ‪ . numberPointer‬داخل الدالة‬


‫‪ ، triplePointer‬لدينا إذن المؤشر ‪ numberPointer‬الذي يؤشر على المتغير ‪. number‬‬

‫‪ .4‬و الآن بما أنه لدينا مؤشر على المتغير ‪ ، number‬يمكننا تغيير قيمة المتغير ‪ number‬مباشرة في الذاكرة ! يكفي‬
‫استعمال *‪ numberPointer‬للإشارة إلى المتغير ‪ ! number‬كمثال‪ ،‬نقوم باختبار بسيط ‪ :‬نضرب المتغير في‬
‫‪.3‬‬
‫‪ .5‬يالعودة إلى ‪ ، main‬يحتوي المتغير ‪ number‬القيمة ‪ 15‬لأن الدالة ‪ triplePointer‬قامت بتعديل قيمته‬
‫مباشرة‪.‬‬

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

‫؟‬
‫ما الفائدة الآن من استعمال ‪ return‬في دالة إذا كان بإمكاننا استعمال المؤشرات لتعديل قيم المتغيرات ؟‬

‫‪145‬‬
‫الفصل ‪ .11‬المؤش ّرات )‪(Pointers‬‬

‫هذا يعتمد عليك و على برنامجك‪ .‬القرار لك‪ .‬يجب أن تعرف أن تعليمة ‪ return‬مستعملة بكثرة في لغة الـ‪ .C‬غالبا ً‬
‫نستعملها لإرجاع شفرة الخطأ ‪ :‬الدالة ترجع القيمة ‪) 1‬صحيح( إذا اشتغل كل شيء على ما ي ُرام‪ ،‬و ‪) 0‬خطأ( إذا حدث‬
‫خطأ ما أثناء تشغيل البرنامج‪.‬‬

‫طر يقة أخرى لإرسال مؤشر إلى دالة‬

‫في الشفرة المصدر ية التي رأيناها‪ ،‬لم يكن هناك مؤشر في الدالة ‪ ، main‬و إنما فقط متغير ‪ . number‬المؤشر الوحيد كان‬
‫في الدالة ‪) triplePointer‬من نوع *‪.( int‬‬

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

‫;)‪1 void triplePointer(int �numberPointer‬‬


‫‪2‬‬
‫)][‪3 int main(int argc, char �argv‬‬
‫{ ‪4‬‬
‫‪5‬‬ ‫;‪int number = 5‬‬
‫‪6‬‬ ‫‪int �pointer = &number; // pointer gets the address of number‬‬
‫‪7‬‬ ‫‪triplePointer(pointer); // We send pointer (the address of number) to‬‬
‫‪the function‬‬
‫‪8‬‬ ‫;)‪printf(”%d”, �pointeur‬‬
‫‪9‬‬ ‫;‪return 0‬‬
‫‪10‬‬ ‫}‬
‫‪11‬‬
‫‪12‬‬ ‫)‪void triplePointer(int �numberPointer‬‬
‫‪13‬‬ ‫{‬
‫‪14‬‬ ‫‪�numberPointer �= 3; // We multiply by 3 the value of number‬‬
‫‪15‬‬ ‫}‬

‫قارن بين الشفرتين المصدريتين السابقتين‪ ،‬ستجد بأن هناك اختلافا بينهما‪ ،‬لـكن النتيجة واحدة ‪:‬‬

‫‪15‬‬

‫ما يهم‪ ،‬هو بعث عنوان المتغير ‪ number‬إلى الدالة‪ .‬لـكن المؤشر يحمل عنوان المتغير ‪ ، number‬فهذا جيد من هذه‬
‫الناحية ! نحن فقط نقوم بالأمر بطر يقة مختلفة بإنشاء المؤشر في الدالة ‪. main‬‬
‫في الدالة ‪) printf‬فقط من أجل التمرين(‪ُ ،‬أظهر محتوى المتغير ‪ number‬بكتابة ‪ . *pointer‬لاحظ أنه في مكان‬
‫ن ‪ *pointer‬و ‪ number‬يشيران إلى نفس الخانة‬
‫هذا‪ ،‬يمكنني كتابة ‪ : number‬ستكون النتيجة هي نفسها لأ ّ‬
‫بالذاكرة‪.‬‬

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

‫;‪1 int number = 0‬‬


‫;)‪2 scanf(”%d”, &number‬‬

‫‪146‬‬
‫‪ .5.11‬من الذي قال ”مشكل مضجر بالفعل” ؟‬

‫تعمل الدالة بمؤشر على المتغير ‪ number‬و بهذا يمكنها تغيير قيمته مباشرة‪.‬‬
‫كما رأينا الآن‪ ،‬يمكننا إنشاء مؤشر و نبعثه للدالة ‪: scanf‬‬

‫;‪1 int number = 0‬‬


‫;‪2 int �pointer = &number‬‬
‫;)‪3 scanf(”%d”, pointer‬‬

‫إنتبه كي لا تضع الرمز & امام اسم مؤشر في الدالة ‪ ! scanf‬هنا ‪ pointer‬يحتوي هو نفسه عنوان المتغير‬
‫‪ ، number‬لذا أنت لا تحتاج لوضع & ! إذا فعلت هذا فإنك ستبعث العنوان الذي يتواجد به المؤشر ‪ :‬و لـكننا بحاجة‬
‫لعنوان ‪! number‬‬

‫من الذي قال ”مشكل مضجر بالفعل” ؟‬ ‫‪5.11‬‬

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

‫‪1‬‬ ‫;)‪void minutesDivision(int� hoursPointer, int� minutesPointer‬‬


‫‪2‬‬
‫‪3‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;‪int hours = 0, minutes = 90‬‬
‫‪6‬‬ ‫‪// We send the addresses of hours and minutes‬‬
‫‪7‬‬ ‫;)‪minutesDivision(&hours, &minutes‬‬
‫‪8‬‬ ‫! ‪// This time, the values are modified‬‬
‫‪9‬‬ ‫;)‪printf(”%d hours and %d minutes”, hours, minutes‬‬
‫‪10‬‬ ‫;‪return 0‬‬
‫‪11‬‬ ‫}‬
‫‪12‬‬
‫‪13‬‬ ‫)‪void minutesDivision(int� hoursPointer, int� minutesPointer‬‬
‫‪14‬‬ ‫{‬
‫‪15‬‬ ‫‪/� Don’t forget to put a star next to the pointer’s name ! so you can‬‬
‫‪modify the value of the variable and not its address ! You don’t‬‬
‫‪want to divide addresses, do you ? �/‬‬
‫‪16‬‬ ‫;‪�hoursPointer = �minutesPointer / 60‬‬
‫‪17‬‬ ‫;‪�minutesPointer = �minutesPointer % 60‬‬
‫‪18‬‬ ‫}‬

‫النتيجة ‪:‬‬

‫‪1 hours and 30 minutes‬‬

‫ل مرة‪ ،‬سأشرح ما يحصل هنا خطوة بخطوة‬


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

‫• يتم إنشاء المتغيرين ‪ hours‬و ‪ minutes‬في الدالة ‪. main‬‬

‫‪147‬‬
‫الفصل ‪ .11‬المؤش ّرات )‪(Pointers‬‬

‫• نرسل للدالة ‪ minutesDevision‬عنواني المتغيرين ‪ hours‬و ‪. minutes‬‬

‫• تقوم الدالة ‪ minutesDevision‬باسترجاع قيمتي المتغيرين في مؤشرين يدعيان ‪ hoursPointer‬و ‪. minutesPointer‬‬


‫لاحظ أنه لا يهم الاسم هنا أيضاً‪ .‬كان بإمكاني تسميتهما ‪ m‬و ‪ ، h‬أو حتى ‪ hours‬و ‪ minutes‬لم أقم بهذا‬
‫لـكي لا تخلط بين المتغيرين الخاصين بالدالة الرئيسية مع هذين المؤشرين اللذان يختلفان معهما‪.‬‬

‫• تقوم الدالة ‪ minutesDivision‬بتعديل مباشر لقيمتي المتغيرين ‪ hours‬و ‪ minutes‬في الذاكرة لأنها تملك‬
‫عنوانيهما في مؤشرين‪ .‬الشيء الوحيد الذي يمكنه أن يشكل مشكلا و يجب أن أبقيه ببالي هو وضع النجمة أمام‬
‫اسمي المؤشرين إذا أردت أن أغير قيمتي المتغيرين ‪ hours‬و ‪ . minutes‬إذا لم نفعل هذا‪ ،‬فإننا سنغير العنوانين‬
‫الموجودين في المؤشرين‪ ،‬و هذا لا ينفع في شيء‪.‬‬

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

‫ملخّ ص‬

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

‫• تشبه المؤشرات المتغيرات لـكنها مخصصة لتخزين العناوين التي ٺتواجد بها المتغيرات في الذاكرة‪.‬‬

‫• إذا وضعنا الرمز & أمام اسم متغير فإننا نتحصل على العنوان الذي يتواجد به المتغير‪ ،‬مثلا ‪. &age :‬‬

‫• إذا وضعنا الرمز * امام إسم مؤشر نتحصل على قيمة المتغير التي يؤش ّر عليها المؤش ّر‪.‬‬

‫• تعتبر المؤشرات إحدى المبادئ الأساسية في لغة الـ‪ ،C‬تبدو صعبة في البداية لـكن عليك أن تفهمها لأن الـكثير من‬
‫المبادئ الأخرى تقوم عليها‪.‬‬

‫‪148‬‬
‫الفصل ‪12‬‬

‫الجداول )‪(Arrays‬‬

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

‫سنتعلم في هذا الفصل كيف ننشئ متغيرات من نوع ”جداول”‪ .‬الجدوال مهمّة للغاية في لغة الـ‪ C‬لأنها تساعد في تنظيم‬
‫سلسلة من القيم‪.‬‬

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

‫الجداول في الذاكرة‬ ‫‪1.12‬‬

‫”الجداول هي ٺتابع متغيرات من نفس النوع‪ ،‬موجودة في مكان متواصل من الذاكرة‪”.‬‬

‫أعرف أن هذا التعر يف يشبه قليلا تعر يف القاموس‪ .‬لهذا فسأوضح بطر يقة أخرى‪ ،‬فعليا ّّ‪ ،‬الجدول عبارة عن ”متغي ّرات‬
‫ضخمة” يمكن لها أن تحتوي على أعداد كبيرة من نفس النوع ) ‪.(... double ، int ، long ، char‬‬

‫للجدول طول محدد‪ .‬يمكنه أن يكون ‪ 10 ،3 ،2‬خانات‪ 2500 ،150 ،‬خانة‪ ،‬أنت من يحدد العدد‪ .‬المخطط التالي‬
‫مثال عن جدول يحجز ‪ 4‬خانات بدءا ً بالعنوان ‪: 1600‬‬

‫‪149‬‬
‫الفصل ‪ .12‬الجداول )‪(Arrays‬‬

‫عندما تطلب إنشاء جدول يحجز ‪ 4‬خانات في الذاكرة‪ ،‬سيطلب برنامجك من نظام التشغيل أن يسمح له باستغلال‬
‫‪ 4‬خانات في الذاكرة‪ ،‬و يجب ان تكون هذه الخانات متتالية يعني الواحدة بجانب الأخرى‪ .‬و كما ترى أعلاه فالخانات‬
‫متتابعة ‪ 1603 ،1602 ،1601 ،1600‬فلا يوجد ”فراغ” بينها‪.‬‬

‫ل خانة يجب أن تحتوي عددا‬


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

‫و كتلخيص‪ ،‬هذا أهم ما يجب أن تعرفه بخصوص الجداول ‪:‬‬

‫• عندما يتم إنشاء جدول‪ ،‬يأخذ مكانا متواصلا ًفي الذاكرة‪ .‬بحيث تكون الخانات متجاورة الواحدة تلو الأخرى‪.‬‬

‫• كل خانات الجدول تكون من نفس النوع‪ ،‬فجدول الـ ‪ int‬يمكن أن يحمل فقط ‪ ، int‬و لا أي نوع آخر‪.‬‬

‫تعر يف جدول‬ ‫‪2.12‬‬

‫كي نبدأ سننشئ جدولا من ‪ 4‬أعداد من نوع ‪: int‬‬


‫;]‪1 int table[4‬‬

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

‫و لـكن الآن‪ ،‬كيف نصل لخانة ما في الجدول ؟ هذا سهل‪ ،‬تكفي كتابة ]‪. table[cellNumber‬‬

‫‪x‬‬
‫احذر ‪ :‬كل جدول يجب أن يبدأ بالفهرس )‪ (Index‬رقم ‪ ! 0‬جدولنا متكو ّن من ‪ int 4‬إذن فالفهارس‬
‫المتوفرة هي ‪ 2 ،1 ،0 :‬و ‪ .3‬لا وجود للفهرس ‪ 4‬في جدول من ‪ 4‬خانات ! هذا مصدر أخطاء متداولة‪ ،‬فلا‬
‫تغفل عنه !‬

‫إذا كنت أريد أن أضع في جدولي نفس القيم التي في المخطط فبجب إذا أن أكت ُب ‪:‬‬

‫‪150‬‬
‫‪ .2.12‬تعر يف جدول‬

‫‪1‬‬ ‫;]‪int table[4‬‬


‫‪2‬‬ ‫;‪table[0] = 10‬‬
‫‪3‬‬ ‫;‪table[1] = 23‬‬
‫‪4‬‬ ‫;‪table[2] = 505‬‬
‫‪5‬‬ ‫;‪table[3] = 8‬‬

‫؟‬
‫لازلت لا أرى العلاقة بين المؤشرات و الجداول ؟‬

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

‫;]‪1 int table[4‬‬


‫;)‪2 printf(”%d”, table‬‬

‫النتيجة ستظهر لك العنوان الذي يتواجد به ‪: table‬‬

‫‪1600‬‬

‫بينما إذا قمت بوضع فهرس الخانة بين قوسين مربعين‪ ،‬فستحصل على القيمة ‪:‬‬

‫;]‪1 int table[4‬‬


‫;)]‪2 printf(”%d”, table[0‬‬

‫‪10‬‬

‫نفس الشيء بالنسبة للفهارس الأخرى‪ .‬بما أن ‪ table‬هو مؤشر‪ ،‬يمكننا استعمال الرمز * للحصول على القيمة‬
‫الأولى ‪:‬‬

‫;]‪1 int table[4‬‬


‫;)‪2 printf(”%d”, �table‬‬

‫‪10‬‬

‫يمكن أيضا الحصول على قيمة الخانة الثانية بكتابة )‪) *(table + 1‬أي عنوان الجدول ‪ .(1 +‬لذا فهذان السطران‬
‫متماثلان ‪:‬‬

‫)‪1 table[1] // Returns the value of the second cell (the first is 0‬‬
‫‪2 �(table + 1) // Same thing : returns the value of the second cell.‬‬

‫لذا فعند كتابة ]‪ ، table[0‬فأنت تطلب قيمة الخانة التي ٺتواجد بعنوان الجدول ‪ 0 +‬خانة‪ ،‬أي ‪.1600‬‬
‫و إذا كتبت ]‪ table[1‬فإنك تطلب القيمة المتواجدة في عنوان الجدول ‪ 1 +‬خانة‪ ،‬أي ‪.1601‬‬
‫و هكذا من أجل الباقي‪.‬‬

‫‪151‬‬
‫الفصل ‪ .12‬الجداول )‪(Arrays‬‬

‫الجداول ذات الحجم المتغي ّر‬

‫هناك عدة نسخ من لغة ‪.C‬‬


‫نسخة حديثة منها تدعى ‪ C99‬تسمح بإنشاء جداول ذات حجم متغي ّر‪ .‬يعني أن حجم الجداول يمكن أن يكون معر ّفا بمتغير‪.‬‬
‫;‪1 int size = 5‬‬
‫;]‪2 int table[size‬‬

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

‫يجب أن نتفق على شيء و هو ‪ :‬لا تملك الحق في وضع متغير بين القوسين المربعين من أجل تعر يف حجم الجدول‪.‬‬
‫حتى و إن كان المتغير ثابتا ! يجب على طول الجدول أن يأخذ قيمة ثابتة‪ ،‬و لهذا عليك أن تحدده كعدد ‪:‬‬
‫;]‪1 int table[5‬‬

‫؟‬
‫إذن ‪ ...‬هل من الممنوع إنشاء جدول يعتمد حجمه على قيمة متغير ؟‬

‫بلى إنه ممكن حتى مع ‪ .C89‬لـكن لفعل هذا سنعتمد على تقنية أخرى )أكيدة أكثر و تعمل مع كل المترجمات(‬
‫ي )‪ .(Dynamic allocation‬سندرسها في مرحلة متقدمة من هذا الكتاب‪.‬‬
‫تدعى بـالحجز الح ّ‬

‫تصفح جدول‬ ‫‪3.12‬‬

‫لنفرض أنني أريد الآن أن ُأظهر كل قيم خانات الجدول‪.‬‬


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

‫الأحسن هو أن نستعين بحلقة‪ .‬لم لا حلقة ‪ for‬؟ فهي الأنسب لتصفح الجداول ‪:‬‬
‫)][‪1 int main(int argc, char �argv‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;‪int table[4], i = 0‬‬
‫‪4‬‬ ‫;‪table[0] = 10‬‬
‫‪5‬‬ ‫;‪table[1] = 23‬‬
‫‪6‬‬ ‫;‪table[2] = 505‬‬
‫‪7‬‬ ‫;‪table[3] = 8‬‬
‫‪8‬‬ ‫)‪for (i = 0 ; i < 4 ; i++‬‬
‫‪9‬‬ ‫{‬
‫‪10‬‬ ‫;)]‪printf(”%d\n”, table[i‬‬
‫‪11‬‬ ‫}‬
‫‪12‬‬ ‫;‪return 0‬‬
‫} ‪13‬‬

‫‪152‬‬
‫‪ .3.12‬تصفح جدول‬

‫‪10‬‬
‫‪23‬‬
‫‪505‬‬
‫‪8‬‬

‫إن حلقتنا ٺتصفح الجدول بمساعدة متغير يسمى ‪) i‬اسم شائع لدى المبرمجـين يخص المتغير الذي يستخدم لتصفح‬
‫جدول !(‪.‬‬

‫إن الشيء العَم َل ِ َيّ خاصة‪ ،‬هو أنه بإمكاننا وضع متغير داخل قوسين مربعين بالفعل‪ .‬فالمتغير كان ممنوعا في مرحلة إنشاء‬
‫الجدول )لتعر يف حجمه(‪ .‬لـكن و لحسن الحظ‪ ،‬فهو مسموح من أجل ”تصفح” الجدول‪ ،‬أي إظهار قيمه !‬
‫هنا قد نعطي المتغير ‪ i‬بشكل متتالي القيم ‪ .3 ،2 ،1 ،0‬بهذا سنقوم إذن بإظهار قيمة ]‪، table[1] ، table[0‬‬
‫]‪ table[2‬و ]‪! table[3‬‬

‫‪x‬‬
‫إنتبه لعدم محاولة إظهار قيمة ]‪ ! table[4‬فجدول من ‪ 4‬خانات يتضمن الفهارس ‪ 3 ،2 ،1 ،0‬فقط‪ .‬فإن‬
‫حاولت عرض ]‪ table[4‬فإمّا أن تحصل على قيمة عشوائية‪ ،‬أو أن يظهر لك خطأ جميل‪ ،‬نظام التشغيل يوقف‬
‫برنامجك فهو يحاول الوصول لعنوان لا ينتمي إليه‪.‬‬

‫تهيئة جدول‬

‫الآن و مادمنا قد عرفنا كيف نتصفح جدولا‪ ،‬يمكننا أن نضبط كل قيمه على ‪ 0‬باستخدام حلقة !‬

‫ل قيمه على الصفر أمر يمكن القيام به بمستواك هذا ‪:‬‬


‫إن القيام بتصفح جدول لضبط ك ّ‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪int table[4], i = 0‬‬
‫‪4‬‬ ‫‪// Initialization of the table‬‬
‫‪5‬‬ ‫)‪for (i = 0 ; i < 4 ; i++‬‬
‫‪6‬‬ ‫{‬
‫‪7‬‬ ‫;‪table[i] = 0‬‬
‫‪8‬‬ ‫}‬
‫‪9‬‬ ‫‪// Displaying the values of the table to check‬‬
‫‪10‬‬ ‫)‪for (i = 0 ; i < 4 ; i++‬‬
‫‪11‬‬ ‫{‬
‫‪12‬‬ ‫;)]‪printf(”%d\n”, table[i‬‬
‫‪13‬‬ ‫}‬
‫‪14‬‬ ‫;‪return 0‬‬
‫} ‪15‬‬

‫‪0‬‬
‫‪0‬‬
‫‪0‬‬
‫‪0‬‬

‫‪153‬‬
‫الفصل ‪ .12‬الجداول )‪(Arrays‬‬

‫طر يقة أخرى للتهيئة‬

‫يجب أن تعرف أنه هناك طر يقة أخرى لإعطاء قيم ابتدائية لجدول في لغة الـ‪.C‬‬
‫و هذه الطر يقة تعمل بكتابة }‪ . table[4] = {value1, value2, value3, value4‬ببساطة‪ ،‬تضع القيم بين‬
‫حاضنتين واحدة تلو الاخرى و تفصل بينها بفواصل‪.‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪int table[4] = {0, 0, 0, 0}, i = 0‬‬
‫‪4‬‬ ‫)‪for (i = 0 ; i < 4 ; i++‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;)]‪printf(”%d\n”, table[i‬‬
‫‪7‬‬ ‫}‬
‫‪8‬‬ ‫;‪return 0‬‬
‫} ‪9‬‬

‫‪0‬‬
‫‪0‬‬
‫‪0‬‬
‫‪0‬‬

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

‫أي أنني إن قمت بكتابة التالي ‪:‬‬

‫‪1 int table[4] = {10, 23}; // Inserted values : 10, 23, 0, 0‬‬

‫الخانة ‪ 0‬تأخذ القيمة ‪ 10‬و الخانة ‪ 1‬تأخذ القيمة ‪ 23‬و كل الخانات المتبقية تأخذ القيمة ‪) 0‬افتراضياً(‪.‬‬

‫كيف نضبط كل الجدول على الصفر بمعرفة هذا ؟‬


‫يكفي أن تضبط القيمة الأولى على ‪ ،0‬و كل القيم الاخرى غير المشار إليها تأخذ القيمة ‪.0‬‬

‫‪1 int table[4] = {0}; // All the columns of the table will be initialised to 0‬‬

‫هذه التقنية لها شيء مميز و هو أنها تعمل مع أي جدول مهما كان حجمه )في هذا المثال نجحت الطر يقة مع ‪ 4‬خانات‬
‫و ستنجح لو كان الجدول بـ‪ 100‬خانة أيضا(‪.‬‬

‫‪x‬‬
‫احذر‪ ،‬قد تصادفك الكتابة التالية ‪ int table[4] = {1}; :‬و التي تعني إدخال القيم التالية في الجدول ‪:‬‬
‫‪.0 ،0 ،0 ،1‬‬
‫خلافا ًلما يعتقده الـكثير‪ ،‬لن يتم تهيئة كل خانات الجدول على ‪ .1‬بل الخانة الأولى هي الوحيدة التي تضبط على‬
‫‪ 1‬أما الباقي فعلى ‪ .0‬لا يمكننا إذن القيام بتهيئة كل الجدول على القيمة ‪ 1‬تلقائي ّا‪ ،‬إلّا باستعمال حلقة‪.‬‬

‫‪154‬‬
‫‪ .4.12‬تمرير جدول لدالة‬

‫تمرير جدول لدالة‬ ‫‪4.12‬‬

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

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

‫و كا قلت لك مسبقا‪ ،‬يمكننا إعتبار ‪ table‬مؤشرا‪ ،‬و لهذا يمكننا أن نرسله للدالة مثلما نرسل مؤشرا عاديا‪.‬‬

‫‪1‬‬ ‫‪// Prototype of the display function‬‬


‫‪2‬‬ ‫;)‪void display(int �table, int tableSize‬‬
‫‪3‬‬
‫‪4‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;}‪int table[4] = {10, 15, 3‬‬
‫‪7‬‬ ‫‪// We display the content of the table‬‬
‫‪8‬‬ ‫;)‪display(table, 4‬‬
‫‪9‬‬ ‫;‪return 0‬‬
‫‪10‬‬ ‫}‬
‫‪11‬‬
‫‪12‬‬ ‫)‪void display(int �table, int tableSize‬‬
‫‪13‬‬ ‫{‬
‫‪14‬‬ ‫;‪int i‬‬
‫‪15‬‬ ‫)‪for (i = 0 ; i < tableSize; i++‬‬
‫‪16‬‬ ‫{‬
‫‪17‬‬ ‫;)]‪printf(”%d\n”, table[i‬‬
‫‪18‬‬ ‫}‬
‫‪19‬‬ ‫}‬

‫‪10‬‬
‫‪15‬‬
‫‪3‬‬
‫‪0‬‬

‫الدالة غير مختلفة عن التي درسناها في فصل المؤشرات‪ ،‬فهي تأخذ كمعامل مؤشرا ً نحو ‪) int‬جدولنا( و أيضا حجمه‬
‫)مهم جدا كي نعرف متى ٺتوقف الحلقة !(‪.‬‬
‫كل محتوى الجدول تُظهره الدالة بواسطة الحلقة‪.‬‬

‫لاحظ أن ّه توجد طر يقة أخرى للإشارة إلى أن الدالة تستقبل جدولا‪ .‬بدلا من أن نشير أن الدالة تستقبل ‪، int *table‬‬
‫أكتب التالي ‪:‬‬

‫)‪1 void display(int table[], int tableSize‬‬

‫‪155‬‬
‫الفصل ‪ .12‬الجداول )‪(Arrays‬‬

‫هذا يعني تماما نفس الشيء‪ ،‬و لـكن وجود القوسين المربعين يُعلمان المبرمج بأن الدالة تأخذ جدولا و ليس مؤشرا عاديا‬
‫و هذا يز يل الغموض‪.‬‬

‫شخصيا أستعمل دائما القوسين المربعين في دوالي لـكي ُأظهر بأن الدالة تنتظر جدولا‪ .‬أنصحك باستعمال نفس الطر يقة‪.‬‬
‫ليس مهما وضع حجم الجدول بين القوسين المربعين هذه المرة‪.‬‬

‫بعض التمارين !‬

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

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

‫التمرين ‪1‬‬

‫أنشئ دالة ‪ tableSum‬ترجع مجموع القيم الموجودة في الجدول )استعمل ‪ return‬لإرجاع النتيجة( و للمساعدة‪ ،‬هذا‬
‫هو نموذج الدالة ‪:‬‬
‫;)‪1 int tableSum(int table[], int tableSize‬‬

‫التمرين ‪2‬‬

‫أنشئ دالة ‪ tableAverage‬تحسب و ت ُرجع معدّل القيم الموجودة في الجدول‪ .‬تفضل النموذج ‪:‬‬
‫;)‪1 double tableAverage(int table[], int tableSize‬‬

‫الدالة ترجع ‪ double‬فالمعدّل عادة هو قيمة عشر ية‪.‬‬

‫التمرين ‪3‬‬

‫أنشئ دالة ‪ tableCopy‬التي تأخذ كمعاملات جدولين‪ ،‬حيث تقوم بنسخ محتوى الجدول الأول في الجدول الثاني‪،‬‬
‫تفضل النموذج ‪:‬‬
‫;)‪1 void tableCopy(int originalTable[], int copyTable[], int tableSize‬‬

‫التمرين ‪4‬‬

‫ل خانة جدول تحوي قيمة أكبر من القيمة العظمى‪ .‬هذه الدالة تأخذ‬
‫أنشئ دالّة ‪ tableMaximum‬دورها إسناد ‪ 0‬لك ّ‬
‫‪maxValue‬‬ ‫ل الخانات ال ّتي تملك عددا أكبر من‬
‫كمعاملات الجدول و العدد الأقصى المسموح به ) ‪ .( maxValue‬ك ّ‬
‫يجب أن تعاد إلى ‪ .0‬النموذج ‪:‬‬
‫;)‪1 void tableMaximum(int table[], int tableSize, int maxValue‬‬

‫‪156‬‬
‫ملخّ ص‬

‫التمرين ‪5‬‬

‫هذا أصعب‪ .‬أنشئ دالة ‪ sortTable‬التي ترتب قيم جدول تصاعديا‪ .‬كمثال جدول يتكو ّن من القيم }‪، {15, 81, 22, 13‬‬
‫بعد العملية يصبح ‪! {13, 15, 22, 81} :‬‬
‫تفضل النموذج ‪:‬‬

‫;)‪1 void sortTable(int table[], int tableSize‬‬

‫هذا التمرين صعب بقليل عن السابقين لـكن القيام به ليس مستحيلا‪ ،‬سيشغلـكم بعض الوقت‪.‬‬

‫م‬
‫ل‬
‫أنشئ ملفا خاصا باسم ‪) tables.c‬مع مرافقه الملف ‪ tables.h‬الذي يحتوي النماذج ! بالطبع( يحوي ك ّ‬
‫الدوال التي تقوم بعملي ّات على الجداول‪.‬‬

‫إلى العمل !‬

‫ملخّ ص‬

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

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

‫• الجدول ذو النوع ‪ int‬لا يحتوي سوى متغيرات من نوع ‪. int‬‬

‫• خانات الجدول مرقّمة عن طر يق الفهارس ابتداءا ً من ‪ ، table[2] ، table[1] ، table[0] : 0‬إلخ‪.‬‬

‫‪157‬‬
‫الفصل ‪ .12‬الجداول )‪(Arrays‬‬

‫‪158‬‬
‫الفصل ‪13‬‬

‫السلاسل المحرفي ّة )‪(Strings‬‬

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

‫كنت قد قلت من قبل أن الحاسوب لا يفهم إلا الأعداد‪ ،‬فما هو السحر الذي يفعله المبرمجون للتعامل مع النصوص‬
‫؟ إنّهم ماكرون‪ ،‬سوف ترى !‬

‫‪char‬‬ ‫النوع‬ ‫‪1.13‬‬

‫في هذا الفصل سنعطي أهمية خاصة للنوع ‪ . char‬إن كنت ٺتذك ّر جيدا ً فهذا النوع يسمح بخزين الأعداد المحصورة‬
‫بين ‪ −127‬و ‪.128‬‬
‫م‬
‫النوع ‪ char‬يسمح بتخزين الأعداد‪ ،‬لـكننا غالبا لا نستخدمه في لغة الـ‪ C‬من أجل ذلك‪ .‬عادة‪ ،‬حت ّى لو كان‬
‫العدد صغيرا‪ ،‬فإنّنا نخزنه في ‪ . int‬بالطبع‪ ،‬هذا سيأخذ شيئا أكبر من الذاكرة‪ ،‬لـكن في هذه الأي ّام‪ ،‬ليست‬
‫الذاكرة ما ينقص الحواسيب فعلا‪.‬‬

‫إذا فالنوع ‪ char‬مستعمل لتخزين ‪” ...‬حرف” ! احذر‪ ،‬لقد قلت ‪ :‬حرف واحد‪.‬‬

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

‫فلنجر ّب ‪:‬‬
‫)][‪1 int main(int argc, char �argv‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;’‪char letter = ’A‬‬
‫‪4‬‬ ‫;) ‪printf(”%d\n”, letter‬‬
‫‪5‬‬ ‫;‪return 0‬‬
‫} ‪6‬‬

‫‪159‬‬
‫الفصل ‪ .13‬السلاسل المحرفي ّة )‪(Strings‬‬

‫نعلم إذن أن الحرف ‪ A‬يمثل بالعدد ‪ B ،65‬بـ‪ C ،66‬بـ‪ ،67‬إلخ‪.‬‬


‫جرّب بالأحرف الصغيرة و سترى أن القيم مختلفة‪ .‬في الواقع‪ ،‬الحرف ’‪ ’a‬ليس مطابقا لـ ’‪ ، ’A‬الحاسوب يقوم بالتفر يق‬
‫بين الحروف الصغيرة و الـكبيرة )نقول أن ّه يحـترم حالة الحرف(‪.‬‬

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

‫إظهار محرف‬

‫كما نعلم فلإظهار أي شيء على الشاشة نستعمل الدالة ‪ ، printf‬هذه الدالة قادرة أيضا على إظهار محرف على الشاشة و‬
‫ذلك باستعمال الرمز ‪ c) %c :‬تعني ‪ (Character‬كالتالي ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;’‪char letter = ’A‬‬
‫‪4‬‬ ‫;)‪printf(”%c\n”, letter‬‬
‫‪5‬‬ ‫;‪return 0‬‬
‫} ‪6‬‬

‫‪A‬‬

‫حسنا‪ ،‬لقد تعلّمنا كيف نظهر حرفا في الشاشة‪.‬‬

‫يمكننا أيضا أن نطلب من المستعمل أن يقوم بإدخال حرف عن طر يق لوحة المفاتيح‪ ،‬و ذلك بالإستعانة بالدالة‬
‫‪ ، scanf‬و هذا بوضع الرمز ‪ %c‬دائماً‪ ،‬كالتالي ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪char letter = 0‬‬
‫‪4‬‬ ‫;)‪scanf(”%c”, &letter‬‬
‫‪5‬‬ ‫;)‪printf(”%c\n”, letter‬‬
‫‪6‬‬ ‫;‪return 0‬‬
‫} ‪7‬‬

‫إن كتبت الحرف ‪ B‬فسأتحصّ ل على ‪:‬‬

‫‪B‬‬
‫‪B‬‬

‫أوّل ‪ B‬هو الّذي كتبته‪ ،‬أمّا الثاني فهو المعروض من طرف ‪. printf‬‬

‫هذا تقريبا ما يجب عليك أن ٺتذكره بخصوص النوع ‪ . char‬تذك ّر جي ّدا ‪:‬‬

‫‪160‬‬
‫‪char‬‬ ‫‪ .2.13‬السلاسل المحرفي ّة هي جداول من نوع‬

‫• النوع ‪ char‬يخز ّن الأعداد من ‪ −128‬إلى ‪ ،127‬بينما النوع ‪ unsigned char‬يسمح بتخزين الأعداد من ‪0‬‬
‫إلى ‪.255‬‬
‫• يستخدم الحاسوب جدولا للتحو يل بين الحروف و الأعداد‪ ،‬الجدول ‪.ASCII‬‬

‫• يمكن استخدام ‪ char‬لتخزين حرف واحد‪.‬‬

‫• ’‪ ’A‬يتم ّ استبدالها أثناء الترجمة بالقيمة الموافقة )‪ 65‬مثلا(‪ .‬نستخدم إذن علامات التنصيص للحصول على قيمة‬
‫حرف‪.‬‬

‫‪char‬‬ ‫السلاسل المحرفي ّة هي جداول من نوع‬ ‫‪2.13‬‬

‫مثلما يشير العنوان‪ .‬في الواقع‪ ،‬فإن السلسلة المحرفي ّة ما هي إلا عبارة عن جدول من نوع ‪ . char‬مجر ّد جدول بسيط‪.‬‬

‫إن قمنا بإنشاء جدول ‪:‬‬


‫;]‪1 char string[5‬‬

‫و قمنا بوضع الحرف ’‪ ’H‬في ’]‪ ، ’string[0‬الحرف ’‪ ’e‬في ’]‪ ... ’string[1‬فيمكننا تكوين سلسلة‬
‫نص‪.‬‬
‫محرفي ّة‪ ،‬أي ّ‬

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

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

‫‪161‬‬
‫الفصل ‪ .13‬السلاسل المحرفي ّة )‪(Strings‬‬

‫سلسلة المحارف لا تحتوي فقط على الحروف‪ ،‬في الواقع المخطط السابق غير كامل ! السلسلة المحرفي ّة تحتوي بالضرورة‬
‫محرفا خاصّا في النهاية‪ ،‬يسمّى ”محرف نهاية السلسلة”‪ .‬هذا المحرف يكتب ‪. \0‬‬

‫؟‬
‫لماذا يجب أن تنتهي السلسلة المحرفي ّة بـ ‪ \0‬؟‬

‫ببساطة لـكي يعرف الحاسوب أين تنتهي السلسلة‪ .‬المحرف ‪ \0‬يقول ‪” :‬توق ّف‪ ،‬لا يوجد المزيد لقراءته !”‪.‬‬

‫لذلك‪ ،‬كي نخزن الكلمة ’‪ ’Hello‬لا نحتاج إلى جدول من ‪ char 5‬و إنما من ‪! 6‬‬
‫ل مرة تقوم فيها بإنشاء سلسلة محرفي ّة‪ ،‬يجب عليك أن تفك ّر في حجز مكان لمحرف نهاية السلسلة‪ .‬يجب دائما إضافة خانة‬
‫في ك ّ‬
‫لتخزين هذا المحرف ‪ ، \0‬هذا ضروريٌ !‬

‫مرة‪.‬‬
‫نسيان محرف نهاية السلسلة ‪ \0‬هو مصدر أخطاء موجعة في لغة ‪ .C‬لهذا فأنا أكرر هذا التحذير أكثر من ّ‬
‫المخطط التالي هو الأصح ّ في تمثيل السلسلة المحرفي ّة ’‪ ’Hello‬في الذاكرة‪.‬‬

‫كما ترى‪ ،‬السلسلة تحوي ‪ 6‬محارف لا ‪ ،5‬يجب أن يكون الأمر كذلك‪ .‬السلسلة تنتهي بـ ‪ ، \0‬محرف نهاية السلسلة‬
‫يسمح للحاسوب بمعرفة أين تنتهي السلسلة‪.‬‬

‫ل على مكان توق ّف‬


‫اعتبر المحرف ‪ \0‬شيئا إ يجابي ّا لك‪ .‬بفضله ليس عليك تذكر حجم الجدول الذي خزنته لأن ّه يد ّ‬
‫ل على حجمه‪.‬‬
‫الجدول‪ .‬يمكنك أن تمر ّر جدول ‪ char‬دون الحاجة إلى استخدام متغي ّر يد ّ‬
‫هذا الأمر صالح فقط للسلاسل المحرفي ّة‪) ،‬أي النوع *‪ char‬الذي يمكننا أيضا كتابته على النحو ][‪ .( char‬بالنسبة‬
‫للأنواع الأخرى من الجداول‪ ،‬عليك أن تحفظ حجم الجدول في مكان ما‪.‬‬

‫‪162‬‬
char ‫ السلاسل المحرفي ّة هي جداول من نوع‬.2.13

‫إنشاء و تهيئة سلسلة محرفي ّة‬

: ‫ يمكننا استعمال الطر يقة اليدو ي ّة و لـكنّها غير فع ّالة‬،’Hello’ ‫النص‬


ّ ‫ يحتوي‬string ‫إن أردنا إنشاء جدول‬
1 char string[6]; // A table of 6 chars used to store H−e−l−l−o + \0
2 string[0] = ’H’;
3 string[1] = ’e’;
4 string[2] = ’l’;
5 string[3] = ’l’;
6 string[4] = ’o’;
7 string[5] = ’\0’;

. printf ‫ يمكننا التحقق من ذلك باستعمال‬.‫هذه الطر يقة تعمل‬

‫ هذه هي‬.(‫ أي ”سلسلة محارف” بالإنجليز ية‬،String ‫ تعني‬s) %s ‫ يجب أن نستعمل الرمز‬printf ‫لاستخدام‬
: ‫’ في الذاكرة‬Hello’ ‫الشفرة الكاملة التي تنشئ السلسلة‬
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(int argc, char �argv[])
5 {
6 char string[6]; // A table of 6 chars used to store H−e−l−l−o + \0
7 // Initializing the string (writing the letters one by one in the
memory)
8 string[0] = ’H’;
9 string[1] = ’e’;
10 string[2] = ’l’;
11 string[3] = ’l’;
12 string[4] = ’o’;
13 string[5] = ’\0’;
14 // Displaying the string thanks to the %s in the function printf
15 printf(”%s”, string);
16 return 0;
17 }

: ‫النتيجة‬
Hello

‫ لحسن‬،‫ لتهيئة سلسلة محرفي ّة توجد‬.‫ أمر متعب للغاية‬string ‫تلاحظ أن القيام بتخزين النص حرفا بحرف في الجدول‬
: ‫ طر يقة أبسط بكثير‬،‫الحظ‬
1 int main(int argc, char �argv[])
2 {
3 char string[] = ”Hello”; // The size of the table is automatically
calculated
4 printf(”%s”, string);
5 return 0;
6 }

163
‫الفصل ‪ .13‬السلاسل المحرفي ّة )‪(Strings‬‬

‫‪Hello‬‬

‫كما تلاحظ في السطر الأول ترى أنني أنشأت متغي ّرا من نوع ][‪ ، char‬كان بإمكاني كتابة *‪ char‬أيضاً‪ ،‬النتيجة‬
‫ستكون نفسها‪.‬‬

‫عندما تكتب بين علامتي اقتباس )” ”( النص الذين تريد تخزينه في الجدول‪ ،‬يقوم الحاسوب بحساب الحجم اللازم‪.‬‬
‫أي أن ّه سيحسب عدد الحروف و يضيف ‪ 1‬من أجل المحرف ‪ . \0‬يبدأ بعدها في تخزين حروف الكلمة ’‪ ’Hello‬واحدا‬
‫واحدا في الذاكرة و في النهاية يضيف ‪ \0‬تماما كما فعلنا يديو ي ّا منذ قليل‪.‬‬
‫باختصار‪ ،‬هذا أمر عمليّ أكثر‪.‬‬

‫و مع ذلك فهناك مشكل ‪ :‬هذا الأمر لا يعمل إلّا مع التهيئة ! لاحقا في الشفرة لا يمكنك كتابة ‪:‬‬

‫;”‪1 string = ”Hello‬‬

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

‫‪scanf‬‬ ‫إدخال سلسلة محرفي ّة عن طر يق‬

‫يمكننا حفظ سلسلة محرف ّية م ُدخَلة من طرف المستخدم عن طر يق ‪ ، scanf‬باستخدام الرمز ‪. %s‬‬
‫‪Luc‬‬ ‫مشكل وحيد ‪ :‬لا يمكنك معرفة كم محرفا سيقوم المستخدم بإدخاله‪ .‬إن طلبت منه اسمه الأوّل‪ ،‬فيمكن أن يسمّى‬
‫)‪ 3‬محارف(‪ ،‬و لـكن من الذي سيضمن أن اسمه لن يكون ‪) Jean-Edouard‬عدد أكبر بكثير من المحارف( ؟‬

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

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;]‪char firstName[100‬‬
‫‪4‬‬ ‫;)” ? ‪printf(”What’s your name‬‬
‫‪5‬‬ ‫;)‪scanf(”%s”, firstName‬‬
‫‪6‬‬ ‫;)‪printf(”Hello %s, nice to meet you !”, firstName‬‬
‫‪7‬‬ ‫;‪return 0‬‬
‫} ‪8‬‬

‫‪What’s your name ? Mateo21‬‬


‫! ‪Hello Mateo21, nice to meet you‬‬

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

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

‫‪char‬‬ ‫ل النصوص التي تراها على الشاشة هي في الواقع جداول‬


‫ل الكلمات‪ ،‬ك ّ‬
‫السلاسل المحرفي ّة مستعملة بكثرة‪ .‬فك ّ‬
‫في الذاكرة و تعمل كما شرحت لك من قبل‪.‬‬
‫للمساعدة في التعامل مع السلاسل المحرفي ّة‪ ،‬توجد المكتبة ‪ string.h‬التي تحتوي على كم كبير من الدوال التي تقوم‬
‫بالحسابات على السلاسل المحرفي ّة‪.‬‬

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

‫‪string.h‬‬ ‫فك ّر في تضمين‬

‫مرة أخرى ‪ :‬بما أنّنا سنستخدم مكتبة جديدة تسمّى‬


‫حت ّى لو بدا لك هذا الأمر بديهي ّا‪ ،‬فأنا أفضّ ل أن أتحدّث عنه ّ‬
‫‪ ، string.h‬فيجب أن تضمّنها في أعلى ملفّات ‪ .c‬حيثما تحتاجها ‪:‬‬
‫>‪1 #include <string.h‬‬

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

‫‪ : strlen‬حساب طول سلسلة محرفي ّة‬

‫‪ strlen‬تقوم بحساب طول السلسلى المحرفي ّة )دون حساب المحرف ‪.( \0‬‬
‫يحب أن تعطيها معاملا وحيدا ‪ :‬السلسلة المحرفي ّة‪ .‬هذه الدالة ت ُرجع طول السلسلة‪.‬‬

‫الآن بما أن ّك تعرف مالّذي يعنيه النموذج‪ ،‬سأعطيك نموذج الدوال ال ّتي أكل ّمك عنها‪ .‬المبرمجون يعتبرونه كـ”دليل‬
‫استخدام” للدالّة‪ .‬هذا نموذج الدالة ‪:‬‬
‫;)‪1 size_t strlen(const char� string‬‬

‫م‬
‫هو ليس نوعا ً قاعديا مثل‬ ‫ن الدالة تعيد عددا يمث ّل طولا‪.‬‬
‫ل على أ ّ‬
‫‪ size_t‬هو نوع خاص يد ّ‬
‫مخـتَر َع”‪ .‬سنتعلم نحن كيف نقوم بإنشاء أنواعنا الخاصة في فصول لاحقة‪.‬‬
‫‪ char ، float ، int‬و إنما هو نوع ” ُ‬
‫حالي ّا‪ ،‬سنكتفي بتخزين النتيجة التي تعيدها ‪ strlen‬في متغي ّر من نوع ‪) int‬سيقوم الحاسوب بالتحو يل تلقائي ّا‬
‫‪int‬‬ ‫من ‪ size_t‬إلى ‪ .( int‬يفترض أننا نقوم بتخزين هذه القيمة في متغي ّر من نوع ‪ ، size_t‬لـكن عملي ّا‬
‫كاف لهذا‪.‬‬

‫الدالّة تأخد معاملا من نوع *‪ . const char‬الـ ‪) const‬التي تعني ثابت‪ ،‬تذك ّر( تعني أن الدالة ”ستَمتَن ِـع” عن تغير‬
‫السلسلة‪ .‬عندما ترى ‪ ، const‬فستعلم أن المتغي َر لا يتم ّ تعديله من طرف الدالّة‪ ،‬بل قراءته فقط‪.‬‬

‫‪strlen‬‬ ‫فلنجر ّب الدالّة‬

‫‪165‬‬
(Strings) ‫ السلاسل المحرفي ّة‬.13 ‫الفصل‬

1 int main(int argc, char �argv[])


2 {
3 char string[] = ”Hello”;
4 int stringLength = 0;
5 // We put the length of string in stringLength
6 stringLength = strlen(string);
7 // We display the length of string
8 printf(”The string %s contains %d characters”, string, stringLength);
9 return 0;
10 }

The string Hello contains 5 characters

‫ يوجد عدّاد تتم ّ ز يادته‬. \0 ‫ حتى تصل إلى المحرف‬char ‫ يكفي القيام بحلقة على جدول‬،‫هذه الدالة سهلة الكتابة‬
.‫ و هذا العدّاد يتم ّ إعادته في النهاية‬،‫في كل دورة من الحلقة‬

‫ هذا سيمكّنك من أن تفهم جي ّدا كيف تعمل هذه الدالّة‬. strlen ‫هذا جعلني أريد كتابة شفرة شبيهة بتلك الخاصّة بـ‬
:

1 int stringLen(const char� string);


2 int main(int argc, char �argv[])
3 {
4 char string[] = ”Hello”;
5 int stringLength = 0;
6 // We put the length of string in stringLength
7 stringLength = stringLen(string);
8 // We display the length of string
9 printf(”The string %s contains %d characters”, string, stringLength);
10 return 0;
11 }
12
13 int stringLen(const char� string)
14 {
15 int charactersCount = 0;
16 char currentCharacter = 0;
17 do
18 {
19 currentCharacter = string[charactersCount];
20 charactersCount++;
21 }
22 while(currentCharacter != ’\0’); // We loop while we didn’t reach \0
23 charactersCount−−; // We decrement by 1 in order to not count \0
24 return charactersCount;
25 }

‫ تقوم في كل مرة بتخزين المحرف الحالي في محرف‬. string ‫ تقوم بحلقة على الجدول‬stringLen ‫هذه الدالة‬
.‫ نخرج من الحلقة‬، \0 ‫ ما إن يكون المحرف الحالي هو‬، currentCharacter ‫م ُساعد سميناه‬

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

‫في النهاية نقوم بإنقاص ‪ 1‬من المجموع‪ ،‬لـكي لا نحسب ‪ . \0‬قبل نهاية الدالة‪ ،‬نقوم بإرجاع الطول الذي حسبناه أي‬
‫‪. charactersCount‬‬

‫‪ : strcpy‬نسخ سلسلة محرفي ّة في أخرى‬

‫الدالة ‪) strcpy‬و ال ّتي تعني ”‪ (”String copy‬تسمح بنسخ سلسلة محرفي ّة إلى داخل أخرى‪.‬‬
‫نموذج الدالّة ‪:‬‬

‫;)‪1 char� strcpy(char� stringCopy, const char� stringToCopy‬‬

‫الدالّة تأخذ معاملين ‪:‬‬

‫• ‪ : stringCopy‬مؤش ّر نحو *‪) char‬جدول ‪ .( char‬في هذا الجدول يتم ّ لصق السلسلة‪.‬‬

‫• ‪ : stringToCopy‬مؤش ّر نحو جدول آخر من ‪ . char‬هذه السلسلة يتم ّ نسخها إلى ‪. stringCopy‬‬

‫الدالّة تعيد مؤش ّرا نحو ‪ ، stringCopy‬و هو غير مفيد جدّا‪ .‬عادة‪ ،‬لا نحفظ ما تعيده هذه الدالّة‪ .‬فلنجر ّبها ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;}‪char string[] = ”Text”, copy[100] = {0‬‬
‫‪4‬‬ ‫”‪strcpy(copy, string); // We copy ”string” in ”copy‬‬
‫‪5‬‬ ‫‪// If everything is okay, the copy must contain the same thing as‬‬
‫‪string‬‬
‫‪6‬‬ ‫;)‪printf(”string is : %s\n”, string‬‬
‫‪7‬‬ ‫;)‪printf(”copy is : %s\n”, copy‬‬
‫‪8‬‬ ‫;‪return 0‬‬
‫} ‪9‬‬

‫‪string is : Text‬‬
‫‪copy is : Text‬‬

‫ن قيمة ‪ string‬هي ”‪ .”Text‬حت ّى الآن‪ ،‬الأمر عاديّ ‪.‬‬


‫نلاحظ أ ّ‬
‫بالمقابل‪ ،‬نرى أيضا أن المتغي ّر ‪ ، copy‬الذي كان فارغا في البداية‪ ،‬قد تم ّ ملؤه بمحتوى ‪ . string‬لقد تم ّ فعلا نسخ‬
‫السلسلة في ‪. copy‬‬

‫!‬
‫]‪copy[5‬‬ ‫تأكّد أن السلسلة ‪ copy‬كبيرة كفاية لاحتواء محتوى ‪ . string‬إن قمت في هذا المثال بتعر يف‬
‫)و الذي هو غير كاف لأنه لن يبق مكان لـ ‪ ،( \0‬الدالّة ‪” strcpy‬ستخرج عن الذاكرة” و ربّما ستوقف‬
‫البرنامج عن العمل‪ .‬تجن ّب ذلك‪ ،‬إلّا إذا كنت تريد تعطيل برنامجك‪.‬‬

‫النسخ يمكن تمثيله كالتالي ‪:‬‬

‫‪167‬‬
‫الفصل ‪ .13‬السلاسل المحرفي ّة )‪(Strings‬‬

‫ل محرف من ‪ string‬يتم ّ وضعه في ‪. copy‬‬


‫ك ّ‬
‫السلسلة المحرفي ّة ‪ copy‬تحوي عددا كبيرا من المحارف غير المستعملة‪ ،‬قد لاحظت هذا‪ .‬أعطيته الحجم ‪ 100‬من باب‬
‫الحماية‪ ،‬لـكنّ الحجم ‪ 6‬كان كافيا‪ .‬الفائدة من إنشاء جدول أكبر هي أن ّه بهذه الطر يقة‪ ،‬السلسلة المحرفي ّة ‪ string‬تكون‬
‫قادرة على احتواء سلاسل أخرى قد تكون أكبر في بقي ّة البرنامج‪.‬‬

‫‪ : strcat‬وصل سلسلتين محرفي ّتين‬

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

‫” ‪string1 = ”Hello‬‬ ‫•‬

‫”‪string2 = ”Mateo21‬‬ ‫•‬

‫إن وصلت ‪ string1‬في ‪ string2‬فـ ‪ string2‬ستصبح ”‪ . ”Hello Mateo21‬أمّا بالنسبة لـ ‪، string2‬‬


‫فلن ٺتغي ّر و ستكون دائما ”‪ . ”Mateo21‬فقط ‪ string1‬التي ٺتغي ّر‪.‬‬

‫هذا تماما ما تفعله ‪ ، strcat‬هذا هو نموذجها ‪:‬‬

‫;)‪1 char� strcat(char� string1, const char� string2‬‬

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

‫الدالّة تضيف إلى ‪ string1‬محتوى ‪ . string2‬فلنرى هذا عن قرب ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;”‪char string1[100] = ”Hello ”, string2[] = ”Mateo21‬‬
‫‪4‬‬ ‫‪strcat(string1, string2); // We concatenate string2 to string1‬‬
‫‪5‬‬ ‫”‪// If everything is okay, string1 is equal to ”Hello Mateo21‬‬
‫‪6‬‬ ‫;)‪printf(”string1 is : %s\n”, string1‬‬
‫‪7‬‬ ‫‪// string2 didn’t change :‬‬
‫‪8‬‬ ‫;)‪printf(”string2 is as always : %s\n”, string2‬‬
‫‪9‬‬ ‫;‪return 0‬‬
‫} ‪10‬‬

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

‫‪string1 is : Hello Mateo21‬‬


‫‪string2 is as always : Mateo21‬‬

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

‫المخطط التالي يلخّ ص عملي ّة الوصل‪.‬‬

‫‪string1‬‬ ‫الجدول ‪ string2‬تم ّت إضافته إلى نهاية ‪) string1‬الذي يحوي ‪ 100‬خانة(‪ .‬الـ ‪ \0‬الخاص بـ‬
‫تم ّ حذفه )في الواقع تم ّ استبداله بـ‪ M‬من ‪ .(Mateo21‬في الواقع‪ ،‬لا يجب ترك ‪ \0‬في وسط سلسلة محرفي ّة‪ ،‬و إلّا فسيتم ّ‬
‫”قطعها” في المنتصف ! لا نضع ‪ \0‬إلّا في نهاية سلسلة محرفي ّة‪ ،‬فقط عندما ننهيها‪.‬‬

‫‪ : strcmp‬مقارنة سلسلتين محرفي ّتين‬

‫‪ strcmp‬تقارن سلسلتين فيما بينهما‪ .‬هذا هو نموذجها ‪:‬‬

‫;)‪1 int strcmp(const char� string1, const char� string2‬‬

‫المتغي ّران ‪ string1‬و ‪ string2‬يتم ّ مقارنتهما‪ .‬كما تلاحظ‪ ،‬لا يتم ّ تعديل أيّ منهما لأن ّه تم ّ التصريح عنهما كثوابت‪.‬‬

‫إنه من الضروري أن نقوم باسترجاع ما تعيده إلينا الدالة‪ .‬في الواقع‪ strcmp ،‬تعيد ‪:‬‬

‫• ‪ 0‬إن كانت السلسلتان متطابقتين‪.‬‬

‫• قيمة أخرى )موجبة أو سالبة( إن كانت السلسلتان مختلفتين‪.‬‬

‫م‬
‫أعلم أن ّه كان من المنطقيّ أكثر أن تعيد الدالّة ‪ 1‬إن كانت السلسلتين متطابقتين لـكي نقول ”صحيح” )تذك ّر المتغي ّرات‬
‫ل المحارف متطابقة‬
‫ل سلسلة واحدا واحدا‪ .‬إن كانت ك ّ‬
‫المنطقي ّة(‪ .‬السبب بسيط ‪ :‬الدالّة تقارن قيم المحارف من ك ّ‬
‫فستعيد ‪ .0‬إن كانت محارف ‪ string1‬أكبر من محارف ‪ ، string2‬فالدالّة تعيد عددا موجبا‪ .‬في الحالة‬
‫ن سلسلتين متطابقتان أم لا‪.‬‬
‫المعاكسة تعيد عددا سالبا‪ .‬عملي ّا‪ ،‬نستخدم ‪ strcmp‬كثيرا للتحقّق من أ ّ‬

‫هذه شفرة التجريب الخاصّة بي ‪:‬‬

‫‪169‬‬
‫الفصل ‪ .13‬السلاسل المحرفي ّة )‪(Strings‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;”‪char string1[] = ”Test text”, string2[] = ”Test text‬‬
‫‪4‬‬ ‫‪if (strcmp(string1, string2) == 0) // If the strings are equal‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;)”‪printf(”The strings are identical\n‬‬
‫‪7‬‬ ‫}‬
‫‪8‬‬ ‫‪else‬‬
‫‪9‬‬ ‫{‬
‫‪10‬‬ ‫;)”‪printf(”The strings are different\n‬‬
‫‪11‬‬ ‫}‬
‫‪12‬‬ ‫;‪return 0‬‬
‫} ‪13‬‬

‫‪The strings are identical‬‬

‫ن السلسلتين متطابقتين‪ ،‬فالدالّة ‪ strcmp‬أعادت ‪.0‬‬


‫بما أ ّ‬
‫لاحظ أن ّه كان بإمكاني تخزين ما أعادته الدالّة في متغي ّر من نوع ‪ . int‬لـكنّ ذلك ليس ضرور ي ّا‪ ،‬فيمكننا وضعها مباشرة‬
‫داخل ‪ if‬كما فعلت‪.‬‬

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

‫‪ : strchr‬البحث عن محرف‬

‫الدالّة ‪ strchr‬تبحث عن محرف في سلسلة محرفي ّة‪ .‬نموذجها ‪:‬‬

‫;)‪1 char� strchr(const char� string, int characterToFind‬‬

‫الدالّة تأخذ معاملين ‪:‬‬

‫• ‪ : string‬السلسلة التي يتم ّ البحث فيها‪.‬‬

‫• ‪ : characterToFind‬المحرف الّذي نبحث عنه في السلسلة‪.‬‬

‫م‬
‫ن ‪ characterToFind‬من نوع ‪ int‬و ليس من نوع ‪ . char‬هذا ليس مشكلا فعلا لأن ّه في‬
‫تلاحظ أ ّ‬
‫الواقع المحرف هو عدد‪ .‬على الرغم من ذلك‪ ،‬نفضّ ل استخدام ‪ char‬على ‪ int‬لتخزين المحارف في الذاكرة‪.‬‬

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

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

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪char string[] = ”Test text”, �restOfString = NULL‬‬
‫‪4‬‬ ‫;)’‪restOfString = strchr(string, ’s‬‬
‫‪5‬‬ ‫‪if (restOfString != NULL) // If we found something‬‬
‫‪6‬‬ ‫{‬
‫‪7‬‬ ‫‪printf(”This is the rest of the string after the first s:%s”,‬‬
‫;)‪restOfString‬‬
‫‪8‬‬ ‫}‬
‫‪9‬‬ ‫;‪return 0‬‬
‫} ‪10‬‬

‫‪This is the rest of the string after the first s: st text‬‬

‫خاص قليلا‪.‬‬
‫ّ‬ ‫هل فهمت ما حصل ؟ إن الأمر‬
‫ن ‪ string‬يؤش ّر على المحرف الأوّل )أي ’‪( ’T‬‬
‫في الحقيقة‪ ،‬إن ‪ restOfString‬هو مؤشر مثل ‪ ، string‬إلّا أ ّ‬
‫بينما ‪ restOfString‬يؤش ّر على أوّل محرف ’‪ ’s‬موجود في ‪. string‬‬

‫ل مؤش ّر ‪:‬‬
‫يوضح أين يؤش ّر ك ّ‬
‫المخطّط التالي ّ‬

‫ل‬
‫عندما أقوم بـ ‪ printf‬لـ ‪ ، restOfString‬فمن العاديّ أن يتم ّ عرض ”‪ .”st Text‬الدالّة ‪ printf‬تعرض ك ّ‬
‫المحارف التي تلقاها )‪ (t،x،e،T،t،s‬حت ّى الوصول إلى ‪ \0‬الذي يعلمها بنهاية السلسلة‪.‬‬

‫نسخة مختلفة‬

‫توجد دالّة ‪ strrchr‬مطابقة تماما لـ ‪ strchr‬باستثناء أنها تعيد مؤش ّرا على آخر ظهور للمحرف الموجود في السلسلة بدلا‬
‫من الأوّل‪.‬‬

‫‪ : strpbrk‬أوّل محرف في القائمة‬

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

‫مثلا‪ ،‬إن أعطيناها السلسلة ”‪ ”xes‬و بحثنا في ”‪ ، ”Test text‬فالدالّة تعيد مؤشرا نحو أول تكرار لأحد المحارف‬
‫‪strpbrk‬‬ ‫التي وجدتها‪ .‬في هذه الحالة‪ ،‬أوّل محرف من ”‪ ”xes‬ال ّتي ستجده في ”‪ ، ”Test text‬هو الـ ’‪ ، ’e‬إذن‬
‫تعيد مؤش ّرا على ’‪. ’e‬‬
‫النموذج ‪:‬‬

‫‪171‬‬
(Strings) ‫ السلاسل المحرفي ّة‬.13 ‫الفصل‬

1 char� strpbrk(const char� string, const char� charactersToFind);

: ‫فلنجر ّب الدالّة‬

1 int main(int argc, char �argv[])


2 {
3 char �restOfString;
4 // We search for the 1st occurrence of x, e or s in ”Test text”
5 restOfString= strpbrk(”Test text”, ”xes”);
6 if (restOfString != NULL)
7 {
8 printf(”This is the rest of the string starting by the first
occurrence of the characters found : %s”, restOfString);
9 }
10 return 0;
11 }

This is the rest of the string starting by the first occurrence of the
characters found : est text

‫ل‬
ّ ‫ لسنا مجـبرين على استخدام متغي ّر في ك‬.(‫ القيم المراد ارسالها إلى الدالّة مباشرة )بين علامتي اقتباس‬،‫في هذا المثال‬
.‫ يمكننا كتابة السلسلة مباشرة‬،‫المر ّات‬
: ‫يجب تذك ّر هذه القاعدة البسيطة‬

.‫ فهذا يعني سلسلة محرفي ّة‬، ” ” ‫• إذا استخدمت علامتي الاقتباس‬

.‫ فهذا يعني محرفا‬، ’ ’ ‫• إذا استخدمت علامتي التنصيص‬

‫ البحث عن سلسلة محرفي ّة في أخرى‬: strstr

.‫هذه الدالّة تبحث عن أوّل ظهور لسلسلة محرفي ّة داخل أخرى‬


: ‫نموذجها‬

1 char� strstr(const char� string, const char� stringToFind);

‫ بينما‬،‫ تبحث عن واحد من المحارف‬strpbrk : ‫ لـكن احذر من الخلط‬، strpbrk ‫النموذج مشابه لذلك الخاص بـ‬
.‫ل السلسلة‬
ّ ‫ تبحث عن ك‬strstr

: ‫مثال‬

1 int main(int argc, char �argv[])


2 {
3 char �restOfString;
4 // We search for the 1st occurrence of ”text” in ”Test text” :
5 restOfString = strstr(”Test text”, ”text”);

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

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


‫‪7‬‬ ‫{‬
‫‪8‬‬ ‫‪printf(”First occurrence of text in Test text : %s\n”,‬‬
‫;)‪restOfString‬‬
‫‪9‬‬ ‫}‬
‫‪10‬‬ ‫;‪return 0‬‬
‫} ‪11‬‬

‫‪First occurrence of text in Test text : text‬‬

‫الدالّة ‪ strstr‬تبحث عن السلسلة ”‪ ”text‬في ”‪.”Test text‬‬


‫كغيرها‪ ،‬تعيد مؤش ّرا عندما تجد ما تبحث عنه‪ .‬و تعيد ‪ NULL‬إن لم تجد شيئا‪.‬‬

‫م جدّا‪ .‬يمكنك فقط القيام‬


‫حت ّى الآن‪ ،‬أنا أقوم بعرض السلسلة عن طر يق المؤش ّر الّذي تعيده الدالّة‪ .‬عملي ّا‪ ،‬هذا غير مه ّ‬
‫”النص الذي تبحث عنه قد تم ّ العثور‬
‫ّ‬ ‫بـ )‪ if (result != NULL‬لمعرفة إن مان البحث قد أعاد أيّ شيء‪ ،‬و تُظهر‬
‫عليه”‪.‬‬

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

‫م‬
‫‪string.h‬‬ ‫هذه الدالّة موجودة في ‪ stdio.h‬بخلاف الدوال التي درسناها إلى ح ّد الآن‪ ،‬و التي كانت في‬

‫هذا الاسم يذك ّرك بشيء ما‪ .‬هذه الدالّة مشابهة إلى ح ّد كبير لـ ‪ printf‬التي تعرفها‪ ،‬و لـكن بدل الكتابة على الشاشة‪،‬‬
‫‪ sprintf‬تكتب في ‪ ...‬سلسلة محرفي ّة ! و من هذا اسمها الذي يبدأ بـ”‪ ”s‬من ”‪) ”string‬سلسلة محرفي ّة بالإنجليز ي ّة(‪.‬‬

‫إنّها دالّة عملي ّة جدّا لتنسيق سلسلة محرفي ّة‪ .‬مثال صغير ‪:‬‬

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


‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;]‪char string[100‬‬
‫‪6‬‬ ‫;‪int age = 15‬‬
‫‪7‬‬ ‫‪// We write ”You have 15 years” in string‬‬
‫‪8‬‬ ‫;)‪sprintf(string, ”You have %d years !”, age‬‬
‫‪9‬‬ ‫‪// We display string to check its content‬‬
‫‪10‬‬ ‫;)‪printf(”%s”, string‬‬
‫‪11‬‬ ‫;‪return 0‬‬
‫‪12‬‬ ‫}‬

‫! ‪You have 15 years‬‬

‫‪173‬‬
‫الفصل ‪ .13‬السلاسل المحرفي ّة )‪(Strings‬‬

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

‫ل‬
‫في هذا المثال‪ ،‬أكتب في ‪ ،”You have %d years” string‬حيث يتم ّ استبدال ‪ %d‬بمحتوى المتغي ّر ‪ . age‬ك ّ‬
‫قواعد ‪ printf‬تطب ّق‪ ،‬يمكنك إذا أردت أم تضع ‪ %s‬لإدراج سلاسل أخرى داخل سلسلتك !‬

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

‫ملخّ ص‬

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

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

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

‫خاص يكتب ‪ \0‬يعني ”نهاية السلسلة”‪.‬‬


‫ّ‬ ‫ل سلسلة محرفي ّة تنتهي بمحرف‬
‫• ك ّ‬

‫‪string.h‬‬ ‫• توجد الـكثير من الدوال الجاهزة للتعامل مع السلاسل المحرفي ّة في المكتبة ‪ .string‬يجب تضمين‬
‫لاستخدامها‪.‬‬

‫‪174‬‬
‫الفصل ‪14‬‬

‫المعالج القبلي )‪(Preprocessor‬‬

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

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

‫الـ‪include‬‬ ‫‪1.14‬‬

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

‫التوجيهة الوحيدة التي رأيناها لح ّد الآن هي ‪. #include‬‬


‫هذه التوجيهة تسمح لنا بتضمين محتوى ملف في آخر‪ .‬قلت لك هذا من قبل‪.‬‬
‫نحن نحتاجها في تضمين الملفات ذات الصيغة ‪ .h‬كملفات ‪ .h‬الخاصّة بالمكتبات ) ‪، stdio.h ، stdlib.h‬‬
‫‪ ،(... math.h ، string.h‬و أيضا ًملفات ‪ .h‬الخاصّة بنا‪.‬‬

‫كالـ‪Code::Blocks‬‬ ‫لنضمّن ملفا ً ذو صيغة ‪ .h‬موجودا ً في نفس المجلّد الذي ثبتنا فيه الـ‪) IDE‬أي البيئة التطوير ية‬
‫مثلا(‪ ،‬نستعمل علامات الترتيب > < كالتالي ‪:‬‬

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

‫ملف ‪ .h‬موجود في المجلّد الذي به مشروعنا‪ ،‬فسنقوم يذلك باستخدام علامتي الترتيب كالتالي ‪:‬‬
‫بينما لتضمين ّ‬

‫”‪1 #include ”myfile.h‬‬

‫ل ملفاتك عن توجيهات المعالج القبلي‪ ،‬تلك الأسطر‬


‫في الحقيقة‪ ،‬المعالج القبلي يتم ّ تشغيله قبل الترجمة‪ .‬يبحث في ك ّ‬
‫المشهورة التي تبدأ بـ ‪. #‬‬
‫الملف في مكان وجود ‪. #include‬‬
‫ّ‬ ‫عندما يجد التوجيهة ‪ ، #include‬يقوم بإدراج محتوى‬

‫‪175‬‬
(Preprocessor) ‫ المعالج القبلي‬.14 ‫الفصل‬

‫ يحتوي نماذج‬file.h ‫ و لدي ملف‬،‫ يحتوي الشفرة الخاصة بالدوال التي كتبتها‬file.c ‫افترض أن لديّ ملفّا‬
.‫ يمكن تلخيص ذلك بالمخطط التالي‬، file.c ‫الدوال التي هي موجودة بالملف‬

‫ في مكان التوجيهة‬file.c ‫ سيتم وضعه داخل الملف‬file.h ‫كل محتوى الملف‬


. #include ”file.h”

: ‫ التالي‬file.c ‫تخي ّل أن لدينا في الملف‬

1 #include ”file.h”
2
3 int myFunction(int something, double stupid)
4 {
5 /� The code of the function �/
6 }
7 void anotherFunction(int value)
8 {
9 /� The code of the function �/
10 }

: file.h ‫و في الملف‬

1 int myFunction(int something, double stupid);


2 void anotherFunction(int value);

file.h ‫ سيضع كما قلت محتوى الملف‬، file.c ‫ قبل أن تتم ترجمة الملف‬،‫عندما يمر المعالج القبلي بهذه الشفرة‬
: ‫ قُبَي ْل الترجمة سيحتوي التالي‬file.c ‫ يعني أن الملف‬،‫ في النهاية‬. file.c ‫في الملف‬

1 int myFunction(int something, double stupid);


2 void anotherFunction(int value);
3
4 int myFunction(int something, double stupid)
5 {
6 /� The code of the function �/
7 }
8 void anotherFunction(int value)
9 {
10 /� The code of the function �/
11 }

176
‫الـ‪define‬‬ ‫‪.2.14‬‬

‫محتوى ‪ .h‬تم ّ إدخاله مكان ‪. #include‬‬

‫ل بعض القراء يشكك في أن الأمر يحصل بهذه الطر يقة‪.‬‬


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

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

‫الـ‪define‬‬ ‫‪2.14‬‬

‫سنتعرف الآن على توجيهة معالج جديدة و هي ‪. #define‬‬

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

‫‪1 #define INITIAL_NUMBER_OF_LIVES 3‬‬

‫يجب أن تكتب بالترتيب ‪:‬‬

‫• الـ ‪. define‬‬
‫• الكلمة التي تريد ربط القيمة بها‪.‬‬

‫• قيمة الكلمة‪.‬‬

‫احذر ‪ :‬رغم التشابه )خصوصا في الاسم الذي اعتدنا كتابته بحروف كبيرة(‪ ،‬فهذه مختلفة كثيرا عن الثوابت التي‬
‫تعلّمناها حت ّى الآن‪ ،‬مثل ‪:‬‬

‫;‪1 const int INITIAL_NUMBER_OF_LIVES = 3‬‬

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

‫ل الكلمات بقيمتهم الموافقة‪ .‬هذا تقريبا مثل‬


‫كيف تعمل ؟ في الواقع‪ ،‬الـ ‪ #define‬تستبدل في شفرتك المصدر ي ّة ك ّ‬
‫عملي ّة البحث و الاستبدال )‪ (Search / Replace‬الموجودة في برنامج ‪ Word‬مثلا‪ .‬إذن السطر ‪:‬‬

‫‪1 #define INITIAL_NUMBER_OF_LIVES 3‬‬

‫‪177‬‬
‫الفصل ‪ .14‬المعالج القبلي )‪(Preprocessor‬‬

‫ل ‪ INITIAL_NUMBER_OF_LIVES‬بالرقم ‪.3‬‬
‫يستبدل في الملف ك ّ‬

‫هذا مثال على ملف ‪ .c‬قبل مرور المعالج القبلي ‪:‬‬


‫‪1 #define INITIAL_NUMBER_OF_LIVES 3‬‬
‫)][‪2 int main(int argc, char �argv‬‬
‫{ ‪3‬‬
‫‪4‬‬ ‫;‪int lives = INITIAL_NUMBER_OF_LIVES‬‬
‫‪5‬‬ ‫‪/� Code ... �/‬‬

‫و بعدما يمر ّ المعالج القبلي ‪:‬‬


‫)][‪1 int main(int argc, char �argv‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;‪int lives = 3‬‬
‫‪4‬‬ ‫‪/� Code ... �/‬‬

‫الملف بعد مرور المعالج القبلي‪ ،‬حيث‬


‫ّ‬ ‫ل ‪ #define‬يتم ّ استبدالها بالقيمة الموافقة‪ .‬المترجم ”يرى”‬
‫قبل الترجمة‪ ،‬ك ّ‬
‫تكون الاستبدالات قد تمت‪.‬‬

‫؟‬
‫ما الفائدة بالنسبة للثوابت التي رأيناها حت ّى الآن ؟‬

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

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

‫هل من مثال واقعيّ لاستخدام ‪ #define‬؟‬


‫هذا ما لن ٺتأخّر هن فعله‪ .‬عندما تفتح نافذة في ‪ ،C‬قد تحتاج إلى تعر يف ثوابت المعالج القبلي لتحديد أبعاد النافذة ‪:‬‬
‫‪1 #define WINDOW_WIDTH 800‬‬
‫‪2 #define WINDOW_HEIGTH 600‬‬

‫الفائدة هي أن ّه إن أردت تغيير حجم الواجهة )لأنّها تبدو لك صغيرة جدّا(‪ ،‬فيكفي أن تغي ّر ‪ #define‬و تعيد ترجمة‬
‫الشفرة‪.‬‬

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

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

‫‪178‬‬
‫الـ‪define‬‬ ‫‪.2.14‬‬

‫الـ‪ define‬من أجل حجم جدول‬

‫نستخدم كثيرا ‪ #define‬من أجل تعر يف حجم الجداول‪ .‬نكتب مثلا ‪:‬‬

‫‪1 #define MAX_SIZE 1000‬‬


‫)][‪2 int main(int argc, char �argv‬‬
‫{ ‪3‬‬
‫‪4‬‬ ‫;]‪char string1[MAX_SIZE], string2[MAX_SIZE‬‬
‫‪5‬‬ ‫‪// ...‬‬

‫؟‬
‫و لـكن ‪ ...‬كنت أعتقد أن ّه لا يمكننا وضع متغي ّر أو ثابت بين القوسين المربعين أثناء تعر يف جدول ؟‬

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

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;]‪char string1[1000], string2[1000‬‬
‫‪4‬‬ ‫‪// ...‬‬

‫و هذا شيء صحيح‪.‬‬

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

‫الـ‪define‬‬ ‫الحسابات في‬

‫من الممكن القيام بحسابات صغيرة في الـ ‪. #define‬‬


‫مثلا‪ ،‬هذه الشفرة تنشئ ثابتا ‪ ، WINDOW_WIDTH‬و آخر ‪ ، WINDOW_HEIGHT‬ثم ّ ثالثا‬
‫‪ ، PIXELS_NUMBER‬الذي يحوي عدد البيكسلز المعروضة داخل النافذة )الحساب بسيط ‪ :‬العرض × الطول(‪.‬‬

‫‪1 #define WINDOW_WIDTH 800‬‬


‫‪2 #define WINDOW_HEIGHT 600‬‬
‫)‪3 #define PIXELS_NUMBER (WINDOW_WIDTH � WINDOW_HEIGHT‬‬

‫قيمة ‪ PIXELS_NUMBER‬يتم ّ استبدالها قبل الترجمة بالشفرة التالية ‪:‬‬


‫)‪ ، (WINDOW_WIDTH * WINDOW_HEIGHT‬أي )‪ ،(600*800‬و تعطينا ‪ .480000‬ضع دائما حساباتك بين قوسين‬
‫كما أفعل من باب الاحتياط لـكي تعزل العملي ّة‪.‬‬

‫يمكنك القيام بكل العمليات القاعدية التي تعرفها ‪ :‬جمع )‪ ،(+‬طرح )‪ ،(-‬ضرب )*(‪ ،‬قسمة )‪ ،(/‬ترديد )‪.(%‬‬

‫‪179‬‬
‫الفصل ‪ .14‬المعالج القبلي )‪(Preprocessor‬‬

‫الثوابت مسبقة التعر يف‬

‫عرفتها‪ ،‬فإنه توجد ثوابت معر ّفة من ق ِب َل المعالج القبلي‪.‬‬


‫بالإضافة إلى الثوابت التي أنت ّ‬

‫كل من هذه الثوابت تبدأ و تنتهي برمزي ‪) _ underscore‬تجده في لوحة المفاتيح تحت الرقم ‪ 8‬أعلى اللوحة بالنسبة‬
‫للتخطيط ‪.(AZERTY‬‬

‫• __‪ : __LINE‬يعطي رقم السطر الحالي من الشفرة‪.‬‬

‫• __‪ : __FILE‬يعطي اسم الملف الحالي‪.‬‬

‫• __‪ : __DATE‬يعطي تاريخ ترجمة الشفرة‪.‬‬

‫• __‪ : __TIME‬تعطي وقت ترجمة الشفرة‪.‬‬

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

‫;)__‪1 printf(”Error in the line n° %d of the file %s\n”, __LINE__, __FILE‬‬


‫;)__‪2 printf(”This file has been compiled on %s at %s\n”, __DATE__, __TIME‬‬

‫‪Error in the line n° 9 of the file main.c‬‬


‫‪This file has been compiled on 13 Jan 2006 at 19:21:10‬‬

‫المعر ّفات البسيطة‬

‫إنه من الممكن أن نكتب بكل بساطة ‪:‬‬

‫‪1 #define CONSTANT‬‬

‫دون إعطاء القيمة‪.‬‬


‫ن الكلمة ‪ CONSTANT‬معر ّفة‪ ،‬بك ّ‬
‫ل بساطة‪ .‬ليست لها قيمة لـكنّها ”موجودة”‪.‬‬ ‫هذا يعني للمعالج القبلي أ ّ‬

‫؟‬
‫ما الفائدة من ذلك ؟‬

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

‫‪180‬‬
‫‪ .3.14‬الماكرو )‪(Macro‬‬

‫الماكرو )‪(Macro‬‬ ‫‪3.14‬‬

‫كنا قد رأينا بانه باستعمال الـ ‪ ، #define‬بامكاننا أن نطلب من المعالج القبلي استبدال كلمة بقيمتها في الشفرة بأكملها‪.‬‬
‫مثال ‪:‬‬

‫‪1 #define NUMBER 9‬‬

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

‫لديّ خبر جديد ! في الواقع ‪ #define‬أقوى من هذا بكثير‪ .‬فهي قادرة على الاستبدال بـ‪ ...‬شفرة مصدر ية بأكملها‬
‫! عندما نستخدم ‪ #define‬للبحث و استبدال كلمة بشفرة مصدر ية نقول أننا أنشأنا ماكرو )‪.(Macro‬‬

‫ماكرو بدون معاملات‬

‫هذا مثال عن ماكرو بسيطة ‪:‬‬

‫;)”‪1 #define COUCOU() printf(”Coucou‬‬

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

‫فلنجرب الماكرو داخل الشفرة المصدر ية ‪:‬‬

‫‪1‬‬ ‫;)”‪#define COUCOU() printf(”Coucou‬‬


‫‪2‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫)(‪COUCOU‬‬
‫‪5‬‬ ‫;‪return 0‬‬
‫‪6‬‬ ‫}‬

‫‪Coucou‬‬

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

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;)”‪printf(”Coucou‬‬
‫‪4‬‬ ‫;‪return 0‬‬
‫} ‪5‬‬

‫‪181‬‬
‫الفصل ‪ .14‬المعالج القبلي )‪(Preprocessor‬‬

‫إذا فهمت هذا فقد فهمت مبدأ عمل الماكرو‪.‬‬

‫؟‬
‫ل ماكرو ؟‬
‫لـكن‪ ،‬هل يمكننا أن نضع سطرا ً واحدا فقط من الشفرة في ك ّ‬

‫ل سطر جديد‪ ،‬مثل هذا ‪:‬‬


‫لا‪ ،‬لحسن الحظ يمكنك وضع عدّة أسطر من الشفرة في المر ّة‪ .‬يكفي وضع \ قبل ك ّ‬
‫‪1‬‬ ‫\ ;)”‪#define TELL_YOUR_STORY() printf(”Hello, my name is Brice\n‬‬
‫‪2‬‬ ‫\ ;)”‪printf(”I live at Nice\n‬‬
‫‪3‬‬ ‫;)”‪printf(”I love rice\n‬‬
‫‪4‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫)(‪TELL_YOUR_STORY‬‬
‫‪7‬‬ ‫;‪return 0‬‬
‫‪8‬‬ ‫}‬

‫‪Hello, my name is Brice‬‬


‫‪I live at Nice‬‬
‫‪I love rice‬‬

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

‫ماكرو بالمعاملات‬

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

‫لـكن الماكرو تصبح مفيدة أكثر عندما نضع لها الأقواس‪ .‬هذا يعمل تقريبا مثل الدوال ‪:‬‬
‫‪1‬‬ ‫\ )‪#define ADULT(age) if (age >= 18‬‬
‫‪2‬‬ ‫;)”‪printf(”You are adult\n‬‬
‫‪3‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫)‪ADULT(22‬‬
‫‪6‬‬ ‫;‪return 0‬‬
‫‪7‬‬ ‫}‬

‫‪You are adult‬‬

‫م‬
‫يمكننا مثلا إضافة الـ ‪ else‬لـكي نُظهر على الشاشة ‪ :‬أنت لست بالغا ً”‪ .”You are not adult‬حاول القيام بذلك‪،‬‬
‫الأمر ليس صعبا‪ .‬لا تنس وضع الشرطة الخلفي ّة \ قبل السطر الجديد‪.‬‬

‫مبدأ الماكرو بسيط جدّا ‪:‬‬

‫‪182‬‬
‫‪ .4.14‬الشروط‬

‫\ )‪1 #define ADULT(age) if (age >= 18‬‬


‫‪2‬‬ ‫;)”‪printf(”You are adult\n‬‬

‫ل شفرة الماكرو‪ age ،‬سيتم استبداله بالعدد المحدد‬


‫نقوم بوضع اسم ”متغير” بين القوسين‪ ،‬و الّذي نسميه ‪ . age‬في ك ّ‬
‫عند الاستدعاء )هنا ‪.(22‬‬

‫أي أن الشفرة المصدر ي ّة السابقة بعد مرور المعالج القبلي مباشرة تصبح هكذا ‪:‬‬
‫)][‪1 int main(int argc, char �argv‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫)‪if (22 >= 18‬‬
‫‪4‬‬ ‫;)”‪printf(”You are adult\n‬‬
‫‪5‬‬ ‫;‪return 0‬‬
‫} ‪6‬‬

‫تم استبدال السطر الذي يستدعي الماكرو بالشفرة التي تحتو يه الماكرو‪ ،‬و تم تعو يض ”المتغير” ‪ age‬بقيمته مباشرة في الشفرة‬
‫المصدر ي ّة للاستبدال‪.‬‬
‫يمكننا إنشاء ماكرو بعدة معاملات ‪:‬‬
‫‪1‬‬ ‫\)‪#define ADULT(age, name) if (age >= 18‬‬
‫‪2‬‬ ‫;)‪printf(”You are adult %s\n”, name‬‬
‫‪3‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫)”‪ADULT(22, ”Maxime‬‬
‫‪6‬‬ ‫;‪return 0‬‬
‫‪7‬‬ ‫}‬

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

‫م‬
‫الـ‪Qt‬‬ ‫في الواقع‪ ،‬أنت لست بحاجة أن ٺتعامل كثيرا ً مع الماكرو‪ ،‬لـكن اعلم أن مكتبات معقدة كالـ‪ wxWidgets‬و‬
‫)مكتبات لإنشاء الواجهات الرسومي ّة( تستعملان بكثرة الماكرو‪ .‬لهذا من المستحسن أن ٺتعل ّم كيف تعمل الأمور‬
‫من الآن كي لا تضيع لاحقا‪.‬‬

‫‪ 4.14‬الشروط‬

‫أجل ‪ :‬يمكننا أن نستعمل الشروط في لغة المعالج القبلي ! لاحظ كيف تعمل ‪:‬‬
‫‪1 #if condition‬‬
‫‪2‬‬ ‫‪/� Code to compile if the condition is true �/‬‬
‫‪3 #elif condition2‬‬
‫‪4‬‬ ‫‪/� Else, compile this code if the condition2 is true �/‬‬
‫‪5 #endif‬‬

‫‪183‬‬
‫الفصل ‪ .14‬المعالج القبلي )‪(Preprocessor‬‬

‫الكلمة المفتاحية ‪ #if‬تسمح بإدراج شرط معالج قبلي‪ #elif ،‬تعني ‪ . else if‬الأمر يتوقف عندما نضع ‪، #endif‬‬
‫تلاحظ أنه لا توجد حاضنتان في لغة المعالج القبلي‪.‬‬

‫الفائدة هي أننا سنتمكن من إجراء ترجمة شرطية )‪.(Conditional compilation‬‬


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

‫‪#ifndef‬‬ ‫‪ #ifdef‬و‬

‫سنرى الآن الفائدة من استعمال ‪ #define‬لتعر يف ثابت دون إعطائه أيّ قيمة‪ ،‬مثلما علّمتك من قبل ‪:‬‬

‫‪1 #define CONSTANT‬‬

‫في الواقع‪ ،‬يمكننا استعمال الشرط ‪ #ifdef‬لنقول ”إن كان الثابت معر ّفا”‪ .‬بالنسبة لـ ‪ ، #ifndef‬فهذا يعني ”إن كان‬
‫الثابت غير معر ّف”‪.‬‬

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

‫‪1‬‬ ‫‪#define WINDOWS‬‬


‫‪2‬‬ ‫‪#ifdef WINDOWS‬‬
‫‪3‬‬ ‫‪/� Source code for Windows �/‬‬
‫‪4‬‬ ‫‪#endif‬‬
‫‪5‬‬ ‫‪#ifdef LINUX‬‬
‫‪6‬‬ ‫‪/� Source code for Linux �/‬‬
‫‪7‬‬ ‫‪#endif‬‬
‫‪8‬‬ ‫‪#ifdef MAC‬‬
‫‪9‬‬ ‫‪/� Source code for Mac �/‬‬
‫‪10‬‬ ‫‪#endif‬‬

‫هذا مثال عن برنامج متعدد المنصات )‪ (multi-platform‬للتلاؤم مع النظام مثلا‪.‬‬


‫ل نظام إعادة ترجمة الشفرة )هذا ليس أمرا سحر ي ّا(‪ .‬إن كنت في ‪ Windows‬فستكتب‬
‫إذن‪ ،‬يجب من أجل ك ّ‬
‫‪ #define WINDOWS‬في الأعلى و تعيد الترجمة‪.‬‬
‫إن أردت الترجمة لـ‪ Linux‬فسيكون عليك تغيير ‪ #define‬لوضع ‪ #define LINUX‬و تعيد الترجمة‪ .‬هذه المر ّة الجزء‬
‫خاص بـ‪ Linux‬الّذي ستتم ّ ترجمته أمّا باقي الشروط فلن تكون محققة يعني أنه سيتم تجاهلها‪.‬‬
‫ال ّ‬

‫‪ #ifndef‬لتفادي التضمينات اللامنتهية‬

‫‪ #ifndef‬مهمّمة جدّا في الملفّات ‪ .h‬لتجن ّب ”التضمينات اللامنتهية”‪.‬‬

‫؟‬
‫ماذا يعني التضمين اللامنتهي ؟‬

‫هذا أمر بسيط‪ ،‬تخيل أن لدينا ملفأ ً ‪ A.h‬و ملفأ ً ‪ ، B.h‬الملف ‪ A.h‬يحتوي ‪ #include‬للملف ‪ . B.h‬إذا‬
‫فالملف ‪ B.h‬مضمّن الآن بـ ‪. A.h‬‬

‫‪184‬‬
‫‪ .4.14‬الشروط‬

‫و هنا يبدأ المشكل‪ ،‬تخي ّل أن الملف ‪ B.h‬يحتوي نفسه على ‪ #include‬للملف ‪ ! A.h‬هذا يحدث أحيانا في البرمجة !‬
‫يعني أن الملف الأول بحاجة إلى الثاني و الثاني بحاجة إلى الأول أيضا‪.‬‬

‫ن هذا ما سيحصل ‪:‬‬


‫إن فك ّرنا قليلا‪ ،‬فسنعرف أ ّ‬

‫• الحاسوب يقرأ ‪ A.h‬و يجد بأن عليه تضمين ‪. B.h‬‬

‫• يقوم بقراءة ‪ B.h‬فيجد بأن عليه تضمين ‪. A.h‬‬

‫• يضمّن ‪ A.h‬في ‪ ، B.h‬لـكن داخل ‪ A.h‬يجد بأن ّه يحتاج إلى تضمين ‪! B.h‬‬

‫ن ‪ B.h‬و يجد أن ّه يجب عليه تضمين ‪. A.h‬‬


‫• يكر ّر‪ ،‬يرى أ ّ‬
‫• إلخ‪.‬‬

‫ن هذا الأمر لا نهاية له !‬


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

‫ل ملفّات ‪ .h‬الخاصّة بك بدون‬


‫كيف السبيل لوقف هذا الكابوس المر يع ؟ إليك الحيلة‪ .‬أطلب منك فعل هذا في ك ّ‬
‫استثناء ‪:‬‬

‫‪1 #ifndef DEF_FILENAME // If the constant has not been defined, the file then has‬‬
‫‪never been included‬‬
‫‪2 #define DEF_FILENAME // We define the constant so the file will not be included‬‬
‫‪the next time‬‬
‫‪3‬‬
‫‪4 /� Content of your file .h (other #include, prototypes, #define...) �/‬‬
‫‪5‬‬
‫‪6 #endif‬‬

‫أي أننا نضع كل محتوى الملف ‪) .h‬بما في ذلك ‪ ، #include‬النماذج‪ ( #define ،‬بين الـ ‪ #ifndef‬و‬
‫الـ ‪. #endif‬‬

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

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

‫أوّل تعليمة سيجدها هي ‪:‬‬

‫‪1 #define DEF_FILENAME‬‬

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

‫يمكنك تسمية اسم الثابت كما تريد‪ ،‬أنا اعتدت على تسميته ‪. DEF_FILENAME‬‬

‫‪185‬‬
‫الفصل ‪ .14‬المعالج القبلي )‪(Preprocessor‬‬

‫الشيء الأهمّ‪ ،‬و الّذي أتمن ّى أن ّك فهمته جي ّدا‪ ،‬هو أن تغي ّر اسم الثابت من ملف ‪ .h‬إلى آخر‪ .‬يجب ألّا يكون نفس‬
‫ل ملفّات ‪ ، .h‬و إلا فلن تتم قراءة سوى أول ملف ‪ .h‬و الباقية سيتم تجاهلها !‬
‫الثابت في ك ّ‬
‫إذا فلتغي ّر ‪ FILENAME‬إلى اسم الملف الحالي‪.‬‬

‫م‬
‫أنصحك بإلقاء نظرة على ‪ .h‬الخاصّة بالمكتبات القياسي ّة المتواجدة في حاسوبك‪ ،‬سترى بأنها كلها مكتوبة بنفس‬
‫المبدأ ) ‪ #ifndef‬في البداية و ‪ #endif‬في النهاية(‪ .‬هذا يضمن عدم إمكاني ّة حصول تضمينات لامنتهية‪.‬‬

‫ملخّ ص‬

‫• المعالج القبلي هو برنامج يحل ّل الشفرة المصدر ي ّة‪ ،‬و يقوم بإجراء تغييرات علىها قبل الترجمة‪.‬‬

‫• تعليمة المعالج القبلي ‪ #include‬تسمح بإدراج محتوى ملف آخر‪.‬‬

‫• تعليمة ‪ #define‬تسمح بتعر يف ثابت معالج قبلي‪ .‬يتم استبدال كلمة مفتاحي ّة بقيمة في الشفرة المصدر ي ّة‪.‬‬

‫• الماكرو هي مجموعة أسطر من الشفرة الجاهزة معر ّفة بالـ ‪ . #define‬يمكنها أن تقبل معاملات‪.‬‬

‫• من الممكن كتابة شروط في لغة المعالج القبلي لاختيار ما يجب ترجمته‪ ،‬نستعمل عادة ‪ #elif ، #if‬و ‪. #endif‬‬

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

‫‪186‬‬
‫الفصل ‪15‬‬

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

‫تسمح لغة الـ‪ C‬بالقيام بشيء يعتبر قو يا ًجدا ً ‪ :‬و هو أن ننشئ أنواعا ًخاصة بنا‪” ،‬أنواع متغي ّرات مخصّ صة”‪ .‬سنرى نوعين‬
‫‪ :‬الـهياكل )‪ (Structures‬و التعدادات )‪.(Enumerations‬‬

‫إن إنشاء أنواع خاصّة بنا يعتبر أمرا ً ضرور يا خاصة إذا أردنا إنشاء برامج أكثر تعقيداً‪.‬‬

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

‫تعر يف هيكل‬ ‫‪1.15‬‬

‫الهيكل هو تجميع لعدد من المتغيرات التي يمكن لها أن تحمل أنواعا مختلفة‪ .‬على عكس الجداول التي ترغمنا على استعمال‬
‫‪double‬‬ ‫ل الجدول‪ ،‬بإمكانك تعر يف هيكل يحمل الأنواع ‪ int ، char ، long :‬و‬
‫خانات من نفس النوع في ك ّ‬
‫مرة واحدة‪.‬‬
‫في ّ‬
‫الهياكل في أغلب الأحيان معر ّفة في ملفات ‪ .h‬مثلما رأينا مع ‪ #define‬و نماذج الدوال‪ .‬هذا مثال عن هيكل ‪:‬‬
‫‪1 struct StructureName‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;‪int variable1‬‬
‫‪4‬‬ ‫;‪int variable2‬‬
‫‪5‬‬ ‫;‪int anotherVariable‬‬
‫‪6‬‬ ‫;‪double decimalNumber‬‬
‫;} ‪7‬‬

‫لتعر يف هيكل‪ ،‬يجب علينا أن نبدأ بالكلمة المفتاحية ‪ ، struct‬متبوعة باسم الهيكل )مثلا ‪ File‬أو ‪.( Screen‬‬

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

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

‫بعد ذلك‪ ،‬نفتح حاضنة لنغلقها لاحقا ًتماما مثل الدوال‪.‬‬

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

‫و الآن‪ ،‬ماذا نضع داخل الحاضنتين ؟‬


‫هذا سهل‪ ،‬سنضع المتغيرات التي يتكون منها الهيكل‪ ،‬و عادة ما يتكون الهيكل من ”م ُتَغَي ِّري ْن داخِلِيَي ْن” على الأقل‪ ،‬و إلا‬
‫فلن يحمل معنى كبيرا‪.‬‬
‫ل الهياكل ماهي إلّا ”تجميعات” لمتغي ّرات من أنواع‬
‫كما ترى‪ ،‬فإنشاء نوع متغي ّرات مخصّ ص ليس بالأمر الصعب‪ .‬ك ّ‬
‫ن نوعا ‪ File‬مثلا ً ما هو إلا مجموعة من الأعداد‬
‫قاعدي ّة مثل ‪ ، double ، int ، long‬إلخ‪ .‬لا توجد معجزة‪ ،‬إ ّ‬
‫القاعدي ّة !‬
‫مثال عن هيكل‬

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

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

‫عندما نعمل في ‪ 2D‬لدينا محوران ‪ :‬محور الفواصل )من اليسار إلى اليمين( و محور التراتيب )من الأسفل إلى الأعلى(‪.‬‬
‫من العادة أن نرمز للفواصل بمتغي ّر يدعى ‪ x‬و للتراتيب بـ ‪. y‬‬

‫هل يمكنك كتابة هيكل ‪ Coordinates‬يسمح بتخزين كلّا من الفاصلة ) ‪ ( x‬و الترتيبة ) ‪ ( y‬لنقطة ما ؟‬
‫هي ّا‪ ،‬هي ّا‪ ،‬الأمر ليس صعبا ‪:‬‬
‫‪1 struct Coordinates‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫‪int x; // Abscissas‬‬
‫‪4‬‬ ‫‪int y; // Ordinates‬‬
‫;} ‪5‬‬

‫هيكلنا يسمّى ‪ Coordinates‬و هو متكو ّن من متغيرين ‪ x‬و ‪ y‬أي الفاصلة )‪ (Abscissa‬و الترتيبة )‪.(Ordinate‬‬

‫ل‬
‫إن أردنا‪ ،‬يمكننا بسهولة إنشاء هيكل ‪ Coordinates‬من أجل ‪ : 3D‬يكفي فقط إضافة متغي ّر ثالث )مثلا ‪ ( z‬يد ّ‬
‫على الارتفاع‪ .‬بهذا سيكون لدينا هيكل لإدارة النقاط الثلاثي ّة الأبعاد في الفضاء !‬

‫‪188‬‬
‫‪ .2.15‬استعمال هيكل‬

‫جدول داخل هيكل‬

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

‫‪1 struct Person‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;]‪char firstName[100‬‬
‫‪4‬‬ ‫;]‪char lastName[100‬‬
‫‪5‬‬ ‫;]‪char address[1000‬‬
‫‪6‬‬ ‫;‪int age‬‬
‫‪7‬‬ ‫‪int boy; // Boolean : 1 = boy, 0 = girl‬‬
‫;} ‪8‬‬

‫هذا الهيكل متشكّل من ‪ 5‬متغيرات داخلي ّة‪ ،‬الثلاث الأولى هي سلاسل محرفي ّة لتخزين الاسم‪ ،‬اللقب و العنوان‪.‬‬
‫المتغيران الأخيران يخزّنان ع ُمر و جنس الشخص‪ .‬الجنس هو متغي ّر منطقي‪ = 1 ،‬صحيح = ولد و ‪ = 0‬خطأ = بنت‪.‬‬

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

‫استعمال هيكل‬ ‫‪2.15‬‬

‫و الآن‪ ،‬بما أن الهيكل معر ّف في ملف ‪ ، .h‬سنتمكّن من استعماله في دالة موجودة بملف ‪. .c‬‬
‫عرفناه سابقا( ‪:‬‬
‫أنظر كيف نقوم بإنشاء متغير من نوع ‪) Coordinates‬الهيكل الّذي ّ‬
‫‪1 #include ”main.h” // Including the files that contains the prototypes and‬‬
‫‪structures‬‬
‫)][‪2 int main(int argc, char �argv‬‬
‫{ ‪3‬‬
‫‪4‬‬ ‫‪struct Coordinates point; // Creating a variable ”point” of type‬‬
‫‪Coordinates‬‬
‫‪5‬‬ ‫;‪return 0‬‬
‫} ‪6‬‬

‫هكذا نكون قد أنشأنا متغيرا ً ‪ point‬من نوع ‪ ! Coordinates‬هذا المتغير سيحمل داخله مركّ بين )متغيرين داخليين(‬
‫‪ x :‬و ‪) y‬فاصلته و ترتيبته(‪.‬‬

‫؟‬
‫هل من اللازم أن نضع الكلمة المفتاحية ‪ struct‬عند تعر يف المتغير ؟‬

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

‫نعم‪ ،‬فهذا يسمح للحاسوب بأن يفر ّق بين نوع عادي )مثل ‪ ( int‬و نوع مخصّ ص‪.‬‬
‫ل تعر يف لمتغي ّر مخصّ ص‪ .‬لمعالجة هذا‬
‫ل مرة الكلمة ‪ struct‬في ك ّ‬
‫المبرمجون وجدوا أنه من المتعب جدّا أن يكتبوا في ك ّ‬
‫المشكل‪ ،‬اخترعوا تعليمة خاصّة ‪ :‬الـ ‪. typedef‬‬

‫الـ‪typedef‬‬

‫لنعد إلى الملف ‪ .h‬الذي يحمل تعر يف هيكلنا من نوع ‪ . Coordinates‬سنضيف تعليمة اسمها ‪ typedef‬و ال ّتي‬
‫تفيد في إعطاء اسم مستعار )‪ (alias‬لهيكل‪ ،‬أي كتابة شيء مكافئ لكتابة آخر‪.‬‬

‫إذا‪ ،‬سنضيف سطرا يبدأ بـ ‪ typedef‬قبل تعر يف الهيكل مباشرة ‪:‬‬

‫‪1‬‬ ‫;‪typedef struct Coordinates Coordinates‬‬


‫‪2‬‬ ‫‪struct Coordinates‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;‪int x‬‬
‫‪5‬‬ ‫;‪int y‬‬
‫‪6‬‬ ‫;}‬

‫هذا السطر متكون من ثلاثة أجزاء ‪:‬‬

‫• ‪ : typedef‬تعني أننا سنقوم بإنشاء اسم مستعار لهيكل‪.‬‬

‫• ‪ : struct Coordinates‬هو اسم الهيكل الذي سنقوم بانشاء اسم مستعار له )أي ”مكافئ”(‪.‬‬

‫• ‪ : Coordinates‬هو الاسم المكافئ‪.‬‬

‫ببساطة‪ ،‬هذا السطر يقول ‪” :‬كتابة ‪ Coordinates‬مكافئ لكتابة ‪ .” struct Coordinates‬بفعل هذا‪ ،‬لن‬
‫ل تعر يف لمتغي ّر من نوع ‪ . Coordinates‬يمكننا العودة إلى ‪ main‬و كتابة‬
‫يكون عليك كتابة الكلمة ‪ struct‬عند ك ّ‬
‫فقط ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪Coordinates point; // The computer understands that we are talking‬‬
‫‪about ”struct Coordinates” thanks to typedef‬‬
‫‪4‬‬ ‫;‪return 0‬‬
‫} ‪5‬‬

‫أنصحك أن تستعمل الـ ‪ typedef‬مثلما فعلت أنا هنا من أجل ‪ . Coordinates‬أغلب المبرمجـين يفعلون هذا‪ .‬هذا‬
‫مرة‪ .‬المبرمج الجي ّد هو مبرمج كسول ! أي أنه يكتب أقل ما يمكن‪.‬‬
‫ل ّ‬‫يسمح لهم بعدم كتابة ‪ struct‬في ك ّ‬

‫‪190‬‬
‫‪ .2.15‬استعمال هيكل‬

‫تغيير مركّبات هيكل‬

‫و الآن بعدما قمنا بإنشاء متغي ّرنا ‪ ، point‬نريد أن نغي ّر إحداثي ّاته‪.‬‬
‫كيف نصل إلى ‪ x‬و ‪ y‬الموجودة في المتغير ‪ point‬؟ هكذا ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪Coordinates point‬‬
‫‪4‬‬ ‫;‪point.x = 10‬‬
‫‪5‬‬ ‫;‪point.y = 20‬‬
‫‪6‬‬ ‫;‪return 0‬‬
‫} ‪7‬‬

‫بهذا نكون قد غي ّرنا قيمة ‪ ، point‬بإعطائه الفاصلة ‪ 10‬و الترتيبة ‪ .20‬نقطتنا أصبحت في الوضعية )‪10‬؛‪) (20‬هذا‬
‫هو الترميز الر ياضياتي للإحداثي ّات(‪.‬‬

‫لـكي نتمكن من الوصول إلى مركّ ب في الهيكل‪ ،‬يجب كتابة ‪:‬‬

‫‪1‬‬ ‫‪variable.componentName‬‬

‫النقطة هي التي تفر ّق بين المتغير و المركّ ب‪.‬‬

‫إن أخذنا الهيكل ‪ Person‬الذي رأيناه منذ قليل و نطلب الاسم و اللقب فسنفعل هكذا ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪Person user‬‬
‫‪4‬‬ ‫;)” ? ‪printf(”What’s your last name‬‬
‫‪5‬‬ ‫;)‪scanf(”%s”, user.lastName‬‬
‫‪6‬‬ ‫;)” ? ‪printf(”What’s your first name‬‬
‫‪7‬‬ ‫;)‪scanf(”%s”, user.firstName‬‬
‫‪8‬‬ ‫;)‪printf(”You are %s %s”, user.firstName, user.lastName‬‬
‫‪9‬‬ ‫;‪return 0‬‬
‫} ‪10‬‬

‫‪What’s your last name ? Dupont‬‬


‫‪What’s your first name ? Jean‬‬
‫‪You are Jean Dupont‬‬

‫نرسل المتغير ‪ user.lastName‬إلى الدالة ‪ ، scanf‬و التي ستكتب مباشرة في ‪. user‬‬


‫نفعل نفس الشيء مع ‪ ، firstName‬يمكننا فعل ذلك أيضا مع العنوان‪ ،‬العمر و الجنس‪ ،‬لـكن ّي لا أرغب بتكرار ذلك‬
‫)يحب أن أكون مبرمجا !(‪.‬‬

‫يمكن فعل هذا بدون معرفة الهياكل‪ ،‬فقط بإنشاء متغي ّر ‪ lastName‬و آخر ‪. firstName‬‬
‫لـكن الفائدة هنا هي أنه بهذه الطر يقة يمكننا أن ننشئ متغيرا آخر من نوع ‪ Person‬و يكون لديه هو أيضا اسمه الخاص‪،‬‬
‫لقبه الخاص‪ ،‬إلخ‪ .‬يمكننا إذن فعل هذا ‪:‬‬

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

‫;‪1 Person player1, player2‬‬

‫ل لاعب سيكون لديه اسمه الخاص‪ ،‬لقبه الخاص‪ ،‬إلخ‪.‬‬


‫ل لاعب‪ .‬ك ّ‬
‫و هكذا نخز ّن معلومات ك ّ‬

‫يمكننا أن نفعل ما هو أفضل ‪ :‬يمكننا تعر يف جدول من ‪! Person‬‬


‫القيام بهذا سهل ‪:‬‬

‫;]‪1 Person players[2‬‬

‫و بعدها يمكننا الوصول إلى لقب اللاعب المتواجد بالخانة الأولى مثلا‪ ،‬هكذا ‪:‬‬

‫‪players[0].lastName‬‬

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

‫ل لاعب باستخدام حلقة‪ .‬إبدأ بجدول ذي‬


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

‫تهيئة هيكل‬

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

‫للتذكير‪ ،‬هكذا نقوم بالتهيئة ‪:‬‬

‫• المتغير ‪ :‬نعطيه القيمة ‪) 0‬الحالة الأبسط(‪.‬‬

‫• المؤش ّر ‪ :‬نجعل قيمته ‪ . NULL‬بالمناسبة فـ ‪ NULL‬هي ‪ #define‬موجود في مكتبة ‪ stdlib.h‬و هي عادة‬


‫‪ ،0‬لـكن ّنا نستمر ّ في استخدام ‪ NULL‬للمؤشرات لـكي نبېّن أنّها مؤش ّرات و ليست متغي ّرات عادي ّة‪.‬‬

‫ل خاناته على القيمة ‪.0‬‬


‫• الجدول ‪ :‬نضع ك ّ‬

‫بالنسبة للهياكل‪ ،‬فالتهيئة شبيهة بتلك الخاصّة بالجدول‪ .‬في الواقع‪ ،‬يمكننا القيام بها عند التصريح عن المتغي ّر ‪:‬‬

‫;}‪1 Coordinates point = {0, 0‬‬

‫‪192‬‬
‫‪ .3.15‬مؤش ّر نحو هيكل‬

‫و هذا يعر ّف بالترتيب ‪ point.x = 0 :‬و ‪ . point.y = 0‬لنعد إلى الهيكل ‪) Person‬الذي يحتوي سلاسل‬
‫محرفي ّة(‪ .‬يمكننا أن نعطي قيمة ابتدائية للسلاسل بكتابة فقط ”” )لا شيء ببن علامتي الاقتباس(‪ .‬لم أعلمك هذا‬
‫خاص بالسلاسل‪ ،‬لـكنّ الوقت ليس متأخّرا على تعلّمها‪ .‬يمكننا إذن تهيئة على الترتيب ‪، firstName‬‬
‫الشيء في الفصل ال ّ‬
‫‪ ، age ، address ، lastName‬و ‪ boy‬هكذا ‪:‬‬

‫;}‪1 Person user= {””, ””, ””, 0, 0‬‬

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

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

‫مؤش ّر نحو هيكل‬ ‫‪3.15‬‬

‫المؤش ّر على الهيكل يتم ّ إنشائه بنفس طر يقة إنشاء مؤش ّر على ‪ int‬أو ‪ double‬أو أيّ نوع قاعديّ آخر ‪:‬‬

‫;‪1 Coordinates� point = NULL‬‬

‫عرفنا مؤش ّرا نحو ‪ Coordinates‬اسمه ‪. point‬‬


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

‫;‪1 Coordinates �point = NULL‬‬

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

‫;‪1 Coordinates �point1 = NULL, �point2 = NULL‬‬

‫إرسال هيكل إلى دالة‬

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

‫هذا ما سنقوم به في هذا المثال‪ ،‬سنقوم بإنشاء متغير من نوع ‪ Coordinates‬في ‪ ، main‬و نرسل عنوانه إلى‬
‫‪ . initializeCoordinates‬دور هذه الدالة هو إعطاء القيمة ‪ 0‬لعناصر الهيكل‪.‬‬

‫دالتنا ‪ initializeCoordinates‬ستأخذ معاملا واحدا ‪ :‬مؤشر نحو هيكل من نوع ‪) Coordinates‬أي‬


‫‪.( *Coordinates‬‬

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

‫‪1‬‬ ‫)][‪int main(int argc, char �argv‬‬


‫‪2‬‬ ‫{‬
‫‪3‬‬ ‫;‪Coordinates myPoint‬‬
‫‪4‬‬ ‫;)‪initializeCoordinates(&myPoint‬‬
‫‪5‬‬ ‫;‪return 0‬‬
‫‪6‬‬ ‫}‬
‫‪7‬‬ ‫)‪void initializeCoordinates(Coordinates� point‬‬
‫‪8‬‬ ‫{‬
‫‪9‬‬ ‫‪// Initializing each member of the structure here‬‬
‫‪10‬‬ ‫}‬

‫متغيري ‪ myPoint‬تم إنشاؤه في ‪. main‬‬


‫نقوم يارسال عنوانه إلى الدالة ‪ initializeCoordinates‬ال ّتي تسترجعه على شكل متغي ّر يسمّى ‪) point‬كان‬
‫بإمكاننا تسميته كما شئنا‪ ،‬هذا الأمر ليس له أيّ تأثير(‪.‬‬

‫الآن بما أنّنا داخل الدالة ‪ ، initializeCoordinates‬سنقوم يتهيئة قيم المتغير ‪ point‬واحدة بواحدة‪.‬‬
‫يجب عدم نسيان وضع النجمة أمام اسم المؤشر للوصول إلى المتغير‪ .‬إن لم تفعل‪ ،‬فأنت تخاطر بتغيير العنوان‪ ،‬و ليس هذا‬
‫ما نريد فعله‪.‬‬
‫و لـكن هاهي مشكلة ‪ ...‬لا يمكننا القيام مباشرة بهذا ‪:‬‬
‫)‪1 void initializeCoordinates(Coordinates� point‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;‪�point.x = 0‬‬
‫‪4‬‬ ‫;‪�point.y = 0‬‬
‫} ‪5‬‬

‫ن النقطة تطب ّق على ‪ point‬فقط و ليس على ‪. *point‬‬


‫سيكون ذلك سهلا جدّا ‪ ...‬لماذا لا يمكننا القيام بهذا ؟ لأ ّ‬
‫لـكنّ ما نريده هو الوصول إلى ‪ *point‬لتغيير قيمته‪.‬‬
‫‪point‬‬ ‫ل هذا المشكل‪ ،‬يجب وضع الأقواس حول ‪ ، *point‬هكذا ستطب ّق النقطة على ‪ *point‬و ليس فقط على‬
‫لح ّ‬
‫‪:‬‬
‫)‪1 void initializeCoordinates(Coordinates� point‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;‪(�point).x = 0‬‬
‫‪4‬‬ ‫;‪(�point).y = 0‬‬
‫} ‪5‬‬

‫هذه الشفرة تعمل‪ ،‬يمكنك تجريبها‪ .‬المتغير من نوع ‪ Coordinates‬تم ّ إرساله إلى الدالة التي هي ّأت ‪ x‬و ‪ y‬على‬
‫‪.0‬‬
‫م‬
‫في لغة الـ‪ ،C‬نهي ّئ عادة هياكلنا بالطر يقة ال ّتي رأيناها سابقا‪ .‬بالمقابل‪ ،‬في لغة الـ‪ ،C++‬التهيئة تكون في الغالب داخل‬
‫”دوال”‪ .‬إن لغة ‪ C++‬ليست سوى ”تحسين خارق” للهياكل‪ .‬كثير من الأشياء تبدأ من هذا و أحتاج إلى كتاب‬
‫ل شيء في وقته(‪.‬‬
‫كامل لأتحدّث عنها )ك ّ‬

‫‪194‬‬
‫‪ .3.15‬مؤش ّر نحو هيكل‬

‫اختصار عمليّ و مستعمل بكثرة‬

‫سترى أننا سنستعمل كثيرا ً مؤشرات نحو هياكل‪ .‬بصراحة‪ ،‬يجب أن أعترف لك بأن ّه في لغة الـ‪ C‬نستخدم المؤشرات نحو‬
‫ل ٺتبعك حت ّى إلى قبرك‪ ،‬فأنا لا‬
‫ن المؤشرات ستظ ّ‬
‫الهياكل أكثر من استعمال الهياكل وحدها‪ .‬لهذا فعندما أقول لك بأ ّ‬
‫أقولها تقريبا من أجل المزاح !‬

‫بما أن المؤشرات نحو الهياكل مستعملة بكثرة‪ ،‬نجد أنفسنا نستعمل هذه الكتابة كثيرا ‪:‬‬

‫;‪1 (�point).x = 0‬‬

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

‫;‪1 point−>x = 0‬‬

‫هذا الاختصار يتم ّ كتابته بمطّة ‪ -‬متبوعة بعلامة ترتيب > ‪.‬‬

‫كتابة ‪ point->x‬هو إذن مكافئ تماما لكتابة ‪. (*point).x‬‬

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

‫لنعد إلى دالتنا ‪ initializeCoordinates‬يمكننا كتابتها بالشكل التالي ‪:‬‬

‫)‪1 void initializingCoordinates(Coordinates� point‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪point−>x = 0‬‬
‫‪4‬‬ ‫;‪point−>y = 0‬‬
‫} ‪5‬‬

‫و تذك ّر جي ّدا اختصار السهم‪ ،‬سنستعمله كثيرا ً من الآن‪ .‬و خاصّة لا تخلط بين السهم و ”النقطة”‪ .‬السهم مخصّ ص‬
‫للمؤش ّرات‪” ،‬النقطة” مخصّ صة للمتغي ّرات‪ .‬استخدم هذا المثال الصغير للتذك ّر ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪Coordinates myPoint‬‬
‫‪4‬‬ ‫;‪Coordinates �pointer = &myPoint‬‬
‫‪5‬‬ ‫”‪myPoint.x = 10; // We work on a variable, we use the ”dot‬‬
‫‪6‬‬ ‫‪pointer−>x = 10; // We work on a pointer, we use the arrow‬‬
‫‪7‬‬ ‫;‪return 0‬‬
‫} ‪8‬‬

‫نغي ّر قيمة ‪ x‬إلى ‪ 10‬بطر يقتين مختلفتين‪ ،‬هنا ‪ :‬الطر يقة الأولى هي بالعمل مباشرة على المتغير‪ ،‬و الطر يقة الثانية‬
‫باستعمال المؤشر‪.‬‬

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

‫التعدادات‬ ‫‪4.15‬‬

‫التعدادات هي طر يقة مختلفة قليلا ًلنعر ّف نوع متغيرات خاص بنا‪.‬‬

‫التعداد ليس متكو ّنا من ”مركّبات” كما هو الحال مع الهياكل‪ .‬و إنما هو مجموعة من ”القيم الممكنة” لمتغي ّر‪ .‬التعداد‬
‫سيأخذ إذن خانة واحدة في الذاكرة و هذه الخانة تأخذ قيمة واحدة من مجموع القيم التي قمت بتعر يفها )واحدة فقط في‬
‫مرة(‪ .‬هذا مثال عن تعداد ‪:‬‬
‫ل ّ‬‫ك ّ‬
‫‪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 ،1‬تساوي ‪ ،2‬إلخ‪ .‬؟‬
‫عملي ّا‪ ،‬هل يهمّنا أن نعرف أ ّ‬

‫ل قيمة‪ .‬بفضل هذا‪ ،‬ليس عليك‬


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

‫‪196‬‬
‫ملخّ ص‬

‫)‪1 if (music == MEDIUM‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪// Play music with medium volume‬‬
‫} ‪4‬‬

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


‫لا يه ّ‬

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

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

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


‫ل عنصر من التعداد‪.‬‬
‫من الممكن طلب إرفاق قيمة محدّدة لك ّ‬

‫ما الفائدة التي يمكن تحصيلها من هذا ؟ حسنا فلنفرض أن ّه في حاسوبك‪ ،‬الصوت يتم تحديده بقيمة بين ‪ 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‬مثلا‪.‬‬

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

‫‪198‬‬
‫الفصل ‪16‬‬

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

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

‫)‪Hard‬‬ ‫مخزّنة في القرص الصلب‬


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

‫ل ما درسناه حت ّى الآن ‪ :‬المؤشرات‪ ،‬الهياكل‪ ،‬السلاسل‬


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

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

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

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

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

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


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

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

‫ل البرامج التي تكتبها في المستقبل‪ ،‬أي ّا‬


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

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

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

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

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

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

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

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

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

‫لهذا فلنرى قليلا ًنموذج ‪: fopen‬‬

‫;)‪1 FILE� fopen(const char� fileName, const char� openMode‬‬

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

‫• اسم الملف الذي نريد فتحه‪.‬‬

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

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

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

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

‫‪200‬‬
‫‪ .1.16‬فتح و غلق ملف‬

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

‫لنعد إلى دالتنا ‪ ، 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.element‬‬ ‫بالضرورة نفس المركّبات في كل الأنظمة(‪ .‬لهذا فلن نعدّل محتوى ‪ FILE‬مباشرة )لا نقوم بـ‬
‫مثلا(‪ .‬بل سنكتفي باستدعاء دوال‪ ،‬ٺتعامل مع ‪ FILE‬نيابة عناً‪.‬‬

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

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

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

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

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

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

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

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

‫لمعلوماتك‪ ،‬أنا عرضت لك بعضا من أوضاع فتح ملف‪ .‬في الحقيقة‪ ،‬يوجد ضعفها ! من أجل كل وضع رأيناه هنا‪،‬‬
‫إن أضفت ”‪ ”b‬بعد المحرف الأول ) ”‪ ،( ”ab+” ، ”wb+” ، ”rb+” ، ”ab” ، ”wb” ، ”rb‬فإن الملف سيتم‬
‫ختص بتخزين ‪ ...‬النص‪،‬‬
‫فتحه بالوضع الثنائي )‪ .(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‬‬
‫‪4‬‬ ‫;)”‪file = fopen(”test.txt”, ”r+‬‬
‫‪5‬‬ ‫;‪return 0‬‬
‫} ‪6‬‬

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

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

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

‫‪202‬‬
‫‪ .1.16‬فتح و غلق ملف‬

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

‫؟‬
‫هل يجب أن يكون الملف بصيغة ‪ .txt‬؟‬

‫‪.level‬‬ ‫لا‪ ،‬الأمر يعود إليك في اختيار صيغة الملف عندما تفتحه‪ .‬أي أنه بإمكانك أن تخـترع صيغتك الخاصّة‬
‫لحفظ مستو يات ألعابك مثلاً‪.‬‬

‫؟‬
‫هل من الواجب أن يكون الملف الذي نريد فتحه في نفس دليل الملف التنفيذي ؟‬

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

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

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

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

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

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

‫هذه الشفرة تفتح الملف ‪ 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‬فقد تم الفتح بنجاح‪.‬‬
‫سنتبع إذن هذا المخطط التالي ‪:‬‬
‫)][‪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‬‬

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

‫‪204‬‬
‫‪ .1.16‬فتح و غلق ملف‬

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

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

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


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

‫هذه الدالة تأخذ معاملا واحدا ‪ :‬المؤشر نحو الملف‪.‬‬

‫تقوم بإرجاع ‪ ، int‬و الذي يأخذ القيم ‪:‬‬

‫• ‪ : 0‬إذا نجح غلق الملف‪.‬‬

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

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

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


‫;)‪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‬لأظهر رسالة خطأ في حال لم ينجح الفتح‪ ،‬يمكنك فعل ذلك إن أردت‪.‬‬

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

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

‫نسيان تحرير الذاكرة أمر يقع‪ .‬بل سيحدث لك هذا كثيرا‪ .‬في هذه الحالة نقول أن ّه قد حدث تسريب للذاكرة‬
‫)‪ .(Memory leak‬هذا يجعل برنامجك يستخدم قدرا من الذاكرة أكبر من اللازم بدون أن تفهم سبب حصول ذلك‪ .‬في‬
‫غالب الأحيان‪ ،‬يكون السبب واحدا أو إثنين من الأمور ”الثانو ية” مثل نسيان ‪. fclose‬‬

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

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

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

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

‫الملف )حرف واحد في المرة(‪.‬‬


‫ّ‬ ‫• ‪ : fputc‬تكتب حرفا في‬

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

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

‫‪fputc‬‬

‫هذه الدالة تكتب حرفا واحدا في المر ّة في الملف‪ .‬نموذجها ‪:‬‬


‫;)‪1 int fputc(int character, FILE� pointerOnFile‬‬

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

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

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

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

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

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

‫)][‪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‬كما ترى في الشكل التالي ‪:‬‬

‫‪fputs‬‬

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

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

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

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

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

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

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

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

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

‫)][‪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‬‬
‫‪4‬‬ ‫;‪int age = 0‬‬
‫‪5‬‬ ‫;)”‪file = fopen(”test.txt”, ”w‬‬
‫‪6‬‬ ‫)‪if (file != NULL‬‬
‫‪7‬‬ ‫{‬
‫‪8‬‬ ‫‪// Request the age‬‬
‫‪9‬‬ ‫;)” ? ‪printf(”How old are you‬‬
‫‪10‬‬ ‫;)‪scanf(”%d”, &age‬‬

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

‫‪11‬‬ ‫‪// Write the age on the file‬‬


‫‪12‬‬ ‫;)‪fprintf(file , ”The mister who uses the PC has %d years”, age‬‬
‫‪13‬‬ ‫;)‪fclose(file‬‬
‫‪14‬‬ ‫}‬
‫‪15‬‬ ‫;‪return 0‬‬
‫‪16‬‬ ‫}‬

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

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

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

‫• ‪ : fgetc‬قراءة محرف‪.‬‬

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

‫• ‪ : fscanf‬قراءة سلسلة منسّقة‪.‬‬

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

‫‪fgetc‬‬

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

‫;)‪1 int fgetc(FILE� pointerOnFile‬‬

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

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

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

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

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

‫ل مرة تقرأ فيها واحدا‪ .‬أي أنك إن استدعيت ‪ 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 fgets didn‬‬
‫)‪’t return EOF (End Of File‬‬
‫‪14‬‬ ‫;)‪fclose(file‬‬
‫‪15‬‬ ‫}‬
‫‪16‬‬ ‫;‪return 0‬‬
‫} ‪17‬‬

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

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

‫‪fgets‬‬

‫ل محارف الملف واحدا واحدا‪ .‬الدالة تقرأ على الأكثر سطرا ً‬


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

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

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

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

‫سنتعل ّم كيف نقرأ سطرا ً واحدا ً باستخدام ‪) ، 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 characters‬‬
‫”‪from the file, store them in ”string‬‬
‫‪10‬‬ ‫‪printf(”%s”, string); // Display the string‬‬
‫‪11‬‬ ‫;)‪fclose(file‬‬
‫‪12‬‬ ‫}‬
‫‪13‬‬ ‫;‪return 0‬‬
‫} ‪14‬‬

‫ن المحتوى يُكتب في الـكونسول ‪:‬‬


‫النتيجة هي نفسها النتيجة السابقة‪ ،‬مع العلم أ ّ‬
‫! ‪Hello, I’m the content of the file test.txt‬‬

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

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

‫مرة اخرى في الـ ‪ fgets‬لنقوم بتحديد عدد المحارف التي نقرؤها‪.‬‬


‫• ّ‬

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

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

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

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

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

‫‪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 read‬‬
‫‪12‬‬ ‫}‬
‫‪13‬‬ ‫;)‪fclose(file‬‬
‫‪14‬‬ ‫}‬
‫‪15‬‬ ‫;‪return 0‬‬
‫} ‪16‬‬

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

‫السطر الأكثر لفتا ًللانتباه في الشفرة هو ‪:‬‬

‫)‪1 while (fgets(string, MAX_SIZE, file) != NULL‬‬

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

‫‪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‬‬

‫‪212‬‬
‫‪ .3.16‬التحرك داخل ملف‬

‫‪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.16‬‬

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

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

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

‫توجد ثلاث دوال لتتعرف عليها ‪:‬‬

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

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

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

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

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

‫ل على موضع المؤشر في الملف‪.‬‬


‫العدد الّذي يتم ارجاعه يد ّ‬

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

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

‫نموذج ‪ fseek‬هو التالي ‪:‬‬

‫;)‪1 int fseek(FILE� pointerOnFile, long deplacement, int origin‬‬

‫ل عليها ‪ ( deplacement‬انطلاقا من الموضع الّذي‬


‫الدالّة ‪ fseek‬تسمح بتحر يك المؤش ّر بـعدد من المحارف )يد ّ‬
‫ل عليه ‪. origin‬‬
‫يد ّ‬

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

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

‫– ‪ SEEK_SET‬تعني بداية الملف‪.‬‬


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

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

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

‫• ‪ : 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‬‬

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

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

‫هذه الدالة تقوم بحذف ملف دون ترك أي أثر ‪:‬‬

‫;)‪1 int remove(const char� fileToDelete‬‬

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

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

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;)”‪remove(”test.txt‬‬
‫‪4‬‬ ‫;‪return 0‬‬
‫} ‪5‬‬

‫‪216‬‬
‫الفصل ‪17‬‬

‫ي للذاكرة )‪(Dynamic memory allocation‬‬


‫الحجز الح ّ‬

‫خاص بلغة ‪ .C‬لقد كانت الطر يقة البسيطة‪.‬‬


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

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

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

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

‫;‪1 int myNumber = 0‬‬

‫عندما يصل المترجم إلى سطر مشابه للسطر السابق‪ ،‬يقوم بالأمور التالية ‪:‬‬

‫• يقوم البرنامج بطلب إذن من نظام التشغيل )‪ ( ... Mac OS ،GNU/Linux ،Windows‬ليحجز شيئا من الذاكرة‪.‬‬

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

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

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

‫في هذا الفصل سنقوم بـ ‪:‬‬

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

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


‫• دراسة كيف تعمل الذاكرة )نعم‪ّ ،‬‬

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

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

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

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

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

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

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

‫)‪1 sizeof(int‬‬

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

‫‪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‬‬

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

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

‫‪218‬‬
‫‪ .1.17‬حجم المتغيرات‬

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

‫أنت تلاحظ أن ‪ 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‬‬

‫كلما احتوى الهيكل من مركّبات كل ّما أخذ حجما أكثر من الذاكرة‪ .‬الأمر منطقي تماما‪ ،‬أليس كذلك ؟‬

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

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

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

‫;‪1 int number = 18‬‬

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

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

‫لنفترض أن المتغير ‪ number‬محجوز بالعنوان ‪ 1600‬من الذاكرة‪ .‬سيكون لدينا إذا المخطط التالي للذاكرة ‪:‬‬

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

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

‫تخي ّل الآن جدولا من ‪! int‬‬


‫كل ”خانة” من الجدول ستحجز ‪ 4‬بايت‪ .‬إن كان الجدول يحوي مثلا ً‪ 100‬خانة ‪:‬‬

‫;]‪1 int table[100‬‬

‫‪220‬‬
‫ي للذاكرة‬
‫‪ .2.17‬الحجز الح ّ‬

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

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

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

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

‫;]‪1 Coordinates table[100‬‬

‫سيستخدم هذه المر ّة ‪ 8 ∗ 100 = 800‬بايت‪.‬‬

‫م الفهم الجي ّد لهذه الحسابات البسيطة لنواصل بقي ّة الفصل‪.‬‬


‫من المه ّ‬

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

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

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

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

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

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

‫‪ .1‬استدعاء ‪ malloc‬من أجل طلب الذاكرة‪.‬‬

‫‪ .2‬اختبار القيمة التي تم إرجاعها من طرف ‪ malloc‬لمعرفة ما إن نجح نظام التشغيل في حجز الذاكرة‪.‬‬

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

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

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

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

‫نموذج الدالة ‪ malloc‬هزليّ جدّا‪ ،‬سترى ‪:‬‬

‫;)‪1 void� malloc(size_t numberOfNecessaryBytes‬‬

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

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

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

‫كن متأكّدا‪ ،‬يوجد سبب‪ .‬في الحقيقة‪ ،‬هذه الدالة تعيد عنوان الخانة التي حجزها نظام التشغيل من أجل متغي ّرك‪ .‬إن‬
‫استطاع النظام إ يجاد مكان لك في العنوان ‪ ،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‬عنوان الخانة التي تم حجزها بالذاكرة‪ .‬هناك احتمالان ‪:‬‬

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

‫‪222‬‬
‫ي للذاكرة‬
‫‪ .2.17‬الحجز الح ّ‬

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

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

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

‫سنستعمل دالة قياسي ّة لم يسبق لنا رؤيتها حت ّى الآن ‪ . 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‬‬

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

‫)][‪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‬‬ ‫{‬

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

‫‪7‬‬ ‫! ‪exit(0); // Error: Stop everything‬‬


‫‪8‬‬ ‫}‬
‫‪9‬‬ ‫‪// We can use the memory here‬‬
‫‪10‬‬ ‫‪free(allocatedMemory); // No need for the memory anymore, free it.‬‬
‫‪11‬‬ ‫;‪return 0‬‬
‫} ‪12‬‬

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

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

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪int� allocatedMemory = NULL‬‬
‫‪4‬‬ ‫‪allocatedMemory = malloc(sizeof(int)); // Allocation of the memory‬‬
‫‪5‬‬ ‫)‪if (allocatedMemory == NULL‬‬
‫‪6‬‬ ‫{‬
‫‪7‬‬ ‫;)‪exit(0‬‬
‫‪8‬‬ ‫}‬
‫‪9‬‬ ‫‪// Using the memory‬‬
‫‪10‬‬ ‫;)” ? ‪printf(”How old are you‬‬
‫‪11‬‬ ‫;)‪scanf(”%d”, allocatedMemory‬‬
‫‪12‬‬ ‫;)‪printf(”You are %d years old\n”, �allocatedMemory‬‬
‫‪13‬‬ ‫‪free(allocatedMemory); // Freeing the memory‬‬
‫‪14‬‬ ‫;‪return 0‬‬
‫} ‪15‬‬

‫‪How old are you ? 31‬‬


‫‪You are 31 years old‬‬

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

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

‫‪224‬‬
‫ي لجدول‬
‫‪ .3.17‬الحجز الح ّ‬

‫)][‪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‬‬
‫‪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.17‬‬

‫ي في هذا‪ .‬نستخدم الطر يقة التلقائي ّة ال ّتي‬


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

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

‫لنتخيل مثلا برنامجا ًيقوم بتخزين أعمار أصدقاء المستخدم في جدول‪ ،‬يمكنك فعل ذلك هكذا ‪:‬‬
‫;]‪1 int friendsAge[15‬‬

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

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

‫كما علّمتك‪ ،‬من الممنوع في لغة الـ‪ C‬إنشاء جدول بتحديد حجمه باستخدام متغي ّر ‪:‬‬

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

1 int friends[friendsNumber];

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

‫ و هذا بفضل شفرة تعمل‬. 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 count
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]);

226
‫ملخّ ص‬

‫‪20‬‬ ‫}‬
‫‪21‬‬ ‫‪// Display the stored ages‬‬
‫‪22‬‬ ‫;)”‪printf(”\n\nYour friends have the next ages :\n‬‬
‫‪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‬بمجر ّد‬
‫• الحجز الح ّ‬
‫الانتهاء من الاستخدام‪.‬‬

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


‫ي يسمح بشكل أساس ّ‬
‫• الحجز الح ّ‬

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

228
‫الفصل ‪18‬‬

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

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

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

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


‫الهدف هو جعلك تستخدم ك ّ‬
‫الأشياء الجي ّدة فقط !‬

‫التعليمات‬ ‫‪1.18‬‬

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

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

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

‫فلنفترض أن الكلمة المخب ّأة هي ‪.RED‬‬


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

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

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

‫• الحرف موجود بالفعل في الكلمة ‪ :‬سنكشف مكان الحرف في الكلمة‪.‬‬

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

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

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

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

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

‫‪Secret word : ��D‬‬

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

‫‪Secret word : R�D‬‬

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

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

‫ل حروف ‪ E‬في كلمة‬


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

‫��‪Secret word : E�E�E‬‬

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

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

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

‫! ‪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‬‬

‫‪230‬‬
‫‪ .1.18‬التعليمات‬

‫‪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‬‬

‫و تماما‪ ،‬هذا جي ّد‪ %c .‬تعني أننا ننتظر محرفاً‪ ،‬و الذي سنقوم بتخزينه في ‪) myLetter‬متغي ّر من نوع ‪.( 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‬الثانية‪.‬‬

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

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

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

‫لتجنب هذا‪ ،‬من الأحسن أن نكتب بأنفسنا دالتنا الخاصّة الصغيرة )(‪: readCharacter‬‬

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

‫)(‪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.h‬و هذا يعود تماما ًإلى كتابة‬
‫;)‪ . scanf(”%c”, &letter‬الدالة )(‪ getchar‬تقوم بإرجاع المحرف الذي قام اللاعب بإدخاله‪.‬‬

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

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

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

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

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


‫اعلم أ ّ‬
‫)’‪1 while (getchar() != ’\n‬‬
‫{ ‪2‬‬
‫‪3‬‬
‫} ‪4‬‬

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

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

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

‫‪232‬‬
‫‪ .1.18‬التعليمات‬

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

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

‫;)(‪1 myLetter = readCharacter‬‬

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

‫لتجربة أولية للشفرة الخاصة بك‪ ،‬أطلب منك أن تقوم بتثبيت الكلمة السر ي ّة مباشرة في الشفرة‪ .‬أكتب مثلا ‪:‬‬

‫;”‪1 char secretWord[] = ”RED‬‬

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

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

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

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

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

‫م‬
‫ل الكلمات بالحروف الـكبيرة‪ .‬في الواقع‪ ،‬في الـ‪ Pendu‬لا يتم التمييز بين‬
‫ستلاحظ أنني منذ البداية تعمّدت كتابة ك ّ‬
‫الحروف الـكبيرة و الحروف الصغيرة‪ ،‬و لهذا فمن المستحسن أن نقول منذ البداية ‪” :‬كل حروف كلمات اللعبة‬
‫كبيرة”‪ .‬عليك أن تنب ّه اللاعب‪ ،‬في دليل استخدام اللعبة مثلا‪ ،‬أنه يفترض به إدخال حروف كبيرة لا صغيرة‪.‬‬
‫بالمقابل‪ ،‬نتعمّد تجنب العلامات الصوتية )‪ (accents‬لتبسيط اللعبة )إن بدأنا اختبار ‪ ... ë ،ê ،è ،é‬فلن ننتهي‬
‫أبدا ً !(‪ .‬عليك إذن أن تكتب كلماتك كل ّها بحروف كبيرة و بدون علامات صوتي ّة‪.‬‬

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

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

‫الملف إلى عدد الكلمات ال ّتي يحويها ‪:‬‬


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

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

‫مرة تضيف فيها كلمة )أو إضافة ‪ 1‬إلى‬


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

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

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

‫بالتوفيق !‬

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

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

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

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

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

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

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

‫ل على جزئين‪.‬‬
‫سأعرض عليك الح ّ‬

‫‪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‬‬ ‫}‬

‫حسنا‪ ،‬لح ّد الآن يجب على الجميع أن يتابعوا‪.‬‬


‫الدالة ‪ 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[6] = {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‬‬ ‫}‬

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

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

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

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

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

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

‫;)”‪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 < 6 ; i++‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫‪if (foundLetter[i]) // If the letter n° i has been found‬‬

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

‫‪6‬‬ ‫‪printf(”%c”, secretWord[i]); // Display it‬‬


‫‪7‬‬ ‫‪else‬‬
‫‪8‬‬ ‫‪printf(”�”); // Else, display � for the letters that are not found‬‬
‫} ‪9‬‬

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

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

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


‫;)(‪2 letter = readCharacter‬‬

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

‫‪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‬في الخانات‬
‫الموافقة للحروف التي تم ّ إ يجادها‪.‬‬

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

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

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

‫‪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‬‬

‫)][‪1 int win(int foundLetter‬‬


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

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

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

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

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

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

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

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

‫)][‪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‬‬

‫مرة‪ ،‬نختبر ما إن كان الحرف الذي اقترحه اللاعب‬


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

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

‫ن هذا الحرف قد تم ّ إ يجاده‪.‬‬


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

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

‫بتحديث الجدول ‪ foundLetter‬بشكل صحيح‪ ،‬في الحالة التي تحتوي فيها الكلمة حرفا ًمكررا ً عدّة ّ‬
‫مرات‪ ،‬مثل حالة ‪ L‬في‬
‫‪.YELLOW‬‬

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

‫ل ماهو ضروري لإدارة جولة في اللعبة‪ ،‬لـكنه لا‬


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

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

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

‫‪1‬‬ ‫‪HOUSE‬‬
‫‪2‬‬ ‫‪BLUE‬‬
‫‪3‬‬ ‫‪AIRPLANE‬‬
‫‪4‬‬ ‫‪XYLOPHONE‬‬
‫‪5‬‬ ‫‪BEE‬‬

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

‫‪6‬‬ ‫‪BUILDING‬‬
‫‪7‬‬ ‫‪WEIGHT‬‬
‫‪8‬‬ ‫‪SNOW‬‬
‫‪9‬‬ ‫‪ZERO‬‬

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

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

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

‫في ‪ 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‬‬
‫ل شيء على مايرام‪ = 0 ،‬كان هناك خطأ ما‪.‬‬
‫الدالة ستعيد ‪ int‬و سيكون قيمة منطقية ‪ = 1 :‬تم ّ ك ّ‬

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

‫)‪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‬لم يخطر ببالي وضعها كل ّها من البداية‪ ،‬يوجد‬‫ّ‬
‫بالتأكيد من قمت بإضافتها لاحقا حينما عرفت أنني بحاجة إليها‪.‬‬

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

‫أسماء الكلمات تعب ّر عن نفسها‪ .‬لدينا المؤش ّر على القاموس ‪ dico‬و الذي سيمكننا من قراءة ‪ ، dico.txt‬متغيرات‬
‫‪fgetc‬‬ ‫ن الدالة‬
‫مؤق ّتة ستخز ّن المحارف‪ ،‬إلخ‪ .‬لاحظ أنني هنا استعملت ‪ int‬لتخزين محرف ) ‪ ( characterRead‬لأ ّ‬
‫ال ّتي سأستخدمها تعيد ‪ . 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‬‬ ‫}‬

‫ليس لديّ الـكثير لأضيفه هنا‪ .‬أفتح الملف ‪ 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‬‬

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

‫‪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‬إن اخترنا مثلا الكلمة رقم‬
‫ل ادخال سيتم ّ إنقاص المتغير ‪ chosenWordNumber‬بواحد‪.‬‬
‫‪ ،5‬في ك ّ‬
‫سيأخذ إذن القيم ‪ 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‬الذي قمنا بتعر يفه‬
‫ن ‪ fgets‬تقرأ سطرا كاملا‪ ،‬بما في ذلك ‪ . \n‬بما أننا لا نريد إبقاء الـ ‪ \n‬في الكلمة النهائية‪،‬‬
‫في الـ ‪ .( main‬تذك ّر أ ّ‬
‫نحذفها باستبدالها بـ ‪ . \0‬لهذا تأثير القيام بقطع الكلمة قبل ‪. \n‬‬

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

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

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

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

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

aleatoryNumber ‫الدالة‬

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

1 int aleatoryNumber(int maxNumber)


2 {
3 srand(time(NULL));
4 return (rand() % maxNumber);
5 }

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

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 it’s 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

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

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)
30 {
31 characterRead = fgetc(dico);
32 if (characterRead == ’\n’)
33 chosenWordNumber−−;
34 }
35 fgets(chosenWord, 100, dico);
36 // Erase the \n at the end of the word
37 chosenWord[strlen(chosenWord) − 1] = ’\0’;
38 fclose(dico);
39 return 1; // Everything is okay, return 1
40 }
41 int aleatoryNumber(int maxNumber)
42 {
43 srand(time(NULL));
44 return (rand() % maxNumber);
45 }

! main‫يجب إذن تعديل الـ‬

.‫ كي نقوم بتحديثها على حسب التغييرات التي قمنا بإجرائها‬main ‫ سنعود للدالة‬،‫ جاهز‬dico.c ‫و الآن بما أن الملف‬

‫ سنقوم أيضا ًبتضمين‬،‫ بالإضافة إلى ذلك‬. dico.c ‫ إذا أردنا استدعاء دوال الملف‬dico.h ‫سنبدأ أوّلا بتضمين‬
: strlen ‫ لأننا سنستعمل الدالة‬string.h

1 #include <string.h>
2 #include ”dico.h”

‫ سننشئ فقط جدول‬، secretWord ‫ فنحن مثلا ً لن نهي ّئ قيمة المتغير‬،‫ سيتم تغيير كيفية تعر يف المتغيرات‬،‫للبدأ‬
.(‫ خانة‬100) char ‫محارف من‬

‫ و بما أننا لازلنا لا‬،‫ فحجمه سيعتمد على طول الكلمة التي سنختارها من القاموس‬foundLetter ‫بالنسبة للجدول‬
‫ و جعل هذا المؤش ّر يـ ُؤش ّر على الخانة التي‬malloc ‫ لاحقا ًسنستعمل الدالة‬.‫ سنكتفي بتعر يف مؤش ّر‬،‫نعرف هذا الطول‬

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

‫سيتم حجزها‪.‬‬
‫ي ‪ :‬نحن لا نعرف حجم الجدول قبل ترجمة الشفرة‪ ،‬أي أننا‬
‫و هذا مثال يعب ّر تماما عن حاجتنا الماسة لاستعمال الحجز الح ّ‬
‫مجـبرون على تعر يف مؤش ّر و استدعاء ‪. malloc‬‬

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

‫نحتاج أيضا ً إلى متغير ‪ wordSize‬و الذي سيحتوي ‪ ...‬حجم الكلمة السر ية‪ .‬في الواقع‪ ،‬لو نلاحظ الـ ‪ main‬كما‬
‫كان في الشفرة السابقة‪ ،‬فسنرى أنه كل ّما احتجنا حجم الكلمة استعملنا ‪) 6‬لأن الكلمة كانت ‪ YELLOW‬ذات ‪ 6‬حروف(‪.‬‬
‫لـكن حالي ّا‪ ،‬بما أن الكلمة ستتغير‪ ،‬فيجب على البرنامج أن يتلائم مع كل الكلمات‪.‬‬

‫إليك إذن التعر يفات النهائية للمتغي ّرات في الدالة ‪: main‬‬


‫)][‪1 int main(int argc, char� argv‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫‪char letter = 0; // Stores the letter suggested by the user‬‬
‫‪4‬‬ ‫‪char secretWord[100] = {0}; // The word that the user must find‬‬
‫‪5‬‬ ‫‪int �foundLetter = NULL; // Boolean table. Each box corresponds to a‬‬
‫‪letter in the secret word. 0 = letter not found, 1 = letter found‬‬
‫‪6‬‬ ‫)‪int remainingTries = 10; // Counting the remaining tries (0 = dead‬‬
‫‪7‬‬ ‫‪int i = 0; // A little variable to browse the table‬‬
‫‪8‬‬ ‫;‪int wordSize= 0‬‬

‫ستتغير بداية الدالة ‪ ، main‬فلنلاحظ هذا ‪:‬‬


‫))‪1 if (!findWord(chosenWord‬‬
‫‪2‬‬ ‫;)‪exit(0‬‬

‫نحن نستدعي أولا الدالة ‪ ، findWord‬و ذلك يتم مباشرة داخل الشرط ‪ . if‬الدالة ‪ findWord‬ستقوم بوضع‬
‫الكلمة التي اختارتها من القاموس في المتغير ‪ . secretWord‬كما أنها ستقوم بإرجاع متغير منطقي لنا لتخبرنا ما إن كانت‬
‫العملية ناجحة أم لا‪ ،‬أي أننا نقرأ الشرط كالتالي ‪ :‬إذا لم يعمل الأمر فسنوقف البرنامج ) )‪.( exit(0‬‬
‫;)‪1 wordSize = strlen(secretWord‬‬

‫نقوم بتخزين طول ‪ secretWord‬في المتغير ‪ wordSize‬كما شرحتُ سابقاً‪.‬‬


‫‪1 foundLetter = malloc(wordSize � sizeof(int)); // We allocate dynamically the‬‬
‫)‪table foundLetter (that we don’t know its size in the beginning‬‬
‫)‪2 if (foundLetter == NULL‬‬
‫‪3‬‬ ‫;)‪exit(0‬‬

‫و الآن سنحجز مكانا ًفي الذاكرة للجدول ‪ . foundLetter‬سنقدّم له حجم الكلمة ‪ . wordSize‬سنختبر بعد ذلك‬
‫ما إن كان المؤش ّر يساوي ‪ . NULL‬إذا كان كذلك‪ ،‬فالحجز قد فشل‪ .‬في هذه الحالة سنوقف البرنامج حالا )باستعمال‬
‫‪.( exit‬‬

‫ل شيء قد عمل تماما‪.‬‬


‫إذا تم ّت قراءة الأسطر السابقة‪ ،‬فك ّ‬

‫هذه هي أهم التعديلات على الـ ‪ ، main‬يبقّى أن تقوم باستبدال كل تكرار للرقم ‪ 6‬بالمتغير ‪ . wordSize‬مثال ‪:‬‬

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

1 for (i = 0 ; i < wordSize ; i++)


2 foundLetter[i] = 0;

. foundLetter ‫ في كل خانة من الجدول‬0 ‫هذه الشفرة تقوم بوضع القيمة‬

‫ فبدون هذا لا يمكن للدالة معرفة متى توقف‬. wordSize ‫ لأضيف المتغير‬win ‫كان يفترض أن أضع نموذج الدالة‬
.‫الحلقة التكرار ية‬

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

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <ctype.h>
4 #include <string.h>
5 #include ”dico.h”
6 int win(int foundLetter[], int wordSize);
7 int findLetter(char letter, char secretWord[], int foundLetter[])
8 char readCharacter();
9 int main(int argc, char� argv[])
10 {
11 char letter = 0; // Stores the letter suggested by the user
12 char secretWord[100] = {0}; // The word that the user must find
13 int �foundLetter = NULL; // Boolean table. Each box corresponds to a
letter in the secret word. 0 = letter not found, 1 = letter found
14 int remainingTries = 10; // Counting the remaining tries (0 = dead)
15 int i = 0; // A little variable to browse the table
16 int wordSize = 0;
17 printf(”Welcome !\n\n”);
18 if (!findWord(chosenWord))
19 exit(0);
20 wordSize = strlen(secretWord);
21 foundLetter = malloc(wordSize � sizeof(int)); // We allocate
dynamically the table foundLetter ( that we don’t know its size in
the beginning )
22
23 if (foundLetter == NULL)
24 exit(0);
25 for (i = 0 ; i < wordSize; i++)
26 foundLetter [i] = 0;
27 while (remainingTries > 0 && !win(foundLetter , wordSize))
28 {
29 printf(”\n\nYou have %ld remaining tries”, remainingTries);
30 printf(”\’nWhats the secret word? ”);
31 for (i = 0 ; i < wordSize; i++)
32 {
33 if (foundLetter [i]) // If we have found the
letter n° i
34 printf(”%c”, secretWord[i]); // We display it
35 else

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

36 printf(”�”); // Else, we display a � for the


letters that are not found
37 }
38 printf(”\nSuggest a letter : ”);
39 letter = readCharacter();
40 // If it’s not the right letter
41 if (!findLetter(letter , secretWord, foundLetter ))
42 {
43 remainingTries−−; // We decrement by 1 the
remaining tries
44 }
45 }
46 if (win(foundLetter , wordSize))
47 printf(”\n\nYou win ! The secret word is : %s”,secretWord);
48 else
49 printf(”\n\nTou lose ! The secret word is : %s”, secretWord);
50 free(foundLetter ); // We free the allocated memory
51 return 0;
52 }
53 char readCharacter()
54 {
55 char character = 0;
56 character = getchar(); // We read the first character
57 character = toupper(character); // We uppercase the character
58 // We read other characters until we reach \n (to erase them)
59 while (getchar() != ’\n’) ;
60 return character; // We return the first character that we read
61 }
62 int win(int foundLetter[], int wordSize)
63 {
64 int i = 0;
65 int playerWins = 1;
66 for (i = 0 ; i < wordSize ; i++)
67 {
68 if (foundLetter[i] == 0)
69 playerWins = 0;
70 }
71 return playerWins;
72 }
73 int findLetter(char letter, char secretWord[], int foundLetter[])
74 {
75 int i = 0;
76 int rightLetter = 0;
77 // We search for the letter in the table foundLetter
78 for (i = 0 ; secretWord[i] != ’\0’ ; i++)
79 {
80 if (letter == secretWord[i]) // If it exists
81 {
82 rightLetter = 1; // We memorize that it was the
right one

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

‫‪83‬‬ ‫‪foundLetter[i] = 1; // We put the‬‬


‫‪correspondent value to 1 in the table‬‬
‫‪84‬‬ ‫}‬
‫‪85‬‬ ‫}‬
‫‪86‬‬ ‫; ‪return rightLetter‬‬
‫} ‪87‬‬

‫أفكار للتحسين‬

‫تنز يل المشروع‬

‫للبدأ‪ ،‬أدعوكم لتنز يل المشروع عبر الرابط التالي‪:‬‬

‫‪(10 Ko) https://openclassrooms.com/uploads/fr/ftp/mateo21/pendu_siteduzero.zip‬‬

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

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

‫ستجد في هذا الملف ‪ .zip‬ملفات من ‪ .h‬و ‪ .c‬كما تجد الملف ‪ .cbp‬الخاص بالمشروع‪ .‬إنه مشروع تم إنشاؤه‬
‫بالبيئة التطوير ية ‪.Code::Blocks‬‬
‫إن كنت تستعمل بيئة تطوير ية أخرى‪ ،‬فلا داعي للقلق‪ ،‬قم بإنشاء مشروع بنفسك‪ ،‬و ضع فيه يدو يا الملفات ‪ .h‬و ‪.c‬‬

‫المتواجدة في الملف ‪ . .zip‬ستجد أيضا ًالملف التنفيذي ) ‪ ( .exe‬و القاموس ) ‪.( dico.txt‬‬

‫الـ‪Pendu‬‬ ‫تحسين‬

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

‫و لـكن مع ذلك سأعطيك بعض الأفكار التي يمكنك إدراجها في اللعبة بهدف تحسينها ‪:‬‬

‫• لحد الآن اللعبة تقترح علينا جولة واحدة‪ ،‬فسيكون من الأحسن أن نستعمل حلقة تكرار ية تسمح بلعب جولة ثانية‬
‫إذا كان اللاعب يريد ذلك !‬

‫• يمكنك أيضا ًأن تجعل اللعبة تسمح بلعب لاعبين‪ ،‬الأول يدخل الكلمة السر ية و الثاني يحاول تخمينها !‬

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

‫‪248‬‬
‫أفكار للتحسين‬

‫• يمكنك أن تطلب من اللاعب أن يختار صعوبة اللعبة‪ ،‬و حسب الصعوبة تغي ّر في عدد المحاولات المسموحة له‪.‬‬

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

‫هي ّا‪ ،‬بالتوفيق !‬

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

‫‪250‬‬
‫الفصل ‪19‬‬

‫نص بشكل أكثر أمانا‬


‫إدخال ّ‬

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

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

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

‫‪scanf‬‬ ‫حدود الدالة‬ ‫‪1.19‬‬

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

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

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

‫إدخال سلسلة محارف تحتوي على فراغات‬

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

‫‪251‬‬
‫نص بشكل أكثر أمانا‬
‫الفصل ‪ .19‬إدخال ّ‬

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


‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;}‪char name[20] = {0‬‬
‫‪6‬‬ ‫;)” ? ‪printf(”What’s your name‬‬
‫‪7‬‬ ‫;)‪scanf(”%s”, name‬‬
‫‪8‬‬ ‫;)‪printf(”Ah ! Your name is %s !\n\n”, name‬‬
‫‪9‬‬ ‫;‪return 0‬‬
‫‪10‬‬ ‫}‬

‫‪What’s your name ? Mathieu Nebra‬‬


‫! ‪Ah ! Your name is Mathieu‬‬

‫؟‬
‫لماذا اختفت الكلمة ”‪ ”Nebra‬؟‬

‫ذلك لأن الدالة ‪ scanf‬ٺتوقف عن القراءة حينما تصل إلى فراغ‪ ،‬أو رجوع إلى السطر أو محرف جدولة‬
‫)‪ .(tabulation‬يعني أنك غير قادر على قراءة سلسلة محرفي ّة تحتوي على فراغات‪.‬‬

‫م‬
‫في الواقع‪ ،‬الكلمة ”‪ ”Nebra‬لازالت مخزّنة في الذاكرة‪ ،‬في شيء نسميه بالمتغير المؤق ّت )‪ ،(buffer‬المرة القادمة عندما‬
‫نستدعي الدالة ‪ scanf‬فهي ستقوم بقراءة الكلمة ”‪ ”Nebra‬وحدها الموجودة في المتغير المؤقت‪.‬‬

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

‫‪http://xrenault.developpez.com/tutoriels/c/scanf/‬‬

‫إدخال سلسلة محارف طو يلة للغاية‬

‫يوجد مشكل آخر‪ ،‬أكثر خطورة‪ ،‬و هو تجاوز الذاكرة‪.‬‬

‫في الشفرة التي رأيناها‪ ،‬يوجد السطر التالي ‪:‬‬

‫;}‪1 char name[5] = {0‬‬

‫ترى أنني قمت بحجز ‪ 5‬خانات من أجل الجدول المسمّى ‪ name‬الذي هو من نوع ‪ . char‬يعني أننا قادرون على‬
‫تخزين كلمة من ‪ 4‬محارف‪ ،‬بينما الحرف الأخير فهو محجوز لعلامة نهاية السلسلة ‪. \0‬‬
‫ل هذا فراجع فصل السلاسل المحرفية‪.‬‬
‫إذا نسيت ك ّ‬

‫عرفناها ‪:‬‬
‫المخطط التالي يمثل المكان الذي هو محجوز للكلمة التي ّ‬

‫‪252‬‬
‫‪scanf‬‬ ‫‪ .1.19‬حدود الدالة‬

‫ماذا لو كتبنا عددا كبيرا من المحارف بالنسبة للمساحة المتوق ّعة لتخزين المتغير ؟‬

‫‪What’s your name ? Patrice‬‬


‫! ‪Ah ! Your name is Patrice‬‬

‫ستقول أن كل شيء على ما يرام لـكن الواقع أنك بصدد مواجهة أكبر كابوس لدى المبرمجـين !‬

‫لقد قمنا بـتجاوز في الذاكرة‪ ،‬هذا ما نسميه بـ‪ buffer overflow‬بالإنجليز ية‪.‬‬

‫كما ترى في المخطط التالي‪ ،‬لقد حجزت ‪ 5‬خانات لـكي تقوم باستعمال ‪ ،8‬ما الذي قامت به الدالة ‪ scanf‬؟ لقد‬
‫قامت بمواصلة الكتابة في الذاكرة وكأن شيئا ًلم يحدث ! فلقد استغل ّت خانات ليس لها الحق في الكتابة فيها‪.‬‬

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

‫؟‬
‫لما الأمر خطير ؟‬

‫دون الدخول في التفاصيل‪ ،‬لأنه بإمكاننا البدء في محادثة قدر ‪ 50‬صفحة و لا نتوقف أبداً‪ ،‬فلنقل بأنه إن لم يقم البرنامج‬
‫بمعالجة حالات كهذه‪ ،‬فالمستعمل سيقوم بكتابة ما يحلو له و تخريب المعلومات المتواجدة في الخانات التالية من الذاكرة‪.‬‬
‫أي أنه قادر على كتابة شفرة في تلك الخانات و برنامجك سيقوم بتشغيل تلك الشفرات و كأنها تابعة له‪ ،‬و هذا ما نسميه‬
‫بالهجوم عبر المتغير المؤقت )‪ ،(Buffer overflow attack‬نوع من الهجومات المعروفة عند القراصنة‪ ،‬و لـكنه صعب التحقيق‪.‬‬
‫إذا كنت مهتما ًبهذا الموضوع‪ ،‬يمكنك قراءة المقال التالي من و يكيبيديا )حذار‪ ،‬إن ّه مع ذلك معقد جدا( ‪:‬‬

‫‪http://fr.wikipedia.org/wiki/D%C3%A9passement_de_tampon‬‬

‫‪253‬‬
‫نص بشكل أكثر أمانا‬
‫الفصل ‪ .19‬إدخال ّ‬

‫الهدف من هذا الفصل هو تأمين قراءة البيانات و ذلك بمنع المستعمل من تجاوز الذاكرة و إحداث ‪.buffer overflow‬‬
‫ل المشكل فالشخص الذي يريد الوصول‬ ‫بالطبع كان بإمكاننا تعر يف جدول كبير للغاية )‪ 10,000‬خانة( لـكن هذا لا يح ّ‬
‫إلى الذاكرة ما عليه سوى إدخال سلسلة يتجاوز طولها ‪ 10,000‬محرف و سيعمل هجومه كما يريد‪.‬‬

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

‫استرجاع سلسلة محارف‬ ‫‪2.19‬‬

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

‫• ‪ : gets‬دالة تقرأ سلسلة محرفي ّة كاملة لـكنها خطيرة جدّا لأنها لا تعالج مشكل الـ‪.buffer overflow‬‬

‫• ‪ : fgets‬تشبه الدالة ‪ gets‬لـكنها تحمي البرنامج و ذلك بالتحكم في عدد المحارف المكتوبة في الذاكرة‪.‬‬

‫أعتقد أن الأمر مفهوم ‪ :‬على الرغم من أنها دالّة قياسي ّة في الـ‪ gets ،C‬هي دالّة خطيرة جدّا‪ .‬كل البرامج ال ّتي‬
‫تستخدمها عرضة لأن يكونوا ضحايا الـ‪.buffer overflow‬‬

‫سنرى كيف تعمل الدالة ‪ ، fgets‬و كيف نستعملها في برامجنا الخاصّة في مكان الدالة ‪. scanf‬‬

‫‪fgets‬‬ ‫الدالّة‬

‫نموذج هذه الدالة‪ ،‬المتواجد في المكتبة ‪ stdio.h‬هو ‪:‬‬

‫;)‪1 char �fgets(char �str, int num, FILE �stream‬‬

‫من المهم أن نفهم هذا النموذج‪ .‬معاملات الدالة هي التالية ‪:‬‬

‫• ‪ : str‬مؤش ّر نحو جدول في الذاكرة‪ ،‬أين ستتمكن الدالة من كتابة النص المدخل من طرف المستخدم‪.‬‬

‫• ‪ : num‬حجم الجدول ‪ str‬المرسل كمعامل أوّل‪.‬‬

‫لاحظ أنه لو قمت بحجز جدول من ‪ ، char‬فإن الدالة ‪ fgets‬ستقرأ ‪ 9‬محارف على الأكثر )آخر خانة محجوزة‬
‫للمحرف ‪ \0‬المشير إلى نهاية السلسلة(‪.‬‬

‫)‪Standard‬‬ ‫• ‪ : stream‬مؤش ّر نحو الملف الذي سنقرأ منه‪ .‬في حالتنا ”الملف المراد قراءته” هو الإدخال القياسي‬
‫‪ ،(input‬أي لوحة المفاتيح‪ .‬لطلب قراءة الإدخال القياسي نرسل المؤش ّر ‪ stdin‬المعر ّف تلقائي ّا في الملفات الرأسي ّة‬
‫للمكتبة القياسي ّة للـ‪ C‬ليشير إلى لوحة المفاتيح‪ .‬مع ذلك‪ ،‬يمكن استخدام ‪ fgets‬لقراءة الملفّات‪ ،‬كما رأينا في‬
‫الفصل الخاص بالملفّات‪.‬‬

‫‪254‬‬
‫‪ .2.19‬استرجاع سلسلة محارف‬

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

‫فلنجر ّب !‬

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


‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;]‪char name[10‬‬
‫‪6‬‬ ‫;)” ? ‪printf(”What’s your name‬‬
‫‪7‬‬ ‫;)‪fgets(name, 10, stdin‬‬
‫‪8‬‬ ‫;)‪printf(”Ah ! Your name is %s !\n\n”, name‬‬
‫‪9‬‬ ‫;‪return 0‬‬
‫‪10‬‬ ‫}‬

‫‪What’s your name ? NEBRA‬‬


‫‪Ah ! Your name is NEBRA‬‬
‫!‬

‫‪\n‬‬ ‫الدالة تعمل بشكل جيد‪ ،‬مع تفصيل بسيط ‪ :‬عندما تضغط على زر الإدخال‪ ،‬تقوم ‪ fgets‬بالاحتفاظ بـ‬
‫الموافق‪ ،‬هذا ما يفس ّر الرجوع إلى السطر بعد الكلمة ”‪ ”NEBRA‬كما يظهر في الـكونسول‪.‬‬

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

‫‪fgets‬‬ ‫كتابة دالتك الخاصّة بالإدخال باستخدام‬

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

‫سنسمّي هذه الدالّة ‪ . read‬ستقوم بإرجاع القيمة ‪ 0‬إن كان هناك خطأ و ‪ 1‬إن لم يكن‪.‬‬

‫‪\n‬‬ ‫حذف الرجوع إلى السطر‬

‫ل شيء على ما يرام‪ ،‬ستبحث عن المحرف ‪ \n‬بمساعدة الدالة ‪ strchr‬ال ّتي‬


‫الدالة ‪ read‬تستدعي ‪ ، fgets‬إذا تم ّ ك ّ‬
‫يفترض بك معرفتها‪ .‬إذا تم ّ العثور على ‪ ، \n‬فستستبدله بـ ‪) \0‬نهاية السلسلة( لتجن ّب الاحتفاظ بـ”علامة الإدخال”‪.‬‬

‫هاهي الشفرة عل ّقت عليها خطوة بخطوة ‪:‬‬

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


‫‪2‬‬ ‫>‪#include <stdlib.h‬‬
‫‪3‬‬ ‫)(‪#include <string.h> // Think to include string.h for strchr‬‬
‫‪4‬‬ ‫)‪int read(char �string, int length‬‬
‫‪5‬‬ ‫{‬

‫‪255‬‬
‫نص بشكل أكثر أمانا‬
‫الفصل ‪ .19‬إدخال ّ‬

‫‪6‬‬ ‫;‪char �enterPosition = NULL‬‬


‫‪7‬‬ ‫‪// We read the text‬‬
‫‪8‬‬ ‫? ‪if (fgets(string, length, stdin) != NULL) // No error‬‬
‫‪9‬‬ ‫{‬
‫‪10‬‬ ‫” ‪enterPosition = strchr(string, ’\n’); // We search for the‬‬
‫”‪Enter‬‬
‫‪11‬‬ ‫‪if (enterPosition != NULL) // If we find the \n‬‬
‫‪12‬‬ ‫{‬
‫‪13‬‬ ‫‪�enterPosition = ’\0’; // We replace the‬‬
‫‪character by \0‬‬
‫‪14‬‬ ‫}‬
‫‪15‬‬ ‫‪return 1; // We return 1 if there’s no error‬‬
‫‪16‬‬ ‫}‬
‫‪17‬‬ ‫‪else‬‬
‫‪18‬‬ ‫{‬
‫‪19‬‬ ‫‪return 0; // We return 0 if there’s error‬‬
‫‪20‬‬ ‫}‬
‫} ‪21‬‬

‫ستلاحظ أنه بالإمكان استدعاء الدالة ‪ fgets‬مباشرة داخل ‪ . if‬هذا اختصار كتابي‪ ،‬كي لا أستعمل مؤش ّرا‬
‫يستقبل القيمة المُرجعة من الدالة ثم أختبر قيمته إن كانت ‪ NULL‬أم لا‪.‬‬

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

‫إذا تم كل شيء بشكل جيد‪ ،‬سأذهب للبحث عن الـ ‪ \n‬باستعمال الدالة ‪ strchr‬ثم استبداله بالمحرف ‪ \0‬كما‬
‫في الشكل التالي‪.‬‬

‫هذا المخطط يبېّن أن السلسلة ال ّتي تمت قراءتها من طرف الدالة ‪ fgets‬هي ” ‪ ،” NEBRA\n\0‬ثم قمنا باستبدال‬
‫الـ ‪ \n‬بـ ‪ \0‬و هذا ما أعطانا ” ‪.” NEBRA\0\0‬‬
‫ليس مشكلا أن توجد علامتا ‪ \0‬متتابعتين‪ .‬الحاسوب سيتوقف عند الإشارة الأولى و يعتبرها نهاية السلسلة‪.‬‬

‫و النتيجة ؟ حسنا‪ ،‬لقد عملت بشكل جي ّد‪.‬‬


‫‪1‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪2‬‬ ‫{‬
‫‪3‬‬ ‫;]‪char name[10‬‬
‫‪4‬‬ ‫;)” ? ‪printf(”What’s your name‬‬
‫‪5‬‬ ‫;)‪read(name, 10‬‬
‫‪6‬‬ ‫;)‪printf(”Ah ! Your name is %s !\n\n”, name‬‬
‫‪7‬‬ ‫;‪return 0‬‬
‫‪8‬‬ ‫}‬

‫‪256‬‬
‫‪ .2.19‬استرجاع سلسلة محارف‬

‫‪What’s your name ? NEBRA‬‬


‫! ‪Ah ! Your name is NEBRA‬‬

‫تفر يغ المتغي ّر المؤق ّت‬

‫لم نصل بعد إلى نهاية الأمور المزعجة‪.‬‬


‫نحن لم نقم بتحليل الحالة التي يقوم فيها المستعمل بإدخال محارف أكثر من ما هو مسموح به !‬

‫‪What’s your name ? Jean Edouard Albert 1er‬‬


‫! ‪Ah ! Your name is Jean Edou‬‬

‫بما أن الدالة ‪ fgets‬تدعم الحماية‪ ،‬فهي توقفت عند الحرف التاسع الذي قام المستعمل بإدخاله لأنّنا حجزنا جدولا‬
‫ن العاشر محجوز لإشارة نهاية السلسلة(‪ .‬المشكل هو أن بقي ّة السلسلة ال ّتي لم تتم قراءتها‪.‬‬
‫من ‪ 10‬محارف )يجب عدم نسيان أ ّ‬
‫”‪ ”ard Albert 1er‬لم تختف ! و إنّما لازالت موجودة في المتغير المؤق ّت‪ .‬هذا المتغير المؤقت هو مكان في الذاكرة يعمل‬
‫‪stdin‬‬ ‫كوسيط بين لوحة المفاتيح و الجدول الّذي سيتم تخزين السلسلة فيه‪ .‬في الـ‪ ،C‬لدينا مؤش ّر نحو المتغير المؤقت‪ ،‬و هو‬
‫الذي تكلمنا عنه قبل قليل‪.‬‬

‫أعتقد أن مخططا صغيرا سيساعد على توضيح الأمور‪.‬‬

‫حينما يقوم المستعمل بإدخال نص بلوحة المفاتيح‪ ،‬فإن نظام التشغيل )‪ Windows‬مثلا( يقوم بنسخ النص مباشرة‬
‫في المتغير المؤق ّت ‪. stdin‬‬

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

‫‪257‬‬
‫نص بشكل أكثر أمانا‬
‫الفصل ‪ .19‬إدخال ّ‬

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

‫فلنجر ّب مع سلسلة كبيرة ‪:‬‬

‫‪1‬‬ ‫)][‪int main(int argc, char �argv‬‬


‫‪2‬‬ ‫{‬
‫‪3‬‬ ‫;]‪char name[10‬‬
‫‪4‬‬ ‫;)” ? ‪printf(”What’s your name‬‬
‫‪5‬‬ ‫;)‪read(name, 10‬‬
‫‪6‬‬ ‫;)‪printf(”Ah ! Your name is %s !\n\n”, name‬‬
‫‪7‬‬ ‫;‪return 0‬‬
‫‪8‬‬ ‫}‬

‫‪What’s your name ? Jean Edouard Albert 1er‬‬


‫! ‪Ah ! Your name is Jean Edou‬‬

‫الدالة ‪ fgets‬قامت بنسخ المحارف التسعة الأولى كما كان متوق ّعا‪ .‬المشكل هو أن المحارف المتبقية لازالت في المتغير‬
‫المؤقت !‬

‫هذا يعني أنه لو استدعينا الدالة ‪ fgets‬مرة أخرى فإنها ستقوم بقراءة ما كان متبقّيا في المتغير المؤقت !‬

‫فلنجر ّب هذه الشفرة ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;]‪char name[10‬‬
‫‪4‬‬ ‫;)” ? ‪printf(”What’s your name‬‬

‫‪258‬‬
‫‪ .2.19‬استرجاع سلسلة محارف‬

‫‪5‬‬ ‫‪read(name,‬‬ ‫;)‪10‬‬


‫‪6‬‬ ‫‪printf(”Ah‬‬ ‫;)‪! Your name is %s !\n\n”, name‬‬
‫‪7‬‬ ‫‪read(name,‬‬ ‫;)‪10‬‬
‫‪8‬‬ ‫‪printf(”Ah‬‬ ‫;)‪! Your name is %s !\n\n”, name‬‬
‫‪9‬‬ ‫;‪return 0‬‬
‫} ‪10‬‬

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

‫‪What’s your name ? Jean Edouard Albert 1er‬‬


‫! ‪Ah ! Your name is Jean Edou‬‬
‫! ‪Ah ! Your name is ard Alber‬‬

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

‫سنقوم إذا بتحسين عمل الدالة ‪ ، read‬و سنقوم في الحالات التي ٺتطلب ذلك باستدعاء دالة نسميها ‪، clearBuffer‬‬
‫لـكي نتأكد من تفر يغ المتغير المؤقت في حال ما احتوى على محارف زائدة ‪:‬‬

‫‪1‬‬ ‫)(‪void clearBuffer‬‬


‫‪2‬‬ ‫{‬
‫‪3‬‬ ‫;‪int c = 0‬‬
‫‪4‬‬ ‫)‪while (c != ’\n’ && c != EOF‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;)(‪c = getchar‬‬
‫‪7‬‬ ‫}‬
‫‪8‬‬ ‫}‬
‫‪9‬‬ ‫)‪int read(char �string, int length‬‬
‫‪10‬‬ ‫{‬
‫‪11‬‬ ‫;‪char �enterPosition = NULL‬‬
‫‪12‬‬ ‫)‪if (fgets(string, length, stdin) != NULL‬‬
‫‪13‬‬ ‫{‬
‫‪14‬‬ ‫;)’‪enterPosition = strchr(string, ’\n‬‬
‫‪15‬‬ ‫)‪if (enterPosition != NULL‬‬
‫‪16‬‬ ‫{‬
‫‪17‬‬ ‫;’‪�enterPosition = ’\0‬‬
‫‪18‬‬ ‫}‬
‫‪19‬‬ ‫‪else‬‬
‫‪20‬‬ ‫{‬
‫‪21‬‬ ‫;)(‪clearBuffer‬‬
‫‪22‬‬ ‫}‬
‫‪23‬‬ ‫;‪return 1‬‬
‫‪24‬‬ ‫}‬
‫‪25‬‬ ‫‪else‬‬
‫‪26‬‬ ‫{‬
‫‪27‬‬ ‫;)(‪clearBuffer‬‬
‫‪28‬‬ ‫;‪return 0‬‬

‫‪259‬‬
‫نص بشكل أكثر أمانا‬
‫الفصل ‪ .19‬إدخال ّ‬

‫‪29‬‬ ‫}‬
‫} ‪30‬‬

‫الدالة ‪ read‬تستدعي الدالة ‪ clearBuffer‬في حالتين ‪:‬‬

‫• السلسلة المدخلة طو يلة جدا ً )يمكننا أن نعرف ذلك بعدم وجود الإشارة ‪ \0‬في السلسلة المنسوخة(‪.‬‬

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

‫الدالة ‪ clearBuffer‬قصيرة لـكنها عميقة‪ .‬فهي تقرأ المتغير المؤقت محرفا ً محرفا ًباستعمال الدالة ‪ . getchar‬هذه‬
‫الدالة تقوم بإرجاع ‪) int‬و ليس ‪ ، char‬سوف تعرف السبب لاحقا‪ ،‬أي ّا يكن(‪.‬‬
‫سنكتفي نحن باسترجاع القيمة في متغير ‪ c‬من نوع ‪ . int‬نقوم بحلقة تكرار ية مادمنا لم نقرأ بعد المحرف ‪ \0‬أو الرمز‬
‫‪) EOF‬نهاية الملف( و هما يعنيان ‪ :‬لقد وصلت إلى نهاية المتغير المؤقت‪ .‬سنتوق ّف عند الوصول إلى أحد هذين المحرفين‪.‬‬

‫يبدو عمل الدالة ‪ clearBuffer‬صعبا ً قليلا ً لـكنها تقوم بعملها‪ .‬لا تتردد في تكرار قراءة الشرح عدة مرات من‬
‫أجل الفهم الجيد‪.‬‬

‫تحو يل سلسلة محرفي ّة إلى عدد‬ ‫‪3.19‬‬

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

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

‫‪long‬‬ ‫‪ : strtol‬تحو يل سلسلة محرفي ّة إلى‬

‫نموذج هذه الدالة خاص نوعا ًما ‪:‬‬

‫;)‪1 long strtol(const char �start, char ��end, int base‬‬

‫الدالة ستقوم بقراءة السلسلة المحرفي ّة المرسلة إليها ) ‪ ( start‬و ستحاول تحو يلها إلى ‪ long‬باستعمال الأساس‬
‫) ‪ ( base‬المحدد )غالبا ً ما نستعمل الأساس ‪ ،10‬لأننا نستعمل الأرقام من ‪ 0‬إلى ‪ ،9‬و لهذا ضع مكانه العدد ‪.(10‬‬
‫ستقوم بإرجاع العدد الذي نجحت في قراءته‪.‬‬
‫بالنسبة لمؤشر المؤشر ‪ ، end‬فالدالة ستقوم باستغلاله لإرجاع أول محرف صادفته و لم يكن رقماً‪ .‬لـكننا لسنا بحاجة إليه‪،‬‬
‫فلنكتفي بوضع ‪ NULL‬مكانه لنقول أننا لا نريد استرجاعه‪.‬‬

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

‫‪260‬‬
‫ تحو يل سلسلة محرفي ّة إلى عدد‬.3.19

1 long i;
2 i = strtol(”148”, NULL, 10); // i = 148
3 i = strtol(”148.215”, NULL, 10); // i = 148
4 i = strtol(” 148.215”, NULL, 10); // i = 148
5 i = strtol(” 148+34”, NULL, 10); // i = 148
6 i = strtol(” 148 dead leaves”, NULL, 10); // i = 148
7 i = strtol( ” There are 148 dead leaves”, NULL, 10 ); // i = 0 (error : The
string doesn’t start with a number)

‫ حتى الوصول إلى محرف غير مقبول‬long ‫كل السلاسل التي تبدأ برقم )أو ربما بفراغات قبله( سيتم تحو يلها إلى‬
.(‫ إلخ‬،‫ زائد‬،‫ علامة استفهام‬،‫ فاصلة‬،‫)نقطة‬

.0 ‫ فلا يمكن تحو يلها و بالتالي تقوم الدالة بإرجاع القيمة‬،‫بالنسبة لسلسلة لا تبدأ بأرقام و أو بفراغات تليها أرقام‬

‫ و بعد ذلك تحو يل النص إلى‬،(‫ )لقراءة النص‬read ‫ و التي تقوم باستدعاء الدالة‬، readLong ‫يمكننا كتابة الدالة‬
: ‫عدد‬

1 long readLong()
2 {
3 char textNumber[100] = {0}; // 100 cells are sufficient
4 if (read(textNumber, 100))
5 {
6 // If we read the text without problems, we convert textNumber
to long and we return it
7 return strtol(textNumber, NULL, 10);
8 }
9 else
10 {
11 // If there’s a problem, we return 0
12 return 0;
13 }
14 }

.‫ بسيط‬main ‫يمكنك تجريب الشفرة داخل‬

1 int main(int argc, char �argv[])


2 {
3 long age = 0;
4 printf(”How old are you ? ”);
5 age = readLong();
6 printf(”Ah ! You are %d years old !\n\n”, age);
7 return 0;
8 }

How old are you ? 18


Ah ! You are 18 years old!

261
‫نص بشكل أكثر أمانا‬
‫الفصل ‪ .19‬إدخال ّ‬

‫‪double‬‬ ‫‪ strtod‬تحو يل سلسلة محرفي ّة إلى‬

‫الدالة ‪ strtod‬مطابقة للدالة ‪ ، strtol‬الفرق الوحيد هو أنها ستحاول قراءة عدد عشريّ و إرجاع ‪. double‬‬

‫;)‪1 double strtod(const char �start, char ��end‬‬

‫تجد أن المعامل الثالث ‪ base‬اختفى هنا‪ ،‬بينما يبقى مؤش ّر المؤش ّر ‪ end‬الّذي لا يفيدنا في شيء‪.‬‬

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

‫فلتقم بكتابة الدالة ‪ readDouble‬بنفسك‪ .‬إن كتابتها مماثلة للدالة ‪ ، readLong‬الاختلاف الوحيد هو أنها ستستدعي‬
‫الدالة ‪ strtod‬ثم ستقوم بإرجاع قيمة ‪. double‬‬

‫يعني أنك ستكتب التالي في الـكونسول ‪:‬‬

‫‪What’s your weight ? 67.4‬‬


‫! ‪Ah ! Your weight is 67.400000 kg‬‬

‫حاول بعد ذلك تعديل الدالة ‪ readDouble‬لتقبل الفاصلة أيضا ً كفاصل عشري‪ .‬إن الأمر بسيط ‪ :‬فقط قم‬
‫باستبدال كل تكرار للمحرف ” ‪ ” ,‬بالمحرف ” ‪) ” .‬بالاستعانة بالدالة ‪ ،( strchr‬ثم قم ببعث النص الجديد إلى الدالة‬
‫‪. strtod‬‬

‫ملخّ ص‬

‫• الدالة ‪ scanf‬بالرغم من أنها تبدو سهلة و طبيعية إلا أنها معقّدة و تفرض علينا بعض الحدود‪ .‬فمثلاً‪ ،‬هي لا تقبل‬
‫قراءة نص يحتوي فراغات‪.‬‬

‫• نقول أننا تسببنا في الـ‪ buffer overflow‬إذا تجاوزنا المساحة المخصصة في الذاكرة‪ ،‬فمثلا ًلو قام المستخدم بإدخال ‪10‬‬
‫محارف و نحن قد حجزنا ‪ 5‬خانات فقط‪.‬‬

‫• الحل الأمثل هو استدعاء الدالة ‪ fgets‬لتقوم باسترجاع النص الذي يُدخله المستعمل‪.‬‬

‫• يجب أن تتجنب استعمال الدالة ‪ gets‬بأي ثمن لأنّها لا تحمي من الـ‪.buffer overflow‬‬

‫• يمكنك كتابة دالة خاصة بك‪ ،‬تقوم باستدعاء الدالة ‪ fgets‬كما فعلنا لـكيّ تحسّن عملها‪.‬‬

‫‪262‬‬
‫الجزء ج‬

‫‪SDL‬‬ ‫إنشاء ألعاب ‪ 2D‬في‬

‫‪263‬‬
‫الفصل ‪20‬‬

‫الـ‪SDL‬‬ ‫ٺثبيت‬

‫ابتداء ً من الآن‪ ،‬إنتهت الدروس النظر ية ! لأننا سنمر ّ إلى مرحلة مهمّة‪ ،‬و سنستمتع بالتطبيق بالاستعانة بمكتبة نسميها‬
‫‪.SDL‬‬

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

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

‫نسمي هذا النوع من المكتبات بمكتبات الطرف الثالث )‪ .(Third party libraries‬يجب أن تعرف أنه يوجد نوعان من‬
‫المكتبات ‪:‬‬

‫ل أنظمة التشغيل )من هنا تم‬


‫• المكتبة القياسية )‪ : (Standard library‬و هي المكتبة القاعدية التي تعمل على ك ّ‬
‫استنباط الكلمة ‪ (standard‬و هي تسمح بالقيام بأمور بسيطة كـ ‪ . printf‬هذه المكتبات يتم ّ تسطيبها تلقائي ّا عند‬
‫ٺثبيتك للبيئة التطوير ية و المترجم‪.‬‬

‫خلال الجزئين الأوّلين من هذا الكتاب‪ ،‬كنا ّ قد استعملنا المكتبة القياسي ّة فقط ) ‪، stdio.h ، stdlib.h‬‬
‫‪ .( ... time.h ، string.h‬لم نقم بدراستها بالتفصيل لكنّا جرّبنا منها جزء ً كبيراً‪ .‬إن كنت تريد معرفة المزيد‬
‫جر ِ بحثا ً في ‪ ،Google‬مثلا ً بكتابة ”‪ ،”C standard library‬و ستجد نماذج الدوال في‬
‫عن هذا النوع من المكتبات أ ْ‬
‫ل دالة‪.‬‬
‫هذه المكتبة‪ ،‬بالإضافة إلى شرح قصير حول دور ك ّ‬

‫• مكتبات الطرف الثالث )‪ :(Third party libraries‬هي مكتبات لا يتم ٺثبيتها تلقائيا‪ .‬و إنّما يجب عليك تنز يلها من‬
‫الأنترنت و ٺثبيتها بنفسك على حاسوبك‪.‬‬
‫على عكس المكتبات القياسية‪ ،‬التي تكون بسيطة نسبي ّا و تحتوي على عدد قليل من الدوال‪ ،‬فإنه توجد الآلاف من‬
‫مكتبات الطرف الثالث‪ ،‬و التي تمت كتابتها من طرف مبرمجـين آخرين‪ .‬بعضها جي ّدة‪ ،‬و أخرى أقل‪ ،‬بعضها مدفوع‪،‬‬
‫و بعضها الآخر مجاني‪ ،‬إلخ‪ .‬الأمر المثالي هو إ يجاد مكتبة جي ّدة و مجانية في نفس الوقت !‬

‫‪265‬‬
‫الـ‪SDL‬‬ ‫الفصل ‪ .20‬ٺثبيت‬

‫إنه لمن المستحيل أن أضع لك درسا ً يشرح كل المكتبات الموجودة‪ .‬حت ّى لو أمضيت حياتي كل ّها ‪ 24‬ساعة ‪،24 /‬‬
‫لن أستطيع !‬
‫لذا سأقدّم لك مكتبة واحدة فقط مكتوبة بالـ‪ C‬و م ُستعملة من طرف مبرمجـين مثلك‪.‬‬

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

‫لماذا نختار الـ‪ SDL‬؟‬ ‫‪1.20‬‬

‫اختيار مكتبة ليس بالأمر السهل !‬

‫كما قلت لك الآن‪ ،‬توجد الآلاف من المكتبات للتنز يل‪.‬‬


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

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

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

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

‫الـ‪ ،SDL‬اختيار جي ّد !‬

‫سنقوم الآن بدراسة هذه المكتبة‪ .‬لماذا اخترتها هي و ليس أخرى ؟‬

‫• هي مكتبة مكتوبة بلغة ‪ : C‬أي أنه بإمكان المبرمجـين أن يستعملوها في برامجهم المكتوبة بالـ‪ .C‬و كما هو الحال بالنسبة‬
‫لأغلب المكتبات المكتوبة بالـ‪ ،C‬يمكن استعمالها في لغة الـ‪ C++‬بالإضافة إلى لغات برمجية أخرى‪.‬‬

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

‫‪266‬‬
‫‪ .1.20‬لماذا نختار الـ‪ SDL‬؟‬

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

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

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

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

‫• هي مكتبة متعددة المنصّ ات )‪ : (Multi-platform‬سواء كنت على ‪ Mac OS X ،Windows‬أو ‪ ،GNU/Linux‬ستعمل‬


‫لديك هذه المكتبة‪ .‬و الحقيقة أن هذه نقطة قو ّة يراها المبرمجون بالمكتبة ‪ :‬يمكنها أن تعمل على عدد كبير جدا ً‬
‫من أنظمة التشغيل‪ ،‬فعلى غرار ‪ Mac OS X ،Windows‬و ‪ ،GNU/Linux‬فهي تشتغل أيضا ً على ‪،Amiga ،Atari‬‬
‫‪ ... Dreamcast ،Symbian‬إلخ‪ .‬أي أنه بالإمكان لبرامجك أن تعمل حتى على أجهزة ‪ Atari‬القديمة ! مع ذلك‬
‫يجب القيام ببعض التعديلات و ربّما استخدام مترجم خاص‪ .‬لن أدخل في التفاصيل هنا‪.‬‬

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

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

‫بالـ‪SDL‬‬ ‫الإمكانيات المتاحة‬

‫المكتبة ‪ SDL‬هي مكتبة منخفضة المستوى‪ .‬هل ٺتذكر أول الكتاب حينما حدّثتك عن لغات البرمجة عالية المستوى و لغات‬
‫البرمجة منخفضة المستوى ؟ هذا ينطبق على المكتبات أيضاً‪.‬‬

‫‪267‬‬
‫الـ‪SDL‬‬ ‫الفصل ‪ .20‬ٺثبيت‬

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

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

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

‫يجب إذا أن ٺتذكر بأن الـ‪ SDL‬تقدّم دوالا قاعدية‪ .‬يمكنك إذا الرسم بيكسلا ببيكسل‪ ،‬رسم مستطيل أو إظهار صور‪.‬‬
‫كاف‪.‬‬
‫ٍ‬ ‫ن هذا‬
‫ل شيء‪ ،‬و صدّقني أ ّ‬
‫هذا ك ّ‬

‫• بتحر يك صورة‪ ،‬يمكنك أن تقوم بتحر يك شخصي ّة‪.‬‬

‫• بإظهار العديد من الصور الواحدة تلو الأخرى بسرعة‪ ،‬يمكنك إنشاء تحر يك )‪.(Animation‬‬

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

‫ن اللعبة الشهيرة ”‪ ،”Civilisation : Call to power‬تم دعمها في نظام اللينكس‬


‫كمثال عن لعبة تم صنعها بالـ‪ ،SDL‬اعلم أ ّ‬
‫لاحقا ًباستخدام الـ‪.SDL‬‬

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

‫الشيء الوحيد الذي يح ّد الـ‪ SDL‬هو أنها تقتصر على الألعاب ثنائية الأبعاد‪ ،‬و لم تُنشأ من أجل الألعاب ثلاثية الأبعاد‪.‬‬
‫ل شيء ممكن مادام ثنائيّ الأبعاد( ‪:‬‬
‫هذه أمثلة على ألعاب يمكن تحقيقها بالـ‪) SDL‬ليست سوى قائمة صغيرة‪ ،‬ك ّ‬

‫‪268‬‬
‫الـ‪SDL‬‬ ‫‪ .2.20‬تنز يل‬

‫‪Breakout‬‬ ‫•‬
‫‪Bomberman‬‬ ‫•‬
‫‪Tetris‬‬ ‫•‬
‫• ألعاب المنصّ ات ‪... ،Rayman ،Sonic ،Super Mario Bros :‬‬

‫• ‪ RPG‬ثنائية الأبعاد ‪ ،Zelda :‬الأجزاء الأولى للعبة ‪ ،Final Fantasy‬إلخ‪.‬‬

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

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

‫الـ‪SDL‬‬ ‫تنز يل‬ ‫‪2.20‬‬

‫ل ما تحتاجه‪ ،‬بدء ً من المكتبة‬


‫الموقع الرسمي للمكتبة ‪ SDL‬سيصبح قريبا الوجهة ال ّتي نقصدها كثيرا‪ .‬هناك‪ ،‬يوجد ك ّ‬
‫نفسها و مرورا إلى التوثيق )‪ (Documentation‬الخاص بها‪.‬‬

‫‪http://www.libsdl.org/‬‬

‫إذهب إلى اللائحة ‪ Download‬المتواجدة على يسار الصفحة الرئيسية للموقع‪.‬‬


‫اختر النسخة الأحدث ال ّتي تجدها )‪ SDL 1.2‬عندما كتبت هذه السطور(‪.‬‬

‫صفحة التنز يل مجز ّأة إلى عدة أجزاء‪.‬‬

‫• الشفرة المصدر ية )‪ : (Source code‬هنا يمكنك تحميل الشفرة المصدر ية الخاصة بالمكتبة‪ .‬أدري أن القراء فضوليون‬
‫ليعرفوا كيف تعمل المكتبة من الداخل‪ ،‬لـكنّ هذا لن يفيدنا‪ .‬الأسوأ هو أن ّه سيقوم بإلهائك عن هدفنا الرئيسي‪.‬‬

‫• مكتبات وقت التشغيل )‪ : (Runtime libraries‬هي الملفات التي تحتاج إلى تقديمها مع الملف التنفيذي حين تريد‬
‫أن تعطي برنامجك لشخص آخر‪ .‬بالنسبة للويندوز‪ ،‬أنا أتكلم عن الملف ‪ . SDL.dll‬هذا الأخير يجدر به أن يتواجد‬
‫إما ‪:‬‬

‫– بنفس المجلّد الذي يحتوي الملف التنفيذي )أنا أنصحك بهذا(‪ .‬الأحسن دائما هو أن تعطي الـ‪ DLL‬مع الملف‬
‫التنفيذي و تبقيهم في نفس المجلد‪ .‬إذا وضعت الـ‪ DLL‬في المجلّد الخاص بالويندوز‪ ،‬لن يكون عليك إلحاق‬
‫ل مجلّد يحتوي البرنامج ‪ .SDL‬و مع ذلك قد تحدث بعض المشاكل في حال ما قمت بمسح نسخة‬
‫الـ‪ DLL‬مع ك ّ‬
‫أحدث من الـ‪.DLL‬‬
‫– في المجلّد ‪. C:\Windows‬‬

‫‪.h‬‬ ‫• مكتبات التطوير )‪ : (Development libraries‬هي الملفات ‪) .a‬أو ‪ .lib‬بالنسبة للـ‪ (Visual‬و الملفات‬
‫التي تسمح بإنشاء برامجك ‪ .SDL‬هذه الملفات ليست مفيدة إلا بالنسبة إليك أنت فقط المبرمج‪ .‬أي أنه ليس عليك‬
‫تقديمها مع ملفات البرنامج حين تنتهي من هذا الأخير‪.‬‬

‫‪269‬‬
‫الـ‪SDL‬‬ ‫الفصل ‪ .20‬ٺثبيت‬

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

‫• ‪ : VC6‬بالنسبة للذين يستخدمون النسخ القديمة غير المجانية من ‪) Visual studio‬لا أعتقد أن هناك من القر ّاء من‬
‫لازال يستعمل هذه النسخ(‪ .‬ستجد فيها على أي حال الملفات ‪. .lib‬‬

‫• ‪ : VC8‬بالنسبة للذين يستعملون ‪ Visual Studio 2005 Express‬أو نسخة أحدث‪ ،‬ستجد فيها الملفات ‪. .lib‬‬
‫• ‪ : mingw32‬بالنسبة للذين يستعملون ‪) Code::Blocks‬ستجدون فيها إذا الملفات ‪.( .a‬‬

‫الشيء الخاص هنا‪ ،‬هو أن ”مكتبات التطوير” تحتوي كل الملفات ‪ .h‬و الملفات ‪) .a‬أو ‪ ( .lib‬بالطبع‪ ،‬لـكنها‬
‫تحتوي أيضا ًالملف ‪ SDL.dll‬و ملفات التوثيق الخاصة بالـ‪! SDL‬‬
‫ل ما تحتاجه يتواجد بداخلها‪.‬‬
‫ل ما عليك تنز يله هو ”مكتبات التطوير”‪ ،‬فك ّ‬
‫باختصار‪ ،‬ك ّ‬

‫‪x‬‬
‫لا تخطئ في الرابط ! قم باختيار الـ‪ SDL‬في قسم ”‪ ”Development libraries‬و ليس من قسم ”‪! ”Source code‬‬

‫؟‬
‫ما هو التوثيق )‪ (Documentation‬؟‬

‫التوثيق هو قائمة تحوي اللائحة الكاملة للدوال الخاصة بمكتبة معي ّنة‪ .‬و كل هذه الملفات تكون مكتوبة بالانجليز ية )حتى‬
‫لو كان كاتبوها مبرمجـين فرنسيين(‪ .‬هذا سبب آخر يدفعك للتقدّم في لغة ‪! Shakespeare‬‬

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

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

‫ملفّات التوثيق توجد أصلا ًفي الحزمة ”‪ ”Development libraries‬كما سبق و ذكرت‪ ،‬لـكن بإمكانك تنز يلها وحدها من‬
‫القائمة ‪. Downloadable / Documentation‬‬
‫خاص )اسمه مثلا ً ‪ ( Doc SDL‬ثم إنشاء اختصار إلى الفهرس‬
‫ّ‬ ‫أنصحك أن تجمع ملفات ‪ HTML‬الخاصة بالتوثيق في مجلّد‬
‫‪ . index.html‬و الهدف من هذا هو الوصول إلى هذه الملفات بشكل أسرع حينما تحتاج إليها‪.‬‬

‫‪Windows‬‬ ‫إنشاء مشروع ‪: SDL‬‬ ‫‪3.20‬‬

‫ٺثبيت مكتبة قد يكون أكثر صعوبة قليلا ًمما تعو ّد عليه الجميع‪ .‬هنا لا يوجد ٺثبيت تلقائيّ يطلب منك أن تنقر ”التالي”‪،‬‬
‫”التالي”‪” ،‬التالي”‪” ،‬إنتهى”‪.‬‬

‫‪270‬‬
‫‪Windows‬‬ ‫‪ .3.20‬إنشاء مشروع ‪: SDL‬‬

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

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

‫ل واحد من البيئات الثلاث السابقة‪.‬‬


‫سأعرض الآن كيف ننشئ مشروع ‪ SDL‬في ك ّ‬

‫‪Code::Blocks‬‬ ‫تسطيب الـ‪ SDL‬في‬

‫الـ‪SDL‬‬ ‫استخراج ملفات‬

‫افتح الملف المضغوط ”‪ ”Development Libraries‬الذي قمت بتنز يله‪.‬‬


‫‪7-Zip‬‬ ‫هذا الملف هو بامتداد ‪ .zip‬بالنسبة للـ‪ Visual‬و ‪ .tar.gz‬بالنسبة للـ‪) mingw32‬يلزمك برنامج مثل ‪ Winrar‬أو‬
‫لـكي تقوم بفك الضغط عن الملفات ذات الصيغة ‪.( .tar.gz‬‬

‫الملف المضغوط يحتوي العديد من المجلّدات الداخلية‪ ،‬و هذه هي الملفات التي تهمّنا ‪:‬‬

‫• ‪ : bin‬يحتوي الملف ‪ .dll‬الخاص بالـ‪.SDL‬‬

‫• ‪ : docs‬يحتوي الملفات التوثيقي ّة الخاصة بالـ‪.SDL‬‬

‫• ‪ : include‬يحتوي الملفات الرأسية ‪. .h‬‬

‫• ‪ : lib‬يحتوي الملفات ‪) .lib‬أو ‪ .a‬بالنسبة لـ‪(Code::Blocks‬‬

‫يجب عليك استخراج كل الملفات و المجلّدات الداخلية ووضعها في مكان ما بالقرص الصلب لحاسوبك‪ ،‬يمكنك مثلا‬
‫وضعها في مجلد خاص بـ‪ SDL‬داخل مجلّد الـ‪.Code::Blocks‬‬

‫‪271‬‬
‫الـ‪SDL‬‬ ‫الفصل ‪ .20‬ٺثبيت‬

‫بالنسبة لي فإن المسار هو التالي ‪:‬‬

‫‪C:\Program Files (x86)\CodeBlocks\SDL-1.2.13‬‬

‫احفظ المسار الذي به البرنامج‪ ،‬ستحتاج إليه عندما تريد تعديل إعدادات ‪ Code::Blocks‬لاحقا‪.‬‬

‫و الآن‪ ،‬علينا بالقيام بخطوة بسيطة‪ ،‬لتسهيل الأمور علينا‪ ،‬توجّه إلى المسار ‪) include/SDL‬في حالتي‪ ،‬هو متواجد‬
‫‪.h‬‬ ‫بـ ‪ ،( C:\Program Files (x86)\CodeBlocks\SDL-1.2.13\include\SDL‬قم بنسخ الملفات الرأسية‬
‫في المجلّد الأب‪) ،‬أي في ‪:‬‬
‫‪.( C:\Program Files (x86)\CodeBlocks\SDL-1.2.13\include‬‬

‫ها قد تم تسطيب المكتبة‪ ،‬فلنقم الآن بتعديل إعدادات ‪.Code::Blocks‬‬

‫‪SDL‬‬ ‫إنشاء مشروع‬

‫افتح ‪ Code::Blocks‬و قم بإنشاء مشروع جديد‪.‬‬

‫‪272‬‬
‫‪Windows‬‬ ‫‪ .3.20‬إنشاء مشروع ‪: SDL‬‬

‫عوض أن تقوم باختيار ‪ Console Application‬كما جرت العادة‪ ،‬اختر ‪.SDL project‬‬

‫النافذة الأولى لا جدوى منها‪ ،‬قم بتجاوزها بالضغط على ”التالي” )‪.(Next‬‬

‫سي ُطلب منك أن تقوم بإدخال اسم المشروع‪ ،‬قم بذلك كالعادة ‪:‬‬

‫‪273‬‬
‫الـ‪SDL‬‬ ‫الفصل ‪ .20‬ٺثبيت‬

‫الآن يجب اختيار المسار الذي ثبتنا فيه المكتبة ‪:‬‬

‫اضغط على الزر الذي يأخذ شكل مرب ّع به ثلاث نقاط‪ ،‬ستظهر لك النافذة التالية ‪:‬‬

‫قم باختيار المسار )بالنسبة لي هو ‪.( C:\Program Files (x86)\CodeBlocks\SDL-1.2.13‬‬

‫‪274‬‬
‫‪Windows‬‬ ‫‪ .3.20‬إنشاء مشروع ‪: SDL‬‬

‫قد تظهر لك في مكان النافذة السابقة هذه النافذة ‪:‬‬

‫املأ الحقل ‪ base‬بنفس الطر يقة السابقة‪ ،‬ثم ّ اضغط على زر الخروج‪ ،‬و ستلاحظ أن المسار قد تم تسجيله كالتالي ‪:‬‬

‫اضغط على ‪ ،Next‬ستظهر لك نافذة اختيار المترجم‪ ،‬قم باختيار الأوضاع ‪ Realease‬أو ‪) Debug‬هذا لا يهم(‪.‬‬

‫أخيرا اضغط على ”إنهاء” )‪ .(Finish‬سيتم إنشاء المشروع التجريبي ‪:‬‬

‫‪275‬‬
‫الـ‪SDL‬‬ ‫الفصل ‪ .20‬ٺثبيت‬

‫يحتوي المشروع على ملفين ‪ main.cpp‬و ملف ‪ .bmp‬قبل أن تحاول القيام بالترجمة‪ .‬يجب القيام بخطوة‬
‫أخيرة )عليك القيام بها دائما(‪ ،‬و هي نسخ الملف ‪ SDL.dll‬من ملفات المكتبة )الّذي يفترض أن يكون في‬
‫المسار ‪ ( C:\Program Files (x86)\CodeBlocks\SDL-1.2.13\bin\SDL.dll‬و ضعه في المجلّد الخاص‬
‫بالمشروع‪.‬‬

‫‪276‬‬
‫‪Windows‬‬ ‫‪ .3.20‬إنشاء مشروع ‪: SDL‬‬

‫أخرج من ‪ Code::Blocks‬و أعد الدخول إليه‪ ،‬ثم قم بترجمة البرنامج المُقترح مسبقاً‪ .‬يفترض أن تظهر النافذة التالية ‪:‬‬

‫إذا ظهرت لك النافذة السابقة‪ ،‬فهنيئا لك‪ ،‬المكتبة مثبتة بشكل جيد !‬

‫‪277‬‬
‫الـ‪SDL‬‬ ‫الفصل ‪ .20‬ٺثبيت‬

‫م‬
‫إن ظهرت لك الرسالة ”‪ ”The application can’t start because the file SDL.dll is missing‬أي أنه لا يمكن‬
‫تشغيل البرنامج‪ ،‬لأن ملف ‪ SDL.dll‬غير موجود‪ ،‬فهذا يعني أنك لم تقم بنسخ الملف الأخير في ملفات المشروع‬
‫كما طلبت منك !‬
‫و كما قلت‪ ،‬إن كنت تريد تسليم المشروع إلى أصدقائك‪ ،‬عليك بارفاق الملف التنفيذي ‪ .exe‬بالملف ‪، SDL.dll‬‬
‫بينما أنت لست بحاجة إلى إعطائهم الملفات ‪ .h‬و ‪ .a‬ال ّتي لا تهم أحدا سواك‪.‬‬

‫ملاحظات مترجمة الكتاب‬

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

‫إن لم يشتغل البرنامج و لازال المترجم يشير دائما إلى عدم وجود الملف ‪ ، SDL.dll‬فجرب نسخ هذا الأخير و‬
‫لصقه في المجلدين ‪ Debug‬و ‪ Release‬من مجلّد المشروع‪ ،‬أي في نفس المكان الذي يتواجد به الملف التنفيذي‪.‬‬
‫أريد أن أنو ّهك بأن المشروع الذي تم انشاؤه هو خاص باللغة ‪) C++‬لأنها اللغة التي يتم اختيارها تلقائيا من بيئة‬
‫‪C‬‬ ‫التطوير‪ ،‬كون أن هذه الأخيرة تم تطويرها للعمل بلغة الـ‪ ،(C++‬سنقوم إذا بتحو يل هذا المشروع من ‪ C++‬إلى‬
‫ببساطة‪.‬‬
‫توجّه إلى ملفات المشروع‪ ،‬ستجد الملف ‪ main.cpp‬قم بتغيير اسمه إلى ‪. main.c‬‬

‫أدخل الآن إلى ‪ ،Code::Blocks‬ستظهر لك على الأرجح النافذة التالية ‪:‬‬

‫اضغط على الزر ”لا” )‪.(No‬‬


‫توجه إلى القائمة اليسار ية‪ ،‬و قم بالنقر باليمين على الملف ‪ main.cpp‬و اختر حذفه من المشروع ‪:‬‬

‫‪278‬‬
‫‪Windows‬‬ ‫‪ .3.20‬إنشاء مشروع ‪: SDL‬‬

‫اضغط على اسم المشروع‪ ،‬و اطلب إضافة ملف جديد ‪:‬‬

‫اختر الملف ‪ main.c‬من ملفات المشروع ‪:‬‬

‫‪279‬‬
‫الـ‪SDL‬‬ ‫الفصل ‪ .20‬ٺثبيت‬

‫ستظهر لك نافذة أخرى‪ ،‬قم باختيار ‪ ،Next‬ثم انقر على ”موافق” )‪.(OK‬‬

‫أنقر باليمين مجددا على الملف ‪ main.c‬و اختر ”خصائص” )‪: (Properties‬‬

‫توجه إلى القائمة ‪ ،Advanced‬ستجد ‪ ،Compiler variable‬غي ّرها من ‪ CPP‬إلى ‪ CC‬كالتالي ‪:‬‬

‫‪280‬‬
‫‪Windows‬‬ ‫‪ .3.20‬إنشاء مشروع ‪: SDL‬‬

‫اضغط بعد ذلك على ‪ OK‬هذا ما سيبدو عليه المشروع الجديد ‪:‬‬

‫ضع الشفرة التالية بدل الشفرة السابقة ‪:‬‬


‫‪1‬‬ ‫>‪#include <stdlib.h‬‬
‫‪2‬‬ ‫>‪#include <stdio.h‬‬
‫‪3‬‬ ‫>‪#include <SDL/SDL.h‬‬
‫‪4‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;)‪SDL_Init(SDL_INIT_VIDEO‬‬
‫‪7‬‬ ‫;)‪SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE‬‬
‫‪8‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫‪9‬‬ ‫}‬

‫و أخيراً‪ ،‬من القائمة العلو ية‪ ،‬اختر هدف البناء ‪. Release‬‬

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

‫يمكنك مسح الملف ‪ .bmp‬لأننا لسنا بحاجة إليه‪ .‬بالنسبة للملف ‪ ، main.c‬يمكنك الآن استبدال محتواه بالشفرة‬

‫‪281‬‬
‫الـ‪SDL‬‬ ‫الفصل ‪ .20‬ٺثبيت‬

‫التالية ‪:‬‬

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


‫‪2‬‬ ‫>‪#include <stdio.h‬‬
‫‪3‬‬ ‫>‪#include <SDL/SDL.h‬‬
‫‪4‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;‪return 0‬‬
‫‪7‬‬ ‫}‬

‫إنها شفرة مبدئية‪ ،‬تشبه الشفرات التي تعو ّدنا عليها )تضمين ‪ stdlib.h‬و ‪ stdio.h‬ثم ‪ .( main‬الشيء الوحيد‬
‫ي يستكلف نفسه بتضمين كل الملفات الرأسية الخاصة بالمكتبة ‪.SDL‬‬
‫الذي تغي ّر هو تضمين الملف ‪ . SDL.h‬إن ّه ملف رأس ّ‬

‫‪Visual C++‬‬ ‫إنشاء مشروع ‪ SDL‬في‬

‫الـ‪SDL‬‬ ‫استخراج ملفات‬

‫‪Visual C++ 2005‬‬ ‫من الموقع الرسمي‪ ،‬قم بتنز يل آخر نسخة من المكتبة من قسم ”‪ ”Development Libraries‬و اختر نسخة‬
‫‪.Service Pack 1‬‬

‫افتح الملف ‪. .zip‬‬


‫إنه يحتوي على التوثيق )في المجلد ‪ ،( docs‬الملفات ‪) .h‬في المجلد ‪ ،( include‬و الملفات ‪) .lib‬في المجّلد ‪( lib‬‬
‫المكافئة للملفات ‪ .a‬بالنسبة لمترجم ‪ .Visual‬ستجد أيضا ًالملف ‪ SDL.dll‬في المجلّد ‪. lib‬‬

‫• انسخ الملف ‪ SDL.dll‬إلى مجلّد المشروع‪.‬‬

‫• انسخ الملفات ‪ .lib‬إلى المجلّد ‪ lib‬الخاص بـ‪ .Visual C++‬بالنسبة لي‪ ،‬أنا أتكلم عن المجلّد‬

‫‪. C:\Program Files (x86)\Microsoft Visual Studio 8\VC\lib‬‬

‫• انسخ الملفات ‪ .h‬إلى المجلّد ‪ includes‬الخاص بـ‪ .Visual C++‬أنشئ مجلّدا ‪ SDL‬في المجلد ‪ includes‬لجمع‬
‫الملفات ‪ .h‬الخاصة بالـ‪ SDL‬فيه‪ .‬بالنسبة لي‪ ،‬سأضع تلك الملفات في المجلد ‪:‬‬
‫‪. C:\Program Files (x86)\Microsoft Visual Studio 8\VC\include\SDL‬‬

‫إنشاء ‪ SDL‬مشروع جديد‬

‫في الـ‪ Visual C++‬أنشئ مشروعا ًمن نوع ‪ .Application console Win32‬سم ّه مثلا ً ‪ testsdl‬ثم اضغط على ”موافق”‪.‬‬

‫ستفتح نافذة مساعدة‪ .‬توجه إلى ‪ Application parameters‬و تأكد من أن الخانة ‪ Empty project‬مختارة‪.‬‬

‫‪282‬‬
‫‪Windows‬‬ ‫‪ .3.20‬إنشاء مشروع ‪: SDL‬‬

‫لقد تم ّ إنشاء المشروع إذا‪ .‬إن ّه فارغ‪ .‬أضف إليه ملفا ً جديدا ً و ذلك بالنقر على ‪ Source files‬ثم ‪ Add‬ثم‬
‫‪: New element‬‬

‫حينما تفتح نافذة جديدة‪ ،‬أطلب إنشاء ملف جديد من نوع )‪ ، C++ File (.cpp‬قم بتسميته ‪. main.c‬‬
‫بوضعك للامتداد ‪ .c‬فإن الـ‪ Visual‬سيقوم بانشاء ملف ‪ C‬و ليس ‪.C++‬‬

‫أكتب )أو انسخ‪/‬ألصِ ق( الشفرة المبدئية الذي وضعتها أعلاه‪ ،‬في الملف الجديد الذي أنشأته‪.‬‬

‫‪Visual C++‬‬ ‫تخصيص مشروع ‪ SDL‬في‬

‫التعديل على المشروع أصعب قليلا ًمما هو الحال مع الـ‪ ،Code::Blocks‬لـكن بقليل من التركيز‪ ،‬ستتمكن من فعله‪ .‬توجّه‬
‫إلى خصائص المشروع من ‪. testsdl properties / Project‬‬

‫• في القسم ‪ Code generation / C / C++‬عدّل قيمة الـ ‪ Runtime Libraries‬إلى‬


‫)‪. DLL multithread (/MD‬‬

‫• في القسم ‪ Advanced / C / C++‬اختر ‪ Compilation as‬و ضع القيمة‬


‫)‪) Compile as C code (/TC‬و إلا فإن ‪ Visual‬سيترجم البرنامج كأنه ملف ‪ C++‬و ليس كملف ‪.(C‬‬

‫‪283‬‬
‫الـ‪SDL‬‬ ‫الفصل ‪ .20‬ٺثبيت‬

‫‪SDL.lib‬‬ ‫• في القسم ‪ Input / Link editor‬عدّل قيمة الـ ‪ Additional dependencies‬لـكي تضيف‬
‫و ‪. SDLmain.lib‬‬

‫• في القسم ‪ System / Link editor‬عدّل قيمة الـ ‪ Sub-System‬إلى الـ ‪. Windows‬‬

‫قم بالضغط على ”موافق” لحفظ التغييرات‪.‬‬


‫يمكنك الآن الترجمة و ذلك بالذهاب إلى ‪ Generate‬ثم ‪. Generate solution‬‬

‫ستجد الملف التنفيذي الذي يتواجد بمجلد المشروع )أو بمجلد داخلي يسمى ‪ ( Debug‬و لا تنس أنه على الملف‬
‫ل شيء على‬
‫مرتين على ‪ ، .exe‬إذا سار ك ّ‬
‫‪ SDL.dll‬أن يتواجد في نفس المجلّد الذي يتواجد به الملف التنفيذي‪ .‬أنقر ّ‬
‫ما يرام‪ ،‬فلن يحصل أيّ شيء‪ ،‬و إلّا فسيحدث خطأ إذا لم يكن الملف ‪ SDL.dll‬في نفس المجلّد‪.‬‬

‫إنشاء مشروع ‪(Xcode) Mac OS : SDL‬‬ ‫‪4.20‬‬

‫فلتقم بتنز يل النسخة ‪ 1.2‬من الـ‪ ،SDL‬و ذلك من خلال الجزء ”‪ ”Download‬أسفل يسار الموقع‪ ،‬كالتالي ‪:‬‬

‫في أسفل الصفحة ستجد قسما ً يدعى ”‪ .”Runtime Libraries‬نز ّل الملف الذي يتناسب مع هندسة معالج جهازك‬
‫)‪ Intel‬أو ‪ ،(PowerPC‬هذا ما ستوضحه الصورة الموالية‪ .‬إن كنت تريد معرفة هندسة المعالج‪ ،‬يمكنك الذهاب إلى القائمة‬
‫”‪ ”Apple‬في أعلى اليسار‪ ،‬و النقر على ”‪ .”About this Mac‬في السطر ”‪ ”Processor‬ستجد إما ‪ Intel‬أو ‪.PowerPC‬‬

‫‪284‬‬
‫‪ .4.20‬إنشاء مشروع ‪(Xcode) Mac OS : SDL‬‬

‫حينما يتم تنز يل الملف‪ ،‬انقر عليه مرتين‪ ،‬يفترض أن يفتح لوحده‪ .‬ستجد بهذا المجلّد مجلّدا ‪ SDL.framework‬قم‬
‫بنسخه و لصقه في المجلد ‪. /Library/Frameworks‬‬

‫إنتهى‪ ،‬المكتبة مسطّبة الآن !‬


‫ستجد مجلّدا آخر اسمه ‪ devel-lite‬أتركه مفتوحا‪ ،‬سنعود إليه لاحقاً‪.‬‬

‫الآن قم بانشاء مشروع جديد ”‪ ،”Cocoa Application‬اضغط على ”‪ .”Next‬في ‪ Product Name‬قم بتسمية المشروع‬
‫)كـ”‪ ”SDL‬مثلاً(‪ .‬و في ‪ ، Company Identifier‬ضع ما تريد )كاسم مستعار لك مثلا(‪ .‬أترك الباقي كما هو ثم‬
‫اضغط على ”‪ .”Next‬اختر أين تريد وضع المشروع‪ .‬سيتم إنشاء مجلّد بطر يقة تلقائية و ليس عليك إنشاء واحد بنفسك ووضع‬
‫ملفات المشروع بداخله‪.‬‬

‫ما إن يتم إنشاء المشروع‪ ،‬قم بالتخلص من الملفات التي لا تحتاجها ‪، AppDelegate.m ، AppDelegate.h :‬‬
‫‪ main.m ، InfoPlist.strings ، MainMenu.xib‬و ‪: Credits.rtf‬‬

‫‪285‬‬
‫الـ‪SDL‬‬ ‫الفصل ‪ .20‬ٺثبيت‬

‫اختر المشروع من التفرع الشجري اليساري )القسم ‪ Install SDL‬من الصورة الموالية( في الشجرة الثانية اختر اسم‬
‫مشروعك من قسم ‪ PROJECT‬و ليس من ‪: TARGETS‬‬

‫‪+‬‬ ‫يمكنك أيضا ًتغيير الـ‪ localisation‬من ‪ English‬إلى ‪ . French‬اختر ‪ ، English‬أنقر على ‪ -‬للمسح و على‬
‫لإضافة ‪ ، French‬هذا يعود إليك و لست مضطرا ً للقيام بذلك‪.‬‬

‫سنقوم الآن بتخصيص المشروع على نظام ‪ ) 32 bits‬لأن المكتبة لا تشتغل على أنظمة ‪ ،(64 bits‬و سنقوم بإضافة‬
‫المسارات من أجل الـ‪ ،frameworks‬و للملفات الرأسية أيضاً‪ .‬اضغط على ‪ Build Settings‬ثم ‪ All‬ثم في‬
‫‪ Architectures‬انقر على ‪ 64-bit Intel‬و اختر ‪: 32-bit Intel‬‬

‫ما إن تفعل ذلك‪ ،‬اختر ‪ LLVM GCC 4.2‬من السطر ‪. Compiler for C/C++/Objective-C‬‬

‫‪286‬‬
‫‪ .4.20‬إنشاء مشروع ‪(Xcode) Mac OS : SDL‬‬

‫اذهب إلى منطقة البحث في أعلى اليمين‪ ،‬و اكتب ”‪ ،”search paths‬يجدر بك أن تجد سطرين مهمّين بالنسبة‬
‫لنا و هما ‪ Header search paths‬و ‪ . Framework search paths‬انقر مرتين على الجهة اليمنى للسطر‬
‫‪ Framework search paths‬أنقر على علامة ‪ +‬و أضف المسار ‪ . /Library/Frameworks‬بالنسبة للسطر‬
‫‪ Header Search paths‬أضف المسار‬
‫‪. /Library/Frameworks/SDL.framework/Headers‬‬

‫اختر الآن ”هدفك”‪ ،‬و هذه المرة من قسم ‪: TARGETS‬‬

‫توجه إلى ‪ ، Summary‬في المنطقة ‪ Application Category‬يمكنك وضع ما تشاء‪ ،‬لن يغير هذا شيئا كبيراً‪،‬‬
‫‪App Icon‬‬ ‫لأنه ينفع من أجل الـ‪ AppStore‬فقط‪ .‬عدّل السطر ‪ Main Interface‬و ضع ”‪ .”SDLMain‬بالنسبة للـ‬
‫فاسمه يدل عليه‪ ،‬فهو يسمح لك بتحديد أيقونة لبرنامجك‪ .‬يكفي سحب ثم تحرير الصورة المُراد استعمالها كأيقونة‪ .‬بالنسبة‬
‫للمنطقة ‪ Linked Frameworks and Libraries‬سنقوم بإضافة الـ‪ framework‬الخاص بنا ‪، SDL.framework‬‬
‫أنقر على ‪ +‬في منطقة البحث ‪ ،‬أكتب ”‪ ،”SDL‬حين تجده في القائمة‪ ،‬أنقر على ‪ ، Add‬إن لم تجده فهذا يعني أنك لم تقم‬
‫بوضعه في المجلّد المناسب ) ‪.( /Library/Frameworks‬‬

‫م‬
‫الأيقونات في ‪ Mac OS‬هي بصيغة ‪ . .icns‬إن استعملت صيغة أخرى فستلاحظ أن الأيقونة لا تظهر‪.‬‬
‫المتواجد في المجلد‬ ‫إن أردت تحو يل صيغة صورة عادية إلى أيقونة استعمل برنامج ‪.Icon Composer‬‬
‫‪ ، /Developer/Applications/Utilities‬يكفي أن تسحب الأيقونة إلى المربع المخصص لها ثم تحفظها‪.‬‬

‫في المنطقة ‪ Info‬يمكننا أن نشير إلى العديد من المعلومات في البرنامج‪ ،‬يمكنك الإطلاع عليها أكثر من خلال قراءة‬
‫الملفات التوثيقية الخاصة بـ‪ .Apple‬الشيء الوحيد الذي يمكنك التعديل عليه هو ‪ Localization‬من ‪ en‬إلى ‪، fr‬‬
‫كما يمكنك تعديل الـ ‪ Copyright‬و وضع ما تريد‪.‬‬

‫توجّه الآن إلى ‪ Build Phases‬و انقر على ‪ Copy Files / Add Build Phase‬في أسفل يمين النافذة‪،‬‬
‫‪Frameworks‬‬ ‫اضغط على ‪ Copy Files‬و غي ّره إلى ‪ . Copy frameworks into app‬في ‪ Destination‬اختر‬
‫لتضيف الخاصة بك‪ ،‬قم بسحبها من التفرع الشجري اليساري و افلاتها في المنطقة ‪ ، Build phase‬كما يظهر بالصورة ‪:‬‬

‫‪287‬‬
‫الـ‪SDL‬‬ ‫الفصل ‪ .20‬ٺثبيت‬

‫أنصحك بترتيب كل الـ‪ frameworks‬الخاص بك في مجلد ‪ Framework‬و هذا لـكي يسهل عليك إ يجادها‪.‬‬
‫و أيضا ً بالنسبة للشفرات المصدر ية‪ ،‬أنصحك بترتيبها في مجلدات ليسهل الوصول إليها‪ .‬لإنشاء مجلد انقر باليمين على الشجرة‬
‫اليسار ية و اختر ‪ New Group‬ثم اسحب الملفات إلى داخلها‪.‬‬

‫سنقوم الآن بإضافة الملفات ‪ SDLMain.h‬و ‪ ، SDLMain.m‬توجه إلى المجلد ‪ devel-lite‬المفتوح مسبقا و قم‬
‫بإضافة الملفين إلى المشروع‪ .‬إذا ظهرت لك نافذة تحديد خصائص النسخ‪ ،‬قم باختيار‬
‫)‪. Copy items into destination group’s folder (if needed‬‬

‫آخر شيء ‪ :‬أنشئ ملفا ‪ . main.c‬توجه إلى القائمة ‪ New File / New / File‬ثم إلى ‪ ، C and C++‬اختر‬
‫‪ C File‬ثم ”‪ .”Next‬قم بتسمية الملف و هاقد أكملت‪.‬‬

‫‪GNU/Linux‬‬ ‫إنشاء مشروع ‪: SDL‬‬ ‫‪5.20‬‬

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

‫؟‬
‫ماذا عن الذين يقومون بترجمة الشفرات يدو يا ؟‬

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

‫‪http://www.siteduzero.com/uploads/fr/ftp/mateo21/makefile_sdl‬‬

‫الشيء الوحيد المختلف‪ ،‬هو إضافة المكتبة ‪ SDL‬إلى محر ّر الروابط ) ‪ .( LDFLAGS‬يجدر بك أن تكون قد نز ّلت المكتبة‬
‫و ثب ّتها في مجلّد ملفات المترجم‪ ،‬بنفس طر يقة ‪) Windows‬المجلدان ‪ include/SDL‬و ‪.( lib‬‬

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

‫‪288‬‬
‫ملخّ ص‬

‫‪make‬‬ ‫‪# To compile the project‬‬


‫‪make clean‬‬ ‫)‪# To delete compilation files (useless .o files‬‬
‫‪make mrproper‬‬ ‫‪# To delete all files except source ones‬‬

‫ملخّ ص‬

‫• الـ‪ SDL‬مكتبة منخفضة المستوى‪ ،‬تسمح بإنشاء نوافذ و التعامل مع الرسوميات ‪.2D‬‬

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

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

‫‪289‬‬
‫الـ‪SDL‬‬ ‫الفصل ‪ .20‬ٺثبيت‬

‫‪290‬‬
‫الفصل ‪21‬‬

‫إنشاء نافذة و مساحات‬

‫ثبت المكتبة‪ ،‬و‬


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

‫سندخل في مضمون موضوعنا في هذا الفصل‪ .‬سنقوم بتطبيق أساسيات لغة الـ‪ C‬مع ‪ .SDL‬كيف يتم تحميل الـ‪ SDL‬؟‬
‫كيف يتم فتح نافذة بالأبعاد التي نريد ؟ كيف نرسم داخل النافذة ؟‬

‫لدينا أمور كثيرة لنعرفها‪ ،‬فهي ّا بنا !‬

‫الـ‪SDL‬‬ ‫تحميل و إيقاف‬ ‫‪1.21‬‬

‫العديد من المكتبات المكتوبة بلغة الـ‪ ،C‬تستلزم أن يتم تحميلها ثم غلقها حين ننتهي منها‪ ،‬و ذلك لاستعمال دوال محددة‪.‬‬
‫المكتبة ‪ SDL‬من بين هذه المكتبات‪.‬‬

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

‫هاهما الدالتان الأولتان الخاصّتان بالـ ‪ SDL‬اللتان يجب عليك أن تعرفهما ‪:‬‬

‫• ‪ : SDL_Init‬تحميل المكتبة في الذاكرة العشوائية )باستخدام الـ ‪.( malloc‬‬

‫• ‪ : SDL_Quit‬تحرير المكتبة من الذاكرة )باستعمال الـ ‪.( free‬‬

‫أي أن أوّل شيء يجب أن تقوم به في البرنامج هو استدعاء ‪ ، SDL_Init‬و آخر شيء هو استدعاء ‪. SDL_Quit‬‬

‫‪291‬‬
‫الفصل ‪ .21‬إنشاء نافذة و مساحات‬

‫‪SDL‬‬ ‫‪ : SDL_Init‬تحميل المكتبة‬

‫الدالة ‪ SDL_Init‬تستقبل معاملا‪ .‬إذ يجب أن يتم تحديد أي جزء من المكتبة نريد تحميله‪.‬‬

‫؟‬
‫آه حقّا ! هل الـ‪ SDL‬ٺتكون من كثير من الأجزاء ؟‬

‫نعم بالطبع ! فهناك جزء من المكتبة يتعامل مع الشاشة‪ ،‬و آخر يتعامل مع الصوت‪ ،‬إلخ‪.‬‬

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

‫الشرح‬ ‫الثابت‬

‫تحميل الجزء الخاص بالعرض )الفيديو(‪ ،‬إنه الجزء الذي نحمله غالباً‪.‬‬ ‫‪SDL_INIT_VIDEO‬‬

‫تحميل الجزء الخاص بالصوت‪ ،‬هذا ما يسمح لك مثلا بتشغيل الموسيقى مثلا‪.‬‬ ‫‪SDL_INIT_AUDIO‬‬

‫تحميل الجزء الخاص بقارئ القرص المضغوط‪ ،‬و ذلك للتحكم به‪.‬‬ ‫‪SDL_INIT_CDROM‬‬

‫تحميل الجزء الخاص بجهاز التحكم ‪.Joystick‬‬ ‫‪SDL_INIT_JOYSTICK‬‬

‫تحميل كل الأجزاء التي ذكرتها سابقا‪.‬‬ ‫‪SDL_INIT_EVERYTHING‬‬

‫إذا استدعيت الدالة بهذا الشكل‬

‫;)‪1 SDL_Init(SDL_INIT_VIDEO‬‬

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

‫الدالة ‪ SDL_Init‬تقرأ العدد و هكذا تحدد الأنظمة الواجب تحميلها‪.‬‬

‫الآن لو تكتب ‪:‬‬

‫;)‪1 SDL_Init(SDL_INIT_EVERYTHING‬‬

‫ل شيء‪ ،‬ليس جيدا ً إثقال‬


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

‫؟‬
‫ماذا لو أردت تحميل الصوت و الفيديو فقط‪ .‬هل يجدر بي استخدام ‪ SDL_INIT_EVERYTHING‬؟‬

‫لن تستعمل ‪ ، SDL_INIT_EVERYTHING‬من أجل تحميل وحدتين‪ ،‬هذا جنون ! لحسن الحظ‪ ،‬يمكننا تجميع الخيارات‬
‫بواسطة الرمز | ‪.‬‬

‫‪292‬‬
‫الـ‪SDL‬‬ ‫‪ .1.21‬تحميل و إيقاف‬

‫‪1 // Loading the video and the audio‬‬


‫;)‪2 SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO‬‬

‫كما يمكنك وضع ثلاثة دون مشاكل ‪:‬‬

‫‪1 // Loading the video, the audio and the timer‬‬


‫;)‪2 SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER‬‬

‫م‬
‫هذه ”الخيارات” التي نبعثها للدالة ‪ SDL_Init‬نسميها بـالأعلام )‪ .(Flags‬هذه الكلمة نستعملها كثيرا ً في علوم‬
‫الحاسوب‪.‬‬
‫تذك ّر إذا أن الإشارة | خاصة بدمج الخيارات‪ .‬إنها تشبه الإضافة إلى ح ّد ما‪.‬‬

‫‪SDL‬‬ ‫‪ : SDL_Quit‬إيقاف المكتبة‬

‫هذه الدالة سهلة الاستعمال لأنها لا تحتاج إلى أي معامل ‪:‬‬

‫;)(‪1 SDL_Quit‬‬

‫كل الأنظمة سيتم إيقافها و يتم تحرير الذاكرة‪.‬‬


‫باختصار‪ ،‬هذه الدالة أداة للخروج من المكتبة بشكل نظيف‪ ،‬و لنقل للخروج من برنامجك‪.‬‬

‫‪SDL‬‬ ‫نموذج عن برنامج‬

‫باختصار‪ ،‬هذا ما يبدو عليه برنامج ‪ SDL‬في نسخته الأبسط ‪:‬‬

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


‫‪2‬‬ ‫>‪#include <stdio.h‬‬
‫‪3‬‬ ‫>‪#include <SDL/SDL.h‬‬
‫‪4‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫‪SDL_Init(SDL_INIT_VIDEO); // Starting the SDL (Here we load the video‬‬
‫)‪system‬‬
‫‪7‬‬ ‫‪SDL_Quit(); // Stopping the SDL (Freeing the memory).‬‬
‫‪8‬‬ ‫;‪return 0‬‬
‫‪9‬‬ ‫}‬

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

‫‪293‬‬
‫الفصل ‪ .21‬إنشاء نافذة و مساحات‬

‫معالجة الأخطاء‬

‫الدالة ‪ SDL_Init‬تقوم بإرجاع قيمة ‪:‬‬

‫• ‪ : −1‬في حال وجود خطأ‪.‬‬

‫• ‪ : 0‬في حالة عدم وجود أي خطأ‪.‬‬

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

‫؟‬
‫لـكن كيف أقوم بإظهار الخطأ الحادث ؟‬

‫سؤال وجيه ! ليس لدينا كونسول الآن‪ ،‬كيف نخز ّن و نعرض رسائل الخطأ ؟‬

‫هناك حل ّان ‪:‬‬

‫• يمكننا التعديل على خاصيات المشروع‪ ،‬لـكي نسمح له باستعمال الـكونسول أيضاً‪ .‬سنتمكّن في هذه الحالة من‬
‫استخدام الدالة ‪،( printf‬‬

‫• أو نكتب الأخطاء في ملف‪ .‬تستخدم الدالة ‪. fprintf‬‬

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

‫يوجد متغير ‪ stderr‬معر ّف في ‪ stdio.h‬يقوم بالتأشير نحو المنطقة التي يُمكن أن يُكتب فيها الخطأ‪ .‬غالبا في‬
‫الويندوز‪ ،‬هذه المنطقة عبارة عن ملف يحمل الاسم ‪ . stderr.txt‬بينما في اللينكس فإن الأخطاء غالبا ًما يتم إظهارها‬
‫على الـكونسول‪ .‬هذا المتغير يتم إنشاؤه تلقائي ّا في بداية البرنامج و يتم حذفه في نهايته‪ ،‬أي أنك لست مجـبرا ً على استعمال‬
‫‪ fopen‬و ‪. fclose‬‬
‫يمكنك استعمال الدالة ‪ fprintf‬على ‪ stderr‬بدون استعمال ‪ fopen‬و ‪: fclose‬‬

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


‫‪2‬‬ ‫>‪#include <stdio.h‬‬
‫‪3‬‬ ‫>‪#include <SDL/SDL.h‬‬
‫‪4‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫‪if (SDL_Init(SDL_INIT_VIDEO) == −1) // Starting the SDL, if there’s an‬‬
‫‪error :‬‬
‫‪7‬‬ ‫{‬
‫‪8‬‬ ‫‪fprintf(stderr, ”Error while initializing SDL : %s\n”,‬‬
‫‪SDL_GetError()); // Writing the error‬‬

‫‪294‬‬
‫‪ .2.21‬فتح نافذة‬

‫‪9‬‬ ‫‪exit(EXIT_FAILURE); // We exit the program‬‬


‫‪10‬‬ ‫}‬
‫‪11‬‬ ‫;)(‪SDL_Quit‬‬
‫‪12‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪13‬‬

‫ما الجديد في هذه الشفرة المصدر ية ؟‬

‫• لقد كتبنا الخطأ الذي وجدناه في ‪ . stderr‬الرمز ‪ %s‬يسمح للـ‪ SDL‬بالإشارة إلى تفاصيل الخطأ ‪ :‬الدالة‬
‫‪ SDL_GetError‬في الحقيقة تقوم بإرجاع آخر خطأ ‪.SDL‬‬

‫• نخرج باستعمال الـ )(‪ . exit‬لح ّد الآن لا يوجد شيء جديد مقارنة بما جرت العادة‪ ،‬ستلاحظ أنني استعمل الثابت‬
‫‪ EXIT_FAILURE‬كقيمة يقوم البرنامج الرئيسي بإرجاعها‪ ،‬بينما استعملت في النهاية الثابت ‪EXIT_SUCCESS‬‬
‫في مكان الـ‪.0‬‬
‫ما الذي قمت به ؟ لقد قمت بتحسين الطر يقة التي تعودنا أن نكتب بها الشفرة‪ .‬لقد استخدمت اسم الثابت الّذي يعني‬
‫”خطأ” و الذي هو نفسه بالنسبة لجميع أنظمة التشغيل‪ .‬بينما الأعداد تختلف من نظام إلى آخر‪.‬‬
‫لهذا فإن الملف ‪ stdlib.h‬تسمح باستعمال ثابتتين )معر ّفي ‪: ( #define‬‬

‫– ‪ : EXIT_FAILURE‬قيمة يتم إرجاعها في حالة وجود خطأ ما في البرنامج‪.‬‬

‫– ‪ : EXIT_SUCCESS‬قيمة يتم إرجاعها في حالة عدم وجود أي خطأ‪.‬‬

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

‫فتح نافذة‬ ‫‪2.21‬‬

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

‫لـكي نبدأ‪ ،‬تأكد من أن لديك ‪ main‬تشبه هذه ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫)‪if (SDL_Init(SDL_INIT_VIDEO) == −1‬‬
‫‪4‬‬ ‫{‬

‫‪295‬‬
‫الفصل ‪ .21‬إنشاء نافذة و مساحات‬

‫‪5‬‬ ‫;)”‪fprintf(stderr, ”Error while initializing SDL‬‬


‫‪6‬‬ ‫;)‪exit(EXIT_FAILURE‬‬
‫‪7‬‬ ‫}‬
‫‪8‬‬ ‫;)(‪SDL_Quit‬‬
‫‪9‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪10‬‬

‫ل ما سنقوم بتحميله هو نظام العرض ) ‪،( SDL_INIT_VIDEO‬‬


‫هذه هي حالتك لو اتبعت جيدا ً من بداية الفصل‪ .‬حالي ّا‪ ،‬ك ّ‬
‫هذا ما يهمّنا‪.‬‬

‫اختيار وضع العرض‬

‫أول شيء نقوم به بعد ‪ ، SDL_Init‬هو تحديد وضع العرض الذي نريد استعماله‪ ،‬أي الدقة )‪ ،(Resolution‬عدد الألوان‬
‫بالإضافة إلى خصائص أخرى‪.‬‬

‫من أجل هذا سنستعمل الدالة ‪ SDL_SetVideoMode‬التي تستقبل ‪ 4‬معاملات ‪:‬‬

‫• عرض النافذة التي نريدها )‪،(pixels‬‬

‫• طول النافذة التي نريدها )‪،(pixels‬‬

‫• عدد الألوان القابلة للعرض )‪،(bits/pixel‬‬

‫• الخيارات )الأعلام(‪.‬‬

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

‫• عدد الألوان ‪ :‬هو العدد الأقصى للألوان التي يمكن أن تظهر في النافذة‪ .‬إن كنت من عشاق ألعاب الفيديو‪ ،‬ستكون‬
‫معتادا ً على هذا الأمر‪ .‬فإن قيمة ‪ 32 bits/pixel‬تسمح بإظهار ملايير الألوان‪ ،‬بينما إنه من الممكن أن نختار قيمة‬
‫أقل كـ‪) 16 bits/pixel‬تسمح بعرض ‪ 65536‬لون( أو حتى ‪) 8 bits/pixel‬تسمح بعرض ‪ 256‬لون مختلف(‪ ،‬هذا‬
‫الأمر مفيد حينما تريد برمجة تطبيقات من أجل جهاز بسيط كالـ‪ PDA‬أو الهاتف المحمول‪.‬‬

‫• الخيارات ‪ :‬تماما مثل الـ ‪ ، SDL_Init‬علينا باستعمال أعلام من أجل تعر يف خصائص‪ .‬هذه أهم الأعلام التي‬
‫يمكنك استعمالها )يمكنك استعمال العديد منها‪ ،‬يتم التفر يق بينها باستعمال الرمز | ( ‪:‬‬

‫– ‪ : SDL_HWSURFACE‬المعطيات سيتم حفظها في في الذاكرة الرسومية للبطاقة ‪ .3D‬الشيء الجيد ‪ :‬إنها‬


‫الذاكرة الأكثر سرعة‪ .‬الشيء السيّء ‪ :‬يصعب إ يجاد مساحة شاغرة في هذا النوع من الذاكرة مقارنة بالأخرى‬
‫) ‪.( SDL_SWSURFACE‬‬
‫– ‪ : SDL_SWSURFACE‬المعطيات يتم حفظها في ذاكرة النظام )أي في الـ‪ ،(RAM‬الشيء الجيد ‪ :‬يوجد الـكثير‬
‫من المكان في هذه الذاكرة‪ .‬الشيء السيء ‪ :‬أقل سرعة و أقل كفاءة‪.‬‬
‫– ‪ : SDL_RESIZABLE‬ستصبح مقاييس أبعاد النافذة قابلة للتعديل‪ ،‬لأنها ليست كذلك تلقائيا‪.‬‬
‫– ‪ : SDL_NOFRAME‬لن يصبح للنافذة أية حواش أو شر يط علوي لكتابة عنوان النافذة‪.‬‬

‫‪296‬‬
‫‪ .2.21‬فتح نافذة‬

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

‫– ‪ : SDL_DOUBLEBUF‬وضع ‪ ،double buffering‬تقنية مستعملة بكثرة في برمجة الألعاب ثنائية الأبعاد‪ .‬تقضي‬
‫بأن يكون تحر ّك الأشياء على الشاشة مرناً‪ ،‬لأنه إن لم يكن كذلك‪ ،‬سيكون التحر ّك سيئاً‪ ،‬سأشرح هذا الأمر‬
‫بالتفصيل لاحقاً‪.‬‬

‫إذا كتبت الشفرة المصدر ية التالية ‪:‬‬

‫;)‪1 SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE‬‬

‫فإنه سيقوم بفتح نافذة ذات أبعاد ‪ ،640 × 480‬و بعدد ألوان ‪) 32 bits/pixel‬ملايير الألوان(‪ ،‬و ستم تحميل النافذة‬
‫على الذاكرة الرسومي ّة )إنّها الأسرع‪ ،‬لذلك نفضّ ل استعمالها(‪.‬‬

‫كمثال آخر‪ ،‬لو نأخذ ‪:‬‬

‫;)‪1 SDL_SetVideoMode(400, 300, 32, SDL_HWSURFACE | SDL_RESIZABLE | SDL_DOUBLEBUF‬‬

‫هذه الشفرة تفتح نافذة مقاييس أبعادها قابلة للتعديل‪ ،‬بأبعاد ابتدائية ‪ ،400 × 300‬و بعدد ألوان ‪ 32 bits/pixel‬كما‬
‫أن تقنية ‪ double buffering‬مفع ّلة‪.‬‬

‫هذه أوّل شفرة مصدر ية بسيطة يمكنك تجريبها ‪:‬‬

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


‫‪2‬‬ ‫>‪#include <stdio.h‬‬
‫‪3‬‬ ‫>‪#include <SDL/SDL.h‬‬
‫‪4‬‬ ‫)][‪int main(int argc, char �argv‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;)‪SDL_Init(SDL_INIT_VIDEO‬‬
‫‪7‬‬ ‫;)‪SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE‬‬
‫‪8‬‬ ‫;)(‪SDL_Quit‬‬
‫‪9‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫‪10‬‬ ‫}‬

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

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

‫‪297‬‬
‫الفصل ‪ .21‬إنشاء نافذة و مساحات‬

‫توقيف البرنامج للحظات‬

‫؟‬
‫ما العمل كي تقوم النافذة بالانتظار و لا تختفي مباشرة ؟‬

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

‫هذه شفرة تعمل‪ ،‬لـكن ّي أدعوك إلى عدم تجريبها‪ ،‬أعطيها لك فقط كشرح ‪:‬‬
‫)][‪1 int main(int argc, char �argv‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;)‪SDL_Init(SDL_INIT_VIDEO‬‬
‫‪4‬‬ ‫;)‪SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE‬‬
‫‪5‬‬ ‫;)‪while(1‬‬
‫‪6‬‬ ‫;)(‪SDL_Quit‬‬
‫‪7‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪8‬‬

‫أنت تعرف الـ ;)‪ : while(1‬إنّها الحلقة التكرار ية غير المنتهية‪ .‬بما أن ‪ 1‬يساوي القيمة المنطقية ”صحيح” )تذك ّر‬
‫المتغيرات المنطقية(‪ ،‬فإن الشرط صحيح دائما و بالتالي فستدور الحلقة إلى الأبد مع عدم وجود وسيلة لإيقافها‪ .‬هذا ليس‬
‫حل ّا جي ّدا‪.‬‬
‫لـكي نتمكن من توقيف النافذة كي لا تختفي فجأة بدون اللجوء إلى حلقة غير منتهية‪ ،‬سنستعمل دالتي ال ّتي أنشأتها و سميتها‬
‫‪: pause‬‬
‫)(‪1 void pause‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;‪int cont = 1‬‬
‫‪4‬‬ ‫;‪SDL_Event event‬‬
‫‪5‬‬ ‫)‪while (cont‬‬
‫‪6‬‬ ‫{‬
‫‪7‬‬ ‫;)‪SDL_WaitEvent(&event‬‬
‫‪8‬‬ ‫)‪switch(event.type‬‬
‫‪9‬‬ ‫{‬
‫‪10‬‬ ‫‪case SDL_QUIT:‬‬
‫‪11‬‬ ‫;‪cont = 0‬‬
‫‪12‬‬ ‫}‬
‫‪13‬‬ ‫}‬
‫} ‪14‬‬

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

‫هذا مثال عن شفرة مصدر ية كاملة‪ ،‬يمكنك )أخيراً( تجريبها ‪:‬‬

‫‪298‬‬
‫ فتح نافذة‬.2.21

1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <SDL/SDL.h>
4 void pause();
5 int main(int argc, char �argv[])
6 {
7 SDL_Init(SDL_INIT_VIDEO); // We initialize the SDL
8 SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
9 pause(); // We pause the program
10 SDL_Quit(); // We stop the SDL
11 return EXIT_SUCCESS; // We close the program
12 }
13 void pause()
14 {
15 int cont = 1;
16 SDL_Event event;
17 while (cont)
18 {
19 SDL_WaitEvent(&event);
20 switch(event.type)
21 {
22 case SDL_QUIT:
23 cont = 0;
24 }
25 }
26 }

.‫ في أعلى البرنامج كي لا أضطر ّ لعرض أكثر من ملف‬pause ‫ستلاحظ أنني وضعت نموذج الدالة‬
‫ هذه الحلقة تنتهي‬.‫ و هي تقوم بالدخول في حلقة تكرار ية غير منتهية أذكى من السابقة‬pause ‫لقد قمت باستدعاء الدالة‬
! ‫ أعلى النافذة‬X ‫حينما تنقر على الزر الأحمر‬

‫الصورة التالية هي عبارة عن النافذة التي نحصل عليها حين نترجم الشفرة المصدر ية السابقة )النافذة ذات أبعاد‬
.(640 × 480

299
‫الفصل ‪ .21‬إنشاء نافذة و مساحات‬

‫ها قد وصلنا !‬

‫إن أردت‪ ،‬قم بوضع الع َلم الذي يسمح بتعديل مقاييس النافذة‪ .‬للعِل ْم‪ ،‬في ألعاب الفيديو نفضّ ل النوافذ ذات الأبعاد‬
‫الثابتة )لأنه يسهل التعامل معها(‪ ،‬إذا فلنترك النافذة ثابتة كما هي الآن‪.‬‬

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

‫تغيير عنوان النافذة‬

‫لح ّد الآن‪ ،‬النافذة أخذت عنوانا تلقائيا )و هو ‪ SDL_app‬في الصورة السابقة(‪.‬‬


‫هل تريد تغييره ؟‬

‫إن الأمر بسيط للغاية‪ ،‬يكفي استعمال الدالة ‪. SDL_WM_SetCaption‬‬


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

‫خلافا ًلما يعتقده الجميع‪ ،‬تغيير اسم الأيقونة لا يعني تغيير صورة الأيقونة التي تظهر أعلى يسار النافذة‪ .‬هذا لا يعمل دائما‬
‫)حسب معرفتي‪ ،‬قد يعطي نتائج على الـ‪ GNU/Linux‬في بيئة الـ‪ .(Gnome‬شخصياً‪ ،‬أنا أبعث القيمة ‪ NULL‬إلى الدالة‪ .‬على‬
‫ن هذا الأمر‬
‫أية حال‪ ،‬يمكننا تغيير شكل الأيقونة التي تظهر أعلى يسار النافذة‪ ،‬لـكننا سنتعل ّم ذلك في الفصل القادم‪ ،‬لأ ّ‬
‫ليس بمستواك بعد‪.‬‬

‫هذه نفس الـ ‪ main‬السابقة‪ ،‬مع إضافة الدالة ‪: SDL_WM_SetCaption‬‬

‫‪300‬‬
‫‪ .3.21‬التعامل مع المساحات‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;)‪SDL_Init(SDL_INIT_VIDEO‬‬
‫‪4‬‬ ‫;)‪SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE‬‬
‫‪5‬‬ ‫;)‪SDL_WM_SetCaption(”Ma super fenêtre SDL !”, NULL‬‬
‫‪6‬‬ ‫;)(‪pause‬‬
‫‪7‬‬ ‫;)(‪SDL_Quit‬‬
‫‪8‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪9‬‬

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

‫للنافذة الآن عنوان‪.‬‬

‫التعامل مع المساحات‬ ‫‪3.21‬‬

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

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

‫‪301‬‬
‫الفصل ‪ .21‬إنشاء نافذة و مساحات‬

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

‫مساحتك الأولى ‪ :‬الشاشة‬

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

‫في الشفرة المصدر ية‪ ،‬كل مساحة يتم تخزينها في متغير من نوع ‪ . SDL_Surface‬نعم‪ ،‬إنه نوع بيانات تم إنشاؤه من‬
‫طرف الـ‪) SDL‬هذا المتغير عبارة عن هيكل(‪.‬‬

‫بما أن أول مساحة ننشئها هي الشاشة‪ ،‬فهيا بنا ‪:‬‬

‫;‪1 SDL_Surface �screen = NULL‬‬

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

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

‫;)‪1 screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE‬‬

‫المؤش ّر الآن يمكن أن يساوي إحدى القيمتين ‪:‬‬

‫• ‪ : NULL‬المتغير ‪ screen‬سيساوي ‪ NULL‬إذا فشلت الدالة ‪ SDL_SetVideoMode‬في تحميل أسلوب العرض‬


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

‫• قيمة أخرى ‪ :‬إذا كانت القيمة مختلفة عن ‪ ، NULL‬فهذا يعني أن الـ‪ SDL‬قامت بحجز المكان‪ ،‬كل شيء على ما‬
‫يرام !‬

‫إنه من المستحسن هنا أن تتم معالجة الأخطاء‪ ،‬تماما مثلما فعلنا حينما أردنا تحميل الـ‪ ،SDL‬هاهي إذا الدالة‬

‫الكاملة بإضافة معالجة الأخطاء للـ ‪. SDL_SetVideoMode‬‬

‫‪302‬‬
‫‪ .3.21‬التعامل مع المساحات‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪SDL_Surface �screen = NULL; // The pointer which stores the surface of‬‬
‫‪the screen‬‬
‫‪4‬‬ ‫;)‪SDL_Init(SDL_INIT_VIDEO‬‬
‫‪5‬‬ ‫‪screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE); // We try to‬‬
‫‪open the window‬‬
‫‪6‬‬ ‫‪if (screen == NULL) // If we can’t, we note it and we exit.‬‬
‫‪7‬‬ ‫{‬
‫‪8‬‬ ‫‪fprintf(stderr, ”Impossible to load the video mode : %s\n”,‬‬
‫;))(‪SDL_GetError‬‬
‫‪9‬‬ ‫;)‪exit(EXIT_FAILURE‬‬
‫‪10‬‬ ‫}‬
‫‪11‬‬ ‫;)‪SDL_WM_SetCaption(”Ma super fenêtre SDL !”, NULL‬‬
‫‪12‬‬ ‫;)(‪pause‬‬
‫‪13‬‬ ‫;)(‪SDL_Quit‬‬
‫‪14‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪15‬‬

‫الرسالة التي تتركها لنا الدالة ‪ ، SDL_GetError‬مفيدة من أجل معرفة ما الّذي لم يعمل‪.‬‬

‫م‬
‫حكاية صغيرة ‪ :‬مرة أخطأت بينما أردت أن أفتح نافذة بأسلوب الشاشة الكاملة )‪ ،(Full screen‬في عوض أن‬
‫أطلب الدقة ‪ 1024 × 768‬كتبت بالخطأ ‪ ،10244 × 768‬لم أفهم لماذا لم يتم التحميل‪ ،‬لأن ّي لم أنتبه إلى أنني‬
‫مرتين )ربما كنت متعباً(‪ .‬و لحل المشكل ألقيتُ نظرة على الملف ‪ ، stderr.txt‬توجهت إلى رسالة‬
‫كتبت ‪ّ 4‬‬
‫الخطأ و اكتشفت بأن الدقة التي طلبتها مرفوضة )شيء يثير الفضول أليس كذلك ؟(‪.‬‬

‫تلوين مساحة‬

‫لا توجد ‪ 36‬طر يقة لملء مساحة‪ ،‬الحقيقة أنه توجد طر يقتان ‪:‬‬

‫• إما أن يتم تلوين المساحة بلون موحّد‪.‬‬

‫• إما أن يتم ملؤها عن طر يق تحميل صورة‪.‬‬

‫م‬
‫يمكنك في الحقيقة الرسم في المساحة بيكسلا ببيكسل‪ ،‬لـكن هذه الطر يقة معقّدة‪ ،‬لن نراها هنا‪.‬‬

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

‫الدالة التي تسمح بتلوين النافذة بلون موحّد هي ‪) SDL_FillRect‬العبارة ‪ FillRect‬تعني ملء مستطيل‬
‫بالإنجليز ي ّة(‪ .‬هذه الدالة تستقبل ‪ 3‬معاملات و هي ‪:‬‬

‫‪303‬‬
‫الفصل ‪ .21‬إنشاء نافذة و مساحات‬

‫• مؤش ّر نحو المساحة التي نريد التلوين عليها )مثلا ‪.( screen‬‬

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

‫• اللون الذي نريد أن نلو ّن به المساحة‪.‬‬

‫كملخّ ص ‪:‬‬

‫;)‪1 SDL_FillRect(surface, NULL, color‬‬

‫بالـ‪SDL‬‬ ‫التحكم في الألوان‬

‫في الـ ‪ SDL‬كل لون مخزن في عدد من نوع ‪. Uint32‬‬

‫؟‬
‫إذا كان عدداً‪ ،‬لماذا إذا لم نستعمل ببساطة النوع ‪ int‬أو النوع ‪ long‬؟‬

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

‫هناك مثلا ً‪:‬‬

‫• ‪ : Uint32‬عدد صحيح بحجم ‪ 32 bits‬أي ‪) 4 octets‬للتذكير ‪.(1 octet = 8 bits :‬‬

‫• ‪ : Uint16‬عدد صحيح مشفر على ‪.(2 octets) 16 bits‬‬

‫• ‪ : Uint8‬عدد طبيعي مشفر على ‪.(1 octet) 8 bits‬‬

‫لن تستعمل المكتبة سوى ‪ typedef‬لتقوم بتغيير قيمة العدد على حسب نظام التشغيل‪ .‬إذا كنت فضولياً‪ ،‬فألق نظرة‬
‫على الملف ‪. SDL_types.h‬‬

‫لن نتأخر في التعامل مع كل هذا‪ ،‬فالتفاصيل لا تهم حالياً‪ .‬كل ما عليك تذكره هو أن النوع ‪ Uint32‬لا يخزن إلا‬
‫عددا صحيحا ًليس إلا‪ ،‬مثل ‪. int‬‬

‫؟‬
‫لـكن كيف أعرف أي عدد يوافق اللون الّذي أريد ؟‬

‫هناك بالفعل دالة من أجل ذلك ‪ ، SDL_MapRGB :‬هذه الأخيرة تستقبل ‪ 4‬معاملات ‪:‬‬

‫• صيغة الألوان ‪ :‬هذه الصيغة تعتمد على عدد ‪ bits/pixel‬التي قد طلبتها بالـ ‪ . SDL_SetVideoMode‬يمكنك استرجاع‬
‫القيمة فهي موجودة في المتغير الداخلي ‪. screen->format‬‬

‫‪304‬‬
‫‪ .3.21‬التعامل مع المساحات‬

‫• كمية الأحمر في اللون‪.‬‬

‫• كمية الأخضر في اللون‪.‬‬

‫• كمية الأزرق في اللون‪.‬‬

‫قد لا يعرف البعض بأن كل الألوان يتم تشكيلها عن طر يق خلط الألوان ‪ :‬أزرق‪ ،‬أحمر و أخضر‪.‬‬
‫كل كمية ٺتدرج من العدد ‪) 0‬لا يوجد لون( إلى العدد ‪) 255‬كل اللون موجود(‪ .‬أي أننا لو كتبنا ‪:‬‬

‫)‪1 SDL_MapRGB(screen−>format, 255, 0, 0‬‬

‫فاللون المتشكل سيكون أحمرا‪ .‬لا وجود للأخضر و لا للأزرق‪ ،‬أما لو نكتب ‪:‬‬

‫)‪1 SDL_MapRGB(screen−>format, 0, 0, 255‬‬

‫اللون سيكون أزرقا‪ ،‬بينما لو نكتب ‪:‬‬

‫)‪1 SDL_MapRGB(screen−>format, 255, 255, 255‬‬

‫اللون سيكون أبيضا لأننا دمجنا كل الألوان‪ ،‬لو أنك تريد تشكيل اللون الأسود‪ ،‬فلتجعل كل القيم على ‪.0‬‬

‫؟‬
‫ألا يمكننا استعمال لون آخر غير هذه الألوان ؟‬

‫‪Colors‬‬ ‫كلّا‪ ،‬يمكنك ذلك لو أنك تقوم بمزج الألوان بشكل ذكي‪ .‬للمساعدة في ذلك‪ ،‬توجه إلى برنامج ‪ Paint‬ثم إلى‬
‫‪ ، Modify the colors /‬انقر على ‪ Define the colors‬ثم ‪. Custom‬‬
‫هنا‪ ،‬اختر اللون الّذي يلائمك‪ .‬أنظر إلى الصورة التالية ‪:‬‬

‫مركّبات اللون متواجدة في أسفل يمين النافذة‪ .‬كما ترى فقد اخترت لونا أخضر مزرقّا‪ ،‬و هو يتكو ّن من ‪ 17‬من‬
‫الأحمر‪ 206 ،‬من الأخضر‪ ،‬و ‪ 112‬من الأزرق‪.‬‬

‫‪305‬‬
‫الفصل ‪ .21‬إنشاء نافذة و مساحات‬

‫تلوين الشاشة‬

‫الدالة ‪ SDL_MapRGB‬تقوم بإرجاع عدد من نوع ‪ Uint32‬يوافق الل ّون المختار‪.‬‬


‫يمكننا إذا تعر يف متغير باسم ‪ blueGreen‬يحوي الشفرة الخاصة لاسترجاع هذا اللون ‪:‬‬

‫;)‪1 Uint32 blueGreen = SDL_MapRGB(screen−>format, 17, 206, 112‬‬

‫ليس من الضروري المرور دائما على متغير لتخزين اللون المراد استعماله )إلا إن كنت تحتاجه فعلا في برنامجك(‪.‬‬
‫يمكنك مباشرة إعطاء القيمة التي تم ارجاعها من طرف الدالة ‪ SDL_MapRGB‬إلى الدالة ‪. SDL_FillRect‬‬

‫لو نريد أن نملأ الشاشة باللون الأخضر المزرق‪ ،‬يمكننا كتابة ‪:‬‬

‫;))‪1 SDL_FillRect(screen , NULL, SDL_MapRGB(screen−>format, 17, 206, 112‬‬

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

‫تحديث الشاشة‬

‫لقد اقتربنا من تحقيق الهدف‪.‬‬


‫لقد نسينا أمرا ً بسيطا ً ‪ :‬و هو الأمر بتحديث الشاشة‪ .‬بالفعل‪ ،‬فالأمر ‪ SDL_FillRect‬يقوم بتلوين الشاشة‪ ،‬لـكن هذا‬
‫لا يحصل إلا في الذاكرة‪ ،‬إذ يجب أن نطلب من الحاسوب تحديث الشاشة لاستعمال البيانات الجديدة‪.‬‬

‫من أجل هذا سنستعمل الدالة ‪ ، SDL_Flip‬سنتكلم بشكل مفصل عن هذه الدالة لاحقا‪.‬‬
‫الدالة تستقبل معاملا واحدا و هو الشاشة ‪. screen‬‬

‫فلنلخّ ص كل شيء !‬

‫هذه دالة ‪ main‬تقوم بفتح نافذة ملونة باللون الأخضر المزرق ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪SDL_Surface �screen = NULL‬‬
‫‪4‬‬ ‫;)‪SDL_Init(SDL_INIT_VIDEO‬‬
‫‪5‬‬ ‫;)‪screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE‬‬
‫‪6‬‬ ‫;)‪SDL_WM_SetCaption(”Ma super fenêtre SDL !”, NULL‬‬
‫‪7‬‬ ‫‪// We colorize the screen with blue−green color‬‬
‫‪8‬‬ ‫;))‪SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 17, 206, 112‬‬
‫‪9‬‬ ‫;)‪SDL_Flip(screen‬‬
‫‪10‬‬ ‫;)(‪pause‬‬
‫‪11‬‬ ‫;)(‪SDL_Quit‬‬
‫‪12‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪13‬‬

‫‪306‬‬
‫‪ .3.21‬التعامل مع المساحات‬

‫هاهي النتيجة ‪:‬‬

‫رسم مساحة أخرى في الشاشة‬

‫النتيجة السابقة جيدة‪ ،‬لـكننا لن نتوقف هنا‪ .‬لحد الآن ليست لدينا سوى مساحة واحدة و هي الشاشة‪ .‬نحن نريد أن نقوم‬
‫بالرسم عليها‪ ،‬أي ”نلصق” مساحات أخرى عليها بألوان مختلفة‪.‬‬

‫لهذا يجب علينا إنشاء متغير من نوع ‪ SDL_Surface‬للمساحة الجديدة ‪:‬‬

‫;‪1 SDL_Surface �rectangle = NULL‬‬

‫سنطلب إذا من الـ ‪ SDL‬أن تقوم بحجز مكان في الذاكرة من أجل المساحة الجديدة‪.‬‬
‫من أجل الشاشة كنا قد استعملنا ‪ . SDL_SetVideoMode‬لـكن هذه الأخيرة لا تعمل إلا على الشاشة )المساحة‬
‫الرئيسية(‪ ،‬لا نريد أن نقوم بإنشاء نافذة من أجل كل مستطيل نريد إنشاءه !‬

‫توجد إذا دالة أخرى من أجل إنشاء مساحة ‪ . SDL_CreateRGBSurface :‬هذه هي التي سنقوم باستعمالها في‬
‫كل مرة نريد أن ننشئ مساحة جديدة‪.‬‬

‫هذه الدالة تستقبل العديد من المعاملات )ثمانية !(‪ .‬لـكنني لن أتطر ّق إلا للمعاملات التي تهمّنا لح ّد الآن‪.‬‬
‫بما أن لغة ‪ C‬تُلزمنا بإدخال قيم لكل المعاملات‪ ،‬فإننا سنقوم بوضع القيمة ‪ 0‬في مكان كل معامل لا يهمّنا‪.‬‬

‫فلنتأمل قليلا في المعاملات الأربع الأولى )يجدر بها أن تذك ّرنا بإنشاء الشاشة(‪.‬‬

‫• قائمة الأعلام )الخيارات(‪ .‬لديك الاختيار بين ‪:‬‬

‫– ‪ : SDL_HWSURFACE‬المساحة يتم تحميلها في الذاكرة الرسومي ّة‪ .‬و هي تحتوي على مكان أقل مقارنة بالذاكرة‬
‫الخاصة بالنظام )حقيقة‪ ،‬مع بطاقات الـ‪ 3D‬في أيامنا هذه‪ ،‬قد لا يكون لهذا تأثير(‪ ،‬لـكنها ذاكرات سر يعة و‬
‫فع ّالة‪.‬‬

‫‪307‬‬
‫الفصل ‪ .21‬إنشاء نافذة و مساحات‬

‫– ‪ : SDL_SWSURFACE‬يتم تحميل المساحة في الذاكرة الخاصة بالنظام‪ ،‬أين يوجد الـكثير من المكان‪ ،‬لـكن هذا‬
‫الاختيار سيجبر المعالج على القيام بحسابات أكثر‪ .‬لو أنك حمّلت المساحة على الذاكرة الرسومي ّة‪ ،‬فإن البطاقة‬
‫‪ 3D‬هي المسؤولة عن القيام بأغلب الحسابات‪.‬‬

‫• عرض المساحة )‪.(pixels‬‬


‫• ارتفاع المساحة )‪.(pixels‬‬

‫• عدد الألوان )‪.(bits/pixel‬‬

‫هكذا إذا نقوم بحجز مكان للمساحة الجديدة في الذاكرة ‪:‬‬

‫;)‪1 rectangle = SDL_CreateRGBSurface(SDL_HWSURFACE, 220, 180, 32, 0, 0, 0, 0‬‬

‫الأربع معاملات الأخيرة تساوي ‪ ،0‬كما قلت لك‪ ،‬لأننا لا نهتم بأمرها حالياً‪.‬‬

‫بما أننا قمنا بالحجز اليدوي للذاكرة‪ ،‬فيجب علينا تحريرها باستعمال الدالة ‪ SDL_FreeSurface‬و التي نستعملها قبل‬
‫‪: SDL_Quit‬‬

‫;)‪1 SDL_FreeSurface(rectangle‬‬
‫;)(‪2 SDL_Quit‬‬

‫م‬
‫ليس هناك من ٍ‬
‫داع إلى تحرير المساحة ‪ screen‬باستعمال ‪ SDL_FreeSurface‬لأنه يتم تحريرها تلقائيا ًعند‬
‫استدعاء ‪. SDL_Quit‬‬

‫يمكننا الآن تلوين المساحة الجديدة باللون الأبيض مثلا ‪:‬‬

‫;))‪1 SDL_FillRect(rectangle, NULL, SDL_MapRGB(screen−>format, 255, 255, 255‬‬

‫لصق المساحة بالشاشة‬

‫اقتربنا من النهاية‪ ،‬هيا بعض الشجاعة ! المساحة جاهزة‪ ،‬لـكن لو تحاول تجريب البرنامج‪ ،‬ستلاحظ أنها لن تظهر على الشاشة‪،‬‬
‫بالفعل إذ أن المساحة ‪ screen‬هي وحدها التي تم إظهارها‪ .‬لـكي نستطيع رؤ ية مساحتنا الجديدة يجب أن نقوم بـتسو ية‬
‫المساحة‪ ،‬أي لصقها على الشاشة‪ ،‬سنستعمل لأجل هذا الدالة ‪ . SDL_BlitSurface‬هذه الدالة تنتظر ‪:‬‬

‫• المساحة التي نريد لصقها )هنا ‪.( rectangle‬‬

‫• معلومة حول الجزء من تلك المساحة الذي نريد لصقه )اختياري(‪ .‬لن يهمنا الأمر الآن فنحن نريد لصق كل‬
‫المساحة و لهذا فستكون القيمة ‪. NULL‬‬

‫• المساحة التي نريد أن نلصق عليها المساحة الجديدة )في حالتنا هذه نتكلم عن الشاشة ‪.( screen‬‬

‫‪308‬‬
‫‪ .3.21‬التعامل مع المساحات‬

‫• مؤش ّر نحو متغير يحتوي الإحداثي ّات‪ .‬هذه الإحداثيات تشير إلى المكان الذي نريد أن نلصق عليه المساحة‪ ،‬أي‬
‫موقعه‪.‬‬
‫للإشارة إلى الإحداثي ّات‪ ،‬نحتاج إلى استعمال متغير من نوع ‪. SDL_Rect‬‬
‫إن ّه هيكل يحتوي العديد من المركّبات‪ ،‬إثنتان منها تهمّنا ‪:‬‬

‫– ‪ : x‬الفاصلة‪.‬‬
‫– ‪ : y‬الترتيبة‪.‬‬

‫يجب أن تعرف أن الإحداثي ّة )‪ (0, 0‬توافق أقصى نقطة في يسار أعلى الشاشة‪.‬‬
‫أما الإحداثي ّة )‪ (640, 480‬فهي توافق النقطة الموجودة في أقصى يمين أسفل الشاشة‪ ،‬و هذا إن كنت قد فتحت نافذة‬
‫بحجم ‪ 640 × 480‬مثلي‪.‬‬

‫هذا المخطط سيساعدك في الفهم ‪:‬‬

‫إذا كنت قد درست الر ياضيات من قبل‪ ،‬فعلى الأرجح لن تضيع بينما تحاول فهم كيفية العمل‪ .‬فلننشئ إذا متغيرا‬
‫‪ . position‬سنعطي القيمة ‪ 0‬لكل من الفاصلة و الترتيبة و ذلك ليتم لصق مساحتنا )المستطيل( في أعلى يسار النافذة ‪:‬‬

‫;‪1 SDL_Rect position‬‬


‫;‪2 position.x = 0‬‬
‫;‪3 position.y = 0‬‬

‫و الآن بما أننا حددنا موقعنا في النافذة‪ ،‬يمكننا تسو ية المساحة الجديدة على الشاشة ‪:‬‬

‫;)‪1 SDL_BlitSurface(rectangle, NULL, screen, &position‬‬

‫لاحظ أنني استعملت الرمز & و ذلك لأنه يجب علينا إرسال عنوان المتغير ‪. position‬‬

‫‪309‬‬
‫ إنشاء نافذة و مساحات‬.21 ‫الفصل‬

‫تلخيص الشفرة المصدر ية‬

: ً ‫أعتقد أن وضع الشفرة المصدر ية ال ّتي تلخص ما شرحته لن يكون مضرا‬

1 int main(int argc, char �argv[])


2 {
3 SDL_Surface �screen = NULL, �rectangle = NULL;
4 SDL_Rect position;
5 SDL_Init(SDL_INIT_VIDEO);
6 screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
7 // Surface allocation
8 rectangle = SDL_CreateRGBSurface(SDL_HWSURFACE, 220, 180, 32, 0,0, 0,
0);
9 SDL_WM_SetCaption(”Ma super fenêtre SDL !”, NULL);
10 SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 17, 206,112));
11 position.x = 0; // The coordinates of the surface will be (0, 0)
12 position.y = 0;
13 // Filling the surface with white color
14 SDL_FillRect(rectangle, NULL, SDL_MapRGB(screen−>format, 255,255, 255))
;
15 SDL_BlitSurface(rectangle, NULL, screen, &position); // Sticking the
surface on the screen
16 SDL_Flip(screen); // Updating the screen
17 pause();
18 SDL_FreeSurface(rectangle); // Freeing the surface
19 SDL_Quit();
20 return EXIT_SUCCESS;
21 }

: ‫شاهد النتيجة‬

310
‫‪ .4.21‬تمرين ‪ :‬إنشاء تدرّج لونيّ‬

‫مركزة ُ المساحة في الشاشة‬

‫نحن نجيد إظهار المساحة في أعلى اليسار‪ .‬يسهل أيضا موقعتها أسفل يمين الشاشة‪ .‬ستكون الإحداثيات )‪،(640 − 220, 480 − 180‬‬
‫لأنه يجب إنقاص حجم المستطيل ليتم إظهاره كاملا‪.‬‬

‫لـكن كيف تتم مركزة ُ المستطيل الأبيض ؟ لو تفك ّر قليلا ً ستجد بأن الحساب ر ياضياتيّ‪ .‬فهنا نعرف الهدف من‬
‫الر ياضيات و الحساب الهندسي !‬
‫ل هذا الأمر بمستوى سهل هنا ‪:‬‬
‫ك ّ‬

‫;)‪1 position.x = (640 / 2) − (220 / 2‬‬


‫;)‪2 position.y = (480 / 2) − (180 / 2‬‬

‫فاصلة المستطيل هي نصف عرض الشاشة )‪ .(640/2‬و لـكن‪ ،‬بالإضافة إلى هذا‪ ،‬يجب أن يتم إنقاص نصف طول‬
‫المستطيل أيضا ً)‪ ،(220/2‬لأنك إن لم تنقص هذا الحجم‪ ،‬سيكون تمركز المستطيل خاطئا ً)جرّب عدم فعل ذلك و ستفهم‬
‫ما الّذي أعنيه(‪.‬‬
‫كذلك بالنسبة للترتيبة مع ارتفاع الشاشة و المستطيل‪.‬‬

‫النتيجة ‪ :‬المستطيل الأبيض يتمركز بشكل جيد في الشاشة‪.‬‬

‫تمرين ‪ :‬إنشاء تدرّج لونيّ‬ ‫‪4.21‬‬

‫سننهي الفصل بتمرين صغير )مصحّ ح( متبوع بسلسلة تمارين أخرى )غير مصححة من أجل حث ّك على التدريب(‪.‬‬

‫التمرين المصحح ليس صعبا ًحقّا ‪ :‬ما نريد إنشاءه هو نافذة متدرّجة الألوان عموديا من الأسود إلى الأبيض‪.‬‬
‫سيكون عليك إنشاء ‪ 255‬مساحة بارتفاع ‪ 1‬بيكسل‪ .‬كل مساحة لها لون مختلف أكثر فأكثر سوادا‪.‬‬

‫‪311‬‬
‫الفصل ‪ .21‬إنشاء نافذة و مساحات‬

‫هذا ما يجب عليك الحصول عليه في النهاية‪ ،‬صورة مشابهة لهذه ‪:‬‬

‫إنه أمر جميل‪ ،‬أليس كذلك ؟ الشيء الأجمل هو أن بعض الحلقات التكرار ية كافية من أجل تحقيق المطلوب‪.‬‬

‫لفعل ذلك يجب إنشاء ‪ 256‬مساحة )أي ‪ 256‬سطر( تحتوي مركبات الألوان )أحمر‪ ،‬أخضر‪ ،‬أزرق( التالية ‪:‬‬

‫‪1‬‬ ‫‪0, 0, 0) // Black‬‬


‫‪2‬‬ ‫‪(1, 1, 1) // Gray that is so so close from black‬‬
‫‪3‬‬ ‫‪(2, 2, 2) // Gray that is so close from black‬‬
‫‪4‬‬ ‫‪...‬‬
‫‪5‬‬ ‫)‪(128, 128, 128) // Medium gray (to 50 %‬‬
‫‪6‬‬ ‫‪...‬‬
‫‪7‬‬ ‫‪(253, 253, 253) // Gray that is so close from white‬‬
‫‪8‬‬ ‫‪(254, 254, 254) // Gray that is so so close from white‬‬
‫‪9‬‬ ‫‪(255, 255, 255) // White‬‬

‫يجب على أيّ كان أن يعرف أن ّه بحاجة إلى حلقة تكرار ي ّة للقيام بهذا )لن تسعد بتكرار ‪ 256‬سطرا !(‪ .‬و لهذا سنقوم‬
‫بإنشاء جدول من نوع *‪ SDL_Surface‬من ‪ 256‬خانة‪.‬‬

‫إلى العمل‪ .‬لديك ‪ 5‬دقائق !‬

‫تصحيح !‬

‫يجب أولا أن نقوم بتعر يف جدول من ‪ . SDL_Surface* 256‬سنهي ّؤه على ‪: NULL‬‬

‫;}‪1 SDL_Surface �lines[256] = {NULL‬‬

‫سنعرف متغيرا ً ‪ i‬من أجل الحلقات ‪. for‬‬

‫سنغي ّر أيضا ًارتفاع النافذة لـكي تكون مناسبة للعمل‪ .‬إذ سنعطيها ‪ 256‬بيكسلز كارتفاع‪ ،‬و ذلك من أجل عرض كل‬
‫سطر من بين ‪ 256‬سطرا‪.‬‬

‫سنستعمل بعد ذلك حلقة تكرار ية ‪ for‬من أجل حجز مكان لـ‪ 256‬مساحة ال ّتي تم إنشاؤها‪ .‬الجدول سيستقبل ‪256‬‬
‫ل واحد من المساحات المنشأة ‪:‬‬
‫مؤش ّرا إلى ك ّ‬

‫)‪1 for (i = 0 ; i <= 255 ; i++‬‬


‫‪2‬‬ ‫;)‪lines[i] = SDL_CreateRGBSurface(SDL_HWSURFACE, 640, 1, 32, 0,0, 0, 0‬‬

‫‪312‬‬
‫‪ .4.21‬تمرين ‪ :‬إنشاء تدرّج لونيّ‬

‫بعد ذلك نقوم بملء و لصق كل مساحة في الشاشة واحدة بواحدة‪.‬‬

‫)‪1 for (i = 0 ; i <= 255 ; i++‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫)‪position.x = 0; // The lines are to the left (0 abscissa‬‬
‫‪4‬‬ ‫‪position.y = i; // The vertical position depends on the line’s number‬‬
‫‪5‬‬ ‫‪SDL_FillRect(lines[i], NULL, SDL_MapRGB(screen−>format, i, i, i)); //‬‬
‫‪Drawing‬‬
‫‪6‬‬ ‫‪SDL_BlitSurface(lines[i], NULL, screen, &position); // Sticking‬‬
‫} ‪7‬‬

‫لاحظ أنني استعمل كل الوقت المتغير ‪ . position‬إذ ليس لازما أن ننشئ ‪ 256‬واحدا‪ ،‬لأننا لن نقوم إلا ببعث‬
‫المتغير إلى الدالة ‪ . SDL_BlitSurface‬يمكننا إذن إعادة استخدامه دون مشاكل‪.‬‬
‫ل مرة على قيمة‬
‫ل مرة أقوم بالتعديل على الترتيبة ) ‪ ،( y‬لتسو ية المساحة على الارتفاع الصحيح‪ .‬اللون يعتمد في ك ّ‬
‫في ك ّ‬
‫مرة(‪.‬‬
‫مرة و ‪ 255, 255, 255‬في آخر ّ‬
‫المتغير ‪) i‬ستكون ‪ 0, 0, 0‬في أوّل ّ‬

‫؟‬
‫لـكن لماذا قيمة ‪ x‬هي ‪ 0‬دائما ً؟‬
‫كيف يمكن للمساحة أن ٺتلون كليا إذا كانت قيمة الـ ‪ x‬دائما ‪ 0‬؟‬

‫المتغير ‪ position‬يشير إلى أي مكان ٺتواجد فيه المنطقة أعلى اليسار )هنا نتكلم عن السطر(‪ .‬هي لا تحدد عرض‬
‫المساحة و إنما فقط أين ٺتواجد المركّ بة على الشاشة‪.‬‬
‫بما أن كل الأسطر تبدأ في أقصى يسار النافذة‪ ،‬فستكون الفاصلة مساو ية لـ‪ .0‬حاول وضع فاصلة تساوي ‪ 50‬لترى ماذا‬
‫سيعطيك ‪ :‬كل الأسطر ستتنحي إلى اليمين‪.‬‬
‫بما أن المساحة تأخذ ‪ 640‬بيكسل كطول ‪ ،‬فإن الـ‪ SDL‬تقوم بإنشاء ‪ 640‬بيكسلا في اتجاه اليمين )من نفس اللون( إنطلاقا ً‬
‫من المركبات التي يشير إليها المتغير ‪. position‬‬

‫في المخطط التالي أر يك مركبات النقطة المتواجدة أعلى يسار الشاشة )وضعية أول سطر( ومركبات النقطة المتواجدة‬
‫أسفل يسار الشاشة )وضعية آخر سطر(‪.‬‬

‫كما ترى‪ ،‬من الأعلى إلى الأسفل‪ ،‬المحور لا يتغير ) ‪ x‬يبقى مساو يا لـ‪ (0‬بينما ‪ y‬وحده يتغير من أجل كل سطر‬
‫جديد‪ ،‬و بهذا ;‪. position.y = i‬‬

‫أخيرا ً لا تنس أنه يجب تحرير الذاكرة من أجل كل مساحة من الـ‪ 256‬مساحة المنشأة‪ ،‬بمساعدة حلقة بالطبع‪.‬‬

‫‪313‬‬
‫ إنشاء نافذة و مساحات‬.21 ‫الفصل‬

1 for (i = 0 ; i <= 255 ; i++) // Don’t forget to free the 256 surfaces
2 SDL_FreeSurface(lines[i]);

main ‫ملخّ ص‬

: ‫ كاملة‬main ‫هذه هي الدالة‬

1 int main(int argc, char �argv[])


2 {
3 SDL_Surface �screen = NULL, �lines[256] = {NULL};
4 SDL_Rect position;
5 int i = 0;
6 SDL_Init(SDL_INIT_VIDEO);
7 screen = SDL_SetVideoMode(640, 256, 32, SDL_HWSURFACE);
8 for (i = 0 ; i <= 255 ; i++)
9 lines[i] = SDL_CreateRGBSurface(SDL_HWSURFACE, 640, 1, 32,0, 0,
0, 0);
10 SDL_WM_SetCaption(”Mon dégradé en SDL !”, NULL);
11 SDL_FillRect(screen , NULL, SDL_MapRGB(screen −>format, 0, 0, 0));
12 for (i = 0 ; i <= 255 ; i++)
13 {
14 position.x = 0; // The lines are to the left
15 position.y = i; // The vertical position depends on the line’s
number
16 SDL_FillRect(lines[i], NULL, SDL_MapRGB(screen−>format, i, i, i
));
17 SDL_BlitSurface(lines[i], NULL, screen, &position);
18 }
19 SDL_Flip(screen);
20 pause();
21 for (i = 0 ; i <= 255 ; i++) // Don’t forget to free the 256 surfaces
22 SDL_FreeSurface(lines[i]);
23 SDL_Quit();
24 return EXIT_SUCCESS;
25 }

”! ‫”أريد تمارين للتدريب‬

! ‫ مولّد التمارين مُشَغ ّل‬،‫لا مشكلة‬

! ‫ هذا الأمر لن يكون صعبا للبدأ‬.‫ أي من الأبيض للأسود‬،‫• قم بإنشاء تدرج عكسي للألوان‬

‫ من الأبيض للأسود ثم من الأسود للأبيض )ستأخذ النافذة ضعف الارتفاع‬،‫• يمكنك أيضا ً وضع كلى التدرّجين‬
.(‫الحالي‬

.‫ يمكنك وضع تدرج أفقي بدل التدرج العمودي‬،‫• أكثر صعوبة قليلا‬

314
‫ملخّ ص‬

‫• حاول إنشاء تدرج ألوان مختلفة عن الأسود و الأبيض‪ .‬جرب مثلا من الأحمر إلى الأسود‪ ،‬من الأخضر إلى‬
‫الأسود‪ ،‬و من الأزرق إلى الأسود‪ ،‬ثم ّ من الأحمر إلى الأبيض‪ ،‬إلخ‪.‬‬

‫ملخّ ص‬

‫• يتم تحميل الـ ‪ SDL‬بواسطة الـ ‪ SDL_Init‬في بداية البرنامج‪ ،‬و يتم إيقافها باستعمال ‪ SDL_Quit‬في النهاية‪.‬‬

‫• الأعلام هي ثوابت يمكن جمعها فيما بينها باستعمال الرمز | ‪ ،‬و هي تلعب دور الخواص‪.‬‬

‫• تقوم الـ‪ SDL‬بالتعامل مع المساحات و التي هي عبارة عن مستطيلات من نوع ‪ . SDL_Surface‬الرسم على النافذة‬
‫يتم بالاستعانة بهذه المساحات‪.‬‬

‫ل النافذة‪ ،‬و نسميها في أغلب الأحيان الشاشة ) ‪.( screen‬‬


‫• توجد دائما على الأقل مساحة واحدة و التي تحجز ك ّ‬

‫• ملء مساحة يتم باستعمال ‪ ، SDL_FillRect‬ولصقها في الشاشة يتم باستعمال ‪. SDL_BlitSurface‬‬

‫• الألوان معر ّفة بمزيج من الأحمر‪ ،‬الأزرق و الأخضر‪.‬‬

‫‪315‬‬
‫الفصل ‪ .21‬إنشاء نافذة و مساحات‬

‫‪316‬‬
‫الفصل ‪22‬‬

‫إظهار صور‬

‫لقد تعلّمنا كيف نقوم بتحميل الـ‪ ،SDL‬فتح نافذة و التعامل مع المساحات‪ .‬إنها بالفعل من المبادئ التي تجب معرفتها‬
‫عن هذه المكتبة‪ .‬لـكن لح ّد الآن لا يمكننا سوى إنشاء مساحات موحّدة اللون‪ ،‬و هذا الأمر بدائي قليلاً‪.‬‬

‫في هذا الفصل‪ ،‬سنتعل ّم كيف نقوم بتحميل صور على مساحات‪ ،‬مهما كانت صيغتها ‪ PNG ،BMP‬أو حتى ‪ GIF‬أو‬
‫‪ .JPG‬التحكم في الصور أمر مهم للغاية لأنه بتجميع الصور )نسميها أيضا ً”‪ (”sprites‬نضع اللبنات الأولى في بناء لعبة فيديو‪.‬‬

‫‪BMP‬‬ ‫تحميل صورة‬ ‫‪1.22‬‬

‫الـ‪ SDL‬هي مكتبة بسيطة جداً‪ .‬فهي لا تستطيع أساسا تحميل سوى صور من نوع ”‪) ”bitmap‬ذات امتداد ‪.( .bmp‬‬
‫لا تقلق‪ ،‬فبفضل إضافة خاصّة بالـ‪) SDL‬المكتبة ‪ ،(SDL_Image‬سنرى بأنه بإمكاننا أيضا ًتحميل صور من صيغ أخرى‪.‬‬

‫للبدأ‪ ،‬سنكتفي الآن بما تسمح لنا به الـ‪ SDL‬بشكل قاعدي‪ .‬سنقوم بدراسة تحميل صور ‪.BMP‬‬

‫‪BMP‬‬ ‫الصيغة‬

‫الصيغة ‪) BMP‬اختصار لـ‪ (bitmap‬هي صيغة صور‪.‬‬


‫الصور ال ّتي نجدها في الحاسوب مخزّنة في ملفات‪ .‬يوجد العديد من صيغ الصور‪ ،‬أي العديد من الطرق لتخزين صورة في‬
‫ملف‪ .‬على حسب الصيغة‪ ،‬يمكن للصورة أخذ الـكثير أو القليل من مساحة القرص الصلب‪ ،‬و تملك جودة أحسن أو‬
‫أسوء‪.‬‬

‫الـ‪ Bitmap‬هي صيغة غير مضغوطة )على عكس الـ‪ ،GIF ،PNG ،JPG‬إلخ(‪ .‬فعلي ّا‪ ،‬هذا يعني الأمور التالية ‪:‬‬

‫• يكون الملف سر يعا ًجدا ً من ناحية قراءته‪ ،‬على عكس الصيغ المضغوطة التي يجب أن يتم فك الضغط عنها‪ ،‬مما يكل ّفنا‬
‫بعض الوقت‪.‬‬

‫• جودة الصورة مثالية‪ .‬بعض الصيغ المضغوطة )أفك ّر في الـ‪ JPG‬خصوصا‪ ،‬لأن الـ‪ PNG‬و الـ‪ GIF‬لا يغي ّرون في‬
‫الصورة( تقوم بتخريب جودة الصورة‪ ،‬و هذا ليس هو الحال بالنسبة للـ‪.BMP‬‬

‫• لـكنّ الملف سيكون ضخما ًبما أنه ليس مضغوطا ً!‬

‫‪317‬‬
‫الفصل ‪ .22‬إظهار صور‬

‫توجد هناك إذا مزايا و مساوئ‪.‬‬


‫بالنسبة للـ‪ ،SDL‬الشيء الجيد هو أن نوع الملف سيكون بسيطا و سهل القراءة‪ .‬إذا كان عليك تحميل الصور دائما في نفس‬
‫وقت تشغيل برنامجك‪ ،‬من المستحسن استعمال صور بصيغة ‪ .BMP‬سيكون حجم الملف ضخما حتما‪ ،‬لـكنه يـ ُحمّل بشكل‬
‫أسرع من الـ‪ GIF‬مثلاً‪ .‬سيكون الأمر مه ّما إذا كان على برنامجك تحميل الـكثير من الصور في وقت قصير‪.‬‬

‫‪Bitmap‬‬ ‫تحميل صورة‬

‫تنز يل حزمة الصور‬

‫في هذا الفصل سنقوم بالعمل على كثير من الصور‪ .‬إذا أردت القيام بتجريب الشفرات بينما أنت تقرأ )و هذا ما يجدر‬
‫بك فعله !(‪ ،‬فأنصحك بتنز يل حزمة الصور التي تحتوي كل الصور التي نحتاج إليها‪.‬‬

‫)‪https://openclassrooms.com/uploads/fr/ftp/mateo21/pack_images_sdz.zip (1 Mo‬‬

‫بالطبع‪ ،‬يمكنك استعمال صورك الخاصة‪ .‬يجب عليك فقط أن تعدّل مقاييس النافذة على حسب مقاييس الصورة‪.‬‬

‫قم بوضع كل الصور في مجلّد المشروع‪ .‬سنبدأ أولا ّ بالعمل على الصورة ‪ . lac_en_montagne.bmp‬هي عبارة‬
‫‪Vue d’Espri‬‬ ‫عن لقطة تم استخلاصها من مشهد ثلاثي الأبعاد مأخوذ من البرنامج الممتاز الخاص بنمذجة المناظر الطبيعية‬
‫‪ ،4‬و الذي تم إيقاف تسو يقه‪ .‬منذ ذلك‪ ،‬تم ّ تغيير اسم البرنامج إلى ‪ Vue‬و تم تطويره كثيراً‪ .‬لمن يريد معرفة المزيد عنه‪،‬‬
‫يمكنه ز يارة الموقع ‪:‬‬
‫‪.http://www.e-onsoftware.com/‬‬

‫تحميل صورة في مساحة‬

‫سنقوم باستعمال دالة تقوم بتحميل صورة ذات صيغة ‪ BMP‬و لصقها في مساحة‪.‬‬
‫هذه الدالة تدعى ‪ SDL_LoadBMP‬و سترى أن استعمالها سهل للغاية ‪:‬‬

‫;)”‪1 mySurface = SDL_LoadBMP(”image.bmp‬‬

‫الدالة ‪ SDL_LoadBMP‬تقوم بتعو يض دالتين تعرفهما ‪:‬‬

‫• ‪ : SDL_CreateRGBSurface‬تقوم بحجز مكان في الذاكرة من أجل تخزين مساحة ذات الحجم المطلوب )تكافئ‬
‫دالة ‪.( malloc‬‬

‫• ‪ : SDL_FillRect‬تقوم بملئ الهيكل بلون موحّد‪.‬‬

‫لماذا تقوم الدالة بتعو يض هذين الدالتين ؟ الأمر بسيط ‪:‬‬

‫• الحجم الذي نقوم بحجزه في الذاكرة من أجل المساحة يعتمد على حجم الصورة ‪ :‬اذا كان حجم الصورة هو ‪250 × 300‬‬
‫فستأخذ المساحة نفس الحجم‪.‬‬

‫‪318‬‬
BMP ‫ تحميل صورة‬.1.22

.BMP ‫ يتم ملأ المساحة بيكسلا ببيكسل بمحتوى الصورة‬،‫• من جهة أخرى‬

: ‫فلنكتب الشفرة دون أي تأخير‬

1 int main(int argc, char �argv[])


2 {
3 SDL_Surface �screen = NULL, �backgroundImage = NULL;
4 SDL_Rect backgroundPosition;
5 backgroundPosition.x = 0;
6 backgroundPosition.y = 0;
7 SDL_Init(SDL_INIT_VIDEO);
8 screen = SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE);
9 SDL_WM_SetCaption(”Chargement d’images en SDL”, NULL);
10 /� Loading a Bitmap image in a surface �/
11 backgroundImage = SDL_LoadBMP(”lac_en_montagne.bmp”);
12 /� We blit on the screen �/
13 SDL_BlitSurface(backgroundImage, NULL, ecran, &backgroundPosition);
14 SDL_Flip(screen);
15 pause();
16 SDL_FreeSurface(backgroundImage); // We free the surface
17 SDL_Quit();
18 return EXIT_SUCCESS;
19 }

.( backgroundPosition ) ‫ ( و نحو كل المركّبات الموافقة لها‬backgroundImage ) ‫و بهذا أكون قد أنشأت مؤش ّرا ً نحو مساحة‬
. SDL_LoadBMP ‫تم إنشاء المساحة في الذاكرة و ملؤها من طرف الدالة‬
: ‫توضح النتيجة‬
ّ ‫ل شيء ! الصورة التالية‬
ّ ‫ و هذا ك‬screen ‫نقوم بتسويتها على المساحة‬

!ً ‫ لم يكن الأمر صعبا‬،‫كما ترى‬

319
‫الفصل ‪ .22‬إظهار صور‬

‫إرفاق أيقونة بالتطبيق‬

‫بما أننا الآن نجيد تحميل الصور‪ ،‬يمكننا اكتشاف كيفية إرفاق أيقونة بالبرنامج‪ .‬سيتم إظهار الأيقونة في أعلى يسار النافذة )و‬
‫أيضا ًفي شر يط المهام(‪ .‬لح ّد الآن نحن لا نملك إلا ّ أيقونة افتراضي ّة‪.‬‬

‫؟‬
‫لـكن ألا يجدر بأيقونات البرامج أن تكون ذات الامتداد ‪ .ico‬؟‬

‫ل أنظمة‬
‫ل فالامتداد ‪ .ico‬لا يوجد إلا في نظام ‪ .Windows‬الـ‪ SDL‬ٺتعامل مع ك ّ‬ ‫كلّا‪ ،‬ليس شرطا ً ! على ك ّ‬
‫التشغيل باستعمالها نظاما خاصا بها ‪ :‬المساحة !‬
‫نعم‪ ،‬أيقونة برنامج ‪ SDL‬ماهي إلا مساحة بسيطة‪.‬‬

‫!‬
‫يجدر بالأيقونة أن تكون ذات حجم ‪ 16 × 16‬بيكسلز‪ .‬بينما في ‪ Windows‬يجب أن تكون بحجم ‪ 32 × 32‬بيكسلز‬
‫و إلا فستسوء جودتها‪ .‬لا تقلق إذ يمكن للـ‪” SDL‬تصغير” أبعاد الصورة لتتمكن من الدخول في ‪ 16 × 16‬بيكسلز‪.‬‬

‫لإضافة الأيقونة إلى النافذة‪ ،‬نستعمل الدالة ‪. SDL_WM_SetIcon‬‬


‫هذه الدالة تأخذ معاملين ‪ :‬المساحة التي تحتوي الصورة التي نريد إظهارها كما أنها تستقبل معلومات حول الشفافية )القيمة‬
‫‪ NULL‬تعني أننا لا نريد أية شفافية(‪ .‬التحكّم في الشفافية الخاصة بأيقونة معقّد قليلا ً)يجب تحديد البيكسلز الشفافة واحدة‬
‫بواحدة(‪ ،‬لن ندرس ذلك إذا‪.‬‬

‫سنقوم باستدعاء دالة في استدعاء لأخرى ‪:‬‬

‫;)‪1 SDL_WM_SetIcon(SDL_LoadBMP(”sdl_icone.bmp”), NULL‬‬

‫تم تحميل الصورة في الذاكرة بواسطة ‪ SDL_LoadBMP‬و بعث عنوان المساحة مباشرة إلى ‪. SDL_WM_SetIcon‬‬

‫‪x‬‬
‫يجب أن يتم استدعاء الدالة ‪ SDL_WM_SetIcon‬قبل أن يتم فتح النافذة‪ ،‬أي أنه يجدر بها التواجد قبل‬
‫‪ SDL_SetVideoMode‬في الشفرة المصدر ية‪.‬‬

‫هذه هي الشفرة المصدر ية الكاملة‪ .‬ستلاحظ أنني أضفت ‪ SDL_WM_SetIcon‬مقارنة بالشفرة السابقة‪.‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪SDL_Surface �screen = NULL, �backgroundImage = NULL‬‬
‫‪4‬‬ ‫;‪SDL_Rect backgroundPosition‬‬
‫‪5‬‬ ‫;‪backgroundPosition.x = 0‬‬
‫‪6‬‬ ‫;‪backgroundPosition.y = 0‬‬
‫‪7‬‬ ‫;)‪SDL_Init(SDL_INIT_VIDEO‬‬
‫‪8‬‬ ‫‪/� Loading the icon before SDL_SetVideoMode�/‬‬
‫‪9‬‬ ‫;)‪SDL_WM_SetIcon(SDL_LoadBMP(”sdl_icone.bmp”), NULL‬‬
‫‪10‬‬ ‫;)‪screen = SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE‬‬

‫‪320‬‬
‫ التحكم في الشفافية‬.2.22

11 SDL_WM_SetCaption(”Chargement d’images en SDL”, NULL);


12 backgroundImage = SDL_LoadBMP(”lac_en_montagne.bmp”);
13 SDL_BlitSurface(backgroundImage, NULL, screen, &backgroundPosition);
14 SDL_Flip(screen);
15 pause();
16 SDL_FreeSurface(backgroundImage);
17 SDL_Quit();
18 return EXIT_SUCCESS;
19 }

.‫ تم تحميل الصورة و عرضها أعلى يسار النافذة‬: ‫النتيجة‬

‫التحكم في الشفافية‬ 2.22

‫مشكل الشفافية‬

.‫ في النافذة‬bitmap ‫لقد قمنا قبل قليل بتحميل صورة‬


‫ غالبا اللاعب الذي يتحر ّك في الخر يطة هو عبارة‬.‫ و هذا ما يحصل كثيرا ً في الألعاب‬.‫لنفرض أننا نريد لصق صورة فوقها‬
.‫ تتحرك فوق صورة خلفية‬bitmap ‫عن صورة‬

‫ حالي ّا( في المشهد‬OpenClassrooms ‫ سلف الموقع‬Site du Zéro ‫ فهو شعار‬،‫ )لمن لا يعرفه‬Zozor ‫سنقوم بلصق صورة‬
:

1 int main(int argc, char �argv[])


2 {
3 SDL_Surface �screen = NULL, �backgroundImage = NULL, �zozor = NULL;
4 SDL_Rect backgroundPosition, zozorPosition;
5 backgroundPosition.x = 0;
6 backgroundPosition.y = 0;
7 zozorPosition.x = 500;
8 zozorPosition.y = 260;
9 SDL_Init(SDL_INIT_VIDEO);
10 SDL_WM_SetIcon(SDL_LoadBMP(”sdl_icone.bmp”), NULL);
11 screen = SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE);
12 SDL_WM_SetCaption(”Chargement d’images en SDL”, NULL);
13 backgroundImage = SDL_LoadBMP(”lac_en_montagne.bmp”);
14 SDL_BlitSurface(backgroundImage, NULL, ecran, &backgroundPosition);
15 // Loading and blitting Zozor on the screen
16 zozor = SDL_LoadBMP(”zozor.bmp”);
17 SDL_BlitSurface(zozor, NULL, screen, &zozorPosition);
18 SDL_Flip(screen);
19 pause();
20 SDL_FreeSurface(backgroundImage);

321
‫الفصل ‪ .22‬إظهار صور‬

‫‪21‬‬ ‫;)‪SDL_FreeSurface(zozor‬‬
‫‪22‬‬ ‫;)(‪SDL_Quit‬‬
‫‪23‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪24‬‬

‫لقد قمنا فقط بإضافة مساحة لنخز ّن فيها ‪ ،Zozor‬و التي نقوم بلصقها في مكان معيّن من المشهد ‪:‬‬

‫يبدو المشهد سي ّئا‪ ،‬أليس كذلك ؟‬

‫؟‬
‫بالطبع يعود ذلك إلى الخلفية الزرقاء التي هي خلف ‪! Zozor‬‬

‫لأنك تعتقد أنه بوجود خلفية سوداء أو بن ّي ّة‪ ،‬ربما سيكون المظهر لائقا ًأكثر ؟ لا بالطبع‪ ،‬المشكل هنا هو أنه من اللازم‬
‫أن يكون شكل الصورة عبارة عن مستطيل‪ ،‬أي أنه إذا قمنا بلصقها على المشهد‪ ،‬سنرى خلفيتها‪ ،‬مما يشو ّه المظهر‪.‬‬

‫حظ أن الـ‪ SDL‬تتحكم في الشفافية !‬


‫من حسن ال ّ‬

‫جعل صورة شفافة‬

‫الخطوة ‪ : 1‬تحضير الصورة‬

‫كبداية‪ ،‬يجب تحضير الصورة التي نريد تسويتها على المشهد‪.‬‬


‫الصيغة ‪ BMP‬لا تتحكم في الشفافية‪ ،‬على عكس الصيغتين ‪ GIF‬و ‪ .PNG‬لهذا يحب علينا أن نجد حلا ًآخر‪.‬‬

‫يجب استعمال نفس اللون للخلفية على الصورة‪ .‬هذه الأخيرة ستكون شفافة من طرف الـ‪ SDL‬في وقت التسو ية‪.‬‬
‫لاحظ كيف تبدو الصورة ‪ zozor.bmp‬من ناحية أقرب ‪:‬‬

‫‪322‬‬
‫‪ .2.22‬التحكم في الشفافية‬

‫الخلفية الزرقاء إذا مـ ُختارة‪ .‬لاحظ أنني اخترت اللون الأزرق بشكل عشوائي‪ ،‬كان بإمكاني استعمال اللون الأحمر أو‬
‫الأصفر مثلاً‪ .‬الشيء المهم هو أنه يجب على اللون أن يكون وحيدا ً و موحّدا‪ .‬لقد اخترت اللون الأزرق لأنه ليس متواجدا‬
‫في صورة ‪ Zozor‬لأنني لو اخترت اللون الأخضر‪ ،‬سأخاطر بجعل العشب الذي يتناوله الحمار )أسفل يسار الصورة( شفافاً‪.‬‬

‫ل واحد من ّا ذوقه( لإعطاء خلفية موحّدة‬


‫استعمل إذا أي برنامج كان )‪ ... ،The Gimp ،Photoshop ،Paint‬لك ّ‬
‫للصورة‪.‬‬

‫الخطوة ‪ : 2‬تحديد اللون الشفاف‬

‫لـكي نقوم بتحديد اللون الذي يجب أن تجعله ‪ SDL‬شفافاً‪ ،‬يجب أن نستعمل الدالة ‪ . SDL_SetColorKey‬يجب استدعاء‬
‫هذه الدالة قبل تسو ية الصورة‪.‬‬
‫هكذا نقوم بتحو يل اللون الذي خلف ‪ Zozor‬إلى الشفاف ‪:‬‬

‫;))‪1 SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor−>format, 0, 0, 255‬‬

‫هناك ثلاثة معاملات ‪:‬‬

‫• المساحة التي يجب أن نقوم بتحو يلها إلى اللون الشفاف )هنا نتكلم عن ‪.( zozor‬‬

‫• قائمة الأعلام ‪ :‬استعمل ‪ SDL_SRCCOLORKEY‬لتفعيل الشفافية‪ 0 ،‬من أجل تعطيلها‪.‬‬

‫• حدد بعد ذلك اللون الذي يجب أن يتم تحو يله إلى الشفاف‪ .‬لقد استعملت ‪ SDL_MapRGB‬لإنشاء اللون بصيغة‬
‫عدد ) ‪ ( Uint32‬كما فعلنا بالسابق‪ .‬كما ترى إنه اللون الأزرق )‪ (0, 0, 255‬الّذي سيتم تحو يله إلى الشفاف‪.‬‬

‫‪SDL_SetColorKey‬‬ ‫كملخص‪ ،‬نقوم أولا بتحميل الصورة باستعمال ‪ ، SDL_LoadBMP‬ثم ّ نحدد اللون الشفاف باستعمال‬
‫ثم نقوم بتسو ية المساحة باستعمال ‪. SDL_BlitSurface‬‬

‫;)”‪1 zozor = SDL_LoadBMP(”zozor.bmp‬‬


‫;))‪2 SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor−>format, 0, 0, 255‬‬
‫;)‪3 SDL_BlitSurface(zozor, NULL, screen, &zozorPosition‬‬

‫النتيجة ‪ :‬تم دمج صورة ‪ Zozor‬بشكل ممتاز في المشهد ‪:‬‬

‫‪323‬‬
‫الفصل ‪ .22‬إظهار صور‬

‫هذه هي التقنية المبدئية التي ستعيد استعمالها كل الوقت في برامجك‪ .‬تعل ّم كيف تتحكم جيدا ً بالشفافية لأنها من أساسيات‬
‫صنع لعبة تملك الح ّد الأدنى من الواقعية‪.‬‬

‫‪Alpha‬‬ ‫الشفافية‬

‫هو نوع آخر من الشفافية‪.‬‬


‫لح ّد الآن قمنا بتعر يف لون واحد شفاف )الأزرق مثلا(‪ .‬هذا اللون لا يظهر في الصورة المُلصقة‪.‬‬

‫الشفافية ‪ Alpha‬توافق شيئا ًآخر‪ ،‬إنها تسمح بعمل ”مزج” بين صورة و خلفية‪ .‬هذا نوع من التلاشي‪.‬‬

‫يمكن تفعيل الشفافية ‪ Alpha‬لمساحة عن طر يق الدالة ‪: SDL_SetAlpha‬‬

‫;)‪1 SDL_SetAlpha(zozor, SDL_SRCALPHA, 128‬‬

‫يوجد هنا ثلاثة معاملات كذلك ‪:‬‬

‫• المساحة التي نتكلم عنها ) ‪.( zozor‬‬

‫• قائمة الأعلام ‪ :‬ضع ‪ SDL_SRCALPHA‬من أجل تفعيل الشفافية‪ 0 ،‬من أجل تعطيلها‪.‬‬

‫• مهم جدا ‪ :‬قيمة الشفافية ‪ Alpha‬هي عدد يتراوح بين ‪) 0‬صورة شفافة تماما ًأي غير مرئية( و ‪) 255‬صورة ظاهرة‬
‫كلياً‪ ،‬و كأن الشفافية ‪ Alpha‬لم تكن موجودة(‪.‬‬

‫كلما كان العدد ‪ Alpha‬صغيرا ً كلما زادت شفافية الصورة و تلاشيها في الخلفية‪.‬‬

‫هذا مثال عن شفرة تقوم بتطبيق شفافية بقيمة ‪ 128‬على الصورة ‪: Zozor‬‬

‫‪324‬‬
‫ التحكم في الشفافية‬.2.22

1 zozor = SDL_LoadBMP(”zozor.bmp”);
2 SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor−>format, 0, 0, 255));
3 /� Average Alpha transparency (128) : �/
4 SDL_SetAlpha(zozor, SDL_SRCALPHA, 128);
5 SDL_BlitSurface(zozor, NULL, screen, &zozorPosition);

.ً‫ يمكن دمج النوعين الاثنين للشفافية معا‬. SDL_SetColorKey ‫تلاحظ أنني حافظت على شفافية‬

.Alpha ‫ باختلاف قيم‬Zozor ‫يوضح لك كيف يبدو‬


ّ ‫الجدول التالي‬

‫النتيجة‬ Alpha

(‫ )مرئي ّة بالكامل‬255

190

(‫ )شفافي ّة متوسّ طة‬128

75

(‫ )غير مرئي ّة بالكامل‬0

325
‫الفصل ‪ .22‬إظهار صور‬

‫م‬
‫قيمة الشفافية ‪) 128 Alpha‬شفافية متوسطة( هي قيمة خاصّة و كثيرة الإستعمال بالـ‪ .SDL‬هذا النمط من الشفافية‬
‫أسرع من ناحية حسابات المعالج مقارنة بالأنماط الأخرى‪ .‬قد يكون من المهم لك معرفة هذه المعلومة خاصة إن‬
‫كنت تستعمل الشفافية ‪ Alpha‬بشكل كبير في برامجك‪.‬‬

‫الـ‪SDL_Image‬‬ ‫تحميل صيغ صور أخرى باستعمال‬ ‫‪3.22‬‬

‫الـ‪ SDL‬لا ٺتعامل إلا مع الـ‪) bitmap‬الصيغة ‪ (BMP‬كما رأينا‪.‬‬


‫و لـكن هذا ليس بمشكل لأن قراءة الصور ذات الصيغة ‪ BMP‬أسرع بالنسبة للـ‪ ،SDL‬و لـكن يجب معرفة أنه في أيامنا‬
‫هذه يتم استعمال صيغ أخرى للصور‪ .‬بالتحديد الصيغ ”المضغوطة” كالـ‪ ،PNG‬الـ‪ GIF‬و الـ‪ .JPEG‬لهذا الغرض توجد مكتبة‬
‫تسمى ‪ SDL_Image‬و تقوم بالتعامل مع كل صيغ الصور التالية ‪:‬‬

‫• ‪،TGA‬‬
‫• ‪،BMP‬‬
‫• ‪،PNM‬‬
‫• ‪،XPM‬‬
‫• ‪،XCF‬‬
‫• ‪،PCX‬‬
‫• ‪،GIF‬‬
‫• ‪،JPG‬‬
‫• ‪،TIF‬‬
‫• ‪،LBM‬‬
‫• ‪.PNG‬‬

‫بالمناسبة فإنه بالإمكان أن تتم إضافة صيغ أخرى للـ‪ .SDL‬و هي المكتبات التي تحتاج إلى الـ‪ SDL‬لـكي تعمل‪ .‬يمكننا‬
‫تسمية هذا الأمر بالـ‪) add-ons‬بمعنى ”إضافات”(‪ SDL_Image .‬هي واحدة من بين هذه المكتبات‪.‬‬

‫‪Windows‬‬ ‫ٺثبيت الـ‪ SDL_Image‬على‬

‫التنز يل‬

‫توجد صفحة خاصة من موقع الـ‪ SDL‬تشير إلى المكتبات التي تستعملها الـ‪ .SDL‬هذه الصفحة تحمل عنوان ”‪.”Libraries‬‬
‫ستجد رابطا ًفي القائمة اليسار ية‪.‬‬
‫ستلاحظ أن هناك الـكثير من المكتبات و أغلبها ليس من طرف المبرمجـين الأصليين للـ‪ .SDL‬بل هم مبرمجون عاديون‬
‫يستعملون الـ‪ SDL‬و يقومون باقتراح مكتباتهم الخاصة لتحسين هذه الأخيرة‪.‬‬

‫‪326‬‬
‫الـ‪SDL_Image‬‬ ‫‪ .3.22‬تحميل صيغ صور أخرى باستعمال‬

‫ل جودة بل ربّما فيه أخطاء‪ .‬لهذا يجب ترتيب‬


‫بعض هذه المكتبات مفيد جدا ً و يستحق إلقاء النظر عليه‪ ،‬و بعضها أق ّ‬
‫هذه المكتبات حسب أهميتها‪.‬‬
‫حاول إ يجاد ‪ SDL_Image‬في القائمة‪ ،‬ستدخل إلى الصفحة المخصصة لهذه المكتبة ‪:‬‬
‫‪https://www.libsdl.org/projects/SDL_image‬‬

‫نز ّل النسخة التي تناسبك من القسم ”‪) ”Binary‬لا تحم ّل الملفات المصدر ية‪ ،‬لن نحتاجها !(‪.‬‬
‫إذا كنت تعمل على ‪ ،Windows‬نز ّل الملف ‪ ، SDL_image-devel-1.2.10-VC.zip‬و هذا حتى و إن لم تكن‬
‫تستعمل البيئة التطوير ية ‪! Visual C++‬‬

‫التثبيت‬

‫في الملف ‪ .zip‬هذا‪ ،‬ستجد ‪:‬‬

‫• ‪ : SDL_image.h‬الملف الرأسي الوحيد الذي تحتاجه الـ‪ ،SDL_Image‬قم بلصقه في المسار‬


‫‪C:\Program Files\CodeBlocks\SDL-1.2.13\include‬‬

‫بمعنى آخر‪ ،‬إلى جانب الملفات الرأسية للـ‪.SDL‬‬


‫• ‪ : SDL_image.lib‬قم بلصقه في المسار‬
‫‪. C:\Program Files\CodeBlocks\SDL-1.2.13\lib‬‬
‫أعرف أنك ستخبرني بأن الملفات ذات الامتداد ‪ .lib‬هي محجوزة للبيئة التطوير ية ‪ ،Visual C++‬لـكن هذه‬
‫حالة استثنائية‪ ،‬فالملف ‪ .lib‬يعمل حتى مع المترجم ‪.mingw‬‬

‫• الـكثير من الملفات ‪ : DLL‬قم بوضعها كلها في المجلّد الخاص بالمشروع )أي بجانب الملف ‪.( SDL.dll‬‬

‫بعد ذلك‪ ،‬يجدر بك تغيير خواص المشروع من أجل محر ّر الروابط )‪ (Linker‬للملف ‪. SDL_image.lib‬‬

‫‪Linker‬‬ ‫إذا كنت تعمل بالـ‪ Code::Blocks‬مثلاً‪ ،‬توجّه إلى القائمة ‪ ، Build options / Projects‬في الفرع‬
‫أنقر على الزر ‪ Add‬و اختر المسار الذي يتواجد به الملف ‪ ، SDL_image.lib‬لاحظ الصورة ‪:‬‬

‫‪327‬‬
‫الفصل ‪ .22‬إظهار صور‬

‫إذا ظهرت لك رسالة تحمل سؤال ‪ ،”Keep as a relative path ?” :‬فلتجب بما أردت لأنه لن يغير شيئا ً في الوقت‬
‫الحالي‪ .‬أنصحك بالإجابة بالسلب‪ ،‬شخصي ّا‪.‬‬

‫بعد ذلك‪ ،‬ما عليك سوى تضمين الملف الرأسي ‪ SDL_image.h‬في الشفرة المصدر ية‪ .‬على حسب المكان الذي‬
‫وضعت فيه الملف ‪ SDL_image.h‬سيكون عليك استعمال هذه الشفرة ‪:‬‬
‫>‪1 #include <SDL/SDL_image.h‬‬

‫أو هذه‬
‫>‪1 #include <SDL_image.h‬‬

‫جرّبهما كليهما‪ ،‬يجدر بأحداهما أن تعمل‪.‬‬


‫م‬
‫إذا كنت تعمل بالـ‪ Visual Studio‬فستكون العملي ّة نفسها‪ .‬لأنه إن تمكنت من ٺثبيت الـ‪ SDL‬لن يصعب عليك‬
‫ٺثبيت الـ‪.SDL_Image‬‬

‫‪Mac OS X‬‬ ‫ٺثبيت الـ‪ SDL_Image‬على‬

‫إن كنت تستعمل ‪ ،Mac OS X‬نز ّل الملف ذا الامتداد ‪ .dmg‬من موقع الـ‪ SDL‬و ضعه في المجلد‬
‫‪. Library/Frameworks‬‬
‫اكتب بعد ذلك ”‪ ”search paths‬في حقل البحث الخاص بـ‪ .Xcode‬اعثر على السطر‬
‫‪ ، Header search paths‬انقر مرتين على السطر من اليمين و أضف‬
‫‪. /Library/Frameworks/SDL_image.framework/Headers‬‬
‫لم يبق لك سوى إضافة إطار العمل إلى المشروع‪ .‬الصورة التالية توضح لك كيف يظهر‬
‫الـ ‪ Header search paths‬بعد ٺثبيت الـ‪.SDL_image‬‬

‫و بهذا يجب عليك تضمين الملف الرأسي في بداية الـكود كالتالي ‪:‬‬
‫”‪1 #include ”SDL_image.h‬‬

‫في عوض استعمال الإشارتين > < قم باستعمال الكتابة السابقة فيما سأعطيك لاحقا‪.‬‬

‫‪328‬‬
SDL_Image‫الـ‬ ‫ تحميل صيغ صور أخرى باستعمال‬.3.22

‫تحميل الصور‬

! ‫ أصعب بمئة مرة من استعمالها ! إنه عليك أنت تحديد صعوبة العمل بالمكتبة‬SDL_image‫الحقيقة أن ٺثبيت الـ‬

. IMG_Load : ‫توجد دالة وحيدة عليك معرفتها‬


.‫ اسم الملف الذي نريد فتحه‬: ‫و هي تستقبل معاملا واحدا‬

،PNG ،JPG) SDL_image‫و هذا أمر عملي لأن هذه الدالة تتمكن من تحميل أي نوع من الملفات التي ٺتعامل معهاالـ‬
.‫ إذ تقوم وحدها بتحديد نوع الملف من خلال امتداده‬.(‫ إلخ‬،TIF‫ و حتى الـ‬GIF

‫؟‬
SDL_LoadBMP ‫ فيمكنك الآن نسيان أمر استعمال الدالة‬،BMP ‫ تستطيع أيضا ًفتح الصور‬SDL_Image‫بما أن الـ‬
.‫ لتحميل كل أنواع الصور‬IMG_Load ‫و استعمال الدالة‬

SDL_Image ‫ن‬
ّ ‫( فإ‬GIF ‫ و‬PNG ‫ إذا كانت الصورة التي تحمّلها تملك الشفافية )كما هو حال الصور‬: ‫شيء جيد آخر‬
ٍ ‫تفع ّل تلقائي ّا الشفافية من أجل هذه الصورة ! مما يعني عدم وجود‬
. SDL_SetColorKey ‫داع لاستدعاء الدالة‬

.‫ و إظهارها‬sapin.png ‫سأٌقدّم لك الشفرة المصدر ية التي تقوم بتحميل الصورة‬


‫ لأن الصورة‬SDL_SetColorKey ‫ كما أنني لا استدعي الدالة‬SDL/SDL_image.h ‫لاحظ جيدا أنني قمت بتضمين‬
.‫ التي استعملها شفافة طبيعي ّا‬PNG
. SDL_LoadBMP ‫ل مكان بالشفرة و ذلك بتعو يض الدالة‬
ّ ‫ في ك‬IMG_Load ‫سترى أنني أستعمل الدالة‬

1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <SDL/SDL.h>
4 /� including the header of SDL_image (adapt to your directory) �/
5 #include <SDL/SDL_image.h>
6 void pause();
7 int main(int argc, char �argv[])
8 {
9 SDL_Surface �screen = NULL, �backgoundImage = NULL, �sapin = NULL;
10 SDL_Rect backgoundPosition, sapinPosition;
11 backgoundPosition.x = 0;
12 backgoundPosition.y = 0;
13 sapinPosition.x = 500;
14 sapinPosition.y = 260;
15 SDL_Init(SDL_INIT_VIDEO);
16 SDL_WM_SetIcon(IMG_Load(”sdl_icone.bmp”), NULL);
17 screen = SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE);
18 SDL_WM_SetCaption(”Chargement d’images en SDL”, NULL);
19 backgoundImage = IMG_Load(”lac_en_montagne.bmp”);
20 SDL_BlitSurface(backgoundImage, NULL, screen, &backgoundPosition);
21 /� Loading a PNG image with IMG_Load
22 We won’t have any problem because the PNG image contains the
transparency information inside �/

329
‫ إظهار صور‬.22 ‫الفصل‬

23 sapin = IMG_Load(”sapin.png”);
24 SDL_BlitSurface(sapin, NULL, screen, &sapinPosition);
25 SDL_Flip(screen);
26 pause();
27 SDL_FreeSurface(backgroundImage);
28 SDL_FreeSurface(sapin);
29 SDL_Quit();
30 return EXIT_SUCCESS;
31 }
32 void pause()
33 {
34 int cont = 1;
35 SDL_Event event;
36 while (cont)
37 {
38 SDL_WaitEvent(&event);
39 switch(event.type)
40 {
41 case SDL_QUIT:
42 cont = 0;
43 }
44 }
45 }

: ‫ فقد تم دمج الصورة مع الخلفية بشكل ممتاز‬،‫كما يمكننا الملاحظة‬

‫ملخّ ص‬

‫ باستعمال‬BMP ‫ هي تسمح بالتعامل مع الصور ذات الصيغة‬،‫ افتراضي ّا‬.‫ بتحميل صور على مساحات‬SDL‫• تسمح الـ‬
. SDL_LoadBMP ‫الدالة‬

330
‫ملخّ ص‬

‫• يمكننا تعر يف لون شفاف باستعمال الدالة ‪. SDL_SetColorKey‬‬

‫• يمكننا جعل الصورة أكثر أو أقل شفافية و ذلك باستعمال الدالة ‪. SDL_SetAlpha‬‬

‫• المكتبة ‪ SDL_image‬تسمح بإدخال صور من أي ّة صيغة كانت )‪ ( ... ،PNG ،JPG‬باستعمال الدالة ‪. IMG_Load‬‬
‫لـكن علينا تسطيب هذه المكتبة بالإضافة إلى الـ‪.SDL‬‬

‫‪331‬‬
‫الفصل ‪ .22‬إظهار صور‬

‫‪332‬‬
‫الفصل ‪23‬‬

‫معالجة الأحداث )‪(Event handling‬‬

‫معالجة الأحداث هو من أهم الأساسيات في الـ‪.SDL‬‬


‫و ربّما قد يكون الشطر الأكثر شغفا ًلاكتشافه‪ .‬لأنه انطلاقا من هنا ستبدأ فعلا ًفي التحكّم في تطبيقك‪.‬‬

‫ل من مرفقات الحاسوب )فأرة‪ ،‬لوحة مفاتيح‪ ( ... ،‬قادرة على إنتاج حدث‪ .‬سنتعل ّم كيف نستقبل كل حدث‬
‫ك ّ‬
‫و نتعامل معه‪ .‬تطبيقك سيصبح أخيرا ً تفاعلي ّا !‬

‫فعلياً‪ ،‬ما هو الحدث ؟ الحدث هو عبارة عن إشارة )‪ (signal‬يتم إرسالها عن طر يق إحدى مرفقات الحاسوب‬
‫)‪) (peripherals‬أو عن طر يق نظام التشغيل بذاته( إلى التطبيق‪ .‬هذه أمثلة عن بعض الأحداث المألوفة ‪:‬‬

‫• حينما يضغط المُستعمل على زر من لوحة المفاتيح‪.‬‬

‫• و أيضا ًحينما ينقر بالفأرة‪.‬‬

‫• حينما يحر ّك الفأرة‪.‬‬


‫• حينما يقوم بتصغير النافذة‪.‬‬

‫• حينما يطلب إغلاق النافذة‪.‬‬


‫• إلى آخره‪.‬‬

‫الهدف من هذا الفصل هو تعل ّم كيفية معالجة الأحداث‪ .‬يمكنك أخيرا ً القول للحاسوب ‪” :‬إذا نقر المستعمل في هذا‬
‫المكان‪ ،‬قم بفعل كذا‪ ،‬و إن لم يفعل‪ ،‬قم بكذا‪ .‬إذا حرّك الفأرة‪ ،‬قم بكذا‪ .‬إذا ضغط على الزر ‪ ، Q‬أوقف البرنامج‪ .‬إلخ”‪.‬‬

‫مبدأ عمل الأحداث‬ ‫‪1.23‬‬

‫ج حينما يقوم‬
‫لنتعو ّد على الأحداث‪ ،‬سنتعل ّم كيف نتعامل مع أسهل حدث ‪ :‬طلب غلق البرنامج‪ .‬هذا حدث يـُنت ُ‬
‫المستعمل بالنقر على الزر ‪: X‬‬

‫‪333‬‬
‫الفصل ‪ .23‬معالجة الأحداث )‪(Event handling‬‬

‫إنه فعلا ً الحدث الأكثر سهولة‪ .‬إضافة على ذلك‪ ،‬هو حدث قد استعملته سابقا ً دون أن تعلم بذلك لأنه متواجد في‬
‫الدالة ‪! pause‬‬
‫بالفعل‪ ،‬دور هذه الدالة هو انتظار المستعمل حت ّى يقررّ غلق البرنامج‪ ،‬لأننا لو لم نستعملها كانت النافذة لتظهر و تختفي بسرعة‬
‫البرق !‬

‫م‬
‫يمكنك من الآن نسيان الدالة ‪ . pause‬قم بحذفها من الشفرة المصدر ية لأننا سنتعل ّم كيف نكتب محتواها بأنفسنا‪.‬‬

‫متغي ّر الحدث‬

‫لمعالجة الأحداث‪ ،‬ستحتاج إلى التصريح عن متغي ّر )واحد فقط‪ ،‬كن متأكدا( من نوع ‪. SDL_Event‬‬
‫فلتقم بتسميته بالاسم الذي يحلو لك‪ ،‬أنا سأسم ّيه ‪ ، event‬و هي تعني ”حدث” بالإنجليز ي ّة‪.‬‬
‫;‪1 SDL_Event event‬‬

‫من أجل اختبارات الشفرة‪ ،‬سنستعمل دالة ‪ main‬بسيطة للغاية تقوم بإظهار نافذة فقط‪ ،‬مثلما رأينا في الفصول‬
‫الأولى‪ .‬هذا ما يجب أن تبدو عليه الدالة ‪: main‬‬
‫)][‪1 int main(int argc, char �argv‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;‪SDL_Surface �screen = NULL‬‬
‫‪4‬‬ ‫‪SDL_Event event; // This variable will help us to manage the events‬‬
‫‪5‬‬ ‫;)‪SDL_Init(SDL_INIT_VIDEO‬‬
‫‪6‬‬ ‫;)‪screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE‬‬
‫‪7‬‬ ‫;)‪SDL_WM_SetCaption(”Gestion des événements en SDL”, NULL‬‬
‫‪8‬‬ ‫;)(‪SDL_Quit‬‬
‫‪9‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪10‬‬

‫إذا‪ ،‬هي شفرة بدائية جداً‪ ،‬و هي لا تحوي سوى شيء جديد ‪ :‬تعر يف المتغير ‪ event‬الذي سنستعين به قريباً‪.‬‬

‫جرّب الشفرة ‪ :‬مثلما توق ّعنا‪ ،‬يجدر بالنافذة أن تظهر و تختفي في لحظة‪.‬‬

‫حلقة الأحداث‬

‫حينما نريد انتظار حدث‪ ،‬نستعمل غالبا ًحلقة‪ .‬هذه الحلقة التكرار ية تستمر في الاشتغال ماد ُمنا لم نستقبل الحدث المـ ُراد‪.‬‬
‫يجب علينا أن نستعمل متغيرا ً منطقيا ًلـكي يحدد لنا ما إن كان علينا البقاء في الحلقة أو الخروج منها‪.‬‬
‫أنشئ هذا المتغير و سم ّه مثلا ‪: 1 cont‬‬

‫‪ 1‬إذا كنت تفك ّر في تسميته ‪ continue‬فلا تفعل‪ ،‬لأنّها كلمة مفتاحي ّة )محجوزة(‪ ،‬و بالتالي لا يمكن استخدامها كاسم لمتغي ّر‪.‬‬

‫‪334‬‬
‫‪ .1.23‬مبدأ عمل الأحداث‬

‫;‪1 int cont = 1‬‬

‫هذا المتغير المنطقي يأخذ القيمة ‪ 1‬في البداية لأننا نريد للحلقة أن ٺتكرر مادام المتغير ‪ cont‬يحمل هذه القيمة )صحيح(‪.‬‬
‫ما إن يأخذ المتغير المنطقي القيمة ‪) 0‬خطأ(‪ ،‬نخرج من الحلقة و يتوقف البرنامج‪.‬‬

‫هذا ما تبدو عليه الحلقة ‪:‬‬

‫)‪1 while (cont‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪// Dealing with the event‬‬
‫} ‪4‬‬

‫هكذا إذا ً ‪ :‬لدينا لح ّد الآن حلقة غير منتهية لا تنتهي إلا إذا أخذ المتغير ‪ cont‬القيمة ‪ .0‬الأكثر أهمية هو ما نكتبه‬
‫في داخل تلك الحلقة‪.‬‬

‫استرجاع الحدث‬

‫الآن سنقوم باستدعاء دالة من الـ‪ SDL‬لـكي نتحقق ما إن تم إنتاج حدث‪.‬‬


‫لدينا دالتان للقيام بهذا العمل‪ ،‬لـكن كلا منه ُما تعمل بطر يقة مختلفة عن الأخرى ‪:‬‬

‫• ‪ : SDL_WaitEvent‬تقوم بانتظار إنتاج حدث‪ .‬هذه الدالة نقول عنها تعطيلية لأنها توقف عمل البرنامج مادام لم‬
‫يتم إنتاج أي حدث‪.‬‬

‫• ‪ : SDL_PollEvent‬هذه الدالة تقوم بنفس العمل لـكنها ليست تعطيلية‪ .‬لأنها ُ‬


‫تخـبرنا ما إن تم إنتاج حدث أم‬
‫لا‪ ،‬فإن لم يكن هناك أي حدث فإنها تعيد التحكّم إلى البرنامج مباشرة‪.‬‬

‫هاتان الدالّتان مهمّتان‪ ،‬لـكن في حالتين مختلفتين‪.‬‬


‫لتبسيط الأمور‪ ،‬إذا استعملت ‪ SDL_WaitEvent‬فإن برنامجك لن يـُتعب كثيرا ً المـ ُعالج لأنه سيتوقف مـُنتظرا ً إنتاج‬
‫حدث‪.‬‬
‫بالمـ ُقابل‪ ،‬إذا استعملت ‪ ، SDL_PollEvent‬سيقوم البرنامج بالعمل على الحلقة ‪ while‬و استدعاء الدالة ‪SDL_PollEvent‬‬

‫بشكل غير معر ّف إلى حين إنتاج حدث م ُعين‪ .‬و بهذا تستعمل المُعالج بنسبة ‪.% 100‬‬

‫؟‬
‫لـكن ألا يجب أن نستعمل دائما ًالدالة ‪ SDL_WaitEvent‬بما أنها لا تستعمل المـ ُعالج كثيرا ً ؟‬

‫كلّا‪ ،‬لأنه توجد حالات لا يمكن الاستغناء فيها عن الدالة ‪ . SDL_PollEvent‬و هي حالة الألعاب التي يتم فيها‬
‫تحديث الشاشة حتى و إن لم يكن هناك أي حدث‪.‬‬
‫فلنأخذ مثلا ًاللعبة ‪ : Tetris‬تقوم الكتل بالنزول لوحدها‪ ،‬لا يحتاج المُستعمل إلى إنتاج حدث من أجل حصول هذا الأمر‬
‫! لو استعملنا ‪ ، SDL_WaitEvent‬سيبقى البرنامج مـ ُعطّلا و لن تتمكّن من تحديث الشاشة لإنزال الكتل !‬

‫‪335‬‬
‫الفصل ‪ .23‬معالجة الأحداث )‪(Event handling‬‬

‫؟‬
‫ماذا تفعل ‪ SDL_WaitEvent‬لـكي لا تستهلك من الـم ُعالج كثيرا ً ؟‬
‫ل الوقت ما إن كان هناك حدث أم لا‪،‬‬
‫مجـبرة على البقاء في حلقة غير منتهية لـكي تختبر ك ّ‬
‫فبعد كل شيء‪ ،‬الدالة ُ‬
‫أليس كذلك ؟‬

‫خص الطر يقة التي يتحكّم فيها النظام‬


‫الحقيقة أنني كنت أطرح هذا السؤال قبل وقت قليل‪ .‬الإجابة معقّدة قليلا ًلأنها ت ّ‬
‫بالعملي ّات )‪) (Processes‬البرامج التي هي في طور الاشتغال(‪.‬‬
‫إذا كنت تريد ‪-‬لـكن ّي سأتحدّث بسرعة‪ ،-‬بالنسبة للدالة ‪ ، SDL_WaitEvent‬عملي ّة البرنامج تُوضع في طور الانتظار‪.‬‬
‫إذا فإن البرنامج لا يعمل عليه المعالج بعد تلك اللحظة‪.‬‬
‫سيتم ”إيقاظه” من طرف نظام التشغيل حينما يتم إنتاج حدث‪ .‬يعني أن المعالج سيعود إلى العمل على البرنامج في هذه‬
‫اللحظة‪ .‬هذا ما يشرح لم َِا لا يستهلك البرنامج من المعالج شيئا بينما يكون في طور انتظار الحدث‪.‬‬

‫مجـبرا ً على فهم كل هذا الآن لأنك ستبدأ في التأقلم مع‬


‫أدري أن هذه المفاهيم تبدو مجر ّدة لك الآن‪ .‬لـكنك لست ُ‬
‫هذه المعلومات شيئا ًفي شيئا ًمع التطبيق‪.‬‬
‫الآن سنستعمل ‪ SDL_WaitEvent‬لأن البرنامج سيبقى بسيطا باستخدامها‪ .‬على أي حال فالتعامل مع هاتين الدالتين لن‬
‫يتغير من واحدة إلى أخرى‪.‬‬

‫يجب أن تبعث للدالة عنوان المتغير ‪ event‬الذي يقوم بتخزين الحدث‪.‬‬


‫بما أن هذا المتغير ليس عبارة عن مؤش ّر )أعد رؤ ية طر يقة التصريح به أعلاه(‪ ،‬سنستعمل الاشارة & قبل اسم المتغير و‬
‫ذلك لن ُعطي عنوانه ‪:‬‬
‫;)‪1 SDL_WaitEvent(&event‬‬

‫بعد استدعاء هذه الدالة‪ ،‬المتغير ‪ event‬يحتوي إجبار يا ًحدثا ًما‪.‬‬

‫م‬
‫هذه الحالة ليست نفسها لو استعملنا ‪ SDL_PollEvent‬لأن هذه الأخيرة قادرة على أن ت ُرجع لنا ‪” :‬لا يوجد‬
‫أي حدث”‪.‬‬

‫تحليل الحدث‬

‫الآن نحن نتوفر على متغير ‪ event‬يحتوي على معلومات حول الحدث الذي تم إنتاجه‪.‬‬
‫يجب أن نرى المركّ ب ‪ event.type‬و نختبر قيمته‪ .‬غالبا ما نستعمل ‪ switch‬لاختبار الحدث‪.‬‬

‫؟‬
‫لـكن كيف لنا أن نعرف ما هي القيمة الموافقة للحدث ”أغلق البرنامج” مثلا ؟‬

‫الـ‪ SDL‬توف ّر لنا بعض الثوابت‪ ،‬مما يسهّل كثيرا ً كتابة البرنامج‪ .‬هذه الثوابت كثيرة العدد )بقدر وجود أحداث ممكن‬
‫حصولها في الحاسوب(‪ .‬سنتعر ّف على هذه الثوابت بتقدّمنا في هذا الفصل‪.‬‬

‫‪336‬‬
‫‪ .1.23‬مبدأ عمل الأحداث‬

‫)‪1 while (cont‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫”‪SDL_WaitEvent(&event); // Getting the event in ”event‬‬
‫‪4‬‬ ‫‪switch(event.type) // Testing the event’s type‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫‪case SDL_QUIT: // If it’s a quit event‬‬
‫‪7‬‬ ‫;‪cont = 0‬‬
‫‪8‬‬ ‫;‪break‬‬
‫‪9‬‬ ‫}‬
‫} ‪10‬‬

‫هكذا تعمل الشفرة ‪:‬‬

‫‪ .1‬ما إن يتم انتاج حدث‪ ،‬ت ُرجع الدالة ‪ SDL_WaitEvent‬الحدث في المتغير ‪. event‬‬

‫‪ .2‬نقوم بتحليل نوع الحدث بالاستعانة بـ ‪ . switch‬نوع الحدث موجود في ‪. event.type‬‬

‫‪ .3‬نختبر بمساعدة ‪ case‬نوع الحدث‪ .‬لح ّد الآن‪ ،‬نحن لا نتحقق إلا إذا ما كان الحدث يوافق ‪) SDL_QUIT‬طلب‬
‫إغلاق البرنامج(‪ ،‬لأنّها الحالة الوحيدة التي تهمّنا‪.‬‬

‫‪ .4‬إذا كان الحدث هو ‪ ، SDL_QUIT‬فهذا يعني أن المستعمل طلب إغلاق البرنامج‪ .‬في هذه الحالة‪ ،‬نعطي للمتغير‬
‫المنطقي ‪ cont‬القيمة ‪ .0‬في الدورة القادمة للحلقة‪ ،‬سيكون الشرط غير محقق‪ ،‬فيتوقف تشغيل البرنامج‪.‬‬

‫‪ .5‬إذا لم يكن الحدث هو ‪ ، SDL_QUIT‬مما يعني أنه قد حدث شيء آخر ‪ :‬قام المستعمل بالضغط على زر‪ ،‬بالنقر على‬
‫الفأرة أو ببساطة قام بتحر يك الفأرة داخل النافذة‪ .‬و بما أن هذه الأحداث لا تهمّنا‪ ،‬لن نقوم بمعالجتها‪ .‬لن نقوم‬
‫ل مرة إلى دورة جديدة ننتظر فيها وقوع حدث جديد )بمعنى آخر‪ ،‬نعود‬
‫إذا بأي شيء ‪ :‬تقوم الحلقة بالانتقال في ك ّ‬
‫إلى النقطة ‪.(1‬‬

‫ل شيء و سيكون باقي الفصل سهلا ً‬


‫ما أنا أشرحه لك الآن هو أمر مهم جداً‪ .‬إذا فهمت هذه الشفرة‪ ،‬فقد فهمت ك ّ‬
‫للغاية‪.‬‬

‫الشفرة الكاملة‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪SDL_Surface �screen = NULL‬‬
‫‪4‬‬ ‫‪SDL_Event event; // The event’s variable‬‬
‫‪5‬‬ ‫‪int cont = 1; // A boolean for the loop‬‬
‫‪6‬‬ ‫;)‪SDL_Init(SDL_INIT_VIDEO‬‬
‫‪7‬‬ ‫;)‪screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE‬‬
‫‪8‬‬ ‫;)‪SDL_WM_SetCaption(”Gestion des événements en SDL”, NULL‬‬
‫‪9‬‬ ‫‪while (cont) // While the variable’s value is‬‬
‫‪10‬‬ ‫{‬ ‫‪// not equal to 0‬‬
‫‪11‬‬ ‫‪SDL_WaitEvent(&event); // We wait for an event that we‬‬
‫”‪recuperate in ”event‬‬

‫‪337‬‬
‫الفصل ‪ .23‬معالجة الأحداث )‪(Event handling‬‬

‫‪12‬‬ ‫‪switch(event.type) // Testing the event’s type‬‬


‫‪13‬‬ ‫{‬
‫‪14‬‬ ‫‪case SDL_QUIT: // If it’s a quit event‬‬
‫‪15‬‬ ‫‪cont = 0; // We change the boolean value so we go out‬‬
‫‪from the loop.‬‬
‫‪16‬‬ ‫;‪break‬‬
‫‪17‬‬ ‫}‬
‫‪18‬‬ ‫}‬
‫‪19‬‬ ‫;)(‪SDL_Quit‬‬
‫‪20‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪21‬‬

‫ل‬
‫هاهي الشفرة الكاملة‪ .‬لا يوجد شيء صعب ‪ :‬إذا قمت بمتابعة الفصل إلى الآن‪ ،‬يجدر بك أن تكون قد فهمت ك ّ‬
‫شيء‪ .‬على أي حال فقد لاحظت أنّنا لم نقم إلا بإعادة كتابة ما تقوم به الدالة ‪ . pause‬قارن هذه الشفرة بما تقوم به‬
‫ل شيء في الدالة ‪ . main‬بالطبع‪ ،‬من المستحسن‬
‫الدالة ‪ : pause‬هو نفس الشيء‪ ،‬إلا أنه في هذه الحالة نقوم بوضع ك ّ‬
‫نقل الشفرة إلى دالة أخرى على حدى كـ ‪ ، pause‬لأن ذلك سيقل ّل من حجم الدالة ‪ main‬و يجعلها أفضل من ناحية‬
‫فهم الشفرة‪.‬‬

‫لوحة المفاتيح‬ ‫‪2.23‬‬

‫سنقوم الآن بدراسة الأحداث التي تـُنتج عن طر يق لوحة المفاتيح‪.‬‬

‫إذا فهمت بداية الفصل‪ ،‬فلن تواجه أي مشكل في التعامل مع أي نوع من الأحداث‪ .‬لا يوجد ماهو أسهل‪.‬‬

‫لماذا هذا سهل ؟ لأننا الآن فهمنا طر يقة عمل الحلقة التكرار ية غير المنتهية‪ ،‬كل ما ستقوم بفعله هو إضافة بعض الحالات‬
‫إلى الـ ‪ switch‬من أجل تحليل أنواع أخرى من الأحداث‪ .‬لا يفترض أن يكون هذا الأمر صعباً‪.‬‬

‫أحداث لوحة المفاتيح‬

‫يوجد نوعان من الأحداث التي يمكن توليدها عن طر يق لوحة المفاتيح ‪:‬‬

‫• ‪ : SDL_KEYDOWN‬حينما يتم بدأ الضغط على زر من لوحة المفاتيح‪.‬‬

‫• ‪ : SDL_KEYUP‬حينما يتحرر زر لوحة المفاتيح‪.‬‬

‫لماذا يوجد حدثان إثنان ؟‬


‫لأننا حينما نضغط على زر‪ ،‬يحدث أمران ‪ :‬ش ّد الزر إلى الأسفل ) ‪ ( SDL_KEYDOWN‬ثم ّ تحريره ) ‪.( SDL_KEYUP‬‬
‫تسمح لنا الـ‪ SDL‬بتحليل كل من هذين الحدثين على حدى‪ ،‬و هذا أمر عملي جداً‪ ،‬سترى ذلك‪.‬‬

‫لح ّد الآن سنكتفي بتحليل الحدث ‪) SDL_KEYDOWN‬الضغط على الزر( ‪:‬‬

‫‪338‬‬
‫‪ .2.23‬لوحة المفاتيح‬

‫)‪1 while (cont‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;)‪SDL_WaitEvent(&event‬‬
‫‪4‬‬ ‫)‪switch(event.type‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫‪case SDL_QUIT:‬‬
‫‪7‬‬ ‫;‪cont = 0‬‬
‫‪8‬‬ ‫;‪break‬‬
‫‪9‬‬ ‫‪case SDL_KEYDOWN: // If we press a button‬‬
‫‪10‬‬ ‫;‪cont = 0‬‬
‫‪11‬‬ ‫;‪break‬‬
‫‪12‬‬ ‫}‬
‫} ‪13‬‬

‫إذا ضغطنا على أي زر سيتوقف البرنامج‪ ،‬جرّب ذلك !‬

‫استرجاع رمز الزر‬

‫معرفة أنه تم الضغط على زر من لوحة المفاتيح هو أمر جيد‪ ،‬لـكن معرفة أي الأزرار تم الضغط عليه بالضبط هو أمر‬
‫أحسن !‬

‫يمكننا معرفة الزر الذي تم الضغط عليه بفضل مركّ ب مركّ ب مركّ ب المتغي ّر )أوف !( و الذي يُدعى ‪. event.key.keysym.sym‬‬
‫هذا المتغير يحتوي قيمة الزر الذي تم الضغط عليه )و هو يعمل حتى في الحـين الذي نحرر فيه الزر ‪.( SDL_KEYUP‬‬

‫م‬
‫الشيء الجيد هو أن الـ‪ SDL‬تسمح باسترجاع هذه القيمة من كل أزرار لوحة المفاتيح و التي ٺتضمّن على الحروف‬
‫و الأرقام‪ ،‬و كذلك الأزرار ‪ ... ، Enter ، Del ، Print scr. ، Esc‬إلخ‪.‬‬

‫يوجد ثابت من أجل كل زر في اللوحة‪ .‬يمكنك الاطلاع على قائمة هذه الثوابت من خلال الملفات التوثيقي ّة الخاصة‬
‫بالـ‪ ،SDL‬ال ّتي من المفترض أن ّك قد نزلتها مع المكتبة ‪.SDL‬‬
‫إن لم تفعل‪ ،‬فأنصحك بالتوجه إلى موقع المكتبة و تحميل هذه الملفات لأنها مهمّة للغاية‪.‬‬

‫ستجد قائمة أزرار لوحة المفاتيح في القسم ”‪ .”Keysym definitions‬هذه القائمة طو يلة جدا ً و لا يمكنني تقديمها هنا و‬
‫لهذا عليك تصفح التوثيق من الموقع مباشرة‪.‬‬
‫‪http://www.siteduzero.com/uploads/fr/ftp/mateo21/sdlkeysym.html‬‬

‫محررة باللغة الانجليز ية‪ ،‬و هي غير متوفرة بلغة أخرى‪ .‬إذا كنت تريد البرمجة حقّا‪ ،‬فمن الواجب أن تجيد‬
‫هذه الملفات ُ‬
‫ل الملفات التوثيقي ّة مكتوبة بها‪ ،‬فلا يمكنك أبدا تجاوزها !‬
‫نك ّ‬
‫هذه اللغة لأ ّ‬

‫يوجد في اللائحة جدولان ‪ :‬واحد كبير )في البداية( و آخر صغير )في النهاية(‪ .‬نحن الآن نهتم بالجدول الأكبر‪.‬‬
‫في العمود الأول تجد الثابت‪ ،‬في العمود الثاني تجد القيمة الموافقة له بالـ‪ ASCII‬و أخيرا في العمود الثالث تجد وصفا ًللزر‪.‬‬
‫لاحظ أن بعض الأزرار كـ ‪ ( Shift ) Maj‬لا تملك قيمة ‪ ASCII‬موافقة لها‪.‬‬

‫فلنأخذ مثلا ًالزر ‪ . Esc‬يمكننا معرفة ما إن كان هذا الزر مضغوطا ًكالتالي ‪:‬‬

‫‪339‬‬
‫الفصل ‪ .23‬معالجة الأحداث )‪(Event handling‬‬

‫)‪1 switch (event.key.keysym.sym‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪case SDLK_ESCAPE: // Pressing Escape button lets us quit the program‬‬
‫‪4‬‬ ‫;‪cont = 0‬‬
‫‪5‬‬ ‫;‪break‬‬
‫} ‪6‬‬

‫م‬
‫أستعمل ‪ switch‬من أجل الاختبار الأول لـكن كان بإمكاني استعمال ‪ if‬ببساطة‪.‬‬
‫ل مرة أميل إلى الاستعانة بالـ ‪ switch‬حينما أعالج الأحداث لأنني أختبر الـكثير من القيم المختلفة )عملياً‪،‬‬
‫في ك ّ‬
‫يتوفر لدينا الـكثير من الحالات في الـ ‪ ، switch‬على عكس هذا المثال(‪.‬‬

‫هذه حلقة حدث كاملة يمكنك تجريبها ‪:‬‬


‫)‪1 while (cont‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;)‪SDL_WaitEvent(&event‬‬
‫‪4‬‬ ‫)‪switch(event.type‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫‪case SDL_QUIT:‬‬
‫‪7‬‬ ‫;‪cont = 0‬‬
‫‪8‬‬ ‫;‪break‬‬
‫‪9‬‬ ‫‪case SDL_KEYDOWN:‬‬
‫‪10‬‬ ‫)‪switch (event.key.keysym.sym‬‬
‫‪11‬‬ ‫{‬
‫‪12‬‬ ‫‪case SDLK_ESCAPE:‬‬
‫‪13‬‬ ‫;‪cont = 0‬‬
‫‪14‬‬ ‫;‪break‬‬
‫‪15‬‬ ‫}‬
‫‪16‬‬ ‫;‪break‬‬
‫‪17‬‬ ‫}‬
‫} ‪18‬‬

‫م‬
‫هذه المرة‪ ،‬يتوقف البرنامج حينما نضغط على الزر ‪ Esc‬أو إذا نقرنا على الرمز ‪ X‬أعلى النافذة‪ .‬و الآن بما أنك‬
‫تعرف كيف تغلق البرنامج بالضغط على زر معين‪ ،‬أنت مـ ُخو ّل لاستعمال وضع الشاشة الكاملة إذا كان هذا ممتعا‬
‫لك )استعمل العـَلم ‪ SDL_FULLSCREEN‬في الـ ‪ ، SDL_SetVideoMode‬كتذكير(‪.‬‬
‫سابقا ً كنت قد منعتك من استعمال هذا الأسلوب في العرض خشية أننا لن نتمكن من غلق البرنامج )لأنه لن‬
‫يظهر لنا زرّ الإغلاق الّذي نضغط عليه لإيقاف البرنامج !(‬

‫تمرين ‪ :‬تحر يك ‪ Zozor‬بواسطة لوحة المفاتيح‬ ‫‪3.23‬‬

‫‪340‬‬
‫‪ .3.23‬تمرين ‪ :‬تحر يك ‪ Zozor‬بواسطة لوحة المفاتيح‬

‫أنت الآن قادر على تحر يك صورة في النافذة بواسطة لوحة المفاتيح !‬
‫م جدا ً سيسمح لنا بالتعرف على كيفية استعمال الـ‪ double buffering‬و الاستعمال المتكرر للأزرار‪.‬‬
‫هذا تمرين مه ّ‬
‫إضافة إلى ذلك‪ ،‬ما أنا بصدد تعليمه لك هو قاعدة كل ألعاب الفيديو التي تُصنع بالـ‪ .SDL‬و لهذا فإن هذا التمرين ليس‬
‫اختيار يا ! أدعوك لقراءته و محاولة حلّه بشكل جدّي‪.‬‬
‫تحميل الصورة‬

‫في البداية‪ ،‬سنقوم بتحميل صورة‪ .‬سيكون الأمر بسيطا ً‪ :‬سنعيد استعمال صورة ‪ Zozor‬المُستعملة في الفصل السابق‪.‬‬
‫أنشئ المساحة ‪ ، zozor‬حم ّل الصورة و حو ّل خلفيتها إلى اللون الشفاف )أذكرك بأن صيغة الصورة هي ‪.(BMP‬‬
‫;)”‪1 zozor = SDL_LoadBMP(”zozor.bmp‬‬
‫;))‪2 SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor−>format, 0, 0, 255‬‬

‫بعد ذلك‪ ،‬الشيء الأكثر أهمية‪ ،‬يجب عليك إنشاء متغير من نوع ‪ SDL_Rect‬لتقوم بح ِفظ مركبات ‪.Zozor‬‬
‫;‪1 SDL_Rect positionZozor‬‬

‫أنصحك بإعطاء قيم ابتدائية للمركبات‪ ،‬ضع مثلا ‪ x = 0‬و ‪) y = 0‬الوضعية أعلى يسار النافذة( أو قم بـ ِمـَرْكَـز َة‬
‫‪ Zozor‬في وسط النافذة كما قمت بتعليمك هذا من قبل‪.‬‬
‫‪1 // We center Zozor in the screen‬‬
‫;‪2 zozorPosition.x = screen−>w / 2 − zozor−>w / 2‬‬
‫;‪3 zozorPosition.y = screen−>h / 2 − zozor−>h / 2‬‬

‫‪x‬‬
‫يجب عليك تهيئة المتغير ‪ zozorPosition‬بعد تحميل المساحتين ‪ screen‬و ‪ . zozor‬في الواقع‪ ،‬سأستعمل‬
‫العرض ‪ w‬و الارتفاع ‪ h‬لهاتين المساحتين من أجل حساب الموقع المركزي لـ‪ Zozor‬في الشاشة‪ ،‬و لهذا كان‬
‫لازما أن يتم تهيئة هاتين المساحتين من قبل‪.‬‬

‫إذا كنت قد تدب ّرت أمرك جي ّدا‪ ،‬يجدر أن يظهر ‪ Zozor‬في وسط النافذة‪.‬‬

‫‪341‬‬
‫الفصل ‪ .23‬معالجة الأحداث )‪(Event handling‬‬

‫لقد اخترت هذه المرة وضع خلفية بيضاء )قمت بـ ‪ ( SDL_FillRect‬لـكن هذا ليس واجباً‪.‬‬

‫مخطط البرمجة بالأحداث‬

‫حينما تقوم ببرمجة برنامج يتفاعل مع الأحداث )كما سنقوم بفعله الآن(‪ ،‬يجب عليك اتّباع نفس ”المخطط” في غالب‬
‫الأحيان‪.‬‬
‫يجدر بك حفظ هذا المخطط عن ظهر قلب ‪:‬‬

‫)‪1 while (cont‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;)‪SDL_WaitEvent(&event‬‬
‫‪4‬‬ ‫)‪switch(event.type‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫‪// Managing the events of type SOMETHING‬‬
‫‪7‬‬ ‫‪case SDL_SOMETHING:‬‬
‫‪8‬‬ ‫‪// Managing the events of type ANOTHERTHING‬‬
‫‪9‬‬ ‫‪case SDL_ANOTHERTHING:‬‬
‫‪10‬‬ ‫}‬
‫‪11‬‬ ‫‪// We clear the screen‬‬
‫‪12‬‬ ‫;))‪SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 255, 255, 255‬‬
‫‪13‬‬ ‫‪// We do all the necessary SDL_BlitSurface to past the surfaces on the‬‬
‫‪screen‬‬
‫‪14‬‬ ‫‪// We update the display‬‬
‫‪15‬‬ ‫;)‪SDL_Flip(screen‬‬
‫} ‪16‬‬

‫م السطور التي تكو ّن الحلقة الرئيسية في برنامج ‪.SDL‬‬


‫سأقدّم لك أه ّ‬
‫سنستمر في تشغيل الحلقة مادام لم يتم طلب غلق البرنامج‪.‬‬

‫‪ .1‬ننتظر حدثا ً ) ‪ ( SDL_WaitEvent‬أو نقوم بالتحقق من وجود حدث لـكن لا نقوم بانتظار حدوث واحد‬
‫) ‪ .( SDL_PollEvent‬حاليا نكتفي باستعمال ‪. SDL_WaitEvent‬‬

‫‪ .2‬نستعمل ‪) switch‬كبير( من أجل معرفة نوع الحدث الذي نحن نتعامل معه‪ .‬نقوم بتحليل الحدث الّذي تلقّيناه‬
‫ثم نقوم ببعض الحسابات و العمليات‪.‬‬

‫‪ .3‬ما إن نخرج من الـ ‪ ، switch‬نحض ّر عرضا جديدا لنقوم بإظهاره‪.‬‬

‫‪ .4‬أول شيء نفعله ‪ :‬نمسح الشاشة باستعمال ‪ . SDL_FillRect‬إن لم نقم بذلك‪ ،‬ستبقى بعض ”آثار” الشاشة‬
‫السابقة في الشاشة الحالية مما يشوش المظهر‪.‬‬

‫‪ .5‬نقوم بعد ذلك بلصق كل المساحات على الشاشة‪.‬‬

‫‪ .6‬أخيرا‪ ،‬ما إن ننتهي من كل ذلك‪ ،‬نقوم بتحديث العرض من أجل المستعمل و ذلك باستدعاء الدالة ‪. SDL_Flip‬‬

‫‪342‬‬
‫‪ .3.23‬تمرين ‪ :‬تحر يك ‪ Zozor‬بواسطة لوحة المفاتيح‬

‫‪SDL_KEYDOWN‬‬ ‫معالجة الحدث‬

‫لنرى الآن كيف نعالج الحدث ‪. SDL_KEYDOWN‬‬


‫هدفنا هو تحر يك ‪ Zozor‬بواسطة لوحة المفاتيح باستعمال الأسهم التوجيهية‪ .‬و لهذا سنقوم بتغيير مركباته في الشاشة بدلالة‬
‫السهم الذي يضغط عليه المستعمل ‪:‬‬

‫)‪1 switch(event.type‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫‪case SDL_QUIT:‬‬
‫‪4‬‬ ‫;‪cont = 0‬‬
‫‪5‬‬ ‫;‪break‬‬
‫‪6‬‬ ‫‪case SDL_KEYDOWN:‬‬
‫‪7‬‬ ‫)‪switch(event.key.keysym.sym‬‬
‫‪8‬‬ ‫{‬
‫‪9‬‬ ‫‪case SDLK_UP: // Up arrow‬‬
‫‪10‬‬ ‫;‪zozorPosition.y−−‬‬
‫‪11‬‬ ‫;‪break‬‬
‫‪12‬‬ ‫‪case SDLK_DOWN: // Down arrow‬‬
‫‪13‬‬ ‫;‪zozorPosition.y++‬‬
‫‪14‬‬ ‫;‪break‬‬
‫‪15‬‬ ‫‪case SDLK_RIGHT: // Right arrow‬‬
‫‪16‬‬ ‫;‪zozorPosition.x++‬‬
‫‪17‬‬ ‫;‪break‬‬
‫‪18‬‬ ‫‪case SDLK_LEFT: // Left arrow‬‬
‫‪19‬‬ ‫;‪zozorPosition.x−−‬‬
‫‪20‬‬ ‫;‪break‬‬
‫‪21‬‬ ‫}‬
‫‪22‬‬ ‫;‪break‬‬
‫} ‪23‬‬

‫أين وجدت أسماء الثوابت ؟ لقد وجدتها في التوثيق !‬


‫لقد اعطيتك قبل قليل الرابط الخاص بالتوثيق الّذي يعطي لائحة كل الأزرارفي لوحة المفاتيح ‪ :‬هنا وجدت ضال ّتي‪.‬‬

‫ما فعلناه هنا هو أمر بسيط جدا ً ‪:‬‬

‫• إذا ضغطنا على السهم ”أعلى”‪ ،‬نقوم بإنقاص الترتيبة ) ‪ ( y‬الخاصة بـ‪ Zozor‬ببيكسل واحد من أجل جعله يصعد‪.‬‬
‫ل مرة‪.‬‬
‫مجـبرين على تحر يكه ببيكسل واحد‪ ،‬يمكننا تحر يكه بـ‪ 10‬بيكسل في ك ّ‬
‫لاحظ أننا لسنا ُ‬

‫• إذا توجهنا إلى الأسفل‪ ،‬سنقوم بز يادة الترتيبة ) ‪ ( y‬لـ‪.Zozor‬‬


‫• إذا توجهنا لليمين نزيد قيمة الفاصلة ) ‪.( x‬‬
‫• إذا توجهنا لليسار نقوم بإنقاص الفاصلة ) ‪.( x‬‬

‫و الآن ؟‬
‫بما أنني أعطيتك التوجيهات و حتى المخطط‪ ،‬يجدر بك أن تكون قادرا ً على كتابة الشفرة التي تسمح بتحر يك ‪ Zozor‬في النافذة‬
‫!‬

‫‪343‬‬
(Event handling) ‫ معالجة الأحداث‬.23 ‫الفصل‬

1 int main(int argc, char �argv[])


2 {
3 SDL_Surface �screen = NULL, �zozor = NULL;
4 SDL_Rect zozorPosition;
5 SDL_Event event;
6 int cont = 1;
7 SDL_Init(SDL_INIT_VIDEO);
8 screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
9 SDL_WM_SetCaption(”Gestion des événements en SDL”, NULL);
10 // Loading Zozor
11 zozor = SDL_LoadBMP(”zozor.bmp”);
12 SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor−>format, 0, 0,
255));
13 zozorPosition.x = screen−>w / 2 − zozor−>w / 2;
14 zozorPosition.y = screen−>h / 2 − zozor−>h / 2;
15 while (cont)
16 {
17 SDL_WaitEvent(&event);
18 switch(event.type)
19 {
20 case SDL_QUIT:
21 cont = 0;
22 break;
23 case SDL_KEYDOWN:
24 switch(event.key.keysym.sym)
25 {
26 case SDLK_UP: // Up arrow
27 zozorPosition.y−−;
28 break;
29 case SDLK_DOWN: // down arrow
30 zozorPosition.y++;
31 break;
32 case SDLK_RIGHT: // Right arrow
33 zozorPosition.x++;
34 break;
35 case SDLK_LEFT: // Left arrow
36 zozorPosition.x−−;
37 break;
38 }
39 break;
40 }
41 // Clear the screen
42 SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 255, 255,
255));
43 // We put zozor in its new position
44 SDL_BlitSurface(zozor, NULL, screen, &zozorPosition);
45 // Update the display
46 SDL_Flip(screen);
47 }

344
‫‪ .3.23‬تمرين ‪ :‬تحر يك ‪ Zozor‬بواسطة لوحة المفاتيح‬

‫‪48‬‬ ‫;)‪SDL_FreeSurface(zozor‬‬
‫‪49‬‬ ‫;)(‪SDL_Quit‬‬
‫‪50‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪51‬‬

‫إنه من الضروري جدا ً الفهم الجيد لـكيفية عمل الحلقة الرئيسية في البرنامج‪ .‬يجب أن تكون قادرا ً على كتابتها بنفسك‪.‬‬
‫استعن بالمخطط الذي أعطيتك إياه أعلاه إذا اقتضت الحاجة‪.‬‬

‫‪cont‬‬ ‫باختصار إذا‪ ،‬توجد حلقة كبيرة تُسمى ”الحلقة الرئيسية في البرنامج”‪ .‬و هي لا ٺتوقف إلا إذا أعطينا للمتغير‬
‫القيمة ‪.0‬‬
‫في هذه الحلقة‪ ،‬نقوم أولا باسترجاع حدث لنقوم بمعالجته‪ .‬نستعمل ‪ switch‬من أجل تحديد نوع الحدث‪ .‬بدلالة‬
‫الحدث‪ ،‬نقوم بعمليات مختلفة‪ .‬في حالتنا هذه‪ ،‬نقوم بتحديث مركبات وضعية ‪ Zozor‬من أجل إعطاء انطباع أننا نقوم‬
‫بتحر يكه‪.‬‬

‫ثم بعد الـ ‪ switch‬يجب عليك تحديث الشاشة كالتالي ‪:‬‬

‫‪ .1‬أولاً‪ ،‬نمسح الشاشة باستعمال ‪) SDL_FillRect‬باستعمال لون خلفية يناسبك(‪.‬‬

‫‪ .2‬ثم تقوم بتسو ية المساحات على الشاشة‪ .‬هنا‪ ،‬لم أحتج إلى لصق إلا ‪ Zozor‬لأننا لا نحتاجه إلا هو كما هو واضح‪ ،‬و‬
‫من المهم جدا ً أن نضع ‪ Zozor‬في الموضع ‪ ! zozorPosition‬لان هذا ما يصنع الفارق ‪ :‬إذا كنتُ قد حدّثتُ‬
‫‪ zozorPosition‬كان ‪ Zozor‬ليظهر في مكان آخر و بهذا نعتقد أننا غي ّرنا مكانه كليا !‬

‫‪ .3‬أخيراً‪ ،‬و آخر شيء للقيام به ‪ SDL_Flip :‬لـكي نحدّث الشاشة من أجل المستعمل‪.‬‬

‫و بهذا نتمكن من تحر يك الحيوان إلى أي مكان نريده !‬

‫‪345‬‬
‫الفصل ‪ .23‬معالجة الأحداث )‪(Event handling‬‬

‫بعض التحسينات‬

‫تكرار الضغط على الأزرار‬

‫مجـب َرون على الضغط من جديد‬


‫لحد الآن‪ ،‬البرنامج يعمل لـكن ّه يُلزم تحر ّك اللاعب أن يكون بيكسلا في المر ّة الواحدة‪ .‬نحن ُ‬
‫على الأسهم إذا أردنا التحرك مرة أخرى ببيكسل‪ .‬لا أدري بالنسبة لك‪ ،‬لـكنني أستمتع أحيانا بالبقاء ضاغطا على نفس‬
‫الزر وقتا أطول لـكي أحرك اللاعب بـ‪ 200‬بيكسل‪.‬‬

‫حظ أن الدالة ‪ SDL_EnableKeyRepeat‬موجودة !‬


‫على كل حال‪ ،‬من حسن ال ّ‬
‫‪SDL_KEYDOWN‬‬ ‫هذه الدالة تسمح بتفعيل الضغط المتكرر على الأزرار‪ .‬فهي تحر ّض الـ‪ SDL‬على إعادة إنتاج حدث من نوع‬
‫إذا بقي المستعمل ضاغطا ًعلى نفس الزر لمدة من الزمن‪.‬‬

‫يمكنك استدعاء هذه الدالة أينما أردت‪ ،‬لـكنني أنصحك باستدعائها قبل الحلقة الرئيسية للبرنامج‪ .‬يمكن للدالة أخذ‬
‫معاملين ‪:‬‬

‫• المدة )بالميلّي ثانية( التي يجدر بالزر أن يبقى فيها مضغوطا ًقبل تفعيل تكرار الضغط على الأزرار‪.‬‬

‫• الأجل )بالميلّي ثانية( بين كل إنتاج لحدث ‪ SDL_KEYDOWN‬و آخر ما إن يتم تفعيل تكرار الضغط على الأزرار‪.‬‬

‫المعامل الأول يشير إلى مدة الزمن التي يجدر بنا بعدها إنتاج تكرار الضغط على الأزرار في المرة الأولى‪ .‬أما الثاني فيشير‬
‫إلى الوقت اللازم ليتم إعادة إنتاج الحدث‪.‬‬
‫شخصياً‪ ،‬و من أجل أسباب ليونة التحرك‪ ،‬أعطي غالبا ًنفس القيمة للمعاملين‪ .‬جرّب القيمة ‪ 10‬ميلّي ثانية ‪:‬‬

‫;)‪1 SDL_EnableKeyRepeat(10, 10‬‬

‫ن هذا أحسن !‬
‫الآن‪ ،‬يمكنك البقاء ضاغطا ًعلى نفس السهم‪ .‬سترى أ ّ‬

‫بالـ‪double buffering‬‬ ‫العمل‬

‫انطلاقا من الآن‪ ،‬سيكون من الجيد تفعيل تقنية الـ‪ double buffering‬الخاصة بالـ‪ .SDL‬الـ‪ double buffering‬هي تقنية‬
‫مستعملة بكثرة في الألعاب‪ .‬تسمح هذه التقني ّة بتجنب التقطع في الصورة‪.‬‬
‫لـِم َا ٺتقطع الصورة ؟ لأنه حينما نرسم في الشاشة‪ ،‬المستعمل ”يرى” كيف ترسم و بهذا يرى كيف تُمسح الشاشة‪ .‬حتى‬
‫وإن جرت العملية بسرعة فإن مخ ّ الإنسان يلتقط إشارات خفيفة و قد تكون مزعجة‪.‬‬

‫تقنية الـ‪ double buffering‬تعمل على استخدام ”شاشتين” ‪ :‬واحدة حقيقية )التي يراها المستعمل في شاشة الحاسوب(‬
‫و أخرى افتراضية )هي صورة يقوم الحاسوب بإنشائها في الذاكرة(‪.‬‬

‫هاتان الشاشتان ٺتناوبان ‪ :‬الشاشة ‪ A‬تظهر في حين تحض ّر الشاشة ‪ B‬الصورة القادمة في الخلفية‪ .‬لاحظ الصورة التالية ‪:‬‬

‫‪346‬‬
‫‪ .3.23‬تمرين ‪ :‬تحر يك ‪ Zozor‬بواسطة لوحة المفاتيح‬

‫ما إن يتم رسم الصورة في الشاشة الخلفية )الشاشة ‪ ،(B‬نقوم بقلب الشاشتين و ذلك باستدعاء الدالة ‪. SDL_Flip‬‬

‫الشاشة ‪ A‬تصبح شاشة خلفية و تقوم بتحضير الصورة القادمة‪ ،‬بينما يتم إظهار الصورة في الشاشة ‪ B‬و يراها المستعمل‪.‬‬
‫النتيجة ‪ :‬لا يوجد تقطع في الصورة !‬

‫لتحقيق هذا كل ما عليك فعله هو تحميل وضع العرض بإضافة الع َلم ‪: SDL_DOUBLEBUF‬‬

‫;)‪1 screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF‬‬

‫لا يوجد شيء آخر لتغييره في الشفرة المصدر ية‪.‬‬

‫م‬
‫الـ‪ double buffering‬هي تقنية معروفة جدا ً في البطاقة الرسومية )‪ (Graphics card‬للحاسوب‪ .‬ما أقصده هو أن‬
‫ل شيء‪ ،‬و يتم ذلك بسرعة ج ّد فائقة‪.‬‬
‫الجهاز هو من يتحكم في ك ّ‬

‫ستتساءل ربّما لماذا كنا قد استعملنا ‪ SDL_Flip‬من قبل دون الـ‪ double buffering‬؟‬
‫الواقع أن لهذه الدالة وظيفتين ‪:‬‬

‫• إذا كانت الـ‪ double buffering‬مفع ّلة‪ ،‬فستتحكم في تناوب الشاشتين‪.‬‬

‫• أما إن كانت غير مفع ّلة‪ ،‬فهي تتحكم في تحديث النافذة يدو يا‪ .‬هذه التقنية تعمل في حالة كان البرنامج لا يقدّم حركية‬
‫كبيرة‪ ،‬و لـكن في غالبية الألعاب‪ ،‬أنصحك بتفعيلها‪.‬‬

‫‪347‬‬
(Event handling) ‫ معالجة الأحداث‬.23 ‫الفصل‬

‫ سأقوم بتفعيل هذه التقنية في كل الشفرات المصدر ية التي أكتبها )لأنها لا تكلف الـكثير و تقدم‬،ً‫من الآن و صاعدا‬
(‫ فمما نشكي ؟‬،‫الـكثير‬

‫ إنها مشابهة‬.‫ و تكرار الضغط على الأزرار‬double buffering‫إليك الشفرة المصدر ية الكاملة التي تسمح باستعمال الـ‬
: ‫ لقد قمت فقط بإضافة بعض التعليمات التي نحن بصدد تعلّمها‬،‫للشفرة التي رأيناها قبل قليل‬
1 int main(int argc, char �argv[])
2 {
3 SDL_Surface �screen = NULL, �zozor = NULL;
4 SDL_Rect zozorPosition;
5 SDL_Event event;
6 int cont = 1;
7 SDL_Init(SDL_INIT_VIDEO);
8 zozorPosition = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE |
SDL_DOUBLEBUF); // Double buffering
9 SDL_WM_SetCaption(”Gestion des événements en SDL”, NULL);
10 zozor = SDL_LoadBMP(”zozor.bmp”);
11 SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor−>format, 0, 0,
255));
12 zozorPosition.x = screen−>w / 2 − zozor−>w / 2;
13 zozorPosition.y = screen−>h / 2 − zozor−>h / 2;
14 SDL_EnableKeyRepeat(10, 10); // Enabling keys repetition
15 while (cont)
16 {
17 SDL_WaitEvent(&event);
18 switch(event.type)
19 {
20 case SDL_QUIT:
21 cont = 0;
22 break;
23 case SDL_KEYDOWN:
24 switch(event.key.keysym.sym)
25 {
26 case SDLK_UP:
27 zozorPosition.y−−;
28 break;
29 case SDLK_DOWN:
30 zozorPosition.y++;
31 break;
32 case SDLK_RIGHT:
33 zozorPosition.x++;
34 break;
35 case SDLK_LEFT:
36 zozorPosition.x−−;
37 break;
38 }
39 break;
40 }
41 SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 255, 255,
255));

348
‫‪ .4.23‬الفأرة‬

‫‪42‬‬ ‫;)‪SDL_BlitSurface(zozor, NULL, screen, &zozorPosition‬‬


‫‪43‬‬ ‫;)‪SDL_Flip(screen‬‬
‫‪44‬‬ ‫}‬
‫‪45‬‬ ‫;)‪SDL_FreeSurface(zozor‬‬
‫‪46‬‬ ‫;)(‪SDL_Quit‬‬
‫‪47‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪48‬‬

‫‪ 4.23‬الفأرة‬

‫ربما تعتقد أن التحكّم في الفأرة أمر أكثر تعقيدا ً من التحكم في لوحة المفاتيح ؟‬
‫كلا‪ ،‬بل حت ّى أن الأمر أسهل‪ ،‬سترى !‬

‫سترى بأن الفأرة يمكن لها أن تُنتج ثلاثة أنواع مختلفة من الأحداث‪.‬‬

‫• ‪ SDL_MOUSEBUTTONDOWN‬حينما ننقر بالفأرة‪ ،‬و هذا الحدث يوافق اللحظة الذي يكون فيه زر الفأرة مضغوطاً‪.‬‬

‫• ‪ : SDL_MOUSEBUTTONUP‬حينما نحرر زر الفأرة‪ .‬كل هذا يعمل وفقا ً لنفس المبدأ التي تعمل به أزرار لوحة‬
‫المفاتيح ‪ :‬يوجد ضغط للزر ثم تحرير لهذا الأخير‪.‬‬

‫• ‪ : SDL_MOUSEMOTION‬حينما نقوم بتحر يك الفأرة‪ .‬في كل مرة تقوم فيها الفأرة بالتحرك في النافذة )هذا لا يتم‬
‫إلا بيكسلا ببيكسل(‪ ،‬يتم إنتاج الحدث ‪! SDL_MOUSEMOTION‬‬

‫سنبدأ أولا بالعمل على النقر على الفأرة و بشكل خاص على ‪ . SDL_MOUSEBUTTONUP‬لن نعمل مع‬
‫الـ ‪ ، SDL_MOUSEBUTTONDOWN‬لـكنك تعرف بأن الطر يقة لا تختلف إلا أن الحدث الأخير يُنتج قبل الحدث الآخر‪.‬‬
‫سنعلم لاحقا ًكيف نتعامل مع الحدث ‪. SDL_MOUSEMOTION‬‬

‫معالجة نقرات الفأرة‬

‫سنقوم إذا باستقبال حدث من نوع ‪) SDL_MOUSEBUTTONUP‬النقر بالفأرة( ثم نرى ما يمكننا استرجاعه من معلومات‪.‬‬
‫كالعادة‪ ،‬يجدر بنا إضافة حالة ‪ case‬في الـ ‪ switch‬كالتالي ‪:‬‬

‫)‪1 switch(event.type‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫‪case SDL_QUIT:‬‬
‫‪4‬‬ ‫;‪cont = 0‬‬
‫‪5‬‬ ‫;‪break‬‬
‫‪6‬‬ ‫‪case SDL_MOUSEBUTTONUP: // Mouse click‬‬
‫‪7‬‬ ‫;‪break‬‬
‫} ‪8‬‬

‫‪349‬‬
‫الفصل ‪ .23‬معالجة الأحداث )‪(Event handling‬‬

‫لحد الآن‪ ،‬لا توجد صعوبة كبيرة‪.‬‬

‫ما هي المعلومات التي يمكن استرجاعها حينما ننقر بالفأرة‪ .‬لدينا معلومتان ‪:‬‬

‫• الزر الذي قمنا بالضغط عليه )الزر الأيسر ؟ الأيمن ؟ الأوسط ؟(‪،‬‬

‫• إحداثيات مؤشر الفأرة لحظة النقر )‪ x‬و ‪.(y‬‬

‫استرجاع زر الفأرة‬

‫يجب أن نرى أولا أي الأزرار تم الضغط عليها ‪ .‬من أجل هذا‪ ،‬يجب تحليل المركب ‪ event.button.button‬و‬
‫مقارنة قيمته بإحدى القيم التالية ‪:‬‬

‫• ‪ : SDL_BUTTON_LEFT‬الضغط بالزر الأيسر للفأرة‪.‬‬

‫• ‪ : SDL_BUTTON_MIDDLE‬الضغط بالزر الأوسط للفأرة )لا يملـكه كل شخص‪ ،‬و هو يمثل غالبا النقر بالعجلة(‪.‬‬

‫• ‪ : SDL_BUTTON_RIGHT‬النقر بالزر الأيمن للفأرة‪.‬‬

‫• ‪ : SDL_BUTTON_WHEELUP‬تحر يك عجلة الفأرة إلى الأعلى‪.‬‬

‫• ‪ : SDL_BUTTON_WHEELDOWN‬تحر يك عجلة الفأرة إلى الأسفل‪.‬‬

‫م‬
‫الثابتان الأخيران يوافقان تحر يك عجلة الفأرة إلى الأعلى و الأسفل‪ .‬و هما لا يوافقان ”النقر” على العجلة كما يمكن‬
‫أن نعتقد بالخطأ‪.‬‬

‫سنقوم باختبار سهل لنرى ما إن تم الضغط بالزر الأيمن للفأرة‪ .‬إذا ضغطنا عليه‪ ،‬نخرج من البرنامج‪) .‬أعرف أن هذا‬
‫ليس بقرار مناسب لـكن لـكي نجر ّب لا أكثر( ‪:‬‬

‫)‪1 switch(event.type‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫‪case SDL_QUIT:‬‬
‫‪4‬‬ ‫;‪cont = 0‬‬
‫‪5‬‬ ‫;‪break‬‬
‫‪6‬‬ ‫‪case SDL_MOUSEBUTTONUP:‬‬
‫‪7‬‬ ‫)‪if (event.button.button == SDL_BUTTON_RIGHT‬‬
‫‪8‬‬ ‫‪// We stop the program on right−click with the mouse‬‬
‫‪9‬‬ ‫;‪cont = 0‬‬
‫‪10‬‬ ‫;‪break‬‬
‫} ‪11‬‬

‫يمكنك التجريب‪ ،‬سترى بأن البرنامج يتوقف حين يتم النقر بالزر الأيمن للفأرة‪.‬‬

‫‪350‬‬
‫‪ .4.23‬الفأرة‬

‫استرجاع إحداثيات الفأرة‬

‫هذه معلومة جد مهمة ‪ :‬إحداثيات مؤش ّر الفأرة في حين النقر !‬


‫سنقوم باستعادتها بواسطة مركّ بين )واحد من أجل الفاصلة و آخر من أجل الترتيبة( ‪ event.button.x :‬و‬
‫‪. event.button.y‬‬

‫فلنستمتع قليلا ً‪ :‬سنقوم بلصق ‪ Zozor‬في الوضعية التي توافق إحداثيات النقطة التي تم النقر عليها بالفأرة‪.‬‬
‫هل هذا صعب ؟ لا أبدا ً ! حاول فعل ذلك‪ ،‬سترى بأنها لعبة أطفال !‬

‫هاهو التصحيح ‪:‬‬

‫)‪1 while (cont‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;)‪SDL_WaitEvent(&event‬‬
‫‪4‬‬ ‫)‪switch(event.type‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫‪case SDL_QUIT:‬‬
‫‪7‬‬ ‫;‪cont = 0‬‬
‫‪8‬‬ ‫;‪break‬‬
‫‪9‬‬ ‫‪case SDL_MOUSEBUTTONUP:‬‬
‫‪10‬‬ ‫;‪zozorPosition.x = event.button.x‬‬
‫‪11‬‬ ‫;‪zozorPosition.y = event.button.y‬‬
‫‪12‬‬ ‫;‪break‬‬
‫‪13‬‬ ‫}‬
‫‪14‬‬ ‫;))‪SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 255, 255, 255‬‬
‫‪15‬‬ ‫‪// We put Zozor in its new position‬‬
‫‪16‬‬ ‫;)‪SDL_BlitSurface(zozor, NULL, screen, &zozorPosition‬‬
‫‪17‬‬ ‫;)‪SDL_Flip(screen‬‬
‫} ‪18‬‬

‫هذه الشفرة تشبه الشفرة التي كتبتُها من أجل أزرار لوحة المفاتيح‪ .‬هنا الأمر أسهل بكثير ‪ :‬نضع مباشرة قيمة ‪ x‬في‬
‫المتغير ‪ zozorPosition.x‬و نفس الشيء بالنسبة لـ ‪. y‬‬
‫ثم نقوم بلصق ‪ Zozor‬في الإحداثيات الخاصة به‪ ،‬و هاهي النتيجة ‪:‬‬

‫‪351‬‬
‫الفصل ‪ .23‬معالجة الأحداث )‪(Event handling‬‬

‫إليك تمرينا ًسهلا ًجدا ً ‪ :‬لحد الآن‪ ،‬نقوم بتحر يك ‪ Zozor‬مهما كان زر الفأرة الذي قمنا بضغطه‪ .‬حاول ألا تحر ّكه إلا‬
‫إذا كان الزر المضغوط هو الأيسر‪ .‬إذا تم الضغط على الزر الأيمن‪ ،‬يتوقف البرنامج‪.‬‬

‫معالجة تحرّكات الفأرة‬

‫تحر ّك الفأرة يقوم بإنتاج حدث من نوع ‪. SDL_MOUSEMOTION‬‬


‫و تيقن أنه يتم إنتاج أحداث بالقدر الذي تتحركه به الفأرة بيكسلا ببيكسل في الشاشة ! لو نحر ّك الفأرة ‪ 100‬بيكسل )هذا‬
‫ليس كثيرا(‪ ،‬سيكون إذا هناك ‪ 100‬حدث مـُنتج‪.‬‬

‫؟‬
‫لـكن ألا تقوم هذه العملية بإنتاج الـكثير من الأحداث بالنسبة للحاسوب ؟‬

‫لا بالتأكيد‪ ،‬تيقّن أنه يستقبل أكثر من ذلك بكثير !‬

‫حسناً‪ ،‬هل ما نقوم باسترجاعه مهم هنا ؟‬


‫إحداثيات الفأرة‪ ،‬بالطبع ! سنجدها في ‪ event.motion.x‬و ‪. event.motion.y‬‬

‫!‬
‫احذر ‪ :‬لن نعمل بنفس المركّ بين اللذين استعملناهما من أجل النقر بالفأرة قبل قليل )سابقاً‪ ،‬كانت‬
‫‪ .( event.button.x‬المركّبات المُستعملة تكون مختلفة في الـ‪ SDL‬بحسب الحدث‪.‬‬

‫سنقوم بتحر يك ‪ Zozor‬إلى نفس إحداثيات الفأرة‪ ،‬هنا أيضا‪ .‬سترى أن العملية فعالة و بسيطة في نفس الوقت !‬

‫)‪1 while (cont‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;)‪SDL_WaitEvent(&event‬‬

‫‪352‬‬
‫‪ .4.23‬الفأرة‬

‫‪4‬‬ ‫)‪switch(event.type‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫‪case SDL_QUIT:‬‬
‫‪7‬‬ ‫;‪cont = 0‬‬
‫‪8‬‬ ‫;‪break‬‬
‫‪9‬‬ ‫‪case SDL_MOUSEMOTION:‬‬
‫‪10‬‬ ‫;‪zozorPosition.x = event.motion.x‬‬
‫‪11‬‬ ‫;‪zozorPosition.y = event.motion.y‬‬
‫‪12‬‬ ‫;‪break‬‬
‫‪13‬‬ ‫}‬
‫‪14‬‬ ‫;))‪SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 255, 255, 255‬‬
‫‪15‬‬ ‫;)‪SDL_BlitSurface(zozor, NULL, screen, &zozorPosition‬‬
‫‪16‬‬ ‫;)‪SDL_Flip(screen‬‬
‫} ‪17‬‬

‫حرّك ‪ Zozor‬في الشاشة‪ ،‬ما رأيك ؟‬


‫سيتبع طبيعيا ًالفأرة أينما تتحر ّك‪ .‬هذا أمر جميل‪ ،‬سر يع‪ ،‬و مرن )بفضل الـ‪.(double buffering‬‬

‫دوال أخرى من أجل التعامل مع الفأرة‬

‫سنرى دالتين سهلتي الاستعمال لهما علاقة بالفأرة‪ .‬هاتان الدالتان ستكونان مفيدتين قريباً‪.‬‬

‫إخفاء مؤش ّر الفأرة‬

‫يمكننا إخفاء مؤشر الفأرة بسهولة تامة‪ ،‬يكفي أن نستدعي الدالة ‪ SDL_ShowCursor‬و نُعطيها عَلَما ً‪:‬‬

‫• ‪ : SDL_DISABLE‬إخفاء مؤش ّر الفأرة‪.‬‬

‫• ‪ : SDL_ENABLE‬إظهار مؤشر الفأرة‪.‬‬

‫مثلا ‪:‬‬

‫;)‪1 SDL_ShowCursor(SDL_DISABLE‬‬

‫مؤش ّر الفأرة سيبقى مختفيا ًطالما هو داخل النافذة‪.‬‬


‫ل دورة للحلقة‪ ،‬مرة واحدة كافية‪.‬‬
‫أنصحك بأن تقوم بإخفائه قبل الحلقة الرئيسية في البرنامج لأنه لا داعي لإخفائه في ك ّ‬

‫‪353‬‬
‫الفصل ‪ .23‬معالجة الأحداث )‪(Event handling‬‬

‫وضع الفأرة في موضع محدد‬

‫يمكننا تحر يك مؤشر الفأرة يدو يا إلى الإحداثيات التي نريدها في النافذة‪.‬‬
‫نستعمل من أجل هذا ‪ SDL_WarpMouse‬و التي تأخذ كمعاملين الإحداثيتان ‪ x‬و ‪ y‬أين يجدر بالمؤش ّر أن يتواجد‪.‬‬

‫مثلا‪ ،‬الشفرة المصدر ية التالية تقوم بتحر يك الفأرة إلى وسط النافذة ‪:‬‬

‫;)‪1 SDL_WarpMouse(screen−>w / 2, screen−>h / 2‬‬

‫م‬
‫حينما تستدعي ‪ ، SDL_WarpMouse‬يتم إنتاج حدث من نوع ‪ . SDL_MOUSEMOTION‬نعم‪ ،‬فالفأرة تحرّكت‬
‫! حتى و إن لم يقم المستعمل بذلك‪ ،‬فقد كان هناك تحر ّك رغم ذلك‪.‬‬

‫أحداث النافذة‬ ‫‪5.23‬‬

‫النافذة نفسها يمكن لها إنتاج عدد من الأحداث ‪:‬‬

‫• حينما يتم تغيير مقاييسها‪.‬‬

‫• حينما يتم إنزالها إلى شر يط المهام الس ُفلي و حينما يتم استعادتها‪.‬‬

‫• حينما تصبح مفع ّلة )شاشة أولى( أو حينما تصبح غير مفعلة‪.‬‬

‫• حينما يكون مؤشر الفأرة داخل النافذة أو حينما يخر ُج منها‪.‬‬

‫فلنبدأ بدراسة الحالة الأولى ‪ :‬الحدثُ الذي يُنتج حينما يتم تغيير مقاييس النافذة‪.‬‬

‫تغيير مقاييس النافذة‬

‫بشكل افتراضي‪ ،‬تكون مقاييس النافذة غير قابلة للتعديل من طرف المستخدم‪.‬‬
‫أذك ّرك بأنه لتغيير ذلك‪ ،‬يجب إضافة الع َلم ‪ SDL_RESIZABLE‬إلى الدالة ‪: SDL_SetVideoMode‬‬

‫| ‪1 screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF‬‬


‫;)‪SDL_RESIZABLE‬‬

‫ما إن تتم إضافة هذا الع َلم‪ ،‬يمكنك تغيير مقاييس النافذة‪ .‬حينما تقوم بذلك‪ ،‬يتم إنتاج حدث من نوع ‪. SDL_VIDEORESIZE‬‬

‫يمكنك استرجاع ‪:‬‬

‫• الع َرض الجديد من ‪. event.resize.w‬‬

‫‪354‬‬
‫‪ .5.23‬أحداث النافذة‬

‫• الارتفاع الجديد من ‪. event.resize.h‬‬

‫يمكننا استعمال هذه المعلومات من أجل أن نعمل على أن يكون ‪ Zozor‬متمركزا ً دائما في وسط النافذة ‪:‬‬

‫‪1‬‬ ‫‪case SDL_VIDEORESIZE:‬‬


‫‪2‬‬ ‫‪zozorPosition.x = event.resize.w / 2‬‬ ‫;‪− zozor−>w / 2‬‬
‫‪3‬‬ ‫‪zozorPosition.y = event.resize.h / 2‬‬ ‫;‪− zozor−>h / 2‬‬
‫‪4‬‬ ‫;‪break‬‬

‫مرأى النافذة )‪(Window visibility‬‬

‫يتم إنتاج الحدث ‪ SDL_ACTIVEEVENT‬حينما يتم تغيير مرأى النافذة‪.‬‬


‫قد يحصل هذا لأسباب كثيرة ‪:‬‬

‫• حينما يتم إنزال النافذة إلى شر يط المهام السفلي أو استرجاعها‪.‬‬

‫• تواجد مؤشر الفأرة داخل النافذة أو خارجها‪.‬‬

‫• حينما يتم تفعيل النافذة أو إلغاء ذلك‪.‬‬

‫م‬
‫في البرمجة نتكلم عن التركيز )‪ .(Focus‬حينما نقول أن تطبيقا ًيملك التركيز‪ ،‬فهذا يعني أنه يستعمل لوحة المفاتيح أو‬
‫الفأرة‪ .‬و أي ضغط على أزرار لوحة المفاتيح أو نقر بالفأرة يتم إرسالها إلى النافذة التي بها التركيز و ليس لأخرى‪.‬‬
‫نافذة واحدة لها الحق في أن يكون لها تركيز في لحظة معي ّنة )لا يمكن أن تكون نافذتان كشاشة أولى في آن‬
‫واحد !(‬

‫نظرا ً لـكثرة الأسباب التي يمكن لها أن تسبب وقوع حدث ما‪ ،‬يجب قطعا ًمعرفة قيمة المتغيرات التالية لمعرفة المزيد ‪:‬‬

‫• ‪ : event.active.gain‬تشير ما إن كان الحدث ر بحا )‪ (1‬أو خسارة )‪ .(0‬مثلاً‪ ،‬إذا انتقلت النافذة إلى‬
‫الخلفية‪ ،‬فهذه خسارة )‪ (0‬و إذا تم ارجاعها كشاشة أولى فهذا ربح )‪.(1‬‬

‫• ‪ : event.active.state‬هو مزج بين عدة أعلام للإشارة إلى نوع الحدث الذي تم إنتاجه‪ .‬هذه قائمة الأعلام‬
‫الممكنة ‪:‬‬
‫– ‪ : SDL_APPMOUSEFOCUS‬مؤش ّر الفأرة كان بصدد الدخول أو الخروج من النافذة‪ .‬يجب رؤ ية قيمة‬
‫‪ event.active.gain‬لنعرف ما إن كان قد دخل )ربح = ‪ (1‬أو خرج )ربح = ‪.(0‬‬
‫– ‪ : SDL_APPINPUTFOCUS‬قامت النافذة باستقبال تركيز لوحة المفاتيح أو فقده‪ .‬أي أنها انتقلت للخلفية أو‬
‫رجعت كشاشة أولى‪.‬‬
‫مرة أخرى‪ ،‬يجب رؤ ية قيمة ‪ event.active.gain‬لنعرف ما إن تم وضع النافذة في الخلفية )ربح =‬
‫‪ (0‬أو في الشاشة الأولى )ربح = ‪.(1‬‬

‫‪355‬‬
‫الفصل ‪ .23‬معالجة الأحداث )‪(Event handling‬‬

‫– ‪ : SDL_APPACTIVE‬تمت عملية أيقنة النافذة‪ ،‬أي أنه تم إنزالُها إلى شر يط المهام السفلي )ربح = ‪ (0‬أو‬
‫إرجاعها إلى مكانها الأصلي )ربح = ‪.(1‬‬

‫أمازلت ٺتابعني ؟ يجب مقارنة قيم المركّبات ‪ gain‬و ‪ state‬لمعرفة ما حصل بالفعل‪.‬‬

‫اختبار قيمة الأعلام المدمجة‬

‫‪ event.active.state‬هي عبارة عن دمج للأعلام‪ .‬هذا يعني أنه في حدث‪ ،‬يمكن أن يحصل أمران )مثلاً‪ ،‬لو ننزل‬
‫النافذة إلى شر يط المهام‪ ،‬سنفقد تركيز لوحة المفاتيح و الفأرة أيضاً(‪.‬‬
‫لهذا‪ ،‬يجب القيام باختبار أكثر تعقيدا من هذا ‪:‬‬
‫)‪1 if (event.active.state == SDL_APPACTIVE‬‬

‫؟‬
‫لماذا الأمر معقد ؟‬

‫ن هذا سيكون‬
‫لأنه عبارة عن مزج لبيتات )‪ .(bits‬لن أقوم بطرح درس حول العمليات المنطقية ‪ bit‬بـ‪ bit‬الآن‪ ،‬لأ ّ‬
‫كثيرا لهذا الدرس و ليس عليك معرفة الـكثير عنها‪.‬‬
‫سأقدم لك شفرة قابلة للتطبيق و التي يجب عليك استعمالها إذا و ُجد عَلم ً في متغير دون الدخول في التفاصيل‪.‬‬

‫لتجريب ما إن تم أي تغيير في التركيز الخاص بالفأرة مثلاً‪ ،‬يجدر بنا كتابة ‪:‬‬
‫)‪1 if ((event.active.state & SDL_APPMOUSEFOCUS) == SDL_APPMOUSEFOCUS‬‬

‫لا توجد أخطاء‪ .‬اِحذر‪ ،‬الأمر دقيق ‪ :‬يجب استعمال إشارة & واحدة و اثنتين من = ‪ ،‬و يجب استعمال الأقواس‬
‫كما فعلت‪.‬‬
‫الشفرة تعمل بنفس الطر يقة بالنسبة للأحداث الأخرى‪ ،‬مثلا ‪:‬‬
‫)‪1 if ((event.active.state & SDL_APPACTIVE) == SDL_APPACTIVE‬‬

‫اختبار الحالة و الربح في آن واحد‬

‫عملياً‪ ،‬ستحتاج مؤكّدا إلى اختبار الحالة و الربح في آن واحد‪ .‬هك ّذا يمكنك معرفة ما قد حصل بالفعل‪.‬‬

‫لنفترض أننا نبرمج لعبة تجعل الحاسوب يقوم بالـكثير من الحسابات‪ ،‬تريد أن يتوقف البرنامج مؤق ّتا تلقائيا عندما يتم إنزال‬
‫النافذة إلى شر يط المهام‪ ،‬ثم عند إعادتها إلى وضعها الأصلي‪ ،‬يُكمل البرنامج عمله تلقائياً‪ .‬هذا سيجنبنا الحالة التي يستمر فيها‬
‫البرنامج بالعمل حتى في غياب اللاعب‪ ،‬و سيجنبنا أيضا ًجعل المعالج يقوم بالـكثير من الحسابات التي لا فائدة منها‪.‬‬

‫الشفرة التالية تسمح للبرنامج بالتوقف قليلا ً و ذلك بتفعيل المتغير المنطقي ‪) pause‬إعطائه القيمة ‪ .(1‬تقوم الشفرة‬
‫بإكمال عمل البرنامج بتعطيل المتغير المنطقي )إعطائه القيمة ‪.(0‬‬

‫‪356‬‬
‫ملخّ ص‬

‫)‪1 if ((event.active.state & SDL_APPACTIVE) == SDL_APPACTIVE‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪if (event.active.gain == 0) // If the window is minimized‬‬
‫‪4‬‬ ‫;‪pause = 1‬‬
‫‪5‬‬ ‫‪else if (event.active.gain == 1) // If the window is restored‬‬
‫‪6‬‬ ‫;‪pause = 0‬‬
‫} ‪7‬‬

‫م‬
‫هذه الشفرة ليست كاملة بطبيعة الحال‪ .‬عليك اختبار قيمة المتغير ‪ pause‬لمعرفة ما إن كان واجبا ً القيام‬
‫بالحسابات في برنامجك أو لا‪.‬‬

‫سأترك لك القيام باختبارات من أجل الحالات الأخرى )مثلا‪ ،‬التأكد ما إن كان مؤشر الفأرة داخل أو خارج‬
‫النافذة( يمكنك التدرّب و ذلك بتحر يك ‪ Zozor‬إلى اليمين إذا دخل مؤش ّر الفأرة إلى النافذة و تحر يكه إلى اليسار إذا خرج‬
‫المؤ ّشر منها‪.‬‬

‫ملخّ ص‬

‫• الأحداث هي عبارة عن إشارات ترسلها إلينا الـ‪ SDL‬من أجل إطلاعنا على فعل قام به المستعمل ‪ :‬الضغط على‬
‫زر‪ ،‬تحر يك أو النقر بالفأرة‪ ،‬غلق النافذة‪ ،‬إلخ‪.‬‬

‫ط ِلة لـكن‬
‫• يتم استرجاع الأحداث في متغير من نوع ‪ SDL_Event‬بواسطة الدالة ‪) SDL_WaitEvent‬دالة مع ّ‬
‫ط ِلة لـكن يصعب التحكم بها(‪.‬‬
‫ل التحكم بها( أو الدالة ‪) SDL_PollEvent‬دالة غير مع ّ‬
‫يسه ُ‬

‫• يجب تحليل المركّ ب ‪ event.type‬من أجل معرفة نوع الحدث الذي تم إنتاجه‪ .‬نقوم بذلك غالبا داخل‬
‫‪. switch‬‬
‫• ما إن يتم تحديد نوع الحدث‪ ،‬نحتاج غالبا إلى تحليل الحدث بالتفصيل‪ .‬مثلا‪ ،‬عند الضغط على زر في لوحة‬
‫المفاتيح ) ‪ ،( SDL_KEYDOWN‬يجب تحليل المركّ ب ‪ event.key.keysym.sym‬لمعرفة الزر الذي تم الضغط‬
‫عليه بالضبط‪.‬‬
‫• تقنية الـ‪ double buffering‬هي تقنية تسمح بتحميل الصورة التالية في الشاشة الخلفية و إظهارها فقط حينما تكون‬
‫جاهزة‪ .‬هذا يسمح بتجنب تقطّع الصورة في الشاشة‪.‬‬

‫‪357‬‬
‫الفصل ‪ .23‬معالجة الأحداث )‪(Event handling‬‬

‫‪358‬‬
‫الفصل ‪24‬‬

‫‪Mario Sokoban‬‬ ‫عمل تطبيقي ‪:‬‬

‫المكتبة ‪ SDL‬تقدّم‪ ،‬مثلما رأينا‪ ،‬عددا كبيرا ً من الدوال الجاهزة للاستعمال‪ .‬يمكن ألا نستطيع التعو ّد عليها في البداية‬
‫لقلّة التطبيق‪.‬‬

‫هذا العمل التطبيقي الأول في هذا الجزء من الكتاب سيعطيك فرصة التطبيق و اختبار أشياء لم تسنح لك فرصة تجريبها‪.‬‬
‫أعتقد أنه بإمكانك التخمين‪ ،‬فهذه المرة لن يكون التطبيق عبارة عن كونسول و إنما سيتحتوي على واجهة رسومية !‬

‫ماذا سيكون موضوع هذا العمل التطبيقي ؟ لعبة ‪! Sokoban‬‬


‫تنص على دفع صناديق لوضعها في أماكن محددة‬
‫قد لا يعني لك هذا العنوان شيئاً‪ ،‬لـكن هذه هي لعبة ذكاء تقليدي ّة‪ .‬إنّها ّ‬
‫في متاهة‪.‬‬

‫‪Sokoban‬‬ ‫مواصفات‬ ‫‪1.24‬‬

‫‪Sokoban‬‬ ‫بخصوص‬

‫الكلمة ”‪ ”Sokoban‬هي كلمة يابانية تعني ”صاحب محلّ”‪.‬‬


‫إنّها عبارة عن لعبة ذكاء تم اختراعها في الثمانينات بواسطة ‪ .Hiroyuki Imabayashi‬و قد مثّلت برمجة هذه اللعبة تحدّيا‬
‫كبيرا ً في ذلك الزمن‪.‬‬

‫الهدف من اللعبة‬

‫المبدأ بسيط ‪ :‬تقوم بتحر يك شخصية في متاهة‪ .‬يجدر بالشخصية أن تقوم بدفع صناديق إلى مواقع محددة‪ .‬لا يمكن للاعب‬
‫أن يدفع صندوقين في آن واحد‪.‬‬

‫حتى و إن كان المبدأ مفهوما ً و بسيطاً‪ ،‬فهذا لا يعني أن اللعبة في ح ّد ذاتها سهلة ! إذ أنه يجب عليك أحيانا تكسير‬
‫ل اللغز‪.‬‬
‫رأسك بالتفكير لح ّ‬

‫الصورة الموالية ت ُر يك كيف تبدو اللعبة التي سنقوم ببرمجتها ‪:‬‬

‫‪359‬‬
‫‪Mario Sokoban‬‬ ‫الفصل ‪ .24‬عمل تطبيقي ‪:‬‬

‫لماذا اخترتُ هذه اللعبة بالذات ؟‬

‫لأنها لعبة شعبية‪ ،‬جيدة لأن تكون موضوعا ًبرمجيا ًو يمكننا إنشاؤها بواسطة ما تعلّمناه من الفصول السابقة‪.‬‬
‫يجب هنا أن نكون منظّمين‪ .‬إذ أن الصعوبة لا تكم ُن في برمجة اللعبة في ح ّد ذاتها لـكن في ما إن نظّمنا العمل‪ .‬و لهذا‬
‫فسنقوم بتقسيم البرنامج إلى عدّة ملفات ‪ .c‬بطر يقة ذكي ّة و نحاول إنشاء الدوال المناسبة‪.‬‬

‫من أجل هذا الأمر‪ ،‬قررت تغيير الطر يقة بالنسبة لهذا العمل التطبيقي ‪ :‬لن أقدّم لك توجيهات و أقدّم التصحيح في‬
‫النهاية‪ .‬بالعكس‪ ،‬سأر يك كيف نقوم ببناء المشروع كل ّه من الألف إلى الياء‪.‬‬

‫؟‬
‫ماذا لو كنتُ أريد التدرّب لوحدي ؟‬

‫حسنا ًإذا فلتنطلق لوحدك‪ ،‬هذا أمر جيد !‬


‫ستحتاج ربّما وقتا ً أكثر ‪ :‬لقد استغرقت شخصيا يوما ً كاملا ً لبرمجة اللعبة‪ ،‬هذا ليس بالوقت الـكثير ربّما لأنه جرت العادة‬
‫مرات(‪.‬‬
‫أن أقوم بالبرمجة و و أن أتحاشى الوقوع في بعض الأفخاخ المتداولة )لـكنّ هذا لم يمنعني من إعادة التفكير عدّة ّ‬
‫اِعلم بأنه توجد الـكثير من الطرق التي يمكن بها برمجة هذه اللعبة‪ .‬سأعطيك طر يقتي في برمجتها ‪ :‬ليست أحسن طر يقة‬
‫و لـكنها بالتأكيد ليست أسوء واحدة‪.‬‬
‫سننتهي من هذا التطبيق بقائمة من الإقتراحات لتحسين اللعبة‪ ،‬كما أنني سأعطيك الرابط لتحميل اللعبة و الشفرة المصدر ية‬
‫الكاملة‪.‬‬

‫أنصحك مجددا ً أن تحاول برمجة اللعبة لوحدك‪ ،‬حتى لو استغرقت ‪ 3‬أو ‪ 4‬أيام‪ .‬اِفعل أحسن ما لديك‪ .‬من المهم جدّا‬
‫أن تقوم بالتطبيق‪.‬‬

‫‪360‬‬
‫‪Sokoban‬‬ ‫‪ .1.24‬مواصفات‬

‫المواصفات‬

‫المواصفات هي عبارة عن وثيقة نكتب فيها كل ما يجب على البرنامج أن يستطيع فعله‪.‬‬

‫هذا ما أقترحه ‪:‬‬

‫• يجب أن يتمكن اللاعب من التحر ّك في المتاهة و دفع الصناديق‪.‬‬

‫• لا يمكنه أن يدفع صندوقين معاً‪.‬‬

‫ل الصناديق في الأماكن المخصصة لها‪.‬‬


‫• ت ُربح الجولة إذا تواجدت ك ّ‬

‫ل مستو يات اللعبة في ملف‪) ،‬ليكن مثلا ‪.( levels.lvl‬‬


‫• سيتم حفظ ك ّ‬

‫• يجب أن يتم دمج مـُنشئ المستو يات )‪ (Levels editor‬في البرنامج ليتمكن أي شخص كان من صنع مستو يات خاصة‬
‫به )هذا ليس أمرا ً ضرور يا ًلـكنه يعتبر إضافة مميزة !(‪.‬‬

‫كاف لنعمل كثيراً‪.‬‬


‫ٍ‬ ‫هذا‬

‫يجب أن تعرف أنه هناك أشياء لا يجيد البرنامج القيام بها‪ ،‬و يجب ذِكر ُ هذا الأمر أيضاً‪.‬‬

‫• برنامجنا قادر على التحكّم في مرحلة واحدة في المر ّة الواحدة‪ .‬إن أردت أن تكون اللعبة عبارة عن ٺتالي جولات‪ ،‬فما‬
‫عليك سوى برمجة ذلك بنفسك في نهاية هذا العمل التطبيقي‪.‬‬

‫ل جولة )نحن لا نجيد فعل ذلك بعد( و لا يمكنه حساب النقاط‪.‬‬


‫• البرنامج لا يقوم بحساب الوقت الم ٌستغرق في ك ّ‬

‫ل الأشياء التي نريد القيام بها )خاصة مـُنش ِئ المراحل( تأخذ منا وقتا ًلابأس به‪.‬‬
‫على أي حال‪ ،‬فك ّ‬

‫م‬
‫سأعطيك في نهاية العمل التطبيقي‪ ،‬جملة التحسينات التي تُمكن إضافتها إلى اللعبة‪ .‬و هذه ليست كلمات في الهواء‪،‬‬
‫لأنّها أفكار طب ّقتها أنا شخصي ّا في نسخة كاملة من اللعبة سأقترح عليك تنز يلها‪.‬‬
‫بالمقابل‪ ،‬لن أعطيك الشفرة المصدر ية الخاصة بالنسخة الكاملة لأنني أريدك أن تعمل بنفسك و ٺتدرّب )لن‬
‫ل شيء على طبق من فضّ ة !(‪.‬‬
‫أعطيك ك ّ‬

‫الحصول على الصور اللازمة لللعبة‬

‫في معظم الألعاب ثنائية الأبعاد‪ ،‬أي ّا كان نوعها‪ ،‬نسمّي الصور التي تشكّل اللعبة ‪.Sprites‬‬
‫‪Mario‬‬ ‫في حالتنا‪ ،‬قر ّرت إنشاء ‪ Sokoban‬و وضع الشخصية ‪ Mario‬لتكون اللاعب الرئيسي فيها )من هنا جاء اسم اللعبة‬

‫‪361‬‬
‫‪Mario Sokoban‬‬ ‫الفصل ‪ .24‬عمل تطبيقي ‪:‬‬

‫‪ .(Sokoban‬بما أن ‪ Mario‬شخصية لها شعبية كبيرة في عالم الألعاب ‪ ،2D‬لن نتعب في الحصول على الـ‪ sprites‬الخاصّة بهذه‬
‫الشخصي ّة‪ .‬سنحتاج أيضا إلى ‪ sprites‬خاصة بالجدران‪ ،‬الصناديق‪ ،‬الأماكن المستهدفة‪ ،‬إلخ‪.‬‬

‫إذا بحثت في ‪ Google‬عن ”‪ ”sprites‬فستحصل على عدّة نتائج‪ .‬توجد العديد من المواقع التي توف ّر ‪ sprites‬خاصّة‬
‫بألعاب ‪ 2D‬قد تكون لعبتها في السابق‪.‬‬

‫و هذه هي ال ّتي سنحتاج إليها ‪:‬‬

‫الشرح‬ ‫‪Sprite‬‬

‫جدار‬

‫صندوق‬

‫صندوق متموضع فوق منطقة مستهدفة‬

‫بطل اللعبة )‪ (Mario‬باتجاه الأسفل‬

‫بطل اللعبة باتجاه اليمين‬

‫بطل اللعبة باتجاه اليسار‬

‫بطل اللعبة باتجاه الأعلى‬

‫الأسهل هو أن تقوم بتحميل الحزمة التي أعددتها لك‪.‬‬

‫‪https://openclassrooms.com/uploads/fr/ftp/mateo21/sprites_mario_sokoban.zip‬‬

‫م‬
‫كان من الممكن أن أستعمل ‪ sprite‬واحدا ً خاصا ًباللاعب‪ .‬كان بإمكاني جعله موجّها ًإلى الأسفل فقط‪ ،‬لـكن‬
‫إضافة امكانية توجيهه في الاتجاهات الأربعة تضيف القليل من الواقعية‪ .‬و هذا يشكّل تحدّيا آخر لنا !‬

‫قمت أيضا ً بإنشاء صورة أخرى لتكون عبارة عن الواجهة الأساسية للعبة حين تبدأ‪ ،‬لقد أرفقت لك الصورة بالحزمة‬
‫ال ّتي يفترض بك تنز يلها‪ .‬لاحظ الصورة التالية ‪:‬‬

‫‪362‬‬
‫‪ .2.24‬الدالة ‪ main‬و الثوابت‬

‫ستلاحظ بأن الصور تأخذ صيغا مختلفة‪ .‬يوجد منها ماهو ‪ ،GIF‬ماهو ‪ PNG‬و حتى ماهو ‪ .JPEG‬و لهذا فنحن بحاجة‬
‫إلى استعمال المكتبة ‪.SDL_Image‬‬
‫فك ّر في جعل مشروعك يعمل مع الـ‪ SDL‬و الـ‪ .SDL_Image‬إذا نسيت كيف تفعل ذلك‪ ،‬فراجع الفصول السابقة‪ .‬إذا‬
‫لم تقم بتخصيص المشروع بشكل صحيح‪ ،‬سيشير المُترجم بأن الدوال التي تستعملها )مثل ‪ ( IMG_Load‬غير موجودة !‬

‫الدالة ‪ main‬و الثوابت‬ ‫‪2.24‬‬

‫ل مرة نبدأ بتحقيق مشروع مهمّ‪ ،‬من الواجب أن نقوم بتنظيم العمل في البداية‪.‬‬
‫في ك ّ‬
‫بشكل عام‪ ،‬أبدأ في إنشاء ملف ثوابت ‪ constants.h‬إضافة إلى ملف ‪ main.c‬يحتوي الدالة ‪) main‬فقط هذه‬
‫ل شخص طر يقته الخاصة‪.‬‬
‫الدالة(‪ .‬هذه ليست قاعدة لـكنها طر يقتي الخاصة في العمل‪ ،‬و لك ّ‬

‫ملفّات المشروع المختلفة‬

‫أقترح أن نقوم بإنشاء ملفات المشروع كل ّه الآن‪) ،‬حتى و إن كانت فارغة في البداية(‪ .‬هاهي الملفات التي أنشئها إذا ‪:‬‬

‫• ‪ : constants.h‬تعر يف الثوابت الشاملة الخاصة بكل البرنامج‪.‬‬

‫• ‪ : main.c‬الملف الذي يحتوي ‪) main‬الدالة الرئيسية في البرنامج(‪.‬‬

‫• ‪ : game.c‬الدوال ال ّتي تسي ّر جولة من اللعبة ‪.Sokoban‬‬

‫• ‪ : game.h‬نماذج الدوال الخاصة بالملف ‪. game.c‬‬

‫• ‪ : editor.c‬ملف يحتوي الدول التي تتحكم في مـُنشئ المستو يات‪.‬‬

‫• ‪ : editor.h‬نماذج الدوال الخاصة بالملف ‪. editor.c‬‬

‫• ‪ : files.c‬الدوال الخاصّة بقراءة و كتابة ملفّات المستو يات )مثل ‪.( levels.lvl‬‬

‫‪363‬‬
‫‪Mario Sokoban‬‬ ‫الفصل ‪ .24‬عمل تطبيقي ‪:‬‬

‫• نماذج الدوال الخاصة بالملف ‪. files.c‬‬

‫سنبدأ بإنشاء ملف الثوابت‪.‬‬

‫‪constants.h‬‬ ‫الثوابت ‪:‬‬

‫هذا محتوى الملف ‪ constants.h‬الخاص بي ‪:‬‬

‫‪1‬‬ ‫�‪/‬‬
‫‪2‬‬ ‫‪constants.h‬‬
‫‪3‬‬ ‫‪−−−−−−−−−−−−‬‬
‫‪4‬‬
‫‪5‬‬ ‫)‪By mateo21, for ”Site du Zéro” (www.siteduzero.com‬‬
‫‪6‬‬
‫‪7‬‬ ‫)‪Role : define some constants for all of the program (window size...‬‬
‫‪8‬‬ ‫‪�/‬‬
‫‪9‬‬ ‫‪#ifndef DEF_CONSTANTS‬‬
‫‪10‬‬ ‫‪#define DEF_CONSTANTS‬‬
‫‪11‬‬ ‫‪#define BLOCK_SIZE 34 // Block size (square) in pixels‬‬
‫‪12‬‬ ‫‪#define NB_BLOCKS_WIDTH 12‬‬
‫‪13‬‬ ‫‪#define NB_BLOCKS_HEIGHT 12‬‬
‫‪14‬‬ ‫‪#define WINDOW_WIDTH BLOCK_SIZE � NB_BLOCKS_WIDTH‬‬
‫‪15‬‬ ‫‪#define WINDOW_HIGHT BLOCK_SIZE � NB_BLOCKS_HEIGHT‬‬
‫‪16‬‬ ‫;}‪enum {UP, DOWN, LEFT, RIGHT‬‬
‫‪17‬‬ ‫;}‪enum {EMPTY, WALL, BOX, GOAL, MARIO, BOX_OK‬‬
‫‪18‬‬ ‫‪#endif‬‬

‫ستلاحظ الـكثير من النقاط المهمّة في هذا الملف الصغير‪.‬‬

‫ل ملفاتك )مهما كانت صيغتها ‪ .c‬أو ‪ .( .h‬بشكل‬


‫• يبدأ الملف بتعليق رأسي‪ .‬أنصحك بوضع تعليق مماثل في ك ّ‬
‫عام‪ ،‬التعليق الرأسي يحوي ‪:‬‬

‫– اسم الملف‪،‬‬
‫– اسم الكاتب )المبرمج(‪،‬‬

‫– مهمّة الملف )أي فائدة الدوال ال ّتي يحويها(‪،‬‬

‫– لم أقم بهذا هنا‪ ،‬لـكن عادة يفترض أيضا ً إضافة تاريخ كتابة الملف و تاريخ آخر تعديل عليه‪ .‬هذا يسمح لك‬
‫بإ يجاد المعلومات بسرعة حينما تحتاج إليها و خاصة حينما يتعل ّق الأمر بمشار يع كبيرة‪.‬‬

‫• الملف محم ّي ضد التضمينات غير المنتهية‪ .‬لقد استعملت لذلك التقنية التي تعلّمناها في نهاية فصل المعالج القبلي‪ .‬هنا‪،‬‬
‫ل ملفاتي ‪ .h‬بدون استثناء‪.‬‬
‫الحماية ليست مهمّة جدّا‪ ،‬لـكن جرت العادة أن أستعملها في ك ّ‬

‫• أخيراً‪ ،‬قلب الملف‪ .‬ستجد لائحة من ‪ . #define‬قمت بتحديد حجم كتلة بالبيكسل )كل ‪ sprites‬هي عبارة عن‬
‫مرب ّعات ذات حجم ‪ 34‬بيكسل(‪ .‬أحدد بأن حجم النافذة يساوي ‪ 12*12‬كتلة كع ُرض‪ .‬و بهذا أقوم بحساب أبعاد‬

‫‪364‬‬
‫‪ .2.24‬الدالة ‪ main‬و الثوابت‬

‫النافذة بعملية ضرب ثوابت بسيطة‪ .‬ما أقوم به هنا ليس ضرور ياً‪ ،‬لـكنه يعود علينا بالفائدة ‪ :‬إذا أردت لاحقا ً‬
‫مراجعة حجم اللعبة‪ ،‬يكفي أن أقوم بتعديل هذا الملف و إعادة ترجمة المشروع فيعمل مع القيم الجديدة دون أية‬
‫مشاكل‪.‬‬

‫• أخيراً‪ ،‬قمت بتعر يف ثوابت عن طر يق تعدادات غير معر ّفة‪ ،‬الأمر مختلف قليلا ً عم ّا تعلّمناه في فصل إنشاء أنواع‬
‫خاصة بنا‪ .‬هنا أنا لست أقوم بتعر يف نوع خاص بي بل أقوم فقط بتعر يف ثوابت‪ .‬هذا يشبه المعر ّفات مع اختلاف‬
‫ل قيمة )بدء ً من ‪ .(0‬و بهذا يكون لدينا ‪= DOWN ،0 = UP :‬‬
‫بسيط ‪ :‬الحاسوب هو من يقوم بإعطاء عدد لك ّ‬
‫‪ ،2 = LEFT ،1‬إلخ‪ .‬هذا ما سيسمح للشفرة بأن تكون مفهومة لاحقاً‪ ،‬سترى ذلك !‬

‫باختصار‪ ،‬لقد استعملت ‪:‬‬

‫• معر ّفات حينما أريد أن أعطي قيمة محددة لثابت )مثلا ً‪ 34‬بيكسل(‪.‬‬

‫• تعدادات حينما تكون قيمة الثابت لا تهمّني‪ .‬هنا‪ ،‬لا يهمني ما إن كانت القيمة المُرفقة بالعنصر ‪ UP‬هي ‪) 0‬كان‬
‫ل ما يهمّني هو أن يكون هذا العنصر مختلفا عن ‪ DOWN‬و‬
‫من الممكن أن تكون ‪ ،150‬هذا لن يغيي ّر شيئا(‪ ،‬ك ّ‬
‫‪ LEFT‬و ‪. RIGHT‬‬

‫تضمين تعر يفات الثوابت‬

‫ل الملفات ‪. .c‬‬
‫المبدأ ينص على تضمين ملف الثوابت في ك ّ‬
‫هك ّذا‪ ،‬أستطيع استعمال الثوابت في أي مكان من الشفرة المصدر ية الخاصة بالمشروع‪.‬‬

‫يعني أنه عليّ أن أكتب السطر التالي في كل بداية للملفات ‪: .c‬‬

‫”‪1 #include ”constants.h‬‬

‫‪main.c‬‬ ‫الدالة ‪: main‬‬

‫الدالة الرئيسي ّة الخاصة بالبرنامج سهلة جداً‪ .‬هي تقوم بإظهار واجهة اللعبة ثم التوجيه إلى الق ِسم المناسب‪.‬‬

‫‪1‬‬ ‫�‪/‬‬
‫‪2‬‬ ‫‪main.c‬‬
‫‪3‬‬ ‫‪−−−−−−‬‬
‫‪4‬‬
‫‪5‬‬ ‫)‪By mateo21, for ”Site du Zéro” (www.siteduzero.com‬‬
‫‪6‬‬
‫‪7‬‬ ‫‪Role : game menu. Allow to choose between the editor and the game.‬‬
‫‪8‬‬ ‫‪�/‬‬
‫‪9‬‬ ‫>‪#include <stdlib.h‬‬
‫‪10‬‬ ‫>‪#include <stdio.h‬‬
‫‪11‬‬ ‫>‪#include <SDL/SDL.h‬‬
‫‪12‬‬ ‫>‪#include <SDL/SDL_image.h‬‬
‫‪13‬‬ ‫”‪#include ”constants.h‬‬

‫‪365‬‬
Mario Sokoban : ‫ عمل تطبيقي‬.24 ‫الفصل‬

14 #include ”game.h”
15 #include ”editor.h”
16 int main(int argc, char �argv[])
17 {
18 SDL_Surface �screen = NULL, �menu = NULL;
19 SDL_Rect menuPosition;
20 SDL_Event event;
21 int cont = 1;
22 SDL_Init(SDL_INIT_EMPTYO);
23 SDL_WM_SetIcon(IMG_Load(”box.jpg”), NULL); // The icon must be loaded
before SDL_SetVideoMode
24 screen = SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HIGHT, 32,SDL_HWSURFACE
| SDL_DOUBLEBUF);
25 SDL_WM_SetCaption(”Mario Sokoban”, NULL);
26 menu = IMG_Load(”menu.jpg”);
27 menuPosition.x = 0;
28 menuPosition.y = 0;
29 while (cont)
30 {
31 SDL_WaitEvent(&event);
32 switch(event.type)
33 {
34 case SDL_QUIT:
35 cont = 0;
36 break;
37 case SDL_KEYDOWN:
38 switch(event.key.keysym.sym)
39 {
40 case SDLK_ESCAPE: // Want to quit the game
41 cont = 0;
42 break;
43 case SDLK_KP1: // Want to play
44 play(screen);
45 break;
46 case SDLK_KP2: // Want to edit levels
47 editor(screen);
48 break;
49 }
50 break;
51 }
52 // Cleaning the screen
53 SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 0, 0,0));
54 SDL_BlitSurface(menu, NULL, screen, &menuPosition);
55 SDL_Flip(screen);
56 }
57 SDL_FreeSurface(menu);
58 SDL_Quit();
59 return EXIT_SUCCESS;
60 }

366
‫‪ .3.24‬اللعبة‬

‫الدالة ‪ main‬ٺتكفّل بتهيئة الـ‪ ،SDL‬و إعطاء عنوان للنافذة إضافة إلى منحها أيقونة‪ .‬في نهاية الدالة‪ ،‬يتم استدعاء الدالة‬
‫‪ SDL_Quit‬لإيقاف الـ‪ SDL‬بشكل سليم‪.‬‬

‫الدالة تقوم بإظهار قائمة يتم تحميلها بواسطة الدالة ‪ IMG_Load‬من المكتبة ‪.SDL_Image‬‬

‫;)”‪1 menu = IMG_Load(”menu.jpg‬‬

‫تلاحظ أنه‪ ،‬لـكي أعطي أبعادا ً للنافذة‪ ،‬أستعمل الثابتين ‪ WINDOW_WIDTH‬و ‪ WINDOW_HIGHT‬المعر ّفين في الملف‬
‫‪. constants.h‬‬

‫حلقة الأحداث‬

‫الحلقة غير المنتهية تعالج الأحداث التالية ‪:‬‬

‫• إيقاف البرنامج ) ‪ : ( SDL_QUIT‬إذا قمنا بطلب غلق البرنامج )النقر على العلامة ‪ X‬أعلى يمين النافذة( فسنعطي‬
‫القيمة ‪ 0‬للمتغير ‪ cont‬و ٺتوقف الحلقة‪ .‬باختصار‪ ،‬هذا أمر تقليديّ ‪.‬‬
‫• الضغط على الزر ‪ : Escape‬إغلاق البرنامج )مثل ‪.( SDL_QUIT‬‬

‫• الضغط على الزر ‪ 1‬من لوحة الأرقام ‪ :‬انطلاق تشغيل اللعبة )استدعاء الدالة ‪.( play‬‬

‫• الضغط على الزر ‪ 2‬من لوحة الأرقام ‪ :‬انطلاق تشغيل م ُنشئ المراحل )استدعاء الدالة ‪.( editor‬‬

‫‪play‬‬ ‫كما ترى فالأمور تجري بسهولة تامة‪ .‬إذا ضغطنا على الزر ‪ ،1‬يتم تشغيل اللعبة‪ ،‬ما إن تنتهي اللعبة‪ ،‬تنتهي الدالة‬
‫و نرجع للـ ‪ main‬من أجل القيام بدورة أخرى للحلقة‪ .‬الحلقة تستمر في الاشتغال مادمنا لم نطلب إيقاف البرنامج‪.‬‬

‫بفضل هذا التنظيم البسيط جدّا‪ ،‬يمكننا التحكم في الدالة ‪ main‬و ترك الدوال الأخرى )مثل ‪ play‬و ‪( editor‬‬
‫تهتم بالتحكم في مختلف أجزاء اللعبة‪.‬‬

‫اللعبة‬ ‫‪3.24‬‬

‫فلندخل إلى المرحلة الأكثر أهمية في الموضوع ‪ :‬الدالة ‪! play‬‬


‫هذه هي الدالة الأكثر أهمية في البرنامج‪ ،‬كن متيقّظا ًلأن هذه الدالة هي حقّا ما يجدر بك فهمه‪ .‬لأنك ستجد بعدها بأن‬
‫م ُنشئ المراحل ليس بالصعوبة التي تتخيّلها‪.‬‬

‫المعاملات التي نبعثها للدالة‬

‫الدالة ‪ play‬تحتاج إلى معامل واحد ‪ :‬المساحة ‪ . screen‬بالفعل‪ ،‬تم فتح النافذة في الدالة الرئيسية‪ ،‬و لـكي تستطيع‬
‫الدالة ‪ play‬أن ترسم على النافذة‪ ،‬يجب أن تقوم باسترجاع المؤش ّر نحو المساحة ‪! screen‬‬

‫لو تقرأ مجددا ً محتوى الدالة الرئيسية‪ ،‬ستجد بأنني قمت باستدعاء الدالة ‪ play‬و ذلك بإعطائها المؤش ّر ‪: screen‬‬

‫‪367‬‬
‫‪Mario Sokoban‬‬ ‫الفصل ‪ .24‬عمل تطبيقي ‪:‬‬

‫;)‪1 play(screen‬‬

‫نموذج الدالة‪ ،‬الذي يمكنك وضعه في الملف ‪ ، game.h‬هو التالي ‪:‬‬

‫;)‪1 void play(SDL_Surface� screen‬‬

‫م‬
‫الدالة لا تقوم بإرجاع أي شيء )و من هنا الـ ‪ .( void‬يمكننا أن نجعلها إن أردنا ت ُرجع قيمة منطقية تشير إلى‬
‫ما كنّا قد ر بحنا الجولة أم لا‪.‬‬

‫التصريح عن المتغيرات‬

‫تحتاج هذه الدالة إلى كثير من المتغيرات‪.‬‬


‫ل المتغيرات التي أحتاجها من الوهلة الأولى‪ .‬هناك من أضفتها لاحقا ًو أنا أكتب الشفرة‪.‬‬
‫لم أفك ّر في ك ّ‬

‫الـ‪SDL‬‬ ‫متغي ّرات من أنواع معر ّفة في‬

‫ل المتغيرات ذات الأنواع المعر ّفة في الـ‪ SDL‬التي نحن بحاجة إليها ‪:‬‬
‫لـكي نبدأ‪ ،‬هاهي ك ّ‬

‫‪1 SDL_Surface �mario[4] = {NULL}; // 4 surfaces for the 4 directions of mario‬‬


‫‪2 SDL_Surface �wall = NULL, �box = NULL, �boxOK = NULL, �objective = NULL, �level‬‬
‫;‪= NULL, �currentMario = NULL‬‬
‫‪3‬‬ ‫;‪SDL_Rect position, playerPosition‬‬
‫‪4‬‬ ‫;‪SDL_Event event‬‬

‫‪Mario‬‬ ‫لقد قمتُ بإنشاء جدول من نوع ‪ SDL_Surface‬يسمّى ‪ . mario‬و هو جدول من أربع خانات يقوم بتخزين‬
‫ل من الاتجاهات الأربعة )واحد للأسفل‪ ،‬الأعلى‪ ،‬اليمين و اليسار(‪.‬‬
‫في ك ّ‬

‫‪boxOK‬‬ ‫ل من ‪ sprites‬التي قمت بتحميلها أعلاه ‪، box ، wall :‬‬


‫توجد بعد ذلك العديد من المساحات الموافقة لك ّ‬
‫و ‪. objective‬‬

‫؟‬
‫بماذا ينفعنا ‪ currentMario‬؟‬

‫هو عبارة عن مؤش ّر نحو مساحة‪ .‬و هو مؤش ّر يؤش ّر نحو المساحة الموافقة لـ‪ Mario‬المت ّجه نحو الإتجاه الحالي‪ .‬أي أنه عبارة‬
‫عن ‪ Mario) currentMario‬الحالي( الّذي سنقوم بتسويته في الشاشة‪ .‬إذا رأيت في أسفل الدالة ‪ play‬ستجد ‪:‬‬

‫;)‪1 SDL_BlitSurface(currentMario, NULL, screen, &position‬‬

‫‪368‬‬
‫‪ .3.24‬اللعبة‬

‫لا نقوم إذا بلصق عنصر من الجدول ‪ ، mario‬بل المؤش ّر ‪. currentMario‬‬


‫و بلصق ‪ ، currentMario‬يعني أننا سنلصق إما ‪ Mario‬نحو الأسفل‪ ،‬أو نحو الأعلى‪ ،‬إلخ‪.‬‬
‫المؤش ّر ‪ currentMario‬يؤش ّر نحو إحدى خانات الجدول ‪. mario‬‬

‫ماذا بعد غير هذا ؟‬


‫متغير ‪ position‬من نوع ‪ SDL_Rect‬سنستعين به من أجل تعر يف موضع العناصر التي سنقوم بتسويتها )سنحتاج‬
‫‪playerPosition‬‬ ‫ل مساحة !(‪ .‬المتغير‬
‫ل الـ‪ ،sprites‬و لا داعي لإنشاء ‪ SDL_Rect‬من أجل ك ّ‬
‫إليها من أجل ك ّ‬
‫مختلف ‪ :‬إنه يشير إلى أية خانة من الخر يطة يوجد اللاعب‪ .‬أخيراً‪ ،‬المتغير ‪ event‬يهتم بتحليل الأحداث‪.‬‬

‫متغي ّرات ”تقليدي ّة”‬

‫أعرف متغيرات تقليدي ّة نوعا ًما من نوع ‪: int‬‬


‫حان الوقت لـكي ّ‬
‫;‪1 int cont = 1, remainingGoals = 0, i = 0, j = 0‬‬
‫;}‪2 int map[NB_BLOCKS_WIDTH][NB_BLOCKS_HEIGHT] = {0‬‬

‫‪ cont‬و ‪ remainingGoals‬هي متغيرات منطقية‪.‬‬


‫‪ i‬و ‪ j‬هي متغيرات م ُساع ِدة ستساعدنا في قراءة الجدول ‪. map‬‬

‫هنا تبدأ الأمور الهامّة حقّا‪ .‬لقد قمت فعليا ًبإنشاء جدول ذو بُعدين‪ .‬لم أكل ّمك عن هذا النوع من الجداول من قبل‪،‬‬
‫لـكنه الوقت المناسب لتتعل ّم ما يعنيه‪ .‬ليس الأمر صعباً‪ ،‬سترى ذلك بنفسك‪.‬‬

‫لاحظ التعر يف عن كثب ‪:‬‬

‫;}‪1 int map[NB_BLOCKS_WIDTH][NB_BLOCKS_HEIGHT] = {0‬‬

‫هو عبارة عن جدول من ‪) int‬أعداد صحيحة( يختلف في كونه يأخذ حاضنتين مرب ّعتين ] [ ‪ .‬إذا كنت ٺتذكر‬
‫جيدا ً الملف ‪ ، constants.h‬فـ ‪ NB_BLOCKS_WIDTH‬و ‪ NB_BLOCKS_HEIGHT‬هما ثابتان يأخذ كلاهما القيمة‬
‫‪.12‬‬

‫هذا الجدول سيتم ّ إنشاءه في وقت الترجمة هكذا ‪:‬‬

‫;}‪1 int map[12][12] = {0‬‬

‫؟‬
‫لـكن‪ ،‬ماذا يعني هذا ؟‬

‫ل ”خانة” من ‪ map‬توجد ‪ 12‬خانة داخلية‪.‬‬‫هذا يعني أنه من أجل ك ّ‬


‫بهذا تكون لدينا المتغيرات التالية ‪:‬‬

‫‪369‬‬
Mario Sokoban : ‫ عمل تطبيقي‬.24 ‫الفصل‬

map[0][0]
map[0][1]
map[0][2]
map[0][3]
map[0][4]
map[0][5]
map[0][6]
map[0][7]
map[0][8]
map[0][9]
map[0][10]
map[0][11]
map[1][0]
map[1][1]
map[1][2]
map[1][3]
map[1][4]
map[1][5]
map[1][6]
map[1][7]
map[1][8]
map[1][9]
map[1][10]
...
map[11][2]
map[11][3]
map[11][4]
map[11][5]
map[11][6]
map[11][7]
map[11][8]
map[11][9]
map[11][10]
map[11][11]

! ‫ خانة‬144 = 12 * 12 ‫إذا هو جدول من‬


.‫ل من هذه الخانات تمث ّل خانة في خر يطة اللعبة‬
ّ ‫ك‬

: ‫الصورة التالية تعطيك فكرة كيف قُمنا بتمثيل الخر يطة‬

370
‫‪ .3.24‬اللعبة‬

‫و بهذا فالخانة في أعلى اليسار مخزّنة في ]‪. map[0][0‬‬


‫الخانة أعلى اليمين مخزنة في ]‪. map[0][11‬‬
‫الخانة أسفل اليمين )آخر خانة( مخزنة في ]‪. map[11][11‬‬

‫حسب قيمة الخانة )و التي هي عدد صحيح(‪ ،‬نعرف أي خانة من النافذة تحتوي جداراً‪ ،‬أو صندوقاً‪ ،‬أو منطقة‬
‫مستهدفة‪ ،‬إلخ‪.‬‬
‫هنا بالضبط سنستفيد من تعر يف التعداد السابق !‬

‫;}‪1 enum {EMPTY, WALL, BOX, GOAL, MARIO, BOX_OK‬‬

‫إذا كانت قيمة الخانة تساوي ‪ (0) EMPTY‬سنعرف بأن هذه المنطقة من الشاشة يجب أن تبقى بيضاء‪ .‬إذا كانت‬
‫تساوي ‪ (1) WALL‬فسنعرف أنه يجب أن نقوم بلصق صورة جدار‪ ،‬إلخ‪.‬‬

‫تهيئات‬

‫تحميل المساحات‬

‫و الآن‪ ،‬بما أننا قمنا بشرح كل متغيرات الدالة ‪ ، play‬يمكننا البدء في القيام ببعض التهيئات ‪:‬‬

‫‪1‬‬ ‫)‪// Loading the sprites (Boxes, player...‬‬


‫‪2‬‬ ‫;)”‪wall = IMG_Load(”wall.jpg‬‬
‫‪3‬‬ ‫;)”‪box = IMG_Load(”box.jpg‬‬
‫‪4‬‬ ‫;)”‪boxOK = IMG_Load(”box_ok.jpg‬‬
‫‪5‬‬ ‫;)”‪level = IMG_Load(”level.png‬‬

‫‪371‬‬
‫‪Mario Sokoban‬‬ ‫الفصل ‪ .24‬عمل تطبيقي ‪:‬‬

‫‪6‬‬ ‫;)”‪mario[DOWN] = IMG_Load(”mario_bas.gif‬‬


‫‪7‬‬ ‫;)”‪mario[LEFT] = IMG_Load(”mario_gauche.gif‬‬
‫‪8‬‬ ‫;)”‪mario[UP] = IMG_Load(”mario_haut.gif‬‬
‫‪9‬‬ ‫;)”‪mario[RIGHT] = IMG_Load(”mario_droite.gif‬‬

‫ل بواسطة ‪. IMG_Load‬‬
‫لا يوجد شيء صعب ‪ :‬نقوم بتحميل الك ّ‬
‫ل من الإتّ جاهات الأربعة في الجدول‬
‫إن كانت هناك حالة خاصّة‪ ،‬فهي تحميل ‪ .Mario‬إذ أننا نقوم بتحميل ‪ Mario‬في ك ّ‬
‫‪ mario‬باستعمال الثوابت ‪ . RIGHT ، LEFT ، DOWN ، UP :‬كوننا استعملنا هنا ثوابت فستصبح الشفرة أكثر‬
‫وضوحا ً ‪-‬كما تلاحظ‪ . -‬كان بإمكاننا استعمال ]‪ ، mario[0‬لـكن من الأفضل و من الأكثر وضوحا ً أن نستعمل‬
‫]‪ mario[UP‬مثلا ً!‬

‫التوجيه الابتدائيّ لـ‪(currentMario) Mario‬‬

‫نهي ّئ بعدها ‪ currentMario‬لـكيّ تكون له وجهة ابتدائي ّة ‪:‬‬


‫‪1 currentMario = mario[DOWN]; // Mario will be headed down when starting the‬‬
‫‪program‬‬

‫وجدت أنه من المنطقي أكثر أن أبدأ المرحلة فيما يكون ‪ Mario‬موجّها نحو الأسفل )أي نحونا(‪ ،‬كان بامكانك أن‬
‫تكتب مثلا ً‪:‬‬
‫;]‪1 currentMario = mario[RIGHT‬‬

‫ستُلاحظ بأن ‪ Mario‬سيكون موجّها ً نحو اليمين في بداية اللعبة‪.‬‬

‫تحميل الخر يطة‬

‫الآن‪ ،‬يجدر بنا ملئ الجدول ثنائي الأبعاد ‪ . map‬لح ّد الآن‪ ،‬الجدول لا يحتوي إلا أصفاراً‪.‬‬
‫يجب أن نقرأ المستوى المخز ّن في الملف ‪: levels.lvl‬‬
‫‪1 // Loading the level‬‬
‫))‪2 if (!loadLevel(map‬‬
‫‪3‬‬ ‫‪exit(EXIT_FAILURE); // We stop the game if we couldn’t load the level‬‬

‫لقد اخترت معالجة تحميل )و حفظ( المستو يات بواسطة دوال متواجدة بالملف ‪. files.c‬‬
‫هنا ‪ ،‬نستدعي إذا الدالة ‪ . loadLevel‬سنقوم بدراستها بالتفصيل لاحقا )هي ليست معقدة كثيرا ً على أي حال(‪ .‬كل‬
‫ما يهمنا هنا هو معرفة أنه تم تحميل المستوى في الجدول ‪. map‬‬

‫‪x‬‬
‫إذا لم يتم تحميل المستوى )لأن ملف ‪ levels.lvl‬غير موجود(‪ ،‬ست ُرجع الدالة ”خطأ”‪ .‬أمّا في الحالة المعاكسة‬
‫فت ُرجع ”صحيح”‪.‬‬

‫‪372‬‬
‫‪ .3.24‬اللعبة‬

‫نقوم إذا باختبار نتيجة التحميل بواسطة شرط‪ .‬إذا كانت النتيجة سلبية )من هنا استعملت إشارة التعجّ ب لأعب ّر عن‬
‫ل شيء ‪ :‬سنستدعي الدالة ‪. exit‬‬
‫ض ّد الشرط( يتوقف ك ّ‬
‫ل شيء يعمل بشكل جي ّد إذا ً و يمكننا المواصلة‪.‬‬
‫في الحالة الأخرى‪ ،‬ك ّ‬

‫ل خانة ‪... BOX ، EMPTY ، WALL :‬‬


‫نحن نملك الآن جدولا ‪ map‬يصف محتوى ك ّ‬

‫لـ‪Mario‬‬ ‫البحث عن وضعية الانطلاق‬

‫يجب الآن أن نعطي قيمة ابتدائية للمتغير ‪. playerPosition‬‬


‫خاص قليلاً‪ .‬لن نستعين به لتخزين الإحداثيات بالبيكسل و إنما بتخزينه بدلالة الـ”خانات”‬
‫ّ‬ ‫هذا المتغير من نوع ‪SDL_Rect‬‬
‫في الخر يطة‪ .‬و بهذا فإن كانت لدينا ‪:‬‬

‫‪playerPosition.y == 11‬‬ ‫‪ playerPosition.x == 11‬و‬

‫فهذا يعني أن اللاعب متواجد في آخر خانة في أسفل يمين الخر يطة‪.‬‬
‫يمكنك الرجوع إلى الصورة السابقة لتتوضح لك الأمور أكثر‪.‬‬

‫سنقوم بالتقدّم داخل الجدول ‪ map‬و ذلك باستعمال حلقتين‪ .‬نستعمل المتغير ‪ i‬للتقدّم في الجدول عمودياً‪ ،‬و‬
‫نستعمل المتغير ‪ j‬للتقدّم فيه أفقيا ً‪:‬‬
‫‪1‬‬ ‫‪// We search for the position of Mario in the beginning of the game‬‬
‫‪2‬‬ ‫)‪for (i = 0 ; i < NB_BLOCKS_WIDTH ; i++‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫)‪for (j = 0 ; j < NB_BLOCKS_HEIGHT ; j++‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫‪if (map[i][j] == MARIO) // If Mario is in this position‬‬
‫‪7‬‬ ‫{‬
‫‪8‬‬ ‫;‪playerPosition.x = i‬‬
‫‪9‬‬ ‫;‪playerPosition.y = j‬‬
‫‪10‬‬ ‫;‪map[i][j] = EMPTY‬‬
‫‪11‬‬ ‫}‬
‫‪12‬‬ ‫}‬
‫‪13‬‬ ‫}‬

‫ل خانة‪ ،‬نختبر ما إن كانت هذه الأخيرة تحتوي ‪) MARIO‬أي نقطة انطلاق اللاعب في الخر يطة(‪ .‬إذا كانت‬ ‫في ك ّ‬
‫كذلك‪ ،‬نقوم بتخزين الإحداثيات الحالية )المتواجدة في ‪ i‬و ‪ ( j‬في المتغير ‪. playerPosition‬‬
‫نمسح أيضا ًالخانة و ذلك بإعطائها القيمة ‪ EMPTY‬لـكي يتم اعتبارها كخانة فارغة لاحقاً‪.‬‬

‫تفعيل تكرار الضغط على الأزرار‬

‫آخر شيء‪ ،‬أمر سهل جدا ً ‪ :‬سنقوم بتفعيل تكرار الضغط على الأزرار لـكي نستطيع التحر ّك في الخر يطة بترك الزر مضغوطاً‪.‬‬
‫‪1 // Enabeling keys repetition‬‬
‫;)‪2 SDL_EnableKeyRepeat(100, 100‬‬

‫‪373‬‬
Mario Sokoban : ‫ عمل تطبيقي‬.24 ‫الفصل‬

‫الحلقة الرئيسية‬

.‫ يمكننا الآن العمل على الحلقة الرئيسية‬،‫ل شيء‬


ّ ‫ لقد قُمنا بتهيئة ك‬،ً‫حسنا‬

.ً‫ هي فقط كبيرة قليلا‬.‫إنها حلقة تقليدي ّة تعمل بنفس المخطط الذي تعمل به الحلقات التي رأيناها لح ّد الآن‬

: ‫ الذي يختبر الحد َث‬switch ‫فلنرى عن قرب الـ‬

1 switch(event.type)
2 {
3 case SDL_QUIT
4 cont = 0;
5 break;
6 case SDL_KEYDOWN:
7 switch(event.key.keysym.sym)
8 {
9 case SDLK_ESCAPE:
10 cont = 0;
11 break;
12 case SDLK_UP:
13 currentMario = mario[UP];
14 movePlayer(map, &playerPosition, UP);
15 break;
16 case SDLK_DOWN:
17 currentMario = mario[DOWN];
18 movePlayer(map, &playerPosition, DOWN);
19 break;
20 case SDLK_RIGHT:
21 currentMario = mario[RIGHT];
22 movePlayer(map, &playerPosition, RIGHT);
23 break;
24 case SDLK_LEFT:
25 currentMario = mario[LEFT];
26 movePlayer(map, &playerPosition, LEFT);
27 break;
28 }
29 break;
30 }

.‫ فستنتهي اللعبة و نرجع للقائمة الرئيسية‬، Esc ‫إذا ضغطنا على الزر‬

”‫”يمين‬،”‫ ”أسفل‬،”‫ سنختبر فقط ما إن ضغط اللاعب على الأزرار ”أعلى‬: ‫ لا توجد العديد من الأحداث لنعالجها‬،‫كما ترى‬
.‫أو ”يسار” من لوحة المفاتيح‬
‫ ! إذا ضغطنا السهم الموجه نحو‬currentMario ‫ و هنا يتدخّل المتغير‬.Mario ‫على حسب الزر المضغوط نغي ّر اتجاه‬
: ً ‫الأعلى إذا‬

1 currentMario = mario[UP];

374
‫‪ .3.24‬اللعبة‬

‫إذا ضغطنا على السهم الموجّه نحو الأسفل فإذا ً ‪:‬‬

‫;]‪1 currentMario = mario[DOWN‬‬

‫م جدا ً ‪ :‬نستدعي الدالة ‪ . movePlayer‬هذه الدالة ستقوم بتحر يك اللاعب في الخر يطة إن كان له‬
‫الآن‪ ،‬شيء مه ّ‬
‫الحق في فعل ذلك‪.‬‬

‫• مثلاً‪ ،‬لا يمكننا أن نُحرك ‪ Mario‬إلى الأعلى إن كان متواجدا ً أصلا ًفي الحافة العلو ية للنافذة‪.‬‬

‫• لا يمكننا أيضا ًأن نحر ّكه للأعلى إن كان فوقه جدار‪.‬‬

‫• لا يمكننا أن نحر ّكه للأعلى إن كان فوقه صندوقان‪.‬‬

‫• على العكس‪ ،‬يمكننا تحر يكه للأعلى إن تواجد صندوق واحد فوقه‪.‬‬

‫• لـكن احذر‪ ،‬لا يمكننا تحر يكه للأعلى إن تواجد صندوق واحد فوقه و كان هذا الصندوق متواجد أصلا ًفي الحافة‬
‫العلو ية للنافذة !‬

‫؟‬
‫يا إلاهي ! ماهذا السوق ؟‬

‫هذا ما نسمّيه بـمعالجة الاصطدامات )‪ .(Collisions management‬و لـكي أضمن لك‪ ،‬نحن نقوم بالتعامل مع‬
‫الاصطدامات البسيطة بما أن اللاعب يتحر ّك خانة بخانة و في أربع اتجاهات فقط‪ .‬في لعبة ثنائية الأبعاد أين يتحر ّك اللاعب‬
‫ل الاتجاهات بيكسلا ببيكسل‪ ،‬يكون التحكّم في الاصطدامات أمرا أصعب‪.‬‬
‫في ك ّ‬

‫لـكن هناك ماهو أسوء ‪ :‬الألعاب ثلاثية الأبعاد‪ .‬التحكم في الإصطدامات في لعبة ثلاثية الأبعاد يُع ّد كابوسا ً بالنسبة‬
‫للمبرمجـين‪ .‬لحسن الحظ‪ ،‬توجد مكتبات للتحكّم في الاصطدامات في العوالم ثلاثية الأبعاد و التي تقوم بالـكثير من العمل في‬
‫مكاننا‪.‬‬

‫لنرجع للدالة ‪ movePlayer‬و لنرك ّز ‪ .‬نقوم بإعطائها ثلاثة معاملات ‪:‬‬

‫• الخر يطة ‪ :‬لـكي تستطيع قراءتها و أيضا ًالتعديل عليها إذا قمنا بتحر يك صندوق مثلاً‪.‬‬

‫• وضعية اللاعب ‪ :‬هنا أيضاً‪ ،‬يجب على الدالة قراءة و ”ربما” تعديل وضعية اللاعب‪.‬‬

‫• الإتجاه الذي نطلب من اللاعب التوجّه إليه ‪ :‬نستعمل هنا أيضا ًالثوابت ‪ RIGHT ، LEFT ، DOWN ، UP :‬من‬
‫أجل فهم الشفرة بشكل أفضل‪.‬‬

‫ل الاختبارات داخل الـ ‪ ، switch‬لـكن بهذا سيصبح‬


‫سندرس الدالة ‪ movePlayer‬لاحقاً‪ .‬كان بإمكاني وضع ك ّ‬
‫كبيرا ً و ستصعب علينا قراءته‪ .‬و من هنا نرى الفائدة من تقسيم الشفرة إلى عدّة دوال‪.‬‬

‫‪375‬‬
Mario Sokoban : ‫ عمل تطبيقي‬.24 ‫الفصل‬

‫ل شيء‬
ّ ‫ فلنسو ّي ك‬،‫التسو ية‬

‫ مهما‬.‫ قد تكون الخر يطة قد تغي ّرت و كذا وضعية اللاعب‬،‫ في هذه الوضعية من البرنامج‬: switch ‫لقد انتهينا من الـ‬
! ‫ لقد حان وقت التسو ية‬،‫كان‬

: ‫سنبدأ بمسح الشاشة و ذلك بإعطائها لون خلفية أبيض‬

1 // Clearing the screen


2 SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 255, 255, 255));

.‫ لـكي نعرف أي عنصر سنقوم بتسويته و في أي منطقة من الشاشة‬map ‫ نقوم بالتقدّم في الجدول ذو البعدين‬،‫و الآن‬
: ‫ خانة من الجدول‬144‫سنستعمل حلقتين كما رأينا سابقا ًللتقدّم في الـ‬

1 // Placing the objects on the screen


2 remainingGoals = 0;
3 for (i = 0 ; i < NB_BLOCKS_WIDTH ; i++)
4 {
5 for (j = 0 ; j < NB_BLOCKS_HEIGHT ; j++)
6 {
7 position.x = i � BLOCK_SIZE;
8 position.y = j � BLOCK_SIZE;
9 switch(map[i][j])
10 {
11 case WALL:
12 SDL_BlitSurface(wall, NULL, screen, &position);
13 break;
14 case BOX:
15 SDL_BlitSurface(box, NULL, screen, &position);
16 break;
17 case BOX_OK:
18 SDL_BlitSurface(boxOK, NULL, screen, &position);
19 break;
20 case GOAL:
21 SDL_BlitSurface(level, NULL, screen, &position);
22 remainingGoals = 1;
23 break;
24 }
25 }
26 }

‫ ( لـكي نضع العنصر الحالي في المكان المناسب‬SDL_Rect ‫ )من نوع‬position ‫ نحض ّر المتغير‬،‫من أجل كل خانة‬
.‫من الشاشة‬
: ‫العملية ج ّد بسيطة‬

1 position.x = i � BLOCK_SIZE;
2 position.y = j � BLOCK_SIZE;

376
‫‪ .3.24‬اللعبة‬

‫يكفي ضرب ‪ i‬بـ ‪ BLOCK_SIZE‬لـكي نعرف قيمة ‪. position.x‬‬


‫و بهذا‪ ،‬فإن كنا نتواجد في الخانة الثالثة‪ ،‬أي أن ‪) 2 = i‬لا تنس أن ‪ i‬يبدأ من ‪ ،(! 0‬نقوم إذا ً بالعملية ‪.68 = 34 * 2‬‬
‫إذا نقوم بلصق الصورة ‪ 68‬بيكسلا نحو اليمين في المساحة ‪. screen‬‬
‫نقوم بنفس الشيء بالنسبة للترتيبة ‪. y‬‬

‫بعد ذلك‪ ،‬نطب ّق ‪ switch‬على الخانة التي نقوم بتحليلها من الخر يطة‪.‬‬
‫هنا أيضاً‪ ،‬استعمال الثوابت يعتبر شيئا ًعمليا ًو يسمح بقراءة م ُثلى للشفرة‪.‬‬
‫نختبر إذا ما إن كانت الخانة تساوي ‪ ، WALL‬في هذه الحالة نقوم بلصق جدار‪ .‬نفس الشيء بالنسبة للصناديق و المناطق‬
‫المُستهدفة‪.‬‬

‫اختبار الفوز‬

‫تلاحظ أنه قبل استعمال الحلقتين المتداخلتين‪ ،‬نعطي القيمة الإبتدائية ‪ 0‬للمتغير المنطقي ‪. remainingGoals‬‬
‫هذا المتغير المنطقي يأخذ القيمة ‪ 1‬ما إن نقوم بكشف منطقة م ُستهدفة على الخر يطة‪ .‬حينما لا ٺتبقّى أية منطقة مستهدفة‪،‬‬
‫فهذا يعني أن كل الصناديق متواجدة فوق هذه المناطق )لم ٺتبقّ سوى صناديق ‪.( BOX_OK‬‬

‫يكفي أن نختبر ما إن كان المتغير المنطقي يحمل القيمة ”خطأ”‪ ،‬أي أنه لم ٺتبقّ أية منطقة مستهدفة‪.‬‬
‫في هذه الحالة‪ ،‬نُعطي القيمة ‪ 0‬للمتغير ‪ cont‬من أجل إيقاف الجولة ‪:‬‬

‫‪1 // If there’s no remaining goal, we win‬‬


‫)‪2 if (!remainingGoals‬‬
‫‪3‬‬ ‫;‪cont = 0‬‬

‫اللاعب‬

‫لم يتبقّ سوى تسو ية اللاعب ‪:‬‬

‫‪1‬‬ ‫‪// We place the player in the‬‬ ‫‪right position‬‬


‫‪2‬‬ ‫‪position.x = playerPosition.x‬‬ ‫;‪� BLOCK_SIZE‬‬
‫‪3‬‬ ‫‪position.y = playerPosition.y‬‬ ‫;‪� BLOCK_SIZE‬‬
‫‪4‬‬ ‫‪SDL_BlitSurface(currentMario,‬‬ ‫;)‪NULL, screen, &position‬‬

‫نحس ُب وضعيته )بالبيكسل هذه المرة( و ذلك بالقيام بعملية ضرب بين ‪ playerPosition‬و ‪. BLOCK_SIZE‬‬
‫بعد ذلك‪ ،‬نقوم بلصق اللاعب في الوضعية المناسبة‪.‬‬

‫القلب !‬

‫ل شيء‪ ،‬يكفي أن نُظهر الشاشة للمستعمل ‪:‬‬


‫لقد قُمنا بك ّ‬

‫;)‪1 SDL_Flip(screen‬‬

‫‪377‬‬
‫‪Mario Sokoban‬‬ ‫الفصل ‪ .24‬عمل تطبيقي ‪:‬‬

‫نهاية الدالة ‪ :‬إلغاء التحميل‬

‫بعد الحلقة الرئيسية‪ ،‬يجدر بنا القيام بتحرير الذاكرة التي حجزناها للـ‪ sprites‬التي حمّلناها‪.‬‬
‫نقوم أيضا بتعطيل تكرار الضغط على الأزرار و ذلك بإعطاء القيمة ‪ 0‬للدالة ‪: SDL_EnableKeyRepeat‬‬
‫‪1‬‬ ‫)‪// Disabling keys repetition (reset to 0‬‬
‫‪2‬‬ ‫;)‪SDL_EnableKeyRepeat(0, 0‬‬
‫‪3‬‬ ‫‪// Freeing the used surfaces‬‬
‫‪4‬‬ ‫;)‪SDL_FreeSurface(wall‬‬
‫‪5‬‬ ‫;)‪SDL_FreeSurface(box‬‬
‫‪6‬‬ ‫;)‪SDL_FreeSurface(boxOK‬‬
‫‪7‬‬ ‫;)‪SDL_FreeSurface(level‬‬
‫‪8‬‬ ‫)‪for (i = 0 ; i < 4 ; i++‬‬
‫‪9‬‬ ‫;)]‪SDL_FreeSurface(mario[i‬‬

‫‪movePlayer‬‬ ‫الدالة‬

‫هذه الدالة متواجدة أيضا ً في الملف ‪ . game.c‬هي دالة ‪ ...‬معقّدة جدّا من ناحية كتابتها‪ .‬و ربّما هي الدالة الأكثر‬
‫صعوبة حينما نريد برمجة لعبة ‪.Sokoban‬‬

‫الدالة ‪ movePlayer‬تختبر ما إن كان لدينا الحق في تحر يك اللاعب في الإتجاه المطلوب‪ .‬تقوم بتحديث وضعية‬ ‫تذكير ‪:‬‬

‫اللاعب ‪ playerPosition‬و أيضا ًبتحديث الخر يطة إذا تم تحر يك صندوق‪.‬‬


‫هذا نموذج الدالة ‪:‬‬
‫;)‪1 void movePlayer(int map[][NB_BLOCKS_HEIGHT], SDL_Rect �pos,int direction‬‬

‫هذا النموذج خاص قليلاً‪ .‬تلاحظ أنني أبعث الجدول ‪ map‬و أحدد الحجم الخاص بالب ُعد الثاني‬
‫) ‪.( NB_BLOCKS_HEIGHT‬‬
‫لماذا هذا ؟‬
‫الإجابة معقّدة قليلا ًلـكي أقوم بمناقشتها في وسط هذا الفصل‪ .‬لـكي نبسّط الأمور‪ ،‬لغة الـ‪ C‬لا ٺتكهّن بأننا نتحدّث عن‬
‫جدول ثنائي الأبعاد و أنه يجب أن نعطي على الأقل حجم الب ُعد الثاني لـكي تشتغل الأمور‪.‬‬
‫إذا‪ ،‬حينما تبعث جدولا ًذا بُعدين إلى دالة‪ ،‬يجب أن تحددّ حجم الب ُعد الثاني للجدول في النموذج‪ .‬هكذا تعمل الأمور‪ ،‬إن‬
‫الأمر ضروري‪.‬‬

‫أمر آخر ‪ :‬تلاحظ أن ‪ playerPosition‬تُسمى ‪ pos‬في هذه الدالة‪ .‬لقد اخترت اختصار الاسم لـكي تسهل‬
‫كتابته بما أننا سنحتاج إلى كتابته عدّة مرات‪ ،‬لـكي لا نتعب‪.‬‬

‫فلنبدأ باختبار الإتجاه الذي نريد التوجّه إليه و ذلك باستعمال ‪ switch‬ضخم ‪:‬‬
‫)‪1 switch(direction‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫‪case UP:‬‬
‫‪4‬‬ ‫‪/� etc �/‬‬

‫‪378‬‬
‫‪ .3.24‬اللعبة‬

‫فلننطلق في رحلة من الاختبارات المجنونة !‬

‫ل حالة ممكنة محاولين ألا ننسى أية واحدة‪.‬‬


‫يجب الآن أن نكتب الاختبارات الخاصة بك ّ‬

‫هكذا تقوم الخطة التي أعتمدها ‪ :‬أختبر كل الحالات الممكنة للاصطدامات حالة بحالة‪ ،‬و ما إن أكشف عن اصطدام‬
‫)أي أن اللاعب غير متمكّن من التحر ّك( أضع الأمر ‪ break‬لأخرج من الـ ‪ ، switch‬و بهذا أمنع التحر ّك‪.‬‬

‫ل حالات الاصطدام المتواجدة للاعب يريد التحر ّك نحو الأعلى ‪:‬‬


‫هذا مثال عن ك ّ‬

‫• اللاعب متواجد أصلا ًفي أقصى أعلى الخر يطة‪.‬‬

‫• يوجد جدار فوق اللاعب‪.‬‬

‫• يوجد صندوقان معا ًفوق اللاعب )و هو غير قادر على دفع صندوقين(‪.‬‬

‫• يوجد صندوق فوق اللاعب و الصندوق متواجد في الحافة العلو ية للخر يطة‪.‬‬

‫ل هذه الاختبارات‪ ،‬يمكننا تحر يك اللاعب‪.‬‬


‫مرت ك ّ‬
‫إذا ّ‬

‫سأر يك الاختبارات اللازمة من أجل التحر ّك نحو الأعلى‪ .‬من أجل الحالات الأخرى‪ ،‬يكفي تعديل الشفرة قليلا‪.‬‬

‫‪1 if (pos−>y − 1 < 0) // If the player exceeds the screen, we stop‬‬


‫‪2‬‬ ‫;‪break‬‬

‫نبدأ بالتحقق ما إن كان اللاعب متواجدا ً أعلى النافذة‪ .‬بالفعل‪ ،‬لو نحاول أن نطلب الخانة ]‪ map[5][-1‬مثلاً‪،‬‬
‫سيتوقف البرنامج بشكل خاطئ !‬
‫نبدأ إذا بالتأكد من أننا لن ”نتجاوز” الشاشة‪.‬‬

‫بعد ذلك ‪:‬‬

‫‪1 if (map[pos−>x][pos−>y − 1] == WALL) // If there’s a wall, we stop‬‬


‫‪2‬‬ ‫;‪break‬‬

‫هنا أيضاً‪ ،‬الأمر بسيط‪ .‬نتحقق من عدم وجود جدار فوق اللاعب‪ .‬إذا كان هناك واحد‪ ،‬نتوق ّف ) ‪.( break‬‬

‫بعد ذلك )حافظ على عينيك( ‪:‬‬

‫( ‪1 // If we want to push a box, we have to verify that there’s no wall behind it‬‬
‫)‪or another box, or the world’s limit‬‬
‫( && )‪2 if ((map[pos−>x][pos−>y − 1] == BOX || map[pos−>x][pos−>y −1] == BOX_OK‬‬
‫‪pos−>y − 2 < 0 || map[pos−>x][pos−>y − 2] == WALL || map[pos−>x][pos−>y −‬‬
‫))‪2] == BOX || map[pos−>x][pos−>y − 2] == BOX_OK‬‬
‫‪3‬‬ ‫;‪break‬‬

‫‪379‬‬
‫‪Mario Sokoban‬‬ ‫الفصل ‪ .24‬عمل تطبيقي ‪:‬‬

‫هذا الاختبار الضخم يمكن ترجمته كالتالي ‪” :‬إذا كان هناك صندوق فوق اللاعب )أو صندوق في الوضعية المناسبة(‬
‫و إذا كان فوق هذا الصندوق يوجد إما الفراغ )سنتجاوز من الحاف ّة العلو ية لأننا في أقصى الأعلى(‪ ،‬أو صندوق آخر‪ ،‬أو‬
‫صندوق في الوضعي ّة المناسبة ‪ :‬إذا ً لا يمكننا التحر ّك ‪ :‬خروج ) ‪.”( break‬‬

‫إذا تمكنّا من عبور هذا الإختبار فنحن قادرون على التحر ّك‪ ،‬أوووف !‬
‫نستدعي اولا ًدالة تقوم بتحر يك الصندوق إن كنا بحاجة إلى ذلك ‪:‬‬
‫‪1 // If we are here, so we can move the player‬‬
‫‪2 // We verify if there’s a box to move first‬‬
‫;)]‪3 moveBox(&map[pos−>x][pos−>y − 1], &map[pos−>x][pos−>y − 2‬‬

‫‪moveBox‬‬ ‫تحر يك الصناديق ‪:‬‬

‫قررت معالجة تحر ّك الصناديق باستعمال دالة أخرى لأن الشفرة تبقى نفسها من أجل الإتجاهات الأربعة‪ .‬يجب فقط أن‬
‫نتأكّد بأننا قادرون على التحر ّك )هذا ما كنتُ بصدد شرحه(‪.‬‬
‫سنبعث للدالة معاملين ‪ :‬محتوى الخانة التي نريد الذهاب إليها و محتوى الخانة التي تليها‪.‬‬
‫)‪1 void moveBox(int �firstSquare, int �secondSquare‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫)‪if (�firstSquare == BOX || �firstSquare == BOX_OK‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫)‪if (�secondSquare == GOAL‬‬
‫‪6‬‬ ‫;‪�secondSquare = BOX_OK‬‬
‫‪7‬‬ ‫‪else‬‬
‫‪8‬‬ ‫;‪�secondSquare = BOX‬‬
‫‪9‬‬ ‫)‪if (�firstSquare == BOX_OK‬‬
‫‪10‬‬ ‫;‪�firstSquare = GOAL‬‬
‫‪11‬‬ ‫‪else‬‬
‫‪12‬‬ ‫;‪�firstSquare = EMPTY‬‬
‫‪13‬‬ ‫}‬
‫} ‪14‬‬

‫هذه الدالة تقوم بتحديث الخر يطة و هي تأخذ كمعاملات مؤش ّرات نحو الخانات المعني ّة‪.‬‬
‫سأتركك لتقرأها‪ ،‬فهي سهلة للفهم‪ .‬لا يجب أن ننسى أننا إذا حرّكنا ‪ . BOX_OK‬يجب تعو يض المكان الذي كان به‬
‫بـ ‪ . OBJECTIVE‬و إلا‪ ،‬إذا كان ‪ ، BOX‬سنعو ّض مكانه بـ ‪. EMPTY‬‬

‫تحر يك اللاعب‬

‫نعود للدالة ‪. movePlayer‬‬


‫نحن هنا في الحالة الصحيحة‪ ،‬سنقوم بتحر يك اللاعب‪.‬‬

‫كيف نفعل ذلك ؟ هذا أمر سهل ‪:‬‬


‫)!‪1 pos−>y−−; // Finally, we can move up the player (ouf‬‬

‫يكفي أن ننق ِص من الترتيبة )لأن اللاعب يريد الصعود للأعلى(‪.‬‬

‫‪380‬‬
‫ تحميل و حِفظ المستو يات‬.4.24

‫تلخيص‬

: ‫ل الاختبارات اللازمة من أجل الصعود إلى الأعلى‬


ّ ‫ هاهي ك‬،‫كملخّ ص‬

1 switch(direction)
2 {
3 case UP:
4 if (pos−>y − 1 < 0) // If the player exceeds the screen, we stop
5 break;
6 if (map[pos−>x][pos−>y − 1] == WALL) // If there’s a wall, we stop
7 break;
8 // If we want to push a box, we have to verify that there’s no wall
behind it (or another box, or the world’s limit)
9 if ((map[pos−>x][pos−>y − 1] == BOX || map[pos−>x][pos−>y − 1] ==
BOX_OK) && (pos−>y − 2 < 0 || map[pos−>x][pos−>y − 2] == WALL ||
map[pos−>x][pos−>y − 2] == BOX || map[pos−>x][pos−>y − 2] == BOX_OK
))
10 break;
11 // If we are here, so we can move the player
12 // We verify if there’s a box to move first
13 moveBox(&map[pos−>x][pos−>y − 1], &map[pos−>x][pos−>y − 2]);
14 pos−>y−−; // Finally, we can move up the player (ouf!)
15 break;

‫ ليست مطابقة تماما‬،‫ عليك ملائمة الشفرة‬،‫سأترك لك عناء نقل الشفرة و تعديلها من أجل الحالات الأخرى )احذر‬
.(! ‫مرة‬
ّ ‫ل‬ّ ‫في ك‬

! ‫ها قد انتهينا من كتابة شفرة اللعبة‬


.‫ سنرى بعد ذلك كيف نقوم بكتابة شفرة م ُنشئ المستو يات‬.‫ بقي لنا أن نرى دالة التحميل و حِفظ المستو يات‬:ً ‫ قريبا‬،ً‫حسنا‬
! ‫ سيكون هذا سر يعا‬،‫كن متأكّدا‬

‫تحميل و حِفظ المستو يات‬ 4.24

: ‫ يحتوي على دالتين‬files.c ‫الملف‬

. loadLevel •

. saveLevel •

.‫فلنبدأ بتحميل المستوى‬

381
‫‪Mario Sokoban‬‬ ‫الفصل ‪ .24‬عمل تطبيقي ‪:‬‬

‫‪loadLevel‬‬ ‫تحميل المستوى‬

‫هذه الدالة تأخذ معاملا ‪ :‬الخر يطة‪ .‬هنا أيضاً‪ ،‬يجب تحديد مقدار الب ُعد الثاني للجدول لأننا نتكلم عن جدول ذو بعدين‪.‬‬
‫الدالة ت ُرجع متغيرا منطقيا ‪” :‬صحيح” إذا تم التحميل بنجاح‪” ،‬خطأ” إذا فشل‪.‬‬

‫النموذج إذا هو ‪:‬‬

‫;)]‪1 int loadLevel(int level[][NB_BLOCKS_HEIGHT‬‬

‫فلنرى بداية الدالة ‪:‬‬

‫‪1‬‬ ‫;‪FILE� file = NULL‬‬


‫‪2‬‬ ‫;}‪char fileLine[NB_BLOCKS_WIDTH � NB_BLOCKS_HEIGHT + 1] = {0‬‬
‫‪3‬‬ ‫;‪int i = 0, j = 0‬‬
‫‪4‬‬ ‫;)”‪file = fopen(”levels.lvl”, ”r‬‬
‫‪5‬‬ ‫)‪if (file == NULL‬‬
‫‪6‬‬ ‫;‪return 0‬‬

‫نقوم بإنشاء جدول للتخزين المؤق ّت للنتيجة الخاصة بتحميل المستوى‪.‬‬


‫نفتح الملف بوضع ”قراءة فقط” ) ‪ .( r‬نوقف الدالة و ذلك بإرجاع القيمة ‪”) 0‬خطأ”( إذا فشلت عملية فتح الملف‪.‬‬
‫عملية تقليدي ّة‪.‬‬

‫الملف ‪ levels.lvl‬يحتوي على سطر و الذي هو عبارة عن ٺتالي أرقام‪ .‬كل رقم يمث ّل خانة من المستوى‪ ،‬مثلا ‪:‬‬

‫]‪11111001111111111400000111110001100103310101101100000200121110 [...‬‬

‫يمكننا إذا قراءة هذا السطر باستعمال ‪: fgets‬‬

‫;)‪1 fgets(fileLine, NB_BLOCKS_WIDTH � NB_BLOCKS_HEIGHT + 1, file‬‬

‫سنقوم بتحليل محتوى ‪ . fileLine‬نحن نعرف أن أول ‪ 12‬محرفا تمثل السطر الأول‪ ،‬الـ‪ 12‬محرفا الموالية تمثل السطر‬
‫الموالي‪ ،‬إلى آخره‪.‬‬

‫‪1 for (i = 0 ; i‬‬ ‫)‪< NB_BLOCKS_WIDTH ; i++‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪for (j‬‬ ‫)‪= 0 ; j < NB_BLOCKS_HEIGHT ; j++‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫‪switch (fileLine[(i‬‬ ‫)]‪� NB_BLOCKS_WIDTH) + j‬‬
‫‪6‬‬ ‫{‬
‫‪7‬‬ ‫‪case ’0’:‬‬
‫‪8‬‬ ‫]‪level[j][i‬‬ ‫;‪= 0‬‬
‫‪9‬‬ ‫;‪break‬‬
‫‪10‬‬ ‫‪case ’1’:‬‬
‫‪11‬‬ ‫]‪level[j][i‬‬ ‫;‪= 1‬‬
‫‪12‬‬ ‫;‪break‬‬
‫‪13‬‬ ‫‪case ’2’:‬‬
‫‪14‬‬ ‫]‪level[j][i‬‬ ‫;‪= 2‬‬

‫‪382‬‬
‫‪ .4.24‬تحميل و حِفظ المستو يات‬

‫‪15‬‬ ‫;‪break‬‬
‫‪16‬‬ ‫‪case ’3’:‬‬
‫‪17‬‬ ‫;‪level[j][i] = 3‬‬
‫‪18‬‬ ‫;‪break‬‬
‫‪19‬‬ ‫‪case ’4’:‬‬
‫‪20‬‬ ‫;‪level[j][i] = 4‬‬
‫‪21‬‬ ‫;‪break‬‬
‫‪22‬‬ ‫}‬
‫‪23‬‬ ‫}‬
‫} ‪24‬‬

‫بواسطة عملية حسابية بسيطة‪ ،‬نأخذ الحرف الذي يهمّنا في ‪ fileLine‬و نحلل قيمته‪.‬‬

‫!‬
‫إنها ”حروف” مخزّنة في الملف‪ .‬ما أريد أن أقوله بهذا هو أن ’‪ ’0‬مخز ّن كمحرف ’‪ ASCII ’0‬و أن قيمته‬
‫ليست ‪! 0‬‬
‫لنحلل الملف‪ ،‬يجب الاختبار بـ ’‪ case ’0‬و ليس ‪ ! case 0‬احذر من الخلط بين الحروف و الأرقام !‬

‫ل في الجدول ‪ . map‬الخر يطة‬


‫يقوم الـ ‪ switch‬بالتحو يل ‪ ، 1 ⇐ ’1’ ، 0 ⇐ ’0’ :‬إلخ‪ .‬يقوم بوضع الك ّ‬
‫تسمّى ‪ level‬في هذه الدالة لـكن هذا لا يغي ّر أي شيء‪.‬‬

‫ل شيء تم ّ على ما ي ُرام‪.‬‬


‫ما إن يتم هذا‪ ،‬يمكننا غلق الملف و إرجاع القيمة ‪ 1‬لنقول أن ك ّ‬

‫;)‪1 fclose(file‬‬
‫;‪2 return 1‬‬

‫‪ASCII‬‬ ‫أخيراً‪ ،‬تحميل المستوى من الملف لم يكن معقداً‪ .‬الفخّ الوحيد الذي و ُجب تجن ّبه هو التفكير في تحو يل القيمة‬
‫’‪ ’0‬إلى الرقم ‪) 0‬نفس الشيء بالنسبة لـ ‪.( ... ، 4 ، 3 ، 2 ، 1‬‬

‫‪saveLevel‬‬ ‫حِفظ المستوى‬

‫هذه الدالة أسهل ‪:‬‬

‫)]‪1 int saveLevel(int level[][NB_BLOCKS_HEIGHT‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪FILE� file = NULL‬‬
‫‪4‬‬ ‫;‪int i = 0, j = 0‬‬
‫‪5‬‬ ‫;)”‪file = fopen(”levels.lvl”, ”w‬‬
‫‪6‬‬ ‫)‪if (file == NULL‬‬
‫‪7‬‬ ‫;‪return 0‬‬
‫‪8‬‬ ‫)‪for (i = 0 ; i < NB_BLOCKS_WIDTH ; i++‬‬
‫‪9‬‬ ‫{‬
‫‪10‬‬ ‫)‪for (j = 0 ; j < NB_BLOCKS_HEIGHT ; j++‬‬
‫‪11‬‬ ‫{‬
‫‪12‬‬ ‫;)]‪fprintf(file, ”%d”, level[j][i‬‬

‫‪383‬‬
‫‪Mario Sokoban‬‬ ‫الفصل ‪ .24‬عمل تطبيقي ‪:‬‬

‫‪13‬‬ ‫}‬
‫‪14‬‬ ‫}‬
‫‪15‬‬ ‫;)‪fclose(file‬‬
‫‪16‬‬ ‫;‪return 1‬‬
‫} ‪17‬‬

‫استعملت الدالة ‪ fprintf‬من أجل ”ترجمة” أعداد الجدول إلى حروف ‪ .ASCII‬كانت هنا الصعوبة الوحيدة ‪ :‬تجب‬
‫عدم كتابة ‪ 0‬و إنما ’‪. ’0‬‬

‫م ُنـشئ المستو يات‬ ‫‪5.24‬‬

‫هذا الأخير سهل الكتابة أكثر مما تتخيل‪.‬‬


‫بالمناسبة‪ ،‬هذه تقنية تسمح بز يادة عمر لعبتنا‪ ،‬فلما نتجاهلها ؟‬

‫هكذا تسري الأمور ‪:‬‬

‫• نستعمل الفأرة لوضع الكتل التي نريدها في النافذة‪.‬‬

‫• النقر باليمين يسمح بمسح الكتلة الذي ٺتواجد فوقها الفأرة‪.‬‬

‫• النقر باليسار يسمح بوضع شيء على الخر يطة‪ .‬هذا الشيء يكون مخزّنا ً‪ :‬افتراضي ّا‪ ،‬نقوم بوضع الجدران بالنقر الأيسر‬
‫للفأرة‪ .‬يمكننا تغيير الشيء الذي نريد وضعه في الخر يطة بالضغط على الأزرار المتواجدة في لوحة الأرقام ‪:‬‬

‫‪ .1‬جدار‪.‬‬

‫‪ .2‬صندوق‪.‬‬

‫‪ .3‬منطقة م ُستهدفة‪.‬‬

‫‪ .4‬مكان انطلاق ‪.Mario‬‬

‫• بالضغط على ‪ S‬يتم حفظ المستوى‪.‬‬

‫• يمكننا الرجوع إلى القائمة الرئيسية بالضغط على ‪. Esc‬‬

‫‪384‬‬
‫ م ُنـشئ المستو يات‬.5.24

‫التهيئات‬

‫ و‬،‫لصق” لدالة اللعبة‬-‫ ولذلك فقط بدأت في كتابتها باستعمال ”نسخ‬.‫ الدالة الخاصة باللعبة‬،‫ تشبه هذه الدالة‬،‫بشكل عام‬
.‫بعد ذلك قمتُ بنزع ما لا أحتاج ُه و أضفت مميزات جديدة‬

: ‫هذه كانت البداية‬


1 void editor(SDL_Surface� screen)
2 {
3 SDL_Surface �wall = NULL, �box = NULL, �level = NULL, �mario = NULL;
4 SDL_Rect position;
5 SDL_Event event;
6 int cont = 1, leftClickInProgress = 0, rightClickInProgress = 0;
7 int currentObject = WALL, i = 0, j = 0;
8 int map[NB_BLOCKS_WIDTH][NB_BLOCKS_HEIGHT] = {0};
9 // Loading the objects and the level
10 wall = IMG_Load(”wall.jpg”);
11 box = IMG_Load(”box.jpg”);
12 level = IMG_Load(”level.png”);
13 mario = IMG_Load(”mario_bas.gif”);
14 if (!loadLevel(map))
15 exit(EXIT_FAILURE);

.‫هنا تجد تعر يف المتغيرات و التهيئات اللازمة‬


‫ بلوحة المفاتيح و‬Mario ‫ لن نقوم بتوجيه‬،‫ في الواقع‬.(‫ واحد )المت ّجه نحو الأسفل‬Mario ‫تلاحظ أنني لا أقوم بتحميل إلا‬
.‫إنما نحتاج إلى ملصق يمث ّل وضعية الانطلاق الخاصة به‬

385
‫‪Mario Sokoban‬‬ ‫الفصل ‪ .24‬عمل تطبيقي ‪:‬‬

‫المتغير ‪ currentObject‬يحفظ الشيء الذي يختاره المُستعمل حالياً‪ .‬افتراضي ّا‪ ،‬هذا الشيء هو ‪ . WALL‬أي أننا في‬
‫البداية إذا نقرنا بالزرّ اليسار سنقوم بوضع جدار‪ ،‬لـكن يمكن تغيير هذا بواسطة المستعمل و ذلك بالضغط على ‪، 2 ، 1‬‬
‫‪ 3‬أو ‪. 4‬‬

‫المتغيرات المنطقية ‪ leftClickInProgress‬و ‪ rightClickInProgress‬كما تشير أسماؤها‪ ،‬تسمح بحفظ‬


‫ما إن كان هناك نقر ياليمين حاليا ً )أي أن زر الفأرة مضغوط(‪ .‬سأشرح لك المبدأ لاحقاً‪ .‬على أي حال‪ ،‬هذه التقنية‬
‫تسمح لنا بإضافة أشياء إلى الخر يطة بترك زر الفأرة مضغوطاً‪ ،‬و إلا فسنكون مجـبرين على الضغط على الزر عدة مرات من‬
‫أجل وضع نفس الشيء عدّة مرات في الخر يطة في أمكنة مختلفة‪ ،‬و هذا أمر م ُتعب قليلا‪.‬‬

‫أخيراً‪ ،‬يتم تحميل الخر يطة المحفوطة حاليا ًفي الملف ‪ . levels.lvl‬سيكون نقطة انطلاقنا‪.‬‬

‫معالجة الأحداث‬

‫هذه المرة سيكون علينا معالجة كثير من الأحداث المختلفة‪ .‬هيا بنا‪ ،‬واحدا ً واحداً‪.‬‬

‫‪SDL_QUIT‬‬

‫‪1 case SDL_QUIT:‬‬


‫;‪2 cont = 0‬‬
‫;‪3 break‬‬

‫إذا ضغطنا على الزر ‪ ، X‬ٺتوقف الحلقة و نعود إلى القائمة الرئيسية‪.‬‬
‫ل بالنسبة لللاعب ‪ :‬فهو يريد الخروج من اللعبة و ليس الرجوع إلى القائمة‬ ‫ليكن في علمك أن هذا الشيء ليس أحسن ح ّ‬
‫الرئيسية‪ .‬يجب أن نجد حلا ًلإيقاف البرنامج و ذلك بإرجاع قيمة خاصّة للدالة الرئيسية مثلاً‪ .‬سأتركك لتجد حلا ًبنفسك‪.‬‬

‫‪SDL_MOUSEBUTTONDOWN‬‬

‫‪1 case SDL_MOUSEBUTTONDOWN:‬‬


‫)‪2 if (event.button.button == SDL_BUTTON_LEFT‬‬
‫{ ‪3‬‬
‫‪4‬‬ ‫‪// We put the chosen object (wall, box) in the click position‬‬
‫‪5‬‬ ‫= ]‪map[event.button.x / BLOCK_SIZE][event.button.y / BLOCK_SIZE‬‬
‫;‪currentObject‬‬
‫‪6‬‬ ‫‪leftClickInProgress = 1; // We put in mind that there’s a pushed button‬‬
‫‪7‬‬ ‫}‬
‫‪8‬‬ ‫‪else if (event.button.button == SDL_BUTTON_RIGHT) // Right click to erase‬‬
‫‪9‬‬ ‫{‬
‫‪10‬‬ ‫;‪map[event.button.x / BLOCK_SIZE][event.button.y /BLOCK_SIZE] = EMPTY‬‬
‫‪11‬‬ ‫;‪rightClickInProgress = 1‬‬
‫‪12‬‬ ‫}‬
‫‪13‬‬ ‫;‪break‬‬

‫‪386‬‬
‫‪ .5.24‬م ُنـشئ المستو يات‬

‫نبدأ باختبار الزر المضغوط )نرى ما إن كان ضغطا ًبالزر الأيسر أو الأيمن( ‪:‬‬

‫• إذا كان ضغطا بالزر الأيسر‪ ،‬نقوم بوضع الشيء الحالي ‪ currentObject‬على الخر يطة في الموضع الذي تشير إليه‬
‫الفأرة‪.‬‬
‫• إذا كان ضغطا بالزر الأيمن‪ ،‬نمسح مايوجد في الموضع الحالي للفأرة )نضع ‪ EMPTY‬كما سبق و قلتُ لك(‪.‬‬

‫؟‬
‫كيف نعرف في أي ”خانة” من الخر يطة نحن متواجدون ؟‬

‫نعرف ذلك عن طر يق عملية حسابية صغيرة‪ .‬يكفي أن نأخذ إحداثيات الفأرة ) ‪ event.button.x‬مثلاً( و نقسم‬
‫هذه القيمة على حجم كتلة ‪. BLOCK_SIZE‬‬
‫هذه قسمة لأعداد صحيحة‪ .‬و بما أن قسمة الأعداد الصحيحة في لغة ‪ C‬تُعطي عددا ً صحيحا‪ ،‬فنتحصّ ل بالتأكيد على قيمة‬
‫توافق خانة من الخر يطة‪.‬‬

‫مثلاً‪ ،‬لو أنني في البيكسل الـ‪ 75‬من الخر يطة )على محور الفواصل ‪ ،(x‬أقسم هذا العدد على ‪ BLOCK_SIZE‬و التي‬
‫تساوي هنا ‪ .34‬يكون لدينا هنا ‪:‬‬
‫‪75/34 = 2‬‬
‫‪ .‬لا تنس هنا أننا نتجاهل باقي القسمة و نقوم بحفظ الجزء الصحيح فقط لأننا نتكلم عن قسمة أعداد صحيحة‪.‬‬
‫نحن نعلم إذا أننا نتواجد في الخانة رقم ‪) 2‬أي الخانة الثالثة لأن الجدول يبدأ من ‪ ،0‬لا تنس ذلك(‪.‬‬

‫مثال آخر ‪ :‬لو أنني في البيكسل العاشر )أي أنني قريب من الحافة(‪ ،‬ستكون لدينا العملية الحسابية التالية ‪:‬‬
‫‪10/34 = 0‬‬
‫أي أننا في الخانة رقم ‪! 0‬‬

‫بفضل هذه العملية الحسابية البسيطة يمكننا أن نعرف في أي خانة من الخر يطة نحن متواجدون‪.‬‬
‫;‪1 map[event.button.x / BLOCK_SIZE][event.button.y / BLOCK_SIZE] = currentObject‬‬

‫‪rightClickInProgress‬‬ ‫شيء آخر مهم ‪ :‬إعطاء القيمة ‪ 1‬للمتغير المنطقي ‪) leftClickInProgress‬أو‬


‫حسب الحالة( يسمح لنا بمعرفة‪ ،‬خلال حدث ‪ ، MOUSEMOTION‬ما إن كان زر الفأرة مضغوطا ًخلال الإنتقال‪.‬‬

‫‪SDL_MOUSEBUTTONUP‬‬

‫‪1 case SDL_MOUSEBUTTONUP: // We disable the boolean which indicates that there’s‬‬
‫‪a clicked button‬‬
‫‪2‬‬ ‫)‪if (event.button.button == SDL_BUTTON_LEFT‬‬
‫‪3‬‬ ‫;‪leftClickInProgress = 0‬‬
‫‪4‬‬ ‫)‪else if (event.button.button == SDL_BUTTON_RIGHT‬‬
‫‪5‬‬ ‫;‪rightClickInProgress = 0‬‬
‫‪6‬‬ ‫;‪break‬‬

‫الحدث ‪ MOUSEBUTTONUP‬يقوم ببساطة بإعادة القيمة ‪ 0‬للمتغير المنطقي‪ .‬نحن نعرف بأن النقر انتهى و بهذا لا‬
‫يوجد أي ”نقر حالي” بالفأرة‪.‬‬

‫‪387‬‬
‫‪Mario Sokoban‬‬ ‫الفصل ‪ .24‬عمل تطبيقي ‪:‬‬

‫‪SDL_MOUSEMOTION‬‬

‫‪1 case SDL_MOUSEMOTION:‬‬


‫‪2 if (leftClickInProgress) // If we move the mouse and the left button is clicked‬‬
‫{ ‪3‬‬
‫‪4‬‬ ‫= ]‪map[event.motion.x / BLOCK_SIZE][event.motion.y /BLOCK_SIZE‬‬
‫;‪currentObject‬‬
‫‪5‬‬ ‫}‬
‫‪6‬‬ ‫‪else if (rightClickInProgress) // The same thing for the right button‬‬
‫‪7‬‬ ‫{‬
‫‪8‬‬ ‫;‪map[event.motion.x / BLOCK_SIZE][event.motion.y / BLOCK_SIZE] = EMPTY‬‬
‫‪9‬‬ ‫}‬
‫‪10‬‬ ‫;‪break‬‬

‫هنا يمكن لنا رؤ ية أهمية المتغيرات المنطقية‪ .‬نختبر حينما نقوم بتحر يك الفأرة ما إن كان هناك نقر حالي‪ .‬إذا كانت‬
‫هذه هي الحالة‪ ،‬نضع على الخر يطة شيئا ًما )أو الفراغ إذا كان نقرا باليمين(‪.‬‬
‫ل تكرار للشيء‪ ،‬يكفي إذا ً‬
‫ل مرة من أجل ك ّ‬
‫ح لنا بوضع شيء واحد لعدة مرات دون الحاجة إلى إلى النقر في ك ّ‬
‫هذا يسم ُ‬
‫أن نُبقي زر الفأرة مضغوطا ًبينما نسحبُ هذه الأخيرة‪.‬‬

‫ل مرة نحر ّك فيها الفأرة )يكون ذلك ببيكسل واحد(‪ ،‬نختبر ما إن كانت المتغيرات المنطقية مفع ّلة‪.‬‬
‫الأمر واضح ‪ :‬في ك ّ‬
‫إذا كان الأمر كذلك‪ ،‬نقوم بوضع شيء على الخر يطة‪ .‬و إلاً‪ ،‬لا نقوم بأي شيء‪.‬‬

‫سألخّص التقنية لأنها ستكون مفيدة من أجل برامج أخرى‪.‬‬ ‫ملخّ ص ‪:‬‬

‫تسمح هذه التقنية بمعرفة ما إن كان زر الفأرة مضغوطا ً بينما يتم تحر يك هذه الأخيرة‪ .‬يمكننا أن نستفيد من هذا الأمر‬
‫لبرمجة السحب و الإفلات )‪.(Drag and drop‬‬

‫‪ .1‬خلال حدث ‪ : MOUSEBUTTONDOWN‬نعطي القيمة ‪ 1‬للمتغير المنطقي ‪. clickInProgress‬‬

‫‪ .2‬خلال حدث ‪ : MOUSEMOTION‬نختبر ما إن كان المتغير المنطقي ‪ clickInProgress‬يساوي ”صحيح”‪ .‬إذا‬


‫كان الأمر كذلك فسنعرف أننا نقوم بالسحب باستخدام الفأرة‪.‬‬

‫‪ .3‬خلال حدث ‪ : MOUSEBUTTONUP‬نعيد القيمة ‪ 0‬للمتغير المنطقي ‪ clickInProgress‬لأن النقر قد انتهى‬


‫)إفلات زر الفأرة(‪.‬‬

‫‪SDL_KEYDOWN‬‬

‫تسمح أزرار لوحة المفاتيح بتحميل و حفظ المستوى و أيضا ًبتغيير الشيء المُختار من أجل النقر اليساري بالفأرة‪.‬‬

‫‪1 case SDL_KEYDOWN:‬‬


‫)‪2 switch(event.key.keysym.sym‬‬
‫{ ‪3‬‬
‫‪4‬‬ ‫‪case SDLK_ESCAPE:‬‬

‫‪388‬‬
‫ م ُنـشئ المستو يات‬.5.24

5 cont = 0;
6 break;
7 case SDLK_s:
8 saveLevel(map);
9 break;
10 case SDLK_c:
11 loadLevel(map);
12 break;
13 case SDLK_KP1:
14 currentObject = WALL;
15 break;
16 case SDLK_KP2:
17 currentObject = BOX;
18 break;
19 case SDLK_KP3:
20 currentObject = GOAL;
21 break;
22 case SDLK_KP4:
23 currentObject = MARIO;
24 break;
25 }
26 break;

‫ نقوم بحفظ المستوى إذا تم الضغط‬،‫ نقوم بتغيير الشيء إذا تم الضغط على الأرقام في اللوحة‬.‫هذه الشفرة سهلة للغاية‬
. C ‫ و نقوم بتحميل آخر مستوى تم حفظه بالنقر على‬S ‫على‬

! ‫وقت اللصق‬

.‫ل الأحداث‬
ّ ‫ لقد أتتممنا ك‬: ‫ها نحن ذا‬
‫ الشفرة التالية تشبه الشفرة التي استعملناها‬.‫ لم يتبقّ لنا سوى لصق كل عناصر الخر يطة بمساعدة حلقتين متداخلتين‬،‫الآن‬
: ‫ سأعطيها لك لـكن ّي لن أعيد شرحها هنا‬.‫في دالة اللعبة‬

1 // Clearing the screen


2 SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 255, 255, 255));
3 // Placing the objects in the screen
4 for (i = 0 ; i < NB_BLOCKS_WIDTH ; i++)
5 {
6 for (j = 0 ; j < NB_BLOCKS_HEIGHT ; j++)
7 {
8 position.x = i � BLOCK_SIZE;
9 position.y = j � BLOCK_SIZE;
10 switch(map[i][j])
11 {
12 case WALL:
13 SDL_BlitSurface(wall, NULL, screen, &position);
14 break;
15 case BOX:

389
‫‪Mario Sokoban‬‬ ‫الفصل ‪ .24‬عمل تطبيقي ‪:‬‬

‫‪16‬‬ ‫;)‪SDL_BlitSurface(box, NULL, screen, &position‬‬


‫‪17‬‬ ‫;‪break‬‬
‫‪18‬‬ ‫‪case GOAL:‬‬
‫‪19‬‬ ‫;)‪SDL_BlitSurface(level, NULL, screen, &position‬‬
‫‪20‬‬ ‫;‪break‬‬
‫‪21‬‬ ‫‪case MARIO:‬‬
‫‪22‬‬ ‫;)‪SDL_BlitSurface(mario, NULL, screen, &position‬‬
‫‪23‬‬ ‫;‪break‬‬
‫‪24‬‬ ‫}‬
‫‪25‬‬ ‫}‬
‫} ‪26‬‬
‫‪27 // Updating the screen‬‬
‫;)‪28 SDL_Flip(screen‬‬

‫لا يجب أن ننسى أن نحرر الذاكرة بعد الانتهاء من الحلقة الرئيسية بالشكل اللازم )باستعمال ‪( SDL_FreeSurface‬‬
‫‪:‬‬

‫‪1‬‬ ‫;)‪SDL_FreeSurface(wall‬‬
‫‪2‬‬ ‫;)‪SDL_FreeSurface(box‬‬
‫‪3‬‬ ‫;)‪SDL_FreeSurface(level‬‬
‫‪4‬‬ ‫;)‪SDL_FreeSurface(mario‬‬

‫حسناً‪ ،‬انتهينا من التنظيف !‬

‫ملخّ ص و تحسينات‬

‫ل شيء و حان وقت التلخيص !‬


‫حسنا ًلقد انتهينا من ك ّ‬

‫هي ّا فلنلخّ ص !‬

‫و ماذا سيكون أحسن تلخيص من الشفرة المصدر ية الكاملة للعبة مع التعلقيات المفصّ لة ؟‬

‫ل ما رأيناه إلى ح ّد الآن‪ ،‬أفضّ ل أن تقوم بتنز يل‬


‫بسبب عدم رغبتي في كتابة عشرات الصفحات من الشفرة تشمل ك ّ‬
‫الشفرة المصدر ي ّة الكاملة مع الملف التنفيذي )المُترجم للويندوز(‪.‬‬

‫)‪https://openclassrooms.com/uploads/fr/ftp/mateo21/mario_sokoban.zip (436 Ko‬‬

‫الملف ‪ .zip‬يحتوي ‪:‬‬

‫• الملف التنفيذي للويندوز )إذا كنت تعمل على نظام تشغيل آخر‪ ،‬تكفي إعادة الترجمة(‪.‬‬

‫• الملفات ‪ DLL‬الخاصة بالـ‪ SDL‬و الـ‪.SDL_Image‬‬


‫• كل الصور التي تحتاجها في البرنامج )هي نفسها التي قمت بتحميلها في حزمة ”‪ ”sprites‬أعلاه(‪.‬‬

‫• الملفات المصدر ية الكاملة الخاصة بالبرنامج‪.‬‬

‫‪390‬‬
‫ملخّ ص و تحسينات‬

‫• الملف ‪ .cbp‬الخاص بمشروع ‪ .Code::Blocks‬إذا أردت فتح المشروع باستعمال بيئة تطوير ية أخرى‪ ،‬قم بإنشاء‬
‫مشروع ‪ ،SDL‬أضف إليه يدو يا ًكل الملفات ‪ .h‬و ‪ . .c‬الأمر ليس صعباً‪ ،‬سترى‪.‬‬

‫تلاحظ أن المشروع يحوي‪ ،‬بالإضافة إلى الملفات ‪ .h‬و ‪ ، .c‬ملفا مصدر يا ‪ . ressources.rc‬إنه ملف يمكن‬
‫إضافته للمشروع )فقط على الويندوز( و يسمح بإدخال ملفات في الملف التنفيذي‪ .‬هنا‪ ،‬استعنت به لإدخال أيقونة في‬
‫الملف التنفيذي‪ .‬و هذا يسمح بإعطاء أيقونة للملف التنفيذي مرئية في الويندوز‪ ،‬أنظر الصورة التالية ‪:‬‬

‫اِعترف بذلك‪ ،‬صنع أيقونة من أجل البرنامج أمر أجمل من ترك الأيقونة الافتراضي ّة !‬
‫يمكنك قراءة المزيد عن هذه التقني ّة في درس ”إنشاء أيقونة لبرنامجك” المتوف ّر على هذا الرابط ‪:‬‬

‫‪http://www.siteduzero.com/tutoriel-3-14177-creer-une-icone-pour-son-programme.‬‬
‫‪html‬‬

‫حسّن اللعبة !‬

‫ألا ترى بأن هذا البرنامج غير مثالي‪ ،‬و أبعد من أي يكون كذلك ؟‬
‫هل تريد أفكارا ً للتطوير ؟‬

‫• ينقص دليل استعمال‪ ،‬حيث يتم إظهار شاشة قبل انطلاق المرحلة و قبل انطلاق مـُنشئ المستو يات‪ .‬نقوم بشرح‬
‫الأزرار اللازمة لـكي يستعملها اللاعب‪.‬‬

‫مختار حالياً‪ .‬سيكون من الجي ّد أن الشيء المختار حالي ّا يتبع مؤش ّر‬
‫• في مـُنشئ المستو يات‪ ،‬اللاعب لا يعرف أيّ شيء ُ‬
‫الفأرة‪ .‬هكذا سيعرف المستخدم مالّذي سيوضع على الخر يطة‪ .‬الأمر سهل للتطبيق‪ ،‬لقد قمنا بجعل ‪ Zozor‬يتبع‬
‫الفأرة في الفصل السابق !‬

‫• يمكن أن نبدأ مستوى ما بوجود بعض الصناديق الموضوعة أساسا ًفوق المناطق المستهدفة ) ‪ .( BOX_OK‬لقد رأيت‬
‫كثيرا من المستوبات تبدأ بصناديق في مكان مناسب هذا )لا يعني أن المستوى سهل‪ ،‬فقد يكون عليك تحر يك‬
‫الصندوق من مكانه في مرحلة من مراحل الجولة(‪.‬‬

‫• في مـُنشئ المستو يات أيضاً‪ ،‬يجب أن نمنع المستعمل من أن يضع موضِعي انطلاق لللاعب في نفس الخر يطة !‬

‫‪391‬‬
‫‪Mario Sokoban‬‬ ‫الفصل ‪ .24‬عمل تطبيقي ‪:‬‬

‫فض نوعا ًما‪ ،‬ما رأيك بـإظهار رسالة في وسط‬


‫• حينما ننجح في م ُستوى‪ ،‬نرجع مباشرة إلى القائمة الرئيسية‪ .‬هذا أمر ّ‬
‫الشاشة ‪” :‬هنيئاً‪ ،‬لقد نجحت في المستوى” ؟‬

‫• أخيراً‪ ،‬سيكون من الجيد أن يتمكن البرنامج من التحكم في عدة مستو يات في المرة الواحدة‪ .‬سيكون علينا بناء رحلة‬
‫لعب تستمر لـ‪ 20‬مستوى مثلا ً‪ .‬سيكون الأمر أصعب قليلا ً من ناحية البرمجة‪ ،‬لـكن يمكن القيام به‪ .‬يجب عليك‬
‫التعديل في شفرة اللعبة و أيضا ً في شفرة مـُنشئ المستو يات‪ .‬أنصحك بأن تضع مستوى واحدا ً في السطر الواحد‬
‫بالملف ‪. levels.lvl‬‬

‫كما وعدتك‪ ،‬هذا ممكن‪ ،‬و لقد فعلته ! لن أعطيك الشفرة المصدر ية الخاصة بهذه التحسينات )أعتقد أن ّي أعطيتك‬
‫الـكثير إلى ح ّد الآن !(‪ ،‬و لـكن ّي سأعطيك مباشرة الملف التنفيذي م ُترجما ًللويندوز و اللينكس‪.‬‬

‫اللعبة تحتوي على مغامرة من ‪ 20‬مستوى تختلف صعوبتها )من سهل جدا ً إلى ‪ ...‬شديد الصعوبة(‪.‬‬
‫لـكي أتمكّن من تحقيق بعض المستو يات‪ ،‬احتجت لز يارة موقع شخص مهووس بلعبة ‪: Sokoban‬‬

‫‪http://sokoban.online.fr/‬‬

‫هاهي اللعبة المحسّنة للويندوز و اللينكس ‪:‬‬

‫‪Windows : https://openclassrooms.com/uploads/fr/ftp/mateo21/mario_sokoban_setup.‬‬
‫)‪exe (656 Ko‬‬

‫‪Linux : https://openclassrooms.com/uploads/fr/ftp/mateo21/mario_sokoban_linux.tar.‬‬
‫)‪gz (64 Ko‬‬

‫لقد استعنت بالبرنامج ‪ Inno Setup‬من أجل صنع برنامج التسطيب‪ ،‬يمكنك القراءة بهذا الخصوص على هذا الرابط ‪:‬‬

‫‪http://www.siteduzero.com/tutoriel-3-14171-creer-une-installation.html‬‬

‫‪392‬‬
‫الفصل ‪25‬‬

‫تحكّم في الوقت !‬

‫لهذا الفصل أهمية كبيرة ‪ :‬سيعلّمك كيف تتحكم في الوقت بالـ‪ .SDL‬إنه لمن النادر أن نقوم بإنشاء برنامج ‪ SDL‬لا تحتاج‬
‫إلى دوال خاصة بالتحكم في الوقت‪ ،‬بالرغم من أن لعبة ‪ Mario Sokoban‬كانت حالة خاصة‪ .‬رغم ذلك‪ ،‬في معظم‬
‫الألعاب‪ ،‬إدارة الوقت هي شيء أساسيّ‪.‬‬

‫مثلاً‪ ،‬كيف لك أن تُنشئ لعبة ‪ Tetris‬أو ‪ Snake‬؟ يجب فعلا ً على الك ُتل أن تتحر ّك كل ‪ X‬ثانية‪ ،‬و هذا ما لا تجيد‬
‫فعله‪ .‬على الأقل‪ ،‬قبل أن تقرأ هذا الفصل‪.‬‬

‫الـ‪Ticks‬‬ ‫‪ 1.25‬الـ‪ Delay‬و‬

‫في بادئ الأمر‪ ،‬سنتعل ّم كيف نستعمل دالتين بسيطتين جدا ً ‪:‬‬

‫• ‪ : SDL_Delay‬تسمح بتوقيف البرنامج مؤقتا لعدد من الميلّي ثواني‪.‬‬

‫• ‪ : SDL_GetTicks‬تقوم بإرجاع عدد الميلّي ثواني التي مضت منذ انطلاق تشغيل البرنامج‪.‬‬

‫هاتان الدالّتان سهلتان جدا ً كما سنرى لـكنّ استعمالهما ليس بسيطا ً كما يبدو الأمر عليه‪.‬‬

‫‪SDL_Delay‬‬

‫كما قلتُ ‪ ،‬تقوم هذه الدالة بإيقاف عمل البرنامج لمدّة محدّدة‪ .‬حينما يكون البرنامج متوق ّفا‪ ،‬نقول أن ّه ”ينام” )‪ : (sleep‬هو‬
‫لا يستعمل المُعالج‪.‬‬

‫يمكن إذا استعمال ‪ SDL_Delay‬للإنقاص من زمن اشتغال المُعالج )‪ .(Processor‬لاحظ أن ّني سأختصره إلى‬
‫‪ CPU‬و هذا الإختصار متداول و يوافق العبارة ”‪ ”Central Processing Unit‬و ال ّتي تعني ”وحدة المعالجة المركز ي ّة”‪.‬‬
‫ل شراهة لموارد المعالج‪ .‬أي أننا لن نثقل على الحاسوب كثيرا ً إذا تم ّ‬
‫بفضل الـ ‪ SDL_Delay‬يمكنك جعل برامجك أق ّ‬
‫استخدام هذه الدالة بذكاء‪.‬‬

‫‪393‬‬
‫الفصل ‪ .25‬تحكّم في الوقت !‬

‫م‬
‫هذا كل ّه يعتمد على البرنامج الذي تـُنشئه ‪ :‬أحياناً‪ ،‬نجد أنه من المستحسن أن يستعمل البرنامج المعالج بشكل أقلّ‪،‬‬
‫يمكن في نفس الوقت أن يقوم المُستعمل بشيء آخر مثلما هو الحال بالنسبة لقارئ ‪ MP3‬الذي يشتغل في الخلفية‬
‫ريثما تقوم بالتصفّح عبر الإنترنت‪.‬‬
‫لـكن أحياناً‪ ،‬نحتاج للبرنامج أن يستعمل المعالج بنسبة ‪ ،100%‬و هو الحال بالنسبة لغالبية الألعاب‪.‬‬

‫لنعد إلى الدالة‪ ،‬هذا نموذجها و هو بسيط للغاية ‪:‬‬

‫;)‪1 void SDL_Delay(Uint32 ms‬‬

‫الأمر واضح‪ ،‬تبعث للدالة عدد الميلّي ثواني التي يجب أن ”ينام” البرنامج خلالها‪.‬‬

‫مثلا ً‪ :‬إذا أردت أن ينام البرنامج لمدّة ثانية واحدة‪ ،‬يجب عليك كتابة ‪:‬‬

‫;)‪1 SDL_Delay(1000‬‬

‫لا تنس أنّها بالميلي ثانية ‪:‬‬

‫• ‪ 1000‬ميلّي ثانية = ثانية‪.‬‬

‫• ‪ 500‬ميلّي ثانية = نصف ثانية‪.‬‬

‫• ‪ 250‬ميلّي ثانية = ر ُبع ثانية‪.‬‬

‫!‬
‫لا يمكنك فعل أي شيء في البرنامج بينما هو متوق ّف مؤق ّتا ! فالبرنامج ”النائم” لا يمكن له فعل أي شيء لأنه ليس‬
‫مفع ّلا بالنسبة للحاسوب‪.‬‬

‫مشكل جزئي ّة الوقت‬

‫لا‪ ،‬تأكّد بأنني لن أخوض في درس للفيز ياء الكمي ّة في هذا الفصل حول الـ‪ ! SDL‬و مع ذلك‪ ،‬أرى بأن هناك أمورا‬
‫يجب عليك معرفتها ‪ SDL_Delay :‬ليست دالة ”مثالية”‪ .‬و هذا ليس خطأها‪ ،‬بل هو خطأ نظام التشغيل )‪،Windows‬‬
‫‪.( ... Mac OS X ،GNU/Linux‬‬

‫لماذا يتدخّل نظام التشغيل هنا ؟ ببساطة لأنه هو الذي يتحكّم في البرامج المشغ ّلة ! فبرنامجك سيقول للنظام ‪” :‬سأنام‪،‬‬
‫أيقظني بعد ثانية”‪ .‬لـكن لن يقوم النظام دائما بإفاقة البرنامج بعد ثانية بالضبط‪.‬‬

‫في الواقع‪ ،‬قد يكون هناك تأخّر بسيط )تأخر ‪ 10‬ميلّي ثانية بالتقريب كمعدّل‪ ،‬هذا يختلف حسب الحاسوب(‪ .‬لماذا ؟ لأن‬
‫‪ CPU‬لا يمكنه العمل إلا على برنامج واحد في المر ّة الواحدة‪ .‬دور نظام التشغيل يتمث ّل في إخبار ‪ CPU‬بخصوص ما يجب أن‬

‫‪394‬‬
‫الـ‪Ticks‬‬ ‫‪ .1.25‬الـ‪ Delay‬و‬

‫ل على ‪ firefox.exe‬ثم لمدّة ‪ 110‬ميلّي ثانية على ‪، explorer.exe‬‬


‫يتم القيام به و لهذا ‪” :‬لمدّة ‪ 40‬ميلّي ثانية ستعم ُ‬
‫بعد ذلك‪ ،‬لمدة ‪ 80‬ميلّي ثانية ستعمل على ‪ program_sdl.exe‬ثم عد إلى العمل على ‪ firefox.exe‬لمدّة ‪ 65‬ميلّي‬
‫ثانية ‪ ” ...‬نظام التشغيل هو بالفعل عبارة عن قائد الأوركسترا !‬

‫تخي ّل الآن أنه لثانية من الزمن‪ ،‬يكون برنامج آخر لازال في طور الإشتغال ‪ :‬يجب أن ينتهي عمله حتى يستطيع برنامجك‬
‫”استعادة التحكّم” على ‪.CPU‬‬

‫ما الذي يجب تذك ّره ؟ أن ‪ CPU‬لا يمكنه أن يتحكّم في برنامجـين في آن واحد‪ .‬و لـكي يعطي انطباعا ً بأنه يُشغ ّل العديد‬
‫ل دورا ً بدور‪.‬‬
‫من البرامج في نفس اللحظة‪ ،‬يقوم بتقسيم الوقت بين هذه البرامج حيث تعم ُ‬
‫قَل ّت صح ّة هذا الكلام لأن المعالجات ”ثنائية النوى” لها القدرة على تشغيل برنامجـين في نفس الآن‪.‬‬

‫ن البرنامج الخاص بنا سيتم ّ إيقاظة في ثانية‬


‫لـكن هذه التقنية في التحكّم بالبرامج معقّدة للغاية و لن نحصل على ضمانات بأ ّ‬
‫بالضبط من الزمن‪.‬‬

‫ن الدالّة ‪ SDL_Delay‬دقيقة جدّا‪.‬‬


‫مع ذلك‪ ،‬يعتمد الأمر دائما ًعلى الحاسوب نفسه كما قلتُ سابقاً‪ .‬عندي‪ ،‬ألاحظ أ ّ‬

‫م‬
‫بسبب مشكل جزئي ّة الوقت‪ ،‬لن تتمكّن إذا من إيقاف برنامجك مؤق ّتا لوقت قصير جدّا من الزمن‪ ،‬أي أنه لو‬
‫استعملت ;)‪ SDL_Delay(1‬ستكون متأكدا ً بأن البرنامج لن ينام لـ‪ 1‬ميلي ثانية و إنما أكثر )حوالي ‪ 9‬أو ‪10‬‬
‫ميلي ثانية(‪.‬‬

‫الدالة ‪ SDL_Delay‬عملي ّة‪ ،‬لـكن لا ٺثق بها كثيراً‪ .‬فهي لا توقف البرنامج بالمقدار الزمني الذي تحدده أنت بالضبط‪.‬‬
‫هذا ليس راجعا ًلـكون الدالة غير م ُبرمجة جيداً‪ ،‬لـكن لأن عمل الجهاز معقّد و لا يمكنه أن يكون دقيقا ًمن هذه الناحية‪.‬‬

‫‪SDL_GetTicks‬‬

‫هذه الدالة ت ُرجع عدد الميلي ثواني التي انقضت منذ بدأ عمل البرنامج‪ .‬و هي عبارة عن مؤش ّر للزمن لا يمكن الاستغناء‬
‫عنه‪ .‬ستجد بأنها مفيدة لـكي تضع مراجع في الزمن‪ ،‬سترى ذلك !‬

‫هذا نموذجها ‪:‬‬

‫;)‪1 Uint32 SDL_GetTicks(void‬‬

‫هذه الدالة لا تنتظر أي معامل‪ ،‬و هي تقوم فقط بإرجاع عدد الثواني المنقضية‪.‬‬
‫هذا العدد يتصاعد مع مضي الزمن‪ .‬لمعلوماتك‪ ،‬التوثيق الخاص بالـ‪ SDL‬يشير إلى أن هذا العدد يصل إلى الحد الأقصى ‪49‬‬
‫ل هذا الوقت و لهذا فلا تقلق من هذه الناحية‪.‬‬
‫يوما ثم يبدأ العدد من جديد ! لـكن يجدر بالبرنامج الذي تكتبه ألا يستمر ّ ك ّ‬

‫‪395‬‬
‫الفصل ‪ .25‬تحكّم في الوقت !‬

‫إستعمال ‪ SDL_GetTicks‬لإدارة الوقت‬

‫إذا كانت الدالة ‪ SDL_Delay‬سهلة للفهم و للاسعمال‪ ،‬فالأمر ليس عينه بالنسبة لـ ‪ . SDL_GetTicks‬حان الوقت‬
‫لنعرف كيف سنستفيد منها‪.‬‬
‫إليك هذا المثال‪ ،‬سنسترجع البرنامج القديم الذي يقوم بإظهار نافدة تحتوي على ‪) Zozor‬الصورة التالية( ‪:‬‬

‫هذه المرة‪ ،‬في عوض التحكّم في حركة ‪ Zozor‬بالفأرة أو بلوحة المفاتيح‪ ،‬سنعتمد فكرة أنه سيقوم بالتحر ّك لوحده في‬
‫الشاشة‪ .‬لبنسّط الأمور‪ ،‬سنجعله يتحر ّك أفقيا في النافذة‪.‬‬

‫سن ُعيد استعمال نفس الشفرة التي استخدمناها في فصل الأحداث‪ ،‬يجدر بك أن تجيد كتابتها بنفسك دون الحاجة‬
‫لمُساعدة مني‪ .‬و إلا‪ ،‬إذا احتجتها‪ ،‬يمكنك استعادتها من الفصول السابقة‪.‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪SDL_Surface �screen = NULL, �zozor = NULL‬‬
‫‪4‬‬ ‫;‪SDL_Rect zozorPosition‬‬
‫‪5‬‬ ‫;‪SDL_Event event‬‬
‫‪6‬‬ ‫;‪int cont = 1‬‬
‫‪7‬‬ ‫;)‪SDL_Init(SDL_INIT_VIDEO‬‬
‫‪8‬‬ ‫;)‪screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF‬‬
‫‪9‬‬ ‫;)‪SDL_WM_SetCaption(”Gestion du temps en SDL”, NULL‬‬
‫‪10‬‬ ‫;)”‪zozor = SDL_LoadBMP(”zozor.bmp‬‬
‫‪11‬‬ ‫‪SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor−>format, 0, 0,‬‬
‫;))‪255‬‬
‫‪12‬‬ ‫‪zozorPosition.x = screen−>w / 2‬‬ ‫;‪− zozor−>w / 2‬‬
‫‪13‬‬ ‫‪zozorPosition.y = screen−>h / 2‬‬ ‫;‪− zozor−>h / 2‬‬
‫‪14‬‬ ‫;)‪SDL_EnableKeyRepeat(10, 10‬‬
‫‪15‬‬ ‫)‪while (cont‬‬
‫‪16‬‬ ‫{‬
‫‪17‬‬ ‫;)‪SDL_WaitEvent(&event‬‬
‫‪18‬‬ ‫)‪switch(event.type‬‬

‫‪396‬‬
‫الـ‪Ticks‬‬ ‫‪ .1.25‬الـ‪ Delay‬و‬

‫‪19‬‬ ‫{‬
‫‪20‬‬ ‫‪case SDL_QUIT:‬‬
‫‪21‬‬ ‫;‪cont = 0‬‬
‫‪22‬‬ ‫;‪break‬‬
‫‪23‬‬ ‫}‬
‫‪24‬‬ ‫‪SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 255, 255,‬‬
‫;))‪255‬‬
‫‪25‬‬ ‫;)‪SDL_BlitSurface(zozor, NULL, screen, &zozorPosition‬‬
‫‪26‬‬ ‫;)‪SDL_Flip(screen‬‬
‫‪27‬‬ ‫}‬
‫‪28‬‬ ‫;)‪SDL_FreeSurface(zozor‬‬
‫‪29‬‬ ‫;)(‪SDL_Quit‬‬
‫‪30‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪31‬‬

‫فلنهتم بـ‪ .Zozor‬نريد أن نحر ّكه‪ .‬من أجل هذا‪ ،‬سيكون من الأفضل استعمال ‪ . SDL_GetTicks‬سنحتاج إلى‬
‫متغيرين ‪ previousTime :‬و ‪ . currentTime‬يستخدمان من أجل تخزين الوقت الذي تم ّ إرجاعه من طرف‬
‫‪ SDL_GetTicks‬في لحظات زمنية مختلفة‪.‬‬
‫يكفي أن نحسب الفرق بين ‪ currentTime‬و ‪ previousTime‬لمعرفة الوقت المنقض ّي‪ .‬إذا كان هذا الأخير يساوي‬
‫‪ 30‬ميلي ثانية‪ ،‬نغي ّر إحداثيات ‪.Zozor‬‬

‫لنبدأ إذا بإنشاء هذين المتغيرين ‪:‬‬

‫;‪1 int previousTime = 0, currentTime = 0‬‬

‫و الآن‪ ،‬في حلقتنا غير المنتهية‪ ،‬نضيف الشفرة المصدر ية التالية ‪:‬‬

‫;)(‪1 currentTime = SDL_GetTicks‬‬


‫‪2 if (currentTime − previousTime > 30) // If 30 ms have passed‬‬
‫{ ‪3‬‬
‫‪4‬‬ ‫‪zozorPosition.x++; // We move Zozor‬‬
‫‪5‬‬ ‫‪previousTime = currentTime; // The current time becomes the previous‬‬
‫‪one.‬‬
‫} ‪6‬‬

‫اِفهم جي ّدا ما يحصل ‪:‬‬

‫‪ .1‬نحصل على الوقت المنقضي باستعمال ‪. SDL_GetTicks‬‬

‫‪ .2‬نقارن هذه القيمة بالوقت الذي تم تسجيله مسبقاً‪ .‬إذا كان هناك فرق ‪ 30‬مث على الأقل‪ ،‬إذا‪...‬‬

‫ل ‪ 30‬مث‪.‬‬
‫ل ‪ 30‬مث‪ .‬هنا‪ ،‬نقوم بتحر يكه إلى اليمين ك ّ‬
‫‪ .3‬نحر ّك ‪ ،Zozor‬لأننا نريده أن يتحر ّك ك ّ‬
‫يجب ان نتأكد ما إن كان الوقت المنقضي أكبر من ‪ 30‬مث‪ ،‬و ليس ما إن كان يساوي تلك القيمة ! لأنه في‬
‫ل‬
‫الواقع سنُخْب َر ما إن كان الوقت المنقضي يساوي على الأقل ‪ 30‬مث‪ .‬نحن لسنا متأكدين بأنه سيتم ّ تنفيذ الأمر ك ّ‬
‫‪ 30‬مث بالضبط‪.‬‬

‫‪397‬‬
‫الفصل ‪ .25‬تحكّم في الوقت !‬

‫‪ .4‬ثم‪ ،‬و الأمر الذي لا يجب فعلا ًنسيانه‪ ،‬نضع قيمة الوقت ”الحالي” في الوقت ”السابق”‪ .‬بالفعل‪ ،‬تخي ّل الدورة القادمة‬
‫للحلقة ‪ :‬الوقت الحالي يتغي ّر و يمكننا مقارنته بالوقت السابق من جديد‪ ،‬أي سنقارن ما إن تم انقضاء ‪ 30‬مث على‬
‫الأقل ثم نحر ّك ‪.Zozor‬‬

‫؟‬
‫و لـكن ماذا يحصل لو أن الحلقة اشتغلت لمدّة أقل من ‪ 30‬مث ؟‬

‫اِقرأ جيدا ً الشفرة‪ ،‬لا شيء سيحدث !‬


‫لن ندخل في الشرط‪ ،‬يعني أننا لن نقوم بأي شيء‪ .‬ننتظر الدورة القادمة للحلقة أين نقوم من جديد باختبار ما إن تم انقضاء‬
‫مرة قمنا فيها بتحر يك ‪.Zozor‬‬
‫‪ 30‬مث منذ آخر ّ‬

‫هذه الشفرة قصيرة‪ ،‬لـكن يجب فهمها ! أعد قراءة شرحي بالعدد اللازم من المر ّات لتفهم جيدا ً لأن هذا الجزء قد‬
‫يكون الأهم في هذا الفصل‪.‬‬

‫تغيير في معالجة بالأحداث‬

‫الشفرة المصدر ية الذي كتبناها مثالية إلى ح ّد ما إذ ينقصها تفصيل بسيط ‪ :‬الدالة ‪ . SDL_WaitEvent‬كانت هذه الدالة‬
‫عملي ّة إلى ح ّد الآن بما أننا لم نتحكّم في الوقت‪ .‬هذه الدالة توقف البرنامج مؤق ّتا )بنفس طر يقة ‪ SDL_Delay‬تقريباً( ما‬
‫دام لا يوجد أي حدث‪.‬‬

‫لـكن هنا‪ ،‬لسنا م ُضطرين إلى انتظار حدث لنقوم بتحر يك ‪ ! Zozor‬إذ يجب عليه التحر ّك لوحده‪.‬‬
‫و لا يجب عليك الاستمرار في تحر يك الفأرة فقط لإنتاج أحداث و منه الخروج من الدالة ‪! SDL_WaitEvent‬‬

‫ل ؟ ‪. SDL_PollEvent‬‬
‫ماهو الح ّ‬
‫لقد قدّمت لك من قبل هذه الدالة ‪ :‬على عكس ‪ ، SDL_WaitEvent‬ت ُرجع هذه الدالة قيمة سواء كان هناك حدث‬
‫أم لا‪ .‬و نقول بأن الدالة غير مـ ُعطّـِلة ‪ :‬هي لا توقف البرنامج مؤق ّتا لأن الحلقة غير المنتهية ستستمر ّ في العمل طوال الوقت‪.‬‬

‫الشفرة المصدر ية الكاملة‬

‫هذه هي الشفرة النهائية التي بإمكانك تجريبها ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪SDL_Surface �screen = NULL, �zozor = NULL‬‬
‫‪4‬‬ ‫;‪SDL_Rect zozorPosition‬‬
‫‪5‬‬ ‫;‪SDL_Event event‬‬
‫‪6‬‬ ‫;‪int cont = 1‬‬
‫‪7‬‬ ‫;‪int previousTime = 0, currentTime = 0‬‬
‫‪8‬‬ ‫;)‪SDL_Init(SDL_INIT_VIDEO‬‬
‫‪9‬‬ ‫;)‪screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF‬‬

‫‪398‬‬
Ticks‫الـ‬ ‫ و‬Delay‫ الـ‬.1.25

10 SDL_WM_SetCaption(”Gestion du temps en SDL”, NULL);


11 zozor = SDL_LoadBMP(”zozor.bmp”);
12 SDL_SetColorKey(zozor,SDL_SRCCOLORKEY, SDL_MapRGB(zozor−>format, 0, 0,
255));
13 zozorPosition.x = screen−>w / 2 − zozor−>w / 2;
14 zozorPosition.y = screen−>h / 2 − zozor−>h / 2;
15 SDL_EnableKeyRepeat(10, 10);
16 while (cont)
17 {
18 SDL_PollEvent(&event); // We use PollEvent and not WaitEvent in
order not to block the program
19 switch(event.type)
20 {
21 case SDL_QUIT:
22 cont = 0;
23 break;
24 }
25 currentTime = SDL_GetTicks();
26 if (currentTime − previousTime > 30) // If 30ms have passed
since the final loop iteration
27 {
28 zozorPosition.x++; // We move Zozor
29 previousTime = currentTime; // The current time becomes
the previous one for our future calculation.
30 }
31 SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 255, 255,
255));
32 SDL_BlitSurface(zozor, NULL, screen, &zozorPosition);
33 SDL_Flip(screen);
34 }
35 SDL_FreeSurface(zozor);
36 SDL_Quit();
37 return EXIT_SUCCESS;
38 }

.‫ هو يتحر ّك نحو اليمين‬.‫ يهتز ّ لوحده على الشاشة‬Zozor ‫يجد ُر بك أن ترى‬


‫ هو‬،‫ أن يتحر ّك إلى اليمين بشكل أسرع بمرتين ! في الواقع‬Zozor‫ يجدر بـ‬: ‫ مث‬15 ‫ مث إلى‬30 ‫حاول مثلا ًتغيير الوقت من‬
.‫ مث كالسابق‬30 ‫مرة كل‬
ّ ‫ مث في عوض‬15 ‫مرة كل‬
ّ ‫يتحر ّك‬

CPU‫للـ‬ ‫استهلاك أقل‬

‫ لـكي نرى‬.‫ من المعالج‬100% ‫ و لهذا فهو يستهلك‬.(ً‫ تقريبا‬،‫ البرنامج يدور في حلقة غير منتهية بسرعة الضوء )حسنا‬،ً‫حاليا‬
: Windows ‫( في‬Processes ‫ )في القائمة‬DEL + ALT + CTRL ‫هذا يكفي مثلا ُأن نضغط على‬

399
‫الفصل ‪ .25‬تحكّم في الوقت !‬

‫كما يمكنك أن ترى‪ ،‬يتم استعمال ‪ CPU‬بنسبة ‪ 100%‬من طرف برنامجنا ‪. testsdl.exe‬‬
‫لقد قلت لك مسب ّقا ً ‪ :‬إذا برمجت لعبة )خاصة إذا كانت بنظام شاشة كاملة(‪ ،‬ليس خطيرا ً أن تستعمل المعالج بنسبة‬
‫‪ .100%‬لـكن إذا كانت لعبة في نافذة مثلاً‪ ،‬يُستحسن استعمال نسبة أقل من ‪ CPU‬لـكي نسمح للمستعمل بالقيام بشيء‬
‫آخر دون أن يُجهد الحاسوب نفسه‪.‬‬

‫الحل ؟ سنقوم بإعادة الشفرة السابقة‪ ،‬لـكننا سنضيف إليه ‪ SDL_Delay‬من أجل انتظار الوقت اللازم لـكي يصل‬
‫إلى ‪ 30‬مث‪.‬‬

‫يكفي أن نضيف ‪ SDL_Delay‬في ‪: else‬‬


‫;)(‪1 currentTime = SDL_GetTicks‬‬
‫‪2 if (currentTime − previousTime > 30) // If 30ms have passed‬‬
‫{ ‪3‬‬
‫‪4‬‬ ‫‪zozorPosition.x++; // We move Zozor‬‬
‫‪5‬‬ ‫‪previousTime = currentTime; // The current time becomes the previous‬‬
‫‪one for our future calculation‬‬
‫‪6‬‬ ‫}‬
‫‪7‬‬ ‫‪else‬‬
‫‪8‬‬ ‫{‬
‫‪9‬‬ ‫‪SDL_Delay(30‬‬ ‫;))‪− (currentTime − previousTime‬‬
‫‪10‬‬ ‫}‬

‫كيف تعمل الأمور هذه المرة ؟ الأمر بسيط‪ ،‬هناك احتمالان )حسب الشرط( ‪:‬‬

‫• إما أنه مضت اكثر من ‪ 30‬مث منذ قمنا بتحر يك ‪ ،Zozor‬في هذه الحالة نحر ّكه‪.‬‬

‫• إما أنه مضى وقت أقل من ‪ 30‬مث‪ ،‬في هذه الحالة سينام البرنامج بفضل ‪ SDL_Delay‬ريثما يسمح بوصول الـ‪30‬‬
‫مث و بهذه العملية الحسابية التي قمتُ بها‬
‫)‪ . 30 - (currentTime - previousTime‬إذا كان الفرق بين الزمن الحالي و الزمن السابق هي ‪ 20‬مث‬
‫مثلاً‪ ،‬فسينام البرنامج لـ ‪ 10 = 20 - 30‬مث لـكي تصل الـ‪ 30‬مث‪.‬‬

‫م‬
‫تذك ّر بأن ‪ SDL_Delay‬يمكن لها أن تضيف بعض الميلّي ثواني أكثر من المتوقع‪.‬‬

‫بهذه الشفرة‪ ،‬سينام البرنامج معظم الوقت و بهذا نقلل من استهلاك ‪ .CPU‬لاحظ الصورة التالية ‪:‬‬

‫‪400‬‬
‫الـ‪Ticks‬‬ ‫‪ .1.25‬الـ‪ Delay‬و‬

‫يستعمل البرنامج كمعدّل حوالي ‪ 0‬إلى ‪ 1%‬من ‪ ...CPU‬أحيانا ًيستعمل أكثر بقليل لـكن ّه يعود سر يعا ًإلى ‪.0%‬‬

‫التحكّم في عدد الصور في الثانية‬

‫‪ FPS‬اِختصارا ً لـ‪Frames‬‬ ‫أنت ٺتساءل حتما ًكيف يمكننا الح ّد من )أو ٺثبيت( عدد الصور في الثانية )غالبا ًما نسمّي هذا‬
‫‪ (per second‬التي يُظهرها الحاسوب‪.‬‬

‫حسناً‪ ،‬هذا تماما ًما نحاول فعله ! فهنا نقوم بإظهار صورة جديدة كل ‪ 30‬مث كمعدّل‪ .‬علما ًأن ثانية واحدة تساوي‬
‫‪ 1000‬مث‪ ،‬لـكي نجد عدد الـ‪ ،FPS‬يجب أن نقوم بعملية قسمة ‪ 33 = 30 / 1000‬صورة في الثانية بالتقريب‪.‬‬

‫بالنسبة لعين الإنسان‪ ،‬نقول عن التحر ّك أنه رشيق إذا احتوى على الأقل ‪ 25‬صورة في الثانية‪ .‬بـ‪ 33‬صورة في الثانية‬
‫إذا ً فالتحر ّك في البرنامج رشيق تماما و بهذا لن يظهر متشن ّجا‪.‬‬

‫إذا أردنا صورا أكثر في الثانية‪ ،‬يجب إنقاص حدود الوقت بين صورتين‪ .‬انتقل من ‪ 30‬إلى ‪ 20‬مث و ستصبح العملية‬
‫‪.FPS 50 = 20 / 1000 :‬‬

‫تمارين‬

‫التحكّم في الوقت ليس أمرا ً بديهياً‪ ،‬سيكون من الجيد لك أن تتمر ّن‪ ،‬ما رأيك ؟ إليك بعض التمارين ‪:‬‬

‫ل مرة إلى اليمين إلى أن يختفي من الشاشة‪ .‬سيكون من الأفضل حينما يصل إلى‬
‫• لح ّد الآن‪ ،‬يتحر ّك ‪ Zozor‬في ك ّ‬
‫حافة النافذة أن يعيد التوجّه إلى اليسار‪ .‬سيكون ذلك أفضل أليس كذلك ؟ لأنه سيعطي انطباعا ًأنه يرتدّ‪.‬‬

‫أنصحك بإنشاء متغير منطقي ‪ toTheRight‬يحمل القيمة ”صحيح” إذا كان ‪ Zozor‬يتحر ّك نحو اليمين )و ”خطأ” إذا‬
‫كان يتحر ّك نحو اليسار(‪ .‬إذا كان المتغير المنطقي يحمل القيمة صحيح‪ ،‬تقوم بتحر يك ‪ Zozor‬إلى اليمين‪ ،‬و إلا فستقوم‬
‫بتحر يكه إلى اليسار‪ .‬لا تنس أن تغي ّر قيمة المتغير المنطقي ما إن يصل ‪ Zozor‬إلى حافة النافذة و ذلك لـكي ينطلق في‬
‫الإتجاه المُعاكس !‬

‫‪zozorPosition.x‬‬ ‫• بدل أن يقوم ‪ Zozor‬بالإرتداد من اليمين إلى اليسار‪ ،‬فيمكن تحر يكه على قطر النافذة ! يكفيك تغيير‬
‫و ‪ zozorPosition.y‬في نفس الوقت‪ .‬يمكنك رؤ ية ماذا يُعطينا الأمر لو نقوم بز يادة قيمة ‪ x‬و إنقاص قيمة‬
‫‪ y‬في نفس الوقت‪ ،‬أو إذا قُمنا بز يادة قيمتيهما معاً‪ ،‬إلخ‪.‬‬

‫• حاول جعل ‪ Zozor‬يتوق ّف عن التحر ّك إذا تم ّ الضغط على الزر ‪ ، P‬و إذا تم الضغط مجددا ً على نفس الزر ينطلق‬
‫‪ Zozor‬مجدداً‪ .‬يحتاج الأمر متغيرا منطقيا بسيطا تقوم بتفعيله أو تعطيله‪.‬‬

‫‪401‬‬
‫الفصل ‪ .25‬تحكّم في الوقت !‬

‫المُؤقـ ِتات )‪(Timers‬‬ ‫‪2.25‬‬

‫!‬
‫استعمال المُؤقـ ِتات أكثر تعقيدا ً قليلا ً لأننا سنعتمد على مبدأ لم نره لح ّد الآن ‪ :‬المؤش ّرات نحو الدوال‪ .‬استعمال‬
‫غض النظر عنها دون أي مشكل‪.‬‬
‫المُؤقـ ِتات ليس أمرا ً ضرور يا ‪ :‬إذا وجدت بأنها صعبة جدا ً لتستعملها‪ ،‬يمكنك ّ‬

‫المُؤقـ ِتات تشكّل طر يقة أخرى لتحقيق ما نحن بصدد رؤيته بالدالة ‪. SDL_GetTicks‬‬
‫هي تقنية خاصّة نوعا ًما‪ .‬بعض المبرمجـين يجدونها عملي ّة‪ ،‬و آخرون لا‪ .‬هذا يعتمد على ذوقك البرمجي‪.‬‬

‫ماهو المُؤقـ ِت ؟‬
‫)(‪moveEnemy‬‬ ‫هو نظام يسمح بالطلب من الـ‪ SDL‬أن تستدعي دالة ما كل ‪ X‬ميلّي ثانية‪ .‬يمكنك بهذا أن تنشئ دالة‬
‫تقوم الـ‪ SDL‬باستدعائها تلقائي ّا كل ‪ 50‬مث كي يستطيع العد ّو التحر ّك في مجالات معي ّنة‪.‬‬

‫م‬
‫كما كنت أقول لك الآن‪ ،‬يمكننا القيام بهذا بواسطة ‪ SDL_GetTicks‬باستعمال التقنية التي رأيناها أعلاه‪.‬‬
‫ما الفائدة إذا ؟ لنقل أن المُؤقـ ِتات تفرض علينا هيكلة برامجنا بشكل أفضل على شكل دوال‪.‬‬

‫تهيئة نظام المُؤقـ ِتات‬

‫لـكي نتمكّن من استعمال المُؤقـ ِتات‪ ،‬يجب علينا تهيئة الـ‪ SDL‬أولا ً باستعمال ع َـلَم خاص ‪ . SDL_INIT_TIMER :‬يجب‬
‫عليك إذا استدعاء الدالة ‪ SDL_Init‬كالتالي ‪:‬‬

‫;)‪1 SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER‬‬

‫الـ‪ SDL‬جاهزة الآن لاستعمال المُؤقـ ِتات !‬

‫إضافة م ُؤقـ ِت‬

‫لنضيف م ُؤقـ ِتا ً يجب علينا استدعاء الدالة ‪ . SDL_AddTimer‬إليك نموذجها ‪:‬‬

‫� ‪1 SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback callback, void‬‬


‫;)‪param‬‬

‫توجد في الواقع دالتان تسمحان بإضافة م ُؤقـ ِت في الـ‪ SDL‬هما ‪ SDL_AddTimer :‬و ‪ SDL_SetTimer‬و هما تقريبا ً‬
‫متطابقتان‪ .‬و مع ذلك‪ SDL_SetTimer ،‬هي دالة قديمة موجودة دائما ً لأسباب التوافقي ّة )‪ .(Compatibility‬حالي ّا‪،‬‬
‫إذا أردنا القيام بالأمر بالشكل الجيد‪ ،‬أنصحك باستعمال ‪. SDL_AddTimer‬‬

‫الدالة تستقبل ثلاثة معاملات ‪:‬‬

‫‪402‬‬
‫‪ .2.25‬المُؤقـ ِتات )‪(Timers‬‬

‫ل استدعاء للدالة و آخر‪.‬‬


‫• المجال الزمني )بالميلي ثانية( بين ك ّ‬

‫• اسم الدالة التي نريد استدعاءها‪ .‬نسمّي هذه بدالة الرد )‪ : (Callback‬يتكفّل البرنامج باستدعاء الدالة بشكل دوري‪.‬‬

‫• المعاملات التي نبعثها لدالة الرد‪.‬‬

‫؟‬
‫كيف يمكن لاسم دالة أن يكون معاملا ؟ اعتقدتُ أن المعاملات لا يمكنها أن تكون إلا أسماء متغيرات !‬

‫في الواقع‪ ،‬يتم أيضا ً حْ فظ الدوال في الذاكرة خلال تحميل البرنامج‪ .‬فهي تملك أيضا ً عناوين خاصة بها‪ .‬لهذا‪ ،‬يمكننا‬
‫الـ‪SDL‬‬ ‫أن نن ُشئ مؤش ّرات نحو دوال ! تكفي كتابة اسم الدالة التي نريد استدعاءها للإشارة إلى عنوانها‪ .‬و بهذا‪ ،‬ستعرف‬
‫العنوان بالذاكرة الذي يجب أن تذهب إليه لاستدعاء دالة الرد‪.‬‬
‫إذا أردت معرفة المزيد حول المؤشرات نحو الدوال‪ ،‬أدعوك إلى قراءة الدرس التعليمي المكتوب بواسطة العضو ‪) mleg‬في‬
‫ل هذا الموضوع ‪:‬‬
‫الموقع الفرنسي( و الذي يحل ّ‬

‫‪http://www.siteduzero.com/tutoriel-3-314203-les-pointeurs-sur-fonctions.html‬‬

‫ت ُرجع الدالة ‪ SDL_AddTimer‬عددا ً خاصا ًبالمُؤقـ ِت )‪ .(ID‬يجدر بك تخزين هذه النتيجة في متغير من نوع ‪. SDL_TimerID‬‬
‫هذا سيسمح لك لاحقا ًبتعطيل المُؤقـ ِت ‪ :‬يكفي أن تشير إلى ‪ ID‬المُؤقـ ِت و سيتم ّ إيقافه‪.‬‬

‫ل م ُؤقـ ِت لـكيّ‬
‫تسمح لنا الـ‪ SDL‬بتفعيل الـكثير من المُؤقـ ِتات في نفس الوقت‪ .‬هذا يشرح الفائدة من تخزين هو ي ّة ك ّ‬
‫نفرق بينها‪.‬‬

‫سنُنشئ إذا هو ية المُؤقـ ِت ‪:‬‬

‫‪1 SDL_TimerID timer; // A variable to save the number of timer‬‬

‫ثم نقوم بإنشاء المُؤقـ ِت ‪:‬‬

‫‪1 timer = SDL_AddTimer(30, moveZozor, &zozorPosition); // Starting the timer‬‬

‫هنا‪ ،‬أنشئ م ُؤقـ ِتا يحمل المعاملات التالية ‪:‬‬

‫ل ‪ 30‬مث‪.‬‬
‫• يتم استدعاؤه ك ّ‬

‫• يقوم باستدعاء دالة الرد المسمّاة ‪. moveZozor‬‬

‫• يبعث له كمعامل‪ ،‬مؤش ّرا ً نحو وضعية ‪ Zozor‬لـكي يتمكّن من التعديل عليه‪.‬‬

‫لقد فهمت المبدأ ‪ :‬دور الدالة ‪ moveZozor‬هو تغيير وضعية ‪ Zozor‬كل ‪ 30‬مث‪.‬‬

‫‪403‬‬
‫الفصل ‪ .25‬تحكّم في الوقت !‬

‫إنشاء دالة الرد‬

‫احذر ‪ :‬يجب أن تكون حذرا ً هنا‪ .‬يجب أن يكون نموذج دالة الرد هو التالي إجبار يا ‪:‬‬

‫;)‪1 Uint32 functionName(Uint32 interval, void �parameter‬‬

‫لـكي ننشئ دالة الرد المسمّاة ‪ ، moveZozor‬يجب أن نكتب الدالة كالتالي ‪:‬‬

‫;)‪1 Uint32 moveZozor(Uint32 interval, void �parameter‬‬

‫إليك الشفرة الخاصة بالدالة ‪ ، moveZozor‬إنها معقّدة أكثر مما تبدو عليه ‪:‬‬

‫)‪1 // Callback function (Will be called every 30 ms‬‬


‫)‪2 Uint32 moveZozor(Uint32 interval, void �parameter‬‬
‫{ ‪3‬‬
‫‪4‬‬ ‫�‪SDL_Rect� zozorPosition = parameter; // Automatic conversion from void‬‬
‫�‪to SDL_Rect‬‬
‫‪5‬‬ ‫;‪zozorPosition−>x++‬‬
‫‪6‬‬ ‫;‪return interval‬‬
‫} ‪7‬‬

‫يتم استدعاء الدالة ‪ moveZozor‬تلقائي ّا كل ‪ 30‬مث بواسطة الـ‪ .SDL‬تقوم هذه الأخيرة ببعث معاملين تماما ً للدالة‬
‫)لا أكثر و لا أقل( ‪:‬‬

‫• المجال الزمني الذي يفر ّق كل استدعائين للدالة )هنا ‪ 30‬مث(‪.‬‬

‫• المعامل ”المخصّ ص” الذي طلبت إعطاءه للدالة ‪.‬لاحظ‪ ،‬و من المهم جداً‪ ،‬أن هذا المعامل هو عبارة عن مؤش ّر نحو‬
‫‪SDL_Rect‬‬ ‫‪ . void‬هذا يعني أنه مؤش ّر يؤش ّر نحو أي نوع كان ‪ :‬على ‪ ، int‬هيكل مخصّ ص‪ ،‬أو ‪،‬مثل هنا‪ ،‬على‬
‫) ‪.( zozorPosition‬‬
‫لاحظ أيضا ًأنه لا يمكن بعث أكثر من معامل مخصّ ص لدالة الرد‪ .‬لحسن الحظ‪ ،‬نحن دائما ًقادرون على إنشاء أنواع‬
‫خاصة بنا )أو جداول( و التي ستكون عبارة عن تجميع لمتغيرات نريد بعثها للدالة‪.‬‬

‫المشكل هو أن هذا المعامل هو مؤش ّر من نوع غير معروف ) ‪ ( void‬للدالة‪ .‬يجب إذا أن نقول للحاسوب أن هذا‬
‫المعامل هو *‪) SDL_Rect‬مؤش ّر نحو ‪.( SDL_Rect‬‬

‫لفعل ذلك‪ ،‬أنشئ مؤش ّرا نحو ‪ SDL_Rect‬في دالتي التي تأخذ كمعامل المؤش ّر ‪. parameter‬‬

‫؟‬
‫ن ليحمل نفس العنوان ؟‬
‫ما الفائدة من إنشاء مؤش ّر ثا ٍ‬

‫الفائدة هي أن ‪ zozorPosition‬من نوع *‪ SDL_Rect‬بعكس المتغير ‪ parameter‬الذي كان من نوع‬


‫*‪. void‬‬

‫‪404‬‬
‫ملخّ ص‬

‫يمكننا إذا الوصول إلى ‪ zozorPosition->x‬و ‪. zozorPosition->y‬‬


‫ن متغي ّرا من نوع ‪ void‬لا يملك‬
‫لو قمت بكتابة ‪ parametre->x‬أو ‪ parametre->y‬فالمترجم كان سيرفضها لأ ّ‬
‫هذه المركّبات‪.‬‬

‫بعد ذلك‪ ،‬السطر التالي بسيط ‪ :‬نعدّل قيمة ‪ zozorPosition->x‬لتحر يك ‪ Zozor‬نحو اليمين‪.‬‬

‫آخر شيء )مهم جداً( ‪ :‬يجب عليك إرجاع المتغير ‪ . interval‬هذا يُشير للـ‪ SDL‬بأننا نريد أن نستمر في اعتبار أنه‬
‫سيتم استدعاء الدالة كل ‪ 30‬مث‪.‬‬
‫إذا كنت تريد تغيير المجال الزمني لاستدعاء الدالة‪ ،‬يكفي أن تبعث قيمة أخرى )في غالب الأحيان لا نفعل ذلك(‪.‬‬

‫إيقاف المُؤقـ ِت‬

‫لإيقاف المُؤقـ ِت‪ ،‬الأمر بسيط ‪:‬‬

‫‪1 SDL_RemoveTimer(timer); // Stopping the timer‬‬

‫يكفي إذا استدعاء ‪ SDL_RemoveTimer‬و ذلك بالإشارة إلى هو ية المُؤقـ ِت الذي نريد إيقافه‪.‬‬
‫هنا أوقف المُؤقـ ِت مباشرة بعد الحلقة غير المنتهية‪ ،‬في نفس موضع ‪. SDL_FreeSurface‬‬

‫ملخّ ص‬

‫• تمكّن الدالة ‪ SDL_Delay‬من إيقاف البرنامج مؤق ّتا لعدد معين من الميلّي ثواني‪ .‬هذا يسمح بإنقاص نسبة استعمال‬
‫المُعالج الذي لن يكون ”خلال نوم البرنامج” مستعملا ًمن طرف هذا الأخير‪.‬‬

‫• يمكننا معرفة عدد الميلّي ثواني المنقضية منذ اشتغال البرنامج باستعمال ‪ ، SDL_GetTicks‬بواسطة عمليات حسابية‬
‫بسيطة‪ ،‬يمكننا الاستفادة من هذا لـكي نقوم بمعالجة غير م ُعطّلة للأحداث بواسطة ‪. SDL_PollEvent‬‬

‫• المُؤقـ ِتات تشكّل نظاما يسمح باستدعاء دوالك )المسمّاة بدوال الرد( على مجالات زمني ّة محدّدة‪ .‬يمكننا التحصل على‬
‫نفس النتيجة باستعمال ‪ SDL_GetTicks‬لـكن المُؤقـ ِتات تساعد على هيكلة البرنامج بشكل أفضل و جعله أحسن‬
‫من ناحية القراءة‪.‬‬

‫‪405‬‬
‫الفصل ‪ .25‬تحكّم في الوقت !‬

‫‪406‬‬
‫الفصل ‪26‬‬

‫‪SDL_ttf‬‬ ‫كتابة نصوص باستعمال‬

‫يمكنني التكهّن بأن معظم القر ّاء قد طرح هذا السؤال من قبل ‪” :‬و لـكن‪ ،‬ألا توجد أي دالة لـكي تكتب نصا ً على‬
‫نافذة ‪ SDL‬؟” حان الوقت لأجيبك ‪ :‬الجواب هو لا‪.‬‬

‫رغم ذلك‪ ،‬توجد طرق لفعل هذا‪ .‬يمكننا فقط ‪ ...‬وضع صور للحروف بجانب بعضها البعض على الشاشة‪ .‬هذا الأمر‬
‫يعمل لـكن ّه ليس عمليا‪.‬‬

‫لحسن الحظ‪ ،‬يوجد ماهو أبسط ‪ :‬يمكننا استعمال المكتبة ‪ .SDL_ttf‬إنها مكتبة تتم إضافتها إلى الـ‪ SDL‬تماما ً مثل‬
‫الـ‪ .SDL_image‬دورها هو إنشاء مساحة ‪ SDL_Surface‬إنطلاقا من النص الذي نبعثه لها‪.‬‬

‫‪SDL_ttf‬‬ ‫تسطيب‬ ‫‪1.26‬‬

‫يجب أن تعرف أنه‪ ،‬مثل ‪ SDL_ttf ،SDL_image‬هي مكتبة تحتاج إلى أن تكون المكتبة ‪ SDL‬مثب ّتة من قبل‪ .‬حسنا ً‬
‫‪ :‬إذا كنت إلى ح ّد الآن لم تتمكّن من تسطيب المكتبة ‪ SDL‬فهذا أمر شنيع و لهذا فسأعتبر أنك قمت بذلك !‬

‫تماما مثل ‪ ،SDL_image‬فإن المكتبة ‪ SDL_ttf‬هي واحدة من المكتبات المُرتبطة بالـ‪ SDL‬الأكثر شعبية )أي أنه يتم‬
‫تنز يلها بكثرة(‪ .‬كما ستُلاحظ‪ ،‬هذه المكتبة م ُبرمجة بشكل جيد‪ .‬ما إن تجيد استعمالها لن يمكنك أن ٺتوق ّف عن ذلك !‬

‫كيف تعمل ‪ SDL_ttf‬؟‬

‫‪ SDL_ttf‬لا تقوم بإظهار صور ‪ bitmap‬لتولّد نصا في مساحات‪ .‬في الحقيقة‪ ،‬هي طر يقة ثقيلة لفعلها و لن يتاح لنا استعمال‬
‫سوى خط واحد‪.‬‬
‫في الواقع‪ ،‬تستدعي المكتبة ‪ SDL_ttf‬مكتبة َأخرى ‪ .FreeType :‬هي مكتبة قادرة على قراءة ملفات خطوط بصيغة ‪.ttf‬‬

‫لت ُخرج منها صورة‪ .‬تقوم ‪ SDL_ttf‬باسترجاع هذه الصورة و تحو ّلها للـ‪ SDL‬و ذلك بإنشاء مساحة ‪. SDL_Surface‬‬

‫و بهذا فإن ‪ SDL_ttf‬تحتاج المكتبة ‪ FreeType‬لـكي تشتغل‪ ،‬و إلا فلن تكون قادرة على قراءة ملفات الخطوط ‪. .ttf‬‬

‫إذا كنت تعمل بـ‪ Windows‬و تستعمل‪ ،‬مثلما أفعل‪ ،‬النسخة المُترجم َة للمكتبة‪ ،‬لن تحتاج إلى تنز يل أي شيء لأن‬
‫‪ FreeType‬مضمّنة من قبل في المكتبة الحي ّة ‪ SDL_ttf.dll‬و لهذا فليس عليك القيام بأي شيء‪.‬‬

‫‪407‬‬
‫‪SDL_ttf‬‬ ‫الفصل ‪ .26‬كتابة نصوص باستعمال‬

‫إذا كنت تعمل بالـ‪ GNU/Linux‬أو ‪ Mac OS X‬فمن اللازم أن تعيد ترجمة المكتبة‪ ،‬فتلزمك ‪ FreeType‬لتتم الترجمة‪.‬‬
‫اذهب إذن إلى صفحة تنز يل ‪: FreeType‬‬

‫‪http://www.freetype.org/download.html#stable‬‬

‫لتنز يل الملفات الخاصة بالمطورين‪.‬‬

‫‪SDL_ttf‬‬ ‫ٺثبيت‬

‫اذهب إلى صفحة تنز يل ‪: SDL_ttf‬‬

‫‪http://www.libsdl.org/projects/SDL_ttf/‬‬

‫هنا‪ ،‬اختر الملف اللازم من القسم ”‪.”Binary‬‬

‫م‬
‫في ‪ ،Windows‬لاحظ أنه لا يوجد سوى ملفان بصيغة ‪ .zip‬يحملان في نهاية اسميهما اللاحقتين ‪ win32‬و‬
‫‪ . VC6‬الأولى ) ‪ ( win32‬تحتوي الـ‪ DLL‬التي تحتاج إلى تقديمها مع الملف التنفيذي‪ .‬يجب عليك أيضا ً وضع‬
‫هذه الـ‪ DLL‬في مجلّد المشروع لتستطيع تجريب البرنامج‪ ،‬طبعا‪.‬‬
‫الثانية ) ‪ ( VC6‬تحتوي الملفات ‪ .h‬و الملفات ‪ .lib‬التي تحتاجها للبرمجة‪ .‬يمكننا أن نفك ّر من خلال الاسم‬
‫أن هذه الملفّات تخص ‪ Visual C++‬فقط‪ ،‬لـكن في الحقيقة‪ ،‬و بشكل خاص‪ ،‬الملف ‪ .lib‬يعمل أيضا ً مع‬
‫‪ ،mingw32‬سيشتغل إذن في الـ‪.Code::Blocks‬‬

‫الملف ‪ .zip‬يحتوي كالعادة مجلد ‪ include‬و مجلد ‪ . lib‬قم بوضع محتوى المجلد ‪ include‬في المسار‬
‫‪ ، mingw32/include/SDL‬و محتوى المجلد ‪ lib‬في المسار ‪. mingw32/lib‬‬

‫!‬
‫و ليس في المجلد‬ ‫‪mingw32/include/SDL‬‬ ‫في المجلد‬ ‫‪SDL_ttf.h‬‬ ‫يجدر بك نسخ الملف‬
‫‪ mingw32/include‬فقط‪ .‬احذر الخطأ !‬

‫الـ‪SDL_ttf‬‬ ‫تخصيص مشروع من أجل‬

‫بقيت لنا مرحلة واحدة أخيرة ‪ :‬تخصيص المشروع لـكي يكون قادرا ً على استعمال ‪ SDL_ttf‬بشكل جيد‪ .‬يجب أن يتم‬
‫التعديل على خصائص محر ّر الروابط لـكي يُترجم البرنامج بشكل جيد و ذلك باستعمال ‪.SDL_ttf‬‬

‫لقد تعلّمت من قبل هذه العملية بالنسبة لـ‪ ،SDL_image‬و لهذا سأسرع قليلاً‪.‬‬
‫بما أنني أعمل في الـ‪ Code::Blocks‬سأعطيك العملية الخاصة بهذه البيئة التطوير ية‪ .‬بالنسبة لباقي البيئات‪ ،‬فالطر يقة لا‬
‫تختلف كثيرا ً عن هذه ‪:‬‬

‫• توجّه نحو القائمة ‪. Build Options / Project‬‬

‫‪408‬‬
‫‪SDL_ttf‬‬ ‫‪ .1.26‬تسطيب‬

‫• في القسم ‪ Linker‬أنقر على الزر الصغير ‪. Add‬‬

‫• أشر إلى المسار الذي يوجد به الملف ‪) SDL_ttf.lib‬بالنسبة لي هو في‬


‫‪.( C:\Program Files\CodeBlocks\mingw32\lib‬‬

‫• ستظهر لك هذه الرسالة ‪ ”Keep this as a relative path ?” :‬لا يه ّم ما تختاره لأن الأمر سيشتغل في كلتا الحالتين‪.‬‬
‫أنصحك أن تجيب بالسلب لأن المشروع لن يشتغل لو وضعته في مسار آخر غير المتواجد به لو أنك أجبت بالإ يجاب‪.‬‬

‫• وافق على التغييرات بالنقر على ‪. OK‬‬

‫؟‬
‫ألا نحتاج إلى ربط المكتبة ‪ FreeType‬أيضا ً؟‬

‫‪SDL_ttf‬‬ ‫كلا‪ ،‬مثلما قلتُ فـ‪ FreeType‬مضمّنة في الـ‪ DLL‬الخاصة بـ‪ .SDL_ttf‬لهذا فلن يكون عليك الاهتمام بها‪ ،‬لأن‬
‫تفعل ذلك الآن‪.‬‬

‫الملفات التوثيقية‬

‫و الآن بما أنك أصبحت مبرمجا ً محن ّكا ًتقريباً‪ ،‬يجدر بك أن تطرح التساؤل التالي ‪” :‬لـكن أين هو التوثيق ؟” إن لم تطرح‬
‫هذا السؤال فهذا يعني أن ّك لازلت لم تصبح بعد مبرمجا ً محن ّكاً‪.‬‬

‫يوجد بالطبع دروس تفصّ ل في كيفية عمل المكتبات‪ ،‬مثل هذا الكتاب‪ ،‬و لـكن ‪:‬‬

‫• لن أستطيع أن أضع لك فصلا ً حول كل المكتبات الموجودة )حتى لو أمضيت حياتي كل ّها في ذلك‪ ،‬لن يكفيني‬
‫الوقت !(‪ .‬و لهذا يجب عاجلا ًأم آجلا قراءة التوثيق و يجدر بك أن ٺتعو ّد على ذلك من الآن !‬

‫• من جهة أخرى‪ ،‬في غالب الأحيان تكون المكتبة معقّدة نوعا ًما و تحتوي كثيرا ً من الدوال‪ .‬لن أتمكن من تقديم‬
‫ل هذه الدوال في هذا الفصل لأنه سيكون بذلك طو يلا ًجدا ً !‬
‫ك ّ‬

‫من الواضح جدا ً أن التوثيق يكون كاملا و يلم ّ بكل خفايا المكتبة‪ ،‬و لهذا أفضّ ل أن أعطيك من الآن رابط صفحة‬
‫التوثيق الخاصة بـ‪: SDL_ttf‬‬

‫‪http://sdl.beuc.net/sdl.wiki/SDL_ttf‬‬

‫التوثيق متوف ّر بصيغ مختلفة ‪ HTML :‬على الشبكة‪ HTML ،‬مضغوطة‪ ،PDF ،‬إلخ‪ .‬خذ النسخة التي تناسبك‪.‬‬

‫ستجد بأن ‪ SDL_ttf‬مكتبة بسيطة جدا ً ‪ :‬يوجد بها قليل من الدوال )حوالي ‪ ،50 - 40‬نعم إنها قليلة !(‪ .‬يجدر بهذا‬
‫أن تكون إشارة )للمبرمجـين المحن ّكين من ضمن القر ّاء( إلى أن هذه المكتبة سهلة و ستستطيع التعامل معها سر يعاً‪.‬‬

‫هيا‪ ،‬حان الوقت لنتعل ّم كيف نستخدم ‪ SDL_ttf‬الآن !‬

‫‪409‬‬
‫‪SDL_ttf‬‬ ‫الفصل ‪ .26‬كتابة نصوص باستعمال‬

‫‪SDL_ttf‬‬ ‫تحميل‬ ‫‪2.26‬‬

‫التضمين‬

‫ل استعمال لهذه المكتبة ‪:‬‬


‫ل شيء‪ ،‬يجب تضمين الملف الرأسي التالي قبل ك ّ‬
‫قبل ك ّ‬

‫>‪1 #include <SDL/SDL_ttf.h‬‬

‫‪mingw32/include/SDL‬‬ ‫إذا صادفت أخطاء ترجمة الآن‪ ،‬تأكد بأنك وضعت الملف ‪ SDL_ttf.h‬في المجلّد‬
‫و ليس في ‪ mingw32/include‬فقط‪.‬‬

‫‪SDL_ttf‬‬ ‫تشغيل‬

‫تماما مثل الـ‪ ،SDL‬تحتاج ‪ SDL_ttf‬أن ُتشغ ّل في بداية الشفرة وتُوق ّف في نهايتها‪.‬‬
‫توجد دالتان تشبهان كثيرا ً الدالتين الخاصتين بالـ‪: SDL‬‬

‫• ‪ : TTF_Init‬تقوم ببدء تشغيل ‪.SDL_ttf‬‬

‫• ‪ : TTF_Quit‬توق ّف ‪.SDL_ttf‬‬

‫م‬
‫ليس واجبا ًأن يتم بدء تشغيل ‪ SDL‬قبل ‪.SDL_ttf‬‬

‫لـكي تقوم ببدء تشغيل ‪) SDL_ttf‬نقول أيضا ً تهيئة(‪ ،‬يجب أن نستدعي الدالة ‪ . TTF_Init‬هذه الأخيرة لا تحتاج‬
‫إلى أن تستقبل أي معامل و هي تقوم بإرجاع القيمة ‪ −1‬إن حدث أي خطأ‪.‬‬

‫يمكنك البدء في تشغيل ‪ SDL_ttf‬ببساطة كالتالي ‪:‬‬

‫;)(‪1 TTF_Init‬‬

‫إذا أردت أن ٺتأكد ما إن كان قد حدث خطأ أم لا‪ ،‬جرّب الشفرة التالية ‪:‬‬

‫)‪1 if(TTF_Init() == −1‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;))(‪fprintf(stderr, ”Error initializing TTF_Init : %s\n”, TTF_GetError‬‬
‫‪4‬‬ ‫;)‪exit(EXIT_FAILURE‬‬
‫} ‪5‬‬

‫إذا كان هناك خطأ في تشغيل ‪ ،SDL_ttf‬سيتم إنشاء ملف ‪) stderr.txt‬في ‪ Windows‬على الأقل( يحتوي على رسالة‬
‫تشرح الخطأ‪.‬‬
‫للذين يطرحون السؤال ‪ :‬الدالة ‪ TTF_GetError‬تقوم بإرجاع آخر رسالة خطأ للـ‪ ،SDL_ttf‬و لهذا استعملتها في‬
‫الـ ‪. fprintf‬‬

‫‪410‬‬
‫‪SDL_ttf‬‬ ‫‪ .2.26‬تحميل‬

‫‪SDL_ttf‬‬ ‫إيقاف‬

‫‪SDL_Quit‬‬ ‫لنوق ّف المكتبة‪ ،‬نستدعي الدالة ‪ . TTF_Quit‬هي أيضا ًلا تحتاج أي معامل‪ .‬يمكنك استدعاؤها قبل أو بعد‬
‫هذا لا يهم ‪:‬‬

‫;)(‪1 TTF_Quit‬‬

‫تحميل خط‬

‫ل شيء جيدا ً و غير مع ّقدٍ‪ ،‬لـكننا لم نستمتع بعد‪ .‬لننتقل إلى الأه ّم إذا أردت ذلك ‪ :‬و الآن بما أنه تم تحميل‬
‫حسنا ًكان ك ّ‬
‫‪ ،SDL_ttf‬يجب علينا أن نقوم بتحميل خط ما‪ .‬ما إن يتم هذا الشيء‪ ،‬يمكننا أخيرا ً كتابة النص !‬

‫هنا أيضاً‪ ،‬توجد دالتان ‪:‬‬

‫• ‪ : TTF_OpenFont‬تفتح ملف خط ) ‪.( .ttf‬‬

‫• ‪ : TTF_CloseFont‬تغلق الملف المفتوح‪.‬‬

‫يجدر بالدالة ‪ TTF_OpenFont‬أن تخز ّن النتيجة في متغير من نوع ‪ . TTF_Font‬لهذا يجب عليك إنشاء مؤش ّر من‬
‫نوع ‪ TTF_Font‬كالتالي ‪:‬‬

‫;‪1 TTF_Font �font = NULL‬‬

‫يحتوي المؤش ّر ‪ font‬إذا على معلومات خاصة بالخط المفتوح‪.‬‬

‫تأخذ الدالة ‪ TTF_OpenFont‬معاملين ‪:‬‬

‫• اسم ملف الخط )بصيغة ‪ ( .ttf‬الذي نريد فتحه‪ .‬الأمثل هو وضع ملف الخط في مجلّد المشروع‪ .‬مثال عن ملف‬
‫‪) arial.ttf :‬من أجل الخط ‪.(Arial‬‬

‫• حجم الخط الذي نريد استعماله‪ .‬يمكنك مثلا استعمال حجم ‪.22‬‬
‫إنها نفس الحجوم التي تستعملها في برامج معالجة النصوص مثل ‪.Word‬‬

‫لم يتبقّ لنا سوى إ يجاد الخطوط ذات الصيغة ‪ . .ttf‬أنت تملك أصلا ًالعديد منها على حاسوبك‪ ،‬لـكن يمكنك تنز يلها‬
‫من الأنترنت كما سنرى الآن‪.‬‬

‫‪411‬‬
‫‪SDL_ttf‬‬ ‫الفصل ‪ .26‬كتابة نصوص باستعمال‬

‫على حاسوبك‬

‫لديك أصلا خطوط على حاسوبك !‬


‫إن كنت تعمل بـ‪ ،Windows‬ستجد الـكثير من هذه الملفات في المجلّد ‪. C:\Windows\Fonts‬‬
‫ليس عليك سوى نسخ الملف الخاص بالخط الذي يعجبك و لصقه في مجلّد المشروع‪.‬‬

‫إذا كان اسم الملف يحتوي على حروف ”غريبة” كالفراغات‪ ،‬الحروف ذات العلامات الصوتية )‪ (accents‬أو حتى‬
‫الحروف الـكبيرة‪ ،‬أنصحك بإعادة تسمية هذا الملف‪ .‬و لـكي نكون متيقنين من عدم وجود أيّ مشكل‪ ،‬لا تستعمل سوى‬
‫الأحرف الصغيرة و تجن ّب الفراغات‪.‬‬

‫• مثال عن اسم خاطئ ‪. TIMES NEW ROMAN.TTF :‬‬

‫• مثال عن اسم صحيح ‪. times.ttf :‬‬

‫على الأنترنت‬

‫خط من الأنترنت‪ .‬ستجد الـكثير من المواقع التي تقترح خطوطا مجانية و أصلية للتنز يل‪.‬‬
‫الخيار الآخر ‪ :‬احصل على ّ‬

‫أنصحك شخصيا بز يارة الموقع ‪ dafont.com‬لأن ّه مصن ّف بشكل جي ّد و محتواه منظّم و منو ّع‪.‬‬

‫لاحظ الصور التالية التي ستعطيك فكرة عن الخطوط التي ستجدها هناك بسهولة ‪:‬‬

‫تحميل الخط‬

‫أقترح عليك استعمال الخط ‪ (http://www.dafont.com/angelina.font) Angelina‬لبقية الأمثلة‪.‬‬

‫فلنفتح الخط كالتالي ‪:‬‬

‫;)‪1 font = TTF_OpenFont(”angelina.ttf”, 65‬‬

‫‪412‬‬
‫‪ .3.26‬الطرق المختلفة للكتابة‬

‫الخط المستعمل سيكون ‪ . angelina.ttf‬لقد قمت وضع هذا الخط في مجلّد المشروع كما قمت بإعادة تسميته لـكي‬
‫يكون كل ّه بحروف صغيرة‪.‬‬
‫خط خاص يستلزم ذلك لـكي يظهر بشكل جيد‪.‬‬
‫سيكون للخط الحجم ‪ .65‬ستبدو الكتابة كبيرة لـكنه ّ‬

‫الأمر المهم هو أن ‪ TTF_OpenFont‬تخز ّن النتيجة في المتغير ‪ ، font‬ستعيد استعمال هذا المتغير الآن بكتابة نص‪.‬‬
‫فهي تسمح بالإشارة إلى الخط الذي نريد أن نستعمله لـكي نكتب النص‪.‬‬

‫م‬
‫مرة واحدة في بداية البرنامج و أغلقه في نهايته‪.‬‬
‫ل مرة تريد فيها الكتابة به ‪ :‬افتحه ّ‬
‫لا تحتاج إلى فتح الخط في ك ّ‬

‫غلق الخط‬

‫يجب التفكير في غلق كل خط قمنا بفتحه قبل استدعاء ‪ . TTF_Quit‬في حالتي‪ ،‬هذا ما تكون عليه الشفرة ‪:‬‬
‫;)(‪1 TTF_CloseFont(font); // Must be before TTF_Quit‬‬
‫;)(‪2 TTF_Quit‬‬

‫هكذا يكون العمل !‬

‫الطرق المختلفة للكتابة‬ ‫‪3.26‬‬

‫و الآن‪ ،‬بما أنه تم تحميل ‪ SDL_ttf‬و أن لدينا متغيرا ‪ font‬محم ّلا هو الآخر‪ ،‬لن يمنعنا أي شيء و أي شخص من كتابة‬
‫نص في نافذة ‪! SDL‬‬

‫ل عن ‪ 12‬دالة لفعل ذلك !‬


‫جيد ‪ :‬كتابة النص هو أمر جيد‪ ،‬لـكن بواسطة أي دالة ؟ من خلال التوثيق يوجد ما لا يق ّ‬

‫في الواقع‪ ،‬توجد ‪ 3‬طرق مختلفة للـ‪ SDL_ttf‬لـكي ترسم نصاً‪.‬‬

‫• ‪) Solid‬الصورة ‪ : (1‬هي التقنية الأكثر سرعة‪ .‬ستتم كتابة النص بسرعة في ‪ . SDL_Surface‬ستكون المساحة‬
‫شفافة لـكنها لن تستخدم إلا مستو ً واحدا ً من الشفافية )لقد تعلّمنا ذلك في الفصول السابقة(‪ .‬هذا أمر عملي‪ ،‬لـكن‬
‫النص لن يكون جميلا ًلأنه حوافه لن تكون منحوتة بشكل جيد و خاصة إن كان مكتوبا بحجم ضخم‪ .‬استعمل هذه‬
‫التقنية حينما يكون عليك تغيير النص كثيراً‪ ،‬مثلا لإظهار الوقت المنقضي أو عدد الـ‪ FPS‬الخاص بلعبة‪.‬‬

‫• ‪) Shaded‬الصورة ‪ : (2‬هذه المرة‪ ،‬سيكون النص جميلاً‪ .‬فالحروف ستكون محسّنة أكثر )هذا يعني أن محيط‬
‫الحواف سيكون م ُـلطّفا بشكل م ُريح لعين الإنسان( و سيظهر النص أكثر نعومة‪ .‬يوجد عيب في هذه التقنية ‪ :‬يجب‬
‫أن تكون الخلفية ذات لون واحد مو ّحد‪ .‬يستحيل جعل خلفية الـ ‪ SDL_Surface‬شفافة بطر يقة الـ‪.Shaded‬‬

‫• ‪) Blended‬الصورة ‪ : (3‬هي التقنية الأكثر قو ّة‪ ،‬لـكنها بطيئة‪ .‬في الواقع‪ ،‬هي تأخذ الوقت اللازم الذي تأخذه‬
‫ق النص‬
‫التقنية ‪ Shaded‬لإنشاء الـ ‪ . SDL_Surface‬الإختلاف الوحيد بينها و بين الـ‪ ،Shaded‬هي أنه يمكنك لص ُ‬
‫على صورة و سيتم احترام الشفافية )على عكس ‪ Shaded‬التي تفرض وجود خلفية موحّدة اللون(‪ .‬احذر ‪ :‬عملية‬
‫اللصق بهذه الطر يقة أبطأ من تلك الخاصة بالـ‪.Shaded‬‬

‫‪413‬‬
‫‪SDL_ttf‬‬ ‫الفصل ‪ .26‬كتابة نصوص باستعمال‬

‫ملخّ ص ‪:‬‬

‫• إذا كان لديك نص يتغير محتواه كثيراً‪ ،‬كعداد عكسي‪ ،‬استعمل التقنية ‪.Solid‬‬

‫• إذا كان النص لا يتغير كثيرا ً و أنك تريد لصق النص على خلفية موحدة اللون‪ ،‬استعمل التقنية ‪.Shaded‬‬

‫• إذا كان النص لا يتغير كثيرا ً و لـكنك تريد لصقه على خلفية غير موحدّة اللون )كصورة مثلاً( استعمل التقنية‬
‫‪.Blended‬‬

‫هكذا إذا‪ ،‬يجدر بك أن تكون قد تعو ّدت قليلا ًعلى هذه الأساليب الخاصة بـ‪ SDL_ttf‬في الكتابة‪.‬‬

‫لقد قلتُ لك أنه توجد ‪ 12‬دالة لذلك‪.‬‬


‫ل دالة تكتب النص بالاستعانة بمجموعة محارف‬
‫ل طر يقة في الكتابة‪ ،‬توجد ‪ 4‬دوال لذلك‪ .‬ك ّ‬
‫في الواقع‪ ،‬من أجل ك ّ‬
‫)‪ (Charset‬مختلفة‪ .‬هذه الدوال هي ‪:‬‬

‫• ‪،Latin1‬‬
‫• ‪،UTF8‬‬
‫• ‪،Unicode‬‬
‫• ‪.Unicode Glyph‬‬

‫‪414‬‬
‫‪ .3.26‬الطرق المختلفة للكتابة‬

‫الأمثل أن تختار ‪ Unicode‬لأنها مجموعة محارف تحوي أغلب الحروف و الإشارات الموجودة على وجه الأرض‪ .‬و‬
‫لـكن‪ ،‬استعمال الـ‪ Unicode‬ليس سهلا دائما ً )محرف واحد يأخذ حجما أكبر من حجم ‪ char‬في الذاكرة(‪ ،‬فلن نرى‬
‫كيفية استعمالها هنا‪.‬‬

‫إذا كان برنامجك مكتوبا ًبالفرنسية فمجموعة ‪ Latin1‬تكفي بإسهاب‪ ،‬يمكنك الاكتفاء بهذه الأخيرة‪.‬‬

‫الدوال الثلاثة التي تستعمل نظام التشفير ‪ Latin1‬هي ‪:‬‬

‫• ‪، TTF_RenderText_Solid‬‬
‫• ‪، TF_RenderText_Shaded‬‬
‫• ‪. TTF_RenderText_Blended‬‬

‫الـ‪Blended‬‬ ‫مثال عن كتابة نص بطر يقة‬

‫لـكيّ نختار لونا بـ‪ ،SDL_ttf‬لن نستعمل نفس النوع كما بالـ‪) SDL‬إنشاء متغير من نوع ‪ Uint32‬بالاستعانة بالدالة‬
‫‪.( SDL_MapRGB‬‬
‫بالعكس‪ ،‬سنستعمل هيكلا جاهزا من طرف الـ‪ SDL‬و هو ‪ . SDL_Color :‬هذا الهيكل يحتوي ثلاثة مركّبات ‪ :‬كمية‬
‫الأحمر‪ ،‬الأخضر و الأزرق‪.‬‬

‫إذا أردت إنشاء متغير ‪ ، blackColor‬يجب عليك أن تكتب إذا ‪:‬‬


‫;}‪1 SDL_Color blackColor = {0, 0, 0‬‬

‫!‬
‫احذر لـكي لا تخلط بينها و بين الألوان التي تستعملها عادة ‪! SDL‬‬
‫الـ‪ SDL‬تستعمل متغيرات ‪ Uint32‬يتم إنشاؤها بمساعدة ‪. SDL_MapRGB‬‬
‫بينما ‪ SDL_ttf‬تستعمل متغيرات ‪. SDL_Color‬‬

‫سنقوم بكتابة نص بالأسود في ‪ ، SDL_Surface‬نسميها ‪. text‬‬


‫;)‪1 text = TTF_RenderText_Blended(font, ”Salut les Zér0s !”, blackColor‬‬

‫أنت ترى المعاملات التي بعثتاها بالترتيب ‪ :‬الخط )من نوع ‪ ،( TTF_Font‬النص الذي نريد كتابته و أخيرا ً اللون‬
‫)من نوع ‪.( SDL_Color‬‬
‫يتم تخزين النتيجة في مساحة‪ .‬تحسب ‪ SDL_ttf‬تلفائيا ً الحجم اللازم للمساحة بدلالة حجم النص و عدد الحروف التي تريد‬
‫كتابتها‪.‬‬

‫كما هو الحال بالنسبة لأي مساحة‪ ،‬سيحتوي المؤش ّر ‪ text‬المركّبات ‪ w‬و ‪ h‬التي تشير بالترتيب إلى عرض و ارتفاع‬
‫المساحة‪ .‬إذن فهذه طر يقة جيدة لمعرفة أبعاد النص ما إن تتم كتابة هذا الأخير على المساحة‪ .‬لن يكون عليك سوى كتابة ‪:‬‬
‫‪1 text−>w // Gives the width‬‬
‫‪2 text−>h // Gives the height‬‬

‫‪415‬‬
SDL_ttf ‫ كتابة نصوص باستعمال‬.26 ‫الفصل‬

‫الشفرة المصدر ية الكاملة لكتابة نص‬

‫ فلنرى الشفرة المصدر ية التي تلخّ ص كتابة نص بطر يقة‬،SDL_ttf‫ل ما يجب أن تتم معرفته بخصوص الـ‬
ّ ‫أنت تعرف الآن ك‬
: Blended‫الـ‬

1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <SDL/SDL.h>
4 #include <SDL/SDL_image.h>
5 #include <SDL/SDL_ttf.h>
6 int main(int argc, char �argv[])
7 {
8 SDL_Surface �screen = NULL, �text = NULL, �wallpaper = NULL;
9 SDL_Rect position;
10 SDL_Event event;
11 TTF_Font �font = NULL;
12 SDL_Color blackColor = {0, 0, 0};
13 int cont = 1;
14 SDL_Init(SDL_INIT_VIDEO);
15 TTF_Init();
16 screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF);
17 SDL_WM_SetCaption(”Gestion du texte avec SDL_ttf”, NULL);
18 wallpaper = IMG_Load(”moraira.jpg”);
19 // Loading the font
20 font = TTF_OpenFont(”angelina.ttf”, 65);
21 // Writing the text on the surface with blended mode (the optimal one)
22 text = TTF_RenderText_Blended(font, ”Salut les Zér0s !”, blackColor);
23 while (cont)
24 {
25 SDL_WaitEvent(&event);
26 switch(event.type)
27 {
28 case SDL_QUIT:
29 cont = 0;
30 break;
31 }
32 SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 255, 255,
255));
33 position.x = 0;
34 position.y = 0;
35 SDL_BlitSurface(wallpaper, NULL, screen, &position); //
Blitting the wallpaper
36 position.x = 60;
37 position.y = 370;
38 SDL_BlitSurface(text, NULL, screen, &position); // Blitting the
text
39 SDL_Flip(screen);
40 }
41 TTF_CloseFont(font);
42 TTF_Quit();

416
‫‪ .3.26‬الطرق المختلفة للكتابة‬

‫‪43‬‬ ‫;)‪SDL_FreeSurface(text‬‬
‫‪44‬‬ ‫;)(‪SDL_Quit‬‬
‫‪45‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪46‬‬

‫النتيجة تمثّلُها الصورة التالية ‪:‬‬

‫إذا أردت تغيير طر يقة الكتابة للتجريب‪ ،‬لا يوجد سوى سطر للتعديل ‪ :‬السطر الخاص بإنشاء المساحة )استدعاء الدالة‬
‫‪.( TTF_RenderText_Blended‬‬

‫!‬
‫تأخذ الدالة ‪ TTF_RenderText_Shaded‬معاملا رابعا على عكس الأخرتين‪ .‬هذا المعامل الأخير هو لون‬
‫الخلفية الذي نريد استعماله‪ .‬يجب عليك إذا إنشاء متغير من نوع ‪ SDL_Color‬للإشارة إلى لون الخلفية )مثلا‬
‫أبيض(‪.‬‬

‫خصائص كتابة نص‬

‫يمكن أيضا ً تحديد خصائص الخط‪ ،‬كـغليظ مثلاً‪ ،‬مائل و مسطّر‪.‬‬

‫يجب أولا ّ أن يتم تحميل الخط و لهذا يجب أن يتوفر لديك متغير ‪ font‬صحيح‪ .‬و يمكنك إذا استدعاء الدالة‬
‫‪ TTF_SetFontStyle‬التي ستقوم بالتعديل على الخط لـكي يكون غليظا‪ ،‬مائلا أو مسطّرا حسب الرغبة‪ .‬الدالة تأخذ‬
‫معاملين ‪:‬‬

‫• الخط الذي نريد تعديله‪.‬‬

‫• دمج أعلام للإشارة إلى نمط الكتابة الذي نريد إعطاءه ‪ :‬غليظ‪ ،‬مائل أو مسطّر‪.‬‬

‫بالنسبة للأعلام‪ ،‬يجب عليك استعمال الثوابت التالية ‪:‬‬

‫‪417‬‬
‫‪SDL_ttf‬‬ ‫الفصل ‪ .26‬كتابة نصوص باستعمال‬

‫• ‪ : TTF_STYLE_NORMAL‬عادي‪.‬‬

‫• ‪ : TTF_STYLE_BOLD‬غليظ‪.‬‬
‫• ‪ : TTF_STYLE_ITALIC‬مائل‪.‬‬

‫• ‪ : TTF_STYLE_UNDERLINE‬مسطّر‪.‬‬

‫بما أنها قائمة من الاعلام‪ ،‬يمكنك الدمج بينها باستعمال الإشارة | كما تعلّمنا القيام بذلك سابقاً‪.‬‬

‫فلنجر ّب ‪:‬‬

‫‪1‬‬ ‫‪// Loading the font‬‬


‫‪2‬‬ ‫;)‪font = TTF_OpenFont(”angelina.ttf”, 65‬‬
‫‪3‬‬ ‫‪// The text will be italic and underlined‬‬
‫‪4‬‬ ‫;)‪TTF_SetFontStyle(font, TTF_STYLE_ITALIC | TTF_STYLE_UNDERLINE‬‬
‫‪5‬‬ ‫‪// Writing the text in italic and underlined modes‬‬
‫‪6‬‬ ‫;)‪text = TTF_RenderText_Blended(font, ”Salut les Zér0s !”, blackColor‬‬

‫النتيجة ‪ :‬النص مكتوب بخاصية مائل و مسطّر ‪:‬‬

‫‪TTF_STYLE_NORMAL‬‬ ‫لإرجاع خط ما إلى حالته العادي ّة‪ ،‬يكفي أن نعيد استدعاء الدالة ‪ TTF_SetFontStyle‬باستعمال العلم‬
‫هذه المر ّة‪.‬‬

‫تمرين ‪ :‬العداد‬

‫سيجمع هذا التمرين بين المفاهيم التي تعلّمتها في هذا الفصل و فصل التحكّم في الوقت‪ .‬مهمّتك‪ ،‬إن قبلتها‪ ،‬هي إنشاء عداد‬
‫ل أعشار الثانية‪ ،‬أي أنه سي ُظهر بشكل تقدّمي القيم التالية ‪ ... 400 ،300 ،200 ،100 ،0 :‬بعد ثانية‪،‬‬
‫ٺتصاعد قيمته ك ّ‬
‫يجدر بالرقم ‪ 1000‬أن يظهر‪.‬‬

‫‪418‬‬
‫‪ .3.26‬الطرق المختلفة للكتابة‬

‫طر يقة للكتابة في سلسلة محارف‬

‫ل هذا التمرين‪ ،‬ستحتاج إلى معرفة كيفية الكتابة داخل سلسلة محارف في الذاكرة‪.‬‬
‫لـكي تح ّ‬
‫في الواقع يجب عليك أن تعطي للدالة ‪ TTF_RenderText‬متغيرا من نوع *‪ char‬لـكن ماهو متوف ّر لديك هو عدد‬
‫)من نوع ‪ int‬مثلا(‪ .‬كيف يمكننا تحو يل عدد إلى سلسلة محارف ؟‬

‫يمكننا أن نستعمل من أجل هذا الدالة ‪. sprintf‬‬


‫إنها تعمل بنفس الطر يقة التي تعمل بها ‪ ، fprintf‬الاختلاف الوحيد هو أنه في عوض الكتابة في ملف‪ ،‬ستتم الكتابة‬
‫في سلسلة محارف )الحرف ‪ s‬يختصر الكلمة ‪ string‬و التي تعني ”سلسلة محارف” بالإنجليز ي ّة(‪.‬‬
‫أوّل معامل تقدّمه سيكون إذا مؤش ّرا نحو جدول من ‪. char‬‬

‫‪x‬‬
‫كاف من أجل جدول ‪ char‬إذا أردت ألا تتجاوز في الذاكرة !‬
‫ٍ‬ ‫قم بحجز مكان‬

‫مثال ‪:‬‬

‫;)‪1 sprintf(time, ”Temps : %d”, counter‬‬

‫هنا‪ ،‬المتغير ‪ time‬هو جدول محارف )‪ 20‬محرفا(‪ ،‬و ‪ counter‬هو متغير من نوع ‪ int‬يحوي الزمن‪.‬‬
‫بعد هذه التعليمة‪ ،‬سلسلة المحارف ‪ time‬ستحتوي مثلا على ”‪.”Temps : 500‬‬

‫هي ّا‪ ،‬حان وقتُ العمل !‬

‫التصحيح‬

‫هذا تصحيح ممكن للتمرين ‪:‬‬

‫)][‪1 int main(int argc, char �argv‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;‪SDL_Surface �screen = NULL, �text = NULL‬‬
‫‪4‬‬ ‫;‪SDL_Rect position‬‬
‫‪5‬‬ ‫;‪SDL_Event event‬‬
‫‪6‬‬ ‫;‪TTF_Font �font = NULL‬‬
‫‪7‬‬ ‫;}‪SDL_Color blackColor = {0, 0, 0}, whiteColor = {255, 255, 255‬‬
‫‪8‬‬ ‫;‪int cont = 1‬‬
‫‪9‬‬ ‫;‪int currentTime = 0, previousTime = 0, counter = 0‬‬
‫‪10‬‬ ‫‪char time[20] = ””; // A table of char big enough‬‬
‫‪11‬‬ ‫;)‪SDL_Init(SDL_INIT_VIDEO‬‬
‫‪12‬‬ ‫;)(‪TTF_Init‬‬
‫‪13‬‬ ‫;)‪screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF‬‬
‫‪14‬‬ ‫;)‪SDL_WM_SetCaption(”Gestion du texte avec SDL_ttf”, NULL‬‬
‫‪15‬‬ ‫‪// Loading the police‬‬
‫‪16‬‬ ‫;)‪font = TTF_OpenFont(”angelina.ttf”, 65‬‬
‫‪17‬‬ ‫‪// Time and text initialization‬‬

‫‪419‬‬
SDL_ttf ‫ كتابة نصوص باستعمال‬.26 ‫الفصل‬

18 currentTime = SDL_GetTicks();
19 sprintf(time, ”Temps : %d”, counter);
20 text = TTF_RenderText_Shaded(font, time, blackColor, whiteColor);
21 while (cont)
22 {
23 SDL_PollEvent(&event);
24 switch(event.type)
25 {
26 case SDL_QUIT:
27 cont = 0;
28 break;
29 }
30 SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 255, 255,
255));
31 currentTime = SDL_GetTicks();
32 if (currentTime − previousTime >= 100) // If 100ms at least
have passed
33 {
34 counter += 100; // We add 100ms to the counter
35 sprintf(time, ”Temps : %d”, counter); // We write in
the string ”time” the new time
36 SDL_FreeSurface(text);// We delete the previous surface
37 text = TTF_RenderText_Shaded(font, time, blackColor,
whiteColor); // We write the sring ”time” in
SDL_Surface
38 previousTime = currentTime; // We update the previous
time
39 }
40 position.x = 180;
41 position.y = 210;
42 SDL_BlitSurface(text, NULL, screen, &position); // Blitting the
text
43 SDL_Flip(screen);
44 }
45 TTF_CloseFont(font);
46 TTF_Quit();
47 SDL_FreeSurface(text);
48 SDL_Quit();
49 return EXIT_SUCCESS;
50 }

: ‫ ثانية بالتحديد‬13,9 ‫الصورة التالية تمث ّل النتيجة في غضون‬

420
‫ملخّ ص‬

‫لا تتردد في تنز يل المشروع إذا أردت دراسته بالتفصيل و تحسينه‪ .‬هو ليس مثاليا ً بعد ‪ :‬يمكننا مثلا ً استعمال‬
‫‪ SDL_Delay‬لتجن ّب استعمال المعالج بنسبة ‪.100%‬‬

‫)‪https://openclassrooms.com/uploads/fr/ftp/mateo21/ttf_exercice_temps.zip (437 Ko‬‬

‫للذهاب بعيدا‬

‫إذا أردت التقدّم و تحسين هذا البرنامج‪ ،‬يمكنك أن تحاول صنع لعبة أين يجب النقر بالفأرة العدد الأقصى من المرات‬
‫ل نقرة‪.‬‬
‫الممكنة في النافذة في وقت محدود حيث تتزايد قيمة العداد بعد ك ّ‬

‫يجب أن يتم إظهار عداد عكسي‪ .‬حينما يصل إلى الصفر‪ ،‬نظهر عدد النقرات التي تم القيام بها و نطلب من المستعمل‬
‫ما إن كان يريد إعادة المحاولة‪.‬‬

‫يمكنك أيضا ًمعالجة أفضل النتائج و تسجيلها في ملف‪ .‬هذا سيساعدك في التدرب من جديد على استخدام الملفات في‬
‫الـ‪.C‬‬

‫حظا ًموفقا !‬

‫ملخّ ص‬

‫• لا يمكننا أن نكتب نصا ًفي الـ‪ ،SDL‬إلا إن استعملنا تمديدا ً كالمكتبة ‪.SDL_ttf‬‬

‫• تسمح هذه المكتبة بتحميل ملفات خطوط ذات صيغة ‪ .ttf‬بالاستعانة بالدالة ‪. TTF_OpenFont‬‬

‫• توجد ثلاث طرق لكتابة نص‪ ،‬ترتيبها من الأبسط إلى الأكثر تعقيدا ‪ ،Solid :‬ثم ‪ Shaded‬ثم ‪.Blended‬‬

‫• يمكننا الكتابة في ‪ SDL_Surface‬عن طر يق دوال مثل ‪. TTF_RenderText_Blended‬‬

‫‪421‬‬
‫‪SDL_ttf‬‬ ‫الفصل ‪ .26‬كتابة نصوص باستعمال‬

‫‪422‬‬
‫الفصل ‪27‬‬

‫بـ‪FMOD‬‬ ‫تشغيل الصوت‬

‫منذ أن اكتشفنا الـ‪ ،SDL‬تعلّمنا موضعة صور على النافذة‪ ،‬التفاعل مع المُستعمل بالفأرة و لوحة المفاتيح‪ ،‬كتابة نصوص‪،‬‬
‫لـكن ينقص أمر بالتأكيد ‪ :‬الصوت !‬

‫سيس ّد هذا الفصل ذلك النقص‪ .‬بما أن الإمكاني ّات التي توف ّرها لنا الـ‪ SDL‬من ناحية الصوت محدودة جداً‪ ،‬سنكتشف‬
‫هنا مكتبة متخصصة في الصوت ‪.FMOD :‬‬

‫‪FMOD‬‬ ‫ٺثبيت‬ ‫‪1.27‬‬

‫لماذا ‪ FMOD‬؟‬

‫أنت تعرف ذلك الآن ‪ :‬الـ‪ SDL‬ليست فقط مكتبة رسومية‪ .‬هي تسمح أيضا ً بمعالجة الصوت عن طر يق وحدة تسمّى‬
‫‪ .SDL_audio‬فلماذا إذا ً سنحض ّر مكتبة خارجية لا علاقة لها بالـ‪ SDL‬كـ‪ FMOD‬؟‬

‫في الواقع هو اختيار قمتُ به بعد عدّة اختبارات‪ .‬كان بإمكاني أن أشرح لك طر يقة معالجة الصوت بالـ‪ SDL‬لـكن ّي‬
‫فضّ لت عدم فعل ذلك‪.‬‬
‫سأشرح موقفي أكثر‪.‬‬

‫لماذا قمتُ بتجن ّب ‪ SDL_audio‬؟‬

‫يعتبر التحكم في الصوت بالـ‪” SDL‬منخفض المستوى”‪ .‬هذا يعني أنه يجب القيام بالعديد من التعام ُلات الدقيقة كي نستطيع‬
‫تشغيل الصوت‪ .‬بمعنى آخر‪ ،‬سيكون الأمر صعبا ًو لا أجد ذلك ممتعاً‪ .‬توجد مكتبات أخرى تسمح بتشغيل الصوت بشكل‬
‫بسيط‪.‬‬

‫‪423‬‬
‫بـ‪FMOD‬‬ ‫الفصل ‪ .27‬تشغيل الصوت‬

‫م‬
‫تذكير بسيط ‪ :‬مكتبة ”منخفضة المستوى” هي مكتبة قريبة من الحاسوب‪ .‬يجب أن نتعر ّف إذا على قليل من‬
‫العمل الداخلي للحاسوب كي نستفيد منها و يتطلب الأمر في الواقع وقتا ًأكثر من الوقت اللازم للقيام بنفس الشيء‬
‫مع مكتبة ”عالية المستوى”‪.‬‬
‫ل شيء نسبيّ ‪ :‬لا توجد مكتبات منخفضة المستوى من جهة و أخرى عالية المستوى من جهة‬
‫نك ّ‬
‫لا تنس أ ّ‬
‫أخرى‪ .‬هي فقط أكثر أو أقل من بعضها البعض في المستوى‪ .‬مثلا‪ ،‬المكتبة ‪ FMOD‬عالية المستوى مقارنة بالوحدة‬
‫‪ SDL_audio‬من الـ‪.SDL‬‬

‫تفصيل آخر مهم‪ ،‬تسمح الـ‪ SDL‬بتشغيل صوت بصيغة ‪ WAV‬فقط‪ .‬صيغة الصوت هذه ليست مضغوطة‪ .‬أي أن‬
‫موسيقى من ‪ 3‬دقائق تأخذ عشرات الميغا أوكتي‪ ،‬على عكس الصيغ المضغوطة مثل ‪ MP3‬أو ‪ Ogg‬التي تحجز حجم ذاكرة‬
‫ل بكثير )من ‪ 2‬إلى ‪ 3‬ميغا أوكتي(‪.‬‬
‫أق ّ‬

‫في الواقع‪ ،‬لو نفك ّر في الأمر جي ًداً‪ ،‬كان الأمر مشابها ً بالنسبة للصور‪ ،‬فالـ‪ SDL‬لا ٺتعامل إلا مع الصيغة ‪) BMP‬صُور‬
‫غير مضغوطة( بشكل مبدئي‪ .‬مما استوجب علينا تسطيب مكتبة إضافية و هي ‪ SDL_image‬لنتمكّن من قراءة صيغ الصور‬
‫الأخرى كـ‪ ،GIF ،PNG ،JPEG‬إلخ‪.‬‬

‫اعلم أنه هناك مكتبة مكافئة بالنسبة للصوت و هي ‪ .SDL_mixer :‬هي قادرة على قراءة عدد كبير من صيغ الصوت‪،‬‬
‫من بينها ‪ ... Midi ،Ogg ،MP3‬و رغم ذلك‪ ،‬لم أكل ّمك عن هذه المكتبة‪ .‬لماذا ؟‬

‫لماذا قمتُ بتجن ّب ‪ SDL_mixer‬؟‬

‫‪ SDL_mixer‬هي مكتبة نضيفها للـ‪ ،SDL‬بطر يقة ‪ .SDL_image‬هي سهلة للاستعمال و تقرأ العديد من صيغ الصوت‬
‫المختلفة‪ .‬لـكن‪ ،‬و بعد الاختبارات التي قمتُ بها‪ ،‬تبېّن لي أن هذه المكتبة تحتوي ع ِلَلا مزعجة بالإضافة إلى كونها محدودة‬
‫من ناحية المزايا التي تمنحها‪.‬‬

‫من أجل هذه الأسباب توجّهت مباشرة إلى ‪ ،FMOD‬مكتبة لا علاقة لها بالـ‪ SDL‬بالتأكيد‪ ،‬لـكن لها الأفضلية كونها‬
‫قو ية و متداولا عليها‪.‬‬

‫‪FMOD‬‬ ‫تنز يل‬

‫إن كنت قد حكيت لك كل هذا‪ ،‬فهذا فقط لأخبرك بأن اختيار ‪ FMOD‬لم يكن عشوائياً‪ .‬ببساطة هي أفضل مكتبة‬
‫مجانية استطعت إ يجادها‪.‬‬
‫كما أنها سهلة الاستخدام كـ‪ SDL_mixer‬بأفضلية لا يمكن تجاهلها ‪ :‬لا توجد بها ع ِل َل برمجية‪.‬‬

‫تسمح ‪ FMOD‬بالقيام بالعديد من الوظائف التي لا تسمح بها ‪ ،SDL_mixer‬كالتأثيرات الصوتي ّة ثلاثية الأبعاد‪.‬‬

‫‪424‬‬
‫‪FMOD‬‬ ‫‪ .1.27‬ٺثبيت‬

‫!‬
‫‪ FMOD‬هي مكتبة مجانية لـكن ليست تحت رخصة ‪ LGPL‬على عكس الـ‪ .SDL‬هذا يعني أنه بإمكانك أن تستخدمها‬
‫مادامت لم تحقق بها برامج مدفوعة‪ .‬إذا أردت أن يكون البرنامج غير مجاني‪ ،‬يجب أن تدفع رسوما ً لمؤل ّف المكتبة‬
‫)سأتركك تطّلع على الأسعار من خلال الموقع الرسمي لـ‪.(FMOD‬‬
‫‪World of Warcraft : ،Starcraft II‬‬ ‫كثير من الألعاب التجار ية تستعمل ‪ FMOD‬و من أشهر هذه الألعاب ‪:‬‬
‫‪ ،Crysis 2 ،Cataclysm‬إلخ‪.‬‬

‫ٺتوفر العديد من نسخ ‪ ،FMOD‬و النسخة الموجهّة إلى الاستعمال في أنظمة التشغيل المألوفة )‪،Windows ،GNU/Linux‬‬
‫‪ ( ... ،Mac OS X‬تُدعى ‪.FMOD Ex Programmers API‬‬

‫نز ّل إذا نسخة ‪ FMOD Ex‬المناسبة لنظام التشغيل الخاص بك‪ .‬خذ النسخة المسمّاة ”مستقرة” )‪.(stable‬‬

‫و تأكد بشكل خاص ما إن كان لديك نظام تشغيل ‪ 32 bits‬أو ‪) 64 bits‬في ‪ ،Windows‬قم بنقر يميني على جهاز‬
‫الكمبيوتر )‪ (Computer‬ثم في قسم الخصائص )‪ (Properties‬تجد المعلومة اللازمة(‪.‬‬

‫‪http://www.fmod.org/fmod-downloads.html#FMODExProgrammersAPI‬‬

‫‪FMOD‬‬ ‫ٺثبيت‬

‫يعمل التثبيت بنفس مبدأ عمل المكتبات السابقة‪ ،‬أي مثل الـ‪.SDL‬‬

‫يجدر بالملف الذي حمّلته أن يكون ملفا ً تنفيذيا ً )في ‪ ،(Windows‬أو أن يكون أرشيفا ) ‪ .dmg‬في ‪ Mac OS X‬و‬
‫‪ .tar.gz‬في ‪.(GNU/Linux‬‬

‫‪ .1‬ثب ّت ‪ FMOD Ex‬على قرصك الصلب‪ .‬الملفات التي نحتاجها يجب أن ٺتواجد في مجلّد يشبه هذا ‪:‬‬
‫‪. C:\Program Files\FMOD SoundSystem\FMOD Programmers API Win32\api‬‬

‫الـ‪DLL‬‬ ‫خاص بـ‪ ( fmodex.dll ) FMOD Ex‬و يجب أن يوضع في مجلّد المشروع‪.‬‬
‫‪ .2‬في هذا المجلّد تجد الـ‪ DLL‬ال ّ‬
‫الأخرى‪ ،‬أي ‪ fmodexL.dll‬تعمل على تنقيح العلل البرمجية‪ .‬لن نقوم بذلك هنا‪ .‬تذك ّر فقط بأن الملف‬
‫‪ fmodex.dll‬هو الذي يجب أن تُعطيه مع الملف التنفيذي للبرنامج‪.‬‬

‫‪ .3‬في المجلّد ‪ ، api/inc‬تجد الملفات ‪ . .h‬ضعها كل ّها إلى جانب الملفات الرأسية التي هي في مجلّد البيئة التطوير ية‪.‬‬
‫مثلا ‪) Code Blocks/mingw32/include/fmodex :‬لقد أنشأت مجلّدا خصيصا ً لأجل ‪ FMOD‬كما مثل‬
‫الـ‪.(SDL‬‬

‫‪ .4‬في المجلّد ‪ ، api/lib‬استرجع الملف الموافق للمترجم‪ .‬يجدر بملف نص ّي أن يشير إلى أي ملف يجب أن نأخذ‪.‬‬

‫‪lib‬‬ ‫• إذا كنت تستعمل ‪ ،Code::Blocks‬فالمترجم هو ‪ .mingw‬أنسخ الملف ‪ libfmodex.a‬في المجلّد‬


‫للبيئة التطوير ية‪.‬‬
‫في ‪ ،Code::Blocks‬إنه المجلّد ‪. CodeBlocks/mingw32/lib‬‬
‫• إذا كنت تستعمل ‪ ،Visual C++‬استرجع الملف ‪. fmodex_vc.lib‬‬

‫‪425‬‬
‫بـ‪FMOD‬‬ ‫الفصل ‪ .27‬تشغيل الصوت‬

‫‪ .5‬أخيراً‪ ،‬الشيء الأكثر أهمية ربّما‪ ،‬يوجد مجلّد ‪ documentation‬في المجلّد ‪ .FMOD Ex‬من المفروض أن تتم إضافة‬
‫ل ميزات‬
‫اختصارات إلى قائمة ”إبدأ” نحو هذه الملفات التوجيهية‪ .‬أبق نظرك عليها لأنه لا يمكننا أن نكتشف ك ّ‬
‫‪ FMOD Ex‬في هذا الفصل‪ .‬ستحتاج إلى هذه الملفات في أقرب الآجال بالتأكيد‪.‬‬

‫ل مرة ‪ :‬افتح المشروع بواسطة البيئة التطوير ية المفضّ لة و أضف الملف‬


‫يبقى أن نخصص المشروع‪ .‬هنا أيضا ًو مثل ك ّ‬
‫‪) .a‬أو ‪ ( .lib‬إلى قائمة الملفات التي يجب أن يسترجعها محرر الروابط‪.‬‬
‫في ‪) Code::Blocks‬يخالجني شعور بأنني أقوم بالتكرار(‪ ،‬إذهب إلى قائمة ‪ Build Options / Project‬ثم‬
‫”‪Keep as‬‬ ‫قسم ‪ ، Linker‬أنقر على ‪ Add‬و أشر إلى المسار الذي يوجد به الملف ‪ .a‬إذا ظهرت لك الرسالة ‪:‬‬
‫? ‪ ،”a relative path‬أنصحك بأن تجيب بالسلب لـكن يجدر بالأمور أن تشتغل في كلتا الحالتين‪.‬‬

‫تم ٺثبيت ‪ ،FMOD Ex‬فلنَر َى بسرعة مما هي مُشَ َ ّ‬


‫كل َة‪.‬‬

‫تهيئة و تحرير غرض نظامي‬ ‫‪2.27‬‬

‫المكتبة ‪ FMOD Ex‬متوف ّرة من أجل اللغتين ‪ C‬و ‪.C++‬‬


‫الشيء الخاص فيها هو أن مطو ّري هذه المكتبة احتفظوا ببعض التناسق في ”تركيب الكلمات” )‪ (Syntax‬بين اللغتين‪.‬‬
‫الميزة الأولى هي أنه إذا تعلّمت التعامل مع ‪ FMOD Ex‬في لغة الـ‪ C‬ستتمكن من فعل ذلك في الـ‪ C++‬بنسبة ‪.95%‬‬

‫تضمين الملف الرأسي‬

‫ل شيء‪ ،‬يلزمك أن تقوم بتضمين الملف الرأسي الخاص بـ‪ .FMOD‬لابأس في التذكير بكتابته ‪:‬‬
‫قبل ك ّ‬

‫>‪1 #include <fmodex/fmod.h‬‬

‫لقد وضعت هذا الملف في المجلّد الداخلي ‪ . fmodex‬عدّل على هذا السطر من الشفرة على حسب المسار الذي يتواجد‬
‫به الملف عندك‪.‬‬
‫إذا كنت تعمل على ‪ ،GNU/Linux‬يجدر بالتسطيب أن يتم تلقائي ّا في المجلّد ‪. fmodex‬‬

‫إنشاء و تهيئة غرض نظامي‬

‫الغرض النظامي هو عبارة عن متغير نستفيد منه على طول البرنامج لـكي نعر ّف معاملات المكتبة‪.‬‬
‫تذك ّر أنه بالـ‪ SDL‬مثلاً‪ ،‬كان يجب أن نهي ّئ المكتبة بشكل مباشر بواسطة دالة‪ .‬هنا‪ ،‬دليل الاستعمال مختلف قليلا ً ‪ :‬في‬
‫ل المكتبة‪ ،‬لن نعمل إلا بغرض )‪ (Object‬دوره تعر يف سلوك هذه الأخيرة‪.‬‬
‫عوض تهيئة ك ّ‬

‫لـكي ننشئ غرضا نظاميا‪ ،‬يكفي أن نعر ّف مؤش ّرا من نوع ‪ . FMOD_SYSTEM‬مثلا ‪:‬‬

‫;‪1 FMOD_SYSTEM �system‬‬

‫لـكي نحجز مكانا ًفي الذاكرة من أجل هذا الغرض النظامي‪ ،‬نستعمل الدالة ‪ FMOD_System_Create‬و التي نموذجها‬
‫هو الآتي ‪:‬‬

‫‪426‬‬
‫‪ .2.27‬تهيئة و تحرير غرض نظامي‬

‫;)‪1 FMOD_RESULT FMOD_System_Create(FMOD_SYSTEM �� system‬‬

‫لاحظ أن هذه الدالة تأخذ مؤش ّرا نحو مؤش ّر يؤش ّر نحو ‪ . FMOD_SYSTEM‬القر ّاء الأكثر حرصا ًكانوا قد لاحظوا أنه‬
‫لدى تعر يف المؤش ّر ‪ ، FMOD_SYSTEM‬لم يتم حجزه بواسطة ‪ malloc‬أو أي دالة أخرى‪ .‬لهذا السبب تماما ًتأخذ الدالة‬
‫‪ FMOD_SYSTEM‬معاملا من ذلك النوع لـكي تحجز مكانا ًللمؤش ّر النظامي‪.‬‬

‫بعد تعر يف الغرض النظامي ‪ ،‬تكفي كتابة ‪:‬‬

‫;‪1 FMOD_SYSTEM �system‬‬


‫;)‪2 FMOD_System_Create(&system‬‬

‫‪FMOD_System_Init‬‬ ‫هكذا إذا‪ ،‬بما أننا نتوف ّر الآن على الغرض النظامي‪ ،‬لم يتب ّق علينا سوى تهيئته‪ .‬لفعل هذا‪ ،‬نستعمل الدالة‬
‫ذات النموذج ‪:‬‬

‫(‪1 FMOD_RESULT FMOD_System_Init‬‬


‫‪2‬‬ ‫‪FMOD_SYSTEM � system,‬‬
‫‪3‬‬ ‫‪int maxchannels,‬‬
‫‪4‬‬ ‫‪FMOD_INITFLAGS flags,‬‬
‫‪5‬‬ ‫‪void � extradriverdata‬‬
‫;) ‪6‬‬

‫• المعامل ‪ system‬هو المعامل الذي يهمّنا أكثر‪ ،‬لأنه المؤش ّر الذي سنقوم بتهيئته‪.‬‬

‫• المعامل ‪ maxchannels‬يمث ّل العدد الأقصى للقنوات التي يجب أن تديرها ‪ . FMOD‬بمعنى آخر‪ ،‬هو العدد الأقصى‬
‫للأصوات التي يمكن أن يتم تشغيلها في نفس الوقت‪ .‬هذا يعتمد على قوة بطاقة الصوت لديك ‪ .‬ننصح عادة بقيمة‬
‫‪) 32‬قيمة كافية من أجل معظم الألعاب البسيطة(‪ .‬لمعلوماتك‪ ،‬يمكن نظر يا ًلـ ‪ FMOD‬إدارة ‪ 1024‬قناة مختلفة‪،‬‬
‫لـكن بهذا المستوى ستخاطر بجعل حاسوبك يشتغل كثيرا !‬

‫• المعامل ‪ flag‬لا يهمّنا كثيرا ً في هذا الفصل‪ ،‬سنكتفي بإعطائه القيمة ‪. FMOD_INIT_NORMAL‬‬

‫• المعامل ‪ extradriverdata‬لا يهمّنا أيضاً‪ ،‬سنعطيه القيمة ‪. NULL‬‬

‫مثلا‪ ،‬لـكي نعر ّف‪ ،‬نحجز‪ ،‬و نهي ّئ غرضا نظاميا‪ ،‬نقوم بكتابة التالي ‪:‬‬

‫;‪1 FMOD_SYSTEM �system‬‬


‫;)‪2 FMOD_System_Create(&system‬‬
‫;)‪3 FMOD_System_Init(system, 2, FMOD_INIT_NORMAL, NULL‬‬

‫نتوف ّر الآن على غرض نظامي جاهز للإستعمال‪.‬‬

‫‪427‬‬
‫بـ‪FMOD‬‬ ‫الفصل ‪ .27‬تشغيل الصوت‬

‫غلق و تحرير غرض نظامي‬

‫نغلق ثم ّ نحرر الغرض النظامي بواسطة دالتين ‪:‬‬

‫;)‪1 FMOD_System_Close(system‬‬
‫;)‪2 FMOD_System_Release(system‬‬

‫هل يجدر بي أن أعل ّق على هذه الشفرة ؟‬

‫الأصوات القصيرة‬ ‫‪3.27‬‬

‫فلنبدأ بدراسة الأصوات قصيرة المدّة‪.‬‬


‫ن )أحيانا ًأقل من ثانية( و غالبا ًما يُوجه للاستعمال المنتظم‪.‬‬
‫”الصوت القصير” كما أسم ّيه‪ ،‬هو صوت يستمر ّ غالبا ًبضعة ثوا ٍ‬

‫أمثلة عن أصوات قصيرة ‪:‬‬

‫• صوت إطلاق رصاصة‪.‬‬


‫• صوت مشي اللاعب‪.‬‬

‫• صوت ‪) tic-tac‬لـكيّ نوت ّر اللاعب قبل انتهاء العد العكسي(‪.‬‬

‫• صوت التصفيق‪.‬‬
‫• إلخ‪.‬‬

‫باختصار‪ ،‬كل صوت لا يعتبر موسيقى‪.‬‬


‫بشكل عام‪ ،‬هذه الأصوات قصيرة المدة إلى درجة أنه لا نحتاج إلى ضغطها‪ .‬نجدها إذا في غالب الأحيان بصيغة ‪WAV‬‬
‫غير مضغوطة‪.‬‬

‫إ يجاد الأصوات القصيرة‬

‫قبل أن نبدأ‪ ،‬سيكون من الجيد أن نتعر ّف على بعض المواقع التي تقترح بنوكا ًمن الأصوات‪ .‬بالفعل‪ ،‬لا أحد يريد أن يبدأ‬
‫في تسجيل الأصوات بنفسه في المنزل‪.‬‬

‫سيكون الأمر جيدا ً فالأنترنت تقترح أصواتا قصيرة‪ ،‬غالبا ًبصيغة ‪.WAV‬‬
‫أين نجدها ؟ قد يبدو الأمر سخيفاً‪ ،‬لا يجب علينا أن نفك ّر في ذلك )مع أنه لازم(‪ ،‬لـكن ‪ Google‬صديقنا‪ .‬بشكل عشوائي‪،‬‬
‫أكتب ‪ ”Free Sounds” :‬و التي تعني ”أصوات مجانية” بالإنجليز ي ّة‪ ،‬ستظهر لي ملايين النتائج‪.‬‬

‫لا شيء تحتاجه أكثر من الصفحة الأولى للبحث‪ ،‬ستجد ضالّتك هناك‪.‬‬
‫شخصيا ً حفظت الموقع ‪ ،FindSounds.com‬محر ّك بحث متخصص في الأصوات‪ .‬لا أدري إن كان الأفضل‪ ،‬لـكن على‬
‫أي حال هو موقع كامل‪.‬‬

‫‪428‬‬
‫‪ .3.27‬الأصوات القصيرة‬

‫م‬
‫إذا لم تعرف ماهي الكلمات المفتاحية التي تستعملها في البحث‪ ،‬توجّه إلى الصفحة الخاصة بأمثلة عن الكلمات‬
‫المفتاحية للبحث‪ .‬يجب عليك أن تجيد بعض الكلمات الإنجليز ية بالتأكيد )لـكن على أي حال إن كنت تريد أن‬
‫تصبح مبرمجاً‪ ،‬كيف ستفعل لو أنك لا تجيد على الأقل اللغة الإنجليز ية ؟(‪.‬‬

‫بالبحث عن كلمة ”‪ ،”gun‬سنجد أطنانا من أصوات إطلاق النار بالبندقية‪ ،‬لو نكتب ”‪ ”door‬سنجد أصوات تحر ّك‬
‫الباب )الصورة التالية(‪ ،‬إلخ‪.‬‬

‫الخطوات التي يجب إتّباعها لتشغيل الصوت‬

‫تنص على تحميل الصوت الذي نريد تشغيله في الذاكرة‪.‬‬


‫الخطوة الأولى ّ‬
‫ل الأصوات التي ترى أنك ستستعملها كثيرا ً منذ بداية البرنامج‪ .‬تقوم بتحريرها في النهاية‪ .‬في الواقع‪ ،‬ما‬
‫أنصحك بتحميل ك ّ‬
‫إن يتم تحميل الصوت في الذاكرة‪ ،‬ستكون قراءته سر يعة جداً‪.‬‬

‫المؤش ّر‬

‫الخطوة الأولى ‪ :‬إنشاء المؤش ّر من نوع ‪ FMOD_SOUND‬و الذي يمث ّل الصوت‪.‬‬


‫;‪1 FMOD_SOUND �fire = NULL‬‬

‫‪429‬‬
‫بـ‪FMOD‬‬ ‫الفصل ‪ .27‬تشغيل الصوت‬

‫تحميل الصوت‬

‫الخطوة الثانية ‪ :‬تحميل الصوت بواسطة الدالة ‪ . FMOD_System_CreateSound‬هي تأخذ ‪ ...‬خمسة معاملات ‪:‬‬

‫• الغرض النظامي الذي تحدّثنا عنه سابقاً‪.‬‬


‫بالطبع يجب أن يكون هذا الغرض جاهزا ً للاستعمال )معر ّفا‪ ،‬محجوزا ً و مهي ّئاً(‪.‬‬

‫• اسم الملف الصوتي الذي نريد تحميله‪ .‬يمكن أن يكون ذو صيغة ‪ ،OGG ،MP3 ،WAV‬إلخ‪ .‬من المستحسن دائما ًأن‬
‫ن كح ّد أقصى( على أن يتم تحميل أصوات طو يلة‪ .‬في الواقع‪ ،‬تحم ّل الدالة و تفك‬
‫يتم تحميل أصوات قصيرة )بضع ثوا ٍ‬
‫ل الصوت في الذاكرة‪ ،‬مما قد يأخذ مكانا كبيرا لو أن الصوت هو موسيقى !‬
‫تشفير ك ّ‬

‫• المعامل الثالث هو عَلم‪.‬‬

‫يهمّنا بشكل خاص هنا لأنه بفضله يمكننا أن نقول لـ‪ FMOD‬أن الصوت الذي ستشّغله هو عبارة عن صوت قصير‪.‬‬
‫من أجل هذا نستعمل القيمة ‪. FMOD_CREATESAMPLE‬‬

‫• و الرابع لا يهمّنا‪ ،‬سنعطيه القيمة ‪. NULL‬‬

‫• المعامل الأخير هو من نوع ‪ ، FMOD_SOUND ** sound‬و هذا المؤ ّشر سنستعمله لاحقا ًمن أجل تشغيل الصوت‪.‬‬
‫بشكل ما‪ ،‬يمكننا القول بأن هذا المؤش ّر سيؤش ّر على الصوت الذي نريد تشغيله‪.‬‬

‫هذا مثال عن تحميل ‪:‬‬


‫;)‪1 FMOD_System_CreateSound(system, ”pan.wav”, FMOD_CREATESAMPLE, 0, &fire‬‬

‫هنا‪ ،‬أقوم بتحميل الصوت ‪ . pan.wav‬المؤش ّر ‪ fire‬سيعتبر كمرجع للصوت لاحقاً‪.‬‬

‫م‬
‫‪pan.wav‬‬ ‫إذا كنت تريد أن تختبر الشفرات في نفس الوقت الذي أعطيها لك‪ ،‬أنصحك بتنز يل الصوت‬
‫)‪(http://www.siteduzero.com/uploads/fr/ftp/mateo21/pan.wav‬‬
‫و الذي سأستعمله أيضا ًفي بقي ّة هذا الفصل‪.‬‬

‫ل شيء على ما ي ُرام‪ ،‬ت ُرجع الدالة القيمة ‪ FMOD_OK‬و إلا‪ ،‬فهذا يعني أنه حدث مشكل خلال فتح‬
‫إذا اشتغل ك ّ‬
‫الملف الصوتي )ملف تالف أو غير موجود مثلا(‪.‬‬

‫تشغيل الصوت‬

‫تريد تشغيل الصوت ؟ لا يوجد مشكل مع الدالة ‪! FMOD_System_PlaySound‬‬


‫يكفي أن تعطيها غرضا نظاميا جاهزا للاستعمال‪ ،‬رقم القناة التي نريد أن يُلعب فيها الصوت و أيضا ً المؤش ّر نحو الصوت‪،‬‬
‫إضافة إلى معاملات أخرى لا تهمّنا سنعطيها القيمة ‪ NULL‬أو ‪ .0‬بالنسبة لرقم القناة‪ ،‬لا تشغل بالك بالتفكير و ابعث‬
‫القيمة ‪ FMOD_CHANNEL_FREE‬و اترك ‪ FMOD‬تتحكّم في ذلك‪.‬‬
‫;)‪1 FMOD_System_PlaySound(system, FMOD_CHANNEL_FREE, fire, 0, NULL‬‬

‫‪430‬‬
‫‪ .3.27‬الأصوات القصيرة‬

‫تحرير الصوت من الذاكرة‬

‫حتاج للصوت‪ ،‬يجب عليك تحريره‪.‬‬


‫حينما تصبح غير م ٍ‬
‫لا يوجد ما هو أسهل‪ ،‬يكفي أن تشير إلى المؤش ّر الذي تريد تحريره بواسطة الدالة ‪. FMOD_Sound_Release‬‬

‫مثال ‪ :‬لعبة إطلاق النار‬

‫الأفضل الآن هو أن نلخّ ص كل ما تعلّمناه‪ ،‬عن طر يق مثال واضح عن برنامج مكتوب بالـ‪.SDL‬‬
‫لا يوجد شيء معقّد هنا و يجدر ألا تصادفك أية مشكلة في تحقيق هذا التمرين‪.‬‬

‫الموضوع‬

‫مهمتك سهلة ‪ :‬إنشاء لعبة إطلاق النار‪.‬‬


‫حسناً‪ ،‬لن ننشئ لعبة كاملة هنا‪ ،‬لـكننا سنتحكّم في المصو ّب‪ .‬لقد صممتُ لك مصو ّبا ًبسيطا ًبواسطة برنامج الرسام ‪:‬‬

‫باختصار‪ ،‬إليك المهام ‪:‬‬

‫• خلفية النافذة ‪ :‬سوداء‪.‬‬

‫• مؤش ّر الفأرة ‪ :‬غير مرئي‪.‬‬

‫• يتم تسو ية صورة المصو ّب على وضعية الفأرة حينما نقوم بتحر يكه‪ .‬احذر ‪ :‬يجب أن يتم لصق مركز الصورة على مستوى‬
‫مؤش ّر الفأرة‪.‬‬

‫• حينما ننقر بالفأرة‪ ،‬يجب تشغيل الصوت ‪. pan.wav‬‬

‫قد تكون هذه البداية لصنع لعبة إطلاق نار كاملة‪.‬‬


‫سهل جدا ً ؟ حسناً‪ ،‬حان وقت العمل إذا !‬

‫التصحيح‬

‫هذه هي الشفرة المصدر ية الكاملة ‪:‬‬

‫‪1‬‬ ‫>‪#include <stdlib.h‬‬


‫‪2‬‬ ‫>‪#include <stdio.h‬‬
‫‪3‬‬ ‫>‪#include <SDL/SDL.h‬‬
‫‪4‬‬ ‫>‪#include <SDL/SDL_image.h‬‬
‫‪5‬‬ ‫>‪#include <fmodex/fmod.h‬‬
‫‪6‬‬ ‫)][‪int main(int argc, char �argv‬‬

‫‪431‬‬
FMOD‫بـ‬ ‫ تشغيل الصوت‬.27 ‫الفصل‬

7 {
8 SDL_Surface �screen = NULL, �gunSight = NULL;
9 SDL_Event event;
10 SDL_Rect position;
11 int cont = 1;
12 FMOD_SYSTEM �system;
13 FMOD_SOUND �fire;
14 FMOD_RESULT result;
15 // Initializing and creating a system object
16 FMOD_System_Create(&system);
17 FMOD_System_Init(system, 1, FMOD_INIT_NORMAL, NULL);
18 // Loading the sound and checking the loading
19 result = FMOD_System_CreateSound(system, ”pan.wav”, FMOD_CREATESAMPLE,
0, &fire);
20 if (result != FMOD_OK)
21 {
22 fprintf(stderr, ”Can’t read pan.wav\n”);
23 exit(EXIT_FAILURE);
24 }
25 // Initializing the SDL
26 SDL_Init(SDL_INIT_VIDEO);
27 SDL_ShowCursor(SDL_DISABLE);
28 screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF);
29 SDL_WM_SetCaption(”Gestion du son avec FMOD”, NULL);
30 gunSight = IMG_Load(”viseur.png”);
31 while (cont)
32 {
33 SDL_WaitEvent(&event);
34 switch(event.type)
35 {
36 case SDL_QUIT:
37 cont = 0;
38 break;
39 case SDL_MOUSEBUTTONDOWN:
40 // When we click, we play the sound
41 FMOD_System_PlaySound(system, FMOD_CHANNEL_FREE , fire,
0, NULL);
42 break;
43 case SDL_MOUSEMOTION:
44 // When we move the mouse, we move the gun sight too.
To do this we have to use gunSight−>w/2, gunSight−>
h/2
45 position.x = event.motion.x − (gunSight−>w / 2);
46 position.y = event.motion.y − (gunSight−>h / 2);
47 break;
48 }
49 SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 0, 0, 0))
;
50 SDL_BlitSurface(gunSight, NULL, screen, &position);
51 SDL_Flip(screen);

432
‫‪ .3.27‬الأصوات القصيرة‬

‫‪52‬‬ ‫}‬
‫‪53‬‬ ‫‪// We close the SDL‬‬
‫‪54‬‬ ‫;)‪SDL_FreeSurface(gunSight‬‬
‫‪55‬‬ ‫;)(‪SDL_Quit‬‬
‫‪56‬‬ ‫‪// We free the sound, free and close the system object‬‬
‫‪57‬‬ ‫;)‪FMOD_Sound_Release(fire‬‬
‫‪58‬‬ ‫;)‪FMOD_System_Close(system‬‬
‫‪59‬‬ ‫;)‪FMOD_System_Release(system‬‬
‫‪60‬‬ ‫;‪return EXIT_SUCCESS‬‬
‫} ‪61‬‬

‫الصورة التالية تعطيك لمحة عن اللعبة المصغ ّرة‪ ،‬لـكن الأفضل أن ترى الفيديو بالصوت على الأنترنت ‪:‬‬

‫مشاهدة الفيديو هنا ‪:‬‬


‫‪https://openclassrooms.com/uploads/fr/ftp/mateo21/viseur.html‬‬

‫هنا‪ ،‬حمّلت ‪ FMOD‬قبل الـ‪ SDL‬و حررتها بعدها‪ .‬لا توجد قواعد من ناحية الترتيب )كان بإمكاني القيام بالعكس(‪.‬‬
‫اخترتُ تحميل الـ‪ SDL‬و فتح النافذة بعد تحميل ‪ FMOD‬لـكي تكون اللعبة جاهزة للاستعمال ما إن يتم فتح النافذة )و إلا‬
‫كان بالإمكان أن تنتظر بعض الميلي ثواني ريثما يتم تحميل ‪.(FMOD‬‬
‫و مع ذلك أنت حر باختيار الترتيب‪ ،‬هذه تفاصيل ليس إلا‪.‬‬

‫أعتقد أنني عل ّقت كفاية على الشفرة‪ .‬لا يوجد فخ معيّن‪ ،‬و لا يوجد جديد يذكر‪ .‬يمكننا أن نذكر الصعوبة ”الصغيرة”‬
‫التي تنص على لصق مركز المصو ّب على مستوى مؤش ّر الفأرة‪ .‬يتم حساب وضعية الصورة بدلالة ذلك‪.‬‬

‫بالنسبة لمن لم يفهم الفرق بعد‪ ،‬سنرى ذلك الآن‪ .‬بالمناسبة‪ ،‬لقد أعدت تفعيل الإظهار الخاص بالمؤش ّر كي نرى كيف‬
‫يتموضع المصو ّب بالنسبة لمؤش ّر الفأرة‪.‬‬

‫‪433‬‬
‫بـ‪FMOD‬‬ ‫الفصل ‪ .27‬تشغيل الصوت‬

‫شفرة خاطئة )المصوب في وضعية خاطئة(‬


‫;‪position.x = event.motion.x‬‬
‫;‪position.y = event.motion.y‬‬

‫شفرة صحيحة )المصوب في وضعية صحيحة(‬


‫;)‪position.x = event.motion.x - (gunSight->w / 2‬‬
‫;)‪position.y = event.motion.y - (gunSight->h / 2‬‬

‫أفكار للتحسين‬

‫هذه اللعبة تعتبر قاعدة للعبة إطلاق النار‪ .‬لديك المصو ّب‪ ،‬صوت إطلاق النار‪ ،‬لم يتبقّ لك سوى إظهار أو تمرير أعداء‬
‫لـكي يقوم اللاعب بإطلاق النار عليهم ثم يتم تسجيل النتيجة‪ .‬كالعادة‪ ،‬عليك بالعمل وحدك‪ .‬تريد صنع لعبة ؟ لا شيء‬
‫يمنعك‪ ،‬بل لديك المستوى الكافي الآن‪ ،‬و لديك أيضا ًشفرة مبدئية للعبة إطلاق النار ! ماذا تنتظر‪ ،‬صدقا ً؟‬

‫م‬
‫طبعاً‪ ،‬منتديات ‪ OpenClassrooms‬ستساعدك في حال علقت في أيّ لحظة أثناء إنشائك للعبة‪ .‬مواجهة الصعوبات‬
‫أمر عادي مهما كان المستوى الذي أنت فيه‪.‬‬

‫الموسيقى )‪(OGG ،MP3 ،WAV‬‬ ‫‪4.27‬‬

‫نظر ياً‪ ،‬يسمح الع َلم ‪ FMOD_CREATESAMPLE‬بتحميل أي صوت مهما كان‪ ،‬من ضمنها الصيغ المضغوطة ‪،MP3 ،WAV‬‬
‫خص الأصوات ”الطو يلة”‪ ،‬أي الموسيقى‪.‬‬
‫‪ .OGG‬المشكل ي ّ‬

‫في الواقع‪ ،‬تدوم الموسيقى كمعدّل من ‪ 3‬إلى ‪ 4‬دقائق‪ .‬لـكن‪ ،‬بهذا العلم‪ ،‬تحم ّل الدالة‬
‫ل الملف في الذاكرة )و النسخة غير المضغوطة هي التي ستتواجد في الذاكرة‪ ،‬فهذا‬
‫‪ FMOD_System_CreateSound‬ك ّ‬
‫يعني أنها ستأخذ حي ّزا ً كبيرا ً !(‪.‬‬

‫إذا كان لديك صوت طو يل المدّة )سنتكلّم عن ”الموسيقى” من الآن و صاعداً(‪ ،‬سيكون من المستحسن أن نحمّلها‬
‫بشكل تدف ّقي )‪ ،(streaming‬يعني أن نحم ّل منها أجزاء صغيرة في نفس الوقت الذي تشغ ّل فيه‪ ،‬هذا في الواقع ما تقوم به‬
‫ل البرامج الخاصة بقراءة الأصوات‪.‬‬
‫ك ّ‬

‫إ يجاد الموسيقى‬

‫ندخل هنا إلى أرضية ملغ ّمة‪ ،‬شائكة‪ ،‬بها متفجّ رات )سم ّها كما تريد(‪.‬‬
‫في الواقع‪ ،‬أغلب الموسيقى و الأغاني التي نعرفها معني ّة بحقوق المؤل ّف‪ .‬حتى لو كتبت برنامجا ًصغيراً‪ ،‬يجب أن تدفع رسوما ً‬
‫إلى الـ‪) SACEM‬في فرنسا على الأقل(‪.‬‬

‫إذا‪ ،‬على غرار الموسيقى ‪ MP3‬المحمي ّة بحقوق المؤل ّف‪ ،‬ماذا يتبقّى لنا ؟‬
‫لحسن الحظ‪ ،‬توجد أغاني حرة من الحقوق ! يسمح أصحابها بنشر أغانيهم بشكل حرّ‪ ،‬إذا لا يوجد أي مشكل في استعمالها‬
‫في برامجك‪.‬‬

‫‪434‬‬
‫‪ .4.27‬الموسيقى )‪(OGG ،MP3 ،WAV‬‬

‫!‬
‫إذا كان برنامجك تجار يا‪ ،‬يجب أن ٺتكلّم مع الفنان نفسه‪ ،‬فهناك من لا يقبل الاستعمالات التجار ية لأغانيه‪.‬‬
‫الأغنية حرّة الحقوق يمكن تنز يلها‪ ،‬نسخها و سماعها بشكل حرّ‪ ،‬لـكن هذا لا يعني أنه بإمكاننا تحصيل المال على‬
‫حساب الفنانين !‬

‫حسنا ًالسؤال هو ‪” :‬أين نجد موسيقى حرّة ؟”‪ .‬يمكننا أن نجري بحثا ًبالعبارة ”‪ ”Free Music‬في ‪ ،Google‬لـكن هنا‪،‬‬
‫هو ليس صديقنا هذه المرة‪ .‬في الواقع‪ ،‬لنعرف لماذا‪ ،‬لقد كتبنا الكلمة ‪ Free‬لـكننا سنقع دائما على مواقع تطلب منا اشتراء‬
‫الأغاني !‬

‫لحسن الحظ‪ ،‬توجد مواقع تحتوي موسيقى حرة الحقوق‪ .‬هنا أنصحك بالموقع الجيد ‪ Jamendo‬لـكنه ليس وحده‬
‫الموجود في المجال‪https://www.jamendo.com/ .‬‬

‫يتم تقسيم الأغاني على حسب النمط‪ .‬لديك الـكثير من الخيارات‪ ،‬ستجد الجيد‪ ،‬السيء و الجيد جداً‪ ،‬و عديم الجودة‪.‬‬
‫ل يعتمد على ذوقك و تقبّلك للأنماط المختلفة من الموسيقى‪ .‬من المستحسن أن تختار موسيقى تشغ ّل في خلفية‬ ‫في الواقع‪ ،‬ك ّ‬
‫اللعبة و تكون متناسبة مع عالم اللعبة‪.‬‬

‫لمعلوماتك‪ ،‬توجد أغنية تنتمي إلى الألبوم ”‪ ”Lies and Speeches‬للمجموعة ”‪ .”Hype‬إذا أردت معرفة المزيد عنها‪،‬‬
‫زر صفحتهم على الموقع ‪http://www.myspace.com/hypemusic : My Space‬‬

‫م‬
‫متيقّن جدا ً أن الأذواق و الألوان لا مجال للنقاش فيهما‪ .‬لا بأس باختيارك لأغنية أخرى إن كانت هذه لا‬
‫تُعجبك‪.‬‬

‫لقد حمّلت إذا ً الألبوم و سأستعمل أغنية ‪ Home‬بصيغة ‪.MP3‬‬


‫يمكنك تنز يلها مباشرة إذا أردت إختبار الشفرة في نفس الوقت معي‪ .‬هذه من ميزات الموسيقى الحرة ‪ :‬يمكننا نسخها‪،‬‬
‫توز يعها بشكل حر و لهذا لن نكون منزعجين‪.‬‬

‫الخطوات الواجب اتّباعها لتشغيل الموسيقى‬

‫الاختلاف الوحيد هو الع َلم المُعطى للدالة ‪. FMOD_System_CreateSound‬‬


‫في عوض أن نعطيها الع َلم ‪ ، FMOD_CREATESAMPLE‬نعطيها الأعلام ‪ FMOD_2D ، FMOD_SOFTWARE :‬و‬
‫‪. FMOD_CREATESTREAM‬‬
‫لا تنتظر كثيرا ً أن أشرح لك بشكل موسّ ع معاني هذه الأعلام‪ ،‬الع َلم الذي يهمّنا أكثر هو ‪ FMOD_CREATESTREAM‬لأنه‬
‫يطلب من ‪ FMOD‬تحميل الموسيقى جزءا ً بجزء‪.‬‬

‫ل هذه الأعلام في نفس الوقت‪ ،‬نستعمل العامل المنطقي | بهذه الطر يقة ‪:‬‬
‫لـكي نستعمل ك ّ‬

‫| ‪1 FMOD_System_CreateSound(system, ”my_music.mp3”, FMOD_SOFTWARE | FMOD_2D‬‬


‫;)‪FMOD_CREATESTREAM, 0, &sound‬‬

‫‪435‬‬
‫بـ‪FMOD‬‬ ‫الفصل ‪ .27‬تشغيل الصوت‬

‫هكذا هو العمل !‬
‫ل شيء‪ .‬في حالة موسيقى‪ ،‬سيكون من الجيد أن نعرف كيف نغي ّر من قو ّة الصوت‪ ،‬نتحكّم في إعادة‬
‫لـكن هذا ليس ك ّ‬
‫الأغنية مرات عديدة‪ ،‬إيقافها مؤق ّتا‪ ،‬أو حتى إيقافها كلياً‪ .‬هذا النوع من الأشياء ما سنراه الآن‪ .‬لـكن قبل هذا‪ ،‬سنحتاج‬
‫أن نعمل على القنوات بشكل مباشر‪.‬‬

‫استرجاع قناة أو مجموعة من القنوات‬

‫في نسخ سابقة من المكتبة ‪ ،FMOD‬رقم الهو ي ّة البسيط لقناة يكفي لـكي يغي ّر قو ّة الصوت أو إيقاف أغنية مؤق ّتا‪.‬‬
‫طرأ تغيير صغير منذ ‪ : FMOD Ex‬من خلال رقم القناة‪ ،‬نستعمل دالة توف ّر لنا مؤش ّرا ً نحو القناة‪ .‬الفكرة تبقى نفسها‪ ،‬ٺتغي ّر‬
‫طر يقة التنفيذ فقط‪.‬‬
‫يتم تعر يف قناة كنوع ‪ FMOD_CHANNEL‬و الدالة التي تسمح باسترجاع قناة إنطلاقا ًمن رقم الهو ية هي‬
‫‪. FMOD_System_GetChannel‬‬
‫مثلاً‪ ،‬إذا كان لدي غرض نظامي و أردت استرجاع القناة رقم ‪ ،9‬يجب أن أكتب ‪:‬‬

‫;‪1 FMOD_CHANNEL �channel‬‬


‫;)‪2 FMOD_System_GetChannel(system, 9, &channel‬‬

‫لا شيء أسهل من هذا !‬

‫• المعامل الأول هو الغرض النظامي‪.‬‬

‫• المعامل الثاني هو رقم الهو ي ّة الخاص بالقناة‪.‬‬

‫• المعامل الثالث هو عنوان المؤش ّر الذي نريد تخزين المعلومة المُرادة فيه‪.‬‬

‫ما إن نتحصّ ل على مؤش ّر القناة‪ ،‬يمكننا بسهولة التعامل مع الموسيقى )تغيير قوة الصوت‪ ،‬إيقاف الموسيقى مؤق ّتا‪.( ... ،‬‬

‫لاحظ أنه يمكننا أيضا ً استرجاع مجموعة كاملة من القنوات في مؤش ّر واحد ‪ :‬بهذا نتجن ّب أن نقوم بنفس العملية من‬
‫ل قناة مختلفة‪.‬‬
‫أجل ك ّ‬
‫نوع مجموعة القنوات هو ‪ FMOD_CHANNELGROUP‬وواحدة من الدوال التي تهمّنا أكثر هي‬
‫ل القنوات المستعملة من طرف‬
‫‪ FMOD_System_GetMasterChannelGroup‬لأنها تسمح بالحصول على مؤش ّر نحو ك ّ‬
‫الغرض النظامي‪.‬‬
‫أسلوب عمل هذه الدالة مماثل لسابقتها‪.‬‬

‫تغيير قوة الصوت‬

‫ل القنوات‪.‬‬
‫لنغي ّر قوة الصوت‪ ،‬يمكننا أن نقوم بذلك إما من أجل قناة محددة أو من أجل ك ّ‬
‫ل القنوات‪ ،‬يجب أوّلا ُ استرجاع المؤش ّر نحو مجموعة القنوات‪ ،‬ثم استعمال الدالة‬‫مثلاً‪ ،‬لـكي نقوم بذلك من أجل ك ّ‬
‫‪ FMOD_ChannelGroup_SetVolume‬ذات النموذج ‪:‬‬

‫‪436‬‬
‫‪ .4.27‬الموسيقى )‪(OGG ،MP3 ،WAV‬‬

‫‪1 FMOD_ChannelGroup_SetVolume‬‬
‫(‪2 FMOD_RESULT FMOD_ChannelGroup_SetVolume‬‬
‫‪3‬‬ ‫‪FMOD_CHANNELGROUP � channelgroup, float volume‬‬
‫;) ‪4‬‬

‫المعامل ‪ channelgroup‬هو المعامل الذي نحن بصدد استرجاعه‪.‬‬


‫المعامل ‪ volume‬من نوع ‪ ، float‬حيث ‪ 0.0‬توافق المستوى الصامت و ‪ 1.0‬توافق قراءة بكامل قو ّة الصوت )هذه‬
‫القيمة هي القيمة المُختارة تلقائيا(‪.‬‬

‫إعادة تشغيل الأغنية‬

‫غالبا ً ما نحتاج إلى إعادة تشغيل الموسيقى الخلفية‪ .‬هذا تماما ما تقترحه الدالة ‪ FMOD_Sound_SetLoopCount‬و التي‬
‫تأخذ معاملين ‪:‬‬

‫• المؤش ّر نحو الأغنية‪.‬‬

‫• عدد المرات التي يجب فيها أن تتم إعادة قراءة الموسيقى‪ .‬إذا وضعت القيمة ‪ ،1‬تتم إذا قراءة الأغنية مرتين‪ .‬إذا‬
‫وضعت قيمة سالبة )مثل ‪ ،(−1‬تتم إعادة قراءة الأغنية إلى ما لانهاية‪.‬‬

‫بهذه الشفرة المصدر ية‪ ،‬يتم تكرار الأغنية إلى ما لانهاية ‪:‬‬

‫;)‪1 FMOD_Sound_SetLoopCount(music, −1‬‬

‫‪x‬‬
‫كمعامل ثالث للدالة‬ ‫‪FMOD_LOOP_NORMAL‬‬ ‫لـكي تشتغل عملية إعادة التشغيل‪ ،‬يجب أن نبعث‬
‫‪. FMOD_System_CreateSound‬‬

‫إيقاف الموسيقى مؤقتا‬

‫توجد هنا دالتان لـكي نتعلّمهما ‪:‬‬

‫• ‪ : FMOD_Channel_GetPaused‬تشير ما إذا كانت الأغنية التي يتم تشغيلها حاليا ً في القناة المختارة في حالة‬
‫متوقفة مؤقتا أم لا‪ .‬تعطي القيمة ”صحيح” للمتغير ‪ state‬إذا كانت الأغنية متوقفة مؤقتا‪ ،‬و القيمة ”خطأ” ما إن‬
‫كانت تشتغل حالياً‪.‬‬

‫• ‪ : FMOD_Channel_SetPaused‬توقف الأغنية مؤقتا أو تعيد تشغيلها في القناة المشار إليها‪ .‬أعطها القيمة ‪1‬‬
‫)صحيح( لإيقافها مؤقتا‪ .‬و ‪) 0‬خطأ( لإعادة تفعيل القراءة‪.‬‬

‫‪437‬‬
‫بـ‪FMOD‬‬ ‫الفصل ‪ .27‬تشغيل الصوت‬

‫هذه الشفرة المصدر ية الخاصة بنافذة ‪ SDL‬تقوم بإيقاف الأغنية مؤقتا إذا ضغطنا على الزر ‪ P‬من لوحة المفاتيح‪ ،‬و‬
‫تعيد تفعيلها إذا ضغطنا مجددا ً على ‪. P‬‬

‫‪1‬‬ ‫‪case SDL_KEYDOWN:‬‬


‫‪2‬‬ ‫‪if (event.key.keysym.sym == SDLK_p) // If we press P‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;‪FMOD_BOOL state‬‬
‫‪5‬‬ ‫;)‪FMOD_Channel_GetPaused(channel, &state‬‬
‫‪6‬‬ ‫‪if (state == 1) // if the music is paused‬‬
‫‪7‬‬ ‫‪FMOD_Channel_SetPaused(channel, 0); // We play it‬‬
‫‪8‬‬ ‫‪else // Else, the music is being played‬‬
‫‪9‬‬ ‫‪FMOD_Channel_SetPaused(channel, 1); // We pause it‬‬
‫‪10‬‬ ‫}‬
‫‪11‬‬ ‫;‪break‬‬

‫‪FMOD_ChannelGroup_GetPaused‬‬ ‫ل القنوات معاً‪ ،‬نستعمل الدالتين‬


‫إذا أردنا إعادة تطبيق نفس الأمر من أجل ك ّ‬
‫و ‪ ، FMOD_ChannelGroup_SetPaused‬الاختلاف الوحيد الذي يجب القيام به هو إعطاءها كمعامل‬
‫‪ FMOD_CHANNELGROUP‬بدلا ًعن ‪. FMOD_CHANNEL‬‬

‫إيقاف التشغيل‬

‫يكفي استدعاء ‪ FMOD_Channel_Stop‬لأجل إيقاف موسيقى في قناة ما‪ ،‬أو‬


‫‪ FMOD_ChannelGroup_Stop‬من أجل مجموعة من القنوات‪ ،‬نبعث لها على الترتيب المؤشر نحو القناة أو المؤش ّر نحو‬
‫مجموعة القنوات‪.‬‬

‫و بالطبع أشياء أخرى‬

‫يمكننا القيام بالـكثير من الأشياء الأخرى‪ ،‬لـكن لن أقوم بتعدادها كل ّها هنا‪ .‬يجب عليك قراءة الملفات التوجيهية ! و التي‬
‫أنصحك بإلقاء نظرة عليها في حال ما احتجت الاطلاع على دوال أخرى‪.‬‬

‫تحرير الذاكرة‬

‫لـكي نقوم بتفر يغ الأغنية من الذاكرة‪ ،‬نستدعي الدالة ‪ FMOD_Sound_Release‬و نعطيها المؤش ّر ‪:‬‬

‫;)‪1 FMOD_Sound_Release(music‬‬

‫‪438‬‬
(OGG ،MP3 ،WAV) ‫ الموسيقى‬.4.27

MP3 ‫الشفرة المصدر ية الكاملة لقراءة ملف‬

.Jamendo ‫” التي حصلنا عليها من الموقع‬Home” ‫الشفرة أسفله تقدّم لنا برنامجا ًيقوم بتشغيل الموسيقى‬
. P ‫ يمكننا إيقاف الموسيقى مؤقتا بالضغط على‬.‫يتم تشغيل الموسيقى منذ بداية تشغيل البرنامج‬
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <SDL/SDL.h>
4 #include <SDL/SDL_image.h>
5 #include <fmodex/fmod.h>
6 int main(int argc, char �argv[])
7 {
8 SDL_Surface �screen = NULL, �wallet = NULL;
9 SDL_Event event;
10 SDL_Rect position;
11 int cont = 1;
12 FMOD_SYSTEM �system;
13 FMOD_SOUND �music;
14 FMOD_RESULT result;
15 FMOD_System_Create(&system);
16 FMOD_System_Init(system, 1, FMOD_INIT_NORMAL, NULL);
17 // We open the music
18 result = FMOD_System_CreateSound(system, ”hype_home.mp3”, FMOD_SOFTWARE
| FMOD_2D | FMOD_CREATESTREAM, 0, &music);
19 // We check if it has been opened successfully (IMPORTANT)
20 if (result != FMOD_OK)
21 {
22 fprintf(stderr, ”Can’t read the mp3 file\n”);
23 exit(EXIT_FAILURE);
24 }
25 // We activate the music repetition infinitely
26 FMOD_Sound_SetLoopCount(music, −1);
27 // We play the music
28 FMOD_System_PlaySound(system, FMOD_CHANNEL_FREE, music, 0, NULL);
29 SDL_Init(SDL_INIT_VIDEO);
30 screen = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF);
31 SDL_WM_SetCaption(”Gestion du son avec FMOD”, NULL);
32 wallet = IMG_Load(”hype_liesandspeeches.jpg”);
33 position.x = 0;
34 position.y = 0;
35 while (cont)
36 {
37 SDL_WaitEvent(&event);
38 switch(event.type)
39 {
40 case SDL_QUIT:
41 cont = 0;
42 break;
43 case SDL_KEYDOWN:
44 if (event.key.keysym.sym == SDLK_p) // If we press P

439
FMOD‫بـ‬ ‫ تشغيل الصوت‬.27 ‫الفصل‬

45 {
46 FMOD_CHANNELGROUP �channel;
47 FMOD_BOOL state;
48 FMOD_System_GetMasterChannelGroup(system, &
channel);
49 FMOD_ChannelGroup_GetPaused(channel, &state);
50 if (state) // If the music is paused
51 FMOD_ChannelGroup_SetPaused(channel, 0)
; // We play it
52 else // Else, the music is being played
53 FMOD_ChannelGroup_SetPaused(channel, 1)
; // We pause it
54 }
55 break;
56 }
57 SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 0, 0, 0))
;
58 SDL_BlitSurface(wallet, NULL, screen, &position);
59 SDL_Flip(screen);
60 }
61 FMOD_Sound_Release(music);
62 FMOD_System_Close(system);
63 FMOD_System_Release(system);
64 SDL_FreeSurface(wallet);
65 SDL_Quit();
66 return EXIT_SUCCESS;
67 }

.‫لـكي لا تكون خلفية البرنامج مجر ّد صورة سوداء استعملت صورة الألبوم كخلفي ّة‬

.‫يمكنك مشاهدة الفيديو الذي يمث ّل تشغيل البرنامج من هنا‬

https://openclassrooms.com/uploads/fr/ftp/mateo21/musique_hype.html (730 Ko)

‫ملخّ ص‬

.FMOD ‫ مزايا محدودة بالنسبة للصوت و يُنصح أن تتم الاستعانة بمكتبة مخصصة لتشغيل الصوت مثل‬SDL‫• لدى الـ‬

.(ً‫ أصوات قصيرة )ضجيج الخطوات مثلا( و أصوات طو يلة )موسيقى مثلا‬: FMOD‫• نمي ّز بين نوعين من الصوت بالـ‬

.‫ل من هذين النوعين يُقرأ بنفس الدالة لـكن بواسطة أعلام مختلفة كخيارات‬
ّ ‫• ك‬

.‫ بتشغيل كثير من الأصوات في آن واحد بالإستعانة بالـكثير من القنوات‬FMOD ‫• تسمح‬

440
‫الفصل ‪28‬‬

‫عمل تطبيقي ‪ :‬الإظهار الطيفي للصوت‬

‫هذا العمل التطبيقي سيقترح عليك التعامل مع الـ‪ SDL‬و الـ‪ FMOD‬في نفس الوقت‪ .‬هذه المر ّة‪ ،‬لن نعمل على لعبة‪.‬‬
‫كما نعرف فالـ‪ SDL‬مخصصة لهذا‪ ،‬لـكن يمكن استعمالها في ميادين أخرى‪ .‬سيقوم هذا الفصل بإثبات أنها صالحة لأجل‬
‫أشياء أخرى‪.‬‬

‫سنحقق هنا إظهارا ً للطيف الصوتي بالـ‪ .SDL‬يتوق ّف هذا على إظهار تركيبة الصوت الذي نشغ ّله‪ ،‬مثلا ًموسيقى‪ .‬نجد‬
‫أمر ممتع و ليس بقدر الصعوبة التي يبدو عليها !‬
‫هذه الخاصية في كثير من برامج قراءة الأصوات‪ .‬إنه ٌ‬

‫سيسمح لك هذا الفصل بالعمل على مفاهيم قُمنا باستكشافها مؤخّرا ً ‪:‬‬

‫• التحكّم في الوقت‪.‬‬

‫• المكتبة ‪.FMOD‬‬

‫سنتعر ّف علاوة على ذلك‪ ،‬على كيفية التعديل على مساحة بيكسلا ببيكسل‪.‬‬

‫الصورة التالية تعطيك مظهرا ً للبرنامج الذي سنكتبه في هذا الفصل‪.‬‬

‫‪441‬‬
‫الفصل ‪ .28‬عمل تطبيقي ‪ :‬الإظهار الطيفي للصوت‬

‫هو نوع الإظهار الذي نجده في قارئي الأصوات كـ‪ Windows Media Player ،Winamp‬أو ‪.AmaroK‬‬
‫كما قلتُ لك إن الأمر ليس صعبَ التحقيق‪ .‬على عكس العمل التطبيقي الخاص بـ‪ ،Mario Sokoban‬هذه المر ّة ستقوم‬
‫بنفسك بالعمل‪ .‬سيمث ّل هذا بالنسبة إليك تمرينا ًجيداً‪.‬‬

‫‪ 1.28‬التعليمات‬

‫التعليمات بسيطة‪ .‬إتّبعها خطوة بخطوة بالترتيب‪ ،‬و لن تواجه أي مشاكل‪.‬‬

‫‪MP3‬‬ ‫قراءة ملف‬

‫لـكي تبدأ‪ ،‬يجب عليك إنشاء برنامج يقوم بقراءة ملف ‪ .MP3‬ليس عليك سوى إعادة الأغنية ”‪ ”Home‬للمجموعة ”‪”Hype‬‬
‫و التي استعملناها في الفصل الخاص بـ‪ FMOD‬لتلخيص كيفية عمل تشغيل الموسيقى‪.‬‬

‫إذا اتّبعت جي ّدا الفصل حول ‪ ،FMOD‬لا تحتاج أكثر من بضعة دقائق لـكي تقوم بالعملية‪ .‬أنصحك بالمناسبة أن تقوم‬
‫بنقل الملف ‪ MP3‬إلى مجلّد المشروع‪.‬‬

‫استرجاع المعلومات الطيفية للصوت‬

‫لـكي نعرف كيف يعمل الإظهار الطيفي للصوت‪ ،‬من الواجب أن أشرح لك كيفية يعمل الأمر من الداخل )بشكل‬
‫تقريبي فقط‪ ،‬و إلا سندخل في درس ر ياضيات(‪.‬‬

‫يمكن أن يتم تقسيم الصوت إلى ترددات )‪ .(Frequencies‬بعض الترددات منخفضة‪ ،‬بعضها متوسطة و بعضها مرتفعة‪.‬‬
‫ل واحدة من الترددات على شكل شرائط و كل ّما يكون الشر يط كبيراً‪،‬‬
‫ما سنقوم به في عملية الإظهار هو إظهار كمية ك ّ‬
‫كلما يكون التردد مستعملا ًأكثر ‪:‬‬

‫‪442‬‬
‫‪ .1.28‬التعليمات‬

‫على يسار النافذة‪ ،‬نقوم بإظهار الترددات المنخفضة‪ ،‬و على اليمين الترددات المرتفعة‪.‬‬

‫؟‬
‫ل تردد ؟‬
‫لـكن كيف نسترجع كمي ّة ك ّ‬

‫ستهتم ‪ FMOD‬بهذا العمل‪ .‬يمكننا استدعاء الدالة ‪ FMOD_Channel_GetSpectrum‬ذات النموذج ‪:‬‬

‫(‪1 FMOD_RESULT FMOD_Channel_GetSpectrum‬‬


‫‪2‬‬ ‫‪FMOD_CHANNEL � channel,‬‬
‫‪3‬‬ ‫‪float � spectrumarray,‬‬
‫‪4‬‬ ‫‪int numvalues,‬‬
‫‪5‬‬ ‫‪int channeloffset,‬‬
‫‪6‬‬ ‫‪FMOD_DSP_FFT_WINDOW windowtype‬‬
‫;) ‪7‬‬

‫و هاهي المعاملات التي تحتاجها الدالة ‪:‬‬

‫• القناة التي تشتغل فيها الموسيقى‪ .‬يجب إذا استرجاع مؤش ّر نحو هذه القناة‪.‬‬

‫لـ‪FMOD‬‬ ‫• جدول ‪ . float‬يجب أن يتم حجز الذاكرة من أجل هذا الجدول مسب ّقاً‪ ،‬بشكل ثابت أو حيّ‪ ،‬لـكي نسمح‬
‫بملئه بشكل صحيح‪.‬‬

‫• حجم الجدول‪ .‬يجب أن يكون حجم الجدول إجبار يا ًعبارة عن قو ّة للعدد ‪ ،2‬مثلا ‪.512‬‬

‫• يسمح هذا المعامل بتعر يف بأي مخرج نحن مهتمون‪ .‬مثلا ًلو أننا في ‪ ،stereo‬فـ‪ 0‬تعني اليسار و ‪ 1‬تعني اليمين‪.‬‬

‫• هذا المعامل معقّد قليلاً‪ ،‬و لا يهمّنا حقيقة في هذا الفصل‪ .‬سنكتفي بإعطائه القيمة‬
‫‪. FMOD_DSP_FFT_WINDOW_RECT‬‬

‫م‬
‫‪double‬‬ ‫تذكير ‪ :‬النوع ‪ float‬هو نوع عشري‪ ،‬مثل ‪ . double‬الاختلاف بين الاثنين يكمن في كون الـ‬
‫أكثر دق ّة من الآخر‪ ،‬لـكن في حالتنا يكفينا الـ ‪ . float‬هذا الأخير مستعمل من طرف ‪ FMOD‬هنا‪ .‬و لذلك‪،‬‬
‫هو ما سنستعمله نحن أيضاً‪.‬‬

‫بشكل واضح‪ ،‬نعر ّف جدول الـ ‪: float‬‬

‫;]‪1 float spectrum[512‬‬

‫ثم‪ ،‬حين يتم تشغيل الموسيقى‪ ،‬نطلب من ‪ FMOD‬ملء جدول الأطياف بالقيام مثلا ًبـ ‪:‬‬

‫;)‪1 FMOD_Channel_GetSpectrum(channel, spectrum, 512, 0, FMOD_DSP_FFT_WINDOW_RECT‬‬

‫يمكننا بعد ذلك تصفّح الجدول لـكي نتحصّ ل على قيم الأطياف ‪:‬‬

‫‪443‬‬
‫الفصل ‪ .28‬عمل تطبيقي ‪ :‬الإظهار الطيفي للصوت‬

‫‪1‬‬ ‫)‪spectrum[0] // The lowest frequency (Left‬‬


‫‪2‬‬ ‫]‪spectrum[1‬‬
‫‪3‬‬ ‫]‪spectrum[2‬‬
‫‪4‬‬ ‫‪...‬‬
‫‪5‬‬ ‫]‪spectrum[509‬‬
‫‪6‬‬ ‫]‪spectrum[510‬‬
‫‪7‬‬ ‫)‪spectrum[511] // The highest frequency (Right‬‬

‫ل‬
‫ينص عملك على إظهار ك ّ‬
‫ّ‬ ‫ل تردد هو عبارة عن عدد عشري محصور بين ‪) 0‬لا شيء( و ‪) 1‬قيمة قصوى(‪.‬‬
‫ك ّ‬
‫ل من خانات الجدول‪.‬‬
‫شر يط سواء كان قصيرا ً أو كبيرا ً بدلالة القيمة التي تحتويها ك ّ‬

‫مثلاً‪ ،‬إذا كانت القيمة هي ‪ 0.5‬يجدر بك رسم شر يط يكون عل ّوه مساو يا ً لنصف علو ّ النافذة‪ .‬إذا كانت القيمة هي‬
‫ل علو النافذة‪.‬‬
‫‪ ،1‬فسيأخذ الشر يط ك ّ‬

‫ل القيم بـ‪ 20‬لـكي ترى الطيف بشكل‬


‫بشكل عام‪ ،‬تكون القيم ضعيفة )أكثر قربا ً من ‪ 0‬على ‪ .(1‬أنصحك بضرب ك ّ‬
‫أفضل‪.‬‬
‫احذر ‪ :‬إذا قمت بهذا‪ ،‬تأكد بأنك لن تتجاوز ‪) 1‬قم بتدوير القيمة إلى ‪ 1‬إذا احجت إلى ذلك(‪ .‬إذا وجدت أنك ٺتعامل مع‬
‫أعداد تفوق ‪ ،1‬فقد تواجه مشاكل لاحقا ًفي رسم الشرائط العمودي ّة لاحقا !‬

‫؟‬
‫لـكن يجدر بالشرائط أن تتحر ّك في نفس الوقت الذي يتم فيه تشغيل الصوت‪ ،‬أليس كذلك ؟ بما أن الصوت يتحر ّك‬
‫ل الوقت‪ ،‬يجب تحديث الصورة الرسومية‪ ،‬ما العمل ؟‬
‫ك ّ‬

‫سؤال جيد‪ .‬في الواقع‪ ،‬الجدول الخاص المتكون من ‪ float 512‬الذي ترجعه لنا ‪ FMOD‬يتغي ّر كل ‪ 25‬مث‬
‫)لـكي نكون في نفس الفاصل الزمني بالنسبة للصوت الحالي(‪ .‬يجب إذا في الشفرة المصدر ية أن تعيد قراءة جدول الـ‪512‬‬
‫ل ‪ 25‬مث(‪ ،‬ثم تقوم بتحديث رسمك ذي الشرائط‪.‬‬
‫‪) float‬بإعادة استدعاء ‪ FMOD_Channel_GetSpectrum‬ك ّ‬

‫أعد قراءة الفصل حول التحكّم في الوقت بالـ‪ SDL‬لـكي ٺتذك ّر كيفية عمل ذلك‪ .‬لديك الخيار بين ‪ GetTicks‬و‬
‫الـ‪ .callbacks‬استعمل ما تراه أكثر سهولة لك‪.‬‬

‫إنشاء التدرّج اللوني‬

‫في البداية‪ ،‬يمكنك تحقيق الشرائط بلون موحّد‪ .‬يمكنك إذا إنشاء مساحات‪ .‬يجب إذا أن تكون هناك ‪ 512‬مساحة ‪:‬‬
‫ل‬
‫ل مساحة تأخذ إذا بيكسلا واحدا كع ُرض‪ .‬و يختلف علو ّ الشرائط بدلالة شدّة ك ّ‬
‫ل شر يط‪ .‬ك ّ‬
‫واحدة من أجل ك ّ‬
‫تردد‪.‬‬

‫أنصحك بعدها أن تقوم بتحسين ‪ :‬يجب على الشر يط أن يميل للأحمر كل ّما زادت كثافة الصوت‪ .‬أي أنه على الشر يط‬
‫أن يكون أخضرا ً من الأسفل و أحمرا ً من الأعلى‪.‬‬

‫‪444‬‬
‫‪ .1.28‬التعليمات‬

‫؟‬
‫ن واحدٍ لو عندما نستعمل الدالة ‪ . SDL_FillRect‬لا‬
‫لـكن ‪ ...‬المساحة الواحدة لا يمكنها أن تأخذ سوى لو ٍ‬
‫يمكننا إنشاء تدرّح لوني !‬

‫ل لون في التدرّج‪ .‬لـكن هذا‬


‫في الواقع‪ ،‬يمكننا بالتأكيد إنشاء مساحات بعَر ْض ‪ 1‬بيكسل و عُلُو ‪ 1‬بيكسل من أجل ك ّ‬
‫سيأخذ بنا إلى إنشاء مساحات عديدة و لن يكون التحكّم فيها مثاليا ً!‬

‫كيف يمكن لنا أن نرسم بيكسلا ببيكسل ؟‬


‫ن هذه التقنية لا تستحقّ فصلا ًكاملاً‪ .‬ستجد أنها في الواقع ليست صعبة‪.‬‬
‫لم أعلّمك هذا من قبل‪ ،‬لأ ّ‬

‫في الواقع‪ ،‬لا تقترح الـ‪ SDL‬أية دالة للرسم بيكسلا ببيكسل‪ .‬لـكن لنا الحق في أن نكتبها بأنفسنا‪ .‬لـكي نقوم بهذا‪ ،‬يجب‬
‫إتّباع هذه الخطوات النموذجية بالترتيب ‪:‬‬

‫‪ .1‬استدع الدالة ‪ SDL_LockSurface‬لنعلن للـ‪ SDL‬أننا سنقوم بالتعديل على المساحة يدو ياً‪ .‬هذا ”يعطّل” المساحة‬
‫للـ‪ SDL‬و ستكون وحدك قادرا ً على التحكّم فيها مادامت المساحة معطّلة‪.‬‬

‫هنا‪ ،‬أنصحك بأن تعمل بمساحة واحدة فقط ‪ :‬الشاشة‪ .‬إذا أردت رسم بيكسل في منطقة محددة من الشاشة‪ ،‬يجب‬
‫عليك تعطيل المساحة ‪: screen‬‬
‫;)‪1 SDL_LockSurface(screen‬‬

‫ل بيكسل من المساحة‪ .‬بما أن الـ‪ SDL‬لا تقترح أية دالة للقيام بهذا‪ ،‬يجب أن نكتبها‬
‫‪ .2‬يمكنك بعد ذلك تغيير محتوى ك ّ‬
‫بأنفسنا في البرنامج‪.‬‬
‫سأعطيك هذه الدالة‪ ،‬و التي استخرجتها من الملفات التوجيهية للـ‪ .SDL‬هي معقدّة أكثر لأنها تعمل على المساحة‬
‫ل أعماق اللون الممكنة )بيتات على البيكسل(‪ .‬لا تحتاج لحفظها أو فهمها‪ ،‬قم بنسخها ببساطة‬
‫مباشرة و تتحكم في ك ّ‬
‫في البرنامج لـكي تتمكّن من استعمالها ‪:‬‬
‫)‪1 void setPixel(SDL_Surface �surface, int x, int y, Uint32 pixel‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;‪int bpp = surface−>format−>BytesPerPixel‬‬
‫‪4‬‬
‫‪5‬‬ ‫‪Uint8 �p = (Uint8 �)surface−>pixels + y � surface−>pitch + x � bpp‬‬
‫;‬
‫‪6‬‬
‫‪7‬‬ ‫{ )‪switch(bpp‬‬
‫‪8‬‬ ‫‪case 1:‬‬
‫‪9‬‬ ‫;‪�p = pixel‬‬
‫‪10‬‬ ‫;‪break‬‬
‫‪11‬‬
‫‪12‬‬ ‫‪case 2:‬‬
‫‪13‬‬ ‫;‪�(Uint16 �)p = pixel‬‬
‫‪14‬‬ ‫;‪break‬‬
‫‪15‬‬
‫‪16‬‬ ‫‪case 3:‬‬

‫‪445‬‬
‫الفصل ‪ .28‬عمل تطبيقي ‪ :‬الإظهار الطيفي للصوت‬

‫‪17‬‬ ‫{ )‪if(SDL_BYTEORDER == SDL_BIG_ENDIAN‬‬


‫‪18‬‬ ‫;‪p[0] = (pixel >> 16) & 0xff‬‬
‫‪19‬‬ ‫;‪p[1] = (pixel >> 8) & 0xff‬‬
‫‪20‬‬ ‫;‪p[2] = pixel & 0xff‬‬
‫‪21‬‬ ‫{ ‪} else‬‬
‫‪22‬‬ ‫;‪p[0] = pixel & 0xff‬‬
‫‪23‬‬ ‫;‪p[1] = (pixel >> 8) & 0xff‬‬
‫‪24‬‬ ‫;‪p[2] = (pixel >> 16) & 0xff‬‬
‫‪25‬‬ ‫}‬
‫‪26‬‬ ‫;‪break‬‬
‫‪27‬‬
‫‪28‬‬ ‫‪case 4:‬‬
‫‪29‬‬ ‫;‪�(Uint32 �)p = pixel‬‬
‫‪30‬‬ ‫;‪break‬‬
‫‪31‬‬ ‫}‬
‫} ‪32‬‬

‫هي سهلة الاستعمال‪ .‬ابعث لها المعاملات التالية ‪:‬‬

‫• المؤش ّر نحو المساحة التي تريد التعديل عليها )يجب أن تكون معطّلة بواسطة ‪.( SDL_LockSurface‬‬

‫• وضعية الفاصلة الخاصة بالبيكسل الذي نريد التعديل عليه في المساحة ) ‪.( x‬‬

‫• وضعية الترتيبة الخاصة بالبيكسل الذي نريد التعديل عليه في المساحة ) ‪.( y‬‬

‫• اللون الجديد الذي نعطيه للبيكسل‪ .‬يجب أن يكون هذا اللون بصيغة ‪ . Uint32‬يمكنك إذا توليده بالاستعانة‬
‫بالدالة ‪ SDL_MapRGB‬التي ٺتقننها جيدا ً الآن‪.‬‬

‫• أخيراً‪ ،‬حينما تنتهي من العمل على المساحة‪ ،‬يجب ألا تنسى أن تز يل تعطيلها باستدعاء ‪. SDL_UnlockSurface‬‬

‫;)‪1 SDL_UnlockSurface(screen‬‬

‫شفرة ملخّ صة للمثال‬

‫ل شيء سهل‪.‬‬
‫لو نلخّ ص‪ ،‬ستجد بأن ك ّ‬
‫هذه الشفرة ترسم بيكسلا أحمرا في منتصف المساحة ‪) screen‬أي في منتصف النافذة(‪.‬‬

‫‪1 SDL_LockSurface(screen); // We lock the surface‬‬


‫‪2 setPixel(screen, screen−>w / 2, screen−>h / 2, SDL_MapRGB(screen−>format, 255,‬‬
‫‪0, 0)); // We draw a red pixel in the middle of the screen‬‬
‫‪3 SDL_UnlockSurface(screen); // We unlock the surface‬‬

‫من هذه القاعدة‪ ،‬يجدر بك أن تتمكن من تحقيق التدرّج اللوني من الأخضر للأحمر )يجب أن تستعمل الحلقات‬
‫التكرار ية(‪.‬‬

‫‪446‬‬
‫ التصحيح‬.2.28

‫التصحيح‬ 2.28

‫ خاصة من أجل تحقيق التدرّج‬،‫ يجب فقط القيام ببعض الحسابات‬،‫ كيف وجدت الموضوع ؟ ليس صعب الفهم‬،‫إذا‬
.‫ يجب فقط أن تفك ّر أكثر‬،‫ مستوى التمرين هو مستو ً عام‬.‫اللوني‬

ّ ‫ إذا لم تتمكّن من ح‬.‫بعض الأشخاص يأخذون وقتا ً أطول من آخرين لإ يجاد التصحيح‬
‫ ما‬.ً‫ هذا ليس سيئا‬،‫ل التمرين‬
‫ فسيكون هناك بالتأكيد أوقات نجد فيها أنه لا‬،‫ مهما كان المشروع الذي تعمل عليه‬.‫م هو أن ننتهي بالوصول إلى هدفنا‬
ّ ‫يه‬
.‫ يجب أيضا ًأن نكون منطقيين و نجيد التفكير‬،‫ل المشكل‬
ّ ‫ينقصنا أن نجيد البرمجة لـكي نتمكّن من ح‬

: ‫كاف‬
ٍ ‫ لقد عل ّقت عليها بشكل‬.‫سأعطيك الشفرة المصدر ية الكاملة‬

1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <SDL/SDL.h>
4 #include <fmodex/fmod.h>
5 #define WINDOW_WIDTH 512 // MUST stay equal to 512 because there are 512 bars
corresponding to 512 floats
6 #define WINDOW_HIGHT 400 // You can change this.
7 #define RATIO (WINDOW_HIGHT / 255.0)
8 #define LIMIT_TIME_TO_REFRESH 25 // Time in ms between two updates of the graph
(25 is the minimum)
9 #define SPECTERUM_SIZE 512
10 void setPixel(SDL_Surface �surface, int x, int y, Uint32 pixel);
11 int main(int argc, char �argv[])
12 {
13 SDL_Surface �screen = NULL;
14 SDL_Event event;
15 int cont = 1, barHeight = 0, currentTime = 0, previousTime = 0, i = 0,
j = 0;
16 float spectrum[SPECTERUM_SIZE];
17 /� Initializing FMOD:
18 Load FMOD, the music and start playing the music
19 �/
20
21 FMOD_SYSTEM �system;
22 FMOD_SOUND �music;
23 FMOD_CHANNEL �channel;
24 FMOD_RESULT result;
25 FMOD_System_Create(&system);
26 FMOD_System_Init(system, 1, FMOD_INIT_NORMAL, NULL);
27 // We open the music
28 result = FMOD_System_CreateSound(system, ”hype_home.mp3”, FMOD_SOFTWARE
| FMOD_2D | FMOD_CREATESTREAM, 0, &music);
29 // We check if it has been opened correctly (IMPORTANT)
30 if (result != FMOD_OK)
31 {
32 fprintf(stderr, ”Can’t read the mp3 file\n”);

447
‫ الإظهار الطيفي للصوت‬: ‫ عمل تطبيقي‬.28 ‫الفصل‬

33 exit(EXIT_FAILURE);
34 }
35 // We play the music
36 FMOD_System_PlaySound(system, FMOD_CHANNEL_FREE, music, 0, NULL);
37
38 // We get the channel pointer
39 FMOD_System_GetChannel(system, 0, &channel);
40 /�
41 Initializing the SDL:
42 −−−−−−−−−−−−−−−−−−−−−
43 We load the SDL, open a window and write in its title bar.
44 We get also a pointer to the surface screen which will be the only
surface to use in this program
45 �/
46 SDL_Init(SDL_INIT_VIDEO);
47 screen = SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HIGHT, 32, SDL_SWSURFACE
| SDL_DOUBLEBUF);
48 SDL_WM_SetCaption(”Visualisation spectrale du son”, NULL);
49 // Main loop
50 while (cont)
51 {
52 SDL_PollEvent(&event); // We have to use PollEvent because we
don’t have to wait for the user’s event to refresh the
window
53 switch(event.type)
54 {
55 case SDL_QUIT:
56 cont = 0;
57 break;
58 }
59 // We clear the screen every time before drawing the graph (
black wallpaper)
60 SDL_FillRect(screen, NULL, SDL_MapRGB(screen−>format, 0, 0, 0))
;
61 /� Managing the time
62 −−−−−−−−−−−−−−−−−
63 We compare between the current time and the previous one (the
last iteration of the loop).
64 If the difference is less than 25 ms (updating time limit)
65 Then we wait until 25ms pass.
66 After that, we update previousTime with the new time. �/
67 currentTime = SDL_GetTicks();
68 if (currentTime − previousTime < LIMIT_TIME_TO_REFRESH)
69 {
70 SDL_Delay(LIMIT_TIME_TO_REFRESH−(currentTime−
previousTime));
71 }
72 previousTime = SDL_GetTicks();
73 /� Drawing the sound spectrum
74 −−−−−−−−−−−−−−−−−−−−−−−−

448
‫ التصحيح‬.2.28

75 It’s the most important part. We have to think a little bit


before drawing the spectrum. Maybe it’s hard but it’s
possible, here is the proof.
76 We fill the 512 floats table via FMOD_Channel_GetSpectrum()
77 Then we work pixel by pixel on the surface screen to draw the
bars.
78 We make a first loop to browse the window in width.
79 The second loop browses the window in height to draw the bars.
80 �/
81
82 /� We fill the 512 floats table. I’ve chosen to be interested
in the left output �/
83 FMOD_Channel_GetSpectrum(channel, spectrum, SPECTERUM_SIZE, 0,
FMOD_DSP_FFT_WINDOW_RECT);
84 SDL_LockSurface(screen);
85 /� We block the surface screen because we’re going to directly
modify its pixels �/
86
87 /� LOOP 1 : We browse the window in width (for every vertical
bar) �/
88 for (i = 0 ; i < WINDOW_WIDTH ; i++)
89 {
90 /� We calculate the vertical bar’s height that we’re
going to draw.
91 spectrum[i] will return a number between 0 and 1 that
we’re going to multiply by 20 to zoom in order to
have a better view (As I said).
92
93 The, we multiply by WINDOW_HEIGHT so the bar will be
expanded comparing to the window’s size. �/
94
95 barHeight = spectrum[i] � 20 � WINDOW_HIGHT;
96 /� We verify that the bar doesn’t exceed the height of
the window
97 If it’s the case, we crop the bar so it become equal to
the window’s height. �/
98 if (barHeight > WINDOW_HIGHT)
99 barHeight = WINDOW_HIGHT;
100 /� LOOP 2 : we browse in height the vertical bar to
draw it �/
101
102 for (j = WINDOW_HIGHT − barHeight ; j < WINDOW_HIGHT ;
j++)
103 {
104 /� We draw each pixel of the bar with the right
colour.
105 We simply vary the red and green colours, each
one in a different way.
106
107 j doesn’t vary between 0 and 255 but between 0

449
‫ الإظهار الطيفي للصوت‬: ‫ عمل تطبيقي‬.28 ‫الفصل‬

and WINDOW_HEIGHT.
108
109 If we want to adapt it proportionally to the
window’s height, we can simply calculate j
/ RATIO, where RATIO is equal to (
WINDOW_HEIGHT / 255.0).
110
111 It tooks for me 2−3 minutes so I can find the
write calculation to do, every one can do
it. You just have to think a little bit �/
112
113 setPixel(screen, i, j, SDL_MapRGB(screen−>
format, 255 − (j / RATIO), j / RATIO, 0));
114 }
115 }
116 SDL_UnlockSurface(screen); /� We have finished working on the
screen, we block the surface �/
117 SDL_Flip(screen);
118 }
119 /� The program is finished.
120 We free the music from the memory
121 And we close FMOD and SDL �/
122
123 FMOD_Sound_Release(music);
124 FMOD_System_Close(system);
125 FMOD_System_Release(system);
126 SDL_Quit();
127 return EXIT_SUCCESS;
128 }
129 /� The function setPixel lets us draw a surface pixel by pixel �/
130
131 void setPixel(SDL_Surface �surface, int x, int y, Uint32 pixel)
132 {
133 int bpp = surface−>format−>BytesPerPixel;
134 Uint8 �p = (Uint8 �)surface−>pixels + y � surface−>pitch + x � bpp;
135 switch(bpp)
136 {
137 case 1:
138 �p = pixel;
139 break;
140 case 2:
141 �(Uint16 �)p = pixel;
142 break;
143 case 3:
144 if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
145 {
146 p[0] = (pixel >> 16) & 0xff;
147 p[1] = (pixel >> 8) & 0xff;
148 p[2] = pixel & 0xff;
149 }

450
‫‪ .2.28‬التصحيح‬

‫‪150‬‬ ‫‪else‬‬
‫‪151‬‬ ‫{‬
‫‪152‬‬ ‫;‪p[0] = pixel & 0xff‬‬
‫‪153‬‬ ‫;‪p[1] = (pixel >> 8) & 0xff‬‬
‫‪154‬‬ ‫;‪p[2] = (pixel >> 16) & 0xff‬‬
‫‪155‬‬ ‫}‬
‫‪156‬‬ ‫;‪break‬‬
‫‪157‬‬ ‫‪case 4:‬‬
‫‪158‬‬ ‫;‪�(Uint32 �)p = pixel‬‬
‫‪159‬‬ ‫;‪break‬‬
‫‪160‬‬ ‫}‬
‫} ‪161‬‬

‫يجدر بك أن تتحصّ ل على نتيجة تشبه الصورة التالية ‪:‬‬

‫من المعلوم أن النتيجة المرئية أفضل لتقدير النتيجة‪ .‬أنصحك بالاطلاع عليها من هنا ‪:‬‬

‫)‪https://openclassrooms.com/uploads/fr/ftp/mateo21/spectre.html (4.3 Mo‬‬

‫لاحظ أن ضغط الملف أنقص من جودة الصوت و عدد الصور في الثانية‪.‬‬


‫الأفضل هو أن تقوم بتحميل البرنامج كاملا ً )مرفقا ً بالشفرة المصدر ية( لـكي تجربه عندك‪ .‬يمكنك حينها تقدير البرنامج في‬
‫ظروف أفضل‪.‬‬

‫تنز يل الملف التنفيذي و الشفرة المصدر ية الكاملة ‪:‬‬

‫‪https://openclassrooms.com/uploads/fr/ftp/mateo21/spectre.zip‬‬

‫‪x‬‬
‫يجب قطعا ًأن يكون الملف ‪ Hype_Home.mp3‬متواجدا ً في مجلّد المشروع لـكي يشتغل البرنامج )و إلا فسيتوقف‬
‫حالاً(‪.‬‬

‫‪451‬‬
‫الفصل ‪ .28‬عمل تطبيقي ‪ :‬الإظهار الطيفي للصوت‬

‫أفكار للتحسين‬

‫يمكن دائما تحسين البرنامج‪ .‬هنا‪ ،‬لدي مثلا ًأفكار تمديد كثيرة يمكنها أن تصل بك إلى إنشاء برنامج صغير لقراءة الملفات‬
‫‪.MP3‬‬

‫ل الملفات بذات‬
‫• سيكون من الجيد أن نختار بأنفسنا الملف ‪ MP3‬الذي نريد قراءته‪ .‬يمكن مثلا ًأن نقدّم لائحة تضم ك ّ‬
‫الصيغة و المتواجدة في مجلّد المشروع‪ .‬لم نر َ كيف نقوم بذلك‪ ،‬لـكن يمكنك وحدك أن تكتشف ذلك‪ .‬كمساعدة ‪:‬‬
‫استعمل المكتبة ‪ ) dirent‬قم بتضمين الملف ‪ .( dirent.h‬عليك بالبحث في الأنترنت لتعرف كيفي ّة العمل بها‪.‬‬

‫• إذا كان البرنامج قادرا على التحكّم في لائحات التشغيل )‪ (Playlists‬المشغ ّلة‪ ،‬سيكون أمرا ً أفضل‪ .‬توجد كثير من‬
‫صيغ اللائحات و أشهرها الصيغة ‪.M3U‬‬

‫• يمكنك إظهار اسم الـ‪ MP3‬التي أنت بصدد تشغيله في النافذة مثلا ً)يجب استعمال ‪.(SDL_ttf‬‬

‫• يمكنك إظهار مؤش ّر يشير إلى المكان في القراءة الّذي وصل إليه التشغيل‪ ،‬هذا ما يفعله أغلب قارئي الـ‪.MP3‬‬

‫• يمكنك أيضا ًأن تقترح التعديل على قوة الصوت‪.‬‬

‫• إلى آخره‪.‬‬

‫باختصار‪ ،‬هناك الـكثير لفعله‪ .‬لديك إمكاني ّة إنشاء قارئي أصوات ممتازة‪ ،‬ليس عليك سوى كتابة الشفرة الخاصة بها !‬

‫‪452‬‬
‫الجزء د‬

‫هياكل البيانات‬

‫‪453‬‬
‫الفصل ‪29‬‬

‫القوائم المتسلسلة )‪(Linked lists‬‬

‫لـكي نخز ّن المعلومات في الذاكرة‪ ،‬استعملنا متغي ّرات بسيطة )من نوع ‪ ،( ... double ، int‬كما استعملنا جداول‬
‫و هياكل مخصّ صة‪ .‬إذا أردت تخزين سلسلة من البيانات‪ ،‬فالأبسط غالبا ًهو استعمال جداول‪.‬‬

‫لـكن تصبح الجداول أحيانا ً محدودة جداً‪ .‬مثلاً‪ ،‬إذا أنشأت جدولا ًذو ‪ 10‬خانات ثم تبېّن لك لاحقا ًفي البرنامج أنك‬
‫تحتاج إلى حجم أكبر‪ ،‬سيكون من المستحيل تكبير حجم الجدول‪ .‬و أيضا ًلا يمكنك إدخال خانة إلى وسط الجدول‪.‬‬

‫تمث ّل القوائم المتسلسلة طر يقة لتنظيم البيانات في الذاكرة بطر يقة أكثر مرونة‪ .‬و بما أن لغة الـ‪ C‬لا تقترح قاعديا ً هذا‬
‫النظام من التخزين‪ ،‬سيكون علينا أن ننشئه بأنفسنا‪ .‬سيكون تمرينا ًممتازا ً يساعدك على أن ترتاح أكثر مع هذه اللغة‪.‬‬

‫تمثيل قائمة متسلسلة‬ ‫‪1.29‬‬

‫ماهي القائمة المتسلسلة ؟ أقترح عليك أن تنطلق من نموذج الجدول‪ .‬يمكن تمثيل الجدول في الذاكرة بالطر يقة التي‬
‫توضحها الصورة التالية‪ .‬نتكلّم هنا عن جدول يحتوي على خانات من نوع ‪. int‬‬
‫ّ‬

‫م‬
‫اخترت هنا تمثيل الجدول أفقياً‪ ،‬لـكن يمكن تمثيله عموديا ًكذلك‪ ،‬هذا لا يهم‪.‬‬

‫كما قلت لك في المقدّمة‪ ،‬مشكل الجداول يكمن في كونها ثابتة‪ .‬لا يمكن تكبير حجمها‪ ،‬إلا إذا فك ّرنا في إعادة إنشائها‬
‫من جديد و تكون أكبر )لاحظ الشكل التالي(‪ .‬أيضاً‪ ،‬لا يمكن أن نضيف عنص ُرا ً في وسط الجدول إلا إذا قمنا بإزاحة‬
‫ل العناصر الأخرى‪.‬‬
‫ك ّ‬

‫‪455‬‬
‫الفصل ‪ .29‬القوائم المتسلسلة )‪(Linked lists‬‬

‫لا تقترح علينا لغة الـ‪ C‬نظاما ً آخرا ً لتخزين البيانات‪ ،‬لـكن من الممكن أن ننشئ بأنفسنا هذا النظام بعناصره الكاملة ‪:‬‬
‫ستكون الغاية من هذا الفصل و الفصول الموالية اكتشاف حلول لهذا المشكل‪.‬‬

‫القائمة المتسلسلة هي طر يقة لتنظيم سلسلة من البيانات في الذاكرة‪ .‬هذا يسمح بجمع هياكل )‪ (structures‬مرتبطة‬
‫ببعضها البعض بواسطة مؤش ّرات‪ .‬يمكننا تمثيلها كالتالي ‪:‬‬

‫ل‬
‫ل عنصر أن يحتوي على ما نريد ‪ :‬قيمة من نوع ‪ int‬أو أكثر‪ ... double ،‬بالإضافة إلى ذلك‪ ،‬ك ّ‬
‫يمكن لك ّ‬
‫عنصر يحتوي على مؤش ّر نحو العنصر الموالي ‪:‬‬

‫ل هذه المعلومات نظر ية و ربّما تبدو لك غير واضحة الآن‪ .‬احفظ فقط طر يقة ات ّصال العناصر ببعضها ‪:‬‬
‫أعرف بأن ك ّ‬
‫هي تشكل سلسلة من المؤش ّرات‪ ،‬و من هنا نجد الاسم ”قائمة متسلسلة”‪.‬‬

‫م‬
‫على عكس الجداول‪ ،‬لا تتموضع عناصر القائمة المتسلسلة جنبا ً إلى جنب في الذاكرة‪ .‬كل خانة تؤش ّر نحو خانة‬
‫أخرى لا ٺتواجد ضرور يا ً بجنب الأخرى‪.‬‬

‫بناء قائمة متسلسلة‬ ‫‪2.29‬‬

‫فلنمر ّ الآن إلى صلب الموضوع‪ .‬سنحاول أن ننشئ بُنية تعمل بنفس المبدأ الذي اكتشفناه الآن‪.‬‬
‫ل ما سنقوم به هنا يستدعي تقنيات لغة الـ‪ C‬التي تعرفها من قبل‪ .‬لا يوجد شيء جديد‪ ،‬سنكتفي بإنشاء‬ ‫أذك ّرك بأن ك ّ‬
‫هياكلنا الخاصة و دوال ثم تحو يلها إلى نظام منطقي قادر على العمل لوحده‪.‬‬

‫عنصر من القائمة‬

‫من أجل الأمثلة‪ ،‬سننشئ قائمة متسلسلة من أعداد صحيحة‪ .‬كل عنصر من القائمة له شكل الهيكل التالي ‪:‬‬

‫‪1‬‬ ‫;‪typedef struct Element Element‬‬


‫‪2‬‬ ‫‪struct Element‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;‪int number‬‬
‫‪5‬‬ ‫;‪Element �next‬‬
‫‪6‬‬ ‫;}‬

‫‪456‬‬
‫‪ .2.29‬بناء قائمة متسلسلة‬

‫م‬
‫يمكننا أيضا ً إنشاء قوائم متسلسلة تحتوي أعدادا عشر ية أو حتى جداول أو هياكل‪ .‬مبدأ القوائم المتسلسلة صالح‬
‫من أجل أي نوع من البيانات مهما كان‪ ،‬لـكن هنا‪ ،‬أنصحك بتبسيط العملية حتى تفهم المبدأ‪.‬‬

‫قُمنا الآن بإنشاء عنصر واحد من القائمة‪ ،‬يوافق الصورة التي رأيناها أعلاه‪ .‬على ماذا يحتوي الهيكل ؟‬

‫• قطعة بيانات‪ ،‬هنا تتمثل في عدد من نوع ‪ : int‬يمكننا تغيير هذا بأي قطعة أخرى ) ‪ ، double‬جدول ‪.( ...‬‬
‫هذا يعتمد على نوع البيانات التي تريد تخزينها‪ ،‬سيكون عليك تغييرها على حسب حاجتك في البرنامج‪.‬‬

‫م‬
‫إذا أردنا العمل بطر يقة عامة‪ ،‬الأمثل هو استعمال مؤش ّر نحو الفراغ ‪ . void* :‬هذا يسمح بالتأشير على‬
‫أي نوع من البيانات‪.‬‬

‫ل عنصر ”يعلم”‬
‫• مؤش ّر نحو عنصر من نفس النوع يسمّى ‪ . next‬هذا ما يسمح بوصل العناصر الواحد بالآخر ‪ :‬ك ّ‬
‫أين يتواجد العنصر الذي يليه في الذاكرة‪ .‬كما قلتُ لك مسبقاً‪ ،‬الخانات لا ٺتواجد جنبا ًإلى جنب في الذاكرة‪ .‬هذا‬
‫هو الاختلاف الـكبير بالنسبة للجداول‪ .‬هذا ما سيمنحنا مرونة أكثر لأنه بإمكاننا بسهولة إضافة خانات أخرى لاحقا ً‬
‫حينما نحتاج إليها‪.‬‬

‫م‬
‫و بالمقابل‪ ،‬لا يمكننا معرفة العنصر السابق‪ ،‬أي أنه من المستحيل الرجوع إلى الخلف انطلاقا ًمن عنصر من‬
‫هذا النوع من القوائم‪ .‬لأننا هنا نتكلّم عن قائمة ”بسيطة التسلسل”‪ ،‬بينما توجد قوائم أخرى تسمّى ”مزدوجة‬
‫التسلسل” و تحتوي على مؤش ّرات في كلتا الجهتين و بهذا فهي أصعب بقليل‪.‬‬

‫هيكل التحكّم‬

‫بالإضافة إلى الهيكل الذي نحن بصدد بنائه )و الذي نضاعفه بعدد المرات التي فيها عناصر أخرى(‪ ،‬سنحتاج إلى هيكل‬
‫آخر لـكي نتحكّم في كامل القائمة المتسلسلة‪ .‬سيكون لهذا الهيكل الشكل التالي ‪:‬‬

‫‪1‬‬ ‫;‪typedef struct List List‬‬


‫‪2‬‬ ‫‪struct List‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;‪Element �first‬‬
‫‪5‬‬ ‫;}‬

‫هذا الهيكل ‪ List‬يحتوي على مؤش ّر نحو أوّل عنصر من القائمة‪ .‬في الواقع‪ ،‬يجب الاحتفاظ بعنوان العنصر الأول‬
‫لـكي نعرف أين تبدأ القائمة‪ .‬إذا عرفنا العنصر الأول‪ ،‬يمكننا أن نجد العناصر الأخرى بـ”القفز” من عنصر لآخر بالإستعانة‬
‫بالمؤشرات الموالية‪.‬‬

‫‪457‬‬
‫الفصل ‪ .29‬القوائم المتسلسلة )‪(Linked lists‬‬

‫م‬
‫هيكل مكو ّن من مركّ ب واحد هو في الغالب غير مفيد‪ .‬و مع ذلك‪ ،‬أعتقد أننا سنحتاج أن نضيف إليه لاحقا ً‬
‫مركّبات أخرى‪ ،‬يمكننا مثلا ًأن نخز ّن به حجم القائمة‪ ،‬أي عدد العناصر التي تحتويها‪.‬‬

‫ل القائمة المتسلسلة ‪:‬‬


‫لن يكون علينا إنشاء سوى نسخة واحدة من الهيكل ‪ . List‬هي تسمح بالتحكّم في ك ّ‬

‫آخر عنصر في القائمة‬

‫المخطط أصبح تقريبا ًكاملاً‪ .‬ينقصه شيء أخير ‪ :‬نفضّ ل أن نحفظ العنصر الأخير من القائمة‪ .‬في الواقع‪ ،‬يجب أن نتوق ّف‬
‫من التقدّم في القائمة المتسلسلة في لحظة ما‪ .‬كيف سيتسنى لنا أن نقول للبرنامج ‪” :‬توقف‪ ،‬هذا هو آخر عنصر” ؟‬

‫سيكون ممكنا ً أن نضيف إلى الهيكل ‪ List‬مؤش ّرا نحو آخر عنصر‪ .‬لـكن هناك ما هو أبسط ‪ :‬يكفي أن يؤش ّر آخر‬
‫عنصر من القائمة على ‪ ، NULL‬أي إعطاء المؤش ّر ‪ next‬القيمة ‪ . NULL‬هذا سيسمح لنا أخيرا ً برسم مخطط كامل لبُنية‬
‫القائمة المتسلسلة ‪:‬‬

‫دوال معالجة القوائم المتسلسلة‬ ‫‪3.29‬‬

‫لقد قًمنا بإنشاء هيكلين يسمحان لنا بالتعامل مع القوائم المتسلسلة ‪:‬‬

‫• ‪ ، Element‬الذي يوافق عنصرا ً من القائمة و الذي يمكن لنا أن نكرره بقدر المرات التي نريد‪.‬‬

‫• ‪ ، List‬الذي يتحكّم في مجموع القائمة المتسلسلة‪ .‬لن نحتاج إلا لنسخة واحدة منه‪.‬‬

‫‪458‬‬
‫‪ .3.29‬دوال معالجة القوائم المتسلسلة‬

‫هذا جيد‪ ،‬لـكن ينقص الآن الأهم ‪ :‬الدوال التي ستتعامل مع القائمة المتسلسلة‪ .‬في الواقع‪ ،‬لن نغي ّر ”يدو يا” محتوى‬
‫ل مرة نحتاج فيها إلى ذلك ! سيكون من الأكثر حكمة و الأكثر نظافة أن نمر ّ بدوال تقوم بجعل العمل يتم‬
‫الهياكل في ك ّ‬
‫بشكل تلقائيّ‪ .‬يجب إنشاؤها هي بدورها‪.‬‬

‫كنظرة أولى‪ ،‬أقول بأنك نحتاج إلى دوال لـكي ‪:‬‬

‫• تهي ّئ القائمة‪.‬‬

‫• تضيف عنصرا ً إليها‪.‬‬

‫• تحذف عنصرا ً منها‪.‬‬

‫• تُظهر محتواها‪.‬‬

‫• تحذف القائمة بأكملها‪.‬‬

‫يمكننا إنشاء دوال أخرى )حساب حجم القائمة مثلاً( لـكن يمكن الاستغناء عنها‪ .‬سنرك ّز الآن على الدوال التي قمتُ‬
‫الآن بتعدادها‪ ،‬هذا سي ُعطينا قاعدة جيدة‪ .‬سأدعوك بعد ذلك إلى تحقيق دوال أخرى لـكي ٺتدرّب بعدما تكون قد فهمت‬
‫المبدأ جي ّداً‪.‬‬

‫تهيئة القائمة‬

‫دالة تهيئة القائمة هي أول دالة سنحتاج إلى استدعائها‪ .‬إذ أنها تقوم بإنشاء هيكل التحكّم و أوّل عنصر من القائمة‪ .‬أقترح‬
‫عليك الدالة أسفله و التي سنعل ّق عليها بعد ذلك ‪:‬‬

‫)(‪1 List �initialization‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;))‪List �list = malloc(sizeof(�list‬‬
‫‪4‬‬ ‫;))‪Element �element = malloc(sizeof(�element‬‬
‫‪5‬‬ ‫)‪if (list == NULL || element == NULL‬‬
‫‪6‬‬ ‫{‬
‫‪7‬‬ ‫;)‪exit(EXIT_FAILURE‬‬
‫‪8‬‬ ‫}‬
‫‪9‬‬ ‫;‪element−>number = 0‬‬
‫‪10‬‬ ‫;‪element−>next = NULL‬‬
‫‪11‬‬ ‫;‪list−>first = element‬‬
‫‪12‬‬ ‫;‪return list‬‬
‫} ‪13‬‬

‫نبدأ بإنشاء هيكل التحكّم ‪. list‬‬

‫م‬
‫لاحظ أن نوع البيانات هو ‪ List‬و أن المتغير يسمى ‪ . list‬تسمح طر يقة كتابة الحرف الأول بالتفر يق بينهما‪.‬‬

‫‪459‬‬
‫الفصل ‪ .29‬القوائم المتسلسلة )‪(Linked lists‬‬

‫حسوب تلقائيا ًباستعمال )‪. sizeof(*list‬‬


‫ٌ‬ ‫نحجز بطر يقة حي ّة هيكل التحكّم باستعمال ‪ . malloc‬الحجم الذي نحجزه م‬
‫سيعرف الجهاز بأنه سيحجز المكان الكافي لتخزين الهيكل ‪. List‬‬

‫م‬
‫كان بإمكاننا أن نكت ُب أيضا ً )‪ ، sizeof(List‬لـكن إن أردنا لاحقا ً تغيير نوع المؤش ّر ‪ list‬سيكون علينا‬
‫تحديث الـ ‪ sizeof‬كذلك‪.‬‬

‫ي قد تم ّ بنجاح‪ .‬في حالة خطأ‪،‬‬


‫نحجز أيضا ًبنفس الطر يقة الذاكرة اللازمة لتخزين أول عنصر‪ .‬نتأكد من أن الحجز الح ّ‬
‫نوقف البرنامج حالا ًباستدعاء الدالة ‪. exit‬‬

‫ل شيء على ما ي ُرام‪ ،‬نعر ّف قيم أول عنصر من القائمة المتسلسلة ‪:‬‬
‫أما إن تم ّ ك ّ‬

‫• يتم إعطاء ‪ 0‬للمتغير ‪ number‬افتراضياً‪.‬‬

‫• المؤش ّر ‪ next‬يؤش ّر نحو ‪ NULL‬لأن أول عنصر في القائمة هو أيضا ً آخر واحد لح ّد الآن‪ .‬كما رأينا سابقاً‪ ،‬يجب‬
‫على آخر عنصر أن يؤش ّر نحو ‪ NULL‬ليشير إلى نهاية القائمة‪.‬‬

‫لقد نجحنا الآن في إنشاء قائمة في الذاكرة متكو ّنة من عنصر واحد و لها الشكل التالي ‪:‬‬

‫إضافة عنصر‬

‫هنا‪ ،‬تبدأ الأمور في التعقد قليلاً‪ .‬أين سنضيف عنصرا َ جديدا ً ؟ في بداية القائمة‪ ،‬في نهايتها أو في الوسط ؟‬

‫الإجابة هي أنه لدينا الخيار‪ .‬سنكون أحرارا ً في اختيار ما نريد‪ .‬في هذا الفصل‪ ،‬أقترح عليك أن نتعل ّم كيفية إضافة‬
‫عنصر إلى بداية القائمة‪ .‬من ناحية‪ ،‬هذا الأمر سهل الفهم‪ ،‬و من ناحية أخرى سأعطيك الفرصة في نهاية الفصل في التفكير‬
‫في طر يقة إنشاء دالة تضيف ع ُنص ُرا ً في مكان محدد من القائمة‪.‬‬

‫يجدر بنا إنشاء دالة قادرة على أن تضيف ع ُنصرا ً جديدا ً إلى بداية القائمة‪ .‬و لـكي نفهم أكثر‪ ،‬تخي ّل أننا في حالة مشابهة‬
‫لما تعرضه الصورة الموالية ‪ :‬ٺتكون القائمة من ثلاثة عناصر و نريد أن نضيف لها ع ُنصرا ً جديدا في البداية ‪:‬‬

‫‪460‬‬
‫‪ .3.29‬دوال معالجة القوائم المتسلسلة‬

‫يجب أن نقوم بتغيير وضعية المؤش ّر ‪ first‬الخاص بالقائمة و أيضا ً المؤش ّر ‪ next‬الخاص بالعنصر الجديد لـكي‬
‫”ندرج” هذا الأخير بشكل صحيح في القائمة‪ .‬أقترح عليك هذه الشفرة المصدر ية التي سنحللها لاحقا ً‪:‬‬

‫)‪1 void insertion(List �list, int newNumber‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪// Creating a new element‬‬
‫‪4‬‬ ‫;))‪Element �new = malloc(sizeof(�new‬‬
‫‪5‬‬ ‫)‪if (list == NULL || new== NULL‬‬
‫‪6‬‬ ‫{‬
‫‪7‬‬ ‫;)‪exit(EXIT_FAILURE‬‬
‫‪8‬‬ ‫}‬
‫‪9‬‬ ‫;‪new−>number = newNumber‬‬
‫‪10‬‬ ‫‪// Inserting the element at the beginning of the list‬‬
‫‪11‬‬ ‫;‪new−>next = list−>first‬‬
‫‪12‬‬ ‫;‪list−>first = new‬‬
‫} ‪13‬‬

‫تأخذ الدالة ‪ insertion‬كمعاملات ‪ :‬عنصر التحكّم في القائمة )الذي يحتوي على عنوان أول عنصر( و العدد الذي‬
‫نريد تخزينه في العنصر الجديد الذي سنقوم بإنشائه‪.‬‬

‫سنقوم أولا بحجز المكان اللازم لتخزين العنصر الجديد و نضع به العدد ‪ . newNumber‬تبقى إذا المرحلة الحساسة ‪:‬‬
‫إدراج العنصر الجديد في القائمة المتسلسلة‪.‬‬

‫لقد اخترنا هنا‪ ،‬تسهيلا ً للعملية‪ ،‬إضافة العنصر إلى بداية القائمة‪ .‬لـكي نحدّث المؤش ّرات بشكل صحيح‪ ،‬سنعتمد على‬
‫الخطوتين التاليتين بهذا الترتيب المحدد ‪:‬‬

‫‪ .1‬تأشير العنصر الجديد نحو العنصر الذي سيليه مستقبلاً‪ ،‬أي العنصر الحالي الأول من القائمة‪.‬‬

‫‪ .2‬تأشير المؤش ّر ‪ first‬نحو العنصر الجديد‪.‬‬

‫!‬
‫لا يمكننا القيام بهاتين الخطوتين في الترتيب المعاكس ! في الواقع‪ ،‬إن قمنا بجعل المؤش ّر ‪ first‬يؤش ّر أولا نحو‬
‫العنصر الجديد‪ ،‬سنخسر عنوان العنصر الأول من القائمة ! جرّب ذلك‪ ،‬و ستفهم بعد ذلك لم َ عكس الخطوتين‬
‫أمر مستحيل‪.‬‬

‫بإتباع الخطوتين سنتمكن من إدراج العنصر الجديد بشكل صحيح إلى القائمة المتسلسلة ‪:‬‬

‫‪461‬‬
‫الفصل ‪ .29‬القوائم المتسلسلة )‪(Linked lists‬‬

‫حذف عنصر‬

‫و نفس الشيء بالنسبة للإضافة‪ ،‬سنرك ّز الآن على عملية حذف أول عنصر من القائمة‪ .‬تقنيا‪ ،‬يُسمح بمسح عنصر متواجد في‬
‫وضعية محددة من وسط القائمة‪ ،‬سيكون هذا واحدا من التمارين التي أقترحها عليك في نهاية الفصل‪.‬‬

‫عملية حذف عنصر من القائمة المتسلسلة لا تطرح مشكلا ً إضافياً‪ .‬يجب فقط أن نجري التغييرات على المؤش ّرات في‬
‫الترتيب الصحيح لـكي لا ”نخسر” أية معلومة‪.‬‬
‫)‪1 void deletion(List �list‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫)‪if (list == NULL‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;)‪exit(EXIT_FAILURE‬‬
‫‪6‬‬ ‫}‬
‫‪7‬‬ ‫)‪if (list−>first != NULL‬‬
‫‪8‬‬ ‫{‬
‫‪9‬‬ ‫;‪Element �toDelete = list−>first‬‬
‫‪10‬‬ ‫;‪list−>first = list−>first−>next‬‬
‫‪11‬‬ ‫;)‪free(toDelete‬‬
‫‪12‬‬ ‫}‬
‫} ‪13‬‬

‫نبدأ بالتأكد من أن المؤش ّر الذي استقبلناه لا يساوي ‪ ، NULL‬و إلا فلن نتمكّن من العمل‪ .‬نتأكد بعد ذلك إذا كان‬
‫هناك على الأقل عنصر واحد في القائمة و إلا فلا يوجد أي شيء لنقوم به‪.‬‬

‫بعد الانتهاء من هذه الاختبارات‪ ،‬يمكننا حفظ عنوان العنصر الذي نريد حذفه في مؤش ّر نسميه ‪ . toDelete‬يؤش ّر‬
‫بعد ذلك المؤش ّر ‪ first‬نحو العنصر الجديد الأول‪ ،‬و الذي هو حاليا ًفي الوضعية الثانية من القائمة المتسلسلة‪.‬‬

‫لا يبقى إلا تحرير العنصر الموافق للمؤش ّر ‪ toDelete‬باستعمال الدالة ‪: free‬‬

‫هذه الدالة قصيرة لـكن هل يمكنك إعادة كتابتها لوحدك ؟ يجب أن نفهم جيدا ً بأننا يجب أن نقوم بالعمل اتّباعا ً‬
‫لخطوات محددة ‪:‬‬

‫‪ .1‬تأشير ‪ first‬نحو العنصر الثاني‪.‬‬

‫‪ .2‬مسح العنصر الأول باستعمال ‪. free‬‬

‫إذا قُمنا بالعكس‪ ،‬سنخسر عنوان العنصر الثاني !‬

‫‪462‬‬
‫‪ .4.29‬اذهب بعيدا‬

‫إظهار محتوى القائمة المتسلسلة‬

‫لـكي نرى بشكل واضح محتوى القائمة المتسلسلة‪ ،‬سيكون من الأمثل أن نكتب دالة عرض ! يكفي أن ننطلق من العنصر‬
‫الأول و إظهار العناصر واحدا ً تلو الآخر بـ”القفز” من كتلة لأخرى‪.‬‬

‫)‪1 void displayList(List �list‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫)‪if (list == NULL‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;)‪exit(EXIT_FAILURE‬‬
‫‪6‬‬ ‫}‬
‫‪7‬‬ ‫;‪Element �current = list−>first‬‬
‫‪8‬‬ ‫)‪while (current != NULL‬‬
‫‪9‬‬ ‫{‬
‫‪10‬‬ ‫;)‪printf(”%d −> ”, current−>number‬‬
‫‪11‬‬ ‫;‪current = current−>next‬‬
‫‪12‬‬ ‫}‬
‫‪13‬‬ ‫;)”‪printf(”NULL\n‬‬
‫} ‪14‬‬

‫ل عنصر )عدد(‪ .‬نستفيد من المؤش ّر ‪ next‬لننتقل‬


‫هذه الدالة بسيطة ‪ :‬ننطلق من العنصر الأول و نُظهر محتوى ك ّ‬
‫ل مرة‪.‬‬
‫إلى العنصر المُوالي في ك ّ‬

‫يمكننا أن نستمتع بتجريب إنشاء قائمتنا المتسلسلة و إظهارها في الـ ‪: main‬‬

‫)(‪1 int main‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;)(‪List �myList = initialization‬‬
‫‪4‬‬ ‫;)‪insertion(myList, 4‬‬
‫‪5‬‬ ‫;)‪insertion(myList, 8‬‬
‫‪6‬‬ ‫;)‪insertion(myList, 15‬‬
‫‪7‬‬ ‫;)‪deletion(myList‬‬
‫‪8‬‬ ‫;)‪displayList(myList‬‬
‫‪9‬‬ ‫;‪return 0‬‬
‫} ‪10‬‬

‫بالإضافة إلى العنصر الأول )و الذي تركناه هنا يحمل القيمة ‪ ،(0‬نضيف ثلاثة عناصر جديدة لهذه القائمة‪ .‬ثم نقوم‬
‫بحذف عنصر واحد‪ .‬في النهاية‪ ،‬يتم إظهار محتوى القائمة المتسلسلة بالشكل التالي ‪:‬‬

‫‪8‬‬ ‫‪−> 4 −> 0 −> NULL‬‬

‫اذهب بعيدا‬ ‫‪4.29‬‬

‫لقد قُمنا الآن بكتابة الدوال اللازمة للتحكّم في قائمة متسلسلة ‪ :‬التهيئة‪ ،‬إضافة عنصر‪ ،‬حذف عنصر‪ ،‬إلخ‪ .‬إليك بعض‬
‫الدوال التي تنقص و التي أدعوك إلى كتابتها‪ ،‬سيكون هذا بمثابة تمرين جيد لك !‬

‫‪463‬‬
‫الفصل ‪ .29‬القوائم المتسلسلة )‪(Linked lists‬‬

‫كاف بشكل عام‪ .‬أما إن‬


‫ٍ‬ ‫• إضافة عنصر في وسط القائمة ‪ :‬حالياً‪ ،‬لا يمكننا إضافة عناصر إلا في بداية القائمة‪ ،‬هذا‬
‫أردنا إضافة عنصر إلى منتصف القائمة‪ ،‬سيكون علينا أن نكتب دالة تأخذ معاملا إضافي ّا ‪ :‬عنوان العنصر الذي‬
‫يسبق العنصر الجديد في القائمة‪ .‬ستقوم الدالة بالتقدّم في القائمة إلى حين الوصول إلى العنصر المُراد و تقوم بإضافة‬
‫العنصر الجديد بعده مباشرة‪.‬‬

‫• حذف عنصر من وسط القائمة ‪ :‬المبدأ نفسه بالنسبة للإضافة في وسط القائمة‪ .‬هذه المرة‪ ،‬يجب عليك أن تضيف‬
‫معاملا يمثل عنوان العنصر الذي نريد حذفه‪.‬‬

‫• تدمير القائمة ‪ :‬يكفي أن نقوم بحذف كل العناصر واحدا ً تلو الآخر !‬

‫• حجم السلسلة ‪ :‬تشير هذه الدالة إلى كم من عنصر ٺتكون القائمة المتسلسلة‪ .‬الأمثل‪ ،‬و في عوض أن يتم حساب هذه‬
‫ل مرة‪ ،‬هو أن نضيف عددا صحيحا ‪ nbOfElements‬إلى الهيكل ‪ . List‬يكفي أن نزيد من قيمته‬
‫القيمة في ك ّ‬
‫ل مرة نحذف عنصرا ً منها‪.‬‬
‫ل مرة نضيف فيها عنصرا ً جديدا ً للقائمة و أن ننقص من قيمته في ك ّ‬
‫في ك ّ‬

‫ل دوال معالجة القوائم المتسلسلة في ملفين ‪ linked_list.c‬و ‪ linked_list.h‬مثلاً‪ .‬ستكون‬


‫أنصحك بجمع ك ّ‬
‫ل برامجك الأخرى التي تحتاج فيها إلى القوائم المتسلسلة‪.‬‬
‫أوّل مكتبة تكتبها بنفسك ! يمكنك إعادة استعمالها في ك ّ‬

‫يمكنك تنز يل مشروع القوائم المتسلسلة الذي يحتوي الدوال التي اكتشفناها سو ياً‪ .‬ستكون هذه بمثابة قاعدة جيدة لك‪.‬‬

‫‪http://www.siteduzero.com/uploads/fr/ftp/mateo21/c/listes_chainees.zip‬‬

‫ملخّ ص‬

‫• تشكّل القوائم المتسلسلة طر يقة جديدة لتخزين البيانات في الذاكرة‪ .‬هي أكثر مرونة من الجداول لأنها تمكّننا من‬
‫إضافة و حذف ”خانات” في أي لحظة نريد‪.‬‬

‫• لا تحتوي لغة الـ‪ C‬على نظام تحكّم في القوائم المتسلسلة‪ ،‬إذ يجب أن نكتبه بأنفسنا ! يعتبر هذا طر يقة ممتازة للتقدّم‬
‫في الخوارزميات و البرمجة بشكل عام‪.‬‬

‫• في قائمة متسلسلة‪ ،‬كل عنصر هو عبارة عن هيكل يحتوي عنوان العنصر الموالي‪.‬‬

‫• يُنصح بإنشاء هيكل تحكّم )من نوع ‪ List‬في حالتنا هذه( يحتوي عنوان أول عنصر في القائمة‪.‬‬

‫• توجد نسخة محسّنة ‪ -‬لـكن أكثر تعقيدا ً ‪ -‬من القوائم المتسلسلة و نسمّيها ”القوائم مزدوجة التسلسل”‪ ،‬و التي يحتوي‬
‫ل عنصر فيها على عنوان العنصر السابق أيضاً‪.‬‬
‫ك ّ‬

‫‪464‬‬
‫الفصل ‪30‬‬

‫المكدّسات و الطوابير )‪(Stacks and Queues‬‬

‫لقد اكتشفنا مع القوائم المتسلسلة طر يقة جديدة أكثر مرونة من الجداول لتخزين البيانات‪ .‬هذه القوائم مرنة بشكل‬
‫خاص لأنه يمكننا أن نُدرج فيها و نحذف منها بيانات من أي مكان أردنا و في أية لحظة‪.‬‬

‫المكدّسات و الطوابير التي سنكتشفها هنا هما شكلان خاصّان نوعا ً ما من القوائم المتسلسلة‪ .‬فهما تسمحان بالتحكّم‬
‫بالطر يقة التي تُضاف بها العناصر الجديدة إليها‪ .‬هذه المرة لن نقوم بإضافة عناصر جديدة في وسط القائمة‪ ،‬بل فقط في‬
‫البداية أو النهاية‪.‬‬
‫المكدّسات و الطوابير تعتبران مفيدتان للغاية من أجل البرامج التي تحلل المعطيات ال ّتي تصل بالتدريج‪ .‬سنرى بالتفصيل‬
‫كيف تعملان في هذا الفصل‪.‬‬

‫ٺتشابه المكدّسات و الطوابير كثيراً‪ ،‬لـكنهما تختلفان اختلافا ً بسيطا ً ستتعرف عليه بسرعة‪ .‬سنكتشف أولا ً المكدّسات‬
‫و التي ستذك ّرك بالقوائم المت ّصلة بشكل كبير‪.‬‬

‫بشكل عام‪ ،‬سيكون هذا الفصل بسيطا ً إذا كنت قد فهمت جي ّدا ً كيفية عمل القوائم المتسلسلة‪ .‬إن لم تكن هذه‬
‫حالتك‪ ،‬فأعد قراءة الفصل السابق لأننا سنحتاج إليه‪.‬‬

‫المكدّسات )‪(Stacks‬‬ ‫‪1.30‬‬

‫تخي ّل مكدّسا ًللقطع النقدية )الصورة التالية(‪ .‬يمكنك إضافة قطع أخرى واحدة تلو الأخرى في أعلى المكدّس‪ ،‬و يمكنك‬
‫أيضا ًنزع القطع من أعلى المكدّس‪ .‬بالمقابل‪ ،‬لا يمكن نزع قطعة من أسفل المكدّس‪ .‬إن أردت التجريب‪ ،‬أتمنى لك حظًا ً‬
‫موف ّقا ً!‬

‫‪465‬‬
‫الفصل ‪ .30‬المكدّسات و الطوابير )‪(Stacks and Queues‬‬

‫كيفية عمل المكدّسات‬

‫ينص على تخزين البيانات مع وصولها على التوالي واحدة فوق الأخرى لـكي نستطيع‬
‫مبدأ عمل المكدّسات في البرمجة ّ‬
‫استرجاعها فيما بعد‪ .‬مثلا‪ ،‬تخي ّل مكدّسا ً للأعداد الصحيحة من نوع ‪) int‬الصورة الموالية(‪ .‬لو أضيف عنصرا ً )نتكلّم‬
‫عن التكديس(‪ ،‬فستتم إضافته في أعلى المكدّس‪ ،‬تماما ً كما في لعبة ‪: Tetris‬‬

‫الأكثر أهمية هو وجود عملية تقوم باستخراج الأعداد من المكدّس‪ .‬نحن نتكلّم عن إلغاء التكديس‪ .‬نسترجع القيم‬
‫واحدة تلو الأخرى‪ ،‬بدء ً من الأخيرة الموضوعة أعلى المكدّس )الصورة الموالية(‪ .‬ننزع البيانات على التوالي حتى نصل إلى‬
‫قاع المكدّس‪.‬‬

‫نسمي هذا بخوارزمية ‪ ،LIFO‬و التي تعني ”‪ .”Last In First Out‬الترجمة ‪” :‬آخر ع ُنصر تمت إضافته‪ ،‬هو أول عنصر‬
‫يخرج”‪.‬‬

‫‪466‬‬
‫‪ .1.30‬المكدّسات )‪(Stacks‬‬

‫عناصر المكدّس مرتبطة ببعضها بطر يقة القوائم المتسلسلة‪ .‬فهي تحمل مؤش ّرا ً نحو العنصر الموالي و لا تتموضع بالضرورة‬
‫بجنب بعضها في الذاكرة‪ .‬يجب على آخر ع ُنصر )في أقصى أسفل المكدّس( أن يؤش ّر نحو ‪ NULL‬لـكي يقول أنّنا ‪ ...‬لمسنا‬
‫القاع ‪:‬‬

‫؟‬
‫ل هذا‪ ،‬واقعي ّا ؟‬
‫فيما ينفع ك ّ‬

‫توجد برامج تحتاج فيها إلى تخزين اليبانات مؤق ّتا ًلإخراجها اعتمادا ً على ترتيب محدد ‪ :‬يجب على آخر ع ُنصر أدخلته أن‬
‫يخرج هو الأول‪.‬‬

‫لأعطي مثالا ً واقعياً‪ ،‬يستعمل نظام التشغيل في حاسوبك هذا النوع من الخوارزميات لـكي يتذكر الترتيب الذي تم‬
‫استدعاء الدوال فيه‪ .‬تخي ّل مثالا ‪:‬‬

‫مرة(‪.‬‬
‫‪ .1‬يبدأ البرنامج بالدالة ‪) main‬مثل كل ّ‬
‫‪ .2‬تستدعي فيها الدالة ‪. play‬‬

‫‪ .3‬تقوم هذه الدالة ‪ play‬بدورها باستدعاء الدالة ‪. load‬‬

‫‪ .4‬ما إن تنتهي الدالة ‪ ، load‬نعود إلى الدالة ‪. play‬‬

‫‪ .5‬ما إن تنتهي الدالة ‪ ، play‬نعود إلى الدالة ‪. main‬‬

‫‪ .6‬أخيراً‪ ،‬ما إن تنتهي الدالة ‪ . main‬لا تبقى أية دالة تحتاج إلى الاستدعاء‪ ،‬ينتهي البرنامج‪.‬‬

‫لـكي ”يتذك ّر” الترتيب الذي تم فيه استدعاء الدوال‪ ،‬يُنشئ الحاسوب مكدّسا ًلهذه الدوال على التوالي ‪:‬‬

‫‪467‬‬
‫الفصل ‪ .30‬المكدّسات و الطوابير )‪(Stacks and Queues‬‬

‫هذا مثال واقعيّ عن استعمال المكدّسات‪ .‬و بفضل هذه التقنية يعرف الجهاز الآن إلى أي دالة يجب عليه أن يعود‪.‬‬
‫يمكنه أن يكدّس ‪ 100‬استدعاء للدوال إن وجب الأمر‪ ،‬لـكن ّه سيرجع ليجد الدالة الرئيسية في أسفل المكدّس !‬

‫إنشاء نظام مكدّس‬

‫و الآن بما أننا فهمنا مبدأ عمل المكدّسات‪ ،‬فلنحاول بناء واحد‪ .‬تماما مثل القوائم المتسلسلة‪ ،‬لا يوجد نظام مكدّس متضمّن‬
‫في لغة الـ‪ .C‬يجب أن ننشئه بأنفسنا‪.‬‬

‫سيكون لكل عنصر من المكدّس هيكل مماثل للهيكل الخاص بالقائمة المتسلسلة ‪:‬‬

‫‪1‬‬ ‫;‪typedef struct Element Element‬‬


‫‪2‬‬ ‫‪struct Element‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;‪int number‬‬
‫‪5‬‬ ‫;‪Element �next‬‬
‫‪6‬‬ ‫;}‬

‫يحتوي هيكل التحكّم على عنوان أول عنصر من المكدس‪ ،‬أي العنصر المتواجد في الأعلى ‪:‬‬

‫‪1‬‬ ‫;‪typedef struct Stack Stack‬‬


‫‪2‬‬ ‫‪struct Stack‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;‪Element �first‬‬
‫‪5‬‬ ‫;}‬

‫سنحتاج ككل إلى الدوال التالية ‪:‬‬

‫• تكديس عنصر‪.‬‬

‫• إلغاء تكديس عنصر‪.‬‬

‫ستلاحظ أنه‪ ،‬على خلاف القوائم المتسلسلة‪ ،‬لا نتكلّم لا عن الإضافة و لا عن الحذف‪ .‬نتكلّم عن التكديس و إلغاء‬
‫التكديس‪ .‬لأن هاتين العمليتين محدودتان على عنصر واحد محدد‪ ،‬كما رأينا‪ .‬بهذا‪ ،‬يمكننا إضافة و نزع عنصر من المكدّس‬
‫من الأعلى فقط‪.‬‬

‫يمكننا أيضا ًكتابة دالة لإظهار محتوى المكدّس‪ ،‬أمر عمليّ للتأكد من أن البرنامج يعمل بشكل صحيح‪.‬‬

‫هيا بنا !‬

‫‪468‬‬
‫‪ .1.30‬المكدّسات )‪(Stacks‬‬

‫التكديس )‪(stacking‬‬

‫يجدر بدالتنا ‪ stack‬أن تأخذ كمعاملين هيكل التحكّم في المكدّس )من نوع ‪ ( Stack‬و العدد الجديد لتخزينه‪.‬‬
‫أذك ّرك بأننا نخز ّن هنا أعدادا ً صحيحة ‪ ، int‬لـكن لا شيء يمنعنا من تعديل هذه الأمثلة لأنواع أخرى من البيانات‪ .‬يمكننا‬
‫تخزين أي شيء ‪ :‬أعداد ‪ ، double‬محارف ‪ ، char‬سلاسل محارف‪ ،‬جداول و حتى هياكل أخرى !‬

‫)‪1 void stack(Stack �stk, int newNumber‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫;))‪Element �new = malloc(sizeof(�new‬‬
‫‪4‬‬ ‫)‪if (stk == NULL || new == NULL‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;)‪exit(EXIT_FAILURE‬‬
‫‪7‬‬ ‫}‬
‫‪8‬‬ ‫;‪new−>number = newNumber‬‬
‫‪9‬‬ ‫;‪new−>next = stk−>first‬‬
‫‪10‬‬ ‫;‪stk−>first = new‬‬
‫} ‪11‬‬

‫تتم الإضافة في بداية المكدّس لأنه‪ ،‬كما رأينا‪ ،‬يستحيل القيام بذلك في المنتصف‪ .‬هذا مبدأ عمل المكدّسات‪ ،‬نضيف‬
‫دائما من الأعلى‪.‬‬
‫بهذا‪ ،‬على عكس القوائم المتسلسلة‪ ،‬لا يجب أن ننشئ دالة لإدراج عنصر في منتصف المكدّس‪ .‬يجب أن تكون الدالة‬
‫‪ stack‬هي الوحيدة ال ّتي يمكنها إضافة عناصر جديدة للمكدس‪.‬‬

‫إلغاء التكديس )‪(unstacking‬‬

‫دور دالة إلغاء التكديس هو حذف العنصر المتواجد في أعلى المكدّس‪ ،‬قد تشك في ذلك‪ .‬لـكن يجب على هذه الدالة أيضا‬
‫أن ت ُرجع إلينا العنصر الذي حذفته‪ .‬في حالتنا‪ ،‬هو العدد الّذي كان موجودا في أعلى المكدّس‪.‬‬

‫هذه هي الطر يقة التي نصل بها إلى عناصر المكدّس ‪ :‬ننزعها واحدا ً تلو الآخر‪ .‬نحن لا نتقدّم فيها باحثين عن الوصول‬
‫إلى ثاني و ثالث عنصر‪ .‬بل نطلب دائما ًاسترجاع على أول عنصر‪.‬‬

‫دالتنا ‪ unstack‬ست ُرجع إذا ‪ int‬يوافق العدد المتواجد في رأس المكدّس ‪:‬‬

‫)‪1 int unstack(Stack �stack‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫)‪if (stack == NULL‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;)‪exit(EXIT_FAILURE‬‬
‫‪6‬‬ ‫}‬
‫‪7‬‬ ‫;‪int unstackedNumber = 0‬‬
‫‪8‬‬ ‫;‪Element �unstackedElement = stack−>first‬‬
‫‪9‬‬ ‫)‪if (stack != NULL1 && stack−>first != NULL‬‬
‫‪10‬‬ ‫{‬
‫‪11‬‬ ‫;‪unstackedNumber = unstackedElement−>number‬‬

‫‪469‬‬
‫الفصل ‪ .30‬المكدّسات و الطوابير )‪(Stacks and Queues‬‬

‫‪12‬‬ ‫;‪stack−>first = unstackedElement−>next‬‬


‫‪13‬‬ ‫;)‪free(unstackedElement‬‬
‫‪14‬‬ ‫}‬
‫‪15‬‬ ‫;‪return unstackedNumber‬‬
‫} ‪16‬‬

‫‪1‬ملاحظة م ُرَاجِـع الكتاب‬

‫‪ ،‬فَب ِو ُصول البرنامج إلى ذلك السطر سيكون هذا الجزء‬ ‫‪stack != NULL‬‬ ‫يبدو لي أن مؤل ّف الكتاب قد أضاف جزء ً عديم الفائدة في الشرط الثاني ‪:‬‬
‫(‪ .‬و حتى عند عدم وجود الشرط الأوّل‪،‬‬ ‫)‪if(stack == NULL‬‬ ‫ل تأكيد لأنّنا قد تحقّقنا من عكسه في الشرط الأوّل )‬
‫من الشرط خاطئا بك ّ‬
‫لا يملك أي ّة مركّبات !‬ ‫‪NULL‬‬ ‫ن مؤش ّرا ً‬
‫كانت ستعطّل البرنامج لأ ّ‬ ‫‪stack->first‬‬ ‫فالتعليمة‬

‫نسترجع العدد الذي في رأس المكدّس لنبعثه في نهاية الدالة‪ .‬نعدّل عنوان أول عنصر من المكدّس بما أن هذا الأخير‬
‫يتغير‪ .‬أخيراً‪ ،‬نحذف بالتأكيد رأس المكدّس القديم باستعمال ‪. free‬‬

‫إظهار محتوى المكدّس‬

‫بالرغم من أن هذه الدالة غير ضرور ي ّة )الدالتان ‪ stack‬و ‪ unstack‬كافيتان للتحكّم في المكدّس !(‪ ،‬ستكون مهمّة‬
‫لاختبار عمل المكدّس و خاصّة ”لمعاينة” النتيجة ‪:‬‬

‫)‪1 void displayStack(Stack �stack‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫)‪if (stack == NULL‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;)‪exit(EXIT_FAILURE‬‬
‫‪6‬‬ ‫}‬
‫‪7‬‬ ‫;‪Element �current = stack−>first‬‬
‫‪8‬‬ ‫)‪while (current != NULL‬‬
‫‪9‬‬ ‫{‬
‫‪10‬‬ ‫;)‪printf(”%d\n”, current−>number‬‬
‫‪11‬‬ ‫;‪current = current −>next‬‬
‫‪12‬‬ ‫}‬
‫‪13‬‬ ‫;)”‪printf(”\n‬‬
‫} ‪14‬‬

‫بما أن هذه الدالة بسيطة بسخافة‪ ،‬فهي لا تحتاج شرحاً‪.‬‬

‫بالمقابل‪ ،‬حان الآن إذا ً لكتابة الدالة الرئيسية لاختبار سلوك مكدّسنا ‪:‬‬

‫)(‪1 int main‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫‪Stack �myStack‬‬ ‫‪= initialization(); // See the previous chapter‬‬
‫‪4‬‬ ‫‪stack(myStack,‬‬ ‫;)‪4‬‬
‫‪5‬‬ ‫‪stack(myStack,‬‬ ‫;)‪8‬‬
‫‪6‬‬ ‫‪stack(myStack,‬‬ ‫;)‪15‬‬

‫‪470‬‬
‫‪ .2.30‬الطوابير )‪(Queues‬‬

‫‪7‬‬ ‫;)‪stack(myStack, 16‬‬


‫‪8‬‬ ‫;)‪stack(myStack, 23‬‬
‫‪9‬‬ ‫;)‪stack(myStack, 42‬‬
‫‪10‬‬ ‫;)”‪printf(”\nStack’s state :\n‬‬
‫‪11‬‬ ‫;)‪displayStack(myStack‬‬
‫‪12‬‬ ‫;))‪printf(”I unstack %d\n”, unstack(myStack‬‬
‫‪13‬‬ ‫;))‪printf(”I unstack %d\n”, stack(myStack‬‬
‫‪14‬‬ ‫;)”‪printf(”\nStack’s state :\n‬‬
‫‪15‬‬ ‫;)‪displayStack(myStack‬‬
‫‪16‬‬ ‫;‪return 0‬‬
‫} ‪17‬‬

‫نُظهر حالة المكدّس بعد الـكثير من التكديس و مرة أخرى بعد كثير من إلغاء التكديس‪ .‬نُظهر أيضا ً العدد الذي قُمنا‬
‫ل مرة نقوم بإلغاء التكديس‪ .‬النتيجة في الـكونسول هي التالية ‪:‬‬
‫بحذفه في ك ّ‬

‫‪Stack’s state :‬‬


‫‪42‬‬
‫‪23‬‬
‫‪16‬‬
‫‪15‬‬
‫‪8‬‬
‫‪4‬‬

‫‪I unstack 42‬‬


‫‪I unstack 23‬‬

‫‪Stack’s state :‬‬


‫‪16‬‬
‫‪15‬‬
‫‪8‬‬
‫‪4‬‬

‫تأكّد أنك تفهم جي ّدا ً ما يحصل في البرنامج‪ .‬إذا فهمت هذا‪ ،‬فقد فهمت كيفية عمل المكدّسات !‬

‫يمكنك تنز يل مشروع المكدّسات كاملا ًلو أردت ‪:‬‬

‫‪http://www.siteduzero.com/uploads/fr/ftp/mateo21/c/piles.zip‬‬

‫الطوابير )‪(Queues‬‬ ‫‪2.30‬‬

‫تشبه الطوابير المكدّسات كثيراً‪ ،‬إلا أنها تعمل بالإتجاه المعاكس !‬

‫كيفية عمل الطوابير‬

‫في هذا النظام‪ ،‬يتم وضع العناصر الواحد بعد َ الآخر‪ .‬أوّل عنصر يخرج من الطابور هو أول عنصر يدخل إليه‪ .‬نتكلّم هنا‬
‫عن خوارزمية ‪ ،(First In First Out) FIFO‬و هذا يعني ‪” :‬أول من يصل هو أول من يخرج”‪.‬‬

‫‪471‬‬
‫الفصل ‪ .30‬المكدّسات و الطوابير )‪(Stacks and Queues‬‬

‫تسهل المشابهة بالحياة اليومي ّة‪ .‬حينما تشتري تذكرة لمشاهدة السينما‪ ،‬تقف في طابور شب ّاك التذاكر )الصورة الموالية(‪.‬‬
‫ل الآخرين‪ .‬أول الواصلين‬
‫باستثناء إن كنت أحد إخوة بائع التذاكر‪ ،‬يجدر بك الوقوف في الطابور و انتظار دورك مثل ك ّ‬
‫هو أول من تتم خدمته‪.‬‬

‫محادثة‪ ،‬لو ٺتلقى‬


‫في البرمجة‪ ،‬تفيد الطوابير في إيقاف مؤق ّت للمعلومات حسب الترتيب الذي وصلت به‪ .‬مثلاً‪ ،‬في برنامج ُ‬
‫ثلاثة رسائل يفصلها فارق زمني قصير جداً‪ ،‬يتم ص ُ ّفها في طابور واحدة تلو الأخرى في الذاكرة‪ .‬ثم يتم إظهار أوّل رسالة‬
‫وصلت ثم الثانية و هكذا‪.‬‬

‫يتم تخزين الأحداث التي تبعثها المكتبة ‪ SDL‬التي قُمنا بدراستها أيضا في طابور‪ .‬إذا حرّكت الفأرة‪ ،‬يتم توليد حدث‬
‫ل مرة‬
‫ل بيكسل تحر ّك فوقه مؤش ّر الفأرة‪ .‬تخزن الـ‪ SDL‬الأحداث في طابور ثم تبعثها لنا واحدا واحدا في ك ّ‬
‫من أجل ك ّ‬
‫نستدعي فيها الدالة ‪) SDL_PollEvent‬أو ‪.( SDL_WaitEvent‬‬

‫ل عنصر فيها بالتأشير على العنصر الموالي‪ ،‬تماما ًمثل المكدّسات‪ .‬آخر‬
‫في لغة الـ‪ .C‬الطابور هو قائمة متسلسلة أين يقوم ك ّ‬
‫عنصر من الطابور يؤش ّر نحو ‪: NULL‬‬

‫إنشاء نظام طابور‬

‫نظام الطابور يشبه ذلك الخاص بالمكدّسات‪ .‬يوجد اختلاف بسيط في كون أن العناصر تخرج في الإتجاه المعاكس‪ ،‬لا‬
‫يوجد شيء صعب إن كنت قد فهمت المكدّسات‪.‬‬

‫سننشئ هيكل ‪ Element‬و هيكل تحكّم ‪: Queue‬‬

‫‪1‬‬ ‫;‪typedef struct Element Element‬‬


‫‪2‬‬ ‫‪struct Element‬‬
‫‪3‬‬ ‫{‬
‫‪4‬‬ ‫;‪int number‬‬
‫‪5‬‬ ‫;‪Element �next‬‬
‫‪6‬‬ ‫;}‬
‫‪7‬‬ ‫;‪typedef struct Queue Queue‬‬

‫‪472‬‬
‫‪ .2.30‬الطوابير )‪(Queues‬‬

‫‪8 struct Queue‬‬


‫{ ‪9‬‬
‫‪10‬‬ ‫;‪Element �first‬‬
‫;} ‪11‬‬

‫تماما كالمكدّسات‪ ،‬كل عنصر من الطابور سيكون من نوع ‪ . Element‬بالإستعانة بالمؤش ّر ‪ first‬سنتوف ّر دائما ً‬
‫على العنصر الأول و يمكننا من خلاله الصعود إلى آخر عنصر‪.‬‬

‫إضافة عنصر إلى الطابور )‪(enqueuing‬‬

‫الدالة التي تضيف ع ُنص ُرا ً إلى الطابور تسمّى دالة ”الإلحاق” )‪ .(enqueuing‬توجد حالتان ‪:‬‬

‫• إما أن الطابور فارغ‪ ،‬في هذه الحالة يجب أن ننشئ الطابور بجعل المؤش ّر ‪ first‬يؤش ّر نحو العنصر الجديد الذي‬
‫نحن بصدد انشائه‪.‬‬

‫• إما أن الطابور غير فارغ‪ ،‬في هذه الحالة يجب أن نتقدّم في الطابور إنطلاقا ً من العنصر الأول حتى نصل إلى آخر‬
‫عنصر‪ .‬نضيف العنصر الجديد بعد آخر عنصر‪.‬‬

‫إليك ما يمكننا فعله عمليا ً‪:‬‬


‫)‪1 void enqueue(Queue �q, int newNumber‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;))‪Element �new = malloc(sizeof(�new‬‬
‫‪4‬‬ ‫)‪if (q == NULL || new == NULL‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;)‪exit(EXIT_FAILURE‬‬
‫‪7‬‬ ‫}‬
‫‪8‬‬ ‫;‪new−>number = newNumber‬‬
‫‪9‬‬ ‫;‪new−>next = NULL‬‬
‫‪10‬‬ ‫‪if (q−>first != NULL) // The queue is not empty‬‬
‫‪11‬‬ ‫{‬
‫‪12‬‬ ‫‪// We move to the end of the queue‬‬
‫‪13‬‬ ‫;‪Element �currentElement = q−>first‬‬
‫‪14‬‬ ‫)‪while (currentElement−>next != NULL‬‬
‫‪15‬‬ ‫{‬
‫‪16‬‬ ‫;‪currentElement = currentElement−>next‬‬
‫‪17‬‬ ‫}‬
‫‪18‬‬ ‫;‪currentElement−>next = new‬‬
‫‪19‬‬ ‫}‬
‫‪20‬‬ ‫‪else /� The queue is empty, it’s our first element �/‬‬
‫‪21‬‬ ‫{‬
‫‪22‬‬ ‫;‪q−>first = new‬‬
‫‪23‬‬ ‫}‬
‫} ‪24‬‬

‫ل منهما يجب أن تتم دراستها على حدى‪ .‬الاختلاف مقارنة ً‬


‫ترى في هذه الشفرة المصدر ية تحليل كلتا الحالتين‪ ،‬ك ّ‬
‫بالمكدّسات‪ ،‬و الذي يضيف لمسة صعوبة صغيرة‪ ،‬هو أنه يجب التموضع في نهاية الطابور لوضع العنصر الجديد‪ .‬لـكن لابأس‬
‫فحلقة ‪ while‬كافية للقيام باللازم‪ ،‬هذا ما يمكنك ملاحظته‪.‬‬

‫‪473‬‬
‫الفصل ‪ .30‬المكدّسات و الطوابير )‪(Stacks and Queues‬‬

‫إزالة عنصر من الطابور )‪(dequeuing‬‬

‫عملية إلغاء الإلحاق )‪ (dequeuing‬تشابه كثيرا ً عملية إلغاء التكديس‪ .‬بامتلاكنا مؤش ّرا نحو أول عنصر من الطابور‪ ،‬يكفي‬
‫أن ننزعه و أن ن ُرجع قيمته‪.‬‬

‫)‪1 int dequeue(Queue �queue‬‬


‫{ ‪2‬‬
‫‪3‬‬ ‫)‪if (queue == NULL‬‬
‫‪4‬‬ ‫{‬
‫‪5‬‬ ‫;)‪exit(EXIT_FAILURE‬‬
‫‪6‬‬ ‫}‬
‫‪7‬‬ ‫;‪int dequeuedNumber = 0‬‬
‫‪8‬‬ ‫‪// We verify if there’s something to dequeue‬‬
‫‪9‬‬ ‫)‪if (queue−>first != NULL‬‬
‫‪10‬‬ ‫{‬
‫‪11‬‬ ‫;‪Element �dequeuedElement = queue−>first‬‬
‫‪12‬‬ ‫;‪dequeuedNumber = dequeuedElement−>number‬‬
‫‪13‬‬ ‫;‪queue−>first = dequeuedElement−>next‬‬
‫‪14‬‬ ‫;)‪free(dequeuedElement‬‬
‫‪15‬‬ ‫}‬
‫‪16‬‬ ‫;‪return dequeuedNumber‬‬
‫} ‪17‬‬

‫حان دورك !‬

‫تبقى دالة إظهار محتوى الطابور ‪ displayQueue‬و عملها مشابه لما قمنا به مع المكدّسات‪ .‬سيسمح لك هذا بالتأكد من‬
‫سلامة عمل الطابور‪.‬‬

‫قم بعد ذلك بكتابة ‪ main‬من أجل تجريب البرنامج‪ .‬يجدر بنتيجة البرنامج أن تشبه هذه ‪:‬‬

‫‪Queue’s state :‬‬


‫‪4 8 15 16 23 42‬‬

‫‪I dequeue 4‬‬


‫‪I dequeue 8‬‬

‫‪Queue’s state :‬‬


‫‪15 16 23 42‬‬

‫يجدر أن تكون قادرا ً على إنشاء مكتبة الطوابير الخاصة بك‪ ،‬بملفات ‪ queue.h‬و ‪ queue.c‬مثلاً‪.‬‬

‫أقترح عليك تنز يل مشروع التحكّم في الطوابير كاملا ًإن أردت‪ .‬إنه يتضمّن الدالة ‪: displayQueue‬‬

‫‪http://www.siteduzero.com/uploads/fr/ftp/mateo21/c/files.zip‬‬

‫‪474‬‬
‫ملخّ ص‬

‫ملخّ ص‬

‫• تسمح المكدّسات و الطوابير بتنظيم معطيات في الذاكرة عند وصولها بالتوالي‪.‬‬

‫• تستعمل المكدّسات و الطوابير نظام قوائم متسلسلة لتجميع العناصر‪.‬‬

‫• في حالة المكدّسات‪ ،‬تتم إضافة المعطيات الواحدة فوق الأخرى‪ .‬و إن أردنا استخراج بيانة‪ ،‬فسنستخرج آخر واحدة‬
‫و التي كنا بصدد إضافتها )الأحدث(‪ .‬نتكّلم هنا عن خوارزمية ‪.(Last In First Out) LIFO‬‬

‫• في حالة الطوابير‪ ،‬تتم إضافة المعطيات الواحدة بعد الأخرى‪ .‬نقوم باستخراج البيانة الأولى و التي قمنا بإضافتها أولا‬
‫للطابور )الأقدم(‪ .‬نتكلّم عن خوارزمية ‪.(First In First Out) FIFO‬‬

‫‪475‬‬
‫الفصل ‪ .30‬المكدّسات و الطوابير )‪(Stacks and Queues‬‬

‫‪476‬‬
‫الفصل ‪31‬‬

‫جداول التجزئة )‪(Hash tables‬‬

‫للقوائم المتسلسلة نقطة ضعف كبيرة في حال أردنا قراءة محتواها ‪ :‬يستحيل الوصول إلى عنصر معيّن مباشرة‪ .‬يجب‬
‫التقدّم في القائمة عنصرا ً بعنصر حتى نجد العنصر الذي نريد‪ .‬هذا يطرح مشاكل من ناحية الأداء ما إن يكون حجم القائمة‬
‫المتسلسلة ضخماً‪ .‬تخي ّل قائمة متسلسلة ٺتكو ّن من ‪ 1000‬عنصر بينما العنصر الذي نبحث عنه موجود في آخرها !‬

‫تمث ّل جداول التجزئة طر يقة أخرى لتخزين البيانات‪ .‬حيث أنها تستند على مبدأ الجداول في لغة الـ‪ C‬و التي نعرف‬
‫التعامل معها جي ّداً‪ .‬ماهي فائدتها الـكُبرى ؟ هي تسمح بإ يجاد سر يع لعنصر محدد‪ ،‬سواء كان الجدول يحتوي ‪،100‬‬
‫‪ 10000 ،1000‬خانة أو حتى أكثر !‬

‫لماذا نستعمل جدول تجزئة ؟‬ ‫‪1.31‬‬

‫لننطلق من المشكل الذي تطرحه القوائم المتسلسلة‪ .‬هذه الأخيرة مرنة بشكل خاص‪ ،‬هذا ما استطعنا ملاحظته ‪:‬‬
‫يمكننا إضافة أو إزالة خانات في أي لحظة نريد‪ ،‬بينما يكون الجدول ”ثابتاً” ما إن يتم إنشاؤه‪.‬‬

‫لـكن‪ ،‬كما قلتُ لك في المقدّمة‪ ،‬للقوائم المتسلسلة عيب كبير ‪ :‬إذا أردنا استرجاع عنصر محدد من القائمة‪ ،‬يجب تصفّح‬
‫هذه الأخيرة حتى نجد ذلك العنصر !‬

‫ل طالب بهيكل نسميه‬


‫تخي ّل قائمة متسلسلة تحتوي معلومات حول الطل ّاب ‪ :‬الاسم‪ ،‬الع ُمر و المعدّل‪ .‬سيتم تمثيل ك ّ‬
‫‪. Student‬‬

‫م‬
‫عملنا سابقا ًعلى القوائم المتسلسلة التي تحتوي على ‪ . int‬كما قلتُ لك‪ ،‬من الممكن تخزين أي شيء نريد في قائمة‪،‬‬
‫حتى مؤش ّرا ً نحو هيكل آخر كما سأقترحه لك الآن‪.‬‬

‫ل القائمة‬
‫إذا أردتُ الوصول إلى المعلومات الخاصة بالشخص ‪ Luc Doncieux‬في الصورة الموالية‪ ،‬يجب عليّ التقدّم في ك ّ‬
‫كي أكتشف بأنه العنصر الأخير فيها !‬

‫‪477‬‬
‫الفصل ‪ .31‬جداول التجزئة )‪(Hash tables‬‬

‫م‬
‫بالفعل‪ ،‬لو أننا بحثنا عن الشخص ‪ ،Julien Lefebvre‬كان البحث ليكون أسرع بما أنه متواجد في بداية القائمة‪ .‬و‬
‫مع ذلك‪ ،‬لتقييم كفاءة الخوارزمية‪ ،‬يجب أن نفك ّر دائما ًفي أسوء الحالات‪ .‬و الأسوء هو ‪ Luc‬هنا‪.‬‬
‫هنا‪ ،‬نقول أن خوارزمية البحث لها تعقيد )‪ ،O(n) (complexity‬لأنه يجب تصفّح كل القائمة المتسلسلة للوصول‬
‫إلى الع ُنصر المراد‪ ،‬و في أسوء الحالات يكون هذا هو آخر عنصر‪ .‬إذا كانت القائمة تحتوي على ‪ 9‬عناصر‪ ،‬يجب‬
‫أن يتم تشغيل ‪ 9‬دورات للحلقة كحد أقصى لإ يجاد العنصر‪.‬‬

‫في هذا المثال‪ ،‬لا تحتوي القائمة المتسلسلة سوى على أربعة عناصر‪ .‬سيجد الحاسوب الشخص ‪ Luc Doncieux‬بسرعة‬
‫كبيرة لا تسمح لنا حتى بأن نقول كلمة ”أووه”‪ .‬لـكن تخي ّل أن هذا الشخص يتواجد في آخر قائمة متسلسلة من ‪10000‬‬
‫عنصر ! ليس مقبولا أن يتم البحث في ‪ 10000‬عنصر لإ يجاد المعلومة‪ .‬هنا ٺتدخّل جداول التجزئة‪.‬‬

‫ماهي جداول التجزئة ؟‬ ‫‪2.31‬‬

‫إذا كنت ٺتذكر جيداً‪ ،‬لا تعرف الجداول هذا النوع من المشاكل‪ .‬لهذا‪ ،‬كي نصل إلى العنصر في الوضعية ‪ 2‬من‬
‫الجدول تكفيني كتابة التالي ‪:‬‬

‫;}‪1 int table[4] = {12, 7, 14, 33‬‬


‫;)]‪2 printf(”%d”, table[2‬‬

‫لو نعطي للحاسوب ]‪ ، table[2‬فسيتوجّه مباشرة إلى المكان في الذاكرة أين هو مخز ّن العدد ‪ .14‬أي أنه لن يتقدّم‬
‫في الجدول خانة بخانة‪.‬‬

‫؟‬
‫هل أنت بصدد القول أن الجداول ليست ”بذلك القدر من السوء” ؟ لـكن في هذه الحالة سنخسر الميزات التي‬
‫توف ّرها القوائم المتسلسلة التي تسمح لنا بإضافة و إزالة خانات في أي لحظة !‬

‫في الواقع‪ ،‬القوائم المتسلسلة مرنة أكثر‪ .‬أما بالنسبة للجداول‪ ،‬فهي تسمح بالوصول السر يع للمعطيات‪ .‬يمكننا القول أن‬
‫جداول التجزئة تشكّل حل ّا وسطا بين الإثنين‪.‬‬

‫يوجد عيب في استعمال الجداول لم نتكلّم عنه سابقا ً ‪ :‬يتم تعر يف خانات الجدول عن طر يق أرقام نسمّيها الفهارس‬
‫)‪ .(Indices‬لا يمكن أن نطلب من الحاسوب ‪” :‬ماهي المعلومات المتواجدة في الخانة التي تسمّى ”‪ .”Luc Doncieux‬أي‬
‫أننا لإ يجاد الع ُمر و المعدّل لن نتمكّن من كتابة ‪:‬‬

‫;]”‪1 table[”Luc Doncieux‬‬

‫‪478‬‬
‫‪ .2.31‬ماهي جداول التجزئة ؟‬

‫مع أنه سيكون عمليا ًلو أننا نستطيع الوصول إلى خانة ما باستعمال الاسم فقط ! حسناً‪ ،‬هذا ممكن باستعمال جداول‬
‫التجزئة‪.‬‬

‫م‬
‫كما رأينا مؤخّراً‪ .‬لا تشكّل جداول التجزئة ”جزءً” من لغة الـ‪ .C‬نتحدّث هنا عن مبدأ‪ .‬سنعيد استعمال أساسيات‬
‫لغة الـ‪ C‬التي نعرفها من قبل لأجل إنشاء نظام ذكي جديد‪ .‬و كأنه في لغة الـ‪ ،C‬باستعمال بعض الأدوات القاعدية‪،‬‬
‫يمكننا إنشاء الـكثير من الأشياء !‬

‫؟‬
‫بما أنه من الواجب أن يتم ترقيم الجدول بالفهارس‪ ،‬كيف سنجد رقم الخانة لو أننا نعرف الاسم ”‪”Luc Doncieux‬‬
‫فقط ؟‬

‫ملاحظة جيدة‪ .‬في الواقع‪ ،‬يبقى الجدول جدولا ًو لن يعمل إلا بالفهارس المرقّمة‪ .‬تخي ّل جدولا ًيوافق الصورة الموالية‬
‫‪ :‬كل خانة لها فهرس و ٺتوفر على مؤش ّر نحو هيكل من نوع ‪ . Student‬أنت تعرف القيام بهذا الآن ‪:‬‬

‫لو أردنا إ يجاد الخانة التي توافق ‪ ،Luc Doncieux‬يجب أن نجيد تحو يل الإسم إلى فهرس في الجدول‪ .‬و بهذا‪ ،‬يجب أن‬
‫ل اسم برقم من خانة في الجدول ‪:‬‬
‫نتمكّن من ربط ك ّ‬

‫• ‪.0 = Julien Lefebvre‬‬


‫• ‪.1 = Aurélie Bassoli‬‬
‫• ‪.2 = Yann Martinez‬‬
‫• ‪.3 = Luc Doncieux‬‬

‫لا يمكننا أن نكتب ]”‪ table[”Luc Doncieux‬كما فعلتُ سابقاً‪ .‬لأن هذا غير مسموح به في لغة الـ‪.C‬‬

‫السؤال الذي يُطرح هو ‪ :‬كيف نحو ّل سلسلة محارف إلى عدد ؟ هذا هو سحر التجزئة‪ .‬تجب كتابة دالة تأخذ كمعامل‬
‫سلسلة محارف‪ ،‬تطب ّق حسابات عليها‪ ،‬ثم ت ُرجع لنا عددا ً يوافق تلك السلسلة‪ .‬سيكون هذا العدد هو فهرس الخانة في الجدول‬
‫‪:‬‬

‫‪479‬‬
‫الفصل ‪ .31‬جداول التجزئة )‪(Hash tables‬‬

‫كتابة دالة تجزئة‬ ‫‪3.31‬‬

‫ل الصعوبة في كتابة دالة تجزئة صحيحة‪ .‬كيف نحو ّل سلسلة ً محرفي ّة إلى عدد وحيد ؟‬
‫تكمن ك ّ‬

‫ل شيء‪ ،‬لنوضح الأمور ‪ :‬جدول التجزئة لا يحتوي ‪ 4‬خانات كما أضع في الأمثلة‪ ،‬لـكن ‪ 100‬أو ‪1000‬‬
‫أولا و قبل ك ّ‬
‫أو أكثر‪ .‬لا يهم حجم الجدول‪ ،‬لأن البحث سيكون سر يعا ًجدا ً دائما‪.‬‬

‫م‬
‫نقول أن هذا تعقيد بـدرجة )‪ O(1‬لأننا نجد مباشرة عنصر البحث‪ .‬في الواقع‪ ،‬دالة التجزئة سترجع لنا فهرسا ‪:‬‬
‫ل الخانات !‬
‫يكفي ”القفز” مباشرة إلى الخانة الموافقة للجدول‪ .‬لسنا بحاجة إلى تصفّح ك ّ‬

‫تخي ّل إذا ً جدولا ًمن ‪ 100‬خانة‪ ،‬تقوم فيه بتخزين مؤش ّرات نحو هياكل من نوع ‪. Student‬‬

‫;]‪1 Student� table[100‬‬

‫يجب علينا أن نكتب دالة‪ ،‬انطلاقا ًمن اسم‪ ،‬تولّد عددا ً محصورا ً بين ‪ 0‬و ‪) 99‬ر ُتب الجدول(‪ .‬هنا يتطل ّب منا الأمر‬
‫الحذاقة‪ .‬توجد طُرق ر ياضية ج ّد معقّدة كي ”نجز ّء” البيانات‪ ،‬أي أن نحو ّلها إلى أعداد‪.‬‬

‫م‬
‫الخوارزميتان ‪ MD5‬و ‪ SHA1‬هما دالتا تجزئة مشهورتان‪ ،‬لـكنهما متقدّمتان كثيرا بالنسبة لنا حالياً‪.‬‬

‫ل حرف‬
‫يمكنك اختراع دالة التجزئة الخاصة بك‪ .‬هنا‪ ،‬لـكي نبسّط الأمور‪ ،‬أقترح عليك ببساطة أن تجمع القيم ‪ ASCII‬لك ّ‬
‫من الاسم‪ ،‬أي من أجل الاسم ‪ Luc Doncieux‬ستكون لدينا عملية الجمع التالية ‪:‬‬

‫’‪1 ’L’ + ’u’ + ’c’ + ’ ’ + ’D’ + ’o’ + ’n’ + ’c’ + ’i’ + ’e’ + ’u’ + ’x‬‬

‫‪480‬‬
‫‪ .4.31‬معالجة التصادمات )‪(Collisions management‬‬

‫سيكون لدينا مشكل ‪ :‬هذا المجموع يتخطّى الـ‪ ! 100‬بما أن الجدول الذي أنشأناه لا يحتوي سوى على ‪ 100‬خانة‪ ،‬فإن‬
‫أخذنا بهذه القيمة فسنخاطر بالخروج من حدود الجدول‪.‬‬
‫ل محرف في جدول ‪ ASCII‬يمكن أن يكون مرقّما حت ّى ‪ .255‬و بهذا سنتجاوز بسرعة حاجز الـ‪.100‬‬
‫أذك ّرك بأن ك ّ‬

‫ل هذا المشكل‪ ،‬يمكننا استعمال عامل الترديد ‪ . %‬هل ٺتذكره ؟ إن ّه يعطي باقي القسمة ! لو نقوم بهذا الحساب ‪:‬‬
‫لح ّ‬
‫‪1 lettersSum % 100‬‬

‫سنتحصّ ل قطعا ً على عدد محصور بين ‪ 0‬و ‪ .99‬مثلاً‪ ،‬لو أن المجموع يساوي ‪ ،4315‬باقي القسمة على ‪ 100‬هو ‪.15‬‬
‫ست ُرجع إذا دالة التجزئة القيمة ‪.15‬‬

‫إليك ما يمكن أن تكون عليه الدالة ‪:‬‬


‫)‪1 int hash(char �string‬‬
‫{ ‪2‬‬
‫‪3‬‬ ‫;‪int i = 0, hashNumber= 0‬‬
‫‪4‬‬ ‫)‪for (i = 0 ; string[i] != ’\0’ ; i++‬‬
‫‪5‬‬ ‫{‬
‫‪6‬‬ ‫;]‪hashNumber += string[i‬‬
‫‪7‬‬ ‫}‬
‫‪8‬‬ ‫;‪hashNumber %= 100‬‬
‫‪9‬‬ ‫;‪return hashNumber‬‬
‫} ‪10‬‬

‫لو نعطيها )”‪ ، hash(”Luc Doncieux‬ست ُرجع لنا القيمة ‪ .55‬و بـ )”‪ ، hash(”Yann Martinez‬نتحصّ ل على‬
‫‪.80‬‬
‫بفضل دالة التجزئة هذه‪ ،‬يمكنك أن تعرف في أي خانة من الجدول يجب أن تضع المعلومات ! إذا أردت الوصول‬
‫إلى هذه الخانات لاحقا ًلاسترجاع المعلومة‪ ،‬تكفي ”تجزئة” اسم الشخص من جديد لـكي نجد فهرس الخانة في الجدول أين‬
‫تخز ّن المعلومات !‬

‫أنصحك بإنشاء دالة بحث ٺتكفّل بتجزئة المفتاح )الاسم( و ت ُرجع لنا مؤش ّرا ً نحو المعلومات التي نبحثُ عنها‪ .‬هذا سيعطينا‬
‫مثلا ‪:‬‬
‫;)”‪1 infoAboutLuc = findHashTable(table, ”Luc Doncieux‬‬

‫معالجة التصادمات )‪(Collisions management‬‬ ‫‪4.31‬‬

‫حينما ت ُرجع دالة التجزئة نفس العدد من أجل مفتاحين مختلفين‪ ،‬نقول أن ّه حدث تصادم‪ .‬مثلا في دالتنا‪ ،‬لو أننا نملك‬
‫شخصا ً اسمه تحر يك أحرف لـ‪ ،Luc Doncieux‬مثلا ً ‪ ،Luc Doncueix‬سيكون مجموع الأحرف هو نفسه‪ ،‬و بهذا فإن نتيجة‬
‫دالة التجزئة ستكون نفسها !‬
‫يمكن لسببين أن يشرحا التصادم ‪:‬‬

‫• دالة التجزئة لا تعمل بكفاءة عالية‪ .‬هذا يمث ّل حالتنا‪ .‬لقد كتبنا دالة سهلة جدا ً )لـكن نوعا ً ما كافية( من أجل‬
‫الأمثلة‪ .‬الدالتان ‪ MD5‬و ‪ SHA1‬المذكورتان أعلاه هما ذات جودة عالية لأنهما تنتجان نسبة قليلة من التصاد ُمات‪.‬‬
‫و لتعلم أن ‪ SHA1‬مفضّ لة في أيامنا هذه أكثر من ‪ MD5‬لأنها تننج نسبة تصادمات أقل مقارنة بنظيرتها‪.‬‬

‫‪481‬‬
‫الفصل ‪ .31‬جداول التجزئة )‪(Hash tables‬‬

‫• الجدول الذي نخزن به المعلومات صغير الحجم كثيراً‪ .‬لو أننا ننشئ جدولا من ‪ 4‬خانات و نريد تخزين ‪ 5‬أشخاص‪،‬‬
‫فسيحدث تصادم بالتأكيد‪ ،‬أي ان دالة التجزئة ست ُعطي نفس الفهرس من أجل اسمين مختلفين‪.‬‬

‫إذا حصل تصادم فلا داعي للخوف ! هناك حلان يمكنك الاختيار بينهما ‪ :‬العـَنوَنـ َة المفتوحة و السَلْسَلَة‪.‬‬

‫العنونة المفتوحة )‪(Open addressing‬‬

‫إذا بقيت أمكنة شاغرة في الجدول‪ ،‬يمكنك تطبيق التقنية التي تُدعى التجزئة الخطي ّة‪ .‬المبدأ سهل‪ .‬هل الخانة محجوزة ؟‬
‫لا يوجد مشكل‪ ،‬سننتقل للخانة التي تليها‪ .‬آه‪ ،‬هل هذه محجوزة أيضا ً؟ توجّه لللتي بعدها‪.‬‬

‫و هكذا حتى تجد خانة موالية فارغة‪ .‬إن وصلت إلى نهاية الجدول‪ ،‬فعد إلى البداية و أكمل البحث‪.‬‬

‫تطبيق هذه الطر يقة سهل جداً‪ ،‬لـكن إن واجهت الـكثير من التصادمات‪ ،‬فسيكون عليك استغراق وقت كبير في‬
‫البحث عن الخانة الشاغرة الموالية‪.‬‬

‫تنص على التجزّئة من جديد حسب دالة أخرى‬


‫توجد طرق بديلة )التجزئة المزدوجة‪ ،‬التجزئة الرباعية ‪ ( ...‬و التي ّ‬
‫في حالة وجود تصادم‪ .‬هذه الدوال أكثر كفاءة لـكن أكثر تعقيدا من ناحية التطبيق‪.‬‬

‫السَلْسَلَة )‪(Chaining‬‬

‫ينص على إنشاء قائمة متسلسلة في مكان التصادم‪ .‬هل تريد تخزين بيانتين )أو أكثر( في نفس الخانة ؟ استعمل‬
‫ل آخر ّ‬
‫ح ّ‬
‫قائمة متسلسلة و أنشئ مؤش ّرا ً نحو هذه القائمة انطلاقا ًمن الجدول ‪:‬‬

‫بالفعل‪ ،‬سنعود لمشكل القوائم المتسلسلة ‪ :‬إذا كان هناك ‪ 300‬عنصرٍ في هذا الموقع من الجدول‪ ،‬يجب تصفّح القائمة‬
‫المتسلسلة إلى حين إ يجاد العنصر الصحيح‪.‬‬

‫‪482‬‬
‫ملخّ ص‬

‫هنا‪ ،‬كما ترى‪ .‬ليست القوائم المتسلسلة دائما ًالأمثل‪ ،‬لـكن لجداول التجزئة حدودها أيضاً‪ .‬يمكننا المزج بين الاثنين من‬
‫ل بنية‪.‬‬
‫أجل الحصول على الجانب الأفضل من ك ّ‬

‫على أية حال‪ ،‬النقطة الحساسة في جداول التجزئة هي دالة التجزئة‪ .‬فكل ّما أنتجت تصاد ُمات أقل‪ .‬كلما كان ذلك‬
‫أفضل‪ .‬سأترك لك مهمة إ يجاد دالة التجزئة المناسبة لحالتنا !‬

‫ملخّ ص‬

‫• القوائم المتسلسلة مرنة‪ ،‬لـكن عملية إ يجاد عنصر محدد تستغرق وقتا ًطو يلا ًلأنه يجب تصفّح القائمة عنصرا ً بعنصر‪.‬‬

‫• جداول التجزئة هي جداول نخز ّن فيها المعلومات في مكان محدد بواسطة دالة التجزئة‪.‬‬

‫• تأخذ دالة التجزئة مفتاحا ًكمعامل )مثلا ً‪ :‬سلسلة محرفي ّة( و تعيد عددا كمخرج‪.‬‬

‫• يتم استعمال هذا العدد لمعرفة عند أي فهرس من الجدول يجب تخزين البيانات‪.‬‬

‫• دالة التجزئة الأكثر كفاءة هي التي لا تولّد عددا ً كبيرا ً من التصاد ُمات‪ ،‬أي أنها تتجنب قدر المستطاع إرجاع نفس‬
‫العدد من أجل مفتاحين مختلفين‪.‬‬

‫• في حالة التصادم‪ ،‬يمكننا استعمال تقنية العنونة المفتوحة )البحث عن خانة شاغرة أخرى في الجدول( أو استعمال‬
‫تقنية السلسلة )الدمج مع القوائم المتسلسلة(‪.‬‬

‫‪483‬‬
‫الفصل ‪ .31‬جداول التجزئة )‪(Hash tables‬‬

‫‪484‬‬
‫خاتمة‬

‫هل تريد المزيد ؟‬

‫لماذا لا ٺتعل ّم لغة الـ‪ C++‬؟‬

‫‪http://www.siteduzero.com/tuto-3-5395-0-apprenez-a-programmer-en-c.html‬‬

‫هذا درس آخر كتبت ُه حول هذه اللغة قريبة الـ‪ .C‬إذا كنت تعرف الـ‪ ،C‬فلن تكون ضائعا ً بل ستفهم بسرعة فائقة‬
‫الفصول الأولى !‬
‫فليكن في علمك أنني كتبت درسا ًقصيرا يسمّى ”من الـ‪ C‬إلى الـ‪ ”C++‬الذي يبېّن جزء ً من الاختلاف بين الـ‪ C‬و الـ‪.C++‬‬

‫‪http://www.siteduzero.com/tutoriel-3-430167-du-c-au-c.html‬‬

‫بلغة الـ‪ ،C++‬يمكنك البدء في البرمجة غرضية التوجّه )أو البرمجة الكائنية( )‪ .(OOP‬قد يكون هذا المبدأ معقّدا قليلا في‬
‫البداية‪ ،‬لـكن ستجد بأن هذه الطر يقة في البرمجة ناجعة جدا ً ! ستكتشف أيضا ًمعها المكتبة ‪ Qt‬التي تسمح بإنجاز واجهات‬
‫رسومية كاملة جدّا‪.‬‬

‫أشكر كثيرا ‪ Taurre‬و ‪ Pouet_forever‬لمساعدتهم الـكبيرة في القيام بالمراجعات الأخيرة لهذه الدروس‪.‬‬

‫‪485‬‬
‫ملخّ �‬
‫ه�ا ال���ب ه� ����� ��رس تع� ّ� ال����� بلغ� ال� ‪ C‬ا���ص ���ق� ‪ OpenClassrooms‬م� الف���يّ� إ�� الع����‪�� .‬ت�ز ه�ا ا��رس بك�ن� ���‬
‫الفه� ��� المبت�ئ�� �� ���ل ال�����‪ ،‬إذ أنّ� �� يف��ض وج�د أي� مك�سب�ت قبليّ� �� ه�ا ا����ل ��ى الق�رئّ‪.‬‬
‫��ت�ي ال���ب ��� مع��م�ت مفصّ �� ��ص�ص ال����� ���� ��م و لغ� ال� ‪�� ���� C‬ص‪ ،‬م��ّ�� بكث�� م� ا���طّط�ت الت�ضيحيّ�‪ ،‬ا��مث��‬
‫و ا����ر�� المصحّ��‪.‬‬
‫��ك�ّن ال���ب م� ‪ 31‬فص�� م�زّ�� ��� ‪ 4‬أ��اء‪ .‬فص�ل ا���ء ا��وّل ���ّل الق�رئ م� ��� �� ���ق� �� ب�ل����� إ�� م���� مبت�ئ ��ي� التع�م�‬
‫م� ا��دوات ا���زم� و ��ي� المف�ه�� ا��س�سيّ� ��ن�اع البي�ن�ت‪ ،‬المتغ� ّ�ات‪ ،‬ال��وط‪ ،‬ا��لق�ت الت��ار يّ� و ا��وال‪ �� .‬ا���ء الث���‪�� ،‬ع� ّ� الق�رئ‬
‫مف�ه�� أك�� تق�ّم� �� ال�����‪�� ،‬لم�� ّ�ات‪ ،‬ا���اول‪ ،‬الس��س� ا����فيّ� وا��ي��� ب���ض�ف� إ�� ا���� ا�� ّ� ���ا��ة والتع�م� م� الملفّ�ت‬
‫بع� ذ��‪�� ،‬ن�ول ا���ء الث�ل� مب�أ است��ام المكتب�ت ال����يّ� ���ّ�ا ��� استغ��ل مكتب� ‪����� SDL‬ء الن�اف� و ا���� ف��� و مكتب� ‪FMOD‬‬
‫ل�شغي� الص�ت و استعم���م� �����ء ��ا�� و ألع�ب حقيقيّ�‪ �� .‬ا�����‪ ،‬فص�ل ا���ء ا��اب� ��ن�ول م�اضي� م��ّ�� �� لغ� ال� ‪��� C‬ثّ� �� تقنيّ�ت‬
‫الش�ئع� ا��ست��ام لتخ��� البي�ن�ت‪�� ،‬لق�ا�� الم�سلس��‪�� ،‬اول التج�ئ�‪ ،‬الم��ّس�ت و الط�اب��‪.‬‬

‫لق� ��صن� ��� نق� � ّ� ا��ف��ر ال�� ق�ّ��� ال���� �� ا��رس ا��ص�� م� ��ا��� إ�� ������‪�� ��� .‬لن� جه�ن�‬
‫�� أن يك�ن الن� ��يط� ق�ر ا��م��ن و مفه�م� ��ق�رئ الع��� الع�دي‪.‬‬

‫و�� ال���ي�‪�� �� ،‬عن� س�ى أن ��طلّ� بلهف� إ�� ����� ��ن���� ا���رق ا�ّ�ي س�ن�شئ�‬
‫بع� خت�م� ���ا ال���ب‪.‬‬

‫ال���� ا��ص�� �����ب الف����‬

‫‪Mathieu Nebra‬‬

‫مق�ول ��وام ��م�‪�� ���� ،‬وام ��م� و م�سّ� مش�رك لم�ق� ‪OpenClassrooms‬‬

‫�����‬
‫ﻋﺪن ﺑﻠﻮاﺿﺢ‬
‫ب�حث� �� تقني�ت ا����ء ا��صطن��� و ا��نظم� المع��م���� ا��كي�‪ ،‬مط�رة ألع�ب في�ي� و ����� لع�ّة مق���ت و‬
‫م�اضي� متعل ّق� ب�لت����جي� و المع��م���� بع�ّة لغ�ت‪.‬‬
‫‪[email protected]‬‬

‫��اجع� و إ��اد‬

‫���ة عبَ�د‬

‫م���� متق�ّم‪ّ � ،‬‬


‫�ت� �� التع� ّ� ا���� و ��� البي�ن�ت‪ ،‬م��� ��نظم� ‪��� ����، GNU/Linux‬ة صغ��ة �� تع�ي� الص�ر‬
‫و مع�ف� ��يط� �� التصم��‪ ،‬و ه� ه�وي إل�ك��و���ت أيض� !‬
‫‪[email protected]‬‬

‫تصم�� الغ��ف‬

‫أﺣﻤﺪ زﺑﻮﳾ‬

‫م���� و إختص��� �� ا����ء ا��صطن���‪ ،‬عص��� �� هن�س� ص����ت و ��ط� المع��م�ت ا��س���‪ ���� ،‬ب�لعم� ا��� و المع��م����ت‪.‬‬
‫‪[email protected]‬‬

‫��ن‪��� ،‬ة و أ��� متخ�ج�ن م� ��مع� ه�اري ب�م��� ��ع��م و التكن�ل�جي� ب����ا��‪ ،‬اختص�ص »ا��نظم� المع��م���� ا��كي�«‪.‬‬

You might also like