سؤال

في هذا الموضوع، نلقي نظرة على أمثلة للاستخدامات الجيدة لـ goto في C أو C++.انها مستوحاة من إجابة الذي صوت الناس عليه لأنهم ظنوا أنني أمزح.

ملخص (تم تغيير التسمية من الأصل لجعل النية أكثر وضوحًا):

infinite_loop:

    // code goes here

goto infinite_loop;

لماذا هو أفضل من البدائل:

  • انها محددة. goto هو البناء اللغوي الذي يسبب فرعا غير مشروط.تعتمد البدائل على استخدام الهياكل التي تدعم الفروع الشرطية ، مع حالة حدوث شاشية دائمًا.
  • توثق التسمية النية دون تعليقات إضافية.
  • لا يتعين على القارئ مسح الرمز المتداخل في وقت مبكر breakS (على الرغم من أنه لا يزال من الممكن للمتسلل غير المبدئي للمحاكاةcontinue مع في وقت مبكر goto).

قواعد:

  • التظاهر بأن gotophobes لم يفز.من المفهوم أنه لا يمكن استخدام ما سبق في رمز حقيقي لأنه يتعارض مع المصطلح المعمول به.
  • افترض أننا سمعنا جميعًا عن "Goto يعتبر ضارًا" ونعلم أنه يمكن استخدام GOTO لكتابة رمز السباغيتي.
  • إذا كنت لا توافق على مثال ، فإنها تنتقدها على الجدارة الفنية وحدها (لأن الناس لا يحبون GOTO "ليس سببًا تقنيًا).

دعونا نرى ما إذا كان بإمكاننا التحدث عن هذا مثل البالغين.

يحرر

يبدو أن هذا السؤال انتهى الآن.لقد ولدت بعض الإجابات عالية الجودة.شكرا للجميع ، وخاصة أولئك الذين أخذوا مثال الحلقة الصغيرة على محمل الجد.كان معظم المتشككين قلقون من عدم وجود نطاق كتلة.كما أشار Qinmars في تعليق ، يمكنك دائمًا وضع الأقواس حول جسم الحلقة.وألاحظ في تمرير ذلك for(;;) و while(true) لا تعطيك الأقواس مجانًا أيضًا (وحذفها يمكن أن يسبب الحشرات المذهلة).على أي حال ، لن أضيع المزيد من قوة دماغك على هذا التبل for(;;) و while(true) (وكذلك إذا كنت أرغب في الاحتفاظ بوظيفتي).

وبالنظر إلى الردود الأخرى، أرى أن الكثير من الناس ينظرون goto كشيء يجب عليك دائمًا إعادة الكتابة بطريقة أخرى.بالطبع يمكنك تجنب أ goto من خلال تقديم حلقة ، علامة إضافية ، كومة متداخلة ifالصورة، أو أيا كان، ولكن لماذا لا تنظر في ما إذا كان goto ربما أفضل أداة لهذا الوظيفة؟وبعبارة أخرى، ما مدى استعداد الأشخاص لتحمل القبح لتجنب استخدام ميزة لغوية مدمجة للغرض المقصود منه؟رأيي هو أنه حتى إضافة العلم هو سعر مرتفع للغاية لدفعه.أحب متغيراتي لتمثيل الأشياء في مجالات المشكلة أو الحلول.'فقط لتجنب أ gotoلا يقطعها.

سأقبل الإجابة الأولى التي أعطت النمط C للتفرع إلى كتلة التنظيف.المنظمة البحرية الدولية (IMO)، وهذا يمثل أقوى حجة لـ أ goto من بين جميع الإجابات المنشورة ، وبالتأكيد إذا قمت بقياسها من خلال التهابات التي يتعين على الكهنة أن يمر بها لتجنبها.

هل كانت مفيدة؟

المحلول

إليك إحدى الحيل التي سمعتها عن أشخاص يستخدمونها.بالرغم من ذلك، لم أره قط في البرية.وهذا ينطبق فقط على لغة C لأن C++ يحتوي على RAII للقيام بذلك بشكل أكثر اصطلاحًا.

void foo()
{
    if (!doA())
        goto exit;
    if (!doB())
        goto cleanupA;
    if (!doC())
        goto cleanupB;

    /* everything has succeeded */
    return;

cleanupB:
    undoB();
cleanupA:
    undoA();
exit:
    return;
}

نصائح أخرى

الحاجة الكلاسيكية لـ GOTO في C هي كما يلي

for ...
  for ...
    if(breakout_condition) 
      goto final;

final:

لا توجد طريقة مباشرة للخروج من الحلقات المتداخلة دون الحاجة إلى الانتقال.

هذا هو المثال غير السخيف (من ستيفنز APITUE) لمكالمات نظام يونكس التي قد تتم مقاطعتها بواسطة إشارة.

restart:
    if (system_call() == -1) {
        if (errno == EINTR) goto restart;

        // handle real errors
    }

البديل هو حلقة متدهورة.يقرأ هذا الإصدار مثل اللغة الإنجليزية "إذا تمت مقاطعة استدعاء النظام بواسطة إشارة، فأعد تشغيله".

إذا لم يكن جهاز Duff بحاجة إلى الانتقال، فلا ينبغي لك ذلك أيضًا!;)

void dsend(int count) {
    int n;
    if (!count) return;
    n = (count + 7) / 8;
    switch (count % 8) {
      case 0: do { puts("case 0");
      case 7:      puts("case 7");
      case 6:      puts("case 6");
      case 5:      puts("case 5");
      case 4:      puts("case 4");
      case 3:      puts("case 3");
      case 2:      puts("case 2");
      case 1:      puts("case 1");
                 } while (--n > 0);
    }
}

