正如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 == 5for(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 10printf("%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].

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

  1. 269道各路算法考试题集锦

    1 某编程大赛题(35道题,中等难度) 1.在实际的开发工作中,对于string的处理是最常见的编程任务,本题是要求程序对用户输入的string进行处理,具体要求如下: 1.每个单词的首字母变为大写. ...

  2. 分割数组(将数组三等分)

    简单面试题--分割数组 时间复杂度O(n) //输入:一个自然数数组,选取其中2个数字num[i], num[j], 把数组三分, // 每一部分的累加和(不包括分割点的数字)相等. // 例:num ...

  3. Redis 笔记(11)— 文本协议 RESP(单行、多行字符串、整数、错误、数组、空值、空串格式、telnet 登录 redis)

    RESP 是 Redis 序列化协议Redis Serialization Protocol 的简写.它是一种直观的文本协议,优势在于实现异常简单,解析性能极好. ​ Redis 协议将传输的结构数据 ...

  4. C++ 笔记(27)— 指针变量、数组和指针、指针数组、数组指针、指针常量与常量指针

    1. 什么是指针变量? 指针变量是专门有一个变量来存放指针. int main(int argc, char *argv[]) {int a = 10;int *p = &a; //通过取地址 ...

  5. Go 学习笔记(63)— Go 中的 for ... range 对切片和数组的差异

    1. 迭代对象是切片,迭代过程中修改切片 package mainimport "fmt"func main() {// 示例1.numbers1 := []int{1, 2, 3 ...

  6. NumPy — 创建全零、全1、空、arange 数组,array 对象类型,astype 转换数据类型,数组和标量以及数组之间的运算,NumPy 数组共享内存

    NumPy 简介 一个用 python 实现的科学计算包.包括: 1.一个强大的 N 维数组对象 Array : 2.比较成熟的(广播)函数库: 3.用于整合 C/C++ 和 Fortran 代码的工 ...

  7. 判断某数组是不是二叉树的前序遍历序列 python递归

    code class Solution:def VerifySquenceOfBST(self, sequence):# write code hereif len(sequence) <= 0 ...

  8. 寻找一个字符串的重复子串 后缀数组

    什么是后缀数组 令字符串 S=S[1]S[2]...S[n]S=S[1]S[2]...S[n]{\displaystyle S=S[1]S[2]...S[n]} , S[i,j]S[i,j]{\dis ...

  9. c语言中字符串数组的地址存放以及%s输出单个字符导致程序崩溃的问题

    代码 总结下c语言中字符串数组的地址存放问题 #include <iostream> using namespace std; #include<bits/stdc++.h>i ...

  10. 关于C语言中的数组指针、指针数组以及二级指针

    概念解释 数组指针:首先它是一个指针,它指向一个数组,即指向数组的指针:在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道.数组指针指向的是数组中的一个具体元素,而不是整个数组,所 ...

最新文章

  1. 2018寒假学习计划
  2. MySQL 8.0 Server层最新架构详解
  3. mybatis简单案例源码详细【注释全面】——测试层(UserMapperTest.java)
  4. swing 聊天气泡背景_Java Swing中的聊天气泡
  5. 马云又出金句:文凭只是学费的收据,真正的文凭是生活中奋斗来的
  6. 有多少小微餐饮创业者陷入了“就业型创业”的死亡漩涡而不自知?
  7. Vue+Vue Router+Vuex页面演示
  8. Objective-C中的NSNumber和NSString
  9. 移动机顶盒cm211-1 刷机
  10. Java 高并发编程详解:多线程与架构设计
  11. win10下itunes安装失败的解决方法
  12. ECCV 2022 | 石溪大学联合小鹏汽车提出先验知识指导的无监督领域自适应
  13. 学生用计算机中括号怎么打,大括号怎么打,教您word大括号怎么输入
  14. 项目管理模型总结---原型模型、迭代模型
  15. 软件定义汽车的关键—车载操作系统
  16. 如何使用QQ收集表收青年大学习?
  17. 页面之间数据调转传输
  18. 桌面远程连接工具哪款最好用
  19. C语言中数据在内存中的存储
  20. SpringCloud入门随感五

热门文章

  1. 算法--------二叉树的中序遍历
  2. Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性-[Android取经之路]
  3. Android流量统计TrafficStats类
  4. Android相机预览方向
  5. QScintilla
  6. 中学计算机基础Word授课ppt,中学信息技术- 计算机硬件与软件基础知识课件.ppt...
  7. devc++64位不兼容_DNF玩家遭强制脱坑,只因64位更新后无法上游戏,如何解决?...
  8. c语言file_C语言 技能提升 系列文章(七)格式化输入/输出
  9. windows 如何配置 Go 环境(Zip archive 方式)?
  10. Android相关面试题---初识