Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2019年09月26日] [更新 2021年02月11日] Delphi 一般・その他
ポインタとポインタ型
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 0650]







ポインタとポインタ型


プログラムで使用する変数の値 (データ) は全てメモリに書き込まれます。データが書き込まれるメモリ上の位置をメモリアドレス、あるいは単にアドレスと言います。
ポインタは、何かを指し示すものという意味です。変数のデータが書き込まれているアドレスを示すのがポインタです。
本ページは、このポインタに関する記事です。






01_ポインタ型


一般的にオブジェクトの操作では、メモリブロックのコピーを作成して処理が行われます。メモリブロックのコピーを作成しないでオブジェクトを操作する変数の型があります。それがポインタ型です。ポインタ型の変数は、メモリ上のデータを直接操作することになります。そのため、ポインタ型を使用すると処理が高速になります。
ポインタ型の変数はもちろんポインタです。

ポインタ型の変数はメモリのデータを直接操作しますから、ポインタとしてのアドレスが意味を持ちます。例えば、関数や手続きの引数に渡す時は「アドレス渡し」になります。一方、ポインタ型ではない変数の場合は、メモリブロックのコピーを使用しますから、アドレスという概念てははなく、その変数の値による操作となります。関数や手続きの引数では「値渡し」となります。

ポインタ型を使用した方が便利なことがあります。ポインタ型のプロパティを持つクラスもあります。Delphi の型には、多くの型にそのポインタ型が用意されています。型名の前に大文字の P があるのがポインタ型です。ポインタ型が用意されていない型でも、型宣言することでポインタ型を使用するこができます。


[備考 1]-
Delphi ではポインタ型のプリフィックスとして大文字の P が慣習的に使われています。
例えば Double 型のポインタ型は PDouble です。Char 型は 1 文字の型ですが、P を付けた PChar は Char 型のポインタ型ではありません。PChar 型は Null 終端文字列の型です。
変数の宣言の時に、変数の型名の先頭に ^ (キャレット) を付けると、その変数の型のポイント型となりますが、Char の先頭に ^ を付けても PChar 型にはなりません。

[備考 2]-
ポインタ型を使用すると処理速度が速くなると言っても、パソコンと呼ばれるコンピュータの現在のクロック数は 3 GHz 以上です。演算速度も Core i7 の 3.00 GHz の CPU では 480 GFLOPS もあります。これは CPU が 1 秒で実行する倍精度の浮動小数点の演算回数です。相当な回数の処理を行わないと、その差を確認することはできません。




02_ポインタ型の変数と逆参照


ポインタ型の変数に @ 演算子付きの変数を代入すると、そのポインタ型の変数は、アドレスの情報と変数の型情報を持つポインタとなります。

ポインタ型の変数からメモリのデータを取得したり、メモリにデータを書き込むには、逆参照と言う手法を使用します。ポインタ型の変数の末尾に記号 ^ (ハット・キャレット) を追加すると逆参照となります。ポインタ型の変数には、変数のデータが書き込まれているメモリ領域 (メモリブロック) の先頭アドレスの値が格納されています。逆参照は、そのメモリアドレスのデータを直接操作することを意味します。これをポインタの逆参照と 言っています。

以下のサンプルコードは、ポインタ型の変数にメモリ上のアドレスをポインタとして代入して、逆参照で元の値を取得する例です。



図1
ポインタ型の変数への代入と、逆参照による元の変数の値の取得

リスト1
procedure TForm1.Button1Click(Sender: TObject);
var
  LSrcValue : Double;
  LDstValue : Double;
  LpDouble  : PDouble;
