为什么我被告知数组是一个指针? C ++中数组和指针之间的关系是什么?

时间:2014-10-22 21:10:17

标签: c++ arrays pointers

我的背景是C ++,我现在即将开始用C#开发,所以我正在做一些研究。然而,在这个过程中,我遇到了一些引发C ++问题的事情。

C# for C++ developers guide表示

  

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

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

  

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

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

  

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

我得到的印象是,微软页面想要简化一些事情,以便总结C ++和C#之间的差异,并在此过程中编写了一些更简单但不是100%准确的内容。

但是阵列首先与指针有什么关系呢?为什么这种关系足够接近他们被概括为“相同”,即使他们不是?

cplusplus.com页面说数组“就像”指向第一个元素的指针。这意味着什么,如果他们实际上并没有指向他们的第一个元素?

6 个答案:

答案 0 :(得分:3)

那里有很多糟糕的写作。例如声明:

  

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

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

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

回到数组/指针主题:我的建议是首先建立一个正确的心理模型,说明当我们使用C ++时对象存储的工作方式。仅仅针对这篇文章写的可能太多了,但这是我从头开始构建它的方式:

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

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


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

因为我们知道指针可以用来找到另一个对象;并且数组是一系列相邻的对象;然后我们可以通过使用指向该数组的第一个元素的指针来处理数组。等价是可以对以下两个使用相同的处理:

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

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

答案 1 :(得分:1)

它们绝对不是完全相同的东西,但在这种情况下,混淆可以被原谅,因为语言语义是......灵活的,旨在最大限度地混淆。

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

指针(类型T)指向一个至少保存一个T的存储空间(假设为非空)。

数组是一个包含多个Ts的内存空间。

指针指向内存,数组是内存,因此您可以指向内部或指向数组。既然你可以做到这一点,指针提供了许多类似数组的操作。从本质上讲,您可以将任何指针编入索引,以确定它实际上指向多个T的内存。

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

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

通常情况下,这只是一个有趣的记忆挣扎,没有人会理所当然。但是语言通过在每个机会将数组转换为指向第一个元素的指针来积极地鼓励它,在某些情况下,你要求一个数组,它实际上会给你一个指针。当你想要像数值一样实际使用数组时,这是最令人困惑的,例如,当语言坚持你把它当作指针值时,要分配给它或按值传递它。

最终,你真正需要了解的关于C ++(和C)本机数组的所有内容都是,不要使用它们,指向数组的指针有一些对称性,指针指向最基本的“内存作为字节数组”的值等级,语言以最容易混淆,不直观和不一致的方式暴露出来。因此,除非您热衷于学习实现细节,否则没有人必须知道,然后使用std::array,它以完全一致,非常理智的方式运行,就像C ++中的其他所有类型一样。 C#通过简单地不暴露这种对称性来实现这一点(因为没有人需要使用它,给予或接受)。

答案 2 :(得分:1)

在绝大多数情况下,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]);

答案 3 :(得分:1)

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

int my_array[10];
int x = 2;

my_array = &x;
my_array++;

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

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

答案 4 :(得分:0)

本指南中的含义只是在C#中,数组是一个对象(可能就像在STL中我们可以在C ++中使用的那样),而在C ++中,数组基本上是一个位于&的变量序列。一个接一个地分配,这就是我们可以使用指针引用它们的原因(pointer++会给我们下一个等等。)。

答案 5 :(得分:-1)

它很简单:

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);