لماذا تحتاج البرامج إلى تفرّعات
كل برنامج حتى الآن جرى تنفيذه بخط مستقيم: السطر 1، ثم 2، ثم 3. أما البرامج الحقيقية فتتّخذ قرارات. هل كلمة المرور صحيحة؟ هل العدد سالب؟ هل فُتح الملف؟ جملة if هي الطريقة التي تطرح بها C سؤالًا وتنفّذ شيفرة فقط حين تكون الإجابة نعم.
يُفحَص الشرط temperature > 30 مرة واحدة. وهو محقّق، فتُطبع الرسالة. أما السطر الأخير فخارج الأقواس المعقوفة، فيُطبع مهما كان. غيّر 31 إلى 18 وشغّل مجددًا - تختفي الرسالة الأولى وتبقى الثانية.
الصياغة: أقواس وأقواس معقوفة وبلا فاصلة منقوطة
لجملة if في C شكل ثابت:
if (condition) {
// يُنفَّذ حين يكون الشرط غير صفري
}
ثلاث قواعد تستحق الحفظ مبكرًا. الأقواس حول الشرط مطلوبة. ولا توجد كلمة مفتاحية then. ولا فاصلة منقوطة بعد القوس المغلق - فواحدة شاردة تُنهي الجملة عند ذلك الحد:
// BUG: the if does nothing; the printf always runs
if (x > 10);
{
printf("big\n");
}
هذا يُترجَم دون مشكلة. فـ ; جملة فارغة تصبح جسم if، والأقواس المعقوفة تحتها مجرّد كتلة قائمة بذاتها تُنفَّذ دائمًا. والترجمة بـ gcc -Wall تلتقط ذلك.
أما الأقواس المعقوفة نفسها فاختيارية لجملة واحدة، لكن أبقِها. فجملة if بلا أقواس معقوفة ثم ينمو لها سطر ثانٍ لاحقًا مصدر كلاسيكي للأخطاء، لأن الإزاحة لا تجمع الجمل في C.
else وelse if
توفّر else المسار الآخر:
ولأكثر من نتيجتين، اربط سلسلة بـ else if. تفحص C كل شرط بالترتيب وتتوقّف عند أول شرط محقّق:
الترتيب هنا بالغ الأهمية. فلأن السلسلة تتوقّف عند أول تطابق، لا يرى الفحص الثاني إلا الدرجات دون 90 - ولهذا لا يحتاج score >= 80 إلى إضافة && score < 90. اكتب السلسلة من أكثر الشروط تقييدًا إلى أقلّها، وإلا وقعت كل القيم في الفرع الأول.
وelse الختامية اختيارية. ضمّنها كلما كانت السلسلة مقصودة لتغطية كل الحالات؛ فغيابها يعني "إن لم يطابق شيء فلا تفعل شيئًا"، وهذا مقبول لكن ينبغي أن يكون مقصودًا.
قاعدة الصحّة: الصفر خاطئ وكل ما عداه صحيح
لا تملك لغة C في جوهرها نوعًا منطقيًا منفصلًا في الشروط. فجملة if تسأل ببساطة عمّا إذا كان التعبير غير صفري.
يعرض السطران الأخيران النصف الآخر من القاعدة: عوامل المقارنة تعبيرات عادية تنتج 1 للصحيح و0 للخطأ. ولهذا تعمل if (5 > 2) - فهي في الحقيقة if (1).
ولهذا أيضًا تكون -3 صحيحة في C. فالقاعدة هي غير صفري، لا موجب. وإن أردت معرفة ما إذا كان عدد موجبًا فاكتب if (n > 0) لا if (n).
أضافت C99 الترويسة <stdbool.h> بنوع bool حقيقي إضافةً إلى true وfalse، وهو أفضل قراءةً في الشيفرة الجديدة. وهما تحت الغطاء 1 و0 ما زالا، فلا يتغيّر شيء من القاعدة أعلاه - انظر القيم المنطقية في C.
عوامل المقارنة والمنطق التي ستستخدمها في الشروط:
== equal to && logical AND (both sides true)
!= not equal to || logical OR (either side true)
< less than ! logical NOT
> greater than
<= less than or equal
>= greater than or equal
و&& و|| تقصّران التقييم: فـ a && b لا تقيّم b إطلاقًا إن كانت a خاطئة، وa || b لا تقيّم b إطلاقًا إن كانت a صحيحة. وهذه ضمانة لا تحسين، فمن الآمن كتابة if (n != 0 && total / n > 5) - إذ لا يمكن أن تُنفَّذ القسمة حين تكون n صفرًا.
خطأ = مقابل ==
هذا أشيع خطأ على الإطلاق في شروط C:
// BUG: assigns 5 to x, then tests 5 (nonzero), so this ALWAYS runs
if (x = 5) {
printf("x is five\n");
}
تسمح C بالإسناد داخل تعبير، فالسطر مشروع. إنه يخزّن 5 في x، وينتج القيمة 5، و5 غير صفرية - فيُنفَّذ الفرع في كل مرة، وقد كُتب فوق x بصمت.
دفاعان. أولًا، ترجم دائمًا مع تفعيل التحذيرات:
gcc -Wall -Wextra program.c -o program
ينبّه GCC وclang كلاهما بعبارة "suggest parentheses around assignment used as truth value". وثانيًا، يكتب بعض المبرمجين الثابت قبل المتغيّر - if (5 == x) - بحيث يكون الخطأ المطبعي if (5 = x) خطأ ترجمة صريحًا لا خللًا صامتًا. وذلك الأسلوب مسألة ذوق؛ أما تفعيل التحذيرات فليس كذلك.
التداخل
يمكن لجملة if أن تحتوي على if أخرى. ويعبّر التداخل عن شروط لا معنى لها إلا بعد تحقّق شرط سابق:
مستويان مقروءان. أما أربعة فلا. وحين يزداد عمق التداخل، ابحث عن طريقة لتسطيحه - غالبًا بدمج الشروط بـ &&، أو بمعالجة حالات الفشل أولًا والعودة مبكرًا:
// flatter: deal with the exceptions, then get on with the work
if (age < 18) { printf("Adults only.\n"); return 0; }
if (!hasTicket) { printf("Please buy a ticket.\n"); return 0; }
printf("Welcome in.\n");
else المعلّقة
حين تتداخل جملة if بلا أقواس معقوفة داخل أخرى، فلأي if تنتمي else؟ قاعدة C: ترتبط else بأقرب if غير مقترنة، بصرف النظر عن كيفية إزاحة الشيفرة.
// The else belongs to the INNER if, despite the indentation
if (a > 0)
if (b > 0)
printf("both positive\n");
else
printf("a is not positive\n"); // WRONG - runs when a > 0 and b <= 0
تقول الإزاحة إن else تقترن بـ if الخارجية. أما المترجم فيقرنها بالداخلية. والأقواس المعقوفة تزيل الالتباس كليًا، وهذه هي الحجة الحقيقية لاستخدامها دائمًا:
if (a > 0) {
if (b > 0) {
printf("both positive\n");
}
} else {
printf("a is not positive\n");
}
العامل الثلاثي
حين لا يوجد التفرّع إلا لاختيار قيمة، يقول العامل الشرطي ? : ذلك في تعبير واحد:
اقرأ cond ? x : y هكذا: "إن تحقّق cond فـ x وإلا فـ y". ويُقيَّم واحد فقط من الفرعين، وينتج الكل قيمة - ولهذا يمكن إسناده أو تمريره كوسيط حيث لا تصلح جملة.
أبقِه لاختيارات القيم البسيطة. فالعوامل الثلاثية المتداخلة (a ? b : c ? d : e) مشروعة وأصعب قراءةً في كل الأحوال تقريبًا من سلسلة else if التي تحلّ محلّها.
الأسئلة الشائعة
كيف تكتب جملة if وelse في لغة C؟
ضع الشرط بين قوسين والشيفرة بين أقواس معقوفة: if (score >= 60) { printf("Pass\n"); } else { printf("Fail\n"); }. تُنفَّذ كتلة else فقط حين يكون الشرط غير محقّق، ولا توجد كلمة مفتاحية then في C.
ما الذي يُعدّ صحيحًا في جملة if بلغة C؟
أي قيمة غير صفرية. لا يملك جوهر لغة C نوعًا منطقيًا مستقلًا في الشروط، فـ if (n) تُنفَّذ حين تكون n أي شيء غير 0، وif (!n) تُنفَّذ فقط حين تكون n صفرًا تمامًا. وعوامل المقارنة مثل == و< تنتج 1 للصحيح و0 للخطأ.
ما الفرق بين = و== في لغة C؟
== تقارن، و= تُسنِد. فكتابة if (x = 5) تُسنِد 5 إلى x ثم تفحص 5، وهي قيمة غير صفرية، فيُنفَّذ الفرع دائمًا. ويُترجَم ذلك دون خطأ، ولهذا هو أشيع أخطاء المبتدئين في شروط C - وتحذّر منه معظم المترجمات بالراية -Wall.
كيف يعمل العامل الثلاثي في لغة C؟
condition ? valueIfTrue : valueIfFalse تعبير ينتج إحدى قيمتين، فيمكن أن يقع داخل إسناد أو داخل وسيط لـ printf: int max = a > b ? a : b;. استخدمه لاختيار قيمة، واستخدم if عادية حين تحتاج إلى تنفيذ جُمل.