Как из статического массива сделать динамический с
Перейти к содержимому

Как из статического массива сделать динамический с

  • автор:

Динамический массив статических массивов

Вы сами себе противоречите! Статический массив — это постоянная, неизменная область память, доступ к которой возможен на протяжении всей работы с программой. Статическую переменную нельзя объявлять динамически. В этом и есть особенность!

13 мая 2012 в 14:04

У меня есть статические массивы объявленные обычным образом: int arr[LENGHT] . Я лишь хочу собрать указатели на них в одном динамическом массиве.

13 мая 2012 в 14:07

Ну так соберите. Хотя проще всего инициализировать массивом-константой, который в программе уже будет, так что заводить ещё динамический массив — лишняя морока. И, кстати, что такое динамический массив (желательно ссылку на место в стандарте языка)? А то я часто тут вижу «динамический массив», а что это такое — не понимаю.

Что такое Статический и Динамический массив?

edward_freedom

Память под статический массив выделяется на стеке. При выходе из области видимости стек очищается и память под массивом освобождается автоматически (не нужна операция освобождения памяти, операция есть, но о ней позаботится компилятор самостоятельно).

Память для динамического массива выделяется в динамической памяти (в куче) (new[]). Когда массив становится не нужным память должна быть освобождена (delete[]), иначе произойдет утечка памяти.

В связи с вышеизложенными принципиальными отличиями, есть несколько следствий:
1. Имя статического массива это не указатель. Это можно понять например сравнив, что возвращает sizeoff() для статического массива и для динамического. Хотя в некоторых ситуациях компилятор ведет себя так, как будто имя статического массива это указатель, например: можно передать имя статического массива в функцию, принимающую указатель.
2. У динамического массива нет имени. Операция new[] возвращает указатель. Имя есть у указателя.
3. Внимание! Оба варианта массивов имеют фиксированный размер. Изменять его нельзя!
То что в std::vector вы можете укладывать кучу элементов постепенно, не указывая нигде предварительного размера является следствием того, что std::vector скрывает от вас всю работу, которую он при этом делает. При добавлении нового элемента, для которого нет места, он выделяет память для нового массива большего размера, копирует старый массив в новый, удаляет старый массив, добавляет новый элемент. Если идет интенсивная работа с push_back(), то это может ОООЧЕНЬ дорого стоить. std::vector — это удобно, но необходимо всегда помнить, за счет чего достигается это удобство. std::vector — это не динамический массив — это обертка над ним для более удобной работы с динамическим массивом.

В языке Си (C99) есть такая штука как Variable Length Array (VLA) — это статический массив с изменяемым размером. Т.е. вы можете, например, в функции объявить int arr[N], где N будет передаваться в функцию как параметр.
В стандарте С++ VLA нет! Но, например, тот же gcc с опциями по умолчанию разрешает его использования в С++ проектах. С опциями, заставляющими gcc более жестко следовать стандарту, он может запрещать использовать VLA в С++ проектах. Но не рассчитывайте, что все компиляторы будут так делать. Например микрософтовский компилятор в принципе не умеет в VLA (хотя я уже пару лет не проверял это).
VLA может показаться классной штукой.
Но, например, в ядре Линукс в свое время проводили целую компанию по выпиливанию VLA из исходников. Торвальдс высказывался негативно про VLA. Все это при желании можно нагуглить.
Стоит помнить, что размер стека ограничен, а VLA то же выделяется на стеке. Кроме того выделение памяти для обычного статического массива это просто увеличение счетчика стека на константу (и все, одна операция сложения регистра с константой, а компилятор одной операцией выделяет память для всех переменных в текущей области видимости). С VLA все сложнее с выделением и удалением. Так же sizeof() для обычного статического массива это операция времени компиляции (при выполнении будет уже заранее известная константа), для VLA — это полноценный вызов функции.

Ответ написан более двух лет назад
Нравится 4 1 комментарий
Wataru @wataru Куратор тега C++

Коммкнтарий про вектор: постоянно делать push_back не особо медленнее, чем сначала выделить память и просто заполнять массив.
Медленнее, конечно, но не ОООЧЕНЬ дорого.

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

