'làm ... trong khi' so với 'trong khi'


86

Có thể trùng lặp:
While so với Do While
Khi nào tôi nên sử dụng do-while thay vì vòng lặp while?

Tôi đã lập trình được một thời gian (2 năm làm việc + 4,5 năm bằng cấp + 1 năm dự bị đại học) và tôi chưa bao giờ sử dụng vòng lặp do-while khi bị buộc phải tham gia khóa học Nhập môn Lập trình. Tôi ngày càng có cảm giác rằng tôi đang lập trình sai nếu tôi không bao giờ gặp phải điều gì đó cơ bản.

Có thể là tôi đã không chạy vào hoàn cảnh chính xác?

Một số ví dụ trong đó cần sử dụng do-while thay vì một lúc là gì?

(Thời đi học của tôi hầu như học bằng C / C ++ và công việc của tôi là C #, vì vậy nếu có một ngôn ngữ khác hoàn toàn hợp lý vì do-whiles hoạt động theo cách khác, thì những câu hỏi này không thực sự áp dụng.)

Để làm rõ ... tôi biết sự khác biệt giữa a whilevà a do-while. Trong khi kiểm tra điều kiện thoát và sau đó thực hiện các tác vụ. do-whilethực hiện các tác vụ và sau đó kiểm tra điều kiện thoát.


42
Đối với những gì nó đáng giá, sau một thập kỷ lập trình như sự nghiệp của tôi, tôi đã sử dụng vòng lặp do-while như một hoặc hai lần.
Matt Greer

@Matt - Điều đó khiến tôi cảm thấy tốt hơn. Ít nhất nó có nghĩa là tôi không đơn độc.
mphair

2
Tôi sẽ thứ hai Matt và thêm một thập kỷ nữa cho điều đó. Tôi cũng hiếm khi sử dụng vòng lặp do-while.
quentin-starin,

Nhiều bản sao. Đây là một loại liên kết đến một bó của họ: stackoverflow.com/questions/3094972/...
gnovice

3
“Câu hỏi chính” có vẻ là stackoverflow.com/questions/224059/…
Donal Fellows

Câu trả lời:


115

Nếu bạn luôn muốn vòng lặp thực thi ít nhất một lần. Nó không phổ biến, nhưng tôi sử dụng nó theo thời gian. Một trường hợp mà bạn có thể muốn sử dụng nó là cố gắng truy cập vào một tài nguyên có thể yêu cầu thử lại, ví dụ:

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

} while (user says retry);

3
Vì vậy, tôi đoán điều này sẽ theo sau "Tôi chưa từng rơi vào trường hợp đó."
mphair

7
một cách trung thực, tôi rất hiếm khi gặp phải một tình huống khi tôi chỉ cần sử dụng một do..while ..
Stan R.

1
Có thể hơi ngắn, nhưng Bob đúng. Do / while nên được sử dụng khi bạn muốn chạy khối mã ít nhất một lần . Bạn nên sử dụng vòng lặp while khi bạn không biết mình có muốn chạy nó một lần hay không. Điều đó nói rằng, tôi có thể nói với bạn rằng với khoảng 15 năm kinh nghiệm phát triển, tôi đã sử dụng vòng lặp while nhiều hơn nhiều lần so với vòng lặp do / while.
ConsultUtah

1
Đôi khi tôi cũng sử dụng chúng, mặc dù hiếm khi. Sau một thời gian ngắn, chúng cũng thường được cấu trúc lại thành một vòng lặp while bình thường - chỉ vì các khu vực xung quanh nó đã thay đổi và thực hiện ... trong khi vụng về hơn ban đầu.
Joshua

2
Nó là khá phổ biến. Nhưng tôi không hiểu tại sao trong ví dụ của bạn, bạn không sử dụng vòng lặp while. Và tôi không hiểu tại sao câu trả lời này lại được ủng hộ.

65

do-while sẽ tốt hơn nếu trình biên dịch không có khả năng tối ưu hóa. do-while chỉ có một bước nhảy có điều kiện duy nhất, trái ngược với for và while có một bước nhảy có điều kiện và một bước nhảy không điều kiện. Đối với các CPU có cấu trúc pipelined và không thực hiện dự đoán nhánh, điều này có thể tạo ra sự khác biệt lớn về hiệu suất của một vòng lặp chặt chẽ.

Ngoài ra, vì hầu hết các trình biên dịch đủ thông minh để thực hiện tối ưu hóa này, tất cả các vòng lặp được tìm thấy trong mã dịch ngược thường sẽ là do-while (nếu trình dịch ngược thậm chí còn bận tâm đến việc tạo lại các vòng lặp từ các gotos cục bộ lạc hậu).


5
+1 để thêm thông tin kỹ thuật có liên quan để lựa chọn giữa các hình thức vòng lặp.
quentin-starin,

2
Không chỉ chết yểu mà còn vô ích. Nếu bạn thực sự muốn có một vòng lặp while tức là khả năng lặp 0 lần, bạn phải kèm theo vòng lặp trong một nếu mà mang lại mà nhảy trở lại trong.
JeremyP

