2008-09-25 49 views
5

Các vòng lặp for này là một trong những ví dụ cơ bản đầu tiên về bằng chứng chính xác về thuật toán. Họ có điều kiện chấm dứt khác nhau nhưng tương đương:Điều kiện kết thúc vòng lặp

1 for (int i = 0; i != N; ++i) 

2 for (int i = 0; i < N; ++i) 

Sự khác biệt trở nên rõ ràng trong postconditions:

  • Người đầu tiên đưa ra đảm bảo mạnh mẽ rằng i == N sau khi vòng lặp kết thúc.

  • Điều thứ hai chỉ đảm bảo yếu kém rằng i >= N sau khi vòng kết thúc chấm dứt, nhưng bạn sẽ bị cám dỗ giả định rằng i == N.

Nếu vì lý do nào đó tăng ++i là bao giờ thay đổi một cái gì đó giống như i += 2, hoặc nếu i bị biến đổi bên trong vòng lặp, hoặc nếu N là tiêu cực, chương trình có thể thất bại:

  • Các người đầu tiên có thể bị mắc kẹt trong một vòng lặp vô hạn. Nó không thành công sớm, trong vòng lặp có lỗi. Gỡ lỗi thật dễ dàng.

  • Vòng lặp thứ hai sẽ kết thúc và sau đó chương trình có thể không thành công do giả định không chính xác của bạn là i == N. Nó có thể thất bại từ vòng lặp gây ra lỗi, làm cho nó khó khăn để theo dõi trở lại. Hoặc nó có thể âm thầm tiếp tục làm điều gì đó bất ngờ, thậm chí còn tồi tệ hơn.

Bạn muốn điều kiện chấm dứt nào và tại sao? Có cân nhắc nào khác không? Tại sao nhiều lập trình viên biết điều này, từ chối áp dụng nó?

+1

Đối với những người đã đề cập rằng tôi không có sẵn bên ngoài cho, nó là hữu ích để lưu ý rằng giá trị của tôi lúc chấm dứt có thể được sử dụng khi lý luận về chương trình. Ví dụ: nếu bạn muốn thiết lập P (N) với một vòng lặp duy trì P bất biến (i), thì i == N cho bạn P (N) trong khi i> = N thì không. – mweerden

+0

+1 cho câu hỏi được viết rõ ràng thể hiện rõ giá trị của từng tùy chọn. –

Trả lời

1

Trong C++, sử dụng thử nghiệm != được ưu tiên cho tính tổng quát. Các bộ lặp trong C++ có các khái niệm khác nhau, như input iterator, forward iterator, bidirectional iterator, random access iterator, mỗi cái mở rộng trước đó với các khả năng mới. Để < hoạt động, trình vòng lặp truy cập ngẫu nhiên là bắt buộc, trong khi != chỉ yêu cầu trình lặp đầu vào.

2

Chúng ta không nên xem bộ đếm - nếu vì lý do nào đó, người đó thay đổi cách bộ đếm được tăng lên, chúng sẽ thay đổi điều kiện kết thúc và logic kết quả nếu nó được yêu cầu cho i == N.

Tôi thích điều kiện thứ hai vì nó chuẩn hơn và sẽ không dẫn đến vòng lặp vô tận.

1

Nếu bạn tin tưởng mã của mình, bạn cũng có thể thực hiện.

Nếu bạn muốn mã của bạn có thể đọc được và dễ hiểu (và do đó khoan dung hơn khi thay đổi từ người bạn phải giả định là klutz), tôi sẽ sử dụng một cái gì đó như;

for (int i = 0 ; i >= 0 && i < N ; ++i) 
1

tôi luôn luôn sử dụng # 2 như sau đó bạn có thể chắc chắn vòng lặp sẽ chấm dứt ... Dựa vào đó là bằng N sau đó được dựa trên một tác dụng phụ ... Bạn sẽ không chỉ được tốt hơn sử dụng biến N chính nó?

[sửa] Xin lỗi ...Tôi có nghĩa là # 2

0

Nói chung tôi muốn

for (int i = 0; i < N; ++i) 

Hình phạt dành cho một chương trình lỗi trong sản xuất, dường như rất nhiều ít nghiêm trọng, bạn sẽ không có một sợi bị mắc kẹt mãi mãi trong một vòng lặp for, một tình huống có thể rất nguy hiểm và rất khó chẩn đoán.

Ngoài ra, nói chung, tôi muốn tránh các loại vòng này có lợi cho các vòng lặp phong cách foreach dễ đọc hơn.

1

Tôi nghĩ hầu hết các lập trình viên đều sử dụng phiên bản thứ 2, vì nó giúp tìm ra những gì xảy ra bên trong vòng lặp. Tôi có thể nhìn vào nó và "biết" rằng tôi sẽ bắt đầu bằng 0 và chắc chắn sẽ nhỏ hơn N.

Biến thể thứ nhất không có chất lượng này. Tôi có thể nhìn vào nó, và tất cả những gì tôi biết là tôi sẽ bắt đầu bằng 0 và nó sẽ không bao giờ bằng N. Không hoàn toàn hữu ích.

Bất kể bạn chấm dứt vòng lặp như thế nào, luôn luôn rất thận trọng khi sử dụng biến điều khiển vòng lặp bên ngoài vòng lặp. Trong ví dụ của bạn, bạn (đúng) khai báo i bên trong vòng lặp, do đó, nó không nằm trong phạm vi bên ngoài vòng lặp và câu hỏi về giá trị của nó là moot ...

