Vòng lặp 'for' dường như thực tế là vô hạn


82

Tôi đang gỡ lỗi một số mã vào lúc này và tôi bắt gặp dòng này:

for (std::size_t j = M; j <= M; --j)

(Được viết bởi sếp của tôi, người đang đi nghỉ.)

Nó trông thực sự kỳ lạ đối với tôi.

Nó làm gì? Đối với tôi, nó giống như một vòng lặp vô hạn.


31
size_tlà unsigned, do đó, được đảm bảo sẽ quấn quanh giá trị lớn nhất của nó khi cố gắng không chấp nhận số 0, kết thúc vòng lặp. Tuy nhiên, vẫn còn mã khủng khiếp.
BoBTFish

4
@Bathsheba Điều gì Msẽ xảy ra nếu xảy ra là giá trị tối đa size_t? Bạn vẫn nghĩ rằng nó là thông minh?
Thorsten Dittmar

8
@Barmar Không xuất hiện nhiều cũng không phải Không bao giờ xuất hiện , vì vậy có xác suất> 0. Có nghĩa là giải pháp này thật ngu ngốc khi có nhiều cách đơn giản.
Thorsten Dittmar 22/12/16

38
Tôi không hiểu sự phản đối. Câu hỏi này rất rõ ràng - tôi không thể thấy nó có thể được cải thiện như thế nào. Từ một người dùng mới, câu hỏi này thực sự thú vị !
Bathsheba

17
#define TRUE FALSEvà đi nghỉ.
Leo Heinsaar

Câu trả lời:


68

std::size_tđược đảm bảo bởi tiêu chuẩn C ++ là một unsignedkiểu. Và nếu bạn giảm một unsignedloại từ 0, tiêu chuẩn đảm bảo rằng kết quả của việc làm đó là giá trị lớn nhất cho loại đó.

Giá trị được bao bọc đó luôn lớn hơn hoặc bằng M1 để vòng lặp kết thúc.

Vì vậy, j <= Mkhi áp dụng cho một unsignedkiểu là một cách thuận tiện để nói "chạy vòng lặp đến 0 rồi dừng lại".