3
@Jeremy: Bước nhảy mà bạn nói đến nằm ngoài vòng lặp, nó không được thực hiện N lần.
Ben Voigt

1
Cũng hãy xem xét cách sử dụng cổ điển của do-while trong thiết bị của Duff (trong đó vòng lặp while được chuyển thành do-while không được cuộn với bước lớn hơn cộng với bảng nhảy để xử lý phần còn lại) giúp giảm đáng kể chi phí vòng lặp - trong các ứng dụng như memset, memcmp, memcpy nó có thể làm tăng thông lượng bởi một yếu tố của 10
Ben Voigt

@BenVoigt Tôi biết nhận xét của bạn đến nay đã được một thập kỷ, nhưng Thiết bị của Duff không nên được đề cập đến mà không có cảnh báo rất quan trọng - nó trở nên kém hiệu quả hơn trên các trình biên dịch hiện đại, bởi vì các trình biên dịch hiện đại thường có thể tự giải nén các vòng lặp đơn giản một cách thông minh là tối ưu cho máy mục tiêu , mặc dù họ thường không có logic để tìm ra mục đích của Thiết bị Duff và do đó gần như không thể tối ưu hóa nó. Đó là một bài học tuyệt vời về tối ưu hóa mã cho một số máy mục tiêu thay vì cho một "trình tối ưu hóa đủ nâng cao" trừu tượng.
mtraceur

12

Tôi đã sử dụng điều này trong một hàm TryDeleteDirectory. Nó là một cái gì đó như thế này

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

Đẹp. Đây là ví dụ đầu tiên thực sự có ý nghĩa khi được gói gọn trong một do ... while.
Joshua

4
Tại sao / làm thế nào điều này tốt hơn một tiêu chuẩn while?
Josh Stodola

Vòng lặp sẽ luôn thực thi một lần, vì vậy bạn có thể muốn làm điều gì đó, sau đó thử lại nếu nó không thành công. Nếu bạn sử dụng vòng lặp while thông thường, bạn sẽ kiểm tra xem nó có bị lỗi hay không trước khi bạn thực hiện ngay trong lần lặp đầu tiên.
NibblyPig 27/07/10

1
@SLC Nhưng bạn sẽ không muốn kiểm tra xem thư mục có tồn tại trước đó không?
Josh Stodola

1
@Josh Trong ví dụ này, có. Nhưng nếu bạn đang cố gắng mở kết nối với một máy chủ bận, trước tiên bạn phải kết nối và xem phản hồi có 'bận' hay không. Nếu đúng như vậy, bạn sẽ thử lại vài lần trước khi từ bỏ.
NibblyPig 27/07/10

11

Do while rất hữu ích khi bạn muốn thực thi điều gì đó ít nhất một lần. Đối với một ví dụ điển hình cho việc sử dụng do while so với while, giả sử bạn muốn thực hiện như sau: Một máy tính.

Bạn có thể tiếp cận điều này bằng cách sử dụng một vòng lặp và kiểm tra sau mỗi phép tính nếu người đó muốn thoát khỏi chương trình. Bây giờ bạn có thể giả định rằng sau khi chương trình được mở, người đó muốn thực hiện điều này ít nhất một lần, vì vậy bạn có thể làm như sau:

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

2
Trên thực tế, bây giờ bạn đề cập đến nó, bất kỳ loại giao diện điều khiển menu dựa trên bảng điều khiển nào cũng sẽ hoạt động tốt trong vòng lặp do ... while. Ví dụ: nhắc các tùy chọn (một phím tại một thời điểm) và chỉ thoát khi họ chọn tiếp tục hoặc thoát.
Joshua

7
continuelà một từ khóa; D
Thomas Việc chỉnh sửa

Atrinithis: Lol ... Tôi trượt lên. Cảm ơn đã sửa cho tôi ở đó.

== truelà không cần thiết. cont == truenếu cont là true trả về true. Điều đó hoàn toàn vô ích.
Mr.DeleteMyMessages

11

Đây là một câu trả lời gián tiếp, nhưng câu hỏi này khiến tôi suy nghĩ về logic đằng sau nó, và tôi nghĩ điều này có thể đáng được chia sẻ.

Như mọi người đã nói, bạn sử dụng do ... whilevòng lặp khi bạn muốn thực thi phần thân ít nhất một lần. Nhưng bạn muốn làm điều đó trong hoàn cảnh nào?

Chà, loại tình huống rõ ràng nhất mà tôi có thể nghĩ đến sẽ là khi giá trị ban đầu ("không có mã") của điều kiện kiểm tra giống như khi bạn muốn thoát . Điều này có nghĩa là bạn cần thực thi phần thân của vòng lặp một lần để điều kiện nguyên tố thành giá trị không thoát, và sau đó thực hiện lặp lại thực tế dựa trên điều kiện đó. Điều gì xảy ra với việc các lập trình viên quá lười biếng, ai đó đã quyết định gói gọn điều này trong một cấu trúc điều khiển.

Vì vậy, ví dụ: đọc các ký tự từ một cổng nối tiếp với thời gian chờ có thể có dạng (bằng Python):

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)