begin
  Memo1.Lines.Clear;

  LSrcValue := 123.5;
  // 変数の型に応じたポインタ型の変数に代入
  // Double 型のポインタとなる
  // このポインタはアドレスと型情報を持つ
  LpDouble := @LSrcValue;

  // ポインタが示すアドレスに書き込まれているデータを逆参照で取得
  LDstValue := LpDouble^;
  Memo1.Lines.Add(FloatToStr(LDstValue));

  // 逆参照でポインタが示すアドレスに値をセット
  // 元の変数の値も変わる
  LpDouble^ := 456.32;
  Memo1.Lines.Add(FloatToStr(LSrcValue));
end;


動的配列等、変数がポインタの時は、メモリを確保する処理が必要です。ポインタ型の変数もポインタですからメモリの確保が必要です。

上のサンプルコードは、代入元の変数のデータに対してポインタ型の機能を利用する例です。
この場合、ポインタ型の変数は、代入元の変数のメモリを使用しますからメモリの確保は必要ありません。メモリを共有していますから、どちらかの変数で値を変更すると他方の値も変わります。

以下は、データを格納するメモリを確保してポインタ型の変数を使用する例です。つまり、他の変数とはメモリを共有しないで、独立したポインタ型の変数として使用する場合です。
ポインタ型のメモリの確保には New 関数あるいは AllocMem 関数を使用します。確保したメモリは解放処理が必要です。



リスト2
procedure TForm1.Button1Click(Sender: TObject);
var
  LpDouble  : PDouble;
  LDstValue : Double;
begin
  Memo1.Lines.Clear;

  New(LpDouble);
  try
    LpDouble^ := 123.5;

    // 逆参照でポインタ型の変数の値を代入
    LDstValue := LpDouble^;

    // ポインタが示すアドレスの値を逆参照で取得
    LDstValue := LpDouble^;
    Memo1.Lines.Add(FloatToStr(LDstValue));

    // 逆参照でポインタが示すアドレスに値をセット
    LpDouble^ := 456.32;
    Memo1.Lines.Add(FloatToStr(LDstValue));

    // メモリを確保したポインタ型の変数には他の変数のアドレスを代入しないこと
    // 代入は可能だがポインタ型の変数のデータの格納アドレスが変わってしまう
    // その結果、確保したアドレスのメモリが解放できなくなる
    // 確保したメモリの解放後であれば代入しても構わない
    // LpDouble := @LDstValue;
  finally
    Dispose(LpDouble);
  end;
end;


メモリを確保したたポインタ型の変数に値を代入したり、別の変数にポインタ型の変数の値を代入するには逆参照を使用します。@ 演算子を付けた変数を代入したりすると、確保したメモリアドレス以外の場所にデータが格納されてしまいます。代入はできても、確保したメモリの解放ができなくなりメモリリークが発生することになります。
そのような状態で処理を実行すると、メモリの解放処理の部分で、下図のような「無効なポインタ操作」のエラーが発生します。




図2
無効なポインタ操作

ローカル変数は初期化されません。以下のコードは PDouble 型の変数用のメモリを確保していませんが、実行しても例外は発生しません。

これは、いわゆる「ゴミ」による結果です。初期化していないメモリには不規則な値が書き込まれています。当然、ポインタ自身のアドレスにもあります。単純型の変数である Double 型の変数はどんなバイト値でも 8 バイトを数値として読み取れます。書き込み先のメモリも、変更されても問題ない場合は書き込めます。
実際のアプリケーションはこのように単純ではありません。確保していないメモリ領域は、いつどのように使用されるか予測することはできません。その結果、例外が発生するか、間違った結果となる処理をしてしまう可能性があります。


リスト3
procedure TForm1.Button1Click(Sender: TObject);
var
  LpValue : PDouble;
  LValue  : Double;
begin
  LpValue^ := 123.5;
  LValue   := LpValue^;
end;




03_汎用のポインタ型 Pointer


前項のサンプルで使用したポインタ型は、変数の型に応じたポインタ型でした。 Pointer 型という型情報を持たない汎用のポインタ型があります。この型の変数はアドレス情報だけを持つポインタです。@ 演算子を付けた変数は汎用のポインタと同じですが、型情報を持つポインタ型の変数に代入した場合、そのポインタ型の変数は、型情報はそのままでアドレス情報だけが変更されます。

