Tại sao toán tử bậc ba với dấu phẩy chỉ đánh giá một biểu thức trong trường hợp đúng?


119

Tôi hiện đang học C ++ với cuốn sách C ++ Primer và một trong những bài tập trong cuốn sách là:

Giải thích biểu thức sau có chức năng gì: someValue ? ++x, ++y : --x, --y

Chúng ta biết những gì? Chúng ta biết rằng toán tử bậc ba có mức độ ưu tiên cao hơn toán tử dấu phẩy. Với toán tử nhị phân, điều này khá dễ hiểu, nhưng với toán tử bậc ba, tôi đang gặp khó khăn một chút. Với các toán tử nhị phân "có mức độ ưu tiên cao hơn" có nghĩa là chúng ta có thể sử dụng dấu ngoặc đơn xung quanh biểu thức có mức độ ưu tiên cao hơn và nó sẽ không thay đổi việc thực thi.

Đối với toán tử bậc ba, tôi sẽ làm:

(someValue ? ++x, ++y : --x, --y)

có hiệu quả dẫn đến cùng một mã, điều này không giúp tôi hiểu cách trình biên dịch sẽ nhóm mã.

Tuy nhiên, từ thử nghiệm với trình biên dịch C ++, tôi biết rằng biểu thức biên dịch và tôi không biết một :toán tử có thể đại diện cho chính nó. Vì vậy, trình biên dịch dường như giải thích toán tử bậc ba một cách chính xác.

Sau đó, tôi thực hiện chương trình theo hai cách:

#include <iostream>

int main()
{
    bool someValue = true;
    int x = 10, y = 10;

    someValue ? ++x, ++y : --x, --y;

    std::cout << x << " " << y << std::endl;
    return 0;
}

Kết quả trong:

11 10

Mặt khác với someValue = falsenó in:

9 9

Tại sao trình biên dịch C ++ lại tạo ra mã cho nhánh true của toán tử bậc ba chỉ tăng x, trong khi đối với nhánh sai của toán tử bậc ba, nó giảm cả xy?

Tôi thậm chí còn đi xa đến mức đặt dấu ngoặc đơn xung quanh nhánh true như thế này:

someValue ? (++x, ++y) : --x, --y;

nhưng nó vẫn dẫn đến 11 10.


5
"Precedence" chỉ là một hiện tượng mới nổi trong C ++. Có thể đơn giản hơn là chỉ cần xem trực tiếp ngữ pháp ngôn ngữ và xem cách diễn đạt hoạt động.
Kerrek SB

26
Chúng tôi không quan tâm rằng nhiều về các nguyên tắc. :-) Thực tế là bạn phải hỏi điều này ở đây cho biết mã sẽ không bao giờ vượt qua đánh giá mã bởi các lập trình viên đồng nghiệp của bạn. Điều đó làm cho kiến ​​thức về cách thức hoạt động thực sự của nó trở nên ít hữu ích hơn. Trừ khi bạn muốn tham gia vào cuộc thi bị xáo trộn C , tất nhiên.
Bo Persson

5
@BoPersson nếu không có các ví dụ như thế này để rút kinh nghiệm, những người đánh giá trong tương lai sẽ không bao giờ tìm hiểu lý do tại sao họ nên từ chối điều này khỏi quá trình sản xuất.
Leushenko

8
@Leushenko - Dù sao thì chuông cảnh báo cũng sẽ vang lên. Nhiều tăng giảm trong cùng một câu lệnh (ding, ding, ding!). Một toán tử bậc ba khi bạn có thể sử dụng if-else (ding, ding, ding!). Chờ đã, những dấu phẩy đó có phải là toán tử dấu phẩy đáng sợ không? (ding, DING, DING!) Với tất cả các toán tử đó, liệu có điều gì được ưu tiên không? (ding, ding, ding!) Vì vậy, chúng tôi sẽ không bao giờ có thể sử dụng nó. Vậy tại sao lại lãng phí thời gian để tìm hiểu xem nó làm gì, nếu có?
Bo Persson

4
Minor nit: tên cho ?toán tử điều kiện . Thuật ngữ toán tử bậc ba chỉ đơn giản có nghĩa là toán tử có ba toán hạng. Toán tử điều kiện là một ví dụ về toán tử bậc ba, nhưng một ngôn ngữ có thể (về mặt lý thuyết) có nhiều toán tử bậc ba.
bta

Câu trả lời:


122