Lưu ý việc sao chép mã: char_read = port.read(1) . Nếu Python có một do ... whilevòng lặp, tôi có thể đã sử dụng:

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

Lợi ích bổ sung cho các ngôn ngữ tạo phạm vi mới cho các vòng lặp: char_readkhông gây ô nhiễm không gian tên hàm. Nhưng cũng lưu ý rằng có một cách tốt hơn để làm điều này, đó là sử dụng Nonegiá trị của Python :

response_buffer = []
char_read = None

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

response = ''.join(response_buffer)

Vì vậy, đây là mấu chốt của quan điểm của tôi: trong các ngôn ngữ có kiểu nullable, tình huống này initial_value == exit_valueít xảy ra hơn rất nhiều và đó có thể là lý do tại sao bạn không gặp phải nó. Tôi không nói rằng nó không bao giờ xảy ra, bởi vì vẫn có những lúc một hàm sẽ quay trở lại Noneđể biểu thị một điều kiện hợp lệ. Nhưng theo ý kiến ​​vội vã và được cân nhắc ngắn gọn của tôi, điều này sẽ xảy ra nhiều hơn nếu các ngôn ngữ bạn sử dụng không cho phép giá trị biểu thị: biến này chưa được khởi tạo.

Đây không phải là lý luận hoàn hảo: trong thực tế, hiện nay giá trị null đã phổ biến, chúng chỉ đơn giản là tạo thành một phần tử nữa của tập giá trị hợp lệ mà một biến có thể nhận. Nhưng trên thực tế, các lập trình viên có một cách để phân biệt giữa một biến đang ở trạng thái hợp lý, có thể bao gồm trạng thái thoát vòng lặp và biến ở trạng thái chưa được khởi tạo.


Đây là một trong những câu trả lời thú vị hơn mà tôi đã đọc ở đây. Đóng góp tốt.
Bob Moore

Hai phiên bản không tương đương. Nếu lần đọc đầu tiên trả về None, whilephiên bản chính xác không thêm bất kỳ thứ gì vào bộ đệm phản hồi, nhưng dophiên bản của bạn luôn thêm một thứ gì đó.
David Stone

@DavidStone read()sẽ không bao giờ quay trở lại None. Nếu bạn đang sử dụng một thư viện mà ở đó, tiền đề sẽ không áp dụng (nghĩa là giá trị sentinel không nằm trong tập hợp các giá trị kiểm tra có thể có).
detly

Với chỉnh sửa mới nhất của bạn, tôi thấy những gì tôi nên nói ''thay vì None. Một số loại giá trị được đánh giá là sai. Đánh dấu sự không quen thuộc của tôi với readchức năng của Python .
David Stone

@DavidStone Tôi không hiểu. Nếu read()trả về '', không có gì sẽ được thêm vào chuỗi, nó trống.
detly

7

Tôi đã sử dụng chúng một chút khi tôi còn đi học, nhưng không quá nhiều kể từ đó.

Về lý thuyết, chúng hữu ích khi bạn muốn phần thân của vòng lặp thực thi một lần trước khi kiểm tra điều kiện thoát. Vấn đề là đối với một số trường hợp mà tôi không muốn kiểm tra trước, thường tôi muốn kiểm tra thoát ở giữa thân vòng lặp hơn là ở cuối. Trong trường hợp đó, tôi thích sử dụng cái nổi tiếng for (;;)với một if (condition) exit;nơi nào đó trong cơ thể.

Trên thực tế, nếu tôi hơi run về điều kiện thoát vòng lặp, đôi khi tôi thấy hữu ích khi bắt đầu viết vòng lặp dưới dạng for (;;) {}câu lệnh thoát khi cần thiết và sau đó khi hoàn thành, tôi có thể xem liệu nó có thể được không " được dọn dẹp "bằng cách di chuyển các lần khởi tạo, điều kiện thoát và / hoặc mã tăng trong fordấu ngoặc đơn của '.


5
Vì tò mò ... tại sao "for (;;)" thay vì "while (true)"?
mphair

4
Có lẽ chỉ là hương vị. Tôi đã phát triển để đánh đồng for(;;)với "vòng lặp vô hạn", trong khi while(true)tôi phải dừng lại và suy nghĩ. Có lẽ là do tôi đã viết mã C trước khi có a true. Ngày xưa chúng ta chỉ có TRUEmột macro, và nếu có gì đó không ổn, tôi phải thuyết phục bản thân rằng macro chưa được xác định lại 0bằng cách nào đó (nó đã xảy ra!). Không for(;;)có cơ hội đó. Nếu bạn nhấn mạnh vào hình thức while, tôi gần như muốn xem while (1). Tất nhiên sau đó một số người có thể tự hỏi liệu đó có phải là chữ cái ...
TED

1
@mphair: Vâng, nếu bạn ghi chú văn bản mà trình soạn thảo đưa vào khi bạn in nghiêng hoặc codeifyvăn bản, bạn có thể tự mình nhập trực tiếp vào trường nhận xét. Tôi đã thấy một số người đặt siêu liên kết, nhưng điều đó hơi quá khó đối với tôi.
TED

