Một số cách tốt hơn để tránh do-while (0); hack trong C ++?


233

Khi dòng mã như thế này:

if(check())
{
  ...
  ...
  if(check())
  {
    ...
    ...
    if(check())
    {
      ...
      ...
    }
  }
}

Tôi thường thấy công việc này xung quanh để tránh dòng mã lộn xộn ở trên:

do {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);

Một số cách tốt hơn để tránh công việc này / hack để nó trở thành mã cấp cao hơn (cấp ngành)?

Bất kỳ đề xuất nào ra khỏi hộp đều được chào đón!


38
RAII và ném ngoại lệ.
ta.speot.is

135
Đối với tôi, đây có vẻ là thời điểm tốt để sử dụng goto- nhưng tôi chắc chắn rằng ai đó sẽ đánh dấu tôi vì đã gợi ý điều đó, vì vậy tôi không viết câu trả lời cho hiệu ứng đó. Để có một DÀI do ... while(0);dường như là điều sai.
Thảm Petersson

42
@dasblinkenlight: Vâng, thực sự. Nếu bạn sẽ sử dụng goto, hãy trung thực về nó và thực hiện nó một cách cởi mở, đừng che giấu nó bằng cách sử dụng breakdo ... while
Mats Petersson

44
@MatsPeterson: Làm cho nó một câu trả lời, tôi sẽ cung cấp cho bạn +1 cho gotonỗ lực phục hồi của bạn . :)
wilx

27
@ ta.speot.is: "RAII và ném ngoại lệ". Bằng cách đó, bạn sẽ mô phỏng kiểm soát dòng chảy với các ngoại lệ. Tức là nó giống như sử dụng phần cứng đắt tiền, chảy máu như búa hoặc chặn giấy. Bạn có thể làm điều đó, nhưng điều đó chắc chắn trông giống như một hương vị rất xấu đối với tôi.
SigTerm

Câu trả lời:


309

Nó được coi là thực hành có thể chấp nhận để cô lập các quyết định này trong một hàm và sử dụng returns thay vì breaks. Mặc dù tất cả các kiểm tra này tương ứng với cùng một mức độ trừu tượng như của hàm, nhưng đó là cách tiếp cận khá logic.

Ví dụ:

void foo(...)
{
   if (!condition)
   {
      return;
   }
   ...
   if (!other condition)
   {
      return;
   }
   ...
   if (!another condition)
   {
      return;
   }
   ... 
   if (!yet another condition)
   {
      return;
   }
   ...
   // Some unconditional stuff       
}

22
@MatsPeterson: "Isolate in function" có nghĩa là tái cấu trúc thành một chức năng mới chỉ thực hiện các bài kiểm tra.
MSalters

35
+1. Đây cũng là một câu trả lời tốt. Trong C ++ 11, hàm bị cô lập có thể là lambda, vì nó cũng có thể nắm bắt các biến cục bộ, do đó làm cho mọi thứ trở nên dễ dàng!
Nawaz

4
@deworde: Tùy theo tình huống, giải pháp này có thể trở nên dài hơn và khó đọc hơn goto. Thật không may, vì C ++ không cho phép các khiếm khuyết chức năng cục bộ, bạn sẽ phải di chuyển chức năng đó (chức năng bạn sẽ quay trở lại) ở một nơi khác, điều này làm giảm khả năng đọc. Sau đó, nó có thể có hàng tá tham số, vì có hàng tá tham số là một điều xấu, bạn sẽ quyết định bọc chúng thành struct, điều này sẽ tạo ra kiểu dữ liệu mới. Quá nhiều gõ cho một tình huống đơn giản.
SigTerm

24
@Damon nếu có bất cứ điều gì, returnsạch hơn bởi vì bất kỳ độc giả nào cũng biết ngay rằng nó hoạt động chính xác và những gì nó làm. Với gotobạn phải nhìn xung quanh để xem nó là để làm gì, và để chắc chắn rằng không có sai lầm nào được thực hiện. Việc ngụy trang lợi ích.
R. Martinho Fernandes

11
@SigTerm: Đôi khi mã nên được chuyển sang một chức năng riêng chỉ để giữ kích thước của từng chức năng xuống kích thước dễ hiểu. Nếu bạn không muốn trả tiền cho một cuộc gọi chức năng, hãy đánh dấu nó forceinline.
Ben Voigt

257

Có những lúc sử dụng goto thực sự là câu trả lời ĐÚNG - ít nhất là với những người không được nuôi dưỡng trong niềm tin tôn giáo rằng " gotokhông bao giờ có thể là câu trả lời, bất kể câu hỏi là gì" - và đây là một trong những trường hợp đó.

Mã này đang sử dụng hack do { ... } while(0);cho mục đích duy nhất là mặc quần áo gotonhư một break. Nếu bạn sẽ sử dụng goto, sau đó hãy cởi mở về nó. Không có điểm nào trong việc tạo mã HARDER để đọc.

Một tình huống cụ thể là khi bạn có nhiều mã với các điều kiện khá phức tạp:

void func()
{
   setup of lots of stuff
   ...
   if (condition)
   {
      ... 
      ...
      if (!other condition)
      {
          ...
          if (another condition)
          {
              ... 
              if (yet another condition)
              {
                  ...
                  if (...)
                     ... 
              }
          }
      }
  .... 

  }
  finish up. 
}

Nó thực sự có thể làm cho nó rõ ràng rằng mã là chính xác bằng cách không có logic phức tạp như vậy.

void func()
{
   setup of lots of stuff
   ...
   if (!condition)
   {
      goto finish;
   }
   ... 
   ...
   if (other condition)
   {
      goto finish;
   }
   ...
   if (!another condition)
   {
      goto finish;
   }
   ... 
   if (!yet another condition)
   {
      goto finish;
   }
   ... 
   .... 
   if (...)
         ...    // No need to use goto here. 
 finish:
   finish up. 
}

Chỉnh sửa: Để làm rõ, tôi không có nghĩa là đề xuất sử dụng goto như một giải pháp chung. Nhưng có những trường hợp gotolà một giải pháp tốt hơn so với các giải pháp khác.

Ví dụ, hãy tưởng tượng rằng chúng tôi đang thu thập một số dữ liệu và các điều kiện khác nhau đang được kiểm tra là một loại "đây là phần cuối của dữ liệu được thu thập" - tùy thuộc vào một số loại dấu "tiếp tục / kết thúc" khác nhau tùy thuộc vào nơi bạn đang ở trong luồng dữ liệu.

