c++ - لغة - متغير منطقي




لماذا لا يمكن الإعلان عن المتغيرات في بيان التبديل؟ (16)

لطالما تساءلت عن ذلك - لماذا لا يمكنك الإعلان عن المتغيرات بعد تسمية الحالة في بيان التبديل؟ في C ++ يمكنك أن تعلن عن المتغيرات إلى حد كبير في أي مكان (ومن الواضح أن الإعلان عن استخدامها لأول مرة هو أمر جيد) ولكن ما يلي لن يعمل:

switch (val)  
{  
case VAL:  
  // This won't work
  int newVal = 42;  
  break;
case ANOTHER_VAL:  
  ...
  break;
}  

ما ورد أعلاه يعطيني الخطأ التالي (MSC):

يتم تخطي التهيئة لـ "newVal" من خلال تصنيف "الحالة"

هذا يبدو أن هناك قيود في اللغات الأخرى أيضا. لماذا هذه مشكلة كهذه؟


أردت فقط التأكيد على point النحافة . بناء التبديل يخلق نطاق كامل من الدرجة الأولى للمواطنين. لذلك من الممكن تحديد (وتهيئة) المتغير في كشف التبديل قبل تسمية الحالة الأولى ، بدون زوج قوس إضافي:

switch (val) {  
  /* This *will* work, even in C89 */
  int newVal = 42;  
case VAL:
  newVal = 1984; 
  break;
case ANOTHER_VAL:  
  newVal = 2001;
  break;
}

أعتقد أن القضية المطروحة هي أن البيان تم تخطي ، وحاولت استخدام var في مكان آخر ، لن يتم الإعلان عنه.


القسم بأكمله من التبديل هو سياق إعلان واحد. لا يمكنك الإعلان عن متغير في بيان حالة مثل ذلك. جرب هذا بدلا من ذلك:

switch (val)  
{  
case VAL:
{
  // This will work
  int newVal = 42;
  break;
}
case ANOTHER_VAL:  
  ...
  break;
}

بعد قراءة جميع الإجابات وبعض البحوث أكثر أحصل على بعض الأشياء.

Case statements are only 'labels'

في C ، وفقا للمواصفات ،

§6.8.1 البيانات المسجلة:

labeled-statement:
    identifier : statement
    case constant-expression : statement
    default : statement

في C لا يوجد أي بند يسمح "بالإعلان المصنف". انها ليست مجرد جزء من اللغة.

وبالتالي

case 1: int x=10;
        printf(" x is %d",x);
break;

لن يتم ترجمة هذا ، راجع http://codepad.org/YiyLQTYw . دول مجلس التعاون الخليجي تعطي خطأ:

label can only be a part of statement and declaration is not a statement

حتى في

  case 1: int x;
          x=10;
            printf(" x is %d",x);
    break;

هذا أيضًا لا يتم تجميعه ، راجع http://codepad.org/BXnRD3bu . أنا هنا أيضا الحصول على نفس الخطأ.

في C ++ ، وفقًا للمواصفات ،

يُسمح بالتسمية المُعلَّمة ، ولكن لا يُسمح بالاسم -Initialization.

انظر http://codepad.org/ZmQ0IyDG .

