Tại sao mã này, được viết ngược, in ấn Hello Hello World!


261

Đây là một số mã mà tôi tìm thấy trên Internet:

class M‮{public static void main(String[]a‭){System.out.print(new char[]
{'H','e','l','l','o',' ','W','o','r','l','d','!'});}}    

Mã này in Hello World!lên màn hình; bạn có thể nhìn thấy nó chạy ở đây . Tôi có thể thấy rõ public static void mainbằng văn bản, nhưng nó là ngược. Làm thế nào để mã này hoạt động? Làm thế nào điều này thậm chí biên dịch?

Chỉnh sửa: Tôi đã thử mã này trong IntellIJ và nó hoạt động tốt. Tuy nhiên, vì một số lý do, nó không hoạt động trong notepad ++, cùng với cmd. Tôi vẫn chưa tìm thấy giải pháp cho vấn đề đó, vì vậy nếu có ai làm vậy, hãy bình luận xuống bên dưới.


38
Điều này thật buồn cười ... Có gì để làm với sự hỗ trợ của RTL không?
Eugene Sh.

12
Có ký tự Unicode # 8237; ngay sau Mvà cũng sau []a: fileformat.info/info/unicode/char/202d/index.htm Nó được gọi là trái sang phải ghi đè lên
Riiverside

45
xkcd bắt buộc: xkcd.com/1137
Pac0

4
Bạn có thể dễ dàng thấy những gì đang diễn ra ở đây chỉ bằng cách thực hiện các lựa chọn trong đoạn mã bằng chuột.
Andreas Rejbrand

14
niam diov citats cilbupnghe có vẻ như một câu tục ngữ Latinh ..
Mick Mnemonic

Câu trả lời:


250

Có những ký tự vô hình ở đây thay đổi cách hiển thị mã. Trong Intellij có thể tìm thấy chúng bằng cách sao chép mã dán vào một chuỗi rỗng ( ""), thay thế chúng bằng các lối thoát Unicode, loại bỏ các hiệu ứng của chúng và tiết lộ thứ tự trình biên dịch nhìn thấy.

Đây là đầu ra của bản sao-dán đó:

"class M\u202E{public static void main(String[]a\u202D){System.out.print(new char[]\n"+
        "{'H','e','l','l','o',' ','W','o','r','l','d','!'});}}   "

Các ký tự mã nguồn được lưu trữ theo thứ tự này và trình biên dịch coi chúng là theo thứ tự này, nhưng chúng được hiển thị khác nhau.

Lưu ý \u202Eký tự, ghi đè từ phải sang trái, bắt đầu một khối trong đó tất cả các ký tự buộc phải được hiển thị từ phải sang trái và \u202D, đó là ghi đè từ trái sang phải, bắt đầu một khối lồng nhau trong đó tất cả các ký tự được buộc theo thứ tự từ trái sang phải, ghi đè lên phần ghi đè đầu tiên.

Ergo, khi nó hiển thị mã gốc, class Mđược hiển thị bình thường, nhưng \u202Eđảo ngược thứ tự hiển thị của mọi thứ từ đó sang \u202D, đảo ngược mọi thứ một lần nữa. (Chính thức, mọi thứ từ đầu cuối \u202Dđến dòng bị đảo ngược hai lần, một lần do \u202Dvà một lần với phần còn lại của văn bản bị đảo ngược do \u202E, đó là lý do tại sao văn bản này hiển thị ở giữa dòng thay vì kết thúc.) Định hướng của dòng tiếp theo được xử lý độc lập với dòng đầu tiên do bộ kết thúc dòng, do đó {'H','e','l','l','o',' ','W','o','r','l','d','!'});}}được hiển thị bình thường.

Để biết thuật toán hai chiều đầy đủ (cực kỳ phức tạp, dài hàng chục trang), hãy xem Phụ lục tiêu chuẩn Unicode # 9 .


Bạn không giải thích trình biên dịch (trái ngược với thói quen hiển thị) làm gì với chính các ký tự Unicode đó. Tôi có thể bỏ qua chúng hoàn toàn (hoặc coi chúng là khoảng trắng) hoặc nó có thể hiểu chúng là thực sự đóng góp cho mã nguồn. Tôi không biết các quy tắc Java ở đây, nhưng thực tế là chúng được đặt ở cuối các số nhận dạng không được sử dụng khác gợi ý cho tôi rằng nó có thể là cái sau và các ký tự Unicode thực tế là một phần của các tên định danh đó.
Marc van Leeuwen

Điều này sẽ làm việc theo cách tương tự trong c #, không quan tâm?
IanF1

14
@ IanF1 Nó sẽ hoạt động trong bất kỳ ngôn ngữ nào mà trình biên dịch / trình thông dịch coi các ký tự RTL và LTR là khoảng trắng. Nhưng đừng bao giờ làm điều này trong mã sản xuất nếu bạn hoàn toàn coi trọng sự tỉnh táo của người tiếp theo chạm vào mã của bạn, đó cũng có thể là bạn.
wizzwizz4

