case
تُدخِل كلمة case المفتاحية وسم حالة (case label) لتعليمة switch، وتنفّذ تعليمات معيّنة بناء على القيمة التي يساويها معامَلها. كذلك يجب أن يكون هذا المعامَل تعبيرًا ثابتًا ومطابقًا لشرط تعليمة switch. تنتقل تعليمة switch عند تنفيذها إلى وسم الحالة التي يساوي فيها المعامَلُ الشرطَ إن وُجِد. انظر المثال التالي:
char c = getchar(); bool confirmed; switch (c) { case 'y': confirmed = true; break; case 'n': confirmed = false; break; default: std::cout << "invalid response!\n"; abort(); }
switch
وفقًا لتوصيف C++، فإن تعليمة switch تحول التحكم إلى تعليمة برمجية او أكثر وفقًا لقيمة الشرط، وتُتبَع كلمة switch المفتاحية بقوسين يحتويان شرطًا ثم كتلة برمجية، والتي قد تحتوي على عناوين case، وعنوان default اختياري.
عند تنفيذ تعليمة switch سيُنقَل التحكّم إلى عنوان case ذي القيمة المطابقة لقيمة الشّرط -إن وُجدت-، أو إلى العنوان default إن وُجد أيضًا.
ويجب أن يكون الشرط تعبيرًا أو تصريحًا يحتوي على عدد صحيح، أو نوع عددي (enumeration type)، أو نوع صنف له دالة تحويل إلى عدد صحيح أو نوع عددي.
char c = getchar(); bool confirmed; switch (c) { case 'y': confirmed = true; break; case 'n': confirmed = false; break; default: std::cout << "invalid response!\n"; abort(); }
catch
تُدخِل كلمة catch المفتاحية معالج اعتراضات ينتقل التحكّم إلى كتلته عند إطلاق اعتراض (exception) من النّوع الموافق. وتُعقَب catch بقوسين يحتويان تصريحًا عن اعتراض، وذلك يشبه في هيئته تصريحات المُعامِلات في الدّوال، إذ يجوز حذف اسم المعامِل ويُسمح بالمقطع ...، والذي يطابق أي نوع.
مُعالجُ الاعتراض لن يُعالجَ الاعتراض إلّا إن كان تصريحه موافقًا لنوع الاعتراض. لمزيد من التفاصيل، انظر إمساك الاعتراضات.
try { std::vector < int > v(N); // افعل شيئا ما } catch (const std::bad_alloc & ) { std::cout << "failed to allocate memory for vector!" << std::endl; } catch (const std::runtime_error & e) { std::cout << "runtime error: " << e.what() << std::endl; } catch (...) { std::cout << "unexpected exception!" << std::endl; throw; }
throw
-
عندما تقع
throwفي تعبير ذي معامَل فإنّها تطلق اعتراضًا يكون نسخة من ذلك المعامَل:
void print_asterisks(int count) { if (count < 0) { throw std::invalid_argument("count cannot be negative!"); } while (count--) { putchar('*'); } }
-
عندما تقع
throwفي تعبير ليس له معامَل، فستعيد إطلاق الاعتراض الحالي، أما إن لم يكن ثمة اعتراض موجود فتُستدعىstd::terminate.
try { // افعل شيئا ما } catch (const std::bad_alloc & ) { std::cerr << "out of memory" << std::endl; } catch (...) { std::cerr << "unexpected exception" << std::endl; // رجاء أن يعرف المستدعي كيفية معالجة هذا الاعتراض. throw; }
-
عندما تقع
throwفي مُصرِّح دالة (function declarator)، فستقدّم توصيفًا للاعتراضات الديناميكية، والذي يسرد أنواع الاعتراضا التي يجوز للدّالة نشرها. انظر المثال التالي لدالة قد تنشر الاعتراضstd::runtime_errorمثلًا، لكن ليسstd::logic_error:
void risky() throw(std::runtime_error); // هذه الدالة لا يمكنها نشر أيّ اعتراض. void safe() throw();
أُهملت توصيفات الاعتراض الديناميكية بدءًا من C++ 11.
أول استخدامَين للعبارة throw المذكورة أعلاه هما تعبيران (expressions) وليسا تعليمتيْن (statements)، لاحظ أن نوع الاعتراض الذي أُطلِق هو void، هذا يجعل تداخلها ممكنًا في التعبيرات على النحو التالي:
unsigned int predecessor(unsigned int x) { return (x > 0) ? (x - 1) : (throw std::invalid_argument("0 has no predecessor")); }
default
تمثل default العنوان الذي سيقفز إليه البرنامج في تعليمة switch، إذا كانت قيمة الشرط لا تساوي أيًا من قيم عناوين case.
char c = getchar(); bool confirmed; switch (c) { case 'y': confirmed = true; break; case 'n': confirmed = false; break; default: std::cout << "invalid response!\n"; abort(); }
الإصدار ≥ C++11
تُعرّف default مُنشِئًا افتراضيًا (default constructor) أو مُنشئ نسخ أو نقل، أو مُدمِّرًا (destructor)، أو عامل إسناد النّسخ (copy assignment operator)، أو عامل إسناد النقل (move assignment operator) ليكون هو السلوك الافتراضي. انظر المثال التالي حيث نريد أن نكون قادرين على محو الأصناف المشتقة عبر *Base، لكن نريد السلوك المعتاد لمدمر Base:
class Base { virtual~Base() = default; };
try
تُتبَع الكلمة المفتاحية try بكتلة برمجية، أو قائمة تهيئة المُنشئ (constructor initializer list) ثمّ كتلة برمجية. وتُتبَع كتلة try بكتلة catch واحدة أو أكثر، وإذا انتشر اعتراض خارج كتلة try فإنّ كل كتل تعليمات catch الموجودة بعد كتلة try تستطيع معالجة الاعتراض إذا كانت الأنواع متطابقة. انظر المثال التالي حيث لن تمسك catch الاعتراض إن أُطلق بعد std::vector الأولى، لكنها ستمسكه إن أُطلق بعد الثانية التي داخل try:
std::vector < int > v(N); try { std::vector < int > v(N); // v افعل شيئا ما بـ } catch (const std::bad_alloc & ) { // try من كتلة bad_alloc عالج اعتراض. }
if
if هي تعليمة شَرْطية يجب أن تُتبَع بقوسين يضُمّان شرطًا يكون إمّا تعبيرًا أو تصريحًا، وإذا تحقّق ذلك الشّرط، فستُنفّذ العبارة الموجودة بعده. انظر:
int x; std::cout << "Please enter a positive number." << std::endl; std::cin >> x; if (x <= 0) { std::cout << "You didn't enter a positive number!" << std::endl; abort(); }
else
قد تَتبع كلمة else المفتاحيةُ أولَ تعليمة فرعية لتعليمة if، وستُنفَّذ التعليمات الفرعية بعد else عند عدم تحقق شرط if، أي عندما لا تُنفّذ كتلة التعليمات الأولى. انظر:
int x; std::cin >> x; if (x % 2 == 0) { std::cout << "The number is even\n"; } else { std::cout << "The number is odd\n"; }
البُنى الشرطية: if و if..else
if و else
تُستخدم لنعرف ما إن كان التعبير المُعطى يعيد true أو false ومن ثم تتصرف وفقها، انظر:
if (condition) statement
يمكن أن يكون الشّرط أيّ تعبير C++ صالح يُعيد شيئًا يمكن التحقق من صحته أو خطئه، انظر المثال التالي حيث تُنفَّذ الشيفرة بين القوسين لتحقق الشرط (أي true)، بينما لا تُنفَّذ الشيفرة في السطر الثاني لعدم تحققه (أي false):
if (true) { /* الشيفرة المراد تنفيذها */ } if (false) { /* الشيفرة المراد تنفيذها */ }
قد يكون الشرط دالة أو متغيرًا أو مقارنة أو غير ذلك، انظر المثال التالي:
// تنفَّذ الشيفرة إن تحققت الدالة if(istrue()) { } // تقييم الدالة بعد تمرير المتغير إليها if(isTrue(var)) { } // a يساوي b ستُنفّذ الشيفرة المقابلة إن كان if(a == b) { } // قيمة بوليانية فستُقيّم بحسب قيمتها الفعلية a إن كانت // وإن كانت عددا، فإنّ أي قيمة غير صفرية فستُعد صحيحة if(a) { }
تستطيع التحقق من عدة تعبيرات بإحدى طريقتين: استخدام العمليات الثنائية:
if (a && b) { } // معًا a و b تتحقق في حال تحقق scope here if (a || b ) { } // تتحقق إن تحقق أي منهما.
استخدام if / ifelse / else: لإجراء تبديل بسيط، يمكنك استخدام إمّا if أو else:
if (a == "test") { // "test" تساوي a ستُنفّذ في حال كانت } else { // تنفذ في حال لم تنفّذ العبارة الأولى }
وفي حالة الخيارات المتعددة:
if (a == 'a') { // 'a' حرفا يساوي a إن كانت } else if (a == 'b') { // 'b' حرفا يساوي a إن كانت } else if (a == 'c') { // 'c' حرفا يساوي a إن كانت } else { // تُنفّذ إن لم يتحقق أي مما سبق }
يُفضّل استخدام "switch " بدلاً من الشيفرة السابقة ما دُمنا نتحقّق من قيمة نفس المتغير.
goto
تنتقل goto إلى التعليمة المعنونة (labelled statement)، والتي ينبغي أن تكون موجودة في الدالة الحاليّة.
bool f(int arg) { bool result = false; hWidget widget = get_widget(arg); if (!g()) { // لا يمكن أن نستمر، لكن لا زال علينا أن ننظف. goto end; } // ... result = true; end: release_widget(widget); return result; }
عبارات القفز: break و continue و goto و exit
التعليمة break
نستطيع مغادرة الحلقة التكرارية باستخدام break -انظر الحلقات التكرارية- حتى لو لم يتحقق شرط إنهائها، كما يمكن استخدامها مثلًا لإنهاء حلقة لا نهائية أو لإجبارها على التوقف قبل نهايتها الطبيعية. وتكون صيغتها كالتالي:
break;
على سبيل المثال: غالبًا ما نستخدم break في حالات switch، كأن تتحقق حالة في switch فتُنفَّذ شيفرة الشرط. انظر:
switch (conditon) { case 1: block1; case 2: block2; case 3: block3; default: blockdefault; }
في الشيفرة السابقة، إن تحققت الحالة الأولى case 1 فإن الكتلة block 1 تُنفَّذ، ثم تُنفَّذ كتل الحالتين الثانية والثالثة رغم أن الحالة الأولى فقط هي المتحققة، وهي ما أردنا تنفيذ الشيفرة المقابلة لها فقط. ولتجنب تنفيذ الشيفرات للحالات التي لم تحقق فإننا نستخدم break في نهاية كل كتلة، انظر:
switch (condition) { case 1: block1; break; case 2: block2; break; case 3: block3; break; default: blockdefault; break; }
لن تُعالَج الآن إلا كتلة واحدة فقط، وسينتقل التحكم إلى خارج حلقة switch، كذلك يمكن استخدام break في الحلقات الشرطية وغير الشرطية الأخرى، مثل if و while و for وغيرها؛ انظر:
if (condition1) { .... if(condition2) { ....... break; } ... }
التعليمة continue
تجعل التعليمة continue البرنامجَ يتخطّى بقيّة تعليمات الحلقة في التكرار الحالي ممّا يؤدّي إلى الانتقال إلى التكرار التالي في الحلقة، وتكون صيغتها كالتالي:
continue;
انظر المثال التالي:
for (int i = 0; i < 10; i++) { if (i % 2 == 0) continue; cout << "\n @" << i; }
الذي ينتج الخرج:
@1 @3 @5 @7 @9
في المثال السابق، كلمّا تحقّق الشرط i%2==0 فستُنفّذ العبارة continue، مما يجعل المُصرّف يتخطّى كل الشيفرة المتبقيّة (طباعة @ و i)، ويعودَ لتنفيذ عبارة الزّيادة/الإنقاص (increment/decrement) في الحلقة.
التعليمة goto
تسمح goto بإجراء قفزة إلى نقطة أخرى في البرنامج. يجب عليك الحذر عند استخدام هذه الميزة لأنّ تنفيذها يتجاهل كلّ قيود التداخل (nesting).
وتُحدَّد النقطة التي سينتقل إليها البرنامج بواسطة عنوان (label) يُمرَّر بعدها كوسيط لتعليمة goto، ويتألّف اسم ذلك العنوان من مُعرِّف صالح متبوع بنقطتين : على النحو التالي:
goto label; .. . label: statement;
ملاحظة: يُوصى بتجنّب استخدام عبارة goto لأنها تصعّب تتبّع سير البرنامج ومن ثم فهمه وتعديله.
مثال:
int num = 1; STEP: do { if (num % 2 == 0) { num = num + 1; goto STEP; } cout << "value of num : " << num << endl; num = num + 1; } while (num < 10);
الناتج:
pre widget
value of num : 1 value of num : 3 value of num : 5 value of num : 7 value of num : 9
تنقل goto تنفيذَ التحكم، عند تحقق شرط num%2==0، إلى بداية حلقة do-while
دالة exit
exit هي دالة مُعرَّفة في مكتبة cstdlib، والغرض منها هو إنهاء البرنامج الذي يتم تنفيذه برمز خروج محدّد يكون على الصورة التالية:
void exit (int exit code);
رمزا الخروج القياسيَّين EXIT_SUCCESS و EXIT_FAILURE مُعرَّفان في مكتبة cstdlib.
return
تعيد تعليمة return التحكمَ من الدالة إلى مُستدعيها، وإذا كان للتعليمة مُعامَل فسيُحوَّل إلى نوع القيمة المعادة الخاص بالدالة، ثم تُعاد القيمة المُحوّلة إلى المستدعي.
int f() { return 42; } int x = f(); // x = 42 int g() { return 3.14; } int y = g(); // x = 3
إذا لم يكن للتعليمة return أيّ معامَل فينبغي أن يكون نوع القيمة المُعادة للدالة هو void. كذلك يمكن للدوال التي نوع قيمتها المعادة فارغ (void) أن تعيد تعبيرًا إن كان ذلك التعبير من النوع void.
void f(int x) { if (x < 0) return; std::cout << sqrt(x); } int g() { return 42; } void h() { return f(); // ثم العودة f استدعاء return g(); // غير صالحة }
تستدعي main دالة std::exit ضمنيًا مع القيمة التي تعيدها، وتعاد القيمة عندها إلى بيئة التنفيذ، غير أن الإعادة من main يدمر المتغيرات المحلية التلقائية في حين أن استدعاء std::exit مباشرة لا يفعل ذلك.
int main(int argc, char ** argv) { if (argc < 2) { std::cout << "Missing argument\n"; return EXIT_FAILURE; // exit(EXIT_FAILURE); يكافئ } }
هذا الدرس جزء من سلسلة دروس عن C++.
ترجمة -بتصرّف- للفصل Chapter 15: Flow Control من كتاب C++ Notes for Professionals



أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.