对于数组,为什么会是 a[5] == 5[a]?

With arrays, why is it the case that a[5] == 5[a]?

提问人:Dinah 提问时间:12/20/2008 最后编辑:CœurDinah 更新时间:12/29/2022 访问量:128698

问:

正如 Joel 在 Stack Overflow 播客 #34 中指出的那样,在 C 编程语言(又名:K & R)中,提到了 C 语言中数组的这个属性:a[5] == 5[a]

乔尔说这是因为指针算术,但我还是不明白。为什么 a[5] == 5[a]

c 数组 指针 算术

评论

61赞 Egon 5/14/2010
像 a[+] 这样的东西也会像 *( a++) 或 *(++a) 一样工作吗?
56赞 Dinah 5/14/2010
@Egon:这很有创意,但不幸的是,编译器不是这样工作的。编译器解释为一系列标记,而不是字符串:*({整数位置}a {运算符}+ {整数}1)与*({integer}1 {运算符}+ {整数位置}a)相同,但与*({整数位置}a {运算符}+ {运算符}+)不同a[1]
15赞 Jonathan Leffler 10/17/2012
在非逻辑数组访问中说明了一个有趣的复合变体,其中您拥有并用作表达式。char bar[]; int foo[];foo[i][bar]
7赞 ach 3/15/2014
@EldritchConundrum,为什么你认为“编译器无法检查左侧部分是否为指针”?是的,它可以。诚然,对于任何给定的 和 ,= 是语言设计者的自由选择,可以定义所有类型的可交换性。没有什么能阻止他们在允许.a[b]*(a + b)ab+i + pp + i
15赞 Eldritch Conundrum 3/18/2014
@Andrey 人们通常期望是可交换的,所以也许真正的问题是选择使指针运算类似于算术,而不是设计一个单独的偏移运算符。+

答:

2128赞 Mehrdad Afshari 12/20/2008 #1

C 标准对运算符的定义如下:[]

a[b] == *(a + b)

因此,将评估为:a[5]

*(a + 5)

并将评估为:5[a]

*(5 + a)

a是指向数组的第一个元素的指针。 是离 相距 5 个元素的值,这与 相同,从小学数学中我们知道它们是相等的(加法是可交换的)。a[5]a*(a + 5)

评论

363赞 John MacIntyre 12/20/2008
我想知道它是否更像 *((5 * sizeof(a)) + a)。不过很好的解释。
116赞 Mehrdad Afshari 12/20/2008
@Dinah:从C编译器的角度来看,你是对的。不需要 sizeof,我提到的那些表达式是相同的。但是,编译器在生成机器代码时会考虑 sizeof。如果 a 是一个 int 数组,则将编译为类似而不是a[5]mov eax, [ebx+20][ebx+5]
14赞 James Curran 12/20/2008
@Dinah:A 是一个地址,比如 0x1230。如果 a 在 32 位 int 数组中,则 a[0] 位于 0x1230,a[1] 位于 0x1234,a[2] 位于 0x1238......a[5] 在 x1244 等。如果我们只将 5 加到 0x1230,我们得到 0x1235,这是错误的。
46赞 aib 12/23/2008
@sr105:这是 + 运算符的特例,其中一个操作数是指针,另一个是整数。该标准规定,结果将与指针的类型相同。编译器/必须/足够聪明。
62赞 LarsH 12/2/2010
“从小学数学开始,我们知道这些是平等的”——我知道你在简化,但我和那些觉得这过于简化的人在一起。这不是基本的.换句话说,这里发生的事情比小学算术还要多。交换性主要依赖于编译器识别哪个操作数是指针(以及对象的大小)。换句话说,但是.*(10 + (int *)13) != *((int *)10 + 13)(1 apple + 2 oranges) = (2 oranges + 1 apple)(1 apple + 2 oranges) != (1 orange + 2 apples)
305赞 David Thornley 12/20/2008 #2

因为数组访问是根据指针定义的。 定义为 ,它是可交换的。a[i]*(a + i)

评论