2
Hay nói cách khác: "Luôn luôn mã hóa như thể người cuối cùng duy trì mã của bạn là một kẻ tâm thần bạo lực, người biết bạn sống ở đâu." , @ IanF1. Hoặc có lẽ: "Luôn luôn mã như thể người cuối cùng duy trì mã của bạn sẽ đặt tên và xấu hổ cho bạn là tác giả ban đầu trên Stack Overflow."
Cody Grey

43

Nó trông khác nhau vì Thuật toán hai chiều Unicode . Có hai ký tự vô hình của RLO và LRO mà Thuật toán hai chiều Unicode sử dụng để thay đổi giao diện trực quan của các ký tự được lồng giữa hai siêu ký tự này.

Kết quả là trực quan họ nhìn theo thứ tự ngược lại, nhưng các ký tự thực tế trong bộ nhớ không bị đảo ngược. Bạn có thể phân tích kết quả ở đây . Trình biên dịch Java sẽ bỏ qua RLO và LRO và coi chúng là khoảng trắng, đó là lý do tại sao mã biên dịch.

Lưu ý 1: Thuật toán này được các trình soạn thảo văn bản và trình duyệt sử dụng để hiển thị trực quan các ký tự cả ký tự LTR (tiếng Anh) và ký tự RTL (ví dụ tiếng Ả Rập, tiếng Do Thái) cùng một lúc - do đó "hai chiều". Bạn có thể đọc thêm về Thuật toán hai chiều tại trang web của Unicode .
Lưu ý 2: Hành vi chính xác của LRO và RLO được xác định trong Phần 2.2 của Thuật toán.


Mục đích của một khả năng như vậy là gì?
Eugene Sh.

6
Những ký tự này đôi khi cần thiết để hiển thị chính xác tiếng Ả Rập và tiếng Do Thái. Các ngôn ngữ này được đọc và viết từ phải sang trái (RTL), ký tự đầu tiên được đọc / viết xuất hiện ở phía bên tay phải . Bạn có thể đọc thêm ở đây .
James Lawson

Tuy nhiên, các ký tự tiếng Ả Rập và tiếng Do Thái thực chất là RTL - chúng sẽ xuất hiện RTL ngay cả khi không có ghi đè rõ ràng và thậm chí chúng sẽ tự động đảo ngược thứ tự của một số ký tự khác gần đó, tôi nghĩ chủ yếu là dấu câu - vì vậy, ghi đè rõ ràng là rất cần thiết.
user2357112 hỗ trợ Monica

Trang này ở đây mô tả khi ghi đè là cần thiết. @ user2357112 là đúng, họ hiếm khi cần. Thật vậy, khi bạn có dấu câu, trích dẫn và số - những ký tự đặc biệt này được coi là "trung tính". Đối với một máy tính không thể đọc các từ và hiểu ngữ cảnh, không rõ nên coi chúng là LTR hay RTL, nhưng thuật toán thầu phải chọn một số thứ tự. Đôi khi nó "hiểu sai" và bạn cần sử dụng các ký tự ghi đè này để "sửa nó".
James Lawson

3
Ngoài ra, U + 202E và U + 202D không được coi là khoảng trắng. Java chỉ coi không gian ASCII, tab ngang, nguồn cấp dữ liệu biểu mẫu và CR / LF / CRLF là khoảng trắng . Chúng thực sự là một phần của các từ định danh M\u202Ea\u202D, nhưng những định danh đó dường như được coi là tương đương với Ma. (JLS không làm tốt công việc giải thích điều này.)
user2357112 hỗ trợ Monica

28

Nhân vật U+202Ephản chiếu mã từ phải sang trái, mặc dù vậy nó rất thông minh. Được ẩn bắt đầu trong M,

"class M\u202E{..."

Làm thế nào tôi tìm thấy điều kỳ diệu đằng sau điều này?

Chà, lúc đầu khi tôi thấy câu hỏi tôi khó khăn, "đó là một trò đùa, để mất thời gian của người khác", nhưng sau đó, tôi đã mở IDE của mình ("IntelliJ"), tạo một lớp và vượt qua mã ... và nó được biên soạn !!! Vì vậy, tôi đã xem xét kỹ hơn và thấy rằng "khoảng trống tĩnh công cộng" đã lạc hậu, vì vậy tôi đã đến đó bằng con trỏ và xóa một vài ký tự ... Và điều gì xảy ra? Các ký tự bắt đầu xóa lùi , vì vậy, tôi nghĩ mmm .... hiếm ... tôi phải thực hiện nó ... Vì vậy, tôi tiến hành thực hiện chương trình, nhưng trước tiên tôi cần phải lưu nó ... và đó là khi tôi Tìm thấy rồi! . Tôi không thể lưu tệp vì IDE của tôi nói rằng có một mã hóa khác cho một số char và chỉ cho tôi biết nó ở đâu, Vì vậy, tôi bắt đầu một nghiên cứu trên Google về các ký tự đặc biệt có thể thực hiện công việc, và đó là :)

Một chút về

Thuật toán hai chiều Unicode và U+202Ecó liên quan, giải thích ngắn gọn :

