Yanıtlar:
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.
goto
ile return
sadece saçma. Hiçbir şeyi "yeniden düzenleme" değil, sadece "yeniden adlandırmak " goto
tı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 goto
kendi başına, sadece bir araç olduğunu , biri sadece bir önlemek için ilgisi olmayan bir yere döngü taşındı görmek goto
.
break
, continue
, return
temelde goto
, sadece güzel ambalajında.
do{....}while(0)
Java'da çalıştığı gerçeği dışında, nasıl goto'dan daha iyi bir fikir olduğunu sanmıyorum.
goto
Edsger 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 goto
ifadeleri 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 goto
C 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, " goto
C 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()
{
_-_-_-_
_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_
_-_-_-_
}
goto
Gö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*/
goto
Orada 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. for
Oradaki 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, #define
karşı 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.
goto
ifadeleri bir programlama ortamı için yazılmış goto
bir edildi sürü
daha potansiyel bir montajcı değildir en modern dilde olandan zarar.goto
, "Bir kez eğlenmeye çalıştım ama bundan hoşlanmadım, şimdi buna karşıyım" demek kadar mantıklı.goto
ifadelerin diğer yapılarla yeterince değiştirilemeyen meşru kullanımları vardır .godo
" her zaman yanlış bir do
döngünün break
a 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
.goto
aslında avantajlarının ne olduğunu (unutulmuş olan soru) göstermeyi unutmayı unutma
En iyi uygulamalara körü körüne uymak en iyi uygulama değildir. goto
Birinin 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 goto
uğruna kaçınmak goto
anlamsı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.
Yana goto
program hakkında akıl yürütme markaları akış sert 1 (aka “spagetti kod”.), goto
Genellikle sadece eksik özellikleri dengelemek için kullanılır: kullanımı goto
aslı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ı goto
iç 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 goto
bazı kısıtlamaları goto
yerine 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.
finally
? Hata işleme dışındaki şeyler için istisnalar kullanmak iyi ama kullanmak goto
kötü mü? Bence istisnalar oldukça uygun bir şekilde adlandırılmıştır.
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
do{}while(false)
Bence deyimsel olarak düşünülebilir.
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.
#define
kullanmaktan çok, çok, çok daha kötü olduğunu çok güzel gösterdiniz goto
: D
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.
goto case 5:
1. durumda olduğunuzu söyleyebilirsiniz ). Konrad Rudolph'un cevabı burada doğru gibi görünüyor: goto
eksik 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 continue
açıkça talep etmek gibi bir şey olurdu.
#ifdef TONGUE_IN_CHEEK
Perl, goto
fakir 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, goto
temizlik 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 longjmp
istisnalar call/cc
ve 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 .)
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.
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, goto
her 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.
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) goto
s 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 goto
araya gelmeden önce de deneyebilirim .
goto
birinin 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 .
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.
goto
Her 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ı goto
bir bonus.
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.
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.
"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.
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 &subroutine
Varyasyon burada yardımcı olabilir.
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 goto
formu, 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 tail
değiştiriciyi içe aktaracak bir modül ve recur
bu 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 );
}
goto
diğer anahtar kelimelerle daha iyi yapılır.Gibi redo
kod biraz ing:
LABEL: ;
...
goto LABEL if $x;
{
...
redo if $x;
}
Ya da last
birden fazla yerden biraz koddan gitmek :
goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
last if $x;
...
last if $y
...
}
Ö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.
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*/)
/*empty*/
olacak stepfailed = 1
? Her durumda, bu bir a'dan daha iyi nasıl do{}while(0)
? Her ikisinde de, break
bunun dışında (ya da sizin stepfailed = 1; continue;
). Bana gereksiz geliyor.
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.
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:
{}
blok gerekli ( do {...} while
daha tanıdık görünse de )loop
dört yerde kullanılan ekstra değişken gereklidirloop
loop
herhangi bir veri tutmaz, sadece basit bir etiket daha az anlaşılabilir olan yürütme akışını kontrol ederBaş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:;
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.
goto &subroutine
. Yığındaki geçerli alt yordamı değiştirirken, alt yordamı geçerli _ ile başlatır.
'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.
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.
goto
değ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.
goto
kod inceleme sitesinde bulunan kodu her yayınladığında, kodun goto
mantığını büyük ölçüde ortadan kaldırır .
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
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.
finally
C # ' da bloklarımız var
finally
). Alternatif olarak, goto
s'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.
goto
, hepsi aynı mantığa sahip aynı çıkış noktasına giden s'yi kullanır (ki kaynak başına ekstra bir gerektirir). Ama C
boş 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
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 .
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_literal
bir 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 continue
mantığı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.