Các toán tử logic ngắn mạch có bắt buộc không? Và đánh giá thứ tự?


140

Tiêu chuẩn ANSI có bắt buộc các toán tử logic được ngắn mạch, trong C hoặc C ++ không?

Tôi bối rối vì tôi nhớ lại cuốn sách K & R nói rằng mã của bạn không nên phụ thuộc vào các hoạt động này bị ngắn mạch, vì chúng có thể không. Ai đó có thể vui lòng chỉ ra nơi mà các tiêu chuẩn logic nói rằng các ops luôn bị ngắn mạch không? Tôi hầu hết quan tâm đến C ++, một câu trả lời cho C sẽ rất tuyệt.

Tôi cũng nhớ việc đọc (không thể nhớ ở đâu) rằng thứ tự đánh giá không được xác định nghiêm ngặt, vì vậy mã của bạn không nên phụ thuộc hoặc giả sử các hàm trong một biểu thức sẽ được thực thi theo một thứ tự cụ thể: vào cuối câu lệnh, tất cả các hàm được tham chiếu sẽ được gọi, nhưng trình biên dịch có quyền tự do lựa chọn thứ tự hiệu quả nhất.

Liệu tiêu chuẩn chỉ ra thứ tự đánh giá của biểu thức này?

if( functionA() && functionB() && functionC() ) cout<<"Hello world";

12
Carefull: Điều này đúng với các loại POD. Nhưng nếu bạn quá tải toán tử && hoặc toán tử | | đối với một lớp cụ thể, chúng KHÔNG lặp lại KHÔNG. Đây là lý do tại sao bạn KHÔNG nên định nghĩa các toán tử này cho các lớp của riêng bạn.
Martin York

Tôi đã định nghĩa lại các toán tử này một thời gian trước đây, khi tôi tạo một lớp sẽ thực hiện một số phép toán đại số boolean cơ bản. Có lẽ nên dán một bình luận cảnh báo "điều này phá hủy đánh giá ngắn mạch và trái phải!" trong trường hợp tôi quên điều này Cũng bị quá tải * / + và biến chúng thành từ đồng nghĩa của chúng :-)
Joe Pineda

Có chức năng gọi trong một khối if không phải là một thực hành lập trình tốt. Luôn có một biến được khai báo giữ giá trị trả về của phương thức và sử dụng nó trong khối if.
SR Chaitanya

6
@SRChaitanya Điều đó không đúng. Những gì bạn tự ý mô tả là thực hành kém được thực hiện mọi lúc, đặc biệt là với các hàm trả về booleans, như ở đây.
Hầu tước Lorne

Câu trả lời:


154

Có, thứ tự ngắn mạch và đánh giá được yêu cầu cho các nhà khai thác ||&&trong cả hai tiêu chuẩn C và C ++.

Tiêu chuẩn C ++ cho biết (cần có một mệnh đề tương đương trong tiêu chuẩn C):

1.9,18

Trong đánh giá các biểu thức sau đây

a && b
a || b
a ? b : c
a , b

sử dụng ý nghĩa tích hợp của các toán tử trong các biểu thức này, có một điểm thứ tự sau khi đánh giá biểu thức đầu tiên (12).

Trong C ++ có một cái bẫy thêm: ngắn mạch KHÔNG áp dụng cho các loại quá tải toán tử ||&&.

Chú thích 12: Các toán tử được chỉ ra trong đoạn này là các toán tử dựng sẵn, như được mô tả trong điều 5. Khi một trong các toán tử này bị quá tải (mệnh đề 13) trong ngữ cảnh hợp lệ, do đó chỉ định hàm toán tử do người dùng định nghĩa, biểu thức chỉ định một lời gọi hàm và các toán hạng tạo thành một danh sách đối số, không có điểm chuỗi ngụ ý giữa chúng.

Thông thường không nên quá tải các toán tử này trong C ++ trừ khi bạn có một yêu cầu rất cụ thể. Bạn có thể làm điều đó, nhưng nó có thể phá vỡ hành vi dự kiến ​​trong mã của người khác, đặc biệt nếu các toán tử này được sử dụng gián tiếp thông qua các mẫu khởi tạo với kiểu nạp chồng các toán tử này.


3
Không biết ngắn mạch sẽ không áp dụng cho các logic logic quá tải, đó là đường ruột. Bạn có thể vui lòng thêm một tài liệu tham khảo cho tiêu chuẩn, hoặc một nguồn? Tôi không tin tưởng bạn, chỉ muốn tìm hiểu thêm về điều này.
Joe Pineda