57赞 Lightness Races in Orbit 5/13/2011
数组不是根据指针定义的,但对它们的访问是。
10赞 Jim Balter 4/6/2013
我会加上“所以它等于 ,可以写成 ”。*(i + a)i[a]
4赞 Vality 2/18/2015
我建议你包括标准中的引用,如下所示: 6.5.2.1: 2 后缀表达式后跟方括号 [] 中的表达式是数组对象元素的下标名称。下标运算符 [] 的定义是 E1[E2] 等同于 (*((E1)+(E2)))。由于适用于二进制 + 运算符的转换规则,如果 E1 是数组对象(相当于指向数组对象的初始元素的指针),而 E2 是整数,则 E1[E2] 表示 E1 的第 2 个元素(从零开始计数)。
2赞 Andreas Rejbrand 10/14/2019
吹毛求疵:说“是可交换的”是没有意义的。但是,因为加法是可交换的。*(a + i)*(a + i) = *(i + a) = i[a]
1赞 U. Windl 11/4/2020
@AndreasRejbrand OTOH 是表达式中唯一的二进制运算符,因此很清楚什么是可交换的。+
212赞 James Curran 12/20/2008 #3

当然,还有

 ("ABCD"[2] == 2["ABCD"]) && (2["ABCD"] == 'C') && ("ABCD"[2] == 'C')

造成这种情况的主要原因是,早在 70 年代设计 C 语言时,计算机就没有太多内存(64KB 是很多),因此 C 编译器没有做太多的语法检查。因此,“”被相当盲目地翻译成”X[Y]*(X+Y)"

这也解释了 “” 和 “” 语法。形式 “” 中的所有内容都具有相同的编译形式。但是,如果 B 与 A 是同一对象,则可以进行程序集级优化。但是编译器不够亮,无法识别它,因此开发人员必须 ()。同样,如果 是 ,则可以进行不同的汇编级优化,并且开发人员必须再次明确它,因为编译器无法识别它。(最近编译器这样做了,所以这些语法现在基本上是不必要的)+=++A = B + CA += CC1

评论

21赞 Thomas Padron-McCarthy 12/20/2008
这不是一个神话吗?我的意思是创建 += 和 ++ 运算符是为了简化编译器?有些代码使用它们会变得更加清晰,并且无论编译器对它做什么,它都是有用的语法。
7赞 Johannes Schaub - litb 12/20/2008
+= 和 ++ 还有另一个显着的好处。如果左侧在评估时更改了某个变量,则更改只会进行一次。a = a + ...;会做两次。
10赞 MSalters 9/21/2009
否 - “ABCD”[2] == *(“ABCD” + 2) = *(“CD”) = 'C'。取消引用字符串会给你一个字符,而不是一个子字符串
7赞 John Bode 5/3/2012
@ThomasPadron-McCarthy:从这里开始:“在开发过程中,[Thompson] 一直在与内存限制作斗争:每次添加语言都会使编译器膨胀,使其几乎无法容纳,但每次利用该功能的重写都会减小其大小。例如,B 引入了广义赋值运算符,使用 x=+y 将 y 添加到 x...Thompson 更进一步,发明了 ++ 和 -- 运算符......更强烈的创新动机可能是他观察到++x的翻译比x=x+1的翻译小。
6赞 Vatine 4/18/2013
@JamesCurran我很确定它最初是这样,并最终被换成了.LHS =- RHS;-=
30赞 PolyThinker 12/20/2008 #4

不错的问题/答案。

只是想指出 C 指针和数组是不一样的,尽管在这种情况下区别不是本质的。

请考虑以下声明:

int a[10];
int* p = a;

在 中,符号位于数组开头的地址,符号位于存储指针的地址,该内存位置的指针值是数组的开头。a.outap

评论

2赞 PolyThinker 12/22/2008
不,从技术上讲,它们是不一样的。如果将某个 b 定义为 int*const 并使其指向数组,它仍然是一个指针,这意味着在符号表中,b 指的是存储地址的内存位置,而地址又指向数组所在的位置。
4赞 Giorgio 5/3/2012
非常好的观点。我记得当我在一个模块中将全局符号定义为 char s[100] 时,我遇到了一个非常讨厌的错误,将其声明为 extern char *s;在另一个模块中。将它们链接在一起后,程序的行为非常奇怪。因为使用 extern 声明的模块使用数组的初始字节作为指向 char 的指针。
1赞 dave 5/3/2012
最初,在 C 的祖父 BCPL 中,数组是一个指针。也就是说,当你写(我已经音译为C)时,你得到的是一个叫做“a”的指针,它指向其他地方的10个整数的足够存储。因此,a+i 和 j+i 具有相同的形式:添加几个内存位置的内容。事实上,我认为 BCPL 是无类型的,所以它们是相同的。并且类型缩放的大小不适用,因为 BCPL 纯粹是面向词的(在词寻址机器上也是如此)。int a[10]
0赞 James Curran 3/13/2013
我认为理解差异的最好方法是比较后者,“b”和“5”都是整数,但“b”是一个变量,而“5”是一个固定值。类似地,“p”和“a”都是字符的地址,但“a”是一个固定值。int*p = a;int b = 5;
1赞 U. Windl 11/4/2020
虽然这个“答案”没有回答问题(因此应该是注释,而不是答案),但你可以总结为“数组不是左值,但指针是”。
59赞 user30364 2/11/2009 #5