汎用のポインタも、元のポインタ型でキャストすれば、その型のポインタ型の変数に代入したり逆参照できます。Pointer 型の変数には型情報がありません。型キャストすることによって、アドレスに書き込まれているデータを取得てきます。以下はその例です。



図3
Pointer 型による処理

リスト4
procedure TForm1.Button1Click(Sender: TObject);
var
  LSrcValue : Double;
  LDstValue : Double;
  LPointer  : Pointer;
begin
  LSrcValue := 123.5;
  // 汎用のポインタ型の変数にアドレスをセット
  // 汎用のポインタとなる
  // 結果的に LPointer := Addr(LSrcValue); と同じになる
  LPointer := @LSrcValue;

  // 元のポインタ型でキャストして逆参照する
  LDstValue := PDouble(LPointer)^;
  Memo1.Lines.Text := FloatToStr(LDstValue);
end;




04_関数や手続きのポインタ型の引数


数値型の変数等、ポインタ型の変数ではない変数を関数や手続きの引数に渡すと。その変数の値は、元の変数と関数や手続き内部では別のメモリ上のアドレスに書き込まれます。つまり、関数や手続き内部ではそれらの変数を独立して操作できるようになっています。

引数の変数の前に予約語 var を付けると、その変数は、関数内部でも元の変数と同じメモリ上のアドレスを使用するようになります。つまり、その引数に対する関数内部での処理結果を呼び出し側で受け取ることになります。この時この引数を「変数パラメータ」と言います。このような引数の渡し方を「参照渡し」あるいは「var 渡し」とか「アドレス渡し」と言っています。

引数がポインタ型等の変数の場合、関数の呼び出し側と難数内部でポインタが示すアドレスが変わることはありません。これは var を付けた変数と同じで「参照渡し」です。データを書き込む時はポインタが示すアドレスに書き込まれ、ポインタが示すアドレスからデータが読み出されます。
以下はそのことを確認するためのサンプルです。



図4
引数がポインタ型の変数の時の動作確認
  • 最初の値は手続きを実行する前の変数の値
  • 次が手続きを実行した後の変数の値

リスト5
//-----------------------------------------------------------------------------
//  ポインタ型の引数を持つ手続きの例
//-----------------------------------------------------------------------------
procedure TestProc(ApValue: PDouble);
begin
  ApValue^ := 500.23;
end;

//=============================================================================
//  テスト用の手続きを使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LValue : Double;
begin
  Memo1.Lines.Clear;

  LValue := 123.5;
  Memo1.Lines.Add(FloatToStr(LValue));

  TestProc(@LValue);
  Memo1.Lines.Add(FloatToStr(LValue));
end;

//=============================================================================
//  テスト用の手続きを使用する例
//  メモリを確保してポインタ型の変数を使用する例
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LpValue : PDouble;
begin
  Memo1.Lines.Clear;

  New(LpValue);
  try
    LpValue^ := 123.5;
    Memo1.Lines.Add(FloatToStr(LpValue^));

    TestProc(LpValue);
    Memo1.Lines.Add(FloatToStr(LpValue^));
  finally
    Dispose(LpValue);
  end;
end;

//=============================================================================
//  テスト用の手続きを使用する例
//  汎用のポインタ型 (アドレス情報のみ) を引数に渡す場合
//  結果の取得には型キャストが必要
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LValue   : Double;
  LPointer : Pointer;
begin
  Memo1.Lines.Clear;

  LValue  := 123.5;
  LPointer := @LValue;
  Memo1.Lines.Add(FloatToStr(LValue));

  TestProc(LPointer);
  Memo1.Lines.Add(FloatToStr(PDouble(LPointer)^));
end;


