Использование указателей в Delphi - PullRequest
6 голосов
/ 27 февраля 2009

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

Так в чем же преимущества указателей? Приложение работает быстрее или использует меньше ресурсов?

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

Ответы [ 4 ]

30 голосов
/ 27 февраля 2009

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

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

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

var
  myClass : TMyClass;
begin
  myClass := TMyClass.Create;

myClass - это указатель на объект.

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

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

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

unit X;

interface

var
  MyVar: Integer;

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

Локальные переменные:

procedure Test;
var
  MyVar: Integer;

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

Переменные кучи:

procedure Test2;
var
  MyClass: TMyClass;
begin
  MyClass := TMyClass.Create;

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

Лучшие практики

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

Например:

var
  myClass: TMyClass;
begin
  myClass := TMyClass.Create;
  try
    DoSomething(myClass);
    DoSomeOtherthing(myClass);
  finally
    myClass.Free;
  end;
end;

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

10 голосов
/ 27 февраля 2009

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

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

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

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

В windows.pas есть тип TBitmapInfo :

type
  PBitmapInfo = ^TBitmapInfo;
  tagBITMAPINFO = packed record
    bmiHeader: TBitmapInfoHeader;
    bmiColors: array[0..0] of TRGBQuad;
  end;
  TBitmapInfo = tagBITMAPINFO;

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

var
  BmpInfo: PBitmapInfo;
begin
  // some other code determines width and height...
  ...
  BmpInfo := AllocMem(SizeOf(TBitmapInfoHeader)
    + BmpWidth * BmpHeight * SizeOf(TRGBQuad));
  ...
end;

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

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

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

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

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

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

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

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

procedure MakeSpacesVisible(const AValue: AnsiString): AnsiString;
var
  i: integer;
begin
  Result := AValue;
  for i := 1 to Length(Result) do begin
    if Result[i] = ' ' then
      Result[i] := $B7;
  end;
end;

с этим кодом

procedure MakeSpacesVisible(const AValue: AnsiString): AnsiString;
var
  P: PAnsiChar;
begin
  Result := AValue;
  P := PAnsiChar(Result);
  while P[0] <> #0 do begin
    if P[0] = ' ' then
      P[0] := $B7;
    Inc(P);
  end;
end;

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

2 голосов
/ 27 февраля 2009

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

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

2 голосов
/ 27 февраля 2009

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

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

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

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