为什么这段代码,倒着写,打印“Hello World!”

以下是我在网上找到的一些代码:

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

这段代码将Hello World!打印到屏幕上;你可以看到它运行在这里。我可以清楚地看到public static void main被写入,但它是反向的。这段代码是如何工作的?这是如何编译的呢?

编辑:我在IntellIJ中尝试了这个代码,它工作得很好。然而,由于某种原因,它不能在notepad++中与cmd一起工作。我还没有找到解决方案,所以如果有人找到了,请在下方评论。

16840 次浏览

字符U+202E从右向左镜像代码,这是非常聪明的。从M开始隐藏,

"class M\u202E{..."

我是如何在这后面找到魔法的?

好吧,一开始当我看到这个问题时,我很难,“这是一个笑话,浪费别人的时间”,但后来,我打开我的IDE(“IntelliJ”),创建一个类,并通过代码…然后编译 ! ! 所以,我仔细看了一下,发现“public static void”是向后的,所以我用光标去了那里,擦去一些字符… 发生了什么?字符开始向后擦去,所以,我想嗯....罕见的…我必须执行它……所以我继续执行程序,但首先我需要保存它…那就是当我找到它的时候!。我无法保存文件,因为我的IDE说,有一个不同的字符编码,告诉我它在哪里,所以我开始在谷歌的特殊字符,可以做这项工作的研究,就是这样:)

有点关于

Unicode双向算法,并涉及U+202E,一个简单的解释:

Unicode标准规定了一种称为逻辑顺序的内存表示顺序。当文本以水平线显示时,大多数脚本从左到右显示字符。然而,有几种脚本(如阿拉伯语或希伯来语)的水平文本显示的自然顺序是从右到左。如果所有文本都具有统一的水平方向,则显示文本的顺序是明确的。

但是,由于这些从右到左的脚本使用从左到右书写的数字,因此文本实际上是双向的:从右到左和从左到右的混合文本。除了数字之外,嵌入英语和其他脚本的单词也是从左向右书写的,同样产生双向文本。如果没有明确的规范,当文本的水平方向不一致时,在确定显示字符的顺序时会出现歧义。

本附件描述了用于确定双向Unicode文本方向的算法。该算法扩展了目前许多现有实现所采用的隐式模型,并为特殊情况添加了显式格式化字符。在大多数情况下,不需要在文本中包含额外的信息来获得正确的显示顺序。

然而,在双向文本的情况下,隐含的双向顺序并不足以产生可理解的文本。为了处理这些情况,定义了一个最小的方向格式化字符集来控制呈现时字符的顺序。这允许对显示顺序进行精确控制,以实现易读的交换,并确保用于简单项目(如文件名或标签)的纯文本始终能够正确地进行显示顺序。

为什么要创建像这样的算法?

bidi算法可以呈现阿拉伯语或希伯来语序列

.

.

这里有一些看不见的字符可以改变代码的显示方式。在Intellij中,可以通过将代码复制粘贴到空字符串("")中来找到这些字符,这将用Unicode转义替换它们,删除它们的效果并显示编译器看到的顺序。

下面是复制粘贴的输出:

"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','!'});}}   "

源代码字符按此顺序存储,编译器也按此顺序处理它们,但它们的显示方式不同。

注意\u202E字符,这是一个从右到左的覆盖,开始一个块,其中所有字符都被强制从右向左显示;\u202D是一个从左到右的覆盖,开始一个嵌套块,其中所有字符都被强制从左到右的顺序,覆盖第一个覆盖。

因此,当它显示原始代码时,class M将正常显示,但\u202E将从那里到\u202D的所有内容的显示顺序颠倒,而\u202D将再次颠倒所有内容。(形式上,从\u202D到行结束符的所有内容都要反转两次,一次是由于\u202D,另一次是由于\u202E,其余文本反转,这就是为什么这个文本出现在行中间而不是末尾。)由于行结束符,下一行的方向将独立于第一行的方向进行处理,因此{'H','e','l','l','o',' ','W','o','r','l','d','!'});}}将正常显示。

完整的Unicode双向算法(极其复杂,长达数十页),请参见Unicode标准附录#9

它看起来不同,因为Unicode双向算法。有两个RLO和LRO的不可见字符,Unicode双向算法使用它们来改变嵌套在这两个元字符之间的字符的视觉外观

结果是在视觉上它们看起来是倒序的,但实际的字符在内存中并没有倒序。你可以分析结果在这里。Java编译器将忽略RLO和LRO,并将它们视为空白,这就是代码编译的原因。

注1:此算法被文本编辑器和浏览器用于可视化地显示LTR字符(英文)和RTL字符(如英文)。 阿拉伯语,希伯来语)同时在一起-因此“bi”-directional。你可以阅读更多关于双向算法的内容 在Unicode的网站.
注2:LRO和RLO的确切行为在2.2节 of中定义 算法。< / em > < / p >

第三章语言规范通过详细描述如何为Java程序进行词法翻译来提供解释。最重要的问题是:

程序使用Unicode(§3.1)编写,但提供了词法翻译(§3.2),以便Unicode转义(§3.3)可用于包含仅使用ASCII字符的任何Unicode字符。

因此,一个程序是用Unicode字符编写的,如果文件编码不支持Unicode字符,作者可以使用\uxxxx转义它们,在这种情况下,它会被翻译成适当的字符。本例中出现的Unicode字符之一是\u202E。它没有在代码片段中显示,但如果您尝试切换浏览器的编码,则可能会出现隐藏字符。

因此,词法翻译的结果是类声明:

class M\u202E{

这意味着类标识符是M\u202E规范认为这是一个有效的标识符:

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

“Java字母或数字”是方法Character.isJavaIdentifierPart(int)为其返回true的字符。

这实际上是因为Unicode双向支持。

U+202E右到左覆盖
U+202D从左到右覆盖

所以,这些是一些棘手的字符。它们实际上是为从右向左的语言支持而定义的。真正的代码是

class M<U+202E>{public static void main(String[]a<U+202D>){System.out.print(new char[]
{'H','e','l','l','o',' ','W','o','r','l','d','!'});}}

(通过粘贴到cmd.exe)。希望这个答案能帮助你了解它是如何工作的。