【问题标题】:Why is an array's dimension part of its type?为什么数组的维度是其类型的一部分?
【发布时间】:2020-03-17 13:03:49
【问题描述】:

在阅读 C++ Primer 一书时,我遇到了这样的说法:“数组中元素的数量是数组类型的一部分。”所以我想用下面的代码找出来:

#include<iostream>

int main()
{
    char Array1[]{'H', 'e', 'l', 'p'};
    char Array2[]{'P', 'l', 'e', 'a', 's', 'e'};

    std::cout<<typeid(Array1).name()<<std::endl;        //prints  A4_c
    std::cout<<typeid(Array2).name()<<std::endl;        //prints  A6_c

    return 0;
}

有趣的是,两个数组的 typeid 结果表明它们在某种程度上有所不同。

  • 幕后发生了什么?
  • 为什么数组的类型必须包含其大小?只是因为它的大小不应该改变吗?
  • 这将如何影响比较数组?

只是希望能够深入理解这个概念。

【问题讨论】:

  • 在类型中包含大小信息不是严格必需,但它很有用
  • 任何关于数组的教程都会解释(1)。我不确定 (3) 是什么意思,因为没有内置方法来比较数组。

标签: c++ arrays c++11


【解决方案1】:

幕后发生了什么?

根据定义,非动态分配是一个固定大小的同类元素容器。 T 类型的 N 元素数组作为 T 类型的 N 对象的连续序列布置在内存中。


为什么数组的类型必须包含它的大小?

我不认为数组类型包含它的大小是“必要的”——事实上,您可以使用指针来引用T 对象的连续序列。这样的指针会丢失数组的大小信息。

然而,拥有它是一件有用的事情。它提高了类型安全性,并在编译时对可以以多种方式使用的有用信息进行编码。例如,您可以使用 references-to-arrays 对不同大小的数组进行重载

void foo(int(&array)[4]) { /* ... */ }
void foo(int(&array)[8]) { /* ... */ }

或将数组的大小计算为常量表达式

template <typename T, std::size_t N>
constexpr auto sizeOf(const T(&array)[N]) { return N; }

这将如何影响比较数组?

真的没有。

您无法像比较两个数字(例如 int 对象)那样比较 C 样式的数组。您将不得不编写某种字典比较,并确定它对不同大小的集合意味着什么。 std::vector&lt;T&gt; provides that,同样的逻辑也可以应用于数组。


奖励: C++11 及更高版本提供了std::array,它是一个围绕具有类似容器接口的 C 样式数组的包装器。它应该优先于 C 样式的数组,因为它与其他容器(例如 std::vector&lt;T&gt;)更一致,并且还支持开箱即用的 lexicographical comparisons

【讨论】:

  • “你必须编写某种字典比较,并确定它对不同大小的集合意味着什么。”您可以只使用std::equal(通过为数组定义的std::beginstd::end)。在这种情况下,不同大小的数组是不相等的。
  • 可能值得注意的是,范围为数组的基于范围的 for 循环需要在编译时读取数组的大小 - 这有点微妙,因为(谢天谢地!)人们从不写出这个例子中数组的类型,但它似乎比基于大小的重载要多得多。
【解决方案2】:

创建对象时分配给对象的空间量完全取决于其类型。我所说的分配不是来自newmalloc 的分配,而是分配的空间,以便您可以运行构造函数并初始化对象。

如果你有一个结构定义为(例如)

struct A { char a, b; }; //sizeof(A) == 2, ie an A needs 2 bytes of space

那么当你构造对象时:

A a{'a', 'b'};

你可以把构造对象的过程想象成一个过程:

  • 分配 2 个字节的空间(在堆栈上,但在此示例中无关紧要)
  • 运行对象的构造函数(在本例中将'a''b' 复制到对象)

重要的是要注意,所需的 2 字节空间完全取决于对象的类型,函数的参数无关紧要。所以,对于一个数组,这个过程是一样的,只是现在需要的空间量取决于数组中元素的数量。

char a[] = {'a'}; //need space for 1 element
char b[] = {'a', 'b', 'c', 'd', 'e'}; //need space for 5 elements

所以ab 的类型必须反映a 需要足够的空间来容纳1 个字符,而b 需要足够的空间来容纳5 个字符。这确实意味着这些数组的大小不会突然改变,一旦创建了 5 元素数组,它始终是 5 元素数组。为了拥有大小可以变化的类似“数组”的对象,您需要动态内存分配,您的书应该在某些时候涵盖。

【讨论】:

    【解决方案3】:

    这是运行时库的内部原因。例如,如果您考虑以下陈述:

    unsigned int i;
    unsigned int *iPtr;
    unsigned int *iPtrArr[2];
    unsigned int **iPtrHandle;
    

    那么问题就很清楚了:例如,unsigned int * 的寻址必须与sizeof operatorunsigned int 的寻址有关。

    您在此处看到的其余内容有更详细的解释,但它主要是对 Kernighan 和 Ritchie 在C Programming Language, 2nd Edition 中有关该程序的内容的概括打印声明类型字符串的纯语言文本。

    【讨论】:

      猜你喜欢
      • 2018-02-18
      • 1970-01-01
      • 2019-11-11
      • 2016-03-25
      • 2018-04-06
      • 1970-01-01
      • 1970-01-01
      • 2021-12-31
      相关资源
      最近更新 更多