【问题标题】:Why am I being told that an array is a pointer? What is the relationship between arrays and pointers in C++?为什么有人告诉我数组是指针? C++中的数组和指针有什么关系?
【发布时间】:2014-10-22 21:10:16
【问题描述】:

我的背景是 C++,我目前即将开始使用 C# 进行开发,所以我正在做一些研究。但是,在此过程中,我遇到了一些关于 C++ 的问题。

C# for C++ developers guide 这么说

在 C++ 中,数组只是一个指针。

但是this * question 有一个高度评价的评论说

数组不是指针。别再告诉别人了。

cplusplus.com page on pointers 表示数组和指针是相关的(并提到了隐式转换,因此它们显然相同)。

数组的概念与指针的概念有关。事实上,数组的工作方式与指向它们的第一个元素的指针非常相似,而且实际上,数组总是可以隐式转换为正确类型的指针。

我的印象是,Microsoft 页面想要简化事情以总结 C++ 和 C# 之间的差异,并且在此过程中写了一些更简单但不是 100% 准确的东西。

但是数组与指针有什么关系呢?为什么他们的关系足够接近,即使他们不是,也可以概括为“相同”?

cplusplus.com 页面说数组“工作方式类似于”指向其第一个元素的指针。如果它们实际上不是指向第一个元素的指针,这意味着什么?

【问题讨论】:

  • *.com/questions/1641957/… 没有回答这个问题的哪一部分???
  • 这个问题更笼统地概念化。
  • 数组不是指针,但您可以像索引数组一样索引指针。数组也很容易衰减为指针。除此之外,看起来像数组的函数参数实际上是指针。所以有很多混乱的来源。全部或大部分来自 C。
  • 考虑数组的类型包括元素的数量,并且该类型的大小占总空间。
  • @starsplusplus 比这要复杂一些。数组可以衰减为指针。但是您也可以通过引用函数来传递数组。在这种情况下,指针没有衰减。您还可以将指针传递给数组。在这两种情况下,都会保留大小信息。数组在传递给需要指针的函数时用作指针。

标签: c++ arrays pointers


【解决方案1】:

那里有很多糟糕的写作。例如语句:

在 C++ 中,数组只是一个指针。

完全是错误的。怎么会写出这么烂的文?我们只能推测,但一种可能的理论是作者使用编译器通过反复试验来学习 C++,并根据他的实验结果形成了一个错误的 C++ 心智模型。这可能是因为 C++ 对数组使用的语法非常规。

下一个问题是,学习者如何知道他/她正在阅读好材料还是坏材料?当然,除了阅读我的帖子 ;-) ,参与像 Stack Overflow 这样的社区有助于让你接触到许多不同的演示和描述,然后过一段时间你就有足够的信息和经验来做出你自己的决定哪个写作好哪个不好。

回到数组/指针主题:我的建议是首先建立一个正确的思维模型,了解我们在 C++ 中工作时对象存储的工作原理。只为这篇文章写的内容可能太多了,但这是我将从头开始构建它的方法:

  • C 和 C++ 是根据抽象内存模型设计的,但在大多数情况下,这会直接转换为系统操作系统或更低层提供的内存模型
  • 内存分为称为字节(通常为8位)的基本单位​​li>
  • 内存可以分配为对象的存储;例如当您编写int x; 时,决定将相邻字节的特定块留出以存储整数值。 object 是分配存储的任何区域。 (是的,这是一个略微循环的定义!)
  • 分配存储的每个字节都有一个地址,它是一个标记(通常表示为一个简单的数字),可用于在内存中查找该字节。对象中任何字节的地址必须是连续的。
  • 名称x只存在于程序的编译阶段。在运行时,可能有int 分配的对象从未有过名称;并且在编译过程中可能有其他 int 对象具有一个或多个名称。
  • 所有这些都适用于任何其他类型的对象,而不仅仅是int
  • 数组是由许多相邻的相同类型的子对象组成的对象
  • 指针 是一个对象,用作标识在何处可以找到另一个对象的标记。

