Почему размерность массива является частью его типа?

14

Читая книгу по 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 на двух массивах показали, что они как-то разные.

  • Что происходит за кулисами?
  • Зачем массиву иметь тип, который включает его размер? Это только потому, что его размер не должен меняться?
  • Как это повлияет на сравнение массивов?

Просто хочу быть в состоянии глубоко понять концепцию.

осьминог
источник
3
Не обязательно включать информацию о размере в тип, но это полезно
byxor
Любое руководство по массивам объяснит (1). Я не уверен, что вы подразумеваете под (3), поскольку нет встроенного способа сравнения массивов.
HolyBlackCat

Ответы:

20

Что происходит за кулисами?

Не динамически распределяемый является по определению контейнером однородного элемента фиксированного размера . Массив Nэлементов типа Tразмещается в памяти как непрерывная последовательность Nобъектов типа T.


Зачем массиву иметь тип, который включает его размер?

Я не верю, что это необходимо для типа массива, чтобы включить его размер - фактически, вы можете использовать указатель для ссылки на непрерывную последовательность Tобъектов. Такой указатель потерял бы информацию о размере массива.

Это, однако, полезно иметь. Это повышает безопасность типов и кодирует полезную информацию во время компиляции, которую можно использовать несколькими способами. Например, вы можете использовать ссылки на массивы для перегрузки массивов разных размеров.

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

Как это повлияет на сравнение массивов?

На самом деле это не так.

Вы не можете сравнивать массивы в стиле C так же, как сравнивали бы два числа (например, intобъекты). Вам придется написать какое-то лексикографическое сравнение и решить, что это значит для коллекций разных размеров. std::vector<T>обеспечивает , и та же логика может быть применена к массивам.


Бонус: C ++ 11 и выше предоставляет std::arrayобертку вокруг массива в стиле C с контейнероподобным интерфейсом. Следует отдавать предпочтение массивам в стиле C, поскольку они более соответствуют другим контейнерам (например std::vector<T>), а также поддерживают лексикографические сравнения из коробки.

Витторио Ромео
источник
2
«Вам придется написать какое-то лексикографическое сравнение и решить, что это значит для коллекций разных размеров». Вы можете просто использовать std::equal(через std::beginи std::endкоторые определены для массивов). В этом случае массивы разных размеров не равны.
Стю
3
Возможно, стоит отметить, что для циклов, основанных на диапазоне, диапазон которых является массивом, необходимо считывать размер массива во время компиляции - это немного сложнее, поскольку (к счастью!) В этом примере никогда не записывается тип массива, но это кажется намного больше, чем перегрузка в зависимости от размера.
Майло Брандт
8

Объем пространства, выделяемого объекту при его создании, полностью зависит от его типа. Распределение, о котором я говорю, - это не выделения из newили malloc, а пространство, выделенное для того, чтобы вы могли запустить свой конструктор и инициализировать свой объект.

Если у вас есть структура, определенная как (например)

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

Таким образом, типы aи bдолжны отражать тот факт, что aнужно достаточно места для 1 символа и bдостаточно места для 5 символов. Это означает, что размер этих массивов не может внезапно измениться: после создания массива из 5 элементов это всегда массив из 5 элементов. Чтобы иметь объекты типа массива, размер которых может варьироваться, вам необходимо динамическое выделение памяти, которое в какой-то момент должно быть покрыто вашей книгой.

SirGuy
источник
0

Это по внутренней причине для библиотеки времени выполнения. Например, если учесть следующие утверждения:

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

Тогда становится ясно, в чем проблема: например, адресация unsigned int *должна касаться sizeof operatorили адресации unsigned int.

Есть более подробное объяснение остальной части того, что вы видите здесь, но это в значительной степени повторение того, что было описано в Языке программирования C, 2-е издание Керниганом и Ричи относительно программы, которая печатает текст на простом языке объявленного типа. строка.

CR Ward
источник