以下のように、引数の型が汎用の Pointer の時は関数内部では必要な型として処理することになります。関数を利用する側は元々何かの型のデータとして処理しますから、引数がポインタとしてのアドレス情報だけを持っている汎用の Pointer型でも処理可能です。
汎用の Pointer 型は、いろいろな型の変数を型キャストによって扱えます。


//-----------------------------------------------------------------------------
//  汎用のポインタ型の引数を持つの手続きの例
//-----------------------------------------------------------------------------
procedure TestProc(ApValue: Pointer);
begin
  PDouble(ApValue)^ := 500.23;
end;


作成する関数を次のようにすれば汎用性が高くなります。関数を使用する側では、汎用のポインタ型の変数でも、型情報付きのポインタ型の変数のどちらでも引数として使用できます。


リスト6
//-----------------------------------------------------------------------------
//  汎用のポインタ型の引数を持つの手続きの例
//  汎用性の高いコード
//
//  (1) 引数の型を処理する対象の型のポインタ型にする
//  (2) 引数の処理はそのポインタ型でキャストしてから逆参照する
//-----------------------------------------------------------------------------
procedure TestProc(ApValue: PDouble);
begin
  PDouble(ApValue)^ := 500.23;
end;


[備考 1]
型キャストは、ポインタのアドレスだけを使用して行われます。したがつて、上の関数内のコードは、次のようにしても結果は同じです。
      PDouble(PDouble(PDouble(ApValue)))^ := 500.23;

[備考 2]
関数や手続きの引数に、変数のデータが格納されているアドレスを渡す場合、それを「参照渡し」と言っています。「ポインタ渡し」と言うこともあります。
「参照渡し」の引数は、関数や手続きの呼出し側と、関数や手続きがその引数を受け取った時に、その引数のデータが格納されてるアドレスは同じです。

ポインタ、あるいはポインタ型ではない引数の場合も、var を付けることにより「参照渡し」となります。var を付けなければ「値渡し」となり、変数のデータが格納されているメモリブロックのコピーが引数に渡されます。




05_関数や手続きのクラス型の引数


次のサンプルで使用している関数の引数には予約語の var がありません。しかし、手続き内部で操作した結果が呼び出し側で取得できています。

このサンプルの手続きの引数は、TStringList というクラス型の変数です。
Delphi のクラスはポインタです。クラス型の変数は、そのオブジェクトのデータが書き込まれているメモリアドレスの情報と、クラスの型の情報を持っているポインタです。クラス型の変数のポインタが示すアドレスの値は、関数や手続きの呼び出し側と関数や手続きの内部で変わることはありません。したがって、ポインタ型の引数と同じように「参照渡し」となります。



図5
引数がクラス型の変数の場合は「参照渡し」となる

リスト7
//-----------------------------------------------------------------------------
//  クラス型の引数の内容を処理する手続きの例
//  引数には参照渡しの予約語 (変数パラメータ) var がない
//-----------------------------------------------------------------------------
procedure TestProc(AList: TStringList);
begin
  AList[0] := '別の文字列に変更';
end;

//=============================================================================
//  上の手続きを使用するコード
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
begin
  Memo1.Lines.Clear;

  LStrList := TStringList.Create;
  try
    LStrList.Add('テスト文字列');
    // 手続き実行
    TestProc(LStrList);

    // 手続き内部で変更した値となる
    Memo1.Lines.Add(LStrList[0]);
  finally
    FreeAndNil(LStrList);
  end;
end;




06_関数や手続きの TObject 型の引数


Delphi では TObject を使用してクラス型のデータの受け渡しが可能です。Delphi のクラス型の変数はポインタです。

TObject 型の変数にクラス型の変数 (オブジェクト) を代入します。TObject はメモリアドレスの情報だけを保持するポインタです。TObject はクラスの型情報を持ちません。TObject 型のポインタから元のオブジェクト (変数の内容) を取得する時は、元のクラス型でキャストします。そうすると、引数が示すアドレスのメモリのデータを、そのクラス型のデータとして使用できるようになります。
@ 演算子や逆参照の記号を使用する必要はありません。代入元も代入先もポインタです。