الكود أعلاه من ويكيبيديا دخول.

كتب Knuth ورقة بحثية بعنوان "البرمجة المنظمة باستخدام عبارات GOTO"، ويمكنك الحصول عليها على سبيل المثال.من هنا.ستجد العديد من الأمثلة هناك.

شائع جدًا.

do_stuff(thingy) {
    lock(thingy);

    foo;
    if (foo failed) {
        status = -EFOO;
        goto OUT;
    }

    bar;
    if (bar failed) {
        status = -EBAR;
        goto OUT;
    }

    do_stuff_to(thingy);

OUT:
    unlock(thingy);
    return status;
}

الحالة الوحيدة التي أستخدمها على الإطلاق goto مخصصة للقفز للأمام، عادةً خارج الكتل، وليس داخل الكتل أبدًا.وهذا يتجنب إساءة استخدام do{}while(0) والبنيات الأخرى التي تزيد من التداخل، مع الحفاظ على التعليمات البرمجية المنظمة والقابلة للقراءة.

ليس لدي أي شيء ضد gotos بشكل عام، ولكن يمكنني التفكير في عدة أسباب لعدم رغبتك في استخدامها في حلقة كما ذكرت:

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

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

if (!openDataFile())
  goto quit;

if (!getDataFromFile())
  goto closeFileAndQuit;

if (!allocateSomeResources)
  goto freeResourcesAndQuit;

// Do more work here....

freeResourcesAndQuit:
   // free resources
closeFileAndQuit:
   // close file
quit:
   // quit!

@fizzer.myopenid.com:مقتطف الرمز المنشور الخاص بك يعادل ما يلي:

    while (system_call() == -1)
    {
        if (errno != EINTR)
        {
            // handle real errors

            break;
        }
    }

أنا بالتأكيد أفضل هذا النموذج.

على الرغم من أنني أصبحت أكره هذا النمط بمرور الوقت، إلا أنه متأصل في برمجة COM.

#define IfFailGo(x) {hr = (x); if (FAILED(hr)) goto Error}
...
HRESULT SomeMethod(IFoo* pFoo) {
  HRESULT hr = S_OK;
  IfFailGo( pFoo->PerformAction() );
  IfFailGo( pFoo->SomeOtherAction() );
Error:
  return hr;
}

فيما يلي مثال على goto الجيد:

// No Code

لقد رأيت استخدام goto بشكل صحيح ولكن المواقف قبيحة بشكل طبيعي.ما هو إلا عند استخدام goto في حد ذاته أقل سوءًا بكثير من الأصل.@ جوناثون هولاند المشكلة هي أن نسختك أقل وضوحًا.يبدو أن الناس خائفون من المتغيرات المحلية:

void foo()
{
    bool doAsuccess = doA();
    bool doBsuccess = doAsuccess && doB();
    bool doCsuccess = doBsuccess && doC();

    if (!doCsuccess)
    {
        if (doBsuccess)
            undoB();
        if (doAsuccess)
            undoA();
    }
}

وأنا أفضل حلقات مثل هذه ولكن بعض الناس يفضلونها while(true).

for (;;)
{
    //code goes here
}

مشكلتي في هذا الأمر هي أنك تفقد نطاق الكتلة؛تظل أي متغيرات محلية مُعلن عنها بين gotos سارية المفعول إذا تم كسر الحلقة.(ربما تفترض أن الحلقة تعمل إلى الأبد؛لا أعتقد أن هذا ما كان يطرحه كاتب السؤال الأصلي.)

مشكلة تحديد النطاق هي مشكلة تتعلق أكثر بـ C++، حيث قد تعتمد بعض الكائنات على dtor الخاص بها الذي يتم استدعاؤه في الأوقات المناسبة.

بالنسبة لي، أفضل سبب لاستخدام goto هو أثناء عملية تهيئة متعددة الخطوات حيث يكون من الضروري أن يتم نسخ جميع الأحرف الأولية احتياطيًا في حالة فشل إحداها، على سبيل المثال:

if(!foo_init())
  goto bye;

if(!bar_init())
  goto foo_bye;

if(!xyzzy_init())
  goto bar_bye;

return TRUE;

bar_bye:
 bar_terminate();

foo_bye:
  foo_terminate();

bye:
  return FALSE;

أنا لا أستخدم goto بنفسي، لكنني عملت مع شخص يستخدمها في حالات محددة.إذا كنت أتذكر بشكل صحيح، كان مبرره يتعلق بقضايا الأداء - وكان لديه أيضًا قواعد محددة لها كيف.دائمًا في نفس الوظيفة، وكانت التسمية دائمًا أسفل عبارة goto.

#include <stdio.h>
#include <string.h>

int main()
{
    char name[64];
    char url[80]; /*The final url name with http://www..com*/
    char *pName;
    int x;

    pName = name;

    INPUT:
    printf("\nWrite the name of a web page (Without www, http, .com) ");
    gets(name);

    for(x=0;x<=(strlen(name));x++)
        if(*(pName+0) == '\0' || *(pName+x) == ' ')
        {
            printf("Name blank or with spaces!");
            getch();
            system("cls");
            goto INPUT;
        }

    strcpy(url,"http://www.");
    strcat(url,name);
    strcat(url,".com");

    printf("%s",url);
    return(0);
}

@ جريج:

لماذا لا تفعل المثال الخاص بك مثل هذا:

void foo()
{
    if (doA())
    {    
        if (doB())
        {
          if (!doC())
          {
             UndoA();
             UndoB();
          }
        }
        else
        {
          UndoA();
        }
    }
    return;
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top