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

正如Joel在堆栈溢出播客#34c编程语言(aka: K&R)中指出的,C:a[5] == 5[a]中提到了数组的这个属性

乔尔说这是因为指针算术,但我还是不明白。

121198 次浏览

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

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

因此a[5]将评估为:

*(a + 5)

并且5[a]将评估为:

*(5 + a)

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

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

而且,当然

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

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

这也解释了“+=”和“++”的语法。“A = B + C”形式的所有内容都有相同的编译形式。但是,如果B和A是同一个对象,那么可以进行汇编级优化。但是编译器不够聪明,无法识别它,所以开发人员不得不(A += C)。类似地,如果C1,则可以进行不同的汇编级优化,开发人员必须再次显式化,因为编译器无法识别它。(最近编译器做了,所以这些语法现在基本上是不必要的)

不错的问题/答案。

只是想指出C指针和数组不是相同,尽管在这种情况下差异并不重要。

考虑以下声明:

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

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

有一件事似乎没有人提到黛娜与sizeof的问题:

你只能向指针添加一个整数,不能将两个指针相加。这样,当向整数添加指针或向指针添加整数时,编译器总是知道哪个位有需要考虑的大小。

不是答案,只是一些思考的食物。如果类具有重载的索引/下标操作符,表达式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&);};

从字面上回答这个问题。x == x并不总是正确的

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

印刷品

false

对于C中的指针,我们有

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

并且还

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

因此,这是真的a[5] == 5[a].

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

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)    
}

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

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

是的,根据定义,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那里继承了这个定义,它从B那里得到了它(1972年用户对B的引用中明确提到了数组索引的交换性),它从BCPL(1967年的手册)得到了它,它很可能是从更早的语言(CPL?Algol?)得到的。

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

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

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

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

所以现在我们有了arr[3]3[arr],意思完全相同,尽管后一种形式永远不应该出现在IOCCC之外。

它在C中的POINTERS和ARRAYS教程中有很好的解释著者:Ted Jensen

Ted Jensen解释说:

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

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

现在,看看这个表达式,它的一部分…(a + i),是一个使用+的简单加法运算符和C的规则表明这样的表达式是交换的。即(a+i)与(i + a)相同。因此我们可以写*(i + a)就像写*(a + i)一样容易。但是*(i + a)可能来自i[a]!所有这些都来自好奇如果是这样的话:

char a[20];

写作

a[3] = 'x';

和写作是一样的

3[a] = 'x';

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

inc编译器

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

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

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

我记得编译器设计的原则,让我们假设a是一个int数组,int的大小是2个字节,�的基地址是1000。

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返回我的对象。

在c

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

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

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

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

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

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

let V = vec 10

实际上分配了11个内存字,而不是10个。通常V是第一个,并且包含紧接着的字的地址。所以与C不同,命名V去那个位置并获取数组的零元素的地址。因此BCPL中的数组间接,表示为

let J = V!5

由于需要获取V来获取数组的基地址,因此确实必须执行J = !(V + 5)(使用BCPL语法)。因此V!55!V是同义词。作为轶事观察,WAFL(Warwick函数式语言)是用BCPL编写的,根据我的记忆,在访问用作数据存储的节点时,倾向于使用后一种语法而不是前一种语法。当然这是35到40年前的事情,所以我的记忆有点生疏。:)

免除额外的存储字并让编译器在命名数组时插入数组的基地址的创新是后来出现的。根据C历史论文,这发生在将结构添加到C的大约时间。

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

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

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

你宁愿读这个:

array[array[head].next].prev

或者这个:

head[array].next[array].prev

顺便说一句,C++对函数调用有类似的交换属性。你可以使用成员函数来编写x.f().g(),而不是像在C中那样编写g(f(x))。用查找表替换f和g,你可以编写g[f[x]](函数式风格)或(x[f])[g](oop风格)。后者对于包含索引的结构非常好:x[xs].y[ys].z[zs]。使用更常见的符号zs[ys[xs[x].y].z]