سننظر في هذا المقال إلى الطرق المختلفة التي تُستخدم بها تعليمات التحكم بتدفق Flow control statements في برنامج سي C، بما فيها بعض التعليمات التي لم نتطرق إليها بعد، والتي تُستخدَم في معظم الحالات مع تعبيرات منطقية تحدّد الخطوة القادمة، وتُعد تعابير if
و while
البسيطة المُستخدمة سابقًا مثالًا على هذه التعابير المنطقية Logical expressions، ويمكنك استخدام تعابير أكثر تعقيدًا من الموازنات البسيطة، مثل >
و =>
و ==
وغيرها، لكن ما قد يفاجئك هو نوع النتيجة.
التعابير المنطقية والعوامل العلاقية
تجنبنا عمدًا التعقيد باستخدام تعابير منطقية في تعليمات التحكم بالتدفق في جميع الأمثلة المُستخدمة سابقًا، إذ صادفنا مثلًا تعابيرًا تشابه:
if(a != 100){...
ومن المفترض الآن أنك تعرف دعم لغة C لمفهوم "صواب True" و"خطأ False" لهذه العلاقات، لكنها تدعمها بطريقةٍ مختلفة عن المتوقّع.
تُستخدم العوامل العلاقية Relational operators المذكورة في الجدول 1 للموازنة بين مُعاملين بالطريقة المذكورة، وعندما تكون المُعاملات من أنواع عددية، تطبّق التحويلات الحسابية الموافقة إليهما.
العامل | العملية |
---|---|
>
|
أصغر من |
=>
|
أصغر من أو يساوي |
<
|
أكبر من |
=<
|
أكبر من أو يساوي |
==
|
يساوي |
=!
|
لا يساوي |
[الجدول 1. العوامل العلاقيّة]
كما أشرنا سابقًا، لا بُدّ من الانتباه لعامل اختبار المساواة ==
وإمكانية خلطه مع عامل الإسناد =
، إذ لا تحذرك لغة سي C عند حدوث ذلك كون التعبير صالح، ولكن ستكون نتائج التعبيرين مختلفة، ويستغرق المبتدئون وقتًا طويلًا للاعتياد على استخدام ==
و =
.
لعلّك تطرح على نفسك السؤال "لماذا؟"، أي "لماذا التعبيران صالحان؟" الإجابة بسيطة، إذ تفسر لغة سي مفهوم "صواب True" و"خطأ False" بقيمة "غير صفرية" و" صفريّة"، وعلى الرغم من استخدامنا للعوامل العلاقيّة في التعابير المُستخدمة للتحكم بتعليمات if
و do
، إلا أننا نستخدم في الحقيقة القيمة العددية الناتجة عن هذا التعبير؛ فإذا كانت قيمة التعبير غير صفرية، فهذا يعني أن النتيجة صحيحة؛ أما إذا تحققت الحالة المعاكسة فالنتيجة خاطئ، وينطبق هذا على جميع التعابير والعوامل العلاقيّة.
توازن العوامل العلاقيّة ما بين المُعاملات وتعطي نتيجة صفر للنتيجة الخاطئة (موازنة غير محقّقة) وواحد للنتيجة الصحيحة، وتكون النتيجة من نوع int
، ويوضح المثال التالي كيفية عملها:
#include <stdio.h> #include <stdlib.h> main(){ int i; i = -10; while(i <= 5){ printf("value of i is %d, ", i); printf("i == 0 = %d, ", i==0 ); printf("i > -5 = %d\n", i > -5); i++; } exit(EXIT_SUCCESS); }
يعطينا المثال السابق الخرج القياسي التالي:
value of i is -10, i == 0 = 0, i > -5 = 0 value of i is -9, i == 0 = 0, i > -5 = 0 value of i is -8, i == 0 = 0, i > -5 = 0 value of i is -7, i == 0 = 0, i > -5 = 0 value of i is -6, i == 0 = 0, i > -5 = 0 value of i is -5, i == 0 = 0, i > -5 = 0 value of i is -4, i == 0 = 0, i > -5 = 1 value of i is -3, i == 0 = 0, i > -5 = 1 value of i is -2, i == 0 = 0, i > -5 = 1 value of i is -1, i == 0 = 0, i > -5 = 1 value of i is 0, i == 0 = 1, i > -5 = 1 value of i is 1, i == 0 = 0, i > -5 = 1 value of i is 2, i == 0 = 0, i > -5 = 1 value of i is 3, i == 0 = 0, i > -5 = 1 value of i is 4, i == 0 = 0, i > -5 = 1 value of i is 5, i == 0 = 0, i > -5 = 1
ما الذي تعتقد حدوثه عند تنفيذ هذه التعليمة المحتمل أنها تكون خاطئة؟
if(a = b)...
تُسند قيمة b
إلى قيمة a
، وكما نعلم تعطي عملية الإسناد نتيجةً من نوع a
مهما كانت القيمة المُسندة إلى a
، وبالتالي ستنفِّذ تعليمة if
الشرطية التعليمة التالية لها إذا كانت القيمة المُسندة لا تساوي الصفر؛ أما إذا كانت القيمة تساوي الصفر، فستتجاهل التعليمة التالية. لذا يجب أن تفهم الآن ما الذي يحدث إن أخطأت استخدام عامل الإسناد بدلًا عن عامل المساواة!
سيُفحَص التعبير في تعليمات if
و while
و do
فيما إذا كان مساويًا للصفر أم لا، وسننظر إلى كلٍّ من هذه التعليمات عن كثب.
التحكم بالتدفق
يتم التحكم بالتدفق في لغة C من خلال التعليمات الآتية:
تعليمة إذا if الشرطية
تُكتب تعليمة if
بطريقتين:
if(expression) statement if(expression) statement1 else statement2
تُنفّذ تعليمة إذا الشرطية في الطريقة الأولى، إذا (وفقط إذا) كان التعبير expression لا يساوي إلى الصفر؛ أما إذا كان التعبير مساويًا للصفر، ستهُمل التعليمة statement. تذكر بأن التعليمة قد تكون مُركبّة، وذلك بوضع عدة تعليمات تابعة لتعليمة if
واحدة.
تُشابه الطريقة الثانية سابقتها، باختلاف أن التعليمة statement1
تُفحص قبل statement2
وتُنفّذ واحدةٌ منهما.
تصنَّف الطريقتان بكونهما تعليمةً واحدة حسب قواعد صياغة لغة سي، وبالتالي يكون المثال التالي صالح تمامًا.
if(expression) if(expression) statement
إذ تُتبع تعليمة (if (expression
بتعليمة if
متكاملة أخرى، وبما أنها تعليمةٌ صالحة، يمكننا قراءة التعليمة الشرطية الأولى على النحو التالي:
if(expression) statement
وبذلك فهي مكتوبة بصورةٍ صحيحة، كما يمكن إضافة مزيدٍ من الوسطاء arguments كما تريد، ولكنها عادةٌ برمجيةٌ سيئة، ومن الأفضل أن تحاول جعل التعليمة مختصرةً قدر الإمكان حتى لو لم يكن الأمر ضروريًا في حالة استخدامها، ﻷن ذلك سيسهّل إضافة مزيدٍ من التعابير إن احتجت إلى الأمر لاحقًا ويحسِّن من سهولة القراءة.
ينطبق ما سبق على طريقة كتابة تعليمة else
، ويمكنك كتابتها على النحو التالي:
if(expression) if(expression) statement else statement
ما تزال طريقة الكتابة هذه باستخدام المسافات فقط غامضة وغير واضحة، فأي تعليمات else
تتبع لتعليمات if
؟ إذا اتبعنا المسافات في مثالنا فسيوحي هذا لنا بأن التعليمة if
الثانية متبوعةٌ بالتعليمة statement
مما يجعل كل منها تعليمةً مستقلة، و else
إذًا تنتمي للتعليمة الشرطية الأولى.
هذه ليست الطريقة التي تنظر بها لغة C إلى المثال، إذ تنص القاعدة على أن else
تتبع التعليمة الشرطية if
التي تسبقها إن لم تحتوي هذه التعليمة على else
؛ وبالتالي تتبع else
إلى التعليمة الشرطية الثانية في المثال الذي ناقشناه.
لتجنُّب أي مشكلة بخصوص else
و if
كما لاحظنا في المثال السابق، يمكن إهمال التعليمة الشرطية باستخدام تعليمة مركّبة. بالعودة إلى مثالنا السابق في الجزئية الأولى من هذه السلسلة:
if(expression){ if(expression) statement }else statement
والذي يصبح باستخدام أقواس التعليمات المركّبة على النحو التالي:
if(expression){ if(expression){ statement } }else{ statement }
إن لم يرق لك مكان الأقواس في المثال السابق، فتغيير مكانها هو تفضيل شخصي ويمكنك تعديله لما تراه مناسبًا، لكن فقط كن متسقًا حيال ذلك، كما تجدر الإشارة إلى أن هناك كثيرٌ من التعصُّب بين المبرمجين بخصوص المكان الصحيح.
تعليمة do و while التكرارية
تعليمة while
بسيطة:
while(expression) statement
تُنفَّذ التعليمة في حال كانت قيمة التعبير لا تساوي الصفر، وتُفحص قيمة التعبير مجدّدًا بعد كل تكرار وتُعاد التعليمة إذا لم تكن قيمة التعبير مساويةً لصفر. هل هناك أي شيء أكثر وضوحًا من ذلك؟ النقطة الوحيدة الواجب الانتباه بشأنها هي إمكانية عدم تنفيذ التعليمة على الإطلاق، أو تنفيذها بدون توقُّف إذا لم تتضمن التعليمة أي شيء يؤثر على قيمة التعبير المبدئيّة.
نحتاج في بعض الأحيان تنفيذ التعليمة مرةً واحدةً على الأقل، ونستطيع في هذه الحالة استخدام الطريقة المعروفة بتعليمة do
على النحو التالي:
do statement while(expression);
ينبغي الانتباه على الفاصلة المنقوطة، فهي غير اختيارية. تضمن بهذه الطريقة تنفيذ التعليمة مرةً واحدةً على الأقل قبل تقييم التعبير. كان استخدام الكلمة المفتاحية while
للاستخدامين السابقين خيارًا غير موفّقًا، لكن لا يبدو أن هناك الكثير من الأخطاء الناتجة عن ذلك.
فكّر طويلًا قبل استخدام التعليمة do
، فعلى الرغم من أهميتها في بعض الحالات إلا أن استخدامها المفرط ينتج شيفرةً برمجيةً سيئة التصميم. هذا الأمر غير محقق في معظم الحالات، لكن يجب أن تتوقف وتسأل نفسك عن أهمية استخدام تعليمة do
في كل حالة قبل استخدامها للتأكد من أن هذا هو الاستخدام الصحيح لها، إذ يدل استخدامها على تفكير غير مخطّط له وتوظيف وسائل تُستخدم في لغات أخرى، أو تصميمًا سيئًا ببساطة. عندما تقتنع بأهمية استخدامها دونًا عن أي وسيلة أخرى، فاستخدمها بحرص.
اختصار عملية الإسناد والتحقق في تعبير واحد
يُعد استخدام نتيجة عملية الإسناد للتحكم بعمل حلقات while
و do
التكرارية حيلةً مستخدمةً كثيرًا في برامج سي C، وهي شائعة الاستخدام كثيرًا إذ سترغب بتعلُّمها إن صادفتها. تقع هذه الحيلة تحت تصنيف لغة سي "الاصطلاحية" واستخدامها طبيعيٌ وتلقائي لكل من يستخدم اللغة. إليك المثال اﻷكثر شيوعًا لاستخدامها:
#include <stdio.h> #include <stdlib.h> main(){ int input_c; /* The Classic Bit */ while( (input_c = getchar()) != EOF){ printf("%c was read\n", input_c); } exit(EXIT_SUCCESS); }
[مثال 2.3]
تكمن الحيلة في تعبير إسناد input_c
، إذ يُستخدم هذا التعبير في إسناد القيمة إلى المتغير وموازنته مع EOF
(نهاية الملف End of File) والتحكم بعمل الحلقة التكرارية في آنٍ واحد. يُعد تضمين عملية الإسناد على هذا النحو تحسينًا عمليًا سهلًا، وعلى الرغم من كونها تختصر سطرًا واحدًا فقط إلا أن فائدتها في تسهيل عملية القراءة (بعد الاعتياد على استخدامها) كبيرة. لا بُد أيضًا من تعلُّم مكان استخدام الأقواس أيضًا، فهي مهمةٌ في تحديد الأسبقية.
لاحظ أن input_c
من النوع int
، وذلك لتمكين الدالة getchar
من إعادة أي قيمة ممكنة لأي char
إضافةً لقيمة EOF
، ولهذا احتجنا لنوع أطول من char
.
تُعد التعليمتان while
و do
وفق قواعد صياغة لغة سي تعليمةً واحدةً مثل تعليمة if
، ويمكن استخدامهما في أي مكان يُمكن استخدام تعليمةٍ واحدةٍ فيه. ينبغي عليك استخدام تعليمة مركّبة إذا أردت التحكُّم بعدّة تعليمات، كما وضّحت أمثلة فقرة تعليمة if
.
تعليمة for التكرارية
يعدّ استخدام الحلقات التكرارية والمتغيرات عدّاداتٍ لها ميزةً شائعةً في لغات البرمجة، إذ لا ينبغي للعداد أن يكون ذا قيمٍ متعاقبة حصرًا، والاستخدام الشائع له هو تهيئته خارج الحلقة وتفقُّد قيمته عند كل تكرار للتأكد من انتهاء الحلقة التكرارية وتحديث قيمته كل دورة. هناك ثلاث خصائص مهمة مرتبطة بتحكم الحلقة، هي: التهيئة initialize والتحقق check والتحديث update، كما هو موضح في المثال التالي:
#include <stdio.h> #include <stdlib.h> main(){ int i; /* initialise التهيئة*/ i = 0; /* check التحقق*/ while(i <= 10){ printf("%d\n", i); /* update التحديث*/ i++; } exit(EXIT_SUCCESS); }
مثال 3.3
اﻷجزاء المرتبطة بعملية التهيئة والتحقق متقاربين كما تلاحظ، وموقعهما واضح بسبب استخدام الكلمة المفتاحية while
، أما الجزء الأكثر صعوبةً لتحديده هو التحديث وبالأخص إذا استُخدمت قيمة المتغير الذي يتحكم بالحلقة داخلها؛ وفي هذه الحالة -الأكثر شيوعًا- يجب أن يكون التحديث في نهاية الحلقة، بعيدًا عن التهيئة والتحقق. يؤثر ذلك في سهولة القراءة بصورةٍ سلبية، إذ من الصعب فهم وظيفة الحلقة إلا بعد قراءة محتواها كاملًا بحرص. إذًا، نحن بحاجة إلى طريقة لجمع أجزاء التهيئة والتحقق والتحديث في مكانٍ واحد لنستطيع قراءتها بسرعة وسهولة، وهذا الغرض من تصميم تعليمة for
، إذ تُكتب على النحو التالي:
for (initialize; check; update) statement
جزء التهيئة هو تعبير إسناد معظم الحالات، ويُستخدم لتهيئة المتحول الذي يتحكم بالحلقة. يأتي تعبير التحقق بعد التهيئة، إذ تُنفَّذ التعليمة داخل الحلقة إذا كان هذا التعبير ذو قيمةٍ غير صفرية، ويُتبع ذلك بتعبير التحديث الذي يزيد من قيمة المتغير المُتحكم (في معظم الحالات)، وتُعاد هذه العملية عند كل دورة، وتنتهي الحلقة التكرارية في حال كانت قيمة تعبير التحقق مساويةً الصفر.
هناك أمران مهمان يجب معرفتهما بخصوص الوصف السابق: الأول وهو أن كل جزء من أجزاء تعليمة حلقة for
التكرارية الثلاث بين القوسين هو تعبير، الثاني وهو أن الشرح وصَفَ بحرص وظيفة الحلقة التكرارية for الأساسية دون ذكر أي استخدامات بديلة. يمكنك استخدام التعابير على النحو الذي تراه مناسبًا، لكن ذلك سيكون على حساب قابلية القراءة إذا لم تُستخدم للغرض المقصود منها.
إليك البرنامج التالي الذي ينجز المهمة ذاتها بطريقتين، الطريقة الأولى باستخدام حلقة while
والطريقة الثانية باستخدام حلقة for
، واستُخدام عامل الزيادة بالطريقة المعتادة التي يُستخدم بها في هذه الحلقات.
#include <stdio.h> #include <stdlib.h> main(){ int i; i = 0; while(i <= 10){ printf("%d\n", i); i++; } /* the same done using ``for'' */ for(i = 0; i <= 10; i++){ printf("%d\n", i); } exit(EXIT_SUCCESS); }
[مثال 4.3]
ليس هناك أي اختلاف بين الطريقتين، عدا أن استخدام الحلقة for
مناسب وقابل للتعديل بصورةٍ أفضل من تعليمة while
. حاول استخدام التعليمة for
في معظم الحالات المناسبة، أي عندما تحتاج إلى حلقة يتحكم فيها عدّاد ما مثلًا؛ بينما يُعد استخدام حلقة while
أنسب عندما يكون العدد الذي يتحكم في عدد الدورات جزءًا من عمل البرنامج. يتطلب الأمر تحكيمًا من كاتب البرنامج وفهمًا لشكل وتنسيق البرنامج المكتوب بطريقةٍ جيّدة، إذ لا يوجد أي دليل يقول أن زيادة هذه الخصائص ينعكس سلبًا على شركات كتابة البرمجيات، فتمرّن على هذه الأمور قدر الإمكان.
يمكن حذف وإهمال أيٍّ من أجزاء التهيئة والتحقق والتحديث في تعليمة for التكرارية، إلا أن الفواصل المنقوطة يجب أن تبقى، ويمكن اتباع هذا الأسلوب عندما يكون العداد مهيّأً مسبقًا أو يُحدّث بداخل متن الحلقة. إذا حُذف جزء تعبير التحقق، فهذا يعطينا نتيجةً افتراضيةً تتمثل بالقيمة "صحيح true" وهذا سيجعلها حلقةً لا نهائية. الطريقة الشائعة في كتابة حلقات تكرارية لا نهائية، هي:
for(;;)
أو
while(1)
يمكنك ملاحظة هذه التعليمات في بعض البرامج المكتوبة بلغة سي C.
أهمية تعليمات التحكم بالتدفق
يمكننا كتابة برامج بدرجات تعقيد متفاوتة باستخدام تعليمات التحكم بالتدفق، إذ تعد هذه التعليمات من صلب لغة C وستوضح قراءتك لبعض البرامج الأساسية في لغة C أهمية هذه التعليمات بما يخص تقديم الأدوات الأساسية وهيكلة البرامج. ستعطي التعليمات المتبقية التي سنذكرها للمبرمج تحكمًا أكبر بهذه الحلقات أو ربما ستساعده في بعض الحالات الاستثنائية. لا تحتاج تعليمة switch
إلى أي شرح بخصوص أهمية استخدامها؛ فمن الممكن استبدالها بالعديد من تعليمات if
، ولكنها تسهِّل قراءة البرنامج كثيرًا. يمكنك النظر إلى break
و continue
و goto
على أنها بهارات لصلصةٍ حساسة المقادير، إذ يمكن لاستخدامها الحريص أن يجعل من هذه الصلصة أكلةً لذيذة، وبالمقابل سيجعل الاستخدام المفرط لها طعم الصلصة مشتّتًا وضائعًا.
تعليمة switch
يمكنك الاستغناء عن هذه التعليمة، فهي ليست جزءً أساسيًّا من لغة سي C، لكن استخدامك لها سيجعل اللغة أقلّ تعبيرًا ومتعةً للاستخدام؛ إذ تُستخدم هذه التعليمة لاختيار عددٍ من الإجراءات المختلفة حسب قيمة تعبيرٍ ما، وتجعل من تعليمة break
تعليمةً مستخدمةً كثيرًا ضمنها، إذ تُكتب على النحو التالي:
switch (expression){ case const1: statements case const2: statements default: statements }
تُقدَّر قيمة التعبير expression وتُوازن قيمته مع جميع تعابير const1 وconst2 إلى آخره، والتي تكون قيمها مختلفة (من نوع تعابير أعداد صحيحة ثابتة حصرًا)؛ فإذا تساوت القيمة مع قيمة التعبير، تُنفّذ التعليمة التي تتبع الكلمة المفتاحية case
؛ وتُنفَّذ الحالة الافتراضية التابعة للكلمة المفتاحية default
-إن وجدت- في حال عدم وجود أي قيمة مكافئة؛ وإن لم تتواجد هذه الكلمة المفتاحية (ولم تتواجد أي قيمة مكافئة)، فلن تنفِّذ التعليمة switch
أي شيء وسيتابع البرنامج تنفيذ التعليمة التالية.
من الميزات المثيرة للاهتمام هي أن الحالات ليست استثنائية، كما هو موضح في المثال التالي:
#include <stdio.h> #include <stdlib.h> main(){ int i; for(i = 0; i <= 10; i++){ switch(i){ case 1: case 2: printf("1 or 2\n"); case 7: printf("7\n"); default: printf("default\n"); } } exit(EXIT_SUCCESS); }
[مثال 5.3]
تتكرّر الحلقة بحسب قيمة i
، إذ تأخذ قيمًا من 0
إلى 10
، تتسبب قيمة 1
أو 2
بطباعة الرسالة "1or 2" عن طريق تنفيذ تعليمة printf
الأولى. وما قد يفاجئك هنا هو طباعة الرسائل التي تليها أيضًا، ﻷن تعليمة switch
تختار نقطة دخول واحدة إلى متن التعليمة، فبعد البدء في نقطة معينة، تُنفّذ جميع التعليمات التي تليها. تُستخدم عناوين case
و default
ببساطة لتحديد أي من التعليمات التي سيقع عليها الاختيار. عندما تكون قيمة i
مساويةً للقيمة 7
، ستُطبع الرسالتان الأخيرتان فقط، وأي قيمة لا تساوي إلى 1
أو 2
أو 7
ستتسبّب بطباعة الرسالة الأخيرة فقط.
يمكن أن تُكتب العناوين labels بأي ترتيب كان، لكن لا يجب أن تتكرر أي قيمة ويمكنك استخدام عنوان default
واحد فقط أو الاستغناء عنه، وليس من الضروري أن يكون العنوان الأخير، كما يمكن وضع تعليمةٍ واحدةٍ لعدّة عناوين أو عدّة تعليمات لعنوان واحد.
يمكن أن يكون التعبير الذي يتحكم بتعليمة switch
من أي نوع عدد صحيح. كانت لغة سي C القديمة تقبل نوع int
فقط، واقتطعت المصرفات قسريًّا الأنواع الأطول مما تسبب ببعض الأخطاء الغامضة بعض الأحيان.
أكبر قيود تعليمة Switch
تكمن المشكلة الأكبر بخصوص تعليمة switch
في عدم إمكانية تحديد أجزاء معينة من التعليمات بصورةٍ استثنائية، إذ تنفَّذ جميع التعليمات الموجود بداخل تعليمة switch
التي تلي التعليمة المُنفذة على نحوٍ متعاقب، والحل هنا هو استخدام تعليمة break
. ألقِ نظرةً على المثال السابق المُعدّل بحيث لا تُطبع الرسائل تباعًا بعد طباعة حالة ما، إذ تتسبب التعليمة break
بمغادرة تنفيذ تعليمة switch
مباشرةً وتمنع تنفيذ أي تعليمات أخرى ضمنها.
#include <stdio.h> #include <stdlib.h> main(){ int i; for(i = 0; i <= 10; i++){ switch(i){ case 1: case 2: printf("1 or 2\n"); break; case 7: printf("7\n"); break; default: printf("default\n"); } } exit(EXIT_SUCCESS); }
[مثال 6.3]
يوجد مزيدٌ من الاستخدامات لتعليمة break
سنتكلم عنها في فقرتها الخاصة.
تعبير العدد الصحيح الثابت
سنتكلم لاحقًا عن التعابير الثابتة، ولكن من المهم التطرق إلى طبيعة تعبير العدد الصحيح الثابت كونه التعبير الذي يجب أن تُلحقه بعنوان case
في تعليمة switch
. عمومًا، لا يحتوي تعبير العدد الصحيح الثابت أي عوامل تغيِّر من قيمته، مثل عامل الزيادة أو الإسناد، أو استدعاءٍ للدوال أو عوامل الفاصلة، ويجب أن تكون جميع المُعاملات في التعبير ثوابت صحيحة وثوابت محرفية وثوابت تعداد numeration constants وتعابير sizeof
وثوابت الفاصلة العائمة وهي المُعاملات المباشرة لمحوّلات النوع casts، كما يجب أن تكون أنواع العدد الصحيح هي الأنواع الناتجة عن أي مُعامل محوِّلٍ للنوع، وجميع هذه القيم يمكن أن تتوقعها بكونها تحت تصنيف تعبير العدد الصحيح الثابت.
تعليمة break
هذه التعليمة بسيطة، ويمكن فهمها في سياق استخدامها ضمن تعليمة switch
، أو do
، أو while
، أو for
، إذ يقفز تدفق البرنامج عند استخدامها إلى التعليمة التالية خارج متن التعليمة الحالية التي تتضمن تعليمة break
؛ وتُستخدم كثيرًا في تعليمات switch
، إذ إنها ضرورية بطريقةٍ أو بأخرى للحصول على التحكم الذي يحتاجه معظم الناس.
استخدام تعليمة break
بداخل الحلقات التكرارية استخدامٌ غير محبّذ بحسب الحالة، إذ إنه مبرّر عند حدوث ظروف استثنائية بداخل الحلقة مما يتطلب مغادرتها. من الجيد أن نستطيع مغادرة عدة حلقات دفعةً واحدة باستخدام تعليمة break
واحدة فقط، لكن هذا غير محقّق. ألقِ نظرةً على المثال التالي:
#include <stdio.h> #include <stdlib.h> main(){ int i; for(i = 0; i < 10000; i++){ if(getchar() == 's') break; printf("%d\n", i); } exit(EXIT_SUCCESS); }
[مثال 7.3]
يقرأ البرنامج السابق محرفًا وحيدًا من الدخل قبل طباعة قيمة i
وفق سلسلةٍ من الأعداد، وتتسبب تعليمة break
بالخروج من الحلقة في حال إدخال المحرف s
.
يُعد استخدام break
خيارًا خاطئًا إذا أردت الخروج من عدة مستويات من الحلقات، إذ أن استخدام goto
هو الطريقة السهلة الوحيدة لكن سنتركها إلى النهاية بما أن ذكرها يتطلب وجود ذكر أشياء أخرى قبلها.
تعليمة continue
يوجد لهذه التعليمة عددٌ محدودٌ من حالات الاستخدام، وقواعد استخدامها مطابقةٌ لقواعد استخدام break
عدا أنها لا تُطبَّق في تعليمات switch
. يبدأ التكرار التالي لأصغر تعليمة (أقلها مستوى) سواء كانت do
، أو while
، أو for
فور تنفيذ تعليمة continue
، ويقتصر استخدامها في بداية الحلقات حيث يجب اتخاذ قرار بشأن تنفيذ بقية متن الحلقة أم لا. تَضمَن تعليمة continue
في المثال التالي عدم تنفيذ القسمة على صفر، والتي تتسبب بسلوك غير محدد.
#include <stdio.h> #include <stdlib.h> main(){ int i; for(i = -10; i < 10; i++){ if(i == 0) continue; printf("%f\n", 15.0/i); /* * Lots of other statements ..... */ } exit(EXIT_SUCCESS); }
[مثال 8.3]
قد تنظر إلى التعليمة continue
بكونها غير ضرورية، وأنه ينبغي أن يكون تنفيذ متن الحلقة شرطيًّا بدلًا من ذلك، لكنك لن تجد الكثير من المؤيدين لرأيك، إذ يفضِّل معظم مبرمجو لغة سي C استخدام continue
بدلًا من استخدام مستوًى إضافي من المسافات لتضمين جزءٍ معين من الحلقة وبالأخص إن كان كبيرًا. يمكنك طبعًا استخدام continue
في أجزاء أخرى من الحلقة، بهدف تبسيط منطق الشيفرة وتحسين قابلية قراءتها، ولكن لا بُد من استخدامها باعتدال.
أبقِ في بالك أن التعليمة continue
ليس لها أي معنًى داخل تعليمة switch
على عكس break
، إذ أن استخدام continue
بداخل switch
ذو قيمةٍ فقط في حالة وجود حلقة تكرارية تحتوي switch
، وفي هذه الحالة يبدأ التكرار التالي من الحلقة عند تنفيذ continue
.
هناك فرقٌ مهمٌ بين الحلقات التكرارية المكتوبة باستخدام while
و for
؛ ففي حلقات while
وعند استخدام continue
يقفز التنفيذ إلى فحص قيمة التعبير التي تتحكم بالحلقة؛ بينما تُنفذ تعليمة التحديث وتعبير التحكم في حالة for
.
تعليمة goto والعناوين labels
يعرف الجميع أن استخدام تعليمة goto
هو "تصرف سيء"، إذ أنها تجعل برنامجك صعب المتابعة والقراءة، وتشتِّت هيكله وتدفقه إذا استُخدمت من دون عناية. كتبت مجموعة دايجكسترا Dijkstra ورقةً شهيرةً في 1968 باسم "تعليمة Goto مُضرة Goto Statement Considered Harmful" التي يذكرها الجميع ولكن لم يقرأها أي أحد.
الشيء الأكثر إزعاجًا هو عندما يكون استخدامها في بعض الحالات ضروريًّا للغاية، إذ تُستخدم في لغة سي C للخروج من عدة حلقات تكرارية متداخلة أو للانتقال إلى مخرج للتعامل مع الأخطاء في نهاية الدالة، وستحتاج لاستخدام عنوان label عندما تقرر استخدام goto
، ويوضح المثال التالي استخدامهما:
goto L1; /* whatever you like here */ L1: /* anything else */
العنوان هو معرّفٌ متبوعٌ بنقطتين، ويوجد للعناوين "مجال أسماء namespace" خاص بهم لتجنب الخلط بينهم وبين أسماء المتغيرات والدوال. مجال الأسماء هذا متواجدٌ فقط بداخل الدالة التي تحتويه، لذلك يمكنك إعادة استخدام أسماء العناوين في دوال مختلفة، كما يُمكن للعنوان أن يُستخدم قبل التصريح عنه أيضًا عن طريق ذكره ضمن تعليمة goto
ببساطة.
يجب أن تكون العناوين جزءًا من تعليمةٍ كاملة، حتى لو كانت فارغة، وعادة ما يكون هذا مهمًا فقط عندما تحاول وضع عنوان في نهاية التعليمة المركبة على النحو التالي:
label_at_end: ; /* empty statement */ }
تعمل goto
بطريقة واضحة، إذ تنتقل إلى التعليمة المعنونة، ولأن اسم العنوان مرئي فقط من داخل الدالة التي تحتويه فمن غير الممكن الانتقال من دالةٍ إلى دالةٍ أخرى.
من الصعب إعطاء قوانين صارمة حول استخدام تعليمة goto
ولكن على نحوٍ مشابه لتعليمات do
و continue
و break
(عدا وجودها في تعليمة switch
)، فإن الاستخدام المفرط لها غير محبذ. فكّر طويلًا قبل استخدماها وليكن استخدامها بالنسبة لهيكلية البرنامج مقنعًا، إذ استخدامك لتعليمة goto
كل 3 أو 5 دوال يدل على مشكلة ويجب أن تجد طريقةً مختلفة لكتابة برنامجك.
خلاصة
الآن وبعد استعراضنا لتعليمات التحكم بتدفق البرنامج المختلفة ورأينا أمثلةً عن استخدامها، يجب أن تستخدم بعضها في أي فرصة تُتاح لك، بينما يُستخدم بعضها الآخر لأغراض خاصة ولا يجب الإفراط في استخدامها. يجعل الانتباه إلى استخدام التعليمات برامجك المكتوبة بلغة سي C أنيقة، إذ تعطيك تعليمات التحكم بالتدفق المخصصة الفرصة لإضافة خصائص غير موجودة في بعض اللغات الأخرى.
وبذلك يبقى التكلُّم عن العوامل المنطقية كل ما تبقى للانتهاء من جانب التحكم بتدفق البرنامج في لغة سي C.
ترجمة -وبتصرف- لقسم من الفصل Control of Flow and Logical Expressions من كتاب The C Book.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.