Delphi указатель на объект

Delphi указатель на объект

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

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

Описание переменных — указателей

Указатель описывается ключевым словом Pointer. По первой букве ключевого слова принято называть переменные — указатели с первой буквы P:

var PIndexer: Pointer; //нетипизированный указатель

Ключевым словом Pointer задаётся так называемый нетипизированный указатель, по аналогии с нетипизированным файлом. Нетипизированный указатель содержит просто адреc некой ячейки памяти. Объект, располагающийся начиная с этой ячейки, соответственно может быть совершенно любого типа и размера.

Также в Delphi существуют и типизированные указатели. Они могут указывать на объект соответствующего типа и размера. Именно "могут указывать", потому что это по прежнему адрес одной — первой ячейки области памяти, где располагается объект. И далее его использование в программе зависит от программиста!
Итак, типизированный указатель описывается ключевым словом означающим данный тип, перед которым ставится значок ^ :

var PInteger: ^Integer; //указатель на переменную целого типа
PText: ^String; //указатель на переменную типа String

Также можно описать любой свой тип, и задать переменную-указатель данного типа:

type TMyType = Record
X: Integer;
S: String;
end;

var PMyPointer: ^TMyType;

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

type TPMyPointer = ^TMyType;

function MyFunc(Point: TMyPointer): TMyPointer;

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

Использование переменных — указателей

Использование указателей предполагает:

  1. присвоение значения указателю;
  2. изменение значения указателя;
  3. создание области памяти нужного типа и присвоение его адреса указателю;
  4. запись значения в область памяти, адресуемой указателем, и чтение из неё;
  5. освобождение области памяти, адресуемой данным указателем.

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

1. Указателю можно присвоить значение другого указателя. В результате оба указателя будут указывать на одну и ту же ячейку памяти. Также указателю можно присвоить пустое значение с помощью ключевого слова nil :

var P1, P2: Pointer;
begin
P1:=P2; //Присвоение указателю значения другого указателя
P2:=nil; //Присвоение указателю "пустого" значения
end;

Указатель со значением nil не адресует никакой ячейки памяти и единственное, что с ним можно сделать — это сравнить с другим указателем или со значением nil .

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

type P: ^Integer;
begin
inc(P); //увеличение значения указателя на 4 байта (размер типа Integer)
dec(P); //уменьшение значения указателя на 4 байта (размер типа Integer)
end;

Попытка выполнить операциии inc либо dec с нетипизированным указателем вызовет ошибку на этапе компиляции, так как компилятору неизвестно, насколько именно изменять значение указателя.

3. Процедурой New можно создать область памяти сответствующего типа и присвоить её адрес указателю (инициировать указатель):

var PInt: ^Integer;
begin
New(PInt); //Указатель PInt получает значение адреса созданной области памяти типа Integer
end;

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

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

var MyVar: TMyType; //Описание переменной, при этом выделяется область памяти соответствующего размера
P: ^TMyType; //Задаётся указатель соответствующего типа
begin
P:=@MyVar; //Указатель получает адрес области памяти, занимаемой переменной MyVar
end;

4. Если область памяти уже создана и её адрес присвоен указателю, то в ячейку памяти, адресуемую данным указателем, можно записать значение объекта, соответствующего типу указателя. Для этого служит операция, обозначаемая также значком ^ , стоящим после имени указателя, например: P^ . Эта операция называется "разыменование указателя". Также с помощью этой операции со значением в данной ячейке памяти можно делать всё что нужно:

Читайте также:  Программа убирает водяные знаки с фото

var MyVar: Integer;
P: ^Integer;
begin
P:=@MyVar; //Указатель получает адрес области памяти, занимаемой переменной MyVar
P^:=2; //В ячейку памяти по адресу переменной MyVar записывается значение 2
Form1.Caption:=IntToStr(P^+3); //В заголовке Формы появится число 5
end;

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

type TMyRec = Record
N: Integer;
S: String;
end;

var MyRec: TMyRec;
PRec: ^TMyRec;

begin
PRec:=@MyRec; //Указатель получает адрес области памяти, занимаемой переменной MyRec
PRec^.S:=’Строка данных’; //С помощью указателя производится изменение строкового поля записи
PRec^.N:=256; //С помощью указателя производится изменение числового поля записи
end;

А теперь уберите стрелку возле PRec: PRec.S:=’Строка данных’; Вы увидите, что никакой ошибки ни компилятор, ни выполнение программы не показали! Выходит, выражения PRec^.S и PRec.S аналогичны.

Далее, а как получить значение, если указатель это элемент массива, например:

var PArray: Array[1..100] of ^Integer;
X: Integer;

Ни PArray^[10] , ни PArray[10]^ не являются правильными выражениями. Ну конечно, нужно использовать скобки:

