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

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

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

答案

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

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

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

*(a + 5)

5[a]将评估为:

*(5 + a)

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

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

我认为其他答案缺少一些东西。

是的,根据定义, 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 语言相比,这些语言的强类型要少得多。特别是,指针和整数之间的区别通常被忽略。 (在将unsigned关键字添加到语言之前,早期的 C 程序员有时会使用指针作为无符号整数。)因此,由于这些操作数的类型不同,因为这些操作数是不同类型,所以使加法运算不可交换的想法就不会发生。 。如果用户想添加两个 “事物”,无论这些 “事物” 是整数,指针还是其他东西,都无法阻止该语言。

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

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

所以现在我们有了arr[3]3[arr]意思是完全一样的,尽管后者的形式永远都不会出现在IOCCC之外。

而且当然

("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 ,则可以使用不同的程序集级优化,并且由于编译器无法识别它,因此开发人员必须再次使其明确。 (最近,编译器会这样做,因此,如今这些语法在很大程度上是不必要的)

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

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

从字面上回答这个问题。 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

好的问题 / 答案。

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

请考虑以下声明:

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

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

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

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)

}

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

对于 C 中的指针,我们有

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

并且

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

因此, a[5] == 5[a].是正确的a[5] == 5[a].

不是答案,而是一些值得深思的地方。如果类具有重载的索引 / 下标运算符,则表达式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&);
};