Bây giờ, khi chúng ta hoàn thành, chúng ta cần lưu dữ liệu vào một tệp.

Và vâng, thường có những giải pháp khác có thể cung cấp một giải pháp hợp lý, nhưng không phải lúc nào cũng vậy.


76
Không đồng ý. gotocó thể có một nơi, nhưng goto cleanupkhông. Dọn dẹp được thực hiện với RAII.
MSalters

14
@MSalters: Điều đó giả định rằng việc dọn dẹp liên quan đến điều gì đó có thể được giải quyết với RAII. Có lẽ tôi nên nói "lỗi phát hành" hoặc một số như vậy thay vào đó.
Mats Petersson

25
Vì vậy, tiếp tục nhận được thông tin về điều này, có lẽ từ những người có niềm tin tôn giáo gotokhông bao giờ là câu trả lời đúng. Tôi sẽ đánh giá cao nếu có một bình luận ...
Mats Petersson

19
mọi người ghét gotovì bạn phải nghĩ sử dụng nó / để hiểu một chương trình sử dụng nó ... Mặt khác, bộ vi xử lý được xây dựng jumpsconditional jumps... vì vậy vấn đề là ở một số người, không phải với logic hay cái gì khác.
woliveirajr

17
+1 để sử dụng thích hợp goto. RAII không phải là giải pháp chính xác nếu có lỗi (không phải là ngoại lệ), vì đó sẽ là một sự lạm dụng các ngoại lệ.
Joe

82

Bạn có thể sử dụng một mẫu tiếp tục đơn giản với một boolbiến:

bool goOn;
if ((goOn = check0())) {
    ...
}
if (goOn && (goOn = check1())) {
    ...
}
if (goOn && (goOn = check2())) {
    ...
}
if (goOn && (goOn = check3())) {
    ...
}

Chuỗi thực thi này sẽ dừng lại ngay khi checkNtrả về a false. Không có check...()cuộc gọi tiếp theo sẽ được thực hiện do ngắn mạch của &&nhà điều hành. Hơn nữa, trình biên dịch tối ưu hóa là đủ thông minh để nhận ra thiết lập đó goOnđể falselà một con đường một chiều, và chèn thiếu goto endcho bạn. Kết quả là, hiệu suất của mã ở trên sẽ giống hệt với do/ while(0), chỉ khi không có một cú đánh đau đớn vào khả năng đọc của nó.


30
Nhiệm vụ bên trong ifđiều kiện trông rất đáng ngờ.
Mikhail

90
@Mikhail Chỉ để một mắt chưa được huấn luyện.
dasblinkenlight

20
Tôi đã sử dụng kỹ thuật này trước đây và nó luôn làm tôi khó chịu khi tôi nghĩ rằng trình biên dịch sẽ phải tạo mã để kiểm tra từng ifcái bất kể goOnlà cái đầu tiên có bị lỗi hay không (trái ngược với việc nhảy / thoát ra) ... nhưng tôi chỉ chạy thử nghiệm và ít nhất VS2012 đủ thông minh để đoản mạch mọi thứ sau lần sai đầu tiên. Tôi sẽ sử dụng nó thường xuyên hơn. lưu ý: nếu bạn sử dụng goOn &= checkN()sau đó checkN()sẽ luôn luôn chạy ngay cả khi goOnđã falsevào lúc bắt đầu của if(tức là, không làm điều đó).
đánh dấu

11
@Nawaz: Nếu bạn có một suy nghĩ chưa được thực hiện, thay đổi tùy ý trên toàn bộ cơ sở mã, bạn có một vấn đề lớn hơn nhiều so với chỉ các bài tập bên trong ifs.
Idelic

6
@sisharp Elegance là như vậy trong mắt của kẻ si tình! Tôi không hiểu làm thế nào trên thế giới lạm dụng một cấu trúc vòng lặp bằng cách nào đó có thể được coi là "thanh lịch", nhưng có lẽ đó chỉ là tôi.
dasblinkenlight

38
  1. Cố gắng trích xuất mã thành một hàm riêng biệt (hoặc có thể nhiều hơn một). Sau đó trả về từ chức năng nếu kiểm tra thất bại.

  2. Nếu nó được kết hợp quá chặt chẽ với mã xung quanh để làm điều đó và bạn không thể tìm cách giảm khớp nối, hãy xem mã sau khối này. Có lẽ, nó dọn sạch một số tài nguyên được sử dụng bởi hàm. Cố gắng quản lý các tài nguyên này bằng cách sử dụng một đối tượng RAII ; sau đó thay thế từng tinh ranh breakbằng return(hoặc throw, nếu phù hợp hơn) và để cho kẻ hủy diệt đối tượng dọn dẹp cho bạn.

  3. Nếu luồng chương trình (nhất thiết) quá nguệch ngoạc đến mức bạn thực sự cần một goto, thì hãy sử dụng nó thay vì cho nó một sự ngụy trang kỳ lạ.

  4. Nếu bạn có các quy tắc mã hóa bị cấm một cách mù quáng gotovà bạn thực sự không thể đơn giản hóa luồng chương trình, thì có lẽ bạn sẽ phải ngụy trang nó với bản dohack của mình .


4
Tôi khiêm tốn gửi RAII, trong khi hữu ích, không phải là viên đạn bạc. Khi bạn thấy mình sắp viết một lớp convert-goto-to-RAII không có công dụng nào khác, tôi chắc chắn nghĩ rằng bạn sẽ được phục vụ tốt hơn chỉ bằng cách sử dụng thành ngữ "goto-end-of-the-world" mà mọi người đã đề cập.
ngốc nghếch

1
@busy_wait: Thật vậy, RAII không thể giải quyết mọi thứ; đó là lý do tại sao câu trả lời của tôi không dừng lại ở điểm thứ hai, nhưng tiếp tục đề xuất gotonếu đó thực sự là một lựa chọn tốt hơn.
Mike Seymour

3
Tôi đồng ý, nhưng tôi nghĩ rằng đó là một ý tưởng tồi để viết các lớp chuyển đổi goto-RAII và tôi nghĩ rằng nó nên được nêu rõ ràng.
ngốc nghếch