図6
EXE 起動直後
図7
Button1 クリック

リスト8
procedure TForm1.Button1Click(Sender: TObject);
var
  LObject : TObject;
  LButton : TButton;
begin
  Memo1.Lines.Clear;

  LObject := Button2;

  LButton := TButton(LObject);
  LButton.Left := 150;
  Memo1.Lines.Add(LButton.Caption);
end;


TButton 等のイベントに TOject 型の引数があります。以下はその TObject 型の引数を使用した例です。上のサンプルコードはこの TObject の使用方法と同じです。
イベントは procedure ですから手続きです。

Button1C2ick メソッドの引数は TObject 型です。TObject 型の変数には、オブジェクトのデータが格納されている (書き込まれている) メモリの先頭アドレスの情報しかありません。TPanel で型キャストすることにより、メモリのデータを TPanel のデータとして取得します。当然ですが、TObject が示すアドレスには TPanel のデータが格納されているわけですから、他のクラス型でキャストしても正しい結果は取得できません。例外が発生することがあります・

Delphi のクラスでは、TObject がクラス型の汎用ポインタと言えます。
イベントのメソッドや関数の引数に TObject 型、あるいは Pointer 型の変数を使用すると、型キャストという操作によっていろいろな型のデータの受け渡しができるようになります。




図8
TObject 型の引数を使用した処理例

リスト9
procedure TForm1.Button1Click(Sender: TObject);
begin
  Button2Click(Panel1);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  LPanel : TPanel;
begin
  LPanel := TPanel(Sender);
  MessageBox(Handle, PChar(LPanel.Caption), '結果', MB_ICONINFORMATION);
end;




07_汎用ポインタとしての TObject 型と Pointer 型


これまでのテストで確認したように、Delphi では、TObject がクラス型の汎用ポインタと言えます。
クラス型のオブジェクトは Pointer 型を使用しても処理できます。以下は手続きの引数に Pointer 型を使用する例です。


リスト10
procedure TForm1.Button1Click(Sender: TObject);
begin
  TestProc(Panel1);
end;

procedure TForm1.TestProc(Sender: Pointer);
var
  LPanel : TPanel;
begin
  LPanel := TPanel(Sender);
  MessageBox(Handle, PChar(LPanel.Caption), '結果', MB_ICONINFORMATION);
end;


クラス型の変数を扱うのに、上のように Pointer 型を使用する必要はありませんが、TBitmap の場合でも、次のように TObject 型と Pointer 型を使用して処理できます。クラス型のオブジェクトをポインタr 型の変数に代入する際、@ 演算子付きで代した場合は、逆参照が必要です。

このサンプルは、クラス型のオブジェクトをポインタ型や TObject 型を使用して処理する必要がある場合を想定したテストです。




図9
Pointer によるクラス型オブジェクトの処理例

リスト11
//=============================================================================
//  クラス型のオブジェクトをポインタ型の変数に代入する例
//
//  Pointer 型の変数に @ 演算子付きで代入
//  ポインタ型の変数からオブジェクトを取得するには逆参照が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LBitmap    : TBitmap;
  LDstBitmap : TBitmap;
  LPointer   : Pointer;
begin
  Image1.Picture.Assign(nil);

  LBitmap := TBitmap.Create;
  try
    LBitmap.LoadFromFile('503.bmp');

    // Pointer に @ 演算子を付けて代入
    LPointer := @LBitmap;

    // ポインタから TBitamap を取得
    // 逆参照が必要
    LDstBitmap := TBitmap(LPointer^);

    Image1.Canvas.Draw(0, 0, LDstBitmap);
  finally
    FreeAndNil(LBitmap);
  end;
end;

