Основы программирования — второй семестр 08-09; Михалкович С.С.; II часть

Материал из Вики ИТ мехмата ЮФУ
Версия от 19:27, 26 февраля 2009; 86.110.171.42 (обсуждение) (Доступ к памяти, имеющей другое внутреннее представление)

Перейти к: навигация, поиск

Лекция 3

Указатели

Адрес

Оперативная память состоит из последовательный ячеек. Каждая ячейка имеет номер, называемый адресом.
В 32-битных системах можно адресовать 232 байт (<math>\approx \;</math> 4Гб) памяти, в 64-битных — 2 64 соответственно.

Переменная (или константа), хранящая адрес, называется указателем.

Для чего нужны указатели

Указатели повышают гибкость доступа к данным:

  1. Вместо самих данных можно хранить указатель на них. Это позволяет хранить данные в одном экземпляре и множество указателей на эти данные. Через разные указатели эти данные можно обновлять (пример — корпоративная БД).
  2. Указателю можно присвоить адрес другого объекта (вместо старого появился новый телефонный справочник).
  3. С помощью указателей можно создавать сложные структуры данных.

Подробнее об указателях

Указатели делятся на:

  • Типизированные (указывают на объект некоторого типа)
    Имеют тип: ^<тип>
    Пример. ^integer — указатель на integer
  • Бестиповые (хранят адрес ячейки памяти неизвестного типа)
    Преимущество: могут хранить что угодно
    Имеют тип: pointer

Пример кода.

var
  i: integer := 5;
  r: real := 6.14;
  
  pi: ^integer;
  pr: ^real;

begin
  pi := @i;
  pr := @r;
  pi := @r; // ОШИБКА компиляции
end.

@ — унарная операция взятия адреса <xh4>Операция разадресации (разыменования)</xh4>

var
  i: integer := 5;
  pi: ^integer;

begin
  pi := @i;
  
  pi^ := 8 - pi^;
  writeln(i); // 3
end.

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

Тут надо вспомнить определение ссылки:
Ссылка — другое имя объекта. <xh4>Нулевой указатель</xh4> Все глобальные неинициализированные указатели хранят специальное значение nil, что говорит о том, что они никуда не указывают.
Указатель, хранящий значение nil называется нулевым.

var
  pi: ^integer; //указатель pi хранит значение nil
  i: integer;

begin
  pi := @i;     //pi хранит адрес переменной i
  pi := nil;    //pi снова никуда не указывает
  
  pi^ := 7;     //ОШИБКА времени выполнения:
                //попытка разыменовать нулевой указатель

Попытка разыменовать нулевой указатель приводит к ошибке времени выполнения. <xh4>Бестиповые указатели</xh4>

var
  p: pointer;
  i: integer;

begin
  p := @i;
end.

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

Попытка разыменовать бестиповой указатель приводит к ошибке компиляции. Т.е. он может только хранить адреса.

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

var 
  pi: ^integer;
  i: integer;
  p: pointer;

begin
  p := @i;
  pi := p;
  pi^ += 2;
end.

Вопрос. Нельзя ли интерпретировать память, на которую указывает p, как принадлежащую к определенному типу?
Ответ — да, можно. Вот как это сделать:

type 
  pinteger = ^integer;
var
  i, j: integer;
  p: pointer;

begin
  p := @i;
  pinteger(p)^ := 7; //используем явное приведение типа
  writeln(i); // 7
end.

Запись

<тип>( <переменная> )

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

Внимание! Неконтролируемая ошибка!

type 
  pinteger = ^integer;
var
  i, j: integer;
  p: pointer;

begin
  p := @i;
  preal(p)^ := 3.14; //ОШИБКА!
end.

Область памяти, на которую указывает p трактуется как область, хранящее вещественное число (8 байт), и потому константа 3.14 записывается в эти 8 байт. Однако, переменная i занимает только 4 байта, поэтому затираются еще 4 соседних байта (в данном случае они принадлежат переменной j).

Доступ к памяти, имеющей другое внутреннее представление

var r: real := 3.1415;

type Rec = record
       b1, b2, b3, b4, b5, b6, b7, b8: byte;
     end;

var pr: ^Rec;

begin
  pr := pointer(@r); //Явное приведение типа
  writeln(pr^.b1, ' ', pr^.b2, ' ', ..., pr^.b8);
end.

Замечание. Важно, что типы real и Rec имеют один размер.

Динамическая память

Явное выделение динамической памяти. Процедуры New и Dispose.

Ошибки при работе с динамической памятью

  • Использование неинициализированного указателя
  • Висячие указатели
  • Утечка памяти

<xh4></xh4>

Классы-начало

Переменная типа класс как ссылка. Сравнение с записями.

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

Шаблоны классов.

Решение проблемы освобождения памяти, занимаемой объектами классов: сборка мусора (.NET, Java).

Управляемая динамическая память и ее возврат. Отсутствие утечки памяти.

Динамические структуры данных. Списки

Виды списков. Рисунки.

Односвязные линейные списки

Класс узла списка (шаблонный)

Стандартные операции с односвязными линейными списками

  • Вставка элемента в начало
  • Удаление элемента из начала
  • Вставка после текущего
  • Удаление после текущего
  • Проход по списку

Двусвязные линейные списки

Стандартные операции с двусвязными линейными списками

  • Инициализация
  • Добавление в начало, конец
  • Удаление из начала, конца
  • Вставка элемента перед текущим, после текущего
  • Удаление текущего
  • Объединение двух списков

Помещение операций по работе со списком внутрь класса