Tất nhiên, biến thể thứ 2 cũng có lợi thế là tất cả các tài liệu tham khảo C tôi đã thấy sử dụng :-)

0

Tôi thích sử dụng # 2, chỉ vì tôi cố gắng không mở rộng ý nghĩa của i bên ngoài vòng lặp. Nếu tôi theo dõi một biến như thế, tôi sẽ tạo một thử nghiệm bổ sung. Một số có thể nói điều này là không cần thiết hoặc không hiệu quả, nhưng nó nhắc nhở người đọc về ý định của tôi: Tại thời điểm này, tôi phải bằng N

@timyates - Tôi đồng ý người ta không nên dựa vào tác dụng phụ

4

Tôi có xu hướng sử dụng hình thức thứ hai, đơn giản bởi vì sau đó tôi có thể chắc chắn hơn rằng vòng lặp sẽ kết thúc. I E. khó hơn để giới thiệu một lỗi không chấm dứt bằng cách thay đổi i bên trong vòng lặp.

Tất nhiên, nó cũng có lợi thế một chút lười biếng của là một nhân vật ít hơn để gõ;)

Tôi cũng muốn tranh luận, rằng trong một ngôn ngữ với quy mô hợp lý, như tôi được khai báo bên trong cấu trúc vòng lặp, nó không nên có sẵn bên ngoài vòng lặp. Điều này sẽ giảm thiểu sự phụ thuộc vào tôi bằng N ở cuối vòng lặp ...

+0

Tôi đã suy nghĩ cùng một điều, chắc chắn hầu hết các ngôn ngữ sẽ có tôi chỉ ở phạm vi vòng lặp. +1 –

+0

Trong Perl: 'for (my $ i = 0; $ i

0

Tôi nghĩ bạn đã nói rất rõ sự khác biệt giữa hai loại. Tôi không có ý kiến ​​sau đây, mặc dù:

  • này không phải là "ngôn ngữ-agnostic", tôi có thể xem các ví dụ của bạn là trong C++ nhưng có là ngôn ngữ mà bạn không được phép thay đổi biến vòng lặp bên trong vòng lặp và những người khác không đảm bảo rằng giá trị của chỉ mục có thể sử dụng sau vòng lặp (và một số làm cả hai).

  • Bạn đã khai báo chỉ số i trong số for vì vậy tôi sẽ không đặt cược vào giá trị i sau vòng lặp. Các ví dụ có một chút sai lệch khi chúng giả định rằng giả định rằng for là một vòng lặp nhất định.Trong thực tế, đó chỉ là cách viết thuận tiện hơn:

    // version 1 
    { int i = 0; 
        while (i != N) { 
        ... 
        ++i; 
        } 
    } 
    

    Lưu ý cách thức i không được xác định sau khối.

Nếu một lập trình viên biết tất cả những điều trên sẽ không làm cho giả định chung về giá trị của i và sẽ đủ khôn ngoan để chọn i<N như các điều kiện kết thúc, để đảm bảo rằng tình trạng lối ra sẽ được dần dần đáp ứng.

0

Sử dụng một trong hai điều trên trong C# sẽ gây ra một lỗi biên dịch nếu bạn sử dụng i bên ngoài vòng lặp

0

tôi thích điều này đôi khi:

for (int i = 0; (i <= (n-1)); i++) { ... } 

Phiên bản này cho thấy trực tiếp một loạt các giá trị mà tôi có thể có. Nhận của tôi về kiểm tra giới hạn dưới và trên của phạm vi là nếu bạn thực sự cần điều này, mã của bạn có quá nhiều tác dụng phụ và cần phải được viết lại.

Phiên bản khác:

for (int i = 1; (i <= n); i++) { ... } 

giúp bạn xác định tần suất thân vòng lặp được gọi. Điều này cũng có trường hợp sử dụng hợp lệ.

0

Đối với công việc lập trình nói chung tôi thích

for (int i = 0; i < N; ++i) 

để

for (int i = 0; i != N; ++i) 

Bởi vì nó là ít lỗi hơn, đặc biệt là khi mã được refactored. Tôi đã thấy loại mã này biến thành một vòng lặp vô hạn một cách tình cờ.

Đối số đó cho rằng "bạn sẽ bị cám dỗ giả định rằng i == N", tôi không tin là sự thật. Tôi chưa bao giờ thực hiện giả định đó hoặc thấy một lập trình viên khác làm cho nó.

0

Từ quan điểm của tôi về xác minh chính thức và phân tích chấm dứt tự động, tôi rất thích # 2 (<). Nó khá dễ dàng để theo dõi rằng một số biến được tăng lên (trước var = x, sau var = x+n đối với một số số không âm n). Tuy nhiên, nó không phải là dễ dàng để thấy rằng i==N cuối cùng nắm giữ. Đối với điều này, người ta cần suy ra rằng i được tăng lên chính xác 1 trong mỗi bước, trong đó (trong các ví dụ phức tạp hơn) có thể bị mất do trừu tượng.

Nếu bạn nghĩ về vòng lặp tăng thêm hai (i = i + 2), ý tưởng chung này trở nên dễ hiểu hơn. Để đảm bảo việc chấm dứt, bây giờ cần biết rằng i%2 == N%2, trong khi điều này là không liên quan khi sử dụng < làm điều kiện.

Các vấn đề liên quan