@idoby điều gì về việc viết một lớp mẫu RAII và khởi tạo nó với bất kỳ việc sử dụng đơn lẻ nào bạn cần trong lambda
Caleth

@Caleth nghe có vẻ như bạn đang phát minh lại bác sĩ / bác sĩ?
ngốc nghếch

37

TLDR : RAII , mã giao dịch (chỉ đặt kết quả hoặc trả lại công cụ khi nó đã được tính toán) và các ngoại lệ.

Câu trả lời dài:

Trong C , cách thực hành tốt nhất cho loại mã này là thêm EXIT / CLEANUP / nhãn khác trong mã, trong đó việc dọn sạch tài nguyên cục bộ xảy ra và mã lỗi (nếu có) được trả về. Đây là cách thực hành tốt nhất vì nó phân tách mã một cách tự nhiên thành khởi tạo, tính toán, cam kết và trả về:

error_code_type c_to_refactor(result_type *r)
{
    error_code_type result = error_ok; //error_code_type/error_ok defd. elsewhere
    some_resource r1, r2; // , ...;
    if(error_ok != (result = computation1(&r1))) // Allocates local resources
        goto cleanup;
    if(error_ok != (result = computation2(&r2))) // Allocates local resources
        goto cleanup;
    // ...

    // Commit code: all operations succeeded
    *r = computed_value_n;
cleanup:
    free_resource1(r1);
    free_resource2(r2);
    return result;
}

