Döngüler ve işlevleri destekleyen bir dilde "git" i kullanmak hiç avantajlı oldu mu? Öyleyse neden?


203

Uzun zamandır gotomümkünse kullanılmaması gereken bir izlenim altındayım . Geçen gün libavcodec'i incelerken (C ile yazılmış), bunun birçok kullanımını fark ettim. gotoDöngüler ve işlevleri destekleyen bir dilde kullanmak hiç avantajlı mıdır ? Öyleyse neden?

Yanıtlar:


245

Farkında olduğum "git" ifadesini kullanmanın birkaç nedeni vardır (bazıları bununla zaten konuşmuştur):

Bir işlevden temiz bir şekilde çıkma

Genellikle bir işlevde, kaynak ayırabilirsiniz ve birden çok yerden çıkmanız gerekebilir. Programcılar, kaynak temizleme kodunu işlevin sonuna koyarak kodlarını basitleştirebilir ve işlevin tüm "çıkış noktaları" temizleme etiketine gider. Bu şekilde, işlevin her "çıkış noktasına" temizleme kodu yazmanız gerekmez.

İç içe döngülerden çıkma

Yuvalanmış bir döngüdeyseniz ve tüm döngülerden kurtulmanız gerekiyorsa , bir goto bunu break ifadelerinden ve if-kontrollerinden daha temiz ve basit hale getirebilir.

Düşük seviye performans iyileştirmeleri

Bu yalnızca mükemmel kritik kod için geçerlidir, ancak goto ifadeleri çok hızlı bir şekilde yürütülür ve bir işlevde hareket ederken size bir destek verebilir. Bununla birlikte, bu iki ucu keskin bir kılıçtır, çünkü bir derleyici genellikle goto içeren kodu optimize edemez.

Tüm bu örneklerde, goto'ların tek bir işlevin kapsamı ile sınırlı olduğunu unutmayın.


15
İç içe döngülerden çıkmanın doğru yolu, iç halkayı ayrı bir yönteme yeniden yansıtmaktır.
jason

130
@Jason - Bah. Bu bir boğa yükü. Değiştirme gotoile returnsadece saçma. Hiçbir şeyi "yeniden düzenleme" değil, sadece "yeniden adlandırmak " gototır , böylece bastırılmış bir ortamda büyüyen insanlar (yani hepimiz) ahlaki açıdan önemli olan şeyleri kullanma konusunda daha iyi hissederler goto. Ben çok kullanmak için döngü görmeyi tercih ve biraz gotokendi başına, sadece bir araç olduğunu , biri sadece bir önlemek için ilgisi olmayan bir yere döngü taşındı görmek goto.
Chris Lutz

18
Goto'ların önemli olduğu bazı uyarılar vardır: örneğin, istisnasız bir C ++ ortamı. Silverlight kaynağında, makroların kullanılmasıyla güvenli işlev için on binlerce (veya daha fazla) goto deyimimiz var - önemli medya kodekleri ve kütüphaneleri genellikle dönüş değerleri ve hiçbir zaman istisnalar olmadan çalışır ve bu hata işleme mekanizmalarını birleştirmek zordur. tek bir performans yolu.
Jeff Wilcox

80
It değerinde belirterek tüm bu break, continue, returntemelde goto, sadece güzel ambalajında.
el.pescado

17
do{....}while(0)Java'da çalıştığı gerçeği dışında, nasıl goto'dan daha iyi bir fikir olduğunu sanmıyorum.
Jeremy List

909

gotoEdsger Dijkstra'nın GoTo Dikkate Alınan Zararlı makalesi, pozisyonlarını doğrulamak için doğrudan ya da dolaylı olarak antikülit olan herkes . Çok kötü Dijkstra'nın makale neredeyse sahiptir hiçbir şekilde ilgisi gotoifadeleri biraz modern programlama olay yerine hiçbir uygulanabilirliği böylece makale vardır ne diyor bu günlerde kullanılan bir üründür. gotoŞimdi bir din -daha az meme kıyılan, onun kutsal hakkı aşağı yükseği, yüksek rahipler ve algılanan kafir shunning (ya da kötü) üzerine gelen dikte.

Konuya biraz ışık tutmak için Dijkstra'nın makalesini bağlam içine koyalım.

Dijkstra makalesini yazdığında, dönemin popüler dilleri BASIC, FORTRAN (önceki lehçeler) ve çeşitli montaj dilleri gibi yapılandırılmamış prosedürel dillerdi. Üst düzey dilleri kullanan kişilerin , "spagetti kodu" terimini doğuran, bükülmüş, bükülmüş yürütme iş parçacıklarında kod tabanlarının her yerine atlaması oldukça yaygındı . Mike Mayfield tarafından yazılan klasik Trek oyununa atlayarak ve işlerin nasıl çalıştığını anlamaya çalışarak bunu görebilirsiniz. Bunu görmek için birkaç dakikanızı ayırın.

BU , Dijkstra'nın 1968'de gazetesinde korktuğu "ifadeye gitme ifadesinin dizginsiz kullanımı" dır . BU , onun bu makaleyi yazmasına neden olan yaşadığı ortamdır. Beğendiğiniz herhangi bir noktada kodunuzda istediğiniz yere atlayabilme yeteneği, eleştirdiği ve durdurulmasını talep ettiği şeydi. Bunu gotoC veya diğer daha modern dillerin anemik güçleriyle karşılaştırmak basitçe riske edilebilir.

Tarikatçılarla yüzleştikçe kültistlerin tezahüratlarını zaten duyabiliyorum. "Ama," zikredecekler, " gotoC ile okumayı çok zorlaştırabilirsin ." Ah evet? Kodu da okumayı çok zorlaştırabilirsiniz goto. Bunun gibi:

#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

gotoGörünürde değil , bu yüzden okunması kolay olmalı, değil mi? Veya buna ne dersiniz:

a[900];     b;c;d=1     ;e=1;f;     g;h;O;      main(k,
l)char*     *l;{g=      atoi(*      ++l);       for(k=
0;k*k<      g;b=k       ++>>1)      ;for(h=     0;h*h<=
g;++h);     --h;c=(     (h+=g>h     *(h+1))     -1)>>1;
while(d     <=g){       ++O;for     (f=0;f<     O&&d<=g
;++f)a[     b<<5|c]     =d++,b+=    e;for(      f=0;f<O
&&d<=g;     ++f)a[b     <<5|c]=     d++,c+=     e;e= -e
;}for(c     =0;c<h;     ++c){       for(b=0     ;b<k;++
b){if(b     <k/2)a[     b<<5|c]     ^=a[(k      -(b+1))
<<5|c]^=    a[b<<5      |c]^=a[     (k-(b+1     ))<<5|c]
;printf(    a[b<<5|c    ]?"%-4d"    :"    "     ,a[b<<5
|c]);}      putchar(    '\n');}}    /*Mike      Laman*/

gotoOrada da yok . Bu nedenle okunabilir olmalıdır.

Bu örneklerle ne demek istiyorum? Okunamaz, sürdürülemez kod yapan dil özellikleri değildir. Bunu yapan sözdizimi değil. Buna neden olan kötü programcılar. Ve kötü programcılar, yukarıdaki öğede gördüğünüz gibi, herhangi bir dil özelliğini okunamaz ve kullanılamaz hale getirebilir. forOradaki döngüler gibi . (Onları görebiliyorsun, değil mi?)

Adil olmak gerekirse, bazı dil yapılarını kötüye kullanmak diğerlerinden daha kolaydır. Bununla birlikte, bir C programcısıysanız, #definekarşı bir haçlı seferi yapmadan önce uzun kullanımların yaklaşık% 50'sine çok daha yakından bakarım goto!

