Урок №75. Фиксированные массивы

  Юрий  | 

  |

  Обновл. 24 Янв 2022  | 

 94486

 ǀ   20 

Этот урок является продолжением предыдущего урока о массивах в языке C++.

Инициализация фиксированных массивов

Элементы массива обрабатываются так же, как и обычные переменные, поэтому они не инициализируются при создании. Одним из способов инициализации массива является присваивание значений каждому элементу поочерёдно:

Однако это не совсем удобно, особенно когда массив большой.

К счастью, язык C++ поддерживает более удобный способ инициализации массивов с помощью списка инициализаторов. Следующий пример эквивалентен примеру выше:

Если в этом списке инициализаторов больше, чем может содержать массив, то компилятор выдаст ошибку.

Однако, если в списке инициализаторов меньше, чем может содержать массив, то остальные элементы будут проинициализированы значением 0. Например:

Результат выполнения программы:

5
7
9
0
0

Следовательно, чтобы инициализировать все элементы массива значением 0, нужно:

В C++11 вместо этого мы можем воспользоваться синтаксисом uniform-инициализации:

Длина массива


Если вы инициализируете фиксированный массив с помощью списка инициализаторов, то компилятор может определить длину массива вместо вас, и вам уже не потребуется её объявлять.

Следующие две строки выполняют одно и то же:

Это не только сэкономит время, но также вам не придется обновлять длину массива, если вы захотите добавить или удалить элементы позже.

Массивы и перечисления

Одна из основных проблем при использовании массивов состоит в том, что целочисленные индексы не предоставляют никакой информации программисту об их значении. Рассмотрим класс из 5 учеников:

Кто представлен элементом testScores[3]? Непонятно!

Это можно решить, используя перечисление, в котором перечислители сопоставляются каждому из возможных индексов массива:

Вот теперь понятно, что представляет собой каждый из элементов массива. Обратите внимание, добавлен дополнительный перечислитель с именем MAX_STUDENTS. Он используется во время объявления массива для гарантирования того, что массив имеет корректную длину (она должна быть на единицу больше самого большого индекса). Это полезно как для подсчета элементов, так и для возможности автоматического изменения длины массива, если добавить еще один перечислитель:

Обратите внимание, этот трюк работает только в том случае, если вы не изменяете значения перечислителей вручную!

Массивы и классы enum


Классы enum не имеют неявного преобразования в целочисленный тип, поэтому, если вы попробуете сделать следующее:

То получите ошибку от компилятора. Это можно решить, используя оператор static_cast для конвертации перечислителя в целое число:

Однако, это также не очень удобно, поэтому лучше использовать стандартное перечисление внутри пространства имен:

Передача массивов в функции

Хотя передача массива в функцию на первый взгляд выглядит так же, как передача обычной переменной, но «под капотом» C++ обрабатывает массивы несколько иначе.

Когда обычная переменная передается по значению, то C++ копирует значение аргумента в параметр функции. Поскольку параметр является копией, то изменение значения параметра не изменяет значение исходного аргумента.

Однако, поскольку копирование больших массивов — дело трудоёмкое, то C++ не копирует массив при его передаче в функцию. Вместо этого передается фактический массив. И здесь мы получаем побочный эффект, позволяющий функциям напрямую изменять значения элементов массива!

Следующий пример хорошо иллюстрирует эту концепцию:



Результат выполнения программы:

before passValue: 1
after passValue: 1
before passArray: 1 4 6 8 10
after passArray: 10 8 6 4 1

В примере, приведенном выше, значение переменной value не изменяется в функции main(), так как параметр value в функции passValue() был лишь копией фактической переменной value. Однако, поскольку массив в параметре функции passArray() является фактическим массивом, то passArray() напрямую изменяет значения его элементов!

Примечание: Если вы не хотите, чтобы функция изменяла значения элементов массива, переданного в нее в качестве параметра, то нужно сделать массив константным:

Оператор sizeof и массивы


Оператор sizeof можно использовать и с массивами: он возвращает общий размер массива (длина массива умножена на размер одного элемента) в байтах. Обратите внимание, из-за того, как C++ передает массивы в функции, следующая операция не будет корректно выполнена с массивами, переданными в функции:

Результат выполнения программы:

32
4

По этой причине будьте осторожны при использовании оператора sizeof с массивами!

Определение длины фиксированного массива

Чтобы определить длину фиксированного массива, поделите размер всего массива на размер одного элемента массива:

Результат выполнения программы:

The array has 8 elements

Как это работает? Во-первых, размер всего массива равен длине массива, умноженной на размер одного элемента. Формула: размер_массива = длина_массива * размер_одного_элемента.

Используя алгебру, мы можем изменить это уравнение: длина_массива = размер_массива / размер_одного_элемента. sizeof(array) — это размер массива, а sizeof(array [0]) — это размер одного элемента массива. Соответственно, длина_массива = sizeof(array) / sizeof(array[0]). Обычно используется нулевой элемент в качестве элемента массива в уравнении, так как только он является единственным элементом, который гарантированно существует в массиве, независимо от его длины.

Это работает только если массив фиксированной длины, и вы выполняете эту операцию в той же функции, в которой объявлен массив.

Примечание: На следующих уроках мы будем использовать термин «длина» для обозначения общего количества элементов в массиве, и термин «размер», когда речь будет идти о байтах.

Индексирование массива вне диапазона

Помните, что массив длиной N содержит элементы от 0 до N-1. Итак, что произойдет, если мы попытаемся получить доступ к индексу массива за пределами этого диапазона? Рассмотрим следующую программу:

Здесь наш массив имеет длину 5, но мы пытаемся записать значение в 6-й элемент (индекс 5).

Язык C++ не выполняет никаких проверок корректности вашего индекса. Таким образом, в вышеприведенном примере значение 14 будет помещено в ячейку памяти, где 6-й элемент существовал бы (если бы вообще был). Но, как вы уже догадались, это будет иметь свои последствия. Например, произойдет перезаписывание значения другой переменной или вообще сбой программы.

Хотя это происходит реже, но C++ также позволяет использовать отрицательный индекс, что тоже приведет к нежелательным результатам.

Правило: При использовании массивов убедитесь, что ваши индексы корректны и соответствуют диапазону вашего массива.

Тест

Задание №1

Объявите массив для хранения температуры (дробное число) каждого дня в году (всего 365 дней). Проинициализируйте массив значением 0.0 для каждого дня.

Ответ №1

Примечание: Если размер не является ограничением, то вместо типа float лучше использовать тип double.

Задание №2

Создайте перечисление со следующими перечислителями: chicken, lion, giraffe, elephant, duck и snake. Поместите перечисление в пространство имен. Объявите массив, где элементами будут эти перечислители и, используя список инициализаторов, инициализируйте каждый элемент соответствующим количеством лап определенного животного. В функции main() выведите количество ног у слона, используя перечислитель.

Ответ №2

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (404 оценок, среднее: 4,93 из 5)
Загрузка...

