ملخص
البرنامج الذي يكتفي بسرد التعليمات واحدةً بعد أخرى يفعل الشيء نفسه دائمًا، أيًّا كانت بياناته؛ ولكي يستجيب لها يحتاج إلى أن يقرّر. يدرس هذا الدرس قرارات C++: if وelse، اللتين تختاران بين طريقين بحسب شرط؛ وسلاسل else if، التي تختار بين طرق عدة، والتي يحدّد فيها ترتيبُ الشروط الحالةَ التي يخدمها كل فرع؛ والقرارات المتداخلة وفخّ else المعلّق؛ وswitch مع enum class، اللذين يختاران بين قيم منفصلة ذات أسماء، مع مترجم ينبّه إذا نقصت حالة أو إذا سقطت حالة في الحالة التي تليها. والمفهوم الذي يوحّد ذلك كله هو التحليل بحسب الحالات: القرار الصحيح يقسم مجموعة المدخلات إلى حالات منفصلة تغطيها تغطيةً تامة، وكل حالة تُفحص باختبار في داخلها وباختبار آخر عند كل حدّ من حدودها.
أهداف التعلم
- كتابة قرارات بسيطة ومتسلسلة ومتداخلة تخدم جميع حالات المواصفات.
- التحقق من أن حالات سلسلة
ifوelse ifمنفصلة وشاملة، وتصميم مُدخل اختبار لكل حالة. - استعمال
switchمعenum classللقيم المنفصلة، وشرح ما ينبّه إليه المترجم إذا نقصت حالة. - اكتشاف
elseالمعلّق والسقوط بين حالاتswitch، وتصحيحهما.
if و else
صورة القرار
قدّم الدرس من المسألة إلى الخوارزمية ثلاث صور يُبنى بها كل خوارزمية: التتابع والقرار والتكرار. وحتى الآن كانت برامج الدورة تتابعات، باستثناء if استُعمل مبكرًا في الوحدة 1؛ وهذا الدرس يكتب بلغة C++ الصورة الثانية. القرار يحسب شرطًا، أي تعبيرًا من النوع bool مثل تعابير الدرس العلاقات والمنطق، وينفّذ كتلة إذا كان الشرط صوابًا وكتلة أخرى إذا كان خطأ:
if (الشرط) { // يُنفَّذ إذا كان الشرط صوابًا} else { // يُنفَّذ إذا كان الشرط خطأ}يوضع الشرط دائمًا بين قوسين. والجزء else اختياري: من دونه، إذا كان الشرط خطأ لا يُفعل شيء ويتابع البرنامج بالتعليمة التي تأتي بعد القرار. وفي الحالتين يلتقي الطريقان في النهاية، بحيث يكون القرار، إذا نُظر إليه من الخارج، تعليمةً واحدة لها مدخل واحد ومخرج واحد، كما اشترط الدرس 1.1.
بيّن الدرس العمليات الحسابية أن a % b يحمل إشارة المقسوم، بحيث يساوي مع و القيمة ، في حين أن الباقي الإقليدي، الذي لا يكون سالبًا أبدًا، يساوي 1؛ وأعلن أن الطريقة الآمنة لتصحيحه هي إضافة فقط حين يكون الباقي سالبًا. ويفترض البرنامج أن .
#include <iostream>#include <print>int main(){ int a{}; int b{}; std::cin >> a >> b; int r{a % b}; if (r < 0) { r = r + b; } std::println("الباقي الإقليدي لقسمة {} على {}: {}", a, b, r);}-7 2الباقي الإقليدي لقسمة -7 على 2: 1لم يعد المتغير r ثابتًا، لأن القرار قد يغيّره. ويتتبّع الشكل الطريقين الممكنين.
البرهان
يصل التنفيذ إلى القرار وقد حُسب
r، وأول ما يفعله هو حساب الشرطr < 0: المعيّن هو النقطة الوحيدة في البرنامج التي تفترق عندها الطرق.إذا كان الشرط صوابًا، كما مع و2، تُنفَّذ كتلة
if: ينتقلrمن إلى 1.وإذا كان خطأ، كما مع 7 و2، تُتخطّى الكتلة كلها، ويحتفظ
rبقيمته.يلتقي الطريقان في النقطة نفسها، ومن هناك يتابع البرنامج على النحو نفسه في الحالتين: التعليمة التي تكتب النتيجة تُنفَّذ دائمًا.
مع else يختار القرار بين فعلين يستبعد أحدهما الآخر. شرط السنة الكبيسة من الدرس العلاقات والمنطق يُنتج bool؛ والقرار يحوّله إلى سلوك للبرنامج.
#include <iostream>#include <print>int main(){ int year{}; std::cin >> year; const bool isLeap{(year % 4 == 0 && year % 100 != 0) || year % 400 == 0}; if (isLeap) { std::println("{} سنة كبيسة: في فبراير 29 يومًا.", year); } else { std::println("{} ليست سنة كبيسة: في فبراير 28 يومًا.", year); }}19001900 ليست سنة كبيسة: في فبراير 28 يومًا.إعطاء الشرط اسمًا، مثل isLeap، يفصل ما يُقرَّر عمّا يُفعل بالقرار.
الأقواس المعقوصة دائمًا
تسمح قواعد C++ بحذف الأقواس المعقوصة حين تحتوي الكتلة على تعليمة واحدة: if (r < 0) r = r + b; صحيح. غير أن القرار من دون أقواس معقوصة لا يحكم إلا التعليمة التي تليه مباشرة، وما يظنّ القارئ أنه محكوم به يتوقف على الإزاحة البادئة، التي يتجاهلها المترجم. وإذا كان الأمر كذلك، فإن أي تعديل لاحق (إضافة تعليمة ثانية، أو تحويل الأولى إلى تعليق) قد يغيّر المعنى دون أن يُظهر النص ذلك. ومن ذلك تلزم قاعدة الدورة: كتلة if أو else تُكتب دائمًا بين أقواس معقوصة، ولو احتوت على تعليمة واحدة.
وأقصر الأخطاء جميعًا يؤكد القاعدة. فالفاصلة المنقوطة بعد القوسين تعليمة فارغة، والقرار يحكم هذه التعليمة الفارغة؛ والكتلة التي تليها لم تعد تابعة لـ if، وتُنفَّذ دائمًا.
#include <print>int main(){ const int stock{12}; if (stock == 0); { std::println("نفد المنتج"); } std::println("الكمية المتاحة: {}", stock);}main.cpp: In function ‘int main()’:main.cpp:6:20: warning: suggest braces around empty body in an ‘if’ statement [-Wempty-body] 6 | if (stock == 0); | ^main.cpp:6:5: warning: this ‘if’ clause does not guard... [-Wmisleading-indentation] 6 | if (stock == 0); | ^~main.cpp:7:5: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’ 7 | { | ^نفد المنتجالكمية المتاحة: 12يعلن البرنامج أن المنتج قد نفد مع أنه بقيت منه 12 قطعة. ويعطي GCC تحذيرين: -Wempty-body يقترح أقواسًا معقوصة حول الجسم الفارغ، و-Wmisleading-indentation يشير إلى أن الكتلة مُزاحة كأن if يحكمها. والخطأ الكلاسيكي الآخر، if (x = 0) بدلًا من if (x == 0)، رأيناه في الدرس الترجمة والتنفيذ والإعداد: قيمة الإسناد صفر، وهي تتحوّل إلى false، و-Wparentheses ينبّه. وفي الحالتين يُترجم البرنامج؛ ولولا التحذيرات لمرّ الخطأ دون أن يُلحظ.
وللأقواس المعقوصة نتيجة أخرى: المتغير المعرَّف داخل كتلة if لا يوجد إلا هناك. فإذا استُعملت بعد القرار قيمةٌ حُسبت في أحد الفرعين، عُرِّف المتغير قبله، مثل r في مثال الباقي؛ وسيُدرس نطاق الرؤية في الدرس الوسائط ونطاق الرؤية.
العامل الشرطي
حين يكتفي فرعا القرار باختيار قيمة، تقدّم C++ صورة أوجز: الشرط ? a : b يساوي a إذا كان الشرط صوابًا وb إذا كان خطأ، ولا يحسب إلا العنصر المختار. وعلى خلاف if، الذي هو تعليمة، فإن العامل الشرطي تعبير: له قيمة ونوع، ويمكن أن يوضع حيث يوضع أي تعبير، مثلًا في تهيئة ثابت، وهو ما لا يتيحه if إلا بالتخلّي عن const.
#include <iostream>#include <print>int main(){ int a{}; int b{}; std::cin >> a >> b; const int larger{a > b ? a : b}; std::println("الأكبر: {}", larger); std::println("{} {}", b, b == 1 ? "قطعة" : "قطع"); const auto mixed{a > b ? a : 0.5}; std::println("النوع المشترك: {:.1f}", mixed); std::println("دون أقواس: {}", 10 + a > b ? 1 : 0); std::println("مع أقواس: {}", 10 + (a > b ? 1 : 0));}7 3الأكبر: 73 قطعالنوع المشترك: 7.0دون أقواس: 1مع أقواس: 11يُظهر السطران الأولان الاستعمال الذي يوضّح: اختيار الأكبر ومطابقة كلمة للعدد، دون تكرار التعليمة كلها في فرعين. ويُظهر الثالث أن للتعبير نوعًا واحدًا، أيًّا كان الفرع المختار: إذا كانت النتيجتان من نوعين مختلفين، حُوّلتا إلى نوع مشترك بالتحويلات الحسابية المعتادة من الدرس العمليات الحسابية؛ وهنا يعطي int وdouble النوع double، ويُحفظ الـ 7 المختار على أنه 7.0. ويُظهر السطران الأخيران الأسبقية: العامل الشرطي من أدنى العوامل أسبقيةً، بحيث يكون الشرط من دون أقواس 10 + a > b والقيمة 1 بدلًا من 11. وحين يكون العامل الشرطي جزءًا من تعبير أكبر، يوضع بين قوسين.
ويصير العامل غامضًا متى طُلب منه أكثر من اختيار قيمة. فإذا تداخل، كما في a > b ? (a > c ? a : c) : (b > c ? b : c)، أجبر القارئ على أن يعيد في ذهنه بناء قرار متداخل دون الأقواس المعقوصة التي تجعله مقروءًا؛ وإذا استُعمل لآثاره، كما في ok ? std::println("نعم") : std::println("لا")، كان if متنكّرًا. وقاعدة الدورة أن يُستعمل فقط للاختيار بين قيمتين قصيرتين، دون تداخل؛ وكل ما عدا ذلك يُكتب بـ if.
جارٍ تحميل المحتوى…