Bu yüzden, şimdiye kadar okumaktan rahatsız olanlar için, dikkat edilmesi gereken birkaç önemli nokta var.

  1. Üzerinde Dijkstra'nın kağıt gotoifadeleri bir programlama ortamı için yazılmış gotobir edildi sürü daha potansiyel bir montajcı değildir en modern dilde olandan zarar.
  2. Bunun tüm kullanımlarını otomatik olarak atmak goto, "Bir kez eğlenmeye çalıştım ama bundan hoşlanmadım, şimdi buna karşıyım" demek kadar mantıklı.
  3. Koddaki modern (anemik) gotoifadelerin diğer yapılarla yeterince değiştirilemeyen meşru kullanımları vardır .
  4. Elbette aynı ifadelerin gayri meşru kullanımları vardır.
  5. Ayrıca, " godo" her zaman yanlış bir dodöngünün breaka yerine kullanılmasının kesildiği "iğrençlik " gibi modern kontrol ifadelerinin gayri meşru kullanımları da vardır goto. Bunlar genellikle makul kullanımlardan daha kötüdür goto.

42
@pocjoc: Örneğin C'de kuyruk yinelemeli optimizasyon yazma. Bu, işlevsel dil tercümanlarının / çalışma zamanlarının uygulanmasında ortaya çıkar, ancak ilginç bir sorun kategorisini temsil eder. C dilinde yazılmış çoğu derleyici bu nedenle goto kullanır. Tabii ki çoğu durumda ortaya çıkmayan bir niş kullanımı, ancak çağrıldığı durumlarda kullanımı çok mantıklı.
zxq9

66
Neden herkes Dijkstra'nın "Zarar görmüş sayılır" kağıdından, Knuth'un cevabından, "İfadelere git ile yapılandırılmış programlama" dan bahsetmeden bahsediyor?
Oblomov

22
@ Nietzche-jou "Bu oldukça açık bir saman adam [...]" Stigmanın neden mantıksız olduğunu göstermek için alaycı bir şekilde yanlış bir ikilik kullanıyor. Bir Strawman , rakibinin pozisyonunu kolayca yenilebileceğini göstermek için kasten yanlış beyan ettiğiniz mantıklı bir yanlıştır. Örneğin "Ateistler sadece ateisttir çünkü Tanrı'dan nefret ederler". Yanlış bir ikilik , en az bir ek olasılık (yani siyah ve beyaz) olduğunda karşıt aşırının doğru olduğunu varsaydığınızda ortaya çıkar. Örneğin, "Tanrı eşcinsellerden nefret eder, bu nedenle heteroseksüelleri sever."
Braden Best

28
@JLRishe Orada olmayan bir şeye çok derin okuyorsun. Bu, onu dürüstçe kullanan kişinin mantıklı olduğuna inanması durumunda gerçek bir mantık yanılgısıdır. Ama alaycı bir şekilde, saçma olduğunu bildiğini açıkça gösterdi ve bunu böyle gösteriyor. O "amacını haklı çıkarmak için kullanmıyor"; bunu neden gotoların otomatik olarak spagetti'ye kod yaptığını söylemenin saçma olduğunu göstermek için kullanıyor. Yani hala boksuz kod yazabilirsiniz. Bu onun amacı. Ve alaycı yanlış ikilik onu esprili bir şekilde gösterme yöntemidir.
Braden Best

32
-1, Dijkstra'nın sözünün neden geçerli olmadığına dair çok büyük bir tartışma yapmak için, gotoaslında avantajlarının ne olduğunu (unutulmuş olan soru) göstermeyi unutmayı unutma
Maarten Bodewes

154

En iyi uygulamalara körü körüne uymak en iyi uygulama değildir. gotoBirinin birincil akış kontrolü biçimi olarak ifadelerden kaçınma fikri okunamayan spagetti kodu üretmekten kaçınmaktır. Doğru yerlerde az kullanılırsa, bazen bir fikri ifade etmenin en basit, en açık yolu olabilirler. Zortech C ++ derleyicisinin ve D programlama dilinin yaratıcısı Walter Bright, bunları sık ama makul bir şekilde kullanıyor. gotoİfadelerle bile , kodu hala mükemmel bir şekilde okunabilir.

Alt satır: Kaçınma gotouğruna kaçınmak gotoanlamsızdır. Gerçekten kaçınmak istediğiniz şey okunamayan kod üretmektir. Senin Eğer goto-laden kod okunabilir, sonra onunla yanlış bir şey yok.


36

Yana gotoprogram hakkında akıl yürütme markaları akış sert 1 (aka “spagetti kod”.), gotoGenellikle sadece eksik özellikleri dengelemek için kullanılır: kullanımı gotoaslında kabul edilebilir, ama dil daha yapılandırılmış varyantını sunmuyor yalnızca elde etmek aynı amaç. Şüphe örneğini alın:

Kullandığımız goto kuralı, goto'nun bir işlevdeki tek bir çıkış temizleme noktasına ileri atlamak için uygun olmasıdır.

Bu doğrudur - ancak dil finally, aynı işi daha iyi yapan (bunu yapmak için özel olarak oluşturulduğu için) temizleme koduyla (RAII veya ) yapılandırılmış istisna işlemeye izin vermezse veya iyi bir neden olmadığında yapılandırılmış istisna işleme (ancak çok düşük bir düzey dışında bu duruma asla sahip olmayacaksınız).

Diğer birçok dilde, kabul edilebilir tek kullanımı gotoiç içe döngülerden çıkmaktır. Ve orada bile dış halkayı kendi yöntemine kaldırmak ve kullanmak neredeyse her zaman daha iyidir return.

Bunun dışında goto, belirli bir kod parçasına yeterince düşünülmemiş bir işarettir.


1 Desteği destekleyen modern diller gotobazı kısıtlamaları gotoyerine getirir (örneğin işlevlere girip çıkmayabilir) ancak sorun temelde aynı kalır.

Bu arada, tabii ki, diğer istisnalar olmak üzere, diğer dil özellikleri için de geçerlidir. Ve istisnai olmayan program akışını kontrol etmek için istisnalar kullanmama kuralı gibi, yalnızca belirtildiği yerlerde bu özellikleri kullanmak için genellikle katı kurallar vardır.


4
Sadece burada merak ediyorum, ama temizleme kodu için gotos kullanma durumu. Temizlemekle kastediyorum, sadece belleğin yeniden yerleştirilmesi değil, aynı zamanda hata günlüğü. Bir sürü gönderiyi okuyordum ve görünüşe göre, hiç kimse günlükleri yazan kod yazmıyor .. hmm ?!
shiva

37
"Temel olarak, goto'nun kusurlu doğası nedeniyle (ve bunun tartışmasız olduğuna inanıyorum)" -1 ve orada okumayı bıraktım.
o0 '.

14
Gitmeye karşı uyarı, erken getirilerin de kötü olduğu düşünülen yapılandırılmış programlama döneminden gelir. İç içe ilmekleri bir işleve taşımak, bir diğeri için "kötülük" esnaf eder ve varolması için gerçek bir nedeni olmayan bir işlev yaratır ("bir goto kullanmaktan kaçınmama izin verdi!" Dışında). Goto'nun kısıtlama olmaksızın kullanıldığında spagetti kodu üretmek için en güçlü araç olduğu doğrudur, ancak bu onun için mükemmel bir uygulamadır; kodu basitleştirir. Knuth bu kullanım için tartıştı ve Dijkstra "Gitmeyle ilgili çok dogmatik olduğuma inanma tuzağına düşme" dedi.
Çamur

5
finally? Hata işleme dışındaki şeyler için istisnalar kullanmak iyi ama kullanmak gotokötü mü? Bence istisnalar oldukça uygun bir şekilde adlandırılmıştır.
Christian