الحل لهذا الشرط هو اثنان

  1. إما استخدام نطاق جديد باستخدام {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. أو استخدم العبارة الوهمية مع الملصق

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. قم بتعريف المتغير قبل التبديل () وقم بتهيئته بقيم مختلفة في كشف الحالة إذا كان يفي بمتطلباتك

    main()
    {
        int x;   // Declare before
        switch(a)
        {
        case 1: x=10;
            break;
    
        case 2: x=20;
            break;
        }
    }
    

بعض الأشياء أكثر مع بيان التبديل

عدم كتابة أي عبارات في المحول لا تشكل جزءًا من أي تصنيف ، لأنها لن تنفذ أبدًا:

switch(a)
{
    printf("This will never print"); // This will never executed

    case 1:
        printf(" 1");
        break;

    default:
        break;
}

انظر http://codepad.org/PA1quYX3 .


بيانات Case هي علامات فقط. هذا يعني أن المترجم سوف يفسر هذا كقفزة مباشرة إلى الملصق. في C ++ ، المشكلة هنا أحد نطاق. تعرّف الأقواس المتعرجة نطاق كل شيء داخل بيان switch . هذا يعني أنك تركت مع نطاق حيث سيتم تنفيذ قفزة أبعد في رمز تخطي التهيئة. والطريقة الصحيحة للتعامل مع هذا هو تحديد نطاق محدد لبيان case وتعريف المتغير الخاص بك داخله.

switch (val)
{   
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}

تم وضع علامة على هذا السؤال باسم [C] و [C ++] في نفس الوقت. الرمز الأصلي غير صالح في الواقع في C و C ++ ، ولكن لأسباب مختلفة تمامًا لا علاقة لها. أعتقد أن هذه التفاصيل المهمة قد تم تفويتها (أو تشويشها) بالإجابات الموجودة.

  • في C ++ هذا الرمز غير صالح لأن case ANOTHER_VAL: تسمية ينتقل إلى نطاق متغير newVal تجاوز التهيئة الخاصة به. القفزات التي تتجاوز تهيئة الكائنات المحلية غير قانونية في C ++. يتم التعامل مع هذا الجانب من المشكلة بشكل صحيح من خلال معظم الإجابات.

  • ومع ذلك ، في لغة C تجاوز التهيئة المتغيرة ليست خطأ. يعتبر الانتقال إلى نطاق متغير عبر التهيئة قانونيًا في C. يعني ببساطة أن المتغير يترك غير مهيأ. لا يتم ترجمة التعليمات البرمجية الأصلية في C لسبب مختلف تمامًا. case VAL: الملصق case VAL: في الكود الأصلي مرفقة بإعلان متغير newVal . في تصريحات لغة C ليست تصريحات. لا يمكن وصفها. وهذا ما يسبب الخطأ عندما يتم تفسير هذا الرمز على أنه رمز C.

    switch (val)  
    {  
    case VAL:             /* <- C error is here */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:     /* <- C++ error is here */
      ...
      break;
    }
    

تؤدي إضافة {} إصلاحات إضافية إلى حل مشكلات كل من C ++ و C ، على الرغم من أن هذه المشكلات تحدث بشكل مختلف تمامًا. على الجانب C ++ فإنه يقيد نطاق newVal ، مع التأكد من تلك case ANOTHER_VAL: لم يعد ينتقل إلى هذا النطاق ، الذي newVal المشكلة C ++. على الجانب C يقدم ذلك {} بيانًا إضافيًا ، مما يجعل case VAL: label لتطبيقه على عبارة ، مما يحل المشكلة C.

  • في حالة C ، يمكن حل المشكلة بسهولة دون {} . ما عليك سوى إضافة عبارة فارغة بعد case VAL: label وسيصبح الرمز صالحًا

    switch (val)  
    {  
    case VAL:;            /* Now it works in C! */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:  
      ...
      break;
    }
    

    لاحظ أنه على الرغم من أنه صالح الآن من وجهة نظر C ، فإنه يظل غير صالح من وجهة نظر C ++.

  • متناظرة ، في حالة C ++ يمكن حل المشكلة بسهولة دون {} . فقط قم بإزالة المُهيئ من تعريف متغير وسيصبح الرمز صالحًا

    switch (val)  
    {  
    case VAL: 
      int newVal;
      newVal = 42;  
      break;
    case ANOTHER_VAL:     /* Now it works in C++! */
      ...
      break;
    }
    

    لاحظ أنه على الرغم من أنه صالح الآن من وجهة نظر C ++ ، فإنه يظل غير صالح من وجهة نظر C.


حتى الآن كانت الإجابات عن C ++.

بالنسبة لـ C ++ ، لا يمكنك القفز فوق التهيئة. يمكنك في C. ومع ذلك ، في C ، لا يعد الإعلان بيانًا ، ويجب أن يتبع بيانات الحالة عبارات.

لذا ، صالح (ولكن قبيح) C ، غير صالح C ++

switch (something)
{
  case 1:; // Ugly hack empty statement
    int i = 6;
    do_stuff_with_i(i);
    break;
  case 2:
    do_something();
    break;
  default:
    get_a_life();
}

بالمقابل ، في C ++ ، يكون التصريح عبارةً عن عبارة ، وبالتالي فإن C ++ صحيحة ، C غير صحيحة

switch (something)
{
  case 1:
    do_something();
    break;
  case 2:
    int i = 12;
    do_something_else();
}

حسنا. فقط لتوضيح هذا بدقة ليس له علاقة بالإعلان. يتعلق فقط "القفز فوق التهيئة" (ISO C ++ '03 6.7 / 3)

ذكرت الكثير من المشاركات هنا أن القفز فوق الإعلان قد يؤدي إلى "عدم الإعلان عن المتغير". هذا ليس صحيحا. يمكن تعريف كائن POD بدون مُهيئ ولكن سيكون له قيمة غير محددة. فمثلا:

switch (i)
{
   case 0:
     int j; // 'j' has indeterminate value
     j = 0; // 'j' initialized to 0, but this statement
            // is jumped when 'i == 1'
     break;
   case 1:
     ++j;   // 'j' is in scope here - but it has an indeterminate value
     break;
}

عندما يكون الكائن غير POD أو تجميعًا ، يضيف المترجم ضمنيًا مُهيئًا ، وبالتالي لا يمكن القفز فوق مثل هذا الإعلان:

class A {
public:
  A ();
};

switch (i)  // Error - jumping over initialization of 'A'
{
   case 0:
     A j;   // Compiler implicitly calls default constructor
     break;
   case 1:
     break;
}

لا يقتصر هذا القيد على بيان التبديل. من الخطأ أيضًا استخدام "goto" للقفز فوق التهيئة:

goto LABEL;    // Error jumping over initialization
int j = 0; 
LABEL:
  ;

قليلاً من trivia أن هذا هو اختلاف بين C ++ و C. In C ، فإنه ليس خطأ القفز فوق التهيئة.

كما ذكر آخرون ، فإن الحل هو إضافة كتلة متداخلة بحيث يقتصر عمر المتغير على ملصق الحالة الفردية.


كتبت هذه الإجابة أصلا عن هذا السؤال . ولكن عندما انتهيت من ذلك وجدت أن الإجابة قد أغلقت. لذلك أنا نشرها هنا ، وربما شخص ما يحب الإشارات إلى معيار تجد أنه من المفيد.

الكود الأصلي في السؤال:

int i;
i = 2;
switch(i)
{
    case 1: 
        int k;
        break;
    case 2:
        k = 1;
        cout<<k<<endl;
        break;
}

يوجد في الواقع سؤالان:

1. لماذا يمكنني أن أعلن عن متغير بعد تسمية case ؟

لأنه في ملصق C ++ يجب أن يكون في الشكل:

N3337 6.1 / 1

المسمى البيان:

...

  • attribute-specifier-seqopt case constant-expression : statement

...

وفي عبارة C++ يعتبر أيضاً عبارة (بدلاً من C ):

N3337 6/1:

بيان :

...

إعلان-بيان

...

2. لماذا يمكنني القفز فوق الإعلان المتغير ثم استخدامها؟

لأن: N3337 6.7 / 3

من الممكن النقل إلى كتلة ، ولكن ليس بطريقة تتجاوز الإعلانات مع التهيئة . برنامج يقفز (يعتبر النقل من حالة عبارة switch إلى ملصق حالة قفزة في هذا الصدد.)

من النقطة التي لا يكون فيها متغير مع مدة التخزين التلقائي في النطاق إلى نقطة حيث يكون نطاقه غير سليم ما لم يكن المتغير يحتوي على نوع العددية ونوع الفئة مع مُنشئ افتراضي تافه و destructor تافه ، إصدار مؤهل cv من أحد هذه الأنواع ، أو صفيف من أحد الأنواع السابقة ويتم الإعلان عنه بدون مُهيئ (8.5).

بما أن k هي من النوع المتسلسل ، ولم تتم تهيئتها عند نقطة الإعلان التي يقفز فوقها ، فمن الممكن الإعلان عنها. هذا معادل لغويا:

goto label;

int x;

label:
cout << x << endl;

ومع ذلك ، لن يكون ذلك ممكنًا ، إذا تمت تهيئة x عند نقطة التعريف:

 goto label;

    int x = 58; //error, jumping over declaration with initialization

    label:
    cout << x << endl;

كتلة switch ليست هي نفس تعاقب if/else if كتل. أنا مندهش لا يشرح إجابة أخرى بوضوح.

ضع في اعتبارك عبارة switch هذه:

switch (value) {
    case 1:
        int a = 10;
        break;
    case 2:
        int a = 20;
        break;
}

قد يكون من المفاجئ ، ولكن المترجم لن يرى أنها بسيطة if/else if . سوف ينتج التعليمة البرمجية التالية:

if (value == 1)
    goto label_1;
else if (value == 2)
    goto label_2;
else
    goto label_end;

{
label_1:
    int a = 10;
    goto label_end;
label_2:
    int a = 20; // Already declared !
    goto label_end;
}

label_end:
    // The code after the switch block

يتم تحويل عبارات case إلى تصنيفات ثم يتم استدعاؤها باستخدام goto . تنشئ الأقواس نطاقًا جديدًا ومن السهل أن ترى الآن لماذا لا يمكنك إعلان عن متغيرين يحملان نفس الاسم داخل كتلة switch .

قد يبدو الأمر غريباً ، لكن من الضروري دعم الاختراق (أي عدم استخدام break للسماح بتواصل التنفيذ في case التالية).


معظم الردود حتى الآن خاطئة في أحد الجوانب: يمكنك الإعلان عن المتغيرات بعد بيان الحالة ، ولكن لا يمكنك التهيئة لها:

case 1:
    int x; // Works
    int y = 0; // Error, initialization is skipped by case
    break;
case 2:
    ...

كما ذكرنا سابقاً ، طريقة لطيفة حول هذا هو استخدام الأقواس لإنشاء نطاق لقضيتك.


معيار C ++ يحتوي على: يمكن التحويل إلى كتلة ، ولكن ليس بطريقة تتجاوز الإعلانات مع التهيئة. هو برنامج يقفز من نقطة لا يكون فيها متغير محلي مع مدة التخزين التلقائي في النطاق إلى نقطة حيث يكون نطاقه غير صحيح ما لم يكن المتغير يحتوي على نوع POD (3.9) ويتم الإعلان عنه بدون مُهيئ (8.5).

الكود لتوضيح هذه القاعدة:

#include <iostream>

using namespace std;

class X {
  public:
    X() 
    {
     cout << "constructor" << endl;
    }
    ~X() 
    {
     cout << "destructor" << endl;
    }
};

template <class type>
void ill_formed()
{
  goto lx;
ly:
  type a;
lx:
  goto ly;
}

template <class type>
void ok()
{
ly:
  type a;
lx:
  goto ly;
}

void test_class()
{
  ok<X>();
  // compile error
  ill_formed<X>();
}

void test_scalar() 
{
  ok<int>();
  ill_formed<int>();
}

int main(int argc, const char *argv[]) 
{
  return 0;
}

رمز لإظهار تأثير المُهيئ:

#include <iostream>

using namespace std;

int test1()
{
  int i = 0;
  // There jumps fo "case 1" and "case 2"
  switch(i) {
    case 1:
      // Compile error because of the initializer
      int r = 1; 
      break;
    case 2:
      break;
  };
}

void test2()
{
  int i = 2;
  switch(i) {
    case 1:
      int r;
      r= 1; 
      break;
    case 2:
      cout << "r: " << r << endl;
      break;
  };
}

int main(int argc, const char *argv[]) 
{
  test1();
  test2();
  return 0;
}

يبدو أن الكائنات المجهولة يمكن إعلانها أو إنشاؤها في بيان حالة التبديل لسبب عدم إمكانية الرجوع إليها ، وبالتالي لا يمكن الوصول إلى الحالة التالية. فكر في هذا المثال الذي تم تجميعه على دول مجلس التعاون الخليجي 4.5.3 و Visual Studio 2008 (قد تكون مشكلة تتعلق بالامتثال "حتى يثمن الخبراء)

#include <cstdlib>

struct Foo{};

int main()
{
    int i = 42;

    switch( i )
    {
    case 42:
        Foo();  // Apparently valid
        break;

    default:
        break;
    }
    return EXIT_SUCCESS;
}

يعتبر:

switch(val)
{
case VAL:
   int newVal = 42;
default:
   int newVal = 23;
}

في غياب بيانات الاستراحة ، في بعض الأحيان يتم الإعلان عن VAL مرتين ، ولا تعرف ما إذا كان يتم ذلك حتى وقت التشغيل. تخميني هو أن القيد بسبب هذا النوع من الارتباك. ما من شأنه أن يكون نطاق newVal؟ الاتفاقية سوف تملي أنه سيكون كامل كتلة التبديل (بين الأقواس).

أنا لا مبرمج C ++ ، ولكن في C:

switch(val) {
    int x;
    case VAL:
        x=1;
}

يعمل بشكل جيد. الإعلان عن متغير داخل كتلة التبديل على ما يرام. التصريح بعد حارس القضية ليست كذلك.


يمكنك الإعلان عن المتغيرات داخل عبارة التبديل في حالة بدء حظر جديد:

switch (thing)
{ 
  case A:
  {
    int i = 0;  // Completely legal
  }
  break;
}

والسبب هو القيام بتخصيص (واستعادة) الفضاء على كدسة لتخزين المتغير (المتغيرات) المحلي.


يوجد newVal في نطاق المفتاح بالكامل ولكن يتم بدءه فقط في حالة إصابة طرف VAL. إذا قمت بإنشاء كتلة حول التعليمة البرمجية في VAL يجب أن تكون "موافق".





switch-statement