'yap ... süre' vs 'süre'


86

Olası Yinelemeler:
While ve Do While
döngüleri yerine do-while kullanmalıyım?

Bir süredir programlama yapıyorum (2 yıl çalışma + 4.5 yıl derece + 1 yıl üniversite öncesi) ve Programlamaya Giriş kursunda asla zorlanmadan kısa bir süre kullanmadım. Hiç bu kadar temel bir şeyle karşılaşmazsam, programlamayı yanlış yaptığıma dair büyüyen bir his var.

Doğru koşullarla karşılaşmamış olabilir miyim?

Bir süre yerine do-while kullanmanın gerekli olduğu bazı örnekler nelerdir?

(Okul eğitimimin neredeyse tamamı C / C ++ içindeydi ve işim C # dilinde, yani do-whiles farklı çalıştığı için kesinlikle mantıklı olduğu başka bir dil varsa, bu sorular gerçekten geçerli değildir.)

Açıklığa kavuşturmak için ... a whileve a arasındaki farkı biliyorum do-while. Çıkış koşulunu kontrol eder ve ardından görevleri gerçekleştirir. do-whilegörevleri gerçekleştirir ve ardından çıkış koşulunu kontrol eder.


43
Ne olursa olsun, kariyerim olarak on yıllık programlamadan sonra, bir veya iki kez gibi bir do-while döngüsü kullandım.
Matt Greer

@Matt - Bu beni daha iyi hissettiriyor. En azından yalnız olmadığım anlamına geliyor.
mphair

2
Matt'e ikinci bir on yıl daha ekleyeceğim. Do-while döngüsünü de nadiren kullanırım.
quentin-starin

BİRÇOK kopya. İşte bunlardan
birkaçı ile

3
Doğru "ana soru" gibi görünüyor stackoverflow.com/questions/224059/…
Donal Fellows

Yanıtlar:


116

Döngünün her zaman en az bir kez yürütülmesini istiyorsanız. Yaygın değil ama ara sıra kullanıyorum. Kullanmak isteyebileceğiniz bir durum, yeniden deneme gerektirebilecek bir kaynağa erişmeye çalışmaktır, örn.

do
{
   try to access resource...
   put up message box with retry option

} while (user says retry);

3
Sanırım bu, "Ben o koşulla karşılaşmadım" başlığı altında olacaktır.
mphair

7
dürüst olmak gerekirse, sadece bir do kullanmam gereken bir durumla çok nadiren karşılaşıyorum .. süre ..
Stan R.

1
Belki biraz kısa, ama Bob haklı. Kod bloğunu en az bir kez çalıştırmak istediğinizde Do / while kullanılmalıdır . Tek seferlik çalıştırmak isteyip istemediğinizi bilmediğinizde while döngüsü kullanmalısınız. Bununla birlikte, yaklaşık 15 yıllık geliştirme deneyimimle, while döngülerini do / while döngülerinden çok daha fazla kullandığımı söyleyebilirim.
ConsultUtah

1
Nadiren de olsa zaman zaman kullandım. Bir süre sonra, genellikle normal bir while döngüsüne de yeniden düzenlenirler - çünkü etrafındaki alanlar değişti ve ilkinden daha sakar iken ...
Joshua

2
Oldukça yaygındır. Ama neden örneğinizde while döngüsü kullanmadığınızı anlamıyorum. Ve bu cevaba neden olumlu oy verildiğini anlamıyorum.

65

derleyici optimizasyon konusunda yetkin değilse do-while daha iyidir. do-while, koşullu bir sıçrama ve koşulsuz bir sıçrama olan for ve while'ın aksine, yalnızca tek bir koşullu sıçrama içerir. Ardışık düzenlenmiş ve dallanma tahmini yapmayan CPU'lar için bu, sıkı bir döngünün performansında büyük bir fark yaratabilir.

Ayrıca, çoğu derleyici bu optimizasyonu gerçekleştirecek kadar akıllı olduğundan, derlenmiş kodda bulunan tüm döngüler genellikle yap-süre olacaktır (eğer derleyici geri yerel gotoslardan döngüleri yeniden yapılandırmayı bile zahmete katarsa).


5
Döngü formları arasındaki seçime ilgili teknik bilgileri eklemek için +1.
quentin-starin

2
Sadece erken değil, işe yaramaz. Gerçekten bir while döngüsü istiyorsanız, yani 0 kez yineleme yeteneği istiyorsanız, döngüyü bir
if'in

3
@ Jeremy: Bahsettiğin sıçrama döngünün dışında, N kere yapılmadı.
Ben Voigt

1
Duff'ın cihazında do-while'ın klasik kullanımını da göz önünde bulundurun (burada bir while döngüsü, daha büyük bir adım ve geri kalanı işlemek için bir atlama tablosu ile birlikte açılmış bir do-while'a dönüştürülür) bu gibi uygulamalarda döngü ek yükünü önemli ölçüde azaltır. memset memcmp, bu 10. kat üretilen iş artabilir memcpy
Ben Voigt

@BenVoigt Yorumunuzun şimdiye kadar on yıllık olduğunu biliyorum, ancak Duff'un Cihazından çok önemli bir uyarı olmadan bahsedilmemelidir - modern derleyicilerde daha az verimli hale geldi , çünkü modern derleyiciler genellikle hedef makine için en uygun olan basit döngüleri akıllıca açabilirler. , ancak bir Duff's Cihazının amacını anlamak için genellikle bir mantığa sahip değiller ve bu nedenle onu neredeyse aynı şekilde optimize edemiyorlar. Bu, soyut bir "yeterince gelişmiş optimize edici" yerine bazı hedef makineler için kodu optimize etme hakkında harika bir ders.
mtraceur

12

Bunu bir TryDeleteDirectory işlevinde kullandım. Bunun gibi bir şeydi

do
{
    try
    {
        DisableReadOnly(directory);
        directory.Delete(true);
    }
    catch (Exception)
    {
        retryDeleteDirectoryCount++;
    }
} while (Directory.Exists(fullPath) && retryDeleteDirectoryCount < 4);

Güzel. Bu, bir do ... while'a sarılmanın gerçekten mantıklı olan ilk örneği.
Joshua

4
Bu neden / nasıl bir standarttan daha iyidir while?
Josh Stodola

Döngü her zaman bir kez yürütülür, bu nedenle bir şeyler yapmak isteyebilir ve başarısız olursa tekrar deneyebilirsiniz. Normal bir while döngüsü kullanırsanız, daha ilk yinelemede yapmadan önce başarısız olup olmadığını kontrol edeceksiniz.
NibblyPig

1
@SLC Ama yine de dizinin önceden var olup olmadığını kontrol etmek istemez misiniz?
Josh Stodola

1
@Josh Bu örnekte evet. Ancak meşgul bir sunucuya bağlantı açmaya çalışıyorsanız, önce bağlanırsınız ve yanıtın 'meşgul' olup olmadığını görürsünüz. Öyle olsaydı, pes etmeden önce birkaç kez tekrar denersin.
NibblyPig

11

Do while, bir şeyi en az bir kez yürütmek istediğinizde kullanışlıdır. Do while vs while kullanımına iyi bir örnek olarak, şunu yapmak istediğinizi varsayalım: Bir hesap makinesi.

Buna bir döngü kullanarak ve her hesaplamadan sonra kişinin programdan çıkmak isteyip istemediğini kontrol ederek yaklaşabilirsiniz. Şimdi, muhtemelen program açıldıktan sonra kişinin bunu en az bir kez yapmak istediğini varsayabilirsiniz, böylece aşağıdakileri yapabilirsiniz:

do
{
    //do calculator logic here
    //prompt user for continue here
} while(cont==true);//cont is short for continue

2
Aslında, bundan bahsettiğinize göre, herhangi bir konsol tabanlı menü tabanlı arayüz bir do ... while döngüsünde de iyi iş çıkarır. Örneğin, seçenekleri sorma (her seferinde bir tuş) ve yalnızca devam et veya çıkmayı seçtiklerinde çıkma.
Joshua

7
continuebir anahtar kelimedir; D
Thomas Eding

Atrinithis: Lol ... Kaydım. Beni orada düzelttiğin için teşekkürler.

== trueGerek yok. cont == truedevam doğruysa, doğru döndürür. Bu tamamen işe yaramaz.
Mr.DeleteMyMessages

11

Bu bir tür dolaylı cevap, ancak bu soru beni bunun arkasındaki mantığı düşündürdü ve bunun paylaşmaya değer olabileceğini düşündüm.

Herkesin söylediği gibi, do ... whilevücudu en az bir kez yürütmek istediğinizde bir döngü kullanırsınız. Ama hangi koşullarda bunu yapmak istersiniz?

Pekala, aklıma gelen en bariz durum sınıfı , kontrol koşulunun ilk ("primlenmemiş") değerinin, çıkmak istediğiniz zamanki ile aynı olması olabilir . Bu, koşulu çıkmayan bir değere hazırlamak için döngü gövdesini bir kez yürütmeniz ve ardından bu koşula göre gerçek tekrarı gerçekleştirmeniz gerektiği anlamına gelir. Programcılar bu kadar tembelken, birisi bunu bir kontrol yapısında toplamaya karar verdi.

Örneğin, bir zaman aşımına sahip bir seri bağlantı noktasından karakter okumak şu biçimi alabilir (Python'da):

response_buffer = []
char_read = port.read(1)

while char_read:
    response_buffer.append(char_read)
    char_read = port.read(1)

# When there's nothing to read after 1s, there is no more data

response = ''.join(response_buffer)

Kod tekrarını Not: char_read = port.read(1) . Python'da bir do ... whiledöngü olsaydı, şunu kullanmış olabilirdim:

do:
    char_read = port.read(1)
    response_buffer.append(char_read)
while char_read

Döngüler için yeni bir kapsam oluşturan diller için eklenen avantaj: char_readişlev ad alanını kirletmez. Ancak bunu yapmanın daha iyi bir yolu olduğunu ve bunun da Python'un Nonedeğerini kullanmak olduğunu unutmayın :

response_buffer = []
char_read = None

while char_read != '':
    char_read = port.read(1)
    response_buffer.append(char_read)

response = ''.join(response_buffer)

İşte benim açımdan en önemli nokta şudur : sıfırlanabilir türlere sahip dillerde, durum initial_value == exit_valueçok daha seyrek ortaya çıkar ve belki de bu yüzden onunla karşılaşmazsınız. Bunun asla olmadığını söylemiyorum, çünkü bir fonksiyonun geçerli bir koşulu belirtmek için döneceği zamanlar hala vardır . Ama aceleyle ve kısaca düşündüğüm görüşüme göre, kullandığınız diller şu anlama gelen bir değere izin vermediyse, bu çok daha fazla olur: bu değişken henüz başlatılmadı.None

Bu mükemmel bir akıl yürütme değildir: gerçekte, artık boş değerler yaygın olduğundan, bir değişkenin alabileceği geçerli değerler kümesinin bir unsurunu daha oluştururlar. Ancak pratik olarak, programcılar, duyarlı durumda olan bir değişkeni, döngü çıkış durumunu içerebilen ve başlatılmamış durumda olan bir değişkeni ayırt etmek için bir yola sahiptir.


Bu, burada okuduğum en ilginç yanıtlardan biri. Güzel katkı.
Bob Moore

İki versiyon eşdeğer değildir. İlk okuma geri dönerse None, whilesürüm doğru şekilde yanıt arabelleğine hiçbir şey eklemez, ancak dosürümünüz her zaman bir şeyler ekler.
David Stone

@DavidStone read()asla geri dönmemeli None. Kullanıldığı yerde bir kitaplık kullanıyorsanız, öncül geçerli değildir (yani, sentinel değerin olası kontrol değerleri kümesinde olmaması).
detly

Son düzenlemenizle, ''bunun yerine söylemem gereken şeyin olduğunu anladım None. Yanlış olarak değerlendirilen bir tür değer. Python'un readişlevine aşina olmamamdan bahsedin .
David Stone

@DavidStone Anlamıyorum. Eğer read()döner '', hiçbir şey dizesine eklenecek, boş.
detly

7

Okuldayken onları biraz kullandım, ama o zamandan beri pek kullanmadım.

Teorik olarak, döngü gövdesinin çıkış koşulu kontrolünden önce bir kez çalışmasını istediğinizde kullanışlıdırlar. Sorun şu ki, ilk önce kontrolü istemediğim birkaç durum için, tipik olarak çıkış kontrolünü en sondan ziyade döngü gövdesinin ortasında istiyorum . Bu durumda, iyi bilinenleri vücudun herhangi for (;;)bir if (condition) exit;yerinde kullanmayı tercih ederim .

Aslında, döngüden çıkış koşulunda biraz titriyorsam, bazen döngüyü for (;;) {}gerektiğinde bir çıkış ifadesiyle yazmaya başlamayı yararlı buluyorum ve sonra bitirdiğimde bunun olup olmadığını görebilirim " başlatmaları, çıkış koşullarını taşıyarak ve / veya forparantez içindeki kodu artırarak temizlendi " .


5
Meraktan ... neden "while (true)" yerine "for (;;)"?
mphair

4
Muhtemelen tadına bak. for(;;)"Sonsuz döngü" ile eşitlenecek şekilde büyüdüm , oysa while(true)durup düşünmem gerekiyor. Belki de bunun nedeni, a olmadan önce C'yi kodluyordum true. O günlerde sadece TRUEmakro olarak sahiptik ve eğer bir şey hatalıysa kendimi makronun bir 0şekilde yeniden tanımlanmadığına ikna etmem gerekir (oldu!). Bunun for(;;)şansı yok. While formunda ısrar edersen, neredeyse görmeyi tercih ederim while (1). Tabii sonra bazı kudreti Şaşılacak ... l harfi değilse
TED

1
@mphair: Evet, italik yaptığınızda veya codeifymetin yazdığınızda editörün koyduğu metni not alırsanız , bunu doğrudan yorum alanına kendiniz yazabilirsiniz. Bazı kişilerin hiperlinkler koyduğunu gördüm, ama bu benim için biraz fazla zor.
TED

2
@TED: Açıkça-çıkılana kadar döngü için tercih ettiğim gösterim "do {} while (1);". Bu arada, gömülü sistemler çalışırken normal döngü paradigmam "i = 10; do {} while (- i);" şeklindedir. Sıkı döngüler için bu, () için tipik olandan çok daha hızlıdır; Performans açısından kritik olmayan durumlarda () için keyfi olarak kullanmaktansa, bir yukarı sayım gerekmediğinde döngü modeli olarak tutarlı bir şekilde bunu kullanmanın daha okunaklı olduğunu düşünüyorum.
supercat

2
While (1) ve (;;) disüzyonuna ek olarak, for (EVER) 'i ​​EVER olarak tanımlı ;;
asr

6

Bir kod parçasını her zaman bir kez ve sonucuna bağlı olarak muhtemelen daha fazla çalıştırmanız gereken bir durum. whileAynısı normal bir döngü ile de üretilebilir .

rc = get_something();
while (rc == wrong_stuff)
{
    rc = get_something();
}

do
{
    rc = get_something();
}
while (rc == wrong_stuff);

6

do whilekod bloğunu en az bir kez çalıştırmak istiyorsanız. whileÖte yandan, belirtilen kriterlere bağlı olarak her zaman çalışmayacaktır.


5

Bu kadar basit:

ön koşul - son koşul

  • while (koşul) {...} - ön koşul, kodu yalnızca kontrol ettikten sonra çalıştırır.
  • do {...} while (koşul) - son koşul, kod en az bir kez çalıştırılır.

Artık sırrı bildiğinize göre .. bunları akıllıca kullanın :)


1
Dediğim gibi, nasıl çalıştıklarını biliyorum, sadece birinin diğerine nazaran mantıklı olduğundan emin değildim.
mphair

Döngüden en az bir kez geçmek zorunda olduğunuzdan emin olduğunuzda do {} 'u kullanmanız mantıklıdır. Örneğin, bir dizide bir eleman bulan bir fonksiyonunuz varsa ve daha önce bir if dizisi koyduysanız.IsEmpty () sonra geri dönün; . Bu kontrolü geçtiyse, güvenle kullanabilirsiniz {} while. do-while'ı tekrarlayan bir döngü olarak düşünmek de kolaydır: "Tatmin olana kadar yemek yerim", {eat ();} while (! memnun ();) anlamına gelir. Döngüden önce durumu test etmek daha güvenli kabul edilir .. ve bu yüzden daha sık kullanılır.
Ioan Paul Pirau

4

Bu sorunun yeterince yanıtlandığını görüyorum, ancak bu çok özel kullanım senaryosunu eklemek istiyorum. Do ... kullanmaya başlayabilirsiniz.

do
{
   ...
} while (0)

genellikle çok satırlı # tanımlar için kullanılır. Örneğin:

#define compute_values     \
   area = pi * r * r;      \
   volume = area * h

Bu, aşağıdakiler için uygundur:

r = 4;
h = 3;
compute_values;

-ama- şunun için bir gotcha var:

if (shape == circle)  compute_values;

bu genişledikçe:

if (shape == circle) area = pi *r * r;
volume = area * h;

Eğer onu bir do ... while (0) döngüsüne sararsanız, düzgün şekilde tek bir bloğa genişler:

if (shape == circle)
  do
  {
    area = pi * r * r;
    volume = area * h;
  } while (0);

2

Şimdiye kadarki cevaplar, "do-while" için genel kullanımı özetliyor. Ancak OP bir örnek istedi, işte bir tanesi: Kullanıcı girdisini alın. Ancak kullanıcının girdisi geçersiz olabilir - bu nedenle girdi istersiniz, onaylarsınız, geçerliyse devam edin, aksi takdirde tekrar edin.

Do-while ile girdi geçerli değilken girdiyi alırsınız. Normal bir while döngüsü ile girdiyi bir kez alırsınız, ancak geçersizse, geçerli olana kadar tekrar tekrar alırsınız. Döngünün gövdesi daha karmaşık hale gelirse, ilkinin daha kısa, daha zarif ve bakımı daha basit olduğunu görmek zor değil.


Sanırım onları bunun için hiç kullanmamamın nedeni, genellikle kullanıcıya girdilerinde neyin yanlış olduğunu anlatmamdır. Bu, koşulun bir döngünün merkezinde olacağı breakve girdinin doğru olması durumunda yapacağım anlamına gelir .
mphair

@mphair: Aslında, bu tür durumlar için bazen "do {} while (0)" kullanmaya daha meyilli olabilirim; döngü, "devam" kullanarak; kullanıcının girdisi kabul edilemez ise. Yalnızca bir sorun ve mesaj varsa, "do {} while (1);" ve "ara"; güzel çalışıyor, ancak yeniden giriş talep etmek için birden fazla neden varsa, "devam"; yapı daha güzel çalışır.
supercat

2

Aynı yapıyı birden çok kez okuyan bir okuyucu için kullandım.

using(IDataReader reader = connection.ExecuteReader())
{
    do
    {
        while(reader.Read())
        {
            //Read record
        }
    } while(reader.NextResult());
}

1

do whileBir dosyanın başında bir sentinel değeri okurken bir kullandım , ancak bunun dışında, bu yapının çok yaygın kullanılmamasının anormal olduğunu düşünmüyorum - do-whiles gerçekten durumsaldır.

-- file --
5
Joe
Bob
Jake
Sarah
Sue

-- code --
int MAX;
int count = 0;
do {
MAX = a.readLine();
k[count] = a.readLine();
count++;
} while(count <= MAX)

1

İşte teorim, çoğu insanın (ben dahil) neden while () {} döngüleriyle {} while () yapmayı tercih ettiğidir: Bir while () {} döngüsü, bir do.. while () döngüsü gibi performans gösterecek şekilde kolayca uyarlanabilirken tersi doğru değil. Bir while döngüsü belirli bir şekilde "daha genel" dir. Ayrıca programcılar kolay anlaşılır kalıpları severler. Bir while döngüsü başlangıçta değişmezinin ne olduğunu söylüyor ve bu güzel bir şey.

İşte "daha genel" şeyle ilgili kastettiğim şey. Bunu yapın ...

do {
 A;
 if (condition) INV=false; 
 B;
} while(INV);

Bunu bir while döngüsüne dönüştürmek basittir:

INV=true;
while(INV) {
 A;
 if (condition) INV=false; 
 B;
}

Şimdi, döngü sırasında bir model alıyoruz:

while(INV) {
     A;
     if (condition) INV=false; 
     B;
}

Ve bunu bir do.. while döngüsüne dönüştürürsek, bu canavarlığı ortaya çıkarır:

if (INV) {
 do
 {
         A;
         if (condition) INV=false; 
         B;

 } while(INV)
}

Şimdi zıt uçlarda iki kontrolümüz var ve eğer değişmezlik değişirse, iki yerde güncellemeniz gerekiyor. Belli bir şekilde, alet kutusundaki özel tornavidalar gibidir, asla kullanmazsınız, çünkü standart tornavida ihtiyacınız olan her şeyi yapar.


1
Bir "do {} while (x);" yerine "while (x)" şeklinde döngü döngü, X'in ya kolayca "hazırlanabilen" bir koşul olmasını veya ilk geçişte "doğal olarak" doğru olmasını gerektirir. Bana göre, bir döngüden önce hazırlama ifadeleri, döngü içindeki bir şeyin hazırlamaya ihtiyacı olduğunu ve bir while (x) {}; "döngüsü, döngünün sıfır kez çalıştırılabileceğini ima eder. En az bir kez çalışacak bir döngü istiyorsam, ben bir "do {} while (x);" döngüsü kullanın.
süper araba

1

Yaklaşık 12 yıl boyunca programlama yapıyorum ve sadece 3 ay önce, bir koşulu kontrol etmeden önce tek bir yineleme her zaman gerekli olduğundan, do-while kullanmanın gerçekten uygun olduğu bir durumla karşılaştım. Öyleyse tahmin et büyük zamanın önde :).


1
Neredeyse her seferinde bir şey görüyorum ... problemi anlamadığım için ve korkunç bir hack içeriyordu. İstisnalar var, ama genel olarak konuşursak, eğer bir işin varsa ... bir süre, ne yaptığını yeniden düşünmelisin ... :-)
Brian Knoblauch