3
@Mud: I (günlük olarak) karşılaştığı durumlarda, "var olma gerçek bir neden vardır" bir işlev oluştururken olduğunu iyi çözüm. Sebep: Üst düzey işlevden ayrıntıyı kaldırır, böylece sonuç okunması kolay kontrol akışına sahiptir. Şahsen bir goto'nun en okunabilir kodu ürettiği durumlarla karşılaşmıyorum. Öte yandan, basit işlevlerde, bir başkasının tek bir çıkış noktasına gitmeyi tercih edebileceği birden fazla dönüş kullanıyorum. Goto'nun iyi adlandırılmış işlevlere yeniden düzenlemekten daha okunabilir olduğu karşı örnekleri görmek isterim.
ToolmakerSteve

35

Her zamankinden daha kötü olan bir şey var goto's; bir goto'yu önlemek için diğer program akışı operatörlerinin garip kullanımı:

Örnekler:

    // 1
    try{
      ...
      throw NoErrorException;
      ...
    } catch (const NoErrorException& noe){
      // This is the worst
    } 


    // 2
    do {
      ...break; 
      ...break;
    } while (false);


    // 3
    for(int i = 0;...) { 
      bool restartOuter = false;
      for (int j = 0;...) {
        if (...)
          restartOuter = true;
      if (restartOuter) {
        i = -1;
      }
    }

etc
etc

2
do{}while(false)Bence deyimsel olarak düşünülebilir.
Katılmanıza

37
@trinithis: Eğer "deyimsel" ise, bu sadece anti-goto kültünden kaynaklanmaktadır. Eğer yakından bakarsanız, bunu söylemeden sadece söylemenin bir yolu olduğunu anlayacaksınız goto after_do_block;. Aksi halde ... tam olarak bir kez çalışan bir "döngü" mü? Buna kontrol yapılarını kötüye kullanma derdim.
cHao

5
@ThomasEding Eding Bu konuda bir istisna var. Biraz C / C ++ programlaması yaptıysanız ve #define kullanmak zorunda kalırsanız, bunu bilirsiniz, {} ise (0) birden çok kod satırını kapsüllemek için standartlardan biridir. Örneğin: #define do {memcpy (a, b, 1); bir şey ++;} (0) #define memcpy (a, b, 1) '
Ignas2526

10
@ Ignas2526 Bir arada bir #definekullanmaktan çok, çok, çok daha kötü olduğunu çok güzel gösterdiniz goto: D
Luaan

3
İnsanların gotolardan kaçınmaya çalıştıkları yolların iyi bir listesi için + 1; Başka yerlerde bu tür tekniklerden söz ettim, ama bu harika, kısa ve öz bir liste. Son 15 yılda, ekibimin neden bu tekniklerin hiçbirine ihtiyaç duymadığını ya da gotos kullanmadığını düşünerek. Birden fazla programcı tarafından yüz binlerce satır kod içeren bir istemci / sunucu uygulamasında bile. C #, java, PHP, python, javascript. İstemci, sunucu ve uygulama kodu. Bazı insanlar durumlarla karşılaşabilirsiniz neden değil en net çözüm olarak gotos, ve diğerleri için yalvarıyorum ... yapma böyle, sadece gerçekten merak palavra, ne de bir pozisyon sözcülüğünü
ToolmakerSteve

28

In C # anahtar deyimi Doest sonbahar-through izin vermez . Bu nedenle , kontrolü belirli bir anahtar kutu etiketine veya varsayılan etikete aktarmak için goto kullanılır .

Örneğin:

switch(value)
{
  case 0:
    Console.Writeln("In case 0");
    goto case 1;
  case 1:
    Console.Writeln("In case 1");
    goto case 2;
  case 2:
    Console.Writeln("In case 2");
    goto default;
  default:
    Console.Writeln("In default");
    break;
}

Düzenleme: "Düşme yok" kuralında bir istisna vardır. Bir vaka ifadesinde kod yoksa düşmeye izin verilir.


3
Geçiş anahtarı .NET 2.0'da desteklenir - msdn.microsoft.com/en-us/library/06tc147t(VS.80).aspx
rjzii

9
Yalnızca kasanın bir kod gövdesi yoksa. Kodu varsa, o zaman goto anahtar sözcüğünü kullanmalısınız.
Matthew

26
Bu cevap çok komik - C # düşme kaldırıldı, çünkü birçok kişi zararlı olarak görüyor ve bu örnek orijinal, sözde zararlı, davranışı canlandırmak için goto (birçok kişi tarafından zararlı olarak da görülüyor) kullanıyor, ancak genel sonuç aslında daha az zararlı ( çünkü kod, geçişin kasıtlı olduğunu açıkça ortaya koyuyor!).
thomasrutter

9
Bir anahtar kelimenin GOTO harfleriyle yazılmış olması onu bir goto yapmaz. Sunulan bu vaka bir goto değildir. Geçiş için bir switch deyimi yapısıdır. Sonra tekrar, gerçekten C # bilmiyorum, bu yüzden yanlış olabilir.
Thomas Eding

1
Bu durumda, düşme işleminden biraz daha fazladır (çünkü goto case 5:1. durumda olduğunuzu söyleyebilirsiniz ). Konrad Rudolph'un cevabı burada doğru gibi görünüyor: gotoeksik bir özelliği telafi ediyor (ve gerçek özelliğin olacağından daha az açık). Eğer gerçekten istediğimiz düşme ise, belki de en iyi varsayılan düşme olmazdı, ama continueaçıkça talep etmek gibi bir şey olurdu.
David Stone

14

#ifdef TONGUE_IN_CHEEK

Perl, gotofakir adamın kuyruk çağrılarını uygulamanızı sağlayan bir var. :-P

sub factorial {
    my ($n, $acc) = (@_, 1);
    return $acc if $n < 1;
    @_ = ($n - 1, $acc * $n);
    goto &factorial;
}

#endif

Tamam, bunun C'lerle bir ilgisi yok goto. Daha da ciddisi, gototemizlik için veya Duff'ın cihazını veya benzerlerini uygulamakla ilgili diğer yorumlara katılıyorum . Her şey kötüye kullanmak değil, kullanmakla ilgilidir.

(Aynı yorum longjmpistisnalar call/ccve benzerleri için de geçerlidir --- meşru kullanımları vardır, ancak kolayca kötüye kullanılabilirler. Örneğin, tamamen istisnai olmayan koşullar altında, tamamen iç içe bir kontrol yapısından kaçmak için bir istisna atmak .)


Perl'de goto kullanmanın tek nedeni olduğunu düşünüyorum.
Brad Gilbert

12

Yıllar boyunca birkaç satırdan fazla montaj dili yazdım. Nihayetinde, her üst düzey dil gotolara derlenir. Tamam, onlara "dallar", "zıplamalar" ya da başka bir şey deyin, ama onlar gotos. Herkes daha az montajcı yazabilir mi?

Şimdi, gotos ile isyan etmenin spagetti bolognaise için bir reçete olduğunu bir Fortran, C veya BASIC programcıya işaret edebilirsiniz. Ancak cevap onlardan kaçınmak değil, dikkatli kullanmaktır.

Bir bıçak yiyecek hazırlamak, birini kurtarmak veya birini öldürmek için kullanılabilir. Bıçak korkusu olmadan bıçaksız mı yapıyoruz? Benzer şekilde goto: dikkatsizce kullanılır engel olur, dikkatli kullanılır yardımcı olur.


1
Belki de bunun neden temelde yanlış olduğuna inandığımı stackoverflow.com/questions/46586/…
Konrad Rudolph

15
"Her şey zaten JMP için derler!" argüman temelde daha yüksek bir dilde programlamanın ardındaki noktayı anlamıyor.
Nietzche-jou

7
Gerçekten ihtiyacınız olan tek şey çıkarma ve dallama. Diğer her şey kolaylık veya performans içindir.
David Stone

8

C Programlama Yaparken Ne Zaman Kullanılacağına Bir Bakış :

Goto kullanımı neredeyse her zaman kötü bir programlama uygulaması olmasına rağmen (kesinlikle XYZ yapmanın daha iyi bir yolunu bulabilirsiniz), gerçekten kötü bir seçim olmadığı zamanlar vardır. Bazıları, yararlı olduğunda en iyi seçim olduğunu bile iddia edebilir.

Goto hakkında söyleyeceklerimin çoğu gerçekten sadece C için geçerlidir. C ++ kullanıyorsanız, istisnalar yerine goto kullanmanın sağlam bir nedeni yoktur. Bununla birlikte, C'de bir istisna işleme mekanizmasının gücüne sahip değilsiniz, bu nedenle hata işlemeyi program mantığınızın geri kalanından ayırmak istiyorsanız ve kodunuz boyunca birden çok kez temizleme kodunu yeniden yazmaktan kaçınmak istiyorsanız, o zaman git iyi bir seçim olabilir.

Ne demek istiyorum? Şuna benzeyen bazı kodlarınız olabilir:

int big_function()
{
    /* do some work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* clean up*/
    return [success];
}

Temizleme kodunuzu değiştirmeniz gerektiğini anlayana kadar bu sorun olmaz. O zaman geçip 4 değişiklik yapmanız gerekiyor. Şimdi, tüm temizlemeyi tek bir işleve kapsülleyebileceğinize karar verebilirsiniz; bu kötü bir fikir değil. Ancak bu, işaretçilerle dikkatli olmanız gerektiği anlamına gelir - temizleme işlevinizde bir işaretçiyi serbest bırakmayı planlıyorsanız, bir işaretçiyi bir işaretçiye geçmedikçe NULL'a işaret etmenin bir yolu yoktur. Çoğu durumda, bu işaretçiyi bir daha kullanmazsınız, bu büyük bir endişe kaynağı olmayabilir. Öte yandan, yeni bir işaretçi, dosya tanıtıcısı veya temizlenmesi gereken başka bir şey eklerseniz, temizleme işlevinizi tekrar değiştirmeniz gerekir; ve sonra argümanları bu işleve değiştirmeniz gerekir.

Kullanarak goto, olacak

int big_function()
{
    int ret_val = [success];
    /* do some work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
end:
    /* clean up*/
    return ret_val;
}

Buradaki avantaj, sondaki kodunuzun temizleme gerçekleştirmek için ihtiyaç duyacağı her şeye erişebilmesidir ve değişiklik noktası sayısını önemli ölçüde azaltmayı başardınız. Bir diğer avantajı, işleviniz için birden fazla çıkış noktasına sahip olmaktan sadece bir tanesine gitmenizdir; temizlik yapmadan yanlışlıkla işlevden geri dönme şansınız yoktur.

Dahası, goto sadece tek bir noktaya atlamak için kullanıldığından, işlev çağrılarını simüle etmek için ileri geri sıçrayan bir spagetti kodu kütlesi oluşturuyormuşsunuz gibi değil. Aksine, goto aslında daha yapılandırılmış kod yazmaya yardımcı olur.


Tek kelimeyle, gotoher zaman azimle ve son çare olarak kullanılmalıdır - ama bunun için bir zaman ve bir yer var. Soru "kullanmak zorunda mısın" değil, "kullanmak için en iyi seçim mi?" Olmalıdır.


7

Gitmenin nedenlerinden biri, kodlama stilinin yanı sıra, üst üste binen , ancak iç içe olmayan döngüler oluşturmak için kullanabilmenizdir :

loop1:
  a
loop2:
  b
  if(cond1) goto loop1
  c
  if(cond2) goto loop2

Bu, derleyici bilgisayar korsanlarını mutsuz yapan (a, b, c, b, a, b, a, b, ...) gibi bir dizinin mümkün olduğu tuhaf, ancak muhtemelen yasal kontrol akışı yapısını yaratacaktır. Görünüşe göre, bu tür yapıya dayanmayan bir dizi akıllı optimizasyon hilesi var. (Ejderha kitabımın kopyasını kontrol etmeliyim ...) Bunun sonucu (bazı derleyicileri kullanarak) gotos içeren kod için diğer optimizasyonların yapılmaması olabilir .

Eğer varsa faydalı olabilir biliyorum sadece, "Ah, bu arada", daha hızlı yayması için kod derleyici ikna olur. Şahsen, derleyiciye olası bir şey ve goto gibi bir hile kullanmadan önce neyin olmadığını açıklamaya çalışmayı tercih ederim, ama tartışmasız bir gotoaraya gelmeden önce de deneyebilirim .


3
Şey ... beni bir finansal bilgi şirketinde FORTRAN'ı programlama günlerine geri götürüyor. 2007'de
Marcin

5
Herhangi bir dil yapısı, okunamaz veya kötü performans gösterecek şekilde kötüye kullanılabilir.
SADECE DOĞRU GÖRÜMÜM

@SADECE: Mesele okunabilirlik veya düşük performans değil, kontrol akışı grafiği hakkında varsayımlar ve garantiler. Gitmenin kötüye kullanılması, performansı (veya okunabilirliği) arttırır .
Anders Eurenius

3
Bunun nedenlerinden gotobirinin yararlı olduğunu iddia ederim , bunun gibi döngüler oluşturmanıza izin verir, aksi takdirde bir sürü mantıksal eğri gerektirir. Ayrıca, optimize edici bunu nasıl yeniden yazacağını bilmiyorsa, o zaman iyi olduğunu iddia ediyorum . Performans veya okunabilirlik için böyle bir döngü yapılmamalıdır, çünkü bu tam olarak işlerin gerçekleşmesi gereken sıradır. Bu durumda özellikle optimize edici onunla vidalama istemem .
cHao

1
... gerekli algoritmanın GOTO tabanlı koda doğrudan çevirisinin doğrulanması, bayrak değişkenlerinin ve istismar edilen döngü yapılarının karışıklığından çok daha kolay olabilir.
supercat

7

Bazı insanların, diğer tüm kullanımların kabul edilemez olduğunu söyleyerek, goto'nun kabul edilebilir olduğu vakaların bir listesini vermek için gidebileceğini komik buluyorum. Gerçekten, bir algoritmayı ifade etmek için goto'nun en iyi seçim olduğu her vakayı bildiğinizi düşünüyor musunuz?

Örneklemek gerekirse, size burada hiç kimsenin göstermediği bir örnek vereceğim:

Bugün karma tabloya eleman eklemek için kod yazıyordum. Karma tablosu, isteğe bağlı olarak üzerine yazılabilen önceki hesapların önbelleğidir (performansı etkiler, ancak doğruluk değil).

Karma tablonun her bir kovasında 4 yuva vardır ve bir kova dolduğunda hangi öğenin üzerine yazılacağına karar vermek için bir grup ölçütüm vardır. Şu anda bu, bir kovadan üçe kadar geçiş yapmak anlamına geliyor, şöyle:

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    goto add;

// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
    goto add;

// Additional passes go here...

add:
// element is written to the hash table here

Şimdi goto kullanmazsam, bu kod neye benzeyecekti?

Bunun gibi bir şey:

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    break;

if (add_index >= ELEMENTS_PER_BUCKET) {
  // Otherwise, find first empty element
  for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
    if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
      break;
  if (add_index >= ELEMENTS_PER_BUCKET)
   // Additional passes go here (nested further)...
}

// element is written to the hash table here

Daha fazla geçiş eklenirse daha kötü ve daha kötü görünürken, goto'lu sürüm her zaman aynı girinti seviyesini korur ve sonucu önceki döngünün uygulanmasıyla ima edilen sahte if ifadelerinin kullanılmasını önler.

Bu yüzden goto'nun kodu daha temiz ve yazmayı ve anlamayı kolaylaştırdığı başka bir durum daha var ... Eminim çok daha fazlası var, bu yüzden goto'nun yararlı olduğu tüm durumları biliyormuş gibi yapmayın, yapamayacağınız iyi olanları silerek düşünmeyin.


1
Verdiğiniz örnekte, bunu oldukça önemli bir şekilde yeniden düzenlemeyi tercih ederim. Genel olarak, bir sonraki kod yığınının ne yaptığını söyleyen tek satırlı yorumlardan kaçınmaya çalışıyorum. Bunun yerine, açıklamaya benzer adlı kendi işlevine ayırıyorum. Böyle bir dönüşüm yapacak olsaydınız, bu işlev, işlevin ne yaptığına dair üst düzey bir genel bakış sunacak ve yeni işlevlerin her biri, her adımı nasıl yaptığınızı belirtecektir. gotoHer işleve aynı soyutlama düzeyinde sahip olmanın herhangi bir karşıtlığından çok daha önemli olduğunu düşünüyorum. Kaçınılması gotobir bonus.
David Stone

2
Daha fazla işlev eklemenin goto, çoklu girinti düzeyleri ve sahte ifadelerden nasıl kurtulduğunu gerçekten açıklamıyorsunuz ...
Ricardo

Standart kapsayıcı gösterim kullanarak böyle bir şey olurdu: container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();Bu tür bir programlamanın okunmasını çok daha kolay buluyorum. Bu durumda her işlev, akıl yürütülmesi çok daha kolay olan saf bir işlev olarak yazılabilir. Ana işlev şimdi kodun sadece işlevlerin adıyla ne yaptığını açıklıyor ve daha sonra isterseniz, nasıl yapıldığını öğrenmek için tanımlarına bakabilirsiniz.
David Stone

3
Herkesin, belirli algoritmaların doğal olarak nasıl bir goto kullanması gerektiğine dair örnekler vermesi gerektiği - günümüzde algoritmik düşüncenin ne kadar az devam ettiği konusunda üzücü bir yansımadır !! Elbette, @ Ricardo'nun örneği, goto'nun zarif ve açık olduğu mükemmel örneklerden biridir.
Fattie

6

Kullandığımız goto kuralı, goto'nun bir fonksiyondaki tek bir çıkış temizleme noktasına ileri atlamak için uygun olmasıdır. Gerçekten karmaşık fonksiyonlarda diğer kuralların ileri atlamasına izin vermek için bu kuralı gevşetiyoruz. Her iki durumda da, genellikle hata kodu denetimi ile ortaya çıkan ve okunabilirlik ve bakım işlemlerine yardımcı olan ifadeler derinden iç içe geçmekten kaçınırız.


1
C gibi bir dilde böyle bir şey görebiliyordum. Ancak, C ++ kurucularının / yıkıcılarının gücüne sahip olduğunuzda, bu genellikle çok kullanışlı değil.
David Stone

1
"Gerçekten karmaşık fonksiyonlarda diğer kuralların ileri atlamasına izin vermek için bu kuralı gevşetiyoruz" Örneğin, karmaşık fonksiyonları atlar kullanarak daha da karmaşık hale getiriyorsanız kulağa hoş geliyor. "Daha iyi" yaklaşım, bu karmaşık işlevleri yeniden düzenlemek ve bölmek değil miydi?
MikeMB

1
Gittiğim en son amaç buydu. Java'da goto'yu özlemiyorum, çünkü aynı işi yapabilen nihayet denedi.
Patricia Shanahan

5

"Erdemli git ifadeleri" yerine kullanılabilir ancak kolayca git ifadeleri olarak Donald Knuth makale "olduğu gibi kötüye kullanılabilir git ifadeleri, onların meşru kullanımları ve alternatif yapılara en düşünceli ve kapsamlı tartışma git Tablolar ile Yapısal Programlama " , Aralık 1974 Hesaplama Anketlerinde (cilt 6, no. 4. s. 261 - 301).

Şaşırtıcı olmayan bir şekilde, bu 39 yaşındaki makalenin bazı yönleri tarihli: İşleme gücünde büyüklükteki siparişler, Knuth'un performans iyileştirmelerinden bazılarını orta boy problemler için fark edilmez kılıyor ve o zamandan beri yeni programlama dili yapıları icat edildi. (Örneğin, try-catch blokları Zahn's Construct'ı kullanır, ancak bu şekilde nadiren kullanılırlar.) Ancak Knuth, argümanın tüm taraflarını kapsar ve bir daha konuyu yeniden düzenlemeden önce okunması gerekir.


3

Bir Perl modülünde, arada sırada alt rutinler veya kapaklar oluşturmak istersiniz. Mesele şu ki, alt programı oluşturduktan sonra, ona nasıl ulaşırsınız. Sadece arayabilirsiniz, ancak daha sonra altyordam kullanırsa caller(), olabildiğince yararlı olmayacaktır. goto &subroutineVaryasyon burada yardımcı olabilir.

İşte kısa bir örnek:

sub AUTOLOAD{
  my($self) = @_;
  my $name = $AUTOLOAD;
  $name =~ s/.*:://;

  *{$name} = my($sub) = sub{
    # the body of the closure
  }

  goto $sub;

  # nothing after the goto will ever be executed.
}

Bu gotoformu, kuyruk çağrısı optimizasyonunun temel bir formunu sağlamak için de kullanabilirsiniz .

sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;

  $tally *= $n--;
  @_ = ($n,$tally);
  goto &factorial;
}