2
@TED: Ký hiệu ưa thích của tôi cho loop-cho đến-tường minh-thoát là "do {} while (1);". Ngẫu nhiên, trong hệ thống nhúng hoạt động, mô hình vòng lặp bình thường của tôi là "i = 10; do {} while (- i);". Đối với các vòng lặp chặt chẽ, điều đó nhanh hơn nhiều so với điển hình cho (); Tôi cho rằng sẽ dễ đọc hơn nếu sử dụng nhất quán làm mẫu vòng lặp khi không cần số lượng tăng hơn là tùy ý sử dụng for () trong các trường hợp không quan trọng về hiệu suất.
supercat

2
Thêm vào sự không đồng ý while (1) và for (;;), tôi đã thấy mã sử dụng for (EVER) với EVER được định nghĩa là ;;
asr

6

Một tình huống mà bạn luôn cần chạy một đoạn mã một lần và tùy thuộc vào kết quả của nó, có thể nhiều lần hơn. Điều tương tự cũng có thể được tạo ra với một whilevòng lặp thông thường .

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

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

6

do whilelà nếu bạn muốn chạy khối mã ít nhất một lần. whilemặt khác không phải lúc nào cũng chạy tùy thuộc vào các tiêu chí được chỉ định.


5

Nó đơn giản như vậy:

điều kiện trước và điều kiện sau

  • while (cond) {...} - điều kiện tiên quyết, nó thực thi mã chỉ sau khi kiểm tra.
  • do {...} while (cond) - điều kiện hậu, mã được thực thi ít nhất một lần.

Bây giờ bạn đã biết bí mật .. hãy sử dụng chúng một cách khôn ngoan :)


1
Như tôi đã nói, tôi biết chúng hoạt động như thế nào, tôi chỉ không chắc trong trường hợp nào thì cái này có ý nghĩa hơn cái kia.
mphair

Thật hợp lý khi bạn sử dụng do {} trong khi bạn biết chắc chắn rằng bạn phải vượt qua ít nhất một lần vượt qua vòng lặp. Chẳng hạn như nếu bạn có một hàm tìm một phần tử trong một mảng và trước đó bạn đã đặt một if array.IsEmpty () then return; . Nếu nó vượt qua kiểm tra đó, bạn có thể an toàn sử dụng do {} while. do-while cũng dễ liên tưởng đến, như một vòng lặp lặp đi lặp lại: "Tôi ăn cho đến khi tôi hài lòng" được dịch là làm {eat ();} while (! thoả mãn ();). Kiểm tra điều kiện trước vòng lặp, được coi là an toàn hơn .. và đó là lý do tại sao nó được sử dụng thường xuyên hơn.
Ioan Paul Pirau,

4

Tôi thấy rằng câu hỏi này đã được trả lời đầy đủ, nhưng tôi muốn thêm vào trường hợp sử dụng rất cụ thể này. Bạn có thể bắt đầu sử dụng do ... trong khi thường xuyên hơn.

do
{
   ...
} while (0)

thường được sử dụng cho các #defines nhiều dòng. Ví dụ:

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

Điều này hoạt động ổn cho:

r = 4;
h = 3;
compute_values;

-nhưng- có một gotcha cho:

if (shape == circle)  compute_values;

vì điều này mở rộng thành:

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

Nếu bạn quấn nó trong vòng lặp do ... while (0), nó sẽ mở rộng đúng cách thành một khối duy nhất:

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

2

Các câu trả lời cho đến nay tóm tắt cách sử dụng chung cho do-while. Nhưng OP đã yêu cầu một ví dụ, vì vậy đây là một ví dụ: Nhận đầu vào của người dùng. Nhưng đầu vào của người dùng có thể không hợp lệ - vì vậy bạn yêu cầu đầu vào, xác thực nó, tiếp tục nếu nó hợp lệ, nếu không thì lặp lại.

Với do-while, bạn nhận được đầu vào trong khi đầu vào không hợp lệ. Với vòng lặp while thông thường, bạn nhận được thông tin đầu vào một lần, nhưng nếu nó không hợp lệ, bạn sẽ lấy lại nhiều lần cho đến khi nó hợp lệ. Không khó để nhận thấy rằng vòng trước ngắn hơn, thanh lịch hơn và đơn giản hơn để duy trì nếu phần thân của vòng tròn phát triển phức tạp hơn.


Tôi nghĩ lý do tôi chưa bao giờ sử dụng chúng cho việc này là tôi thường kết thúc với việc nói cho người dùng biết điều gì đã xảy ra với đầu vào của họ. Có nghĩa là điều kiện sẽ nằm ở trung tâm của một vòng lặp và tôi sẽ chỉ breaknếu đầu vào là chính xác.
mphair

@mphair: Trên thực tế, đối với loại tình huống đó, đôi khi tôi có thể có xu hướng sử dụng "do {} while (0);" vòng lặp, sử dụng "continue;" nếu đầu vào của người dùng là không thể chấp nhận. Nếu chỉ có một vấn đề và thông báo, hãy sử dụng "do {} while (1);" và "break;" hoạt động tốt, nhưng nếu có nhiều lý do để yêu cầu nhập lại, "tiếp tục;" xây dựng công trình đẹp hơn.
supercat