关于黛娜的问题,似乎没有人提到过一件事:sizeof

您只能向指针添加一个整数,不能将两个指针相加。这样,在向整数添加指针或向指针添加整数时,编译器始终知道哪个位的大小需要考虑。

评论

2赞 Dinah 4/21/2009
在已接受答案的评论中,对此进行了相当详尽的对话。我在编辑中引用了原始问题的上述对话,但没有直接解决您对 sizeof 的非常有效的关注。不知道如何在 SO 中最好地做到这一点。我应该对原始问题进行另一次编辑吗?
0赞 U. Windl 11/4/2020
我想指出的是,您不能添加指针,但您可以减去指针(返回之间的项目数)。
18赞 Ajay 6/19/2011 #6

不是答案,而只是一些值得深思的东西。 如果类具有重载的索引/下标运算符,则表达式将不起作用:0[x]

class Sub
{
public:
    int operator [](size_t nIndex)
    {
        return 0;
    }   
};

int main()
{
    Sub s;
    s[0];
    0[s]; // ERROR 
}

由于我们无法访问 int 类,因此无法执行此操作:

class int
{
   int operator[](const Sub&);
};

评论

3赞 Ben Voigt 4/6/2013
class Sub { public: int operator[](size_t nIndex) const { return 0; } friend int operator[](size_t nIndex, const Sub& This) { return 0; } };
1赞 Ajay 4/6/2013
你真的试过编译它吗?有一些运算符不能在类外实现(即作为非静态函数)!
4赞 Ben Voigt 4/6/2013
哎呀,你是对的。“应为具有一个参数的非静态成员函数。”我熟悉这个限制,认为它不适用于。operator[]operator=[]
2赞 Luis Colorado 9/19/2014
当然,如果更改运算符的定义,它就再也不会等价了......如果等于并且你更改它,你也必须重载并且不是一个类......[]a[b]*(a + b)int::operator[](const Sub&);int
13赞 MD XF 12/13/2016
这。。。不是。。。C.
53赞 Peter Lawrey 8/11/2011 #7

从字面上回答问题。并非总是如此x == x

double zero = 0.0;
double a[] = { 0,0,0,0,0, zero/zero}; // NaN
cout << (a[5] == 5[a] ? "true" : "false") << endl;

指纹

false

评论

34赞 TrueY 4/23/2013
实际上,“nan”并不等于它自己:是。cout << (a[5] == a[5] ? "true" : "false") << endl;false
11赞 Tim Čas 2/13/2015
@TrueY:他确实专门针对 NaN 案例指出了这一点(具体来说,这并不总是正确的)。我想这就是他的本意。所以他在技术上是正确的(而且,正如他们所说,可能是最好的正确!x == x
8赞 12431234123412341234123 5/15/2018
问题是关于 C 的,你的代码不是 C 代码。还有一个 in ,它比 更好,因为 是 UB 时未定义(大多数实现不定义,但在大多数实现上仍然有效)NAN<math.h>0.0/0.00.0/0.0__STDC_IEC_559____STDC_IEC_559__0.0/0.0
23赞 user1287577 3/23/2012 #8

对于 C 语言中的指针,我们有

a[5] == *(a + 5)

以及

5[a] == *(5 + a)

因此,确实如此a[5] == 5[a].

37赞 Frédéric Terrazzoni 6/11/2012 #9

我只是发现这种丑陋的语法可能是“有用的”,或者至少当你想处理一个将位置引用到同一数组中的索引数组时,玩起来非常有趣。它可以替换嵌套的方括号,使代码更具可读性!

int a[] = { 2 , 3 , 3 , 2 , 4 };
int s = sizeof a / sizeof *a;  //  s == 5

for(int i = 0 ; i < s ; ++i) {  
           
    cout << a[a[a[i]]] << endl;
    // ... is equivalent to ...
    cout << i[a][a][a] << endl;  // but I prefer this one, it's easier to increase the level of indirection (without loop)
    
}

当然,我很确定在实际代码中没有用例,但无论如何我都觉得它很有趣:)

