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

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

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


#1楼

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

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)

}

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


#2楼

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

是的,根据定义, 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之外。


#3楼

它在Ted Jensen 撰写的《 C中指针与数组教程》中有很好的解释。

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

实际上,这是正确的,即无论在哪里写a[i]都可以用*(a + i)替换,而不会出现任何问题。 实际上,无论哪种情况,编译器都会创建相同的代码。 因此,我们看到指针算术与数组索引是同一回事。 两种语法都会产生相同的结果。

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

现在,看最后一个表达式,它的一部分.. (a + i)是使用+运算符和C规则的简单加法,表示这样的表达式是可交换的。 即(a + i)与(i + a)相同。 因此,我们可以像*(a + i)一样容易地编写*(i + a) *(a + i) 。 但是*(i + a)可能来自i[a] ! 所有这些都产生了一个奇怪的事实,即:

 char a[20]; 

写作

 a[3] = 'x'; 

和写作一样

 3[a] = 'x'; 

#4楼

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


#5楼

在C编译器中

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

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


#6楼

我知道问题已经解决,但我无法抗拒分享这个解释。

我记得编译器设计原理,假设a是一个int数组, int大小是2个字节,而a基址是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处的对象。


#7楼

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

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

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

*(a + 5)

5[a]将评估为:

*(5 + a)

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


#8楼

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


#9楼

而且当然

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


#10楼

好的问题/答案。

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

请考虑以下声明:

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

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


#11楼

在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

指针是“变量”

数组名称是“助记符”或“同义词”

p++; 有效,但a++无效

a[2]等于2 [a],因为这两个函数的内部运算都是

内部计算的“指针算术”为

*(a+3)等于*(3+a)


#12楼

嗯,这是仅由于语言支持才可能实现的功能。

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


#13楼

指针类型

1)指向数据的指针

int *ptr;

2)const指向数据的指针

int const *ptr;

3)const指向const数据的指针

int const *const ptr;

数组是我们列表中的(2)类型
当您一次定义一个数组时,该指针中的一个地址将被初始化
我们知道我们无法在程序中更改或修改const值,因为它在编译时会引发ERROR

我发现的主要区别是...

我们可以通过地址重新初始化指针,但数组不能使用相同的大小写。

======
回到你的问题...
a [5]只是*(a + 5)
你可以很容易地理解
包含地址(人们称其为基地址),就像列表中的(2)类型的指针一样
[]-该运算符可以替换为指针*。

所以最后...

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

#14楼

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

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


#15楼

现在的历史。 在其他语言中,BCPL对C的早期发展有相当大的影响。 如果您在BCPL中声明了类似以下内容的数组:

let V = vec 10

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

let J = V!5

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

后来又有了创新,省去了多余的存储字并让编译器在命名数组时插入了数组的基地址。 根据C历史记录,这大约是在将结构添加到C时发生的。

注意! BCPL中的int既是一元前缀运算符,又是二进制中缀运算符,在两种情况下都是间接的。 只是二进制形式在执行间接操作之前将两个操作数相加。 考虑到BCPL(和B)的面向单词的性质,这实际上很有意义。 当获取数据类型时,在C中必须使用“指针和整数”的限制,并且sizeof变成了问题。


#16楼

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

#17楼

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

#18楼

对于C中的指针,我们有

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

并且

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值