Smoug1894 0 / 0 / 0 Регистрация: 15.09.2018 Сообщений: 20 |
||||
1 |
||||
Можно ли менять размер динамического массива?28.01.2019, 05:25. Показов 4870. Ответов 17 Метки нет (Все метки)
Здравствуйте.Такой вопрос: можно ли изменить размер динамического массива.Допустим я задал динамический массив таким образом:
Могу ли я после этого изменить его размер?И если да то как?
__________________
0 |
Модератор 12641 / 10135 / 6102 Регистрация: 18.12.2011 Сообщений: 27,170 |
|
28.01.2019, 05:37 |
2 |
1. Создать новый массив нужного размера.
0 |
TrollHammer 1173 / 678 / 324 Регистрация: 22.02.2018 Сообщений: 1,943 Записей в блоге: 2 |
||||
28.01.2019, 05:47 |
3 |
|||
А realloc не сработает?
0 |
Модератор 12641 / 10135 / 6102 Регистрация: 18.12.2011 Сообщений: 27,170 |
|
28.01.2019, 06:04 |
4 |
А realloc не сработает? Если вместо new/delete будете использовать malloc/free, то сработает,
0 |
1173 / 678 / 324 Регистрация: 22.02.2018 Сообщений: 1,943 Записей в блоге: 2 |
|
28.01.2019, 06:14 |
5 |
Если вместо new/delete будете использовать Пример нормально отработал, массив увеличился
0 |
Параллельный Кот 1904 / 826 / 350 Регистрация: 25.03.2016 Сообщений: 2,045 |
|
28.01.2019, 08:46 |
6 |
TrollHammer, в этом и заключается UB: нельзя опираться только на тот факт, что программа в данном конкретном эксперименте работает правильно. new/delete могут транслироваться в malloc/free, а могут и не транслироваться. По поводу realloc стандарт говорит следующее: ptr — Pointer to a memory block previously allocated with malloc, calloc or realloc.
0 |
610 / 415 / 151 Регистрация: 11.01.2019 Сообщений: 1,746 |
|
28.01.2019, 10:20 |
7 |
Могу ли я после этого изменить его размер?И если да то как? Можно поступить так же, как сделано в STL. Ввести 2 переменные: предельный размер массива (capacity) и число элементов массива (size). Изначально size == capacity. Если размер надо уменьшить, то никаких операций с памятью не делаем, просто тупо уменьшаем size до нужной величины. Если же размер надо увеличить больше capacity, то придется старый массив перебросить в новое место в памяти, установив новое значение size == capacity, и потом удалить.
0 |
3531 / 2193 / 401 Регистрация: 09.09.2017 Сообщений: 9,033 |
|
28.01.2019, 11:45 |
8 |
Но для примитивных типов данных (не классов) можно просто заменить new/delete на malloc/free
0 |
7423 / 5018 / 2890 Регистрация: 18.12.2017 Сообщений: 15,694 |
|
28.01.2019, 15:56 |
9 |
Могу ли я после этого изменить его размер? считаю что можно в сторону уменьшения. если размер нужно увеличить — через создание доп. массива
0 |
3531 / 2193 / 401 Регистрация: 09.09.2017 Сообщений: 9,033 |
|
28.01.2019, 18:04 |
10 |
считаю что можно в сторону уменьшения. Если использовать realloc(), то и в сторону уменьшения нельзя. Не будут вызваны деструкторы для удаляемых элементов.
0 |
7423 / 5018 / 2890 Регистрация: 18.12.2017 Сообщений: 15,694 |
|
29.01.2019, 01:29 |
11 |
Если использовать realloc я не про realloc, а про вопрос TC (выделение памяти через new). SIZE задан не как константная величина, почему бы не уменьшить SIZE если нужно. а вот увеличить нельзя — залезаем в чужую память.
0 |
610 / 415 / 151 Регистрация: 11.01.2019 Сообщений: 1,746 |
|
29.01.2019, 09:56 |
12 |
заменить new/delete на malloc/free Как ни крути, а все эти new/delete в библиотеке реализуются, в конечном счете, через malloc/free
0 |
3531 / 2193 / 401 Регистрация: 09.09.2017 Сообщений: 9,033 |
|
29.01.2019, 11:18 |
13 |
почему бы не уменьшить SIZE если нужно. а вот увеличить нельзя — залезаем в чужую память.
Если же речь просто о том, чтобы часть массива оставить незадействованной, то можно. Вот только уменьшением это не будет. .
Как ни крути, а все эти new/delete в библиотеке реализуются, в конечном счете, через malloc/free
Не будут вызваны деструкторы для удаляемых элементов. .
0 |
610 / 415 / 151 Регистрация: 11.01.2019 Сообщений: 1,746 |
|
29.01.2019, 12:03 |
14 |
Зачем вы задаете вопросы, на которые постом ранее был озвучен ответ? Причем тут деструкторы??? Вы читать умеете? New/delete реализуются НА ОСНОВЕ malloc/free. И ДОПОЛНИТЕЛЬНО вызывают деструкторы объектов. И вообще, только неопытный программист будет пытаться скрестить обычные массивы с классами.
0 |
3531 / 2193 / 401 Регистрация: 09.09.2017 Сообщений: 9,033 |
|
29.01.2019, 12:44 |
15 |
Причем тут деструкторы??? Вы читать умеете? New/delete реализуются НА ОСНОВЕ malloc/free. И ДОПОЛНИТЕЛЬНО вызывают деструкторы объектов Могу еще раз себя процитировать:
Если использовать realloc(), то и в сторону уменьшения нельзя. Не будут вызваны деструкторы для удаляемых элементов. Если все еще непонятно, распишу более подробно: new/delete могут быть реализованы через malloc/free. Но не обязаны. Поэтому применять их вперемешку к одному объекту нельзя. То есть если массив выделен через new, то менять его размер (в любую сторону) при помощи realloc нельзя. Освобождать при помощи free нельзя. Хотя можно попробовать после realloc’а вызвать деструкторы у удаляемых элементов вручную, но тоже сомнительное решение. Как минимум, new/delete могут быть реализованы не через malloc/free, а через какой-нибудь mmap.
И вообще, только неопытный программист будет пытаться скрестить обычные массивы с классами. А как будет действовать опытный если ему нужен массив объектов?
0 |
610 / 415 / 151 Регистрация: 11.01.2019 Сообщений: 1,746 |
|
29.01.2019, 14:53 |
16 |
А как будет действовать опытный если ему нужен массив объектов? Ну, видимо, создаст что-то на основе STL.
Поэтому применять их вперемешку к одному объекту нельзя. С этим совершенно согласен! А теперь я процитирую себя:
Можно поступить так же, как сделано в STL. Ввести 2 переменные: предельный размер массива (capacity) и число элементов массива (size). Изначально size == capacity. Если размер надо уменьшить, то никаких операций с памятью не делаем, просто тупо уменьшаем size до нужной величины. Если же размер надо увеличить больше capacity, то придется старый массив перебросить в новое место в памяти, установив новое значение size == capacity, и потом удалить.
0 |
3531 / 2193 / 401 Регистрация: 09.09.2017 Сообщений: 9,033 |
|
29.01.2019, 15:54 |
17 |
Ну, видимо, создаст что-то на основе STL. я бы не был столь категоричен. Зачем тянуть stl если и обычные массивы решают задачу не хуже
Можно поступить так же, как сделано в STL. Ввести 2 переменные: предельный размер массива (capacity) и число элементов массива (size). Это-то один из стандартных способов. Можно еще для увеличения размера удваивать выделяемый размер и т.п. Но это значительно сложнее в реализации, чем хотел ТС и чем простой realloc
0 |
610 / 415 / 151 Регистрация: 11.01.2019 Сообщений: 1,746 |
|
29.01.2019, 15:58 |
18 |
Зачем тянуть stl если и обычные массивы решают задачу не хуже Ну-ну… До тех пор, пока класс объектов, включаемых в массив, не станет базовым.
0 |
Мы продолжаем курс по структурам данных.
На этом занятии речь пойдет о динамических массивах. Что это такое? На
данный момент мы с вами уже хорошо знаем, что из себя представляет и как
работает статический массив. Но у него есть один существенный недостаток –
неизменяемое число элементов. Поэтому, когда создается статический массив,
программист должен как то решить, сколько элементов он должен иметь. Далеко не
всегда можно указать разумное значение. Чтобы как то выйти из этой ситуации в
мире программирования появилась более гибкая структура данных тоже в виде
массива, но с изменяемым (увеличивающимся) числом элементов. Такие массивы
получили название динамические.
Приведу один
простой пример их использования. Предположим, мы просматриваем файловую систему
и сохраняем имена файлов в каждом каталоге. Очевидно, число файлов может
варьироваться от нескольких десятков до нескольких тысяч. Если воспользоваться
статическим массивом, то придется задавать число элементов, скажем, в
10 000. И это приведет к неоправданному расходу памяти. Гораздо лучше
взять динамические массивы с начальным размером в 100 элементов. А, затем, по
мере необходимости, увеличивать этот размер. Тогда память будет расходоваться
куда экономнее.
Структура динамического массива
Давайте теперь
посмотрим, как можно организовать массив с изменяемым числом элементов. Для
этого воспользуемся обычным массивом с некоторым начальным размером, допустим,
в 10 элементов:
Реальный размер
массива называют физическим, а число записанных в него данных – логическим.
Чтобы программно оперировать этими величинами вводят две вспомогательные
переменные, например:
currentLength = 7 maxCapacity = 10
Переменная currentLength содержит индекс
следующего добавляемого в массив значения, а также определяет число уже
записанных данных. Переменная maxCapacity равна
максимальному числу элементов, который имеет массив dar.
Добавление и вставка элементов в массив
Теперь
предположим, что мы хотим добавить новое значение в конец этого массива. Так
как все элементы в массивах должны следовать строго друг за другом с самого
начала без каких-либо пропусков, то новое значение нам следует записать в
ячейку с индексом currentLength:
и увеличить значение currentLength на единицу:
С точки зрения О
большого скорость этой операции составляет O(1).
Но это
добавление в конец. А что если нам нужно вставить новое значение, скажем, после
пятерки. Как в этом случае будет выглядеть алгоритм? В действительности, так же,
как и в обычном статическом массиве. Необходимо сдвинуть значения 6, 7, 8
вправо на один элемент и на прежнее место шестерки записать вставляемое
значение. Переменная currentLength увеличивается
на единицу:
Вычислительная
сложность этой операции с точки зрения О большого составляет O(n), где n – физический
размер динамического массива.
Изменение физического размера динамического массива
Я думаю эти
простые операции добавления и вставки новых значений в массив, пока есть
свободное место, вы себе хорошо представляете. Но что делать, когда все
элементы заняты, а мы хотим добавить еще один?
Было бы неплохо
выделить следующие несколько байт под новый элемент массива и записать туда
требуемое значение.
При этом вся
выделенная под массив область памяти должна быть непрерывной. К сожалению, в
общем случае, такую операцию выполнить не получится, т.к. следующие ячейки
памяти могут быть заняты другими процессами. Поэтому приходится идти несколько
более сложным, но более надежным путем. Выделяется память под новый статический
массив длиной в два, три раза больший первоначального (чаще всего удваивают
физический размер). В этот новый массив копируют все значения из прежнего
массива, записывают новое значение и увеличивают переменную currentLength:
Вычислительная
сложность этой операции с позиции О большого составляет O(n), где n – физический
размер динамического массива.
Далее, если все
новые элементы также будут заполнены, то операция удвоения физического размера
массива повторяется. Это принцип работы динамического массива.
Здесь у вас
может возникнуть вопрос, зачем под новый массив выделять в несколько раз больше
элементов, чем в прежнем? Почему бы не увеличить его просто на один элемент
(или требуемое число элементов)? Зачем удваивать? Ответ прост. Если нам не
хватило физического размера прошлого массива, то скорее всего не хватит и для
нового массива с одним дополнительным элементов. А, значит, операцию выделения
памяти и копирования всех прежних значений в новый массив снова придется
повторять. Это приводит к неоправданному расходу процессорного времени.
Логичнее сделать размер массива сразу в два раза больше и минимизировать
вероятность создания новых массивов с большими размерами.
Удаление элементов в динамическом массиве
Итак, с
добавлением новых значений и изменением физического размера динамического
массива мы с вами разобрались. Осталось сказать пару слов про удаление
значений. Здесь все намного проще, т.к. физический размер массива при этом не
меняется. Поэтому здесь все абсолютно так же, как и в случае со статическими
массивами.
Предположим, нам
нужно исключить последнее значение. Нет ничего проще. Достаточно уменьшить
значение currentLength на единицу и
все. Последнего значения теперь как бы нет. Вычислительная сложность этой
операции составляет O(1).
Немного сложнее
выполняется удаление остальных значений (не последнего). В этом случае нам
нужно сдвинуть все элементы правее удаляемого влево на одну позицию и не забыть
уменьшить значение currentLength на единицу:
Сложность этой
операции составляет O(n). Еще раз отмечу, что при удалении
значений физический размер массива не меняется, даже если ранее он
увеличивался.
Заключение
Давайте подведем
итог этого занятия.
Динамический массив – это массив, который может менять число своих элементов в процессе работы
программы.
Динамические
массивы реализуются на основе обычных статических массивов и хранят данные в
непрерывной области памяти. Благодаря этому доступ к произвольному элементу
выполняется за фиксированное время с вычислительной сложностью O(1).
Если начального
физического размера динамического массива недостаточно, то создается новый
массив размером в несколько раз больше предыдущего с копированием всех прежних
значений. Часто делают удвоение размеров.
Операции
добавления и удаления значений элементов в динамическом массиве выполняются
почти так же, как и в статических массивах. Разница только в том, что при
добавлении новых значений может дополнительно происходить увеличение
физического размера массива. При этом вычислительная сложность операций вставки/удаления
составляет O(n), где n – общий размер
динамического массива.
На следующем
занятии мы продолжим эту тему и увидим, как реализуются динамические массивы на
языках Python и С++.
Видео по теме
Добавлено 8 июня 2021 в 22:33
Помимо динамического распределения памяти для отдельных значений, мы также можем динамически выделять память для переменных массивов. В отличие от фиксированного массива, где размер массива должен быть установлен во время компиляции, динамическое распределение массива позволяет нам выбирать длину массива во время выполнения.
Чтобы распределить массив динамически, мы используем формы new
и delete
для массивов (часто называемые new[]
и delete[]
):
#include <iostream>
#include <cstddef> // std::size_t
int main()
{
std::cout << "Enter a positive integer: ";
std::size_t length{};
std::cin >> length;
// Используем new для массива. Обратите внимание, что длина (length)
// не обязательно должна быть константной!
int *array{ new int[length]{} };
std::cout << "I just allocated an array of integers of length " << length << 'n';
// установить элемент 0 в значение 5
array[0] = 5;
// используем delete для массива, чтобы освободить память,
// используемую для массива
delete[] array;
// здесь нам не нужно устанавливать массив в nullptr/0,
// потому что сразу после этого он всё равно выйдет из области видимости
return 0;
}
Поскольку мы распределяем массив, C++ знает, что вместо простой версии new
он должен использовать версию new
для массива. По сути, вызывается оператор new[]
, даже если []
не помещается рядом с ключевым словом new
.
Значение длины динамически распределяемых массивов должно принадлежать типу, который можно преобразовать в std::size_t
. Мы могли бы использовать int
, но это вызовет предупреждение компилятора, если тот настроен на высокий уровень предупреждений. У нас есть выбор между использованием std::size_t
в качестве типа length
или объявлением length
как int
и последующим приведением его типа при создании массива следующим образом:
int length{};
std::cin >> length;
int *array{ new int[static_cast<std::size_t>(length)]{} };
Обратите внимание: поскольку эта память выделяется из места, отличающегося от памяти, используемой для фиксированных массивов, размер массива может быть довольно большим. Вы можете запустить показанную выше программу и без проблем выделить память для массива длиной 1000000 (или, возможно, даже 100000000). Попробуйте! Поэтому программы, которым необходимо выделить много памяти, в C++ обычно делают это динамически.
Динамическое удаление массивов
При удалении динамически распределенного массива мы должны использовать версию delete
для массива, то есть delete[]
.
Это сообщает процессору, что ему необходимо очистить несколько переменных вместо одной. Одна из наиболее распространенных ошибок, которые допускают начинающие программисты при работе с динамическим распределением памяти, – это использование delete
вместо delete[]
при удалении динамически распределенного массива. Использование простой версии delete
для массива приведет к неопределенному поведению, например к повреждению данных, утечкам памяти, сбоям или другим проблемам.
Один из часто задаваемых вопросов о delete[]
для массивов: «Откуда при удалении массива известно, сколько памяти нужно удалить?» Ответ заключается в том, что new[]
для массивов отслеживает, сколько памяти было выделено переменной, поэтому delete[]
для массивов может удалить нужный объем. К сожалению, значение этого размера/длины недоступно программисту.
Динамические массивы почти идентичны фиксированным массивам
В уроке «10.10 – Указатели и массивы» вы узнали, что фиксированный массив содержит адрес памяти первого элемента массива. Вы также узнали, что фиксированный массив может раскладываться на указатель, указывающий на первый элемент массива. В этой разложенной форме длина фиксированного массива недоступна (и, следовательно, размер массива не доступен через sizeof()
), но в остальном разница небольшая.
Динамический массив начинает свою жизнь как указатель, указывающий на первый элемент массива. Следовательно, он имеет те же ограничения в том, что он не знает своей длины или размера. Динамический массив работает идентично разложенному фиксированному массиву, за исключением того, что программист отвечает за освобождение динамического массива с помощью ключевого слова delete[]
.
Инициализация динамически распределяемых массивов
Если вы хотите инициализировать динамически распределяемый массив значением 0, синтаксис будет довольно простым:
int *array{ new int[length]{} };
До C++11 не было простого способа инициализировать динамический массив ненулевым значением (списки инициализаторов работали только для фиксированных массивов). Это означает, что вам нужно было перебрать массив и явно присвоить значения элементам.
int *array = new int[5];
array[0] = 9;
array[1] = 7;
array[2] = 5;
array[3] = 3;
array[4] = 1;
Это очень раздражает!
Однако, начиная с C++11, теперь можно инициализировать динамические массивы с помощью списков инициализаторов!
// инициализируем фиксированный массив до C++11
int fixedArray[5] = { 9, 7, 5, 3, 1 };
// инициализируем динамический массив, начиная с C++11
int *array{ new int[5]{ 9, 7, 5, 3, 1 } };
// Чтобы избежать печати типа дважды, мы можем использовать auto.
// Это часто делается для типов с длинными именами.
auto *array{ new int[5]{ 9, 7, 5, 3, 1 } };
Обратите внимание, что в этом синтаксисе нет оператора =
между длиной массива и списком инициализаторов.
Для единообразия фиксированные массивы также можно инициализировать с помощью унифицированной инициализации:
int fixedArray[]{ 9, 7, 5, 3, 1 }; // инициализируем фиксированный массив в C++11
char fixedArray[]{ "Hello, world!" }; // инициализируем фиксированный массив в C ++ 11
Явное указание размера массива необязательно. Но это может помочь в раннем обнаружении ошибок, поскольку компилятор предупредит вас, когда указанная длина меньше фактической.
Изменение размера массивов
Динамическое распределение массива позволяет вам установить длину массива во время выделения. Однако C++ не предоставляет встроенного способа изменения размера уже распределенного массива. Это ограничение можно обойти, динамически распределяя новый массив, копируя в него элементы и удаляя старый массив. Однако это подвержено ошибкам, особенно когда тип элемента является классом (у которого есть особые правила, регулирующие его создание).
Следовательно, мы рекомендуем не делать этого самостоятельно.
К счастью, если вам нужна эта возможность, C++ предоставляет массив с изменяемым размером как часть стандартной библиотеки под названием std::vector
. О нем мы скоро поговорим.
Небольшой тест
Вопрос 1
Напишите программу, которая:
- спрашивает пользователя, сколько имен он хочет ввести;
- динамически создает массив
std::string
; - просит пользователя ввести каждое имя;
- вызывает
std::sort
для сортировки имен (смотрите «10.4 – Сортировка массива с помощью сортировки выбором» и «10.11 – Арифметика указателей и индексирование массивов»); - печатает отсортированный список имен.
std::string
поддерживает сравнение строк с помощью операторов сравнения <
и >
. Вам не нужно выполнять сравнение строк вручную.
Ваш вывод должен соответствовать этому:
How many names would you like to enter? 5
Enter name #1: Jason
Enter name #2: Mark
Enter name #3: Alex
Enter name #4: Chris
Enter name #5: John
Here is your sorted list:
Name #1: Alex
Name #2: Chris
Name #3: Jason
Name #4: John
Name #5: Mark
Напоминание
Вы можете использовать std::getline()
для чтения имен, содержащих пробелы.
Напоминание
Чтобы использовать std::sort()
с указателем на массив, вычислите начало и конец вручную
std::sort(array, array + arrayLength);
Ответ
#include <algorithm> // std::sort #include <cstddef> // std::size_t #include <iostream> #include <limits> // std::numeric_limits #include <string> std::size_t getNameCount() { std::cout << "How many names would you like to enter? "; std::size_t length{}; std::cin >> length; return length; } // Просим пользователя ввести все имена void getNames(std::string* names, std::size_t length) { // Игнорировать перевод строки, оставленный std::cin std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n'); for (std::size_t i{ 0 }; i < length; ++i) { std::cout << "Enter name #" << i + 1 << ": "; std::getline(std::cin, names[i]); } } // Печатает отсортированные имена void printNames(std::string* names, std::size_t length) { std::cout << "nHere is your sorted list:n"; for (std::size_t i{ 0 }; i < length; ++i) std::cout << "Name #" << i + 1 << ": " << names[i] << 'n'; } int main() { std::size_t length{ getNameCount() }; // Распределяем массив для хранения имен auto* names{ new std::string[length]{} }; getNames(names, length); // Сортируем массив std::sort(names, names + length); printNames(names, length); // не забываем использовать delete для массива delete[] names; // здесь нам не нужно устанавливать names в nullptr/0, потому что он // в любом случае уничтожится сразу после этого. return 0; }
Теги
arrayC++ / CppLearnCppnew / deleteВыделение памятиДинамическое распределение памятиДля начинающихМассивОбучениеПрограммирование
Определение: |
Массив — набор однотипных переменных, доступ к которым осуществляется по индексу. Динамический массив может изменять свой размер в зависимости от количества элементов в нём. |
Содержание
- 1 Операции
- 1.1 get(i)
- 1.2 set(i,x)
- 1.3 add(x)
- 1.4 del()
- 1.5 size()
- 2 Амортизационная стоимость каждой операции
- 2.1 Метод предоплаты
- 2.1.1 Стоимость операции add(x)
- 2.1.2 Стоимость операции del()
- 2.2 Метод потенциалов
- 2.2.1 Стоимость операции add(x)
- 2.2.2 Стоимость операции del()
- 2.1 Метод предоплаты
- 3 Динамические массивы в современных языках программирования
- 3.1 С++ — vector
- 3.2 Java — ArrayList
- 4 Источники информации
Операции
Определим операции, которые мы будем применять к динамическому массиву:
get(i)
- Возвращает значение -ой ячейки массива. Время выполнения — .
set(i,x)
- В -ую ячейку массива записывается элемент . Время выполнения — .
add(x)
- Добавление в массив элемента . Время выполнения — ; в худшем случае, при котором необходимо перенести все элементы из текущего массива во вдвое больший массив — ( — размер массива).
del()
- Удаляет последний элемент массива. В случае, если количество элементов в массиве в раз меньше его длины, то происходит сжатие в раз. ( — константы, зависящие от реализации). Время выполнения операции в худшем случае — .
size()
- Возвращает количество элементов массива. Время выполнения — .
Амортизационная стоимость каждой операции
Пусть наш массив расширяется в раза, и уменьшается в раза, когда длина массива в раза больше количества элементов в массиве. В этом случае амортизационная стоимость каждой операции будет .
Метод предоплаты
Стоимость операции add(x)
Пусть у нас единицей стоимости операции является одна монетка. Тогда при каждой операции add(x), при которой нам не требуется копирование, мы будем использовать три монетки. Из них одна пойдёт на стоимость самой этой операции, а две будут в резерве (пусть, если мы добавили -ый элемент, мы будем класть по одной монетке к элементам с номерами и ). В итоге, к тому моменту, как массив будет заполнен, рядом с каждым элементом будет лежать по одной монетке, которую мы и можем использовать на его копирование в новый массив. Таким образом, амортизационная стоимость каждой операции add(x) — , и среднее время её работы — .
Стоимость операции del()
При каждой операции будем использовать две монетки. Одну из них потратим на само удаление элемента, другую на элемент, стоящий на позиции . Тогда даже в самом худшем случае (только что расширились, а потом удалили) у каждого элемента из первых будет по монете и на удаление надо будет потратить только монету.
Метод потенциалов
За потенциал примем число:
, где — размер массива, — число элементов массива.
Стоимость операции add(x)
- , массив расширяется:
- , массив не расширяется:
- , массив не расширяется:
- , массив не расширяется:
В итоге, средняя стоимость операции — , а среднее время работы — .
Стоимость операции del()
- , массив сужается:
- , массив не сужается:
- , массив не сужается:
- , массив не сужается:
Средняя стоимость операции — , а среднее время работы — .
Динамические массивы в современных языках программирования
Динамические массивы широко применяются во многих языках программирования. Рассмотрим, как эта структура данных реализуется в С++ и Java.
С++ — vector
В С++ динамический массив используется в структуре vector, она описана в STL(<vector>). Стратегия расширения проста: при попытке записи в массив нового элемента в момент полного заполнения памяти происходит увеличение размера в раза при компиляции GNU C++ и в раза при компиляции Microsoft Visual C++. При удалении элементов уменьшение размера массива никогда не происходит. При инициализации vector по-умолчанию начальный размер равен .
Java — ArrayList
В Java структура ArrayList основана на динамическом массиве. При превышении максимального на данный момент размера происходит увеличение в раза. Причем начальный размер равен . Как и в vector, в ArrayList не предусмотрено изменение размера при удалении элементов. Для принудительного изменения размера следует использовать метод trimToSize().
Источники информации
- Wikipedia — Dynamic array
- Wikipedia — Динамический массив
Реализация шаблона класса “Динамический массив”
Рассмотрим реализацию класса dynamic_array — динамический массив, то есть массив, размер которого может изменяться.
Реализация класса представляет собой шаблон, параметром шаблона является тип хранимых в массиве элементов.
Закрытые поля класса:
m_size — размер массива (количество элементов в массиве, доступных пользователю).
m_capacity — «вместимость» массива, то есть размер выделенной памяти для хранения элементов. При увеличении размера массива если новый размер не превосходит m_capacity, то новые элементы можно создать в массиве без выделения дополнительной памяти.
m_data — указатель на область памяти, где хранятся сами элементы массива.
Конструкторы и деструкторы
Конструктор по умолчанию создает пустой массив, не содержащий элементов.
public:
dynamic_array()
m_size = 0;
m_capacity = 0;
m_data = NULL;
>
Копи-конструктор создает копию существующего массива. Он нужен для того, чтобы при создании копии массива выделить новую память для хранения данных копии массива и скопировать туда все элементы. Если не сделать копи-конструктор, то при создании копии массива поле m_data у копии будет указывать на ту же область памяти, что и у исходного массива. Поэтому если в классе используется динамическое распределение памяти, то всегда необходимо создавать копи-конструктор.
Конструктор, который создает массив заданного размера.
Деструктор необходим для того, чтобы освободить выделенную память при удалении объекта.
dynamic_array()
if (m_data)
delete[] m_data;
>
Изменение размера массива
Метод resize изменяет размер массива, новый размер передается параметром size . Если значение size не превосходит значения m_capacity , то этот метод только изменяет значение поля m_size , иначе необходимо перевыделить память — выделяется новая область памяти для хранения элементов, существующие элементы массива копируются из старой области памяти в новую, выделенная ранее память освобождается.
Для того, чтобы память не выделялась слишком часто, размер выделенной памяти удваивается по сравнению со старым размером массива.
Метод push_back добавляет один новый элемент в конец массива.
void push_back(T val)
resize(m_size + 1);
m_data[m_size — 1] = val;
>
Метод size возвращает размер массива.
Доступ к элементам массива
Доступ к элементам массива перегрузим оператор [] . Это позволит обращаться к элементам класса dynamic_array так же, как к элементам обычного массива: a[i] .
Перегрузка оператора вывода
Для вывода массива перегрузим оператор << . Это необходимо сделать отдельной функцией после реализации класса dynamic_array .
Array. Resize<T>(T[], Int32) Метод
Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
Изменяет количество элементов в одномерном массиве до указанной величины.
Параметры типа
Тип элементов массива.
Параметры
Подлежащий изменению размера одномерный массив, индексация которого начинается с нуля, или значение null для создания нового массива заданного размера.
Размер нового массива.
Исключения
Значение параметра newSize меньше нуля.
Примеры
В следующем примере показано, как изменение размера влияет на массив.
Комментарии
Этот метод выделяет новый массив с указанным размером, копирует элементы из старого массива в новый, а затем заменяет старый массив новым. array должен быть одномерным массивом.
Если array это так null , этот метод создает новый массив с указанным размером.
Length Если newSize больше старого массива, выделяется новый массив, а все элементы копируются из старого массива в новый. Если newSize значение меньше Length старого массива, выделяется новый массив, а элементы копируются из старого массива в новый, пока не будет заполнен новый, остальные элементы старого массива игнорируются. Если newSize значение равно Length старому массиву, этот метод ничего не делает.
Этот метод является операцией O( n ), где n находится newSize .
Метод Resize изменяет размер только одномерного массива. Класс Array не включает метод изменения размера многомерных массивов. Для этого необходимо предоставить собственный код или вызвать специальный метод в сторонней библиотеке. Следующий код иллюстрирует одну возможную реализацию метода, который изменяет размер массива n измерений.
Как правильнее изменять размер массива в С++?
Я не знаю, как работает realloc, но мне кажется, что явно быстрее сочетания «выделить новое место требуемого размера и скопировать в него старый массив, а потом его еще и удалить». Это правда или проще сделать realloc и переприсвоить указатели?
P.S. Именно для языка С++, то есть учитывать всю философию и т. д. Или насрать?
Именно для языка С++, то есть учитывать всю философию и т. д.
Ну а для массивов?
std::vector, не стоит думать, потом поймешь.
realloc, vector внутри так и делает
вектор — это такой контейнер, который выделяет непрерывные куски памяти. так что, под массив покатит. почитай матчасть.
Использовать вместо массивов std::vector, очевидно же.
У тебя что за задача? Эффективный менеджер памяти писать или что-то более высокого уровня? Нечего забивать себе голову такими вещами, если это высокоуровневая задача.
Если же менеджер памяти, то выделяй пул памяти и менеджи руками.
Это просто я решил посмотреть лыба из следующего года. Там надо велосипедить всякие стеки, очереди и т. д. на плюсах. Если бы был Си, вопрос бы не встал. Но преподы такие преподы — если просто #include <stack>, это явно не прокатит, если #include <vector> и держать вектор внутри велосипедного стека, это тоже не прокатывает — типа слишком просто. А вот С++ и средства Си — это нормально. Я думаю, можно и на Си, наверное, просто. Так будет даже как-то целостнее. Хотя велосипеды велосипедить — это какое-то зло(
Хотя я год назад писал свой вело-клон vector. Там как раз realloc с placement new и зайчатками шаблонной магии.
Я теоретически интересуюсь.
The realloc() function changes the size of the memory block pointed to by ptr to size bytes. The contents will be unchanged in the range from the start of the region up to the minimum of the old and new sizes.
Хочешь посмотреть, как надо, посмотри как сделан std::vector.
realloc может облажаться с выделением непрерывного куска, выделить его где-то ещё и скопировать старые данные туда, убив старый указатель. Именно поэтому она возвращает указатель на расширенный кусок памяти и использовать надо именно его.
Вот скажи, это как понимать? Можно мне просто #inlude <stack>?
З використанням бібліотеки стандартних шаблонів STL (Standard Template Library) реалізувати:
1. Стек.
2. Чергу.
Забей ты на эти лабы, что как маленький.
Если речь не идёт о расчёте разлёта осколков после подрыва ЯВУ, то на современных машинах он не ошибается. И, если я не ошибаюсь, кусок не обязательно будет непрерывным. Хотя, с точки зрения приложения будет.
Вот и спросишь у преподавателя. А то мало ли, может они не в курсе, что в STL есть и стеки, и очереди, а не только один vector и list.
А какие варианты? Просто не сдать лабы? Отмазаться «доколе будем переписывать STL?!»? Если есть реальные варианты, я бы сделал)
Ну, действительно не знаю. У меня были мысли, что правильный realloc подшаманивает с таблицей виртуальной памяти и оно именно так и выходит: всегда непрерывно. Может и так; но мне это почему-то казалось неэффективным: типа что ж это такое, целая +1 косвенная адресация воще ко всему.
Vector containers are implemented as dynamic arrays; Just as regular arrays, vector containers have their elements stored in contiguous storage locations, which means that their elements can be accessed not only using iterators but also using offsets on regular pointers to elements.
посмотрел bits/vector. Это же какое-то нечитабельное что-то! Да здравствует инкапсуляция!
Хз, как они это сделали, но в мане пишут безальтернативно:
А потом идут пространные рассуждения, что будет, если память кончится.
И причём тут stl?
Ну вообще-то, в __учебной__ практике написание своей упрощённой реализации кусочка STL — это нормально.
А то можно вообще спросить, зачем изучать классичесские алгоритмы типа пузырьковой сортировки — они, внезапно в библиотеках тоже имеются.
Хотя, я тут подумал. Страницы ж размером по 4 килобайта или типа того. И расходы использование виртуальной памяти всегда есть. То есть последствия облажавшегося реаллока — это максимум копирование одной неполной страницы в другое место; а это уж подавно лучше, чем всегда копировать весь массив.
действительно..
просто выше шла дискуссия про стл 😉
Ну то unchaged — это ж про содержимое. Типа, realloc не загадит вам то, что уже хранилось в массиве.
Насколько я помню, все эти стеки/очереди требовалось реализовать на основе структуры:
struct foo datatype data;
foo* next;
>
Но это было лет 7 назад.
Там как раз пишут про новую память:
the added memory will not be initialized
А про старую ничего не пишут. Вообще. Думаю, если они предупреждают, что не инициализированная память не будет инициализирована, то уж о перемещениях написали бы.
О перемещениях чуть ниже:
If the area pointed to was moved, a free(ptr) is done.
.
If realloc() fails the original block is left untouched; it is not freed or moved.
The realloc() function returns a pointer to the newly allocated memory, which is suitably aligned for any kind of variable and may be different from ptr,
Т.е. редко, но бывает.
Ну а как мне реализовать стек стредствами STL? Где предел дозволенного? Что можно включать, а что нельзя уже?
Пойду поставлю в TODO: таки прочитать главу о менеджменте памяти из «Структур данных и алгоритмов» Ахо, Ульмана и Хопкрофта.
Это где? У нас такого не предъявляют. Наоборот — сказано реализовать как массив и как список. Это что, можно включить вектор и лист и все?
Ты всерьёз думаешь, что тут могут сделать astral <<EOF Какие тараканы у преподавателей cdshines и что ему позволят использовать из STL? EOF ?
Это риторический вопрос, который призывает всех вместе покачать головой и сказать «ндо, тупое какое-то задание».
Вот так и скажешь преподавательскому составу. И приведёшь пример хорошего задания. Уверен, скажут спасибо.
А вообще, не забивай сейчас голову. Тебе успеют накидать требований.
Я думаю, надо плотнее общаться с преподавательским составом.
Учебная программа всё-таки рассчитана бОльшей частью на тех, для кого STL — вообще ругательное слово. И я думаю, что если нормальный преподаватель видит студента, который явно знает больше среднего, он пойдёт навстречу. По крайней мере, расскажет про те самые границы.
не осилил магию шаблонов?
В c++ нет realloc, потому что бывают нетривиальные (не побайтовые) операторы присваивания/копирования. Из-за этого существуют классы, для которых результат a = b не равен результату memcpy(&a, &b, sizeof(a)). realloc делает именно memcpy без знания о том, что хранится по этому адресу, из-за чего не имеет смысла в си++. Для new[]/delete[] нет чего-то вроде realloc[], так как он был бы очень опасной операцией с точки зрения исключений (привет отсутствию прямого запрета на кидание в деструкторе), а написать один цикл с учётом текущей стратегии безопасности обычно не составляет никакого труда.
В моем ВУЗе процветал Delphi и делали мы все это на самописных списках. На самом деле не лишним будет поинтересоваться лично у преподавателя, что конкретно он от вас хочет.
Хотя я год назад писал свой вело-клон vector. Там как раз realloc с placement new и зайчатками шаблонной магии.
шаблонная магия была как раз для написания is_pod<T>, чтобы делать realloc для всяких интов, и феерию деструкторов и конструкторов для объектов; а то преподаватель жаловался, что это «не эффективно всё копировать». Таки убедил его, что с объектами realloc не прокатит.