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.
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.
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?
#define TRUE FALSEvà đi nghỉ.
Câu trả lời:
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ể đó.
Mkhông tồn tại std::numeric_limits<std::size_t>::max().
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ả.
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.)
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 Mlà size_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++;
}
0. Tại sao không for(size_t j = M; j-- != 0; )?
numeric_limits<size_t>::max().
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 và 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.
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.
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.