4
Vâng, đó là hợp lý. nó đóng vai trò là đối số cho toán tử && (a, b). đó là việc thực hiện nó nói những gì xảy ra.
Johannes Schaub - litb

10
litb: Không thể chuyển b cho toán tử && (a, b) mà không đánh giá nó. Và không có cách nào để hoàn tác đánh giá b vì trình biên dịch không thể đảm bảo không có tác dụng phụ.
jmucchiello

2
Tôi thấy buồn thế này. Tôi đã nghĩ rằng, tôi có nên xác định lại các toán tử && và || và chúng vẫn hoàn toàn xác định , trình biên dịch sẽ phát hiện ra điều đó và giữ cho đánh giá của chúng được ngắn gọn: sau tất cả, thứ tự là không liên quan, chúng đảm bảo không có tác dụng phụ!
Joe Pineda

2
@Joe: nhưng giá trị trả về và đối số của toán tử có thể thay đổi từ boolean sang thứ khác. Tôi đã sử dụng để triển khai logic "đặc biệt" với BA giá trị ("đúng", "sai" và "không xác định"). Giá trị trả về là xác định, nhưng hành vi ngắn mạch là không phù hợp.
Alex B

70

Đánh giá ngắn mạch và thứ tự đánh giá, là một tiêu chuẩn ngữ nghĩa bắt buộc trong cả C và C ++.

Nếu không, mã như thế này sẽ không phải là một thành ngữ phổ biến

   char* pChar = 0;
   // some actions which may or may not set pChar to something
   if ((pChar != 0) && (*pChar != '\0')) {
      // do something useful

   }

Mục 6.5.13 Toán tử VÀ toán tử của đặc tả C99 (liên kết PDF) cho biết

(4). Không giống như toán tử nhị phân & toán tử bit, toán tử && đảm bảo đánh giá từ trái sang phải; có một điểm thứ tự sau khi đánh giá toán hạng đầu tiên. Nếu toán hạng thứ nhất so với 0, toán hạng thứ hai không được ước tính.

Tương tự, phần 6.5.14 logic OR nói

(4) Không giống như bitwise | toán tử, | | nhà điều hành đảm bảo đánh giá từ trái sang phải; có một điểm thứ tự sau khi đánh giá toán hạng đầu tiên. Nếu toán hạng thứ nhất so sánh bằng 0, toán hạng thứ hai không được ước tính.

Từ ngữ tương tự có thể được tìm thấy trong các tiêu chuẩn C ++, kiểm tra phần 5.14 trong bản sao dự thảo này . Khi người kiểm tra ghi chú trong một câu trả lời khác, nếu bạn ghi đè && hoặc ||, thì cả hai toán hạng phải được đánh giá khi nó trở thành một lệnh gọi hàm thông thường.


Ah, những gì tôi đang tìm kiếm! OK, vì vậy cả thứ tự đánh giá ngắn mạch đều được ủy quyền theo ANSI-C 99! Tôi thực sự thích xem tài liệu tham khảo tương đương với ANSI-C ++, mặc dù tôi gần như 99% nó phải giống nhau.
Joe Pineda

Khó tìm thấy một liên kết miễn phí tốt cho các tiêu chuẩn C ++, đã liên kết với một bản sao nháp mà tôi tìm thấy với một số googling.
Paul Dixon

Đúng cho các loại POD. Nhưng nếu bạn quá tải toán tử && hoặc toán tử | | đây không phải là phím tắt
Martin York

1
vâng thật thú vị khi lưu ý rằng đối với bool, bạn sẽ luôn có thứ tự đánh giá được đảm bảo và hành vi ngắn mạch. bởi vì bạn không thể quá tải toán tử && cho hai loại tích hợp. bạn cần ít nhất một kiểu người dùng xác định trong toán hạng để nó hoạt động khác nhau.
Julian Schaub - litb

Tôi ước tôi có thể chấp nhận cả Người kiểm tra và câu trả lời này. Vì tôi chủ yếu quan tâm đến C ++, tôi chấp nhận người khác, mặc dù cũng phải thừa nhận điều này là tuyệt vời! Cảm ơn rât nhiều!
Joe Pineda

19

Có, nó bắt buộc (cả thứ tự đánh giá và ngắn mạch). Trong ví dụ của bạn nếu tất cả các hàm trả về true, thứ tự của các cuộc gọi hoàn toàn từ functionA rồi functionB và sau đó là functionC. Được sử dụng cho như thế này