Các lựa chọn thay thế như chạy jmột cái lớn hơn bạn muốn và thậm chí sử dụng toán tử slide for (std::size_t j = M + 1; j --> 0; ){ tồn tại, được cho là rõ ràng hơn mặc dù yêu cầu nhập nhiều hơn. Mặc dù vậy, tôi đoán có một điểm bất lợi (ngoài tác động gây hoang mang mà nó tạo ra khi kiểm tra lần đầu) là nó không chuyển tốt sang các ngôn ngữ không có kiểu không dấu, chẳng hạn như Java.

Cũng lưu ý rằng sơ đồ mà sếp của bạn đã chọn "vay" một giá trị có thể có từ unsignedtập hợp: điều đó xảy ra trong trường hợp này mà Mđược đặt thành std::numeric_limits<std::size_t>::max()sẽ không có hành vi chính xác. Trong thực tế, trong trường hợp đó, vòng lặp là vô hạn . (Đó có phải là những gì bạn đang quan sát không?) Bạn nên chèn một nhận xét cho hiệu ứng đó trong mã, và thậm chí có thể khẳng định về điều kiện cụ thể đó.


1 Đối tượng Mkhông tồn tại std::numeric_limits<std::size_t>::max().


7
Tôi đang đổ lỗi cho thời tiết.
Gà trống có mũ

3
Nếu M là std::numeric_limits<std::size_t>::max()sau đó M + 1sẽ được không, và for (std::size_t j = M + 1; j --> 0; )vòng lặp sẽ không lặp ở tất cả.
Pete Kirkham

51
Đừng gọi nó là "nhà điều hành slide". Không có toán tử như vậy trong C ++, nó chỉ là một sự xáo trộn thông minh.
Bạn

26
@DimitarMirchev: Vì nó không phải là toán tử. Đó là hai toán tử, với khoảng cách lẻ. Gọi nó là một thành ngữ nếu bạn khăng khăng hỏi người được phỏng vấn những câu hỏi không liên quan (nhưng lý tưởng nhất là hỏi họ cách họ triển khai chức năng thay vì hỏi về cú pháp "thông minh").
Bạn

1
Giả sử đó size_tlà 64 bit, sẽ mất vài trăm năm để quan sát hành vi không chính xác trong trường hợp cạnh. (Trừ khi optimizer có thể thoát khỏi vòng lặp.)
Carsten S

27

Những gì sếp của bạn có thể đang cố gắng làm là đếm ngược từ M0 đến 0, thực hiện một số hành động trên mỗi số.

Thật không may, có một trường hợp cạnh mà điều đó thực sự sẽ cung cấp cho bạn một vòng lặp vô hạn, vòng lặp Msize_tgiá trị lớn nhất mà bạn có thể có. Và, mặc dù nó được xác định rõ ràng giá trị không dấu sẽ làm gì khi bạn giảm nó từ 0, nhưng tôi vẫn khẳng định rằng bản thân mã là một ví dụ về suy nghĩ cẩu thả, đặc biệt là vì có một giải pháp hoàn toàn khả thi mà không có thiếu sót của các ông chủ của bạn.

Biến thể an toàn hơn đó (và dễ đọc hơn, theo ý kiến ​​của tôi, trong khi vẫn duy trì giới hạn phạm vi chặt chẽ), sẽ là:

{
    std::size_t j = M;
    do {
        doSomethingWith(j);
    } while (j-- != 0);
}

Ví dụ, hãy xem đoạn mã sau:

#include <iostream>
#include <cstdint>
#include <climits>
int main (void) {
    uint32_t quant = 0;
    unsigned short us = USHRT_MAX;
    std::cout << "Starting at " << us;
    do {
        quant++;
    } while (us-- != 0);
    std::cout << ", we would loop " << quant << " times.\n";
    return 0;
}

Về cơ bản, điều này thực hiện tương tự với một unsigned shortvà bạn có thể thấy nó xử lý mọi giá trị:

Starting at 65535, we would loop 65536 times.

Thay thế do..whilevòng lặp trong đoạn mã trên bằng những gì sếp của bạn đã làm về cơ bản sẽ dẫn đến một vòng lặp vô hạn. Hãy thử nó và xem:

for (unsigned int us2 = us; us2 <= us; --us2) {
    quant++;
}

7
Bây giờ, trường hợp cạnh là 0. Tại sao không for(size_t j = M; j-- != 0; )?
LogicStuff

Vâng, điều này xảy ra với trường hợp góc có lẽ thường xuyên hơn numeric_limits<size_t>::max().
Bathsheba

7
@Logic et al, không có trường hợp cạnh nào trong phương pháp tôi đã cung cấp, tôi đã thêm mã để hiển thị điều này. Với bạn đề xuất giải pháp, một giá trị ban đầu của M == 0sẽ dẫn đến các yếu tố 0 không được xử lý, vì vậy nó không có một trường hợp cạnh. Sử dụng do..whilephương pháp kiểm tra sau của tôi loại bỏ hoàn toàn trường hợp cạnh. Nếu bạn thử nó với M == 1, bạn sẽ thấy nó có cả 1 0. Tương tự, hãy bắt đầu với max_size_t(bất cứ điều gì xảy ra) và nó sẽ bắt đầu thành công tại thời điểm đó, sau đó giảm xuống và bao gồm cả 0.
paxdiablo

Điều thú vị là trường hợp này thúc đẩy việc sử dụng những gì một số người sẽ nói là "mã không có cấu trúc" bởi vì nó sử dụng " exitif", cụ thể là { j =M; for(;;){ f(j); if( j == 0 )break; j -= 1; } }. Người ta thậm chí có thể cần thay thế breakbằng a gotonếu mọi thứ được lồng vào nhau, vì C không có vòng lặp được đặt tên. Nếu "có cấu trúc" có nghĩa là "dễ bị suy luận về các phần" thì nó là có cấu trúc (bố cục giúp ích!), Và nếu nó có nghĩa là "dễ bị xác nhận chính thức bằng lý luận về điều kiện trước và sau". Mặc dù trong trường hợp này có hiệu j--quả, nhưng phong cách exitif có thể hợp lý khi cần chuyển đổi phức tạp hơn.
PJTraill

@PJTraill Tôi không thể biết những gì bạn đang nói là có cấu trúc và những gì bạn đang nói không có cấu trúc. Lý luận về do-while rất đơn giản. Nó không "sử dụng" một lối ra nào nhiều hơn, hoặc theo bất kỳ cách nào khác không có cấu trúc, trong khi thực hiện.
philipxy
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.