2

Tôi đã sử dụng nó cho một người đọc nhiều lần cùng một cấu trúc.

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

1

Tôi đã sử dụng một do whilekhi tôi đang đọc một giá trị sentinel ở đầu một tệp, nhưng khác với điều đó, tôi không nghĩ là bất thường khi cấu trúc này không được sử dụng quá phổ biến-- do-whilethực sự là tình huống.

-- 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

Đây là lý thuyết của tôi tại sao hầu hết mọi người (bao gồm cả tôi) thích vòng lặp while () {} để làm {} while (): Vòng lặp while () {} có thể dễ dàng được điều chỉnh để thực hiện giống như vòng lặp do.. while () trong khi ngược lại là không đúng sự thật. Vòng lặp while theo một cách nào đó "tổng quát hơn". Ngoài ra các lập trình viên thích các mẫu dễ nắm bắt. Vòng lặp while cho biết ngay lúc bắt đầu bất biến của nó là gì và đây là một điều tốt.

Đây là những gì tôi muốn nói về điều "tổng quát hơn". Thực hiện vòng lặp do.. while này:

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

Chuyển đổi điều này thành một vòng lặp while rất đơn giản:

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

Bây giờ, chúng ta lấy một mô hình vòng lặp while:

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

Và biến nó thành một vòng lặp do..ared, tạo ra sự quái dị này:

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

 } while(INV)
}

Bây giờ chúng tôi có hai kiểm tra ở hai đầu đối diện và nếu thay đổi bất biến, bạn phải cập nhật nó ở hai nơi. Ở một khía cạnh nào đó, ... giá trị cũng giống như tua vít chuyên dụng trong hộp dụng cụ mà bạn không bao giờ sử dụng, bởi vì tua vít tiêu chuẩn làm được mọi thứ bạn cần.


1
Thay thế "do {} while (x);" lặp thành một "while (x);" vòng lặp yêu cầu X là một điều kiện có thể dễ dàng "mồi" hoặc nó "tự nhiên" đúng trong lần vượt qua đầu tiên. Đối với tôi, các câu lệnh mồi trước một vòng lặp ngụ ý rằng một cái gì đó trong vòng lặp cần mồi và một vòng lặp while (x) {}; "ngụ ý rằng vòng lặp có thể thực thi 0 lần. Nếu tôi muốn một vòng lặp sẽ thực thi ít nhất một lần, tôi sử dụng vòng lặp "do {} while (x);".
supercat

1

Tôi đang lập trình khoảng 12 năm và chỉ 3 tháng trước, tôi đã gặp một tình huống thực sự thuận tiện khi sử dụng do-while vì một lần lặp luôn cần thiết trước khi kiểm tra một điều kiện. Vì vậy, đoán thời gian lớn của bạn đang ở phía trước :).


1
Gần như lần nào tôi cũng thấy một việc làm ... trong khi đó là do thiếu hiểu biết về vấn đề và liên quan đến một số vụ hack khủng khiếp. Có những ngoại lệ, nhưng nói chung là nói, nếu bạn đã có một do ... while, bạn nên suy nghĩ lại những gì bạn đang làm ... :-)
Brian Knoblauch

2
Nếu bạn đúng thì do-while sẽ bị loại trừ khỏi tất cả các ngôn ngữ lập trình để không gây nhầm lẫn cho các lập trình viên :).
Narek

@Narek: Đã gần bảy năm nữa kể từ khi bạn viết bài này, bạn đã có nhiều trường hợp hơn đâu do whilelà giải pháp tốt hơn mà không phải hối tiếc?
chqrlie

Vâng, có lẽ một lần nữa: D
Narek

1

Tôi không thể tưởng tượng làm thế nào bạn đã đi lâu như vậy mà không sử dụng một do...whilevòng lặp.

Hiện có một trên một màn hình khác và có nhiều vòng lặp như vậy trong chương trình đó. Tất cả chúng đều có dạng:

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

1

Đây là một cấu trúc khá phổ biến trong máy chủ / người tiêu dùng:

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

các REPEAT UNTIL(cond)trở thành mộtdo {...} while(!cond)

Đôi khi, việc chờ đợi công việc (0) có thể khiến CPU rẻ hơn một cách khôn ngoan (thậm chí việc loại bỏ tính toán thời gian chờ có thể là một cải tiến với tỷ lệ đến rất cao). Hơn nữa, có nhiều kết quả lý thuyết về xếp hàng khiến số lượng phục vụ trong một khoảng thời gian bận rộn trở thành một thống kê quan trọng. (Xem ví dụ Kleinrock - Vol 1.)

Tương tự:

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

nơi check for and do other workcó thể đắt cắt cổ để đưa vào vòng lặp chính hoặc có thể là một nhân không hỗ trợ waitany(waitcontrol*,n)hoạt động kiểu hiệu quả hoặc có thể là tình huống mà một hàng đợi được ưu tiên có thể bỏ đói công việc kia và van tiết lưu được sử dụng làm điều khiển đói.