( Perl 5 sürüm 16'da daha iyi olarak yazılır goto __SUB__;)

Bir taildeğiştiriciyi içe aktaracak bir modül ve recurbu formunu kullanmak istemiyorsanız içe aktarılacak bir modül vardır goto.

use Sub::Call::Tail;
sub AUTOLOAD {
  ...
  tail &$sub( @_ );
}

use Sub::Call::Recur;
sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;
  recur( $n-1, $tally * $n );
}

Kullanmanın diğer nedenlerinin çoğu gotodiğer anahtar kelimelerle daha iyi yapılır.

Gibi redokod biraz ing:

LABEL: ;
...
goto LABEL if $x;
{
  ...
  redo if $x;
}

Ya da lastbirden fazla yerden biraz koddan gitmek :

goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
  last if $x;
  ...
  last if $y
  ...
}

2

Öyleyse neden?

C'nin çok seviyeli / etiketli bir kırılması yoktur ve tüm kontrol akışları, C'nin yinelemesi ve karar ilkelleriyle kolayca modellenemez. gotolar bu kusurları gidermek için uzun bir yol kat ederler.

Bazen bir tür sahte çok düzeyli kopmayı gerçekleştirmek için bir tür bayrak değişkenini kullanmak daha açıktır, ancak her zaman goto'dan daha üstün değildir (en azından bir goto, bir bayrak değişkeninin aksine, kontrolün nereye gittiğini kolayca belirleyebilir ) ve bazen sadece goto'yu önlemek için bayrakların / diğer bükülmelerin performans fiyatını ödemek istemezsiniz.