Như @Rakete đã nói trong câu trả lời xuất sắc của họ, điều này thật khó. Tôi muốn thêm vào điều đó một chút.

Toán tử bậc ba phải có dạng:

logic hoặc thể hiện ? biểu hiện : nhiệm vụ thể hiện

Vì vậy, chúng tôi có các ánh xạ sau:

  • someValue: logic-hoặc-biểu thức
  • ++x, ++y: biểu thức
  • ??? là phép gán-biểu thức --x, --y hay chỉ --x?

Trên thực tế, đó chỉ là --xdo một biểu thức gán không thể được phân tích cú pháp thành hai biểu thức được phân tách bằng dấu phẩy (theo quy tắc ngữ pháp của C ++), vì vậy --x, --ykhông thể được coi là một biểu thức gán .

Kết quả dẫn đến phần biểu thức bậc ba (có điều kiện) trông giống như sau:

someValue?++x,++y:--x

Nó có thể hữu ích vì lợi ích của việc dễ đọc, được coi ++x,++ylà được tính như thể được đặt trong ngoặc đơn (++x,++y); bất kỳ thứ gì chứa giữa ?:sẽ được sắp xếp theo thứ tự sau điều kiện. (Tôi sẽ đặt chúng trong ngoặc đơn cho phần còn lại của bài đăng).

và được đánh giá theo thứ tự này:

  1. someValue?
  2. (++x,++y)hoặc --x(tùy thuộc vào boolkết quả của 1.)

Sau đó, biểu thức này được coi là biểu thức con bên trái đối với toán tử dấu phẩy, với biểu thức con bên phải --y, như sau:

(someValue?(++x,++y):--x), --y;

Có nghĩa là phía bên trái là một biểu thức giá trị bị loại bỏ , có nghĩa là nó chắc chắn được đánh giá, nhưng sau đó chúng tôi đánh giá phía bên phải và trả về điều đó.

Vì vậy, những gì xảy ra khi someValuetrue?

  1. (someValue?(++x,++y):--x)thực thi và gia tăng xyđược 1111
  2. Biểu thức bên trái bị loại bỏ (mặc dù các tác dụng phụ của sự gia tăng vẫn còn)
  3. Chúng tôi đánh giá phía bên phải của toán tử dấu phẩy --y:, sau đó giảm ytrở lại10

Để "sửa chữa" hành vi, bạn có thể nhóm --x, --yvới dấu ngoặc đơn để biến nó thành một biểu hiện chính một mục hợp lệ cho một nhiệm vụ thể hiện *:

someValue?++x,++y:(--x, --y);

* Đó là một chuỗi dài khá vui nhộn kết nối một biểu thức gán trở lại một biểu thức chính:

phép gán-biểu thức --- (có thể bao gồm) -> biểu thức điều kiện -> logic-hoặc-biểu thức -> logic-và-biểu thức -> bao gồm-hoặc-biểu thức -> loại trừ-hoặc-biểu thức - -> và-biểu thức -> đẳng thức -> quan hệ-biểu thức -> chuyển-biểu thức -> cộng-biểu thức -> nhân-biểu thức -> pm-biểu thức -> đúc-biểu thức -> biểu thức đơn phân -> biểu thức hậu tố -> biểu thức chính


10
Cảm ơn bạn đã chịu khó làm sáng tỏ các quy tắc ngữ pháp; làm như vậy cho thấy rằng có nhiều ngữ pháp của C ++ hơn bạn sẽ tìm thấy trong hầu hết các sách giáo khoa.
sdenham

4
@sdenham: Khi mọi người hỏi tại sao "ngôn ngữ hướng biểu thức" lại hay (nghĩa là khi nào { ... }có thể được coi là một biểu thức), thì bây giờ tôi đã có câu trả lời => đó là để tránh phải giới thiệu một toán tử dấu phẩy hoạt động theo những cách phức tạp như vậy.
Matthieu M.

Bạn có thể cho tôi một liên kết để đọc về assignment-expressionchuỗi không?
MiP

@MiP: Tôi nhấc nó ra khỏi tiêu chuẩn riêng của mình, bạn có thể tìm thấy nó dưới gram.expr
AndyG

88

Chà, thật là khó.

Trình biên dịch xem biểu thức của bạn là:

(someValue ? (++x, ++y) : --x), --y;

Toán tử bậc ba cần a :, nó không thể tự đứng trong ngữ cảnh đó, nhưng sau nó, không có lý do gì dấu phẩy phải thuộc về trường hợp sai.

