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