libavcodec performansa duyarlı bir kod parçasıdır. Kontrol akışının doğrudan ifadesi muhtemelen bir önceliktir, çünkü daha iyi çalışma eğilimindedir.


2

Tam olarak hiç kimse "COME FROM" ifadesini uygulamamıştır ....


8
Ya da C ++, C #, Java, JS, Python, Ruby .... vs vs vs. Sadece buna "istisnalar" derler.
cHao

2

Do {} while (false) kullanımını tamamen iğrenç buluyorum. Beni bazı garip durumlarda gerekli olduğuna ikna edebilir, ama asla temiz mantıklı bir kod olmadığı düşünülebilir.

Böyle bir döngü yapmanız gerekiyorsa, flag değişkenine bağımlılığı neden açık hale getirmeyesiniz?

for (stepfailed=0 ; ! stepfailed ; /*empty*/)

Değil mı /*empty*/olacak stepfailed = 1? Her durumda, bu bir a'dan daha iyi nasıl do{}while(0)? Her ikisinde de, breakbunun dışında (ya da sizin stepfailed = 1; continue;). Bana gereksiz geliyor.
Thomas Eding

2

1) Bildiğim en yaygın goto kullanımı, C olarak sunmayan dillerde istisna işlemeyi taklit etmektir. bu şekilde kullanılan bir bazilyon goto göreceksiniz; 2013'te yapılan hızlı bir araştırmaya göre Linux kodunda yaklaşık 100.000 goto vardı: http://blog.regehr.org/archives/894 . Kullanıma Linux kodlama stil kılavuzunda da değinilmiştir: https://www.kernel.org/doc/Documentation/CodingStyle . Tıpkı nesne yönelimli programlamanın, işlev işaretçileriyle doldurulmuş yapılar kullanılarak taklit edilmesi gibi, goto'nun da C programlamasındaki yeri vardır. Peki kim haklı: Dijkstra veya Linus (ve tüm Linux çekirdek kodlayıcıları)? Teori ve pratik temelde.