5. Память, выделенную процедурой New, всегда нужно явно освобождать. Освободить область памяти, адресуемую указателем, инициированным с помощью New, можно процедурой Dispose :

var MyVar: TMyType;
P: ^TMyType;
begin
P:=@MyVar;
Dispose(P); //Освобождение области памяти, адресуемой указателем P
end;

При выполнении процедуры Dispose указатель снова приобретает неопределённое значение, не равное даже nil, и его использование может привести к неопределённым результатам, даже к краху программы.

Указатели в Delphi

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

Но прежде чем что-то объяснять, рассмотрим, зачем нужны указатели. Давайте вспомним про процедуры, а именно, как происходит их вызов. Допустим, у вас есть процедура с именем муРгос, у которой есть два параметра: число и строка. Как происходит вызов такой процедуры и как ей передаются эти параметры? Очень просто. Сначала параметры принимаются в стек (напомню, что стек — это область памяти для хранения временных или локальных переменных). Первым заносится первый параметр, затем второй и после этого вызывается процедура. Прежде чем процедура начнет свое выполнение, она извлекает эти параметры из стека в обрат­ном порядке.

Теперь вспомним о наших параметрах. Первый — это число, которое будет за­нимать 2 байта. Когда происходит его запись в стек, оно займет там свои положен­ные 2 байта. Второй параметр — строка. Каждый символ строки — это отдельный байт. Допустим, что строка состоит из 10 символов. Это значит, что для передачи такой строки в процедуру в стеке понадобится 10 байт плюс 1 байт для указания конца строки или ее размера (это зависит от типа строки). Всего для передачи в процедуру понадобится в стеке как минимум 12 байт. Это не так уж и много, по­этому такое можно себе позволить.

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

А если нам нужно передать в процедуру фотографию размером в 3 мегабайта? Что ее тоже копировать в стек? Несколько фотографий высокого качества — и стек закончится.

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

Указатель в Delphi объявляется как Pointer. Например, давайте объявим пере­менную р типа указатель:

var р:Pointer

Для того чтобы получить адрес переменной или объекта, необходимо перед его именем поставить знак Например, у вас есть строка str, и чтобы присвоить ее адрес в указатель р, надо выполнить следующее: p:=@str. Теперь в указателе нахо­дится адрес строки. Если вы будете напрямую читать указатель, то увидите адрес, а для того чтобы увидеть содержащиеся по этому адресу данные, надо разымено­вывать указатель. Для этого надо написать р^. Итак, мы пришли к следующему:

  • р: = @str — получить адрес строки;
  • р — указатель на строку;
  • р ^ — данные, содержащиеся по адресу, указанному в р.

Давайте создадим маленькое приложение, которое будет работать с указателя­ми. Для этого на форму надо поместить кнопку с заголовком "Работа со ссылками" и строку ввода.

Для события — нажатие кнопки Работа со ссылками — напишем код, пред­ставленный в листинге:

Читайте также:  Как приклеить крючок в ванной на плитку

var р:Pointer Str:String; begin

p:=@Str; // Присваиваю указателю ссылку на строку Str:=’Привет мой друг’; // Изменяю значение строки Edit1.

Text: =String (р74) ; // Вывожу текст end;

В этом примере в первой строке мы присваиваем указателю р ссылку на стро­ку str. После этого меняем содержимое строки. В последней строке выводится содержащийся по адресу р текст. Для этого приходится явно указывать, что по адресу р находится именно строка string (р^). Вспомните приведение схожих типов. Таким же образом можно приводить строку string к pchar. Здесь указы­вается, что по адресу переменной р находится строка, а не какой-нибудь другой тип данных. Это необходимо, потому что данные, расположенные по определен­ному адресу, могут иметь совершенно любой тип. Как видите, "жесткое" указа­ние типа похоже на преобразование типов, поэтому никаких проблем с этим не должно возникнуть.

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

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

Любой переменной-указателю можно присвоить нулевое значение, только это не число 0, a nil — нулевой указатель, например, p:=nil (в принципе, nil это тот же О, просто используется для обнуления указателей). Когда вы присваиваете нулевое зна­чение, то как бы уничтожаете ссылку. Точно так же, если переменной-объекту при­своить нулевое значение, вы его уничтожите. Никогда не обнуляйте переменные ука­затели, которые указывают на существующие объекты. Сначала уничтожьте объекты (освободите память), а потом можно указателю присвоить nil.

А зачем указателям присваивать нулевое значение? В принципе, на память это не влияет, но поможет с точки зрения безопасности. Дело в том, что после освобо­ждения памяти, указатель содержит какое-то число (какой-то адрес), но данные по этому адресу уже не действительны. Если обратиться по ним, то может произойти ошибка. Чтобы было видно, что указатель недействительный, после освобождения обнуляйте указатель.