Если вы знаете, сколько элементов будет — то reserve стоит сделать. Но не стоит избегать push_back, как огня из-за его медленности.

Как из статического массива сделать динамический с

Кроме отдельных динамических объектов в языке C++ мы можем использовать динамические массивы. Для выделения памяти под динамический массив также используется оператор new , после которого в квадратных скобках указывается, сколько массив будет содержать объектов:

int *numbers ; // динамический массив из 4 чисел // или так // int *numbers = new int[4];

Причем в этом случае оператор new также возвращает указатель на объект типа int — первый элемент в созданном массиве.

В данном случае определяется массив из четырех элементов типа int, но каждый из них имеет неопределенное значение. Однако мы также можем инициализировать массив значениями:

int *numbers1 >; // массив состоит из чисел 0, 0, 0, 0 int *numbers2 >; // массив состоит из чисел 1, 2, 3, 4 int *numbers3 >; // массив состоит из чисел 1, 2, 0, 0 // аналогичные определения массивов // int *numbers1 = new int[4]<>; // массив состоит из чисел 0, 0, 0, 0 // int *numbers1 = new int[4](); // массив состоит из чисел 0, 0, 0, 0 // int *numbers2 = new int[4]< 1, 2, 3, 4 >; // массив состоит из чисел 1, 2, 3, 4 // int *numbers3 = new int[4]< 1, 2 >; // массив состоит из чисел 1, 2, 0, 0

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

Стоит отметить, что в стандарт С++20 добавлена возможность выведения размера массива, поэтому, если применяется стандарт С++20, то можно не указывать длину массива:

int *numbers >; // массив состоит из чисел 1, 2, 3, 4

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

int *numbers >; // получение элементов через синтаксис массивов std::cout 

Причем для доступа к элементам динамического массива можно использовать как синтаксис массивов ( numbers[0] ), так и операцию разыменования ( *numbers )

Соответственно для перебора такого массива можно использовать различные способы:

unsigned n< 5 >; // размер массива int* p < new int[n] < 1, 2, 3, 4, 5 >>; // используем индексы for (unsigned i<>; i < n; i++) < std::cout std::cout ; i < n; i++) < std::cout std::cout ; q != p + n; q++) < std::cout std::cout 

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

Для удаления динамического массива и освобождения его памяти применяется специальная форма оператора delete :

delete [] указатель_на_динамический_массив;

#include int main() < unsigned n< 5 >; // размер массива int* p < new int[n] < 1, 2, 3, 4, 5 >>; // используем индексы for (unsigned i<>; i < n; i++) < std::cout std::cout

Чтобы после освобождения памяти указатель не хранил старый адрес, также рекомендуется обнулить его:

delete [] p; p = nullptr; // обнуляем указатель

Многомерные массивы

Также мы можем создавать многомерные динамические массивы. Рассмотрим на примере двухмерных массивов. Что такое по сути двухмерный массив? Это набор массив массивов. Соответственно, чтобы создать динамический двухмерный массив, нам надо создать общий динамический массив указателей, а затем его элементы - вложенные динамические массивы. В общем случае это выглядит так:

#include int main() < unsigned rows = 3; // количество строк unsigned columns = 2; // количество столбцов int** numbers>; // выделяем память под двухмерный массив // выделяем память для вложенных массивов for (unsigned i<>; i < rows; i++) < numbers[i] = new int[columns]<>; > // удаление массивов for (unsigned i<>; i < rows; i++) < delete[] numbers[i]; >delete[] numbers; >

Вначале выделяем память для массива указателей (условно таблицы):

int** numbers>;

Затем в цикле выделяем память для каждого отдельного массива (условно строки таблицы):

numbers[i] = new int[columns]<>;

Освобождение памяти идет в обратном порядке - сначала освобождаем память для каждого отдельного вложенного массива, а затем для всего массива указателей.

Пример с вводом и выводом данных двухмерного динамического массива:

#include int main() < unsigned rows = 3; // количество строк unsigned columns = 2; // количество столбцов int** numbers>; // выделяем память под двухмерный массив for (unsigned i<>; i < rows; i++) < numbers[i] = new int[columns]<>; > // вводим данные для таблицы rows x columns for (unsigned i<>; i < rows; i++) < std::cout ; j < columns; j++) < std::cout > numbers[i][j]; > > // вывод данных for (unsigned i<>; i < rows; i++) < // выводим данные столбцов i-й строки for (unsigned j<>; j < columns; j++) < std::cout std::cout for (unsigned i<>; i < rows; i++) < delete[] numbers[i]; >delete[] numbers; >

Пример работы программы:

Enter data for 1 row 1 column: 2 2 column: 3 Enter data for 2 row 1 column: 4 2 column: 5 Enter data for 3 row 1 column: 6 2 column: 7 2 3 4 5 6 7

Указатель на массив

От типа int** , который представляет указатель на указатель (pointer-to-pointer) следует отличать ситуацию "указатель на массив" (pointer to array). Например:

#include int main() < unsigned n; // количество строк int (*a)[2] = new int[n][2]; int k<>; // устанавливаем значения for (unsigned i<>; i < n; i++) < // устанавливаем данные для столбцов i-й строки for (unsigned j<>; j < 2; j++) < a[i][j] = ++k; >> // вывод данных for (unsigned i<>; i < n; i++) < // выводим данные столбцов i-й строки for (unsigned j<>; j < 2; j++) < std::cout std::cout // удаляем данные delete[] a; a = nullptr; >

Здесь запись int (*a)[2] представляет указатель на массив из двух элементов типа int. Фактически мы можем работать с этим объектом как с двухмерным массивом (таблицей), только количество столбцов в данном случае фиксировано - 2. И память для такого массива выделяется один раз:

int (*a)[2] = new int[n][2];

То есть в данном случае мы имеем дело с таблице из n строк и 2 столцов. Используя два индекса (для строки и столца), можно обращаться к определенному элементу, установить или получить его значение. Консольный вывод данной программы:

1 2 3 4 5 6

Массивы в языке Си

Массивы служат для хранения множества данных одного типа.

Массив характеризуется типом хранимых данных и размером массива - количество хранимых данных.

Массивы делятся на статические - создаваемые при запуске программы и динамические - создаваемые в процессе выполнения программы.

Массивы бывают одномерные и многомерные. Легко визуализировать массивы с размерностью до трех. Одномерные массивы это хранение элементов в линию, двумерные - прямоугольником, трехмерные - параллелепипедом. Четырехмерный массив - это одномерный массив трехмерных массивов и т.д.

Статические массивы

Статические массивы создаются при запуске программы в статической памяти и существуют всё время выполнения программы.

Статические массивы создаются в области памяти называемой стеком. Размер стека ограничен сильнее чем размер кучи (heap) (динамическая память), поэтому нельзя создать большой статический массив (например в 100 миллионов записей). И в общем случае размер динамического массива может быть больше чем статического.

Размер статического массива должен быть известен на момент запуска программы, поэтому он должен либо быть явно прописан в коде цифрами, либо вставляться через макроподстановки (некоторые компиляторы позволяют использовать переменную для задания размера статического массива). В любом случае вы НЕ МОЖЕТЕ задать размер статического массива во время выполнения программы.

Инициализация статического массива

 int a[5]; // Статический массив a состоящий из 5 элементов int. int a[5] = <>; // Статический массив инициализированный нулями static int a[5]; // Статический массив инициализированный нулями int a[3] = ; //Создание и явная инициализация статического массива со значениями 1, 3, 8. int a[] = ; //Создание и явная инициализация статического массива со значениями 1, 3, 8. int a[2][3];//Двумерный массив из двух строк (row) и трех столбцов (column) int a[2][3]=< //Двумерный массив с явной инициализацией , , >; int a[][3]=< //Двумерный массив с явной инициализацией . Первую размерность можно не указывать , , >; 

Ниже приведен пример программы с прямой и косвенной адресацией элементов массива.

 #include // Требуется для prinf() #define AR_SIZE 10 // Размер массива int main() < int a[10]; // Статический массив размером 10 #define AR_SIZE 10 // Размер массива можно задать через макроопределение int b[AR_SIZE]; // Статический массив размером AR_SIZE for(int n = 0; n < AR_SIZE; n++)< // Перебираем массив printf("a[%d] = %d\n", n, a[n]); // Выводим значения printf("a + %d = %d\n", n, *(a + n)); // Аналогично предыдущей строке (косвенная адресация) >return 0; > 