Tiêu chuẩn Unicode quy định một trật tự biểu diễn bộ nhớ được gọi là thứ tự logic. Khi văn bản được trình bày theo hàng ngang, hầu hết các tập lệnh hiển thị các ký tự từ trái sang phải. Tuy nhiên, có một số tập lệnh (như tiếng Ả Rập hoặc tiếng Do Thái) trong đó thứ tự tự nhiên của văn bản ngang được hiển thị là từ phải sang trái. Nếu tất cả các văn bản có hướng ngang thống nhất, thì thứ tự của văn bản hiển thị là không rõ ràng.

Tuy nhiên, vì các tập lệnh từ phải sang trái này sử dụng các chữ số được viết từ trái sang phải, văn bản thực sự là hai chiều: hỗn hợp văn bản từ phải sang trái và từ trái sang phải. Ngoài chữ số, các từ được nhúng từ tiếng Anh và các chữ viết khác cũng được viết từ trái sang phải, cũng tạo ra văn bản hai chiều. Nếu không có một đặc điểm kỹ thuật rõ ràng, sự mơ hồ có thể phát sinh trong việc xác định thứ tự của các ký tự được hiển thị khi hướng ngang của văn bản không đồng nhất.

Phụ lục này mô tả thuật toán được sử dụng để xác định hướng cho văn bản Unicode hai chiều. Thuật toán mở rộng mô hình ẩn hiện đang được sử dụng bởi một số triển khai hiện có và thêm các ký tự định dạng rõ ràng cho các trường hợp đặc biệt. Trong hầu hết các trường hợp, không cần bao gồm thông tin bổ sung với văn bản để có được thứ tự hiển thị chính xác.

Tuy nhiên, trong trường hợp văn bản hai chiều, có những trường hợp một thứ tự hai chiều ngầm định không đủ để tạo ra văn bản dễ hiểu. Để xử lý các trường hợp này, một bộ ký tự định dạng hướng tối thiểu được xác định để kiểm soát thứ tự các ký tự khi được hiển thị. Điều này cho phép kiểm soát chính xác thứ tự hiển thị để trao đổi rõ ràng và đảm bảo rằng văn bản đơn giản được sử dụng cho các mục đơn giản như tên tệp hoặc nhãn luôn có thể được sắp xếp chính xác để hiển thị.

Tại sao tạo ra một số thuật toán như thế này ?

thuật toán bidi có thể hiển thị một chuỗi các ký tự tiếng Ả Rập hoặc tiếng Do Thái lần lượt từ phải sang trái.


4

Chương 3 của đặc tả ngôn ngữ cung cấp một lời giải thích bằng cách mô tả chi tiết cách dịch thuật từ vựng được thực hiện cho một chương trình Java. Điều quan trọng nhất cho câu hỏi:

Các chương trình được viết bằng Unicode (§3.1) , nhưng các bản dịch từ vựng được cung cấp (§3.2) để có thể sử dụng các lối thoát Unicode (§3.3) để bao gồm bất kỳ ký tự Unicode nào chỉ sử dụng các ký tự ASCII.

Vì vậy, một chương trình được viết bằng các ký tự Unicode và tác giả có thể thoát chúng bằng cách sử dụng \uxxxxtrong trường hợp mã hóa tệp không hỗ trợ ký tự Unicode, trong trường hợp đó, nó được dịch sang ký tự phù hợp. Một trong những ký tự Unicode có trong trường hợp này là \u202E. Nó không được hiển thị trực quan trong đoạn trích, nhưng nếu bạn thử chuyển đổi mã hóa của trình duyệt, các ký tự ẩn có thể xuất hiện.

Do đó, kết quả dịch thuật từ vựng trong khai báo lớp:

class M\u202E{

có nghĩa là định danh lớp là M\u202E. Thông số kỹ thuật coi đây là một định danh hợp lệ:

Identifier:
    IdentifierChars but not a Keyword or BooleanLiteral or NullLiteral
IdentifierChars:
    JavaLetter {JavaLetterOrDigit}

"Chữ cái hoặc chữ số Java" là một ký tự mà phương thức Character.isJavaIdentifierPart(int)trả về giá trị true.


Xin lỗi nhưng điều này là lạc hậu (ý định chơi chữ). Không có lối thoát trong mã nguồn; bạn đang mô tả làm thế nào nó có thể được viết. Và, nó biên dịch thành một lớp có tên "M" (chỉ một ký tự).
Tom Blodget

@TomBlodget Thật vậy, nhưng điểm (trong thực tế tôi đã nhấn mạnh trong trích dẫn đặc tả) là trình biên dịch cũng có thể xử lý các ký tự Unicode thô. Đó thực sự là toàn bộ lời giải thích. Bản dịch thoát chỉ là một thông tin bổ sung và không liên quan trực tiếp đến trường hợp này. Đối với lớp được biên dịch, tôi nghĩ đó là do ký tự chuyển đổi RTL bằng cách nào đó bị trình biên dịch loại bỏ. Tôi sẽ cố gắng để xem nếu điều này được mong đợi, nhưng tôi nghĩ sẽ xảy ra sau giai đoạn dịch thuật từ vựng.
M Anouti
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.