На предыдущих уроках мы подробно говорили о фиксированных и динамических массивах. Хотя они очень полезны и активно используются в языке C++, у них также есть свои недостатки: фиксированные массивы распадаются в указатели, теряя информацию о своей длине; в динамических массивах проблемы могут возникнуть с освобождением памяти и с попытками изменить их длину после выделения.
Поэтому в Стандартную библиотеку C++ добавили функционал, который упрощает процесс управления массивами: std::array и std::vector. На этом уроке мы рассмотрим std::array, а на следующем — std::vector.
Введение в std::array
Представленный в C++11, std::array — это фиксированный массив, который не распадается в указатель при передаче в функцию. std::array определяется в заголовочном файле array, внутри пространства имен std. Объявление переменной std::array следующее:
1 2 3 |
#include <array> std::array<int, 4> myarray; // объявляем массив типа int длиной 4 |
Подобно обычным фиксированным массивам, длина std::array должна быть установлена во время компиляции. std::array можно инициализировать с использованием списка инициализаторов или uniform-инициализации:
1 2 |
std::array<int, 4> myarray = { 8, 6, 4, 1 }; // список инициализаторов std::array<int, 4> myarray2 { 8, 6, 4, 1 }; // uniform-инициализация |
В отличие от стандартных фиксированных массивов, в std::array вы не можете пропустить (не указывать) длину массива:
1 |
std::array<int, > myarray = { 8, 6, 4, 1 }; // нельзя, должна быть указана длина массива |
Также можно присваивать значения массиву с помощью списка инициализаторов:
1 2 3 4 |
std::array<int, 4> myarray; myarray = { 0, 1, 2, 3 }; // ок myarray = { 8, 6 }; // ок, элементам 2 и 3 присвоен нуль! myarray = { 0, 1, 3, 5, 7, 9 }; // нельзя, слишком много элементов в списке инициализаторов! |
Доступ к значениям массива через оператор индекса осуществляется как обычно:
1 2 |
std::cout << myarray[1]; myarray[2] = 7; |
Так же, как и в стандартных фиксированных массивах, оператор индекса не выполняет никаких проверок на диапазон. Если указан недопустимый индекс, то произойдут плохие вещи.
std::array поддерживает вторую форму доступа к элементам массива — функция at(), которая осуществляет проверку диапазона:
1 2 3 |
std::array<int, 4> myarray { 8, 6, 4, 1 }; myarray.at(1) = 7; // элемент массива под номером 1 - корректный, присваиваем ему значение 7 myarray.at(8) = 15; // элемент массива под номером 8 - некорректный, получим ошибку |
В примере, приведенном выше, вызов myarray.at(1)
проверяет, есть ли элемент массива под номером 1, и, поскольку он есть, возвращается ссылка на этот элемент. Затем мы присваиваем ему значение 7
. Однако, вызов myarray.at(8)
не срабатывает, так как элемента под номером 8 в массиве нет. Вместо возвращения ссылки, функция at() выдает ошибку, которая завершает работу программы (на самом деле выбрасывается исключение типа std::out_of_range
. Об исключениях мы поговорим на соответствующих уроках). Поскольку проверка диапазона выполняется, то функция at() работает медленнее (но безопаснее), чем оператор []
.
std::array автоматически делает все очистки после себя, когда выходит из области видимости, поэтому нет необходимости прописывать это вручную.
Размер и сортировка
С помощью функции size() можно узнать длину массива:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> #include <array> int main() { std::array<double, 4> myarray{ 8.0, 6.4, 4.3, 1.9 }; std::cout << "length: " << myarray.size(); return 0; } |
Результат:
length: 4
Поскольку std::array не распадается в указатель при передаче в функцию, то функция size() будет работать, даже если её вызвать из другой функции:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> #include <array> void printLength(const std::array<double, 4> &myarray) { std::cout << "length: " << myarray.size(); } int main() { std::array<double, 4> myarray { 8.0, 6.4, 4.3, 1.9 }; printLength(myarray); return 0; } |
Результат тот же:
length: 4
Обратите внимание, Стандартная библиотека C++ использует термин «размер» для обозначения длины массива — не путайте это с результатами выполнения оператора sizeof с обычным фиксированным массивом, когда возвращается фактический размер массива в памяти (размер_элемента * длина_массива
).
Также обратите внимание на то, что мы передаем std::array по (константной) ссылке. Это делается по соображениям производительности для того, чтобы компилятор не выполнял копирование массива при передаче в функцию.
Правило: Всегда передавайте std::array в функции по обычной или по константной ссылке.
Поскольку длина массива всегда известна, то циклы foreach также можно использовать с std::array:
1 2 3 4 |
std::array<int, 4> myarray { 8, 6, 4, 1 }; for (auto &element : myarray) std::cout << element << ' '; |
Вы можете отсортировать std::array, используя функцию std::sort(), которая находится в заголовочном файле algorithm:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> #include <array> #include <algorithm> // для std::sort int main() { std::array<int, 5> myarray { 8, 4, 2, 7, 1 }; std::sort(myarray.begin(), myarray.end()); // сортировка массива по возрастанию // std::sort(myarray.rbegin(), myarray.rend()); // сортировка массива по убыванию for (const auto &element : myarray) std::cout << element << ' '; return 0; } |
Результат:
1 2 4 7 8
Функция сортировки использует итераторы, которые мы еще не рассматривали. О них мы поговорим несколько позже.
Заключение
std::array — это отличная замена стандартных фиксированных массивов. Массивы, созданные с помощью std::array, более эффективны, так как используют меньше памяти. Единственными недостатками std::array по сравнению со стандартными фиксированными массивами являются немного неудобный синтаксис и то, что нужно явно указывать длину массива (компилятор не будет вычислять её за нас). Но это сравнительно незначительные нюансы. Рекомендуется использовать std::array вместо стандартных фиксированных массивов в любых нетривиальных задачах.
Он использует операторы new и delete? То есть он не в стеке, и следовательно не может вызвать его переполнение?
А поясните пожалуйста, зачем здесь "const"? Вроде читал про это, но не могу найти где..
в
Это скорее всего для того чтобы в функции нельзя было 'случайно изменить массив'
Это вроде я понимаю. Мне не понятно зачем конкретн ов этом коде нужен const, ведь в нем нет ничего, что могло бы изменять массив. Может это связано как-то с тем, что массивы std::array способны целиком передаваться в функцию, не преобразуясь в указатель, как обычные массивы? Может это вспомогательный синтаксис для "&" ? Чтобы тело массива не начало передаваться, а только адрес..? Просто из ранних уроков я заметил что const часто появляется там где есть & …возможно это как то связано ,но не разобрано или я пропустил
Это для того, чтобы было понятно, что функция не меняет массив
Если говорить коротко, то слово const используется как на всякий пожарный для защиты от дальнейших изменений значений в массиве, даже если кажется что все под контролем. Но это лишь малый пример. В "реальности" программы могут быть гораздо большими и уследить за всеми переменными не всегда удается.
На счет передачи целого массива в функцию. Мне кажется тут все равно задействуются указатели, но так как у нас есть значение длины массива, который представляет собой непрерывную последовательность адресов элементов массива, то за счет этого значения вычисляется адрес последнего элемента, а затем и длина.
Лишь мои предположения.
Также обратите внимание на то, что мы передаем std::array по (константной) ссылке. Это делается по соображениям производительности для того, чтобы компилятор не выполнял копирование массива при передаче в функцию.
Ох сколько времени прошло… я уже Питон почти выучил 😀 Некогда изучать с++, а я так люблю его, он мне как первая девушка
const используется вместе с ссылкой для того что бы можно было передавать в функцию r-value. Так как ссылка это неявно разыменованый константный указатель, то присвоить ей мы можем только l-value. Но когда мы делаем ссылку константной она получает способность продолжать время жизни r-value. Прочитать об этом можно здесь https://ravesli.com/urok-89-ssylki-i-const/
Здравствуйте!
Извините за частный случай: пишу программу для Wemos D1 mini Pro (головное устройство), для удобства работы с EEPROM, в работающую структуру, объявленную глобально, кроме прочего, был добавлен еще и двумерный массив в виде std:array для хранения адресов и статусов ведомых устройств:
Все компилируется без предупреждений, но на строке ввода значения в массив, программа вылетает с "exception 29" — несанкционированная попытка записи в запрещенную для записи область памяти.
ВОПРОС: как правильно вводить данные в двумерный массив помещенный в структуру из других участков программы?
С уважением.
У меня всё скомпилировалось (как онлайн, так и на VS 2019 16.8.6) и выдало ожидаемый результат:
https://onlinegdb.com/B1vrOJSfO
Дмитрий, у меня тоже все компилируется, проблема возникает после заливки скомпилированной прошивки в устройство. Но, на 19-й строчке моего примера у Wemos начинается когнитивный диссонанс ввиде переполнение стека и вываливается исключение 29 (попытка записи в область памяти недоступную для записи):
21:55:14.004 ->
21:55:14.004 -> Exception (29):
21:55:14.004 -> epc1=0x40219fe1 epc2=0x00000000 epc3=0x00000000 excvaddr=0x00000000 depc=0x00000000
21:55:14.004 ->
21:55:14.004 -> >>>stack>>>
21:55:14.004 ->
21:55:14.004 -> ctx: cont
21:55:14.004 -> sp: 3ffff700 end: 3fffffc0 offset: 01a0
21:55:14.004 -> 3ffff8a0: 3ffe0030 00000450 3ffffd24 4020fd02
21:55:14.004 -> 3ffff8b0: 3ffef990 00000468 3ffefe84 3ffff8e0
21:55:14.098 -> 3ffff8c0: 3ffef6b4 00000438 3ffef6b4 4020fe0e
и т.п. и т.д.
Причем при записи значения в аналогичный массив внутри этой же программы, но не вложенный в структуру, переполнения не происходит. Поэтому я и посчитал, что возможно я неправильно обращаюсь к такому массиву…
Похоже на то, что вы перепутали row и columns местами.
Мгер, Кто вам такое сказал ?
Ваш код с обычным двумерным массивом легко справляется.
Если вы не знаете как это сделать, то это не значит что невозможно.
Не вводите людей в заблуждение
Кстати, начиная с C++17 компилятор может вывести не только длину, но и тип массива:
Можете объяснить этот момент?
Почему елементам 2 и 3, а не 1 и 2 ?
тут все что не заполнено, получает значение по умолчанию.
так же как и с обычным массивом:
"std::array — это отличная замена стандартных фиксированных массивов. Они более эффективны, так как используют меньше памяти." Это вдруг почему?
Не по чему. На деле, std::array и обычный массив подуцируют один и тот же ассемблерный код (проверял в VS). С std::array удобнее присваивание целого массива, передача в функции, копирование и т. д. (с обычными массивами тут большие проблемы). По эффективности они абсолютно идентичны
Ну конечно array будет знать свой размер , если указывать его в параметрах :
А если функция должна работать с разными array. Передавать размер как аргумент или я что то не понял?
Не совсем то что ты хочешь, но вот еще как можно сделать:
В общем если массивы одинаковые по типу и размеру, то прокатывает. А вот если разные то нет.
А как на счет двумерного массива?
Двумерный массив объявляется как:
Здесь myarray — это название вашего двумерного массива, а сам двумерный массив создается с помощью вложенного массива (т.е. один внутри другого).
Это очень неудобно. Тут лучше использовать обычные квадратные скобки
с обычным массивом много что нельзя сделать. Например это:
Покажите синтаксис использования функции at() для двухмерного массива и почему size() не показывает суммарную длину всех ячеек двухмерного массива?
Спасибо alex_1988 за вопросы.
И конечно автору сайта за качественный перевод.
Синтаксис использования функции at() в двумерных массивах выглядит так: myarray2.at().at()
Но есть особенность, например:
Здесь сначала идет проверка последнего (второго) индекса массива myarray2 (объявлено 5, проверяем от 0 до 4), а затем предпоследнего (первого) индекса массива myarray2 (объявлено 10, проверяем от 0 до 9).
А как многомерный std::array инициализировать?
Пишет, слишком много инициализаторов.
А кажется, что в самый раз