Bây giờ nó có thể có ý nghĩa hơn tại sao bạn nhận được đầu ra đó. Nếu someValuelà sự thật, sau đó ++x, ++y--yđược thực hiện, mà không thay đổi một cách hiệu quả ynhưng thêm một đến x.

Nếu someValuelà false, thì --x--yđược thực thi, giảm cả hai.


42

Tại sao trình biên dịch C ++ lại tạo ra mã cho nhánh thực của toán tử bậc ba chỉ tăng x

Bạn đã hiểu sai những gì đã xảy ra. Nhánh true tăng cả xy. Tuy nhiên, yđược giảm ngay sau đó, vô điều kiện.

Đây là cách điều này xảy ra: vì toán tử điều kiện có mức độ ưu tiên cao hơn toán tử dấu phẩy trong C ++ , trình biên dịch phân tích biểu thức như sau:

   (someValue ? ++x, ++y : --x), (--y);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^

Lưu ý "mồ côi" --ysau dấu phẩy. Đây là những gì dẫn đến giảm dần yđã được tăng lên ban đầu.

Tôi thậm chí còn đi xa đến mức đặt dấu ngoặc đơn xung quanh nhánh true như thế này:

someValue ? (++x, ++y) : --x, --y;

Bạn đã đi đúng đường, nhưng bạn đã đặt dấu ngoặc đơn cho một nhánh sai: bạn có thể sửa lỗi này bằng cách đặt dấu ngoặc đơn cho nhánh khác, như sau:

someValue ? ++x, ++y : (--x, --y);

Demo (bản in 11 11)


5

Vấn đề của bạn là biểu thức bậc ba không thực sự có quyền ưu tiên cao hơn dấu phẩy. Trên thực tế, C ++ không thể được mô tả chính xác đơn giản theo thứ tự ưu tiên - và nó chính xác là sự tương tác giữa toán tử bậc ba và dấu phẩy nơi nó được chia nhỏ.

a ? b++, c++ : d++

được coi là:

a ? (b++, c++) : d++

(dấu phẩy hoạt động như thể nó có mức độ ưu tiên cao hơn). Mặt khác,

a ? b++ : c++, d++

được coi là:

(a ? b++ : c++), d++

và toán tử bậc ba được ưu tiên cao hơn.


Tôi nghĩ điều này vẫn nằm trong phạm vi ưu tiên vì chỉ có một phân tích cú pháp hợp lệ cho đường giữa, phải không? Vẫn là một ví dụ hữu ích mặc dù
sudo rm -rf giảm

2

Một điểm đã bị bỏ qua trong các câu trả lời (mặc dù đã đề cập đến các nhận xét) là toán tử điều kiện luôn được sử dụng (do thiết kế dự định?) Trong mã thực như một phím tắt để gán một trong hai giá trị cho một biến.

Vì vậy, bối cảnh lớn hơn sẽ là:

whatIreallyWanted = someValue ? ++x, ++y : --x, --y;

Điều đó là vô lý trên khuôn mặt của nó, vì vậy tội ác rất đa dạng:

  • Ngôn ngữ cho phép các tác dụng phụ vô lý trong một bài tập.
  • Trình biên dịch đã không cảnh báo bạn rằng bạn đang làm những điều kỳ lạ.
  • Cuốn sách dường như tập trung vào các câu hỏi 'mẹo'. Người ta chỉ có thể hy vọng rằng câu trả lời ở phía sau là "Những gì biểu thức này làm là phụ thuộc vào các trường hợp cạnh kỳ lạ trong một ví dụ giả tạo để tạo ra các tác dụng phụ mà không ai mong đợi. Đừng bao giờ làm điều này."

1
Việc gán một trong hai biến là trường hợp thông thường của toán tử bậc ba, nhưng có những trường hợp hữu ích khi có dạng biểu thức của if(ví dụ: biểu thức tăng trong vòng lặp for). Bối cảnh lớn hơn cũng có thể là for (x = 0, y=0; x+y < 100; someValue?(++x, ++y) :( --x, --y))với một vòng lặp có thể sửa đổi xyđộc lập.
Martin Bonner hỗ trợ Monica

@MartinBonner Tôi không bị thuyết phục và ví dụ này dường như đưa ra quan điểm của bo-perrson khá tốt, như anh ấy đã trích lời Tony Hoare.
Taryn
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.