Kiểu cân bằng này có vẻ giống như một cuộc tấn công, nhưng nó có thể là cần thiết. Việc sử dụng một cách mù quáng các nhóm luồng sẽ đánh bại hoàn toàn những lợi ích về hiệu suất của việc sử dụng một chuỗi người chăm sóc với hàng đợi riêng tư cho cấu trúc dữ liệu phức tạp có tốc độ cập nhật cao vì việc sử dụng một nhóm luồng thay vì một luồng người chăm sóc sẽ yêu cầu triển khai an toàn cho luồng.

Tôi thực sự không muốn tranh luận về mã giả (ví dụ: liệu việc tắt được yêu cầu có nên được kiểm tra trong UNTIL hay không) hoặc các chuỗi caretaker so với nhóm luồng - điều này chỉ nhằm cung cấp cho một trường hợp sử dụng cụ thể của cấu trúc dòng điều khiển.


1

Đây là ý kiến ​​cá nhân của tôi, nhưng câu hỏi này đòi hỏi một câu trả lời bắt nguồn từ kinh nghiệm:

  • Tôi đã lập trình bằng C trong 38 năm và tôi không bao giờ sử dụng do/ whilevòng trong mã thông thường.

  • Cách sử dụng hấp dẫn duy nhất cho cấu trúc này là trong các macro nơi nó có thể bao bọc nhiều câu lệnh thành một câu lệnh duy nhất thông qua do { multiple statements } while (0)

  • Tôi đã thấy vô số ví dụ về do/ whilevòng lặp với phát hiện lỗi không có thật hoặc các lệnh gọi hàm dư thừa.

  • Lời giải thích của tôi cho quan sát này là các lập trình viên có xu hướng mô hình hóa các vấn đề không chính xác khi họ nghĩ về mặt do/ whilevòng lặp. Họ hoặc bỏ lỡ một điều kiện kết thúc quan trọng hoặc họ bỏ lỡ sự thất bại có thể xảy ra của điều kiện ban đầu mà họ chuyển đến phần cuối.

Vì những lý do này, tôi tin rằng ở đâu có vòng lặp do/ whilethì ở đó có lỗi và tôi thường xuyên thách thức các lập trình viên mới chỉ cho tôi một vòng lặp do/ whilemà tôi không thể phát hiện ra lỗi ở gần đó.

Có thể dễ dàng tránh được loại vòng lặp này: sử dụng a for (;;) { ... }và thêm các thử nghiệm kết thúc cần thiết nếu chúng thích hợp. Khá phổ biến là cần có nhiều hơn một bài kiểm tra như vậy.

Đây là một ví dụ cổ điển:

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

Điều này sẽ không thành công nếu tệp không kết thúc bằng dòng mới. Một ví dụ tầm thường của một tệp như vậy là tệp trống.

Một phiên bản tốt hơn là:

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

Ngoài ra, phiên bản này cũng ẩn cbiến:

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

Hãy thử tìm kiếm while (c != '\n');trong bất kỳ công cụ tìm kiếm nào và bạn sẽ tìm thấy các lỗi như công cụ này (truy xuất ngày 24 tháng 6 năm 2017):

Trong ftp://ftp.dante.de/tex-archive/biblio/tib/src/streams.c , hàm getword(stream,p,ignore), có một do/ whilevà chắc chắn đủ ít nhất 2 lỗi:

  • cđược định nghĩa là char
  • có một vòng lặp vô hạn tiềm năng while (c!='\n') c=getc(stream);

Kết luận: tránh do/ whilelặp lại và tìm lỗi khi bạn nhìn thấy lỗi.


1
Tôi thích tránh while() {}các vòng lặp hơn, bởi vì chúng an toàn hơn về mặt kiểm tra ban đầu, do đó có xu hướng khiến bạn "mất não", lười suy nghĩ về thuật toán tốt hơn, đặt hàng tấn kiểm tra lỗi không cần thiết, v.v. "Thiếu do{}while()cách sử dụng" là dấu hiệu của một lập trình viên kém (không có trí tuệ) hoặc một người mới, tạo ra mã chất lượng thấp hơn và chậm hơn. Vì vậy, trước tiên tôi tự hỏi bản thân "Có phải trường hợp while () {} nghiêm ngặt không?" Nếu không - Tôi chỉ sẽ cố gắng viết làm trong khi vòng lặp, mặc dù nó có thể đòi hỏi nhiều suy nghĩ và dữ liệu đầu vào chính xác
xakepp35

Tôi hiểu câu trả lời của bạn nhưng tôi có thể nghĩ về một số trường hợp có thể xảy ra mà điều này thực sự sẽ tốt hơn. Vui lòng xem while: prnt.sc/v2zy04 hoặc do while: prnt.sc/v2zyka . Tôi đã không làm lập trình viên lâu như bạn nhưng tôi không thể tìm thấy một lỗi nào trong đó. Rõ ràng là do while sạch hơn vì nó không yêu cầu kiểm tra thêm thời gian để vào vòng lặp. Nếu chúng ta đang nói về việc tối ưu hóa mã thì tôi muốn nói rằng điều đó khá rõ ràng.
Roy Berris