Trong C, trong hầu hết các cơ sở mã, mã if(error_ok != ...gotomã thường được ẩn đằng sau một số macro tiện lợi ( RET(computation_result),ENSURE_SUCCESS(computation_result, return_code) , vv).

C ++ cung cấp các công cụ bổ sung qua C :

  • Chức năng khối dọn dẹp có thể được triển khai dưới dạng RAII, có nghĩa là bạn không còn cần toàn bộ cleanupkhối và cho phép mã máy khách để thêm các câu lệnh trả về sớm.

  • Bạn ném bất cứ khi nào bạn không thể tiếp tục, chuyển tất cả các if(error_ok != ...cuộc gọi thẳng.

Mã C ++ tương đương:

result_type cpp_code()
{
    raii_resource1 r1 = computation1();
    raii_resource2 r2 = computation2();
    // ...
    return computed_value_n;
}

Đây là cách thực hành tốt nhất vì:

  • Đó là rõ ràng (có nghĩa là, trong khi xử lý lỗi không rõ ràng, luồng chính của thuật toán là)

  • Thật đơn giản để viết mã máy khách

  • Nó là tối thiểu

  • Nó đơn giản

  • Nó không có cấu trúc mã lặp đi lặp lại

  • Nó không sử dụng macro

  • Nó không sử dụng các do { ... } while(0)cấu trúc kỳ lạ

  • Nó có thể được sử dụng lại với nỗ lực tối thiểu (nghĩa là, nếu tôi muốn sao chép cuộc gọi sang computation2();một chức năng khác, tôi không phải đảm bảo rằng tôi đã thêm một do { ... } while(0)mã mới, cũng không phải #definelà macro bao bọc goto và nhãn dọn dẹp, cũng không phải còn gì nữa không).


+1. Đây là những gì tôi cố gắng sử dụng, sử dụng RAII hoặc một cái gì đó. shared_ptrvới một deleter tùy chỉnh có thể làm rất nhiều thứ. Thậm chí dễ dàng hơn với lambdas trong C ++ 11.
Macke

Đúng, nhưng nếu bạn kết thúc bằng shared_ptr cho những thứ không phải là con trỏ, hãy xem xét ít nhất là typedef-ing nó: namespace xyz { typedef shared_ptr<some_handle> shared_handle; shared_handle make_shared_handle(a, b, c); };Trong trường hợp này (với make_handleviệc đặt loại deleter chính xác khi xây dựng), tên của loại không còn gợi ý nó là con trỏ .
utnapistim

21

Tôi đang thêm một câu trả lời cho sự hoàn chỉnh. Một số câu trả lời khác chỉ ra rằng khối điều kiện lớn có thể được tách ra thành một hàm riêng biệt. Nhưng như đã được chỉ ra một số lần là cách tiếp cận này tách mã có điều kiện khỏi bối cảnh ban đầu. Đây là một lý do mà lambdas đã được thêm vào ngôn ngữ trong C ++ 11. Sử dụng lambdas đã được đề xuất bởi những người khác nhưng không có mẫu rõ ràng được cung cấp. Tôi đã đặt một trong câu trả lời này. Điều gây ấn tượng với tôi là nó cảm thấy rất giống với do { } while(0)cách tiếp cận theo nhiều cách - và có lẽ điều đó có nghĩa là nó vẫn còn gotongụy trang ....

earlier operations
...
[&]()->void {

    if (!check()) return;
    ...
    ...
    if (!check()) return;
    ...
    ...
    if (!check()) return;
    ...
    ...
}();
later operations

7
Đối với tôi, bản hack này trông tệ hơn bản ... trong khi hack.
Michael

18

Chắc chắn không phải câu trả lời, mà là một câu trả lời (vì sự hoàn chỉnh)

Thay vì :

do {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);

Bạn có thể viết:

switch (0) {
case 0:
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
}

Đây vẫn là một goto ngụy trang, nhưng ít nhất nó không phải là một vòng lặp nữa. Điều đó có nghĩa là bạn sẽ không phải kiểm tra rất cẩn thận, không có một số tiếp tục ẩn ở đâu đó trong khối.

Cấu trúc này cũng đủ đơn giản để bạn có thể hy vọng trình biên dịch sẽ tối ưu hóa nó đi.

Theo đề xuất của @jamesdlin, bạn thậm chí có thể ẩn nó đằng sau một macro như

#define BLOC switch(0) case 0:

Và sử dụng nó như

BLOC {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
}

Điều này là có thể bởi vì cú pháp ngôn ngữ C mong đợi một câu lệnh sau một chuyển đổi, không phải là một khối được đặt dấu ngoặc và bạn có thể đặt nhãn trường hợp trước câu lệnh đó. Cho đến bây giờ tôi đã không nhìn thấy điểm cho phép điều đó, nhưng trong trường hợp cụ thể này, thật tiện lợi để ẩn công tắc đằng sau một macro đẹp.


2
Ôi, thông minh. Bạn thậm chí có thể ẩn nó đằng sau một macro thích define BLOCK switch (0) case 0:và sử dụng nó như thế nào BLOCK { ... break; }.
jamesdlin

@jamesdlin: điều đó chưa bao giờ xảy ra với tôi trước đây vì nó có thể hữu ích khi đặt một trường hợp trước khung mở của một công tắc. Nhưng nó thực sự được C cho phép và trong trường hợp này thật tiện để viết một macro đẹp.
kriss

15

Tôi muốn giới thiệu một cách tiếp cận tương tự như câu trả lời của Mats trừ đi những thứ không cần thiết goto. Chỉ đặt logic điều kiện trong hàm. Bất kỳ mã nào luôn chạy nên đi trước hoặc sau khi hàm được gọi trong trình gọi:

void main()
{
    //do stuff always
    func();
    //do other stuff always
}

void func()
{
    if (!condition)
        return;
    ...
    if (!other condition)
        return;
    ...
    if (!another condition)
        return;
    ... 
    if (!yet another condition)
        return;
    ...
}

3
Nếu bạn phải có được một tài nguyên khác ở giữa func, bạn cần tìm ra một chức năng khác (theo mẫu của bạn). Nếu tất cả các hàm bị cô lập này cần cùng một dữ liệu, cuối cùng bạn sẽ sao chép cùng một lúc các đối số ngăn xếp hoặc quyết định phân bổ các đối số của bạn trên heap và chuyển một con trỏ xung quanh, không tận dụng tính năng cơ bản nhất của ngôn ngữ ( đối số chức năng). Nói tóm lại, tôi không tin giải pháp này sẽ cân nhắc cho trường hợp xấu nhất khi mọi điều kiện được kiểm tra sau khi có được các tài nguyên mới phải được dọn sạch. Lưu ý Nawaz bình luận về lambdas tuy nhiên.
tne

2
Tôi không nhớ OP nói bất cứ điều gì về việc mua tài nguyên ở giữa khối mã của anh ấy, nhưng tôi chấp nhận đó là một yêu cầu khả thi. Trong trường hợp đó, có gì sai khi khai báo tài nguyên trên ngăn xếp ở bất cứ đâu trong func()và cho phép hàm hủy của nó xử lý giải phóng tài nguyên? Nếu bất cứ điều gì ngoài func()nhu cầu truy cập vào cùng một tài nguyên thì nó phải được khai báo trên heap trước khi gọi func()bởi người quản lý tài nguyên thích hợp.
Dan Bechard

12

Chính dòng mã đã là một mùi mã mà nhiều thứ đang xảy ra trong hàm. Nếu không có giải pháp trực tiếp cho điều đó (chức năng là chức năng kiểm tra chung), thì sử dụng RAII để bạn có thể quay lại thay vì nhảy đến phần cuối của chức năng có thể tốt hơn.


11

Nếu bạn không cần phải giới thiệu các biến cục bộ trong quá trình thực thi thì bạn có thể thường xuyên làm phẳng điều này:

if (check()) {
  doStuff();
}  
if (stillOk()) {
  doMoreStuff();
}
if (amIStillReallyOk()) {
  doEvenMore();
}

// edit 
doThingsAtEndAndReportErrorStatus()

2
Nhưng sau đó, mỗi điều kiện sẽ phải bao gồm điều kiện trước, điều này không chỉ xấu mà còn có khả năng xấu cho hiệu suất. Tốt hơn là nhảy qua những kiểm tra và dọn dẹp ngay lập tức ngay khi chúng tôi biết chúng tôi "không ổn".
tne

Điều đó đúng, tuy nhiên cách tiếp cận này có lợi thế nếu có những việc phải làm cuối cùng để bạn không muốn quay lại sớm (xem chỉnh sửa). Nếu bạn kết hợp với phương pháp sử dụng bool của Denise trong các điều kiện, thì hiệu năng đạt được sẽ không đáng kể trừ khi điều này nằm trong một vòng lặp rất chặt chẽ.
the_mandrill

10

Tương tự như câu trả lời của dasblinkenlight, nhưng tránh sự phân công bên trong ifcó thể bị "sửa" bởi người đánh giá mã:

bool goOn = check0();
if (goOn) {
    ...
    goOn = check1();
}
if (goOn) {
    ...
    goOn = check2();
}
if (goOn) {
    ...
}

...

Tôi sử dụng mẫu này khi kết quả của một bước cần phải được kiểm tra trước bước tiếp theo, khác với tình huống tất cả các kiểm tra có thể được thực hiện trước với một if( check1() && check2()...mẫu loại lớn .


Lưu ý rằng check1 () thực sự có thể là PerformanceStep1 () trả về mã kết quả của bước. Điều này sẽ giữ cho sự phức tạp của chức năng dòng quy trình của bạn.
Denise Skidmore

10

Sử dụng ngoại lệ. Mã của bạn sẽ trông gọn gàng hơn nhiều (và các ngoại lệ được tạo chính xác để xử lý các lỗi trong luồng thực thi của chương trình). Để làm sạch tài nguyên (mô tả tệp, kết nối cơ sở dữ liệu, v.v.), hãy đọc bài viết Tại sao C ++ không cung cấp cấu trúc "cuối cùng"? .

#include <iostream>
#include <stdexcept>   // For exception, runtime_error, out_of_range

int main () {
    try {
        if (!condition)
            throw std::runtime_error("nope.");
        ...
        if (!other condition)
            throw std::runtime_error("nope again.");
        ...
        if (!another condition)
            throw std::runtime_error("told you.");
        ...
        if (!yet another condition)
            throw std::runtime_error("OK, just forget it...");
    }
    catch (std::runtime_error &e) {
        std::cout << e.what() << std::endl;
    }
    catch (...) {
        std::cout << "Caught an unknown exception\n";
    }
    return 0;
}

10
Có thật không? Đầu tiên, tôi thực sự không thấy bất kỳ cải thiện khả năng đọc nào ở đó. KHÔNG SỬ DỤNG NGOẠI TRỪ ĐỂ KIỂM SOÁT CHƯƠNG TRÌNH. Đó không phải là mục đích đúng đắn của họ. Ngoài ra, ngoại lệ mang lại hình phạt hiệu suất đáng kể. Trường hợp sử dụng thích hợp cho một ngoại lệ là khi có một số điều kiện THÌ BẠN KHÔNG THỂ LÀM GÌ NÀO VỀ, chẳng hạn như cố mở một tệp đã bị khóa bởi một quy trình khác, hoặc kết nối mạng không thành công hoặc cuộc gọi đến cơ sở dữ liệu không thành công hoặc một người gọi chuyển một tham số không hợp lệ cho một thủ tục. Đó là loại điều. Nhưng KHÔNG sử dụng ngoại lệ để kiểm soát luồng chương trình.
Craig

3
Ý tôi là, không phải là một trò đùa về nó, nhưng hãy đi đến bài viết Stroustrup mà bạn đã tham khảo và tìm kiếm "Tôi không nên sử dụng ngoại lệ để làm gì?" phần. Trong số những thứ khác, ông nói: "Đặc biệt, ném không chỉ đơn giản là một cách khác để trả về giá trị từ một hàm (tương tự như trả về). Làm như vậy sẽ chậm và sẽ gây nhầm lẫn cho hầu hết các lập trình viên C ++ được sử dụng để lấy ngoại lệ chỉ sử dụng cho lỗi xử lý. Tương tự như vậy, ném không phải là một cách tốt để thoát khỏi vòng lặp. "
Craig

3
@Craig Tất cả những gì bạn đã chỉ ra là đúng, nhưng bạn cho rằng chương trình mẫu có thể tiếp tục sau một check()điều kiện thất bại, và đó chắc chắn là giả định CỦA BẠN, không có ngữ cảnh trong mẫu. Giả sử rằng chương trình không thể tiếp tục, sử dụng ngoại lệ là cách để đi.
Cartucho

2
Vâng, đủ đúng nếu đó thực sự là bối cảnh. Nhưng, điều buồn cười về các giả định ... ;-)
Craig

10

Đối với tôi do{...}while(0)là tốt. Nếu bạn không muốn xem do{...}while(0), bạn có thể xác định từ khóa thay thế cho chúng.

Thí dụ:

//--------SomeUtilities.hpp---------
#define BEGIN_TEST do{
#define END_TEST }while(0);

//--------SomeSourceFile.cpp--------
BEGIN_TEST
   if(!condition1) break;
   if(!condition2) break;
   if(!condition3) break;
   if(!condition4) break;
   if(!condition5) break;

   //processing code here

END_TEST

Tôi nghĩ rằng trình biên dịch sẽ loại bỏ các while(0)điều kiện không cần thiết trongdo{...}while(0) trong phiên bản nhị phân và chuyển đổi các ngắt thành bước nhảy vô điều kiện. Bạn có thể kiểm tra phiên bản ngôn ngữ lắp ráp để đảm bảo.

Việc sử dụng gotocũng tạo ra mã sạch hơn và nó đơn giản với logic điều kiện-sau đó nhảy. Bạn có thể làm như sau:

{
   if(!condition1) goto end_blahblah;
   if(!condition2) goto end_blahblah;
   if(!condition3) goto end_blahblah;
   if(!condition4) goto end_blahblah;
   if(!condition5) goto end_blahblah;

   //processing code here

 }end_blah_blah:;  //use appropriate label here to describe...
                   //  ...the whole code inside the block.

Lưu ý nhãn được đặt sau khi đóng }. Đây là một vấn đề có thể tránh gotođược là việc vô tình đặt mã ở giữa vì bạn không nhìn thấy nhãn. Bây giờ giống như do{...}while(0)không có mã điều kiện.

Để làm cho mã này sạch hơn và dễ hiểu hơn, bạn có thể làm điều này:

//--------SomeUtilities.hpp---------
#define BEGIN_TEST {
#define END_TEST(_test_label_) }_test_label_:;
#define FAILED(_test_label_) goto _test_label_

//--------SomeSourceFile.cpp--------
BEGIN_TEST
   if(!condition1) FAILED(NormalizeData);
   if(!condition2) FAILED(NormalizeData);
   if(!condition3) FAILED(NormalizeData);
   if(!condition4) FAILED(NormalizeData);
   if(!condition5) FAILED(NormalizeData);

END_TEST(NormalizeData)

Với điều này, bạn có thể thực hiện các khối lồng nhau và chỉ định nơi bạn muốn thoát / nhảy ra.

//--------SomeUtilities.hpp---------
#define BEGIN_TEST {
#define END_TEST(_test_label_) }_test_label_:;
#define FAILED(_test_label_) goto _test_label_

//--------SomeSourceFile.cpp--------
BEGIN_TEST
   if(!condition1) FAILED(NormalizeData);
   if(!condition2) FAILED(NormalizeData);

   BEGIN_TEST
      if(!conditionAA) FAILED(DecryptBlah);
      if(!conditionBB) FAILED(NormalizeData);   //Jump out to the outmost block
      if(!conditionCC) FAILED(DecryptBlah);

      // --We can now decrypt and do other stuffs.

   END_TEST(DecryptBlah)

   if(!condition3) FAILED(NormalizeData);
   if(!condition4) FAILED(NormalizeData);

   // --other code here

   BEGIN_TEST
      if(!conditionA) FAILED(TrimSpaces);
      if(!conditionB) FAILED(TrimSpaces);
      if(!conditionC) FAILED(NormalizeData);   //Jump out to the outmost block
      if(!conditionD) FAILED(TrimSpaces);

      // --We can now trim completely or do other stuffs.

   END_TEST(TrimSpaces)

   // --Other code here...

   if(!condition5) FAILED(NormalizeData);

   //Ok, we got here. We can now process what we need to process.

END_TEST(NormalizeData)

Mã spaghetti không phải là lỗi của goto, đó là lỗi của lập trình viên. Bạn vẫn có thể sản xuất mã spaghetti mà không cần sử dụng goto.


10
Tôi sẽ chọn gotomở rộng cú pháp ngôn ngữ bằng cách sử dụng bộ tiền xử lý hàng triệu lần.
Christian

2
"Để làm cho mã này sạch hơn và dễ hiểu hơn, bạn có thể [sử dụng LOADS_OF_weIRD_MACROS]" : không tính toán.
gạch dưới

8

Đây là một vấn đề nổi tiếng và được giải quyết tốt từ góc độ lập trình chức năng - có thể là đơn nguyên.

Để trả lời cho nhận xét tôi nhận được bên dưới, tôi đã chỉnh sửa phần giới thiệu của mình ở đây: Bạn có thể tìm thấy chi tiết đầy đủ về việc triển khai các đơn vị C ++nhiều nơi khác nhau cho phép bạn đạt được những gì Rotsor gợi ý. Phải mất một thời gian để tìm kiếm các đơn vị, vì vậy thay vào đó tôi sẽ đề xuất ở đây một cơ chế giống như đơn vị "người nghèo" nhanh chóng mà bạn cần biết về không gì khác hơn là boost :: tùy chọn.

Thiết lập các bước tính toán của bạn như sau:

boost::optional<EnabledContext> enabled(boost::optional<Context> context);
boost::optional<EnergisedContext> energised(boost::optional<EnabledContext> context);

Mỗi bước tính toán rõ ràng có thể làm một cái gì đó như trả về boost::nonenếu tùy chọn được đưa ra là trống. Ví dụ:

struct Context { std::string coordinates_filename; /* ... */ };

struct EnabledContext { int x; int y; int z; /* ... */ };

boost::optional<EnabledContext> enabled(boost::optional<Context> c) {
   if (!c) return boost::none; // this line becomes implicit if going the whole hog with monads
   if (!exists((*c).coordinates_filename)) return boost::none; // return none when any error is encountered.
   EnabledContext ec;
   std::ifstream file_in((*c).coordinates_filename.c_str());
   file_in >> ec.x >> ec.y >> ec.z;
   return boost::optional<EnabledContext>(ec); // All ok. Return non-empty value.
}

Sau đó xâu chuỗi chúng lại với nhau:

Context context("planet_surface.txt", ...); // Close over all needed bits and pieces

boost::optional<EnergisedContext> result(energised(enabled(context)));
if (result) { // A single level "if" statement
    // do work on *result
} else {
    // error
}

Điều tuyệt vời ở đây là bạn có thể viết các bài kiểm tra đơn vị được xác định rõ ràng cho từng bước tính toán. Ngoài ra lời mời đọc giống như tiếng Anh đơn giản (như thường thấy với phong cách chức năng).

Nếu bạn không quan tâm đến tính bất biến và sẽ thuận tiện hơn khi trả lại cùng một đối tượng mỗi lần bạn có thể đưa ra một số biến thể bằng cách sử dụng shared_ptr hoặc tương tự.


3
Mã này có một thuộc tính không mong muốn là buộc mỗi hàm riêng lẻ phải xử lý lỗi của hàm trước đó, do đó không sử dụng đúng thành ngữ Monad (trong đó hiệu ứng đơn âm, trong trường hợp này là thất bại, được cho là được xử lý ngầm). Để làm điều đó, bạn cần phải có optional<EnabledContext> enabled(Context); optional<EnergisedContext> energised(EnabledContext);và sử dụng thao tác sáng tác đơn âm ('liên kết') thay vì ứng dụng chức năng.
Rotsor

Cảm ơn. Bạn đã đúng - đó là cách để làm điều đó đúng. Tôi không muốn viết quá nhiều trong câu trả lời của mình để giải thích điều đó (do đó thuật ngữ "người nghèo" có nghĩa là tôi sẽ không nói toàn bộ con lợn ở đây).
Benedict

7

Làm thế nào về việc di chuyển các câu lệnh if thành một hàm bổ sung mang lại kết quả bằng số hoặc enum?

int ConditionCode (void) {
   if (condition1)
      return 1;
   if (condition2)
      return 2;
   ...
   return 0;
}


void MyFunc (void) {
   switch (ConditionCode ()) {
      case 1:
         ...
         break;

      case 2:
         ...
         break;

      ...

      default:
         ...
         break;
   }
}

Điều này là tốt khi có thể, nhưng ít chung chung hơn nhiều so với câu hỏi được hỏi ở đây. Mọi điều kiện có thể phụ thuộc vào mã được thực thi sau thử nghiệm phân tích cuối cùng.
kriss

Vấn đề ở đây là bạn có nguyên nhân và hậu quả riêng biệt. Tức là bạn tách mã liên quan đến cùng một số điều kiện và đây có thể là nguồn gây ra các lỗi tiếp theo.
Riga

@kriss: Chà, hàm conditionCode () có thể được điều chỉnh để xử lý việc này. Điểm mấu chốt của chức năng đó là bạn có thể sử dụng return <result> cho một lối thoát sạch ngay khi một điều kiện cuối cùng được tính toán; Và đó là những gì đang cung cấp sự rõ ràng về cấu trúc ở đây.
karx11erx

@Riga: Imo đây là những phản đối học thuật hoàn toàn. Tôi thấy C ++ trở nên phức tạp hơn, khó hiểu hơn và không thể đọc được với mọi phiên bản mới. Tôi không thể thấy một vấn đề với hàm trợ giúp nhỏ đánh giá các điều kiện phức tạp theo cách có cấu trúc tốt để làm cho hàm mục tiêu ngay bên dưới dễ đọc hơn.
karx11erx

1
@ karx11erx sự phản đối của tôi là thiết thực và dựa trên kinh nghiệm của tôi. Mẫu này không liên quan đến C ++ 11 hoặc bất kỳ ngôn ngữ nào. Nếu bạn gặp khó khăn với các cấu trúc ngôn ngữ cho phép bạn viết kiến ​​trúc tốt không phải là vấn đề ngôn ngữ.
Riga

5

Một cái gì đó như thế này có lẽ

#define EVER ;;

for(EVER)
{
    if(!check()) break;
}

hoặc sử dụng ngoại lệ

try
{
    for(;;)
        if(!check()) throw 1;
}
catch()
{
}

Sử dụng ngoại lệ, bạn cũng có thể truyền dữ liệu.


10
Xin đừng làm những điều thông minh như định nghĩa của bạn từ trước đến giờ, chúng thường làm cho mã khó đọc hơn đối với các nhà phát triển đồng nghiệp. Tôi đã thấy ai đó định nghĩa Case là break; case trong tệp tiêu đề và đã sử dụng nó trong switch trong tệp cpp, khiến người khác tự hỏi hàng giờ tại sao công tắc lại ngắt giữa các câu lệnh Case. Grrr ...
Michael

5
Và khi bạn đặt tên cho macro, bạn nên làm cho chúng trông giống như macro (nghĩa là trong tất cả chữ hoa). Nếu không, ai đó tình cờ đặt tên một biến / hàm / loại / vv. được đặt tên eversẽ rất không vui ...
jamesdlin

5

Tôi không đặc biệt thích cách sử dụng breakhoặcreturn trong trường hợp như vậy. Cho rằng bình thường khi chúng ta đang phải đối mặt với một tình huống như vậy, nó thường là một phương pháp tương đối dài.

Nếu chúng ta có nhiều điểm thoát, điều đó có thể gây khó khăn khi chúng ta muốn biết điều gì sẽ khiến logic nhất định được thực thi: Thông thường chúng ta cứ tiếp tục đi lên các khối bao quanh đoạn logic đó và tiêu chí của các khối bao quanh đó cho chúng ta biết tình hình:

Ví dụ,

if (conditionA) {
    ....
    if (conditionB) {
        ....
        if (conditionC) {
            myLogic();
        }
    }
}

Bằng cách nhìn vào các khối kèm theo, thật dễ dàng để tìm ra rằng myLogic()chỉ xảy ra khi đó conditionA and conditionB and conditionClà sự thật.

Nó trở nên ít nhìn thấy hơn khi có lợi nhuận sớm:

if (conditionA) {
    ....
    if (!conditionB) {
        return;
    }
    if (!conditionD) {
        return;
    }
    if (conditionC) {
        myLogic();
    }
}

Chúng tôi không còn có thể điều hướng lên từ myLogic(), nhìn vào khối kèm theo để tìm ra điều kiện.

Có nhiều cách giải quyết khác nhau mà tôi đã sử dụng. Đây là một trong số chúng:

if (conditionA) {
    isA = true;
    ....
}

if (isA && conditionB) {
    isB = true;
    ...
}

if (isB && conditionC) {
    isC = true;
    myLogic();
}

(Tất nhiên người ta hoan nghênh sử dụng cùng một biến để thay thế tất cả isA isB isC .)

Cách tiếp cận như vậy ít nhất sẽ cung cấp cho người đọc mã, myLogic()được thực thi khi isB && conditionC. Người đọc được đưa ra một gợi ý rằng anh ta cần tra cứu thêm những gì sẽ khiến isB trở thành sự thật.


3
typedef bool (*Checker)();

Checker * checkers[]={
 &checker0,&checker1,.....,&checkerN,NULL
};

bool checker1(){
  if(condition){
    .....
    .....
    return true;
  }
  return false;
}

bool checker2(){
  if(condition){
    .....
    .....
    return true;
  }
  return false;
}

......

void doCheck(){
  Checker ** checker = checkers;
  while( *checker && (*checker)())
    checker++;
}

Thế còn cái đó?


nếu lỗi thời, chỉ là return condition;, nếu không tôi nghĩ rằng điều này là duy trì tốt.
SpaceTrucker

2

Một mẫu khác hữu ích nếu bạn cần các bước dọn dẹp khác nhau tùy thuộc vào nơi thất bại:

    private ResultCode DoEverything()
    {
        ResultCode processResult = ResultCode.FAILURE;
        if (DoStep1() != ResultCode.SUCCESSFUL)
        {
            Step1FailureCleanup();
        }
        else if (DoStep2() != ResultCode.SUCCESSFUL)
        {
            Step2FailureCleanup();
            processResult = ResultCode.SPECIFIC_FAILURE;
        }
        else if (DoStep3() != ResultCode.SUCCESSFUL)
        {
            Step3FailureCleanup();
        }
        ...
        else
        {
            processResult = ResultCode.SUCCESSFUL;
        }
        return processResult;
    }

2

Tôi không phải là lập trình viên C ++ , vì vậy tôi sẽ không viết bất kỳ mã nào ở đây, nhưng cho đến nay không ai đề cập đến một giải pháp hướng đối tượng. Vì vậy, đây là dự đoán của tôi về điều đó:

Có một giao diện chung cung cấp một phương pháp để đánh giá một điều kiện duy nhất. Bây giờ bạn có thể sử dụng một danh sách các triển khai của các điều kiện đó trong đối tượng của bạn có chứa phương thức được đề cập. Bạn lặp lại danh sách và đánh giá từng điều kiện, có thể thoát ra sớm nếu thất bại.

Điều tốt là thiết kế như vậy bám rất tốt vào nguyên tắc mở / đóng , bởi vì bạn có thể dễ dàng thêm các điều kiện mới trong quá trình khởi tạo đối tượng có chứa phương thức đang đề cập. Bạn thậm chí có thể thêm một phương thức thứ hai vào giao diện với phương thức đánh giá điều kiện trả về một mô tả về điều kiện. Điều này có thể được sử dụng cho các hệ thống tự tài liệu.

Tuy nhiên, nhược điểm là có nhiều chi phí liên quan hơn một chút do sử dụng nhiều đối tượng hơn và lặp lại trong danh sách.


Bạn có thể thêm một ví dụ trong một ngôn ngữ khác? Tôi nghĩ rằng câu hỏi này áp dụng cho nhiều ngôn ngữ mặc dù nó đã được hỏi cụ thể về C ++.
Denise Skidmore

1

Đây là cách tôi làm.

void func() {
  if (!check()) return;
  ...
  ...

  if (!check()) return;
  ...
  ...

  if (!check()) return;
  ...
  ...
}

1

Đầu tiên, một ví dụ ngắn để cho thấy tại sao gotokhông phải là một giải pháp tốt cho C ++:

struct Bar {
    Bar();
};

extern bool check();

void foo()
{
    if (!check())
       goto out;

    Bar x;

    out:
}

Hãy thử biên dịch nó thành một tệp đối tượng và xem điều gì sẽ xảy ra. Sau đó thử tương đương do+ break+while(0) .

Đó là một bên. Điểm chính sau.

Những đoạn mã nhỏ đó thường yêu cầu một số loại dọn dẹp nên toàn bộ chức năng bị lỗi. Những lần dọn dẹp đó thường muốn xảy ra theo thứ tự ngược lại từ chính các khối, khi bạn "thư giãn" tính toán đã hoàn thành một phần.

Một lựa chọn để có được các ngữ nghĩa này là RAII ; xem câu trả lời của @ utnapistim. C ++ đảm bảo rằng các hàm hủy tự động chạy theo thứ tự ngược lại với các nhà xây dựng, vốn tự nhiên cung cấp một "giải phóng".

Nhưng điều đó đòi hỏi rất nhiều lớp RAII. Đôi khi một tùy chọn đơn giản hơn chỉ là sử dụng ngăn xếp:

bool calc1()
{
    if (!check())
        return false;

    // ... Do stuff1 here ...

    if (!calc2()) {
        // ... Undo stuff1 here ...
        return false;
    }

    return true;
}

bool calc2()
{
    if (!check())
        return false;

    // ... Do stuff2 here ...

    if (!calc3()) {
        // ... Undo stuff2 here ...
        return false;
    }

    return true;
}

...và như thế. Điều này rất dễ kiểm toán, vì nó đặt mã "hoàn tác" bên cạnh mã "làm". Kiểm toán dễ dàng là tốt. Nó cũng làm cho dòng điều khiển rất rõ ràng. Nó là một mô hình hữu ích cho C, quá.

Nó có thể yêu cầu các calchàm để có nhiều đối số, nhưng đó thường không phải là vấn đề nếu các lớp / cấu trúc của bạn có sự gắn kết tốt. (Nghĩa là, những thứ thuộc về nhau sống trong một đối tượng, vì vậy các hàm này có thể lấy con trỏ hoặc tham chiếu đến một số lượng nhỏ đối tượng và vẫn thực hiện nhiều công việc hữu ích.)


Rất dễ dàng để kiểm tra đường dẫn dọn dẹp, nhưng có lẽ không dễ để theo dõi đường dẫn vàng. Nhưng nhìn chung, tôi nghĩ rằng một cái gì đó như thế này không khuyến khích một mô hình dọn dẹp nhất quán.
Denise Skidmore

0

Nếu mã của bạn có một khối dài if..else if..else các câu lệnh, bạn có thể thử và viết lại toàn bộ khối với sự trợ giúp của Functorshoặcfunction pointers . Nó có thể không phải là giải pháp đúng luôn luôn nhưng khá thường xuyên là vậy.

http://www.cprogramming.com/tutorial/functor-feft-objects-in-c++.html


Về nguyên tắc điều này là có thể (và không phải là xấu), nhưng với các đối tượng hàm hoặc hàm trỏ rõ ràng, nó chỉ làm gián đoạn dòng mã quá mạnh. OTOH, ở đây tương đương, sử dụng lambdas hoặc các chức năng được đặt tên thông thường là thực hành tốt, hiệu quả và đọc tốt.
leftaroundabout

0

Tôi ngạc nhiên bởi số lượng câu trả lời khác nhau được trình bày ở đây. Nhưng, cuối cùng trong mã mà tôi phải thay đổi (nghĩa là loại bỏ do-while(0)hack này hoặc bất cứ điều gì), tôi đã làm một cái gì đó khác với bất kỳ câu trả lời nào được đề cập ở đây và tôi bối rối tại sao không ai nghĩ điều này. Đây là những gì tôi đã làm:

Mã ban đầu:

do {

    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);

finishingUpStuff.

Hiện nay:

finish(params)
{
  ...
  ...
}

if(!check()){
    finish(params);    
    return;
}
...
...
if(!check()){
    finish(params);    
    return;
}
...
...
if(!check()){
    finish(params);    
    return;
}
...
...

Vì vậy, những gì đã được thực hiện ở đây là các công cụ hoàn thiện đã bị cô lập trong một chức năng và mọi thứ đột nhiên trở nên đơn giản và sạch sẽ!

Tôi nghĩ rằng giải pháp này là đáng nói, vì vậy cung cấp nó ở đây.


0

Hợp nhất nó thành một iftuyên bố:

if(
    condition
    && other_condition
    && another_condition
    && yet_another_condition
    && ...
) {
        if (final_cond){
            //Do stuff
        } else {
            //Do other stuff
        }
}

Đây là mẫu được sử dụng trong các ngôn ngữ như Java nơi loại bỏ từ khóa goto.


2
Điều đó chỉ hoạt động nếu bạn không phải thực hiện bất kỳ công cụ nào giữa các bài kiểm tra điều kiện. (vâng, tôi cho rằng bạn có thể che giấu công việc bên trong một số cuộc gọi chức năng được thực hiện bởi các bài kiểm tra điều kiện, nhưng điều đó có thể gây khó chịu một chút nếu bạn làm quá nhiều)
Jeremy Friesner

@JeremyFriesner Trên thực tế, bạn thực sự có thể thực hiện các công cụ xen kẽ như các hàm boolean riêng biệt luôn luôn đánh giá là true. Đánh giá ngắn mạch sẽ đảm bảo rằng bạn không bao giờ chạy giữa các công cụ mà tất cả các bài kiểm tra tiên quyết không vượt qua.
AJMansfield

@AJMansfield có, đó là những gì tôi đã đề cập đến trong câu thứ hai của mình ... nhưng tôi không chắc chắn rằng đó sẽ là một sự cải thiện về chất lượng mã.
Jeremy Friesner

@JeremyFriesner Không có gì ngăn bạn viết các điều kiện như (/*do other stuff*/, /*next condition*/), thậm chí bạn có thể định dạng nó một cách độc đáo. Chỉ không mong đợi mọi người thích nó. Nhưng thành thật mà nói, điều này chỉ cho thấy rằng đó là một sai lầm khi Java loại bỏ gototuyên bố đó ...
cmaster - khôi phục monica

@JeremyFriesner Tôi đã giả sử rằng đây là những booleans. Nếu các chức năng được chạy bên trong mỗi điều kiện, thì có một cách tốt hơn để xử lý nó.
Tyzoid

0

Nếu sử dụng cùng một trình xử lý lỗi cho tất cả các lỗi và mỗi bước sẽ trả về một bool biểu thị thành công:

if(
    DoSomething() &&
    DoSomethingElse() &&
    DoAThirdThing() )
{
    // do good condition action
}
else
{
    // handle error
}

(Tương tự như câu trả lời của tyzoid, nhưng điều kiện là các hành động và && ngăn các hành động bổ sung xảy ra sau thất bại đầu tiên.)


0

Tại sao phương pháp gắn cờ không trả lời nó được sử dụng từ rất lâu đời.

//you can use something like this (pseudocode)
long var = 0;
if(condition)  flag a bit in var
if(condition)  flag another bit in var
if(condition)  flag another bit in var
............
if(var == certain number) {
Do the required task
}
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.