Bununla birlikte, derleyici düzeyinde desteğe sahip olmadığı ve ortak yapılar / kalıplar için kontroller olmadığı için olağan bir durum vardır: bunları yanlış kullanmak ve derleme zamanı kontrolleri olmadan hataları tanıtmak daha kolaydır. Windows ve Visual C ++ ancak C modunda SEH / VEH üzerinden bu nedenle istisna yönetimi sunar: istisnalar OOP dillerinin dışında, yani bir prosedür dilinde bile kullanışlıdır. Ancak derleyici, dilde istisnalar için sözdizimsel destek sunsa bile pastırmanızı her zaman kaydedemez. İkinci durumun bir örneği olarak, sadece bir goto'yu feci sonuçlarla çoğaltan ünlü Apple SSL "goto başarısız" hatasını düşünün ( https://www.imperialviolet.org/2014/02/22/applebug.html ):

if (something())
  goto fail;
  goto fail; // copypasta bug
printf("Never reached\n");
fail:
  // control jumps here

Derleyici destekli özel durumları kullanarak tam olarak aynı hataya sahip olabilirsiniz, örn. C ++:

struct Fail {};

try {
  if (something())
    throw Fail();
    throw Fail(); // copypasta bug
  printf("Never reached\n");
}
catch (Fail&) {
  // control jumps here
}

Ancak, derleyici sizi erişilemez kod hakkında analiz edip uyarırsa, hatanın her iki çeşidinden de kaçınılabilir. Örneğin, Visual C ++ ile / W4 uyarı düzeyinde derleme her iki durumda da hata bulur. Örneğin Java, ulaşılamayan kodu (nerede bulabilirse!) Oldukça iyi bir nedenden dolayı yasaklar: muhtemelen ortalama Joe'nun kodunda bir hata olması muhtemeldir. Goto yapısı, derleyicinin kolayca çözemediği hedeflere izin vermediği sürece, hesaplanmış adreslere giden gotos gibi (**), derleyicinin gotos içeren bir işlev içinde erişilemez kod bulması Dijkstra'yı kullanmaktan daha zor değildir - onaylanmış kod.

(**) Dipnot: Bazı Basic sürümlerinde hesaplanan satır numaralarına gitme mümkündür, örn. GOTO 10 * x burada x bir değişkendir. Daha ziyade kafa karıştırıcı olarak, Fortran'da "hesaplanmış goto", C'deki bir anahtar ifadesine eşdeğer bir yapıya karşılık gelir. Standart C, dilde hesaplanmış gotolara izin vermez, sadece statik / sözdizimsel olarak beyan edilen etiketlere döner. Ancak GNU C, bir etiketin adresini (tekli, önek && operatörü) almak için bir uzantıya sahiptir ve ayrıca void * türünde bir değişkene gitmeye izin verir. Bu belirsiz alt konu hakkında daha fazla bilgi için https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html adresine bakın . Bu yazının geri kalanı bu belirsiz GNU C özelliğiyle ilgili değil.

Standart C (yani hesaplanmamış) goto'lar genellikle erişilemeyen kodun derleme zamanında bulunamamasının nedeni değildir. Genel neden aşağıdaki gibi mantık kodudur. verilmiş

int computation1() {
  return 1;
}

int computation2() {
  return computation1();
}

Bir derleyicinin aşağıdaki 3 yapıdan herhangi birinde erişilemez kod bulması da aynı derecede zordur:

void tough1() {
  if (computation1() != computation2())
    printf("Unreachable\n");
}

void tough2() {
  if (computation1() == computation2())
    goto out;
  printf("Unreachable\n");
out:;
}

struct Out{};

void tough3() {
  try {
    if (computation1() == computation2())
      throw Out();
    printf("Unreachable\n");
  }
  catch (Out&) {
  }
}

(Brace ile ilgili kodlama tarzımı affedin, ancak örnekleri mümkün olduğunca kompakt tutmaya çalıştım.)

Visual C ++ / W4 (/ Ox ile bile) bunlardan hiçbirinde ulaşılamayan kod bulamıyor ve muhtemelen bildiğiniz gibi erişilemeyen kod bulma sorunu genel olarak kararsız. (Bana bu konuda inanmıyorsanız: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )

İlgili bir konu olarak, C goto yalnızca bir işlev gövdesi içinde istisnaları taklit etmek için kullanılabilir. Standart C kütüphanesi, yerel olmayan çıkışları / istisnaları taklit etmek için bir setjmp () ve longjmp () çifti işlevi sunar, ancak bunların diğer dillerin sunduklarına kıyasla ciddi dezavantajları vardır. Wikipedia makalesi http://tr.wikipedia.org/wiki/Setjmp.h bu son sorunu oldukça iyi açıklamaktadır. Bu işlev çifti Windows'ta da çalışır ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ), ancak SEH / VEH daha üstün olduğu için bunları hemen hemen hiç kimse kullanmaz. Unix'te bile setjmp ve longjmp çok nadiren kullanılır.

2) C'de goto'nun ikinci en yaygın kullanımının, çok tartışmalı bir kullanım örneği olan çok seviyeli mola veya çok seviyeli devam uygulaması olduğunu düşünüyorum. Java'nın goto etiketine izin vermediğini, ancak mola etiketine veya devam etiketine izin verdiğini hatırlayın. Http://www.oracle.com/technetwork/java/simple-142616.html'ye göre , bu aslında C'deki en yaygın kullanım örneğidir (söyledikleri% 90), ancak öznel deneyimime göre, sistem kodu eğilimi daha sık hata işleme için gotos kullanmak. Belki bilimsel kodda veya işletim sisteminin istisna işleme (Windows) sunduğu yerlerde, çok seviyeli çıkışlar baskın kullanım durumudur. Anketlerinin bağlamıyla ilgili herhangi bir ayrıntı vermiyorlar.

Eklemek için düzenlendi: Bu iki kullanım modelinin, Kernighan ve Ritchie'nin C kitabında, sayfa 60'da (sürüme bağlı olarak) bulunduğu ortaya çıktı. Dikkat edilmesi gereken bir başka nokta, her iki kullanım vakasının da sadece ileri gotoları içermesidir. Ve MISRA C 2012 baskısının (2004 baskısının aksine) artık sadece ileriye dönük oldukları sürece goto'lara izin verdiği ortaya çıkıyor.


