ChatGPT解决这个技术问题 Extra ChatGPT

为什么这段代码,倒写,打印“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! 打印到屏幕上;您可以看到它运行 here。我可以清楚地看到 public static void main 写的,但它是倒退的。这段代码是如何工作的?这甚至如何编译?

编辑:我在 IntellIJ 中尝试了这段代码,它工作正常。但是,由于某种原因,它在 notepad++ 和 cmd 中不起作用。我还没有找到解决方案,所以如果有人这样做,请在下面评论。

这个很有趣...与 RTL 支持有关吗?
有Unicode字符#8237;在 M 之后和 []a 之后:fileformat.info/info/unicode/char/202d/index.htm 它被称为 LEFT-TO-RIGHT OVERRIDE
强制性 xkcd:xkcd.com/1137
只需使用鼠标在代码片段中进行选择,您就可以很容易地看到这里发生了什么。
niam diov citats cilbup 听起来像是拉丁谚语。

D
Davis Broda

这里有一些不可见的字符会改变代码的显示方式。在 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 而一次是由于 \u202E 而其余的文本被反转了,这就是为什么这个文本出现在中间由于行终止符,下一行的方向性独立于第一行的方向性处理,因此 {'H','e','l','l','o',' ','W','o','r','l','d','!'});}} 正常显示。

有关完整的(极其复杂,几十页长)Unicode 双向算法,请参阅Unicode Standard Annex #9


您没有解释编译器(相对于显示例程)对这些 Unicode 字符本身做了什么。我可能会直接忽略它们(或将它们视为空白),或者它可能会将它们解释为实际上对源代码有贡献。我不知道这里的 Java 规则,但它们被放置在其他未使用的标识符末尾的事实向我表明它可能是后者,而 Unicode 字符实际上是这些标识符名称的一部分。
出于兴趣,这会在 c# 中以同样的方式工作吗?
@IanF1 它可以在编译器/解释器将 RTL 和 LTR 字符视为空格的任何语言中工作。但是,如果您完全重视下一个接触您的代码的人的理智,那么永远不要在生产代码中这样做,这很可能是您。
或者,换句话说:"Always code as if the person who ends up maintaining your code is a violent psychopath who knows where you live.",@IanF1。或者也许是:“始终编写代码,就好像最终维护您的代码的人会以 Stack Overflow 的原始作者的身份辱骂您一样。”
J
James Lawson

由于 Unicode Bidirectional Algorithm,它看起来不同。 Unicode 双向算法使用 RLO 和 LRO 两个不可见字符来更改嵌套在这两个元字符之间的字符的视觉外观

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

注 1:文本编辑器和浏览器使用此算法来同时直观地显示 LTR 字符(英语)和 RTL 字符(例如阿拉伯语、希伯来语)的字符 - 因此是“双向”的。您可以在 Unicode 的 website 阅读有关双向算法的更多信息。
注意 2:LRO 和 RLO 的确切行为在算法的 Section 2.2 中定义。


这种能力的目的是什么?
有时需要这些字符才能在视觉上正确呈现阿拉伯语和希伯来语。这些语言是从右到左 (RTL) 读写的,被读/写的第一个字符出现在右手边。您可以阅读更多here
不过,阿拉伯语和希伯来语字符本质上是 RTL - 即使没有显式覆盖,它们也会出现 RTL,它们甚至会自动反转附近某些其他字符的顺序,我认为主要是标点符号 - 所以很少需要显式覆盖。
此页面 here 描述了何时需要覆盖。 @user2357112 是对的,它们很少需要。事实上,当你有标点、引号和数字时——这些特殊字符被认为是“中性的”。对于无法阅读单词并理解上下文的计算机,不清楚是将它们视为 LTR 还是 RTL,但双向算法必须选择 some 排序。有时它“弄错了”,您需要使用这些覆盖字符来“纠正它”。
此外,U+202E 和 U+202D 不被视为空白。 Java 只考虑 ASCII space, horizontal tab, form feed, and CR/LF/CRLF as whitespace。它们实际上是标识符 M\u202Ea\u202D 的词法部分,但这些标识符似乎被视为等同于 Ma。 (JLS 没有很好地解释这一点。)
D
Damian Lattenero

字符 U+202E 从右到左反映了代码,但它非常聪明。隐藏在 M 开头,

"class M\u202E{..."

我是如何发现这背后的魔力的?

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

一点关于

Unicode 双向算法和涉及的 U+202E,简要说明 explain

Unicode 标准规定了一种称为逻辑顺序的内存表示顺序。当文本以水平线显示时,大多数脚本从左到右显示字符。但是,有几个脚本(例如阿拉伯语或希伯来语)显示水平文本的自然顺序是从右到左。如果所有文本具有统一的水平方向,则显示文本的顺序是明确的。但是,因为这些从右到左的脚本使用从左到右书写的数字,所以文本实际上是双向的:从右到左和从左到右文本的混合。除了数字,来自英语和其他脚本的嵌入单词也从左到右书写,也产生双向文本。如果没有明确的规范,当文本的水平方向不统一时,在确定显示字符的顺序时可能会出现歧义。本附件描述了用于确定双向 Unicode 文本方向性的算法。该算法扩展了许多现有实现当前使用的隐式模型,并为特殊情况添加了显式格式化字符。在大多数情况下,无需在文本中包含其他信息即可获得正确的显示顺序。但是,在双向文本的情况下,在某些情况下,隐式双向排序不足以产生可理解的文本。为了处理这些情况,定义了一组最小的方向格式字符来控制渲染时字符的顺序。这允许精确控制显示顺序以实现清晰的交换,并确保用于简单项目(如文件名或标签)的纯文本始终可以正确排序以进行显示。

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

比迪算法可以从右到左依次渲染一系列阿拉伯或希伯来字符。


M
M A

Chapter 3 of the language specification 通过详细描述如何为 Java 程序完成词法转换来提供解释。对这个问题最重要的是:

程序是用 Unicode(第 3.1 节)编写的,但提供了词汇翻译(第 3.2 节),因此 Unicode 转义(第 3.3 节)可用于包含仅使用 ASCII 字符的任何 Unicode 字符。

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

因此,词法翻译导致类声明:

class M\u202E{

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

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

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


抱歉,这是落后的(双关语)。源代码中没有转义;您正在描述它是如何编写的。而且,它编译成一个名为“M”的类(只有一个字符)。
@TomBlodget 确实,但重点(实际上我在规范引用中强调了这一点)是编译器还可以处理原始 Unicode 字符。这就是全部的解释。转义翻译只是一个附加信息,与本案没有直接关系。至于编译的类,我认为这是因为 RTL 开关字符被编译器以某种方式丢弃了。我会尝试看看这是否是预期的,但我认为发生在词汇翻译阶段之后。
E
Eric Silveira

这实际上是因为 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 得到这个)。希望这个答案可以帮助您了解它是如何工作的。