Lear C
Lear C
��َ� 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
جدول المحتو يات
6
جدول المحتو يات
7
جدول المحتو يات
8
جدول المحتو يات
9
جدول المحتو يات
10
جدول المحتو يات
11
جدول المحتو يات
479 خاتمة
12
الجزء ا
13
الفصل 1
قلت برمجة ؟
؟
ما الذي تعنيه كلمة ”بَرْمَج َ”؟
لن أتعبك وأعطيك أصل كلمة ”بَرْمَج َ” ،لـكنني سأختصر كل شيء في جملة :البرمجة تعني إنشاء برامج حاسوب .وهذه
البرامج التي تنشئها تأمر الجهاز بالقيام بتعليمات وأفعال معي ّنة .حاسوبك الخاص يحتوي على كثير من هذه البرامج وبمختلف
أنواعها :
15
الفصل .1قلت برمجة ؟
باختصار البرامج موجودة في كل جهاز ،وهي التي تعطي الحاسوب قدرته على إنجاز مختلف المهام التي تُخ َو ّل إليه .يمكنك
أن تنشئ برنامج تشفير أو لعبة ثنائية /ثلاثية الأبعاد باستخدام لغة برمجة مثل .C
ملاحظة :لم أقل أن إنشاء لعبة يتم برمشة عين ،لقد قلت فقط بأنه شيء ممكن ،لـكن كن متأكداً ،سوف يتطلب
ذلك جهدا كبيرا ً !
وبما أننا في بداية الطر يق ،فإّننا لن نقوم بإنشاء لعبة ثلاثية الأبعاد ! لـكن ّنا سنبدأ بكيفية عرض نص على الشاشة ،طبعا
ستقول ما علاقة هذا بإنشاء الألعاب ؟ لـكن ث ِق بي ،هذا الأمر ليس بسيطا كما يبدو !
بالطبع هذا ليس شيئا م ُبهراً ،ولـكن يجب علينا أن نبدأ من هنا؛ وشيئا فشيئا يمكنك أن تنشئ برامج معقّدة أكثر.
فالهدف من هذا الكتاب هو أن أعرفك على كل ما يتعلق بهذه اللغة.
حاسوبك هو آلة غريبة جداً ،هذا أقل ما يمكن أن نقوله عنه .يمكننا أن نخاطبه فقط بالصفر والواحد ،فمثلا إذا
طلبنا منه حساب 3+5فيمكن لهذا أن يعطينا نتيجة كالتالي )هذه ليست ترجمة دقيقة ولـكنها تشبه ما يحدث بالفعل(:
0010110110010011010011110
ما ت َر َاه هنا يسمى اللغة الثنائية ) (Binary languageأو لغة الآلة ) ،(Machine languageوحاسوبك لا يفهم سوى
هذه اللغة ،وكما تلاحظ ،هذه اللغة غير مفهومة على الإطلاق !
؟
كيف يمكننا التعامل مع حاسوب لا يفهم سوى اللغة الثنائية ؟
حاسوبك لا يتحدث الإنجليز ية ،ولا العربية ،ولا أي لغة غير هذه اللغة ،ولـكنها صعبة جدا لدرجة أن حتى أكبر خبراء
جم َ إلى اللغة الثنائية ،لـكن الشيء
الحاسوب لا يستخدمونها .لهذا قام بعض مهندسي الحواسيب باختراع لغات يمكن أن تُت َر َ
الأصعب هو إنشاء البرامج ال ّتي تقوم بهذه الترجمة .ولحسن الحظ فقد قاموا بهذا العمل نيابة عنا .هذه البرامج تقوم بترجمة
الأوامر ال ّتي تكتبها )مثلا ُ ” :أحسب (”3+5إلى شيء يشبه هذا . 0010110110010011010011110 :
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قلت برمجة ؟
كما قلت سابقا ،يوجد كثير من اللغات عالية المستوى ،فلماذا ينبغي علينا أن نبدأ بإحداها على وجه الخصوص ؟ سؤال
عظيم !
على أية حال يجب علينا أن نختار بأي لغة سنبدأ البرمجة عاجلا أم آجلا ،وبالتالي لديك الخيار في البدء بـ :
• لغة ذات مستوى عالي جدّا :وتكون سهلة جدّا أوعامة ،نذكر من بينها ،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
هذا سؤال يعذّب روح كل من يريد تعل ّم البرمجة ! هل يجب أن تكون أستاذ ر ياضيات كبير درس 10سنوات من
التعليم العالي حت ّى تبدأ البرمجة ؟
الجواب هو لا بالطبع .كل ما تحتاج إليه هو معرفة العمليات الأربع الأساسية :
• الجمع
• الطرح
• الضرب
• القسمة
هذا ليس مخيفا ! سوف أشرح لك في فصل لاحق كيف يقوم الحاسوب بهذه العمليات الأساسية في برامجك.
باختصار ،لا توجد صعوبات غير قابلة للحلّ .في الواقع ،هذا يعتمد على طبيعة برنامجك ،فإذا كنت تريد إنشاء برنامج
تشفير فيجب عليك معرفة بعض الأشياء في الر ياضيات ،وإن كان برنامجك يقوم بالرسم ثلاثي الأبعاد فيجب أن تكون
لديك بعض المعرفة بالهندسة الفضائية.
كل حالة تعامل بطر يقة خاصّة .ولـكن لتعل ّم لغة Cنفسها لا تحتاج إلى أي ّة معارف قبلي ّة.
؟
إذن أين هو الفخ ؟ وأين تكمن الصعوبة ؟
ل
يجب أن تعرف كيف يعمل الحاسوب ،لتفهم ما الّذي نقوم به في .Cمن هذا المنطلق ،كن متيقّنا أن ّي سأعلّمك ك ّ
هذا شيئا فشيئا.
اعلم أن للمبرمج صفات أيضا مثل :
حس المنطق :صحيح أن ّك لست بحاجة إلى أن يكون لديك مستوى جي ّد في الر ياضي ّات ،لـكنّ هذا لا يمنع من ّ •
التفكير وتحليل المشكلات بالمنطق.
• الهدوء :فيجب عليك ألّا تضرب حاسوبك بالمطرقة ،فهذا لن يجعل برنامجك يعمل !
19
الفصل .1قلت برمجة ؟
20
الفصل 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
Code::Blocksهي بيئة تطوير متكاملة حرّة و مجاني ّة ،متوف ّرة للـ Mac ،Windowsو .GNU/Linux
حالي ّا Code::Blocksمتوف ّر بالإنجليز ي ّة فقط .لـكن هذا ليس أمرا يدعوك إلى تجن ّب استخدامه ! فنحن قلّما نحتاج إلى
العمل بقوائم واجهته ،فلغة Cهي ال ّتي تهمّنا.
كن على علم أنه عندما تبرمج سوف تقابل عادة توثيقا بالإنجليز ية .هذا سبب آخر يدفعك للتدرّب على استخدام هذه
اللغة.
http://www.codeblocks.org/downloads/binaries
mingw • إذا كنت تستخدم ،Windowsاذهب إلى القسم ” ”Windowsفي أسفل الصفحة .نز ّل البرنامج الّذي يحوي
في اسمه )مثلا .( codeblocks-10.05mingw-setup.exe :النسخة الأخرى لا تحوي مترجما ،لن تتمكن
في حال استخدمتها من ترجمة برامجك !
• إذا كنت تستخدم ،Macاختر الملف الأحدث في القائمة ،مثلا . codeblocks-10.05-p2-mac.zip :
x
أقول و أكر ّر :إذا كنت تستخدم Windowsفيجب عليك تنز يل النسخة ال ّتي يتضم َن اسمها كلمة mingwلأن ّه إذا
اخترت النسخة الخاطئة فلن تتمكّن من ترجمة برامجك فيما بعد !
التثبيت بسيط و سر يع .أترك جميع الخيارات كما هي و شغ ّل البرنامج .سوف تظهر لك نافذة شبيهة بهذه :
23
الفصل .2الحصول على الأدوات اللازمة
نمي ّز أربعة أقسام رئيسية في واجهة البرنامج ،و هي مرقّمة في الصورة :
.1شر يط الأدوات ) : (Toolbarيحتوي على كثير من الأزرار و لـكن ّنا سوف نستخدم بعضها فقط باستمرار ،سأعود
للحديث عن هذا فيما بعد.
.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تكون محدّدة على الأقل(.
في الخانة الخاصة بالمشار يع على اليسار ،قم بتوسيعها بالضغط على ’ ’ +لـكي تظهر قائمة الملفات في المشروع .سيكون
ل شيء !
لديك على الأقل ملف يسمّى . main.cهذا هو ك ّ
• برنامج مدفوع في الأصل ،لـكن توجد نسخة مج ّانية منه تسمّى .Visual C++ Express
• تمكّن من البرمجة باستخدام كلتا اللغتين Cو ) C++و ليس فقط C++كما يوحي الاسم(.
طبعا ستقوم بتحميل النسخة المجانية ) Visual C++ Expressاحذر ،هو غير متوافق مع Windows 7إلّا بداية من النسخة
: (2010
27
الفصل .2الحصول على الأدوات اللازمة
؟
ما الفرق بين هذه النسخة و النسخة ”الحقيقي ّة” ؟
لا تحتوي على محر ّر موارد يسمح لك برسم الصور ،الأيقونات أو النوافذ .هذا لا يهمّنا لأنّنا لن نحتاج إلى هذه الوظائف
في هذا الكتاب .وجود هذه الوظائف أمر مستحسن لـكن ّه ليس لازما.
https://msdn.microsoft.com/fr-fr/express/aa975050.aspx
التثبيت
التثبيت سهل .سوف يقوم البرنامج بتحميل آخر نسخة من الأنترنت تلقائيا.
أنصحك بترك الخيارات كما هي.
بعد ذلك سيطلب منك التسجيل في غضون 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فقط(
ل جزء.
هذه النافذة تشبه مثيلتها في .Code::Blocksو لـكن رغم ذلك سوف نعيد رؤ ية معنى ك ّ
31
الفصل .2الحصول على الأدوات اللازمة
.1شر يط الأدوات :فيه أزرار اعتيادية .لـكن كما ترى لا يوجد أيّ زرّ للترجمة .يمكنك إضافته عن طر يق النقر باليمين
على هذا الشر يط و اختيار ”تنقيح” ) ( Debugو ”توليد” ) ( Generateمن القائمة.
.2هذه المساحة ج ّد مهمّة ،إذ أنها تحتوي على الملفات الخاصة بمشروعك .أنقر على ”مستكشف الحلول” ) ( Solution explorer
في الأسفل إن لم يكن ف ُع ِل من قبل .سوف ترى أن ّه قد تم ّ إنشاء مجلّدات لفصل أنواع الملفّات المختلفة )مصدر ي ّة،
رأسي ّة و موارد( .سنتعرف لاحقا على مختلف أنواع الملفات التي تكو ّن المشروع.
أكملنا جولتنا في .Visual C++يمكنك إلقاء نظرة على الخيارات إن أردت لـكن لا تأخذ من وقتك ثلاث ساعات
هناك ! لأنه يوجد كثير منها.
،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يوافق ملف المشروع .إنه الملف الذي عليك اختياره في المرة القادمة لفتح مشروعك.
نافذة التطوير
.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مخصّ صة لهذا الغرض .حالي ّا هذا الخيار لا يهمّنا.
ملخّ ص
• من الممكن ٺثبيت هذه الأدوات منفصلة ،لـكن ّه من المعتاد البوم الحصول على ح ُزمة ٍ ثلاثة-في-واحد نسميها بيئة
التطوير المتكاملة.
• ،Visual C++ ،Code::Blocksو Xcodeتع ّد من بين بيئات التطوير الأكثر شهرة.
34
الفصل 3
برنامجك الأوّل
ل شيء إلى حد الآن ويمكننا أن نبدأ قليلا من البرمجة .مع نهاية هذا الفصل ستكون قد نجحت في
لقد قمنا بحضير ك ّ
إنشاء أوّل برنامج لك.
لـكي أصدقك القول ،سيظهر البرنامج بالأبيض والأسود ولن يقوم بشيء سوى إلقاء التحي ّة .يبدو عديم الفائدة ،لـكن ّه
برنامجك الأوّل وأؤكّد لك أن ّك ستكون فخورا به.
لقد تحدثنا سابقا عن فكرة برامج الـكونسول وبرامج النوافذ في الفصل السابق .البيئة التطوير ية تطلب منا تحديد أي نوع
من البرامج نريد أن ننشئها .ولقد قلنا إننا سننشئ برامج من نوع كونسول.
• ،برامج بنوافذ
هي البرامج التي نعرفها جميعا .هذا مثال على برنامج من نوع نافذة ،مثل الرسام.
35
الفصل .3برنامجك الأوّل
أعتقد أن ّك تحب إنشاء برامج كهذه ،لـكنّ هذا ليس في مقدورك حاليا .في الواقع ،إنشاء برامج بنوافذ هو أمر ممكن بلغة
،Cلـكنّ بالنسبة لمبتدئ ،هذا أمر معقّد جدّا .كبداية ،يستحسن إنشاء برامج الـكونسول.
؟
لـكن ماذا يعنى برنامج Console؟
برامج الـكونسول هي أول ما ظهر من برامج .في ذلك الوقت ،شاشات الحواسيب لم تكن سوى بالأبيض والأسود ،ولم تكن
فع ّالة لـكي تتمكّن من رسم النوافذ كما هو الحال مع حواسيبنا حالي ّا.
مر الزمن بسرعة وزادت شعبية الويندوز نظرا ً لبساطته إلى أن نسي كثير من الناس ما هي الـكونسول.
ّ
لديّ خبر جي ّد لك ! الـكونسول لم تمت بعد ! في الواقع GNU/Linux ،قد أعاد الـكونسول إلى الحياة .هذه صورة
لـكونسول على .GNU/Linux
36
.2.3الح ّد الأدنى من الشفرة المصدر ية
مرعب ! صحيح ؟ لـكن على الأقل عرفت ما هي الـكونسول ،وهذه بعض الملاحظات :
كما قلت لك ،إنشاء برامج كونسول أمر سهل جدّا وملائم للمبتدئين )وهذا عكس برامج النوافذ( .ليكن في علمك أيضا
ن الـكونسول قد تطو ّرت وبإمكانها عرض الألوان ،ولا شيء يمنعك من إضافة صورة خلفي ّة لها.
أ ّ
؟
وفي الويندوز ألا توجد Console؟
بلى ،لـكنّها مخفي ّة لو صح القول .يمكنك فتحها بالذهاب إلى ”إبدأ” ) ( Startثم ّ ”ملحقات” ) ( Accessories
ثم ّ ”موجه الأوامر” ) ( Command promptأو بالذهاب إلى ”إبدأ” ثم ّ ”تشغيل” ) ( Runواكتب فيها cmdواضغط
على ”موافق”.
إذا كنت تستخدم نظام ويندوز ،فاعلم بأن أولى برامجك ستكون في نوافذ شبيهة بهذه .أنا لم أختر البداية هكذا لجعلك
تشعر بالملل ،بل لتعليمك الأساسي ّات اللازمة لـكي تتمكّن لاحقا من إنشاء النوافذ.
إذن فلتكن متيقّناً ،بمجر ّد أن تصل إلى المستوى اللازم لإنشاء النوافذ ،سوف أعلّمك كيف تفعل ذلك.
أطلب من البيئة التطوير ية الخاصة بك تزويدك بالحد الأدنى من الشفرة المصدر ية
لقد لاحظت أن طر يقة إنشاء مشروع جديد تختلف من بيئة تطوير ية إلى أخرى .إليك تذكيرا ً بسيطا :في برنامج
) Code::Blocksالذي سنستخدمه في هذا الكتاب( ،عليك التوجه نحو Fileثم ّ Newثم ّ Projectثم تختار
Console Applicationوبعدها اللغة .Cسيولّد لك الحد الأدنى من الشفرة المصدر ية Cالتي تحتاجها .ها هي :
37
الفصل .3برنامجك الأوّل
م
لاحظ أن ّه يوجد سطر فارغ في نهاية الشفرة .يفترض أن ينتهي كل ملف مكتوب بلغة Cهكذا .إن لم تفعل
ذلك ،فهذه ليست بمشكلة ،لـكن توق ّع أن يعرض لك المترجم تحذيرا ً ).(Warning
ن السطر :
علما ًأ ّ
كلتا العبارتين تحملان نفس المعنى لـكن الثانية ،الأكثر تعقيدا ،هي الأكثر شيوعا ،لذلك فإنّنا سنستخدمها في الفصول
القادمة.
إستخدامنا للشكل الأوّل أو الثاني لا يغي ّر شيئا بالنسبة لنا .لذلك لا داعي لإضاعة الوقت هنا ،خصوصا ً أن ّك لا تملك
المستوى اللازم لفهم ما تعنيه.
إذا كنت تستخدم بيئة تطوير ية أخرى فقم بنسخ هذه الشفرة المصدر ية وألصقها في الملف main.cليكون لديكم
نفس الشفرة.
مرة.
ل ّأخيرا ،قم بحفظ عملك في المشروع .أعلم أننا لم نقم بشيء حت ّى الآن لـكن من الجي ّد التعو ّد على الحفظ في ك ّ
قد تبدو لك الشفرة المصدر ية السابقة أنّها كاللغة الصيني ّة ،أنا أتخي ّل ذلك ! في الواقع هي تسمح بإنشاء برنامج كونسول
ل هذا.
يعرض نصّ ا على الشاشة .يجب تعل ّم كيفي ّة قراءة ك ّ
38
.2.3الح ّد الأدنى من الشفرة المصدر ية
هذان السطران يبدآن بعلامة . #وهي أسطر خاصّة تُعرف باسم توجيهات المعالج القبلي ).(Preprocessor directives
اسم معقّد ،أليس كذلك ؟ هذه الأسطر تتم ّ قراءتها من طرف البرنامج المسمّى بالمعالج القبلي ،وهو برنامج يتم ّ تشغيله في بداية
الترجمة.
ما رأيناه سابقا كان مخطّطا بسيطا لعملي ّة الترجمة .لـكنّ في الواقع ،هناك الـكثير من المراحل التي تحدث في هذه العملي ّة.
ل ملفّاتك.
سنقوم بتفصيل هذا لاحقا .حالي ّا عليك فقط تذك ّر وضع هذين السطرين أعلى ك ّ
؟
حسنا لـكن ماذا يعنيه هذان السطران ؟ أريد أن أعرف !
كلمة includeبالإنجليز ي ّة تعني ”تضمين” .هذان السطران يقومان بتضمين ملفّات في المشروع ،أي إضافة هذه
stdio.h الملفّات من أجل عملي ّة الترجمة .هناك سطران وبالتالي هناك ملفان يتم ّ تضمينهما في المشروع وهما بالترتيب :
و . stdlib.hهذان الملفّان موجودان بالفعل على حاسوبك وهما ملفّان مصدر ي ّان جاهزان ،سوف تعرف مستقبلا
نص على الشاشة.
أنّنا نسميها مكتبات ) .(Librariesهذه الملفّات تحتوي الشفرة المصدر ية اللازمة لعرض ّ
نص على الشاشة سيكون أمرا مستحيلاً .فالحاسوب لا يعرف فعل أي شيء مبدئيا.
بدون هذين الملفّين ،كتابة ّ
ل سهولة.
نص على الشاشة بك ّ
باختصار ،السطران الأول والثاني يقومان بتضمين المكتبات التي ستساعدنا في إظهار ّ
ما تراه هنا هو ما نسميه بـالتابع أو الدالّة ) .(Functionالبرنامج في لغة Cيتكو ّن من مجموعة دوال .حالي ّا برنامجنا لا
يحوي سوى دالّة واحدة.
الدالّة تمكّننا من تجميع مجموعة من الأوامر .الغرض من تجميع الأوامر هو جعلها تقوم بوظيفة ما .مثلا يمكننا إنشاء دالّة
باسم open_fileوجعلها تحتوي التعليمات التي تشرح للحاسوب كيفي ّة فتح ملف.
دون الدخول في تفاصيل إنشاء الدالّة )الوقت مبك ّر ،سوف نتحدّث عن الدوال في وقت لاحق( لنحل ّل رغم ذلك
أجزائه الـكبيرة .السطر الأوّل يحتوي اسم الدالّة ،إن ّه الكلمة الثانية.
أجل ،اسم دالّتنا هو mainوالذي يعني ”الرئيسية” .وتشغيل البرنامج دائما يبدأ من الدالة . main
للدالّة بداية ونهاية ،وهي محدودة بالحاضنتين { و } .محتوى الدالّة موجود بين هاتين الحاضنتين .إن كنت قد تابعت
ن الدالّة مشكّلة من سطرين :
جيدا ً فقد عرفت أ ّ
39
الفصل .3برنامجك الأوّل
ل
هاته الأسطر في الداخل نسميها التعليمات )) (Instructionsهذه إحدى المصطلحات ال ّتي يجب عليك حفظها( .ك ّ
ل واحدة منها تطلب منه فعل شيء محدّد.
تعليمة تمث ّل أمرا ً بالنسبة للحاسوب .فك ّ
كما قلت لك ،بتجميع ذكيّ للتعليمات في الدالّة يمكننا إنشاء أجزاء برنامج جاهزة للاستخدام .باستخدام التعليمات المناسبة
يمكننا إنشاء دالّة open_fileكما شرحت لك قبل قليل ،و أيضا دالّة move_characterفي لعبة فيديو ،على سبيل
المثال.
البرنامج في الواقع ما هو إلّا ٺتابع لتعليمات :إفعل هذا و إفعل ذاك .أنت تعطي أوامر للحاسوب و هو يقوم بتنفيذها.
x
ل تعليمة بفاصلة منقوطة ” ; ” .بهذا يمكن التفر يق بين ما إذا كانت هذه تعليمة
هامّ جدّا :لا ب ّد أن تنتهي ك ّ
أم لا .إذا نسيت وضع فاصلة منقوطة نهاية تعليمة ما ،فلن تتم ّ ترجمة برنامجك.
السطر الأول printf(”Hello world!n”); :يطلب إظهار الرسالة ”! ”Hello worldعلى الشاشة .عندما يصل
برنامجك إلى هذا السطر ،فسوف يقوم بعرض هذه الرسالة ثم ّ المرور إلى التعليمة التالية.
؟
لماذا يقوم برنامجي بإعادة العدد 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
سيطلب منك الضغط على إحدى المفاتيح لإغلاق النافذة .أعلم أن الأمر لم يكن ممتعا جدّا .لـكنه برنامجك الأوّل،
وهذه لحظة ستتذكرها طيلة حياتك ! ألا تعتقد ذلك ؟
من الآن سنقوم بإدخال التعديلات على الشفرة المصدر ية السابقة .مهمّتك ،إن قبلتها :عرض رسالة ” ”Bonjourعلى
الشاشة.
؟
كيف يمكنني اختيار النص الّذي سيظهر على الشاشة ؟
41
الفصل .3برنامجك الأوّل
الأمر بسيط جدا ،إذا بدأت من الشفرة التي رأيناها سابقاً ،فسيكون عليك استبدال ”! ”Hello worldبـ” ”Bonjourفي
السطر الذي يستدعي . printf
كما قلت من قبل printf ،هي تعليمة وهي تعطي أمرا ً للحاسوب ” :قم بعرض هذه الرسالة على الشاشة”.
يجب أن تعرف أيضا أن printfهي دالّة كُت ِب َت من قبل من طرف مبرمجـين قبلك.
؟
أين توجد هذه الدالّة ؟ أنا لا أرى سوى الدالّة ! main
قلت لك من قبل أنهما يمكنان البرنامج من إضافة مكتبات .المكتبات في الحقيقة هي ملفّات تحوي أطنانا من الدوال
جاهزة للإستخدام .هذه الملفات ) stdio.hو ( stdlib.hتحوي أغلب الدوال الأساسية التي قد نحتاجها في برنامج
ما stdio.h .بحد ذاته يحوي دوال تمكّن من عرض أشياء على الشاشة )مثل ( printfو أيضا الطلب من المستخدم
إدخال شيء ما )هذه دوال سنتعر ّف عليها لاحقا(.
في دالّتنا mainنستدعي الدالّة . printfأي أن لدينا دالّة تستدعي أخرى )هنا mainتستدعي .( printfسترى
أن هذا ما يحدث دائما في لغة : Cدالّة تحتوي تعليمات تستدعي دوال أخرى ،وهكذا.
إذن ،لاستدعاء دالّة يكفي كتابة اسمها متبوعا بقوسين ،ثم فاصلة منقوطة.
;)(1 printf
printf هذا جيد ،لـكنه غير كاف .يجب أن نُعلم البرنامج بما يجب أن يكتبه في الشاشة .لفعل هذا يجب أن نعطي
النص المطلوب عرضه .لفعل هذا نقوم بوضع النص داخل علامات الإقتباس المزدوجة بين القوسين.
في حالتنا هذه سنكتب تماما :
;)”1 printf(”Bonjour
42
.3.3كتابة رسالة على الشاشة
لدينا إذن تعليمتان تطلبان من الحاسوب القيام بهذين الأمرين بهذا الترتيب :
كما ترى ،السطر الذي يحتوي الرسالة يكون ملتصقا ًقليلا بباقي النص ،على خلاف ما رأيناه سابقا.
أحد الحلول الممكنة هو إضافة رمز للعودة إلى السطر بعد ”) ”Bonjourكما لو أنّنا ضغطنا على المفتاح .( Enter
)Special ولـكن ضغط المفتاح Enterفي الشفرة المصدر ية لن يعمل كما ٺتوقع ،لهذا يجب استخدام المحارف الخاصّة
.(characters
المحارف الخاصّة
المحارف أو الرموز الخاصّة هي محارف تمكّن من تعر يف عودة إلى السطر ،جدولة ،إلخ.
من السهل التعر ّف عليها ،فهي مكو ّنة من محرفين .الأوّل هو الشَرْطَة ُ المائلة الخلفية )\( ) (Backslashوالثاني يكون رقما
أو حرفا .إليك محرفين خاصّين قد تحتاجهما كثيرا :
Bonjour في حالتنا هذه ،يكفي أن نكتب \nلإنشاء العودة إلى السطر .إذن ،إذا أردنا أن نضع عودة إلى السطر بعد
،فيكفي أن نكتب :
;)”1 printf(”Bonjour\n
43
الفصل .3برنامجك الأوّل
م
ل ما تكتبه بعد \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
44
.4.3التعليقات ،مهمّة جدا !
• العثور على ما تبحث عنه بسهولة في الشفرة المصدر ية عندما تعود إليه بعد مدّة .من الطبيعيّ أن ننسى كيف تعمل
البرامج ال ّتي كتبناها بعد مدّة .إن توق ّفت عن البرمجة لأي ّام ثم ّ عدت فستكون بحاجة إلى التعليقات لإ يجاد ما تريد
في شفرة كبيرة جدّا.
• إذا أعطيت مشروعك لأحد غيرك )وهو لا يعرف شيئا عن الشفرة المصدر ية الخاصة بك( ،فالتعليقات تمكّنه من
التآلف مع مشروعك بسرعة.
• وأخيرا ،ستسمح لي بإضافة شروحات وملاحظات حول الشفرة المصدر ية في هذه الدروس .وهذا سيفيدك في
ل سطر.
فهم ما الذي يعنيه ك ّ
توجد طر يقتان لإضافة تعليق .وهذا يعتمد على طول التعليق المراد إدراجه :
• إذا كان تعليقك قصيرا :فيمكن كتابته على سطر واحد ،ولا يحتوي سوى كلمات قليلة .في هذه الحالة ،عليك كتابة
شرطتين مائلتين ) ( //متبوعين بتعليقك .على سبيل المثال :
1 // This is a comment.
• إذا كان تعليقك طو يلا :لديك الـكثير لتقوله ،تريد كتابة الـكثير من الجمل على كثير من الأسطر .في هذه الحالة،
يجب عليك كتابة شفرة تشير إلى ”بداية التعليق” وأخرى تشير إلى ”نهاية التعليق”:
فلنعد إلى الشفرة المصدر ية التي تُظهر ” ”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التي تعني الرئيسي ّة( هي الدالة ال ّتي يبدأ بها تنفيذ البرنامج .إنّها الدالة الوحيدة الإجبار ية في البرنامج،
لا يمكن لأي برنامج أن يُترجم بدونها.
47
الفصل .3برنامجك الأوّل
48
الفصل 4
نص على الشاشة .جيد ،لـكنّ هذا ليس شيئا مهماً .هذا لأنك لا تعرف بعد ما يدعى بـالمتغي ّرات
تعلّمت كيفية إظهار ّ
) (Variablesفي البرمجة.
فائدة هذه المتغيرات هي تمكين الحاسوب من حفظ أعداد في الذاكرة .سنبدأ ببعض الشرح حول ذاكرة الحاسوب
وكيفي ّة عملها .قد يبدو هذا بسيطا جدّا للبعض ،لـكن ّي أفترض أن ّك لا تعرف شيئا عن ذاكرة الحاسوب.
كل إنسان حيّ له ذاكرة .الأمر عينه بالنسبة للحاسوب ،لـكن الحاسوب له أنواع عديدة من الذاكرة.
؟
لم يملك الحاسوب أنواع عديدة من الذاكرة ،واحدة يمكنها أن تكفي ،أليس الأمر كذلك ؟
كلّا :المشكلة أننا نحتاج ذاكرة سر يعة )لاسترجاع المعلومات بسرعة( وفي نفس الوقت كبيرة )لحفظ بيانات كثيرة(
قد تضحك إن أخبرتك أننا حتى اليوم لم نتمكن من صنع ذاكرة بهذه المواصفات .أو بالأحرى الذاكرة السر يعة باهظة الثمن
لذلك لا يتم إنتاج الـكثير منها.
لذلك نجد في الحواسيب الحديثة ذاكرة سر يعة جدا لـكنها ليس ذات سعة كبيرة ،وأخرى ذات سعة كبيرة جدّا لـكنها
غير سر يعة.
كي أوضح لك الصورة أكثر ،إليك أنواع الذاكرة الموجودة في الحاسوب ،من الأسرع إلى الأبطأ :
.2ذاكرة التخبئة ) : (Cache memoryتمثل همزة وصل بين السجلات والذاكرة الحية.
49
الفصل .4عالم المتغي ّرات
.3ذاكرة الوصول العشوائي ) : (Random access memoryو هي الذاكرة التي نستخدمها كثيرا ،وتدعى اختصارا
.RAM
كما قلت لك ،لقد رتبتها من الأسرع )السجلات( إلى الأبطأ )القرص الصلب( ،وإن كنت قد تابعت جيدا فقد
فهمت أن الذاكرة الأصغر هي الأسرع والأبطأ هي الأكبر.
السجلات لا تسع إلا لحمل بضعة أعداد أما القرص الصلب فيمكنه تخزين ملفات ضخمة.
م
عندما أقول ذاكرة بطيئة فهذا بالنسبة لحاسوبك ،ففي نظر الحاسوب استغراق 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التصريح عن متغير
صدّقني هذه المقدّمة القصيرة عن الذاكرة ستكون مهمّة أكثر مما تعتقد .الآن يمكننا العودة إلى البرمجة.
ل مكان.
في برامجنا سيكون هناك الـكثير من المتغيرات .ستراها في ك ّ
• اسم :وهو الذي يمكننا من معرفة المتغي ّر .في البرمجة لن يكون علينا تذك ّر عناوين الذاكرة .بدلا من ذلك علينا فقط
استخدام أسماء المتغيرات .المترجم هو من سيقوم بتحو يل الأسماء إلى عناوين.
في لغة البرمجة Cكل متغير يجب أن يملك اسما خاصا به .ومن أجل متغيرنا الذي يحوي عدد الأرواح المتبقية للاعب
يمكننا أن نسميه ” ”Number of livesأو شيء من هذا القبيل.
للأسف توجد بعض الشروط ،لا يمكنك تسمية المتغير كيفما شئت :
• لا يجب أن يحتوي الاسم سوى على الحروف الصغيرة والـكبيرة والأرقام ) .( abcABC012
• المسافات ممنوعة .بدلا من ذلك يمكننا استخدام الحرف المعروف باسم .( _ ) underscoreإنه الحرف الخاص
الوحيد غير الحروف والأرقام الذي يمكن استعماله في اسم متغير.
وأخيرا يجب أن تعرف أن لغة Cتفر ّق بين الحروف الصغيرة والـكبيرة .ولثقافتك ،نقول إن Cحساسة لحالة الحروف
) .(Case sensitiveكمثال ،الأسماء widthأو WIDTHأو WiDthتعتبر أسماء متغيرات مختلفة ،حتى لو كانت تعني
لنا الأمر نفسه.
هذه أمثلة عن أسماء متغيرات صالحة . phoneNumber ، phone_number ، surname ، name ، numberOfLives :
لكل مبرمج طر يقة خاصة في كتابة أسماء المتغيرات .خلال هذا الفصل سأر يك طر يقتي :
53
الفصل .4عالم المتغي ّرات
ل كلمة.
• إن كان في الاسم أكثر من كلمة أضع حرف كبيرا في بداية ك ّ
أطلب منك كتابة أسماء متغيراتك بنفس الطر يقة التي أتبعها ،هذا لـكي نكون على تفاهم.
x
أي ّا كان اختيارك ،فعليك دائما إعطاء أسماء واضحة لمتغيراتك .كان بإمكاننا اختصار numberOfLivesإلى
nolمثلا .هذا أقصر في الكتابة ،لـكنه أقل وضوحا عندما تعيد قراءة الشفرة المصدر ية .فأنصحك بإعطاء أسماء
أطول لمتغيراتك إن كان ذلك يحسّن فهمها.
أنواع المتغيرات
حاسوبنا كما نعلم ليس سوى آلة كبيرة جدا للحساب .لا يجيد التعامل سوى مع الأعداد .لـكن يوجد أنواع كثيرة من
الأعداد :
• الأعداد العشر ية ،أي التي تحوي فاصلة عشر ية .9810.7 ،1.7741 ،75.909 :
حاسوبك المسكين بحاجة للمساعدة ! عندما تطلب منه تخزين عدد ،يجب أن تذكر له نوعه .هذا ليس لأن ّه لا يمكنه
التعرف عليه تلقائي ّا ،ولـكن للتنظيم ولعدم أخذ كميات كبيرة من الذاكرة بدون فائدة.
عندما تصرّح عن متغي ّر فسيكون عليك تحديد نوعه .إليك أنواع المتغيرات الأساسية في لغة : C
!
القيم المعروضة هنا تمثل الحد الأدنى المضمون من طرف اللغة .في الحقيقة قد تتمكن من تخزين أعداد أكبر من
ل الأحوال من المستحسن تذك ّر هذه القيم عندما تختار نوع متغيراتك.
هذه .في ك ّ
54
.2.4التصريح عن متغير
م
للعلم أن ّي لم أعرض جميع الأنواع هنا ،بل الأساسية منها فقط.
الأنواع الثلاثة الأولى ) ( long ، int ، charتسمح يتخزين الأعداد الصحيحة ).( ... ،4 ،3 ،2 ،1
النوعان الأخيران ) ( double ، floatيسمحان بتخزين الأعداد العشر ية ).( ... 16.911 ،13.8
سترى أنّنا نتعامل من الأعداد الصحيحة معظم الوقت لأنّها سهلة الإستخدام.
x
احذر في الأعداد العشر ية من استخدام الفاصلة ،حاسوبك لا يستخدم سوى النقطة .لذلك لا تكتب 54, 9بدل
! 54.9
ل شيء ،توجد أنواع أخرى تعرف بـ ) unsignedعديمة الإشارة( تصلح لتخزين الأعداد الموجبة فقط.
هذا ليس ك ّ
يجب إضافة كلمة unsignedإلى النوع لاستخدامها.
كما ترى ،مشكلة الأنواع عديمة الإشارة هي عدم القدرة على تخزين الأعداد السالبة ،لـكن الشيء الإ يجابي هي أنّها توف ّر
ل نوع موافق )مثلا signed charيتوق ّف عند ،127بينما unsigned charيمتد إلى
لنا ضعف حجم التخزين لك ّ
.(255
م
unsigned ن النوع charقد تم ّ إدراجه إمّا مع الكلمة المفتاحية ، signedأو مع الكلمة المفتاحية
تلاحظ أ ّ
لـكن لا يوضع وحده أبدا .السبب بسيط :هذا النوع يمكن أن يكون بإشارة أو بدون إشارة حسب الحواسيب.
لذلك أنصحكم بتحديد أي واحد منهما تريدون حسب نوع القيمة المراد تخزينها.
؟
لم َِاذا توجد ثلاثة أنواع من المتغيرات الصحيحة ؟ ألا يكفي نوع واحد ؟
بلى ،و لـكن إنشاء أنواع متعدّدة هدفه الاقتصاد من استهلاك الذاكرة .فعندما نطلب من الحاسوب حجز مساحة لمتغي ّر
من نوع charفهذا سيكون أقل من المساحة المستهلـكة لو إخترنا متغي ّرا من نوع . int
كان هذا مهمّا جدّا عندما كانت الحواسيب محدودة الذاكرة .أمّا اليوم فحواسيبنا بها ذاكرات كبيرة جدّا فلم يعد هذا
يمث ّل مشكلة .إذا احترت أيّ واحد من الأنواع تستخدم ،فاختر intللأعداد الصحيحة )أو doubleللعشر ية(.
55
الفصل .4عالم المتغي ّرات
التصريح سيكون سهلا الآن ،يجب علينا فقط أن نت ّبع الترتيب التالي :
.2نترك فراغا.
مثلا ،إذا أردنا إنشاء المتغي ّر numberOfLivesمن نوع intسنكتب السطر التالي :
ما فعلناه للتو يسمّى تصر يحا عن متغي ّر )) (Declaring a variableهذا مصطلح يجب حفظه( .يمكنك وضع التصر يحات
في بدايات الدوال .و بما أن لدينا دالّة وحيدة فقط )الدالة ( mainفسنصرّح المتغي ّر هكذا :
56
.2.4التصريح عن متغير
بعض التوضيحات
في الواقع ،هناك أشياء تحدث ،لـكن ّك لا تراها .عندما يصل البرنامج إلى سطر التصريح عن المتغي ّر فإن ّه يطلب من الحاسوب
بهدوء أن يعطيه شيئا من ذاكرة الوصول العشوائي.
ل شيء على أحسن حال )و هذا ما يقع في غالب الأحيان( فإن الحاسوب يوافق على هذا الطلب .المشكل الوحيد
إن تم ك ّ
الذي يمكن أن يقع هو امتلاء الذاكرة ،لـكنّ هذا أمر نادر الحدوث ،من الذي سيملؤ الذاكرة بمتغيرات int؟
م
معلومة صغيرة :إن كان لديك عدد من المتغي ّرات بنفس النوع تريد التصريح عنها ،فمن غير الضروري
يكفي فصل أسماء المتغي ّرات عن بعضها بفواصل على نفس السطر ،مثلا : ل متغي ّر.
كتابة سطر لك ّ
هذا ينشئ ثلاثة متغي ّرات أسماؤها على التوالي : ;. int numberOfLives, level, playerAge
level ، numberOfLivesو . playerAge
هذا أمر سهل للغاية فإن أردنا أن نعطي قيمة لمتغيرنا numberOfLivesفيكمننا ببساطة كتابة هذا :
لن نفعل شيئا بعد هذا .ستضع اسم المتغير ،إشارة تساوي ،بعدها القيمة التي تريد أن تضعها بالداخل ،في هذه الحالة
سنعطي القيمة 5للمتغير . numberOfLivesبرنامجنا الكامل سيكون هكذا :
57
الفصل .4عالم المتغي ّرات
؟
عند التصريح عن متغي ّر ،أيّ قيمة تكون فيه ؟
فسيحجز مكانا في الذاكرة لهذا المتغي ّر ،لـكن ما هي قيمته في هذه اللحظة ؟
هل يملك قيمة افتراضي ّة ؟ ) 0مثلا(.
الجواب هو لا ،لا و لا ثم ّ لا ! لا توجد قيمة افتراضي ّة .المكان محجوز لـكنّ القيمة لا ٺتغي ّر .لا يتم ّ حذف ما يوجد في
خانة الذاكرة .ستكون قيمة متغي ّرك هي نفس القيمة التي كانت موجودة من قبل في تلك الخانة ،و يمكن أن تكون أيّ
شيء !
إن لم يتم تغيير هذا المكان من الذاكرة من قبل ،فقد تكون قيمته ،0لـكنّ هذا ليس مؤكّدا ،قد يأخد متغي ّرك القيمة
ل على بقايا برنامج قد استخدم هذه الخانة من قبل.
363أو ،18و هذا يد ّ
ن كل ما يجب
و لهذا كي لا تعترضنا المشاكل لاحقا يجب تهيئة المتغير عند التصريح عنه ،في لغة Cهذا أمر ممكن حيث أ ّ
القيام به هو الدمج بين التصريح و تعيين قيمة المتغير في نفس التعليمة كالتالي :
هنا يتم ّ التصريح عن المتغي ّر ثم ّ إسناد قيمة 5له بطر يقة مباشرة.
ن المتغي ّر يحمل القيمة 5مبدئي ّا.
الشيء الإ يجابي هنا أننا متأكدون من أ ّ
58
.3.4عرض محتوى متغير
الثوابت
قد نريد أحيانا إنشاء متغي ّر يحمل قيمة ثابتة طوال وقت تشغيل البرنامج .هذا يعني أن ّه بمجر ّد التصريح عنه ،فإن ّه يحافظ على
قيمته و لا يمكن لأحد تغيير قيمته التي يحويها.
للتصريح عن ثابت ،تكفي إضافة كلمة constقبل نوع المتغي ّر .و لـكن يجب إعطاء الثابت قيمة بمجر ّد التصريح عنه،
لأن ّه لاحقا سيكون الوقت متأخّرا و لن نتمكّن من تغيير قيمته.
مثال على التصريح بثابت :
;1 const int INITIAL_NUMBER_OF_LIVES = 5
م
ليس من الضروري أن يكون اسم الثابت مكو ّنا من حروف كبيرة فقط ،لـكنّ هذا يساعدنا على التفر يق بينها و
بين المتغي ّرات .لاحظ أيضا أننا نستخدم الرمز _ بدلا من الفراغ.
بغض النظر عن هذا ،الثابت يستعمل بشكل عادي كالمتغير ،يمكنك عرض قيمته إن أردت .الفرق الوحيد هو أن ّه
ّ
إذا حاولت تغيير قيمة الثابت في برنامجك فسينبّهك المترجم إلى وجود خطأ.
أخطاء الترجمة تعرض أسفل الشاشة )في المكان الذي أسميه ”منطقة الموت” هل ٺتذكر ؟( ،في هذه الحالة سيقوم
المترجم بعرض رسالة تشبه التالي :
’[Warning] assignment of read-only variable ’INITIAL_NUMBER_OF_LIVES
نحن نعرف كيف نعرض نصّ ا على الشاشة باستخدام الدالة . printf
الآن سنتعلم كيف نعرض القيمة الخاصة بالمتغير باسخدام نفس الدالة.
سنستخدم الدالة printfبنفس الطر يقة ،باستثناء أننا سنقوم بإضافة رمز خاص في المكان الّذي نريد عرض قيمة
المتغي ّر فيه .مثلا :
;)”1 printf(”You have %d lives left
هذا الرمز الخاص ما هو إلّا %متبوعا بمحرف ) ’ ’dفي هذا المثال( .هذا الحرف يبېّن ما الذي سنقوم بعرضه.
’ ’dتعني أننا سنعرض قيمة متغي ّر من نوع . int
توجد حروف أخرى عديدة ،لـكن من أجل التبسيط فسنكتفي بهذه :
59
الفصل .4عالم المتغي ّرات
م
ن الشكل المستخدم لعرض floatو doubleهو نفسه.
لاحظ أ ّ
شارفنا على الإنتهاء .حددنا موضع كتابة عدد صحيح ،لـكننا لم نذكر ما هو ! يجب علينا أن نحدد للدالة printfالمتغي ّر
الذي نريد عرض قيمته.
لفعل ذلك ،أكتب اسم المتغي ّر بعد علامات الاقتباس بعد وضع فاصلة :
60
.4.4استرجاع إدخال
يمكن عرض قيم متغيرات عديدة بنفس الدالة . printfيكفي فقط أن تضع %dأو %fفي الأمكنة المناسبة ،ثم
تحديد المتغي ّرات الموافقة بنفس الترتيب ،مفصولة عن بعضها البعض بفواصل.
مثال :
1 printf(”You have %d lives and you are in the level n° %d”, numberOfLives, level
;)
!
%d يجب عليك تحديد المتغي ّرات بالترتيب الصحيح %d ،الأولى توافق المتغي ّر الأوّل ) ( numberOfLivesو
الثانية توافق المتغي ّر الثاني ) .( levelإذا أخطأت في الترتيب ،فلن يكون للجملة أيّ معنى.
حسنا ،فلنقم بتجربة صغيرة الآن .لاحظ أنني قد حذفت توجيهات المعالج القبلي )أعني تلك السطور التي تبدأ برمز
مرة الآن :
ل ّ ،( #أنا أفترض أنك تضعها في ك ّ
بدأت المتغيرات تصبح ممتعة الآن .سنتعلم كيف نطلب من المستخدم كتابة عدد في الـكونسول .سنسترجع هذا العدد
و نحفظه في متغير .عندما نفعل ذلك سيكون بإمكاننا القيام بكثير من الأمور ،سوف ترى ذلك.
لـكي نطلب من المستخدم إدخال شيء سنقوم باستخدام دالة جاهزة تسمّى . scanf :
هذه الدالة تشبه إلى حد كبير . printfيجب عليك وضع شكل لتحديد ما سيدخله المستخدم ) ... float ، int
( .ثم يجب عليك تعيين إسم المتغير الذي سيأخذ هذا العدد.
61
الفصل .4عالم المتغي ّرات
علينا وضع %dبين مزدوجتين .كما يجب وضع & أمام اسم المتغي ّر الذي سيستقبل القيمة.
؟
و لـكن لماذا نضع & أمام اسم المتغي ّر ؟
هنا يجب عليك أن ٺثق بي .لأنه لا يمكنني أن أشرح لك ما الذي تعنيه في الوقت الحالي .لـكني أضمن لك أنيّ سأقوم
بشرحه في وقت لاحق.
x
احذر ! يوجد اختلاف صغير في الشكل بين printfو ! scanfلاسترجاع floatيجب استخدام الشكل
” ، ”%fأما من أجل النوع doubleفيتم استخدام ”. ”%lf
فلنعد إلى برنامجنا .عندما يصل هذا الأخير إلى ، 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
يتوقف البرنامج مؤق ّتا بعد عرض السؤال و يظهر مؤشرا على الشاشة ،عليك إذن أن تكتب عددا )عمرك( .اِضغط بعدها
على المفتاح ”إدخال” ) .(Enterبعد ذلك يواصل البرنامج عمله .هنا ،يقوم البرنامج بعرض قيمة المتغي ّر ageعلى الشاشة.
حسنا ،لقد فهمت الأساس .بفضل الدالة scanfيمكننا البدء في التفاعل مع المستخدم.
ليكن في علمك أنه لا مانع من إدخال أي شيء غير عدد صحيح :
• إن قمت بإدخال عدد عشري ،مثلا ،2.9فسيتم قطعه تلقائي ّا .في هذه الحالة سيتم حفظ العدد 2في المتغي ّر.
62
ملخّ ص
• إن قمت بكتابة أحرف عشوائية ،مثلا ،éèdyf :فلن ٺتغي ّر قيمة المتغي ّر .و الشيء الجي ّد في هذه الحالة هو أننا قمنا بتهيئة
قيمة المتغير على .0لذلك سيعرض البرنامج ”Ah ! so you are 0 years old” :إن لم يتم الأمر بشكل صحيح .لو لم
نقم بتهيئة المتغي ّر فسيعرض البرنامج عددا عشوائيا !
ملخّ ص
• حواسيبنا تملك عدة أنواع من الذاكرة .من الأسرع إلى الأبطأ :السجل ّات ،ذاكرة التخبئة ،الذاكرة الحي ّة و القرص
الصلب.
• للاحتفاظ بالمعلومات ،يحتاج برنامجنا إلى تخزين البيانات في الذاكرة .لهذا يستخدم الذاكرة الحية .السجل ّات و
ذاكرة التخبئة تستخدم أيضا لز يادة الأداء ،لـكنّ هذا يحدث تلقائيا و ليس علينا أن نهتم بهذا الأمر.
• في الشفرة المصدر ية ،المتغي ّرات هي البيانات المحفوظة مؤق ّتا في الذاكرة الحي ّة .قيمة هذه البيانات يمكن أن ٺتغي ّر
أثناء تشغيل البرنامج.
• بالمقابل ،نطلق اسم الثوابت على بيانات محفوظة في الذاكرة الحي ّة .قيمة هذه البيانات لا يمكن أن ٺتغي ّر.
• توجد أنواع عديدة من المتغي ّرات ،تختلف في حجم الذاكرة التي تشغله .بعض الأنواع مثل intتستخدم لتخزين
عدد صحيح ،و أخرى مثل doubleتخز ّن أعدادا عشر ي ّة.
63
الفصل .4عالم المتغي ّرات
64
الفصل 5
حسابات سهلة
كما قلت لك في الفصل السابق :جهازك ماهو إلا آلة حاسبة كبيرة .سواء كنت تسمع الموسيقى ،تشاهد فلما ًأو تلعب
لعبة ،فإن الحاسوب ينجز الحسابات طيلة الوقت.
هذا الفصل سيساعدك على التعرف على معظم الحسابات التي يقوم بها الجهاز .سنعيد استعمال ما نحن بصدد تعلّمه
عن عالم المتغيرات .الفكرة هي أننا سنقوم بعمليات على المتغيرات :نجمعها ،نضربها ،نخز ّن النتائج في متغيرات أخرى ،إلخ.
حتى و إن لم تكن من هواة الر ياضيات ،فإن هذا الفصل إلزامي و لا مفر ّ منه.
بالرغم من قدرة الجهاز الواسعة إلا أنه في الأساس يتعمد في حساباته على عمليات بسيطة للغاية و هي :
• الجمع،
• الطرح،
• القسمة،
• الضرب،
• الترديد )) (Moduloسأشرح لاحقا ما الّذي يعنيه إذا لم تكن تعرفه الآن(.
إن كان بودك القيام بحسابات أكثر تعقيدا )كالأسس و اللوغار يثم و ماشابه( ،يجب عليك إذا برمجتها أو بمعنى آخر :
توضح للجهاز كيف يقوم بها.
لحسن الحظ ،سترى لاحقا ًفي هذا الفصل أنه توجد مكتبة في لغة الـ ،Cتحتوي على دوال ر ياضية جاهزة .لن يكون عليك
إعادة كتابتها إلا إذا أردت فعل ذلك تطو ّعيا أو كنت أستاذ ر ياضيات.
65
الفصل .5حسابات سهلة
لا يجب أن تكون محـترفا في الحساب الذهني لتعرف أن النتيجة ستكون 8بعد تشغيل البرنامج.
بالطبع البرنامج لن يظهر أية نتيجة باستعمال هذه الشفرة المصدر ية .إذا أردت معرفة محتوى المتغير resultعليك
باستعمال الدالة printfالتي تجيد كيفية استخدامها جيدا ً الآن :
5 + 3 = 8
إذا كنت قد استعملت من قبل الآلة الحاسبة الخاصة بحاسوبك ،فيفترض بك أن تكون متعو ّدا على هذه الإشارات.
لا يوجد أي شيء صعب بخصوصها باستثناء القسمة و الترديد اللذان سأشرحهما فيما يلي بالتفصيل.
القسمة
ينجز الحاسوب عملية القسمة بشكل طبيعيّ عندما لا يوجد أي باق .مثلا العملية 6 / 2تعطينا النتيجة ،3النتيجة صحيحة.
حت ّى الآن ،لا مشكلة.
ق مثل ... 5 / 2نتوقع أن النتيجة ستكون ،2.5و لـكن أنظر إلى ما تعطيه
لـكن لو نأخذ الآن عملية قسمة ببا ٍ
الشفرة :
5 / 2 = 2
66
.1.5الحسابات القاعدية
هناك مشكل كبير ،فنحن نتوق ّع أن نحصل على القيمة ،2.5لـكن الحاسوب أعطى القيمة ! 2
؟
double هه أنا أعرف السبب ! لأن المتغير resultالذي استخدمناه هو من نوع ! intلو استخدمنا النوع
لاستطاع تخزين العدد العشري !
لا ،ليس هذا هو السبب ! جرب نفس الشفرة بتغيير نوع النتيجة إلى doubleو ستجد بأننا نتحصّ ل على نفس النتيجة
2لأن طرفا العملية من نوع intفإن الحاسوب سيعيد نتيجة من نوع . int
إن أردنا أن يظهر لنا الجهاز القيمة الصحيحة ،يجب أن نغير العددين 2و 5إلى عددين عشر يين كالتالي 2.0 :و
)5.0قيمتهما هي نفسها لـكن الجهاز سيعتبرهما عددين عشر يين ،و بالتالي هو يظن بأنه يقوم بقسمة عددين عشر يين( :
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
حسنا ،لا يوجد ما يمكنني إضافته بخصوص عملية الترديد .كان هذا فقط شرحا لمن لا يعرفها.
الشيء الجيد هو أنه بعد أن تعلمت كيف تستخدم العمليات القاعدية ،يمكنك الآن أن ٺتعل ّم كيفية القيام بهذه العمليات
على المتغيرات.
لا شيء يمكنه منعك من كتابة الشفرة التالية :
هذا السطر يعمل على جمع المتغيرين number1و number2ثم يخزن النتيجة في المتغير . result
هنا بدأت الامور الممتعة تظهر ،و حقيقة ،مستواك الحالي يسمح لك ببرمجة آلة حاسبة بسيطة .نعم ،نعم ،أؤكّد لك
ذلك !
تخيل وجود برنامج يطلب من المستخدم إدخال عددين ،ثم يقوم بتخزينهما في متغيرين ،ثم يجمع هذين المتغيرين و يخزن
النتيجة في متغير اسمه . resultلم يبق سوى إظهار النتيجة على الشاشة في وقت لا يتمكّن فيه المستخدم حتى من تخمين
النتيجة.
68
.2.5الاختصارات
بدون أن تشعر ،لقد أنشأت أول برنامج لك ذو فائدة .إن ّه قادر على جمع عددين و إظهارا النتيجة على الشاشة !
يمكنك التجريب باستخدام أعداد أخرى )يجب ألا تتجاوز الحد الأقصى لتحمّل نوع الـ ( intو سيقوم الحاسوب
بالحساب بشكل سر يع جدا ً لا يتجاوز بعض أجزاء من المليار من الثانية !
أنصحك أيضا ًبتجريب العمليات الأخرى )الطرح ،القسمة و الضرب( لـكي ٺتدرب .لن يكون هذا متعبا إلّا بقدر تغيير
إشارة أو اثنتين .يمكنك أيضا ًإضافة متغير ثالث و جمع ثلاثة متغيرات دفعة واحدة .سيشتغل البرنامج دون مشاكل :
;1 result = number1 + number2 + number3
2.5الاختصارات
توجد طرق في لغة الـ Cتسمح لنا باختصار كتابة بعض العمليات .لماذا نستعمل هذه الإختصارات ؟ لأننا نحتاج في
غالب الأحيان من كتابة عمليات مكر ّرة .ستفهم ما أريد قوله حينما ترون ما نسمّيه بالز يادة.
69
الفصل .5حسابات سهلة
في غالب الأحيان ستضطر إلى إضافة 1إلى محتوى متغير .و بالتقدّم في برنامجك ،تكون لديك متغيرات يزيد محتواها في
ل مرة بـ.1
ك ّ
نفترض أن لديك متغيرا يحمل اسم ، numberهل تعرف كيف تضيف له 1دون أن تعرف محتواه ؟
ما الذي يحصل هنا ؟ نقوم بالحساب number + 1ثم نخزن الناتج في المتغير ! numberو منه فإن كان المتغير
يحمل القيمة 4فهو بعد العملية يحمل القيمة .5لو أنه كان يحمل القيمة ،8فهو الآن يحمل القيمة ،9إلخ.
هذه العملية ٺتكرر كثيرا .و بما أن المبرمج شخص كسول ،سيتعبه أمر كتابة اسم المتغير مرتين في نفس التعليمة )نعم
هذا أمر متعب !( .لهذا تم اختراع اختصار لهذه العملية بما نسميه بـالز يادة ) (Incrementationالتعليمة أسفله تعطي تماما
نفس نتيجة التعليمة السابقة :
1 ;number++
هذا السطر له نفس وظيفة السطر السابق الذي كتبناه قبل قليل ،أليس مختصرا و قصيرا ؟ إنه يعني ”إضافة 1لمتغير”.
يكفي إذا أن نرفق باسم المتغير numberالاشارة +مرتين ،مع عدم نسيان الفاصلة المنقوطة الخاصة بنهاية التعليمة.
هذه العملية ستساعدنا كثيرا مستقبلا لأننا سنضطر للقيام بعملية الز يادة كثيرا.
م
إذا كنت دقيق الملاحظة ،كنت لتلحظ أن إشارتي ++متواجدتان أيضا ًفي اسم اللغة .C++أنت الآن قادر على
أن تفهم السر وراء ذلك ! الـ C++تعني أننا نتكلم عن لغة الـ” Cمع ز يادة” .عملي ّا ،يسمح لنا الـ C++بالبرمجة بطر يقة
مختلفة ،لـكن لا يعني أنه ”أفضل” من الـ .Cهو فقط مختلف.
الإنقاص )(Decrementation
ل بساطة عكس عملية الز يادة ،فهي تقوم بإنقاص 1من متغير.
إنّها بك ّ
بالرغم من أن عملية الز يادة هي أكثر استعمالا ًإلا أن عملية الإنقاص تبقى شائعة أيضا.
ربّما كان بإمكانك تخمين ذلك وحدك ! بدل وضع إشارة ++نضع إشارة . --إذا كان محتوى المتغير هو 5فسيصبح
بعد الإنقاص يساوي .4
70
.3.5المكتبة الر ياضياتي ّة
الاختصارات الأخرى
توجد اختصارات أخرى تعمل بنفس المنطلق .هذه الاختصارات تصلح لكل العمليات القاعدية . % / * - + :
هي تساعدنا على تجنب تكرار نفس اسم المتغير في نفس التعليمة.
لضرب محتوى المتغير في 2مثلا نقوم بالتالي :
1 ;number = 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
ل العمليات القاعدية ،فيمكننا أن نجمع ،نطرح ،نضرب أيّ الشيء الجيد هنا أنه يمكننا استعمال اختصارات على ك ّ
عدد.
هي اختصارات عليك تعلّمها إن كان البرنامج الذي تكتبه يحتوي الـكثير من التعليمات المكر ّرة.
في لغة الـ Cهناك دائما ما نسميه بالمكتبات القياسية ) ،(Standard librariesو هي المكتبات التي تستخدم على الدوام.
إنّها مكتبات قاعدية تستخدم كثيرا.
أذكرك بما قلت سابقا ،المكتبة هي مجموعة دوال جاهزة .هذه الدوال تمت كتابتها من طرف مبرمجـين قبلك ،و هي
ل برنامج جديد.
تساعدك على تجنب إعادة اختراع العجلة في ك ّ
أعطيك مثالا ،لغة الـ Cلا تحتوي على عملية الأس ! كيف نحسب المربع ؟ يمكنك كتابة العملية 52في برنامجك لـكن
الجهاز لن يفهمها أبدا ً لأن ّه لا يعرف مالّذي تعنيه هذه ،إلا إن قمت بشرح العملية له باستخدام المكتبة الر ياضياتي ّة !
يمكننا الاستعانة بالدوال الجاهزة في المكتبة الر ياضياتي ّة ،لـكن لا تنس كتابة توجيهات المعالج القبلي الخاصة بها في بداية
كل برنامج :
71
الفصل .5حسابات سهلة
ما إن تكتب السطر السابق حت ّى تصبح قادرا ً على استخدام كل الدوال المتوفرة في هذه المكتبة.
fabs
هذه الدالة تحسب القيمة المطلقة للرقم ،و نرمز لها بالشكل التالي .|x| :
القيمة المطلقة لعدد هو قيمته الموجبة :
م
هناك دالة اخرى مماثلة لـ 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
pow
هذه خاصة بحساب قوى عدد )الأسس( .يجب أن تعطيها قيمتين ،الأولى هي العدد الذي تريد إجراء العملية عليه و
الثانية هي القوة ال ّتي يجب رفع العدد إليها .هذا مخطط الدالة :
كمثال 2” :قوة ) ”3ال ّتي نكتبها عادة 23على الحاسوب( هو الحساب 2 ∗ 2 ∗ 2الّذي يعطي النتيجة : 8
sqrt
exp
هذه الدالة تحسب قيمة الدالة الأسي ّة ذات الأساس eلعدد معين.
تعيد . double
73
الفصل .5حسابات سهلة
log
هذه الدالة تحسب اللوغار يتم النيبيري لعدد معين) .الّذي نرمز له أيضا بـ”(”ln
log10
ملخّ ص
• الحاسوب ما هو سوى آلة حاسبة كبيرة :كل ما يجيد فعله هو القيام بالعملي ّات.
• العمليات التي يجيدها الحاسوب قاعدية جدا :الضرب ،القسمة ،الجمع ،الطرح و الترديد )باقي القسمة(.
• بالإمكان إجراء عمليات على المتغيرات ،الحاسوب سر يع جدا ً في هذا النوع من العمليات.
• الز يادة ) (Incrementationهي عملية إضافة الرقم 1إلى متغير .نكتبها . variable++
• لز يادة عدد العمليات التي يمكن للحاسوب القيام بها ،نستعمل المكتبة الر ياضي ّاتي ّة )أي
>( #include <math.h
74
الفصل 6
الشروط )(Conditions
لقد رأينا فيما سبق بأن هناك العديد من لغات البرمجة .بعضها متشابه :الـكثير منها مستلهم من الـ.C
في الواقع ،لغة الـُ Cأنشئت منذ زمن طو يل ،و هذا جعلها نموذجا لل ّغات الجديدة.
لغات البرمجة تختلف في بعض الأمور ،لـكن هناك مبادئ لا يمكن أن تخلو منها أية لغة برمجية .لقد رأينا كيف ننشئ
المتغي ّرات ،كيف نقوم بالحسابات ،و الآن سنمر ّ إلى الشروط.
من دون استعمال الشروط ،برامجنا ستقوم دائما بنفس العمل !
الشروط تسمح لنا بأن نقوم باختبارات على المتغيرات .مثلا يمكننا القول” ،إن كان المتغي ّر machineيساوي ،50
يجب أن نقوم بكذا و كذا .و لـكن من المؤسف عدم إمكانية اختبار سوى المساواة ! يجب أيضا اختبار ما إن كان المتغي ّر،
أقل من ،50أقل أو يساوي ،50أكبر ،أكبر أو يساوي ...لا تقلق ،في الـ Cكل شيء مُع َد !
• الشرط ، if
• الشرط ، else
قبل أن نرى كيف نكتب شرطا من النوع if ... elseفي الـ ،Cيجب أن تعرف ثلاثة رموز أساسية .هذه
الرموز ضرور ية لإنشاء الشروط.
75
الفصل .6الشروط )(Conditions
الجدول التالي يحوي رموز لغة الـ Cال ّتي يجب حفظها عن ظهر قلب :
المعنى الرمز
يساوي ==
أكبر >
أصغر <
x
انتبه جيدا ،هناك رمزا مساواة == لنقوم باختبار المساواة .فالمبتدؤون يقومون غالبا باقتراف خطأ وضع إشارة
واحدة = ،و هذا لديه معنى مختلف في الـ .Cسأذك ّرك بهذا لاحقا.
ifبسيط
فلنبدأ ،لنقم باختبار بسيط ،يقول للحاسوب :إن كان المتغير يساوي كذا فلنقم بكذا.
في الإنجليز ي ّة ،كلمة ”إذا” تُت َرجم إلى . ifو هذا ما الّذي نستخدمه في لغة الـ Cلإنشاء اختبار.
أكتب إذن ifثم الأقواس و في داخلها الشرط.
76
else ... if .1.6الشرط
م
عندما لا يكون هناك سوى تعليمة واحدة بين الحاضنتين ،يمكننا أن نتخلى عنهما .لـكني أفضل أن تضعوهما دائما
من أجل قراءة أفضل للشفرة.
لـكي نجر ّب الشفرات السابقة و نفهم كيف يعمل الـ ، ifيجب أن نضعه داخل الدالة mainو لا ننس التصريح بالمتغير
ageو إعطاءه قيمة ابتدائية.
هذا قد يبدو بديهي ّا للبعض ،و لـكنّ كثيرا من القر ّاء قد ضاعوا في هذه الأسطر و هذا ما دفعني لإضافة هذا الشرح.
هذه شفرة كاملة يمكنك تجريبها :
مسألة نظافة
77
الفصل .6الشروط )(Conditions
حاول عرض شفرتك بنفس طر يقتي :حاضنة على سطر ،ثم ّ التعليمات )مسبوقة بجدولة ) ،((tabulationثم ّ حاضنة
الإغلاق على سطر آخر.
م
توجد طرق عديدة لعرض الشفرة بشكل جي ّد .هذا لا يغي ّر في عمل البرنامج شيئا ،لـكنّها مسألة ”نمط معلوماتيّ” إن
أردت .إذا رأيت الشفرة شخص آخر معروضة بشكل مختلف قليلا ،فهذا لأن ّه يبرمج بنمط مختلف .الهدف من هذا
كل ّه هو أن تبقى الشفرة مهو ّاة و مقروءة.
الآن و بما أن ّك تعرف كيف تقوم باختبار بسيط ،سنذهب بعيدا :إن لم يعمل الشرط )كان خاطئا( ،فسنطلب من
الحاسوب تشغيل تعليمات أخرى.
هذا يشبه ما يلي :إن كان المتغير يساوي كذا فلنقم بكذا ،و إلا فلنقم بكذا.
الأمر سهل :إن كان المتغي ّر ageأكبر من أو يساوي 18سنكتب على الشاشة ”! ،”You are majorو إن لم يكن
الأمر كذلك فسنكتب ”! .”You are minor
سنرى كيف نقوم بـ”إذا” و ”إلّا” .يمكن أيضا ًالقيام بـ”و إلا ّ فإذا” للقيام باختبار آخر في حالة ما إذا لم ينجح الأوّل .هذا
الاختبار يوضع بين ifو . else
يمكن ترجمة ذلك بما يلي :إن كان المتغير يساوي كذا ،فافعل كذا و إلا فإن كان يساوي كذا ،فافعل كذا و إلا
)جميع الحالات المتبقية( ،افعل كذا.
78
else ... if .1.6الشرط
• سيختبر الـ ifالأول ،إن كان الشرط محققا ،فسيقوم بتنفيذ التعليمات الموجودة بين الحاضنتين الأولتين.
• إن لم يكن الشرط الأول محققا )يعني أننا في الـ ( else ifسيختبر الشرط الثاني ،إن كان هذا الأخير محققا فإنه
سينفذ التعليمات الموجودة بين الحاضنتين الثانيتين.
• في حالة ما لم يكن الشرط الأول محققا و لا حتى الثاني ،فإنه سينفذ التعليمات الموجودة بين الحاضنتين الأخيرتين.
م
الـ elseو الـ else ifليسلا ضرور يّين .لإنشاء شرط ،فقط ifهو الضروري )هذا منطقي ،و إلّا فكيف
سيكون هناك شرط !(.
مرة واحدة
عدّة شروط في ّ
قد يكون من المهم القيام بعدّة اختبارات في شرط واحد .مثلا ،قد تريد اختبار ما إن كان العمر أكبر من 18و العمر
أصغر من .25
لهذا علينا بتعلم رموز جديدة :
المعنى الرمز
و &&
أو ||
لا )عكس الشرط( !
لنختبر إن كان العمر في نفس الوقت أكبر من 18و أقل من 25يجب كتابة :
)1 if (age > 18 && age < 25
&& يعنيان ”و” .بالعربية هذا يعني ” :إذا كان العمر أكبر من 18و إذا كان العمر أصغر من .”25
79
الفصل .6الشروط )(Conditions
لاستخدام ”أو” ،يجب كتابة الرمزين || .لكتابة الرمز | ،نضغط في لوحة المفاتيح على ) 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
دينار مثلاً.
الرمز الموافق لهذا الاختبار هو علامة التعجّ ب )!( .في علوم الحاسوب ،علامة التعجّ ب تعني ”لا” .يجب وضع هذه
العلامة من أجل عكس الشرط لقول ”إن لم يكن هذا صحيحا” :
))1 if (!(age < 18
يمكن أن نترجم هذا إلى ”إن كان الشخص غير قاصر” .لو حذفنا ! فسيعكس المعنى ” :إن كان الشخص قاصرا”.
مثلما قلت سابقا ،لـكي نختبر ما إن كان العمر يساوي 18نكتب :
)1 if (age == 18
{ 2
3 ;)”! printf(”You have just become major
} 4
إن وضعت علامة = واحدة فإن المتغير 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
سنبدأ ببعض التجارب قبل أن نقدّم هذا المفهوم .إليك شفرة مصدر ي ّة بسيطة جدّا أقترح عليك تجريبها :
1 ;)if (1
2 {
3 ;)”! printf(”It is true
4 }
5 else
6 {
7 ;)”! printf(”It is false
8 }
النتيجة :
! It is true
؟
لـكن ،لم نضع شرطا داخل ، ifهذا عدد فقط .مالّذي يعنيه ؟ لا معنى لهذا.
81
الفصل .6الشروط )(Conditions
النتيجة :
! It is false
حاول الآن استبدال الصفر بأي عدد صحيح ،مثل ،−36 ،−10 ،226 ،15 ،4إلخ .النتيجة دائما .”It is true !” :
إذا وضعنا ،0الاختبار سيعتبر خاطئا ،أمّا إن وضعنا 1أو أيّ عدد آخر ،فالاختبار سيكون صحيحا. ملخّ ص اختباراتنا :
الشرح واجب
ل مرة تقوم فيها باختبار شرط ،فإن الشرط سيقوم بارجاع القيمة 1إن كان صحيحا ًو القيمة 0إن كان
في الواقع أنه في ك ّ
خاطئاً.
لنفرض أن ageكان .23إذن ،الشرط صحيح ،و الحاسوب ”سيستبدل” بطر يقة ما age >= 18بـ.1
بعد ذلك ،سيحصل الحاسوب )في رأسه( على ) . if (1عندما يكون ،1كما رأينا ،فسيعتبره صحيحا.
بالمثل ،إذا كان الشرط خاطئا ،فسيستبدل age >= 18بـ ،0فالشرط خاطئ ،و الحاسوب سيقرأ تعليمات . else
فلنجرب مع متغير
فلنجر ّب الآن شيئا آخر :وضع نتيجة الشرط في متغير ،إن هذا الأمر ممكن مادام الشرط معتبرا ً من طرف الحاسوب
كتعليمة.
كما تلاحظ فإن الشرط age >= 18أعطى 1و بالتالي فإن المتغير majorأخذ القيمة 1يعني صحيح .يمكنك
التأكد بالـ . printf
قم بنفس الاختبار مع وضع . age == 10هذه المر ّة major ،سيكون .0
82
.2.6المتغيرات المنطقية ) ،(Booleansأساس الشروط
تذك ّر هذا جي ّدا :نقول عن متغي ّر نجعله يأخذ القيم 1و 0أن ّه متغي ّر منطقيّ ).(boolean
• = 0خطأ،
• = 1صحيح.
ل الأعداد الأخرى تعني صحيح )كما رأينا سابقا( .و لـكن من أجل تبسيط الأمور ،لن
في الواقع 0يمث ّل خطأ ،و ك ّ
نستخدم سوى 0و 1لقول إن كان الشرط صحيحا أو خاطئا.
بما أن المتغير majorيساوي 1فإن الشرط محقق و بالتالي سيظهر على الشاشة .”You are major !” :
و هذا عمليّ جدّا ،فالشرط أصبح مفهوما بشكل أفضل ،فنقرأ ) ، if (majorو الّذي يعني ”إن كنت بالغاً”.
الشروط على المتغي ّرات المنطقي ّة هي إذن سهلة للقراءة و الفهم ،ما دمت قد أعطيت أسماء واضحة لمتغي ّراتك كما طلبت منك
من البداية.
يعني ”إن كان الشخص راشدا و ذكراً” .في هذه الحالة ،المتغير boyأيضا هو متغير منطقي ،يساوي 1إذا كان
الشخص ولدا ً و 0إذا كان بنتاً .أعتقد أنك فهمت المقصود.
باختصار ،المتغيرات المنطقية تسمح لنا بمعرفة ما إن كان الاختبار صحيحا ًأم خاطئاً.
م جدّا و ما شرحته لك سيمكّنك من فهم كثير من الأمور ال ّتي ستأتي لاحقا.
هذا مه ّ
83
الفصل .6الشروط )(Conditions
؟
سؤال صغير :إن كتبنا if (major == 1) :فسيعمل ،أليس كذلك ؟
ن
هذا صحيح ،لـكن مبدأ المتغي ّرات هي أن نستطيع اختصار عبارة الشرط و جعلها أكثر قابلي ّة للقراءة .اعترف أ ّ
) if (majorتُفهم أحسن ،أليس كذلك ؟
إذا كان متغي ّرك يحمل عددا )مثل العمر( ،قم باختبار من الشكل ). if (variable == 1 تذك ّر إذن :
بالمقابل،إذا كان المتغي ّر منطقيا ّ) ،أي إمّا 0أو 1لقول صحيح أو خطأ( ،قم باختبار من الشكل ). if (variable
switch 3.6الشرط
مع ذلك if ... else ،يبدو قليلا ...تكرار ي ّا .فلنَر َ هذا المثال :
84
switch .3.6الشرط
switch بناء
إذن ،من أجل تجن ّب التكرار في اختبار قيمة متغي ّر وحيد ،فقد اخترعوا تعليمة مثل . if ... elseهذه التعليمة
الخاصّة تدعى . switchإليكم مثالا نقوم فيه باستعمال . switchعلى المثال السابق :
استلهم من مثالي لإنشاء switchالخاصة بك .نستخدمها نادرا ،لـكنّها عملي ّة كثيرا لأنّها تجعلنا نكتب قدرا أقل
)قليلا( من الشفرة.
الفكرة هي أن نكتب ) switch (myVariableلـكي نقول :سنقوم باختبار حول قيمة المتغير . myVariable
ثم نقوم بفتح حاضنتين نغلقهما في الأسفل.
بعد ذلك ،في داخل الحاضنتين ،ٺتعامل مع كل الحالات ... ، case 45 ، case 5 ، case 4 ، case 2 :
يجب عليك في نهاية كل حالة ،أن تضع التعليمة ; . breakإن لم تفعل فإن الحاسوب سينتقل لقراءة التعليمات
الموالية التي هي من المفروض محجوزة للحالات الأخرى ! أي أن التعليمة ; breakتجـبر الحاسوب على الخروج من
الحاضنتين.
85
الفصل .6الشروط )(Conditions
في النهاية default ،هي مثابة الـ elseالذي تعرفه جيدا ً الآن .أي أنه إن لم يساوي المتغير أي من الحالات
المذكورة فإن الحاسوب يقوم بتشغيل الحالة . default
إلى العمل !
نريد أن نظهر في الـكونسول قائمة للمستعمل ،نستخدم printfلعرض مختلف الخيارات المتوف ّرة .كل اختيار يرافقه
رقم ،و على المستعمل أن يدخل رقم الاختيار الذي يريد.
هذا على سبيل المثال ما يجب أن يظهر :
ستقوم بإظهار القائمة بالإستعانة بـ printfو ستستعمل scanfلاسترجاع اختيار المستعمل في مهمّتك )إن قبلتها( :
متغي ّر choiceو من ثم ّ تستعمل switchلتختبر الإختيار الذي قام به المستعمل .و في النهاية حسب الحالة ستقول
للمستعمل ماذا اختار ،هل اختار ” ”Big Macأو ” ”Mc Baconمثلا.
تصحيح
86
الشروط المختصرة: الثلاثيات.4.6
! و هذا هو العمل
ّ عندما تبرمج يجب عليك التفكير في ك، ! في الواقعswitch في نهاية الـdefault أتمنى أنك لم تنس الـ
،ل الحالات
. و هذا ليس ما كنت تنتظره،” أو حت ّى ”مرحبا10 فسيأتيك دائما أحمق ليكتب،4 و1 فحت ّى لو طلبت عددا بين
يجب عليك اختبار حالة. لأن ّه يمكنه إدخال أيّ شيء، عليك دائما أن تكون حذرا و لا ٺثق في المستخدم،باختصار
. if في الشروط التي تنشئها بالـelse أيضا ًأوdefault
م
. لأنّنا سننشئ عادة برامج فيها و ستحتاجها حتما،أنصحك أن تألف عمل القوائم في الـكونسول
87
الفصل .6الشروط )(Conditions
ن لدينا متغي ّرا منطقي ّا majorيأخذ القيمة 1إن كان الشخص راشدا ً و 0إن كان قاصراً.
فلنفرض أ ّ
نريد تغيير قيمة المتغي ّر ageحسب المتغي ّر المنطقي ،نضع فيها 18إن كان راشدا و 17إن كان قاصرا .أوافقك الرأي
على أن ّه مثال غبيّ ،و لـكن ّه يسمح لنا بفهم آلية عمل العبارات الثلاثي ّة.
)1 if(major
2 ;age = 18
3 else
4 ;age = 17
م
تلاحظ أنني قمت بنزع الحاضنتين لأنه لا توجد سوى تعليمة واحدة داخل ifو أخرى داخل ، elseكما قد
شرحت لك سابقا.
هذه الشفرة تقوم تماما بنفس عمل الشفرة السابقة ،لـكنّها مكتوبة بالشكل الثلاثي :
الثلاثيات تسمح ،بسطر واحد ،بتغيير قيمة متغي ّر حسب شرط معيّن .هنا ،الشرط هو 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
بعدما تعلمنا كيف ننشئ شروطا بلغة ،Cسنكتشف معا ًالحلقات التكرار ي ّة ) .(Loopsما هي الحلقة ؟ هي تقنية تسمح
بتكرار نفس التعليمات عدة مرات .و ستساعدنا كثيرا من الآن و صاعدا خاصة في العمل التطبيقي الأوّل الذي ينتظرنا
بعد هذا الفصل.
استرخ :هذا الفصل سيكون سهلاً .لقد تعرفنا سابقا على ما تعنيه المتغي ّرات المنطقية ) (booleansو الشروط
) (conditionsفي الفصل السابق ،و بذلك كنا قد تخلصنا من عمل كبير .من الآن فصاعدا ً ستكون الأمور سلسة أكثر و
لن يكون في العمل التطبيقي القادم الـكثير من المشاكل.
فلننتهز الفرصة ،لأننا لن نتأخر في الدخول في الجزء الثاني من الكتاب .سيكون من الجي ّد لك أن تنتبه !
كما قلت سابقا ً :هي عبارة عن تعليمة تسمح لنا بتكرار نفس التعليمات عدة مرات.
تماما مثل الشروط ،توجد طرق عديدة لإنشاء الحلقات .و لـكن مهما اختلفت الطرائق فالهدف واحد :تكرار تعليمات
لعدد معيّن من المرات.
لدينا في لغة Cثلاثة أنواع من الحلقات :
while •
do ... while •
for •
91
الفصل .7الحلقات التكرار ية )(Loops
المشكلة في هذا النظام هو أننا إن لم نقم بإيقافه ،فالجهاز قادر على تكرار نفس التعليمات إلى مالانهاية ! و لن يتذمّر،
أنت تعرف :هو يفعل ما تأمره أنت بفعله ...يمكنه أن يعلق في حلقة غير منتهية ،و هذا النوع من الحالات يعتبر مصدر
خوف بالنسبة للمبرمجـين.
و هنا نجد ...الشروط ! فعندما ننشئ حلقة نقوم دائما بتعر يف شرطها .هذا الشرط يعني ”كر ّر الحلقة دون توقف
مادام هذا الشرط صحيحا”.
كما قلت ،فهناك عدة طرق للقيام بذلك و سنبدأ من دون تأخير بإنشاء حلقة من نوع whileفي الـ.C
لا يوجد أبسط من هذا .الكلمة whileتعني ”مادام” ،لذا نقول للجهاز :مادام الشرط صحيحا ،كرر التعليمات
المتواجدة بين الحاضنتين.
أقترح عليك أن نقوم باختبار بسيط :سنطلب من المستعمل ادخال العدد ،47مادام لم يقم بإدخاله ،نطلب منه إعادة
إدخاله مجددا ً ...و لن يتوقف البرنامج حتى يقوم المستعمل بإدخال العدد ) 47نعم أعرف ،إنه عمل شيطاني( :
أنظر إلى الاختبار الذي قمت به ،للعلم أنني تعمدت الخطأ ثلاث مرات :
92
while .2.7الحلقة
الآن لنجعل الأمر ممتعا ًأكثر :نريد من الحلقة أن ٺتوقف بعد عدد معين من التكرارات.
لهذا سنستعين بمتغير counterالذي سيأخذ القيمة 0في بداية البرنامج ثم نقوم بز يادته ،هل ٺتذكر ما قلناه في الفصل
السابق حول الز يادة ) (incrementation؟ التي تنص على إضافة 1لمتغير حينما نكتب . variable++
إقرأ جيدا الشفرة المصدر ية التالية و حاول التمعن فيها و فهمها :
النتيجة :
Hello !
Hello !
Hello !
Hello !
Hello !
Hello !
Hello !
Hello !
Hello !
Hello !
؟
كيف يعمل هذا بالتحديد ؟
.2الحلقة whileتأمر بالتكرار مادامت قيمة المتغير counterأصغر من .10بما أن قيمة المتغير counterهي
0في البداية ،فإننا ندخل في الحلقة لأن الشرط محقق.
.4نقوم بز يادة قيمة المتغير counterبفضل التعليمة ; . counter++كان المتغير counterيحمل القيمة ،0
أمّا الآن فهو يحمل القيمة .1
.5نصل لنهاية الحلقة )حاضنة الإغلاق( :نعيد العملية من جديد و نتأكد ما إن كان الشرط محققا ًأي ما إن كانت
قيمة المتغير أصغر من 10؟ في هذه الحالة نعم لأن المتغير counterيحمل القيمة 1و هي أصغر من 10إذا سنمر ّ
بنفس التعليمة داخل الحاضنتين.
93
الفصل .7الحلقات التكرار ية )(Loops
و هكذا دواليك counter ،تصبح 9 ،8 ، ... ،3 ،2 ،1ثم ّ .10حينها يصيح الشرط counter < 10غير
محقق ،و عندها نخرج من الحلقة.
ل شيء !
إن كنت قد فهمت المثال السابق فقد فهمت ك ّ
يمكنك الاستمتاع بتجربة أعداد أكبر من ) 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الحلقة
هذا النوع من الحلقات يشبه كثيرا whileإلا أنه قليل الاستعمال عادة.
الشيء الوحيد الذي يتغي ّر هو مكان الشرط في الحلقة .فع ِو َض أن يكون الشرط في بداية الحلقة ،فهو موجود في نهايتها :
لهذا يجب أحيانا استخدام الحلقات من هذا النوع ،لنضمن تكرارها للعملية على الأقل مرة واحدة.
!
هناك استثناء في الحلقة do ... whileيغفل عنه الـكثير من المبتدئين ،و هو وجود فاصلة منقوطة في نهاية
الحلقة ! لا تنس وضع واحدة بعد الـ whileو إلا ستظهر لك أخطاء أثناء الترجمة !
for 4.7الحلقة
الحلقات forمستخدمة كثيرا في البرمجة .لا أملك إحصائيات لـكن كن واثقا ًأنك ستستخدم الحلقة forأكثر
من الحلقة . whileو لهذا يجب عليك أن تجيد استخدام كلتا الحلقتين.
و كما قلت لك فالحلقة forماهي إلا عبارة عن طر يقة أخرى لكتابة الحلقة whileالتي رأيناها قبل قليل.
من المثال السابق لدينا :
95
الفصل .7الحلقات التكرار ية )(Loops
ما الاختلاف ؟
• تلاحظ أننا لم نهي ّئ المتغير counterعلى قيمة ابتدائية تساوي 0عند إنشائه )لـكن ّه كان بإمكاننا فعل ذلك(.
فلنهتم بما يوجد بين القوسين فهو كل ما يهم في الحلقة . forهناك ثلاث تعليمات مختلفة نفصل فيما بينها بفواصل
منقوطة.
• الأولى هي التهيئة ) : (initialisationهذه التعليمة تعمل على تحضير المتغير . counterفي حالتنا ،تهيئته بالقيمة
.0
• الثانية هي الشرط ) : (conditionكما رأينا في الحلقة whileفإن الشرط يخـبرنا ما إن كان يجب تكرار الحلقة أم
لا .مادام الشرط محققا ًفإن الحلقة تستمر في تكرار التعليمات.
• أخيرا ،الز يادة ) : (incrementationهذه التعليمة يتم تنفيذها بعد كل التعليمات أي في نهاية الدورة الواحدة و هي
ل مرة .في غالب الأحيان نقوم بالز يادة ،لـكن يمكننا أن نستعمل الإنقاص
تقوم بتعديل قيمة المتغير counterفي ك ّ
)مثلا ،( counter--كما يمكننا القيام بعملي ّات أخرى ،مثلا ز يادة قيمة العداد بـ.( counter+=2 ) 2
في النهاية ،نلاحظ أن حلقة forماهي سوى حلقة كتابة مختصرة .تعل ّم كيفية استعمالها لأنك ستحتاجها بكثرة !
ملخّ ص
• الحلقة forهي التي سنستعملها بكثرة في التطبيقات .نقوم فيها غالبا ًبز يادات أو إنقاصات للمتغيرات.
96
الفصل 8
نصل اليوم إلى أول عمل تطبيقي .الهدف هو أن أر يك أنك قادر على برمجة الـكثير من الأشياء بما علّمتك إياه .لأنه في
الواقع ،الجانب النظري للغة أمر جي ّد لـكننا إن كنا لا نعرف كيف نطب ّق ما تعلّمناه بشكل سلس فلا داعي لإهدار وقتنا
بتعلم المزيد.
صدّق أو لا تصدّق ،يسمح لك مستواك الآن ببرمجة أول برنامج ممتع .إن ّه لعبة كونسول )أذك ّرك بأننا سنصل للبرامج
بنافذة لاحقا( .مبدأ عمل اللعبة سهل للغاية ،وسهل البرمجة ،و لهذا اخترتها لتكون موضوع أول عمل تطبيقي لك.
ل شيء ،سأشرح عمل برنامجنا .إن ّه لعبة صغيرة نسمّيها ”أكثر أو أقل”.
قبل ك ّ
.2يطلب منك أن تخمن عددا و بالتالي ستختار بدورك عددا من 1إلى .100
.3يقوم الجهاز بمقارنة العدد الذي كتبته بالعدد ”الغامض” الّذي حصل عليه عشوائي ّا ،ثم يقول لك ما إن كان العدد
الغامض أصغر أو أكبر من العدد الذي اخترته أنت.
.4ثم يقوم الجهاز بإعادة طلب العدد منك.
.5ثم يقول لك ما إن كان العدد الغامض أصغر أو أكبر من العدد الذي اخترته أنت.
و الهدف من اللعبة هو أن تجد العدد الغامض في أقل عدد ممكن من المحاولات بالطبع.
و هذه ”لقطة شاشة” مما يجب أن تكون عليه اللعبة في طور التنفيذ :
97
الفصل .8عمل تطبيقي ” :أكثر أو أقل” ،لعبتك الأولى
؟
لـكن كيف يختار الجهاز عددا عشوائيا ً؟ أنا لا أجيد فعل هذا !
صحيح ،نحن لا نجيد كيفية توليد عدد عشوائي .و يجب القول أن طلب ذلك من الحاسوب ليس أمرا ً سهلا ً :هو يجيد
القيام بعمليات حسابية ،لـكن أن يستخرج عددا ً عشوائيا ،هذا أمر لا يجيد فعله !
في الواقع ،لـ”محاولة” الحصول على عدد عشوائيّ ،يجب القيام بحسابات معقّدة للحاسوب ،و هذا ما يعود في النهاية إلى
القيام بحسابات !
• إما أن نطلب من المستعمل أن يقوم باختيار عددٍ عشوائي في بداية اللعبة بواسطة الدالة . 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و أنصحك بتعر يف
الثابتين في بداية البرنامج هكذا :
كي يشتغل برنامجك بشكل صحيح ،يجب أن تقوم بتضمين المكتبات stdlibو stdioو ) timeالأخيرة تستعمل
من أجل الأعداد العشوائية(.
يجب إذن أن يبدأ برنامجك بالتالي :
يكفي شرحا !
حسنا ،سأتوقف هنا لأنه لو أكملت سأعطيك الشفرة الخاصة ببرمجة اللعبة كاملة !
م
لتوليد الأعداد العشوائية ،اضطررت لإعطائك شفرة ”جاهزة” دون أن أشرح كيف تعمل بالضبط .عادة ،لا
أحب فعل هذا ،لـكن لا يوجد خيار هنا لأنني لا أريد تعقيد الأشياء كثيرا حالي ّا.
ّ
تأكّد أنه ستتعلم فيما يلي الـكثير من المفاهيم التي ستسمح لك بفهم هذه الأشياء بنفسك.
باختصار ،أنت تعرف الـكثير .لقد شرحت لك المبدأ و أعطيتك صورة عن البرنامج لحظة التشغيل.
بعد كل هذا أنت قادر على كتابة البرنامج لوحدك.
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يمكنك ترجمة البرنامج من جديد للتجريب كما يمكنك التعديل عليه إن أردت.
الشرح
هي الأسطر التي تبدأ بعلامة #في الأعلى .و هي تقوم بتضمين المكتبات التي نحتاج إليها.
أعطيتها لك جاهزة أعلاه ،لذلك فإن استطعت ارتكاب خطأ هنا ،فأنت قويّ جدّا !
المتغيرات
قمت بتعر يف الثوابت كما ذكرت في بداية هذا الفصل .الشيء الجيد أيضا ًهو إمكانية تحديد صعوبة اللعبة )بوضع أعلى
قيمة هي 1000مثلا( ،يكفي أن نعدّل على هذا السطر و نعيد ترجمة البرنامج.
الحلقة
لقد اخترت الحلقة . do ... whileنظر ي ّا ،حلقة whileبسيطة يمكنها أن تقوم بالمهمة أيضا ،لـكن ّي وجدت بأن
استخدام do ... whileمنطقيّ أكثر.
مرة
لماذا ؟ لأنه إن كنت ٺتذكر do ... while ،هي حلقة تسمح لنا بتشغيل التعليمات بين الحاضنتين على الأقل ّ
واحدة .و نحن سنطلب من المستعمل إدخال العدد الغامض لمرة واحدة على الأقل أيضا ً)فالمستعمل غير قادر على تخمين
العدد في أقل من محاولة واحدة ،إلا إن كان شخصا ًخارقا ً!(.
ل دورة من الحلقة نطلب من المستعمل إدخال عدد .نقوم بتخزين القيمة في المتغير . inputNumber
في ك ّ
بعدها ،نقارن المتغير السابق بالمتغير . mysteriousNumberهنا نجد ثلاثة احتمالات :
101
الفصل .8عمل تطبيقي ” :أكثر أو أقل” ،لعبتك الأولى
• يكون العدد الغامض أكبر من العدد الذي أدخله المستعمل و بالتالي تظهر على الشاشة العبارة ”! .”Greater
• يكون العدد الغامض أصغر من العدد الذي أدخله المستعمل و بالتالي تظهر على الشاشة العبارة ”! .”Lesser
• يكون العدد الغامض لا أكبر و لا أصغر من الّذي أدخله المستعمل ،أي أن ّه مسا ٍو بالضرورة ! و بالتالي تظهر على
الشاشة العبارة ”! .”Bravo, you have found it
يجب أن تضع شرطا للحلقة .و هذا سهل للإ يجاد :نعيد تشغيل الحلقة مادام العدد الذي تم إدخاله لا يساوي العدد
الغامض.
و ما إن يتم إدخال العدد المطلوب ٺتوقف الحلقة و بالتالي ينتهى البرنامج هنا.
أفكار للتحسين
لم تعتقد أن ّي أريد للعمل التطبيقي أن ينتهي هنا ؟ أنا أصرّ على أن تقوم بتحسين البرنامج ،من أجل التدريب .و لتتأكد
بأنه مع التدريب ستتطور قدراتك ! من يعتقد أنه يقرأ الدروس و لا يطب ّق ما جاء فيها فهو مخطئ ،أقولها و أكررها !
لقد عرفت أن ّه لديّ خيال واسع ،و حتى بكونه برنامجا ًسهلا ًفأنا أملك أفكارا لتطويره !
لـكن احذر هذه المرة فلن أعطيك التصحيح ،عليك أن ٺتدب ّر أمرك بنفسك ! فإن كانت لديك حقّا مشاكل ،فيمكنك
ز يارة منتديات OpenClassroomsمن أجل طلب المساعدة من الآخرين.
• في هذا البرنامج ،عندما يجد اللاعب العدد الغامض يتوقف البرنامج ،لماذا لا نجعل البرنامج يسأله ما إن أراد جولة
ثانية ؟
إذا أردت تطبيق الفكرة فيجب عليك وضع حلقة تشمل تقريبا كل برنامجك .و هذه الحلقة ٺتكرر مادام اللاعب لم
يطلب إيقاف البرنامج ،و أنصحك بإضافة متغير منطقي اسمه مثلا continuePlayingذي قيمة ابتدائية مساو ية
لـ .1إذا طلب اللاعب إيقاف البرنامج ،تُغي ّر القيمة لـ 0و يتوقف البرنامج.
• أنشئ وضع لاعبين ! يعني بالإضافة إلى وضع اللاعب الواحد ،ضع إمكانية اشتراك لاعبين !
لهذا عليك بصنع قائمة ) (menuفي البداية لتطلب من المستخدم اختيار لاعب واحد أو لاعبين إثنين .الفرق
بين الوضعين هو توليد العدد الغامض ،ففي الحالة الأولى نستعمل الدالة randو في الحالة الثانية نستعمل الدالة
! scanf
• إنشاء مستو يات مختلفة لصعوبة اللعبة .يمكنك وضع قائمة في البداية ليختار المستخدم فيه المستوى و هذا مثال :
102
أفكار للتحسين
لفعل هذا يجب التعديل على الثابت . MAXبطبيعة الحال لا يمكننا أن نسميه ثابتا إن كانت قيمته ٺتغير أثناء البرنامج
! قم بتغيير اسم هذا المتغير إلى ) maximumNumberو لا تنس نزع الكلمة المفتاحية constلأنه ببقاءها يبقى
ثابتا !( .ٺتغير قيمة هذه المتغير حسب المستوى المختار.
ستساعدك هذه التحسينات على الإشتغال قليلاً .لا تتردد في إ يجاد أفكار أخرى لتطوير هذه اللعبة ،أعلم أن ّه يوجد ! و
لا تنس أن المنتديات في خدمتك في حالة واجهت صعوبات.
103
الفصل .8عمل تطبيقي ” :أكثر أو أقل” ،لعبتك الأولى
104
الفصل 9
الدوال )(Functions
ننتهي من الجزء الأول من الكتاب )المبادئ( بهذها المفهوم المهم الذي يتكلّم عن الدوال في لغة .Cكل البرامج في لغة
Cترتكز على المبدأ الذي سأشرحه في هذا الفصل.
سوف نتعلم هيكلة برامجنا وتقسيمها لعدة أجزاء ،تقريبا ً كما لو كنّا نلعب لعبة .Lego
البرامج الـكبيرة في لغة Cماهي إلا تجميعات لأجزاء صغيرة من الشفرات المصدر ية ،و هذه الأجزاء هي بالضبط ما نسمّيه
...بالدوال !
رأينا في الفصول السابقة بأن برامج الـ Cتبدأ بدالة تدعى . mainلقد أعطيتك مخططا ًتلخيصي ّا ًلتذكيرك ببعض الكلمات
المفتاحية :
في الأعلى ،نجد توجيهات المعالج القبلي )اسم معقد سأرجع لشرحه في وقت لاحق( .من السهل التعر ّف على هذه
التوجيهات :هي تبدأ بإشارة #و هي غالبا ًتوضع في أعلى الملف المصدري.
لقد قلت لك بأن البرنامج في الـ Cيبدأ بالدالة . mainأؤكد لك ،هذا صحيح ! إلا أننا في هذه الحالة بقينا داخل الدالة
mainو لم نخرج أبدا ً منها .أعد قراءة الشفرات المصدر ية في الدروس السابقة و سترى :لقد بقينا دائما داخل الحاضنتين
الخاصتين بالدالة الرئيسية.
؟
حسنا ،هل من السيء فعل ذلك ؟
105
الفصل .9الدوال )(Functions
لا ،هذا ليس ”سيئا” .لـكن ذلك خلاف ما يفعله المبرمجون بلغة Cحقيقة.
بل و إن كل البرامج تقريبا ًلا تكون مكتوبة فقط داخل حاضنتي الدالة . mainلح ّد الآن كانت البرامج التي نكتبها صغيرة
و لهذا فهي لم تطرح أي مشكل ،لـكن تخي ّل معي برامج ضخمة تحتوي على آلاف الأسطر من الشفرة المصدر ية ! لو كانت
ل الأسطر مكتوبة داخل حاضنتي الدالة الرئيسية لأصبحنا في السوق.
ك ّ
سنتعل ّم الآن كيف ننظّم عملنا .سنبدأ بتقسيم برامجنا إلى قطع صغيرة )تذك ّر فكرة Legoال ّتي حدّثتك عنها قبل قليل(.
ل قطعة تسمّى دالّة.
ك ّ
الدالة تقوم بتنفيذ تعليمات و تقوم بإرجاع نتيجة .هي عبارة عن قطعة من الشفرة المصدر ية تعمل على القيام بمهمة
معينة.
نقول بأن الدالة تملك قيمة الإدخال و قيمة الإخراج .المخطط التالي يمثل المبدأ الذي تعمل به الدالة :
.1الإدخال :نقوم بإدخال المعلومات إلى الدالة )بإعطائها معلومات تعمل عليها(.
.3الإخراج :بعد أن تنتهي الدالة من الحسابات تعطينا النتيجة على شكل قيمة الإخراج أو الإرجاع.
يمكن أن نتصو ّر دالة تسمى tripleتضرب العدد في .3بالطبع الدوال في الغالب هي أكثر تعقيدا ً من هذا.
هدف الدوال إذا هو تبسيط الشفرة المصدر ية ،لـكي لا نضطر إلى إعادة كتابة نفس الشفرة المصدر ية عدّة مرات على
التوالي.
ُأحْلُم قليلا ً :لاحقاً ،سننشئ مثلا دالة اسمها showWindowتقوم بفتح نافذة في الشاشة .ما إن نكتب الدالة
)المرحلة الأصعب( ،لن يتبقّ لنا سوى القول ”أيتها الدالة ، showWindowأظهري لي النافذة !” .يمكننا أيضا ًكتابة دالة
moveCharacterتهدف إلى تحر يك شخصية ما في اللعبة ،إلخ.
106
.1.9إنشاء و استدعاء دالة
مخطط دالة
لقد تكو ّنت لديك فكرة على الطر يقة التي تعمل بها الدالة . main
و مع ذلك يجب أن أر يك كيف نقوم بإنشاء دالة.
الشفرة المصدر ية التالية تمث ّل دالة تخطيطياً .هذا نموذج للحفظ :
• ) typeنوع قيمة الإخراج( :هو نوع الدالة .مثل المتغيرات ،للدوال أنواعها الخاصة .هذا النوع يعتمد على القيمة
التي ترجعها الدالة :إن كانت الدالة ترجع عددا ً عشر ياً ،فسنضع بالتأكيد الكلمة المفتاحية ، doubleأما إن
كانت ترجع عددا ً صحيحاً ،سنضع النوع intأو longمثلا .و لـكن يمكن أيضا ًإنشاء دوال لا ترجع أي شيء
! هناك إذا نوعان من الدوال :
– دوال ترجع قيمة :تعطيها أحد الأنواع ال ّتي نعرفها كـ intأو charأو ، doubleإلخ.
– دوال لا ترجع أية قيمة :نعطيها نوعا خاصا يدعى ) voidو الذي يعني الفراغ(.
– : functionNameهو اسم الدالة .يمكنك أن تسمي الدالة مثلما تريد لطالما تحـترم القواعد التي ٺتبعها في
تسمية المتغيرات )لا للأحرف التي تحتوي على العلامات الصوتية ) ،(accentsلا فراغات ،إلخ(.
– ) : parametersهي قيم الإدخال( :داخل قوسين ،يمكنك أن تبعث معاملات للدالة .هي القيم التي
ستعمل بها الدالة.
م
يمكنك أن تبعث القدر الذي تريد من المعاملات .كما يمكنك ألا تبعث أية معامل ،و لـكن نادرا ً ما
يُستخدم هذا.
مثلاً ،بالنسبة للدالة ، tripleأنت تبعث عددا ً كمعامل .الدالة ”تسترجع” العدد و تضربه في .3تقوم بعد
ذلك بإرجاع نتيجة حساباتها.
– بعد ذلك ،نجد الحاضنتين الل ّتان تشيران إلى بداية الدالة و نهايتها .داخل الحاضنتين ،تضع التعليمات التي
تريدها .بالنسبة للدالة ، tripleيجب أن تكتب التعليمات التي توافق ضرب قيمة الإدخال في .3
الدالة إذا هي عبارة عن آلية ٺتلقّى قيم إدخال )المعاملات( و ترجع قيمة إخراج.
107
الفصل .9الدوال )(Functions
إنشاء دالة
فلنرى مثالا تطبيقي ّا دون مزيد من التأخير :الدالة tripleالتي حدثتك عنها منذ قليل .فلنقل أن هذه الدالة ٺتلقّى
عددا ً صحيحا ًمن نوع intو أنها ت ُرجع عددا ً صحيحا ًأيضا ًمن نوع . intهذه الدالة تضرب العدد الذي نعطيها في : 3
م للغاية :كما ترى ،الدالة من نوع . intفهي مجـبرة على أن ترجع قيمة من نوع . int
هاهي أول دالة لك ! شيء مه ّ
داخل القوسين ،نجد المتغيرات التي ٺتلقّاها الدالة .الدالة tripleٺتلقّى متغيرا من نوع intيسمى . number
السطر الذي يشير إلى أن الدالة تقوم بـ”إرجاع قيمة” هو السطر الذي يحتوي على الكلمة المفتاحية . returnهذا
السطر يوجد في العادة في نهاية الدالة ،بعد الحسابات.
هذه الشفرة المصدر ية تعني للدالة ” :توق ّفي و أرجعي العدد .” resultيجب أن يكون هذا المتغير resultمن
نوع ، intلأن الدالة تقوم بإرجاع قيمة من نوع intكما قلت في الأعلى.
صرّحت عن )= أنشأت( المتغير resultفي الدالة . tripleهذا يعني أنه لا يستخدم إلا داخل هذه الدالة و
ليس داخل أخرى كالـدالة mainمثلا .أي أنه متغير خاص بالدالة . triple
هذه الدالة تقوم بنفس المهمة التي تقوم بها الدالة السابقة ،هي فقط أسرع من ناحية كتابتها .عموماً ،الدوال التي تكتبها
تحتوي الـكثير من المتغيرات من أجل إجراء الحسابات عليها ،نادرة هي الدوال القصيرة مثل . triple
العديد من المعاملات
الدالة tripleتحوي معاملا ًواحد ،لـكن من الممكن إنشاء دوال تقبل العديد من المعاملات.
مثلا ،دالة additionلجمع عددين aو : b
108
.1.9إنشاء و استدعاء دالة
لا معاملات
بعض الدوال ،نادرة أكثر ،لا تأخذ أية معامل كقيمة إدخال .هذه الدوال تقوم بنفس الشيء في غالب الأحيان .في
الواقع ،إذا لم تكن لديها أعداد تعمل عليها ،فمهمة هذه الدوال هي القيام بوظائف معينة ،كإظهار رسالة على الشاشة .و
ل مرة لأن الدالة لا ٺتلقّى أيّ معامل قد يكون قادرا على تغيير سلوكها !
أيضاً ،سيكون نفس النص الذي تظهره في ك ّ
بالفعل ،كما ترى فالدالة لا تحتاج إلى التعليمة returnلأنها لا ترجع أي شيء .الدالة التي لا ترجع أي شيء هي
دالة من النوع . void
استدعاء دالة
لحد الآن ،أطلب منك كتابة الدالة tripleقبل الدالة . mainفإذا وضعتها بعدها ،فلن يشتغل البرنامج .سأشرح
لك هذا لاحقاً.
109
الفصل .9الدوال )(Functions
داخل القوسين ،نبعث المتغير كمدخل للدالة ، 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إنشاء و استدعاء دالة
.5يقرأ التعليمة ...آه نحن نستدعي الدالة ، tripleيجب إذا أن نقفز إلى أول سطر من محتوى هذه الدالة في
الأعلى.
.7نقوم بالحسابات و ننتهي من الدالة .الكلمة المفتاحية returnتعني أن الدالة قد انتهت و تسمح بتحديد النتيجة التي
سترجعها الدالة.
إذا فهمت في أي ترتيب يتم تنفيذ التعليمات فيه ،فقد فهمت المبدأ .الآن يجب أن تفهم بأن الدالة تستقبل معاملات
كمداخل و ترجع قيمة كمخرج.
ليس الأمر نفسه بالنسبة لكل الدوال .أحياناً ،لا تأخذ الدالة أية معامل كإدخال ،و بالعكس أحيانا ً تأخذ ملاحظة :
الـكثير من المعاملات كإدخال )لقد شرحت لك هذا سابقاً(.
أيضاً ،يمكن لدالة أن ترجع قيمة كما يمكنها ألا ترجع أي شيء )و في هذه الحالة لا يكون هناك .( return
111
الفصل .9الدوال )(Functions
م
لست مضطرا ً إلى أن تخزن النتيجة في متغير ! يمكنك أن تعطي النتيجة المُرجعة من طرف الدالة 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
هذه الحالة تمث ّل نوعا ًما تداخل الدوال .الشيء الذي نستنتجه من هذا هو أنه بإمكان دالة أن تستدعي دالة أخرى !
ل شيء مركّ ب مع الأشياء الأخرى ،كما في لعبة .Lego
هذا هو مبدأ البرمجة بلغة ! Cك ّ
في النهاية ،سيبقى الشيء الأصعب هو كتابة الدوال .ما إن تكتبها ،لن يبق عليك سوى استدعائها دون أن تلقي بالا على
العمليات التي تجري بداخلها .هذا سيسمح لك بتبسيط كتابة برامجك بشكل كبير .و صدّقني ستحتاج إلى هذه المبادئ
كثيرا ً !
112
.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 }
لا يوجد اختلاف كبير مقارنة بالدالة ، tripleلقد أخبرتك بذلك مسب ّقاً .الدالة conversionطو يلة قليلا ًو
يمكن أن يتم اختصارها في سطر واحد ،سأترك لك عناء فعل ذلك بنفس الطر يقة التي شرحتها لك مسبقاً.
في الدالة ، mainتعمّدت وضع الـكثير من printfلأر يك الهدف من استعمال الدوال .لـكي أحصل على قيمة
50أورو ،ليس علي سوى استدعاء الدالة conversionبإعطائها 50كقيمة إدخال .و إذا أردت تحو يل 100أورو
إلى الفرنك ،كل ما أحتاج إلى فعله هو تغيير المعامل المرسل إلى الدالة )وضع القيمة 100في مكان القيمة .(50
113
الفصل .9الدوال )(Functions
اكتب دالة ثانية )دائما ً قبل الدالة ( mainتقوم بالعملية العكسية أي تحول من الفرنك إلى الأورو .لن حان دورك !
العقوبة
هنا نتكلم عن دالة لا ت ُرجع أية قيمة .تكتفي هذه الدالة بالقيام بمهمة )هنا ،إظهار النص على الشاشة(.
الدالة التي لا ترجع أيه قيمة هي دالة من نوع ، voidغير هذا لا يوجد شيء مختلف.
سيكون من الممتع أن نكتب دالة punishmentٺتلائم مع أيّ عقوبة ،فتقوم بإظهار النص الذي نريده نحن على
الشاشة .نبعث لها معاملين :النص الذي نريد و عدد المرات التي نريد أن يتم إظهاره .المشكل هو أننا لا نجيد بعد التعامل
مع النصوص في الـ) Cإذا كنت لم تنتبه فأذكرك أنه لح ّد الآن لم نتعامل سوى مع متغيرات تحمل قيما عددي ّة داخلها منذ
بداية الكتاب !( .و بهذا الصدد ،أخبرك أننا لن نتأخر حتى نتعل ّم كيفية التعامل مع النصوص .فعل ذلك معقّد قليلا ًو لا
يصلح أن نبدأ به في أول الكتاب !
114
أمثلة للفهم الجي ّد.2.9
مساحة مستطيل
؟
عرض و مساحة المستطيل داخل الدالة ؟،هل يمكننا أن نظهر مباشرة طول
هذه فقط طر يقة. يعرض نفس الرسالة السابقة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
الجزء ب
119
الفصل 10
في هذه المرحلة الثانية ،سنكتشف مبادئ متقدّمة في لغة الـ Cلن أخفي عليك ،هذه المرحلة صعبة الفهم و تحتاج منك
التركيز .في نهاية المرحلة ،ستكون قادرا ً على تدب ّر أمرك في معظم البرامج المكتوبة بلغة الـ .Cفي المرحلة التي تليها نتعل ّم كيف
نفتح نافذة ،كيف ننشئ لعبة ثنائية الأبعاد ...إلخ.
لح ّد الآن عملنا في ملف واحد سم ّيناه . main.cكان أمرا ً مقبولا ً لح ّد الآن لأن برامجنا كانت صغيرة ،لـكنها ستصبح
في القريب العاجل مركّ بة من عشرات ،لن أقول من مئات الدوال ،و إن كنت تريد وضعها كل ّها في نفس الملف ،فإن
هذا الأخير سيصبح ضخما ً جداً .لهذا السبب تم اختراع ما نسمّيه بالبرمجة المجز ّأة .المبدأ سهل :بدل أن نضع كل الشفرة
المصدر ية في ملف واحد ، main.cسنقوم بتفر يقها إلى عدة ملفات.
لح ّد الآن ،كنت عندما تنشئ دالة ،أطلب منك وضعها قبل الدالة الرئيسية . mainلماذا ؟
لأن للترتيب أهمية حقيقية هنا :فإن قمت بوضع الدالة قبل الـ mainفي الشفرة المصدر ية ،سيقرؤها الجهاز و يتعرف
عليها .حينما تقوم باستدعاء الدالة داخل الـ ، mainسيعرفها الجهاز و يعرف أيضا ًأين يبحث عليها.
بالعكس ،لو تضع الدالة بعد الـ ، mainلن يعمل البرنامج لأن الجهاز لم يتعر ّف بعد على الدالة .جرّب ذلك و سترى.
؟
لـكنه تصميم سيّء نوعا ًما ،أليس كذلك ؟
ل المشكل.
أنا متفق معك ! لـكن انتبه المبرمجون لهذه النقطة قبلك و عملوا على ح ّ
بفضل ما سأعلمك إياه الآن ،ستتمكن من الدوال في أي ترتيب كان في الشفرة المصدر ية ،هكذا لن تقلق من هذه
الناحية.
استعمال النموذج للتصريح عن دالة
سنقوم بتصريح دوالنا للحاسوب ،و هذا بكتابة ما نسميه بـالنماذج .لا تنبهر بهذا الاسم ،إنه يخب ّئ معلومة بسيطة جداً.
121
الفصل .10البرمجة المجز ّأة )(Modular Programming
قم بنسخ السطر الأول ) ( double rectangleSurface...المتواجد أعلى الشفرة المصدر ية )مباشرة بعد
تعليمات التضمين .( #includeأضف فاصلة منقوطة في نهاية هذا السطر.
و هكذا يمكنك أن تضع الدالة الخاصة بك rectangleSurfaceبعد الدالة mainان أردت !
الشيء الذي تغي ّر هنا هو إضافة النموذج أعلى الشفرة المصدر ية.
النموذج هو عبارة عن إشارة للجهاز ،يوحي إليه بوجود دالة تسمى rectangleSurfaceو التي تأخذ معاملات إدخال
معينة و ت ُرجِـع مخرجا من نوع أنت من تحدده .هذا يساعد الجهاز على تنظيم نفسه.
بفضل ذلك السطر ،يمكنك الآن وضع دوالك في أي ترتيب كان دون أي تفكير زائد.
أكتب دائما النموذج الخاص بدوالك .البرامج التي ستكتبها من الآن و صاعدا ً ستصبح أكثر تعقيدا ً و تستعمل الـكثير
من الدوال :من الأحسن أن ٺتعل ّم منذ الآن العادة الجيدة بوضع نموذج لكل دالة في الشفرة المصدر ية.
كما ترى ،الدالة mainلا تملك أي نموذج ،و كمعلومة فهي الوحيدة التي لا تملك نموذجا ً! لأن الجهاز يعرفها )فهي
نفسها مكررة في جميع البرامج(.
عليك أن تعرف أنه في سطر النموذج ،لست مضطرا ً إلى تحديد المعاملات التي ٺتلقاها الدالة كمدخل .الجهاز يحتاج أن
يتعر ّف إلى نوع المداخل فقط.
122
.2.10الملفات الرأسية )(Headers
x
لا تنس أبدا وضع فاصلة منقوطة بعد النموذج ،هذا يمكّن الحاسوب من التفر يق بين النموذج و بداية الدالة.
إن لم تفعل ،ستعترضك أخطاء غير مفهومة أثناء عملية الترجمة.
تطبيقياً ،برامجك لن تكون مكتوبة في ملف واحد . main.cبالطبع يمكن فعل ذلك ،لـكن لن يكون من الممتع أن تتجو ّل
في ملف به 10000سطر )شخصيا ًأعتقد هذا( .و لهذا فإنه في العادة ننشئ العديد من الملفات في المشروع الواحد.
؟
عفوا ...ماهو المشروع ؟
لا ! هل نسيت بسرعة ؟ سأعيد الشرح لأنه من اللازم أن نت ّفق على هذا المصطلح.
المشروع هو مجموع الملفات المصدر ية الخاصة ببرنامجك .لحد الآن برنامجنا لم ٺتكون إلا من ملف واحد .و يمكنك
التحقق من هذا بالنظر في البيئة التطوير ية الخاصة بك ،غالبا ما يظهر المشروع في القائمة على اليسار )الصورة الموالية( :
كما يمكنك رؤيته في يسار الصورة ،هذا المشروع ليس مكو ّنا إلا من الملف . main.c
123
الفصل .10البرمجة المجز ّأة )(Modular Programming
كما ترى ،هناك ملفات عديدة .هذا ما يكون عليه المشروع الحقيقي ،أي ٺتواجد به ملفات عديدة في القائمة اليسار ية
يمكن التعر ّف على الملف main.cمن بين القائمة و الذي يحتوي الدالة . mainبصورة عامة في برامجي ،لا أضع إلّا
الدالة mainفي الـملف . main.cلمعلوماتك ،هذا ليس أمرا ً إجبار ياً ،كل واحد ينظّم ملفاته بالشكل الذي يريد.
لـكن لـكي ٺتبعني جي ّدا ً أنصحك بفعل ذلك.
؟
لـكن لم يجب عليّ إنشاء ملفات عديدة ؟ و كم من ملف يجب علىّ أن أنشئ في مشروعي ؟
هذا يبقى اختيارك أنت ،في الغالب نجمع في نفس الملف المصدري الدوال التي تشترك في الموضوع الذي تعالجه .و هكذا
ل الدوال الخاصة ببناء المستوى ،و في الملف jeu.cقمت بتجميع الدوال الخاصة
ففي الملف editeur.cجمعت ك ّ
باللعبة نفسها و هكذا ...
• ملفات ذات الإمتداد : .hتسمى الملفات الرأسية و هي تحتوي النماذج الخاصة بالدوال.
عموما ،إنه لمن النادر وضع نماذج في الملفات من صيغة .cمثلما فعلنا للتو ّ في الـملف ) main.cإلا إذا كان
برنامجك صغيرا(.
من أجل كل ملف .cهناك ملف مكافئ له ،و الذي يحتوي نماذجا للدوال الموجودة في الملف ، .cتمع ّن في
الصورة السابقة.
124
.2.10الملفات الرأسية )(Headers
؟
لـكن كيف يعرف الحاسوب بأن نماذج الدوال موجودة في ملف آخر خارج الملف .c؟
كيف نقوم بتضمين ملف رأسي ؟ أنت تجيد فعل ذلك لأنك قمت بذلك من قبل.
التضمين يتم عن طر يق توجيهات المعالج القبلي #includeالتي يجدر بك أن تكون قد تعلّمتها من قبل.
تمعن في التالي :
قمنا بتضمين ثلاثة ملفات من صيغة .hو هي stdlib ، stdio :و . jeu
لاحظ الفرق :الملفات التي قمت بإنشائها ووضعها في الـمجلّد الخاص بمشروعك يجب أن تكون مضمّنة بإشارات الاقتباس
) ” ( ”jeu.hبينما ملفات المكتبات )التي توجد عادة في البيئة التطوير ية الخاصة بك( تكون مضمّنة بعلامات الترتيب
) >.( <stdio.h
• علامتي الترتيب > < :لتضمين الملفات المتواجدة في المجلّد includeالخاص بالبيئة التطوير ية.
• علامتي الاقتباس ” ” :لتضمين الملفات المتواجدة في مجلّد المشروع )و غالبا بجانب الملفات .( .c
الأمر #includeيطلب إدخال محتوى ملف معيّن في الملف .cفهي تعليمة تقول ”:أدخل الملف jeu.hهنا”
مثلا .
؟
و في الملف jeu.hماذا نجد ؟
125
الفصل .10البرمجة المجز ّأة )(Modular Programming
؟
ما الهدف من وضع نماذج في ملفات من نوع .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أنت من يحدد ذلك أثناء عملية حفظ
الملف .قم إذن بحفظه )حت ّى و إن كان لا يزال فارغا !( و هنا يطلب منكم إدخال اسم للملف ،يمكنك هنا اختيار صيغة
الملف :
126
.2.10الملفات الرأسية )(Headers
هذا سهل .قم بحفظ الملف في المجلّد أين ٺتواجد باقي الملفات الخاصة بمشروعك )نفس المجلّد أين يتواجد الملف
.( main.cعموما كل ملفات المشروع تقوم بحفظها في نفس المجلّد سواء كانت ذات صيغة .cأو . .h
الملف الذي أنشأته محفوظ لـكن لم تتم إضافته إلى مشروعك بعد !
لإضافته قم بالنقر يمينا على القائمة أيسر الشاشة )الخاصة بملفات المشروع( و اختر Add filesكالتالي :
ستظهر لك نافذة تطلب منك اختيار الملفات التي تريد أن تدخلها للمشروع ،اختر الملف الذي قمت بإنشاءه ،للتو ،و
سيتم إدخاله أخيرا في المشروع .ستجده حاضرا ً في القائمة اليسار ية !
127
الفصل .10البرمجة المجز ّأة )(Modular Programming
كذلك ؟
نعم بالطبع !
يفترض أنهما مسطبان في المكان الذي ٺتواجد به البيئة التطوير ية الخاصة بك ،بالنسبة للبيئة Code::Blocksأجدهم هنا :
C:\Program Files\CodeBlocks\MinGW\include
يمكنك فتحها إذا أردت ،لـكن ستتفاجئ بالعديد من الأشياء التي لم أدرّسها لك من قبل خاصة بما يتعلق ببعض
توجيهات المعالج القبلي .يمكنك أن تلاحظ بأن الملف مليء بنماذج لدوال نموذجية مثل . printf
؟
حسناً ،الآن عرفت أين أجد نماذج الدوال النموذجية لـكن ألا يمكنني رؤ ية الشفرة المصدر ية الخاصة بالدوال ؟
أين هي الملفات .c؟
إنها غير موجودة أساسا ،لأنها مترجمة )إلى ملفات ثنائية ) ،(binary filesيعني إلى لغة الحاسوب( .و لهذا فإنه من
المستحيل أن تقرأها.
يمكنك إ يجاد الملفات المترجمة في المجلّد المسمى ) libو الذي هو اختصار لكلمة libraryأي مكتبة( ،بالنسبة
لي هي موجودة في المسار :
C:\Program Files\CodeBlocks\MinGW\lib
ملفات المكتبات المترجمة لها الصيغة .aفي البيئة Code::Blocksو التي تستخدم mingwكمترجم .و لها صيغة
. .libفي برنامج Visual C++الذي يستخدم المترجم . Visualلا تحاولوا قراءتها لأنها غير قابلة للقراءة من طرف
إنسان عادي.
باختصار ،يجب عليك أن تضمّن الملفات الرأسية .hفي الملفات .cتتمكن من استخدام الدوال النموذجية مثل
printfو كما تعرف فالجهاز على اطلاع على النماذج فهو يعرف ما إن كنت قد طلبت الدوال بشكل صحيح )إن لم
تنس أحد المعاملات مثلا(.
الآن و بعدما عرفت أن المشروع مبنى على أساس ملفات مصدر ية عديدة ،يمكننا الدخول الآن في تفاصيل عملية
الترجمة فلحد الآن لم نر سوى مخطط مبسط عنها.
سأعطيك الآن مخططا مفصلا عنها و من المستحسن أن تحفظه عن ظهر قلب :
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و تعطي للجهاز تعليمات خاصة
ل شيء في وقته.
بكيفية إظهار نافذة على الشاشة كمثال .لـكن طبعا ،لن ندرسها الآن ،ك ّ
لننهي هذا الفصل ،يجب أن أطلعكم عما يسمى بـنطاق المتغيرات و الدوال ،سنعرف إمكانية الوصول للدوال و المتغيرات،
يعني متى يمكننا استدعاؤها.
عندما تصرّح عن متغير في داخل دالة يتم حذف هذا المتغير من الذاكرة مع نهاية الدالة.
130
.4.10نطاق الدوال و المتغيرات
ل متغير تم التصريح عنه في دالة ،لا يكون موجودا سوى حينما تكون الدالة في طور الإشتغال.
ك ّ
لـكن ماذا يعني هذا تحديدا ؟ أنه لا يمكن الوصول إليه من خلال دالة اخرى !
triple في الدالة الرئيسية أحاول الوصول إلى المتغير resultو بما أن هذا المتغير تم التصريح عنه داخل الدالة
فطبعا لا يمكنني الوصول إليه من خلال الدالة ! main
تذك ّر جي ّدا :كل متغير تم التصريح عنه داخل دالة ،لا يسرى مفعوله إلا في داخل هذه الدالة نفسها ! و نقول أن
المتغير محلّي ).(Local
ل الملفات
متغير شامل قابل للوصول إليه من خلال ك ّ
إنه من الممكن التصريح عن متغير يمكن الوصول إليه من خلال كل الدوال من ملفات المشروع .سأر يك كيفية فعل ذلك
كي تعرف بأنه أمر موجود ،لـكن عموما تجنب القيام بذلك .قد يظهر أنها ستسهل لك التعامل مع الشفرة المصدر ية لـكن
ل مكان مما سيصع ّب عليك عملية إدارتها.
قد يؤدي بك هذا لوجود العديد من المتغيرات التي يمكننا الوصول إليها من ك ّ
131
الفصل .10البرمجة المجز ّأة )(Modular Programming
في هذا المثال ،الدالة tripleلا ت ُرجع أي شيء ) .( voidإنها تقوم بتعديل قيمة المتغير الشامل resultالتي
يمكن للدالة mainأن تسترجعه.
ل دوال
المتغير resultيمكن الوصول إليه من خلال كل الملفات في المشروع و منه يمكننا استدعاؤها من خلال ك ّ
البرنامج.
!
هذا شيء يجب ألا يتواجد في برامج الـ Cالخاصة بك .من المستحسن استعمال التعليمة returnلإرجاع النتيجة
بدل التعديل عليه كمتغير شامل.
المتغير الشامل الذي أريتك إياه قبل قليل يمكن الوصول إليه من خلال كل الملفات الخاصة بالمشروع.
يمكننا جعل متغي ّر شامل مرئيا فقط في الملف الذي ٺتواجد به .و لـكنه يبقى متغيرا شاملا على أية حال حتى و إن كنا
نقول أنه ليس كذلك إلا على الدوال المتواجدة في ذات الملف و ليس على كل دوال البرنامج.
لإنشاء متغير شامل مرئي في ملف واحد نستعمل الكلمة المفتاحية staticقبله :
حذار :الأمر حساس هنا قليلاً .إن استعملت الكلمة المفتاحية staticعند التصريح عن متغير في داخل دالة ،فلهذا
معنى آخر غير الخاص بالمتغيرات الشاملة.
مرة أخرى ،سيحفظ المتغير قيمته
في هذه الحالة ،لا يتم حذف المتغير الساكن مع نهاية الدالة ،بل حينما نستدعي الدالة ّ
مثلا :
132
.4.10نطاق الدوال و المتغيرات
1
2
3
4
هنا ،في المرة الأولى التي نطلب فيها الدالة ، incrementيتم إنشاء المتغير . numberثم نقوم بز يادة 1إلى قيمته.
و ما إن تنتهي الدالة لا يمسح المتغير.
عندما نطلب الدالة للمرة الثانية ،يتم ببساطة قفز السطر الخاص بالتصريح بالمتغير ،و لا نقوم بإعادة إنشاء المتغير بل فقط
نعيد استعمال المتغير الذي أنشأناه سابقا .عندما يأخذ المتغير القيمة ،1تصبح قيمته 2ثم 3ثم ... 4إلخ.
هذا النوع من المتغيرات ليس مستعملا بكثرة ،لـكن يمكنه مساعدتك في بعض الأحيان و لهذا ذكرته في هذا الكتاب.
لإنهاء حديثنا عن المتغيرات و الدوال ،نتكلم قليلا حول نطاق الدوال .من المفروض ،عندما تنشئ دالة ،و التي هي شاملة
بالنسبة لكل البرنامج ،يمكن الوصول إليها من أي ملف . .c
قد تحتاج أحيانا ً إلى إنشاء دوال يمكن الوصول إليها فقط في الملفات التي ٺتواجد بها ،و للقيام بذلك ،أضف الكلمة
المفتاحي ّة staticقبل الدالة كالتالي :
133
الفصل .10البرمجة المجز ّأة )(Modular Programming
إنتهى ! دالتك الساكنة tripleلا يمكن استدعاؤها إلا من داخل دوال تنتمى لنفس الملف )مثلا .( main.c
إذا حاولت استدعاء الدالة tripleمن خلال دالة أخرى من ملف آخر )مثلا ،( display.cلن يشتغل البرنامج
لأن tripleوقتها لن تكون مرئية.
• المتغير الذي يتم التصريح عنه داخل دالة يتم حذفه في نهاية الدالة مباشرة .لا يمكن أن يكون مرئيا إلا داخل هذه
الدالة.
• المتغير الذي يتم التصريح عنه في داخل دالة بالكلمة المفتاحية ، staticلا يحذف في نهاية الدالة لـكنه يحتفظ
بقيمته ما دام البرنامج يعمل.
ل ملفات
ل الدوال و في ك ّ
• المتغير الذي يتم التصريح عنه خارج الدوال يسمى متغيرا شاملا ،يكون مرئيا في ك ّ
المشروع.
• المتغير الشامل المصرّح عنه بالكلمة المفتاحية staticمرئي فقط في الملف الّذي يتواجد به ،و لا يكون مرئيا في
دوال باقي الملفات.
• الدالة ال ّتي يتم التصريح عنها عاديا ًتكون مرئية في كل ملفات المشروع ،يمكننا طلبها إذا من أي ملف كان.
• إذا أردنا أن تكون الدالة مرئية فقط في الملف الذي ٺتواجد به فيجب إضافة الكلمة المفتاحي ّة staticقبلها.
ملخّ ص
• البرنامج يحتوي العديد من الملفات . .cكقاعدة عامة ،لكل ملف .cملف مرافق له يحمل الإمتداد ) .hالذي
يعني ملفّا رأسي ّا ) .((Headerالـ .cيحوي الدوال بينما .hيحوي النماذج ) ،(Prototypesأي توقيعات هذه
الدوال.
• يتم تضمين محتوى الملفات .hفي أعلى الملفات .cبالاستعانة ببرنامج يسمّى المعالج القبلي.
• يتم تجميع الملفات .oفي ملف تنفيذي ) ( .exeمن طرف محرر الروابط ).(Linker
• المتغير الّذي يتم التصريح عنه داخل دالة غير قابل للوصول إلا من داخل هذه الدالة .نتكلّم هنا عن نطاق المتغيرات.
134
الفصل 11
لقد حان الوقت لنكتشف المؤشرات .خذ نفسا عميقا قبل أن تقرر قراءة هذا الفصل لأنه لن يكون فصلا ًللهو و المرح.
تمثل المؤشرات واحدا ً من المبادئ الأكثر أهمية و حساسية في لغة الـ . Cو إن كنت أصرّ على أهميتها فهذا لأنه لا يمكنك
ل مكان ،و لقد استعملتها من قبل دون أن تعلم بذلك.
البرمجة بـ Cدون معرفتها و فهمها جي ّدا .المؤشرات موجودة في ك ّ
كثير من المتعلّمين يصلون إلى المؤشرات و يواجهون صعوبات في فهمها .سنعمل على ألا يكون الأمر مماثلا بالنسبة
لك .ضاعف التركيز و خذ الوقت اللازم لفهم المخططات التي سأقدمها لك في هذا الفصل.
واحد من أكبر المشاكل مع المؤشرات هي أن ّه بالإضافة إلى أنها صعبة الاستيعاب قليلا بالنسبة للمبتدئين ،فإن المتعل ّم
لا يعرف ما هي أهميتها و فيما يمكننا استعمالها.
يمكنني أن أقول لك بأن ”المؤشرات لا يمكن الاستغناء عنها في أي برنامج ، Cصدقني !” ،لـكن ّي أعرف أن هذه الحج ّة
ليست كافية لك.
سأطرح عليك مشكلا ًلا يمكنك حلّه إلا باستخدام المؤشرات .سيكون هذا الخيط الأحمر في هذا الفصل .سنعود إليه
في نهاية هذا الفصل و سترى حلّه باستعمال ما تعلّمته في هذا الفصل.
إليك المشكل :أريد كتابة دالة تقوم بإرجاع قيمتين مختلفتين .ستجيبني ”هذا مستحيل !” .بالفعل ،الدالة لا يمكنها
إرجاع سوى قيمة واحدة.
إذا استخدمنا intترجع لنا قيمة من نوع ) intبفضل التعليمة .( return
يمكننا أيضا كتابة دالة لا ت ُرجع أية قيمة باستخدام الكلمة المفتاحية . void
135
(Pointers) المؤش ّرات.11 الفصل
1 void function()
2 {
3
4 }
. return هذا أمر مستحيل لأنه لا يمكننا استعمال تعليمتي... لـكن إرجاع قيمتين مختلفتين في نفس الوقت
تقوم الدالة بإرجاع عدد الساعات و الدقائق المواقفة.لنفرض أنني أريد كتابة دالة أعطيها كمدخل عددا من الدقائق
.لها
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الذاكرة ،مسألة عنوان
النتيجة :
لم تشتغل ! ما الأمر يا ترى ؟ في الواقع ،عندما نبعث متغيرا إلى دالة ،يتم إنشاء نسخة من المتغير ،و لهذا فالمتغير
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فهذا لا يطرح أيّ مشكل !
أي ّا كانت إجابتك ،أنصحك بأن تعود إلى ذلك الفصل و تقرأ منه القسم الذي يحمل عنوان )مسألة ذاكرة( .هناك
مخطط مهم جدا ً سأقترحه عليك من جديد :
137
الفصل .11المؤش ّرات )(Pointers
يجب قراءة المخطط سطرا ً بسطر ،السطر الأول يمثل ”الخانة” الخاصة بأول الذاكرة .لكل خانة رقم ،هذا الرقم يمثل
عنوانها )تذك ّر هذا المصطلح جيدا( .تحتوي الذاكرة على عدد كبير جدا ً من العناوين تبدأ من الرقم 0و تنتهي بالرقم )ضع
رقما ًكبيرا ً جدا ً هنا ( .عدد العناوين التي ٺتوفر عليها تعتمد على حجم الذاكرة التي يحتوي عليها الجهاز الخاص بك.
ل عنوان يمكننا تخزين عدد واحد فقط .لا يمكننا تخزين عددين في نفس العنوان.
في ك ّ
الذاكرة ليست مصنوعة سوى لتخزين الأعداد .لا يمكنها تخزين لا حروف و لا جُمل .و للتخلص من هذا المشكل تم
اختراع جدول يقوم بالربط بين الحروف و الأعداد .يقول الجدول مثلا ً ”:العدد 89يمث ّل الحرف .”Yسنعود في فصل
لاحق إلى كيفي ّة معالجة الحروف .حالي ّا ،سنتكفي بالتكلّم عن عمل الذاكرة.
عنوان و قيمة
يطلب البرنامج من نظام التشغيل )الويندوز مثلا( الإذن لاستعمال جزء من الذاكرة .نظام التشغيل يجيب بالإشارة
إلى أي عنوان سيسمح لك بتخزين العدد.
ل
هنا تكمن أحد أهم وظائف نظام التشغيل :نقول أنه يحجز الذاكرة للبرامج .يمكننا القول أنه هو القائد ،يتحكم في ك ّ
برنامج و يتأكد من أن هذا الأخير له الإذن لاستعمال الذاكرة في المكان الذي يطلبه.
138
.2.11الذاكرة ،مسألة عنوان
م
إن هذا هو السبب الرئيسي في توقف البرامج عن العمل :إذا حاول برنامجك الوصول إلى مكان غير مسموح له
بالوصول إليه في الذاكرة ،سيرفض نظام التشغيل و يوقف تشغيله بشكل عنيف )لأنه القائد هنا( .بينما يتلقّى
المستعمل نافذة خطأ تحتوي على رسالة تشير بأن البرنامج يحاول القيام بعملية غير لائقة.
لنعد للمتغير . ageتم تخزين القيمة 10في مكان ما من الذاكرة ،لنقل مثلا ًفي العنوان رقم .4655
ل مرة
ما يحدث )و هذا دور المترجم( هو أن الكلمة ageيتم تعو يضها بالعنوان 4655لحظة التنفيذ .مما يعني أنه في ك ّ
قمت فيها بكتابة الكلمة ageفي الشفرة المصدر ية ،يتم تعوضيها بـ ،4655و بهذا يرى الجهاز إلى أي عنوان في الذاكرة عليه
ل فخر بأن المتغير ageيحتوي القيمة .10
الذهاب .و منه يجيب بك ّ
أنت تعرف كيف تظهر قيمة متغير ،لـكن هل تعرف أنه بإمكاننا أيضا إظهار عنوانه ؟
لـكي نُظهر عنوان متغير ،نستعمل الإشارة ) %pالحرف pمأخوذ من الكلمة (Pointerفي الدالة . printfأي أننا
لن نبعث للدالة printfالمتغير في ح ّد ذاته لـكن نبعث لها عنوانه .و لفعل هذا ،يجب عليك استعمال الإشارة & أمام
المتغير ageكما طلبت منك أن تفعل مع الدالة scanfمن قبل دون أن أشرح لك لماذا.
أكتب إذا:
النتيجة :
ما تراه هنا هو عنوان المتغير ageفي اللحظة التي طلبتُ فيها تنفيذ البرنامج من طرف حاسوبي .نعم نعم0023FF74 ،
هو رقم ،هو فقط مكتوب في النظام الست عشري ) (Hexadecimalعوض النظام العشري الذي تعو ّدنا عليه .لو تقوم
بتعو يض %pبـ %dفإنه سيظهر لك رقما عشر يا كما تعو ّدنا.
139
الفصل .11المؤش ّرات )(Pointers
م
إذا شغ ّلت البرنامج على حاسوبك فمن المؤكّد أن تحصل على عنوان آخر .الأمر يعتمد على المكان في الذاكرة ،البرامج
المشتغلة ،إلخ .فيستحيل أن ٺتوقع العنوان الّذي سيتم تخزين المتغي ّر فيه .إذا قمت بتشغيل البرنامج عدة مرات
الواحدة تلو الأخرى قد تتحصل على نفس النتيجة كون الذاكرة لم ٺتغير في ذلك الزمن القصير .لـكن بالمقابل إن
أعدت تشغيل الحاسوب فستتحصل بكل تأكيد على نتائج مختلفة.
عند استخدام ageسيقرأ الحاسوب قيمة المتغي ّر في الذاكرة .أمّا عند استخدام &ageفسيعيد العنوان الذي يوجد
فيه المتغي ّر.
لح ّد الآن ،قمنا فقط بإنشاء متغيرات تحتوي على أعداد .الآن سنتعل ّم كيف ننشئ متغيرات تحتوي على عناوين :هذا
ما نسميه بالمؤشرات.
؟
لـكن ...العناوين هي أعداد أيضاً ،أليس كذلك ؟ هذا يعني أننا سنخزن أعدادا ً دائما !
هذا صحيح ،لـكن لهذه الأعداد معنى آخر :هي تشير إلى عنوان متغير آخر في الذاكرة.
لإنشاء متغير من نوع مؤش ّر ،يجب علينا أن نضيف الرمز * أمام إسم المتغير :
م
لاحظ أنه يمكننا أيضا أن نكتب ; int* myPointerلهذا نفس المعنى .لـكن الطر يقة الأولى هي المفضّ لة.
في الواقع ،إن كنت تريد التصريح عن العديد من المؤشرات في نفس السطر ،سيكون عليك أن تعيد كتابة النجمة
;int *pointer1, *pointer2, *pointer3 أمام كل اسم :
م أن تقوم بإعطاء قيم إبتدائية للمتغيرات منذ البداية ،و ذلك بإعطائها القيمة 0مثلا ! إنه من
كما قلت لك ،من المه ّ
المهم أكثر أن تفعل نفس الشيء مع المؤشرات.
لتهيئة مؤش ّر ،نعطيه قيمة افتراضية ،لا نستعمل غالبا القيمة 0و لـكن الكلمة المفتاحية ) NULLأكتبها بأحرف كبيرة(.
140
.3.11استعمال المؤشرات
هنا لدينا مؤشر يحمل القيمة الإبتدائية . NULLهكذا ستعرف لاحقا ًفي البرنامج أن المؤشر لا يحتوي على أي عنوان.
ما الذي يحصل ؟ ستقوم هذه الشفرة المصدر ية بحجز خانة في الذاكرة كما لو أننا أنشأنا متغيرا ً عادياً .الشيء الذي يتغير
هو أن المؤشر سيحتوي عنوانا .عنوان متغير آخر.
لم لا عنوان المتغير age؟ أنت تعرف الآن كيف تشير إلى عنوان متغير في مكان قيمته )باستعمال الرمز & ( ،هيا
بنا إذن ! هذا ما عليك كتابته :
السطر الأول يعني ” :أنشئ متغيرا من نوع intيحمل القيمة .”10السطر الثاني يعني ”أنشئ متغيرا ً من نوع مؤش ّر
قيمته هي عنوان المتغير .” age
يقوم إذا السطر الثاني بمهمّتين معاً .لـكي لا تختلط عليك الأمور ،إعلم أنه يمكننا تقسيم السطر إلى سطرين :
يمكنك الملاحظة أنه لا يوجد في لغة الـ Cنوع نسميه pointerكالنوع intو . doubleأي أنه لا يمكننا أن
نكتب :
في مكان هذا ،نستعمل الرمز * ،و لـكن نستمر في كتابة . intماذا يعني هذا ؟ في الواقع يجب أن نشير إلى نوع
المتغير الذي سيحوي عنوانه المؤشر .بما أن المؤشر PointerOnAgeسيحتوي عنوان المتغير ) ageالذي هو من نوع
،( intإذا فالمؤشر يجب أن يكون من نوع * ! intإذا كان المتغير من نوع doubleفإنه يجب عليّ أن أكتب
. double *myPointer
141
الفصل .11المؤش ّرات )(Pointers
PointerOnAge في هذا المخطط ،تم تعو يض المتغير ageبالعنوان ) 177450أنت ترى بأن قيمته هي ،(10و المؤش ّر
تم تعو يضه بالعنوان ) 3هذه محض صدفة(.
حينما يتم إنشاء المؤشر ،يقوم نظام التشغيل بحجز خانة في الذاكرة كما فعل مع المتغير . ageالشيء المختلف هنا هو
أن المتغير PointerOnAgeله معنى آخر .أنظر للمخطط جيدا ً :قيمته هي عنوان المتغير . age
هذا ،عزيزي القارئ ،هو السرّ المطلق من وراء كتابة البرامج في لغة الـ .Cبهذا نحن ندخل في عالم المؤشرات العجيب !
؟
و ...ما هي فائدة هذا ؟
هذا لا يقوم بتحو يل الحاسوب إلى آلة صنع القهوة ،طبعا .لـكن الآن لدينا المؤشر PointerOnAgeيحتوي عنوان
المتغير . age
177450
هذا ليس مفاجئاً ،نحن نطلب قيمة PointerOnAgeو قيمته هي عنوان المتغير ) ageأي .(177450
ماذا نفعل لـكي نطلب قيمة المتغير المتواجدة في العنوان الذي يشير إليه المؤش ّر PointerOnAge؟ يجب أن نضع الرمز
* أمام إسم المؤش ّر :
142
.3.11استعمال المؤشرات
10
ها قد وصلنا ! بوضع الرمز * أمام إسم المؤش ّر ،يمكننا الوصول إلى قيمة المتغير . age
لو استعملنا الرمز & أمام اسم المؤش ّر ،سنتحصل على العنوان الذي يتواجد به المؤش ّر )هنا الرقم .(3
؟
ماذا نربح هنا ؟ لقد نجحنا في تعقيد الأمور لا أكثر .لم نكن نحتاج إلى مؤش ّر لنظهر قيمة المتغير ! age
هذا السؤال )الذي لا مفر من طرحه( شرعي ،حالي ّا الهدف ليس واضحا ،لـكن شيئا ً فشيئاً ،و مع تقدّم الفصول،
ل سذاجة.
ل هذه المبادئ لم يتم اختراعها من أجل تعقيد الأمور بك ّ
ستفهم بأن ك ّ
المهم هو أن تفهم المبدأ الآن و بعده ستتوضح الأمور لوحدها رويدا ً رويداً.
هذا ما يفترض أن تكون قد فهمته و يجب عليك تذكره لباقي الفصل :
– *PointerOnAgeتعني ” :أريد قيمة المتغير المتواجد في العنوان الذي يحتو يه المؤشر .” PointerOnAge
اكتف بفهم و حفظ النقاط الأربع السابقة ،قم باختبارات و تأكد أنها تعمل .المخطط التالي سيعينك على فهم هذه
ِ
النقاط :
143
الفصل .11المؤش ّرات )(Pointers
!
حينما تصرّح عن مؤش ّر ،فّإن النجمة احذر في عدم الخلط بين مختلف تفسيرات النجمة !
; int *PointerOnAgeبينما لما نكتبها بجانب اسم المؤشر تشير إلى أننا بصدد إنشاء مؤشر :
;) printf(”%d”, *PointerOnAgeفهذا يعني :أريد قيمة المتغير التي يشير إليها المؤشر بكتابة :
. PointerOnAge
إذا كنت تشعر بأنك ضائع قليلاً ،فكر في الأشخاص المحترفين في البرمجة :لا أحد منهم فهم مبدأ عمل المؤشرات من
المر ّة الأولى .و إن كان هذا الشخص موجوداً ،أطلب منك أن تقدّمه لي الآن.
أهم فائدة للمؤشرات )لـكنها ليست الوحيدة( هي أنه بامكاننا إرسالها إلى دالة كي تقوم بتعديل مباشر على قيمة متغير
في الذاكرة لا لنسخة منها كما رأينا سابقاً.
كيف يعمل هذا ؟ هناك الـكثير من الطرق لفعل هذا .إليك مثالا ً:
144
.4.11إرسال مؤش ّر إلى دالة
15
الدالة triplePointerتأخذ معاملا من نوع *) intأي مؤش ّر على .( int
إليك ما يحدث بالترتيب ،إنطلاقا ًمن بداية الدالة : main
.1يتم إنشاء متغير numberفي الدالة ، mainنُسن ِد له القيمة ،5أنت تعرف هذا.
.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
قارن بين الشفرتين المصدريتين السابقتين ،ستجد بأن هناك اختلافا بينهما ،لـكن النتيجة واحدة :
15
ما يهم ،هو بعث عنوان المتغير numberإلى الدالة .لـكن المؤشر يحمل عنوان المتغير ، numberفهذا جيد من هذه
الناحية ! نحن فقط نقوم بالأمر بطر يقة مختلفة بإنشاء المؤشر في الدالة . main
في الدالة ) printfفقط من أجل التمرين(ُ ،أظهر محتوى المتغير numberبكتابة . *pointerلاحظ أنه في مكان
ن *pointerو numberيشيران إلى نفس الخانة
هذا ،يمكنني كتابة : numberستكون النتيجة هي نفسها لأ ّ
بالذاكرة.
في البرنامج ”أكثر أو أقل” ،استعملنا مؤشرا ً دون أن نعرف بذلك .كان ذلك عند استدعاء الدالة . scanfبالفعل،
دور هذه الدالة هو قراءة ما كتبه المستعمل في لوحة المفاتيح و إرجاع النتيجة .لـكي تتمكن الدالة من تغيير محتوى المتغير
مباشرة أي تضع بها الكلمة أو الجملة التي تمت كتابتها في لوحة المفاتيح ،احتاجت لعنوان المتغير :
146
.5.11من الذي قال ”مشكل مضجر بالفعل” ؟
تعمل الدالة بمؤشر على المتغير numberو بهذا يمكنها تغيير قيمته مباشرة.
كما رأينا الآن ،يمكننا إنشاء مؤشر و نبعثه للدالة : scanf
إنتبه كي لا تضع الرمز & امام اسم مؤشر في الدالة ! scanfهنا pointerيحتوي هو نفسه عنوان المتغير
، numberلذا أنت لا تحتاج لوضع & ! إذا فعلت هذا فإنك ستبعث العنوان الذي يتواجد به المؤشر :و لـكننا بحاجة
لعنوان ! number
ل المشكل.
اقتربنا من نهاية الفصل ،حان الوقت لإ يجاد الخيط الأحمر .إذا كنت قد فهمت الفصل ،فأنت قادر على ح ّ
الآن ،ما الذي تقوله .حاول حل المشكل و إليك الحل للمقارنة :
النتيجة :
147
الفصل .11المؤش ّرات )(Pointers
• تقوم الدالة minutesDivisionبتعديل مباشر لقيمتي المتغيرين hoursو minutesفي الذاكرة لأنها تملك
عنوانيهما في مؤشرين .الشيء الوحيد الذي يمكنه أن يشكل مشكلا و يجب أن أبقيه ببالي هو وضع النجمة أمام
اسمي المؤشرين إذا أردت أن أغير قيمتي المتغيرين hoursو . minutesإذا لم نفعل هذا ،فإننا سنغير العنوانين
الموجودين في المؤشرين ،و هذا لا ينفع في شيء.
م
كثير من القر ّاء سيشيرون إلى أنه يمكن حل المشكل دون اللجوء إلى المؤشرات .نعم هذا صحيح ،لـكنّ هذا سيضطرنا
لتجاوز القواعد التي وضعناها معاً .يمكننا استعمال المتغيرات الشاملة )و كما قلتُ هذا شيء سيء( ،و يمكننا أيضا ً
وضع الدالة printfداخل الدالة ) minutesDivisionلـكننا نحن نريد وضع العرض في الدالة الرئيسية(.
هذا يعني أنه يمكننا دائما حل المشكل باعتماد طرق غير لائقة مما يجعلك تشك في الفائدة من المؤشرات .لـكن
تأكد من أنك ستجد بأن استعمال المؤشرات أمر بديهي مع التقدم في الكتاب.
ملخّ ص
• تشبه المؤشرات المتغيرات لـكنها مخصصة لتخزين العناوين التي ٺتواجد بها المتغيرات في الذاكرة.
• إذا وضعنا الرمز & أمام اسم متغير فإننا نتحصل على العنوان الذي يتواجد به المتغير ،مثلا . &age :
• إذا وضعنا الرمز * امام إسم مؤشر نتحصل على قيمة المتغير التي يؤش ّر عليها المؤش ّر.
• تعتبر المؤشرات إحدى المبادئ الأساسية في لغة الـ ،Cتبدو صعبة في البداية لـكن عليك أن تفهمها لأن الـكثير من
المبادئ الأخرى تقوم عليها.
148
الفصل 12
الجداول )(Arrays
هذا الفصل هو ملحق مباشر للفصل المتعلق بالمؤشرات ،و سيعلّمك أهميتها أكثر .إن كنت تعتقد بأنك قادر على تفادي
ل مكان في لغة الـ .Cلقد حذّرتك !
المؤشرات فأنت مخطئ ! هي في ك ّ
سنتعلم في هذا الفصل كيف ننشئ متغيرات من نوع ”جداول” .الجدوال مهمّة للغاية في لغة الـ Cلأنها تساعد في تنظيم
سلسلة من القيم.
نبدأ هذا الفصل ببعض الشروحات و التفسيرات حول كيفية عمل الجداول في الذاكرة )سأقدم لك الـكثير من
المخططات التفسير ية( .هذه المقدمات حول الذاكرة مهمة جدا ً :ستساعدك في في معرفة عمل الجداول .فمن المستحسن
أن يعرف المبرمج ما يقوم به كي يتحكم في برامجه أكثر ،أليس كذلك ؟
أعرف أن هذا التعر يف يشبه قليلا تعر يف القاموس .لهذا فسأوضح بطر يقة أخرى ،فعليا ّّ ،الجدول عبارة عن ”متغي ّرات
ضخمة” يمكن لها أن تحتوي على أعداد كبيرة من نفس النوع ) .(... double ، int ، long ، char
للجدول طول محدد .يمكنه أن يكون 10 ،3 ،2خانات 2500 ،150 ،خانة ،أنت من يحدد العدد .المخطط التالي
مثال عن جدول يحجز 4خانات بدءا ً بالعنوان : 1600
149
الفصل .12الجداول )(Arrays
عندما تطلب إنشاء جدول يحجز 4خانات في الذاكرة ،سيطلب برنامجك من نظام التشغيل أن يسمح له باستغلال
4خانات في الذاكرة ،و يجب ان تكون هذه الخانات متتالية يعني الواحدة بجانب الأخرى .و كما ترى أعلاه فالخانات
متتابعة 1603 ،1602 ،1601 ،1600فلا يوجد ”فراغ” بينها.
• عندما يتم إنشاء جدول ،يأخذ مكانا متواصلا ًفي الذاكرة .بحيث تكون الخانات متجاورة الواحدة تلو الأخرى.
• كل خانات الجدول تكون من نفس النوع ،فجدول الـ intيمكن أن يحمل فقط ، intو لا أي نوع آخر.
ل شيء .يكفي إذن أن تضيف قوسين مربعين ) [ و ] ( عدد الخانات التي تريد أن يحجزها جدولك ،و اعلم
هذا ك ّ
أنه لا يوجد حدود )إلا إن تجاوزت الح ّد الذي تسمح به ذاكرة جهازك طبعا(.
و لـكن الآن ،كيف نصل لخانة ما في الجدول ؟ هذا سهل ،تكفي كتابة ]. table[cellNumber
x
احذر :كل جدول يجب أن يبدأ بالفهرس ) (Indexرقم ! 0جدولنا متكو ّن من int 4إذن فالفهارس
المتوفرة هي 2 ،1 ،0 :و .3لا وجود للفهرس 4في جدول من 4خانات ! هذا مصدر أخطاء متداولة ،فلا
تغفل عنه !
إذا كنت أريد أن أضع في جدولي نفس القيم التي في المخطط فبجب إذا أن أكت ُب :
150
.2.12تعر يف جدول
؟
لازلت لا أرى العلاقة بين المؤشرات و الجداول ؟
في الواقع ،لو تكتب فقط tableفستحصل على مؤشر ،و هو مؤشر على الخانة الأولى من الجدول ،قم باختبار التالي
:
1600
بينما إذا قمت بوضع فهرس الخانة بين قوسين مربعين ،فستحصل على القيمة :
10
نفس الشيء بالنسبة للفهارس الأخرى .بما أن 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 إلا أن هذه الكتابة ليست مفهومة بالنسبة لكل الـمترجمات ) (Compilersفبعضها ٺتوقف في السطر الثاني .إن لغة
ال ّتي اعلمك إياها منذ البداية )تدعى (C89لا تسمح بهذا النوع من الكتابات .و لذا يمكننا القول أن فعل هذه الأشياء أمر
ممنوع.
يجب أن نتفق على شيء و هو :لا تملك الحق في وضع متغير بين القوسين المربعين من أجل تعر يف حجم الجدول.
حتى و إن كان المتغير ثابتا ! يجب على طول الجدول أن يأخذ قيمة ثابتة ،و لهذا عليك أن تحدده كعدد :
;]1 int table[5
؟
إذن ...هل من الممنوع إنشاء جدول يعتمد حجمه على قيمة متغير ؟
بلى إنه ممكن حتى مع .C89لـكن لفعل هذا سنعتمد على تقنية أخرى )أكيدة أكثر و تعمل مع كل المترجمات(
ي ) .(Dynamic allocationسندرسها في مرحلة متقدمة من هذا الكتاب.
تدعى بـالحجز الح ّ
الأحسن هو أن نستعين بحلقة .لم لا حلقة 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باستخدام حلقة !
0
0
0
0
153
الفصل .12الجداول )(Arrays
يجب أن تعرف أنه هناك طر يقة أخرى لإعطاء قيم ابتدائية لجدول في لغة الـ.C
و هذه الطر يقة تعمل بكتابة } . table[4] = {value1, value2, value3, value4ببساطة ،تضع القيم بين
حاضنتين واحدة تلو الاخرى و تفصل بينها بفواصل.
0
0
0
0
و هناك أحسن من هذه الطر يقة :بإمكانك تعر يف القيم الخاصة بالخانات الأولى من الجدول و كل التي لم تُشر إليها
ست ُضبط على .0
1 int table[4] = {10, 23}; // Inserted values : 10, 23, 0, 0
الخانة 0تأخذ القيمة 10و الخانة 1تأخذ القيمة 23و كل الخانات المتبقية تأخذ القيمة ) 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تمرير جدول لدالة
ل الجدول ،فلما لا نقوم بكتابة دالة تقوم بهذا ؟ بهذا ستكتشف كيف
في كثير من الأوقات ستحتاج لإظهار محتوى ك ّ
نمرر جدولا إلى دالة )و هذا يساعدني(.
يتطلب الأمر أن تبعث معلومتين للدالة .الجدول )أي عنوان الجدول( و أيضا حجمه خاصّة !
بالفعل ،يجب أن تكون دالتنا قادرة على تهيئة جدول مهما كان حجمه .لـكن في دالتنا ،أنت لا تعرف حجم جدولك و لهذا
يجب أن ترسل متغيرا يحمل الاسم tableSizeمثلا.
و كا قلت لك مسبقا ،يمكننا إعتبار tableمؤشرا ،و لهذا يمكننا أن نرسله للدالة مثلما نرسل مؤشرا عاديا.
10
15
3
0
الدالة غير مختلفة عن التي درسناها في فصل المؤشرات ،فهي تأخذ كمعامل مؤشرا ً نحو ) intجدولنا( و أيضا حجمه
)مهم جدا كي نعرف متى ٺتوقف الحلقة !(.
كل محتوى الجدول تُظهره الدالة بواسطة الحلقة.
لاحظ أن ّه توجد طر يقة أخرى للإشارة إلى أن الدالة تستقبل جدولا .بدلا من أن نشير أن الدالة تستقبل ، int *table
أكتب التالي :
155
الفصل .12الجداول )(Arrays
هذا يعني تماما نفس الشيء ،و لـكن وجود القوسين المربعين يُعلمان المبرمج بأن الدالة تأخذ جدولا و ليس مؤشرا عاديا
و هذا يز يل الغموض.
شخصيا أستعمل دائما القوسين المربعين في دوالي لـكي ُأظهر بأن الدالة تنتظر جدولا .أنصحك باستعمال نفس الطر يقة.
ليس مهما وضع حجم الجدول بين القوسين المربعين هذه المرة.
بعض التمارين !
لدي أفكار متعددة عن تمارين متعلقة بالجداول ستساعدك على التدريب ! و فكرة التمارين هي إنشاء دوال تعمل على
الجداول.
و كتحدي ،ستجد هنا نصوص التمارين فقط ،و لن أعطي الإجابة كي أجبرك على الاجتهاد في إ يجاد الحلول ،فإن لم
تستطع فيمكنك ز يارة المنتديات لطرح أسئلتك.
التمرين 1
أنشئ دالة tableSumترجع مجموع القيم الموجودة في الجدول )استعمل returnلإرجاع النتيجة( و للمساعدة ،هذا
هو نموذج الدالة :
;)1 int tableSum(int table[], int tableSize
التمرين 2
أنشئ دالة tableAverageتحسب و ت ُرجع معدّل القيم الموجودة في الجدول .تفضل النموذج :
;)1 double tableAverage(int table[], int tableSize
التمرين 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} :
تفضل النموذج :
هذا التمرين صعب بقليل عن السابقين لـكن القيام به ليس مستحيلا ،سيشغلـكم بعض الوقت.
م
ل
أنشئ ملفا خاصا باسم ) tables.cمع مرافقه الملف tables.hالذي يحتوي النماذج ! بالطبع( يحوي ك ّ
الدوال التي تقوم بعملي ّات على الجداول.
إلى العمل !
ملخّ ص
• الجداول هي عبارة عن مجموعة متغيرات لها نوع واحد مرتبة بجنب بعضها في الذاكرة.
• يجب أن يكون حجم الجدول محددا ً قبل ترجمة البرنامج ،لا يمكن أن تعتمد على متغي ّر.
157
الفصل .12الجداول )(Arrays
158
الفصل 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
أغلب الحروف ”الأساسي ّة” مشفّرة بين 0و .127يوجد جدول يقوم بالتحو يل بين الأعداد و الحروف :الجدول
) ASCIIينطق ”أسكي”( .الموقع AsciiTable.comمشهور لعرض هذا الجدول لـكن ّه ليس الوحيد ،يمكننا أن نجده على
و يكيبيديا و مواقع أخرى أيضا.
إظهار محرف
كما نعلم فلإظهار أي شيء على الشاشة نستعمل الدالة ، printfهذه الدالة قادرة أيضا على إظهار محرف على الشاشة و
ذلك باستعمال الرمز c) %c :تعني (Characterكالتالي :
A
يمكننا أيضا أن نطلب من المستعمل أن يقوم بإدخال حرف عن طر يق لوحة المفاتيح ،و ذلك بالإستعانة بالدالة
، scanfو هذا بوضع الرمز %cدائماً ،كالتالي :
B
B
أوّل Bهو الّذي كتبته ،أمّا الثاني فهو المعروض من طرف . printf
هذا تقريبا ما يجب عليك أن ٺتذكره بخصوص النوع . charتذك ّر جي ّدا :
160
char .2.13السلاسل المحرفي ّة هي جداول من نوع
• النوع charيخز ّن الأعداد من −128إلى ،127بينما النوع unsigned charيسمح بتخزين الأعداد من 0
إلى .255
• يستخدم الحاسوب جدولا للتحو يل بين الحروف و الأعداد ،الجدول .ASCII
• ’ ’Aيتم ّ استبدالها أثناء الترجمة بالقيمة الموافقة ) 65مثلا( .نستخدم إذن علامات التنصيص للحصول على قيمة
حرف.
مثلما يشير العنوان .في الواقع ،فإن السلسلة المحرفي ّة ما هي إلا عبارة عن جدول من نوع . charمجر ّد جدول بسيط.
و قمنا بوضع الحرف ’ ’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محرف نهاية السلسلة
يسمح للحاسوب بمعرفة أين تنتهي السلسلة.
162
char السلاسل المحرفي ّة هي جداول من نوع.2.13
هذه هي.( أي ”سلسلة محارف” بالإنجليز ية،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تماما كما فعلنا يديو ي ّا منذ قليل.
باختصار ،هذا أمر عمليّ أكثر.
و مع ذلك فهناك مشكل :هذا الأمر لا يعمل إلّا مع التهيئة ! لاحقا في الشفرة لا يمكنك كتابة :
هذه التقني ّة محجوزة للتهيئة فقط .بعد هذا ،يجب أن نقوم بالتعديل على المحارف يدو ي ّا في الذاكرة واحدا ً واحدا ً كما
فعلنا في البداية.
يمكننا حفظ سلسلة محرف ّية م ُدخَلة من طرف المستخدم عن طر يق ، scanfباستخدام الرمز . %s
Luc مشكل وحيد :لا يمكنك معرفة كم محرفا سيقوم المستخدم بإدخاله .إن طلبت منه اسمه الأوّل ،فيمكن أن يسمّى
) 3محارف( ،و لـكن من الذي سيضمن أن اسمه لن يكون ) Jean-Edouardعدد أكبر بكثير من المحارف( ؟
من أجل هذا ،لا يوجد 36حل ّا .يجب إنشاء جدول من charكبير جدّا ،كبير بما يكفي لتخزين الاسم .سنقوم
ن المكان في الذاكرة ليس الشيء
مرة أخرى بأ ّ إذن بإنشاء ] . char[100قد تشعر بأ ّ
ن هذا إهدار للذاكرة ،لـكن تذك ّر ّ
الذي ينقصنا )كما أن ّه توجد برامج تهدر الذاكرة بطر يقة أسوء بكثير من هذه !(.
164
.3.13دوال التعامل مع السلاسل المحرفي ّة
لا يمكنني أن أشرحها لك كل ّها هنا ،سيتطل ّب الأمر وقتا طو يلا كما أنّها ليست كل ّها ضرور ي ّة.
سأعلّمك الأساسي ّة منها و ال ّتي ستحتاجها حتما لاحقا.
إن لم تقم بهذا فإن المترجم لن يتعرف على الدوال التي سأعرضها لك لأن ّه لا يملك نماذجها ،و بالتالي فستتوقف الترجمة.
مرة تستخدم فيها دوال التعامل مع النصوص.
ل ّباختصار ،لا تنس تضمين هذه المكتبة في ك ّ
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فستعلم أن المتغي َر لا يتم ّ تعديله من طرف الدالّة ،بل قراءته فقط.
165
(Strings) السلاسل المحرفي ّة.13 الفصل
يوجد عدّاد تتم ّ ز يادته. \0 حتى تصل إلى المحرفchar يكفي القيام بحلقة على جدول،هذه الدالة سهلة الكتابة
. و هذا العدّاد يتم ّ إعادته في النهاية،في كل دورة من الحلقة
هذا سيمكّنك من أن تفهم جي ّدا كيف تعمل هذه الدالّة. strlen هذا جعلني أريد كتابة شفرة شبيهة بتلك الخاصّة بـ
:
تقوم في كل مرة بتخزين المحرف الحالي في محرف. string تقوم بحلقة على الجدولstringLen هذه الدالة
. نخرج من الحلقة، \0 ما إن يكون المحرف الحالي هو، currentCharacter م ُساعد سميناه
166
.3.13دوال التعامل مع السلاسل المحرفي ّة
في النهاية نقوم بإنقاص 1من المجموع ،لـكي لا نحسب . \0قبل نهاية الدالة ،نقوم بإرجاع الطول الذي حسبناه أي
. charactersCount
الدالة ) strcpyو ال ّتي تعني ” (”String copyتسمح بنسخ سلسلة محرفي ّة إلى داخل أخرى.
نموذج الدالّة :
• : stringCopyمؤش ّر نحو *) charجدول .( charفي هذا الجدول يتم ّ لصق السلسلة.
• : stringToCopyمؤش ّر نحو جدول آخر من . charهذه السلسلة يتم ّ نسخها إلى . stringCopy
الدالّة تعيد مؤش ّرا نحو ، stringCopyو هو غير مفيد جدّا .عادة ،لا نحفظ ما تعيده هذه الدالّة .فلنجر ّبها :
string is : Text
copy is : Text
!
]copy[5 تأكّد أن السلسلة copyكبيرة كفاية لاحتواء محتوى . stringإن قمت في هذا المثال بتعر يف
)و الذي هو غير كاف لأنه لن يبق مكان لـ ،( \0الدالّة ” strcpyستخرج عن الذاكرة” و ربّما ستوقف
البرنامج عن العمل .تجن ّب ذلك ،إلّا إذا كنت تريد تعطيل برنامجك.
167
الفصل .13السلاسل المحرفي ّة )(Strings
هذه الدالة تضيف سلسلة محرفي ّة إلى نهاية الأخرى .نسمّي هذه العملية بالوصل ).(Concatenation
فلنفرض أن لدينا المتغي ّرات التالية :
كما يمكنك أن ترى string2 ،غير قابل للتعديل لأن ّه تم ّ التصريح عنه كثابت في نموذج الدالّة.
الدالّة تعيد مؤش ّرا نحو ، string1و مثلما هو الحال مع ، strcpyفهذا لا يصلح لشيء كبير في حالتنا هذه :يمكننا
إذن تجاهل ما تعيده الدالّة.
168
.3.13دوال التعامل مع السلاسل المحرفي ّة
ن string1كبيرة بما فيه الـكفاية لـكي نستطيع إضافة محتوى string2إليها ،و إلّا فستحدث خروجا
تأكّد دائما أ ّ
عن الذاكرة و قد يتسبب في تعطّل البرنامج.
لهذا السبب قمت بتعر يف string2بحجم .100بينما تركت الحاسوب يحسب حجم string1تلقائي ّا )هذا يعني
ن هذه السلسلة لا يتم ّ تعديلها ،فلا حاجة إذن لجعلها أكبر من اللازم.
أنني لم أحدد الحجم( لأ ّ
string1 الجدول string2تم ّت إضافته إلى نهاية ) string1الذي يحوي 100خانة( .الـ \0الخاص بـ
تم ّ حذفه )في الواقع تم ّ استبداله بـ Mمن .(Mateo21في الواقع ،لا يجب ترك \0في وسط سلسلة محرفي ّة ،و إلّا فسيتم ّ
”قطعها” في المنتصف ! لا نضع \0إلّا في نهاية سلسلة محرفي ّة ،فقط عندما ننهيها.
المتغي ّران string1و string2يتم ّ مقارنتهما .كما تلاحظ ،لا يتم ّ تعديل أيّ منهما لأن ّه تم ّ التصريح عنهما كثوابت.
إنه من الضروري أن نقوم باسترجاع ما تعيده إلينا الدالة .في الواقع strcmp ،تعيد :
م
أعلم أن ّه كان من المنطقيّ أكثر أن تعيد الدالّة 1إن كانت السلسلتين متطابقتين لـكي نقول ”صحيح” )تذك ّر المتغي ّرات
ل المحارف متطابقة
ل سلسلة واحدا واحدا .إن كانت ك ّ
المنطقي ّة( .السبب بسيط :الدالّة تقارن قيم المحارف من ك ّ
فستعيد .0إن كانت محارف string1أكبر من محارف ، string2فالدالّة تعيد عددا موجبا .في الحالة
ن سلسلتين متطابقتان أم لا.
المعاكسة تعيد عددا سالبا .عملي ّا ،نستخدم strcmpكثيرا للتحقّق من أ ّ
169
الفصل .13السلاسل المحرفي ّة )(Strings
ن
ليس لديّ ما أضيفه فيما يتعل ّق بهذه الدالّة .هي بسيطة الاستخدام ،لـكنّ الشيء الوحيد الذي لا يجب نسيانه هو أ ّ
0يعني ”متطابق” و أي قيمة أخرى تعني ”مختلف” .هذا هو مصدر الأخطاء الوحيد هنا.
: strchrالبحث عن محرف
م
ن characterToFindمن نوع intو ليس من نوع . charهذا ليس مشكلا فعلا لأن ّه في
تلاحظ أ ّ
الواقع المحرف هو عدد .على الرغم من ذلك ،نفضّ ل استخدام charعلى intلتخزين المحارف في الذاكرة.
الدالة تقوم بإرجاع مؤشر نحو أوّل ظهور للمحرف الذي وجدته في السلسلة ،أي أنها ترجع عنوانه في الذاكرة .إن لم
تجد شيئا فستعيد . NULL
في المثال التالي ،سأسترحع هذا المؤش ّر في . restOfString
170
.3.13دوال التعامل مع السلاسل المحرفي ّة
خاص قليلا.
ّ هل فهمت ما حصل ؟ إن الأمر
ن stringيؤش ّر على المحرف الأوّل )أي ’( ’T
في الحقيقة ،إن restOfStringهو مؤشر مثل ، stringإلّا أ ّ
بينما restOfStringيؤش ّر على أوّل محرف ’ ’sموجود في . string
ل مؤش ّر :
يوضح أين يؤش ّر ك ّ
المخطّط التالي ّ
ل
عندما أقوم بـ printfلـ ، restOfStringفمن العاديّ أن يتم ّ عرض ” .”st Textالدالّة printfتعرض ك ّ
المحارف التي تلقاها ) (t،x،e،T،t،sحت ّى الوصول إلى \0الذي يعلمها بنهاية السلسلة.
نسخة مختلفة
توجد دالّة strrchrمطابقة تماما لـ strchrباستثناء أنها تعيد مؤش ّرا على آخر ظهور للمحرف الموجود في السلسلة بدلا
من الأوّل.
strchr هذه الدالّة تشبه كثيرا السابقة .هذه تبحث عن محرف من بين تلك التي تعطيها على شكل سلسلة محرفي ّة ،بخلاف
التي لا يمكنها البحث عن سوى محرف وحيد في المر ّة.
مثلا ،إن أعطيناها السلسلة ” ”xesو بحثنا في ” ، ”Test textفالدالّة تعيد مؤشرا نحو أول تكرار لأحد المحارف
strpbrk التي وجدتها .في هذه الحالة ،أوّل محرف من ” ”xesال ّتي ستجده في ” ، ”Test textهو الـ ’ ، ’eإذن
تعيد مؤش ّرا على ’. ’e
النموذج :
171
(Strings) السلاسل المحرفي ّة.13 الفصل
: فلنجر ّب الدالّة
This is the rest of the string starting by the first occurrence of the
characters found : est text
ل
ّ لسنا مجـبرين على استخدام متغي ّر في ك.( القيم المراد ارسالها إلى الدالّة مباشرة )بين علامتي اقتباس،في هذا المثال
. يمكننا كتابة السلسلة مباشرة،المر ّات
: يجب تذك ّر هذه القاعدة البسيطة
بينما، تبحث عن واحد من المحارفstrpbrk : لـكن احذر من الخلط، strpbrk النموذج مشابه لذلك الخاص بـ
.ل السلسلة
ّ تبحث عن كstrstr
: مثال
172
.3.13دوال التعامل مع السلاسل المحرفي ّة
م
string.h هذه الدالّة موجودة في stdio.hبخلاف الدوال التي درسناها إلى ح ّد الآن ،و التي كانت في
هذا الاسم يذك ّرك بشيء ما .هذه الدالّة مشابهة إلى ح ّد كبير لـ printfالتي تعرفها ،و لـكن بدل الكتابة على الشاشة،
sprintfتكتب في ...سلسلة محرفي ّة ! و من هذا اسمها الذي يبدأ بـ” ”sمن ”) ”stringسلسلة محرفي ّة بالإنجليز ي ّة(.
إنّها دالّة عملي ّة جدّا لتنسيق سلسلة محرفي ّة .مثال صغير :
173
الفصل .13السلاسل المحرفي ّة )(Strings
تُستخدم بنفس طر يقة printfباستثناء أن ّه يجب إعطائها كمعامل أوّل مؤش ّرا نحو السلسلة التي يجب أن تستقبل
النص.
ّ
ل
في هذا المثال ،أكتب في ،”You have %d years” stringحيث يتم ّ استبدال %dبمحتوى المتغي ّر . ageك ّ
قواعد printfتطب ّق ،يمكنك إذا أردت أم تضع %sلإدراج سلاسل أخرى داخل سلسلتك !
النص الذي سترسله لها . sprintfو إلّا ،فقد يحدث تجاوز في
كالعادة ،تأكّد أن سلسلتك كبيرة كفاية لاحتواء ّ
الذاكرة و بالتالّي تعطّل برنامجك.
ملخّ ص
ل حرف
• الحاسوب لا يجيد التعامل مع النصوص ،هو لا يعرف إلّا الأعداد .لإصلاح هذا المشكل ،تم ّ ربط ك ّ
بعدد موافق له في جدول يسمّى جدول .ASCII
• النوع charيستخدم لتخزين حرف واحد .يخز ّن في الحقيقة عددا ،لـكنّ هذا العدد تتم ّ ترجمته إلى حرف من
طرف الحاسوب أثناء العرض.
• لإنشاء كلمة أو جملة ،علينا بناء سلسلة محرفي ّة .من أجل هذا ،نستخدم جدول .char
string.h • توجد الـكثير من الدوال الجاهزة للتعامل مع السلاسل المحرفي ّة في المكتبة .stringيجب تضمين
لاستخدامها.
174
الفصل 14
بعد كل المعلومات المتعبة التي تلقّيتها في الفصول حول الجداول ،النصوص و المؤشرات ،فسنقوم بالتوقف قليلا .لقد
تعلّمت أشياء جديدة كثيرة في الفصول السابقة ،لن يكون لديّ مانع من نسترجع أنفاسنا قليلا.
هذا الفصل يتحدّث عن المعالج القبلي ،هذا البرنامج الّذي يعمل مباشرة قبل الترجمة.
لا تخطئ :المعلومات التي به ستكون مهمّة لك .لـكنّها ستكون أقل تعقيدا ً من ال ّتي تعلّمتها مؤخّراً.
الـinclude 1.14
كما شرحت لك في الفصول الأولى من الكتاب ،نجد في الشفرات المصدر ي ّة سطورا خاصّة تسمّى بـتوجيهات المعالج
القبلي ).(Preprocessor directives
هذه السطور لديها الخاصي ّة التالية :تبدأ دائما بالرمز . #لذا فمن السهل التعر ّف عليها.
كالـCode::Blocks لنضمّن ملفا ً ذو صيغة .hموجودا ً في نفس المجلّد الذي ثبتنا فيه الـ) IDEأي البيئة التطوير ية
مثلا( ،نستعمل علامات الترتيب > < كالتالي :
ملف .hموجود في المجلّد الذي به مشروعنا ،فسنقوم يذلك باستخدام علامتي الترتيب كالتالي :
بينما لتضمين ّ
175
(Preprocessor) المعالج القبلي.14 الفصل
يحتوي نماذجfile.h و لدي ملف، يحتوي الشفرة الخاصة بالدوال التي كتبتهاfile.c افترض أن لديّ ملفّا
. يمكن تلخيص ذلك بالمخطط التالي، 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 و في الملف
file.h سيضع كما قلت محتوى الملف، file.c قبل أن تتم ترجمة الملف،عندما يمر المعالج القبلي بهذه الشفرة
: قُبَي ْل الترجمة سيحتوي التاليfile.c يعني أن الملف، في النهاية. file.c في الملف
176
الـdefine .2.14
م
إن كنّا قد قررنا وضع النماذج في ملفّات .hبدل ملفّات ، .cفهذا من المبدأ .بالطبع ،كان بإمكاننا وضع نماذج
الدوال في أعلى الملفات .cبأنفسنا )قد نفعل هذا أحيانا في بعض البرامج الصغيرة( ،لـكن لأسباب تنظيمي ّة،
.c من المنصوح به جدّا وضع النماذج في ملفّات . .hعندما يكبر برنامجك و يصبح لديك الـكثير من ملفّات
يعتمدون على نفس ، .hستكون سعيدا لأن ّك لن تظطر ّ إلى نسخ و لصق النماذج الخاصّة بنفس الدوال عدّة
مرات !
ّ
الـdefine 2.14
هذه التوجيهة تسمح بالتصريح عن ثابت معالج قبلي .هذا يسمح بإرفاق قيمة بعبارة.
إليك مثالا :
• الـ . define
• الكلمة التي تريد ربط القيمة بها.
• قيمة الكلمة.
احذر :رغم التشابه )خصوصا في الاسم الذي اعتدنا كتابته بحروف كبيرة( ،فهذه مختلفة كثيرا عن الثوابت التي
تعلّمناها حت ّى الآن ،مثل :
الثوابت تأخذ حي ّزا في الذاكرة .حتى و إن لم ٺتغير قيمتها فإن العدد 3مخز ّن في مكان ما من الذاكرة .هذا ليس هو
الحال مع ثوابت المعالج القبلي !
177
الفصل .14المعالج القبلي )(Preprocessor
ل INITIAL_NUMBER_OF_LIVESبالرقم .3
يستبدل في الملف ك ّ
؟
ما الفائدة بالنسبة للثوابت التي رأيناها حت ّى الآن ؟
كما قلت لك ،هي لا تأخذ مكانا في الذاكرة .هذا منطقيّ ،نظرا لأن ّه عند الترجمة لا يتبقّى سوى الأرقام في الشفرة
المصدر ي ّة.
ن الاستبدال يتم ّ في كامل الملف حيث توجد . #defineإن قمت بتعر يف ثابت في
توجد فائدة أخرى و هي أ ّ
الذاكرة داخل دالّة ،فلن يكون صالحا إلّا داخل تلك الدالّة ،ثم ّ يتم ّ حذفه بعد نهايتها .بينما بالـنسبة لـ #defineفإنها
ل دوال الملف ،و هذا قد يكون عملي ّا جدّا في بعض الحالات.
تُطب ّق على ك ّ
الفائدة هي أن ّه إن أردت تغيير حجم الواجهة )لأنّها تبدو لك صغيرة جدّا( ،فيكفي أن تغي ّر #defineو تعيد ترجمة
الشفرة.
ن #defineتكون عادة في ملفات .hمع نماذج الدوال )بامكانك أن ترى .hالخاصّة بالمكتبات مثل
لاحظ أ ّ
، stdlib.hستجد الـكثير من .( #define
#defineإذن هي ”مسهّلات وصول” ،يمكنك تعديل حجم نافذه عن طر يق تعديل #defineبدل الذهاب للبحث
في الدوال عن الموضع الذي تفتح فيه النافذة لتعديل الأبعاد .هذا ربح وقت للمبرمج.
كملخّ ص ،ثوابت المعالج القبلي تسمح بـ”إعداد” برنامجك قبل ترجمته .إنّها أشبه بطر يقة إعدادات صغيرة.
178
الـdefine .2.14
نستخدم كثيرا #defineمن أجل تعر يف حجم الجداول .نكتب مثلا :
؟
و لـكن ...كنت أعتقد أن ّه لا يمكننا وضع متغي ّر أو ثابت بين القوسين المربعين أثناء تعر يف جدول ؟
نعم هذا صحيح ،لـكنّ MAX_SIZEليس متغي ّرا و لا ثابتا .في الواقع لقد قلت لك ،المعالج القبلي يحو ّل الملف قبل
الترجمة إلى :
بتعر يف MAX_SIZEبهذه الطر يقة ،يمكنك استخدامها لإنشاء جداول ذات حجوم محدّدة .إذا صارت في المستقبل
غير كافية ،فليس عليك سوى تعديل سطر ، #defineإعادة الترجمة ،و جداول charتأخذ القيمة الجديدة ال ّتي
حددتها.
يمكنك القيام بكل العمليات القاعدية التي تعرفها :جمع ) ،(+طرح ) ،(-ضرب )*( ،قسمة ) ،(/ترديد ).(%
179
الفصل .14المعالج القبلي )(Preprocessor
كل من هذه الثوابت تبدأ و تنتهي برمزي ) _ underscoreتجده في لوحة المفاتيح تحت الرقم 8أعلى اللوحة بالنسبة
للتخطيط .(AZERTY
؟
ما الفائدة من ذلك ؟
القائدة قد لا تبدو واضحة كما كان الأمر في السابق ،لـكن لهذا فائدة و سنكتشفها بسرعة.
180
.3.14الماكرو )(Macro
كنا قد رأينا بانه باستعمال الـ ، #defineبامكاننا أن نطلب من المعالج القبلي استبدال كلمة بقيمتها في الشفرة بأكملها.
مثال :
ن جميع NUMBERفي الشفرة يتم ّ استبدالها بـ .9لقد رأينا أنّها تعمل كوظيفة بحث و استبدال يقوم بها
و الذي يعني أ ّ
المعالج القبلي قبل الترجمة.
لديّ خبر جديد ! في الواقع #defineأقوى من هذا بكثير .فهي قادرة على الاستبدال بـ ...شفرة مصدر ية بأكملها
! عندما نستخدم #defineللبحث و استبدال كلمة بشفرة مصدر ية نقول أننا أنشأنا ماكرو ).(Macro
الشيء الذي تغي ّر هو القوسين الذين أضفناهما بعد الكلمة المفتاحي ّة )هنا )( .( COUCOUسنرى فائدتهما بعد قليل.
Coucou
ن هذا ليس شيئا جديدا حالي ّا .لـكنّ الّذي عليك فهمه ،هو أن الماكرو عبارة عن بضعة أسطر من الشفرة التي
أعلم أ ّ
يتم استبدالها مباشرة في الشفرة قبل الترجمة.
الشفرة التي كتبناها تصبح هكذا قبل الترجمة :
181
الفصل .14المعالج القبلي )(Preprocessor
؟
ل ماكرو ؟
لـكن ،هل يمكننا أن نضع سطرا ً واحدا فقط من الشفرة في ك ّ
ن استدعاء الماكرو لا يوضع بعده فاصلة منقوطة في النهاية .في الواقع ،لأنها توجيهة خاصة
كما تلاحظ في ، 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 }
م
يمكننا مثلا إضافة الـ elseلـكي نُظهر على الشاشة :أنت لست بالغا ً” .”You are not adultحاول القيام بذلك،
الأمر ليس صعبا .لا تنس وضع الشرطة الخلفي ّة \ قبل السطر الجديد.
182
.4.14الشروط
أي أن الشفرة المصدر ي ّة السابقة بعد مرور المعالج القبلي مباشرة تصبح هكذا :
)][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
تلاحظ أنه لا توجد حاضنتان في لغة المعالج القبلي.
#ifndef #ifdefو
سنرى الآن الفائدة من استعمال #defineلتعر يف ثابت دون إعطائه أيّ قيمة ،مثلما علّمتك من قبل :
في الواقع ،يمكننا استعمال الشرط #ifdefلنقول ”إن كان الثابت معر ّفا” .بالنسبة لـ ، #ifndefفهذا يعني ”إن كان
الثابت غير معر ّف”.
؟
ماذا يعني التضمين اللامنتهي ؟
هذا أمر بسيط ،تخيل أن لدينا ملفأ ً 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لـكن داخل A.hيجد بأن ّه يحتاج إلى تضمين ! B.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
الآن لقد تم تعر يف الثابت .في المر ّة القادمة التي يتم فيها تضمين الملف ،لن يكون الشرط فيها صحيحا و لهذا لن نخاطر
بإعادة تضمين الملف من جديد.
يمكنك تسمية اسم الثابت كما تريد ،أنا اعتدت على تسميته . DEF_FILENAME
185
الفصل .14المعالج القبلي )(Preprocessor
الشيء الأهمّ ،و الّذي أتمن ّى أن ّك فهمته جي ّدا ،هو أن تغي ّر اسم الثابت من ملف .hإلى آخر .يجب ألّا يكون نفس
ل ملفّات ، .hو إلا فلن تتم قراءة سوى أول ملف .hو الباقية سيتم تجاهلها !
الثابت في ك ّ
إذا فلتغي ّر FILENAMEإلى اسم الملف الحالي.
م
أنصحك بإلقاء نظرة على .hالخاصّة بالمكتبات القياسي ّة المتواجدة في حاسوبك ،سترى بأنها كلها مكتوبة بنفس
المبدأ ) #ifndefفي البداية و #endifفي النهاية( .هذا يضمن عدم إمكاني ّة حصول تضمينات لامنتهية.
ملخّ ص
• المعالج القبلي هو برنامج يحل ّل الشفرة المصدر ي ّة ،و يقوم بإجراء تغييرات علىها قبل الترجمة.
• تعليمة #defineتسمح بتعر يف ثابت معالج قبلي .يتم استبدال كلمة مفتاحي ّة بقيمة في الشفرة المصدر ي ّة.
• الماكرو هي مجموعة أسطر من الشفرة الجاهزة معر ّفة بالـ . #defineيمكنها أن تقبل معاملات.
• من الممكن كتابة شروط في لغة المعالج القبلي لاختيار ما يجب ترجمته ،نستعمل عادة #elif ، #ifو . #endif
ل
ن ملفّا ً .hيتم ّ تضمينه عددا لامنتهيا من المر ّات ،نحميه بمجموعة من ثوابت المعالج القبلي و الشروط .ك ّ
• لنتجنب أ ّ
ملفّاتك .hالمستقبلي ّة يجب حمايتها بهذه الطر يقة.
186
الفصل 15
صة بك
أنشئ أنواع متغيرات خا ّ
تسمح لغة الـ Cبالقيام بشيء يعتبر قو يا ًجدا ً :و هو أن ننشئ أنواعا ًخاصة بنا” ،أنواع متغي ّرات مخصّ صة” .سنرى نوعين
:الـهياكل ) (Structuresو التعدادات ).(Enumerations
إن إنشاء أنواع خاصّة بنا يعتبر أمرا ً ضرور يا خاصة إذا أردنا إنشاء برامج أكثر تعقيداً.
حظ( بالصعب ،لـكن رك ّز جي ّدا لأننا سنستعمل الهياكل كل الوقت انطلاقا من الفصل القادم.
الأمر ليس )لحسن ال ّ
ن المكتبات تنشئ غالبا أنواعها الخاصّة .لن يمر ّ وقت كثير حت ّى تستخدم نوعا يدعى ”ملف” ،و بعده بقليل،
يجب أن تعلم أ ّ
أنواع أخرى مثل ”نافذة”” ،صوت”” ،لوحة مفاتيح” ،إلخ.
الهيكل هو تجميع لعدد من المتغيرات التي يمكن لها أن تحمل أنواعا مختلفة .على عكس الجداول التي ترغمنا على استعمال
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
خاص هنا :بالنسبة للهياكل ،يجب أن تضع بعد الحاضنة النهائية فاصلة منقوطة .هذا أمر إجباري.
ّ احذر ،الأمر
إن لم تفعله فستتوق ّف الترجمة.
تخيل أنك تريد إنشاء متغي ّر لـكي يٌخز ّن إحداثيات نقطة في معلم الشاشة .ستحتاج بالتأكيد إلى هيكل كهذا عندما تبدأ في
برمجة ألعاب ثنائية الأبعاد في الجزء التالي من الكتاب ،هذه إذن فرصة للتقدّم قليلا.
إذا كانت كلمة ”علم الهندسة” تُحدث ظهور بُق َع غير مفهومة على كامل وجهك ،فالمخطّط التالي سيذك ّرك قليلا بأساسي ّات
الأبعاد الثنائي ّة ).(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و الذي يحتوي على معلومات عن شخص :
هذا الهيكل متشكّل من 5متغيرات داخلي ّة ،الثلاث الأولى هي سلاسل محرفي ّة لتخزين الاسم ،اللقب و العنوان.
المتغيران الأخيران يخزّنان ع ُمر و جنس الشخص .الجنس هو متغي ّر منطقي = 1 ،صحيح = ولد و = 0خطأ = بنت.
يمكن لهذا الهيكل أن يساعدنا في كتابة برنامج مذك ّرة عناوين .يمكنك بالطبع إضافة القدر الذين تريد من المتغيرات داخل
الهيكل من أجل إتمامها إذا أردت .لا يوجد ح ّد لعدد المتغي ّرات في هيكل.
و الآن ،بما أن الهيكل معر ّف في ملف ، .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لهيكل ،أي كتابة شيء مكافئ لكتابة آخر.
• : struct Coordinatesهو اسم الهيكل الذي سنقوم بانشاء اسم مستعار له )أي ”مكافئ”(.
ببساطة ،هذا السطر يقول ” :كتابة Coordinatesمكافئ لكتابة .” struct Coordinatesبفعل هذا ،لن
ل تعر يف لمتغي ّر من نوع . Coordinatesيمكننا العودة إلى mainو كتابة
يكون عليك كتابة الكلمة structعند ك ّ
فقط :
أنصحك أن تستعمل الـ typedefمثلما فعلت أنا هنا من أجل . Coordinatesأغلب المبرمجـين يفعلون هذا .هذا
مرة .المبرمج الجي ّد هو مبرمج كسول ! أي أنه يكتب أقل ما يمكن.
ل ّيسمح لهم بعدم كتابة structفي ك ّ
190
.2.15استعمال هيكل
و الآن بعدما قمنا بإنشاء متغي ّرنا ، pointنريد أن نغي ّر إحداثي ّاته.
كيف نصل إلى xو yالموجودة في المتغير point؟ هكذا :
بهذا نكون قد غي ّرنا قيمة ، pointبإعطائه الفاصلة 10و الترتيبة .20نقطتنا أصبحت في الوضعية )10؛) (20هذا
هو الترميز الر ياضياتي للإحداثي ّات(.
1 variable.componentName
إن أخذنا الهيكل Personالذي رأيناه منذ قليل و نطلب الاسم و اللقب فسنفعل هكذا :
يمكن فعل هذا بدون معرفة الهياكل ،فقط بإنشاء متغي ّر lastNameو آخر . firstName
لـكن الفائدة هنا هي أنه بهذه الطر يقة يمكننا أن ننشئ متغيرا آخر من نوع Personو يكون لديه هو أيضا اسمه الخاص،
لقبه الخاص ،إلخ .يمكننا إذن فعل هذا :
191
الفصل .15أنشئ أنواع متغيرات خاصّة بك
و بعدها يمكننا الوصول إلى لقب اللاعب المتواجد بالخانة الأولى مثلا ،هكذا :
players[0].lastName
الفائدة من استعمال الجدول هنا ،هو أنها بامكاننا استعمال حلقة لنقرأ المعلومات الخاصة باللاعب 1و اللاعب 2
مرة اللقب ،الاسم ،العنوان ...
ل ّمرتين .يكفي تصفّح الجدول playersو طلب ك ّ بدون الاضطرار إلى إعادة الشفرة ّ
تهيئة هيكل
ل المتغيرات ،الجداول و المؤش ّرات ،فنحن نفضّ ل أن نعطيها قيما ابتدائية كي نضمن أنّها لن تحوي
بالنسبة للهياكل ،مثل ك ّ
”قيما عشوائية” .في الواقع ،أعيد تذكيرك ،المتغير الّذي يتم ّ إنشائه يأخذ القيمة الموجودة في الذاكرة حيث تم ّ وضعه .أحيانا
مر قبلك ،لذلك ستكون قيمته شيئا لا معنى له ،مثل .−84570
تكون هذه القيمة ،0و أحيانا بقايا برنامج ّ
بالنسبة للهياكل ،فالتهيئة شبيهة بتلك الخاصّة بالجدول .في الواقع ،يمكننا القيام بها عند التصريح عن المتغي ّر :
192
.3.15مؤش ّر نحو هيكل
و هذا يعر ّف بالترتيب point.x = 0 :و . point.y = 0لنعد إلى الهيكل ) Personالذي يحتوي سلاسل
محرفي ّة( .يمكننا أن نعطي قيمة ابتدائية للسلاسل بكتابة فقط ”” )لا شيء ببن علامتي الاقتباس( .لم أعلمك هذا
خاص بالسلاسل ،لـكنّ الوقت ليس متأخّرا على تعلّمها .يمكننا إذن تهيئة على الترتيب ، firstName
الشيء في الفصل ال ّ
، age ، address ، lastNameو boyهكذا :
initializeCoordinates رغم ذلك ،أنا لا أستخدم هذه التقني ّة كثيرا .أفضّ ل أن أرسل متغي ّري ،مثلا ، pointإلى دالّة
تقوم بالتهيئات من أجلي على متغيري.
لفعل هذا ،يجب إرسال مؤش ّر نحو متغيري .في الواقع ،إن أرسلت فقط المتغي ّر ،سيتم إنشاء نسخة عنه )مثل أيّ متغي ّر
عادي( و تعديل قيم النسخة لا قيم المتغي ّر .راجع الخيط الأحمر من فصل المؤشرات إن نسيت كيف يعمل هذا الأمر.
يجب إذن تعل ّم كيفي ّة استخدام المؤشرات على الهياكل .الأمور بدأت تصعب قليلا !
المؤش ّر على الهيكل يتم ّ إنشائه بنفس طر يقة إنشاء مؤش ّر على intأو doubleأو أيّ نوع قاعديّ آخر :
أنا أفعل هكذا كثيرا ،لأنه لتعر يف عدّة مؤشرات على سطر واحد ،سيكون علينا وضع النجمة أمام اسم كل واحد
منها :
الشيء الذي يهمّنا هنا ،هو كيفي ّة إرسال مؤشر هيكل إلى دالة كي تقوم هذه الأخيرة بتعديل محتواه.
هذا ما سنقوم به في هذا المثال ،سنقوم بإنشاء متغير من نوع Coordinatesفي ، mainو نرسل عنوانه إلى
. initializeCoordinatesدور هذه الدالة هو إعطاء القيمة 0لعناصر الهيكل.
193
الفصل .15أنشئ أنواع متغيرات خاصّة بك
الآن بما أنّنا داخل الدالة ، initializeCoordinatesسنقوم يتهيئة قيم المتغير 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نستخدم المؤشرات نحو
ل ٺتبعك حت ّى إلى قبرك ،فأنا لا
ن المؤشرات ستظ ّ
الهياكل أكثر من استعمال الهياكل وحدها .لهذا فعندما أقول لك بأ ّ
أقولها تقريبا من أجل المزاح !
بما أن المؤشرات نحو الهياكل مستعملة بكثرة ،نجد أنفسنا نستعمل هذه الكتابة كثيرا :
مرة أخرى ،المبرمجون وجدوا هذه الكتابة طو يلة جدّا .الأقواس حول ، *pointيا لها من بلوى ! إذن ،بما أن
ّ
المبرمجـين أشخاص كسالى )لقد قلت هذا سابقا على ما أعتقد( ،فقد اخترعوا هذا الاختصار :
هذا الاختصار يتم ّ كتابته بمطّة -متبوعة بعلامة ترتيب > .
!
لا تنس أننا لا نستطيع استعمال السهم إلا مع المؤشرات.
إن كنت تعمل على المتغير مباشرة ،يجب عليك استخدام النقطة كما رأينا في البداية.
و تذك ّر جي ّدا اختصار السهم ،سنستعمله كثيرا ً من الآن .و خاصّة لا تخلط بين السهم و ”النقطة” .السهم مخصّ ص
للمؤش ّرات” ،النقطة” مخصّ صة للمتغي ّرات .استخدم هذا المثال الصغير للتذك ّر :
نغي ّر قيمة xإلى 10بطر يقتين مختلفتين ،هنا :الطر يقة الأولى هي بالعمل مباشرة على المتغير ،و الطر يقة الثانية
باستعمال المؤشر.
195
الفصل .15أنشئ أنواع متغيرات خاصّة بك
التعدادات 4.15
التعداد ليس متكو ّنا من ”مركّبات” كما هو الحال مع الهياكل .و إنما هو مجموعة من ”القيم الممكنة” لمتغي ّر .التعداد
سيأخذ إذن خانة واحدة في الذاكرة و هذه الخانة تأخذ قيمة واحدة من مجموع القيم التي قمت بتعر يفها )واحدة فقط في
مرة( .هذا مثال عن تعداد :
ل ّك ّ
1 ;typedef enum Volume Volume
2 enum Volume
3 {
4 LOW, MEDIUM, HIGH
5 ;}
لـكي نقوم بتعر يف تعداد نستعمل الكلمة المفتاحية . 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
ملخّ ص
ل هذا ؟ هي أنها تجعل الشفرة قابلة للقراءة جي ّدا .في الواقع ،أيّ شخص يمكنه بسهولة قراءة ifالسابق
الفائدة من ك ّ
ن الشرط يعني ”إن كانت الموسيقى بمستوى صوت متوسّ ط”(.
)نفهم جي ّدا أ ّ
ما الفائدة التي يمكن تحصيلها من هذا ؟ حسنا فلنفرض أن ّه في حاسوبك ،الصوت يتم تحديده بقيمة بين 0و 0) 100
ل عنصر :
= لا صوت 100% = 100 ،من الصوت( ،فسيكون من الجيد ارفاق قيمة محدّدة بك ّ
ملخّ ص
• الهيكل هو نوع بيانات مخصّ ص يمكنك إنشاؤه و استخدامه في برامجك .يجب عليك تعر يفه ،عكس الأنواع القاعدي ّة
ل البرامج.
مثل intو doubleال ّتي نجدها في ك ّ
• الهيكل يتكو ّن من ”متغي ّرات داخلي ّة” تكون عادة من أنواع قاعدي ّة مثل intو ، doubleو أيضا من الجداول.
• نستطيع الوصول إلى أحد مركّبات الهيكل بفصل اسم المتغي ّر و المركّ ب بنقطة . player.firstName :
• إذا كنّا نتعامل مع مؤشر نحو هيكل و أردنا الوصول إلى أحد مركّباته ،نستخدم السهم بدل النقطة :
. playerPointer->firstName
• التعداد هو نوع مخصّ ص يمكنه فقط أخذ إحدى القيم المسبقة التعر يف MEDIUM ، LOW :أو HIGHمثلا.
197
الفصل .15أنشئ أنواع متغيرات خاصّة بك
198
الفصل 16
ل المتغي ّرات
المشكل مع استعمال المتغي ّرات ،هو أنها موجودة فقط في الذاكرة العشوائية .RAMبخروجنا من البرنامج ،ك ّ
يتم حذفها من الذاكرة و لن يصبح ممكنا استعادة قيمها .كيف يمكننا إذن أن نحتفظ بأحسن العلامات التي تحصّ لنا عليها
النص المكتوب يختفي بمجر ّد إيقاف البرنامج ؟
ّ ل
في لعبة ؟ كيف يمكننا إنشاء محرر نصوص إذا كان ك ّ
للقراءة و الكتابة في الملفّات ،سنستعمل دوالا ًمعر ّفة في المكتبة stdioالتي استعملناها سابقاً.
نعم ،هذه المكتبة تحتوي على الدالتين scanfو printfاللتان نعرفهما جي ّدا ! لـكن ليس هذا فحسب :يوجد بها
الـكثير من الدوال الأخرى ،خصوصا التي تعمل على الملفات.
م
كل المكتبات التي استعملناها حت ّى الآن ) ( ... string.h ، math.h ، stdio.h ، stdlib.hتشكّل
ما نسميه بالمكتبات القياسية ) ،(Standard librariesو هي مكتبات تأتي تلقائيا مع البيئة التطوير ية التي تستخدمها
و لديها الميزة في أنّها تعمل على كل أنظمة التشغيل .بالإمكان استعمالها في أيّ مكان ،سواء كنت في ،Windows
أو GNU/Linuxأو Macأو غير ذلك .المكتبات القياسي ّة ليست كثيرة و لا تمكّننا من القيام بأكثر من بعض
الأمور الأساسي ّة ،كما فعلنا لغاية الآن .للحصول على وظائف أكثر تقدّما ،كفتح النوافذ ،يجب تنز يل و ٺثبيت
مكتبات جديدة .سنرى ذلك قريبا !
تأكّد إذن ،للبدأ ،أن تقوم بتضمين المكتبتين stdio.hو stdlib.hعلى الأقل أعلى ملفك : .c
199
الفصل .16قراءة و كتابة الملفات
حسنا ًو بعدما قمنا بتضمين المكتبتين ،يمكننا أن ننطلق في بالأمور الجدّي ّة .إليك الخطوات التي يجب اتّباعها دائما ًحينما
تريد العمل على ملف ،سواء للقراءة منه أو للكتابة فيه :
• نقوم باستدعاء دالة فتح الملف fopenالتي تقوم بإرجاع مؤش ّر نحو هذا الملف.
الملف موجودا( باختبار قيمة المؤشر الذي أرجعته الدالة .فإن كان المؤشر
ّ • نتأكّد من نجاح عملي ّة الفتح )أي إن كان
ن فتح الملف لم ينجح ،في هذه الحالة لا يمكننا الإكمال )يجب أن نظهر رسالة خطا(.
يساوي ، NULLفهذا يعني أ ّ
• إذا تم الفتح بنجاح )أي أن قيمة المؤشر تختلف عن ،( NULLسنستمتع بالكتابة على الملف أو القراءة منه ،و ذلك
باستخدام دوال سنراها لاحقاً.
• بمجر ّد أن ننهي العمل على الملف ،يجب تذك ّر ”غلقه” باستعمال الدالة . fclose
سنتعل ّم كخطوة أولى كيف نستخدم fopenو ، fcloseحينما ٺتعل ّم هذا ،سنتعل ّم كيف نقرأ محتواه و نكتب نصّ ا
فيه.
: fopenفتح ملف
في فصل السلاسل المحرفي ّة ،كنا نستعين بنماذج الدوال مثل ”دليل استخدام” .هذا ما يفعله المبرمجون غالبا :يقرؤون نموذج
دالة و يفهمون كيف يستخدمونها .مع ذلك ،أعلم أنّنا بحاجة إلى بعض الشروحات البسيطة !
• وضع فتح الملف ،أي دلالة تذكر ما الّذي تريد فعله :القراءة من الملف ،أو الكتابة فيه ،أو كليهما.
هذه الدالة ترجع ...مؤش ّرا على ! FILEإن ّه مؤش ّر على هيكل من نوع . FILEهذا الهيكل متواجد في المكتبة
. stdio.hيمكنك فتح الملف لترى مما يتكو ّن النوع ، FILEلـكن هذا ليس ما يهمّنا.
؟
لـكن لم َِا اسم الهيكل كله بحروف كبيرة ؟ اعتقدت أن الأسماء بالحروف الـكبيرة حجزناها للثوابت و لـ #define؟
ن من
هذه ”القاعدة” ،أنا من قمت بتحديدها )و كثير من المبرمجـين يتبعونها( ،و لـكنّها لم تكن أبدا مفروضة .و يبدو أ ّ
برمجوا stdio.hلا يتبعون نفس القواعد !
200
.1.16فتح و غلق ملف
ن المكتبات ال ّتي سندرسها لاحقا ٺتب ّع نفس القواعد التي أتّبعها ،أي أن اسم
هذا لا يجب أن يشو ّشك كثيرا .سوف ترى أ ّ
الهيكل يبتدئ فقط بحرف واحد كبير.
لنعد إلى دالتنا ، fopenإنها تقوم بارجاع * . FILEإنه من المهم جدّا استرجاع هذا المؤش ّر كي نتمكّن لاحقا ً من
القراءة و الكتابة في الملف .و لهذا سنقوم بإنشاء مؤش ّر على ، FILEفي بداية دالتنا ) mainمثلا( :
ن لم
ل المؤش ّرات على 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+يعمل أيضا ،لـكن بوضع
إن كنت تريد قراءة ّ
الملف لا يمكن تعديله ،هذا نوع من الحماية.
ّ ن
فأنت تضمن أ ّ
الشفرة التالية ستفتح الملف test.txtفي وضع ”) ”r+قراءة و كتابة( :
؟
أين يجب أن يكون الملف test.txt؟
يجب أن يكون في نفس المجلّد الذي يتواجد به الملف التنفيذي ) .( .exe
من أجل متطلّبات هذا الفصل ،أطلب منك أن تقوم بإنشاء ملف test.txtفي نفس المسار الذي به ، .exeمثلما
أفعل أنا )الشكل الموالي(.
202
.1.16فتح و غلق ملف
كما ترى فأنا أستعمل حالي ّا بيئة التطوير Code::Blocksالأمر الذي يفس ّر وجود ملف المشروع بصيغة ) .cbpفي
مكان الصيغة .slnإن كنت تستعمل Visual C++مثلاً( .باختصار ،الأمر المهم هو أن برنامجي ) ( tests.exe
موجود في نفس مجلّد الملف الذي نريد قراءته أو كتابته ) .( test.txt
؟
هل يجب أن يكون الملف بصيغة .txt؟
.level لا ،الأمر يعود إليك في اختيار صيغة الملف عندما تفتحه .أي أنه بإمكانك أن تخـترع صيغتك الخاصّة
لحفظ مستو يات ألعابك مثلاً.
؟
هل من الواجب أن يكون الملف الذي نريد فتحه في نفس دليل الملف التنفيذي ؟
هنا ،الملف test.txtفي مجلّد داخليّ اسمه . directoryهذه الطر يقة التي نسميها المسار النسبي عملي ّة أكثر.
هكذا ،يمكن للبرنامج أن يعمل أينما كان مثب ّتا.
ملف أينما كان في القرص الصلب .في هذه الحالة يجب كتابة المسار الكامل )ما نسميه المسار
من الممكن أيضا فتح ّ
المطلق( :
203
الفصل .16قراءة و كتابة الملفات
!
تعمّدت استعمال شرطتبن خلفي ّتين \ كما تلاحظ .في الواقع ،إن كتبت اشارة واحدة ،سيعتقد الحاسوب أنني
مرتين !
أريد أن استخدم رمزا خاصا )مثل الـ \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التي تقوم بتحرير الذاكرة .يعني
أن ّه سيتم حذف الملف المحمّل في الذاكرة العشوائية.
• : EOFإذا فشل الغلق EOF .هي عبارة عن #defineموجودة في stdio.hو هي توافق عددا ً خاصاً،
يُستعمل للقول أنه حصل خطأ ،أو أننا وصلنا إلى نهاية الملف .في حالتنا هذه ،هذا يعني حدوث خطأ.
في غالب الأحيان ،تنجح عملية غلق الملف :هذا ما يدفعني إلى عدم اختبار إن كانت fcloseقد عملت .رغم
هذا ،يمكنك فعل ذلك إن أردت.
في النهاية ،المخطّط الذي نت ّبعه لفتح و غلق ملف سيكون كالتالي :
)][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
و الآن مادمنا تعلّمنا كيف نفتح و نغلق ملفا ،لم يبق سوى أن نضيف الشفرة ال ّتي تقوم بالقراءة و الكتابة عليه.
ملف.
ملف )الأمر الأبسط قليلا( ،ثم ّ نمر ّ يعدها إلى كيفي ّة القراءة من ّ
سنبدأ برؤ ية كيفي ّة الكتابة في ّ
الكتابة في ملف
توجد الـكثير من الدوال التي تسمح بالكتابة في ملف .يبقى عليك أن تختار أيها الأنسب لك لتستخدمها .هذه الثلاث دوال
ال ّتي سنتعلّمها :
• : fprintfتكتب سلسلة ”منسّقة ً” في الملف ،طر يقة عملها مطابقة تقريبا للدالة . printf
fputc
• المحرف الذي يجب كتابته )من نوع ، intمثلما قلت فاستعماله يعود تقريبا ً إلى استعمال ، charإلا أن عدد
المحارف الممكن استعمالها هنا أكبر( .يمكنك إذن أن تكتب مباشرة ’ ’Aكمثال.
• المؤش ّر نحو الملف الذي نريد أن نكتب فيه .في مثالنا ،المؤش ّر اسمه . fileاستعمال المؤش ّر في ك ّ
ل مرة يساعدنا
ل واحد من هذه الملفّات .لست محدّدا
لأنه بإمكاننا أن نفتح العديد من الملفات في آن واحد ،و نقرأ و نكتب في ك ّ
ملف واحد في المر ّة.
بفتح ّ
الدالة تقوم بإرجاع ، intو هو رمز الخطأ .هذا الـ intيساوي EOFإذا فشلت الكتابة ،و إلّا فسيأخذ قيمة
أخرى.
ل واحدة من fputcقد نجحت ،ولـكن يمكنك
الملف قد تم ّ فتحه بنجاح ،فليس من عادتي إختبار إن كانت ك ّ
ّ ن
بما أ ّ
فعل ذلك إن أردت.
الشفرة التالية تسمح بكتابة الحرف ’ ’Aفي الملف ) test.txtإذا كان موجودا ً من قبل فإنه سيتم استبداله ،أمّا
إن لم يكن موجودا ً سيتم إنشاؤه( .الشفرة تحتوي كل الخطوات التي تكل ّمنا عنها سابقا ً :فتح الملف ،اختبار الفتح ،الكتابة
و الغلق :
206
.2.16طرق مختلفة للقراءة و الكتابة في الملفات
fputs
هذه الدالة شبيهة جدا ً بالدالة ، fputcإلا أنها تسمح بكتابة سلسلة محرفي ّة كاملة ،و هذا عادة أحسن من الكتابة حرفا ً
حرفاً.
لـكن fputcتبقى ضرور ي ّة حينما نحتاج إلى الكتابة محرفا بمحرف ،و هذا يحدث كثيرا.
• : stringالسلسلة ال ّتي نريد كتابتها .تلاحظ أن النوع هنا هو * : const charإضافة الكلمة constفي
ن الدالة لن تقوم بتغييرها .هذا أمر منطقي عندما
النموذج تشير إلى أن السلسة ال ّتي سنعطيها للدالة تُفترض ثابتة .أي أ ّ
ن سلسلتك لن يتم ّ إدخال
نفك ّر فيه fputs :يجب أن تقرأ السلسلة بدون تعديلها .هذه إذن معلومة لك )و حماية( أ ّ
أي ّة تعديلات عليها.
• : pointerOnFileمثل fputc ،تحتاج هذه الدالة إلى مؤشر من نوع * FILEنحو الملف الّذي فتحته.
207
الفصل .16قراءة و كتابة الملفات
الدالّة تعيد القيمة EOF ،في حالة وجود خطأ ،و إلّا ،فهذا يعني أنّها عملت على ما يرام .و هنا أيضا ،لن أقوم عادة
باختبار القيمة التي ترجعها الدالة.
الشكل التالي يظهر الملف بعد التعديل عليه من طرف البرنامج :
fprintf
إليك نوعا ًآخرا ً من الدالة . printfهذه تستخدم للكتابة في ملف .هذه الدالة تستعمل بنفس الطر يقة التي نستعمل بها
، printfإلا أنه يجب إعطاؤها المؤشر نحو FILEكمعامل أوّل.
الشفرة التالية تطلب من المستخدم إدخال عمره ،ثم ّ تقوم بكتابته في الملف :
208
.2.16طرق مختلفة للقراءة و الكتابة في الملفات
fprintf يمكنك إذا إعادة استعمال ما تعرفه عن printfللكتابة في ملف ! لهذا السبب أنا غالبا ما استعمل
للكتابة في الملفّات.
القراءة من ملف
لدينا أيضا ًثلاث دوال للقراءة من ملف ،اسمها مختلف قليلا ًفقط عن دوال الكتابة :
• : fgetcقراءة محرف.
سأسرع قليلا ًفي شرح هذه الدوال :إذا كنت قد فهمت ما كتبته من قبل ،فلن تجد أي صعوبة مع هذه الدوال.
fgetc
هذه الدالة تقوم بإرجاع : intإنه المحرف الذي تم ّت قراءته .إذا لم تقرأ أيّ محرف ،فستعيد القيمة . EOF
؟
لـكن كيف لنا أن نعرف المحرف الذي نقرؤه ؟ ماذا لو أردنا قراءة المحرف الثالث و أيضا العاشر ،كيف نفعل
هذا ؟
ل مرة تقرأ فيها ملفّا ،فهناك ”مؤشر” )) (Cursorمثل المؤش ّر الذي يغمز في محرر النصوص( يتحر ّك في
في الواقع ،في ك ّ
ل مرة .و هذا المؤش ّر افتراضي طبعاً ،لن تتمكن من رؤيته على الشاشة .و هو يشير إلى أين وصلنا في قراءة الملف.
ك ّ
209
الفصل .16قراءة و كتابة الملفات
سنتعلم لاحقا ًكيف نعرف الوضعية التي وصل إليها المؤش ّر بالضبط و أيضا كيف نحر ّكه من مكانه )و ذلك لـكي نقوم
بتحر يكه إلى بداية الملف مثلا ،أو إلى مكان محرف محدّد ،كالمحرف العاشر(.
fgets
هذه الدالة ٺتطلب معاملا خاصّا نوعا ً ما ،و لـكن ّه سيكون عملي ّا جدّا :عدد المحارف التي نريد قراءتها .هذا ما يطلب
من الدالة 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
مرة.
الفرق هو أننا هنا لم نستعمل حلقة تكرار ية .نقوم بإسترجاع محتوى الملف كاملا في ّ
ل تأكيد الآن فائدة استعمال #defineفي شفرتك لتعر يف الحجم الأقصى لجدول مثلاً .في الواقع، أنت تلاحظ بك ّ
MAX_SIZEمستعمل في مكانين مختلفين في الشفرة :
الفائدة هنا ،هي أن ّه في حال ما وجدت أن السلسلة المحرفي ّة غير كبيرة كفاية لقراءة الملف ،فلن يكون عليك سوى
ل مكان من الشفرة وضعت فيه حجم الجدول.
تعديل سطر الـ #defineو إعادة الترجمة .هذا سيجن ّبك البحث عن ك ّ
المعالج القبلي سيقوم باستبدال كل تكرار لـ MAX_SIZEبالقيمة الجديدة.
كما قلت فإن fgetsتقرأ على الأكثر سطرا ً واحدا في المر ّة .ٺتوقف عن قراءة السطر عندما تتجاوز عدد المحارف
الذي سمحت لها بقراءتها.
نعم و لـكن :حالي ّا ،نحن لا نجيد سوى قراءة سطر واحد باستخدام . fgetsكيف لنا أن نقرأ كل الملف ؟ الجواب
بسيط :بحلقة تكرار ية !
الدالة fgetsتعيد NULLفي حالة لم تستطع قراءة ما طلبته منها.
أي أن الحلقة يجب أن تنتهي بمجر ّد أن تعيد fgetsالقيمة . NULL
ليس علينا سوى استعمال الحلقة whileلـكي نقوم بالتكرار ما دامت fgetsلم ترجع : NULL
211
الفصل .16قراءة و كتابة الملفات
سطر الـ whileيقوم بأمرين :قراءة سطر من الملف و التأكد أن fgetsلم تُع ِد . NULLيمكن ترجمة هذا كالتالي
” :اقرأ سطرا ً جديدا ً ما دمنا لم نصل إلى نهاية الملف”.
fscanf
لنفترض أن الملف يحتوي على ثلاثة أعداد مفصولة بفراغ ،و هي مثلا أكبر ثلاثة نقاط تم التحصل عليها في لعبتك :
. 15 20 30
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
كما ترى ،فالدالة fscanfتنتظر ثلاث أعداد مفصولة بفراغ ) ” .( ”%d %d %dستقوم بتخزينهم في جدولنا ذو
الخانات الثلاث.
ل القيم المسترجعة.
نقوم لاحقا ًبإظهار ك ّ
م
حت ّى الآن ،لم استعمل سوى رمز %dواحدا ً في الدالة . scanf
اليوم اكتشفتَ بأنه بإمكانك أن تستعمل العديد منها .إذا كان الملف مكتوبا بطر يقة محدّدة جي ّدا ،فهذا يسمح
ل واحدة من هذه القيم.
لك بالإسراع لاسترجاع ك ّ
كنت قد كل ّمتك عن وجود ”مؤش ّر” افتراضي ) (Virtual cursorقبل قليل .سنقوم الآن بدراسته بشكل أكثر تفصيلاً.
ل
ل مرة تفتح فيها ملفا ،فهناك مؤش ّر يشير إلى وضعيتك في الملف .و لتتخيّله تماما مثل مؤشر محرر النصوص .يد ّ
في ك ّ
على الكان الّذي أنت فيه من الملف ،أي أين ستقوم بالكتابة.
• : rewindتقوم بإرجاع المؤش ّر إلى بداية الملف )هذا مكافئ للطلب من الدالة fseekأن تموضع المؤش ّر في
البداية(.
: ftellالموضع في الملف
هذه الدالة بسيطة الاستعمال جدّا .تعيد الموضع الذي يتواجد به المؤش ّر حاليا بنوع : long
;)1 long ftell(FILE� pointerOnFile
213
الفصل .16قراءة و كتابة الملفات
• العدد deplacementيمكن له أن يكون عددا ً موجبا ً )للتقدم إلى الأمام( ،معدوما )= (0أو سالبا ً )للرجوع
إلى الخلف(.
إليك بعض الأمثلة لـكي تفهم جي ّدا كيف ٺتلاعب بـ deplacementو : origin
• هذه الشفرة تضع المؤش ّر أربع محارف قبل الوضعية الحالية :
;)1 fseek(file, −4, SEEK_CUR
إذا كتبت ،بعد القيام بـ fseekتحرّكك إلى نهاية الملف ،فذلك سيضيف معلومات إلى نهاية الملف )الملف سيتم ّ
إكماله(.
النص الموجود هناك .لا توجد طر يقة لـ”إدراج”
بالمقابل ،إذا وضعت المؤش ّر في بداية الملف وكتبت ،فهذا سيستبدل ّ
نص في ملف .إلا إن قمت بنفسك ببرمجة دالة تقرأ المحارف لتتذك ّرها قبل إستبدالها !
؟
لـكن كيف لي أن أعرف أيّ موضع يجب أن أذهب إليه للقراءة و الكتابة في الملف ؟
هذا يعود إليك .إن كان ملفا ً قمت أنت بكتابته ،فأنت تعرف كيف تم ّ بناءه .أنت تعرف أين تذهب للبحث عن
المعلومة :مثلا ،أحسن النتائج المسجلة في اللعبة في الموضع ،0أسماء آخر اللاعبين في الموضع ،50إلخ.
214
.4.16إعادة تسميه و حذف ملف
سنقوم بعمل تطبيقي لاحقا ًحيث ستفهم ،إذا لم تكن قد فهمت بالفعل الآن ،كيف نذهب للبحث عن معلومة تهمّنا.
لا تنس بأن ّك أنت من يعر ّف كيفي ّة بناءه .إذن عليك أن تقول ” :أضع نتيجة أحسن لاعب في السطر الأوّل ،الخاصة
بثاني أحسن لاعب في السطر الثاني ،إلخ”.
!
الدالة fseekقد ٺتعامل بشكل غريب مع الملفات المفتوحة بوضع النص ) .(Text modeعادة ،نحن نستعملها
النص ،فإنّنا عادة
أكثر مع الملفات المفتوحة بالوضع الثنائي ) .(Binary modeعند القراءة و الكتابة في ملف بوضع ّ
النص مع fseekهو العودة إلى البداية
ما نفعل ذلك محرفا ً محرفاً .الشيء الوحيد الذي نسمح به غالبا في وضع ّ
أو التموضع في نهاية الملف فقط.
هذه الدالة مكافئة لاستخدام fseekلإرجاعنا إلى الموضع 0في الملف :
;)1 void rewind(FILE� pointerOnFile
طر يقة الاستعمال بسيطة كالنموذج .أنت لست بحاجة إلى شرح إضافيّ.
خاص في هاتين الدالتين هو أنهما لا تحتاجان مؤشرا ً نحو الملف لـكي تعملا .يكفيهما فقط اسم الملف المراد
الشيء ال ّ
حذفه أو تغيير اسمه.
الدالة تعيد 0إذا نجحت في اعادة التسمية ،و إلّا فستعيد قيمة مختلفة عن .0هل من اللازم أن أعطيك مثالا ؟ إليك
واحدا :
)][1 int main(int argc, char �argv
{ 2
3 ;)”rename(”test.txt”, ”test_rename.txt
4 ;return 0
} 5
215
الفصل .16قراءة و كتابة الملفات
: removeحذف ملف
x
كن حذرا جدا ً عند استعمالك لهذه الدالة ! هي تحذف الملف بدون أن تطلب منك أيّ تأكيد ! الملف لن يوضع
ملف محذوف بهذه الطر يقة )إلّا
في سلة المحذوفات ،بل يسحذف حرفي ّا من القرص الصلب .لن يمكنك استعادة ّ
باستعمال أدوات خاصّة باسترجاع الملفّات ،لـكنّ هذه العملية قد تكون طو يلة ،معقّدة و قد لا تنجح(.
هذه الدالة مناسبة لإنهاء الفصل ،فلم أعد في حاجة إلى الملف ، test.txtيمكنني الآن حذفه :
216
الفصل 17
ي هو السماح لبرنامج بحجز مكان لازم لتخزين جدول في الذاكرة لا يُعرف حجمه قبل بداية
من بين فوائد الحجز الح ّ
الترجمة .في الواقع ،حت ّى الآن ،كان حجم جداولنا ثابتا ًفي الشفرة المصدر ي ّة .بعد قراءة هذا الفصل ،ستستطيع إنشاء جداول
بطر يقة أكثر مرونة !
من الضروري أن ٺتقن التعامل مع المؤشرات لتتمكّن من قراءة هذا الفصل ! إن كانت لديك بعض الشكوك حول
المؤشرات ،أنصحك بالذهاب لإعادة قراءة الفصل الموافق قبل البدأ.
عندما نقوم بالتصريح عن متغي ّر ،فإننا نقول أننا طلبنا حجز مكان في الذاكرة :
عندما يصل المترجم إلى سطر مشابه للسطر السابق ،يقوم بالأمور التالية :
• يقوم البرنامج بطلب إذن من نظام التشغيل ) ( ... Mac OS ،GNU/Linux ،Windowsليحجز شيئا من الذاكرة.
• يستجيب نظام التشغيل بإعطاء البرنامج عنوان الخانة حيث يمكنه تخزين المتغي ّر )يعطيه العنوان الّذي حجزه له(.
• عندما تنتهي الدالّة ،المتغي ّر يتم حذفه من الذاكرة .برنامجك يقول لنظام التشغيل ” :أنا لم أعد بحاجة إلى المكان في
الذاكرة الّذي حجزته في ذلك العنوان ،شكرا ! التاريخ لا يحدّد إن كان البرنامج قد قال فعلا ”شكرا” لنظام التشغيل،
ن نظام التشغيل هو الّذي يتحكم في الذاكرة !
لـكنّ هذا في مصلحته لأ ّ
لحد الآن كل الأمور كانت تلقائي ّة .عندما نصرّح عن متغير فإن نظام التشغيل يتم ّ استدعاءه تلقائيا ًمن طرف البرنامج.
ما رأيك إذا بفعل هذا بطر يقة يدو ية ؟ ليس لأننا نريد أن نستمتع بفعل شيء معقّد ،بل لأننا أحيانا نضطر ّ لفعل ذلك !
217
ي للذاكرة )(Dynamic memory allocation
الفصل .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مثلا :
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حيث الذاكرة المتوف ّرة أقل .أظن مثلا في البرامج
الموجّهة للهواتف المحمولة ،الألي ّات ،إلخ.
؟
هل بإمكاننا أن نُظهر حجم نوع مخصّ ص قمنا نحن بإنشائه )هيكل( ؟
Coordinates : 8 bytes
كلما احتوى الهيكل من مركّبات كل ّما أخذ حجما أكثر من الذاكرة .الأمر منطقي تماما ،أليس كذلك ؟
لحد الآن ،كل المخططات التي قدّمتها لك عن الذاكرة لم تكن دقيقة .سنجعلها أخيرا دقيقة حقا و صحيحة بما أننا تعلّمنا
الآن كم يأخذ كل نوع من حجم بالذاكرة.
219
ي للذاكرة )(Dynamic memory allocation
الفصل .17الحجز الح ّ
و ) sizeof(intيعطينا 4بايت على حاسوبنا ،هذا يعني أن المتغير يحجز 4بايت في الذاكرة !
لنفترض أن المتغير numberمحجوز بالعنوان 1600من الذاكرة .سيكون لدينا إذا المخطط التالي للذاكرة :
هنا ،يمكننا فعلا ً أن نرى بأن المتغير numberمن النوع intيحجز 4بايت من الذاكرة .فهو يبدأ من العنوان
1600و ينتهي عند العنوان ،1603المتغير القادم لن يتم تخزينه إلا إبتداء ً من العنوان ! 1604
إن جربنا نفس الشيء مع ، charفالمتغير لن يأخذ سوى بايت واحد في الذاكرة )الشكل التالي( :
220
ي للذاكرة
.2.17الحجز الح ّ
؟
ماذا لو كان الجدول فارغاً ،هل سيحجز 400بايت ؟
نعم بالطبع ! فالمكان في الذاكرة قد تم ّ حجزه ،و لا يملك أي برنامج الحقّ في استخدام هذه الخانات )غير هذا البرنامج(.
بمجر ّد التصريح عن متغي ّر ،سيأخذ مكانه مباشرة المكان في الذاكرة.
ي للذاكرة
الحجز الح ّ 2.17
فلندخل إلى صلب الموضوع .سأذك ّرك بهدفنا :تعل ّم كيفي ّة طلب الذاكرة يدو ياً.
سنحتاج إلى تضمين المكتبة . stdlib.hإن كنت قد اتّبعت نصائحي ،فقد ضم ّنتها في ك ّ
ل برامجك .هذه المكتبة
تحتوي على دالّتين سنحتاج إليهما :
• ”Memory ALLOcation”) mallocبمعنى ”حجز الذاكرة”( :تطلب الإذن من نظام التشغيل لاستخدام الذاكرة.
• ) freeتحرير( :تسمح للإشارة لنظام التشغيل بأننا لم نعد بحاجة إلى الذاكرة ال ّتي طلبناها .المكان في الذاكرة تم ّ
تحريره ،يستطيع برنامج آخر الآن استخدامها عند الحاجة.
عندما تقوم بحجز يدوي للذاكرة ،فعليك اتباع الخطوات التالية :
.2اختبار القيمة التي تم إرجاعها من طرف mallocلمعرفة ما إن نجح نظام التشغيل في حجز الذاكرة.
.3ما إن ننتهي من استخدام الذاكرة ،يجب علينا تحريرها باستعمال . freeإن لم نفعل هذا ،فسنتعر ّض لتسريبات
ل هذا المكان.
ن البرنامج يخاطر بحجز كثير من الذاكرة مع أن ّه ليس بحاجة إلى ك ّ
ذاكرة ،أي أ ّ
هل ٺتذك ّرك هذه الخطوات الثلاث في فصل الملفات ؟ نعم يجب أن تفعل ! المبدأ واحد تماما :نحجز ،نختبر إن نجح
الحجز ،ثم ّ نحرر عندما ننتهي من الاستعمال.
221
ي للذاكرة )(Dynamic memory allocation
الفصل .17الحجز الح ّ
الدالة تأخذ معاملا واحدا :عدد البايتات ال ّتي يجب حجزها .هكذا ،يكفي كتابة ) sizeof(intلحجز مكان من أجل
تخزين . int
و لـكنّ الشيء الذي يثير الفضول ،هو القيمة التي ترجعها الدالة :إنّها تعيد ! void* ...إذا لازلت ٺتذك ّر فصل
الدوال ،كنت قد قلت لك بأن الكلمة voidتعني ”الفراغ” و نستعملها لنشير إلى أن الدالة لا تُعيد أية قيمة.
إذن هنا ،لدينا دالة تُعيد ” ...مؤش ّرا ً نحو فراغ” ؟ هذه نكتة جيدة !
حس فكاهي متطو ّر.
ّ يبدو أن هؤلاء المبرمجـين لديهم
كن متأكّدا ،يوجد سبب .في الحقيقة ،هذه الدالة تعيد عنوان الخانة التي حجزها نظام التشغيل من أجل متغي ّرك .إن
استطاع النظام إ يجاد مكان لك في العنوان ،1600فالدالة ستعيد مؤش ّرا يحوي العنوان .1600
المشكل هو أن الدالة mallocلا تعرف نوع المتغير التي نريد إنشاءه .في الواقع ،أنت لا تعطيها سوى معامل واحد :
عدد البايتات في الذاكرة ال ّتي تحتاجها .فإذا طلبت 4بايت ،فهذا يمكن أن يعني intأو ربما longمثلا !
ن mallocلا تعرف أيّ نوع يجب عليها أن تعيد ،فهي تعيد النوع * . voidسيكون مؤش ّرا نحو أيّ نوع كان.
بما أ ّ
يمكننا أن نقول أن ّه مؤش ّر جامع.
في نهاية هذه الشفرة allocatedMemory ،هو مؤش ّر يحتوي على عنوان حجزه نظام التشغيل لك ،لنقل مثلا القيمة
1600للإكمال من مخططاتي السابقة.
الدالة mallocأعادت في المتغير allocatedMemoryعنوان الخانة التي تم حجزها بالذاكرة .هناك احتمالان :
222
ي للذاكرة
.2.17الحجز الح ّ
إنه من النادر أن تفشل عملية حجز الذاكرة ،لـكن هذا ممكن .تخي ّل أنك تطلب حجز 34 Goمن الذاكرة العشوائية ،في
هذه الحالة ،ستفشل عملية الحجز على أغلب الظن.
من المستحسن دائما ًأن نختبر ما إن تمت العملية بنجاح .سنفعل هذا :إن فشل الحجز ،فهذا يعني أن المساحة الحر ّة من
الذاكرة العشوائية لم تكن كافية )هذه حالة حرجة( .في حالة كهذه ،يجب إيقاف البرنامج فورا لأن ّه ،على أية حال ،لن
يكون قادرا ً على الاستمرار بشكل عاديّ .
سنستعمل دالة قياسي ّة لم يسبق لنا رؤيتها حت ّى الآن . exit() :هذه الأخيرة توقف البرنامج فورا .إنّها تأخذ معاملا
:القيمة ال ّتي يجب إعادتها من طرف البرنامج )هذا في الحقيقة يوافق الـ returnالخاص بالـ .( main
إذا كان المؤشر مختلفا عن ، NULLيمكن للبرنامج أن يواصل العمل ،و إلا فيجب إظهار رسالة خطأ أو حت ّى إنهاء
البرنامج لأن ّه لن يتمكّن من الاستمرار بشكل صحيح إن لّم يكن هناك مكان في الذاكرة.
: freeتحرير الذاكرة
مثلما استعملنا الدالة fcloseلنغلق ملفا ًلم نعد في حاجة إليه ،سنستعمل الدالة freeمن أجل تحرير الذاكرة التي لم
نعد بحاجة إليها.
allocatedMemory الدالة freeبحاجة فقط إلى عنوان الذاكرة المراد تحريرها .سنرسل لها إذن مؤش ّرنا ،أي
في مثالنا.
إليكم المخطط الكامل و النهائي ،مشابه بشكل كبير لما رأينا في الفصل الخاص بالملفات :
223
ي للذاكرة )(Dynamic memory allocation
الفصل .17الحجز الح ّ
سنبرمج شيئا درسناه منذ زمن طو يل :الطلب من المستخدم تزويدنا بع ُمره ثم ّ عرضه له .الشيء المختلف عم ّا كنّا نفعله سابقا
هو أن المتغي ّر هنا سيتم ّ حجزه يدو يا )نقول أيضا حيو ي ّا( و ليس تلقائي ّا كالسابق .إذن نعم ،في المر ّة الأولى ،الشفرة أصعب
قليلا .لـكن قم بالمجهود اللازم لفهمها جي ّدا ،هذا ضروريّ :
!
حذار :بما أن allocatedMemoryهو مؤش ّر ،فلا نستعمله بنفس الطر يقة التي نستعمل بها متغي ّرا حقيقي ّا.
للحصول على قيمة المتغير يجب وضع نجمة أمامه ) *allocatedMemory :لاحظ .( printfبينما للحصول
على العنوان ،يكفي فقط أن كتابة اسم المؤش ّر ) allocatedMemoryلاحظ .( scanf
ل هذا تم شرحه في فصل المؤش ّرات .رغم ذلك ،أعرف أن هذا سيأخذ وقتا و من الممكن أن تخلط بينهما.
ك ّ
إن كانت هذه حالتك ،فعليك بإعادة قراءة فصل المؤش ّرات ،فهو أساسي.
لنعد إلى الشفرة .لقد قمنا بحجز حيّ لمتغي ّر من نوع . intفي النهاية ،ما كتبناه يعود تماما لاستخدام الطر يقة ”التلقائي ّة”
ال ّتي نعرفها الآن جي ّدا :
224
ي لجدول
.3.17الحجز الح ّ
كملخص ،لدينا طر يقتان لإنشاء متغير ،أي لحجز الذاكرة .إمّا أن نقوم بذلك :
• تلقائي ّا :هي الطر يقة ال ّتي تعرفها و التي استعملناها لغاية الآن.
• يدو يا )حيو ي ّا( :هي الطر يقة ال ّتي أعلّمك إ ي ّاها في هذا الفصل.
؟
أنا أجد أن الطر يقة الحي ّة معقّدة و بلا فائدة !
أكثر تعقيدا ...بالتأكيد .لـكن بدون فائدة ،لا ! أحيانا نكون مجـبرين على حجز الذاكرة يدو ي ّا كما سنرى الآن.
ي لجدول
الحجز الح ّ 3.17
متى نحتاج للحجز الحيّ ،ٺتسائلون ؟ أكثر شيء ،نستخدمه لإنشاء جدول لا نعرف حجمه قبل تشغيل البرنامج.
لنتخيل مثلا برنامجا ًيقوم بتخزين أعمار أصدقاء المستخدم في جدول ،يمكنك فعل ذلك هكذا :
;]1 int friendsAge[15
لـكن من قال أنه لديه 15صديقا ؟ ربما لديه أكثر من هذا ! عندما تكتب الشفرة المصدر ي ّة ،لا يمكنك معرفة حجم
الجدول قبل التشغيل ،عندما تطلب من المستعمل إدخال عدد الأصدقاء.
ي موجودة هنا :تطلب من المستخدم إدخال عدد الأصدقاء ،ثم تنشئ جدولا لديه تماما الحجم اللازم )لا
فائدة الحجز الح ّ
أصغر و لا أكبر( .إن كان للمستخدم 15صديقا ،فسننشئ جدولا من ، int 15إن كان لديه ،28فسننشئ جدولا
من ، int 28إلخ.
كما علّمتك ،من الممنوع في لغة الـ Cإنشاء جدول بتحديد حجمه باستخدام متغي ّر :
225
(Dynamic memory allocation) ي للذاكرة
ّ الحجز الح.17 الفصل
1 int friends[friendsNumber];
م
! من المنصوح به عدم استخدامها،هذه الشفرة قد تعمل في بعض المترجمات لـكن في حالات معي ّنة
و هذا بفضل شفرة تعمل. friendsNumber هي أن ّه يمكننا من إنشاء جدول حجمه تماما المتغي ّر،ّفائدة الحجز الحي
! ل مكان
ّ في ك
! حجمه بوافق تماما عدد الأصدقاءint هذه الشفرة تسمح بإنشاء جدول من نوع
.ل شيء
ّ نظهر أعمار الأصدقاء من محتوى الجدول لنتأكد بأننا خزّننا ك.4
. free نقوم بتحريره بالدالة، و بما أننا لسنا بحاجة إلى الجدول الذي يحوي أعمار الأصدقاء، في النهاية.5
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
هذا البرنامج عديم الفائدة :يطلب الأعمار ثم ّ يعرضها بعد ذلك .لقد اخترت فعل هذا لأن ّه مثال ”بسيط” )هذا إن
فهمت .( malloc
أؤكّد لك :في بقي ّة هذا الكتاب ستكون لنا فرص لاستخدام mallocفي أمور أكثر إفادة من تخزين أعمار الأصدقاء
!
ملخّ ص
• يمكننا معرفة عدد البايتات التي يشغلها كل نوع باستعمال العامل )(. sizeof
ي يتم باستعمال الدالة )( mallocو يجب خاصّة عدم نسيان تحرير الذاكرة باستعمال )( freeبمجر ّد
• الحجز الح ّ
الانتهاء من الاستخدام.
227
(Dynamic memory allocation) ي للذاكرة
ّ الحجز الح.17 الفصل
228
الفصل 18
أكرر دائما :التطبيق شيء ضروريّ .هو ضروريّ لك لأنك اكتشفت كثيرا من المفاهيم النظر ية و ،أي ّا كان ما تقول،
لن تفهمها حقّا بدون تطبيق.
في هذا العمل التطبيقي ،أقترح عليك إنشاء لعبة الـ .Penduو هي لعبة حروف تقليدي ّة يتم ّ فيها تخمين كلمة سر ي ّة حرفا
بحرف .الـ Penduسيكون إذن لعبة في الـكونسول بلغة .C
التعليمات 1.18
سأقوم بشرح قواعد الـ Penduالواجب إنشاءه .سأعطيك هنا التعليمات ،أي سأشرح لك بدق ّة كيف يجب أن تعمل
اللعبة التي ستُنشئها.
أعتقد أن الجميع يعرف الـ ،Penduأليس كذلك ؟ هي ّا ،تذكير صغير لا يمكن أن يحدث ضررا :هدف الـ Penduهو
ل من عشر محاولات )يمكنك تغيير العدد الأقصى لتغيير صعوبة اللعبة ،بالطبع !(.
إ يجاد الكلمة المخب ّأة في أق ّ
م
تذك ّر :هناك دالة جاهزة في string.hتقوم بالبحث عن حرف في كلمة ! و بالطبع أنت لست مجـبرا ً على
استخدامها )شخصي ّا ،أنا لم أفعل(.
229
الـPendu الفصل .18برمجة لعبة
• الحرف غير موجود في الكلمة )هذا هو الحال هنا ،لأن Aليس موجودا ً في الكلمة : (REDسنخبر اللاعب بأن
الحرف هذا غير موجود في الكلمة ،و سننقص عدد المحاولات المتبقّية .عندما لا ٺتبق أية محاولة ) 0محاولة( ،ستنتهي
اللعبة و سيخسر.
م
مره نخطئ فيها .في الـكونسول ،سيكون من
ل ّفي لعبة ” Penduحقيقة” ،يفترض وجود شخص يتأسّ ف في ك ّ
الصعب كثيرا رسم شخص يتأسّ ف بواسطة لاشيء غير النص ،لذا سنكتفي بعرض جملة بسيطة مثل ”بقي لك X
فلنفرض الآن أن اللاعب أدخل الحرف .Dهذا الحرف موجود في الكلمة المخفي ّة ،لهذا لن نقوم بإنقاص عدد المحاولات
المتبقّية للاعب .سنقوم بإظهار الكلمة مع الحروف ال ّتي تم إ يجادها ،أي شيء كهذا :
إذا أدخل اللاعب فيما بعد الحرف ،Rو بما أن ّه موجود في الكلمة ،سنضيف الحرف إلى قائمة الحروف التي تم إ يجادها
و يتم إظهار الكلمة مع الحروف ال ّتي تم ّ اكتشافها :
في بعض الكلمات ،يمكن أن نجد حرفا ًمكررا ً مرتين أو ثلاث ،أو ربّما أكثر !
مثلا :يوجد إثنان من Zفي كلمة ،PUZZLEو كذلك يوجد ثلاثة Eفي كلمة .ELEMENT
يعني أنه ليس على اللاعب أن يدخل 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التعليمات
و تماما ،هذا جي ّد %c .تعني أننا ننتظر محرفاً ،و الذي سنقوم بتخزينه في ) myLetterمتغي ّر من نوع .( char
؟
ما الذي حصل ؟
Enter في الواقع ،حينما تدخل نصا ًفي الـكونسول ،فإن كل ما قمت بإدخاله يتم ّ تخزينه في الذاكرة ،بما في ذلك الزر
) .( \n
مرة تدخل فيها حرفا ) Aمثلاً( ثم ّ تضغط على Enterفإن الحرف Aهو من يتم إعادته من طرف لذلك ،في أوّل ّ
. scanfبينما في المر ّة الثانية scanf ،سيعيد \nالموافق لـ Enterالّذي أدخلته سابقا !
لتجنب هذا ،من الأحسن أن نكتب بأنفسنا دالتنا الخاصّة الصغيرة )(: readCharacter
231
الـPendu الفصل .18برمجة لعبة
هذه الدالة تستخدم )( getcharال ّتي هي دالة من stdio.hو هذا يعود تماما ًإلى كتابة
;) . scanf(”%c”, &letterالدالة )( getcharتقوم بإرجاع المحرف الذي قام اللاعب بإدخاله.
بعد ذلك ،أستعمل أيضا ً الدالة القياسي ّة التي لم تسنح لنا فرصة تعلّمها في كتابنا . toupper() :هذه الدالّة تحو ّل
الحرف المعطى إلى كبير ) .(Uppercaseهك ّذا ،اللعبة ستعمل حتى إن أدخل اللاعب حروفا ً صغيرة .يجب تضمين
ctype.hلتستطيع استخدام هذه الدالة )لا تنس ذلك !(.
تأتي بعد ذلك المرحلة الأكثر أهمية :و هي أن نقوم بمسح المحارف التي يمكن أن نكون قد أدخلناها .في الواقع ،بإعادة
استدعاء getcharنحصل على المحرف الثاني الّذي تم ّ إدخاله )مثلا .( \n
ما أقوم به بسيط و يأخذ سطرا واحدا :أستدعي الدالة getcharفي حلقة تكرار ية حتى الوصول إلى . \nٺتوقف
ل المحارف الأخرى ،سيتم ّ إذن إفراغها من الذاكرة .نقول أنّنا نفرغ المتغير المؤقت
الحلقة إذن ،و هذا يعني أننا ”قرأنا” ك ّ
).(Buffer
؟
لماذا توجد فاصلة منقوطة في نهاية الـ whileو لماذا لا نرى أية حاضنة ؟
في الواقع ،استعملت حلقة تكرار ية لا تحتوي على تعليمات )التعليمة الوحيدة ،هي getcharداخل القوسين(.
الحاضنتان ليستا ضرور يّتين نظرا لأنه ليس لدينا ما نفعله غير . getcharلهذا أضع فاصلة منقوطة لتعو يض الحاضنتين.
ل دورة للحلقة” .هذا أمر غريب قليلا ،لـكنها تقني ّة يجب معرفتها ،تقني ّةهذه الفاصلة المنقوطة تعني ”لا تفعل شيئا ً في ك ّ
يستعملها المبرمجون لإنشاء حلقات بسيطة و قصيرة.
لا يوجد شيء داخل الحاضنتين ،إنّها اختيار ي ّة ،نظرا لأن ّه ليس هناك شيء آخر لفعله .تقني ّتي ال ّتي تقتضي وضع فاصلة
منقوطة فقط أبسط من تلك الخاصّة بالحاضنتين.
أخيرا ،تقوم الدالة readCharacterبإرجاع المحرف الأوّل الذي قمنا بقراءته :المتغي ّر . character
232
.1.18التعليمات
قاموس الكلمات
لتجربة أولية للشفرة الخاصة بك ،أطلب منك أن تقوم بتثبيت الكلمة السر ي ّة مباشرة في الشفرة .أكتب مثلا :
طبعا ستبقى الكلمة السر ي ّة نفسها دائما إن تركناها هكذا ،هذا ليس ممتعا .لـكني طلبت منك فعل ذلك لـكي لا تخلط
المشاكل .في الواقع ،عندما تعمل لعبة Penduجي ّدا )و فقط ابتداء من هذه اللحظة( ،يمكنك البدء بالطور الثاني :إنشاء
قاموس الكلمات.
؟
ما هو هذا ”قاموس الكلمات” ؟
هو ملف يحتوي كثيرا من الكلمات للعبتك .Penduيجب أن تكون كل كلمة على سطر .مثلا :
HOUSE
BLUE
AIRPLANE
XYLOPHONE
BEE
BUILDING
WEIGHT
SNOW
ZERO
في كل جولة جديدة ،يجب على برنامجك أن يفتح الملف ،و يأخذ كلمة عشوائية من القائمة .بفضل هذه الطر يقة،
سيكون لديك ملف يمكنك التعديل عليه كل ّما أردت من أجل إضافة كلمات سر ي ّة ممكنة من أجل .Pendu
م
ل الكلمات بالحروف الـكبيرة .في الواقع ،في الـ Penduلا يتم التمييز بين
ستلاحظ أنني منذ البداية تعمّدت كتابة ك ّ
الحروف الـكبيرة و الحروف الصغيرة ،و لهذا فمن المستحسن أن نقول منذ البداية ” :كل حروف كلمات اللعبة
كبيرة” .عليك أن تنب ّه اللاعب ،في دليل استخدام اللعبة مثلا ،أنه يفترض به إدخال حروف كبيرة لا صغيرة.
بالمقابل ،نتعمّد تجنب العلامات الصوتية ) (accentsلتبسيط اللعبة )إن بدأنا اختبار ... ë ،ê ،è ،éفلن ننتهي
أبدا ً !( .عليك إذن أن تكتب كلماتك كل ّها بحروف كبيرة و بدون علامات صوتي ّة.
233
الـPendu الفصل .18برمجة لعبة
المشكل الذي سيحدث لك سر يعا هو أنه عليك معرفة عدد الكلمات الموجودة في القاموس .في الواقع ،إن أردت
إختيار كلمة عشوائية ،يجب أن يتم أخذ عدد بين 0و ،Xو أنت لا تعرف في بادئ الأمر كم من الكلمات يحتوي الملف.
3
HOUSE
BLUE
AIRPLANE
مرة أولى لع ّد ، \nفعليك القيام بـ rewindللعودة إلى البداية .لن يكون عليك إذن سوى الملف في ّ
ّ حينما تقرأ
أخذ عدد عشوائيّ بين عدد الكلمات ال ّتي عددتها ،ثم ّ عليك تخزين هذه الكلمة في سلسلة محرفي ّة في الذاكرة.
ل المعارف ال ّتي
ل هذا ،لن أساعدك أكثر ،و إلّا فلن يكون عملا تطبيقيا ! و اعلم بأن ك ّ
سأتركك قليلا لتفك ّر في ك ّ
ل
تحتاجها موجودة في الفصول السابقة ،فأنت قادر تماما على إنشاء هذه اللعبة .إنه يتطل ّب منك بعض الوقت و هو أق ّ
سهولة مم ّا يبدو عليه ،و لـكن إذا نظّمت الأمور جي ّدا )بإنشاء قدر كاف من الدوال( سوف تصل.
بالتوفيق !
بقراءتك لهذه السطور ،يعني أنك قد أكملت البرنامج ،أو أنك لم تستطع إكماله.
لقد استغرقت شخصي ّا وقتا أكبر مم ّا كنت أعتقد في إنشاء هذه اللعبة البسيطة للغاية .هكذا دائما :نقول ”هذا بسيط”،
لـكن في الحقيقة توجد الـكثير من الحالات لدراستها.
رغم ذلك أصرّ على القول بأنك قادر على فعل هذا .يلزمك فقط بعض الوقت )بضع دقائق ،بضع ساعات بضع أيام
؟( ،لـكن ّنا لم نكن أبدا في سباق .أنا أفضّ ل أن تأخذ كثيرا من الوقت للوصول إلى الحل على ألّا تجر ّب سوى 5دقائق و
ترى التصحيح.
لا تعتقد أن ّي كتبت البرنامج من المحاولة الأولى .أنا أيضا ،كنت أعمل خطوة بخطوة .بدأت بشيء بسيط جدّا ،ثم ّ شيئا
فشيئا حسّنت الشفرة للوصول إلى النتيجة النهائي ّة.
قمت بعدّة أخطاء أثناء كتابة الشفرة :نسيت في لحظة ما تهيئة متغير بشكل صحيح ،نسيت كتابة نموذج دالة و كذلك حذف
متغير لم يعد مفيدا في شفرتي .و حتى أن ّي -أعترف -نسيت فاصلة منقوطة سخيفة في لحظة ما عند نهاية تعليمة.
لماذا أقول كل هذا ؟ لـكي أخبرك أن ّني لست معصوما من الأخطاء و أن ّي أواجه تقريبا نفس المشاكل مثلك )”أيّها
البرنامج البائس ،هل ستعمل أم لا ؟!”(.
234
.2.18التصحيح ) : 1شفرة اللعبة(
ل على جزئين.
سأعرض عليك الح ّ
YELLOW • أوّلا سأر يك كيف أنشأت شفرة اللعبة نفسها ،بتثبيت الكلمة المخفي ّة مباشرة في الشفرة .اخترت الكلمة
لأنّها تسمح باختبار ما إن كنت تعاملت جي ّدا مع المحارف المتكر ّرة.
• بعد ذلك ،سأر يك كيف أضفت العمل بقاموس الكلمات لإعطاء كلمة سرّية عشوائي ّة لللاعب.
ل شيء يبدأ بـ . mainبجب ألا ننسى تضمين المكتبات stdlib ، stdioو ) ctypeمن أجل
مثلما يعلم الجميع ،ك ّ
الدالة ( toupperال ّتي سنحتاج إليها أيضا :
235
الـPendu الفصل .18برمجة لعبة
ل متغير على سطر و وضعت كثيرا من التعليقات لشرح دور كل متغير .عملي ّا،
لقد كتبت بمحض إرادتي تصريح ك ّ
ل هذه التعليقات كما يمكنك وضع الـكثير من التصر يحات في نفس السطر.
لست مضطر ّا إلى وضع ك ّ
ل مرة،
أعتقد أن أغلب المتغيرات تبدوا منطقية :المتغير letterيخز ّن الحرف الذي يدخله المستخدم في ك ّ
i secretWordيحوي الكلمة الواجب اكتشافها remainingTries .يحتوي عدد المحاولات المتبقّية ،إلخ .المتغي ّر
هو متغير صغير استعمله كي أتصفّح الجدول مستعملا الحلقة . forفهو ليس مهمّا جدّا لـكن ّه ضروريّ إذا أردنا القيام
بحلقات.
و أخيرا ً المتغير الّذي يجب التفكير فيه ،و الذي سي ُمث ّل الفرق ،إن ّه عبارة عن جدول من القيم المنطقية . foundLetter
ل خانة
ستلاحظ بأن ّي جعلت حجم الجدول يساوي عدد حروف الكلمة السر ي ّة ) .(6هذا ليس أمرا ً عشوائيا ً :إذ أن ك ّ
من جدول القيم المنطقية تمث ّل حرفا ًمن الكلمة السر ية .هكذا ،الخانة الأولى تمث ّل الحرف الأوّل ،الثانية الحرف الثاني ،إلخ.
ل خانات الجدول مهي ّئة في البداية على ،0و التي تعني ”الحرف لم يتم إ يجاده بعد” .بتقدّم اللعبة ،الجدول سيتم ّ تعديله.
ك ّ
ل حرف تم ّ إ يجاده من الكلمة ،الخانة التي توافقها من foundLetterستأخذ .1
من أجل ك ّ
مثلا ،إذا كان في مرحلة من الجولة ،لدينا العرض ” ،”Y*LL*Wفإن جدول الـ intسيحوي القيم 1) 101101 :
لكل حرف تم ّ إ يجاده(.
هذه الطر يقة تسهّل علينا معرفة متى يربح اللاعب :يكفي التحقّق من أن جميع خانات الجدول لا تحوي سوى .1
في الحالة الأخرى ،سيخسر اللاعب إذا وصل العدّاد remainingTriesإلى .0
هذه رسالة ترحيب ،لا يوجد أي شيء مثير فيها .بالمقابل ،الحلقة الرئيسي ّة هي الأكثر أهمي ّة :
اللعبة تستمر ّ مادام قد بقي بعض المحاولات ) ( remainingTries > 0و اللاعب لم يربح.
إذا لم تبق له أية محاولة ،فهذا يعني أنه فشل .إن ربح ،فهذا يعني ...أن ّه ربح .في كلتا الحالتين ،يجب إيقاف اللعبة ،أي
ل مرة.
إيقاف الحلقة التي تطلب قراءة حرف في ك ّ
winهي دالة تقوم بتحليل الجدول . foundLetterتقوم بإعادة ”صحيح” ) (1إذا كان اللاعب قد ربح )أي أن
الجدول foundLetterلا يحمل سوى ” ،(1خطأ” ) (0إن كان لم يربح بعد .لن أشرح لك الآن عمل الدالة بشكل
مفصل ،سنرى ذلك لاحقاً .حالي ّا ،يجب عليك فقط معرفة ما تفعله.
236
.2.18التصحيح ) : 1شفرة اللعبة(
نقوم في كل مرة بإظهار عدد المحاولات المتبقّية و كذا الكلمة السر ي ّة )مخفي ّة بـ* بالنسبة للحروف التي لم يتم ّ إ يجادها(.
ل حرف لنرى إن تم ّإ يجاده ) )].( if(foundLetter[i
يتم إظهار الكلمة السر ي ّة المخفي ّة بـ* بفضل حلقة forحيث أننا نحل ّل ك ّ
إن كان الشرط محققاً ،سنظهر الحرف ،و إلا سنظهر * لإخفاءه.
الآن بعدما أظهرنا ما يجب ،سنطلب من اللاعب أن يدخل حرفا ًجديدا ً :
أستدعي دالتنا )( . readCharacterهذه الدالة تقرأ الحرف الأول الذي تم ّ إدخاله ،تجعله كبيرا ً ثم تفر ّغ المتغير
المؤق ّت ،أي أنّها تمسح بقي ّة الحروف التي يمكن أن تبقى في الذاكرة.
نختبر ما إن كان الحرف الذي تم ّ إدخاله موجودا في . secretWordنستدعي لأجل هذا دالّة أنشأناها تسمّى
. findLetterسنرى بعد قليل شفرة هذه الدالّة.
ن هذه الدالّة تعيد ”صحيح” إن كان الحرف موجودا في الكلمة” ،خطأ” إن لم تجده.
ل ما يجب أن تعرفه ،هو أ ّ
حالي ّا ،ك ّ
كما تلاحظ فالـ ifيبدأ بعلامة تعجّ ب ! و التي تعني ”لا” .الشرط يُقرأ إذن بهذه الطر يقة ” :إذا لم يتم إ يجاد
الحرف”.
ماذا نفعل في حالة عدم إ يجاد الحرف ؟ نقوم بتقليل عدد المحاولات المتبقية.
م
لاحظ أيضا ًأن الدالة findLetterتقوم بتحديث قيم الجدول . foundLetterتقوم بوضع 1في الخانات
الموافقة للحروف التي تم ّ إ يجادها.
الحلقة الرئيسية في اللعبة ٺتوقف هنا .لهذا فسنعيد من بداية الحلقة و نختبر ما إن كان قد بقي شيء من المحاولات للعب
و اللاعب لم بربح بعد.
عند الخروج من الحلقة الرئيسي ّة ،لا يبقى سوى إظهار إن كان اللاعب قد نجح في اللعبة أو خسر قبل إنهاء البرنامج :
237
الـPendu الفصل .18برمجة لعبة
سنستدعي الدالة winلنرى ما إن كان اللاعب قد ربح .إن كانت هذه هي الحالة ،نقوم بإظهار الرسالة ”ربح !” ،و
إلّا ،فقد انتهت فرص اللعب ،فقد خسر.
هذه الدالة تأخذ جدول القيم المنطقي ّة foundLetterكمعامل .تعيد قيمة منطقية ” :صحيح” إذا ربح اللاعب و
”خطأ” إذا خس ِر.
الشفرة الخاصة بهذه الدالة بسيطة ،بفترض بك فهمها .نتصفّح foundLetterو نختبر ما إن كانت إحدى خانات
الجدول تحوي ”خطأ” ) .(0إن كان هناك حرف واحد لم يتم ّ إ يجاده فلقد خسر اللاعب :سيتم وضع ”خطأ” ) (0في
المتغير المنطقي . playerWinsو إلّا ،إن تم ّ إ يجاد كل الحروف ،فالمتغي ّر المنطقي سيكون ”صحيحا” ) (1و الدالة تعيد
”صحيح”.
• إرجاع متغير منطقي يشير ما إن كان الحرف موجودا ً في الكلمة السر ية.
• تحديث )على (1خانات الجدول foundLetterفي المواضع الموافقة للحرف الذي تم ّ إ يجاده.
238
.3.18التصحيح ) : 2استعمال قاموس الكلمات(
• تعديل المتغير المنطقي ، rightLetterإلى ،1لـكي تعيد الدالّة 1لأن الحرف متواجد بالفعل في . secretWord
ل الجدول )لا نتوقف عند أول حرف تم إ يجاده( .هذا سيسمح لنا
الشيء الجي ّد في هذه الطر يقة ،هو أننا سنتصفّح ك ّ
بتحديث الجدول foundLetterبشكل صحيح ،في الحالة التي تحتوي فيها الكلمة حرفا ًمكررا ً عدّة ّ
مرات ،مثل حالة Lفي
.YELLOW
قبل الذهاب بعيدا ،الشيء الأوّل الواجب فعله هو إنشاء قاموس الكلمات .و حتى إن كان قصيرا ً فهذا ليس سي ّئا،
سيكون مناسبا للاختبارات.
سأقوم إذن بإنشاء ملف 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في نهاية الدالّة.
findWord الدالة
هذه الدالة تأخذ معاملا واحدا :مؤشرا ً نحو الذاكرة حيث يمكن كتابة الكلمة .هذا المؤش ّر يتم تزويدنا به عن طر يق . main
ل شيء على مايرام = 0 ،كان هناك خطأ ما.
الدالة ستعيد intو سيكون قيمة منطقية = 1 :تم ّ ك ّ
أعرف بعض المتغيرات ال ّتي ستكون ضرور ية لي .مثل الـ ، 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و ) 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
لم يتبقّ سوى غلق الملف ،و إعادة القيمة 1لتتوقف الدالة و تشير إلى أن كل شيء على ما ي ُرام :
1 ;)fclose(dico
2 return 1; // Everything is okay, return 1
} 3
242
( استعمال قاموس الكلمات: 2) التصحيح.3.18
aleatoryNumber الدالة
أما السطر.” مثلما تعلّمنا فعل ذلك في العمل التطبيقي الأوّل ”أكثر أو أقل،السطر الأوّل يهي ّئ مولّد القيم العشوائية
،ل ذلك في سطر واحد
ّ يمكنك ملاحظة أنني قمت بك. و يعيده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 الملف
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 الفصل
. كي نقوم بتحديثها على حسب التغييرات التي قمنا بإجرائها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حروف(.
لـكن حالي ّا ،بما أن الكلمة ستتغير ،فيجب على البرنامج أن يتلائم مع كل الكلمات.
نحن نستدعي أولا الدالة ، findWordو ذلك يتم مباشرة داخل الشرط . ifالدالة findWordستقوم بوضع
الكلمة التي اختارتها من القاموس في المتغير . secretWordكما أنها ستقوم بإرجاع متغير منطقي لنا لتخبرنا ما إن كانت
العملية ناجحة أم لا ،أي أننا نقرأ الشرط كالتالي :إذا لم يعمل الأمر فسنوقف البرنامج ) ).( exit(0
;)1 wordSize = strlen(secretWord
و الآن سنحجز مكانا ًفي الذاكرة للجدول . foundLetterسنقدّم له حجم الكلمة . wordSizeسنختبر بعد ذلك
ما إن كان المؤش ّر يساوي . NULLإذا كان كذلك ،فالحجز قد فشل .في هذه الحالة سنوقف البرنامج حالا )باستعمال
.( exit
هذه هي أهم التعديلات على الـ ، mainيبقّى أن تقوم باستبدال كل تكرار للرقم 6بالمتغير . wordSizeمثال :
245
Penduالـ برمجة لعبة.18 الفصل
فبدون هذا لا يمكن للدالة معرفة متى توقف. wordSize لأضيف المتغيرwin كان يفترض أن أضع نموذج الدالة
.الحلقة التكرار ية
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
247
الـPendu الفصل .18برمجة لعبة
أفكار للتحسين
تنز يل المشروع
إذا كنت تعمل على الماك أو اللينكس ،قم بحذف الملف 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سهلة الاستعمال و في نفس الوقت أكثر تعقيدا ً مما نتصور ،سأر يك
الحدود التي يمكن لهذه الدالة أن تصل إليها و ذلك بتقديم مثالين واقعيين.
لنفرض أننا طلبنا من المستعمل أن يقوم بإدخال سلسلة محارف في الـكونسول ،و هو يقوم بكتابة فراغ في سلسلته :
251
نص بشكل أكثر أمانا
الفصل .19إدخال ّ
؟
لماذا اختفت الكلمة ” ”Nebra؟
ذلك لأن الدالة scanfٺتوقف عن القراءة حينما تصل إلى فراغ ،أو رجوع إلى السطر أو محرف جدولة
) .(tabulationيعني أنك غير قادر على قراءة سلسلة محرفي ّة تحتوي على فراغات.
م
في الواقع ،الكلمة ” ”Nebraلازالت مخزّنة في الذاكرة ،في شيء نسميه بالمتغير المؤق ّت ) ،(bufferالمرة القادمة عندما
نستدعي الدالة scanfفهي ستقوم بقراءة الكلمة ” ”Nebraوحدها الموجودة في المتغير المؤقت.
يمكننا استعمال الدالة scanfبشكل يسمح لها بقراءة الفراغات ،لـكن الأمر معقّد جدّا .لمن يصرّ على ذلك ،يمكنك
إ يجاد دروس مفصّ لة على الويب ،مثل الدرس الأجنبي المتوف ّر على هذا الرابط :
http://xrenault.developpez.com/tutoriels/c/scanf/
ترى أنني قمت بحجز 5خانات من أجل الجدول المسمّى nameالذي هو من نوع . charيعني أننا قادرون على
تخزين كلمة من 4محارف ،بينما الحرف الأخير فهو محجوز لعلامة نهاية السلسلة . \0
ل هذا فراجع فصل السلاسل المحرفية.
إذا نسيت ك ّ
عرفناها :
المخطط التالي يمثل المكان الذي هو محجوز للكلمة التي ّ
252
scanf .1.19حدود الدالة
ماذا لو كتبنا عددا كبيرا من المحارف بالنسبة للمساحة المتوق ّعة لتخزين المتغير ؟
ستقول أن كل شيء على ما يرام لـكن الواقع أنك بصدد مواجهة أكبر كابوس لدى المبرمجـين !
لقد قمنا بـتجاوز في الذاكرة ،هذا ما نسميه بـ 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محرف و سيعمل هجومه كما يريد.
الشيء المحزن هو أن معظم المبرمجـين لا ينتبهون دائما لهذه الأخطاء ،و لو أنهم قاموا بكتابة الشفرة من المرة الأولى بشكل
نظيف و صحيح ،لما ظهرت كثير من الثغرات التي نتحدّث عنها اليوم.
توجد العديد من الدوال القياسي ّة في لغة Cالتي تسمح باسترجاع سلسلة نصّ ي ّة .إضافة إلى الدالة ، scanfو التي من
الصعب دراستها هنا ،لدينا :
• : getsدالة تقرأ سلسلة محرفي ّة كاملة لـكنها خطيرة جدّا لأنها لا تعالج مشكل الـ.buffer overflow
• : fgetsتشبه الدالة getsلـكنها تحمي البرنامج و ذلك بالتحكم في عدد المحارف المكتوبة في الذاكرة.
أعتقد أن الأمر مفهوم :على الرغم من أنها دالّة قياسي ّة في الـ gets ،Cهي دالّة خطيرة جدّا .كل البرامج ال ّتي
تستخدمها عرضة لأن يكونوا ضحايا الـ.buffer overflow
سنرى كيف تعمل الدالة ، fgetsو كيف نستعملها في برامجنا الخاصّة في مكان الدالة . scanf
fgets الدالّة
• : strمؤش ّر نحو جدول في الذاكرة ،أين ستتمكن الدالة من كتابة النص المدخل من طرف المستخدم.
لاحظ أنه لو قمت بحجز جدول من ، charفإن الدالة fgetsستقرأ 9محارف على الأكثر )آخر خانة محجوزة
للمحرف \0المشير إلى نهاية السلسلة(.
)Standard • : streamمؤش ّر نحو الملف الذي سنقرأ منه .في حالتنا ”الملف المراد قراءته” هو الإدخال القياسي
،(inputأي لوحة المفاتيح .لطلب قراءة الإدخال القياسي نرسل المؤش ّر stdinالمعر ّف تلقائي ّا في الملفات الرأسي ّة
للمكتبة القياسي ّة للـ Cليشير إلى لوحة المفاتيح .مع ذلك ،يمكن استخدام fgetsلقراءة الملفّات ،كما رأينا في
الفصل الخاص بالملفّات.
254
.2.19استرجاع سلسلة محارف
الدالة ستقوم بإرجاع نفس المؤش ّر strللإشارة إلى إن كانت القراءة قد تمت بشكل صحيح أم لا .يكفي إذا أن
نختبر ما إن كانت قيمة هذا المؤش ّر تساوي ، NULLفإن كانت كذلك ،فهناك خطأ.
فلنجر ّب !
\n الدالة تعمل بشكل جيد ،مع تفصيل بسيط :عندما تضغط على زر الإدخال ،تقوم fgetsبالاحتفاظ بـ
الموافق ،هذا ما يفس ّر الرجوع إلى السطر بعد الكلمة ” ”NEBRAكما يظهر في الـكونسول.
لا يمكننا أن نمنع هذه الدالة من كتابة المحرف \nلأن الدالة تعمل هكذا .بالمقابل ،هذا لا يمنع كتابتنا لدالة خاصّة
بالإدخال تقوم نفسها باستدعاء fgetsو حذف ذلك المحرف !
مرة.
ل ّليس صعبا ًجدّا أن نقوم بكتابة دالة خاصة بك تقوم ببعض التصحيحات من أجلك في ك ّ
سنسمّي هذه الدالّة . readستقوم بإرجاع القيمة 0إن كان هناك خطأ و 1إن لم يكن.
255
نص بشكل أكثر أمانا
الفصل .19إدخال ّ
ستلاحظ أنه بالإمكان استدعاء الدالة fgetsمباشرة داخل . ifهذا اختصار كتابي ،كي لا أستعمل مؤش ّرا
يستقبل القيمة المُرجعة من الدالة ثم أختبر قيمته إن كانت NULLأم لا.
انطلاقا من ifالأول ،أعرف هل fgetsعملت على ما يرام أم حدث مشكل ما )قام المستخدم بادخال محارف
أكبر من العدد المسموح به(.
إذا تم كل شيء بشكل جيد ،سأذهب للبحث عن الـ \nباستعمال الدالة strchrثم استبداله بالمحرف \0كما
في الشكل التالي.
هذا المخطط يبېّن أن السلسلة ال ّتي تمت قراءتها من طرف الدالة fgetsهي ” ،” NEBRA\n\0ثم قمنا باستبدال
الـ \nبـ \0و هذا ما أعطانا ” .” NEBRA\0\0
ليس مشكلا أن توجد علامتا \0متتابعتين .الحاسوب سيتوقف عند الإشارة الأولى و يعتبرها نهاية السلسلة.
256
.2.19استرجاع سلسلة محارف
بما أن الدالة fgetsتدعم الحماية ،فهي توقفت عند الحرف التاسع الذي قام المستعمل بإدخاله لأنّنا حجزنا جدولا
ن العاشر محجوز لإشارة نهاية السلسلة( .المشكل هو أن بقي ّة السلسلة ال ّتي لم تتم قراءتها.
من 10محارف )يجب عدم نسيان أ ّ
” ”ard Albert 1erلم تختف ! و إنّما لازالت موجودة في المتغير المؤق ّت .هذا المتغير المؤقت هو مكان في الذاكرة يعمل
stdin كوسيط بين لوحة المفاتيح و الجدول الّذي سيتم تخزين السلسلة فيه .في الـ ،Cلدينا مؤش ّر نحو المتغير المؤقت ،و هو
الذي تكلمنا عنه قبل قليل.
حينما يقوم المستعمل بإدخال نص بلوحة المفاتيح ،فإن نظام التشغيل ) Windowsمثلا( يقوم بنسخ النص مباشرة
في المتغير المؤق ّت . stdin
مهمة الدالة fgetsهي إحضار المحارف الموجودة في المتغير المؤقت ووضعها في الذاكرة التي قمت أنت بتحديدها
)الجدول .( string
بعد القيام بعملها ،تمسح ما قامت بنسخه من المتغير المؤق ّت.
257
نص بشكل أكثر أمانا
الفصل .19إدخال ّ
إذا عمل كل شيء على ما يرام ،فالدالة fgetsستقوم بإفراغ كل محتوى المتغير المؤق ّت ،أي أن هذا الأخير سيكون
فارغا ًبعد استدعاء الدالة .لـكن في الحالة التي يقوم المستخدم بإدخال كثير من المحارف لا يمكن أن يسعها المكان المحجوز
لها ،فإنه يتم مسح الحروف التي تمت قراءتها فقط ،و بالتالي بعد استدعاء الدالة fgetsفإن المتغير المؤق ّت يحتوي دائما
المحارف المتبقّية !
الدالة fgetsقامت بنسخ المحارف التسعة الأولى كما كان متوق ّعا .المشكل هو أن المحارف المتبقية لازالت في المتغير
المؤقت !
هذا يعني أنه لو استدعينا الدالة fgetsمرة أخرى فإنها ستقوم بقراءة ما كان متبقّيا في المتغير المؤقت !
258
.2.19استرجاع سلسلة محارف
مرتين لـكنك ستلاحظ أن ّه لن يتم السماح لك بإدخال اسمك مرتين ،وذلك لأن نحن نقوم باستدعاء الدالة ّ read
الدالة fgetsلن تطلب من المستخدم إدخال أيّ ّ
نص في المر ّة الثانية لأنّها ستجده في المتغير المؤقت !
إذا قام المستعمل بإدخال محارف كثيرة ،فإن الدالة fgetsستحمي البرنامج من مشكل تجاوز الذاكرة ،لـكن يبقى
النص في المتغير المؤقت .لذا يجب تفر يغ هذا الأخير.
ّ دائما آثار
سنقوم إذا بتحسين عمل الدالة ، readو سنقوم في الحالات التي ٺتطلب ذلك باستدعاء دالة نسميها ، clearBuffer
لـكي نتأكد من تفر يغ المتغير المؤقت في حال ما احتوى على محارف زائدة :
259
نص بشكل أكثر أمانا
الفصل .19إدخال ّ
29 }
} 30
• السلسلة المدخلة طو يلة جدا ً )يمكننا أن نعرف ذلك بعدم وجود الإشارة \0في السلسلة المنسوخة(.
• إذا حدث أي خطأ مهما كان ،يجب تفر يغ محتوى المتغير المؤقت لأسباب حماية لـكي لا يبقى شيء هناك.
الدالة clearBufferقصيرة لـكنها عميقة .فهي تقرأ المتغير المؤقت محرفا ً محرفا ًباستعمال الدالة . getcharهذه
الدالة تقوم بإرجاع ) intو ليس ، charسوف تعرف السبب لاحقا ،أي ّا يكن(.
سنكتفي نحن باسترجاع القيمة في متغير cمن نوع . intنقوم بحلقة تكرار ية مادمنا لم نقرأ بعد المحرف \0أو الرمز
) EOFنهاية الملف( و هما يعنيان :لقد وصلت إلى نهاية المتغير المؤقت .سنتوق ّف عند الوصول إلى أحد هذين المحرفين.
يبدو عمل الدالة clearBufferصعبا ً قليلا ً لـكنها تقوم بعملها .لا تتردد في تكرار قراءة الشرح عدة مرات من
أجل الفهم الجيد.
دالتنا readهي فعالة و قو ي ّة الآن ،لـكنها تجيد قراءة النصوص فقط .ستتساءل حتما ” :لـكن كيف نقوم باسترجاع
عدد ؟”
الحقيقة أن الدالة fgetsهي دالة مبدئية .مع fgetsلا يمكن قراءة سوى النصوص ،و لـكن توجد دوال أخرى
تقوم بتحو يل النص إلى عدد.
الدالة ستقوم بقراءة السلسلة المحرفي ّة المرسلة إليها ) ( 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 }
261
نص بشكل أكثر أمانا
الفصل .19إدخال ّ
الدالة strtodمطابقة للدالة ، strtolالفرق الوحيد هو أنها ستحاول قراءة عدد عشريّ و إرجاع . double
تجد أن المعامل الثالث baseاختفى هنا ،بينما يبقى مؤش ّر المؤش ّر endالّذي لا يفيدنا في شيء.
على خلاف الدالة السابقة ،فإن هذه الدالة ستأخذ في الحسبان ”النقطة” العشر ية .عندما أقول نقطة يعني أن الدالة لا
تقبل الفاصلة ”) ”,يبدو أنّها مبرمجة من طرف ناطقين بالإنجليز ية(.
فلتقم بكتابة الدالة readDoubleبنفسك .إن كتابتها مماثلة للدالة ، readLongالاختلاف الوحيد هو أنها ستستدعي
الدالة strtodثم ستقوم بإرجاع قيمة . double
حاول بعد ذلك تعديل الدالة readDoubleلتقبل الفاصلة أيضا ً كفاصل عشري .إن الأمر بسيط :فقط قم
باستبدال كل تكرار للمحرف ” ” ,بالمحرف ” ) ” .بالاستعانة بالدالة ،( strchrثم قم ببعث النص الجديد إلى الدالة
. strtod
ملخّ ص
• الدالة scanfبالرغم من أنها تبدو سهلة و طبيعية إلا أنها معقّدة و تفرض علينا بعض الحدود .فمثلاً ،هي لا تقبل
قراءة نص يحتوي فراغات.
• نقول أننا تسببنا في الـ buffer overflowإذا تجاوزنا المساحة المخصصة في الذاكرة ،فمثلا ًلو قام المستخدم بإدخال 10
محارف و نحن قد حجزنا 5خانات فقط.
• الحل الأمثل هو استدعاء الدالة fgetsلتقوم باسترجاع النص الذي يُدخله المستعمل.
• يجب أن تتجنب استعمال الدالة getsبأي ثمن لأنّها لا تحمي من الـ.buffer overflow
• يمكنك كتابة دالة خاصة بك ،تقوم باستدعاء الدالة fgetsكما فعلنا لـكيّ تحسّن عملها.
262
الجزء ج
263
الفصل 20
الـSDL ٺثبيت
ابتداء ً من الآن ،إنتهت الدروس النظر ية ! لأننا سنمر ّ إلى مرحلة مهمّة ،و سنستمتع بالتطبيق بالاستعانة بمكتبة نسميها
.SDL
ل أساسيات اللغة ،Cلـكن تبقى هناك دائما ًبعض التفاصيل الصعبة نوعا ًما
في الفصول السابقة كنا قد تطر ّقنا تقريبا ًلك ّ
لنكتشفها .سأقول لك بأنه يُمكن لهذا الكتاب أن يتوق ّف هنا مخـبرا إ ي ّاك ” :نعم لقد تعلّمت البرمجة بلغة ،”Cلـكني متأكّد
سيحس نفسه دائما ًمبتدئا ًمادام لم ”يخرج” من الـكونسول !
ّ بأن الجميع سيشاركني الرأي لو قلت بأن المُبرمج
الـ SDLهي مكتبة تُستخدم خاصّة لإنشاء ألعاب ثنائية الأبعاد .سنتعر ّف في هذا الفصل على هذه المكتبة و نتعل ّم كيف
نقوم بتثبيتها.
نسمي هذا النوع من المكتبات بمكتبات الطرف الثالث ) .(Third party librariesيجب أن تعرف أنه يوجد نوعان من
المكتبات :
خلال الجزئين الأوّلين من هذا الكتاب ،كنا ّ قد استعملنا المكتبة القياسي ّة فقط ) ، stdio.h ، stdlib.h
.( ... time.h ، string.hلم نقم بدراستها بالتفصيل لكنّا جرّبنا منها جزء ً كبيراً .إن كنت تريد معرفة المزيد
جر ِ بحثا ً في ،Googleمثلا ً بكتابة ” ،”C standard libraryو ستجد نماذج الدوال في
عن هذا النوع من المكتبات أ ْ
ل دالة.
هذه المكتبة ،بالإضافة إلى شرح قصير حول دور ك ّ
• مكتبات الطرف الثالث ) :(Third party librariesهي مكتبات لا يتم ٺثبيتها تلقائيا .و إنّما يجب عليك تنز يلها من
الأنترنت و ٺثبيتها بنفسك على حاسوبك.
على عكس المكتبات القياسية ،التي تكون بسيطة نسبي ّا و تحتوي على عدد قليل من الدوال ،فإنه توجد الآلاف من
مكتبات الطرف الثالث ،و التي تمت كتابتها من طرف مبرمجـين آخرين .بعضها جي ّدة ،و أخرى أقل ،بعضها مدفوع،
و بعضها الآخر مجاني ،إلخ .الأمر المثالي هو إ يجاد مكتبة جي ّدة و مجانية في نفس الوقت !
265
الـSDL الفصل .20ٺثبيت
إنه لمن المستحيل أن أضع لك درسا ً يشرح كل المكتبات الموجودة .حت ّى لو أمضيت حياتي كل ّها 24ساعة ،24 /
لن أستطيع !
لذا سأقدّم لك مكتبة واحدة فقط مكتوبة بالـ Cو م ُستعملة من طرف مبرمجـين مثلك.
هذه المكتبة تدعى .SDLالسؤال المطروح هو لماذا اخترت هذه المكتبة بالضبط ؟ ما الذي يمي ّزها عن باقي المكتبات ؟
هذه أسئلة سأبدأ في الإجابة عليها إنطلاقا ًمن الآن.
الاختيار صعب .لـكن ّي اخترت هذه المكتبة ،التي هي نوعا ًما سهلة الاستعمال ،كبداية .ستكون هذه إذا أوّل مكتبة
تقوم باستعمالها )إذا لم نحسب المكتبة القياسية(.
إنه من الواضح أن أغلب القر ّاء يريدون معرفة كيفية فتح نوافذ ،إنشاء لعبة ،إلخ .و لـكن إن كنت تحب الـكونسول
فيمكننا الاستمرار فيها لوقت أطول ،إذا أردت ،لا ؟ إذا لدينا هنا بعض الفضول !
ل هذه الأمور ،لـكننا سنحاول أن نتطر ّق إليها خطوة بخطوة ،و بالنسبة للأعمالأودّ كثيرا ً أن أر يك كيف تعمل ك ّ
التطبيقية ،فلدينا عملان تطبيقيان لهذا الجزء من الكتاب !
لقد اخترت لك مكتبة سهلة و قو ية ،ستكون كبداية لك في تحقيق )تقريبا( أحلامك المتعل ّقة بالواجهة الرسومية ،و
ل شيء نسبيّ بالطبع !(.
من دون تعب )حسناً ،ك ّ
الـ ،SDLاختيار جي ّد !
• هي مكتبة مكتوبة بلغة : Cأي أنه بإمكان المبرمجـين أن يستعملوها في برامجهم المكتوبة بالـ .Cو كما هو الحال بالنسبة
لأغلب المكتبات المكتوبة بالـ ،Cيمكن استعمالها في لغة الـ C++بالإضافة إلى لغات برمجية أخرى.
• هي مكتبة حُرّة و مجانية :و هذا كي لا تضطر ّ لدفع أي ثمن مقابل استعمالك ما سأقدّمه لك في بقي ّة الكتاب .على
عكس ما قد نعتقد ،إ يجاد مكتبة جيدة و مجانية ليس أمرا ً صعبا ًكثيراً ،فقد انتشرت كثيرا ً في أيامنا هذه .المكتبة
الحرة هي ببساطة مكتبة يمكنك الحصول على الشفرة المصدر ية الخاصة بها .في حالتنا هذه ،رؤ ية الشفرة ليس مُهمّا
266
.1.20لماذا نختار الـ SDL؟
بالنسبة لنا .لـكن كونها حرة يفتح لنا الباب من أجل ميزات أخرى أهم ّها المداومة )أي أنه إن توقف صاحب
المكتبة عن تطويرها ،يُمكن لمبرمجـين آخرين أن يكملوا عمله( ،بالإضافة إلى مج ّاني ّتها غالبا .هذا يعني عدم إمكاني ّة اختفاء
المكتبة في يوم من الأيام.
• يُمكنك إنشاء برامج تجار ية ذات ملـكية خاصة بفضل هذه المكتبة .قد أكون قد تسرّعت بهذا الكلام ،لـكن ّه يجب
اختيار مكتبة حرّة تمنحك الحر ي ّة الأقصى .الحقيقة أنه يوجد نوعان من المكتبات الحُرة :
– المكتبات تحت رخصة : GPLمكتبات مجانية ،و يمكنك رؤ ية الشفرة المصدر ية الخاصة بها ،لـكن بشرط أن
تقوم أنت كذلك بنشر الشفرة المصدر ية الخاصة بالبرنامج الذي أنشأته باستخدامها.
– المكتبات تحت رخصة : LGPLمثل سابقتها ،لـكن ليس عليك أن تنشر الشفرة المصدر ية الخاصة بالبرنامج.
أي أنه يمكنك بها إنشاء برامج مملوكة.
م
بالرغم من أنه يمكنك قانوني ّا عدم نشر الشفرة المصدر ية الخاصة بالبرنامج ،إلا أنني أنصحك بذلك .فبهذا
يمكنك أن تأخذ رأي المبرمجـين الأكثر تمر ّسا ًمنك .و هذا يسمح لك بالتحسّن .بعد هذا ،فإن إنشاء برنامج
ح ُر أو ذو ملـكية خاصة ،يرجع لطبيعة تفكير كل شخص .لن أدخل في نقاش بخصوص هذا الموضوع،
ل النوعين له مميزاته و مساوءه.
لـكن فلتعلم أن ك ّ
• أخيرا ،فإن هذه المكتبة تسمح لك بالقيام بالـكثير من الأمور الممتعة التي سنتعر ّف إليها من خلال الفصول القادمة.
ل معادلات من الدرجة الرابعة ليست ممتعة ،لـكن ّي سأرك ّز على أن يكون
ن مكتبة ر ياضي ّاتي ّة قادرة على ح ّ
لا أقول أ ّ
هذا الدرس سهلا قدر الإمكان لـكيّ يحث ّك على البرمجة.
هذه المكتبة ليست مخصصة فقط لإنشاء ألعاب الفيديو .سأعترف بأن معظم البرامج التي تمت كتابتها بهذه المكتبة ،هي
ل شيء ممكن بالعمل و الاجتهاد.
عبارة عن ألعاب ،لـكن هذا لا يعني أنك مجـبر لاستعمالها من أجل ذلك .كما نعلم ،ك ّ
كنت قد رأيت من قبل محرر نصوص تمت برمجته بالـ ،SDLعلى الرغم من أن ّه هناك مكتبات أخرى أحسن لهذا الغرض.
إن كنت تريد برمجة واجهة رسومية تقليدي ّة تسمح بإظهار نافذة ،زر ،قائمة ،إلخ .فأنا أنصحك إذا بالتوجّه إلى المكتبة
.GTK+
المكتبة SDLهي مكتبة منخفضة المستوى .هل ٺتذكر أول الكتاب حينما حدّثتك عن لغات البرمجة عالية المستوى و لغات
البرمجة منخفضة المستوى ؟ هذا ينطبق على المكتبات أيضاً.
267
الـSDL الفصل .20ٺثبيت
• المكتبات منخفضة المستوى :تحتوي على دوال قاعدية جدّا .يوجد عدد قليل من هذه الدوال لأن ّه يمكننا القيام
ل شيء بها .و هذه الدوال لبساطتها تكون سر يعة جدّا .لهذا فالبرامج المنشأة بهذا النوع من المكتبات تكون عادة
بك ّ
الأسرع.
• المكتبات عالية المستوى :تحتوي على الـكثير من الدوال التي تسمح بالقيام بالـكثير من المهام .هذا يجعلها أبسط من
ناحية الاستخدام.
لـكن هذا النوع من المكتبات يكون عادة ”كبيرا” ،و ليس من السهل دراستها و معرفتها بأكملها .كما أنها قد تكون
أثقل من المكتبات منخفضة المستوى )لـكنّ هذا قد لا يكون واضحا(.
ل
على العموم ،لا يمكننا القول بأن ”مكتبة منخفضة المستوى هي أحسن من مكتبة عالية المستوى” أو العكس .فك ّ
منهما لها مميزات و مساوئ .الـ SDLالتي سنقوم بدراستها ،تنتمي إلى المكتبات منخفضة المستوى.
يجب إذا أن ٺتذكر بأن الـ SDLتقدّم دوالا قاعدية .يمكنك إذا الرسم بيكسلا ببيكسل ،رسم مستطيل أو إظهار صور.
كاف.
ٍ ن هذا
ل شيء ،و صدّقني أ ّ
هذا ك ّ
• بإظهار العديد من الصور الواحدة تلو الأخرى بسرعة ،يمكنك إنشاء تحر يك ).(Animation
• بوضع العديد من الصور ،الواحدة بجنب الأخرى ،يكون باستطاعتك إنشاء لعبة حقيقي ّة.
ن جودة اللعبة تعود إليك و إلى الفر يق الذي تعمل معه .إن كان لديك مصمم موهوب ،فيمكنك صنع
يجب أن تعلم أ ّ
لعبة أجمل.
الشيء الوحيد الذي يح ّد الـ SDLهو أنها تقتصر على الألعاب ثنائية الأبعاد ،و لم تُنشأ من أجل الألعاب ثلاثية الأبعاد.
ل شيء ممكن مادام ثنائيّ الأبعاد( :
هذه أمثلة على ألعاب يمكن تحقيقها بالـ) SDLليست سوى قائمة صغيرة ،ك ّ
268
الـSDL .2.20تنز يل
Breakout •
Bomberman •
Tetris •
• ألعاب المنصّ ات ... ،Rayman ،Sonic ،Super Mario Bros :
لا يمكن وضع لائحة كاملة ،الأمر يعود فقط للقدرة على التخي ّل .و صدّقني بأنك قادر على برمجة ألعاب فائقة الروعة.
فلقد رأيت أحد القر ّاء ينشئ تهجينا بين Breakoutو .Tetris
فلنعد إلى الأرض و لنمسك خيط هذا الفصل .سنقوم الآن بتسطيب المكتبة ،لنتمكّن من التقدّم في العمل.
http://www.libsdl.org/
• الشفرة المصدر ية ) : (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و الهدف من هذا هو الوصول إلى هذه الملفات بشكل أسرع حينما تحتاج إليها.
ٺثبيت مكتبة قد يكون أكثر صعوبة قليلا ًمما تعو ّد عليه الجميع .هنا لا يوجد ٺثبيت تلقائيّ يطلب منك أن تنقر ”التالي”،
”التالي”” ،التالي”” ،إنتهى”.
270
Windows .3.20إنشاء مشروع : SDL
الحقيقة أن ٺثبيت مكتبة أمر صعب على المبتدئين .لـكن لأقوم برفع المعنو يات فإن تسطيب مكتبة SDLأمر سهل جدا ً
مقارنة بتسطيب مكتبات أخرى أتيحت ليّ فرصة استخدامها من قبل )هناك من يتم إعطاؤك منها الشفرة المصدر ية فقط،
بينما أنت ٺتولى أمر الترجمة !(.
و الحقيقة أن كلمة ”ٺثبيت” ليست الملائمة هنا .لن نقوم بتثبيت أي شيء ،فقط نريد أن نصل إلى الـكيفية التي ننشئ
فيها مشروع SDLفي البيئة التطوير ية الخاصة بنا.
ل بيئة من بيئات التطوير
ستختلف كيفية التعامل حسب البيئة التطوير ية التي تستعملها .سأقوم بتقديم الطر يقة الخاصة بك ّ
التي قدّتمها في بداية الكتاب ،و هكذا كي يستطيع الجميع المتابعة.
الملف المضغوط يحتوي العديد من المجلّدات الداخلية ،و هذه هي الملفات التي تهمّنا :
يجب عليك استخراج كل الملفات و المجلّدات الداخلية ووضعها في مكان ما بالقرص الصلب لحاسوبك ،يمكنك مثلا
وضعها في مجلد خاص بـ SDLداخل مجلّد الـ.Code::Blocks
271
الـSDL الفصل .20ٺثبيت
احفظ المسار الذي به البرنامج ،ستحتاج إليه عندما تريد تعديل إعدادات 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
272
Windows .3.20إنشاء مشروع : SDL
عوض أن تقوم باختيار Console Applicationكما جرت العادة ،اختر .SDL project
النافذة الأولى لا جدوى منها ،قم بتجاوزها بالضغط على ”التالي” ).(Next
سي ُطلب منك أن تقوم بإدخال اسم المشروع ،قم بذلك كالعادة :
273
الـSDL الفصل .20ٺثبيت
اضغط على الزر الذي يأخذ شكل مرب ّع به ثلاث نقاط ،ستظهر لك النافذة التالية :
274
Windows .3.20إنشاء مشروع : SDL
املأ الحقل baseبنفس الطر يقة السابقة ،ثم ّ اضغط على زر الخروج ،و ستلاحظ أن المسار قد تم تسجيله كالتالي :
اضغط على ،Nextستظهر لك نافذة اختيار المترجم ،قم باختيار الأوضاع Realeaseأو ) Debugهذا لا يهم(.
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
278
Windows .3.20إنشاء مشروع : SDL
اضغط على اسم المشروع ،و اطلب إضافة ملف جديد :
279
الـSDL الفصل .20ٺثبيت
ستظهر لك نافذة أخرى ،قم باختيار ،Nextثم انقر على ”موافق” ).(OK
أنقر باليمين مجددا على الملف main.cو اختر ”خصائص” ): (Properties
توجه إلى القائمة ،Advancedستجد ،Compiler variableغي ّرها من CPPإلى CCكالتالي :
280
Windows .3.20إنشاء مشروع : SDL
اضغط بعد ذلك على OKهذا ما سيبدو عليه المشروع الجديد :
يمكنك ترجمة البرنامج ،ستظهر لك نافذة و تختفي فجأة ،لا تقلق ،سنعالج ذلك لاحقاً ،أنا أهن ّئك ،كل شيء يعمل
بشكل جيد جدا.
يمكنك مسح الملف .bmpلأننا لسنا بحاجة إليه .بالنسبة للملف ، main.cيمكنك الآن استبدال محتواه بالشفرة
281
الـSDL الفصل .20ٺثبيت
التالية :
إنها شفرة مبدئية ،تشبه الشفرات التي تعو ّدنا عليها )تضمين stdlib.hو stdio.hثم .( mainالشيء الوحيد
ي يستكلف نفسه بتضمين كل الملفات الرأسية الخاصة بالمكتبة .SDL
الذي تغي ّر هو تضمين الملف . SDL.hإن ّه ملف رأس ّ
Visual C++ 2005 من الموقع الرسمي ،قم بتنز يل آخر نسخة من المكتبة من قسم ” ”Development Librariesو اختر نسخة
.Service Pack 1
• انسخ الملفات .libإلى المجلّد libالخاص بـ .Visual C++بالنسبة لي ،أنا أتكلم عن المجلّد
• انسخ الملفات .hإلى المجلّد includesالخاص بـ .Visual C++أنشئ مجلّدا SDLفي المجلد includesلجمع
الملفات .hالخاصة بالـ SDLفيه .بالنسبة لي ،سأضع تلك الملفات في المجلد :
. C:\Program Files (x86)\Microsoft Visual Studio 8\VC\include\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++
أكتب )أو انسخ/ألصِ ق( الشفرة المبدئية الذي وضعتها أعلاه ،في الملف الجديد الذي أنشأته.
التعديل على المشروع أصعب قليلا ًمما هو الحال مع الـ ،Code::Blocksلـكن بقليل من التركيز ،ستتمكن من فعله .توجّه
إلى خصائص المشروع من . testsdl properties / Project
283
الـSDL الفصل .20ٺثبيت
SDL.lib • في القسم Input / Link editorعدّل قيمة الـ Additional dependenciesلـكي تضيف
و . SDLmain.lib
ستجد الملف التنفيذي الذي يتواجد بمجلد المشروع )أو بمجلد داخلي يسمى ( Debugو لا تنس أنه على الملف
ل شيء على
مرتين على ، .exeإذا سار ك ّ
SDL.dllأن يتواجد في نفس المجلّد الذي يتواجد به الملف التنفيذي .أنقر ّ
ما يرام ،فلن يحصل أيّ شيء ،و إلّا فسيحدث خطأ إذا لم يكن الملف SDL.dllفي نفس المجلّد.
فلتقم بتنز يل النسخة 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
الآن قم بانشاء مشروع جديد ” ،”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
توجه إلى ، 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قم بتسمية الملف و هاقد أكملت.
لمن يستعملون بيئة تطوير ية من أجل الترجمة ،فعليهم بتغيير خواص المشروع )فالعملية مشابهة لما كنت قد شرحت(.
بالنسبة لمن يستعمل ،Code::Blocksفالطر يقة هي نفسها التي شرحتها سابقاً.
؟
ماذا عن الذين يقومون بترجمة الشفرات يدو يا ؟
قد يوجد بين القراء من اعتاد على ترجمة الشفرات يدو يا بالاستعانة بـ) Makefileملف يساعد على عملية الترجمة(.
إذا كانت هذه حالتك ،فأدعوك لتحميل Makefileالّذي يُمكن أن يُستخدم لترجمة مشار يع الـ.SDL
http://www.siteduzero.com/uploads/fr/ftp/mateo21/makefile_sdl
الشيء الوحيد المختلف ،هو إضافة المكتبة SDLإلى محر ّر الروابط ) .( LDFLAGSيجدر بك أن تكون قد نز ّلت المكتبة
و ثب ّتها في مجلّد ملفات المترجم ،بنفس طر يقة ) Windowsالمجلدان include/SDLو .( lib
288
ملخّ ص
ملخّ ص
• الـ SDLمكتبة منخفضة المستوى ،تسمح بإنشاء نوافذ و التعامل مع الرسوميات .2D
• المكتبة ليست مسطّبة تلقائيا في الحاسوب ،يجب عليك أن تنز ّلها بنفسك و تقوم بتخصيص البيئة التطوير ية لتعمل
معها.
• المكتبة حرة و مج ّانية ،مما يسمح باستعمالها السر يع و الدائم.
• توجد آلاف المكتبات الأخرى ،و كثير منها ذو جودة عالية جداً .و قد تم اختيار المكتبة SDLلبقي ّة هذا الكتاب
لأجل سهولتها .لمن يريد بناء واجهات رسومية بنوافذ ،أزرار و قوائم ،فأنا أنصحه بالمكتبة GTK+مثلا.
289
الـSDL الفصل .20ٺثبيت
290
الفصل 21
سندخل في مضمون موضوعنا في هذا الفصل .سنقوم بتطبيق أساسيات لغة الـ Cمع .SDLكيف يتم تحميل الـ SDL؟
كيف يتم فتح نافذة بالأبعاد التي نريد ؟ كيف نرسم داخل النافذة ؟
العديد من المكتبات المكتوبة بلغة الـ ،Cتستلزم أن يتم تحميلها ثم غلقها حين ننتهي منها ،و ذلك لاستعمال دوال محددة.
المكتبة SDLمن بين هذه المكتبات.
بالفعل ،فالمكتبة تحتاج أن يتم تحميل عدد معيّن من المعلومات إلى الذاكرة العشوائية لتستطيع أن تشتغل بشكل صحيح.
يتم هذا التحميل بشكل حيّ باستعمال الدالة ) mallocإنّها مهمّة جدّا هنا !( .و كما تعلم فإن قلت ، mallocسأقول
كذلك ! free
يجب عليك تحرير الذاكرة التي حجزتها و لم تعد بحاجة إليها .إن لم تفعل ،فالبرنامج يمكن أن يأخذ حي ّزا ً كبيرا ً من الذاكرة
بدون فائدة ،و يمكن لذلك أحيانا أن يدرّ بنتائج كارثية .تخي ّل القيام بحلقة غير منتهية من mallocدون قصد ،في بضع
ل الذاكرة !
ثوان ستس ّد ك ّ
هاهما الدالتان الأولتان الخاصّتان بالـ SDLاللتان يجب عليك أن تعرفهما :
أي أن أوّل شيء يجب أن تقوم به في البرنامج هو استدعاء ، SDL_Initو آخر شيء هو استدعاء . SDL_Quit
291
الفصل .21إنشاء نافذة و مساحات
الدالة SDL_Initتستقبل معاملا .إذ يجب أن يتم تحديد أي جزء من المكتبة نريد تحميله.
؟
آه حقّا ! هل الـ SDLٺتكون من كثير من الأجزاء ؟
نعم بالطبع ! فهناك جزء من المكتبة يتعامل مع الشاشة ،و آخر يتعامل مع الصوت ،إلخ.
توف ّر لنا المكتبة عددا ً من الثوابت التي تسمح لنا بتحديد اسم الجزء الذي نريد تحميله من المكتبة.
الشرح الثابت
تحميل الجزء الخاص بالعرض )الفيديو( ،إنه الجزء الذي نحمله غالباً. SDL_INIT_VIDEO
تحميل الجزء الخاص بالصوت ،هذا ما يسمح لك مثلا بتشغيل الموسيقى مثلا. SDL_INIT_AUDIO
تحميل الجزء الخاص بقارئ القرص المضغوط ،و ذلك للتحكم به. SDL_INIT_CDROM
;)1 SDL_Init(SDL_INIT_VIDEO
فإن نظام العرض سيتم تحميله في الذاكرة ،فيمكنك أن تفتح نافذة و ترسم فيها ،إلخ.
كل ما قمنا به هو إعطاء عدد إلى الدالة SDL_Initبالاستعانة بثابت .أنت لا تعرف أي عدد هو ،و هذا أمر جيد .إذ
أنك غير مجـبر على حفظ العدد ،بل التعبير عنه باسم الثابت فقط.
;)1 SDL_Init(SDL_INIT_EVERYTHING
؟
ماذا لو أردت تحميل الصوت و الفيديو فقط .هل يجدر بي استخدام SDL_INIT_EVERYTHING؟
لن تستعمل ، SDL_INIT_EVERYTHINGمن أجل تحميل وحدتين ،هذا جنون ! لحسن الحظ ،يمكننا تجميع الخيارات
بواسطة الرمز | .
292
الـSDL .1.21تحميل و إيقاف
م
هذه ”الخيارات” التي نبعثها للدالة SDL_Initنسميها بـالأعلام ) .(Flagsهذه الكلمة نستعملها كثيرا ً في علوم
الحاسوب.
تذك ّر إذا أن الإشارة | خاصة بدمج الخيارات .إنها تشبه الإضافة إلى ح ّد ما.
;)(1 SDL_Quit
هذا نموذج عن برنامج بسيط ،عبارة عن مخطط لبرامج SDLالتي نكتبها .في الواقع ،البرنامج الحقيقي يكون ممتلأ ً كثيرا إذ
يحتوي عدّة استدعاءات لدوال ،تقوم بدورها بمزيد من الاستدعاءات.
ن SDLيجب أن تُحم ّل في البداية و تُغلق عندما لا تصبح بحاجة إليها.
م في النهاية ،هو أ ّ
الأمر المه ّ
293
الفصل .21إنشاء نافذة و مساحات
معالجة الأخطاء
لست مجـبراً ،لـكن يمكنك اختبار القيمة المُرجعة .قد تكون طر يقة جي ّدة لمعالجة الأخطاء في برنامجك ،و هذا ما
سيساعدك على حل ّها.
؟
لـكن كيف أقوم بإظهار الخطأ الحادث ؟
سؤال وجيه ! ليس لدينا كونسول الآن ،كيف نخز ّن و نعرض رسائل الخطأ ؟
• يمكننا التعديل على خاصيات المشروع ،لـكي نسمح له باستعمال الـكونسول أيضاً .سنتمكّن في هذه الحالة من
استخدام الدالة ،( printf
لقد اخترت أن نكتب في ملف .و بهذا فإن العمل على ملف يحتاج إلى فتح هذا الأخير بـ fopenو غلقه بـ ، fclose
ل سهولة من استعمال الـ . printf
و الأمر أق ّ
لحسن الحظ ،هناك طر يقة أسهل و هي استعمال مخرج الأخطاء القياسي.
يوجد متغير stderrمعر ّف في stdio.hيقوم بالتأشير نحو المنطقة التي يُمكن أن يُكتب فيها الخطأ .غالبا في
الويندوز ،هذه المنطقة عبارة عن ملف يحمل الاسم . stderr.txtبينما في اللينكس فإن الأخطاء غالبا ًما يتم إظهارها
على الـكونسول .هذا المتغير يتم إنشاؤه تلقائي ّا في بداية البرنامج و يتم حذفه في نهايته ،أي أنك لست مجـبرا ً على استعمال
fopenو . fclose
يمكنك استعمال الدالة fprintfعلى stderrبدون استعمال fopenو : fclose
294
.2.21فتح نافذة
• لقد كتبنا الخطأ الذي وجدناه في . stderrالرمز %sيسمح للـ SDLبالإشارة إلى تفاصيل الخطأ :الدالة
SDL_GetErrorفي الحقيقة تقوم بإرجاع آخر خطأ .SDL
• نخرج باستعمال الـ )( . exitلح ّد الآن لا يوجد شيء جديد مقارنة بما جرت العادة ،ستلاحظ أنني استعمل الثابت
EXIT_FAILUREكقيمة يقوم البرنامج الرئيسي بإرجاعها ،بينما استعملت في النهاية الثابت EXIT_SUCCESS
في مكان الـ.0
ما الذي قمت به ؟ لقد قمت بتحسين الطر يقة التي تعودنا أن نكتب بها الشفرة .لقد استخدمت اسم الثابت الّذي يعني
”خطأ” و الذي هو نفسه بالنسبة لجميع أنظمة التشغيل .بينما الأعداد تختلف من نظام إلى آخر.
لهذا فإن الملف stdlib.hتسمح باستعمال ثابتتين )معر ّفي : ( #define
باستعمال أسماء الثوابت بدلا ًمن قيمها ،ستضمن بأنك قد بعثت القيمة الصحيحة.
لماذا ؟ لأن الملف stdlib.hيتغي ّر حسب نظام التشغيل الّذي أنت عليه ،لذا فقيم الثوابت ستتأقلم مع النظام
ل أنظمة التشغيل )بافتراض أن ّك تبرمج
ن نحتاج إلى تغيير شيء ! و هذا ما يجعل لغة الـ Cمتوافقة مع ك ّ
من دون أ ّ
بالطر يقة الصحيحة باستخدام الأدوات المتوف ّرة ،كما فعلنا هنا(.
م
استعمال أسماء الثوابت لا يعود علينا بكثير من النفع الآن ،لـكن من الأحسن استعمالها .سنقوم بذلك
انطلاقا ًمن الآن.
حسناً ،لقد تم فتح و غلق المكتبة بنجاح .الخطوة التالية التي يريد تعلّمها الجميع هي كيفية فتح نافذة !
295
الفصل .21إنشاء نافذة و مساحات
أول شيء نقوم به بعد ، SDL_Initهو تحديد وضع العرض الذي نريد استعماله ،أي الدقة ) ،(Resolutionعدد الألوان
بالإضافة إلى خصائص أخرى.
• الخيارات )الأعلام(.
لا أعتقد أن طول و عرض النافذة يحتاجان إلى شرح ،بينما عدد الألوان و الأعلام هما المعاملان الأكثر أهمي ّة.
• عدد الألوان :هو العدد الأقصى للألوان التي يمكن أن تظهر في النافذة .إن كنت من عشاق ألعاب الفيديو ،ستكون
معتادا ً على هذا الأمر .فإن قيمة 32 bits/pixelتسمح بإظهار ملايير الألوان ،بينما إنه من الممكن أن نختار قيمة
أقل كـ) 16 bits/pixelتسمح بعرض 65536لون( أو حتى ) 8 bits/pixelتسمح بعرض 256لون مختلف( ،هذا
الأمر مفيد حينما تريد برمجة تطبيقات من أجل جهاز بسيط كالـ PDAأو الهاتف المحمول.
• الخيارات :تماما مثل الـ ، SDL_Initعلينا باستعمال أعلام من أجل تعر يف خصائص .هذه أهم الأعلام التي
يمكنك استعمالها )يمكنك استعمال العديد منها ،يتم التفر يق بينها باستعمال الرمز | ( :
296
.2.21فتح نافذة
– : SDL_FULLSCREENنمط الشاشة الكاملة .لن تستطيع رؤ ية أية نافذة أخرى لأن نافذة البرنامج الحالي
ل الشاشة ،مع تعديل دق ّة الشاشة في حالة الضرورة.
تهيمن على ك ّ
– : SDL_DOUBLEBUFوضع ،double bufferingتقنية مستعملة بكثرة في برمجة الألعاب ثنائية الأبعاد .تقضي
بأن يكون تحر ّك الأشياء على الشاشة مرناً ،لأنه إن لم يكن كذلك ،سيكون التحر ّك سيئاً ،سأشرح هذا الأمر
بالتفصيل لاحقاً.
فإنه سيقوم بفتح نافذة ذات أبعاد ،640 × 480و بعدد ألوان ) 32 bits/pixelملايير الألوان( ،و ستم تحميل النافذة
على الذاكرة الرسومي ّة )إنّها الأسرع ،لذلك نفضّ ل استعمالها(.
هذه الشفرة تفتح نافذة مقاييس أبعادها قابلة للتعديل ،بأبعاد ابتدائية ،400 × 300و بعدد ألوان 32 bits/pixelكما
أن تقنية double bufferingمفع ّلة.
لقد اخترت أن أسحب معالجة الأخطاء لتبسيط الشفرة ،لـكن بالنسبة لك ،يجب عليك أن تكتب برامج كاملة و أخذ
ل الاحتياطات اللازمة لمعالجة الأخطاء.
ك ّ
قم بتجريب الشفرة .ما الذي يحصل ؟ تظهر النافذة و تختفي بسرعة البرق.
ل شيء.
الحقيقة أن استدعاء الدالة 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الذي يقوم
بإخفاء حواشي النافذة .بما أن ّه لن يكون هناك شر يط للعنوان ،فلن نكون قادرين على الخروج من البرنامج ،إلا
بالاستعانة بالمعالج !
تر ي ّث قليلا ًحتى نتعل ّم معالجة الأحداث )في الفصول القادمة( و ستتمكن بعدها من الخروج من النافذة بطر يقة
أقل عنفا ًمن استدعاء المعالج.
خلافا ًلما يعتقده الجميع ،تغيير اسم الأيقونة لا يعني تغيير صورة الأيقونة التي تظهر أعلى يسار النافذة .هذا لا يعمل دائما
)حسب معرفتي ،قد يعطي نتائج على الـ GNU/Linuxفي بيئة الـ .(Gnomeشخصياً ،أنا أبعث القيمة NULLإلى الدالة .على
ن هذا الأمر
أية حال ،يمكننا تغيير شكل الأيقونة التي تظهر أعلى يسار النافذة ،لـكننا سنتعل ّم ذلك في الفصل القادم ،لأ ّ
ليس بمستواك بعد.
300
.3.21التعامل مع المساحات
م
لاحظ بأنني استعملت القيمة NULLللمعاملات غير المهمة بشكل كبير .بالنسبة للـ ،Cيجب أن يتم إعطاء قيم
لكل المعاملات التي تستقبلها الدوال ،حتى لو كانت هذه المعاملات غير مهمة لك ،فأعطها NULLكما فعلت أنا
هنا .بينما الـ C++تسمح بألا نعطي أساسا ًقيمة لبعض المعاملات الاختيار ي ّة عندما نستدعي الدوال.
لحد الآن تمكّنا من فتح نافذة ذات خلفي ّة سوداء .ما نريد الآن هو أن نملأها ببعض الأشياء ،أي أن ”نرسم” فيها.
كما قلت لك في الفصل السابق ،فإن المكتبة SDLهي مكتبة منخفضة المستوى ،أي أنها لا توف ّر لنا سوى دوال قاعدية،
بسيطة جداً.
ل ما سنقوم به هو جمع بعض المستطيلات
الصراحة هي أن الشكل الوحيد الذي تسمح لنا الـ SDLبرسمه هو المستطيل ! ك ّ
في نافذة .نسمّي هذه المستطيلات بـالمساحات ) ،(Surfacesالمساحة هي الوحدة الرسومية القاعدية في الـ.SDL
301
الفصل .21إنشاء نافذة و مساحات
م
إنه من الممكن أن نرسم أشياء أخرى ،مثل الدوائر و المثلثات ،إلخ .و لـكن لـكي نفعل ذلك ،يجب أن نكتب
بأنفسنا الدوال ال ّتي تمكّن من فعل ذلك ،برسم تلك الأشكال بيكسلا ببيكسل ،و إما أن نستعمل مكتبة أخرى
ل هذا في التطبيق.
إلى جانب الـ .SDLالأمر معقّد نوعا ًما ،لـكن لا تقلق ،ستجد بأننا لسنا بحاجة إلى ك ّ
في كل برامج الـ ،SDLتوجد على الأقل مساحة عمل واحدة و هي ما نسميه بالشاشة ) ،(Screenو هي مساحة توافق كل
ل المساحة السوداء التي تظهر بالنافذة.
النافذة ،أي ك ّ
في الشفرة المصدر ية ،كل مساحة يتم تخزينها في متغير من نوع . SDL_Surfaceنعم ،إنه نوع بيانات تم إنشاؤه من
طرف الـ) SDLهذا المتغير عبارة عن هيكل(.
تلاحظ أنني قمت بإنشاء مؤش ّر .لماذا أفعل هذا ؟ لأن الـ SDLهي من ستقوم بحجز مكان في الذاكرة من أجل
مساحتنا .المساحة بالفعل ليس لها بالضرورة دائما نفس الحجم و لهذا فعلى الـ SDLأن تقوم بحجز حيّ من أجلنا )هنا ،هذا
يعتمد على حجم النافذة التي فتحناها(.
لم أقل لك هذا من قبل ،لـكن الدالة SDL_SetVideoModeتقوم بإرجاع قيمة ! ستقوم بإرجاع مؤش ّر نحو المكان
بالذاكرة المخصص لمساحة الشاشة.
ممتاز ،يمكننا إذا استرجاع المؤش ّر في المتغير : screen
• قيمة أخرى :إذا كانت القيمة مختلفة عن ، NULLفهذا يعني أن الـ SDLقامت بحجز المكان ،كل شيء على ما
يرام !
إنه من المستحسن هنا أن تتم معالجة الأخطاء ،تماما مثلما فعلنا حينما أردنا تحميل الـ ،SDLهاهي إذا الدالة
302
.3.21التعامل مع المساحات
الرسالة التي تتركها لنا الدالة ، SDL_GetErrorمفيدة من أجل معرفة ما الّذي لم يعمل.
م
حكاية صغيرة :مرة أخطأت بينما أردت أن أفتح نافذة بأسلوب الشاشة الكاملة ) ،(Full screenفي عوض أن
أطلب الدقة 1024 × 768كتبت بالخطأ ،10244 × 768لم أفهم لماذا لم يتم التحميل ،لأن ّي لم أنتبه إلى أنني
مرتين )ربما كنت متعباً( .و لحل المشكل ألقيتُ نظرة على الملف ، stderr.txtتوجهت إلى رسالة
كتبت ّ 4
الخطأ و اكتشفت بأن الدقة التي طلبتها مرفوضة )شيء يثير الفضول أليس كذلك ؟(.
تلوين مساحة
لا توجد 36طر يقة لملء مساحة ،الحقيقة أنه توجد طر يقتان :
م
يمكنك في الحقيقة الرسم في المساحة بيكسلا ببيكسل ،لـكن هذه الطر يقة معقّدة ،لن نراها هنا.
سنرى أولا كيف نقوم بتلوين مساحة بلون موحّد .في الفصل القادم سنتعل ّم كيف نقوم بتحميل صورة.
الدالة التي تسمح بتلوين النافذة بلون موحّد هي ) SDL_FillRectالعبارة FillRectتعني ملء مستطيل
بالإنجليز ي ّة( .هذه الدالة تستقبل 3معاملات و هي :
303
الفصل .21إنشاء نافذة و مساحات
• مؤش ّر نحو المساحة التي نريد التلوين عليها )مثلا .( screen
• الجزء من المساحة الذي نريد تلوينه ،إذا أردت تلوين كل المساحة )و هذا الّذي نريده( فلتكن قيمة المؤشر . NULL
كملخّ ص :
؟
إذا كان عدداً ،لماذا إذا لم نستعمل ببساطة النوع intأو النوع long؟
SDL الـ SDLهي مكتبة متعددة المنصات ،و كما تعلم فحجم الـ intيتغير من نظام تشغيل إلى آخر .لهذا فإن الـ
تقوم باستخدام أعداد من أنواع جديدة ،هذه الأنواع الجديدة تحجز نفس المكان بالذاكرة في كل أنظمة التشغيل.
لن تستعمل المكتبة سوى typedefلتقوم بتغيير قيمة العدد على حسب نظام التشغيل .إذا كنت فضولياً ،فألق نظرة
على الملف . SDL_types.h
لن نتأخر في التعامل مع كل هذا ،فالتفاصيل لا تهم حالياً .كل ما عليك تذكره هو أن النوع Uint32لا يخزن إلا
عددا صحيحا ًليس إلا ،مثل . int
؟
لـكن كيف أعرف أي عدد يوافق اللون الّذي أريد ؟
هناك بالفعل دالة من أجل ذلك ، SDL_MapRGB :هذه الأخيرة تستقبل 4معاملات :
• صيغة الألوان :هذه الصيغة تعتمد على عدد bits/pixelالتي قد طلبتها بالـ . SDL_SetVideoModeيمكنك استرجاع
القيمة فهي موجودة في المتغير الداخلي . screen->format
304
.3.21التعامل مع المساحات
قد لا يعرف البعض بأن كل الألوان يتم تشكيلها عن طر يق خلط الألوان :أزرق ،أحمر و أخضر.
كل كمية ٺتدرج من العدد ) 0لا يوجد لون( إلى العدد ) 255كل اللون موجود( .أي أننا لو كتبنا :
فاللون المتشكل سيكون أحمرا .لا وجود للأخضر و لا للأزرق ،أما لو نكتب :
اللون سيكون أبيضا لأننا دمجنا كل الألوان ،لو أنك تريد تشكيل اللون الأسود ،فلتجعل كل القيم على .0
؟
ألا يمكننا استعمال لون آخر غير هذه الألوان ؟
Colors كلّا ،يمكنك ذلك لو أنك تقوم بمزج الألوان بشكل ذكي .للمساعدة في ذلك ،توجه إلى برنامج Paintثم إلى
، Modify the colors /انقر على Define the colorsثم . Custom
هنا ،اختر اللون الّذي يلائمك .أنظر إلى الصورة التالية :
مركّبات اللون متواجدة في أسفل يمين النافذة .كما ترى فقد اخترت لونا أخضر مزرقّا ،و هو يتكو ّن من 17من
الأحمر 206 ،من الأخضر ،و 112من الأزرق.
305
الفصل .21إنشاء نافذة و مساحات
تلوين الشاشة
ليس من الضروري المرور دائما على متغير لتخزين اللون المراد استعماله )إلا إن كنت تحتاجه فعلا في برنامجك(.
يمكنك مباشرة إعطاء القيمة التي تم ارجاعها من طرف الدالة SDL_MapRGBإلى الدالة . SDL_FillRect
لو نريد أن نملأ الشاشة باللون الأخضر المزرق ،يمكننا كتابة :
لقد قمنا باستدعاء دالة خلال استدعاء دالة أخرى ،أعتقد أنك تعرف بأن الأمر ممكن و لا يسبب أيّ مشاكل في لغة
.C
تحديث الشاشة
من أجل هذا سنستعمل الدالة ، SDL_Flipسنتكلم بشكل مفصل عن هذه الدالة لاحقا.
الدالة تستقبل معاملا واحدا و هو الشاشة . screen
فلنلخّ ص كل شيء !
هذه دالة mainتقوم بفتح نافذة ملونة باللون الأخضر المزرق :
306
.3.21التعامل مع المساحات
النتيجة السابقة جيدة ،لـكننا لن نتوقف هنا .لحد الآن ليست لدينا سوى مساحة واحدة و هي الشاشة .نحن نريد أن نقوم
بالرسم عليها ،أي ”نلصق” مساحات أخرى عليها بألوان مختلفة.
سنطلب إذا من الـ SDLأن تقوم بحجز مكان في الذاكرة من أجل المساحة الجديدة.
من أجل الشاشة كنا قد استعملنا . SDL_SetVideoModeلـكن هذه الأخيرة لا تعمل إلا على الشاشة )المساحة
الرئيسية( ،لا نريد أن نقوم بإنشاء نافذة من أجل كل مستطيل نريد إنشاءه !
توجد إذا دالة أخرى من أجل إنشاء مساحة . SDL_CreateRGBSurface :هذه هي التي سنقوم باستعمالها في
كل مرة نريد أن ننشئ مساحة جديدة.
هذه الدالة تستقبل العديد من المعاملات )ثمانية !( .لـكنني لن أتطر ّق إلا للمعاملات التي تهمّنا لح ّد الآن.
بما أن لغة Cتُلزمنا بإدخال قيم لكل المعاملات ،فإننا سنقوم بوضع القيمة 0في مكان كل معامل لا يهمّنا.
فلنتأمل قليلا في المعاملات الأربع الأولى )يجدر بها أن تذك ّرنا بإنشاء الشاشة(.
– : SDL_HWSURFACEالمساحة يتم تحميلها في الذاكرة الرسومي ّة .و هي تحتوي على مكان أقل مقارنة بالذاكرة
الخاصة بالنظام )حقيقة ،مع بطاقات الـ 3Dفي أيامنا هذه ،قد لا يكون لهذا تأثير( ،لـكنها ذاكرات سر يعة و
فع ّالة.
307
الفصل .21إنشاء نافذة و مساحات
– : SDL_SWSURFACEيتم تحميل المساحة في الذاكرة الخاصة بالنظام ،أين يوجد الـكثير من المكان ،لـكن هذا
الاختيار سيجبر المعالج على القيام بحسابات أكثر .لو أنك حمّلت المساحة على الذاكرة الرسومي ّة ،فإن البطاقة
3Dهي المسؤولة عن القيام بأغلب الحسابات.
الأربع معاملات الأخيرة تساوي ،0كما قلت لك ،لأننا لا نهتم بأمرها حالياً.
بما أننا قمنا بالحجز اليدوي للذاكرة ،فيجب علينا تحريرها باستعمال الدالة SDL_FreeSurfaceو التي نستعملها قبل
: SDL_Quit
;)1 SDL_FreeSurface(rectangle
;)(2 SDL_Quit
م
ليس هناك من ٍ
داع إلى تحرير المساحة screenباستعمال SDL_FreeSurfaceلأنه يتم تحريرها تلقائيا ًعند
استدعاء . SDL_Quit
اقتربنا من النهاية ،هيا بعض الشجاعة ! المساحة جاهزة ،لـكن لو تحاول تجريب البرنامج ،ستلاحظ أنها لن تظهر على الشاشة،
بالفعل إذ أن المساحة screenهي وحدها التي تم إظهارها .لـكي نستطيع رؤ ية مساحتنا الجديدة يجب أن نقوم بـتسو ية
المساحة ،أي لصقها على الشاشة ،سنستعمل لأجل هذا الدالة . SDL_BlitSurfaceهذه الدالة تنتظر :
• معلومة حول الجزء من تلك المساحة الذي نريد لصقه )اختياري( .لن يهمنا الأمر الآن فنحن نريد لصق كل
المساحة و لهذا فستكون القيمة . NULL
• المساحة التي نريد أن نلصق عليها المساحة الجديدة )في حالتنا هذه نتكلم عن الشاشة .( screen
308
.3.21التعامل مع المساحات
• مؤش ّر نحو متغير يحتوي الإحداثي ّات .هذه الإحداثيات تشير إلى المكان الذي نريد أن نلصق عليه المساحة ،أي
موقعه.
للإشارة إلى الإحداثي ّات ،نحتاج إلى استعمال متغير من نوع . SDL_Rect
إن ّه هيكل يحتوي العديد من المركّبات ،إثنتان منها تهمّنا :
– : xالفاصلة.
– : yالترتيبة.
يجب أن تعرف أن الإحداثي ّة ) (0, 0توافق أقصى نقطة في يسار أعلى الشاشة.
أما الإحداثي ّة ) (640, 480فهي توافق النقطة الموجودة في أقصى يمين أسفل الشاشة ،و هذا إن كنت قد فتحت نافذة
بحجم 640 × 480مثلي.
إذا كنت قد درست الر ياضيات من قبل ،فعلى الأرجح لن تضيع بينما تحاول فهم كيفية العمل .فلننشئ إذا متغيرا
. positionسنعطي القيمة 0لكل من الفاصلة و الترتيبة و ذلك ليتم لصق مساحتنا )المستطيل( في أعلى يسار النافذة :
و الآن بما أننا حددنا موقعنا في النافذة ،يمكننا تسو ية المساحة الجديدة على الشاشة :
لاحظ أنني استعملت الرمز & و ذلك لأنه يجب علينا إرسال عنوان المتغير . position
309
إنشاء نافذة و مساحات.21 الفصل
: شاهد النتيجة
310
.4.21تمرين :إنشاء تدرّج لونيّ
نحن نجيد إظهار المساحة في أعلى اليسار .يسهل أيضا موقعتها أسفل يمين الشاشة .ستكون الإحداثيات )،(640 − 220, 480 − 180
لأنه يجب إنقاص حجم المستطيل ليتم إظهاره كاملا.
لـكن كيف تتم مركزة ُ المستطيل الأبيض ؟ لو تفك ّر قليلا ً ستجد بأن الحساب ر ياضياتيّ .فهنا نعرف الهدف من
الر ياضيات و الحساب الهندسي !
ل هذا الأمر بمستوى سهل هنا :
ك ّ
فاصلة المستطيل هي نصف عرض الشاشة ) .(640/2و لـكن ،بالإضافة إلى هذا ،يجب أن يتم إنقاص نصف طول
المستطيل أيضا ً) ،(220/2لأنك إن لم تنقص هذا الحجم ،سيكون تمركز المستطيل خاطئا ً)جرّب عدم فعل ذلك و ستفهم
ما الّذي أعنيه(.
كذلك بالنسبة للترتيبة مع ارتفاع الشاشة و المستطيل.
سننهي الفصل بتمرين صغير )مصحّ ح( متبوع بسلسلة تمارين أخرى )غير مصححة من أجل حث ّك على التدريب(.
التمرين المصحح ليس صعبا ًحقّا :ما نريد إنشاءه هو نافذة متدرّجة الألوان عموديا من الأسود إلى الأبيض.
سيكون عليك إنشاء 255مساحة بارتفاع 1بيكسل .كل مساحة لها لون مختلف أكثر فأكثر سوادا.
311
الفصل .21إنشاء نافذة و مساحات
هذا ما يجب عليك الحصول عليه في النهاية ،صورة مشابهة لهذه :
إنه أمر جميل ،أليس كذلك ؟ الشيء الأجمل هو أن بعض الحلقات التكرار ية كافية من أجل تحقيق المطلوب.
لفعل ذلك يجب إنشاء 256مساحة )أي 256سطر( تحتوي مركبات الألوان )أحمر ،أخضر ،أزرق( التالية :
يجب على أيّ كان أن يعرف أن ّه بحاجة إلى حلقة تكرار ي ّة للقيام بهذا )لن تسعد بتكرار 256سطرا !( .و لهذا سنقوم
بإنشاء جدول من نوع * SDL_Surfaceمن 256خانة.
تصحيح !
يجب أولا أن نقوم بتعر يف جدول من . SDL_Surface* 256سنهي ّؤه على : NULL
سنغي ّر أيضا ًارتفاع النافذة لـكي تكون مناسبة للعمل .إذ سنعطيها 256بيكسلز كارتفاع ،و ذلك من أجل عرض كل
سطر من بين 256سطرا.
سنستعمل بعد ذلك حلقة تكرار ية forمن أجل حجز مكان لـ 256مساحة ال ّتي تم إنشاؤها .الجدول سيستقبل 256
ل واحد من المساحات المنشأة :
مؤش ّرا إلى ك ّ
312
.4.21تمرين :إنشاء تدرّج لونيّ
لاحظ أنني استعمل كل الوقت المتغير . 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 ملخّ ص
! هذا الأمر لن يكون صعبا للبدأ. أي من الأبيض للأسود،• قم بإنشاء تدرج عكسي للألوان
من الأبيض للأسود ثم من الأسود للأبيض )ستأخذ النافذة ضعف الارتفاع،• يمكنك أيضا ً وضع كلى التدرّجين
.(الحالي
. يمكنك وضع تدرج أفقي بدل التدرج العمودي،• أكثر صعوبة قليلا
314
ملخّ ص
• حاول إنشاء تدرج ألوان مختلفة عن الأسود و الأبيض .جرب مثلا من الأحمر إلى الأسود ،من الأخضر إلى
الأسود ،و من الأزرق إلى الأسود ،ثم ّ من الأحمر إلى الأبيض ،إلخ.
ملخّ ص
• يتم تحميل الـ SDLبواسطة الـ SDL_Initفي بداية البرنامج ،و يتم إيقافها باستعمال SDL_Quitفي النهاية.
• الأعلام هي ثوابت يمكن جمعها فيما بينها باستعمال الرمز | ،و هي تلعب دور الخواص.
• تقوم الـ SDLبالتعامل مع المساحات و التي هي عبارة عن مستطيلات من نوع . SDL_Surfaceالرسم على النافذة
يتم بالاستعانة بهذه المساحات.
315
الفصل .21إنشاء نافذة و مساحات
316
الفصل 22
إظهار صور
لقد تعلّمنا كيف نقوم بتحميل الـ ،SDLفتح نافذة و التعامل مع المساحات .إنها بالفعل من المبادئ التي تجب معرفتها
عن هذه المكتبة .لـكن لح ّد الآن لا يمكننا سوى إنشاء مساحات موحّدة اللون ،و هذا الأمر بدائي قليلاً.
في هذا الفصل ،سنتعل ّم كيف نقوم بتحميل صور على مساحات ،مهما كانت صيغتها PNG ،BMPأو حتى GIFأو
.JPGالتحكم في الصور أمر مهم للغاية لأنه بتجميع الصور )نسميها أيضا ً” (”spritesنضع اللبنات الأولى في بناء لعبة فيديو.
الـ SDLهي مكتبة بسيطة جداً .فهي لا تستطيع أساسا تحميل سوى صور من نوع ”) ”bitmapذات امتداد .( .bmp
لا تقلق ،فبفضل إضافة خاصّة بالـ) SDLالمكتبة ،(SDL_Imageسنرى بأنه بإمكاننا أيضا ًتحميل صور من صيغ أخرى.
للبدأ ،سنكتفي الآن بما تسمح لنا به الـ SDLبشكل قاعدي .سنقوم بدراسة تحميل صور .BMP
BMP الصيغة
الـ Bitmapهي صيغة غير مضغوطة )على عكس الـ ،GIF ،PNG ،JPGإلخ( .فعلي ّا ،هذا يعني الأمور التالية :
• يكون الملف سر يعا ًجدا ً من ناحية قراءته ،على عكس الصيغ المضغوطة التي يجب أن يتم فك الضغط عنها ،مما يكل ّفنا
بعض الوقت.
• جودة الصورة مثالية .بعض الصيغ المضغوطة )أفك ّر في الـ JPGخصوصا ،لأن الـ PNGو الـ GIFلا يغي ّرون في
الصورة( تقوم بتخريب جودة الصورة ،و هذا ليس هو الحال بالنسبة للـ.BMP
317
الفصل .22إظهار صور
في هذا الفصل سنقوم بالعمل على كثير من الصور .إذا أردت القيام بتجريب الشفرات بينما أنت تقرأ )و هذا ما يجدر
بك فعله !( ،فأنصحك بتنز يل حزمة الصور التي تحتوي كل الصور التي نحتاج إليها.
)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و سترى أن استعمالها سهل للغاية :
• : SDL_CreateRGBSurfaceتقوم بحجز مكان في الذاكرة من أجل تخزين مساحة ذات الحجم المطلوب )تكافئ
دالة .( malloc
• الحجم الذي نقوم بحجزه في الذاكرة من أجل المساحة يعتمد على حجم الصورة :اذا كان حجم الصورة هو 250 × 300
فستأخذ المساحة نفس الحجم.
318
BMP تحميل صورة.1.22
.BMP يتم ملأ المساحة بيكسلا ببيكسل بمحتوى الصورة،• من جهة أخرى
.( backgroundPosition ) ( و نحو كل المركّبات الموافقة لهاbackgroundImage ) و بهذا أكون قد أنشأت مؤش ّرا ً نحو مساحة
. SDL_LoadBMP تم إنشاء المساحة في الذاكرة و ملؤها من طرف الدالة
: توضح النتيجة
ّ ل شيء ! الصورة التالية
ّ و هذا كscreen نقوم بتسويتها على المساحة
319
الفصل .22إظهار صور
بما أننا الآن نجيد تحميل الصور ،يمكننا اكتشاف كيفية إرفاق أيقونة بالبرنامج .سيتم إظهار الأيقونة في أعلى يسار النافذة )و
أيضا ًفي شر يط المهام( .لح ّد الآن نحن لا نملك إلا ّ أيقونة افتراضي ّة.
؟
لـكن ألا يجدر بأيقونات البرامج أن تكون ذات الامتداد .ico؟
ل أنظمة
ل فالامتداد .icoلا يوجد إلا في نظام .Windowsالـ SDLٺتعامل مع ك ّ كلّا ،ليس شرطا ً ! على ك ّ
التشغيل باستعمالها نظاما خاصا بها :المساحة !
نعم ،أيقونة برنامج SDLماهي إلا مساحة بسيطة.
!
يجدر بالأيقونة أن تكون ذات حجم 16 × 16بيكسلز .بينما في Windowsيجب أن تكون بحجم 32 × 32بيكسلز
و إلا فستسوء جودتها .لا تقلق إذ يمكن للـ” SDLتصغير” أبعاد الصورة لتتمكن من الدخول في 16 × 16بيكسلز.
تم تحميل الصورة في الذاكرة بواسطة SDL_LoadBMPو بعث عنوان المساحة مباشرة إلى . SDL_WM_SetIcon
x
يجب أن يتم استدعاء الدالة SDL_WM_SetIconقبل أن يتم فتح النافذة ،أي أنه يجدر بها التواجد قبل
SDL_SetVideoModeفي الشفرة المصدر ية.
هذه هي الشفرة المصدر ية الكاملة .ستلاحظ أنني أضفت SDL_WM_SetIconمقارنة بالشفرة السابقة.
320
التحكم في الشفافية.2.22
مشكل الشفافية
حالي ّا( في المشهدOpenClassrooms سلف الموقعSite du Zéro فهو شعار، )لمن لا يعرفهZozor سنقوم بلصق صورة
:
321
الفصل .22إظهار صور
21 ;)SDL_FreeSurface(zozor
22 ;)(SDL_Quit
23 ;return EXIT_SUCCESS
} 24
لقد قمنا فقط بإضافة مساحة لنخز ّن فيها ،Zozorو التي نقوم بلصقها في مكان معيّن من المشهد :
؟
بالطبع يعود ذلك إلى الخلفية الزرقاء التي هي خلف ! Zozor
لأنك تعتقد أنه بوجود خلفية سوداء أو بن ّي ّة ،ربما سيكون المظهر لائقا ًأكثر ؟ لا بالطبع ،المشكل هنا هو أنه من اللازم
أن يكون شكل الصورة عبارة عن مستطيل ،أي أنه إذا قمنا بلصقها على المشهد ،سنرى خلفيتها ،مما يشو ّه المظهر.
يجب استعمال نفس اللون للخلفية على الصورة .هذه الأخيرة ستكون شفافة من طرف الـ SDLفي وقت التسو ية.
لاحظ كيف تبدو الصورة zozor.bmpمن ناحية أقرب :
322
.2.22التحكم في الشفافية
الخلفية الزرقاء إذا مـ ُختارة .لاحظ أنني اخترت اللون الأزرق بشكل عشوائي ،كان بإمكاني استعمال اللون الأحمر أو
الأصفر مثلاً .الشيء المهم هو أنه يجب على اللون أن يكون وحيدا ً و موحّدا .لقد اخترت اللون الأزرق لأنه ليس متواجدا
في صورة Zozorلأنني لو اخترت اللون الأخضر ،سأخاطر بجعل العشب الذي يتناوله الحمار )أسفل يسار الصورة( شفافاً.
لـكي نقوم بتحديد اللون الذي يجب أن تجعله SDLشفافاً ،يجب أن نستعمل الدالة . SDL_SetColorKeyيجب استدعاء
هذه الدالة قبل تسو ية الصورة.
هكذا نقوم بتحو يل اللون الذي خلف Zozorإلى الشفاف :
• المساحة التي يجب أن نقوم بتحو يلها إلى اللون الشفاف )هنا نتكلم عن .( zozor
• حدد بعد ذلك اللون الذي يجب أن يتم تحو يله إلى الشفاف .لقد استعملت SDL_MapRGBلإنشاء اللون بصيغة
عدد ) ( Uint32كما فعلنا بالسابق .كما ترى إنه اللون الأزرق ) (0, 0, 255الّذي سيتم تحو يله إلى الشفاف.
SDL_SetColorKey كملخص ،نقوم أولا بتحميل الصورة باستعمال ، SDL_LoadBMPثم ّ نحدد اللون الشفاف باستعمال
ثم نقوم بتسو ية المساحة باستعمال . SDL_BlitSurface
323
الفصل .22إظهار صور
هذه هي التقنية المبدئية التي ستعيد استعمالها كل الوقت في برامجك .تعل ّم كيف تتحكم جيدا ً بالشفافية لأنها من أساسيات
صنع لعبة تملك الح ّد الأدنى من الواقعية.
Alpha الشفافية
الشفافية Alphaتوافق شيئا ًآخر ،إنها تسمح بعمل ”مزج” بين صورة و خلفية .هذا نوع من التلاشي.
• قائمة الأعلام :ضع 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
( )مرئي ّة بالكامل255
190
75
325
الفصل .22إظهار صور
م
قيمة الشفافية ) 128 Alphaشفافية متوسطة( هي قيمة خاصّة و كثيرة الإستعمال بالـ .SDLهذا النمط من الشفافية
أسرع من ناحية حسابات المعالج مقارنة بالأنماط الأخرى .قد يكون من المهم لك معرفة هذه المعلومة خاصة إن
كنت تستعمل الشفافية Alphaبشكل كبير في برامجك.
• ،TGA
• ،BMP
• ،PNM
• ،XPM
• ،XCF
• ،PCX
• ،GIF
• ،JPG
• ،TIF
• ،LBM
• .PNG
بالمناسبة فإنه بالإمكان أن تتم إضافة صيغ أخرى للـ .SDLو هي المكتبات التي تحتاج إلى الـ SDLلـكي تعمل .يمكننا
تسمية هذا الأمر بالـ) add-onsبمعنى ”إضافات”( SDL_Image .هي واحدة من بين هذه المكتبات.
التنز يل
توجد صفحة خاصة من موقع الـ SDLتشير إلى المكتبات التي تستعملها الـ .SDLهذه الصفحة تحمل عنوان ”.”Libraries
ستجد رابطا ًفي القائمة اليسار ية.
ستلاحظ أن هناك الـكثير من المكتبات و أغلبها ليس من طرف المبرمجـين الأصليين للـ .SDLبل هم مبرمجون عاديون
يستعملون الـ SDLو يقومون باقتراح مكتباتهم الخاصة لتحسين هذه الأخيرة.
326
الـSDL_Image .3.22تحميل صيغ صور أخرى باستعمال
نز ّل النسخة التي تناسبك من القسم ”) ”Binaryلا تحم ّل الملفات المصدر ية ،لن نحتاجها !(.
إذا كنت تعمل على ،Windowsنز ّل الملف ، SDL_image-devel-1.2.10-VC.zipو هذا حتى و إن لم تكن
تستعمل البيئة التطوير ية ! Visual C++
التثبيت
• الـكثير من الملفات : 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
إن كنت تستعمل ،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الحقيقة أن ٺثبيت الـ
،PNG ،JPG) SDL_imageو هذا أمر عملي لأن هذه الدالة تتمكن من تحميل أي نوع من الملفات التي ٺتعامل معهاالـ
. إذ تقوم وحدها بتحديد نوع الملف من خلال امتداده.( إلخ،TIF و حتى الـGIF
؟
SDL_LoadBMP فيمكنك الآن نسيان أمر استعمال الدالة،BMP تستطيع أيضا ًفتح الصورSDL_Imageبما أن الـ
. لتحميل كل أنواع الصورIMG_Load و استعمال الدالة
SDL_Image ن
ّ ( فإGIF وPNG إذا كانت الصورة التي تحمّلها تملك الشفافية )كما هو حال الصور: شيء جيد آخر
ٍ تفع ّل تلقائي ّا الشفافية من أجل هذه الصورة ! مما يعني عدم وجود
. SDL_SetColorKey داع لاستدعاء الدالة
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_SetAlpha
• المكتبة SDL_imageتسمح بإدخال صور من أي ّة صيغة كانت ) ( ... ،PNG ،JPGباستعمال الدالة . IMG_Load
لـكن علينا تسطيب هذه المكتبة بالإضافة إلى الـ.SDL
331
الفصل .22إظهار صور
332
الفصل 23
ل من مرفقات الحاسوب )فأرة ،لوحة مفاتيح ( ... ،قادرة على إنتاج حدث .سنتعل ّم كيف نستقبل كل حدث
ك ّ
و نتعامل معه .تطبيقك سيصبح أخيرا ً تفاعلي ّا !
فعلياً ،ما هو الحدث ؟ الحدث هو عبارة عن إشارة ) (signalيتم إرسالها عن طر يق إحدى مرفقات الحاسوب
)) (peripheralsأو عن طر يق نظام التشغيل بذاته( إلى التطبيق .هذه أمثلة عن بعض الأحداث المألوفة :
الهدف من هذا الفصل هو تعل ّم كيفية معالجة الأحداث .يمكنك أخيرا ً القول للحاسوب ” :إذا نقر المستعمل في هذا
المكان ،قم بفعل كذا ،و إن لم يفعل ،قم بكذا .إذا حرّك الفأرة ،قم بكذا .إذا ضغط على الزر ، Qأوقف البرنامج .إلخ”.
ج حينما يقوم
لنتعو ّد على الأحداث ،سنتعل ّم كيف نتعامل مع أسهل حدث :طلب غلق البرنامج .هذا حدث يـُنت ُ
المستعمل بالنقر على الزر : 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في البداية لأننا نريد للحلقة أن ٺتكرر مادام المتغير contيحمل هذه القيمة )صحيح(.
ما إن يأخذ المتغير المنطقي القيمة ) 0خطأ( ،نخرج من الحلقة و يتوقف البرنامج.
هكذا إذا ً :لدينا لح ّد الآن حلقة غير منتهية لا تنتهي إلا إذا أخذ المتغير contالقيمة .0الأكثر أهمية هو ما نكتبه
في داخل تلك الحلقة.
استرجاع الحدث
• : SDL_WaitEventتقوم بانتظار إنتاج حدث .هذه الدالة نقول عنها تعطيلية لأنها توقف عمل البرنامج مادام لم
يتم إنتاج أي حدث.
بشكل غير معر ّف إلى حين إنتاج حدث م ُعين .و بهذا تستعمل المُعالج بنسبة .% 100
؟
لـكن ألا يجب أن نستعمل دائما ًالدالة SDL_WaitEventبما أنها لا تستعمل المـ ُعالج كثيرا ً ؟
كلّا ،لأنه توجد حالات لا يمكن الاستغناء فيها عن الدالة . SDL_PollEventو هي حالة الألعاب التي يتم فيها
تحديث الشاشة حتى و إن لم يكن هناك أي حدث.
فلنأخذ مثلا ًاللعبة : Tetrisتقوم الكتل بالنزول لوحدها ،لا يحتاج المُستعمل إلى إنتاج حدث من أجل حصول هذا الأمر
! لو استعملنا ، SDL_WaitEventسيبقى البرنامج مـ ُعطّلا و لن تتمكّن من تحديث الشاشة لإنزال الكتل !
335
الفصل .23معالجة الأحداث )(Event handling
؟
ماذا تفعل SDL_WaitEventلـكي لا تستهلك من الـم ُعالج كثيرا ً ؟
ل الوقت ما إن كان هناك حدث أم لا،
مجـبرة على البقاء في حلقة غير منتهية لـكي تختبر ك ّ
فبعد كل شيء ،الدالة ُ
أليس كذلك ؟
م
هذه الحالة ليست نفسها لو استعملنا SDL_PollEventلأن هذه الأخيرة قادرة على أن ت ُرجع لنا ” :لا يوجد
أي حدث”.
تحليل الحدث
الآن نحن نتوفر على متغير eventيحتوي على معلومات حول الحدث الذي تم إنتاجه.
يجب أن نرى المركّ ب event.typeو نختبر قيمته .غالبا ما نستعمل switchلاختبار الحدث.
؟
لـكن كيف لنا أن نعرف ما هي القيمة الموافقة للحدث ”أغلق البرنامج” مثلا ؟
الـ SDLتوف ّر لنا بعض الثوابت ،مما يسهّل كثيرا ً كتابة البرنامج .هذه الثوابت كثيرة العدد )بقدر وجود أحداث ممكن
حصولها في الحاسوب( .سنتعر ّف على هذه الثوابت بتقدّمنا في هذا الفصل.
336
.1.23مبدأ عمل الأحداث
.1ما إن يتم انتاج حدث ،ت ُرجع الدالة SDL_WaitEventالحدث في المتغير . event
.3نختبر بمساعدة caseنوع الحدث .لح ّد الآن ،نحن لا نتحقق إلا إذا ما كان الحدث يوافق ) SDL_QUITطلب
إغلاق البرنامج( ،لأنّها الحالة الوحيدة التي تهمّنا.
.4إذا كان الحدث هو ، SDL_QUITفهذا يعني أن المستعمل طلب إغلاق البرنامج .في هذه الحالة ،نعطي للمتغير
المنطقي contالقيمة .0في الدورة القادمة للحلقة ،سيكون الشرط غير محقق ،فيتوقف تشغيل البرنامج.
.5إذا لم يكن الحدث هو ، SDL_QUITمما يعني أنه قد حدث شيء آخر :قام المستعمل بالضغط على زر ،بالنقر على
الفأرة أو ببساطة قام بتحر يك الفأرة داخل النافذة .و بما أن هذه الأحداث لا تهمّنا ،لن نقوم بمعالجتها .لن نقوم
ل مرة إلى دورة جديدة ننتظر فيها وقوع حدث جديد )بمعنى آخر ،نعود
إذا بأي شيء :تقوم الحلقة بالانتقال في ك ّ
إلى النقطة .(1
الشفرة الكاملة
337
الفصل .23معالجة الأحداث )(Event handling
ل
هاهي الشفرة الكاملة .لا يوجد شيء صعب :إذا قمت بمتابعة الفصل إلى الآن ،يجدر بك أن تكون قد فهمت ك ّ
شيء .على أي حال فقد لاحظت أنّنا لم نقم إلا بإعادة كتابة ما تقوم به الدالة . pauseقارن هذه الشفرة بما تقوم به
ل شيء في الدالة . mainبالطبع ،من المستحسن
الدالة : pauseهو نفس الشيء ،إلا أنه في هذه الحالة نقوم بوضع ك ّ
نقل الشفرة إلى دالة أخرى على حدى كـ ، pauseلأن ذلك سيقل ّل من حجم الدالة mainو يجعلها أفضل من ناحية
فهم الشفرة.
إذا فهمت بداية الفصل ،فلن تواجه أي مشكل في التعامل مع أي نوع من الأحداث .لا يوجد ماهو أسهل.
لماذا هذا سهل ؟ لأننا الآن فهمنا طر يقة عمل الحلقة التكرار ية غير المنتهية ،كل ما ستقوم بفعله هو إضافة بعض الحالات
إلى الـ switchمن أجل تحليل أنواع أخرى من الأحداث .لا يفترض أن يكون هذا الأمر صعباً.
338
.2.23لوحة المفاتيح
معرفة أنه تم الضغط على زر من لوحة المفاتيح هو أمر جيد ،لـكن معرفة أي الأزرار تم الضغط عليه بالضبط هو أمر
أحسن !
يمكننا معرفة الزر الذي تم الضغط عليه بفضل مركّ ب مركّ ب مركّ ب المتغي ّر )أوف !( و الذي يُدعى . 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
م
أستعمل switchمن أجل الاختبار الأول لـكن كان بإمكاني استعمال ifببساطة.
ل مرة أميل إلى الاستعانة بالـ switchحينما أعالج الأحداث لأنني أختبر الـكثير من القيم المختلفة )عملياً،
في ك ّ
يتوفر لدينا الـكثير من الحالات في الـ ، switchعلى عكس هذا المثال(.
م
هذه المرة ،يتوقف البرنامج حينما نضغط على الزر Escأو إذا نقرنا على الرمز Xأعلى النافذة .و الآن بما أنك
تعرف كيف تغلق البرنامج بالضغط على زر معين ،أنت مـ ُخو ّل لاستعمال وضع الشاشة الكاملة إذا كان هذا ممتعا
لك )استعمل العـَلم SDL_FULLSCREENفي الـ ، SDL_SetVideoModeكتذكير(.
سابقا ً كنت قد منعتك من استعمال هذا الأسلوب في العرض خشية أننا لن نتمكن من غلق البرنامج )لأنه لن
يظهر لنا زرّ الإغلاق الّذي نضغط عليه لإيقاف البرنامج !(
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ننتظر حدثا ً ) ( SDL_WaitEventأو نقوم بالتحقق من وجود حدث لـكن لا نقوم بانتظار حدوث واحد
) .( SDL_PollEventحاليا نكتفي باستعمال . SDL_WaitEvent
.2نستعمل ) switchكبير( من أجل معرفة نوع الحدث الذي نحن نتعامل معه .نقوم بتحليل الحدث الّذي تلقّيناه
ثم نقوم ببعض الحسابات و العمليات.
.4أول شيء نفعله :نمسح الشاشة باستعمال . SDL_FillRectإن لم نقم بذلك ،ستبقى بعض ”آثار” الشاشة
السابقة في الشاشة الحالية مما يشوش المظهر.
.6أخيرا ،ما إن ننتهي من كل ذلك ،نقوم بتحديث العرض من أجل المستعمل و ذلك باستدعاء الدالة . SDL_Flip
342
.3.23تمرين :تحر يك 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بيكسل في ك ّ
لاحظ أننا لسنا ُ
و الآن ؟
بما أنني أعطيتك التوجيهات و حتى المخطط ،يجدر بك أن تكون قادرا ً على كتابة الشفرة التي تسمح بتحر يك Zozorفي النافذة
!
343
(Event handling) معالجة الأحداث.23 الفصل
344
.3.23تمرين :تحر يك Zozorبواسطة لوحة المفاتيح
48 ;)SDL_FreeSurface(zozor
49 ;)(SDL_Quit
50 ;return EXIT_SUCCESS
} 51
إنه من الضروري جدا ً الفهم الجيد لـكيفية عمل الحلقة الرئيسية في البرنامج .يجب أن تكون قادرا ً على كتابتها بنفسك.
استعن بالمخطط الذي أعطيتك إياه أعلاه إذا اقتضت الحاجة.
cont باختصار إذا ،توجد حلقة كبيرة تُسمى ”الحلقة الرئيسية في البرنامج” .و هي لا ٺتوقف إلا إذا أعطينا للمتغير
القيمة .0
في هذه الحلقة ،نقوم أولا باسترجاع حدث لنقوم بمعالجته .نستعمل switchمن أجل تحديد نوع الحدث .بدلالة
الحدث ،نقوم بعمليات مختلفة .في حالتنا هذه ،نقوم بتحديث مركبات وضعية Zozorمن أجل إعطاء انطباع أننا نقوم
بتحر يكه.
.2ثم تقوم بتسو ية المساحات على الشاشة .هنا ،لم أحتج إلى لصق إلا Zozorلأننا لا نحتاجه إلا هو كما هو واضح ،و
من المهم جدا ً أن نضع Zozorفي الموضع ! zozorPositionلان هذا ما يصنع الفارق :إذا كنتُ قد حدّثتُ
zozorPositionكان Zozorليظهر في مكان آخر و بهذا نعتقد أننا غي ّرنا مكانه كليا !
.3أخيراً ،و آخر شيء للقيام به SDL_Flip :لـكي نحدّث الشاشة من أجل المستعمل.
345
الفصل .23معالجة الأحداث )(Event handling
بعض التحسينات
يمكنك استدعاء هذه الدالة أينما أردت ،لـكنني أنصحك باستدعائها قبل الحلقة الرئيسية للبرنامج .يمكن للدالة أخذ
معاملين :
• المدة )بالميلّي ثانية( التي يجدر بالزر أن يبقى فيها مضغوطا ًقبل تفعيل تكرار الضغط على الأزرار.
• الأجل )بالميلّي ثانية( بين كل إنتاج لحدث SDL_KEYDOWNو آخر ما إن يتم تفعيل تكرار الضغط على الأزرار.
المعامل الأول يشير إلى مدة الزمن التي يجدر بنا بعدها إنتاج تكرار الضغط على الأزرار في المرة الأولى .أما الثاني فيشير
إلى الوقت اللازم ليتم إعادة إنتاج الحدث.
شخصياً ،و من أجل أسباب ليونة التحرك ،أعطي غالبا ًنفس القيمة للمعاملين .جرّب القيمة 10ميلّي ثانية :
ن هذا أحسن !
الآن ،يمكنك البقاء ضاغطا ًعلى نفس السهم .سترى أ ّ
انطلاقا من الآن ،سيكون من الجيد تفعيل تقنية الـ double bufferingالخاصة بالـ .SDLالـ double bufferingهي تقنية
مستعملة بكثرة في الألعاب .تسمح هذه التقني ّة بتجنب التقطع في الصورة.
لـِم َا ٺتقطع الصورة ؟ لأنه حينما نرسم في الشاشة ،المستعمل ”يرى” كيف ترسم و بهذا يرى كيف تُمسح الشاشة .حتى
وإن جرت العملية بسرعة فإن مخ ّ الإنسان يلتقط إشارات خفيفة و قد تكون مزعجة.
تقنية الـ double bufferingتعمل على استخدام ”شاشتين” :واحدة حقيقية )التي يراها المستعمل في شاشة الحاسوب(
و أخرى افتراضية )هي صورة يقوم الحاسوب بإنشائها في الذاكرة(.
هاتان الشاشتان ٺتناوبان :الشاشة Aتظهر في حين تحض ّر الشاشة Bالصورة القادمة في الخلفية .لاحظ الصورة التالية :
346
.3.23تمرين :تحر يك Zozorبواسطة لوحة المفاتيح
ما إن يتم رسم الصورة في الشاشة الخلفية )الشاشة ،(Bنقوم بقلب الشاشتين و ذلك باستدعاء الدالة . SDL_Flip
الشاشة Aتصبح شاشة خلفية و تقوم بتحضير الصورة القادمة ،بينما يتم إظهار الصورة في الشاشة Bو يراها المستعمل.
النتيجة :لا يوجد تقطع في الصورة !
لتحقيق هذا كل ما عليك فعله هو تحميل وضع العرض بإضافة الع َلم : SDL_DOUBLEBUF
م
الـ double bufferingهي تقنية معروفة جدا ً في البطاقة الرسومية ) (Graphics cardللحاسوب .ما أقصده هو أن
ل شيء ،و يتم ذلك بسرعة ج ّد فائقة.
الجهاز هو من يتحكم في ك ّ
ستتساءل ربّما لماذا كنا قد استعملنا SDL_Flipمن قبل دون الـ 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الفأرة
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
ما هي المعلومات التي يمكن استرجاعها حينما ننقر بالفأرة .لدينا معلومتان :
• الزر الذي قمنا بالضغط عليه )الزر الأيسر ؟ الأيمن ؟ الأوسط ؟(،
استرجاع زر الفأرة
يجب أن نرى أولا أي الأزرار تم الضغط عليها .من أجل هذا ،يجب تحليل المركب event.button.buttonو
مقارنة قيمته بإحدى القيم التالية :
• : SDL_BUTTON_MIDDLEالضغط بالزر الأوسط للفأرة )لا يملـكه كل شخص ،و هو يمثل غالبا النقر بالعجلة(.
م
الثابتان الأخيران يوافقان تحر يك عجلة الفأرة إلى الأعلى و الأسفل .و هما لا يوافقان ”النقر” على العجلة كما يمكن
أن نعتقد بالخطأ.
سنقوم باختبار سهل لنرى ما إن تم الضغط بالزر الأيمن للفأرة .إذا ضغطنا عليه ،نخرج من البرنامج) .أعرف أن هذا
ليس بقرار مناسب لـكن لـكي نجر ّب لا أكثر( :
)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الفأرة
فلنستمتع قليلا ً :سنقوم بلصق Zozorفي الوضعية التي توافق إحداثيات النقطة التي تم النقر عليها بالفأرة.
هل هذا صعب ؟ لا أبدا ً ! حاول فعل ذلك ،سترى بأنها لعبة أطفال !
هذه الشفرة تشبه الشفرة التي كتبتُها من أجل أزرار لوحة المفاتيح .هنا الأمر أسهل بكثير :نضع مباشرة قيمة xفي
المتغير zozorPosition.xو نفس الشيء بالنسبة لـ . y
ثم نقوم بلصق Zozorفي الإحداثيات الخاصة به ،و هاهي النتيجة :
351
الفصل .23معالجة الأحداث )(Event handling
إليك تمرينا ًسهلا ًجدا ً :لحد الآن ،نقوم بتحر يك Zozorمهما كان زر الفأرة الذي قمنا بضغطه .حاول ألا تحر ّكه إلا
إذا كان الزر المضغوط هو الأيسر .إذا تم الضغط على الزر الأيمن ،يتوقف البرنامج.
؟
لـكن ألا تقوم هذه العملية بإنتاج الـكثير من الأحداث بالنسبة للحاسوب ؟
!
احذر :لن نعمل بنفس المركّ بين اللذين استعملناهما من أجل النقر بالفأرة قبل قليل )سابقاً ،كانت
.( event.button.xالمركّبات المُستعملة تكون مختلفة في الـ SDLبحسب الحدث.
سنقوم بتحر يك Zozorإلى نفس إحداثيات الفأرة ،هنا أيضا .سترى أن العملية فعالة و بسيطة في نفس الوقت !
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
سنرى دالتين سهلتي الاستعمال لهما علاقة بالفأرة .هاتان الدالتان ستكونان مفيدتين قريباً.
يمكننا إخفاء مؤشر الفأرة بسهولة تامة ،يكفي أن نستدعي الدالة SDL_ShowCursorو نُعطيها عَلَما ً:
مثلا :
;)1 SDL_ShowCursor(SDL_DISABLE
353
الفصل .23معالجة الأحداث )(Event handling
يمكننا تحر يك مؤشر الفأرة يدو يا إلى الإحداثيات التي نريدها في النافذة.
نستعمل من أجل هذا SDL_WarpMouseو التي تأخذ كمعاملين الإحداثيتان xو yأين يجدر بالمؤش ّر أن يتواجد.
مثلا ،الشفرة المصدر ية التالية تقوم بتحر يك الفأرة إلى وسط النافذة :
م
حينما تستدعي ، SDL_WarpMouseيتم إنتاج حدث من نوع . SDL_MOUSEMOTIONنعم ،فالفأرة تحرّكت
! حتى و إن لم يقم المستعمل بذلك ،فقد كان هناك تحر ّك رغم ذلك.
• حينما يتم إنزالها إلى شر يط المهام الس ُفلي و حينما يتم استعادتها.
• حينما تصبح مفع ّلة )شاشة أولى( أو حينما تصبح غير مفعلة.
فلنبدأ بدراسة الحالة الأولى :الحدثُ الذي يُنتج حينما يتم تغيير مقاييس النافذة.
بشكل افتراضي ،تكون مقاييس النافذة غير قابلة للتعديل من طرف المستخدم.
أذك ّرك بأنه لتغيير ذلك ،يجب إضافة الع َلم SDL_RESIZABLEإلى الدالة : SDL_SetVideoMode
ما إن تتم إضافة هذا الع َلم ،يمكنك تغيير مقاييس النافذة .حينما تقوم بذلك ،يتم إنتاج حدث من نوع . SDL_VIDEORESIZE
354
.5.23أحداث النافذة
يمكننا استعمال هذه المعلومات من أجل أن نعمل على أن يكون Zozorمتمركزا ً دائما في وسط النافذة :
م
في البرمجة نتكلم عن التركيز ) .(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
ملخّ ص
م
هذه الشفرة ليست كاملة بطبيعة الحال .عليك اختبار قيمة المتغير 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
المكتبة SDLتقدّم ،مثلما رأينا ،عددا كبيرا ً من الدوال الجاهزة للاستعمال .يمكن ألا نستطيع التعو ّد عليها في البداية
لقلّة التطبيق.
هذا العمل التطبيقي الأول في هذا الجزء من الكتاب سيعطيك فرصة التطبيق و اختبار أشياء لم تسنح لك فرصة تجريبها.
أعتقد أنه بإمكانك التخمين ،فهذه المرة لن يكون التطبيق عبارة عن كونسول و إنما سيتحتوي على واجهة رسومية !
Sokoban بخصوص
الهدف من اللعبة
المبدأ بسيط :تقوم بتحر يك شخصية في متاهة .يجدر بالشخصية أن تقوم بدفع صناديق إلى مواقع محددة .لا يمكن للاعب
أن يدفع صندوقين في آن واحد.
حتى و إن كان المبدأ مفهوما ً و بسيطاً ،فهذا لا يعني أن اللعبة في ح ّد ذاتها سهلة ! إذ أنه يجب عليك أحيانا تكسير
ل اللغز.
رأسك بالتفكير لح ّ
359
Mario Sokoban الفصل .24عمل تطبيقي :
لأنها لعبة شعبية ،جيدة لأن تكون موضوعا ًبرمجيا ًو يمكننا إنشاؤها بواسطة ما تعلّمناه من الفصول السابقة.
يجب هنا أن نكون منظّمين .إذ أن الصعوبة لا تكم ُن في برمجة اللعبة في ح ّد ذاتها لـكن في ما إن نظّمنا العمل .و لهذا
فسنقوم بتقسيم البرنامج إلى عدّة ملفات .cبطر يقة ذكي ّة و نحاول إنشاء الدوال المناسبة.
من أجل هذا الأمر ،قررت تغيير الطر يقة بالنسبة لهذا العمل التطبيقي :لن أقدّم لك توجيهات و أقدّم التصحيح في
النهاية .بالعكس ،سأر يك كيف نقوم ببناء المشروع كل ّه من الألف إلى الياء.
؟
ماذا لو كنتُ أريد التدرّب لوحدي ؟
أنصحك مجددا ً أن تحاول برمجة اللعبة لوحدك ،حتى لو استغرقت 3أو 4أيام .اِفعل أحسن ما لديك .من المهم جدّا
أن تقوم بالتطبيق.
360
Sokoban .1.24مواصفات
المواصفات
المواصفات هي عبارة عن وثيقة نكتب فيها كل ما يجب على البرنامج أن يستطيع فعله.
• يجب أن يتم دمج مـُنشئ المستو يات ) (Levels editorفي البرنامج ليتمكن أي شخص كان من صنع مستو يات خاصة
به )هذا ليس أمرا ً ضرور يا ًلـكنه يعتبر إضافة مميزة !(.
يجب أن تعرف أنه هناك أشياء لا يجيد البرنامج القيام بها ،و يجب ذِكر ُ هذا الأمر أيضاً.
• برنامجنا قادر على التحكّم في مرحلة واحدة في المر ّة الواحدة .إن أردت أن تكون اللعبة عبارة عن ٺتالي جولات ،فما
عليك سوى برمجة ذلك بنفسك في نهاية هذا العمل التطبيقي.
ل الأشياء التي نريد القيام بها )خاصة مـُنش ِئ المراحل( تأخذ منا وقتا ًلابأس به.
على أي حال ،فك ّ
م
سأعطيك في نهاية العمل التطبيقي ،جملة التحسينات التي تُمكن إضافتها إلى اللعبة .و هذه ليست كلمات في الهواء،
لأنّها أفكار طب ّقتها أنا شخصي ّا في نسخة كاملة من اللعبة سأقترح عليك تنز يلها.
بالمقابل ،لن أعطيك الشفرة المصدر ية الخاصة بالنسخة الكاملة لأنني أريدك أن تعمل بنفسك و ٺتدرّب )لن
ل شيء على طبق من فضّ ة !(.
أعطيك ك ّ
في معظم الألعاب ثنائية الأبعاد ،أي ّا كان نوعها ،نسمّي الصور التي تشكّل اللعبة .Sprites
Mario في حالتنا ،قر ّرت إنشاء Sokobanو وضع الشخصية Marioلتكون اللاعب الرئيسي فيها )من هنا جاء اسم اللعبة
361
Mario Sokoban الفصل .24عمل تطبيقي :
.(Sokobanبما أن Marioشخصية لها شعبية كبيرة في عالم الألعاب ،2Dلن نتعب في الحصول على الـ spritesالخاصّة بهذه
الشخصي ّة .سنحتاج أيضا إلى spritesخاصة بالجدران ،الصناديق ،الأماكن المستهدفة ،إلخ.
إذا بحثت في Googleعن ” ”spritesفستحصل على عدّة نتائج .توجد العديد من المواقع التي توف ّر spritesخاصّة
بألعاب 2Dقد تكون لعبتها في السابق.
الشرح Sprite
جدار
صندوق
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غير موجودة !
ل مرة نبدأ بتحقيق مشروع مهمّ ،من الواجب أن نقوم بتنظيم العمل في البداية.
في ك ّ
بشكل عام ،أبدأ في إنشاء ملف ثوابت constants.hإضافة إلى ملف main.cيحتوي الدالة ) mainفقط هذه
ل شخص طر يقته الخاصة.
الدالة( .هذه ليست قاعدة لـكنها طر يقتي الخاصة في العمل ،و لك ّ
أقترح أن نقوم بإنشاء ملفات المشروع كل ّه الآن) ،حتى و إن كانت فارغة في البداية( .هاهي الملفات التي أنشئها إذا :
• : files.cالدوال الخاصّة بقراءة و كتابة ملفّات المستو يات )مثل .( levels.lvl
363
Mario Sokoban الفصل .24عمل تطبيقي :
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
– اسم الملف،
– اسم الكاتب )المبرمج(،
– لم أقم بهذا هنا ،لـكن عادة يفترض أيضا ً إضافة تاريخ كتابة الملف و تاريخ آخر تعديل عليه .هذا يسمح لك
بإ يجاد المعلومات بسرعة حينما تحتاج إليها و خاصة حينما يتعل ّق الأمر بمشار يع كبيرة.
• الملف محم ّي ضد التضمينات غير المنتهية .لقد استعملت لذلك التقنية التي تعلّمناها في نهاية فصل المعالج القبلي .هنا،
ل ملفاتي .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
المبدأ ينص على تضمين ملف الثوابت في ك ّ
هك ّذا ،أستطيع استعمال الثوابت في أي مكان من الشفرة المصدر ية الخاصة بالمشروع.
الدالة الرئيسي ّة الخاصة بالبرنامج سهلة جداً .هي تقوم بإظهار واجهة اللعبة ثم التوجيه إلى الق ِسم المناسب.
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
تلاحظ أنه ،لـكي أعطي أبعادا ً للنافذة ،أستعمل الثابتين 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تحتاج إلى معامل واحد :المساحة . screenبالفعل ،تم فتح النافذة في الدالة الرئيسية ،و لـكي تستطيع
الدالة playأن ترسم على النافذة ،يجب أن تقوم باسترجاع المؤش ّر نحو المساحة ! screen
لو تقرأ مجددا ً محتوى الدالة الرئيسية ،ستجد بأنني قمت باستدعاء الدالة playو ذلك بإعطائها المؤش ّر : screen
367
Mario Sokoban الفصل .24عمل تطبيقي :
;)1 play(screen
م
الدالة لا تقوم بإرجاع أي شيء )و من هنا الـ .( voidيمكننا أن نجعلها إن أردنا ت ُرجع قيمة منطقية تشير إلى
ما كنّا قد ر بحنا الجولة أم لا.
التصريح عن المتغيرات
ل المتغيرات ذات الأنواع المعر ّفة في الـ SDLالتي نحن بحاجة إليها :
لـكي نبدأ ،هاهي ك ّ
Mario لقد قمتُ بإنشاء جدول من نوع SDL_Surfaceيسمّى . marioو هو جدول من أربع خانات يقوم بتخزين
ل من الاتجاهات الأربعة )واحد للأسفل ،الأعلى ،اليمين و اليسار(.
في ك ّ
؟
بماذا ينفعنا currentMario؟
هو عبارة عن مؤش ّر نحو مساحة .و هو مؤش ّر يؤش ّر نحو المساحة الموافقة لـ Marioالمت ّجه نحو الإتجاه الحالي .أي أنه عبارة
عن Mario) currentMarioالحالي( الّذي سنقوم بتسويته في الشاشة .إذا رأيت في أسفل الدالة playستجد :
368
.3.24اللعبة
هنا تبدأ الأمور الهامّة حقّا .لقد قمت فعليا ًبإنشاء جدول ذو بُعدين .لم أكل ّمك عن هذا النوع من الجداول من قبل،
لـكنه الوقت المناسب لتتعل ّم ما يعنيه .ليس الأمر صعباً ،سترى ذلك بنفسك.
هو عبارة عن جدول من ) intأعداد صحيحة( يختلف في كونه يأخذ حاضنتين مرب ّعتين ] [ .إذا كنت ٺتذكر
جيدا ً الملف ، constants.hفـ NB_BLOCKS_WIDTHو NB_BLOCKS_HEIGHTهما ثابتان يأخذ كلاهما القيمة
.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]
370
.3.24اللعبة
حسب قيمة الخانة )و التي هي عدد صحيح( ،نعرف أي خانة من النافذة تحتوي جداراً ،أو صندوقاً ،أو منطقة
مستهدفة ،إلخ.
هنا بالضبط سنستفيد من تعر يف التعداد السابق !
إذا كانت قيمة الخانة تساوي (0) EMPTYسنعرف بأن هذه المنطقة من الشاشة يجب أن تبقى بيضاء .إذا كانت
تساوي (1) WALLفسنعرف أنه يجب أن نقوم بلصق صورة جدار ،إلخ.
تهيئات
تحميل المساحات
و الآن ،بما أننا قمنا بشرح كل متغيرات الدالة ، playيمكننا البدء في القيام ببعض التهيئات :
371
Mario Sokoban الفصل .24عمل تطبيقي :
ل بواسطة . IMG_Load
لا يوجد شيء صعب :نقوم بتحميل الك ّ
ل من الإتّ جاهات الأربعة في الجدول
إن كانت هناك حالة خاصّة ،فهي تحميل .Marioإذ أننا نقوم بتحميل Marioفي ك ّ
marioباستعمال الثوابت . RIGHT ، LEFT ، DOWN ، UP :كوننا استعملنا هنا ثوابت فستصبح الشفرة أكثر
وضوحا ً -كما تلاحظ . -كان بإمكاننا استعمال ] ، mario[0لـكن من الأفضل و من الأكثر وضوحا ً أن نستعمل
] mario[UPمثلا ً!
وجدت أنه من المنطقي أكثر أن أبدأ المرحلة فيما يكون Marioموجّها نحو الأسفل )أي نحونا( ،كان بامكانك أن
تكتب مثلا ً:
;]1 currentMario = mario[RIGHT
الآن ،يجدر بنا ملئ الجدول ثنائي الأبعاد . 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
ض ّد الشرط( يتوقف ك ّ
ل شيء يعمل بشكل جي ّد إذا ً و يمكننا المواصلة.
في الحالة الأخرى ،ك ّ
فهذا يعني أن اللاعب متواجد في آخر خانة في أسفل يمين الخر يطة.
يمكنك الرجوع إلى الصورة السابقة لتتوضح لك الأمور أكثر.
سنقوم بالتقدّم داخل الجدول 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 الفصل
الحلقة الرئيسية
.ً هي فقط كبيرة قليلا.إنها حلقة تقليدي ّة تعمل بنفس المخطط الذي تعمل به الحلقات التي رأيناها لح ّد الآن
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اللعبة
م جدا ً :نستدعي الدالة . movePlayerهذه الدالة ستقوم بتحر يك اللاعب في الخر يطة إن كان له
الآن ،شيء مه ّ
الحق في فعل ذلك.
• مثلاً ،لا يمكننا أن نُحرك Marioإلى الأعلى إن كان متواجدا ً أصلا ًفي الحافة العلو ية للنافذة.
• على العكس ،يمكننا تحر يكه للأعلى إن تواجد صندوق واحد فوقه.
• لـكن احذر ،لا يمكننا تحر يكه للأعلى إن تواجد صندوق واحد فوقه و كان هذا الصندوق متواجد أصلا ًفي الحافة
العلو ية للنافذة !
؟
يا إلاهي ! ماهذا السوق ؟
هذا ما نسمّيه بـمعالجة الاصطدامات ) .(Collisions managementو لـكي أضمن لك ،نحن نقوم بالتعامل مع
الاصطدامات البسيطة بما أن اللاعب يتحر ّك خانة بخانة و في أربع اتجاهات فقط .في لعبة ثنائية الأبعاد أين يتحر ّك اللاعب
ل الاتجاهات بيكسلا ببيكسل ،يكون التحكّم في الاصطدامات أمرا أصعب.
في ك ّ
لـكن هناك ماهو أسوء :الألعاب ثلاثية الأبعاد .التحكم في الإصطدامات في لعبة ثلاثية الأبعاد يُع ّد كابوسا ً بالنسبة
للمبرمجـين .لحسن الحظ ،توجد مكتبات للتحكّم في الاصطدامات في العوالم ثلاثية الأبعاد و التي تقوم بالـكثير من العمل في
مكاننا.
• الخر يطة :لـكي تستطيع قراءتها و أيضا ًالتعديل عليها إذا قمنا بتحر يك صندوق مثلاً.
• وضعية اللاعب :هنا أيضاً ،يجب على الدالة قراءة و ”ربما” تعديل وضعية اللاعب.
• الإتجاه الذي نطلب من اللاعب التوجّه إليه :نستعمل هنا أيضا ًالثوابت RIGHT ، LEFT ، DOWN ، UP :من
أجل فهم الشفرة بشكل أفضل.
375
Mario Sokoban : عمل تطبيقي.24 الفصل
ل شيء
ّ فلنسو ّي ك،التسو ية
مهما. قد تكون الخر يطة قد تغي ّرت و كذا وضعية اللاعب، في هذه الوضعية من البرنامج: switch لقد انتهينا من الـ
! لقد حان وقت التسو ية،كان
. لـكي نعرف أي عنصر سنقوم بتسويته و في أي منطقة من الشاشةmap نقوم بالتقدّم في الجدول ذو البعدين،و الآن
: خانة من الجدول144سنستعمل حلقتين كما رأينا سابقا ًللتقدّم في الـ
( لـكي نضع العنصر الحالي في المكان المناسبSDL_Rect )من نوعposition نحض ّر المتغير،من أجل كل خانة
.من الشاشة
: العملية ج ّد بسيطة
1 position.x = i � BLOCK_SIZE;
2 position.y = j � BLOCK_SIZE;
376
.3.24اللعبة
بعد ذلك ،نطب ّق switchعلى الخانة التي نقوم بتحليلها من الخر يطة.
هنا أيضاً ،استعمال الثوابت يعتبر شيئا ًعمليا ًو يسمح بقراءة م ُثلى للشفرة.
نختبر إذا ما إن كانت الخانة تساوي ، WALLفي هذه الحالة نقوم بلصق جدار .نفس الشيء بالنسبة للصناديق و المناطق
المُستهدفة.
اختبار الفوز
تلاحظ أنه قبل استعمال الحلقتين المتداخلتين ،نعطي القيمة الإبتدائية 0للمتغير المنطقي . remainingGoals
هذا المتغير المنطقي يأخذ القيمة 1ما إن نقوم بكشف منطقة م ُستهدفة على الخر يطة .حينما لا ٺتبقّى أية منطقة مستهدفة،
فهذا يعني أن كل الصناديق متواجدة فوق هذه المناطق )لم ٺتبقّ سوى صناديق .( BOX_OK
يكفي أن نختبر ما إن كان المتغير المنطقي يحمل القيمة ”خطأ” ،أي أنه لم ٺتبقّ أية منطقة مستهدفة.
في هذه الحالة ،نُعطي القيمة 0للمتغير contمن أجل إيقاف الجولة :
اللاعب
نحس ُب وضعيته )بالبيكسل هذه المرة( و ذلك بالقيام بعملية ضرب بين 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تختبر ما إن كان لدينا الحق في تحر يك اللاعب في الإتجاه المطلوب .تقوم بتحديث وضعية تذكير :
هذا النموذج خاص قليلاً .تلاحظ أنني أبعث الجدول mapو أحدد الحجم الخاص بالب ُعد الثاني
) .( NB_BLOCKS_HEIGHT
لماذا هذا ؟
الإجابة معقّدة قليلا ًلـكي أقوم بمناقشتها في وسط هذا الفصل .لـكي نبسّط الأمور ،لغة الـ Cلا ٺتكهّن بأننا نتحدّث عن
جدول ثنائي الأبعاد و أنه يجب أن نعطي على الأقل حجم الب ُعد الثاني لـكي تشتغل الأمور.
إذا ،حينما تبعث جدولا ًذا بُعدين إلى دالة ،يجب أن تحددّ حجم الب ُعد الثاني للجدول في النموذج .هكذا تعمل الأمور ،إن
الأمر ضروري.
أمر آخر :تلاحظ أن playerPositionتُسمى posفي هذه الدالة .لقد اخترت اختصار الاسم لـكي تسهل
كتابته بما أننا سنحتاج إلى كتابته عدّة مرات ،لـكي لا نتعب.
فلنبدأ باختبار الإتجاه الذي نريد التوجّه إليه و ذلك باستعمال switchضخم :
)1 switch(direction
{ 2
3 case UP:
4 /� etc �/
378
.3.24اللعبة
هكذا تقوم الخطة التي أعتمدها :أختبر كل الحالات الممكنة للاصطدامات حالة بحالة ،و ما إن أكشف عن اصطدام
)أي أن اللاعب غير متمكّن من التحر ّك( أضع الأمر breakلأخرج من الـ ، switchو بهذا أمنع التحر ّك.
• يوجد صندوقان معا ًفوق اللاعب )و هو غير قادر على دفع صندوقين(.
• يوجد صندوق فوق اللاعب و الصندوق متواجد في الحافة العلو ية للخر يطة.
سأر يك الاختبارات اللازمة من أجل التحر ّك نحو الأعلى .من أجل الحالات الأخرى ،يكفي تعديل الشفرة قليلا.
نبدأ بالتحقق ما إن كان اللاعب متواجدا ً أعلى النافذة .بالفعل ،لو نحاول أن نطلب الخانة ] map[5][-1مثلاً،
سيتوقف البرنامج بشكل خاطئ !
نبدأ إذا بالتأكد من أننا لن ”نتجاوز” الشاشة.
هنا أيضاً ،الأمر بسيط .نتحقق من عدم وجود جدار فوق اللاعب .إذا كان هناك واحد ،نتوق ّف ) .( 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
قررت معالجة تحر ّك الصناديق باستعمال دالة أخرى لأن الشفرة تبقى نفسها من أجل الإتجاهات الأربعة .يجب فقط أن
نتأكّد بأننا قادرون على التحر ّك )هذا ما كنتُ بصدد شرحه(.
سنبعث للدالة معاملين :محتوى الخانة التي نريد الذهاب إليها و محتوى الخانة التي تليها.
)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
تحر يك اللاعب
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;
ليست مطابقة تماما، عليك ملائمة الشفرة،سأترك لك عناء نقل الشفرة و تعديلها من أجل الحالات الأخرى )احذر
.(! مرة
ّ لّ في ك
. loadLevel •
. saveLevel •
381
Mario Sokoban الفصل .24عمل تطبيقي :
هذه الدالة تأخذ معاملا :الخر يطة .هنا أيضاً ،يجب تحديد مقدار الب ُعد الثاني للجدول لأننا نتكلم عن جدول ذو بعدين.
الدالة ت ُرجع متغيرا منطقيا ” :صحيح” إذا تم التحميل بنجاح” ،خطأ” إذا فشل.
الملف levels.lvlيحتوي على سطر و الذي هو عبارة عن ٺتالي أرقام .كل رقم يمث ّل خانة من المستوى ،مثلا :
]11111001111111111400000111110001100103310101101100000200121110 [...
سنقوم بتحليل محتوى . fileLineنحن نعرف أن أول 12محرفا تمثل السطر الأول ،الـ 12محرفا الموالية تمثل السطر
الموالي ،إلى آخره.
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احذر من الخلط بين الحروف و الأرقام !
;)1 fclose(file
;2 return 1
ASCII أخيراً ،تحميل المستوى من الملف لم يكن معقداً .الفخّ الوحيد الذي و ُجب تجن ّبه هو التفكير في تحو يل القيمة
’ ’0إلى الرقم ) 0نفس الشيء بالنسبة لـ .( ... ، 4 ، 3 ، 2 ، 1
383
Mario Sokoban الفصل .24عمل تطبيقي :
13 }
14 }
15 ;)fclose(file
16 ;return 1
} 17
استعملت الدالة fprintfمن أجل ”ترجمة” أعداد الجدول إلى حروف .ASCIIكانت هنا الصعوبة الوحيدة :تجب
عدم كتابة 0و إنما ’. ’0
• النقر باليسار يسمح بوضع شيء على الخر يطة .هذا الشيء يكون مخزّنا ً :افتراضي ّا ،نقوم بوضع الجدران بالنقر الأيسر
للفأرة .يمكننا تغيير الشيء الذي نريد وضعه في الخر يطة بالضغط على الأزرار المتواجدة في لوحة الأرقام :
.1جدار.
.2صندوق.
.3منطقة م ُستهدفة.
384
م ُنـشئ المستو يات.5.24
التهيئات
و،لصق” لدالة اللعبة- ولذلك فقط بدأت في كتابتها باستعمال ”نسخ. الدالة الخاصة باللعبة، تشبه هذه الدالة،بشكل عام
.بعد ذلك قمتُ بنزع ما لا أحتاج ُه و أضفت مميزات جديدة
385
Mario Sokoban الفصل .24عمل تطبيقي :
المتغير currentObjectيحفظ الشيء الذي يختاره المُستعمل حالياً .افتراضي ّا ،هذا الشيء هو . WALLأي أننا في
البداية إذا نقرنا بالزرّ اليسار سنقوم بوضع جدار ،لـكن يمكن تغيير هذا بواسطة المستعمل و ذلك بالضغط على ، 2 ، 1
3أو . 4
أخيراً ،يتم تحميل الخر يطة المحفوطة حاليا ًفي الملف . levels.lvlسيكون نقطة انطلاقنا.
معالجة الأحداث
هذه المرة سيكون علينا معالجة كثير من الأحداث المختلفة .هيا بنا ،واحدا ً واحداً.
SDL_QUIT
إذا ضغطنا على الزر ، Xٺتوقف الحلقة و نعود إلى القائمة الرئيسية.
ل بالنسبة لللاعب :فهو يريد الخروج من اللعبة و ليس الرجوع إلى القائمة ليكن في علمك أن هذا الشيء ليس أحسن ح ّ
الرئيسية .يجب أن نجد حلا ًلإيقاف البرنامج و ذلك بإرجاع قيمة خاصّة للدالة الرئيسية مثلاً .سأتركك لتجد حلا ًبنفسك.
SDL_MOUSEBUTTONDOWN
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
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
هنا يمكن لنا رؤ ية أهمية المتغيرات المنطقية .نختبر حينما نقوم بتحر يك الفأرة ما إن كان هناك نقر حالي .إذا كانت
هذه هي الحالة ،نضع على الخر يطة شيئا ًما )أو الفراغ إذا كان نقرا باليمين(.
ل تكرار للشيء ،يكفي إذا ً
ل مرة من أجل ك ّ
ح لنا بوضع شيء واحد لعدة مرات دون الحاجة إلى إلى النقر في ك ّ
هذا يسم ُ
أن نُبقي زر الفأرة مضغوطا ًبينما نسحبُ هذه الأخيرة.
ل مرة نحر ّك فيها الفأرة )يكون ذلك ببيكسل واحد( ،نختبر ما إن كانت المتغيرات المنطقية مفع ّلة.
الأمر واضح :في ك ّ
إذا كان الأمر كذلك ،نقوم بوضع شيء على الخر يطة .و إلاً ،لا نقوم بأي شيء.
سألخّص التقنية لأنها ستكون مفيدة من أجل برامج أخرى. ملخّ ص :
تسمح هذه التقنية بمعرفة ما إن كان زر الفأرة مضغوطا ً بينما يتم تحر يك هذه الأخيرة .يمكننا أن نستفيد من هذا الأمر
لبرمجة السحب و الإفلات ).(Drag and drop
SDL_KEYDOWN
تسمح أزرار لوحة المفاتيح بتحميل و حفظ المستوى و أيضا ًبتغيير الشيء المُختار من أجل النقر اليساري بالفأرة.
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 على
! وقت اللصق
.ل الأحداث
ّ لقد أتتممنا ك: ها نحن ذا
الشفرة التالية تشبه الشفرة التي استعملناها. لم يتبقّ لنا سوى لصق كل عناصر الخر يطة بمساعدة حلقتين متداخلتين،الآن
: سأعطيها لك لـكن ّي لن أعيد شرحها هنا.في دالة اللعبة
389
Mario Sokoban الفصل .24عمل تطبيقي :
لا يجب أن ننسى أن نحرر الذاكرة بعد الانتهاء من الحلقة الرئيسية بالشكل اللازم )باستعمال ( SDL_FreeSurface
:
1 ;)SDL_FreeSurface(wall
2 ;)SDL_FreeSurface(box
3 ;)SDL_FreeSurface(level
4 ;)SDL_FreeSurface(mario
ملخّ ص و تحسينات
هي ّا فلنلخّ ص !
و ماذا سيكون أحسن تلخيص من الشفرة المصدر ية الكاملة للعبة مع التعلقيات المفصّ لة ؟
• الملف التنفيذي للويندوز )إذا كنت تعمل على نظام تشغيل آخر ،تكفي إعادة الترجمة(.
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ثانية ،و هذا ما لا تجيد
فعله .على الأقل ،قبل أن تقرأ هذا الفصل.
في بادئ الأمر ،سنتعل ّم كيف نستعمل دالتين بسيطتين جدا ً :
• : SDL_GetTicksتقوم بإرجاع عدد الميلّي ثواني التي مضت منذ انطلاق تشغيل البرنامج.
هاتان الدالّتان سهلتان جدا ً كما سنرى لـكنّ استعمالهما ليس بسيطا ً كما يبدو الأمر عليه.
SDL_Delay
كما قلتُ ،تقوم هذه الدالة بإيقاف عمل البرنامج لمدّة محدّدة .حينما يكون البرنامج متوق ّفا ،نقول أن ّه ”ينام” ) : (sleepهو
لا يستعمل المُعالج.
يمكن إذا استعمال SDL_Delayللإنقاص من زمن اشتغال المُعالج ) .(Processorلاحظ أن ّني سأختصره إلى
CPUو هذا الإختصار متداول و يوافق العبارة ” ”Central Processing Unitو ال ّتي تعني ”وحدة المعالجة المركز ي ّة”.
ل شراهة لموارد المعالج .أي أننا لن نثقل على الحاسوب كثيرا ً إذا تم ّ
بفضل الـ SDL_Delayيمكنك جعل برامجك أق ّ
استخدام هذه الدالة بذكاء.
393
الفصل .25تحكّم في الوقت !
م
هذا كل ّه يعتمد على البرنامج الذي تـُنشئه :أحياناً ،نجد أنه من المستحسن أن يستعمل البرنامج المعالج بشكل أقلّ،
يمكن في نفس الوقت أن يقوم المُستعمل بشيء آخر مثلما هو الحال بالنسبة لقارئ MP3الذي يشتغل في الخلفية
ريثما تقوم بالتصفّح عبر الإنترنت.
لـكن أحياناً ،نحتاج للبرنامج أن يستعمل المعالج بنسبة ،100%و هو الحال بالنسبة لغالبية الألعاب.
الأمر واضح ،تبعث للدالة عدد الميلّي ثواني التي يجب أن ”ينام” البرنامج خلالها.
مثلا ً :إذا أردت أن ينام البرنامج لمدّة ثانية واحدة ،يجب عليك كتابة :
;)1 SDL_Delay(1000
!
لا يمكنك فعل أي شيء في البرنامج بينما هو متوق ّف مؤق ّتا ! فالبرنامج ”النائم” لا يمكن له فعل أي شيء لأنه ليس
مفع ّلا بالنسبة للحاسوب.
لا ،تأكّد بأنني لن أخوض في درس للفيز ياء الكمي ّة في هذا الفصل حول الـ ! SDLو مع ذلك ،أرى بأن هناك أمورا
يجب عليك معرفتها SDL_Delay :ليست دالة ”مثالية” .و هذا ليس خطأها ،بل هو خطأ نظام التشغيل )،Windows
.( ... Mac OS X ،GNU/Linux
لماذا يتدخّل نظام التشغيل هنا ؟ ببساطة لأنه هو الذي يتحكّم في البرامج المشغ ّلة ! فبرنامجك سيقول للنظام ” :سأنام،
أيقظني بعد ثانية” .لـكن لن يقوم النظام دائما بإفاقة البرنامج بعد ثانية بالضبط.
في الواقع ،قد يكون هناك تأخّر بسيط )تأخر 10ميلّي ثانية بالتقريب كمعدّل ،هذا يختلف حسب الحاسوب( .لماذا ؟ لأن
CPUلا يمكنه العمل إلا على برنامج واحد في المر ّة الواحدة .دور نظام التشغيل يتمث ّل في إخبار CPUبخصوص ما يجب أن
394
الـTicks .1.25الـ Delayو
تخي ّل الآن أنه لثانية من الزمن ،يكون برنامج آخر لازال في طور الإشتغال :يجب أن ينتهي عمله حتى يستطيع برنامجك
”استعادة التحكّم” على .CPU
ما الذي يجب تذك ّره ؟ أن CPUلا يمكنه أن يتحكّم في برنامجـين في آن واحد .و لـكي يعطي انطباعا ً بأنه يُشغ ّل العديد
ل دورا ً بدور.
من البرامج في نفس اللحظة ،يقوم بتقسيم الوقت بين هذه البرامج حيث تعم ُ
قَل ّت صح ّة هذا الكلام لأن المعالجات ”ثنائية النوى” لها القدرة على تشغيل برنامجـين في نفس الآن.
م
بسبب مشكل جزئي ّة الوقت ،لن تتمكّن إذا من إيقاف برنامجك مؤق ّتا لوقت قصير جدّا من الزمن ،أي أنه لو
استعملت ;) SDL_Delay(1ستكون متأكدا ً بأن البرنامج لن ينام لـ 1ميلي ثانية و إنما أكثر )حوالي 9أو 10
ميلي ثانية(.
الدالة SDL_Delayعملي ّة ،لـكن لا ٺثق بها كثيراً .فهي لا توقف البرنامج بالمقدار الزمني الذي تحدده أنت بالضبط.
هذا ليس راجعا ًلـكون الدالة غير م ُبرمجة جيداً ،لـكن لأن عمل الجهاز معقّد و لا يمكنه أن يكون دقيقا ًمن هذه الناحية.
SDL_GetTicks
هذه الدالة ت ُرجع عدد الميلي ثواني التي انقضت منذ بدأ عمل البرنامج .و هي عبارة عن مؤش ّر للزمن لا يمكن الاستغناء
عنه .ستجد بأنها مفيدة لـكي تضع مراجع في الزمن ،سترى ذلك !
هذه الدالة لا تنتظر أي معامل ،و هي تقوم فقط بإرجاع عدد الثواني المنقضية.
هذا العدد يتصاعد مع مضي الزمن .لمعلوماتك ،التوثيق الخاص بالـ SDLيشير إلى أن هذا العدد يصل إلى الحد الأقصى 49
ل هذا الوقت و لهذا فلا تقلق من هذه الناحية.
يوما ثم يبدأ العدد من جديد ! لـكن يجدر بالبرنامج الذي تكتبه ألا يستمر ّ ك ّ
395
الفصل .25تحكّم في الوقت !
إذا كانت الدالة SDL_Delayسهلة للفهم و للاسعمال ،فالأمر ليس عينه بالنسبة لـ . SDL_GetTicksحان الوقت
لنعرف كيف سنستفيد منها.
إليك هذا المثال ،سنسترجع البرنامج القديم الذي يقوم بإظهار نافدة تحتوي على ) Zozorالصورة التالية( :
هذه المرة ،في عوض التحكّم في حركة Zozorبالفأرة أو بلوحة المفاتيح ،سنعتمد فكرة أنه سيقوم بالتحر ّك لوحده في
الشاشة .لبنسّط الأمور ،سنجعله يتحر ّك أفقيا في النافذة.
سن ُعيد استعمال نفس الشفرة التي استخدمناها في فصل الأحداث ،يجدر بك أن تجيد كتابتها بنفسك دون الحاجة
لمُساعدة مني .و إلا ،إذا احتجتها ،يمكنك استعادتها من الفصول السابقة.
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
و الآن ،في حلقتنا غير المنتهية ،نضيف الشفرة المصدر ية التالية :
.2نقارن هذه القيمة بالوقت الذي تم تسجيله مسبقاً .إذا كان هناك فرق 30مث على الأقل ،إذا...
ل 30مث.
ل 30مث .هنا ،نقوم بتحر يكه إلى اليمين ك ّ
.3نحر ّك ،Zozorلأننا نريده أن يتحر ّك ك ّ
يجب ان نتأكد ما إن كان الوقت المنقضي أكبر من 30مث ،و ليس ما إن كان يساوي تلك القيمة ! لأنه في
ل
الواقع سنُخْب َر ما إن كان الوقت المنقضي يساوي على الأقل 30مث .نحن لسنا متأكدين بأنه سيتم ّ تنفيذ الأمر ك ّ
30مث بالضبط.
397
الفصل .25تحكّم في الوقت !
.4ثم ،و الأمر الذي لا يجب فعلا ًنسيانه ،نضع قيمة الوقت ”الحالي” في الوقت ”السابق” .بالفعل ،تخي ّل الدورة القادمة
للحلقة :الوقت الحالي يتغي ّر و يمكننا مقارنته بالوقت السابق من جديد ،أي سنقارن ما إن تم انقضاء 30مث على
الأقل ثم نحر ّك .Zozor
؟
و لـكن ماذا يحصل لو أن الحلقة اشتغلت لمدّة أقل من 30مث ؟
هذه الشفرة قصيرة ،لـكن يجب فهمها ! أعد قراءة شرحي بالعدد اللازم من المر ّات لتفهم جيدا ً لأن هذا الجزء قد
يكون الأهم في هذا الفصل.
الشفرة المصدر ية الذي كتبناها مثالية إلى ح ّد ما إذ ينقصها تفصيل بسيط :الدالة . SDL_WaitEventكانت هذه الدالة
عملي ّة إلى ح ّد الآن بما أننا لم نتحكّم في الوقت .هذه الدالة توقف البرنامج مؤق ّتا )بنفس طر يقة SDL_Delayتقريباً( ما
دام لا يوجد أي حدث.
لـكن هنا ،لسنا م ُضطرين إلى انتظار حدث لنقوم بتحر يك ! Zozorإذ يجب عليه التحر ّك لوحده.
و لا يجب عليك الاستمرار في تحر يك الفأرة فقط لإنتاج أحداث و منه الخروج من الدالة ! SDL_WaitEvent
ل ؟ . SDL_PollEvent
ماهو الح ّ
لقد قدّمت لك من قبل هذه الدالة :على عكس ، SDL_WaitEventت ُرجع هذه الدالة قيمة سواء كان هناك حدث
أم لا .و نقول بأن الدالة غير مـ ُعطّـِلة :هي لا توقف البرنامج مؤق ّتا لأن الحلقة غير المنتهية ستستمر ّ في العمل طوال الوقت.
398
Ticksالـ وDelay الـ.1.25
لـكي نرى. من المعالج100% و لهذا فهو يستهلك.(ً تقريبا، البرنامج يدور في حلقة غير منتهية بسرعة الضوء )حسنا،ًحاليا
: Windows ( فيProcesses )في القائمةDEL + ALT + CTRL هذا يكفي مثلا ُأن نضغط على
399
الفصل .25تحكّم في الوقت !
كما يمكنك أن ترى ،يتم استعمال CPUبنسبة 100%من طرف برنامجنا . testsdl.exe
لقد قلت لك مسب ّقا ً :إذا برمجت لعبة )خاصة إذا كانت بنظام شاشة كاملة( ،ليس خطيرا ً أن تستعمل المعالج بنسبة
.100%لـكن إذا كانت لعبة في نافذة مثلاً ،يُستحسن استعمال نسبة أقل من CPUلـكي نسمح للمستعمل بالقيام بشيء
آخر دون أن يُجهد الحاسوب نفسه.
الحل ؟ سنقوم بإعادة الشفرة السابقة ،لـكننا سنضيف إليه SDL_Delayمن أجل انتظار الوقت اللازم لـكي يصل
إلى 30مث.
كيف تعمل الأمور هذه المرة ؟ الأمر بسيط ،هناك احتمالان )حسب الشرط( :
• إما أنه مضت اكثر من 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تحكّم في الوقت !
!
استعمال المُؤقـ ِتات أكثر تعقيدا ً قليلا ً لأننا سنعتمد على مبدأ لم نره لح ّد الآن :المؤش ّرات نحو الدوال .استعمال
غض النظر عنها دون أي مشكل.
المُؤقـ ِتات ليس أمرا ً ضرور يا :إذا وجدت بأنها صعبة جدا ً لتستعملها ،يمكنك ّ
المُؤقـ ِتات تشكّل طر يقة أخرى لتحقيق ما نحن بصدد رؤيته بالدالة . SDL_GetTicks
هي تقنية خاصّة نوعا ًما .بعض المبرمجـين يجدونها عملي ّة ،و آخرون لا .هذا يعتمد على ذوقك البرمجي.
ماهو المُؤقـ ِت ؟
)(moveEnemy هو نظام يسمح بالطلب من الـ SDLأن تستدعي دالة ما كل Xميلّي ثانية .يمكنك بهذا أن تنشئ دالة
تقوم الـ SDLباستدعائها تلقائي ّا كل 50مث كي يستطيع العد ّو التحر ّك في مجالات معي ّنة.
م
كما كنت أقول لك الآن ،يمكننا القيام بهذا بواسطة SDL_GetTicksباستعمال التقنية التي رأيناها أعلاه.
ما الفائدة إذا ؟ لنقل أن المُؤقـ ِتات تفرض علينا هيكلة برامجنا بشكل أفضل على شكل دوال.
لـكي نتمكّن من استعمال المُؤقـ ِتات ،يجب علينا تهيئة الـ SDLأولا ً باستعمال ع َـلَم خاص . SDL_INIT_TIMER :يجب
عليك إذا استدعاء الدالة SDL_Initكالتالي :
لنضيف م ُؤقـ ِتا ً يجب علينا استدعاء الدالة . SDL_AddTimerإليك نموذجها :
توجد في الواقع دالتان تسمحان بإضافة م ُؤقـ ِت في الـ 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بتفعيل الـكثير من المُؤقـ ِتات في نفس الوقت .هذا يشرح الفائدة من تخزين هو ي ّة ك ّ
نفرق بينها.
ل 30مث.
• يتم استدعاؤه ك ّ
• يبعث له كمعامل ،مؤش ّرا ً نحو وضعية Zozorلـكي يتمكّن من التعديل عليه.
لقد فهمت المبدأ :دور الدالة moveZozorهو تغيير وضعية Zozorكل 30مث.
403
الفصل .25تحكّم في الوقت !
احذر :يجب أن تكون حذرا ً هنا .يجب أن يكون نموذج دالة الرد هو التالي إجبار يا :
لـكي ننشئ دالة الرد المسمّاة ، moveZozorيجب أن نكتب الدالة كالتالي :
إليك الشفرة الخاصة بالدالة ، moveZozorإنها معقّدة أكثر مما تبدو عليه :
يتم استدعاء الدالة moveZozorتلقائي ّا كل 30مث بواسطة الـ .SDLتقوم هذه الأخيرة ببعث معاملين تماما ً للدالة
)لا أكثر و لا أقل( :
• المعامل ”المخصّ ص” الذي طلبت إعطاءه للدالة .لاحظ ،و من المهم جداً ،أن هذا المعامل هو عبارة عن مؤش ّر نحو
SDL_Rect . voidهذا يعني أنه مؤش ّر يؤش ّر نحو أي نوع كان :على ، intهيكل مخصّ ص ،أو ،مثل هنا ،على
) .( zozorPosition
لاحظ أيضا ًأنه لا يمكن بعث أكثر من معامل مخصّ ص لدالة الرد .لحسن الحظ ،نحن دائما ًقادرون على إنشاء أنواع
خاصة بنا )أو جداول( و التي ستكون عبارة عن تجميع لمتغيرات نريد بعثها للدالة.
المشكل هو أن هذا المعامل هو مؤش ّر من نوع غير معروف ) ( voidللدالة .يجب إذا أن نقول للحاسوب أن هذا
المعامل هو *) SDL_Rectمؤش ّر نحو .( SDL_Rect
لفعل ذلك ،أنشئ مؤش ّرا نحو SDL_Rectفي دالتي التي تأخذ كمعامل المؤش ّر . parameter
؟
ن ليحمل نفس العنوان ؟
ما الفائدة من إنشاء مؤش ّر ثا ٍ
404
ملخّ ص
بعد ذلك ،السطر التالي بسيط :نعدّل قيمة zozorPosition->xلتحر يك Zozorنحو اليمين.
آخر شيء )مهم جداً( :يجب عليك إرجاع المتغير . intervalهذا يُشير للـ SDLبأننا نريد أن نستمر في اعتبار أنه
سيتم استدعاء الدالة كل 30مث.
إذا كنت تريد تغيير المجال الزمني لاستدعاء الدالة ،يكفي أن تبعث قيمة أخرى )في غالب الأحيان لا نفعل ذلك(.
يكفي إذا استدعاء SDL_RemoveTimerو ذلك بالإشارة إلى هو ية المُؤقـ ِت الذي نريد إيقافه.
هنا أوقف المُؤقـ ِت مباشرة بعد الحلقة غير المنتهية ،في نفس موضع . SDL_FreeSurface
ملخّ ص
• تمكّن الدالة SDL_Delayمن إيقاف البرنامج مؤق ّتا لعدد معين من الميلّي ثواني .هذا يسمح بإنقاص نسبة استعمال
المُعالج الذي لن يكون ”خلال نوم البرنامج” مستعملا ًمن طرف هذا الأخير.
• يمكننا معرفة عدد الميلّي ثواني المنقضية منذ اشتغال البرنامج باستعمال ، SDL_GetTicksبواسطة عمليات حسابية
بسيطة ،يمكننا الاستفادة من هذا لـكي نقوم بمعالجة غير م ُعطّلة للأحداث بواسطة . SDL_PollEvent
• المُؤقـ ِتات تشكّل نظاما يسمح باستدعاء دوالك )المسمّاة بدوال الرد( على مجالات زمني ّة محدّدة .يمكننا التحصل على
نفس النتيجة باستعمال SDL_GetTicksلـكن المُؤقـ ِتات تساعد على هيكلة البرنامج بشكل أفضل و جعله أحسن
من ناحية القراءة.
405
الفصل .25تحكّم في الوقت !
406
الفصل 26
يمكنني التكهّن بأن معظم القر ّاء قد طرح هذا السؤال من قبل ” :و لـكن ،ألا توجد أي دالة لـكي تكتب نصا ً على
نافذة SDL؟” حان الوقت لأجيبك :الجواب هو لا.
رغم ذلك ،توجد طرق لفعل هذا .يمكننا فقط ...وضع صور للحروف بجانب بعضها البعض على الشاشة .هذا الأمر
يعمل لـكن ّه ليس عمليا.
لحسن الحظ ،يوجد ماهو أبسط :يمكننا استعمال المكتبة .SDL_ttfإنها مكتبة تتم إضافتها إلى الـ SDLتماما ً مثل
الـ .SDL_imageدورها هو إنشاء مساحة SDL_Surfaceإنطلاقا من النص الذي نبعثه لها.
يجب أن تعرف أنه ،مثل SDL_ttf ،SDL_imageهي مكتبة تحتاج إلى أن تكون المكتبة SDLمثب ّتة من قبل .حسنا ً
:إذا كنت إلى ح ّد الآن لم تتمكّن من تسطيب المكتبة SDLفهذا أمر شنيع و لهذا فسأعتبر أنك قمت بذلك !
تماما مثل ،SDL_imageفإن المكتبة SDL_ttfهي واحدة من المكتبات المُرتبطة بالـ SDLالأكثر شعبية )أي أنه يتم
تنز يلها بكثرة( .كما ستُلاحظ ،هذه المكتبة م ُبرمجة بشكل جيد .ما إن تجيد استعمالها لن يمكنك أن ٺتوق ّف عن ذلك !
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 ٺثبيت
http://www.libsdl.org/projects/SDL_ttf/
م
في ،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_imageو لهذا سأسرع قليلاً.
بما أنني أعمل في الـ Code::Blocksسأعطيك العملية الخاصة بهذه البيئة التطوير ية .بالنسبة لباقي البيئات ،فالطر يقة لا
تختلف كثيرا ً عن هذه :
408
SDL_ttf .1.26تسطيب
• ستظهر لك هذه الرسالة ”Keep this as a relative path ?” :لا يه ّم ما تختاره لأن الأمر سيشتغل في كلتا الحالتين.
أنصحك أن تجيب بالسلب لأن المشروع لن يشتغل لو وضعته في مسار آخر غير المتواجد به لو أنك أجبت بالإ يجاب.
؟
ألا نحتاج إلى ربط المكتبة FreeTypeأيضا ً؟
SDL_ttf كلا ،مثلما قلتُ فـ FreeTypeمضمّنة في الـ DLLالخاصة بـ .SDL_ttfلهذا فلن يكون عليك الاهتمام بها ،لأن
تفعل ذلك الآن.
الملفات التوثيقية
و الآن بما أنك أصبحت مبرمجا ً محن ّكا ًتقريباً ،يجدر بك أن تطرح التساؤل التالي ” :لـكن أين هو التوثيق ؟” إن لم تطرح
هذا السؤال فهذا يعني أن ّك لازلت لم تصبح بعد مبرمجا ً محن ّكاً.
يوجد بالطبع دروس تفصّ ل في كيفية عمل المكتبات ،مثل هذا الكتاب ،و لـكن :
• لن أستطيع أن أضع لك فصلا ً حول كل المكتبات الموجودة )حتى لو أمضيت حياتي كل ّها في ذلك ،لن يكفيني
الوقت !( .و لهذا يجب عاجلا ًأم آجلا قراءة التوثيق و يجدر بك أن ٺتعو ّد على ذلك من الآن !
• من جهة أخرى ،في غالب الأحيان تكون المكتبة معقّدة نوعا ًما و تحتوي كثيرا ً من الدوال .لن أتمكن من تقديم
ل هذه الدوال في هذا الفصل لأنه سيكون بذلك طو يلا ًجدا ً !
ك ّ
من الواضح جدا ً أن التوثيق يكون كاملا و يلم ّ بكل خفايا المكتبة ،و لهذا أفضّ ل أن أعطيك من الآن رابط صفحة
التوثيق الخاصة بـ: SDL_ttf
http://sdl.beuc.net/sdl.wiki/SDL_ttf
التوثيق متوف ّر بصيغ مختلفة HTML :على الشبكة HTML ،مضغوطة ،PDF ،إلخ .خذ النسخة التي تناسبك.
ستجد بأن SDL_ttfمكتبة بسيطة جدا ً :يوجد بها قليل من الدوال )حوالي ،50 - 40نعم إنها قليلة !( .يجدر بهذا
أن تكون إشارة )للمبرمجـين المحن ّكين من ضمن القر ّاء( إلى أن هذه المكتبة سهلة و ستستطيع التعامل معها سر يعاً.
409
SDL_ttf الفصل .26كتابة نصوص باستعمال
التضمين
mingw32/include/SDL إذا صادفت أخطاء ترجمة الآن ،تأكد بأنك وضعت الملف SDL_ttf.hفي المجلّد
و ليس في mingw32/includeفقط.
SDL_ttf تشغيل
تماما مثل الـ ،SDLتحتاج SDL_ttfأن ُتشغ ّل في بداية الشفرة وتُوق ّف في نهايتها.
توجد دالتان تشبهان كثيرا ً الدالتين الخاصتين بالـ: SDL
• : TTF_Quitتوق ّف .SDL_ttf
م
ليس واجبا ًأن يتم بدء تشغيل SDLقبل .SDL_ttf
لـكي تقوم ببدء تشغيل ) SDL_ttfنقول أيضا ً تهيئة( ،يجب أن نستدعي الدالة . TTF_Initهذه الأخيرة لا تحتاج
إلى أن تستقبل أي معامل و هي تقوم بإرجاع القيمة −1إن حدث أي خطأ.
;)(1 TTF_Init
إذا أردت أن ٺتأكد ما إن كان قد حدث خطأ أم لا ،جرّب الشفرة التالية :
إذا كان هناك خطأ في تشغيل ،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_Fontلهذا يجب عليك إنشاء مؤش ّر من
نوع TTF_Fontكالتالي :
• اسم ملف الخط )بصيغة ( .ttfالذي نريد فتحه .الأمثل هو وضع ملف الخط في مجلّد المشروع .مثال عن ملف
) arial.ttf :من أجل الخط .(Arial
• حجم الخط الذي نريد استعماله .يمكنك مثلا استعمال حجم .22
إنها نفس الحجوم التي تستعملها في برامج معالجة النصوص مثل .Word
لم يتبقّ لنا سوى إ يجاد الخطوط ذات الصيغة . .ttfأنت تملك أصلا ًالعديد منها على حاسوبك ،لـكن يمكنك تنز يلها
من الأنترنت كما سنرى الآن.
411
SDL_ttf الفصل .26كتابة نصوص باستعمال
على حاسوبك
إذا كان اسم الملف يحتوي على حروف ”غريبة” كالفراغات ،الحروف ذات العلامات الصوتية ) (accentsأو حتى
الحروف الـكبيرة ،أنصحك بإعادة تسمية هذا الملف .و لـكي نكون متيقنين من عدم وجود أيّ مشكل ،لا تستعمل سوى
الأحرف الصغيرة و تجن ّب الفراغات.
على الأنترنت
خط من الأنترنت .ستجد الـكثير من المواقع التي تقترح خطوطا مجانية و أصلية للتنز يل.
الخيار الآخر :احصل على ّ
أنصحك شخصيا بز يارة الموقع dafont.comلأن ّه مصن ّف بشكل جي ّد و محتواه منظّم و منو ّع.
لاحظ الصور التالية التي ستعطيك فكرة عن الخطوط التي ستجدها هناك بسهولة :
تحميل الخط
412
.3.26الطرق المختلفة للكتابة
الخط المستعمل سيكون . angelina.ttfلقد قمت وضع هذا الخط في مجلّد المشروع كما قمت بإعادة تسميته لـكي
يكون كل ّه بحروف صغيرة.
خط خاص يستلزم ذلك لـكي يظهر بشكل جيد.
سيكون للخط الحجم .65ستبدو الكتابة كبيرة لـكنه ّ
الأمر المهم هو أن TTF_OpenFontتخز ّن النتيجة في المتغير ، fontستعيد استعمال هذا المتغير الآن بكتابة نص.
فهي تسمح بالإشارة إلى الخط الذي نريد أن نستعمله لـكي نكتب النص.
م
مرة واحدة في بداية البرنامج و أغلقه في نهايته.
ل مرة تريد فيها الكتابة به :افتحه ّ
لا تحتاج إلى فتح الخط في ك ّ
غلق الخط
يجب التفكير في غلق كل خط قمنا بفتحه قبل استدعاء . TTF_Quitفي حالتي ،هذا ما تكون عليه الشفرة :
;)(1 TTF_CloseFont(font); // Must be before TTF_Quit
;)(2 TTF_Quit
و الآن ،بما أنه تم تحميل SDL_ttfو أن لدينا متغيرا fontمحم ّلا هو الآخر ،لن يمنعنا أي شيء و أي شخص من كتابة
نص في نافذة ! SDL
• ) 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في الكتابة.
• ،Latin1
• ،UTF8
• ،Unicode
• .Unicode Glyph
414
.3.26الطرق المختلفة للكتابة
الأمثل أن تختار Unicodeلأنها مجموعة محارف تحوي أغلب الحروف و الإشارات الموجودة على وجه الأرض .و
لـكن ،استعمال الـ Unicodeليس سهلا دائما ً )محرف واحد يأخذ حجما أكبر من حجم charفي الذاكرة( ،فلن نرى
كيفية استعمالها هنا.
إذا كان برنامجك مكتوبا ًبالفرنسية فمجموعة Latin1تكفي بإسهاب ،يمكنك الاكتفاء بهذه الأخيرة.
• ، TTF_RenderText_Solid
• ، TF_RenderText_Shaded
• . TTF_RenderText_Blended
لـكيّ نختار لونا بـ ،SDL_ttfلن نستعمل نفس النوع كما بالـ) SDLإنشاء متغير من نوع Uint32بالاستعانة بالدالة
.( SDL_MapRGB
بالعكس ،سنستعمل هيكلا جاهزا من طرف الـ SDLو هو . SDL_Color :هذا الهيكل يحتوي ثلاثة مركّبات :كمية
الأحمر ،الأخضر و الأزرق.
!
احذر لـكي لا تخلط بينها و بين الألوان التي تستعملها عادة ! SDL
الـ SDLتستعمل متغيرات Uint32يتم إنشاؤها بمساعدة . SDL_MapRGB
بينما SDL_ttfتستعمل متغيرات . SDL_Color
أنت ترى المعاملات التي بعثتاها بالترتيب :الخط )من نوع ،( 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مسطّر.
بما أنها قائمة من الاعلام ،يمكنك الدمج بينها باستعمال الإشارة | كما تعلّمنا القيام بذلك سابقاً.
فلنجر ّب :
TTF_STYLE_NORMAL لإرجاع خط ما إلى حالته العادي ّة ،يكفي أن نعيد استدعاء الدالة TTF_SetFontStyleباستعمال العلم
هذه المر ّة.
تمرين :العداد
سيجمع هذا التمرين بين المفاهيم التي تعلّمتها في هذا الفصل و فصل التحكّم في الوقت .مهمّتك ،إن قبلتها ،هي إنشاء عداد
ل أعشار الثانية ،أي أنه سي ُظهر بشكل تقدّمي القيم التالية ... 400 ،300 ،200 ،100 ،0 :بعد ثانية،
ٺتصاعد قيمته ك ّ
يجدر بالرقم 1000أن يظهر.
418
.3.26الطرق المختلفة للكتابة
ل هذا التمرين ،ستحتاج إلى معرفة كيفية الكتابة داخل سلسلة محارف في الذاكرة.
لـكي تح ّ
في الواقع يجب عليك أن تعطي للدالة TTF_RenderTextمتغيرا من نوع * charلـكن ماهو متوف ّر لديك هو عدد
)من نوع intمثلا( .كيف يمكننا تحو يل عدد إلى سلسلة محارف ؟
x
كاف من أجل جدول charإذا أردت ألا تتجاوز في الذاكرة !
ٍ قم بحجز مكان
مثال :
هنا ،المتغير timeهو جدول محارف ) 20محرفا( ،و counterهو متغير من نوع intيحوي الزمن.
بعد هذه التعليمة ،سلسلة المحارف timeستحتوي مثلا على ”.”Temps : 500
التصحيح
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 }
420
ملخّ ص
لا تتردد في تنز يل المشروع إذا أردت دراسته بالتفصيل و تحسينه .هو ليس مثاليا ً بعد :يمكننا مثلا ً استعمال
SDL_Delayلتجن ّب استعمال المعالج بنسبة .100%
للذهاب بعيدا
إذا أردت التقدّم و تحسين هذا البرنامج ،يمكنك أن تحاول صنع لعبة أين يجب النقر بالفأرة العدد الأقصى من المرات
ل نقرة.
الممكنة في النافذة في وقت محدود حيث تتزايد قيمة العداد بعد ك ّ
يجب أن يتم إظهار عداد عكسي .حينما يصل إلى الصفر ،نظهر عدد النقرات التي تم القيام بها و نطلب من المستعمل
ما إن كان يريد إعادة المحاولة.
يمكنك أيضا ًمعالجة أفضل النتائج و تسجيلها في ملف .هذا سيساعدك في التدرب من جديد على استخدام الملفات في
الـ.C
حظا ًموفقا !
ملخّ ص
• لا يمكننا أن نكتب نصا ًفي الـ ،SDLإلا إن استعملنا تمديدا ً كالمكتبة .SDL_ttf
• تسمح هذه المكتبة بتحميل ملفات خطوط ذات صيغة .ttfبالاستعانة بالدالة . TTF_OpenFont
• توجد ثلاث طرق لكتابة نص ،ترتيبها من الأبسط إلى الأكثر تعقيدا ،Solid :ثم Shadedثم .Blended
421
SDL_ttf الفصل .26كتابة نصوص باستعمال
422
الفصل 27
منذ أن اكتشفنا الـ ،SDLتعلّمنا موضعة صور على النافذة ،التفاعل مع المُستعمل بالفأرة و لوحة المفاتيح ،كتابة نصوص،
لـكن ينقص أمر بالتأكيد :الصوت !
سيس ّد هذا الفصل ذلك النقص .بما أن الإمكاني ّات التي توف ّرها لنا الـ SDLمن ناحية الصوت محدودة جداً ،سنكتشف
هنا مكتبة متخصصة في الصوت .FMOD :
لماذا FMOD؟
أنت تعرف ذلك الآن :الـ SDLليست فقط مكتبة رسومية .هي تسمح أيضا ً بمعالجة الصوت عن طر يق وحدة تسمّى
.SDL_audioفلماذا إذا ً سنحض ّر مكتبة خارجية لا علاقة لها بالـ SDLكـ FMOD؟
في الواقع هو اختيار قمتُ به بعد عدّة اختبارات .كان بإمكاني أن أشرح لك طر يقة معالجة الصوت بالـ SDLلـكن ّي
فضّ لت عدم فعل ذلك.
سأشرح موقفي أكثر.
يعتبر التحكم في الصوت بالـ” 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بطر يقة .SDL_imageهي سهلة للاستعمال و تقرأ العديد من صيغ الصوت
المختلفة .لـكن ،و بعد الاختبارات التي قمتُ بها ،تبېّن لي أن هذه المكتبة تحتوي ع ِلَلا مزعجة بالإضافة إلى كونها محدودة
من ناحية المزايا التي تمنحها.
من أجل هذه الأسباب توجّهت مباشرة إلى ،FMODمكتبة لا علاقة لها بالـ SDLبالتأكيد ،لـكن لها الأفضلية كونها
قو ية و متداولا عليها.
إن كنت قد حكيت لك كل هذا ،فهذا فقط لأخبرك بأن اختيار 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استرجع الملف الموافق للمترجم .يجدر بملف نص ّي أن يشير إلى أي ملف يجب أن نأخذ.
425
بـFMOD الفصل .27تشغيل الصوت
.5أخيراً ،الشيء الأكثر أهمية ربّما ،يوجد مجلّد documentationفي المجلّد .FMOD Exمن المفروض أن تتم إضافة
ل ميزات
اختصارات إلى قائمة ”إبدأ” نحو هذه الملفات التوجيهية .أبق نظرك عليها لأنه لا يمكننا أن نكتشف ك ّ
FMOD Exفي هذا الفصل .ستحتاج إلى هذه الملفات في أقرب الآجال بالتأكيد.
ل شيء ،يلزمك أن تقوم بتضمين الملف الرأسي الخاص بـ .FMODلابأس في التذكير بكتابته :
قبل ك ّ
لقد وضعت هذا الملف في المجلّد الداخلي . fmodexعدّل على هذا السطر من الشفرة على حسب المسار الذي يتواجد
به الملف عندك.
إذا كنت تعمل على ،GNU/Linuxيجدر بالتسطيب أن يتم تلقائي ّا في المجلّد . fmodex
الغرض النظامي هو عبارة عن متغير نستفيد منه على طول البرنامج لـكي نعر ّف معاملات المكتبة.
تذك ّر أنه بالـ SDLمثلاً ،كان يجب أن نهي ّئ المكتبة بشكل مباشر بواسطة دالة .هنا ،دليل الاستعمال مختلف قليلا ً :في
ل المكتبة ،لن نعمل إلا بغرض ) (Objectدوره تعر يف سلوك هذه الأخيرة.
عوض تهيئة ك ّ
لـكي ننشئ غرضا نظاميا ،يكفي أن نعر ّف مؤش ّرا من نوع . FMOD_SYSTEMمثلا :
لـكي نحجز مكانا ًفي الذاكرة من أجل هذا الغرض النظامي ،نستعمل الدالة FMOD_System_Createو التي نموذجها
هو الآتي :
426
.2.27تهيئة و تحرير غرض نظامي
لاحظ أن هذه الدالة تأخذ مؤش ّرا نحو مؤش ّر يؤش ّر نحو . FMOD_SYSTEMالقر ّاء الأكثر حرصا ًكانوا قد لاحظوا أنه
لدى تعر يف المؤش ّر ، FMOD_SYSTEMلم يتم حجزه بواسطة mallocأو أي دالة أخرى .لهذا السبب تماما ًتأخذ الدالة
FMOD_SYSTEMمعاملا من ذلك النوع لـكي تحجز مكانا ًللمؤش ّر النظامي.
FMOD_System_Init هكذا إذا ،بما أننا نتوف ّر الآن على الغرض النظامي ،لم يتب ّق علينا سوى تهيئته .لفعل هذا ،نستعمل الدالة
ذات النموذج :
• المعامل systemهو المعامل الذي يهمّنا أكثر ،لأنه المؤش ّر الذي سنقوم بتهيئته.
• المعامل maxchannelsيمث ّل العدد الأقصى للقنوات التي يجب أن تديرها . FMODبمعنى آخر ،هو العدد الأقصى
للأصوات التي يمكن أن يتم تشغيلها في نفس الوقت .هذا يعتمد على قوة بطاقة الصوت لديك .ننصح عادة بقيمة
) 32قيمة كافية من أجل معظم الألعاب البسيطة( .لمعلوماتك ،يمكن نظر يا ًلـ FMODإدارة 1024قناة مختلفة،
لـكن بهذا المستوى ستخاطر بجعل حاسوبك يشتغل كثيرا !
• المعامل flagلا يهمّنا كثيرا ً في هذا الفصل ،سنكتفي بإعطائه القيمة . FMOD_INIT_NORMAL
مثلا ،لـكي نعر ّف ،نحجز ،و نهي ّئ غرضا نظاميا ،نقوم بكتابة التالي :
427
بـFMOD الفصل .27تشغيل الصوت
;)1 FMOD_System_Close(system
;)2 FMOD_System_Release(system
• صوت التصفيق.
• إلخ.
قبل أن نبدأ ،سيكون من الجيد أن نتعر ّف على بعض المواقع التي تقترح بنوكا ًمن الأصوات .بالفعل ،لا أحد يريد أن يبدأ
في تسجيل الأصوات بنفسه في المنزل.
سيكون الأمر جيدا ً فالأنترنت تقترح أصواتا قصيرة ،غالبا ًبصيغة .WAV
أين نجدها ؟ قد يبدو الأمر سخيفاً ،لا يجب علينا أن نفك ّر في ذلك )مع أنه لازم( ،لـكن Googleصديقنا .بشكل عشوائي،
أكتب ”Free Sounds” :و التي تعني ”أصوات مجانية” بالإنجليز ي ّة ،ستظهر لي ملايين النتائج.
لا شيء تحتاجه أكثر من الصفحة الأولى للبحث ،ستجد ضالّتك هناك.
شخصيا ً حفظت الموقع ،FindSounds.comمحر ّك بحث متخصص في الأصوات .لا أدري إن كان الأفضل ،لـكن على
أي حال هو موقع كامل.
428
.3.27الأصوات القصيرة
م
إذا لم تعرف ماهي الكلمات المفتاحية التي تستعملها في البحث ،توجّه إلى الصفحة الخاصة بأمثلة عن الكلمات
المفتاحية للبحث .يجب عليك أن تجيد بعض الكلمات الإنجليز ية بالتأكيد )لـكن على أي حال إن كنت تريد أن
تصبح مبرمجاً ،كيف ستفعل لو أنك لا تجيد على الأقل اللغة الإنجليز ية ؟(.
بالبحث عن كلمة ” ،”gunسنجد أطنانا من أصوات إطلاق النار بالبندقية ،لو نكتب ” ”doorسنجد أصوات تحر ّك
الباب )الصورة التالية( ،إلخ.
المؤش ّر
429
بـFMOD الفصل .27تشغيل الصوت
تحميل الصوت
الخطوة الثانية :تحميل الصوت بواسطة الدالة . FMOD_System_CreateSoundهي تأخذ ...خمسة معاملات :
• اسم الملف الصوتي الذي نريد تحميله .يمكن أن يكون ذو صيغة ،OGG ،MP3 ،WAVإلخ .من المستحسن دائما ًأن
ن كح ّد أقصى( على أن يتم تحميل أصوات طو يلة .في الواقع ،تحم ّل الدالة و تفك
يتم تحميل أصوات قصيرة )بضع ثوا ٍ
ل الصوت في الذاكرة ،مما قد يأخذ مكانا كبيرا لو أن الصوت هو موسيقى !
تشفير ك ّ
يهمّنا بشكل خاص هنا لأنه بفضله يمكننا أن نقول لـ FMODأن الصوت الذي ستشّغله هو عبارة عن صوت قصير.
من أجل هذا نستعمل القيمة . FMOD_CREATESAMPLE
• المعامل الأخير هو من نوع ، FMOD_SOUND ** soundو هذا المؤ ّشر سنستعمله لاحقا ًمن أجل تشغيل الصوت.
بشكل ما ،يمكننا القول بأن هذا المؤش ّر سيؤش ّر على الصوت الذي نريد تشغيله.
م
pan.wav إذا كنت تريد أن تختبر الشفرات في نفس الوقت الذي أعطيها لك ،أنصحك بتنز يل الصوت
)(http://www.siteduzero.com/uploads/fr/ftp/mateo21/pan.wav
و الذي سأستعمله أيضا ًفي بقي ّة هذا الفصل.
ل شيء على ما ي ُرام ،ت ُرجع الدالة القيمة FMOD_OKو إلا ،فهذا يعني أنه حدث مشكل خلال فتح
إذا اشتغل ك ّ
الملف الصوتي )ملف تالف أو غير موجود مثلا(.
تشغيل الصوت
430
.3.27الأصوات القصيرة
الأفضل الآن هو أن نلخّ ص كل ما تعلّمناه ،عن طر يق مثال واضح عن برنامج مكتوب بالـ.SDL
لا يوجد شيء معقّد هنا و يجدر ألا تصادفك أية مشكلة في تحقيق هذا التمرين.
الموضوع
• يتم تسو ية صورة المصو ّب على وضعية الفأرة حينما نقوم بتحر يكه .احذر :يجب أن يتم لصق مركز الصورة على مستوى
مؤش ّر الفأرة.
التصحيح
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
الصورة التالية تعطيك لمحة عن اللعبة المصغ ّرة ،لـكن الأفضل أن ترى الفيديو بالصوت على الأنترنت :
هنا ،حمّلت FMODقبل الـ SDLو حررتها بعدها .لا توجد قواعد من ناحية الترتيب )كان بإمكاني القيام بالعكس(.
اخترتُ تحميل الـ SDLو فتح النافذة بعد تحميل FMODلـكي تكون اللعبة جاهزة للاستعمال ما إن يتم فتح النافذة )و إلا
كان بالإمكان أن تنتظر بعض الميلي ثواني ريثما يتم تحميل .(FMOD
و مع ذلك أنت حر باختيار الترتيب ،هذه تفاصيل ليس إلا.
أعتقد أنني عل ّقت كفاية على الشفرة .لا يوجد فخ معيّن ،و لا يوجد جديد يذكر .يمكننا أن نذكر الصعوبة ”الصغيرة”
التي تنص على لصق مركز المصو ّب على مستوى مؤش ّر الفأرة .يتم حساب وضعية الصورة بدلالة ذلك.
بالنسبة لمن لم يفهم الفرق بعد ،سنرى ذلك الآن .بالمناسبة ،لقد أعدت تفعيل الإظهار الخاص بالمؤش ّر كي نرى كيف
يتموضع المصو ّب بالنسبة لمؤش ّر الفأرة.
433
بـFMOD الفصل .27تشغيل الصوت
أفكار للتحسين
هذه اللعبة تعتبر قاعدة للعبة إطلاق النار .لديك المصو ّب ،صوت إطلاق النار ،لم يتبقّ لك سوى إظهار أو تمرير أعداء
لـكي يقوم اللاعب بإطلاق النار عليهم ثم يتم تسجيل النتيجة .كالعادة ،عليك بالعمل وحدك .تريد صنع لعبة ؟ لا شيء
يمنعك ،بل لديك المستوى الكافي الآن ،و لديك أيضا ًشفرة مبدئية للعبة إطلاق النار ! ماذا تنتظر ،صدقا ً؟
م
طبعاً ،منتديات OpenClassroomsستساعدك في حال علقت في أيّ لحظة أثناء إنشائك للعبة .مواجهة الصعوبات
أمر عادي مهما كان المستوى الذي أنت فيه.
نظر ياً ،يسمح الع َلم 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
م
متيقّن جدا ً أن الأذواق و الألوان لا مجال للنقاش فيهما .لا بأس باختيارك لأغنية أخرى إن كانت هذه لا
تُعجبك.
ل هذه الأعلام في نفس الوقت ،نستعمل العامل المنطقي | بهذه الطر يقة :
لـكي نستعمل ك ّ
435
بـFMOD الفصل .27تشغيل الصوت
هكذا هو العمل !
ل شيء .في حالة موسيقى ،سيكون من الجيد أن نعرف كيف نغي ّر من قو ّة الصوت ،نتحكّم في إعادة
لـكن هذا ليس ك ّ
الأغنية مرات عديدة ،إيقافها مؤق ّتا ،أو حتى إيقافها كلياً .هذا النوع من الأشياء ما سنراه الآن .لـكن قبل هذا ،سنحتاج
أن نعمل على القنوات بشكل مباشر.
في نسخ سابقة من المكتبة ،FMODرقم الهو ي ّة البسيط لقناة يكفي لـكي يغي ّر قو ّة الصوت أو إيقاف أغنية مؤق ّتا.
طرأ تغيير صغير منذ : FMOD Exمن خلال رقم القناة ،نستعمل دالة توف ّر لنا مؤش ّرا ً نحو القناة .الفكرة تبقى نفسها ،ٺتغي ّر
طر يقة التنفيذ فقط.
يتم تعر يف قناة كنوع FMOD_CHANNELو الدالة التي تسمح باسترجاع قناة إنطلاقا ًمن رقم الهو ية هي
. FMOD_System_GetChannel
مثلاً ،إذا كان لدي غرض نظامي و أردت استرجاع القناة رقم ،9يجب أن أكتب :
• المعامل الثالث هو عنوان المؤش ّر الذي نريد تخزين المعلومة المُرادة فيه.
ما إن نتحصّ ل على مؤش ّر القناة ،يمكننا بسهولة التعامل مع الموسيقى )تغيير قوة الصوت ،إيقاف الموسيقى مؤق ّتا.( ... ،
لاحظ أنه يمكننا أيضا ً استرجاع مجموعة كاملة من القنوات في مؤش ّر واحد :بهذا نتجن ّب أن نقوم بنفس العملية من
ل قناة مختلفة.
أجل ك ّ
نوع مجموعة القنوات هو 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
غالبا ً ما نحتاج إلى إعادة تشغيل الموسيقى الخلفية .هذا تماما ما تقترحه الدالة FMOD_Sound_SetLoopCountو التي
تأخذ معاملين :
• عدد المرات التي يجب فيها أن تتم إعادة قراءة الموسيقى .إذا وضعت القيمة ،1تتم إذا قراءة الأغنية مرتين .إذا
وضعت قيمة سالبة )مثل ،(−1تتم إعادة قراءة الأغنية إلى ما لانهاية.
بهذه الشفرة المصدر ية ،يتم تكرار الأغنية إلى ما لانهاية :
x
كمعامل ثالث للدالة FMOD_LOOP_NORMAL لـكي تشتغل عملية إعادة التشغيل ،يجب أن نبعث
. FMOD_System_CreateSound
• : FMOD_Channel_GetPausedتشير ما إذا كانت الأغنية التي يتم تشغيلها حاليا ً في القناة المختارة في حالة
متوقفة مؤقتا أم لا .تعطي القيمة ”صحيح” للمتغير stateإذا كانت الأغنية متوقفة مؤقتا ،و القيمة ”خطأ” ما إن
كانت تشتغل حالياً.
• : FMOD_Channel_SetPausedتوقف الأغنية مؤقتا أو تعيد تشغيلها في القناة المشار إليها .أعطها القيمة 1
)صحيح( لإيقافها مؤقتا .و ) 0خطأ( لإعادة تفعيل القراءة.
437
بـFMOD الفصل .27تشغيل الصوت
هذه الشفرة المصدر ية الخاصة بنافذة SDLتقوم بإيقاف الأغنية مؤقتا إذا ضغطنا على الزر Pمن لوحة المفاتيح ،و
تعيد تفعيلها إذا ضغطنا مجددا ً على . P
إيقاف التشغيل
يمكننا القيام بالـكثير من الأشياء الأخرى ،لـكن لن أقوم بتعدادها كل ّها هنا .يجب عليك قراءة الملفات التوجيهية ! و التي
أنصحك بإلقاء نظرة عليها في حال ما احتجت الاطلاع على دوال أخرى.
تحرير الذاكرة
لـكي نقوم بتفر يغ الأغنية من الذاكرة ،نستدعي الدالة FMOD_Sound_Releaseو نعطيها المؤش ّر :
;)1 FMOD_Sound_Release(music
438
(OGG ،MP3 ،WAV) الموسيقى.4.27
.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 }
.لـكي لا تكون خلفية البرنامج مجر ّد صورة سوداء استعملت صورة الألبوم كخلفي ّة
ملخّ ص
.FMOD مزايا محدودة بالنسبة للصوت و يُنصح أن تتم الاستعانة بمكتبة مخصصة لتشغيل الصوت مثلSDL• لدى الـ
.(ً أصوات قصيرة )ضجيج الخطوات مثلا( و أصوات طو يلة )موسيقى مثلا: FMOD• نمي ّز بين نوعين من الصوت بالـ
.ل من هذين النوعين يُقرأ بنفس الدالة لـكن بواسطة أعلام مختلفة كخيارات
ّ • ك
440
الفصل 28
هذا العمل التطبيقي سيقترح عليك التعامل مع الـ SDLو الـ FMODفي نفس الوقت .هذه المر ّة ،لن نعمل على لعبة.
كما نعرف فالـ SDLمخصصة لهذا ،لـكن يمكن استعمالها في ميادين أخرى .سيقوم هذا الفصل بإثبات أنها صالحة لأجل
أشياء أخرى.
سنحقق هنا إظهارا ً للطيف الصوتي بالـ .SDLيتوق ّف هذا على إظهار تركيبة الصوت الذي نشغ ّله ،مثلا ًموسيقى .نجد
أمر ممتع و ليس بقدر الصعوبة التي يبدو عليها !
هذه الخاصية في كثير من برامج قراءة الأصوات .إنه ٌ
سيسمح لك هذا الفصل بالعمل على مفاهيم قُمنا باستكشافها مؤخّرا ً :
• التحكّم في الوقت.
• المكتبة .FMOD
سنتعر ّف علاوة على ذلك ،على كيفية التعديل على مساحة بيكسلا ببيكسل.
441
الفصل .28عمل تطبيقي :الإظهار الطيفي للصوت
هو نوع الإظهار الذي نجده في قارئي الأصوات كـ Windows Media Player ،Winampأو .AmaroK
كما قلتُ لك إن الأمر ليس صعبَ التحقيق .على عكس العمل التطبيقي الخاص بـ ،Mario Sokobanهذه المر ّة ستقوم
بنفسك بالعمل .سيمث ّل هذا بالنسبة إليك تمرينا ًجيداً.
1.28التعليمات
لـكي تبدأ ،يجب عليك إنشاء برنامج يقوم بقراءة ملف .MP3ليس عليك سوى إعادة الأغنية ” ”Homeللمجموعة ””Hype
و التي استعملناها في الفصل الخاص بـ FMODلتلخيص كيفية عمل تشغيل الموسيقى.
إذا اتّبعت جي ّدا الفصل حول ،FMODلا تحتاج أكثر من بضعة دقائق لـكي تقوم بالعملية .أنصحك بالمناسبة أن تقوم
بنقل الملف MP3إلى مجلّد المشروع.
لـكي نعرف كيف يعمل الإظهار الطيفي للصوت ،من الواجب أن أشرح لك كيفية يعمل الأمر من الداخل )بشكل
تقريبي فقط ،و إلا سندخل في درس ر ياضيات(.
يمكن أن يتم تقسيم الصوت إلى ترددات ) .(Frequenciesبعض الترددات منخفضة ،بعضها متوسطة و بعضها مرتفعة.
ل واحدة من الترددات على شكل شرائط و كل ّما يكون الشر يط كبيراً،
ما سنقوم به في عملية الإظهار هو إظهار كمية ك ّ
كلما يكون التردد مستعملا ًأكثر :
442
.1.28التعليمات
على يسار النافذة ،نقوم بإظهار الترددات المنخفضة ،و على اليمين الترددات المرتفعة.
؟
ل تردد ؟
لـكن كيف نسترجع كمي ّة ك ّ
• القناة التي تشتغل فيها الموسيقى .يجب إذا استرجاع مؤش ّر نحو هذه القناة.
لـFMOD • جدول . floatيجب أن يتم حجز الذاكرة من أجل هذا الجدول مسب ّقاً ،بشكل ثابت أو حيّ ،لـكي نسمح
بملئه بشكل صحيح.
• حجم الجدول .يجب أن يكون حجم الجدول إجبار يا ًعبارة عن قو ّة للعدد ،2مثلا .512
• يسمح هذا المعامل بتعر يف بأي مخرج نحن مهتمون .مثلا ًلو أننا في ،stereoفـ 0تعني اليسار و 1تعني اليمين.
• هذا المعامل معقّد قليلاً ،و لا يهمّنا حقيقة في هذا الفصل .سنكتفي بإعطائه القيمة
. FMOD_DSP_FFT_WINDOW_RECT
م
double تذكير :النوع floatهو نوع عشري ،مثل . doubleالاختلاف بين الاثنين يكمن في كون الـ
أكثر دق ّة من الآخر ،لـكن في حالتنا يكفينا الـ . floatهذا الأخير مستعمل من طرف FMODهنا .و لذلك،
هو ما سنستعمله نحن أيضاً.
ثم ،حين يتم تشغيل الموسيقى ،نطلب من FMODملء جدول الأطياف بالقيام مثلا ًبـ :
يمكننا بعد ذلك تصفّح الجدول لـكي نتحصّ ل على قيم الأطياف :
443
الفصل .28عمل تطبيقي :الإظهار الطيفي للصوت
ل
ينص عملك على إظهار ك ّ
ّ ل تردد هو عبارة عن عدد عشري محصور بين ) 0لا شيء( و ) 1قيمة قصوى(.
ك ّ
ل من خانات الجدول.
شر يط سواء كان قصيرا ً أو كبيرا ً بدلالة القيمة التي تحتويها ك ّ
مثلاً ،إذا كانت القيمة هي 0.5يجدر بك رسم شر يط يكون عل ّوه مساو يا ً لنصف علو ّ النافذة .إذا كانت القيمة هي
ل علو النافذة.
،1فسيأخذ الشر يط ك ّ
؟
لـكن يجدر بالشرائط أن تتحر ّك في نفس الوقت الذي يتم فيه تشغيل الصوت ،أليس كذلك ؟ بما أن الصوت يتحر ّك
ل الوقت ،يجب تحديث الصورة الرسومية ،ما العمل ؟
ك ّ
سؤال جيد .في الواقع ،الجدول الخاص المتكون من float 512الذي ترجعه لنا FMODيتغي ّر كل 25مث
)لـكي نكون في نفس الفاصل الزمني بالنسبة للصوت الحالي( .يجب إذا في الشفرة المصدر ية أن تعيد قراءة جدول الـ512
ل 25مث( ،ثم تقوم بتحديث رسمك ذي الشرائط.
) floatبإعادة استدعاء FMOD_Channel_GetSpectrumك ّ
أعد قراءة الفصل حول التحكّم في الوقت بالـ SDLلـكي ٺتذك ّر كيفية عمل ذلك .لديك الخيار بين GetTicksو
الـ .callbacksاستعمل ما تراه أكثر سهولة لك.
في البداية ،يمكنك تحقيق الشرائط بلون موحّد .يمكنك إذا إنشاء مساحات .يجب إذا أن تكون هناك 512مساحة :
ل
ل مساحة تأخذ إذا بيكسلا واحدا كع ُرض .و يختلف علو ّ الشرائط بدلالة شدّة ك ّ
ل شر يط .ك ّ
واحدة من أجل ك ّ
تردد.
أنصحك بعدها أن تقوم بتحسين :يجب على الشر يط أن يميل للأحمر كل ّما زادت كثافة الصوت .أي أنه على الشر يط
أن يكون أخضرا ً من الأسفل و أحمرا ً من الأعلى.
444
.1.28التعليمات
؟
ن واحدٍ لو عندما نستعمل الدالة . SDL_FillRectلا
لـكن ...المساحة الواحدة لا يمكنها أن تأخذ سوى لو ٍ
يمكننا إنشاء تدرّح لوني !
في الواقع ،لا تقترح الـ 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عمل تطبيقي :الإظهار الطيفي للصوت
• المؤش ّر نحو المساحة التي تريد التعديل عليها )يجب أن تكون معطّلة بواسطة .( SDL_LockSurface
• وضعية الفاصلة الخاصة بالبيكسل الذي نريد التعديل عليه في المساحة ) .( x
• وضعية الترتيبة الخاصة بالبيكسل الذي نريد التعديل عليه في المساحة ) .( y
• اللون الجديد الذي نعطيه للبيكسل .يجب أن يكون هذا اللون بصيغة . Uint32يمكنك إذا توليده بالاستعانة
بالدالة SDL_MapRGBالتي ٺتقننها جيدا ً الآن.
• أخيراً ،حينما تنتهي من العمل على المساحة ،يجب ألا تنسى أن تز يل تعطيلها باستدعاء . SDL_UnlockSurface
;)1 SDL_UnlockSurface(screen
ل شيء سهل.
لو نلخّ ص ،ستجد بأن ك ّ
هذه الشفرة ترسم بيكسلا أحمرا في منتصف المساحة ) screenأي في منتصف النافذة(.
من هذه القاعدة ،يجدر بك أن تتمكن من تحقيق التدرّج اللوني من الأخضر للأحمر )يجب أن تستعمل الحلقات
التكرار ية(.
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
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.zip
x
يجب قطعا ًأن يكون الملف Hype_Home.mp3متواجدا ً في مجلّد المشروع لـكي يشتغل البرنامج )و إلا فسيتوقف
حالاً(.
451
الفصل .28عمل تطبيقي :الإظهار الطيفي للصوت
أفكار للتحسين
يمكن دائما تحسين البرنامج .هنا ،لدي مثلا ًأفكار تمديد كثيرة يمكنها أن تصل بك إلى إنشاء برنامج صغير لقراءة الملفات
.MP3
ل الملفات بذات
• سيكون من الجيد أن نختار بأنفسنا الملف MP3الذي نريد قراءته .يمكن مثلا ًأن نقدّم لائحة تضم ك ّ
الصيغة و المتواجدة في مجلّد المشروع .لم نر َ كيف نقوم بذلك ،لـكن يمكنك وحدك أن تكتشف ذلك .كمساعدة :
استعمل المكتبة ) direntقم بتضمين الملف .( dirent.hعليك بالبحث في الأنترنت لتعرف كيفي ّة العمل بها.
• إذا كان البرنامج قادرا على التحكّم في لائحات التشغيل ) (Playlistsالمشغ ّلة ،سيكون أمرا ً أفضل .توجد كثير من
صيغ اللائحات و أشهرها الصيغة .M3U
• يمكنك إظهار اسم الـ MP3التي أنت بصدد تشغيله في النافذة مثلا ً)يجب استعمال .(SDL_ttf
• يمكنك إظهار مؤش ّر يشير إلى المكان في القراءة الّذي وصل إليه التشغيل ،هذا ما يفعله أغلب قارئي الـ.MP3
• إلى آخره.
باختصار ،هناك الـكثير لفعله .لديك إمكاني ّة إنشاء قارئي أصوات ممتازة ،ليس عليك سوى كتابة الشفرة الخاصة بها !
452
الجزء د
هياكل البيانات
453
الفصل 29
لـكي نخز ّن المعلومات في الذاكرة ،استعملنا متغي ّرات بسيطة )من نوع ،( ... double ، intكما استعملنا جداول
و هياكل مخصّ صة .إذا أردت تخزين سلسلة من البيانات ،فالأبسط غالبا ًهو استعمال جداول.
لـكن تصبح الجداول أحيانا ً محدودة جداً .مثلاً ،إذا أنشأت جدولا ًذو 10خانات ثم تبېّن لك لاحقا ًفي البرنامج أنك
تحتاج إلى حجم أكبر ،سيكون من المستحيل تكبير حجم الجدول .و أيضا ًلا يمكنك إدخال خانة إلى وسط الجدول.
تمث ّل القوائم المتسلسلة طر يقة لتنظيم البيانات في الذاكرة بطر يقة أكثر مرونة .و بما أن لغة الـ Cلا تقترح قاعديا ً هذا
النظام من التخزين ،سيكون علينا أن ننشئه بأنفسنا .سيكون تمرينا ًممتازا ً يساعدك على أن ترتاح أكثر مع هذه اللغة.
ماهي القائمة المتسلسلة ؟ أقترح عليك أن تنطلق من نموذج الجدول .يمكن تمثيل الجدول في الذاكرة بالطر يقة التي
توضحها الصورة التالية .نتكلّم هنا عن جدول يحتوي على خانات من نوع . int
ّ
م
اخترت هنا تمثيل الجدول أفقياً ،لـكن يمكن تمثيله عموديا ًكذلك ،هذا لا يهم.
كما قلت لك في المقدّمة ،مشكل الجداول يكمن في كونها ثابتة .لا يمكن تكبير حجمها ،إلا إذا فك ّرنا في إعادة إنشائها
من جديد و تكون أكبر )لاحظ الشكل التالي( .أيضاً ،لا يمكن أن نضيف عنص ُرا ً في وسط الجدول إلا إذا قمنا بإزاحة
ل العناصر الأخرى.
ك ّ
455
الفصل .29القوائم المتسلسلة )(Linked lists
لا تقترح علينا لغة الـ Cنظاما ً آخرا ً لتخزين البيانات ،لـكن من الممكن أن ننشئ بأنفسنا هذا النظام بعناصره الكاملة :
ستكون الغاية من هذا الفصل و الفصول الموالية اكتشاف حلول لهذا المشكل.
القائمة المتسلسلة هي طر يقة لتنظيم سلسلة من البيانات في الذاكرة .هذا يسمح بجمع هياكل ) (structuresمرتبطة
ببعضها البعض بواسطة مؤش ّرات .يمكننا تمثيلها كالتالي :
ل
ل عنصر أن يحتوي على ما نريد :قيمة من نوع intأو أكثر ... double ،بالإضافة إلى ذلك ،ك ّ
يمكن لك ّ
عنصر يحتوي على مؤش ّر نحو العنصر الموالي :
ل هذه المعلومات نظر ية و ربّما تبدو لك غير واضحة الآن .احفظ فقط طر يقة ات ّصال العناصر ببعضها :
أعرف بأن ك ّ
هي تشكل سلسلة من المؤش ّرات ،و من هنا نجد الاسم ”قائمة متسلسلة”.
م
على عكس الجداول ،لا تتموضع عناصر القائمة المتسلسلة جنبا ً إلى جنب في الذاكرة .كل خانة تؤش ّر نحو خانة
أخرى لا ٺتواجد ضرور يا ً بجنب الأخرى.
فلنمر ّ الآن إلى صلب الموضوع .سنحاول أن ننشئ بُنية تعمل بنفس المبدأ الذي اكتشفناه الآن.
ل ما سنقوم به هنا يستدعي تقنيات لغة الـ Cالتي تعرفها من قبل .لا يوجد شيء جديد ،سنكتفي بإنشاء أذك ّرك بأن ك ّ
هياكلنا الخاصة و دوال ثم تحو يلها إلى نظام منطقي قادر على العمل لوحده.
عنصر من القائمة
من أجل الأمثلة ،سننشئ قائمة متسلسلة من أعداد صحيحة .كل عنصر من القائمة له شكل الهيكل التالي :
456
.2.29بناء قائمة متسلسلة
م
يمكننا أيضا ً إنشاء قوائم متسلسلة تحتوي أعدادا عشر ية أو حتى جداول أو هياكل .مبدأ القوائم المتسلسلة صالح
من أجل أي نوع من البيانات مهما كان ،لـكن هنا ،أنصحك بتبسيط العملية حتى تفهم المبدأ.
قُمنا الآن بإنشاء عنصر واحد من القائمة ،يوافق الصورة التي رأيناها أعلاه .على ماذا يحتوي الهيكل ؟
• قطعة بيانات ،هنا تتمثل في عدد من نوع : intيمكننا تغيير هذا بأي قطعة أخرى ) ، doubleجدول .( ...
هذا يعتمد على نوع البيانات التي تريد تخزينها ،سيكون عليك تغييرها على حسب حاجتك في البرنامج.
م
إذا أردنا العمل بطر يقة عامة ،الأمثل هو استعمال مؤش ّر نحو الفراغ . void* :هذا يسمح بالتأشير على
أي نوع من البيانات.
ل عنصر ”يعلم”
• مؤش ّر نحو عنصر من نفس النوع يسمّى . nextهذا ما يسمح بوصل العناصر الواحد بالآخر :ك ّ
أين يتواجد العنصر الذي يليه في الذاكرة .كما قلتُ لك مسبقاً ،الخانات لا ٺتواجد جنبا ًإلى جنب في الذاكرة .هذا
هو الاختلاف الـكبير بالنسبة للجداول .هذا ما سيمنحنا مرونة أكثر لأنه بإمكاننا بسهولة إضافة خانات أخرى لاحقا ً
حينما نحتاج إليها.
م
و بالمقابل ،لا يمكننا معرفة العنصر السابق ،أي أنه من المستحيل الرجوع إلى الخلف انطلاقا ًمن عنصر من
هذا النوع من القوائم .لأننا هنا نتكلّم عن قائمة ”بسيطة التسلسل” ،بينما توجد قوائم أخرى تسمّى ”مزدوجة
التسلسل” و تحتوي على مؤش ّرات في كلتا الجهتين و بهذا فهي أصعب بقليل.
هيكل التحكّم
بالإضافة إلى الهيكل الذي نحن بصدد بنائه )و الذي نضاعفه بعدد المرات التي فيها عناصر أخرى( ،سنحتاج إلى هيكل
آخر لـكي نتحكّم في كامل القائمة المتسلسلة .سيكون لهذا الهيكل الشكل التالي :
هذا الهيكل Listيحتوي على مؤش ّر نحو أوّل عنصر من القائمة .في الواقع ،يجب الاحتفاظ بعنوان العنصر الأول
لـكي نعرف أين تبدأ القائمة .إذا عرفنا العنصر الأول ،يمكننا أن نجد العناصر الأخرى بـ”القفز” من عنصر لآخر بالإستعانة
بالمؤشرات الموالية.
457
الفصل .29القوائم المتسلسلة )(Linked lists
م
هيكل مكو ّن من مركّ ب واحد هو في الغالب غير مفيد .و مع ذلك ،أعتقد أننا سنحتاج أن نضيف إليه لاحقا ً
مركّبات أخرى ،يمكننا مثلا ًأن نخز ّن به حجم القائمة ،أي عدد العناصر التي تحتويها.
المخطط أصبح تقريبا ًكاملاً .ينقصه شيء أخير :نفضّ ل أن نحفظ العنصر الأخير من القائمة .في الواقع ،يجب أن نتوق ّف
من التقدّم في القائمة المتسلسلة في لحظة ما .كيف سيتسنى لنا أن نقول للبرنامج ” :توقف ،هذا هو آخر عنصر” ؟
سيكون ممكنا ً أن نضيف إلى الهيكل Listمؤش ّرا نحو آخر عنصر .لـكن هناك ما هو أبسط :يكفي أن يؤش ّر آخر
عنصر من القائمة على ، NULLأي إعطاء المؤش ّر nextالقيمة . NULLهذا سيسمح لنا أخيرا ً برسم مخطط كامل لبُنية
القائمة المتسلسلة :
لقد قًمنا بإنشاء هيكلين يسمحان لنا بالتعامل مع القوائم المتسلسلة :
• ، Elementالذي يوافق عنصرا ً من القائمة و الذي يمكن لنا أن نكرره بقدر المرات التي نريد.
• ، Listالذي يتحكّم في مجموع القائمة المتسلسلة .لن نحتاج إلا لنسخة واحدة منه.
458
.3.29دوال معالجة القوائم المتسلسلة
هذا جيد ،لـكن ينقص الآن الأهم :الدوال التي ستتعامل مع القائمة المتسلسلة .في الواقع ،لن نغي ّر ”يدو يا” محتوى
ل مرة نحتاج فيها إلى ذلك ! سيكون من الأكثر حكمة و الأكثر نظافة أن نمر ّ بدوال تقوم بجعل العمل يتم
الهياكل في ك ّ
بشكل تلقائيّ .يجب إنشاؤها هي بدورها.
• تهي ّئ القائمة.
• تُظهر محتواها.
يمكننا إنشاء دوال أخرى )حساب حجم القائمة مثلاً( لـكن يمكن الاستغناء عنها .سنرك ّز الآن على الدوال التي قمتُ
الآن بتعدادها ،هذا سي ُعطينا قاعدة جيدة .سأدعوك بعد ذلك إلى تحقيق دوال أخرى لـكي ٺتدرّب بعدما تكون قد فهمت
المبدأ جي ّداً.
تهيئة القائمة
دالة تهيئة القائمة هي أول دالة سنحتاج إلى استدعائها .إذ أنها تقوم بإنشاء هيكل التحكّم و أوّل عنصر من القائمة .أقترح
عليك الدالة أسفله و التي سنعل ّق عليها بعد ذلك :
م
لاحظ أن نوع البيانات هو Listو أن المتغير يسمى . listتسمح طر يقة كتابة الحرف الأول بالتفر يق بينهما.
459
الفصل .29القوائم المتسلسلة )(Linked lists
م
كان بإمكاننا أن نكت ُب أيضا ً ) ، sizeof(Listلـكن إن أردنا لاحقا ً تغيير نوع المؤش ّر listسيكون علينا
تحديث الـ sizeofكذلك.
ل شيء على ما ي ُرام ،نعر ّف قيم أول عنصر من القائمة المتسلسلة :
أما إن تم ّ ك ّ
• المؤش ّر nextيؤش ّر نحو NULLلأن أول عنصر في القائمة هو أيضا ً آخر واحد لح ّد الآن .كما رأينا سابقاً ،يجب
على آخر عنصر أن يؤش ّر نحو NULLليشير إلى نهاية القائمة.
لقد نجحنا الآن في إنشاء قائمة في الذاكرة متكو ّنة من عنصر واحد و لها الشكل التالي :
إضافة عنصر
هنا ،تبدأ الأمور في التعقد قليلاً .أين سنضيف عنصرا َ جديدا ً ؟ في بداية القائمة ،في نهايتها أو في الوسط ؟
الإجابة هي أنه لدينا الخيار .سنكون أحرارا ً في اختيار ما نريد .في هذا الفصل ،أقترح عليك أن نتعل ّم كيفية إضافة
عنصر إلى بداية القائمة .من ناحية ،هذا الأمر سهل الفهم ،و من ناحية أخرى سأعطيك الفرصة في نهاية الفصل في التفكير
في طر يقة إنشاء دالة تضيف ع ُنص ُرا ً في مكان محدد من القائمة.
يجدر بنا إنشاء دالة قادرة على أن تضيف ع ُنصرا ً جديدا ً إلى بداية القائمة .و لـكي نفهم أكثر ،تخي ّل أننا في حالة مشابهة
لما تعرضه الصورة الموالية :ٺتكون القائمة من ثلاثة عناصر و نريد أن نضيف لها ع ُنصرا ً جديدا في البداية :
460
.3.29دوال معالجة القوائم المتسلسلة
يجب أن نقوم بتغيير وضعية المؤش ّر firstالخاص بالقائمة و أيضا ً المؤش ّر nextالخاص بالعنصر الجديد لـكي
”ندرج” هذا الأخير بشكل صحيح في القائمة .أقترح عليك هذه الشفرة المصدر ية التي سنحللها لاحقا ً:
تأخذ الدالة insertionكمعاملات :عنصر التحكّم في القائمة )الذي يحتوي على عنوان أول عنصر( و العدد الذي
نريد تخزينه في العنصر الجديد الذي سنقوم بإنشائه.
سنقوم أولا بحجز المكان اللازم لتخزين العنصر الجديد و نضع به العدد . newNumberتبقى إذا المرحلة الحساسة :
إدراج العنصر الجديد في القائمة المتسلسلة.
لقد اخترنا هنا ،تسهيلا ً للعملية ،إضافة العنصر إلى بداية القائمة .لـكي نحدّث المؤش ّرات بشكل صحيح ،سنعتمد على
الخطوتين التاليتين بهذا الترتيب المحدد :
.1تأشير العنصر الجديد نحو العنصر الذي سيليه مستقبلاً ،أي العنصر الحالي الأول من القائمة.
!
لا يمكننا القيام بهاتين الخطوتين في الترتيب المعاكس ! في الواقع ،إن قمنا بجعل المؤش ّر 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
هذه الدالة قصيرة لـكن هل يمكنك إعادة كتابتها لوحدك ؟ يجب أن نفهم جيدا ً بأننا يجب أن نقوم بالعمل اتّباعا ً
لخطوات محددة :
462
.4.29اذهب بعيدا
لـكي نرى بشكل واضح محتوى القائمة المتسلسلة ،سيكون من الأمثل أن نكتب دالة عرض ! يكفي أن ننطلق من العنصر
الأول و إظهار العناصر واحدا ً تلو الآخر بـ”القفز” من كتلة لأخرى.
بالإضافة إلى العنصر الأول )و الذي تركناه هنا يحمل القيمة ،(0نضيف ثلاثة عناصر جديدة لهذه القائمة .ثم نقوم
بحذف عنصر واحد .في النهاية ،يتم إظهار محتوى القائمة المتسلسلة بالشكل التالي :
لقد قُمنا الآن بكتابة الدوال اللازمة للتحكّم في قائمة متسلسلة :التهيئة ،إضافة عنصر ،حذف عنصر ،إلخ .إليك بعض
الدوال التي تنقص و التي أدعوك إلى كتابتها ،سيكون هذا بمثابة تمرين جيد لك !
463
الفصل .29القوائم المتسلسلة )(Linked lists
• حذف عنصر من وسط القائمة :المبدأ نفسه بالنسبة للإضافة في وسط القائمة .هذه المرة ،يجب عليك أن تضيف
معاملا يمثل عنوان العنصر الذي نريد حذفه.
• حجم السلسلة :تشير هذه الدالة إلى كم من عنصر ٺتكون القائمة المتسلسلة .الأمثل ،و في عوض أن يتم حساب هذه
ل مرة ،هو أن نضيف عددا صحيحا nbOfElementsإلى الهيكل . Listيكفي أن نزيد من قيمته
القيمة في ك ّ
ل مرة نحذف عنصرا ً منها.
ل مرة نضيف فيها عنصرا ً جديدا ً للقائمة و أن ننقص من قيمته في ك ّ
في ك ّ
يمكنك تنز يل مشروع القوائم المتسلسلة الذي يحتوي الدوال التي اكتشفناها سو ياً .ستكون هذه بمثابة قاعدة جيدة لك.
http://www.siteduzero.com/uploads/fr/ftp/mateo21/c/listes_chainees.zip
ملخّ ص
• تشكّل القوائم المتسلسلة طر يقة جديدة لتخزين البيانات في الذاكرة .هي أكثر مرونة من الجداول لأنها تمكّننا من
إضافة و حذف ”خانات” في أي لحظة نريد.
• لا تحتوي لغة الـ Cعلى نظام تحكّم في القوائم المتسلسلة ،إذ يجب أن نكتبه بأنفسنا ! يعتبر هذا طر يقة ممتازة للتقدّم
في الخوارزميات و البرمجة بشكل عام.
• في قائمة متسلسلة ،كل عنصر هو عبارة عن هيكل يحتوي عنوان العنصر الموالي.
• يُنصح بإنشاء هيكل تحكّم )من نوع Listفي حالتنا هذه( يحتوي عنوان أول عنصر في القائمة.
• توجد نسخة محسّنة -لـكن أكثر تعقيدا ً -من القوائم المتسلسلة و نسمّيها ”القوائم مزدوجة التسلسل” ،و التي يحتوي
ل عنصر فيها على عنوان العنصر السابق أيضاً.
ك ّ
464
الفصل 30
لقد اكتشفنا مع القوائم المتسلسلة طر يقة جديدة أكثر مرونة من الجداول لتخزين البيانات .هذه القوائم مرنة بشكل
خاص لأنه يمكننا أن نُدرج فيها و نحذف منها بيانات من أي مكان أردنا و في أية لحظة.
المكدّسات و الطوابير التي سنكتشفها هنا هما شكلان خاصّان نوعا ً ما من القوائم المتسلسلة .فهما تسمحان بالتحكّم
بالطر يقة التي تُضاف بها العناصر الجديدة إليها .هذه المرة لن نقوم بإضافة عناصر جديدة في وسط القائمة ،بل فقط في
البداية أو النهاية.
المكدّسات و الطوابير تعتبران مفيدتان للغاية من أجل البرامج التي تحلل المعطيات ال ّتي تصل بالتدريج .سنرى بالتفصيل
كيف تعملان في هذا الفصل.
ٺتشابه المكدّسات و الطوابير كثيراً ،لـكنهما تختلفان اختلافا ً بسيطا ً ستتعرف عليه بسرعة .سنكتشف أولا ً المكدّسات
و التي ستذك ّرك بالقوائم المت ّصلة بشكل كبير.
بشكل عام ،سيكون هذا الفصل بسيطا ً إذا كنت قد فهمت جي ّدا ً كيفية عمل القوائم المتسلسلة .إن لم تكن هذه
حالتك ،فأعد قراءة الفصل السابق لأننا سنحتاج إليه.
تخي ّل مكدّسا ًللقطع النقدية )الصورة التالية( .يمكنك إضافة قطع أخرى واحدة تلو الأخرى في أعلى المكدّس ،و يمكنك
أيضا ًنزع القطع من أعلى المكدّس .بالمقابل ،لا يمكن نزع قطعة من أسفل المكدّس .إن أردت التجريب ،أتمنى لك حظًا ً
موف ّقا ً!
465
الفصل .30المكدّسات و الطوابير )(Stacks and Queues
ينص على تخزين البيانات مع وصولها على التوالي واحدة فوق الأخرى لـكي نستطيع
مبدأ عمل المكدّسات في البرمجة ّ
استرجاعها فيما بعد .مثلا ،تخي ّل مكدّسا ً للأعداد الصحيحة من نوع ) intالصورة الموالية( .لو أضيف عنصرا ً )نتكلّم
عن التكديس( ،فستتم إضافته في أعلى المكدّس ،تماما ً كما في لعبة : Tetris
الأكثر أهمية هو وجود عملية تقوم باستخراج الأعداد من المكدّس .نحن نتكلّم عن إلغاء التكديس .نسترجع القيم
واحدة تلو الأخرى ،بدء ً من الأخيرة الموضوعة أعلى المكدّس )الصورة الموالية( .ننزع البيانات على التوالي حتى نصل إلى
قاع المكدّس.
نسمي هذا بخوارزمية ،LIFOو التي تعني ” .”Last In First Outالترجمة ” :آخر ع ُنصر تمت إضافته ،هو أول عنصر
يخرج”.
466
.1.30المكدّسات )(Stacks
عناصر المكدّس مرتبطة ببعضها بطر يقة القوائم المتسلسلة .فهي تحمل مؤش ّرا ً نحو العنصر الموالي و لا تتموضع بالضرورة
بجنب بعضها في الذاكرة .يجب على آخر ع ُنصر )في أقصى أسفل المكدّس( أن يؤش ّر نحو NULLلـكي يقول أنّنا ...لمسنا
القاع :
؟
ل هذا ،واقعي ّا ؟
فيما ينفع ك ّ
توجد برامج تحتاج فيها إلى تخزين اليبانات مؤق ّتا ًلإخراجها اعتمادا ً على ترتيب محدد :يجب على آخر ع ُنصر أدخلته أن
يخرج هو الأول.
لأعطي مثالا ً واقعياً ،يستعمل نظام التشغيل في حاسوبك هذا النوع من الخوارزميات لـكي يتذكر الترتيب الذي تم
استدعاء الدوال فيه .تخي ّل مثالا :
مرة(.
.1يبدأ البرنامج بالدالة ) mainمثل كل ّ
.2تستدعي فيها الدالة . play
.6أخيراً ،ما إن تنتهي الدالة . mainلا تبقى أية دالة تحتاج إلى الاستدعاء ،ينتهي البرنامج.
لـكي ”يتذك ّر” الترتيب الذي تم فيه استدعاء الدوال ،يُنشئ الحاسوب مكدّسا ًلهذه الدوال على التوالي :
467
الفصل .30المكدّسات و الطوابير )(Stacks and Queues
هذا مثال واقعيّ عن استعمال المكدّسات .و بفضل هذه التقنية يعرف الجهاز الآن إلى أي دالة يجب عليه أن يعود.
يمكنه أن يكدّس 100استدعاء للدوال إن وجب الأمر ،لـكن ّه سيرجع ليجد الدالة الرئيسية في أسفل المكدّس !
و الآن بما أننا فهمنا مبدأ عمل المكدّسات ،فلنحاول بناء واحد .تماما مثل القوائم المتسلسلة ،لا يوجد نظام مكدّس متضمّن
في لغة الـ .Cيجب أن ننشئه بأنفسنا.
سيكون لكل عنصر من المكدّس هيكل مماثل للهيكل الخاص بالقائمة المتسلسلة :
يحتوي هيكل التحكّم على عنوان أول عنصر من المكدس ،أي العنصر المتواجد في الأعلى :
• تكديس عنصر.
ستلاحظ أنه ،على خلاف القوائم المتسلسلة ،لا نتكلّم لا عن الإضافة و لا عن الحذف .نتكلّم عن التكديس و إلغاء
التكديس .لأن هاتين العمليتين محدودتان على عنصر واحد محدد ،كما رأينا .بهذا ،يمكننا إضافة و نزع عنصر من المكدّس
من الأعلى فقط.
يمكننا أيضا ًكتابة دالة لإظهار محتوى المكدّس ،أمر عمليّ للتأكد من أن البرنامج يعمل بشكل صحيح.
هيا بنا !
468
.1.30المكدّسات )(Stacks
التكديس )(stacking
يجدر بدالتنا stackأن تأخذ كمعاملين هيكل التحكّم في المكدّس )من نوع ( Stackو العدد الجديد لتخزينه.
أذك ّرك بأننا نخز ّن هنا أعدادا ً صحيحة ، intلـكن لا شيء يمنعنا من تعديل هذه الأمثلة لأنواع أخرى من البيانات .يمكننا
تخزين أي شيء :أعداد ، doubleمحارف ، charسلاسل محارف ،جداول و حتى هياكل أخرى !
تتم الإضافة في بداية المكدّس لأنه ،كما رأينا ،يستحيل القيام بذلك في المنتصف .هذا مبدأ عمل المكدّسات ،نضيف
دائما من الأعلى.
بهذا ،على عكس القوائم المتسلسلة ،لا يجب أن ننشئ دالة لإدراج عنصر في منتصف المكدّس .يجب أن تكون الدالة
stackهي الوحيدة ال ّتي يمكنها إضافة عناصر جديدة للمكدس.
دور دالة إلغاء التكديس هو حذف العنصر المتواجد في أعلى المكدّس ،قد تشك في ذلك .لـكن يجب على هذه الدالة أيضا
أن ت ُرجع إلينا العنصر الذي حذفته .في حالتنا ،هو العدد الّذي كان موجودا في أعلى المكدّس.
هذه هي الطر يقة التي نصل بها إلى عناصر المكدّس :ننزعها واحدا ً تلو الآخر .نحن لا نتقدّم فيها باحثين عن الوصول
إلى ثاني و ثالث عنصر .بل نطلب دائما ًاسترجاع على أول عنصر.
دالتنا unstackست ُرجع إذا intيوافق العدد المتواجد في رأس المكدّس :
469
الفصل .30المكدّسات و الطوابير )(Stacks and Queues
،فَب ِو ُصول البرنامج إلى ذلك السطر سيكون هذا الجزء stack != NULL يبدو لي أن مؤل ّف الكتاب قد أضاف جزء ً عديم الفائدة في الشرط الثاني :
( .و حتى عند عدم وجود الشرط الأوّل، )if(stack == NULL ل تأكيد لأنّنا قد تحقّقنا من عكسه في الشرط الأوّل )
من الشرط خاطئا بك ّ
لا يملك أي ّة مركّبات ! NULL ن مؤش ّرا ً
كانت ستعطّل البرنامج لأ ّ stack->first فالتعليمة
نسترجع العدد الذي في رأس المكدّس لنبعثه في نهاية الدالة .نعدّل عنوان أول عنصر من المكدّس بما أن هذا الأخير
يتغير .أخيراً ،نحذف بالتأكيد رأس المكدّس القديم باستعمال . free
بالرغم من أن هذه الدالة غير ضرور ي ّة )الدالتان stackو unstackكافيتان للتحكّم في المكدّس !( ،ستكون مهمّة
لاختبار عمل المكدّس و خاصّة ”لمعاينة” النتيجة :
بالمقابل ،حان الآن إذا ً لكتابة الدالة الرئيسية لاختبار سلوك مكدّسنا :
470
.2.30الطوابير )(Queues
نُظهر حالة المكدّس بعد الـكثير من التكديس و مرة أخرى بعد كثير من إلغاء التكديس .نُظهر أيضا ً العدد الذي قُمنا
ل مرة نقوم بإلغاء التكديس .النتيجة في الـكونسول هي التالية :
بحذفه في ك ّ
تأكّد أنك تفهم جي ّدا ً ما يحصل في البرنامج .إذا فهمت هذا ،فقد فهمت كيفية عمل المكدّسات !
http://www.siteduzero.com/uploads/fr/ftp/mateo21/c/piles.zip
في هذا النظام ،يتم وضع العناصر الواحد بعد َ الآخر .أوّل عنصر يخرج من الطابور هو أول عنصر يدخل إليه .نتكلّم هنا
عن خوارزمية ،(First In First Out) FIFOو هذا يعني ” :أول من يصل هو أول من يخرج”.
471
الفصل .30المكدّسات و الطوابير )(Stacks and Queues
تسهل المشابهة بالحياة اليومي ّة .حينما تشتري تذكرة لمشاهدة السينما ،تقف في طابور شب ّاك التذاكر )الصورة الموالية(.
ل الآخرين .أول الواصلين
باستثناء إن كنت أحد إخوة بائع التذاكر ،يجدر بك الوقوف في الطابور و انتظار دورك مثل ك ّ
هو أول من تتم خدمته.
يتم تخزين الأحداث التي تبعثها المكتبة SDLالتي قُمنا بدراستها أيضا في طابور .إذا حرّكت الفأرة ،يتم توليد حدث
ل مرة
ل بيكسل تحر ّك فوقه مؤش ّر الفأرة .تخزن الـ SDLالأحداث في طابور ثم تبعثها لنا واحدا واحدا في ك ّ
من أجل ك ّ
نستدعي فيها الدالة ) SDL_PollEventأو .( SDL_WaitEvent
ل عنصر فيها بالتأشير على العنصر الموالي ،تماما ًمثل المكدّسات .آخر
في لغة الـ .Cالطابور هو قائمة متسلسلة أين يقوم ك ّ
عنصر من الطابور يؤش ّر نحو : NULL
نظام الطابور يشبه ذلك الخاص بالمكدّسات .يوجد اختلاف بسيط في كون أن العناصر تخرج في الإتجاه المعاكس ،لا
يوجد شيء صعب إن كنت قد فهمت المكدّسات.
472
.2.30الطوابير )(Queues
تماما كالمكدّسات ،كل عنصر من الطابور سيكون من نوع . Elementبالإستعانة بالمؤش ّر firstسنتوف ّر دائما ً
على العنصر الأول و يمكننا من خلاله الصعود إلى آخر عنصر.
الدالة التي تضيف ع ُنص ُرا ً إلى الطابور تسمّى دالة ”الإلحاق” ) .(enqueuingتوجد حالتان :
• إما أن الطابور فارغ ،في هذه الحالة يجب أن ننشئ الطابور بجعل المؤش ّر firstيؤش ّر نحو العنصر الجديد الذي
نحن بصدد انشائه.
• إما أن الطابور غير فارغ ،في هذه الحالة يجب أن نتقدّم في الطابور إنطلاقا ً من العنصر الأول حتى نصل إلى آخر
عنصر .نضيف العنصر الجديد بعد آخر عنصر.
473
الفصل .30المكدّسات و الطوابير )(Stacks and Queues
عملية إلغاء الإلحاق ) (dequeuingتشابه كثيرا ً عملية إلغاء التكديس .بامتلاكنا مؤش ّرا نحو أول عنصر من الطابور ،يكفي
أن ننزعه و أن ن ُرجع قيمته.
حان دورك !
تبقى دالة إظهار محتوى الطابور displayQueueو عملها مشابه لما قمنا به مع المكدّسات .سيسمح لك هذا بالتأكد من
سلامة عمل الطابور.
قم بعد ذلك بكتابة mainمن أجل تجريب البرنامج .يجدر بنتيجة البرنامج أن تشبه هذه :
يجدر أن تكون قادرا ً على إنشاء مكتبة الطوابير الخاصة بك ،بملفات 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
للقوائم المتسلسلة نقطة ضعف كبيرة في حال أردنا قراءة محتواها :يستحيل الوصول إلى عنصر معيّن مباشرة .يجب
التقدّم في القائمة عنصرا ً بعنصر حتى نجد العنصر الذي نريد .هذا يطرح مشاكل من ناحية الأداء ما إن يكون حجم القائمة
المتسلسلة ضخماً .تخي ّل قائمة متسلسلة ٺتكو ّن من 1000عنصر بينما العنصر الذي نبحث عنه موجود في آخرها !
تمث ّل جداول التجزئة طر يقة أخرى لتخزين البيانات .حيث أنها تستند على مبدأ الجداول في لغة الـ Cو التي نعرف
التعامل معها جي ّداً .ماهي فائدتها الـكُبرى ؟ هي تسمح بإ يجاد سر يع لعنصر محدد ،سواء كان الجدول يحتوي ،100
10000 ،1000خانة أو حتى أكثر !
لننطلق من المشكل الذي تطرحه القوائم المتسلسلة .هذه الأخيرة مرنة بشكل خاص ،هذا ما استطعنا ملاحظته :
يمكننا إضافة أو إزالة خانات في أي لحظة نريد ،بينما يكون الجدول ”ثابتاً” ما إن يتم إنشاؤه.
لـكن ،كما قلتُ لك في المقدّمة ،للقوائم المتسلسلة عيب كبير :إذا أردنا استرجاع عنصر محدد من القائمة ،يجب تصفّح
هذه الأخيرة حتى نجد ذلك العنصر !
م
عملنا سابقا ًعلى القوائم المتسلسلة التي تحتوي على . intكما قلتُ لك ،من الممكن تخزين أي شيء نريد في قائمة،
حتى مؤش ّرا ً نحو هيكل آخر كما سأقترحه لك الآن.
ل القائمة
إذا أردتُ الوصول إلى المعلومات الخاصة بالشخص Luc Doncieuxفي الصورة الموالية ،يجب عليّ التقدّم في ك ّ
كي أكتشف بأنه العنصر الأخير فيها !
477
الفصل .31جداول التجزئة )(Hash tables
م
بالفعل ،لو أننا بحثنا عن الشخص ،Julien Lefebvreكان البحث ليكون أسرع بما أنه متواجد في بداية القائمة .و
مع ذلك ،لتقييم كفاءة الخوارزمية ،يجب أن نفك ّر دائما ًفي أسوء الحالات .و الأسوء هو Lucهنا.
هنا ،نقول أن خوارزمية البحث لها تعقيد ) ،O(n) (complexityلأنه يجب تصفّح كل القائمة المتسلسلة للوصول
إلى الع ُنصر المراد ،و في أسوء الحالات يكون هذا هو آخر عنصر .إذا كانت القائمة تحتوي على 9عناصر ،يجب
أن يتم تشغيل 9دورات للحلقة كحد أقصى لإ يجاد العنصر.
في هذا المثال ،لا تحتوي القائمة المتسلسلة سوى على أربعة عناصر .سيجد الحاسوب الشخص Luc Doncieuxبسرعة
كبيرة لا تسمح لنا حتى بأن نقول كلمة ”أووه” .لـكن تخي ّل أن هذا الشخص يتواجد في آخر قائمة متسلسلة من 10000
عنصر ! ليس مقبولا أن يتم البحث في 10000عنصر لإ يجاد المعلومة .هنا ٺتدخّل جداول التجزئة.
إذا كنت ٺتذكر جيداً ،لا تعرف الجداول هذا النوع من المشاكل .لهذا ،كي نصل إلى العنصر في الوضعية 2من
الجدول تكفيني كتابة التالي :
لو نعطي للحاسوب ] ، table[2فسيتوجّه مباشرة إلى المكان في الذاكرة أين هو مخز ّن العدد .14أي أنه لن يتقدّم
في الجدول خانة بخانة.
؟
هل أنت بصدد القول أن الجداول ليست ”بذلك القدر من السوء” ؟ لـكن في هذه الحالة سنخسر الميزات التي
توف ّرها القوائم المتسلسلة التي تسمح لنا بإضافة و إزالة خانات في أي لحظة !
في الواقع ،القوائم المتسلسلة مرنة أكثر .أما بالنسبة للجداول ،فهي تسمح بالوصول السر يع للمعطيات .يمكننا القول أن
جداول التجزئة تشكّل حل ّا وسطا بين الإثنين.
يوجد عيب في استعمال الجداول لم نتكلّم عنه سابقا ً :يتم تعر يف خانات الجدول عن طر يق أرقام نسمّيها الفهارس
) .(Indicesلا يمكن أن نطلب من الحاسوب ” :ماهي المعلومات المتواجدة في الخانة التي تسمّى ” .”Luc Doncieuxأي
أننا لإ يجاد الع ُمر و المعدّل لن نتمكّن من كتابة :
478
.2.31ماهي جداول التجزئة ؟
مع أنه سيكون عمليا ًلو أننا نستطيع الوصول إلى خانة ما باستعمال الاسم فقط ! حسناً ،هذا ممكن باستعمال جداول
التجزئة.
م
كما رأينا مؤخّراً .لا تشكّل جداول التجزئة ”جزءً” من لغة الـ .Cنتحدّث هنا عن مبدأ .سنعيد استعمال أساسيات
لغة الـ Cالتي نعرفها من قبل لأجل إنشاء نظام ذكي جديد .و كأنه في لغة الـ ،Cباستعمال بعض الأدوات القاعدية،
يمكننا إنشاء الـكثير من الأشياء !
؟
بما أنه من الواجب أن يتم ترقيم الجدول بالفهارس ،كيف سنجد رقم الخانة لو أننا نعرف الاسم ””Luc Doncieux
فقط ؟
ملاحظة جيدة .في الواقع ،يبقى الجدول جدولا ًو لن يعمل إلا بالفهارس المرقّمة .تخي ّل جدولا ًيوافق الصورة الموالية
:كل خانة لها فهرس و ٺتوفر على مؤش ّر نحو هيكل من نوع . Studentأنت تعرف القيام بهذا الآن :
لو أردنا إ يجاد الخانة التي توافق ،Luc Doncieuxيجب أن نجيد تحو يل الإسم إلى فهرس في الجدول .و بهذا ،يجب أن
ل اسم برقم من خانة في الجدول :
نتمكّن من ربط ك ّ
لا يمكننا أن نكتب ]” table[”Luc Doncieuxكما فعلتُ سابقاً .لأن هذا غير مسموح به في لغة الـ.C
السؤال الذي يُطرح هو :كيف نحو ّل سلسلة محارف إلى عدد ؟ هذا هو سحر التجزئة .تجب كتابة دالة تأخذ كمعامل
سلسلة محارف ،تطب ّق حسابات عليها ،ثم ت ُرجع لنا عددا ً يوافق تلك السلسلة .سيكون هذا العدد هو فهرس الخانة في الجدول
:
479
الفصل .31جداول التجزئة )(Hash tables
ل الصعوبة في كتابة دالة تجزئة صحيحة .كيف نحو ّل سلسلة ً محرفي ّة إلى عدد وحيد ؟
تكمن ك ّ
ل شيء ،لنوضح الأمور :جدول التجزئة لا يحتوي 4خانات كما أضع في الأمثلة ،لـكن 100أو 1000
أولا و قبل ك ّ
أو أكثر .لا يهم حجم الجدول ،لأن البحث سيكون سر يعا ًجدا ً دائما.
م
نقول أن هذا تعقيد بـدرجة ) O(1لأننا نجد مباشرة عنصر البحث .في الواقع ،دالة التجزئة سترجع لنا فهرسا :
ل الخانات !
يكفي ”القفز” مباشرة إلى الخانة الموافقة للجدول .لسنا بحاجة إلى تصفّح ك ّ
تخي ّل إذا ً جدولا ًمن 100خانة ،تقوم فيه بتخزين مؤش ّرات نحو هياكل من نوع . Student
يجب علينا أن نكتب دالة ،انطلاقا ًمن اسم ،تولّد عددا ً محصورا ً بين 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
لو نعطيها )” ، hash(”Luc Doncieuxست ُرجع لنا القيمة .55و بـ )” ، hash(”Yann Martinezنتحصّ ل على
.80
بفضل دالة التجزئة هذه ،يمكنك أن تعرف في أي خانة من الجدول يجب أن تضع المعلومات ! إذا أردت الوصول
إلى هذه الخانات لاحقا ًلاسترجاع المعلومة ،تكفي ”تجزئة” اسم الشخص من جديد لـكي نجد فهرس الخانة في الجدول أين
تخز ّن المعلومات !
أنصحك بإنشاء دالة بحث ٺتكفّل بتجزئة المفتاح )الاسم( و ت ُرجع لنا مؤش ّرا ً نحو المعلومات التي نبحثُ عنها .هذا سيعطينا
مثلا :
;)”1 infoAboutLuc = findHashTable(table, ”Luc Doncieux
حينما ت ُرجع دالة التجزئة نفس العدد من أجل مفتاحين مختلفين ،نقول أن ّه حدث تصادم .مثلا في دالتنا ،لو أننا نملك
شخصا ً اسمه تحر يك أحرف لـ ،Luc Doncieuxمثلا ً ،Luc Doncueixسيكون مجموع الأحرف هو نفسه ،و بهذا فإن نتيجة
دالة التجزئة ستكون نفسها !
يمكن لسببين أن يشرحا التصادم :
• دالة التجزئة لا تعمل بكفاءة عالية .هذا يمث ّل حالتنا .لقد كتبنا دالة سهلة جدا ً )لـكن نوعا ً ما كافية( من أجل
الأمثلة .الدالتان MD5و SHA1المذكورتان أعلاه هما ذات جودة عالية لأنهما تنتجان نسبة قليلة من التصاد ُمات.
و لتعلم أن SHA1مفضّ لة في أيامنا هذه أكثر من MD5لأنها تننج نسبة تصادمات أقل مقارنة بنظيرتها.
481
الفصل .31جداول التجزئة )(Hash tables
• الجدول الذي نخزن به المعلومات صغير الحجم كثيراً .لو أننا ننشئ جدولا من 4خانات و نريد تخزين 5أشخاص،
فسيحدث تصادم بالتأكيد ،أي ان دالة التجزئة ست ُعطي نفس الفهرس من أجل اسمين مختلفين.
إذا حصل تصادم فلا داعي للخوف ! هناك حلان يمكنك الاختيار بينهما :العـَنوَنـ َة المفتوحة و السَلْسَلَة.
إذا بقيت أمكنة شاغرة في الجدول ،يمكنك تطبيق التقنية التي تُدعى التجزئة الخطي ّة .المبدأ سهل .هل الخانة محجوزة ؟
لا يوجد مشكل ،سننتقل للخانة التي تليها .آه ،هل هذه محجوزة أيضا ً؟ توجّه لللتي بعدها.
و هكذا حتى تجد خانة موالية فارغة .إن وصلت إلى نهاية الجدول ،فعد إلى البداية و أكمل البحث.
تطبيق هذه الطر يقة سهل جداً ،لـكن إن واجهت الـكثير من التصادمات ،فسيكون عليك استغراق وقت كبير في
البحث عن الخانة الشاغرة الموالية.
السَلْسَلَة )(Chaining
ينص على إنشاء قائمة متسلسلة في مكان التصادم .هل تريد تخزين بيانتين )أو أكثر( في نفس الخانة ؟ استعمل
ل آخر ّ
ح ّ
قائمة متسلسلة و أنشئ مؤش ّرا ً نحو هذه القائمة انطلاقا ًمن الجدول :
بالفعل ،سنعود لمشكل القوائم المتسلسلة :إذا كان هناك 300عنصرٍ في هذا الموقع من الجدول ،يجب تصفّح القائمة
المتسلسلة إلى حين إ يجاد العنصر الصحيح.
482
ملخّ ص
هنا ،كما ترى .ليست القوائم المتسلسلة دائما ًالأمثل ،لـكن لجداول التجزئة حدودها أيضاً .يمكننا المزج بين الاثنين من
ل بنية.
أجل الحصول على الجانب الأفضل من ك ّ
على أية حال ،النقطة الحساسة في جداول التجزئة هي دالة التجزئة .فكل ّما أنتجت تصاد ُمات أقل .كلما كان ذلك
أفضل .سأترك لك مهمة إ يجاد دالة التجزئة المناسبة لحالتنا !
ملخّ ص
• القوائم المتسلسلة مرنة ،لـكن عملية إ يجاد عنصر محدد تستغرق وقتا ًطو يلا ًلأنه يجب تصفّح القائمة عنصرا ً بعنصر.
• جداول التجزئة هي جداول نخز ّن فيها المعلومات في مكان محدد بواسطة دالة التجزئة.
• تأخذ دالة التجزئة مفتاحا ًكمعامل )مثلا ً :سلسلة محرفي ّة( و تعيد عددا كمخرج.
• يتم استعمال هذا العدد لمعرفة عند أي فهرس من الجدول يجب تخزين البيانات.
• دالة التجزئة الأكثر كفاءة هي التي لا تولّد عددا ً كبيرا ً من التصاد ُمات ،أي أنها تتجنب قدر المستطاع إرجاع نفس
العدد من أجل مفتاحين مختلفين.
• في حالة التصادم ،يمكننا استعمال تقنية العنونة المفتوحة )البحث عن خانة شاغرة أخرى في الجدول( أو استعمال
تقنية السلسلة )الدمج مع القوائم المتسلسلة(.
483
الفصل .31جداول التجزئة )(Hash tables
484
خاتمة
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]
��اجع� و إ��اد
���ة عبَ�د
تصم�� الغ��ف
أﺣﻤﺪ زﺑﻮﳾ
م���� و إختص��� �� ا����ء ا��صطن��� ،عص��� �� هن�س� ص����ت و ��ط� المع��م�ت ا��س��� ���� ،ب�لعم� ا��� و المع��م����ت.
[email protected]
��ن��� ،ة و أ��� متخ�ج�ن م� ��مع� ه�اري ب�م��� ��ع��م و التكن�ل�جي� ب����ا�� ،اختص�ص »ا��نظم� المع��م���� ا��كي�«.