if(ptr && ptr->value) { 
    ...
}

Tương tự cho toán tử dấu phẩy:

// calls a, then b and evaluates to the value returned by b
// which is used to initialize c
int c = (a(), b()); 

Một nói giữa bên trái và bên phải của toán hạng &&, ||, ,và giữa người đầu tiên và thứ hai / toán hạng thứ ba của ?:(nhà điều hành có điều kiện) là một "điểm chuỗi". Bất kỳ tác dụng phụ được đánh giá hoàn toàn trước thời điểm đó. Vì vậy, điều này là an toàn:

int a = 0;
int b = (a++, a); // b initialized with 1, and a is 1

Lưu ý rằng toán tử dấu phẩy không được nhầm lẫn với dấu phẩy cú pháp được sử dụng để phân tách các thứ:

// order of calls to a and b is unspecified!
function(a(), b());

Tiêu chuẩn C ++ nói trong 5.14/1:

Các nhóm toán tử && từ trái sang phải. Các toán hạng đều được chuyển đổi hoàn toàn thành kiểu bool (mệnh đề 4). Kết quả là đúng nếu cả hai toán hạng đều đúng và sai. Không giống như &, && đảm bảo đánh giá từ trái sang phải: toán hạng thứ hai không được đánh giá nếu toán hạng thứ nhất sai.

Và trong 5.15/1:

| | nhóm vận hành từ trái sang phải. Các toán hạng đều được chuyển đổi hoàn toàn thành bool (mệnh đề 4). Nó trả về true nếu một trong hai toán hạng của nó là true và ngược lại là false. Không giống như |, || đảm bảo đánh giá từ trái sang phải; hơn nữa, toán hạng thứ hai không được đánh giá nếu toán hạng thứ nhất đánh giá là đúng.

Nó nói cho cả hai bên cạnh những người:

Kết quả là một bool. Tất cả các tác dụng phụ của biểu thức thứ nhất ngoại trừ phá hủy tạm thời (12.2) xảy ra trước khi biểu thức thứ hai được ước tính.

Thêm vào đó, 1.9/18nói

Trong đánh giá của từng biểu thức

  • a && b
  • a || b
  • a ? b : C
  • a , b

sử dụng ý nghĩa tích hợp của các toán tử trong các biểu thức này (5.14, 5.15, 5.16, 5.18), có một điểm thứ tự sau khi đánh giá biểu thức đầu tiên.


10

Trực tiếp từ K & R cũ tốt:

C đảm bảo rằng &&||được đánh giá từ trái sang phải - chúng ta sẽ sớm thấy các trường hợp có vấn đề này.


3
K & R phiên bản 2 p40. "Biểu thức được kết nối bởi && hoặc || được đánh giá từ trái sang phải và việc đánh giá dừng lại ngay khi biết sự thật hoặc sai của kết quả. Hầu hết các chương trình C đều dựa vào các thuộc tính này." Tôi không thể tìm thấy văn bản trích dẫn của bạn ở bất cứ đâu trong cuốn sách. Đây có phải là từ phiên bản 1 cực kỳ lỗi thời? Hãy làm rõ nơi bạn tìm thấy văn bản này.
Lundin

1
Ok hóa ra bạn đang trích dẫn hướng dẫn cổ xưa này . Đó là từ năm 1974 và rất không liên quan.
Lundin

6

Hãy rất cẩn thận.

Đối với các loại cơ bản, đây là các toán tử phím tắt.

Nhưng nếu bạn định nghĩa các toán tử này cho các kiểu lớp hoặc liệt kê của riêng bạn thì chúng không phải là phím tắt. Do sự khác biệt về ngữ nghĩa trong cách sử dụng của chúng trong các trường hợp khác nhau này, bạn không nên xác định các toán tử này.

Đối với operator &&operator ||với nhiều loại cơ bản trình tự đánh giá được trái sang phải (cắt cách khác ngắn sẽ rất khó :-) Nhưng đối với các nhà khai thác quá tải mà bạn xác định, đây là những cơ bản cú pháp đường để xác định một phương pháp và do đó tự đánh giá các thông số là chưa xác định.


1
Quá tải toán tử không liên quan gì đến kiểu POD hay không. Để xác định hàm toán tử, ít nhất một trong các đối số cần phải là một lớp (hoặc struct hoặc union) hoặc enum hoặc tham chiếu đến một trong các đối số đó. Trở thành POD có nghĩa là bạn có thể sử dụng memcpy trên nó.
Derek Ledbetter

