Указатели - вещь незаменимая во многих случаях. Позволю себе привести некоторые:
1) Адресная арифметика - позволяет увеличивать или уменьшать значение указателя на размер его типа, то есть позволяет "путешествовать" по памяти. Конечно, можно и не на размер типа увеличивать адрес в указателе, если указатель приводить к нужному типу при арифметических операциях.
Классический пример - вычисление длины строки PAnsiChar. Строка оканчивается символом #0, а все символы расположены линейно по порядку - для подсчета длины можно просто инкрементировать указатель на текущий символ.
| Код | function StringLength(P: PAnsiChar): Cardinal; begin Result := 0; if P = NIL then Exit; while P^ <> #0 do begin Inc(Result); Inc(P); end; end;
|
2) Возможность произвольного доступа к данным, при этом код получается более наглядным.
Удобно при работе с API. Допустим, в функцию передается целая структура, но параметр числовой. Как обработать? Тут помогают указатели. Рассмотрим жутко напаренный пример, но подобные вещи в API сплошь и рядом: обработка разнообразных сообщений, hook-процедур и т.д.)
| Код | type PMyRecord = ^TMyRecord; TMyRecord = packed record FStringField: ShortString; FIntField: Integer; end;
....
procedure MyProc(Param: Integer); begin with PMyRecord(Param)^ do begin ShowMessage(FStringField); ShowMessage(IntToStr(FIntField)); end; end;
procedure TForm1.Button1Click(Sender: TObject); var Rec: TMyRecord; begin Rec.FStringField := 'Text'; Rec.FIntField := 10; MyProc(Integer(@Rec)); end;
|
В своей практике я очень часто использую адресную арифметику вместе с произвольным доступом, например, для путешествия по элементам таблиц Windows
3) Еще одна интересная особенность - с указателями часто можно выиграть в быстродействии. Рассмотрим две функции, обе принимают большой статический массив и что-нибудь с ним делают.
| Код | type PMyArray = ^TMyArray; TMyArray = array[1..10000] of Integer; ... procedure Proc1(Value: PMyArray); var I :Integer; begin for I := 1 to High(Value^) do Value^[I] := 1; end;
procedure Proc2(Value: TMyArray); var I: Integer; begin for I := 1 to High(Value) do Value[I] := 1; end;
|
Замерим их примерное быстродействие через GetThreadTimes.
| Код | procedure TForm1.Button2Click(Sender: TObject);
function FileTimeToQuadWord(FileTime: TFileTime): Int64;
function Int64ShllMod32(Value: Int64; ShiftCount: DWORD) :Int64; begin Result := Value shl ShiftCount; end;
begin Result := Int64ShllMod32(FileTime.dwHighDateTime, 32) or FileTime.dwLowDateTime; end;
var ftKernelTimeStart, ftKernelTimeEnd: TFileTime; ftUserTimeStart, ftUserTimeEnd: TFileTime; ftDummy: TFileTime; qwKernelTimeElapsed1, qwUserTimeElapsed1, qwTotalTimeElapsed1: Int64; qwKernelTimeElapsed2, qwUserTimeElapsed2, qwTotalTimeElapsed2: Int64; I: Integer; Arr: TMyArray; begin { Часть 1 } GetThreadTimes(GetCurrentThread, ftDummy, ftDummy, ftKernelTimeStart, ftUserTimeStart);
// Начало кода for I := 0 to 100000 do { Принимает указатель } Proc1(@Arr); // Конец кода
GetThreadTimes(GetCurrentThread, ftDummy, ftDummy, ftKernelTimeEnd, ftUserTimeEnd); qwKernelTimeElapsed1 := FileTimeToQuadWord(ftKernelTimeEnd) - FileTimeToQuadWord(ftKernelTimeStart); qwUserTimeElapsed1 := FileTimeToQuadWord(ftUserTimeEnd) - FileTimeToQuadWord(ftUserTimeStart); qwTotalTimeElapsed1 := qwKernelTimeElapsed1 + qwUserTimeElapsed1; { Часть 2 } GetThreadTimes(GetCurrentThread, ftDummy, ftDummy, ftKernelTimeStart, ftUserTimeStart);
// Начало кода for I := 0 to 100000 do { Принимает массив } Proc2(Arr); // Конец кода
GetThreadTimes(GetCurrentThread, ftDummy, ftDummy, ftKernelTimeEnd, ftUserTimeEnd); qwKernelTimeElapsed2 := FileTimeToQuadWord(ftKernelTimeEnd) - FileTimeToQuadWord(ftKernelTimeStart); qwUserTimeElapsed2 := FileTimeToQuadWord(ftUserTimeEnd) - FileTimeToQuadWord(ftUserTimeStart); qwTotalTimeElapsed2 := qwKernelTimeElapsed2 + qwUserTimeElapsed2; { Результат } ShowMessage(IntToStr(Round((qwTotalTimeElapsed2 - qwTotalTimeElapsed1) / qwTotalTimeElapsed2 * 100)) + '%'); end;
|
У меня разница в быстродействии примерно 40%, то есть функция, принимающая указатель, работает быстрее. Подумай сам, почему так происходит
4) Добавлю еще один классический пример, где без указателей не обойтись
Допустим, нужно создать односвязный список через массив записей (10 элементов). В каждой записи ровно два поля - указатель на следующий элемент списка и данные (пусть у нас это целое число). Односвязный список подразумевает, что мы можем проходить его только в одном направлении. Следующее объявление некорректно:
| Код | type TOneWayListItem = record { Следующий элемент } FNextItem: TOneWayListItem; { Данные } FData: Integer; end;
TOneWayList = array[1..10] of TOneWayListItem;
|
| Цитата | [DCC Error] Unit1.pas(19): E2086 Type 'TOneWayListItem' is not yet completely defined
|
Решить проблему можно с помощью указателей. Дефинировать (объявлять) их можно до дефиниции самого типа. Вот рабочий вариант:
| Код | type POneWayListItem = ^TOneWayListItem; TOneWayListItem = record { Следующий элемент } FNextItem: POneWayListItem; { Данные } FData: Integer; end;
TOneWayList = array[1..10] of TOneWayListItem;
|
С таким списком нужно работать, самостоятельно выделяя и освобожая память. |