Как написать динамический массив на си

 

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

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

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

int *p; // указатель на тип int

Начальный адрес статического массива определяется компилятором в момент его объявления и не может быть изменен.

Для динамического массива начальный адрес присваивается объявленному указателю на массив в процессе выполнения программы.

Стандартные функции динамического выделения памяти

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

Функции динамического распределения памяти:

void* malloc(РазмерМассиваВБайтах);
void* calloc(ЧислоЭлементов, РазмерЭлементаВБайтах);

Для использования функций динамического распределения памяти необходимо подключение библиотеки <malloc.h>:

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

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

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

Память, динамически выделенная с использованием функций calloc(), malloc(), может быть освобождена с использованием функции

«Правилом хорошего тона» в программировании является освобождение динамически выделенной памяти в случае отсутствия ее дальнейшего использования. Однако если динамически выделенная память не освобождается явным образом, она будет освобождена по завершении выполнения программы.

Динамическое выделение памяти для одномерных массивов

Форма обращения к элементам массива с помощью указателей имеет следующий вид:

int a[10], *p; // описываем статический массив и указатель
int b;
p = a; // присваиваем указателю начальный адрес массива
… // ввод элементов массива
b = *p; // b = a[0];
b = *(p+i) // b = a[i];

Пример на Си: Организация динамического одномерного массива и ввод его элементов.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
int main()
{
  int *a;  // указатель на массив
  int i, n;
  system(«chcp 1251»);
  system(«cls»);
  printf(«Введите размер массива: «);
  scanf(«%d», &n);
  // Выделение памяти
  a = (int*)malloc(n * sizeof(int));
  // Ввод элементов массива
  for (i = 0; i<n; i++)
  {
    printf(«a[%d] = «, i);
    scanf(«%d», &a[i]);
  }
  // Вывод элементов массива
  for (i = 0; i<n; i++)
    printf(«%d «, a[i]);
  free(a);
  getchar();   getchar();
  return 0;
}

Результат выполнения программы:
Динамическое выделение памяти

Динамическое выделение памяти для двумерных массивов

Пусть требуется разместить в динамической памяти матрицу, содержащую n строк и m столбцов. Двумерная матрица будет располагаться в оперативной памяти в форме ленты, состоящей из элементов строк. При этом индекс любого элемента двумерной матрицы можно получить по формуле

index = i*m+j;

где i — номер текущей строки; j — номер текущего столбца.

Рассмотрим матрицу 3×4 (см. рис.)
Матрица 3х4
Индекс выделенного элемента определится как

index = 1*4+2=6

 
Объем памяти, требуемый для размещения двумерного массива, определится как

n·m·(размер элемента)

Однако поскольку при таком объявлении компилятору явно не указывается количество элементов в строке и столбце двумерного массива, традиционное обращение к элементу путем указания индекса строки и индекса столбца является некорректным:

a[i][j] — некорректно.

Правильное обращение к элементу с использованием указателя будет выглядеть как

*(p+i*m+j),
где

  • p — указатель на массив,
  • m — количество столбцов,
  • i — индекс строки,
  • j — индекс столбца.

Пример на Си Ввод и вывод значений динамического двумерного массива

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
int main()
{
  int *a;  // указатель на массив
  int i, j, n, m;
  system(«chcp 1251»);
  system(«cls»);
  printf(«Введите количество строк: «);
  scanf(«%d», &n);
  printf(«Введите количество столбцов: «);
  scanf(«%d», &m);
  // Выделение памяти
  a = (int*)malloc(n*m * sizeof(int));
  // Ввод элементов массива
  for (i = 0; i<n; i++)  // цикл по строкам
  {
    for (j = 0; j<m; j++)  // цикл по столбцам
    {
      printf(«a[%d][%d] = «, i, j);
      scanf(«%d», (a + i*m + j));
    }
  }
  // Вывод элементов массива
  for (i = 0; i<n; i++)  // цикл по строкам
  {
    for (j = 0; j<m; j++)  // цикл по столбцам
    {
      printf(«%5d «, *(a + i*m + j)); // 5 знакомест под элемент массива
    }
    printf(«n»);
  }
  free(a);
  getchar();   getchar();
  return 0;
}

Результат выполнения
Ввод и вывод значений динамического двумерного массива

Возможен также другой способ динамического выделения памяти под двумерный массив — с использованием массива указателей. Для этого необходимо:

  • выделить блок оперативной памяти под массив указателей;
  • выделить блоки оперативной памяти под одномерные массивы, представляющие собой строки искомой матрицы;
  • записать адреса строк в массив указателей.

Динамическое выделение памяти под двумерный массив

Графически такой способ выделения памяти можно представить следующим образом.
Выделение памяти под двумерный массив - графическое представление
При таком способе выделения памяти компилятору явно указано количество строк и количество столбцов в массиве.
Пример на Си

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
int main()
{
  int **a;  // указатель на указатель на строку элементов
  int i, j, n, m;
  system(«chcp 1251»);
  system(«cls»);
  printf(«Введите количество строк: «);
  scanf(«%d», &n);
  printf(«Введите количество столбцов: «);
  scanf(«%d», &m);
  // Выделение памяти под указатели на строки
  a = (int**)malloc(n * sizeof(int*));
  // Ввод элементов массива
  for (i = 0; i<n; i++)  // цикл по строкам
  {
    // Выделение памяти под хранение строк
    a[i] = (int*)malloc(m * sizeof(int));
    for (j = 0; j<m; j++)  // цикл по столбцам
    {
      printf(«a[%d][%d] = «, i, j);
      scanf(«%d», &a[i][j]);
    }
  }
  // Вывод элементов массива
  for (i = 0; i < n; i++)  // цикл по строкам
  {
    for (j = 0; j < m; j++)  // цикл по столбцам
    {
      printf(«%5d «, a[i][j]); // 5 знакомест под элемент массива
    }
    printf(«n»);
  }
  // Очистка памяти
  for (i = 0; i < n; i++)  // цикл по строкам
    free(a[i]);   // освобождение памяти под строку
  free(a);
  getchar();   getchar();
  return 0;
}

Результат выполнения программы аналогичен предыдущему случаю.

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

Для размещения в оперативной памяти матрицы со строками разной длины необходимо ввести дополнительный массив m, в котором будут храниться размеры строк.

Пример на Си: Свободный массив

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
  int **a;
  int i, j, n, *m;
  system(«chcp 1251»);
  system(«cls»);
  printf(«Введите количество строк: «);
  scanf(«%d», &n);
  a = (int**)malloc(n * sizeof(int*));
  m = (int*)malloc(n * sizeof(int)); // массив кол-ва элеменов в строках массива a
                     // Ввод элементов массива
  for (i = 0; i<n; i++) 
  {
    printf(«Введите количество столбцов строки %d: «, i);
    scanf(«%d», &m[i]);
    a[i] = (int*)malloc(m[i] * sizeof(int));
    for (j = 0; j<m[i]; j++) {
      printf(«a[%d][%d]= «, i, j);
      scanf(«%d», &a[i][j]);
    }
  }
  // Вывод элементов массива
  for (i = 0; i<n; i++) 
  {
    for (j = 0; j<m[i]; j++) 
    {
      printf(«%3d «, a[i][j]);
    }
    printf(«n»);
  }
  // Освобождение памяти
  for (i = 0; i < n; i++)
  {
    free(a[i]);
  }
  free(a);
  free(m);
  getchar(); getchar();
  return 0;
}

Результат выполнения
Свободный массив

Перераспределение памяти

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

  • Выделить блок памяти размерности n+1 (на 1 больше текущего размера массива)
  • Скопировать все значения, хранящиеся в массиве во вновь выделенную область памяти
  • Освободить память, выделенную ранее для хранения массива
  • Переместить указатель начала массива на начало вновь выделенной области памяти
  • Дополнить массив последним введенным значением

Все перечисленные выше действия (кроме последнего) выполняет функция

void* realloc (void* ptr, size_t size);

  • ptr — указатель на блок ранее выделенной памяти функциями malloc(), calloc() или realloc() для перемещения в новое место. Если этот параметр равен NULL, то выделяется новый блок, и функция возвращает на него указатель.
  • size — новый размер, в байтах, выделяемого блока памяти. Если size = 0, ранее выделенная память освобождается и функция возвращает нулевой указатель, ptr устанавливается в NULL.