//=============================================================================
//  クラス型のオブジェクトをポインタ型の変数に代入する例
//
//  Pointer 型の変数に @ 演算子なしで代入
//  ポインタ型の変数からオブジェクトを取得する時に逆参照記号は不要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LBitmap    : TBitmap;
  LDstBitmap : TBitmap;
  LPointer   : Pointer;
begin
  Image1.Picture.Assign(nil);

  LBitmap := TBitmap.Create;
  try
    LBitmap.LoadFromFile('503.bmp');

    // Pointer 型に @ 演算子なしで代入
    LPointer := LBitmap;

    // ポインタから TBitamap を取得
    // 逆参照は不要
    LDstBitmap := TBitmap(LPointer);

    Image1.Canvas.Draw(0, 0, LDstBitmap);
  finally
    FreeAndNil(LBitmap);
  end;
end;

//=============================================================================
//  クラス型のオブジェクトを TObject 型の変数に代入する例
//  クラス型変数の特性を使用する最も確実と思われるな方法
//
//  汎用ポインタとしての TObject 型の変数に @ 演算子なしで代入
//  TObject 型の変数からオブジェクトを取得する時に逆参照は不要
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LBitmap    : TBitmap;
  LDstBitmap : TBitmap;
  LObject    : TObject;
begin
  Image1.Picture.Assign(nil);

  LBitmap := TBitmap.Create;
  try
    LBitmap.LoadFromFile('503.bmp');

    // TObjcet 型の変数に @ 演算子なしで代入
    // これは LObject := TObject(LBitmap); と同じ意味
    LObject := LBitmap;

    // ポインタから TBitamap を取得
    // 逆参照は不要
    LDstBitmap := TBitmap(LObject);

    Image1.Canvas.Draw(0, 0, LDstBitmap);
  finally
    FreeAndNil(LBitmap);
  end;
end;

//=============================================================================
//  クラス型のオブジェクトを TObject 型の変数に代入する例
//
//  汎用ポインタとしての TObject 型の変数に @ 演算子付きで代入
//  TObject 型の変数からオブジェクトを取得するには Pointer でキャストして逆参照する
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  LBitmap    : TBitmap;
  LDstBitmap : TBitmap;
  LObject    : TObject;
begin
  Image1.Picture.Assign(nil);

  LBitmap := TBitmap.Create;
  try
    LBitmap.LoadFromFile('503.bmp');

    // TObject 型の変数に @ 演算子付きで代入
    LObject := @LBitmap;

    // TObject 型の変数から TBitamap を取得
    // Pointer でキャストしてから逆参照が必要
    LDstBitmap := TBitmap(Pointer(LObject)^);

    Image1.Canvas.Draw(0, 0, LDstBitmap);
  finally
    FreeAndNil(LBitmap);
  end;
end;




08_クラス型のポインタ型


Delphi のクラス型の変数はポインタですから、クラス型のポインタ型を使用する機会はあまりないかも知れませんが、クラス型もポインタ型を宣言できます。
ポインタ型を宣言するには、クラスの型名の前に記号 ^ (ハット・キャレット) を付けます。逆参照は変数名の末尾に ^ を付けますから、その逆と言うことなります。ポインタ型を使用すると、@ 演算子を使用して、その専用のポインタ型の変数に代入がてぎます。逆参照の機能も利用できます。



リスト12
type
 // ポインタ型を定義
 PBitmap = ^TBitmap;

procedure TForm1.Button1Click(Sender: TObject);
var
  LBitmap    : TBitmap;
  LpBitmap   : PBitmap;
  LDstBitmap : TBitmap;
  LPointer   : Pointer;
begin
  LBitmap := TBitmap.Create;
  try
    LBitmap.LoadFromFile('503.bmp');

    // ポインタ型の変数に代入
    LpBitmap := @LBitmap;
    // 逆参照で元のクラス型のオブジェクトを取得
    LDstBitmap := LpBitmap^;

    Image1.Canvas.Draw(0, 0, LDstBitmap);
  finally
    FreeAndNil(LBitmap);
  end;