评论

1赞 12431234123412341234123 5/14/2018
当你看到时,你认为我要么是指向数组的指针,要么是指向数组或数组的指针的数组......并且是一个索引。当你看到 时,你认为 a 是指向数组或数组的指针,并且是一个索引。i[a][a][a]aa[a[a[i]]]i
3赞 Serge Breusov 6/28/2018
哇!这个“愚蠢”功能的用法非常酷。在某些问题的算法竞赛中可能很有用))
1赞 12431234123412341234123 8/11/2021
问题是关于 C 的,你的代码不是 C 代码。
285赞 Keith Thompson 8/23/2013 #10

我认为其他答案遗漏了一些东西。

是的,根据定义,它等价于 ,它(因为加法是可交换的)等价于 ,它(同样,根据运算符的定义)等价于 。p[i]*(p+i)*(i+p)[]i[p]

(在 中,数组名称被隐式转换为指向数组第一个元素的指针。array[i]

但在这种情况下,加法的可交换性并不那么明显。

当两个操作数属于同一类型,甚至属于不同的数值类型,这些类型被提升为公共类型时,可交换性就非常有意义了: .x + y == y + x

但在本例中,我们专门讨论指针算术,其中一个操作数是指针,另一个是整数。(整数+整数是不同的操作,指针+指针是无稽之谈。

C 标准对运算符的描述 (N1570 6.5.6) 说:+

此外,两个操作数都应具有算术类型,或者一个 操作数应为指向完整对象类型的指针,另一个 应具有整数类型。

它可以很容易地说:

此外,两个操作数都应具有算术类型,或者操作数应为指向完整对象类型的指针,而右操作数应为整数类型。

在这种情况下,两者都是非法的。i + pi[p]

用 C++ 术语来说,我们实际上有两组重载运算符,可以粗略地描述为:+

pointer operator+(pointer p, integer i);

pointer operator+(integer i, pointer p);

其中只有第一个是真正必要的。

那么为什么会这样呢?

C++从C继承了这个定义,C从B得到它(数组索引的交换性在1972年的用户参考B中明确提到),它从BCPL(1967年的手册)中得到它,它很可能从更早的语言(CPL?Algol?

因此,数组索引是根据加法来定义的,而加法,即使是指针和整数,也是可交换的,这种想法可以追溯到几十年前,可以追溯到 C 的祖先语言。

这些语言的类型远不如现代 C 语言那么强。特别是,指针和整数之间的区别经常被忽略。(早期的 C 程序员有时会将指针用作无符号整数,然后再将关键字添加到语言中。因此,由于操作数是不同类型的,因此使加法成为非交换性的想法可能不会出现在这些语言的设计者身上。如果用户想要添加两个“事物”,无论这些“事物”是整数、指针还是其他东西,都不能由语言来阻止它。unsigned

多年来,对该规则的任何更改都会破坏现有代码(尽管 1989 年的 ANSI C 标准可能是一个很好的机会)。

将 C 和/或 C++ 更改为需要将指针放在左侧,将整数放在右侧可能会破坏一些现有代码,但不会损失真正的表达能力。

因此,现在我们拥有和含义完全相同的东西,尽管后一种形式不应该出现在 IOCCC 之外。arr[3]3[arr]

评论

16赞 Dinah 8/24/2013
这家酒店的精彩描述。从高层次的角度来看,我认为这是一个有趣的工件,但应该很少使用。我前段时间问过这个问题(<stackoverflow.com/q/1390365/356>)的公认答案改变了我对语法的思考方式。尽管从技术上讲,做这些事情通常没有对错之分,但这些类型的功能会让你以一种与实现细节分开的方式开始思考。这种不同的思维方式是有好处的,当你专注于实现细节时,这种思维方式在一定程度上会丢失。3[arr]
3赞 iheanyi 4/22/2014
加法是可交换的。对于C标准来说,否则定义它会很奇怪。这就是为什么它不能轻易地说“另外,要么两个操作数都应具有算术类型,要么左操作数应为指向完整对象类型的指针,而右操作数应为整数类型。 - 对于大多数添加东西的人来说,这是没有意义的。
13赞 Keith Thompson 4/22/2014
@iheanyi:加法通常是可交换的,它通常需要两个相同类型的操作数。指针添加允许您添加指针和整数,但不能添加两个指针。恕我直言,这已经是一个非常奇怪的特殊情况,要求指针是左操作数不会是一个很大的负担。(有些语言使用“+”进行字符串连接;这当然不是可交换的。
3赞 iheanyi 10/22/2014
@supercat,那就更糟了。这意味着有时 x + 1 != 1 + x。这将完全违反加法的关联性质。
4赞 supercat 10/22/2014
@iheanyi:我想你的意思是交换属性;加法已经不是关联的,因为在大多数实现中 (1LL+1U)-2 != 1LL+(1U-2)。事实上,这种变化会使一些情况产生关联,而目前还没有,例如3U+(UINT_MAX-2L)等于(3U+UINT_MAX)-2。不过,最好的办法是让语言为可提升的整数和“包装”代数环添加新的不同类型,以便将 2 添加到包含 65535 的 a 中将产生值为 1 的 a,int 的大小无关ring16_tring16_t
14赞 A.s. Bhullar 9/27/2013 #11

在 Ted Jensen 的 A TUTORIAL ON POINTERS AND ARRAYS IN C 中对此有很好的解释。

泰德·詹森(Ted Jensen)将其解释为:

事实上,这是真的,即无论一个人在哪里写它都可以 替换为没有任何问题。事实上,编译器 无论哪种情况,都将创建相同的代码。因此,我们看到该指针 算术与数组索引是一回事。任一语法都会产生 相同的结果。a[i]*(a + i)

这并不是说指针和数组 是一回事,他们不是。我们只是说要识别 数组的给定元素,我们可以选择两种语法,一种 使用数组索引,另一个使用指针算术,其中 产生相同的结果。

现在,看看最后这个 表达,其中的一部分。,是使用 + 的简单加法 运算符和 C 的规则声明这样的表达式是 交换。也就是说,(a + i) 等同于 。因此,我们可以 写起来就像 . 但本来可以来的!从这一切中产生了好奇心 如果:(a + i)(i + a)*(i + a)*(a + i)*(i + a)i[a]

char a[20];

写作

a[3] = 'x';

和写作是一样的

3[a] = 'x';

评论

5赞 Alex Brown 12/5/2015
a+i 不是简单的加法,因为它是指针算术。如果 A 元素的大小为 1 (char),那么是的,它就像整数 + 一样。但是,如果它(例如)是一个整数,那么它可能等同于 + 4*i。
1赞 jschultz410 3/22/2018
@AlexBrown 是的,这是指针算术,这正是你最后一句话错误的原因,除非你首先将“a”转换为 a (char*)(假设 int 是 4 个字符)。我真的不明白为什么这么多人对指针算术的实际值结果感到执着。指针算术的全部目的是抽象出底层指针值,让程序员考虑作的对象,而不是寻址值。
8赞 Krishan 12/17/2013 #12

在 C 数组中,和是相同的,并且它们的等效指针表示法是 。但相反,或者是不正确的,并且会导致语法错误,因为和都是无效的表达式。原因是取消引用运算符应放在表达式生成的地址之前,而不是地址之后。arr[3]3[arr]*(arr + 3)*(3 + arr)[arr]3[3]arr(arr + 3)*(3 + arr)*

8赞 AVIK DUTTA 10/29/2014 #13

在 C 编译器中

a[i]
i[a]
*(a+i)

是引用数组中元素的不同方式!(一点也不奇怪)

11赞 Ajinkya Patil 5/4/2016 #14

我知道这个问题已经得到回答,但我忍不住分享这个解释。

我记得编译器设计的原则, 假设是一个数组,大小为 2 个字节, 的基址是 1000。aintinta

将如何工作 ->a[5]

Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010

所以

同样,当 c 代码分解为 3 地址代码时,将变为 ->5[a]

Base Address of your Array a + (size of(data type for array a)*5)
i.e. 1000 + (2*5) = 1010 

因此,基本上这两个语句都指向内存中的同一位置,因此,.a[5] = 5[a]

这种解释也是数组中的负索引在 C 中起作用的原因。

即如果我访问它会给我a[-5]

Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990

它将在位置 990 返回我的对象。

2赞 JgWangdu 2/12/2017 #15

在 C 语言中

 int a[]={10,20,30,40,50};
 int *p=a;
 printf("%d\n",*p++);//output will be 10
 printf("%d\n",*a++);//will give an error

指针是“变量”,数组名是“助记词”或“同义词”, 所以是有效的,但无效。pap++a++

a[2]等于,因为对这两者的内部运算都是“指针算术”,内部计算为等于2[a]*(a+2)*(2+a)

3赞 Harsha J K 4/3/2018 #16

嗯,这是一个只有语言支持才能实现的功能。

编译器解释为 ,表达式的计算结果为 。由于加法是可交换的,因此事实证明两者都是相等的。因此,表达式的计算结果为 。a[i]*(a+i)5[a]*(5+a)true

评论

0赞 Bill K 12/6/2019
虽然多余,但这是清晰、简洁和简短的。
10赞 dgnuff 4/9/2019 #17

现在有一点历史。在其他语言中,BCPL 对 C 的早期发展产生了相当大的影响。如果您在 BCPL 中声明了一个数组,如下所示:

let V = vec 10

这实际上分配了 11 个单词的记忆,而不是 10 个。通常,V 是第一个,包含紧随其后的单词的地址。因此,与 C 不同的是,命名 V 会转到该位置并获取数组中 zeroeth 元素的地址。因此,BCPL 中的数组间接,表示为

let J = V!5

确实必须这样做(使用 BCPL 语法),因为必须获取 V 才能获取数组的基址。因此,和是同义词。作为一个轶事观察,WAFL(Warwick Functional Language)是用 BCPL 编写的,据我所知,它倾向于使用后一种语法而不是前一种语法来访问用作数据存储的节点。当然,这是 35 到 40 年前的某个地方,所以我的记忆有点生疏。:)J = !(V + 5)V!55!V

省去额外的存储字,让编译器在命名数组时插入数组的基址的创新是后来才出现的。根据 C 历史论文,这发生在向 C 添加结构时。

请注意,在 BCPL 中,既是一元前缀运算符,也是二进制中缀运算符,在这两种情况下都是间接的。只是在进行间接操作之前,二进制形式包括了两个操作数的相加。鉴于 BCPL(和 B)的面向文字性质,这实际上很有意义。“指针和整数”的限制在C语言中获得了数据类型,并成为一种东西。!sizeof

3赞 user15375057 8/31/2021 #18

因为 C 编译器总是以指针表示法转换数组表示法。 也所以,两者都是平等的。a[5] = *(a + 5)5[a] = *(5 + a) = *(a + 5)

3赞 Samuel Danielson 9/20/2021 #19

因为它有助于避免混淆嵌套。

你愿意阅读这个吗:

array[array[head].next].prev

或者这个:

head[array].next[array].prev

顺便说一下,C++ 对函数调用具有类似的交换属性。与其在 C 中编写,不如使用成员函数编写 .将 f 和 g 替换为查找表,您可以编写 (函数式) 或 (oop 式)。后者在包含索引的结构中变得非常好: .使用更常见的表示法,即 .g(f(x))x.f().g()g[f[x]](x[f])[g]x[xs].y[ys].z[zs]zs[ys[xs[x].y].z]

评论

0赞 luser droog 3/11/2023
我可能在 FP 中做了太多的阅读,但第二个对我来说似乎读得更好:“数组的头部”、“数组的下一个”。当然,这取决于阅读过程中的大量编辑许可。
5赞 Michael Kay 12/29/2022 #20

C 基于 BCPL。BCPL 直接将内存公开为一系列可寻址字。一元运算符(也称为 LV)为您提供了地址位置 X 的内容。为方便起见,还有一个二进制运算符等价物,它为您提供了位置 X 处数组的第 Y 个单词的内容,或者等效地提供了位置 Y 处数组的第 X 个单词的内容。!XX!Y!(X+Y)

在 C 语言中,变成了 ,但原始的 BCPL 语义是 show through,这解释了为什么算子是可交换的。X!YX[Y]!(X+Y)