从这里开始,C++ 语法进入其中。 C++ 的类型系统使用强类型,这意味着每个对象都有一个类型。类型系统扩展到指针。在几乎所有情况下,用于存储指针的存储空间只保存被指向对象的第一个字节的地址;并且类型系统在编译时用于跟踪所指向的内容。这就是为什么我们有不同类型的指针(例如int *float *),尽管在这两种情况下存储可能包含相同类型的地址。


最后:如果你理解了我的最后两个要点,所谓的“数组指针等价”并不是存储等价。这是查找数组成员的等价语法。

既然我们知道指针可以用来查找另一个对象;数组是一系列许多相邻的对象;然后我们可以通过使用指向该数组第一个元素的指针来处理该数组。等价的是,相同的处理可以用于以下两种情况:

  • 查找数组的第 N 个元素
  • 在内存中查找我们正在查看的对象之后的第 N 个对象

此外,这些概念都可以使用相同的语法来表达。

【讨论】:

    【解决方案2】:

    它们绝对不是一回事,但在这种情况下,混淆是可以原谅的,因为语言语义......灵活且旨在最大程度地混淆。

    让我们从简单地定义一个指针和一个数组开始。

    一个指针(指向类型 T)指向一个内存空间,该空间至少保存一个 T(假设非空)。

    数组是一个存放多个T的内存空间。

    指针指向内存,数组就是内存,所以你可以指向内部或数组。由于您可以这样做,因此指针提供了许多类似数组的操作。本质上,您可以在假定它实际上指向内存超过一个 T 的情况下索引任何指针。

    因此,(指向)“某些 T 的内存空间”和“指向某些 T 的内存空间”之间存在一些语义重叠。在任何语言(包括 C#)中都是如此。主要区别在于它们不允许您简单地假设您的 T 引用实际上是指一个存在多个 T 的空间,而 C++ 将允许您这样做。

    由于所有指向 T 的指针都可以是指向任意大小的 T 数组的指针,因此您可以互换地对待指向数组的指针和指向 T 的指针。指向第一个元素的指针的特殊情况是指针的“some Ts”和数组的“some Ts”相等。也就是说,指向第一个元素的指针产生指向 N Ts 的指针(对于大小为 N 的数组),指向数组的指针产生......指向 N Ts 的指针,其中 N 相等。

    通常,这只是有趣的记忆废话,没有理智的人会尝试这样做。但是该语言通过将数组转换为指向第一个元素的指针来积极鼓励它,并且在某些情况下,当您请求一个数组时,它实际上给了您一个指针。当您想像值一样实际使用数组时,这是最令人困惑的,例如,分配给它或按值传递它,而语言坚持将其视为指针值。

    最终,关于 C++(和 C)本机数组,您真正需要了解的只是,不要使用它们,指向数组的指针与指向最基本的“作为字节数组的内存”类型的值的指针有一些对称性水平,而语言以可以想象的最令人困惑、不直观和不一致的方式暴露了这一点。因此,除非您热衷于学习没人应该知道的实现细节,否则请使用std::array,它的行为方式完全一致、非常理智,就像 C++ 中的所有其他类型一样。 C# 通过简单地不向您暴露这种对称性来做到这一点(因为没有人需要使用它,给予或接受)。

    【讨论】:

    • 指向类型的指针不必指向该类型的至少一个对象。
    • “你可以将指向数组的指针和指向 T 的指针互换” 你的意思是真正的指向数组的类型,还是指向数组元素的指针?
    • 为了增加混乱,指向数组的指针与指向数组的元素的指针是不同的。
    • @Deduplicator:我不记得确切的标准措辞,但简单地形成某种无效指针就是 UB,即使你不取消引用它们,IIRC。所有合法的别名只是混淆了“对象”和“类型”在这方面的实际含义。
    • @dyp:指向数组元素的指针。指向数组的类型是可以互换的,只要你不索引它们:P
    【解决方案3】:

    在绝大多数情况下,C 和 C++ 中的数组和指针可以使用完全相同的语义和语法。

    这是通过一个功能实现的:

    在几乎所有上下文中,数组都会衰减为指向其第一个元素的指针。
    C 中的异常:sizeof_Alignas_Alignas& 的地址
    在 C++ 中,这种差异对于重载解析也很重要。

    此外,函数参数的数组表示法具有欺骗性,这些函数声明是等价的:

    int f(int* a);
    int f(int a[]);
    int f(int a[3]);
    

    但不是这个:

    int f(int (&a)[3]);
    

    【讨论】:

    • 认为你的最后一个是int f(int(&a)[3]);
    • @chris:感谢您发现这个错误。如果这不是语言上的缺陷......
    【解决方案4】:

    除了已经说过的,还有一个很大的不同: 指针是存储内存地址的变量,它们可以递增或递减,并且它们存储的值可以更改(它们可以指向任何其他内存位置)。数组就不一样了。一旦分配了它们,您就无法更改它们引用的内存区域,例如您不能为它们分配其他值:

    int my_array[10];
    int x = 2;
    
    my_array = &x;
    my_array++;
    

    而你可以用指针做同样的事情:

    int *p = array;
    p++;
    p = &x;
    

    【讨论】:

    • 数组是左值。尝试获取一个参考,你会看到。
    • 明确地说,当您说他们存储的值时,您的意思是地址,对吧?不是指针指向的东西的值?
    【解决方案5】:

    本指南中的意思很简单,在 C# 中,数组是一个对象(可能就像我们可以在 C++ 中使用的 STL 一样),而在 C++ 中,数组基本上是一系列变量一个接一个地定位和分配,这就是为什么我们可以使用指针来引用它们(pointer++ 会给我们下一个等等)。

    【讨论】:

    • 数组在 C++ 中也是一个对象。是什么让你觉得它不是?它由数组元素类型的连续子对象组成,但这丝毫不影响其对象性。
    • 好吧,我不知道你对对象的定义,但对我来说,对象是一个类的实例,我可以以 OOP 方式使用它:从它继承、重载运算符和方法等. 声明'int arr[10];','arr'毕竟是一个指针,它指向第一个元素的地址。它没有方法或数据成员。所以说数组是指针不是很正确,但在我看来,它也绝对不是对象。
    • 首先,根据您的定义,int 不是对象。我怀疑你想要那个。而且,如果您允许 int 成为对象,那么您对数组作为对象的反对就变得非常脆弱。最后,在谈论 C++ 时使用 C++ 定义。
    • 来自上述指南:“在 C# 中,数组是包含方法和属性的对象。例如,可以通过 Length 属性查询数组的大小。”这里对象的含义是我可以将其称为一个实体的东西,例如-“告诉”数组对其自身进行排序,获取最大元素等。例如,int确实具有运算符等等,所以我能做到: int a(1),b(2); a+b;//求和 1+2 但是:int arr1[10], arr2[10]; arr1+arr2; //某个地址 - 不是一个新数组
    • 但是 - 在指南中,术语“对象”指的是 OOP 概念,您不能通过使用其中一种语言的术语来比较两种语言。所以,从 OOP 来说,一个数组在概念上不是一个对象。说它是一个 C++ 对象很好,但这与 OOP 对对象的定义无关。无论如何,在我看来。
    【解决方案6】:

    很简单:

    int arr[10];
    
    int* arr_pointer1 = arr;
    int* arr_pointer2 = &arr[0];
    

    所以,由于数组在内存中是连续的,所以写

    arr[1];
    

    同写:

    *(arr_pointer+1)
    

    把事情推得更远一点,写作:

    arr[2];
    //resolves to
    *(arr+2);
    //note also that this is perfectly valid 
    2[arr];
    //resolves to
    *(2+arr);
    

    【讨论】:

    • arrNOT 类型的 int*
    • 您显然没有阅读 cmets 或链接的问题。 arr 的类型为 int[10]