2
Doğru yerdeyseniz, programcıların kafasını karıştırmamak için do-while tüm programlama dillerinden çıkarılır :).
Narek

@Narek: Bunu yazdığınızdan bu yana yedi yıla yakın bir zaman geçti, do whilepişmanlık duymadan daha iyi bir çözümün olduğu daha fazla koşulunuz oldu mu?
chqrlie

Evet, muhtemelen bir kez daha: D
Narek

1

Kullanmadan bu kadar uzun süre nasıl gittiğini hayal edemiyorum do...while döngü .

Şu anda başka bir monitörde bir tane var ve bu programda bu tür birden çok döngü var. Hepsi şu biçimdedir:

do
{
    GetProspectiveResult();
}
while (!ProspectIsGood());

1

Bir sunucuda / tüketicide oldukça yaygın bir yapıdır:

DOWHILE (no shutdown requested)
   determine timeout
   wait for work(timeout)
   IF (there is work)
      REPEAT
          process
      UNTIL(wait for work(0 timeout) indicates no work)
      do what is supposed to be done at end of busy period.
   ENDIF
ENDDO

REPEAT UNTIL(cond)olmakdo {...} while(!cond)

Bazen iş için bekleme (0) CPU açısından daha ucuz olabilir (hatta zaman aşımı hesaplamasını ortadan kaldırmak bile çok yüksek varış oranlarında bir gelişme olabilir). Dahası, yoğun bir dönemde servis edilen sayıyı önemli bir istatistik yapan birçok kuyruk teorisi sonucu vardır. (Örneğin Kleinrock - Cilt 1'e bakın.)

Benzer şekilde:

DOWHILE (no shutdown requested)
   determine timeout
   wait for work(timeout)
   IF (there is work)
      set throttle
      REPEAT
          process
      UNTIL(--throttle<0 **OR** wait for work(0 timeout) indicates no work)
   ENDIF
   check for and do other (perhaps polled) work.
ENDDO

burada check for and do other workana döngüye koymak aşırı derecede pahalı olabilir veya belki de verimli waitany(waitcontrol*,n)tipte bir işlemi desteklemeyen bir çekirdek veya belki de önceliklendirilmiş bir kuyruğun diğer işi aç bırakabileceği ve kısma açlık kontrolü olarak kullanıldığı bir durum olabilir.

Bu tür bir dengeleme bir hile gibi görünebilir, ancak gerekli olabilir. İş parçacığı havuzlarının kör kullanımı, bir bakıcı iş parçacığı yerine bir iş parçacığı havuzunun kullanılması iş parçacığı güvenli uygulama gerektireceğinden, yüksek güncelleme hızı karmaşık veri yapısı için özel bir kuyruğa sahip bir bekçi iş parçacığının kullanımının performans faydalarını tamamen ortadan kaldıracaktır.

Sözde kod (örneğin, talep edilen kapatmanın UNTIL'de test edilip edilmeyeceği) veya iş parçacığı havuzlarına karşı bakıcı iş parçacığı hakkında bir tartışmaya girmek istemiyorum - bu sadece belirli bir kullanım durumunun bir çeşidini vermek içindir: kontrol akış yapısı.


1

Bu benim kişisel görüşüm, ancak bu soru, deneyime dayanan bir cevabı gerektiriyor:

  • 38 yıldır C ile programlama yapıyorum ve normal kodda asla do/ whiledöngü kullanmıyorum .

  • Bu yapının tek zorlayıcı kullanımı, birden çok ifadeyi tek bir ifadeye sarmalayabildiği makrolardır. do { multiple statements } while (0)

  • Sahte hata algılama veya gereksiz işlev çağrıları içeren sayısız do/ whiledöngü örneği gördüm .

  • Bu gözlem için benim açıklamam, programcıların do/ whiledöngüleri açısından düşündüklerinde problemleri yanlış modelleme eğiliminde olmalarıdır . Ya önemli bir bitiş koşulunu kaçırırlar ya da sonuna kadar taşıdıkları başlangıç ​​koşulunun olası başarısızlığını kaçırırlar.

Bu nedenlerden dolayı, ben inanmak için gelmiş bir olduğu yerde do/ whiledöngü, bir hata var ve ben düzenli bana göstermek için acemi programcıları meydan do/ whileı Yakın bir hata fark edemiyorum döngü.

Bu tür döngüden kolayca kaçınılabilir: a kullanın for (;;) { ... }ve uygun olan yerlerde gerekli sonlandırma testlerini ekleyin. Böyle birden fazla teste ihtiyaç duyulması oldukça yaygındır.

İşte klasik bir örnek:

/* skip the line */
do {
    c = getc(fp);
} while (c != '\n');

Dosya bir satırsonu ile bitmezse bu başarısız olur. Böyle bir dosyanın önemsiz bir örneği boş dosyadır.

Daha iyi bir versiyon şudur:

int c;  // another classic bug is to define c as char.
while ((c = getc(fp)) != EOF && c != '\n')
    continue;

Alternatif olarak, bu sürüm cdeğişkeni de gizler :

for (;;) {
    int c = getc(fp);
    if (c == EOF || c == '\n')
        break;
}

while (c != '\n');Herhangi bir arama motorunda aramayı deneyin ve bunun gibi hataları bulacaksınız (24 Haziran 2017'de alındı):

Gelen ftp://ftp.dante.de/tex-archive/biblio/tib/src/streams.c , işlevi getword(stream,p,ignore), bir var do/ whileen az 2 böcek ve emin yeterli:

  • cbir charve olarak tanımlanır
  • potansiyel bir sonsuz döngü var while (c!='\n') c=getc(stream);

Sonuç: do/ whiledöngülerden kaçının ve bir tane gördüğünüzde böcek arayın.


1
while() {}Döngülerden kaçınmayı tercih ederim , çünkü bunlar ilk kontroller açısından daha güvenli, bu yüzden sizi "beyinsiz" yapma, daha iyi algoritma üzerinde düşünmek için tembellik yapma, tonlarca beklenmedik hata kontrolü yapma vb. Eğilimindedir .. " do{}while()Kullanım eksikliği " kötü (beyinsiz) veya acemi bir programcı belirtisi, daha düşük kaliteli ve daha yavaş kod üretir. Bu yüzden ilk önce kendime "Bu kesinlikle bir while () {} durumu mu?" Değilse - Ben daha fazla düşünme ve giriş veri doğruluğunu gerektirebilir rağmen döngü sırasında yapmak yazmaya çalışacağım
xakepp35

Cevabınızı anlıyorum ancak bunun daha iyi olacağı bazı olası senaryolar düşünebilirim. Lütfen while: prnt.sc/v2zy04'e veya do while: prnt.sc/v2zyka'ya bakın . Senin kadar uzun süredir programcı değilim ama orada bir hata bulamadım. Do while'ın daha temiz olduğu çok açık çünkü döngüye girmek için süre üzerinde ekstra kontrol gerektirmiyor. Kodu optimize etmekten bahsediyorsak, bunun oldukça temiz olduğunu söyleyebilirim.
Roy Berris

@RoyBerris: Örneğiniz ilginç. Bir C programında, döngü içinde kullanılan işlevler, başarısız olmadıklarından emin olmak için ekstra testler gerektirir. Bu fazladan dağınıklık ve / breakveya returnifadeler ekleyecektir . Torna do/ whilea içine döngü for(;;)döngü daha tutarlı IMHO olması. Bu ifadeye karşı dışlanmam, bunun doğru araç olacağı ender durumlarda strncpy() bile kullanmayı reddetmeme benzer : Sıradan okuyucuların bu yapıların zararsız olduğunu düşünmelerine izin vermeyin, çünkü çoğu durumda bulması zor böcek kaynaklarıdır. .
chqrlie

@chqrlie orijinal cevabı görmek herhangi bir C dili hakkındaydı sanırım her iki cevap da işe yarar. C #, öncekilerden çok "kurtarıcı" dır, bu nedenle do while ile teknik olarak daha hızlı olacaktır.
Roy Berris

0

whiledöngüler döngüden önceki koşulu, do...whiledöngüler döngüden sonraki koşulu kontrol eder. Bu yararlıdır, koşulu döngüden kaynaklanan yan etkilere dayandırmak veya diğer posterlerin söylediği gibi döngünün en az bir kez çalışmasını istiyorsanız.

Nereden geldiğinizi anlıyorum, ancak do-whilebu en çok nadiren kullanılan bir şey ve kendimi hiç kullanmadım. Yanlış yapmıyorsun.

Yanlış yapmıyorsun. Bu, byteilkel olanı hiç kullanmadıkları için birinin yanlış yaptığını söylemek gibidir . Sadece o kadar yaygın kullanılmıyor.


0

do/ whileDöngüsünü kullandığım en yaygın senaryo , bazı girdilere göre çalışan ve kullanıcının istediği kadar tekrar eden küçük bir konsol programıdır. Açıkçası, bir konsol programının hiç çalışmaması mantıklı değil; ancak ilk seferin ötesinde bu kullanıcıya bağlı - dolayısıyla do/ whilesadece yerine while.

Bu, kullanıcının istenirse bir dizi farklı girişi denemesine izin verir.

do
{
   int input = GetInt("Enter any integer");
   // Do something with input.
}
while (GetBool("Go again?"));

Yazılım geliştiricilerin bugünlerde do/ whiledaha az ve daha az kullandığından şüpheleniyorum , şimdi pratik olarak güneş altındaki her programın bir çeşit GUI'si var. Talimatlar sağlamak veya kullanıcıya yeni bilgiler sağlamak için çıktıyı sürekli olarak yenilemeye ihtiyaç duyulduğundan, konsol uygulamalarında daha mantıklıdır. Bir GUI ile, aksine, kullanıcıya bu bilgiyi sağlayan metin sadece bir formun üzerine oturabilir ve hiçbir zaman programlı olarak tekrarlanması gerekmez.


0

Dosyaları okurken her zaman do-while döngüleri kullanıyorum. Başlıkta yorum içeren birçok metin dosyasıyla çalışıyorum:

# some comments
# some more comments
column1 column2
  1.234   5.678
  9.012   3.456
    ...     ...

"column1 column2" satırını okumak için bir do-while döngüsü kullanacağım, böylece ilgilenilen sütunu arayabilirim. Sözde kod şu şekildedir:

do {
    line = read_line();
} while ( line[0] == '#');
/* parse line */

Sonra dosyanın geri kalanını okumak için bir süre döngü yapacağım.


Ana while döngüsünüzdeki çağrının if (line[0]=='#') continue;hemen sonrasına koyarsanız, dosyanın herhangi bir yerinde yorumları desteklemek önemsiz olur (ve genel olarak kodu basitleştirir) read_line...
R .. GitHub DUR YARDIM BUZ

Bir başlık bulmak için önerinizi nasıl uygulayacağımı göremiyorum. (dang, yorumlarda kodun nasıl biçimlendirileceğini çözemiyorum, plz yardım et!) header = false; while (! başlık) {line = read_line (); eğer (satır [0] == '#') devam et; başlık = true; } bu sizin için daha açık / basit mi?
uçuyor

0

Moruk bir programcı olarak, okul programlama projelerimin çoğu metin menüsü odaklı etkileşimler kullandı. Hemen hemen tümü, ana prosedür için aşağıdaki mantık gibi bir şey kullandı:

do
    display options
    get choice
    perform action appropriate to choice
while choice is something other than exit

Okul günlerinden beri while döngüsünü daha sık kullandığımı fark ettim.


0

Sonuç setlerine baktığımızda gördüğüm uygulamalardan biri Oracle'da.

Bir sonuç kümesine sahip olduğunuzda, önce ondan (do) ve o noktadan sonra getirme işleminin bir öğe döndürüp döndürmediğini kontrol edin (öğe bulunurken ..) .. Aynısı başka herhangi biri için de geçerli olabilir " getirme benzeri "uygulamalar.


0

Bunu bir utf-8 dizesinde sonraki karakter konumunu döndüren bir işlevde kullandım:

char *next_utf8_character(const char *txt)
{
    if (!txt || *txt == '\0')
        return txt;

    do {
        txt++;
    } while (((signed char) *txt) < 0 && (((unsigned char) *txt) & 0xc0) == 0xc0)

    return (char *)txt;
}

Bu işlevin akıldan yazıldığını ve test edilmediğini unutmayın. Mesele şu ki, yine de ilk adımı atmanız gerekiyor ve durumu değerlendirmeden önce bunu yapmanız gerekiyor.


Bu kötü bir yazı biçimi *(unsigned char *)txt-0x80U<0x40.
R .. GitHub BUZA YARDIM ETMEYİ DURDUR

0

Her tür konsol girdisi do-while ile iyi çalışır çünkü ilk seferinde uyarırsınız ve giriş doğrulaması başarısız olduğunda yeniden uyarı verir.


0

Burada pek çok cevap olmasına rağmen benim fikrim. Her şey, optimizasyona bağlı. Birinin diğerinden daha hızlı olduğu iki örnek göstereceğim.

Dava 1: while

string fileName = string.Empty, fullPath = string.Empty;

while (string.IsNullOrEmpty(fileName) || File.Exists(fullPath))
{
    fileName = Guid.NewGuid().ToString() + fileExtension;
    fullPath = Path.Combine(uploadDirectory, fileName);
}

Durum 2: do while

string fileName = string.Empty, fullPath = string.Empty;

do
{
    fileName = Guid.NewGuid().ToString() + fileExtension;
    fullPath = Path.Combine(uploadDirectory, fileName);
}
while (File.Exists(fullPath));

Yani ikisi aynı şeyi yapacak. Ancak temel bir fark var ve bu while'ın while'a girmek için fazladan bir ifade gerektirmesidir. Bu çirkin, çünkü diyelim ki Guidsınıfın olası her senaryosu, bir varyant dışında zaten alınmış. Bu, birkaç 5,316,911,983,139,663,491,615,228,241,121,400,000kez döngü yapmam gerektiği anlamına gelir . While ifademin sonuna her geldiğimde string.IsNullOrEmpty(fileName)kontrolü yapmam gerekecek . Yani bu biraz zaman alır, CPU işinin çok küçük bir kısmı. Ancak bu çok küçük görev, Guidsınıfın sahip olduğu olası kombinasyonları ve saatlerden, günlerden, aylardan veya fazladan zamandan bahsettiğimizle mi çarpıyor?

Elbette bu uç bir örnek çünkü muhtemelen bunu üretimde göremezsiniz. Ancak YouTube algoritması hakkında düşünecek olursak, bazı kimliklerin zaten alınmış olduğu bir kimliğin oluşturulmasıyla karşılaşmaları çok olasıdır. Bu yüzden büyük projeler ve optimizasyon söz konusudur.


0

Bu ikisini şu şekilde anlamayı seviyorum:
while-> 'kadar tekrarla',
do ... while-> 'eğer tekrarla'.


-1

Sahip olduğum bir durumda kullanmak için uygun döngüyü araştırırken bununla karşılaştım. Bunun, bir do .. while döngüsünün bir while döngüsünden daha iyi bir uygulama olduğu yaygın bir durumu tamamen tatmin edeceğine inanıyorum (C # dili, çünkü bunun iş için birincil olduğunu belirttin).

Bir SQL sorgusunun sonuçlarına göre bir dizi listesi oluşturuyorum. Sorgum tarafından döndürülen nesne bir SQLDataReader. Bu nesne, nesneyi sonraki veri satırına ilerleten ve başka bir satır varsa true döndüren Read () adında bir işleve sahiptir. Başka bir satır yoksa yanlış döndürür.

Bu bilgiyi kullanarak, her satırı bir listeye döndürmek, ardından döndürülecek daha fazla veri kalmadığında durmak istiyorum. Bir Do ... While döngüsü bu durumda en iyi sonucu verir, çünkü listeye bir öğe eklemenin başka bir satır olup olmadığını kontrol etmeden ÖNCE gerçekleşmesini sağlar. Bunun while (koşul) 'u kontrol etmeden ÖNCE yapılması gerekmesinin nedeni, kontrol ettiğinde aynı zamanda ilerlemesidir. Bu durumda bir while döngüsünün kullanılması, söz konusu işlevin doğası gereği ilk satırı atlamasına neden olur.

Kısacası:

Bu benim durumumda işe yaramayacak.

    //This will skip the first row because Read() returns true after advancing.
    while (_read.NextResult())
           {
               list.Add(_read.GetValue(0).ToString());
           }

   return list;

Bu irade.

    //This will make sure the currently read row is added before advancing.
    do
        {
            list.Add(_read.GetValue(0).ToString());
        } 
        while (_read.NextResult());

    return list;

SQLDataReader kullanımı iç içe döngüler olacaktır. İçteki çağrı çağırır Read()ve ilk Read()çağrı ilk satıra eriştiği için bir while döngüsü olmalıdır . Dıştaki arama yapar NextResult()ve bir süre olmalıdır çünkü ilk sonuç veri kümesi ilk NextResult()aramadan önce etkindir . Kod parçacıkları pek bir anlam ifade etmiyor, özellikle de yorumlar kodla eşleşmediği ve yanlış olduğu için.
Ben Voigt

-1
Console.WriteLine("hoeveel keer moet je de zin schrijven?");
        int aantal = Convert.ToInt32(Console.ReadLine());
        int counter = 0;

        while ( counter <= aantal)
        {
            Console.WriteLine("Ik mag geen stiften gooien");
            counter = counter + 1;

Sanki başka birinin sorusunu yanıtlayacakmış gibi kendi sorunuzu yanıtlamalısınız. Bir açıklama ve kod dahil
Simon
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.