Чому розмірність масиву є частиною його типу?


14

Читаючи книгу букваря C ++, я натрапив на це твердження: "Кількість елементів у масиві є частиною типу масиву". Тому я хотів дізнатися, використовуючи наступний код:

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

І що цікаво, результат типу на двох масивах показав, що вони якось різні.

  • Що відбувається за лаштунками?
  • Чому для масиву потрібно мати тип, що включає його розмір? Це лише тому, що його розмір не повинен змінюватися?
  • Як це вплине на порівняння масивів?

Просто хочеться мати можливість глибоко зрозуміти концепцію.


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

Як це вплине на порівняння масивів?

Насправді це не так.

Ви не можете порівнювати масиви у стилі С таким же чином, як ви порівнювали два числа (наприклад, intоб'єкти). Вам доведеться написати якесь лексикографічне порівняння та вирішити, що це означає для колекцій різного розміру. std::vector<T>передбачає, що та сама логіка може бути застосована до масивів.


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


2
"Вам доведеться написати якесь лексикографічне порівняння та вирішити, що це означає для колекцій різної величини." Ви можете просто використовувати std::equal(через std::beginта std::endякі визначені для масивів). У цьому випадку масиви різного розміру нерівні.
Stu

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-елементний масив. Для того щоб мати об’єкти, схожі на "масив", розмір яких може змінюватись, вам потрібно динамічне розподілення пам'яті, яке ваша книга повинна охоплювати в якийсь момент.


0

Це з внутрішньої причини бібліотеки часу виконання. Якщо розглядати такі твердження, наприклад:

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

Тоді стає зрозуміло, в чому проблема: Наприклад, адресація unsigned int *повинна стосуватися себе sizeof operatorабо адресації unsigned int.

Для решти того, що ви бачите тут, є більш детальне пояснення, але це значною мірою рекапітуляція того, що було висвітлено у мові програмування C, 2-е видання Керніган та Річі щодо програми, яка друкує текст простої мови заявленого типу рядок.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.