|
|
Строка 219: |
Строка 219: |
| end.</source> | | end.</source> |
| После вызова <tt>Dispose(p)</tt>, <tt>p</tt> называют '''''висячим указателем''''' (т.к. он указывает на недоступную более область памяти). | | После вызова <tt>Dispose(p)</tt>, <tt>p</tt> называют '''''висячим указателем''''' (т.к. он указывает на недоступную более область памяти). |
− |
| |
− | == Введение в классы ==
| |
− | === Отличие классов от записей ===
| |
− | Рассмотрим запись студент:
| |
− | <source lang="Pascal">type
| |
− | Student = record
| |
− | name: string;
| |
− | age: integer;
| |
− |
| |
− | procedure Init(n: string; a: integer);
| |
− | begin
| |
− | name := n;
| |
− | age := a;
| |
− | end;
| |
− |
| |
− | procedure Print;
| |
− | begin
| |
− | writelnFormat('Имя: {0} Возраст: {1}',
| |
− | name, age);
| |
− | end;
| |
− | end;
| |
− |
| |
− | var
| |
− | s: student;
| |
− | begin
| |
− | s.Init('Иванов', 18);
| |
− | end.</source>
| |
− | Когда мы описываем переменную типа Student, соответствующая ей ячейка памяти отводится на программном стеке.
| |
− |
| |
− | А вот так выглядит класс Student:
| |
− | <source lang="Pascal">type
| |
− | Student = class
| |
− | name: string;
| |
− | age: integer;
| |
− |
| |
− | constructor Create(n: string; a: integer);
| |
− | begin
| |
− | name := n;
| |
− | age := a;
| |
− | end;
| |
− |
| |
− | procedure Print;
| |
− | begin
| |
− | writelnFormat('Имя: {0} Возраст: {1}',
| |
− | name, age);
| |
− | end;
| |
− | end;
| |
− |
| |
− | var
| |
− | s: Student;
| |
− | begin
| |
− | s := new Student('Иванов', 18);
| |
− | s.Print;
| |
− | end.</source>
| |
− | Переменная типа класс хранит ссылку на объект, память под который выделяется динамически при вызове конструктора.
| |
− | В памяти ссылка представляется как указатель - хранит адрес объекта. Однако, такой "указатель" как бы всегда разыменован (не надо писать s^).
| |
− | Если переменная типа класс представляет собой ссылку. то говорят. что в языке программирования реализована '''ссылочная объектная модель'''. В большинстве современных универсальных ЯП реализована именно ссылочная объектная модель (C#, Java, Delphi). Исключение составляет C++, в котором реализована '''размерная объектная модель'''.
| |
− |
| |
− | является указателем. Для выделения динамической памяти под объект класса Student используется вызов специального метода, называемого '''конструктором''' (<tt>'''New''' Student(<имя>, <возраст>)</tt>).
| |
− |
| |
− | === Переменная Self ===
| |
− | Внутри каждого нестатического метода имеется переменная Self, принадлежащая к типу данного класса и являющаяся ссылкой «на себя», т.е. на объект класса, вызывающий этот метод.
| |
− |
| |
− | Поэтому возможно написать, например, так:
| |
− | <source lang="Delphi">
| |
− | type
| |
− | /// Студент
| |
− | Student = class
| |
− | /// Имя
| |
− | Name: string;
| |
− | /// Возраст
| |
− | Age: integer;
| |
− | /// Курс
| |
− | Course: integer;
| |
− | /// Группа
| |
− | Group: integer;
| |
− |
| |
− | constructor (Name: string; Age, Course, Group: integer);
| |
− | begin
| |
− | Self.Name := Name;
| |
− | Self.Age := Age;
| |
− | Self.Course := Course;
| |
− | Self.Group := Group;
| |
− | end;
| |
− | end;
| |
− | </source>
| |
− |
| |
− | Оказывается, во-первых, компилятор добавляет переменную '''<tt>Self</tt>''' в качестве первого параметра любого нестатического метода. <br />
| |
− | Во-вторых, при обращении к любому полю класса внутри метода, перед этим полем неявно добавляется '''<tt>Self.</tt>'''
| |
− |
| |
− | <small>Лекция 4</small>
| |
− |
| |
− | === Шаблоны классов ===
| |
− | <source lang="Pascal">type
| |
− | Point<T> = class
| |
− | x, y: T;
| |
− |
| |
− | constructor(x, y: T);
| |
− | begin
| |
− | Self.x := x;
| |
− | Self.y := y;
| |
− | end;
| |
− | end;
| |
− |
| |
− | begin
| |
− | var p1 : Point<real>;
| |
− | var p2 : Point<integer>;
| |
− |
| |
− | p1 := new Point<real>(2.3, 5.7);
| |
− | p2 := new Point<integer>(2, 7);
| |
− |
| |
− | p1.x := 2;
| |
− | p2.x := 1.5; // ошибка компиляции, т.к.
| |
− | // тип поля x объекта класса Point<integer> (integer)
| |
− | // не совместим по присваиваиванию с вещественным типом real
| |
− |
| |
− | //можем пользоваться автоопределением типов:
| |
− | var p3 := new Point<real>(3.14, 7.9);
| |
− | end.</source>
| |
− | Тип <tt>Point<T></tt> называют '''обобщенным''' типом.
| |
− |
| |
− | Теперь рассмотрим присваивание объектов класса:
| |
− | <source lang="Pascal">var p11 := new Point<real>(0, 1.5);
| |
− | var p12 := new Point<real>(2.3, 5);
| |
− |
| |
− | p11 := p12; // происходит присваивание ссылок:
| |
− | // p11 теперь указывает на тот же объект, что и p12;
| |
− | // область памяти, на которую до этого указывал p11 более недоступна
| |
− |
| |
− | var p2 := new Point<integer>(5, 7);
| |
− | p11 := p2; // ошибка
| |
− | // типы объектов не совпадают</source>
| |
− | При присваивании же друг другу записей, происходит копирование самих записей.
| |
− |
| |
− | === Сборка мусора ===
| |
− | ('''Garbage collection''')
| |
− |
| |
− | '''Мусор''' — любые ненужные объекты.
| |
− | <br />Под '''''ненужными''''' понимаются объекты, которые занимают память, но недоступны в программе.
| |
− |
| |
− | '''Сборка мусора''' — процесс освобождения памяти, занятой ненужными объектами.
| |
− |
| |
− | Обычно, сборка мусора запускается при нехватке места в памяти. Это позволяет не заботиться об ''утечках памяти'', т.к. все выделенное — освободится. (При этом, от всех остальных ошибок мы не застрахованы!!!)
| |
− | <br />Главный недостаток механизма сборки мусора — во время сборки выполнение программы приостанавливается.
| |
− |
| |
− | == Динамические структуры данных ==
| |
− | === Введение ===
| |
− | Данные объединяются в структуры.
| |
− | <br />Мы уже знаем такие структуры данных как:
| |
− | *''массивы'' (подразумеваем статические)
| |
− | *''записи''
| |
− | Их основная проблема — фиксированный размер, определяемый на этапе компиляции.
| |
− | <br />Решением проблемы являются '''динамические структуры данных'''. Они строятся из '''узлов''', которые, в свою очередь, состоят из '''''данных''''' и '''''полей связи'''''.
| |
− |
| |
− | Рассмотрим такой пример:
| |
− | <source lang="Pascal">type
| |
− | Node<DataType> = class
| |
− | data: DataType;
| |
− | next: Node<DataType>;
| |
− |
| |
− | constructor (d: DataType; n: Node<DataType>);
| |
− | begin
| |
− | data := d;
| |
− | next := n;
| |
− | end;
| |
− | end;
| |
− |
| |
− | begin
| |
− | var p := new Node<char>('!', nil); // под объект класса Node<char> выделилась динамическая память;
| |
− | // p начала указывать на эту динамическую память
| |
− | end.</source>
| |
− |
| |
− | === Виды списков ===
| |
− | # ''Линейный односвязный список''<br>[[Изображение:Линейный_односвязный_список.png]]
| |
− | # ''Циклический односвязный список''<br>[[Изображение:Циклический_односвязный_список.png]]
| |
− | # ''Двусвязный линейный список''<br> [[Изображение:Двусвязный_линейный_список.png|Двусвязный_линейный_список.png]]
| |
− | # ''Циклический двусвязный список''<br>[[Изображение:Циклический_двусвязный_список.png]]
| |
− | === Односвязные линейные списки ===
| |
− | <xh4>Класс узла односвязного списка</xh4>
| |
− | <source lang="Pascal">type
| |
− | Node<DataType> = class
| |
− | data: DataType;
| |
− | next: Node<DataType>;
| |
− |
| |
− | constructor (d: DataType; n: Node<DataType>);
| |
− | begin
| |
− | data := d;
| |
− | next := n;
| |
− | end;
| |
− | end;
| |
− |
| |
− | begin
| |
− | {Пусть у нас есть указатель на начало списка:}
| |
− | var head: Node<char>;
| |
− | // инициализация head</source>
| |
− |
| |
− | <xh4>Стандартные операции с односвязными линейными списками</xh4>
| |
− | * '''''Вставка элемента в начало'''''<br />
| |
− | [[Изображение:Добавление_элемента_в_начало_линейного_односвязного_списка.gif]]<br />
| |
− | {{Hider
| |
− | |title = Код
| |
− | |content = <source lang="Pascal">head := new Node<char>('A', head);</source>
| |
− | При многократной вставке в начало элементы располагаются в ''обратном порядке''.}}
| |
− |
| |
− | * '''''Удаление элемента из начала'''''<br />
| |
− | [[Изображение:Удаление начального элемента.gif]] <br />
| |
− | {{Hider
| |
− | |title = Код
| |
− | |content = <source lang="Pascal">head := head.next;</source>
| |
− | Если изначально список пуст, произойдет ошибка<span style="color: red"> «попытка разыменования нулевого указателя»</span>. Эту ситуацию надо предусмотреть:
| |
− | <source lang="Pascal">if head <> nil then
| |
− | head := head.next;</source>}}
| |
− |
| |
− | * '''''Вставка элемента после текущего''''' <br />
| |
− | [[Изображение:Вставка элемента после текущего.gif]] <br />
| |
− | {{Hider
| |
− | |title = Код
| |
− | |content = <source lang="Pascal">cur.next := new Node<char>('C', cur.next);</source>
| |
− | Если <tt>cur</tt> никуда не указывает, произойдет ошибка. Предусмотрим эту ситуацию:
| |
− | <source lang="Pascal">if cur <> nil then
| |
− | cur.next := new Node<char>('d', cur.next);</source>
| |
− | Заметим также, что если <tt>cur</tt> указывает на последний элемент списка, ошибки не произойдет (фактически, будет произведена ''вставка в конец списка'').}}
| |
− |
| |
− | * '''''Удаление элемента после текущего''''' <br />
| |
− | [[Изображение:Удаление после текущего.gif]]<br />
| |
− | {{Hider
| |
− | |title = Код
| |
− | |content = <source lang="Pascal">cur.next := cur.next.next;</source> <br />
| |
− |
| |
− | Заметим, что текущий элемент — <tt>cur</tt>, должен не только не быть пустым, но и не быть последним в списке, т.к. происходят два разыменования: <tt>'''cur.'''next</tt> и <tt>'''cur.next.'''next</tt>. Для проверки этого факта можем воспользоваться утверждением:
| |
− | <source lang="Pascal">Assert( (cur <> nil) and (cur.next <> nil) );
| |
− | cur.next := cur.next.next;</source>
| |
− | }}
| |
− |
| |
− | * '''''Проход по списку''''' <br />
| |
− | [[Изображение:Проход_по_списку.gif]]<br />
| |
− | {{Hider
| |
− | |title = Код
| |
− | |content = <source lang="Pascal">var cur := head;
| |
− | while cur <> nil do
| |
− | begin
| |
− | writeln(cur.data);
| |
− | cur := cur.next;
| |
− | end;</source> }}
| |
− |
| |
− | <xh4>Примеры использования</xh4>
| |
− | ''<u>Пример 1</u>''.
| |
− | <br />Дан файл целых чисел. <br />Записать все его элементы в линейный односвязный список.
| |
− | <source lang="Pascal">var f: file of integer;
| |
− | Assign(f, 'numbers.dat');
| |
− | Reset(f);
| |
− |
| |
− | var a: integer;
| |
− | Read(f, a);
| |
− | var head := new Node<integer>(a, nil);
| |
− |
| |
− | var cur := head;
| |
− | while not Eof(f) do
| |
− | begin
| |
− | read(f, a);
| |
− | cur.next := new Node<integer>(a, nil);
| |
− | cur := cur.next;
| |
− | end;
| |
− |
| |
− | Close(f);</source>
| |
− | ''<u>Пример 2</u>''.
| |
− | <br />Поиск элемента с заданным значением.
| |
− | <source lang="Pascal">// x — искомый символ
| |
− | var cur := head;
| |
− | while (cur <> nil) and (cur.data <> x) do
| |
− | cur := cur.next;
| |
− |
| |
− | if cur = nil then
| |
− | // не найдено
| |
− | else
| |
− | // cur — ссылка на искомый x</source>
| |
− |
| |
− | ===Двусвязные линейные списки===
| |
− | <xh4>Класс узла двусвязного списка</xh4>
| |
− | В отличие от односвязных линейных списков, двусвязные, помимо полей <tt>data</tt> и <tt>next</tt>, имеют поле <tt>prev</tt> (указатель на предыдущий элемент списка):
| |
− | <source lang="Pascal">type
| |
− | Node<T> = class
| |
− | data: T;
| |
− | prev, next: Node<T>;
| |
− |
| |
− | constructor (d: T; p, n: Node<T>);
| |
− | begin
| |
− | data := d;
| |
− | prev := p;
| |
− | next := n;
| |
− | end;
| |
− | end;</source>
| |
− | В случае двусвязного списка нам достаточно иметь ссылку на любой узел, тогда все остальные можно найти. Однако, для удобства, будем считать, что у нас есть две ссылки:
| |
− | * <tt>head</tt> — на начало списка
| |
− | * <tt>tail</tt> — на конец списка
| |
− | <xh4>Стандартные операции с двусвязными линейными списками</xh4>
| |
− | '''Замечание.''' При выполнении любой операции нужно следить за возможными изменениями <tt>head</tt> и <tt>tail</tt>.
| |
− | * '''''Инициализация''''' <br />
| |
− | <source lang="Pascal">head := nil;
| |
− | tail := nil;</source>
| |
− |
| |
− | * '''''Добавление элемента в начало''''' <br />
| |
− | [[Изображение:Вставка_в_начало.gif]] <br />
| |
− | {{Hider
| |
− | |title = Код
| |
− | |content = '''Примечание.''' Если изначально список был пуст, после добавления элемента надо не забыть сделать <tt>tail</tt> указывающим на него.
| |
− | <source lang="Pascal">head := new Node<T>(0, nil, head);
| |
− | if head.next <> nil then
| |
− | head.next.prev := head
| |
− | else // если список был пуст
| |
− | tail := head;</source>}}
| |
− |
| |
− | * '''''Добавление элемента в конец''''' <br />
| |
− | [[Изображение:Вставка_в_конец.gif]] <br />
| |
− | {{Hider
| |
− | |title = Код
| |
− | |content = <source lang="Pascal">tail := new Node<T>(2, tail, nil);
| |
− | if tail.prev <> nil then
| |
− | tail.prev.next := tail
| |
− | else // если список был пуст
| |
− | head := tail;</source> }}
| |
− |
| |
− | * '''''Удаление элемента из начала''''' <br />
| |
− | [[Изображение:Удаление_из_начала_д.gif]] <br />
| |
− | {{Hider
| |
− | |title = Код
| |
− | |content = <source lang="Pascal">head := head.next;
| |
− | if head = nil then
| |
− | tail := nil
| |
− | else
| |
− | head.prev := nil;</source> }}
| |
− |
| |
− | * '''''Удаление элемента из конца''''' <br />
| |
− | [[Изображение:Удаление_из_конца_д.gif]] <br />
| |
− | {{Hider
| |
− | |title = Код
| |
− | |content = <source lang="Pascal">tail := tail.prev;
| |
− | if tail = nil then
| |
− | head := nil
| |
− | else
| |
− | tail.next := nil;</source> }}
| |
− |
| |
− | * '''''Вставка элемента перед текущим''''' <br />
| |
− | [[Изображение:Вставка_перед_текущим.gif]] <br />
| |
− | {{Hider
| |
− | |title = Код
| |
− | |content = <source lang="Pascal">if cur = head then
| |
− | // вставка в начало
| |
− | else
| |
− | begin
| |
− | cur.prev := new Node<T>(3, cur.prev, cur);
| |
− | cur.prev.prev.next := cur.prev;
| |
− | end;</source>}}
| |
− |
| |
− | * '''''Вставка элемента после текущего''''' <br />
| |
− | [[Изображение:Вставка_после_текущего.gif]] <br />
| |
− | {{Hider
| |
− | |title = Код
| |
− | |content = <source lang="Pascal">if cur = tail then
| |
− | // вставка в конец
| |
− | else
| |
− | begin
| |
− | cur.next := new Node<T>(3, cur, cur.next);
| |
− | cur.next.next.prev := cur.next;
| |
− | end;</source> }}
| |
− |
| |
− | * '''''Удаление текущего''''' <br />
| |
− | [[Изображение:Удаление_текущего.gif]] <br />
| |
− | {{Hider
| |
− | |title = Код
| |
− | |content = <source lang="Pascal">if cur = head then
| |
− | // удаление из начала
| |
− | else if cur = tail then
| |
− | // удаление из конца
| |
− | else
| |
− | begin
| |
− | cur.prev.next := cur.next;
| |
− | cur.next.prev := cur.prev;
| |
− | cur := cur.next;
| |
− | end;</source>}}
| |
− |
| |
− | * '''''Проход по списку''''' <br />
| |
− | Проход по списку в ''прямом порядке'' аналогичен этой операции для односвязных списков.
| |
− | <br />Проход в ''обратном порядке'' можно организовать заменой:
| |
− | *<tt>head</tt> на <tt>tail</tt>
| |
− | *<tt>next</tt> на <tt>prev</tt>
| |
− |
| |
− | <xh4>Класс двусвязного линейного списка</xh4>
| |
− | Ясно, что удобно оформить все операции в виде подпрограмм. Но тогда каждый раз в качестве параметров надо передавать ссылки на начало и конец списка.
| |
− | <br />Создадим класс ''двусвязный линейный список'', полями которого будут <tt>head</tt> и <tt>tail</tt>:
| |
− | <source lang="Pascal">type
| |
− | Node<T> = class
| |
− | data: T;
| |
− | prev, next: Node<T>;
| |
− |
| |
− | constructor (d: T; p, n: Node<T>);
| |
− | begin
| |
− | data := d;
| |
− | prev := p;
| |
− | next := n;
| |
− | end;
| |
− | end;
| |
− |
| |
− | DoubleLinkedList<T> = class
| |
− | head, tail: Node<T>;
| |
− |
| |
− | constructor;
| |
− | begin
| |
− | head := nil;
| |
− | tail := nil;
| |
− | end;
| |
− |
| |
− | procedure AddFirst(d: T);
| |
− | begin
| |
− | head := new Node<T>(d, nil, head);
| |
− | if head.next <> nil then
| |
− | head.next.prev := head
| |
− | else // если список был пуст
| |
− | tail := head;
| |
− | end;
| |
− | procedure AddLast(d: T);
| |
− | begin
| |
− | tail := new Node<T>(d, tail, nil);
| |
− | if tail.prev <> nil then
| |
− | tail.prev.next := tail
| |
− | else // если список был пуст
| |
− | head := tail;
| |
− | end;
| |
− |
| |
− | procedure DeleteFirst;
| |
− | begin
| |
− | head := head.next;
| |
− | if head = nil then
| |
− | tail := nil
| |
− | else
| |
− | head.prev := nil;
| |
− | end;
| |
− | procedure DeleteLast;
| |
− | begin
| |
− | tail := tail.prev;
| |
− | if tail = nil then
| |
− | head := nil
| |
− | else
| |
− | tail.next := nil;
| |
− | end;
| |
− |
| |
− | procedure InsertBefore(cur: Node<T>; d: T);
| |
− | begin
| |
− | if cur = head then
| |
− | AddFirst(d)
| |
− | else
| |
− | begin
| |
− | cur.prev := new Node<T>(d, cur.prev, cur);
| |
− | cur.prev.prev.next := cur.prev;
| |
− | end;
| |
− | end;
| |
− | procedure InsertAfter(cur: Node<T>; d: T);
| |
− | begin
| |
− | if cur = tail then
| |
− | AddLast(d)
| |
− | else
| |
− | begin
| |
− | cur.next := new Node<T>(d, cur, cur.next);
| |
− | cur.next.next.prev := cur.next;
| |
− | end;
| |
− | end;
| |
− |
| |
− | function RemoveAt(cur: Node<T>): Node<T>;
| |
− | begin
| |
− | if cur = head then
| |
− | begin
| |
− | DeleteFirst;
| |
− | Result:=head;
| |
− | end
| |
− | else if cur = tail then
| |
− | begin
| |
− | DeleteLast;
| |
− | Result:=nil;
| |
− | end
| |
− | else if cur = tail then
| |
− | begin
| |
− | DeleteLast;
| |
− | result := nil;
| |
− | end
| |
− | else
| |
− | begin
| |
− | cur.prev.next := cur.next;
| |
− | cur.next.prev := cur.prev;
| |
− | result := cur.next;
| |
− | end;
| |
− | end;
| |
− |
| |
− | procedure Print;
| |
− | begin
| |
− | var cur := head;
| |
− | while cur <> nil do
| |
− | begin
| |
− | writeln(cur.data);
| |
− | cur := cur.next;
| |
− | end;
| |
− | end;
| |
− | procedure PrintReverse;
| |
− | begin
| |
− | var cur := tail;
| |
− | while cur <> nil do
| |
− | begin
| |
− | writeln(cur.data);
| |
− | cur := cur.prev;
| |
− | end;
| |
− | end;
| |
− | end;</source>
| |
− | ''<u>Пример</u>''.
| |
− | <br />Дан двусвязный линейный список с целыми значениями. <br />Удалить все его отрицательные элементы.
| |
− | <source lang="Pascal">var list: DoublyLinkedList<integer>;
| |
− | // создание списка
| |
− |
| |
− | var cur := list.head;
| |
− | while cur <> nil do
| |
− | if cur.data < 0 then
| |
− | cur := list.RemoveAt(cur)
| |
− | else
| |
− | cur := cur.next;</source>
| |
− |
| |
− | ====Сравнение списков и массивов ====
| |
− | по количеству операция (n - кол-во элементов)
| |
− |
| |
− | {| {{prettytable}}
| |
− | |- style="background: #DDFFDD;"
| |
− | !
| |
− | ! Массив
| |
− | ! Список
| |
− | |-
| |
− | | Вставка в конец, удаление из конца
| |
− | | <math>\Theta (1)</math>
| |
− | | <math>\Theta (1)</math>
| |
− | |-
| |
− | | Вставка в начало, удаление из начала
| |
− | | <math>\Theta (n)</math>
| |
− | | <math>\Theta (1)</math>
| |
− | |-
| |
− | | Вставка в середину, удаление из середины
| |
− | | <math>\Theta (n)</math>
| |
− | | <math>\Theta (1)</math>
| |
− | |-
| |
− | | Проход
| |
− | | <math>\Theta (n)</math>
| |
− | | <math>\Theta (n)</math>
| |
− | |-
| |
− | | Доступ по индексу
| |
− | | <math>\Theta (1)</math>
| |
− | | <math>\Theta (i)</math>
| |
− | |-
| |
− | | Поиск
| |
− | | <math>\Theta (n)</math>
| |
− | | <math>\Theta (n)</math>
| |
− | |}
| |
Оперативная память состоит из последовательный ячеек. Каждая ячейка имеет номер, называемый адресом.
В 32-битных системах можно адресовать 232 байт (<math>\approx \;</math> 4Гб) памяти, в 64-битных — 2 64 соответственно.
Бестиповому указателю можно присвоить адрес переменной любого типа, т.е. бестиповой указатель совместим по присваиванию с любым типовым указателем.
Область памяти, на которую указывает p трактуется как область, хранящее вещественное число (8 байт), и потому константа 3.14 записывается в эти 8 байт. Однако, переменная i занимает только 4 байта, поэтому затираются еще 4 соседних байта (в данном случае они принадлежат переменной j).
В дополнение к статической и автоматической памяти, которые фиксированы после запуска программы, программа может получать нефиксированное количество динамической памяти. Ограничения на объём выделяемой динамической памяти связаны лишь с настройками операционной системы и объемом оперативной памяти компьютера.
Основная проблема — явно выделенную динамическую память необходимо возвращать, иначе не хватит памяти другим программам.
По окончании работы программы, вся затребованная программой динамическая память возвращается ОС.
Но лучше освобождать динамическую память явно! Иначе в процессе работы программы она может занимать большие объёмы (ещё не освобождённой) памяти, что вредит общей производительности системы.
1.
2.
2a.
Утечка памяти в подпрограмме: обычно если динамическая память выделяется в подпрограмме, то она должна в этой же подпрограмме возвращаться. Исключение составляют т.н. "создающие" п/п:
Ответственность за удаление памяти, выделенной в подпрограмме, лежит на программисте, вызвавшем эту подпрограмму.
3.
4.