@RoyBerris: ví dụ của bạn thật thú vị. Trong một chương trình C, các hàm được sử dụng bên trong vòng lặp sẽ yêu cầu các bài kiểm tra bổ sung để đảm bảo chúng không bị lỗi. Điều này sẽ thêm lộn xộn và breakhoặc các returncâu lệnh. Biến vòng lặp do/ whilethành một for(;;)vòng lặp IMHO nhất quán hơn. Sự tẩy chay của tôi đối với tuyên bố này cũng giống như việc tôi từ chối sử dụng strncpy() ngay cả trong những trường hợp hiếm hoi mà nó sẽ là công cụ phù hợp: đừng để những người đọc bình thường tưởng tượng rằng những công trình này là vô hại, bởi vì trong hầu hết các trường hợp, chúng là những nguồn khó tìm thấy lỗi .
chqrlie

@chqrlie khi thấy câu trả lời ban đầu là về bất kỳ ngôn ngữ C nào, tôi đoán cả hai câu trả lời sẽ làm được. C # là "tiết kiệm" rất nhiều so với các phiên bản tiền nhiệm của chúng, vì vậy về mặt kỹ thuật, nó sẽ nhanh hơn khi thực hiện.
Roy Berris

0

whilevòng lặp kiểm tra điều kiện trước vòng lặp, do...whilevòng lặp kiểm tra điều kiện sau vòng lặp. Điều này rất hữu ích là bạn muốn điều kiện dựa trên các tác dụng phụ từ việc chạy vòng lặp hoặc, như các áp phích khác đã nói, nếu bạn muốn vòng lặp chạy ít nhất một lần.

Tôi hiểu bạn đến từ đâu, nhưng đây do-whilelà thứ hiếm khi sử dụng nhất và bản thân tôi cũng chưa bao giờ sử dụng. Bạn không làm điều đó sai.

Bạn không làm điều đó sai. Điều đó giống như nói rằng ai đó đang làm điều đó sai bởi vì họ chưa bao giờ sử dụng bytenguyên thủy. Nó không được sử dụng phổ biến.


0

Tình huống phổ biến nhất mà tôi gặp phải khi tôi sử dụng vòng lặp do/ whilelà trong một chương trình console nhỏ chạy dựa trên một số đầu vào và sẽ lặp lại bao nhiêu lần tùy thích. Rõ ràng là không có ý nghĩa gì đối với một chương trình console không chạy lần nào; nhưng ngoài lần đầu tiên, nó phụ thuộc vào người dùng - do đó do/ whilethay vì chỉ while.

Điều này cho phép người dùng thử một loạt các đầu vào khác nhau nếu muốn.

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

Tôi nghi ngờ rằng các nhà phát triển phần mềm ngày nay sử dụng do/ ngày whilecàng ít hơn, vì thực tế mọi chương trình dưới ánh nắng mặt trời đều có GUI của một số loại. Nó có ý nghĩa hơn với các ứng dụng bảng điều khiển, vì cần phải liên tục làm mới đầu ra để cung cấp hướng dẫn hoặc nhắc người dùng với thông tin mới. Ngược lại, với GUI, văn bản cung cấp thông tin đó cho người dùng có thể chỉ nằm trên một biểu mẫu và không bao giờ cần phải lặp lại theo chương trình.


0

Tôi sử dụng vòng lặp do-while mọi lúc khi đọc trong tệp. Tôi làm việc với rất nhiều tệp văn bản bao gồm nhận xét trong tiêu đề:

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

tôi sẽ sử dụng vòng lặp do-while để đọc đến dòng "column1 column2" để tôi có thể tìm kiếm cột quan tâm. Đây là mã giả:

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

Sau đó, tôi sẽ thực hiện một vòng lặp trong khi để đọc qua phần còn lại của tệp.


Hỗ trợ bình luận bất cứ nơi nào trong file sẽ là tầm thường (và đơn giản hóa mã tổng thể) nếu bạn chỉ cần đặt if (line[0]=='#') continue;ngay sau khi read_linecuộc gọi trong vòng lặp while chính của bạn ...
R .. GitHub DỪNG GIÚP ICE

Tôi không thể thấy cách tôi sẽ triển khai đề xuất của bạn để tìm tiêu đề. (dang, không thể tìm ra cách định dạng mã trong các nhận xét, xin hãy giúp đỡ!) header = false; while (! header) {line = read_line (); if (dòng [0] == '#') tiếp tục; header = true; } cái này rõ ràng hơn / đơn giản hơn với bạn?
bay

0

Là một lập trình viên chuyên nghiệp, nhiều dự án lập trình ở trường của tôi đã sử dụng các tương tác theo hướng menu văn bản. Hầu như tất cả đều sử dụng một cái gì đó giống như logic sau cho thủ tục chính:

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

Từ những ngày còn đi học, tôi nhận thấy rằng tôi sử dụng vòng lặp while thường xuyên hơn.


0

Một trong những ứng dụng tôi đã thấy nó là trong Oracle khi chúng tôi xem xét các tập kết quả.

Khi bạn có một tập hợp kết quả, trước tiên bạn tìm nạp từ nó (do) và từ thời điểm đó trở đi .. hãy kiểm tra xem quá trình tìm nạp có trả về một phần tử hay không (trong khi phần tử được tìm thấy ..) .. Điều tương tự có thể áp dụng cho bất kỳ phần tử nào khác " triển khai giống như tìm nạp ".