Và đó là những gì tôi đã nói. Nếu bạn quá tải && cho lớp của bạn thì đó thực sự chỉ là một cuộc gọi phương thức. Do đó, bạn không thể dựa vào thứ tự đánh giá các tham số. Rõ ràng là bạn không thể quá tải && cho các loại POD.
Martin York

3
Bạn đang sử dụng thuật ngữ "loại POD" không chính xác. Bạn có thể quá tải && cho bất kỳ struct, class, union hoặc enum, POD hay không. Bạn không thể quá tải && nếu cả hai bên là loại số hoặc con trỏ.
Derek Ledbetter

Tôi đã sử dụng POD vì (char / int / float, v.v.) không phải là POD tích hợp (đó là những gì bạn đang nói đến) và thường được đề cập đến một cách rõ ràng hoặc rõ ràng hơn vì nó không phải là một loại được xây dựng.
Martin York

2
Vì vậy, bạn có nghĩa là "loại cơ bản" nhưng đã viết "loại POD"?
Öö Tiib

0

Câu hỏi của bạn thuộc về quyền ưu tiên và tính kết hợp của toán tử C ++ . Về cơ bản, trong các biểu thức có nhiều toán tử và không có dấu ngoặc đơn, trình biên dịch sẽ xây dựng cây biểu thức bằng cách tuân theo các quy tắc này.

Để được ưu tiên, khi bạn có một cái gì đó như thế A op1 B op2 C, bạn có thể nhóm các thứ theo (A op1 B) op2 Choặc A op1 (B op2 C). Nếu op1có quyền ưu tiên cao hơn op2, bạn sẽ nhận được biểu thức đầu tiên. Nếu không, bạn sẽ nhận được cái thứ hai.

Đối với sự kết hợp, khi bạn có một cái gì đó như thế A op B op C, bạn có thể nhóm lại một lần nữa (A op B) op Choặc A op (B op C). Nếu opcó sự kết hợp trái, chúng ta kết thúc bằng biểu thức đầu tiên. Nếu nó có sự kết hợp đúng, chúng ta sẽ kết thúc với cái thứ hai. Điều này cũng hoạt động cho các nhà khai thác ở mức độ ưu tiên tương tự.

Trong trường hợp cụ thể này, &&có độ ưu tiên cao hơn ||, vì vậy biểu thức sẽ được đánh giá là (a != "" && it == seqMap.end()) || isEven.

Thứ tự chính là "từ trái sang phải" trên biểu mẫu cây biểu thức. Vì vậy, trước tiên chúng ta sẽ đánh giá a != "" && it == seqMap.end(). Nếu đó là sự thật thì toàn bộ biểu thức là đúng, nếu không thì chúng ta sẽ làm isEven. Tất nhiên, thủ tục lặp lại theo cách đệ quy bên trong phần phụ trái.


Các mẩu tin thú vị, nhưng khái niệm về quyền ưu tiên có nguồn gốc từ ký hiệu toán học. Điều tương tự xảy ra ở a*b + c, nơi *có quyền ưu tiên cao hơn +.

Thậm chí thú vị hơn / tối nghĩa hơn, đối với một biểu thức không được phân tách A1 op1 A2 op2 ... opn-1 An, trong đó tất cả các toán tử có cùng mức ưu tiên, số lượng cây biểu thức nhị phân mà chúng ta có thể hình thành được đưa ra bởi các số được gọi là số Catalan . Đối với lớn n, những phát triển cực kỳ nhanh chóng. d


Tất cả điều này là chính xác, nhưng đó là về sự ưu tiên của nhà điều hành và sự kết hợp, không phải về thứ tự đánh giá và điều chỉnh ngắn. Đó là những điều khác nhau.
Thomas Padron-McCarthy

0

Nếu bạn tin tưởng Wikipedia:

[ &&||] khác biệt về mặt ngữ nghĩa với các toán tử bit-khôn ngoan & và | bởi vì họ sẽ không bao giờ đánh giá toán hạng bên phải nếu kết quả có thể được xác định từ bên trái

C (ngôn ngữ lập trình)


11
Tại sao tin tưởng wiki khi chúng ta có một tiêu chuẩn!
Martin York


Điều này đúng theo như nó nói, nhưng không đầy đủ, vì các toán tử quá tải trong C ++ không bị ngắn mạch.
Thomas Padron-McCarthy
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.