Sağ. "Odadaki fil", dünya için kritik bir kod tabanına örnek olarak iyilik uğruna Linux çekirdeğinin goto ile yüklü olmasıdır. Tabiki öyle. Açıkçası. "Anti-goto-meme" onlarca yıl önceki bir meraktır. DERSİN PROGRAMLAMASINDA, profesyonel olmayanlar tarafından kötüye kullanılabilecek programlamada (özellikle "statik", aslında "globaller" ve örneğin "elseif") vardır. Yani, eğer çocuk kuzeniniz 2 programı öğreniyorsa, onlara "oh globalleri kullanma" ve "asla elseif kullanma" dersiniz.
Fattie

Goto başarısız böceğinin goto ile ilgisi yoktur. Sorun daha sonra diş telleri olmadan if ifadesinden kaynaklanır. Hemen hemen iki kez yapıştırılan herhangi bir ifade kopyası bir soruna neden olurdu. Goto'dan çok daha zararlı olması durumunda, bu tip çıplak parantezlerin olmadığını düşünüyorum.
muusbolla

2

Bazıları C ++ 'da goto için bir neden olmadığını söylüyor. Bazıları% 99 vakada daha iyi alternatifler olduğunu söylüyor. Bu muhakeme değil, sadece mantıksız izlenimlerdir. İşte goto'nun gelişmiş bir do-while döngüsü gibi güzel bir koda yol açtığı sağlam bir örnek:

int i;

PROMPT_INSERT_NUMBER:
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    goto PROMPT_INSERT_NUMBER;          
  }

std::cout << "your number is " << i;

Bunu goto içermeyen kodla karşılaştırın:

int i;

bool loop;
do {
  loop = false;
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    loop = true;          
  }
} while(loop);

std::cout << "your number is " << i;

Bu farklılıkları görüyorum:

  • iç içe {}blok gerekli ( do {...} whiledaha tanıdık görünse de )
  • loopdört yerde kullanılan ekstra değişken gereklidir
  • ile çalışmayı okumak ve anlamak daha uzun zaman alır loop
  • loopherhangi bir veri tutmaz, sadece basit bir etiket daha az anlaşılabilir olan yürütme akışını kontrol eder

Başka bir örnek var

void sort(int* array, int length) {
SORT:
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    goto SORT; // it is very easy to understand this code, right?
  }
}

Şimdi "kötülük" goto'sundan kurtulalım:

void sort(int* array, int length) {
  bool seemslegit;
  do {
    seemslegit = true;
    for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
      swap(data[i], data[i+1]);
      seemslegit = false;
    }
  } while(!seemslegit);
}

Gördüğünüz gibi, goto kullanmanın aynı türü, iyi yapılandırılmış bir modeldir ve önerilen tek yol olarak pek çok teşvik etmek için ileri gitmez. Şüphesiz böyle "akıllı" kodlardan kaçınmak istersiniz:

void sort(int* array, int length) {
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    i = -1; // it works, but WTF on the first glance
  }
}

Mesele şu ki, goto kolayca kötüye kullanılabilir, ancak goto'nun kendisi suçlanmak değildir. Etiketin C ++ 'da fonksiyon kapsamı olduğuna dikkat edin, bu nedenle, üst üste binen döngülerin yerini aldığı ve çok yaygın olduğu - 7 segmentli ekranın P1'e bağlı olduğu aşağıdaki kodda olduğu gibi, saf montajda olduğu gibi küresel kapsamı kirletmez . Program yıldırım segmentini döngüler:

; P1 states loops
; 11111110 <-
; 11111101  |
; 11111011  |
; 11110111  |
; 11101111  |
; 11011111  |
; |_________|

init_roll_state:
    MOV P1,#11111110b
    ACALL delay
next_roll_state:
    MOV A,P1
    RL A
    MOV P1,A
    ACALL delay
    JNB P1.5, init_roll_state
    SJMP next_roll_state

Başka bir avantaj daha vardır: goto adlandırılmış döngüler, koşullar ve diğer akışlar olarak hizmet edebilir:

if(valid) {
  do { // while(loop)

// more than one page of code here
// so it is better to comment the meaning
// of the corresponding curly bracket

  } while(loop);
} // if(valid)

Veya girintili eşdeğer goto'yu kullanabilirsiniz, bu nedenle etiket adını akıllıca seçerseniz yoruma ihtiyacınız yoktur:

if(!valid) goto NOTVALID;
  LOOPBACK:

// more than one page of code here

  if(loop) goto LOOPBACK;
NOTVALID:;

1

Perl'de, bir döngüden "git" için etiket kullanılması - kesmeye benzer bir "son" deyimi kullanarak.

Bu, iç içe döngüler üzerinde daha iyi kontrol sağlar.

Geleneksel goto etiketi de destekleniyor, ancak istediğinizi elde etmenin tek yolunun bu kadar çok örneği olduğundan emin değilim - alt rutinler ve döngüler çoğu durumda yeterli olmalıdır.


Perl'de kullanacağınız tek goto formu olduğunu düşünüyorum goto &subroutine. Yığındaki geçerli alt yordamı değiştirirken, alt yordamı geçerli _ ile başlatır.
Brad Gilbert

1

'Git' ile ilgili sorun ve 'gitmesiz programlama' hareketinin en önemli argümanı, kodunuzu çok sık kullanırsanız, doğru davranmasına rağmen okunamaz, sürdürülemez, gözden geçirilemez vb. vakalar 'git' spagetti koduna yol açar. Şahsen, neden 'git' kullanacağım konusunda iyi bir neden düşünemiyorum.


11
Argümanınızda "Hiçbir sebep düşünemiyorum" demek resmen en.wikipedia.org/wiki/Argument_from_ignorance olmakla birlikte, "hayal gücünden yoksun kanıt" terimini tercih rağmen.
SADECE benim doğru görüşüm

2
@SADECE benim doğru görüşüm: Bu sadece post-hoc kullanılıyorsa mantıklı bir yanlıştır. Bir dil tasarımcısının bakış açısından, bir özelliğin ( goto) eklenmesinin maliyetini tartmak geçerli bir argüman olabilir . @ cschol'un kullanımı benzer: Belki şu anda bir dil tasarlamasa da, temel olarak tasarımcının çabasını değerlendiriyor.
Konrad Rudolph

1
@KonradRudolph: IMHO, gotodeğişkenleri meydana getireceği bağlamlar dışında bir dil iznine sahip olmak, birinin ihtiyaç duyabileceği her türlü kontrol yapısını desteklemeye çalışmaktan daha ucuz olmaya yatkındır . Kod yazma goto, başka bir yapıyı kullanmak kadar güzel olmayabilir, ancak böyle bir kodu yazabilmek goto, "ifade delikleri" - dilin etkin kod yazamayacağı yapılar - oluşmasını önlemeye yardımcı olacaktır.
supercat

1
@supercat Korkarım temelde farklı dil tasarım okullarından geliyoruz. Dillerin anlaşılabilirlik (veya doğruluk) fiyatından en iyi şekilde ifade edilmesine itiraz ediyorum. İzin veren bir dilden daha kısıtlayıcı olmayı tercih ederim.
Konrad Rudolph

1
@thb Evet tabii ki yaparım. Genellikle kodun okunmasını ve gerekçelendirilmesini çok daha zorlaştırır. Birisi gotokod inceleme sitesinde bulunan kodu her yayınladığında, kodun gotomantığını büyük ölçüde ortadan kaldırır .
Konrad Rudolph

1