До Windows 2000 в программах Windows можно было использовать один инте­ресный трюк— после освобождения памяти, ее еще можно было использовать в течение короткого промежутка времени (пока другая программа не выделит себе этот же участок). Но потом разработчики посчитали, что это небезопасно, и запре­тили использование памяти после ее освобождения.

6 Jlouro [2009-02-27 18:17:00]

Я развиваюсь уже некоторое время, и я пока не использовал указатели в своем развитии.

Итак, каковы преимущества указателей? Выполняется ли приложение быстрее или использует меньше ресурсов?

Поскольку я уверен, что указатели важны, можете ли вы "указать" мне на некоторые статьи, основные, но хорошо начать использовать указатели в Delphi? Google дает мне слишком много, слишком специальные результаты.

4 ответа

30 Решение Toon Krijthe [2009-02-27 18:24:00]

Указатель — это переменная, которая указывает на часть памяти. Преимущества:

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

Delphi использует много скрытых указателей. Например, если вы используете:

myClass является указателем на объект.

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

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

Например, глобальные переменные:

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

Локальная переменная существует в стеке. Это часть памяти, которая используется для домашнего хозяйства. Он содержит параметры для функции (ok некоторые помещаются в регистр, но это не важно сейчас). Он содержит обратный адрес, поэтому CPU знает, куда вернуть, если программа закончилась. И он содержит локальные переменные, используемые в функциях. Локальные переменные существуют только в течение жизни функции. Если функция завершена, вы не можете получить доступ к локальной переменной надежным способом.

Переменная MyClass — это указатель (локальная переменная, которая определена в стеке). Построив объект, вы выделяете кусок памяти в кучу (большая часть "другой" памяти, которая не используется для программ и стеков). Переменная MyClass содержит адрес этой части памяти. Переменные кучи существуют до тех пор, пока вы их не отпустите. Это означает, что если вы выйдете из теста Test2 без освобождения объекта, объект все еще существует в куче. Но вы не сможете получить к нему доступ, потому что адрес (переменная MyClass) отсутствует.

Рекомендации

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

Читайте также:  Asus geforce gtx 660 2gb gddr5

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

10 mghie [2009-02-27 22:12:00]

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

Позвольте мне привести два примера использования указателя в Delphi. Вы увидите, что это, вероятно, совсем не подходит для вас, если вы в основном пишете бизнес-приложения. Однако может стать важным, если вам когда-либо понадобится использовать функции Windows или сторонних API, которые не импортируются ни одним из стандартных модулей Delphi, и для которых не существует единиц импорта в (например) библиотеках JEDI. И это может быть ключом к достижению необходимого необходимого бита скорости в коде обработки строк.

Указатели могут использоваться для обработки типов данных различного размера (неизвестно во время компиляции)

Рассмотрим тип данных растрового изображения Windows. Каждое изображение может иметь разную ширину и высоту, и существуют разные форматы: от черного и белого (1 бит на пиксель) от 2 ^ 4, 2 ^ 8, 2 ^ 16, 2 ^ 24 или даже от 2 ^ 32 серых значений или цветов, Это означает, что во время компиляции неизвестно, сколько памяти займет растровое изображение.

В windows.pas существует тип TBitmapInfo:

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

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

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

И, конечно, гораздо проще просто создать TBitmap и назначить его ширину, высоту и формат пикселей. Чтобы снова указать его, Delphi VCL устраняет большинство случаев, когда в противном случае указатели были бы необходимы.

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

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

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

Неподобное свойство строк состоит в том, что они подсчитываются по ссылке. Каждая операция, которая может изменить строку, должна убедиться, что счетчик ссылок равен 1, поскольку в противном случае модификация строки была бы опасной. Замена символа в строке является такой модификацией. Чтобы убедиться, что счетчик ссылок равен 1, вызов UniqueString() добавляется компилятором всякий раз, когда записывается символ в строке. Теперь запись n символов строки в цикле приведет к тому, что UniqueString() будет вызываться n раз, даже если после первого раза обеспечивается, что счетчик ссылок равен 1. Это означает, что в основном выполняется n — 1 вызовов UniqueString() без необходимости.

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

Во второй функции будет только один вызов UniqueString(), когда адрес первого символа строки присваивается указателю char.

2 The_Fox [2009-02-27 18:29:00]

Вероятно, вы использовали указатели, но вы просто этого не знаете. Переменная класса — это указатель, строка — указатель, динамический массив — указатель, Delphi просто скрывает его для вас. Вы увидите их, когда выполняете вызовы API (кастинг строк на PChar), но даже тогда Delphi может скрыть много.

См. ответ Gamecats за преимущества указателей.

В этом Статьи About.com вы можете найти основное объяснение указателей в Delphi.

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

Это относится ко многим языкам, включая Object Pascal (Delphi).

Ссылка на основную публикацию
Adblock detector