xin lỗi vì mình chưa hiểu ý nghĩa của câu cuối
Mong được bạn giải đáp lại.
Mình cám ơn rất nhiều.
xin lỗi vì mình chưa hiểu ý nghĩa của câu cuối
Mong được bạn giải đáp lại.
Mình cám ơn rất nhiều.
?: đánh giá từ trái sang phải vậy nhập 25 nó ktra với 40, sai nó ktra tiếp với 30, sai nó ktra tiếp với 20, đúng nó trả về ‘C’. Như đọc từ trái qua phải thôi chứ ở đâu ra mà ktra với 10 trước thế kia :V :V
đây em :V nó ktra theo thứ tự trái qua phải 40 30 20 10 đâu ra vừa vào nhảy vào ktra với 10 ~.~
vâng anh, em đang tìm trường hợp mà nó áp dụng thứ tự kết hợp từ phải sang trái ạ, còn đoạn anh nói thì em biết em sai rồi.
Thì trong PHP đó
Left-associative:
a + b + c = (a + b) + c
a - b - c = (a - b) - c
a / b / c = (a / b) / c
Right-associative:
a = b = c: a = (b = c)
ok căm ơn bạn, đoạn này thì mình hiểu rồi. còn với thứ tự kết hợp phải sang trái của ?: thì mình chưa hình dung ra
Hình như ở đây bạn bị nhầm lần giữa thứ tự thực hiện và thứ tự kết hợp.
Với toán tử ?: thì kết hợp từ phải sang trái là đúng rồi:
C0?a:C1?b:c tương đương với C0?a:(C1?b:c)
Còn thứ tự thực hiện thì do ?:
nó chỉ tính một bên của dấu :
nên luôn phải tính điều kiện trước rồi mới xét tiếp được, nên nó vẫn xét C0
rồi mới quyết định tiếp là trả về a
hay xét tiếp C1
Hình như là mình đang bị nhầm lẫn nếu 2 khái niệm đó là khác nhau, còn đoạn dưới đây bạn có thể giải thích rõ hơn giúp mình là nó kết hợp như thế nào.
cám ơn bạn.
Thì 2 cái đó khác nhau mà: kết hợp chính là thêm dấu ngoặc vào để “nhóm” các cụm vào với nhau:
Với C0?a:C1?b:c
:
(C0?a:C1)?b:c
C0?a:(C1?b:c)
Mà theo “quy định” của chuẩn C++ thì nó là kết hợp từ phải sang trái, nên nó sẽ ra trường hợp sau thôi.
để lý giải cho suy luận trên, là mình áp dụng cách tính của hàm mũ:
hàm mũ cũng có thứ tự kết hợp từ phải sang trái, chẳng hạn có ví dụ:
4^5^6
thì đầu tiền là mình thực hiện 5^6 trước, xong lấy 4 mũ với kết quả nhận được ở trên. nó giống với thứ tự thực hiện.
Cái này làm sao bạn lại biết là nó tính “5^6” trước mà không phải “tính” 4 để ra 4 trước?
Lấy ví dụ: (1+2)^5^6
với ^
là hàm mũ thì chúng ta không thể xác định được là compiler sẽ tính 1+2
trước hay là 5^6
trước đâu bạn à. Chúng ta chỉ có thể khẳng định là nó sẽ tính 5^6
“kết hợp” thành 1 nhóm, chứ nó không “kết hợp” (1+2)^5
thành 1 nhóm. Đó mới là ý nghĩa của kết hợp.
Cái vụ thứ tự thực hiện, thì bên C++ có 1 keyword advance hơn nhiều là sequence point còn C thì có rule như này
cám ơn bạn, mĩnh đã hiểu rồi.
đoạn này bạn bị sai nhé, nếu nó tính xong mới xét tiếp đoạn sau thì sai hoàn toàn vì nếu như thế 2 đoạn code sau tương đương int a; 1?printf(“1”):a = 2; ( code bị lỗi) , int a; 1?printf(“1”):(a = 2)(code này chạy); vậy rõ ràng bạn bảo C0?a:C1?b:c tương đương với C0?a:(C1?b:c) là sai.
thực ra mình nghĩ đoạn này bạn nói tính 4 là bị sai. Trong môn cấu trúc dữ liệu và thuật toán hẳn bạn đã được học về stack, chắc chắn nếu bn học bạn sẽ học được bài là cây nhị phân biểu thức, có đề cập tới biểu thức prefix, infix và postfix. Thứ tự thực hiện các phép toán trong chương trình cũng như vậy. 4 không được “tính” mà sẽ được cho vào trong ngăn nhớ stack, và vì 1+2 ở trong ngoặc nên nó được thực hiện trước chứ không phải là " chúng ta không thể xác định được là compiler sẽ tính 1+2
trước hay là 5^6
" như bạn nói. Mọi việc sẽ rất là dễ hiểu nếu bạn “có học” cấu trúc dữ liệu và thuật toán. Hi vọng bạn có thể tìm đọc lại để có câu trả lời xem là à, nó thực sự thực hiện từ phải qua trái “như thế nào”
Khi có các toán tử khác nhau thì căn cứ vào độ ưu tiên để thêm ngoặc chứ. Do =
ưu tiên thấp hơn ?:
nên sẽ trở thành (1?printf(“1”):a) = 2; // lỗi gán cho rvalue
Cái bạn nói nghe thì có vẻ rất đúng theo lý thuyết (và có thể theo thực tế), nhưng nếu bạn có đọc link về sequence point bên trên, bạn sẽ thấy ngay đoạn đầu tiên như này:
Order of evaluation of any part of any expression, including order of evaluation of function arguments is unspecified (with some exceptions listed below). The compiler can evaluate operands and other subexpressions in any order, and may choose another order when the same expression is evaluated again.
Dịch nôm na, có nghĩa là: ví dụ với biểu thức (1+2)+(3+4)
, C++ compiler hoàn toàn có quyền tính 3+4
trước, rồi mới tính 1+2
, rồi cộng 2 kết quả kia lại với nhau, chứ không bắt buộc phải tính từ trái qua phải. Điều này quy định rõ trong C++ standard, nên là hy vọng bạn đọc và tìm hiểu kỹ trước để có mặt bằng chung thảo luận tiếp nhé.
Bên trên, mình dùng chữ "tính" 4
, với tính
trong ngoặc kép, kèm ví dụ (1+2)^5^6
là để thể hiện rõ: nếu 4 là một biểu thức khác, thì compiler có quyền chọn tính cái nào trước cũng được
Ok thực ra mình đã xem trước khi bình luận, tuy nhiên trang này là một nguồn tài liệu tham khảo chứ không hoàn toàn chính xác hết được giả sử bạn code cho mình đoạn b = ++a + ++a + ++a với a=10 thì bạn nghĩ compiler sẽ trả kết quả bnh ? Và tại sao ko phải là 39 , hoàn toàn là do cài đặt ctdl gây ra side effects. ngoài ra việc compiler thực hiện phép tính nào trước thì bạn có vẻ đang hiểu sai ý mình, trong phép toán có sự kết hợp của các toán tử khác nhau thì chúng ta mới đang nói đến thứ tự thực hiện thì hiển nhiên 1+3*5 sẽ nhân 3 với 5 trước chứ làm gì phải là tùy ý như bạn nói? Còn ví dụ về việc tính của bạn thì có lẽ mình hiểu sai ý bạn nhưng mà trong ngoặc trước ngoài ngoặc sau còn về vde của bạn thì phép mũ thực hiện từ phải qua trái là rõ ràng còn việc 4^5 trước hay 5^6 trước thì hiển nhiên 5^6 trước chứ sao mà 4^5 trước được? Vde toán cơ bản mà vậy còn ví dụ bạn đưa ra ở bình luận mới, thì cứ cho là nó sẽ thực hiện tùy ý đi, nó cũng ko liên quan đến bình luận của bạn bên trên
Nếu bạn đọc đến cuối trang, phần References, bạn sẽ thấy mục ISO/IEC 14882:xxxx, đấy chính là tài liệu chính xác đấy bạn. Và nếu bạn không tìm được thông tin cần thiết trong mấy cái ISO đấy, thì bạn xem mục 6.9.1 sẽ rõ nhé.
Nếu bạn không rõ được code này là UB, hoặc không biết UB là gì thì mình xin phép không thảo luận tiếp về đoạn code này nhé.
Và đây là ý của bạn:
Thì mình khẳng định luôn là ý bạn hoàn toàn sai. 1+2
sẽ không bắt buộc phải tính trước 5^6
trong (1+2)^5^6
với phép ^
là phép lấy mũ.
Phần này thì mình hoàn toàn đồng ý. Nhưng ví dụ này của bạn là về operator precedence của các operator khác nhau, không liên quan gì đến vấn đề kết hợp (associativity) giữa các operator giống nhau (chỉ có ternary operation hoặc chỉ có hàm mũ) như bên trên đã thảo luận. Mình nghĩ comment của bạn @rogp10 ở Hỏi về toán tử 3 ngôi - ternary operation đã cố gắng giải thích cho bạn hiểu rõ vấn đề đang thảo luận là gì để kéo bạn về đúng chủ đề rồi đấy.
Không biết là bạn đọc đoạn này từ đâu, chứ đoạn bên trên rõ ràng là mình chỉ bảo tính 4
trước, chứ không phải 4^5
. Mong bạn đọc kỹ lại trước khi thảo luận nhé.
Ví dụ này là ví dụ trực tiếp, đơn giản, dễ hiểu nhất mình có thể nghĩ ra để giải thích link sequence point (đã được đề cập từ May 18, 2020). Nên bản thân mình thì thấy hoàn toàn có liên quan nhé.
Về topic này, mình nghĩ đã giải thích đủ cho chủ topic hiểu. Nên nếu bạn @Cuong_Trinh_Viet còn thắc mắc nào chưa rõ liên quan topic thì mình sẵn lòng giải thích tiếp nhé. Còn thảo luận ngoài lề, có lẽ một topic mới sẽ tốt hơn.
Thân
C++ có hàm mũ đâu mà đi bàn luận hàm mũ thế này thế kia
bạn kia chắc chưa biết C++ order of evaluation là unspecified rồi https://en.cppreference.com/w/cpp/language/eval_order
trong C++ gọi hàm f(<expression A>, <expression B>)
thì chuẩn ko bắt buộc expression A được eval trước hay là expression B được eval trước. Nên có thể có trình dịch eval B rồi A rồi gọi f, có trình dịch eval A rồi B rồi gọi f.
toán tử + cũng là 1 hàm. Viết <expression A> + <expression B>
thì trình dịch nó hiểu là gọi hàm operator+(<expression A>, <expression B>)
. Ví dụ gọi foo() + bar()
thì tùy trình dịch mà foo() được gọi trước bar() hay bar() được gọi trước foo(). Thế nên viết (a+b) * (c+d) thì chưa chắc a+b được tính trước hay c+d được tính trước đâu, phải xem trình dịch nó biên dịch ra thế nào nữa
cái này khác với nhiều ngôn ngữ. Hình như chỉ có C và C++ là ko specified order of evaluation này. Tất cả các ngôn ngữ lập trình khác đều quy định hoặc là trái qua phải hoặc là phải qua trái hết Trong toán thì thứ tự này ko là vấn đề vì trong toán học ko có biến số, toàn là hằng số Ví dụ ax^2 + bx + c = 0 giải ra 2 đáp số cũng đặt tên khác nhau là x0 x1, nên trong toán thật ra ko có biến số Còn trong ngôn ngữ lập trình thì thứ tự tính toán có vấn đề thật, vì 1 hàm có thể có side effect, tức là hàm này thay đổi giá trị biến bên ngoài nó. Ví dụ foo() + bar()
, foo sửa biến string s thành hello, bar sửa biến s thành world, cả 2 cùng trả về reference tới s thì nếu foo được eval trước bar kết quả sẽ là worldworld, nhưng nếu bar eval trước thì kết quả lại là hellohello
đây nha: https://godbolt.org/z/MeW7fr4eE
clang in ra worldworld, g++ in ra hellohello
ớ mà hình như có thể toy nói sai nữa. Nếu foo và bar cùng modify s thì kết quả foo() + bar()
có thể là UB hay sao ấy, nếu như foo xài s += “hi” bar xài s += “hello”: trình dịch có thể tự biên tự diễn interleave các bước trong operator +=: allocate s ra 1 chỗ khác dư 5 chỗ trống cho “hello” trước, rồi sau đó allocate s ra 1 chỗ khác dư 2 chỗ cho “hi”, rồi chép “hello” vào s chỉ có dư 2 chỗ trống này dẫn tới UB
nói chung ví dụ của toy là hơi xấu xí nhưng cũng đủ để thấy C++ nó ko bắt buộc vế trái tính trước hay vế phải tính trước hay f(a1, a2, a3, a4, …, an) thì ko bắt buộc a1 trước a2 a3 v.v…, có thể nó tính a2 a4 an a1 vẫn được.
Đều là indeterminately sequenced nên không sao.
- A function call that is not sequenced before or sequenced after another expression evaluation outside of the function (possibly another function call) is indeterminately sequenced with respect to that evaluation
ok, mình hiểu UB là gì, còn ý mình, UB này được gây ra do cài đặt các cấu trúc dữ liệu, vì vậy việc các cấu trúc dữ liệu được cài đặt là có cơ sở còn về ý phản hồi của bạn, mình xin tiếp nhận để rút kinh nghiệm. Cảm ơn bạn