Динамические массивы

Динамические массивы создаются во время работы программы в динамической памяти функцией malloc() и выделенная под них память освобождается либо вручную, вызовом функции free(), либо во время завершения программы.

Динамические массивы создаются в динамической памяти называемой кучей. Размер кучи ограничен операционной системой и для 32-х битный систем не может превышать 4 гигабайта.

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

Необходимость ручного выделения памяти под динамические массивы функцией malloc() и освобождения памяти функцией free() усложняет программу и может вызывать утечки памяти. Поэтому, если есть возможность - всегда используйте статические массивы.

Утечкой памяти называют ситуацию когда динамическая память, используемая программой, постоянно растет. Такая ситуация возникает когда выделение памяти (функцией malloc()) происходит чаще чем освобождение (функцией free()). В результате чего динамическая память кончается и программа завершается с ошибкой и теряет работоспособность. Выделение памяти может происходить в цикле и программисту приходится вручную следить чтобы она своевременно освобождалась. Существует множество языков где процессом освобождения памяти занимается так называемый сборщик мусора (garbage collector), но на данный момент данные языки сильно проигрывают Си по производительности. Для маленьких и короткоживущих программ проблема утечки памяти может быть не актуальна, но для программ и сервисов которые работают постоянно это бывает серьезной проблемой, решением которой бывают рукожопые решения вроде периодических ручных или автоматических перезапусков программы - не делайте так.

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

 #include //printf() #include // malloc(), free() int main() < const int AR_SIZE = 5; // Размер массива int* ar = malloc(5 * sizeof(int)); // Создание динамического массива на 5 элементов. Необходимо далее их инициализировать. printf("&ar = %p, ar = %p \n", &ar, ar); for(int i = 0; i < AR_SIZE; i++)< ar[i] = i * i; // Инициализация массива printf("&ar[%d] = %p, ar[%d] = %d \n", i, &ar[i], i, ar[i]); // Использование массива >free(ar); // Освобождение памяти > 

Двумерный динамический массив

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

 #include //printf() #include // malloc(), free() int main() < const int ROW = 2, COL = 4; // Размер массива рядов, столбцов char **ar = malloc(ROW * sizeof (char *)); // Выделение памяти под указатели на строки (ряды) printf("&ar = %p, ar = %p \n", &ar, ar); // Адрес массива for (int i = 0; i < ROW; i++)< // Перебираем строки (ряды) ar[i] = malloc(COL * sizeof(char)); // Выделение памяти под хранение столбцов printf("&ar[%d] = %p, ar[%d] = %p \n", i, &ar[i], i, ar[i]); // Адреса массивов for(int n = 0; n < COL; n++)< // Перебираем элементы столбца(ов) ar[i][n] = i * COL + n; // Заполняем значения массива возростающими значениями >> for (int i = 0; i < ROW; i++)< // Перебираем строки (ряды) for(int n = 0; n < COL; n++)< // Перебираем элементы столбца(ов) printf("&ar[%d][%d] = %p, ar[%d][%d] = %d \n", i, n, &ar[i][n], i, n, ar[i][n]); // Выводим значения массива >> for (int i = 0; i < ROW; i++)< // Перебираем строки (ряды) free(ar[i]); // Освобождение памяти >free(ar); // Освобождение памяти > 

Понравилась страница? Не забудь сохранить и поделиться!

Связанные темы

array - Статические и динамические массивы в языке Си. Описание и примеры кода.
free -
malloc - #include void * malloc(size_t size_in_bytes); // size_in_bytes - Размер памяти требуемый для выделения, в байтах. // Возвращаемое значение - указатель на начало блока непрерывной выделенной памяти. Или ноль в случае неудачи.
memccpy - memccpy() функция языка Си. Копирование блока памяти с поиском символа.
memcpy - memcpy() функция языка Си. Копирование области памяти.
memmove - memmove() функция языка Си. Перемещение областей памяти.
restrict - restrict ключевое слово языка Си.
sizeof - sizeof оператор в языке Си. Узнать размер переменной.

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

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