Размер блока памяти, на который ссылается параметр ptr изменяется на size байтов. Блок памяти может уменьшаться или увеличиваться в размере. Содержимое блока памяти сохраняется даже если новый блок имеет меньший размер, чем старый. Но отбрасываются те данные, которые выходят за рамки нового блока. Если новый блок памяти больше старого, то содержимое вновь выделенной памяти будет неопределенным.

 
Пример на Си Выделить память для ввода массива целых чисел. После ввода каждого значения задавать вопрос о вводе следующего значения.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <malloc.h>
int main()
{
  int *a = NULL, i = 0, elem;
  char c;
  do {
    printf(«a[%d]= «, i);
    scanf(«%d», &elem);
    a = (int*)realloc(a, (i + 1) * sizeof(int));
    a[i] = elem;
    i++;
    getchar();
    printf(«Next (y/n)? «);
    c = getchar();
  } while (c == ‘y’);
  for (int j = 0; j < i; j++)
    printf(«%d «, a[j]);
  if (i>2) i -= 2;
  printf(«n»);
  a = (int*)realloc(a, i * sizeof(int)); // уменьшение размера массива на 2
  for (int j = 0; j < i; j++)
    printf(«%d «, a[j]);
  getchar(); getchar();
  return 0;
}

Результат выполнения
realloc()

Назад: Язык Си

  • Понятие массива
  • Статический массив
  • Динамический массив
  • Строка как массив

Понятие массива

Массив — это именнованная последовательность элементов в памяти. Особенностью массива является, что элементы хранятся в памяти друг за другом, причем подряд. Доступ до элементов идет по индексу элемента. Индексация всегда начинается с нуля.

0 1 2 3 4
12 60 25 5 19

Это иллюстрация ячеек памяти массива на 5 элементов.

Массив может быть объявлен статически и динамически.

  1. При статическом объявлении память под массив выделяется в стеке,
    массив является локальной переменной и будет уничтожен по правилам
    локальных переменных.

  2. При динамическом объявлении память выделяется в куче программы —
    ее еще называют динамической памятью. Управление ею лежит полностью
    на совести самого программиста. Доступ до массива осуществляется
    через указатель.

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

Объявить и проинициализировать такой массив можно следюущим образом:

  • int — тип данных элементов массива. Это будет массив, хранящий целые числа. Тип данных элементов может быть любой.
  • array1 — имя переменной. Вы сами его выбираете
  • [n], где n — кол-во элементов в массиве. Причем n должно быть константой так или иначе.
   int n = 10;
   const int m = 10;
   int array01[m];
   int array02[10];
 
   int array03[n]; - А вот так не получится.

Не получится, потому что компилятор не может быть на момент исполнения предсказать, какое число будет вместо n. А значит и выделить память в стеке небезопасно, а значит… Не пошел бы программист лесом. :)

Обращение к ячейке памяти происходит с помощью оператора []
Как параметр этот параметр принимает СМЕЩЕНИЕ относительно начала массива.

array1[0] = 12;
array1[1] = 60;
array1[2] = 25;
array1[3] = 5;
array1[4] = 19;

Догадливые уже могли понять:
имя массива является адресом начала массива в памяти.

0 1 2 3 4
12 60 25 5 19

Alt  Texr

Переменная array1 хранит именно АДРЕС самой первой ячейки массива (см. раздел указатели, в котором пояснено, что, например, 1 число типа int занимает не 1 ячейку памяти, а 4, потому что 1 ячейка = 1 байт).
Alt  Texr

array1 — локальная переменная. А значит хранится в стеке. array1 хранит адрес ячейки в стеке.

cout << "array1 = " << array1 << endl;

Естественно, что массивом можно управлять и с помощью цикла:

for (int i = 0; i < 5; i++) cout << "array1[" << i << "] = " << array1[i] << endl;

Если элементы массива извесстны заранее, то объявление и инициализацию можно объединить:

float array2[3] = { 1.2, 3.6, 20.5 };

А еще можно так:

double array3[] = { 1, 2, 3 }; 

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

А теперь задумаемся вот над каким вопросом: если имя массива — это адрес самой первой ячейки в самом массиве, иными словами: имя массива — это указатель на начало массива. Но как же оператор [] по индексу элемента извлекает данные из массива? Если вы хорошо усвоили тему указателей и операций над указателями, то вы легко найдете ответ на этот вопрос.

Допустим, нам нужен самый первый элемент массива. Сам указатель массива уже хранит адрес этого элемента, нам достаточно разыменовать указатель на массив и мы получим искомый элемент.

cout << "array1[0] = " <<*array1 << endl

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

  cout << "array1[1] = " << *(array1 + 1) << endl;

Заметили? Индекс, который передается в оператор [] — это СМЕЩЕНИЕ элемента относительно начала массива! Т.е. индексация с нуля взялась не на пустом месте, это обусловлено косвенной адресацией через указатели!

Вау!!! Магия. ^_^

Поэтому, на самом деле за строчкой

cout << "array1[0] = " <<*array1 << endl;

скрывается

cout << "array1[0] = " <<*(array1 + 0) << endl;

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

Так что вывести на экран можно двумя способами:

 for (int i = 0; i < 5; i++) cout << "array1[" << i << "] = " << array1[i] << endl;

или

for (int i = 0; i < 5; i++) cout << "array1[" << i << "] = " << *(array1 + i) << endl;

Так что оператор [] это опять же пресловутый синтаксический «сахар» для программиста, и этот оператор просто прибавляет к адресу начала массива смещение и разыменовывает полученный указатель.

Естественно, никто не мешает использовать полную запись доступа до элемента массива не только для чтения элемента из массива, но и для записи:

 *(array1 + 2) = 2;
 cout << array1[2] << endl;

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

Итак, со статическими массивами разобрались, теперь пора перейти к динамическим.

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

  • Статический — в стеке.

  • Динамический — в куче.

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

    Суть динамического массива в том, что программист сам, как хочет управляет памятью. Это дает безграничную гибкость в написании кода, но и безграничные возможности выстрелить себе в ногу… Sad, but true.

    Раз абсолютно все находится в ведении программиста, то даже выделять, высвобождать и следить за целостностью данных (их сохранностью) должны мы сами.

    Есть две основные команды по работе с динамической памятью (кучей):

  1. Выделить память.
  2. Высвободить память.

Выделение памяти.

Для выделения памяти есть оператор new. Оператор new резервирует память под хранение данных. Не трудно догадаться, что память в компьютере либо есть, либо ее нет. Поэтому сказать, что new «создает» память — неверно, он же не добавит вам новые планки оперативной память (а жаль, было бы круто). Память была еще до new. Более того, память под использование программой выделяется операционной системой в момент запуска программы: программа точно знает «куда можно ходить» ну или какую память можно, а какую нельзя использовать.

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

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

Скажите, вам понравится, если вы сохраните какие-то данные по адресу в куче, а потом программа без вашего ведома возьмет и поменяет эти данные? Что получится в итоге — непредсказуемо, а значит опасно.

Чтобы такого не происходило, компиляторами продуман механизм «резервирования» памяти.

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

Вот что делает оператор new. Он резервирует определенный объем памяти в куче.

Кстати, кстати.

Оператор new это опять же синтаксический «сахар». На самом деле он внутри своей реализации вызывает ф-цию выделения памяти malloc(size_to_allocate);
malloc расшифровывается как memory allocate (memory — память, allocate — выделить, зарезервировать).

В качестве параметра malloc принимает кол-во байт, которое нужно выделить.

new float; преобразуется компилятором в malloc( sizeof(float) );

А как же работает оператор new и ф-ция malloc? Как она находит место, где можно зарезервировать память? Очень просто. Поиск начинается с адреса начала кучи, находится НЕПРЕРЫВНЫЙ и свободный участок в памяти, и уже он резервируется.

С++ тупой, поэтому работает так. Это на самом деле не очень оптимально. Но вот такой это простой язык. Языки с виртуальными машинами типа c# и java работают с памятью по более сложным алгоритмам.

float* ptr1 = new float; // зарезервировали sizeof(float) байт памяти в куче
      *ptr1 = 1.5;
      cout << "*ptr1 = " << *ptr1 << endl;

Итак, что тут произошло.

Мы создали указатель. Затем с помощью оператора new выделили память под ячейку памяти типа данных float (4 или сколько там байт — всегда можно запустить sizeof(float), чтобы узнать точно). И адрес выделенной памяти сохранился в указатель ptr1.

Что важно:

ptr1 — это локальная переменная и будет храниться в стеке.
оператор new — выделил память в куче, не в стеке.

Alt  Texr

Как любая локальная переменная ptr1 будет уничтожена, когда завершится ее блок кода. А вот память в куче… Нет. Потому что вся ответственность за нее лежит на программисте. Как хочешь так и разбирайся.

Выглядеть это будет так.

Alt  Texr

Стек избавился от своей переменной ptr1, а вот зарезервированная область в памяти так и осталась зарезервированной. Программа не может использовать эту память повторно. Да и мы больше не имеем доступа до этой памяти. Мы же потеряли адрес, где эта память хранится.

Конечно, после завершения программы, вся память выделенная программе ОС, в том числе и куча, будет высвобождена, очищена и пожертвована другому процессу в ОС. Но вы правда хотите, чтобы ваша программа оставляла за собой неиспользуемый мусор, засоряла память и т.д.?

Вам нравится, когда программы жрут по 1Гб, 2Гб, 3Гб памяти причем непонятно зачем, когда они ничего такого мега сложного не делают? Конечно, не нравится. Поэтому не надо мусорить там, где вы работаете, т.е. в памяти вашей программы.

Высвобождение памяти.

Чтобы иметь возможно подчищать за собой больше ненужную нам память в куче был введен оператор delete.

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

Кстати, delete также как и new на самом деле запускает ф-цию высвобождения памяти free, которая как параметр принимает указатель.

delete pointer; эквивалентно free(pointer);

Правило обращения с динамической памятью достаточно простое: Каждому new даешь свой delete. Написали где-то new? Значит, нужно где-то эту память высвободить. Никак иначе.

Ок, если мы передаем указатель, то это адрес всего лишь 1 байта в памяти, но даже выше мы зарезервировали память под float, а это всяко больше 1 байта!

Получается, освободится только 1 байт? А потом нам в цикле нужно бегать и высвобождать все остальные ячейки? Без паники. Разработчики компилятора о вас уже позаботились.

Информация для продвинутых.

На самом деле при резервировании памяти, вам для использования выдается указатель не на первую зарезервированную ячейку, а на следующую… Потому что в самой первой ячейке хранится так называемые метаданные о памяти. В этих метаданных хранится объем выделенной памяти, тип данных, под который изначально память выделялась и некоторая еще информация.

Alt  Texr

Кстати, метаданные могут занимать не 1 байт памяти, но это значение всегда фиксировано у компилятора, он знает сколько метаданных ему нужно для работы. Но для простоты сейчас используется, чтометаданные занимают 1 байт памяти.

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

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

После высвобождения памяти, флаг «занято» с памяти снимается. Соответственно, если мы попытаемся обратиться к такой памяти еще раз уже после ее освобождения, программа вывалится в ужасе с ошибкой «Access violation», что в вольном переводе означает «у меня нет прав сюда лезть».

Но скажем так, С++ далеко не всегда настолько усожлив, чтобы сообщить нам, что мы полезли куда-то не туда. Обычно предупреждающей таблички нет, мы залезаем на чужую территорию и кто-нибудь потом, когда мы уже не ждем стреляет нам в ногу… Или голову, в зависимости от критичности ошибки в коде.

// cout << "*ptr1 = " << *ptr1 << endl; // Тут будет ошибка времени исполнения

Ладно, ладно. Мы немного отвлеклись, но это было необходимо.

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

 int* array1 = new int[5];

Итак, начнем разбирать с самого легкого. Правая половина выражения.

new int[5];

Это синтаксис выделения памяти под массив, элеметами которого являются int и таких чисел 5 штук.

Через malloc записывается так: malloc( sizeof(int) * 5 );

Таким образом выделится 20 байт памяти.

Как и положено после резервирования памяти, оператор new вернет как результат указатель на НАЧАЛО выделенной области памяти.

Все то же самое было и со статическими массивами, там тоже имя массива являлось адресом начала массива. Только хранился он в стеке, а тут в куче.

Короче, вернулся нам адрес первого элемента в массиве, он же начало всего массива.

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

Так что слева нам и пришлось написать int* array1 — указатель на тип данных int.

Ну а в памяти все это безобразие выглядеть будет так:
Alt  Texr

В плане работы с массивом нет никакой разницы ни по синтаксису, ни по логике по сравнению со статическим массивом.

  array1[0] = 11;
  array1[1] = 12;
  array1[2] = 13;
  array1[3] = 14;
  array1[4] = 15;

  for (int i = 0; i < 5; i++) cout << "array1[" << i << "] = " << array1[i] << " "; 

// Не забываем высвободить память

  int* ptr = array1;
  delete[] array1;

«Что за фигня?» спросите вы, увидев delete[], «зачем у delete написан оператор []«

Если кратко, то delete[] должен быть вызван, если выделение памяти было через new[], в частности для массивов.

delete соответственно вызывается, если память выделялась с помощью new.

Сейчас придется вам поверить мне на слово:

Для массивов из простых типов данных, вы не почувствуете разницу между delete и delete[], работать при удалении массива они будут одинаково. Но вот дальше, когда будут изучаться пользовательские типы данных, классы, там путаница с delete и delete[] может вызвать ошибки времени исполнения и повреждение памяти.

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

Пример, когда вызов delete для массива объектов повредит память будет в разделе «жизненный цикл объектов».

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

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

Сохранить эту информацию можно 3 способами:

  1. Несколько небольших массивов.
  2. Один большой массив.
  3. Двумерный массив.

Рассмотрим каждый способ по порядку;

  1. Несколько небольших массивов:
    Для каждого студента будем создавать свой собственный массив на 2 оценки.
  int arrayStudent1[2];
// Первый студент пусть будет отличником
  arrayStudent1[0] = 5;
  arrayStudent1[1] = 5;
  
  int arrayStudent2[2];
// Второй - двоечником
  arrayStudent2[2] = 2;
  arrayStudent2[3] = 2;
  
  int arrayStudent3[2];
// Третий - перебивающийся
  arrayStudent3[4] = 4;
  arrayStudent3[5] = 3;

Вроде, выглядит неплохо. Легко читается, код понятен. Тогда что не так? Универсальность. Допустим, мы не знаем до начала программы, сколько будет студентов. Пользователь только после запуска программы введет число студентов в группе. Сколько тогда создать переменных? 10? 50? 100? 1000? И что? Все их объявлять? А как обращаться к такому непонятному числу переменных? В общем, это не то чтобы невозможно. Но такие извращения в такой задаче излишни. Все должно быть намного проще.

  1. Один массив для всех данных.
    Чтобы сохранить оценки для 3 человек понадобится 6 ячеек памяти. Ячейки 0 и 1 будут оценками для студента 0 (индексация приведена с 0, как в массиве); Ячейки 2 и 3 — оценки 1 студента; Ячейки 4 и 5 — оценки 2 студента.
  int array1[6];
     
  array1[0] = 5;
  array1[1] = 5;
     
  array1[2] = 2;
  array1[3] = 2;

  array1[4] = 4;
  array1[5] = 3;

Итак у нас есть 3 студента у каждого по 2 оценки. Все они хранятся в одном массиве. Мы решили проблему с универсальностью (допустим, мы как-то обошли ограничение, что число элементов в массиве должно быть только константой).

Тогда, допустим, что мы можем создавать массив для n студентов и m дисциплин. Размер массива для хранения всего этого дела будет равен n * m.

Но как обратиться, скажем, к оценке по первой дисциплине у второго студента? Если индексация идет с 0, то нам нужен студент с индексом 1 и дисциплина с индексом 0.

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

Далее несложно вывести формулу индекса нужной оценки в массиве: i * m + j = 1 * 2 + 0 = 2

где i — индекс искомого студента, а j — индекс нужной дисциплины.

Такую формулу можно запрограммировать. Пользователь вводит номер студента, номер дисциплины, а программа, рассчитав формулу, выдаст нужную оценку. Профит.

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

Поэтому во всех языках программирования есть понятие многомерных массивов. В данном случае нам будет достаточно двумерного массива.

  1. Двумерный массив.
  int array[3][2]; // 3 студента по 2-м дисциплинам
// Оценки 0 студента
  array[0][0] = 5; // 0 дисциплина
  array[0][1] = 5; // 1 дисциплина
// Оценки 1 студента
  array[1][0] = 2; // 0 дисциплина
  array[1][1] = 2; // 1 дисциплина
// Оценки 2 студента
  array[2][0] = 4; // 0 дисциплина
  array[2][1] = 3; // 1 дисциплина

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

Как же это работает.

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

int array[3][2]; преобразуется компилятором в строчку int array[3 * 2];

А строчка array[i][j]; преобразуется в array[i * m + j];

На самом деле, как вы помните, даже оператор [] — это тоже сахар и на самом деле это выглядит еще немного по-другому: *(array + i * m + j);

И это истинная запись такой простой команды: array[i][j];

Ну, хорошо, с использованием двумерных массивов разобрались, но как же они хранятся в памяти. Ответ на этот вопрос уже звучал: двумерный массив преобразуется компилятором в длинный двумерный массив. А значит массив int array[3][2]; в памяти выглядит так:

Alt  Texr

Еще раз:

Массив любой размерности хранится в памяти ЛИНЕЙНО. Память в принципе линейна. Ячейки идут друг за другом. Перемещаться в памяти можно либо вперед, либо назад. Никаких «верх», «вниз» в памяти быть не может по определению.

Да, двумерный массив по смыслу и использованию похож на матрицу или таблицу.

Но

НИКАКИХ ТАБЛИЦ В ПАМЯТИ БЫТЬ НЕ МОЖЕТ.

Память линейна. Все данные в массивах хранятся подряд.

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

Разбирая двумерные массивы, было упомянуто, что в программировании массивы многомерны. И на самом деле «мерность» массива ничем не ограничена. Размерность может быть 3, 4, 5 — хоть сколько.

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

Допустим, у нас есть 3 группы студентов в каждой по 3 человека, у каждого по 2 оценки.

   int array[3][3][2];
// Заполним массив (дайте мне помечтать и представить, что все студенты - отличники):
   for (int i = 0; i < 3; i++)
   for (int j = 0; j < 3; j++)
   for (int k = 0; k < 2; k++)
   array[i][j][k] = 5;

Таким образом в памяти будет создан ОДИН линейный массив на 3 * 3 * 2 = 18 элемента.

Ну или компилятор запишет это так: int array[3 * 3 * 2];

А в памяти это будет выглядеть так:

Alt  Texr

Задание для упоротых:

попробуйте записать array[i][j][k] вообще без использования оператора [], как это демонстировалось в разделе «Двумерные массивы» Подсказка: расписывайте не сразу все 3 применения [], а начините с первых двух [], т.к. их понятно как расписать.

Или нарисуйте весь массив линейно и возьмите несколько индексов для примера (это тоже демонстрировалось в разделе про двумерные массивы) — и рассчитайте формулу, по которой вычисляется индекс.

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

Строка как массив

Строка — это массив символов, оканчивающийся символом конца строки (нулевым байтом , т.е. просто нулем)

А значит, как в любом массиве, переменная «строки» — это адрес на первый символ в строке. Остальные символы располагаются сразу за первым.

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

Например, строка «Hi» в памяти занимает на самом деле 3 байта: по 1 байту для каждого символа = 2 байта + 1 нулевой байт, как символ конца строки. Т.е. в памяти последовательность байт выглядит так:

Наглядный пример

В памяти все символы хранятся как код по таблице ASCII/Unicode/другая в зависимости от кодировки:

72 — это код символа ‘H’ в таблице ASCII

105 — это код символа ‘i’ в таблице ASCII

0 — это признак конца строки

Т.к. в памяти все данные хранятся только в числовом виде, то все символы кодируются через их коды. Таблицы кодировок бывают разными. Основные это ASCII и Unicode.

ASCII хранит символы в диапазоне от 0 до 255, поэтому содержит не так уж много символов: латинский алфавит, цифры и некоторое кол-во спец.символов. Поэтому для кодирования этой таблицы достаточно объема памяти в 1 байт. Это и есть тип данных char.

Unicode же может кодировать символы 2, 4 или 8 байтами. Поэтому содержит огромное кол-во символов. Все региональные алфавитные системы, включая арабские, иероглифичные, греческие и т.д. и т.д.

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

Например,

char str0[7] = "qwerty";
cout << str0 << endl;

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

Думаете программа будет выводить ровно 6 или 7 байт, по кол-ву выведенной памяти? Давайте проверим.

char str0[7] = "qwerty";
cout << str0 << endl;
str0[6] = '!'; // Убираем нулевой байт из конца строки
cout << str0 << endl;

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

И теперь вывод на экран строки str0 будет примерно следующий:

Все что идет после символа ! (вместо которого был нулевой байт) — это «мусор». Случайные цифры, которые были преобразованы в символы, потому что программы интерпретирует любые байты от адреса начала строки и до нулевого байта как символы.

Остановилась программа только тогда, когда нашла в памяти «случайный» ноль. В моем случае до этого момента вывелось 12 лишних символов.

По принципу «до нулевого байта» работают абсолютно все строковые ф-ции: strcpy, strcmp, strcat и другие.

Как вы видите в С++ работать со строками не так уж просто. Нужно всегда держать в голове состояние памяти и случайно его не испортить.

Чтобы облегчить эту задачи существует тип данных string, который по сути является оберткой вокруг обычного char*. У него есть функции получения длины, перегружены операторы для объединения строк, есть ф-ции поиска подстроки и много-много других удобных вещей.

Однако, как любая обертка, она не только упрощает вашу работу, но и не позволяет самостоятельно разобраться, как идет работа со строками.

Пока вы самостоятельно не освоите и не будете чувствовать себя уверенно с типом данных char* не используйте тип данных string. Это позволит вам отточить навыки работы со строками, указателями и памятью.

Статическая и динамическая строка

Также как и массив, строку можно создавать статическим путем:

или данимическим:

char* fio = new char[100];

Статическая строка

Пример создания статической строки:

Создадим статическую строку и считаем в нее значение.

char str1[5];
cin >> str1;
cout << endl << str1 << endl;

Вроде бы, все хорошо. Однако даже в таком коде уже есть потенциальная ошибка.

Для строки str1 было выделено всего 5 байт. Если ввод будет, допустим, :), то будет все хорошо. Из буфера консоли в строку запишется 3 байта: : ) 0. Последний нулевой байт нужен как символ конца строки.

Поэтому после строчки cout << str1; выведется как и положено :), хотя в строке и зарезервировано 5 байт (нулевой байт и все дела).

Однако, давайте попробуем считать с консоли, например, строку abdce. В ней же всего 5 символов. Как и положено. Ну ок, добавится 6 куда-нибудь после 5 наших. Все же должно быть хорошо? А вот фиг вам!

Будет ошибка: Run-Time Check Failure #2 - Stack around the variable 'str1' was corrupted.
По-русски это будет звучать примерно как (крайне не рекомендую пользоваться русской версией IDE, кстати): Ошибка времени выполнения #2: Стек вокруг переменной str1 поврежден.

Как так? Да очень просто. Мы выделили 5 байт под строку. Вместе с нулевым байтом у нас из буфера консоли пришло 6 байт. 6, нулевой байт, вышел за границы массива. С++, конечно, тупой, но состояние стека и кучи во время выполнения программы он все-таки отслеживает.

Во время выполнения выполнения программа ожидала, что состояние будет одно (например, что переменная str1 занимает всего 5 байт), а внезапно 6 байт после начала строки тоже оказался изменен! И программа естественно такая: Что за нафиг?! И выдала вам ошибку.

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

char str1[5];
cin.getline(str1, 5);
cout << str1 << endl;

В этом случае даже если пользователь введет больше 4 символов (5й-то нам нужен под нулевой байт), например, qwerty, то все 6 символов запишутся в буфер консоли, однако команда cin.getline(str1, 5); из буфера извлечет и запишет в переменную str1 только 5 байт (4 символа + нулевой байт).

И выведется на экран только: qwer. Ошибки уже не будет.

Статические строки (их еще называют строки фиксированной, постоянной длины) подходят для хранения только значений определенной длины!

Ну хорошо, почему нельзя хранить больше, мы разобрались — больше просто не влезет, будет ошибка, но почему нельзя, скажем, в 10 байтах символов хранить всего 2 символа?

С++ позиционируется как язык с высокой оптимизацией не только скорости работы, но и использования памяти. Каждый байт при резервировании в памяти должен быть обоснован. Если вы создаете строку на 10 байтов, все эти 10 байт «заняты», компилятор не может их использовать. И если вы будете хранить в этих 10 байтах только 2 символа, то остальные 8 будут простаивать.

Хорошо 8 «потерянных» байт это не страшно. Однако привычка — страшная вещь. Не научившись сразу бережно относиться в памяти, вы рискуете всегда «жрать память» как не в себя.

На эту тему даже есть анекдот:

Аккуратный стол, белая тарелочка на нем, на тарелочке оперативка. Сидит С++, аккуратно ножом отрезает кусочек и кушает.
И ТУТ В СТОЛОВУЮ ВЛАМЫВАЕТСЯ СВИНЬЯ И СЖИРАЕТ ВСЮ ОПЕРАТИВКУ СРАЗУ — это Java.
…С++ от испуга выплевывает откушенный кусочек и кричит: Segmentation fault!!!

Поэтому не нужно так. Храните в статических строках данные, которые не будут менять своей длины: номера телефонов, номера паспортов и т.д.

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

Динамическая строка

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

Алгоритм:

1. Считать строку в "большой" буфер
2. Определить, сколько в буфере "полезных" данных
3. Создать строку, выделив память ровно под "полезные" данные
4. Скопировать данные из буфера в строку
5. Уничтожить буфер, чтобы не занимал память
6. Профит

А теперь смысл:

Мы не знаем, сколько символов введет пользователь. Может быть, всего 10, а может быть и 100500. Но если он введет всего 10, мы не хотим иметь зарезервированную, но не используемую память. Если он введет 100500 символов, то мы не хотим случайно их потерять, потому что считываем из консоли только определенное кол-во символов.

Для этого и создается буфер с запасом памяти, чтобы точно хватило. И из консоли данные считываются в него. Если буфер у нас на 255 символов, а пользователь введет всего 10 символов, то нулевой байт даст нам знать, что «полезных» символов будет всего 10.

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

А теперь как это может выглядеть в коде:

char* buf = new char[255];
cin.getline(buf, 255);
char* str2 = new char[strlen(buf)];
strcpy(str2, buf);
delete[] buf;
cout << str2;

Пошаговое пояснение кода:

1. Создаем буфер с "запасом", чтобы хватило: char* buf = new char[255];
2. Читаем данные в него: cin.getline(buf, 255);
3. Выделяем ровно столько памяти, сколько "полезных" символов в буфере: char* str2 = new char[strlen(buf)];
4. strlen - ф-ция определения длины строки, которая считает символы только до нулевого байта.
5. Если пользователь ввел всего 10 символов, то strlen и вернет 10.
6. Копируем реальную строку из буфера в переменную str2: strcpy(str2, buf);
7. Удаляем ненужный больше буфер: delete[] buf;

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

char* readString()
{
   char buf[255];
   cin.getline(buf, 255);
   char* str = new char[strlen(buf)]; 
   strcpy(str, buf);
   return str;
}

В данном случае нет необходимости создавать буфер, как динамическую строку, чтобы затем удалить ее вручную. Т.к. это ф-ция, а переменная buf — локальная переменная, она уничтожится сама, когда закончится блок кода, в котором она была объявлена, т.е. сама ф-ция.

Теперь считывать строку с экрана будет очень просто:

char* str3 = readString();
cout << str3 << endl;

Динамический массив в С это массив, у которого количественное значение ячеек задается переменной, а значит, его размер может изменяться. Такие массивы нужны для того, чтобы более эффективно эксплуатировать оперативную память компьютера в С-программах. Напомним, что в статическом массиве количество ячеек задается числовой константой, а не переменной.

Как это работает? Например, вы написали программку, где получаете какие-то данные от пользователя. Вы объявили массив и задали ему фиксированный размер от 0 до 60 ячеек. Но такая программка не будет функциональной, потому что:

  • если пользователь заполнит только 10 элементов массива, тогда будет нерациональное использование памяти, потому что памяти будет выделяться под 60 элементов;

  • если пользователь решит занять 100 элементов массива, тогда у него ничего не получится, так как у нас выделено места только под 60.

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

Динамический массив в С

В языке программирования С есть две связанные операции, которые применяются при выделении компьютерной памяти для программ:

  • «new» — отвечает за выделение памяти из области свободной памяти;

  • «delete» — отвечает за высвобождение выделенной памяти.

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

При помощи оператора «new» можно выделить память под разные типы данных, например:

  • int,

  • float,

  • double,

  • char,

  • и др.

При выделении памяти для динамического массива также принято пользоваться этими операторами. Как происходит создание динамического массива при помощи этих операторов смотрите чуть ниже.

Динамический массив Си: создание

Создание динамического массива в С происходит по следующему шаблону:

<тип данных> *<имя массива> = new <тип переменной> [<количество элементов>], где

  • тип данных может быть разным, однако лучше, чтобы тип данных массива и тип переменной имели одинаковое значение;

  • тип переменной указывает, какой тип информации будет располагаться в элементах массива;

  • количество элементов здесь задается объем будущего массива: если указать «n», тогда количество элементов будет динамическим, а если указать фиксированное число, тогда количество элементов будет соответствовать числу.

Удаляется динамический массив строчкой:

delete [] <имя массива>

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

  • способом инициализации;

  • возможностью вовремя высвободить память.

Динамические массивы могут быть:

  • одномерными;

  • двумерными.

Как создать одномерный динамический массив в С

Как выглядит создание одномерного динамического массива в С в коде? Создаем небольшую программку:

int main() {

  setlocale(0, «»);

  int n;

  cout << «Определите количество чисел, которое вы будете вводить: «;

  cin >> n;

  cout << «Нужно ввести » << n << » чисел: «; 

  int *dynamic_array = new int [n];    // происходит создание динамического массива

    for (int i = 0; i < n; i++) { 

    cin >> dynamic_array[i];  // считываются числа в элементах массива

  }

  cout << «Выводим числа массива в обратном порядке: «;

  for (int i = n — 1 ; i >= 0; i—) {

    cout << dynamic_array[i] << » «;  // выводятся значения каждой ячейки

  }

    cout << endl << «Теперь массив удаляется!»;

  delete [] dynamic_array;  // происходит удаление динамического массива

  return 0;

}

Результат выполнения программы будет следующий:

Определите количество чисел, которое вы будете вводить: 6

Нужно ввести 6 чисел: 3 5 7 9 17 21

Выводим числа массива в обратном порядке: 21 17 9 7 5 3

Теперь массив удаляется!

Как создать двумерный динамический массив в Си

Двумерный динамический массив в С создается по похожему шаблону, что и одномерный:

<тип данных> **<имя массива> = new <тип переменной>* [<количество элементов>]

Однако нужно отметить, что появился еще один оператор «*» перед именем массива и после типа переменной. Также к созданию такого рода массивов подключается цикличный оператор «for».

Как двумерный массив выглядит в коде:

#include <iostream>

 using namespace std;

 int main() {

     setlocale(0, «»);

 int **dynamic_array2 = new int* [6];   // создается двумерный динамический массив

  for (int i = 0; i < 6; i++) {         

    dynamic_array2[i] = new int [i + 1]; 

  }

  for (int i = 0; i < 6; i++) {

    cout << «Необходимо ввести числовые значения» << «(» << i + 1 << «)» << «:»;

    for (int x = 0; x < i + 1; x++) { 

      cin >> dynamic_array2[i][x];

    }

  }

  for (int i = 0; i < 6; i++) {

    int total = 0;

    for (int x = 0; x < i + 1; x++) {

      total += dinamic_array2[i][x];

    }

    cout << «Суммарное значение » << i + 1 << » массивов будет равняться » << total << endl;

  }

  for (int i = 0; i < 6; i++) {

    delete [] dynamic_array2[i];  // удаляется массив

  }

  system(«Пауза»);

  return 0;

}

Этот код создает двумерный массив, потом заполняет его значениями, потом подсчитывает и выводит сумму значений всех массивов. В конце массив удаляется.

Заключение

Динамический массив в С реализуется довольно часто в тех случаях, когда заранее трудно предугадать количество элементов массива или количество памяти для его обработки. Реализация динамического массива не так сложна, и от создания статического массива его отделяет пара собственных «штрихов». Однако функционально динамический массив в Си способен выручить во многих нестандартных ситуациях.

Необходимо прочитать из файла число N, создать массив NxN и полностью инициализировать его 1-ми. Вот как я делаю это на C++:

int **Graf, i, j, N;
 ifstream input("Graf.txt");
 input >> N;

 Graf = new int *[N];
 for(i=0; i<N; i++)
     Graf[i] = new int[N];

 //Обнуляет все элементы массива
 for(i=0; i<N; i++)
     for(j=0; j<N; j++)
         Graf[i][j]=0;
 while(!input.eof())
 {
     input>>i>>j;
     Graf[i][j]=1;
     Graf[j][i]=1;
 }

Как это перевести на C?

задан 28 окт 2013 в 17:46

Adam Shakhabov's user avatar

Adam ShakhabovAdam Shakhabov

3,8176 золотых знаков31 серебряный знак81 бронзовый знак

4

Graf = new int *[N];

Заменить на

Graf = (int**) malloc(N * sizeof(int*));

а

Graf[i] = new int[N];

Заменить на

Graf = (int*) malloc(N * sizeof(int));

Освобождение памяти для такой матрицы будет выглядеть так

for(i = 0;i < N;++i) free(Graf[i]);
free(Graf);

Ну и плюс ко всему нужно подключить stdlib.h

ответ дан 28 окт 2013 в 18:34

cgtalium's user avatar

2

Выстраивать тут jagged array, как вы сделали в своем С++ коде, нет никакой необходимости. В языке С такой массив можно организовать существенно проще. Прочитав предварительно величину N просто делаем

int (*Graf)[N] = malloc(N * sizeof(int [N]));

Все.

После чего с полученным массивом можно работать как обычно

Graf[i][j] = 42;

Освобождение памяти делается единственным вызвом free

free(Graf);

ответ дан 16 мар 2017 в 7:56

AnT stands with Russia's user avatar

Вам понадобятся ссылки и функции типа malloc(). Вот пример базовых операций на одномерном массиве (автор casablanca @ SO):

typedef struct {
  int *array;
  size_t used;
  size_t size;
} Array;

void initArray(Array *a, size_t initialSize) {
  a->array = (int *)malloc(initialSize * sizeof(int));
  a->used = 0;
  a->size = initialSize;
}

void insertArray(Array *a, int element) {
  if (a->used == a->size) {
    a->size *= 2;
    a->array = (int *)realloc(a->array, a->size * sizeof(int));
  }
  a->array[a->used++] = element;
}

void freeArray(Array *a) {
  free(a->array);
  a->array = NULL;
  a->used = a->size = 0;
}

Пример использования:

Array a;
int i;

initArray(&a, 5);  // initially 5 elements
for (i = 0; i < 100; i++)
  insertArray(&a, i);  // automatically resizes as necessary
printf("%dn", a.array[9]);  // print 10th element
printf("%dn", a.used);  // print number of elements
freeArray(&a);

Двумерный массив может быть реализован как **int.

Дух сообщества's user avatar

ответ дан 28 окт 2013 в 18:18

Alexander Serebrenik's user avatar

Вы можете использовать библиотеку cdcontainers. Она написана на C, а интрфейс напоминает интрефейс стандартной библиотеки C++

https://github.com/maksimandrianov/cdcontainers

#define CDC_USE_SHORT_NAMES  // for short names (functions and structs without prefix cdc_*)
#include <cdcontainers/vector.h>
#include <cdcontainers/casts.h>
#include <stdio.h>

int main(int argc, char** argv)
{
    vector_t *v;
    size_t i;

    if (vector_ctor(&v, NULL) != CDC_STATUS_OK)
        /* error handling */;

    if (vector_push_back(v, CDC_INT_TO_PTR(7)) != CDC_STATUS_OK)
        /* error handling */;

    if (vector_push_back(v, CDC_INT_TO_PTR(8)) != CDC_STATUS_OK)
        /* error handling */;

    for (i = 0; i < vector_size(v); ++i)
        printf("%i ", CDC_PTR_TO_INT(vector_get(v, i)));

    printf("n");

    vector_dtor(v);

    return 0;
}

ответ дан 3 апр 2018 в 15:49

user8122949's user avatar

Добрый день друзья. Сегодня у нас особый урок. Во-первых, он
будет более практичный и небольшой по объему, во-вторых, он посвящен ответам на
вопросы, которые мне задали в нашей группе вконтакте, и в-третьих, он будет уже
использовать некоторые возможности С++. Это осознанный шаг, и я думаю, во
многом, он облегчит жизнь и вам и мне. Приступим.

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

Примерно, это выглядело бы так:

Листинг
17.1

#include <stdio.h>

int main(){

      int N;

      printf(«Vvedite kolichestvo dannihN»);

      scanf(«%d»,&N);

      int arr[N];

      return 0;

}

ВНИМАНИЕ! В современных компиляторах языка Си этот код будет работать! В Си такая возможность предполагается новым стандартом.

Но если мы сделаем так, то наша программа не скомпилируется
даже. Получим ошибку, что при объявлении массива, нужно константное
выражение.  Естественно, мы могли бы
создать массив на 100 элементов и не париться об этом, но мы пойдем другим
путём, более оптимальным.

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

Это называется выделение памяти «в куче». Для того, чтобы
выделить себе некоторую область в памяти, необходимо использовать команду new(). Смотрите, как это
работает.

Листинг
17.2

#include <stdio.h>

int main(){

      int *num = new (int);

      *num=4;

      printf(«%d n«,*num);

      return 0;

}

Результат работы это программы, представлен на следующем
рисунке.

Рис.1 Выделение памяти под переменную типа int.

Команда new()
выделяет необходимое количество памяти, под тип объекта, который указан в
скобках. Ну т.е. в нашем случае, мы попросили выделить для нас память под одну
переменную типа int. Данная
команда возвращает указатель на выделенный фрагмент памяти. Поэтому мы в
принципе и сохраняем её в соответствующую переменную.

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

Листинг 17.3

#include <stdio.h>

int main(){

      int N; 

      printf(«Vvedite kolichestvo dannihn»);

      scanf(«%d»,&N);

      int *arr = new (int [N]); 

      return 0;

}

Вот в таком виде наша программа уже скомпилируется и будет
делать именно то, что нам нужно. Пользователь введет 20 и она создаст массив из
20 элементов типа int.
Введет пять — будет массив из пяти элементов. Удобно, не правда ли? Не надо
расходовать лишнюю память, создавая массив из 100 элементов из которых только
пять первых будут использоваться.

А самое классно знаете что? То, что работать с этим массивом
можно прямо точно так же как если бы мы создали его самостоятельно. Ну т.е. нам
не потребуется добавлять звездочки и т.д. Вот, например, в следующей программе
мы присваиваем вновь созданному динамическому массиву из двух элементов
некоторые значения.

Листинг 17.4

#include <stdio.h>

int main(){

      int N;

      printf(«Vvedite kolichestvo dannihn»);

      scanf(«%d»,&N);

      int *arr = new (int [N]);

      arr[0]=1;

      scanf(«%d», &arr[1]);

      printf(«%d %dn», arr[0],arr[1]);

      return 0;

}

Рис. 2. Иллюстрация работы программы использующей динамический массив

Как видите, все как и прежде. Это действительно удобно.
Теперь немножко отвлечемся. Команда new() это команда языка С++, в чистом Си её нет. Там есть
некоторый её аналог команда malloc.
Но она менее удобная, чем new.
Поэтому пользуйтесь.

Я думаю, всех учили убирать за собой, когда навели
беспорядок. В этом плане память, как мама. Не любит, когда не убрано. Поэтому
есть и еще одна команда, которая позволяет убрать за собой. Ну т.е. освободить
память, которую мы заняли. Она называется delete(). Логично, да?

Передаете ей в качестве аргумента, то что вы выделили и она
самостоятельно наведет уборку. Это нужно делать всегда, когда вы выделяли
память. Конечно, если вы не сделаете так, ничего особенно страшного не случится.
Но представьте ситуацию, что каждая программа, которую вы запускаете, сохраняет
в оперативной памяти вашего компьютера какие-то данные и потом не удаляет их.
Не порядок, не так ли? Или пришел к вам гость, сходил в туалет и не смыл за
собой.  Ну вот и вы не сорите за собой. Я
вот, кстати намусорил сейчас в вашем компьютере немного, если вы уже запускали
программы из примеров. =))

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

Листинг
17.5

#include <stdio.h>

int main(){

      int *num = new (int);

      *num=4;

      printf(«%d n»,*num);

      delete num;

      return 0;

}

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

Листинг 17.6

delete []arr;

С первым вопросом разобрались и на этом закончим сегодняшний
мини-урок. 

Я надеюсь он был полезен вам.  Не пожалейте лайков, как говорится. )

До скорой, я надеюсь, встречи.

О динамических массивах в языке Си: что это такое, виды, как с ними работать

Содержание:

  • Что такое динамический массив

    • Указатели для динамических массивов
    • Ошибки, возникающие при неверном использовании динамических массивов
  • Виды динамических массивов
  • Стандартные функции динамического выделения памяти
  • Выделение памяти под двумерный массив
  • Нестандартные функции динамического выделения памяти
  • Перераспределение памяти

Что такое динамический массив

Определение

Массив — последовательная группа ячеек памяти, имеющих одинаковое имя и одинаковый тип. То есть это структуры, которые содержат связанные друг с другом однотипные элементы данных.

Они занимают область в памяти. Компилятор резервирует соответствующий объем памяти для каждого типа и количества элементов каждого массива.

Чтобы зарезервировать память при помощи компилятора для статического массива, необходимо указать ему сколько в нем будет элементов целых чисел. Для этого используется объявление int a [] ;

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

Пример

Пример объявления для 12 элементов:

int a [12] ;

Для динамического массива количество элементов не указывают.

На этапе написания программы размер динамических массивов неизвестен. Ввод количества элементов осуществляется во время работы программы.

Размером при объявлении динамического массива является числовая переменная, а не константа.

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

В динамических массивах также не указывают длину строк и их количество. Границы массивов полностью контролируются разработчиком.

Указатели для динамических массивов

Одним из основных понятий языка Си являются указатели. Они представляют собой переменные, хранящие информацию об адресе какого-либо объекта. Их особенностью является то, что иногда только с их помощью можно выполнить некоторые операции, или записать код более компактно и эффективно, чем при использовании других способов.

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

Действия с указателями

  1. Объявление.

Указатели:

  • int *pI;
  • char *pC;
  • float *pF;

Чтобы объявить указатель, объявляют тип переменной и ставят перед именем символ *. Указанная переменная должна быть одного типа с указателем, который на ее указывает.

  1. Присвоение адреса.

Указатели:

  • pI = &i;
  • int i, *pI;

Адрес присваивается указателю с помощью оператора &. Знак &, стоящий перед переменной, будет указывать на ее адрес.

  1. Получение значения по этому адресу.

Указатели:

  • f = *pF;
  • float f, *pF;

Для переменной определенного типа, на которую определен указатель, например, pF, указывается число соответствующего типа. Символ * перед pF означает информацию ячейки, указанной pF.

  1. Сдвижение.

Указатели:

  • int *pI;
  • pI ++;
  • pI -= 4;
  • pI += 4;

Подобные операции позволяют сдвигать значение указателя на то или иное целое число или количество байтов.

  1. Обнуление.

Указатели:

  • char *pC;
  • pC = NULL;

Указатель указывает на недействительный адрес, который равен нулю, соответственно, указатель тоже недействительный. Запись по нему не может быть осуществлена.

  1. Выведение на экран.

Указатели:

  • int i, *pI;
  • printf(«Адр.i =%p», pI);
  • pI = &i;

Для вывода указателей на экран применяют формат %p.

Ошибки, возникающие при неверном использовании динамических массивов

  1. Запись в чужую область памяти.

Причина: Выделение памяти произошло неудачно, при этом массив используется.

Вывод: необходимо всегда осуществлять проверку указателя на NULL. Если значение указателя будет равно нулю при проверке после выделения памяти, то использование такого массива будет приводить к зависанию компьютера.

  1. Повторное удаление указателя.

Причина: Массив уже удален и теперь удаляется снова.

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

  1. Выход за границы массива

Причина: В массив записан элемент с отрицательным индексом или индексом, выходящим за границу массива.

  1. Утечка памяти

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

Виды динамических массивов

Классификация динамических массивов:

  • одномерные: int * A;
  • двумерные: int ** A;

Одномерный динамический массив представляет собой множество элементов одного типа данных.

Двухмерный массив имеет более сложную структуру и содержит в качестве элементов массива другие массивы. Каждому хранящемуся в массиве А указателю присвоено не одно значение, а одномерный динамический массив таких значений.

Переменная А сама является указателем и объявляется как указатель на указатель на нужный тип. Данный указатель нужно инициализировать, то есть положить ему адрес массива адресов. В памяти программы должен существовать массив адресов указателей на int и сам массив указателей на int. В этом массиве указателей должны быть разложены адреса, возможно лежащие отдельно друг от друга в памяти.

Пример двумерного массива:

  1. #include <iostream> // здесь указывают заголовок файла, содержащего функции, переменные, классы, для организации операций ввода-вывода
  2. using namespace std; // объявление пространства имен для ограничения видимости переменных, функций и т.д.
  3. int main() {
  4. setlocale(0, «»); “// указывают тип значения, возвращаемого функцией, и задают локаль для программы
  5. int **dinamic_array2 = new int* [5];
  6. for (int i = 0; i < 5; i++) {
  7. dinamic_array2[i] = new int [i + 1];
  8. } // Создание двумерного массива
  9. for (int i = 0; i < 5; i++) {
  10. cout << «Введите числа» << «(» << i + 1 << «)» << «:»;
  11. for (int j = 0; j < i + 1; j++) {
  12. cin >> dinamic_array2[i][j];
  13. }
  14. } // заполнение массива
  15. for (int j = 0; j < i + 1; j++) {
  16. sum += dinamic_array2[i][j];
  17. }
  18. cout << «Сумма » << i + 1 << » массива равна » << sum << endl;
  19. } // проведение различных операций по вводу-выводу
  20. for (int i = 0; i < 5; i++) {
  21. delete [] dinamic_array2[i]; // удаление массива
  22. }
  23. system(«pause»);
  24. return 0;
  25. }

Строки 5-8 — создание динамического массива.

Строки 9-14 — заполнение.

Строки 15-19 — подсчет, сортировка и вывод на экран суммы всех массивов.

Строки 20-22 — удаление массива.

Стандартные функции динамического выделения памяти

В языке программирования Си имеются стандартные функции управления памятью:

  1. Malloc (от англ. memory allocation, выделение памяти). Функция malloc возвращает указатель на n байт неинициализированной памяти или NULL, если запрос на память нельзя выполнить: void *malloc(size_t n). Значение, которое возвращает malloc, — это адрес начала выделенной области памяти. При этом гарантируется соответствующее выравнивание этого адреса на границу памяти, обеспечивающую хранение в области любого объекта.
  2. Calloc (от англ. clear allocation, чистое выделение памяти). Функция calloc возвращает указатель на участок памяти, достаточный для размещения п объектов заданного размера size или NULL, если запрос на память невыполним. Память инициализируется нулями; void *calloc(size_t n, size_t size).
  3. Realloc (от англ. reallocation, перераспределение памяти). Используется для изменения величины выделенной памяти, на которую указывает ptr, на новую величину, задаваемую параметром newsize. Эта функция может перемещать блок памяти на новое место, в этом случае функция возвращает указатель на новое место в памяти.
  4. Free (англ. free, освободить). Функция free(p) освобождает участок памяти, на который указывает указатель р, первоначально полученный вызовом функции malloc или calloc. Порядок освобождения выделенных участков памяти не регламентируется. Однако если указатель не был получен с помощью malloc или calloc, то его освобождение является грубой ошибкой. Обращение по указателю после его освобождения — также ошибка. Правильным было бы записать все, что нужно, перед освобождением:

for (p = head; p != NULL; p = q) {

q = p->next;

free(p);

}

Выделение памяти под двумерный массив

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

Например, есть указатель А: int ** A ;

Сначала создают массив, состоящий из указателей, которые будут отвечать количеству строк:

A = (int **) malloc (N * sizeof(int *)). Здесь важно указать тип указателя, а не int. Потому что в этом массиве, на который указывает A, будут храниться указатели, а не int.

Далее инициализируют уже сами элементы этого массива указателей путем запуска цикла:

for (int i = 0 ; i < N ; ++i).

Для каждого A[i] проводим инициализацию по отдельности. По сути этого разыменование А со смещением i:

A[i] = (int *) malloc (M * sizeof (int)). Указатели A[i] уже указывают на массив int. Если бы был другой тип, то указывали бы на него.

Для освобождения памяти используют схему: сначала освобождают память для указателей, а потом уже для самого указателя A. То есть массив, который выделил массив указателей, тоже сбрасывают.

int ** A ;

A = (int **) malloc (N * sizeof(int *)).

for (int i = 0 ; i < N ; ++i).

free (A[i]) ;

free (A) ;

Нестандартные функции динамического выделения памяти

Когда требуемый размер массива становится известен, то используют оператор new из языка Си ++, который является расширенной версией языка Си. В скобках указывают размер массива:

pI =new int [N] ;

При отрицательном или нулевом N использовать оператор new нельзя.

При динамическом распределении памяти основными операциями являются new и delete.

Операция new принимает в качестве аргумента тип динамически размещаемого объекта и возвращает на него указатель.

Например, оператор Node *newPtr = new NodeA0); используется для выделения области памяти размером sizeof(Node) байтов, а также его применяют для сохранения указателя на данной области памяти в указателе newPtr.

Если планируется использовать память повторно, после того, как динамический массив в какой-то момент работы программы перестанет быть нужным, то ее освобождают с помощью delete[],

При этом не указывают размерность массива. Операция delete освобождает область памяти, выделенную при помощи оператора new. Таким образом, она возвращает системе эту область памяти для дальнейшего распределения. Чтобы освободить память, которая была динамически выделена предыдущим оператором, применяют оператор delete newPtr;

Иногда выделение памяти под динамические массивы осуществляют с помощью двух языков Си и Си++ одновременно: операции new и функции malloc.

Пример

int n = 10;

int *a = new int[n];

double *b = (double *)malloc(n * sizeof (double));

Во второй строке описан указатель на целую величину. Ему присвоен адрес начала непрерывной области динамической памяти, выделенной с помощью операции new. В зависимости от того, сколько присутствует n величин типа int, столько выделяется памяти для их хранения. Величина n может являться переменной.

В третьей строке применяется функция malloc, которая в данном случае выделяет память под n элементов типа double. Для того, чтобы освободить память, выделенную с помощью функции malloc, применяют функцию free. Память не обнуляется при ее выделении. Динамический массив нельзя инициировать.

Перераспределение памяти

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

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

Функция realloc осуществляет перераспределение памяти. Она меняет размер динамически выделяемой области памяти, адресуемой указателем ptr, на новый размер size.

Realloc может возвращать адрес новой области памяти:

realloc(void *ptr, size_t size);

Если ptr равен NULL, то realloc ведет себя подобно функции malloc.

Поведение функции realloc будет неопределенным, если:

  • значение ptr не было возвращено функциями calloc, malloc или realloc;
  • пространство памяти было освобождено функцией free, и ptr указывают на него.

Size указывают в абсолютном значении.

Важные моменты в перераспределении памяти с помощью функции realloc:

  1. Если существующее пространство меньше по размеру, чем size, то в конце будет выделяться новое неинициализированное непрерывное пространство, при этом предыдущие данные пространства будут сохранены.
  2. Значение, равное NULL, будет возвращено, если realloc не может выделить запрашиваемое пространство, а в указанном ptr пространстве содержимое остается нетронутым.
  3. Realloc будет действовать, как функция free, если size=0, а ptr не равен NULL.
  4. Если первым параметром функции realloc использовать NULL, то можно получить эффект, который достигается с помощью функции malloc с тем же значением size.

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

Например, при создании связанного списка linked list и применения realloc, чтобы выделить для цепочки пространство памяти, может оказаться, что оно будет перемещаться. В таком случае, указатели будут адресованы не в место их расположения в настоящий момент, а в то место, где были размещены последовательные звенья цепочки.

Пример

Пример использования функции realloc:

p2 = realloc(p1, new_size);

if (p2 1= NULL) p1 = p2;

Необходимо учитывать, что realloc работает с ранее выделенной памятью и старыми строками. Добавление новых строк и выделение памяти осуществляется посредством функций calloc или malloc.

На чтение 8 мин. Опубликовано 21.03.2021

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

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

Динамическое выделение памяти для двумерных массивов

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

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

Для того, чтобы реализовать процедуру динамического распределения памяти, в языке программирования С используются такие функции, как delete и new. Функция new дает возможность выделить фрагмент свободной памяти, а посредством использования функции delete можно освободить ту память, которая была выделена. После того, как произойдет использование выделяемой памяти, она должна в обязательном порядке освобождаться.

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

Динамический массив на языке С

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

Динамический массив на языке С

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

Динамический массив на языке С

В 10-ой строке можно наблюдать за тем, как происходит такой процесс, как метод инициализации и объявления девяткой динамического массива. Для этого нужно после типа данных в круглых скобках указать необходимое значение. В процессе выполненной работы будет получен следующий результат, который можно увидеть на рисунке:

Динамический массив на языке С

Особенности создания динамических массивов

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

Ознакомимся с фрагментом программного кода, в котором происходит процесс создания динамического одномерного массива:

Динамический массив на языке С

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

Динамический массив на языке С

После того, как будет указана функция delete, непосредственно после нее нужно установить квадратные скобки. Их наличие указывает на то, что происходит процесс освобождения определенного участка памяти, который отводился на одномерный массив. Рассмотрим пример программы, в которой осуществляется процесс разработки динамического одномерного массива, в котором используются случайные числа.

Динамический массив на языке С

Сформированный динамический одномерный массив заполняется произвольными случайными числами, которые берутся за счет генерации случайных чисел. Генерация происходит в диапазоне от 1 до 10-ти. Для того, чтобы задать интервал генерации, применяется команда rand() % 10 + 1.В том случае, если есть необходимость в процедуре получения вещественных чисел случайного типа, происходит процесс выполнения такого действия, как деление. Для этого применяется такая операция, как float((rand() % 10 + 1)).

В том случае, если после запятой есть необходимость в выведение всего лишь двух знаков, для этого применяется команда setprecision(2). В заголовочном файле можно найти прототип представленной функции. За счет использования такой функции, как time(0), происходит процедура засевания генератора случайных чисел посредством применения временных значений. Это дает возможность получать произвольные числовые значения.

Динамический массив на языке С

После того, как работа с массивом будет закончена, происходит процесс его удаления. Это дает возможность получить свободную память, которая отводилась для временного хранения. Теперь же стоит обратить внимание на фрагмент кода, в котором применяется объявление динамического двумерного массива:

Динамический массив на языке С

Первоначально стоит объявить указатель второго порядка, в качестве которого выступает float **ptrarray. Он ссылается на массив, где присутствуют указатели float* [2]. Здесь размер массива равняется двум. После этого в цикле for каждой строке, которая объявлена в массиве в строке 2 выделяется память, которая будет принадлежать 5-ти элементам. В результате этого удастся получить двумерный массив.

Особенности удаления динамических переменных и массивов

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

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

Динамический массив на языке С

Первоначально стоит указать оператор, после чего имя используемой переменной. Стоит обратить внимание на то, что оператор * отсутствует непосредственно перед именем используемой переменной. Очень часто происходит так, что начинающие программисты забывают об этом и на протяжении нескольких часов пытаются найти ошибку, которая не существует.

Особенности инициализации двухмерных массивов

В том случае, если есть необходимость в выполнении такого действия, как инициализация динамического массива с помощью такого значения, как 0, то в таком случае все достаточно просто:

Динамический массив на языке С

До того момента, пока не появился язык программирования С и С++, не было нормально и доступного метода для инициализации динамического массива посредством применения ненулевых значений. Список, используемый для инициализации, ранее работал только с теми значениями, которые являлись фиксированными. Это указывает на то, что перед программистом возникает такая задача, как ручной перебор всех элементов используемого массива и присвоение ему определенного значения:

Динамический массив на языке С

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

Динамический массив на языке С

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

В языке программирования С есть возможность осуществить процесс инициализации фиксированных массивов посредством использования uniform-инициализации:

Динамический массив на языке С

Но здесь нужно быть максимально осторожными, так как в этом языке программирования нет возможности реализовать процесс инициализации посредством применения строки C-style:

Динамический массив на языке С

Вместо использования вышеперечисленных вариантов есть возможность в применении динамического выделения std::string. Помимо этого, не стоит забывать о том, что объявление динамических массивов должно параллельно сопровождаться таким процессом, как объявление их длины. Для этого применяется такая команда:

Динамический массив на языке С

Особенности изменения длины массивов

Осуществляя такой процесс, как динамическое выделение массивов, стоит обратить внимание на то, что при выполнении этого действия есть возможность в выполнении такого действия, как изменение размера в процессе реализации программы. Но здесь также стоит помнить о том, что в языке программирования С не предусматривается встроенный метод, посредством изменения которого можно осуществлять изменение длины массива.

Обойти такое ограничение удалось, так как есть возможность осуществить динамическое выделение совершенно нового массива. Для этого нужно реализовать такое действие, как процесс копирования необходимых элементов из старого массива. После этого реализуется такой процесс, как удаление старого массива. Но здесь стоит обратить внимание на то, что этот массив подвергается ошибкам.

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

Особенности перераспределения памяти

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

  • осуществить выделение блока памяти, размерность которого составляет n+1 (на 1 больше от того размера, который использовался до этого);
  • осуществить процедуру копирования всех значений, которые хранятся на том участке памяти, который был выделен для этого;
  • выполнить процедуру освобождения памяти, которая ранее выделялась для хранения определенного массива;
  • осуществить перемещение указателя массива на начало той области памяти, которая была выделена;
  • осуществить процесс дополнения массива теми значениями, которые были введены последними.

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

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

Like this post? Please share to your friends:
  • Как написать диктофон на андроид
  • Как написать диктанты
  • Как написать диктант на пять
  • Как написать диктант на 4 или 5
  • Как написать диклофенак правильно