0

Tôi đã sử dụng nó trong một hàm trả về vị trí ký tự tiếp theo trong chuỗi utf-8:

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;
}

Lưu ý rằng, chức năng này được viết từ tâm và không được kiểm tra. Vấn đề là bạn phải làm bước đầu tiên và bạn phải làm nó trước khi bạn có thể đánh giá tình trạng bệnh.


Đó là một cách viết khó chịu *(unsigned char *)txt-0x80U<0x40.
R .. GitHub DỪNG TRỢ GIÚP NGAY LÚC NÀY

0

Bất kỳ loại đầu vào bảng điều khiển nào cũng hoạt động tốt với do-while vì bạn nhắc lần đầu tiên và nhắc lại bất cứ khi nào xác thực đầu vào không thành công.


0

Mặc dù có rất nhiều câu trả lời ở đây là của tôi. Tất cả đều được tối ưu hóa. Tôi sẽ đưa ra hai ví dụ trong đó một ví dụ nhanh hơn cái kia.

Trường hợp 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);
}

Trường hợp 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));

Vì vậy, có hai sẽ làm những điều chính xác giống nhau. Nhưng có một điểm khác biệt cơ bản và đó là while yêu cầu thêm một câu lệnh để nhập while. Điều này thật tệ vì giả sử mọi tình huống có thể xảy ra của Guidlớp đều đã được thực hiện ngoại trừ một biến thể. Điều này có nghĩa là tôi sẽ phải lặp lại 5,316,911,983,139,663,491,615,228,241,121,400,000nhiều lần. Mỗi khi đến cuối câu lệnh while, tôi sẽ cần phải string.IsNullOrEmpty(fileName)kiểm tra. Vì vậy, điều này sẽ chiếm một chút, một phần nhỏ công việc của CPU. Nhưng thực hiện nhiệm vụ rất nhỏ này gấp bao nhiêu lần sự kết hợp khả dĩ mà Guidlớp có và chúng ta đang nói về giờ, ngày, tháng hoặc thời gian thêm?

Tất nhiên đây là một ví dụ cực đoan vì có thể bạn sẽ không thấy điều này trong quá trình sản xuất. Nhưng nếu chúng ta nghĩ về thuật toán YouTube, rất có thể họ sẽ gặp phải việc tạo ra một ID trong đó một số ID đã được sử dụng. Vì vậy, nó đi xuống các dự án lớn và tối ưu hóa.


0

Tôi muốn hiểu hai điều này là:
while-> 'lặp lại cho đến khi',
do ... while-> 'lặp lại nếu'.


-1

Tôi đã chạy qua điều này trong khi nghiên cứu vòng lặp thích hợp để sử dụng cho tình huống tôi gặp phải. Tôi tin rằng điều này sẽ đáp ứng đầy đủ một tình huống phổ biến trong đó vòng lặp do .. while là cách triển khai tốt hơn vòng lặp while (ngôn ngữ C #, vì bạn đã nói rằng đó là ngôn ngữ chính của bạn cho công việc).

Tôi đang tạo danh sách các chuỗi dựa trên kết quả của một truy vấn SQL. Đối tượng được trả về bởi truy vấn của tôi là SQLDataReader. Đối tượng này có một hàm được gọi là Read () để chuyển đối tượng lên hàng dữ liệu tiếp theo và trả về true nếu có một hàng khác. Nó sẽ trả về false nếu không có hàng khác.

Sử dụng thông tin này, tôi muốn trả lại từng hàng cho một danh sách, sau đó dừng lại khi không còn dữ liệu để trả lại. Vòng lặp Do ... While hoạt động tốt nhất trong trường hợp này vì nó đảm bảo rằng việc thêm một mục vào danh sách sẽ xảy ra TRƯỚC KHI kiểm tra xem có hàng khác không. Lý do điều này phải được thực hiện TRƯỚC khi kiểm tra while (điều kiện) là khi nó kiểm tra, nó cũng tiến lên. Sử dụng vòng lặp while trong trường hợp này sẽ khiến nó bỏ qua hàng đầu tiên do bản chất của chức năng cụ thể đó.

Nói ngắn gọn:

Điều này sẽ không hoạt động trong tình huống của tôi.

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

   return list;

Điều này sẽ.

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

    return list;

Việc sử dụng SQLDataReader sẽ là các vòng lặp lồng nhau. Cuộc gọi bên trong Read()và phải là một vòng lặp while, vì Read()cuộc gọi đầu tiên truy cập vào hàng đầu tiên. Phần bên ngoài gọi NextResult()và phải là do-while, vì tập dữ liệu kết quả đầu tiên đang hoạt động trước lần NextResult()gọi đầu tiên . Các đoạn mã không có nhiều ý nghĩa, đặc biệt là vì cả hai nhận xét không khớp với mã và sai.
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;

Bạn nên trả lời câu hỏi của chính mình như thể bạn đang trả lời câu hỏi của người khác. Bao gồm lời giải thích cũng như mã
Simon
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.