end;


ポインタ型は、クラス型に限らず、次のように、変数の宣言時でも設定できます。型名の先頭に ^ (キャレット) を追加するだけです。



リスト13
//=============================================================================
//  ポインタ型は、変数は宣言時でも宣言が可能
//  ポインタ型の変数と他の変数とでメモリを共有する場合
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LValue  : Double;
  LpValue : ^Double;
begin
  Memo1.Lines.Clear;

  LValue  := 123.5;
  LpValue := @LValue;
  LValue  := 500.5;

  Memo1.Lines.Add(FloatToStr(Lpvalue^));
end;

//=============================================================================
//  ポインタ型は、変数の宣言時でも宣言が可能
//  ポインタ型の変数を独立した変数にする場合
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LValue  : Double;
  LpValue : ^Double;
begin
  Memo1.Lines.Clear;

  New(LpValue);
  TRY
    LValue   := 123.5;
    LpValue^ := LValue;
    LValue   := 500.5;

    Memo1.Lines.Add(FloatToStr(Lpvalue^));
  finally
    Dispose(LpValue);
  end;
end;


[備考]
Char の先頭に ^ を付けても PChar 型にはなりません。PChar 型は Char 型のポインタ型ではありません。PChar 型は Null 終端文字列の型です。




09_レコード型のポインタ型


Delphi のレコード型はポインタ型ではありませんが、ポインタ型も定義されています。
以下は TRect レコード型のポインタ型である PRect 型の使用例です。処理方法は数値型のポインタ型の場合とほとんど同じです。レコード型は、ポインタ型を使用した方が便利なこともあり、よく使われているようです。
独自のレコード型を宣言して使用する場合は、ポインタ型も定義しておくと便利です。

以下のリンクの記事に、メモリを確保して、レコード型のポインタ型の変数を、独立したポインタ型の変数として操作する例があります。




図10
TRect のポインタ型 PRect の使用例

リスト14
procedure TForm1.Button1Click(Sender: TObject);
var
  LRect    : TRect;
  LDstRect : TRect;
  LpRect   : PRect;
begin
  Memo1.Lines.Clear;

  // テスト用データ
  LRect := Rect(100, 150, 300, 350);

  // レコード型のポインタ型の変数にレコード型変数のアドレスを代入
  LpRect := @LRect;

  // 逆参照で元のレコード型変数のデータを取得
  LDstRect := LpRect^;

  // 取得結果を表示
  Memo1.Lines.Add(IntToStr(LDstRect.Left));
  Memo1.Lines.Add(IntToStr(LDstRect.Top));
  Memo1.Lines.Add(IntToStr(LDstRect.Right));
  Memo1.Lines.Add(IntToStr(LDstRect.Bottom));
end;


[備考]
Delphi のレコード型のポインタ型には、Windows API の表記と合わせるために、プリフィックスの P がないものがあります。




10_汎用ポインタ型の Objects プロパティと Data プロパティ


TListView 等、項目をリスト操作するコントロールには、TListItem 型のクラスを使用して各項目にインデックスでアクセスします。この TListItem には Data という名前の Pointer 型のプロパティが実装されててます。
また、TListBox や TMemo 等の Items プロパティは TStrings 型ですが、TStrings 型には Objects と言う名前の TObject 型のプロパティが実装されています。

Pointer はポインタ型の汎用ポインタで、TObject はクラス型の汎用のポインタですから、数値型、レコード型、クラス型等のポインタ型の変数を代入できます。リストの項目にそれらのポインタ型の変数を代入すると、リストの各項目が固有のデータを持つことになり機能を拡張できます。

Objects プロパティや Data プロパティに代入するのはポインタとしてのアドレスだけです。したがって、それらのプロパティの操作には TObject 等によるキャスト、あるいは逆参照を使用します。ポインタ型の変数を使用するには、そのポインタ型の変数のメモリの確保と解放処理が必要です。