GOTO elbette kullanılabilir, ancak kod stilinden daha önemli bir şey vardır veya kodu okuduğunuzda aklınızda bulundurmanız gereken okunabilir veya okunaklı değilse: içindeki kod sizin kadar sağlam olmayabilir düşün .

Örneğin, aşağıdaki iki kod parçacığına bakın:

If A <> 0 Then A = 0 EndIf
Write("Value of A:" + A)

GOTO ile eşdeğer bir kod

If A == 0 Then GOTO FINAL EndIf
   A = 0
FINAL:
Write("Value of A:" + A)

Düşündüğümüz ilk şey, her iki kod parçasının sonucunun "A: 0 Değeri" olacağıdır (elbette paralelliksiz bir yürütme olduğunu varsayalım)

Bu doğru değil: ilk örnekte A her zaman 0 olur, ancak ikinci örnekte (GOTO ifadesiyle) A 0 olmayabilir. Neden?

Bunun nedeni, programın başka bir noktasından GOTO FINAL A değerini kontrol etmeden .

Bu örnek çok açıktır, ancak programlar daha karmaşık hale geldikçe, bu tür şeyleri görmenin zorluğu artar.

İlgili materyal, Bay Dijkstra "GO TO ifadesine karşı bir dava" adlı ünlü makalede bulunabilir


6
Eski okul BASIC'inde durum böyleydi. Bununla birlikte, modern varyantlarda, başka bir işlevin ortasına atlamanıza izin verilmez ... veya birçok durumda, bir değişkenin beyanını geçse bile. Temel olarak (cinas amaçlanmamış), modern diller büyük ölçüde Dijkstra'nın bahsettiği "dizginsiz" GOTO'larla ortadan kalktı ... ve tartıştığı gibi kullanmanın tek yolu diğer bazı iğrenç günahları işlemektir. :)
cHao

1

Aşağıdaki durumda goto kullanıyorum: farklı yerlerde işlevlerden dönmek gerektiğinde ve geri dönmeden önce bazı başlatma işlemlerinin yapılması gerekir:

goto olmayan sürüm:

int doSomething (struct my_complicated_stuff *ctx)    
{
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
        db_disconnect(conn);
        return -1;      
        }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        free(temp_data);    
        db_disconnect(conn);    
        return -2;
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        pthread_mutex_unlock(ctx->mutex);
        free(temp_data);
        db_disconnect(conn);        
        return -3;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -4;  
    }

    if (ctx->something_else->additional_check) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -5;  
    }


    pthread_mutex_unlock(ctx->mutex);
    free(temp_data);    
    db_disconnect(conn);    
    return 0;     
}

versiyona git:

int doSomething_goto (struct my_complicated_stuff *ctx)
{
    int ret=0;
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
            ret=-1;
           goto exit_db;   
          }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        ret=-2;
        goto exit_freetmp;      
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        ret=-3;
        goto exit;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
        ret=-4;
        goto exit_freekey; 
    }

    if (ctx->something_else->additional_check) {
        ret=-5;
        goto exit_freekey;  
    }

exit_freekey:
    rsa_free(key);
exit:    
    pthread_mutex_unlock(ctx->mutex);
exit_freetmp:
    free(temp_data);        
exit_db:
    db_disconnect(conn);    
    return ret;     
}

İkinci sürüm, deallocation ifadelerinde (her biri kodda bir kez kullanılır) bir şeyi değiştirmeniz gerektiğinde kolaylaştırır ve yeni bir şube eklerken bunların herhangi birini atlama şansını azaltır. Bunları bir işlevde taşımak burada yardımcı olmayacaktır, çünkü dağıtma farklı "düzeylerde" yapılabilir.


3
Bu yüzden finallyC # ' da bloklarımız var
John Saunders

^ @JohnSaunders gibi dedi. Bu, "goto kullanımı çünkü bir dilin uygun kontrol yapısından yoksun" örneğidir. ANCAK, dönüşün yakınında ÇOK puan kazanmasını gerektiren bir kod kokusudur. Daha güvenli (vidalanmaması daha kolay) VE "nihayet" bulunmayan dillerde bile iyi çalışan gotos gerektirmeyen bir programlama tarzı vardır : bu "temizleme" çağrılarını, temiz olmadığında zararsız olacak şekilde tasarlayın up gereklidir. Temizleme dışında her şeyi, çok dönüşlü tasarımı kullanan bir yönteme dönüştürün. Bu yöntemi çağırın, ardından temizleme çağrılarını yapın.
ToolmakerSteve

Açıkladığım yaklaşımın fazladan bir yöntem çağrısı gerektirdiğini unutmayın (ancak sadece eksik bir dilde finally). Alternatif olarak, gotos'yi kullanın , ancak her zaman tüm temizliği yapan ortak bir çıkış noktasına kullanın . Ancak her temizleme yöntemi boş veya zaten temiz olan bir değeri işleyebilir veya koşullu bir testle korunur, bu nedenle uygun olmadığında atlanır.
ToolmakerSteve

@ToolmakerSteve bu bir kod kokusu değil; aslında, C'de son derece yaygın bir örüntüdür ve goto kullanmanın en geçerli yollarından biri olarak kabul edilir. Her biri kendi gereksiz if-testine sahip 5 yöntem oluşturmamı mı istiyorsun, sadece bu fonksiyondan temizlemeyi mi yapmalısın? Artık kod VE performans şişmesi oluşturdunuz. Ya da sadece goto kullanabilirsiniz.
muusbolla

@muusbolla - 1) "5 yöntem oluştur" - Hayır . Boş olmayan bir değere sahip kaynakları temizleyen tek bir yöntem öneririm . VEYA alternatifi bakın goto, hepsi aynı mantığa sahip aynı çıkış noktasına giden s'yi kullanır (ki kaynak başına ekstra bir gerektirir). Ama Cboş verin, sizi kullanırken haklısınız - kodun C nedeni ne olursa olsun, neredeyse kesinlikle en "doğrudan" kodu tercih eden bir değiş tokuş. (Benim önerim, herhangi bir kaynağın tahsis edilmiş veya tahsis edilmiş olabileceği karmaşık durumları ele alır. Ancak evet, bu durumda aşırıya
kaçın

0

Sahada büyük katkıları olan bir bilgisayar bilimcisi olan Edsger Dijkstra da GoTo kullanımını eleştirdiği için ünlüdür. Wikipedia hakkındaki argümanı hakkında kısa bir makale var .


0

Zaman zaman karakter odaklı dize işleme için kullanışlıdır.

Bu printf-esque örneğine benzer bir şey düşünün:

for cur_char, next_char in sliding_window(input_string) {
    if cur_char == '%' {
        if next_char == '%' {
            cur_char_index += 1
            goto handle_literal
        }
        # Some additional logic
        if chars_should_be_handled_literally() {
            goto handle_literal
        }
        # Handle the format
    }
    # some other control characters
    else {
      handle_literal:
        # Complicated logic here
        # Maybe it's writing to an array for some OpenGL calls later or something,
        # all while modifying a bunch of local variables declared outside the loop
    }
}

Bunu goto handle_literalbir işlev çağrısına yeniden düzenleyebilirsiniz, ancak birkaç farklı yerel değişkeni değiştiriyorsa, diliniz değiştirilebilir kapanışları desteklemiyorsa, her birine referanslar iletmeniz gerekir. Eğer continuemantığınız başka bir vakayı çalışmazsa aynı anlamsallığı elde etmek için çağrıdan sonra hala bir ifade (muhtemelen bir tür goto) kullanmak zorunda kalacaksınız.

Goto'ları, benzer durumlarda, sözlü olarak makul bir şekilde kullandım. Çoğu zaman onlara ihtiyacınız yoktur, ancak bu garip durumlar için sahip olmaları hoştur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.