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
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.