انتقل إلى المحتوى
Topos Uranos

ملخص

البرنامج الذي يكتفي بسرد التعليمات واحدةً بعد أخرى يفعل الشيء نفسه دائمًا، أيًّا كانت بياناته؛ ولكي يستجيب لها يحتاج إلى أن يقرّر. يدرس هذا الدرس قرارات C++: if وelse، اللتين تختاران بين طريقين بحسب شرط؛ وسلاسل else if، التي تختار بين طرق عدة، والتي يحدّد فيها ترتيبُ الشروط الحالةَ التي يخدمها كل فرع؛ والقرارات المتداخلة وفخّ else المعلّق؛ وswitch مع enum class، اللذين يختاران بين قيم منفصلة ذات أسماء، مع مترجم ينبّه إذا نقصت حالة أو إذا سقطت حالة في الحالة التي تليها. والمفهوم الذي يوحّد ذلك كله هو التحليل بحسب الحالات: القرار الصحيح يقسم مجموعة المدخلات إلى حالات منفصلة تغطيها تغطيةً تامة، وكل حالة تُفحص باختبار في داخلها وباختبار آخر عند كل حدّ من حدودها.

أهداف التعلم

  1. كتابة قرارات بسيطة ومتسلسلة ومتداخلة تخدم جميع حالات المواصفات.
  2. التحقق من أن حالات سلسلة if وelse if منفصلة وشاملة، وتصميم مُدخل اختبار لكل حالة.
  3. استعمال switch مع enum class للقيم المنفصلة، وشرح ما ينبّه إليه المترجم إذا نقصت حالة.
  4. اكتشاف else المعلّق والسقوط بين حالات switch، وتصحيحهما.

if و else

صورة القرار

قدّم الدرس من المسألة إلى الخوارزمية ثلاث صور يُبنى بها كل خوارزمية: التتابع والقرار والتكرار. وحتى الآن كانت برامج الدورة تتابعات، باستثناء if استُعمل مبكرًا في الوحدة 1؛ وهذا الدرس يكتب بلغة C++ الصورة الثانية. القرار يحسب شرطًا، أي تعبيرًا من النوع bool مثل تعابير الدرس العلاقات والمنطق، وينفّذ كتلة إذا كان الشرط صوابًا وكتلة أخرى إذا كان خطأ:

شيفرة C++
if (الشرط) {    // يُنفَّذ إذا كان الشرط صوابًا} else {    // يُنفَّذ إذا كان الشرط خطأ}

يوضع الشرط دائمًا بين قوسين. والجزء else اختياري: من دونه، إذا كان الشرط خطأ لا يُفعل شيء ويتابع البرنامج بالتعليمة التي تأتي بعد القرار. وفي الحالتين يلتقي الطريقان في النهاية، بحيث يكون القرار، إذا نُظر إليه من الخارج، تعليمةً واحدة لها مدخل واحد ومخرج واحد، كما اشترط الدرس 1.1.

مثالالباقي الإقليدي

بيّن الدرس العمليات الحسابية أن a % b يحمل إشارة المقسوم، بحيث يساوي مع a=−7a = -7 وb=2b = 2 القيمة −1-1، في حين أن الباقي الإقليدي، الذي لا يكون سالبًا أبدًا، يساوي 1؛ وأعلن أن الطريقة الآمنة لتصحيحه هي إضافة bb فقط حين يكون الباقي سالبًا. ويفترض البرنامج أن b>0b > 0.

برنامج بلغة C++
#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 ثابتًا، لأن القرار قد يغيّره. ويتتبّع الشكل الطريقين الممكنين.

البرهان

  1. يصل التنفيذ إلى القرار وقد حُسب r، وأول ما يفعله هو حساب الشرط r < 0: المعيّن هو النقطة الوحيدة في البرنامج التي تفترق عندها الطرق.

  2. إذا كان الشرط صوابًا، كما مع −7-7 و2، تُنفَّذ كتلة if: ينتقل r من −1-1 إلى 1.

  3. وإذا كان خطأ، كما مع 7 و2، تُتخطّى الكتلة كلها، ويحتفظ r بقيمته.

  4. يلتقي الطريقان في النقطة نفسها، ومن هناك يتابع البرنامج على النحو نفسه في الحالتين: التعليمة التي تكتب النتيجة تُنفَّذ دائمًا.

مع else يختار القرار بين فعلين يستبعد أحدهما الآخر. شرط السنة الكبيسة من الدرس العلاقات والمنطق يُنتج bool؛ والقرار يحوّله إلى سلوك للبرنامج.

برنامج بلغة C++
#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);    }}
مدخلات البرنامج
1900
المخرجات
1900 ليست سنة كبيسة: في فبراير 28 يومًا.

إعطاء الشرط اسمًا، مثل isLeap، يفصل ما يُقرَّر عمّا يُفعل بالقرار.

الأقواس المعقوصة دائمًا

تسمح قواعد C++ بحذف الأقواس المعقوصة حين تحتوي الكتلة على تعليمة واحدة: if (r < 0) r = r + b; صحيح. غير أن القرار من دون أقواس معقوصة لا يحكم إلا التعليمة التي تليه مباشرة، وما يظنّ القارئ أنه محكوم به يتوقف على الإزاحة البادئة، التي يتجاهلها المترجم. وإذا كان الأمر كذلك، فإن أي تعديل لاحق (إضافة تعليمة ثانية، أو تحويل الأولى إلى تعليق) قد يغيّر المعنى دون أن يُظهر النص ذلك. ومن ذلك تلزم قاعدة الدورة: كتلة if أو else تُكتب دائمًا بين أقواس معقوصة، ولو احتوت على تعليمة واحدة.

وأقصر الأخطاء جميعًا يؤكد القاعدة. فالفاصلة المنقوطة بعد القوسين تعليمة فارغة، والقرار يحكم هذه التعليمة الفارغة؛ والكتلة التي تليها لم تعد تابعة لـ if، وتُنفَّذ دائمًا.

برنامج بلغة C++
#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.

برنامج بلغة C++
#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.