بنية برنامج مكتوب بلغة C وبعض المبادئ
إذا كنت معتادًا على لغات تتبع بنية الكُتل مثل لغة باسكال Pascal، فستجد بنية برنامج لغة سي C مفاجئًا لك؛ وإذا كانت خبرتك في مجال لغات مشابهة للغة فورتران FORTRAN، فستجد البنية مشابهةً لما اعتدت عليه -بالرغم من اختلافها بصورةٍ كبيرة في التفاصيل، وفي الحقيقة استعارت لغة سي من كلا الأسلوبين المُستخدمين بصورةٍ واضحة، ومن أماكن أخرى أيضًا. نتيجةً لأخذ بعض القواعد من مصادر مختلفة، تبدو لغة سي أشبه بنتيجة تزاوج فصيلة كلاب ترير Terrier غير الأنيقة والمعروفة بعنادها وقوتها لكنها متسامحة مع أفراد العائلة. يطلق علماء الأحياء على هذا النوع من الفصائل “القوة الهجينة”، قد يذكرك كلامنا أيضًا بمخلوق كمير Chimera الأسطوري الذي يبدو خليطًا من الخرفان والماعز، قد يمنحنا الحليب والصوف، ولكنه سيزعجنا بثغائه المرتفع ورائحته غير اللطيفة.
إذا نظرنا للأمر عمومًا نلاحظ أن ميزة لغة سي C العامة هي بنية البرنامج الموزعة على عدة ملفات، لأنها تسمح بتصريف منفصل لهذه الملفات، إذ تسمح لغة سي بتوزيع أجزاء من برنامج مكتمل على عدة ملفات مصدرية والتصريف على نحوٍ متفرق عن بعضها بعضًا. مبدأ العمل هنا هو أن جميع عمليات التصريف هذه ستعطينا ملفات يمكن ربطها Linked سويًّا عن طريق أي محرر ربط، أو محمّل ربط يستخدمه نظامك، ولكن بنية الكتل لبعض لغات البرمجة المشابهة للغة ألغول ALGOL تجعل هذه الطريقة صعبة التنفيذ، نظرًا لأن البرنامج مكتوبٌ بطريقة تجعل منه كتلةً واحدةً مترابطة، إلا أن هناك بعض الطرق للتغلُّب على هذه المشكلة.
تتغلب لغة سي على هذه المشكلة بطريقةٍ مثيرة للاهتمام، إذ من المفترض أن تسرّع عملية التصريف، لأن تصريف البرنامج إلى تعليمات مصرَّفة Object Code بطيء ومكلف من ناحية الموارد، فالتصريف عملية شاقة. أتت من هنا فكرة استخدام المحمّل في ربط مجموعة من التعليمات المصرفة، إذ تتطلب هذه العملية ترتيب التعليمات حسب عنوانها للتوصل للبرنامج الكامل ببساطة. يُفترض أن يكون هذا الحل خفيف الاستهلاك للموارد، وعلى المحمّل طبعًا فحص المكتبات المستخدمة في التعليمات المصرفة واختيارها أيضًا.
الفائدة المكتسبة هنا هو أننا لسنا بحاجة تصريف كامل البرنامج بعد تعديل جزءٍ بسيط منه، في هذه الحالة نحن بحاجة إعادة تصريف الجزء المُعدّل من البرنامج فقط؛ ولكن قد يصبح المحمّل في بعض الأحيان أبطأ عمليات تصريف البرنامج وأكثرها استهلاكًا للموارد بزيادة العمل المطلوب منه، وقد تكون العملية أسرع في بعض الأنظمة إذا صُرّف كل شيء دفعةً واحدة، وتُعد لغة أدا Ada إحدى الأمثلة المعروفة باتباعها لهذا الأسلوب. بالنسبة للغة سي فالعمل المنجز من المصرف ليس ضخمًا ومعقول إلى حدٍّ ما، يوضح الشكل 1.1 طريقة عمل المصرف في لغة سي.
هذه الطريقة مهمة في لغة سي، إذ من الشائع أن تجد جميع البرامج باستثناء الصغيرة منها مؤلفةٌ من عددٍ من ملفات الشيفرة المصدرية، هذا يعني أيضًا أن جميع البرامج مهما كانت بسيطة ستمرّ بالمحمّل، نظرًا لاعتماد لغة سي المكثف على المكتبات، وهذا ما قد يكون غير واضح عند النظرة الأولى أو للمتعلّم الجديد.
الدوال
تتكون لغة C من مجموعة عناصر تشكل لبنات البناء الأساسية لها، مثل الدوالّ Functions وما نطلق عليه تسمية المتغيرات العامة global variables، إذ تُسمى هذه العناصر في نقطة ما من البرنامج عند تعريفها، وتحتوي طريقة الوصول لهذه العناصر باستخدام اسمائهم ضمن البرنامج على بعض القواعد، وتُوصف هذه القواعد في المعيار بمصطلح الربط Linkage. سنتكلم في الوقت الحالي فقط عن الربط الخارجي External Linkage وانعدام الربط No linkage، إذ تُدعى العناصر التي يمكن الوصول إليها ضمن البرنامج كاملًا، مثل دوال مكتبة معينة، بعناصر الربط الخارجي، وتُستخدم العناصر عديمة الربط بكثرة أيضًا ولكن الوصول إليها محدودٌ بصورةٍ أكبر.
تُسمى المتغيرات المستخدمة داخل الدالة بالمتغيرات “المحلية Local” وهي عديمة الربط، وعلى الرغم من أننا نتفادى المصطلحات المعقدة قدر الإمكان في هذا الكتاب مثل المصطلحات التي ذكرناها سابقًا، ولكن لا توجد طريقة أبسط من شرح هذه المصطلحات. ستألف مصطلح الربط ضمن هذا الكتاب، والنوع الوحيد من الربط الخارجي الذي ستراه حاليًّا هو استخدام الدوال.
تكافئ الدوال في لغة سي الدوال والبرامج الفرعية في لغة فورتران FORTRAN والدوال والإجراءات في لغة باسكال Pascal وألغول ALGOL، بينما لا تمتلك لغة بيسك BASIC ومعظم طفراتها mutations البسيطة أو لغة كوبول COBOL مقدار الدوال التي تمتلكه لغة سي.
الفكرة من الدالة بسيطة وتتمثّل بإعطائك الإمكانية بتضمين فكرةٍ واحدة أو عملية ضمن قالب، وإعطائها اسمًا ما واستدعائها ضمن أجزاءٍ مختلفة من برنامجك باستخدام اسمها فقط. تكون تفاصيل العملية هذه غير واضحة عند ذكر الاسم ضمن البرنامج، وهذا الأمر طبيعي. وفي البرامج المصممة المهيكلة بصورةٍ جيدة، يمكنك تغيير طريقة عمل دالة ما (شرط ألّا يغير ذلك من طبيعة العمل) دون أن تؤثر على الأجزاء الأخرى من البرنامج.
هناك دالةٌ ذات اسم مميز في البيئات المستضافة ألا وهي دالة “main”، إذ تمثِّل هذه الدالة نقطة بداية البرنامج عند تشغيله، أما في البيئات المستقلة فالذي يحدد أولى خطوات البرنامج هي دالة معرفة مسبقًا Implementation defined؛ وهذا يعني أنه على الرغم من أن المعيار لا يحدد ما الذي سيحدث، إلا أن سلوك البرنامج يجب أن يكون محدّدًا وموثقًا. يتوقف البرنامج عندما يغادر دالة “main”. ألقِ نظرةً على البرنامج البسيط التالي الذي يحتوي على دالتَين:
#include
/*
* Tell the compiler that we intend
* to use a function called show_message.
* It has no arguments and returns no value
* This is the “declaration”.
*
*/
void show_message(void);
/*
* Another function, but this includes the body of
* the function. This is a “definition”.
*/
main(){
int count;
count = 0;
while(count < 10){
show_message();
count = count + 1;
}
return(0);
}
/*
* The body of the simple function.
* This is now a “definition”.
*/
void show_message(void){
printf(“hello\n”);
}
شرح تمرين 1.1
ما الذي احتواه التمرين السابق؟
يمكن لمثال صغير جدًا أن يقدِّم لك الكثير عن لغة سي، إذ احتوى التمرين السابق على دالتين وتعليمة
include#
، وبعض التعليقات، بالإضافة لأشياء أخرى، وبما أنّ التعليق هو أبسط الأجزاء في البرنامج دعونا نبدأ به.
التنسيق والتعليق
تخطيط برنامج مكتوب بلغة سي ليس مهمًّا للمصرف، ولكنه هام لنقل بعض المعلومات عن البرنامج للقارئ البشري ولتسهيل عملية قراءته، إذ تسمح لك لغة سي باستخدام محرف المسافة space ومسافات الجدولة tabs والسطور الجديدة newline دون أن تؤثر على عمل البرنامج. تُدعى المحارف الثلاث المذكور سابقًا باسم المسافة الفارغة white space، ولا يميّز بينها المصرّف لأنها ببساطة تغير من موضع الكلمات دون التأثير “مرئيًّا” على ما يُعرض على جهاز الخرج. يمكن أن نلاحظ المسافة البيضاء في أي مكان من البرنامج عدا وسط المعرّفات Identifiers والسلاسل النصية Strings والمحارف الثابتة Character constants؛ إذ نقصد بالمعرفات هنا اسم الدالة أو كائن Object آخر، لا تشغل بالك بالسلاسل النصية والمحارف الثابتة إذ سنناقشها في الفصول القادمة.
يُعد الفصل بين شيئين مختلفين من الحالات التي يصبح فيها استخدام المسافات الفارغة ضروريًّا، إذ قد يتسبب تلاصقهما بتفسيرهما شيئًا آخر، مثل الجزء
void show_message
، إذ نحتاج لمسافة فارغة للفصل بين الكلمتين
void
و
show_message
، لكن احتواء
)show_message
على مسافة بيضاء بينها وبين القوس المفتوح غير ضروري، ووجودها مجرّد أسلوب تنسيقي في كتابة الشيفرة لا أكثر.
يبدأ التعليق في لغة C باستخدام المحرفين “*/” المتلاصقين دون أي فراغ بينهما، ويُعد أي شيء يأتي بعدهما إضافةً للمحرفين “/*” مسافةً فارغةً واحدة. لم يكن الأمر مماثلًا في معيار سي القديم، إذ كانت تنص القاعدة على إمكانية استخدام التعليق في أي مكان يمكن أن تُستخدم فيه المسافة الفارغة، أما الآن يُعد التعليق بحد ذاته مسافةً فارغة؛ التغيير الحاصل طفيف وستكون الأمور أكثر وضوحًا في جزئية لاحقة من هذه السلسلة عندما نناقش المعالج المُسبق preprocessor. يجعل تنسيق تعليقات لغة سي بهذا الشكل تضمين تعليق بداخل تعليق آخر غير ممكنٍ، نظرًا لإغلاق أول زوج محارف “*/” في التعليق الثاني كتلة التعليق، وهذا إزعاجٌ بسيط ستعتاد عليه لاحقًا.
من الممارسات الشائعة وضع زوج المحارف “*/” في بداية كل سطر من تعليق متعدد الأسطر لإبراز كلّ منهم، كما هو موضحٌ في مثالنا السابق.
تعليمات المعالج المسبق
التعليمة الأولى في مثالنا السابق هي موجّه للمعالج المُسبق Preprocessor directive، إذ تضمَّن مصرّف لغة سي سابقًا مرحلتين، هما المعالجة المُسبقة ومرحلة التصريف الاعتيادية؛ إذ تمثّل المعالجة المُسبقة معالج ماكرو macro processor تجري بعض عمليات التلاعب النصي البسيطة على البرنامج قبل أن تمرّر النص الناتج إلى المصرّف، وقد أصبحت عملية المعالجة المسبقة جزءًا أساسيًّا من عمل المصرّف ولهذا تُعد جزءًا لا يتجزّأ من اللغة.
على الرغم من أن لغة سي حساسة لما يقع في نهاية الأسطر إلا أن عملية المعالجة المسبقة تلاحظ الأسطر فقط، ومع ذلك من الممكن كتابة موجّه معالجة مسبقة متعدد الأسطر، ولكنه غير شائع، وستشعر بالغرابة عندما تجد هذه الطريقة متبعة. يُعد أي سطرٍ يبدأ بالمحرف # توجيهًا للمعالج المسبق.
في التمرين 1.1 يؤدي موجّه المعالجة المسبقة
include#
إلى تبديل السطر هذا بمحتويات ملف آخر، وفي هذه الحالة اسم الملف يوجد ما بين القوسين
<
و
>
، وهذه طريقة شائعة لتضمين محتوى ملفات الترويسات Header files القياسية ضمن برنامجك، دون الاضطرار لكتابتها بنفسك. يحتوي ملف
<stdio.h>
المهم العديد من المعلومات الضرورية التي تسمح لك باستخدام مكتبة الدخل والخرج القياسية، بهدف الحصول على الدخل وإظهار الخرج، فإذا أردت استخدام هذه المكتبة عليك أن تتأكد من وجود
<stdio.h>
. كان معيار سي السابق متساهلًا أكثر بهذا الشأن.
تعليمات التعريف Define
تُعد تعليمة
define#
من الإمكانيات الأخرى والمستخدمة كثيرًا للمُعالج المُسبق، إذ تُستخدم على النحو التالي:
#define
IDENTIFIER replacement تعني التعليمة السابقة أنه على المعالج المُسبق استبدال جميع الكلمات المطابقة إلىIDENTIFIER
بالكلمةreplacement
عند جميع نقاط ورودها ضمن البرنامج. يُكتب دائمًا المعرِّف بأحرف كبيرة IDENTIFIER، وهذا اصطلاحٌ برمجي يساعد قارئ الشيفرة على فهمها، أما الجزء المُبدّل به replacement فقد يكون أي سلسلة نصية. تذكر أن المعالج المسبق لا يعرف قواعد لغة سي، إذ أن مهمته هي التلاعب النصي فقط. يكون الاستخدام الأكثر شيوعًا لهذه التعليمة هو التصريح Declare عن أسماء القيم العددية الثابتة كما هو موضح فيما يلي:
#define PI 3.141592 #define SECS_PER_MIN 60 #define MINS_PER_HOUR 60 #define HOURS_PER_DAY 24
واستخدام القيم على النحو التالي:
circumf = 2*PI*radius; if(timer >= SECS_PER_MIN){ mins = mins+1; timer = timer - SECS_PER_MIN; }
سيكون الخرج الناتج عن المعالج المُسبق مماثلًا فيما لو كتبت الشيفرة التالية:
circumf = 2*3.141592*radius; if(timer >= 60){ mins = mins+1; timer = timer - 60; }
أخيرًا، نستطيع تلخيص ما سبق على النحو التالي:
- تتعامل تعليمات المعالج المُسبق مع الملفات النصية سطرًا بسطر، على نقيض لغة سي.
- تُستخدم التعليمات من النوع
include#
لقراءة محتوًى من ملفٍ معين، عادةً لتسهيل التعامل مع دوال مكتبةٍ ما. - تُستخدم تعليمات
define#
لتسمية الثوابت، وتُكتب الأسماء اصطلاحًا بحروفٍ كبيرة.
تعريف وتصريح الدالة
التصريح
نلاحظ وجود ما يُسمى تصريح الدالة function declaration بعد تضمين ملف
<stdio.h>
، الذي يخبر المصرّف أن
show_message
دالة لا تأخذ أي وسيط ولا تُعيد أي قيمة، ويوضح لنا هذا تغييرًا جرى على المعيار، ألا وهو النموذج الأولي للدالة function prototype، وسنناقش هذا الموضوع بتوسّع لاحقًا. ليس من الضروري التصريح عن الدالة مسبقًا، إذ ستستخدم لغة سي بعض القواعد القديمة الافتراضية في هذه الحالة، إلا أنه ينصح بشدة التصريح عن الدالة في البداية.
الفرق بين التصريح والتعريف هو أن التصريح يصف نوع الدالة والوسطاء المُمرّرة له، بينما يحتوي التعريف على بنية الدالة بالكامل. سيهمّنا فهم الفرق بين المصطلحين لاحقًا.
يستطيع المصرّف تفقد استعمال الدالة
show_message
فيما إذا كان صحيحًا أم لا بالتصريح المُسبق عنها قبل استخدامها، ويصف التصريح ثلاث خصائص عن الدالة، هي: اسمها ونوعها وعدد الوسطاء ونوعهم؛ إذ يشير الجزء
)void show_message
إلى نوع الدالة والقيمة التي تُعيدها بعد استدعائها وهي
void
(سنناقش معناها لاحقًا). نستطيع رؤية الاستخدام الثاني لكلمة
void
في قائمة الوسطاء للدالة
(void)
، والذي يعني أن الدالة لا تقبل أي وسطاء.
التعريف
تلاحظ في نهاية البرنامج تعريف الدالة، وبالرغم من أنّ أن طولها ثلاث أسطر فقط، إلا أنها تُعد مثالًا على تعريف دالة متكامل.
تنفِّذ دوال لغة سي المهام التي قد تقسِّمها بعض اللغات الأخرى إلى جزأين، إذ تستخدم لغات البرمجة الدوال لإعادة قيمةٍ ما، مثل دالة الجيب المثلثي
sin
وجيب التمام
cos
أو ربما دالة تُعيد الجذر التربيعي لعددٍ ما، وهذه الطريقة التي تعمل بها دوالّ لغة سي، بينما تجري بعض لغات البرمجة هذه العملية باستخدام ما يشبه الدوال ولكن الفرق هنا هو عدم إعادة القيمة، مثل استخدام فورتران للبرامج الفرعية واستخدام باسكال وألغول للإجراءات. تنجز لغة سي كل هذه المهام باستخدام الدوال عن طريق تحديد نوع القيمة المُعادة عند التصريح عن الدالة، ولا تُعيد الدالة
show_message
أي قيمة، لذلك نوعها
void
.
إما أن يكون استخدام القيمة
void
بديهيًّا لك، أو صعب الفهم حسب الطريقة التي تنظر لها للأمر. ففي الحقيقة، يمكننا الدخول في نقاشات فلسفيّة جانبية وغير مثمرة عن كون
void
يصف نوع قيمةٍ أو لا، لكن أفضل تجنب ذلك. بغض النظر عن رأيك، استخدام النوع
void
التي تعني: “أنا لا أهتم بأي قيمة ترجعها هذه الدالة (أو لا ترجعها)”.
إذًا، نوع الدالة هو
void
واسمها
show_message
، أما القوسان
()
اللذان يتبعان هذه المعلومات، فهما لتنبيه المصرّف أننا نقصد التعريف، أو التصريح عن دالة. إذا كانت الدالة تقبل أي وسيط، فيجب وضع اسمه داخل القوسين. الدالة التي نتكلم عليها في مثالنا لا تأخذ أي وسطاء وهذا الأمر موضّح عن طريق استعمال الكلمة
void
بداخل القوسين. وبالتالي اتّضح أن للكلمة التي تصف الفراغ والرفض أهميّةٌ بالغة.
يشكّل متن الدالة تعليمة مركبة compound statement، وهي مجموعةٌ من التعليمات المُحاطة بأقواس معقوصة
{}
، على الرغم من وجود تعليمة واحدة فقط إلا أن الأقواس مطلوبة، وعمومًا، تسمح لك لغة سي باستخدام تعليمة مركبة في أي مكان تسمح به عادةً باستخدام تعليمة واحدة بسيطة، وتهدف الأقواس المعقوصة لتحويل عدة تعليمات متتالية إلى تعليمة واحدة.
إذا سألت السؤال المبرَّر “هل يتوجب استخدام الأقواس المعقوصة في كل مكان، إذا كان الهدف منها جمع عدّة تعليمات لتعليمة واحدة؟” الإجابة: نعم، عليك استخدام الأقواس المعقوصة، والمكان الوحيد الذي لا تستطيع فيه استخدام تعليمة واحدة عوضًا عن تعليمة مركبة هو عند تعريف دالةٍ ما. بالتالي، أبسط دالة يمكننا إنشاؤها هي دالةٌ فارغة، لا تفعل أي عملٍ إطلاقًا:
void do_nothing(void){}
التعليمة الموجودة بداخل دالة
show_message
هي استدعاءٌ لدالةٍ من مكتبة
printf
، إذ تُستخدم هذه الدالة لتنسيق وطباعة الأشياء، والاستخدام الموجود في المثال هو أبسط استخدامات هذه الدالة. تقبل الدالة
printf
وسيطًا واحدًا أو أكثر، التي تُمرَّر قيمهم من استدعاء الدالة إلى الدالة نفسها، في مثالنا هذا، الوسيط هو السلسلة النصية. يُفسّر محتوى السلسلة النصية من الدالة
printf
وتتحكم الدالة بطريقة عرض الخرج حسب الوسطاء الممرّرة له، يماثل عمل الدالة عمل تعليمة
FORMAT
في فورتران إلى حدٍّ ما.
خُلاصة القول:
- تُستخدم التصريحات في التصريح عن اسم الدالة ونوع القيمة المُعادة ونوع قيم الوسطاء إن وُجدت.
- تعريف الدالة هي تصريح للدالة مع محتواها أيضًا.
- يُصرَّح عن الدالة التي لا تعيد أي قيمة باستخدام الكلمة
void
، على سبيل المثال:
;void func(/* list of arguments */)
- يجب أن يصرّح عن الدالة التي لا تأخذ أي وسيط باستخدام الكلمة
void
ضمن لائحة الوسطاء، على سبيل المثال:
;void func(void)
السلاسل النصية
تُعرَّف السلاسل النصية في لغة سي بأنها سلسلة من المحارف المحتواة داخل علامتي تنصيص على النحو التالي:
"like this"
من غير المسموح أن تشغر السلسلة النصية عدة أسطر، إذ تُعد عنصرًا واحدًا بصورةٍ مشابهة للمعرفات. إلا أنه من الممكن استخدام محرفَي المسافة ومسافة الجدولة داخل السلسلة النصية. يوضح المثال أدناه سلسلةً نصيّةً صالحةً وأخرى غير صالحة في السطرين الثاني والثالث:
"This is a valid string" "This has a newline in it and is NOT a valid string"
يمكنك الحصول على سلسلة نصية طويلة جدًا بالاستفادة من حقيقة أن المحرف “\” في نهاية السطر يختفي تمامًا ضمن برنامج سي عند التنفيذ:
"This would not be valid but doesn't have \ a newline in it as far as the compiler is concerned"
الحل الثاني هو باستخدام ميّزة ضم السلاسل النصية، التي تنظر لكل سلسلتين نصيتين متجاورتين على أنهما سلسلةٌ نصيةٌ واحدة:
"All this " "comes out as " "just one string"
بالعودة للمثال 1.1، تُعد السلسلة النصية “n\” مثالًا على ما يُدعى محرف الهروب escape character ويمثّل في حالتنا هذه حالة إنشاء سطر جديد، وستطبع الدالة
printf
محتوى السلسلة النصية على ملف برنامج الخرج، بحيث سيكون الخرج “Hello” متبوعًا بسطرٍ جديد.
يسمح المعيار الجديد باستخدام المحارف ذات البايتات المتعدّدة multibyte characters لدعم الأشخاص العاملين ببيئة تستخدم مجموعة محارف أوسع من معيار آسكي ASCII الأميركي، مثل معيار Shift JIS المُستخدم في اليابان. يعرّف المعيار الجديد 96 محرفًا تمثِّل أبجدية لغة سي (المتطرّق لها في مقال أبجدية لغة C)، وفي حال كان نظامك يستخدم مجموعة محارف موسّعة extended، فسيكون المكان الوحيد الذي قد تستخدمها هو بداخل سلسلة نصية، أو متغيرات من نوع محرف، أو ضمن التعليقات وأسماء ملفات الترويسة Header files. ينبغي عليك تفقُّد ملفات توثيق نظامك إذا أردت استخدام ميزة دعم المحارف الموسّعة.
دالة main
يحتوي المثال 1.1 على دالتين، هما: دالة
show_message
ودالة
main
؛ فإذا صرفنا النظر عن طول دالة
main
موازنةً مع الدالة
show_message
، فسنلاحظ أن الدالتين مبنيتان بالشكل نفسه، إذ تحتوي كلا الدالتين على اسمٍ وقوسين
()
متبوعين بقوس معقوص، وتعليمةٍ مركبة محتواة داخل القوسين المعقوصين تتبٍّع تعريف الدالة. على الرغم من أن الدالة المركبة مختلفةٌ عن الدالة الأخرى، إلا أنك ستجد قوس الإغلاق المعقوص نفسه
{
الذي يتماشى مع القوس الأول
}
.
يُعد هذا المثال دالةً حقيقيّةً يمكن استخدامها في التطبيقات الواقعية، إذ تحتوي على عدة تعليمات ضمن متن الدالة بدلًا من تعليمة واحدة في الدالة السابقة، ولربما لاحظت أيضًا أن الدالة غير مصرّح عنها باستخدام الكلمة
void
، لأن الدالة في الحقيقة تمرّر قيمة معينة، ولكننا سنتكلم عنها لاحقًا.
الشيء المميز بخصوص دالة
main
هو كونها أول دالة تُستدعى عند تنفيذ البرنامج في بيئة مستضافة، إذ تستدعي لغة سي الدالة
main
أوّلًا عند تشغيل البرنامج، وهذا هو السبب في تسمية الدالة بهذا الاسم (main تعني رئيس)، وعلى هذا فهي دالة هامة، ولكن محتوى الدالة هام بقدر مساوٍ، وكما ذكرنا سابقًا يمكن أن تحتوي الدالة على عدّة تعليمات بداخل التعليمة المركبة، دعونا نأخذ نظرة على هذه التعليمات.
التصريح
التعليمة الأولى هي:
int count;
وهي ليست تعليمة لتنفيذ أمرٍ ما، بل تعلن عن متغيرٍ جديد ضمن البرنامج، واسمه
count
من نوع “عدد صحيح integer” والكلمة التي تحدد هذا النوع من المتغيرات ضمن برنامج سي مُختصرة إلى الكلمة
int
. لا تدل جميع أسماء أنواع المتغيرات على نوعها بوضوح في لغة سي، إذ يُستخدم اسم نوع المتغير في بعض الأحيان مثل كلمةٍ مفتاحية مختصرة وفي أحيان أخرى يُكتب كاملًا. لحسن الحظ يمكن تخمين نوع البيانات للكلمة
int
بقراءتها.
بفضل هذه التعليمة، يعلم المصرّف الآن أن هناك متغيّرًا باسم
count
لتخزين قيم الأعداد الصحيحة. يجب التصريح عن جميع المتغيرات قبل استخدامها في لغة سي، على نقيض لغة FORTRAN، التي يجب أن يأتي التصريح قبل استخدام المتغير ضمن أي تعليمة.
ملاحظة: يُعد التصريح عن المتغير
count
تعريفًا عنه في ذات الوقت، وسنناقش الفرق بين الاثنين وسنلاحظ أن الفرق مهم.
تعليمة الإسناد
بالانتقال إلى السطور التالية، نلاحظ تعليمة الإسناد assignment statement، وهي التعليمة التي أسندت أول قيمة للمتغير
count
(القيمة هي صفر في حالتنا هذه). كانت قيمة المتغير
count
قبل تعليمة الإسناد غير معرّفة undefined وغير آمنة الاستخدام، وربما تتفاجئ بحقيقة أن رمز الإسناد -أي عامل الإسناد assignment operator– يُمثَّل بإشارة مساواة واحدة
=
، وهذا التمثيل مستخدمٌ في معظم لغات البرمجة الحديثة، ولكنه ليس بمشكلة كبيرة.
إذًا، بالوصول لهذه النقطة في برنامجنا نكون قد صرّحنا عن متغير وأسندنا له قيمة الصفر، ماذا بعد؟
تعليمة الحلقة التكرارية While
سنتكلم عن واحدة من تعليمات التحكم بالحلقات، ألا وهي تعليمة
while
لنلقي نظرةً على شكلها العام:
while(expression) statement
هل هذا كل ما هنالك؟ نعم، هذه بنية تعليمة
while
، الجزء التالي في مثالنا هو:
count < 10
ويدعى باسم التعبير العلائقي relational expression، وهو مثال عن إحدى التعبيرات الصالحة الممكن استخدامها في جملة الحلقة التكرارية -مكان
expression
– ويُتبَع التعبير بتعليمة مركّبة، وهي مثال عن التعليمات الصالحة أيضًا الممكن استخدامها -مكان
statement
-. وهكذا، نستطيع تشكيل تعليمة
while
صحيحة.
إذا برمجت برنامجًا في السابق، فستلاحظ أن متن الحلقة سينفَّذ بصورةٍ متكررة طالما أن التعبير
count < 10
صحيح؛ وإذا أردنا برمجة حلقةٍ منتهية، فعلينا كتابة تعليمة ما تتسبّب بجعل التعبير السابق خاطئًا في مرحلة من المراحل، وهذا موجود في مثالنا فعلًا.
هناك تعليمتان فقط داخل متن الحلقة، إذ تمثِّل الأولى استدعاءً لدالة
show_message
وتُكتب تعليمة استدعاء الدالة منتهيةً بقوسين
()
تحتويان ضمنهما لائحة الوسطاء؛ فإذا لم تحتوي الدالة على أي وسيط، نكتب قوسين فارغين؛ وإذا احتوت على وسيط أو عدة وسطاء نكتب ذلك ضمن القوسين على النحو التالي:
/* call a function with several arguments */ function_name(first_arg, second_arg, third_arg);
استدعاء الدالة
printf
هو مثال آخر، وسنشرح المزيد عن هذا الموضوع في جزئية لاحقة من هذه السلسلة.
تمثّل التعليمة الأخيرة ضمن الحلقة تعليمة إسناد تضيف الرقم واحد إلى قيمة المتغير
count
، لكي نتوصل في نقطة ما إلى قيمة تجعل من التعبير الخاص بالحلقة خاطئًا.
تعليمة الإعادة return
التعليمة الأخيرة المتبقية في المثال هي تعليمة
return
، وتبدو للوهلة الأولى أنها استدعاءٌ لدالة ٍما، ولكن التعليمة تُكتب على النحو التالي:
return expression;
والتعبير
expression
اختياري، إذ يتبِّع مثالنا تنسيقًا شائعًا ألا وهو وضع التعبير ضمن قوسين، ولكن الأقواس غير ضرورية ولا تؤثر على عمل التعليمة.
تعيد تعليمة
return
قيمةً من الدالة الحالية الواقعة ضمنها إلى مستدعي caller الدالة؛ فإذا لم تُزوَّد التعليمة بالتعبير
expression
فستعاد قيمةٌ غير معروفة unknown إلى المستدعي، وهو أمرٌ خاطئ، عدا حالة إعادة
void
.
الدالة
main
غير مصرّح بنوع خاص بها على الإطلاق على عكس دالة
show_message
، فأيّ نوع من القيم تعيدها هذه الدالة؟ الإجابة هي
int
، إذ تفترض لغة سي نوع البيانات المُعاد
int
في حالة عدم تخصيص النوع بوضوح، لذا من الشائع أن تجد الدالة
main
مكتوبةً بهذه الطريقة:
int main(){
وهو تعبير مماثل لما ورد في مثالنا، ويعطي النتائج نفسها، لكنك لا تستطيع استعمال هذه الميزة للحصول على نوع اعتيادي للمتغيرات بالطريقة نفسها، ويجب أن تصرِّح عن نوعها بوضوح.
لكن ما الذي تعني القيمة المُعادة من الدالة
main
وإلى أين تُرسل؟ كانت القيمة تُرسل إلى نظام التشغيل أو الجهة التي شغّلت البرنامج في لغة سي القديمة، وفي البيئات المشابهة لنظام يونيكس UNIX، كانت القيمة “0” تعني “نجاح” العملية، بينما يدلّ على “فشل” العملية أي رقم آخر (غالبًا “1-“). حافظ المعيار عند قدومه على هذا التقليد، إذ يدل “0” على التنفيذ الناجح للبرنامج، ولكن هذا لا يعني أن القيمة “0” تُمرّر إلى البيئة المُضيفة، بل تُمرّر القيمة المناسبة للدلالة على نجاح البرنامج ضمن هذا النظام. يسبب هذا الكثير من الالتباس، لذا قد تحبذ استخدام القيمتين المعرّفتين “EXIT_SUCCESS” و”EXIT_FAILURE” ضمن ملف الترويسة
<stdlib.h>
.
يماثل استخدام التعليمة
return
ضمن الدالة
main
استخدام الدالة
exit
مزوّدًا بالقيمة المُعادة وسيطًا، والفرق هنا أن الدالة
exit
قد تُستدعى من أي مكان ضمن البرنامج، وأن توقف البرنامج في النقطة التي استُدعيت فيها بعد إنجاز بعض العمليات النهائية. إذا أردت استخدام الدالة
exit
، يجب عليك تضمين ملف الترويسة
<stdlib.h>
، ومن هذه اللحظة فصاعدًا سنستخدم
exit
بدلًا من استخدام
return
ضمن الدالة
main
.
الملخص
تُعيد الدالة
main
قيمةً من نوع
int
.
يُماثل استخدام التعليمة
return
ضمن الدالة
main
استدعاء الدالة
exit
، الفرق هنا أنه من الممكن استدعاء
exit
في أي نقطة ضمن البرنامج.
إعادة القيمة 0 أو
EXIT_SUCCESS
يعني نجاح البرنامج، بينما تُعد أي قيمة أخرى فشلًا للبرنامج.
مع أنّ المثال المُناقش قصير، إلا أنه سمح لنا بمناقشة العديد من مزايا اللغة المهمة، وهي:
- بنية البرنامج Program structure.
- التعليق Comment.
- تضمين الملفات File inclusion.
- تعريف الدوال Function definition.
- التعليمات المركبة Compound statements.
- استدعاء الدوال Function calling.
- التصريح عن المتغيرات Variable declaration.
- العمليات الحسابية Arithmetic.
- الحلقات التكرارية Looping.
لكننا طبعًا لم نناقش هذه المواضيع بتعمّق بعد.
اترك تعليقاً