Комментариев: 20

  1. Сашуня:

    Первое:

    Второе:

  2. Анатолий:

    2 задание действительно легче, чем в предыдущем итоговом тесте. Цикл for тут точно лишним не будет.

  3. Lil7even:

    Задание 2, без перечисления

  4. Александр:

    Лёгкие задания у меня получаются.

  5. Vicktoras:

    В первом задании написано: Объявите массив для хранения температуры (дробное число) каждого дня в году (всего 365 дней).

    Но в ответе на первое задание указано 366 дней: double temperature[365]
    Вед в уроке говорилось что массив начинается с нуля. Или я что-то не понял?

    1. Павел:

      При объявлении массива в квадратных скобках указывается общее кол-во его ячеек, а вот уже сами ячейки нумеруются с нуля

  6. Борис:

    По поводу выхода за границы массива:
    1) В этом случае, как я понимаю, процесс может испортить только свою память? Порча (или чтение) памяти других процессов тут невозможно? Иначе это просто катастрофа.
    2) Если (1) верно — это обеспечено самой операционкой (защищённая модель памяти и т. д.) или чем-то ещё? И будет ли безопасно на других ОС (не винда) и платформах?
    В сети ясного ответа на это не нашёл.

    1. Артём:

      Насколько я помню из видеокурсов по углубленному изучению языка С, то выделение памяти отдаётся на откуп ОС.
      Любая программа (компилятор тоже программа) при запуске запрашивает у ОС память. Как правило, ОС выделяют не блок памяти под всю программу, а участками, поэтому ни о какой защищенной модели памяти здесь речи не идёт.
      Если ваш выход за массив залез в вашу же программу — вам повезло.

    2. Сергей:

      Механизм защиты памяти (МЗП) используется операционной системой (ОС) и поддерживается процессорами серии i86 и старше.
      В зависимости от размера программы, ОС выбирает способ работы с памятью: сегментный (обычно не более 64 кБ) или страничный (обычно по 4кБ). ОС выделяет память с выровненными границами, поэтому рекомендуется, чтобы данные вашей программы в памяти тоже были выровнены (немного касались этого вопроса Урок №61. Структуры -> Размер структур https://ravesli.com/urok-61-struktury/).
      МЗП не позволит Вам "залесть" в чужую область памяти, т. е., если вам позволит уровень приоритетов (доступа ОС), можно прочитать данные другого процесса из другого доступного участка памяти, но не записать. Вы можете, что угодно делать только со своей выделенной областью памяти.
      Но помните, что при вызове функций или передачи управления выполнения программы, результат записи за пределами индекса массива может привести к коллапсу, так как здесь могут находится данные вашей программы или, что хуже всего, сама Ваша исполняемая программа.

  7. Алексей:

    После всего — второе задание оказалось простым в исполнении, только текстом не приукрасил.

  8. Алексей:

    Немного не понял первое — почему double temp[365] = {0.0}.
    Я использовал double temp[365] = { }.

    В том и ином случаи "0".

  9. Анна:

    Не подскажите, зачем нужно писать
    = {0.0}, если все элементы по умолчанию, без присваивания значений равны 0.
    нельзя ли просто написать
    duoble god[365]; например?
    или это будет неправильно?

  10. Oleksiy:

    "Трюк: Чтобы определить длину фиксированного массива — разделите размер всего массива на размер одного элемента массива:"

    В русском языке принято употреблять слово "размер" массива, когда речь идет о количестве элементов в нем? Разве "длина" в этом случае не будет более уместным термином? Размер больше ассоциируется с количеством байт.

  11. Алексей:

    Вместо "трюка" — уравнения с sizeof, на мой взгляд, гораздо удобнее использовать более компактную встроенную функцию:

    Она возвращает количество элементов в контейнере (массивы, vector, string)
    Должна подключаться отдельным заголовком

    но в Visual Studio 2017 работает уже при

    1. Фото аватара Юрий:

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

    2. ц3к32к:

      Нет, чувак, тогда выведется предупреждение: <: несоответствие типов со знаком и без знака

  12. Антон:

    "Тест 1. Инициализируйте массив значением 0.0 для каждого дня."
    "double temperature[365] = { 0.0 };"
    Так ведь инициализируется только temperature[0], значением 0.0. Остальные тоже будут 0, но не потому что мы так захотели. Нужно ведь через
    for (int count = 0; count < 365; ++count)
    temperature[count] = 0.0;
    ну либо double temperature[365] = { 0.0, 0.0, …362раза…, 0.0 };
    или я ошибаюсь?

    1. Фото аватара Юрий:

      Зачем использовать цикл или 365 раз писать 0.0, если массив инициализируется просто одним лишь { 0.0 } или {}. Есть действия, которые установлены уже по умолчанию, просто так прописывать то, что установлено по умолчанию — зачем? Вы просто напишите лишний код. В этом задании никакой программист писать 365 раз нули не будет, цикл здесь также не уместен. Другое дело уже, если нужно заполнить массив ненулевыми значениями.

      1. Антон:

        Так я думал задание и состоит в том, чтобы каждому значению массива присвоить число, просто для примера взят 0 и так уж совпало, что это "значение по умолчанию" для элементов массива. Меня смутила фраза в задании "для каждого дня".

        1. Фото аватара Юрий:

          В задании говорится именно о 0. Если бы было другое значение, то тогда бы уже использовался цикл.

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *