Объявление и инициализация указателей
Рассмотрим процесс объявления указателей. Компьютеру необходимо отслеживать тип значения, на которое ссылается указатель. Например, адрес переменной типа char выглядит так же, как и адрес переменной типа real, но для типов char и real используется различное количество байтов и различные внутренние форматы для хранения значений. Поэтому при объявлении указателя необходимо точно определять, на какой тип данных он указывает. Последний пример содержит следующее объявление: p_updates: ^Integer; Это выражение указывает, что комбинация p_updates^ принадлежит к типу Integer. Поскольку операция ^ выполняется по отношению к указателю, переменная p_updates сама должна быть указателем. Мы говорим, что p_updates указывает на значение типа Integer. Мы также говорим, что типом для p_updates должен быть указатель на Integer. Повторим: p_updates является указателем (адресом), a p_updates^ — переменной типа Integer, но не указателем. Для объявления указателей на другие типы переменных используется аналогичный синтаксис: tax_ptr: ^Real; //tax_ptr указывает на тип Real str: ^Char; //str указывает на тип Char Поскольку tax_ptr объявляется как указатель на тип Real, компилятор определяет, что значение tax_ptr^ принадлежит к типу Real. Другими словами, он распознает, что tax_ptr^ представляет собой величину, которая хранится в формате с плавающей точкой и занимает (в большинстве систем) восемь байтов. Переменная-указатель никогда не бывает просто указателем. Она всегда указывает на определенный тип данных. Так, переменная tax_ptr имеет тип "указатель на тип Real ", a str — "указатель на тип char ". Несмотря на то, что обе переменные являются указателями, они указывают на два различных типа данных. Подобно массивам, указатели являются производными от других типов данных.
Заметим, что в то время, как tax_ptr и str указывают на типы данных, имеющие различную размерность, сами по себе две переменные — tax_ptr и str — имеют одинаковый размер. Иными словами, адрес переменной типа char имеет тот же размер, что и адрес переменной типа Real (точно так же, как номер дома 1016 может означать адрес большого универмага, а 1024 — адрес маленького коттеджа). По размеру или значению адреса ничего нельзя сказать о размере или типе переменной, так же, как и по номеру дома — о здании, которое находится по этому адресу. Обычно для хранения адреса требуются два или четыре байта, в зависимости от компьютера (в некоторых системах могут использоваться более длинные адреса или адреса различной длины для различных типов данных). Для инициализации указателя можно использовать оператор объявления. В этом случае инициализируется указатель, а не указываемое значение. Таким образом, следующие операторы: Var higgens: Integer = 5; pt: ^Integer = @higgens; присваивают указателю pt адрес переменной higgens. Программа 3_6 // инициализация указателя program Project1;
{$APPTYPE CONSOLE}
Uses SysUtils; Var higgens: Integer = 5; pi: ^Integer = @higgens; Begin Writeln('Value of higgens = ', higgens, '; Address of higgens = ', Integer(@higgens)); Writeln('Value of pi^ = ', pi^, '; Address of pi = ', Integer(pi)); Readln; end. Результат: Value of higgens = 5; Address of higgens = 4237700 Value of pi^ = 5; Address of pi = 4237700 Можно видеть, что программа присваивает адрес переменной higgens указателю pi, но не значению pi^. Отметим однако, что неосторожное использование указателей может привести к нежелательным последствиям. Крайне важно не забывать о том, что при создании указателя компьютер выделяет память для хранения адреса, но не для хранения данных, на которые указывает этот адрес. Чтобы выделить места для данных, требуется дополнительное действие. Если вы такое действие не выполните (как в приведенном ниже примере), то окажетесь на прямом пути к краху:
Var fellow: ^Integer; //создание указателя на переменную типа Integer Begin fellow^:= 5; //помещение значения по неопределенному адресу Конечно, переменная fellow является указателем. Но на что она указывает? Программа не присвоила ей адрес. Куда же будет помещено значение 5? Мы не в состоянии ответить на этот вопрос. Поскольку переменная fellow не была инициализирована, она может принять любое значение. Какое бы значение она ни имела, программа будет интерпретировать его как адрес, по которому нужно сохранить число 5. Если fellow имеет значение 1200, то компьютер попытается поместить данные по адресу 1200, даже если окажется, что этот адрес находится в середине кода программы. Понятно одно: куда бы ни указывал указатель fellow, это не то место, куда вы намерены поместить число 5. Ошибки подобного рода коварны. Кроме того, их сложно выявлять. Золотое правило для указателей: ВСЕГДА присваивайте указателю определенное и правильное значение адреса до применения к нему операции разыменования (^). Выделение памяти с помощью оператора New Теперь, когда вы имеете некоторое представление о том, как работают указатели, давайте посмотрим, как они могут реализовать такую важную технологию ООП, как распределение памяти во время выполнения программы. До сих пор мы присваивали указателям адреса переменных. Переменная является именованной областью памяти, которая резервируется во время компиляции, а указатели всего лишь служат псевдонимами для областей памяти, к которым можно в любом случае обратиться напрямую по имени. Истинная ценность указателей проявляется тогда, когда для хранения данных выделяется неименованная область памяти во время выполнения программы. В этом случае указатели становятся единственным способом доступа к этим областям памяти. Для этого существует оператор new. Попробуем использовать эту новую технологию — создадим неименованное хранилище значений типа
Var pi: ^Integer; Begin New(pi); Выражение New(pi) сообщает программе о том, что необходимо некоторое новое хранилище, подходящее для размещения значения типа Integer. Оператор new анализирует тип для вычисления необходимого количества байтов. Затем он отыскивает область памяти и возвращает адрес. Далее присваиваем адрес переменной pi, которая объявляется как указатель на тип Integer. Теперь рi является адресом, а рi^ — значением, хранящимся по этому адресу. Сравним это с присваиванием указателю адреса переменной: Var pt: ^Integer; higgens: Integer; Begin pi:= @higgens; В обоих примерах (рi и pt) указателю присваивается адрес переменной типа Integer. Во втором случае можно также обращаться к переменной типа Integer по имени: higgens. В первом же случае обращение возможно исключительно через указатель. Возникает вопрос: так как область памяти, на которую указывает рi, не имеет имени, как же к ней обращаться? Мы говорим, что рi указывает на объект данных. Но это не "объект" в смысле "объектно-ориентированного программирования"; это всего лишь "объект" в смысле "предмет". "Объект данных", под которым понимается любой блок памяти, выделенный для хранения элементарной группы данных, является более общим понятием, чем "переменная". Поэтому переменная также является объектом данных, но память, на которую указывает рi, не является переменной. Способ управления объектами данных с помощью указателей может показаться на первый взгляд довольно неудобным, однако он обеспечивает большие возможности по управлению организацией памяти в программе. Программа 3_7 // использование оператора new program Project1;
{$APPTYPE CONSOLE}
Uses SysUtils; Var pt: ^Integer; pd: ^Real;
Begin New(pt); //выделение области памяти для значений типа Integer pt^:= 1001; //место хранения значения Writeln('Integer value = ', pt^, ': location = ', Integer(pt)); New(pd); //выделение области памяти для Real pd^:= 10000001.0; //место хранения значения типа Real
Writeln('Real value = ', pd^:1:1, ': location = ', Integer(pd)); Write('size of pt = ', SizeOf(pt)); Writeln(': size of pt^ = ', SizeOf(pt^)); Write('size of pd = ', SizeOf(pd)); Writeln(': size of pd^ = ', SizeOf(pd^)); Dispose(pt); //освобождаем память Dispose(pd);
Readln; end. Результат: Integer value = 1001: location = 10571360 Real value = 10000001.0: location = 10571376 size of pt = 4: size of pt^ = 4 size of pd = 4: size of pd^ = 8 Примечания к программе Программа использует оператор new для выделения памяти объектам данных типа Integer и Real. Это происходит при выполнении программы. Указатели pt и pd указывают на эти два объекта данных. Без них невозможно обращаться к выделенным участкам памяти. Благодаря указателям, можно использовать выражения pt^ и pd^ просто как переменные. Значения выражениям pt^ и pd^, присваиваются для того, чтобы присвоить значения новым объектам данных. Аналогично, выражения pt^ и pd^ используются для отображения этих значений с помощью оператора WriteLn. Программа также демонстрирует одну из причин, по которой необходимо объявлять тип данных, на которые указывает указатель. Адрес сам по себе определяет только начало области памяти, в которой хранится объект, но не его тип или занимаемый размер. Взгляните на адреса двух значений из нашего примера. Они — всего лишь числа без обозначения типа или размера. К тому же заметим, что размерность указателя на тип Integer совпадает с размерностью указателя на тип Real: оба являются просто адресами. Но поскольку в программе объявлены типы указателей, программа знает, что значение pd^ относится к типу Real с размерностью восемь байтов, в то время как значение pt^ относится к типу Integer размерностью четыре байта. Когда программа выводит значение pd^, оператор WriteLn определяет, сколько байтов нужно прочесть и как их представить.
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|