Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2018年05月29日] [更新 2018年05月29日] Delphi 一般・その他
   
Delphi の文字列 - String 型と PChar 型
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 0770]







Delphi の文字列 - String 型と PChar 型


Delphi で使用する String 型の文字列と Null 終端文字列の記事です.
それらの文字列の操作ではなく,String 型の文字列と PChar 型の文字列の基本的な性質と基本的な操作に関する記事です.

本ページのサンプルでは下図のようなフォームを作成して,このフォームを使用するプロジェクトをコピーしてテストしています.



図1
本ページのテストで使用しているフォーム


文字列の操作関係については,以下のオンラインヘルプの記事を参考にしてください.



[備考 1]
本ページのサンプルは [動作確認等] に記述しているように Delphi XE5 でテストしています.
Delphi XE5 は Unicode 版の Delphi です.ネット上のサンプルには Ansi 版の Delphi の記事やサンプルコードも多くあります.
文字列関係だけではなく,ネット上のコードを利用する時は動作確認環境に注意してください.

[備考 2]
Null 文字 (#0) は,改行文字や TAB 文字と同じように,視覚的に文字としては認識されず,表示されない「文字」です.ASCII 制御文字 (アスキー制御文字) と呼ばれることもあります.




01_ シングルクォーテーションで囲んだのが String 型の文字列


Delphi の String 型の文字列は,文字列を ' (シングルクォーテーションまたはシングルクォート) で括ったものです.
この時,文字列変数には ' (シングルクォート) は含まれません.' は,この内側に書かれているのが文字列であると言う記号です.

以下のコードは String 型の変数を定義して,その変数に文字列を代入する単純なテストです.


図2
String 型の変数に文字列を代入して,それを TMemo に表示した結果

リスト1
//=============================================================================
//  シングルクォートで囲まれたのが文字列
//  シングルクォートは文字列には含まれない
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LText : string;
begin
  LText := '日本語';

  Memo1.Lines.Clear;
  Memo1.Lines.Add(LText);
end;


String 型の文字列は文字配列の構造になっています.以下のように 1 文字単位で操作できます.
指定したインデックスの要素に文字を代入することもできます.



図3
String 型の文字列を 1 文字づつ取得して表示

リスト2
//=============================================================================
//  String 型の文字列は文字配列
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LText  : string;
  LChr   : Char;
  LIndex : Integer;
begin
  LText := '日本語 ABC 文字列';

  Memo1.Lines.Clear;

  // String 型の最初の文字のインデックスは 1
  // Length は String 型の文字数を取得する関数
  // LChr は 1 文字の型 (Char) の変数
  for LIndex := 1 to Length(LText) do begin
    LChr :=  LText[LIndex];
    Memo1.Lines.Add(LChr);
  end;
end;




02_TEdit 等の Text プロパティの値は String 型の文字列


文字列の入力用コントロールである TEdit で入力した文字列は Text プロパティで取得できます.
この Text プロパティは String 型の文字列です.したがって,これを String 型の変数に代入するとそのまま文字列となります.シングルクォートで括る必要はありません.
以下のコードでは TMemo の Lines.Add メソッドで直接 TMemo に表示する例です.



図4
' TEdit.Text プロパティの値は文字列

リスト3
//=============================================================================
//  TEdit等に入力した値は,それが文字列
//  シングルクォートは必要ない
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Clear;
  Memo1.Lines.Add(Edit1.Text);
end;


以下のように,String 型の変数に代入する際,シングルクォートで囲ってしまうと,下図のような結果になってしまいます.



図5
TEdit.Text を ' (シングルクォーテーション) で括った場合

リスト4
//=============================================================================
//  TEdit等に入力した値は,それが文字列
//  シングルクォートは必要ない
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LText : string;
begin
  LText := 'Edit1.Text';

  Memo1.Lines.Clear;
  Memo1.Lines.Add(LText);
end;




03_シングルクォーテーションとダブルクォーテーション


シングルクォート (') は文字列を括る文字です.この文字は文字列には含まれません.シングルクォートを文字列の中に含ませるには,シングルクォートを 2 つ続けて書きます.
以下はその例です.
他のプログラミング言語では " (ダブルクォーテーションまたはダブルクォート) が文字列を括る文字として使用されていることがありますが,Delphi では文字列の中に書けば,そのままダブルクォートとして認識してくれます.


図6
' (シングルクォート) を文字列に含める例
  • シングルクォートを 2 つ続けて書く

リスト5
//=============================================================================
//  文字列にシングルクォートまたはダブルクォートを含める
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LText : string;
begin

  LText := '日''本語';
  Memo1.Lines.Clear;
  Memo1.Lines.Add(LText);

  LText := '日"本語';
  Memo1.Lines.Add(LText);
end;


TEdit で文字列を入力する時は,シングルクォートを 2 つ続けて書く必要はありません.TEdit で入力したそのままが文字列となります.



図7
' (シングルクォート) を文字列に含める例
  • TEdit 等に入力した ' はそのままでよい
  • 2 つ続けて入力する必要はない

リスト6
//=============================================================================
//  文字列にシングルクォートまたはダブルクォートを含める
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Clear;
  Memo1.Lines.Add(Edit1.Text);
end;


文字列の両端,つまり先頭と末尾にシングルクォートまたはダブルクォートを追加するには,以下のコードで使用している関数あるいはメソッドが使用できます.



図8
文字列の両端にシングルクォートまたはダブルクォートを追加

リスト7
//=============================================================================
//  文字列の両端にシングルクォートまたはダブルクォートを追加する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSrcText : string;
  LDstText : string;
begin
  Memo1.Lines.Clear;

  LSrcText := '日本語ABC';

  // 文字列の両端にシングルクォーテーションを追加
  LDstText := AnsiQuotedStr(LSrcText, '''');
  LDstText := QuotedStr(LSrcText);
  Memo1.Lines.Add(LDstText);
  // Delphi XE3 以降で使用可能
  LDstText := LSrcText.QuotedString;
  Memo1.Lines.Add(LDstText);

  Memo1.Lines.Add('');

  // 文字列の両端にダブルクォーテーションを追加
  LDstText := AnsiQuotedStr(LSrcText,'"');
  Memo1.Lines.Add(LDstText);
  // Delphi XE3 以降で使用可能
  LDstText := LSrcText.QuotedString('"');
  Memo1.Lines.Add(LDstText);
end;


逆に,文字列の両端からシングルクォートまたはダブルクォートを削除するには,以下のコードで使用している関数あるいはメソッドが使用できます.



図9
文字列の両端のシングルクォートまたはダブルクォートを削除

リスト8
//=============================================================================
//  文字列の両端のシングルクォートまたはダブルクォートを削除する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSrcText : string;
  LDstText : string;
begin
  Memo1.Lines.Clear;

  LSrcText := '''日本語ABC''';
  Memo1.Lines.Add(LSrcText);

  // 文字列の両端のシングルクォーテーションを削除
  LDstText := AnsiDequotedStr(LSrcText, '''');
  Memo1.Lines.Add(LDstText);
  // Delphi XE3 以降で使用可能
  LDstText := LSrcText.DeQuotedString;
  Memo1.Lines.Add(LDstText);

  Memo1.Lines.Add('');
  LSrcText := '"日本語ABC"';
  Memo1.Lines.Add(LSrcText);

  // 文字列の両端のダブルクォーテーションを削除
  LDstText := AnsiDequotedStr(LSrcText,'"');
  Memo1.Lines.Add(LDstText);
  // Delphi XE3 以降で使用可能
  LDstText := LSrcText.DeQuotedString('"');
  Memo1.Lines.Add(LDstText);
end;




04_複数の String 型の文字列を結合して 1 つの文字列にする


複数の文字列を結合して 1 つの文字列にするには + 演算子を使用します.
文字列の結合用の関数やメソッドは他にもありますが,+ 演算子を使用する方が高速です.



図10
文字列は + 演算子で結合する

リスト9
//=============================================================================
//  複数の文字列を結合して 1 つの文字列にする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LText : string;
begin
  LText := '日本語' + 'Delphi' + ' 外国語';

  Memo1.Lines.Add(LText);
end;


複数の文字列を + 演算子で結合する際,以下のように改行して書くことがあります.
1 行の長さが長くなると,IDE のコードエディタで横方向にスクロールする必要があります.それを避ける意味でこのように書くことがあります.



リスト10
//=============================================================================
//  複数の文字列を結合して 1 つの文字列にする
//  1つの文字列が長い時によく使用する書き方の例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LText : string;
begin
  LText := '日本語'
         + 'Delphi'
         + ' 外国語';

  Memo1.Lines.Add(LText);
end;


上のコードでは,シングルクォートで括った文字列を結合しています.
以下のように,文字列として String 型の変数も使用できます.もちろん,変数名はシングルクォートで括らないで使用します.



図11
変数名で結合した結果

リスト11
//=============================================================================
//  複数の文字列を結合して 1 つの文字列にする
//  文字列型の変数名でも結合できる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrA : string;
  LStrB : string;
  LText : string;
begin
  LStrA := '日本語';
  LStrB := ' 外国語';
  LText := LStrA + 'Delphi' + LStrB;

  Memo1.Lines.Add(LText);
end;




05_String 型の文字列のリストや文字列の配列


複数の文字列をリストとして管理したい時は,以下のサンプルのように TStringList 等を使用することができます.本ページのテストで使用している TMemo の Lines プロパティも文字列のリストです.つまり,TMemo というのは文字列のリストを表示していることになります.
文字列のリストは,要素の追加や削除等の操作が必要な場合,あるいはリストの要素の文字列検索等の用途に向いています.

Delphi には,TStringList 以外にもリストや配列の操作用の型が用意されています.



図12
TStringList に格納した文字列を表示

リスト12
//=============================================================================
//  文字列のリスト
//  TStringListを使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
  LText    : string;
  LIndex   : Integer;
begin
  LStrList := TStringList.Create;
  try
    //文字列のリストに文字列を追加
    LStrList.Add('日本語');
    LStrList.Add('Delphi');
    LStrList.Add('外国語');

    //文字列のリストから文字列を取り出す
    for LIndex := 0 to LStrList.Count - 1 do begin
      LText := LStrList[LIndex];
      Memo1.Lines.Add(LText);
    end;
  finally
    FreeAndNil(LStrList);
  end;
end;


配列は数学で言えば行列に該当しますが,Delphi の多次元の配列は数学の行列とは違い,次元ごとに格納する要素数を設定することができます.配列は,固定した要素数の文字列を扱ったり,他の要素間で演算等の操作が必要な時に利用されています.
以下は動的な配列に複数の文字列を格納する例です.動的な配列は,実行時に配列の要素数を設定する配列のことです.

一般的な配列には,TList や TStringList のクラス型とは違って,プロパティやメソッド,イベントが実装されていません.配列は単なる配列ですが,TArray<T> のような動的な配列ではプロパティやメソッドが実装されています.



リスト13
//=============================================================================
//  文字列の配列
//  動的配列の例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrArray : array of string;
  LText     : string;
  LIndex    : Integer;
begin
  //動的配列の要素数を設定
  SetLength(LStrArray, 3);
  //動的配列の各要素に文字列を格納
  LStrArray[0] := '日本語';
  LStrArray[1] := 'Delphi';
  LStrArray[2] := '外国語';

  //文字列の配列から文字列を取り出す
  for LIndex := 0 to Length(LStrArray) - 1 do begin
    LText := LStrArray[LIndex];
    Memo1.Lines.Add(LText);
  end;
end;


以下は静的な配列に複数の文字列を格納する例です.静的な配列は,変数を定義する時に配列の要素数も定義する配列のことです.



リスト14
//=============================================================================
//  文字列の配列
//  静的配列の例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrArray : array [0..2] of string;
  LText     : string;
  LIndex    : Integer;
begin
  //静的配列の各要素に文字列を格納
  LStrArray[0] := '日本語';
  LStrArray[1] := 'Delphi';
  LStrArray[2] := '外国語';

  //文字列の配列から文字列を取り出す
  for LIndex := 0 to Length(LStrArray) - 1 do begin
    LText := LStrArray[LIndex];
    Memo1.Lines.Add(LText);
  end;
end;




06_String 型のポインタ型 PString


String 型のポインタ型は PString 型です.ポインタ型を使用すると便利なことがあります.
PString 型の変数に String 型の変数を代入するには,String 型の変数のポインタとしてのアドレスを指定します.変数の先頭に @ を付けて @LString とするか,Addr(LString) と書くとポインタとしてのアドレスを意味します.
逆にポインタ型の変数から実際の値を取得するには,ポインタ型の変数の最後に ^ (ハット・キャレット) を書きます.これは逆参照と言って,そのポインタが示すアドレスにある値を取得します.
以下はその操作例です.

Delphi ではポインタ型のプリフィックス (先頭に付ける文字) として大文字の P を使う慣習となっています.Delphi の型の多くはポインタ型も用意されています.



図13
String 型のポインタ型 PString の操作例

リスト15
//=============================================================================
//  String型のポインタ型PString
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LString   : string;
  LResStr   : string;
  LPString  : PString;
begin
  LString  := 'String 型のポインタ型';

  //PString型に代入するにはアドレスを指定する
  LPString := @LString;

  //PStringの値をString型に代入するには^で逆参照する
  LResStr := LPString^;

  //確認表示
  Memo1.Lines.Add(LResStr);
end;


上のサンプルでは変数のアドレスをポインタ型の変数に代入しています.そうすると,ポインタ型の変数のアドレスがその変数のアドレスと同じになり,メモリを共有します.したがつて,ポインタ型の変数の値と元の変数の値は常に同じです.

PString 型の変数は,宣言しただれの状態゛では nil になっていのす・
PString 型の変数の値を独立した値とするには,次のように PString 型の変数のメモリを確保します.これでポインタとしてのアドレスが決まります.確保したメモリアドレスは解放処理が必要です.



リスト16
//=============================================================================
//  String型のポインタ型PString
//  ポインタ型の変数のメモリの確保要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LString   : string;
  LResStr   : string;
  LPString  : PString;
begin
  LString  := 'String 型のポインタ型';

  // ポインタ型の変数用のメモリを確保
  New(LPString);
  try
    // ポインタ型の変数に逆参照で文字列を代入
    LPString^ := LString;

    // PStringの値をString型に代入するには^で逆参照する
    LResStr := LPString^;

    // 確認表示
    Memo1.Lines.Add(LResStr);
  finally
    // 確保したメモリを解放
    Dispose(LPString);
  end;
end;


次のように,最初から逆参照で PString 型の変数に直接値を代入する場合も同じです,ポインタ型の変数のポインタとしてのメモリアドレスの確保が必要です.



リスト17
//=============================================================================
//  String型のポインタ型PString
//  ポインタ型の変数のメモリの確保要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LResStr   : string;
  LPString  : PString;
begin
  // ポインタ型の変数用のメモリを確保
  New(LPString);
  try
    // ポインタ型の変数に逆参照で文字列を代入
    LPString^ := 'String 型のポインタ型';

    // PStringの値をString型に代入するには^で逆参照する
    LResStr := LPString^;

    // 確認表示
    Memo1.Lines.Add(LResStr);
  finally
    // 確保したメモリを解放
    Dispose(LPString);
  end;
end;




07_Windows API の文字列は PChar 型を使用する


Windows API の関数の引数や戻り値の文字列は Null 終端文字列です.文字列の最後示す文字が Null (#0) 文字であることからそう呼ばれています.Delphi で Null 終端文字列を扱うには主に PChar 型を使用します.
Null 終端文字列のことを「C の文字列」,それに対して Delphi の String 型の文字列を「Pascal の文字列」と言うことがあります.

下に示すコードは ShellExecute という Windows API の関数を実行して,Windows に付属のメモ帳を起動する例です.この例のように,String 型の変数を引数にして PChar を関数のように使用すると自動的に PChar 型の文字列への変換になります.これを PChar によるキャスト,あるいは PChar でキャストすると言います.



[備考 1]
PChar は null 終端文字列のポインタです.Windows API が扱う文字列には,Delphi の String 型に相当する文字列の型はありません.String 型のポインタ型は PString です.
Delphi ではポインタの型名として,慣習的に先頭を P とする型名が使われています.

[備考 2]
Windows API の関数を使用するということは,Windows が提供している DLL 内の関数を使用することです.一般に,DLL 内の関数や手続きの文字列の引数には PChar 型,あるいは Char 型の静的文字配列を使用します.


リスト18
//=============================================================================
//  Windows API の文字列は PChar を使用する
//  ShellExecute関数を使用してメモ帳を起動する例
//
//  このコードの実行にはusesにShellAPIが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LExeName : string;
begin
  LExeName :='notepad.exe';

  ShellExecute(0, '', PChar(LExeName), nil, nil, SW_SHOWNORMAL);
end;


PChar 型の文字列 は文字配列の構造になっています.String 型の文字列と同じように文字単位での操作が可能ですが,代入する場合は工夫が必要です.後述の 1 文字単位で処理の記事にサンプルコードを掲載しています.




08_String 型の文字列を PChar 型の文字列にする


上の例のように,String 型の変数を PChar でキャストすると PChar 型の文字列にすることができますが,文字列をそのまま PChar 型の変数に代入するこもできます.以下はその例です.



図14
文字列を PChar 型の変数に代入した結果

リスト19
//=============================================================================
//  Windows API の文字列は PChar を使用する
//  string型をPCharの変数に代入
//  文字列を直接代入する例
//
//  このコードの実行にはusesにShellAPIが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPCharText : PChar;
begin
  LPCharText :='notepad.exe';

  Memo1.Lines.Add(LPCharText);

  ShellExecute(0, '', LPCharText, nil, nil, SW_SHOWNORMAL);
end;

//=============================================================================
//  Windows API の文字列は PChar を使用する
//  PCharの変数にstring型の変数値を代入する時はPCharでキャストする
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LStrText   : string;
  LPCharText : PChar;
begin
  LStrText   := 'notepad.exe';
  LPCharText := PChar(LStrText);

  Memo1.Lines.Add(LPCharText);
end;




09_メモリ領域を確保して String 型から PChar 型の文字列にする


ShellExecute 関数では必要ありませんが,Windows API の関数によっては,PChar 型の文字列を関数の引数として使用する際,その PChar 型の文字列に必要なメモリ領域をあらかじめ確保する必要がある場合があります.
メモリの確保には以下のように AllocMem 関数あるいは StrAlloc 関数が使用できます.

PChar 型の文字列データ用として確保したそのメモリ領域に,StrPCopy 関数や StrPLCopy 関数を使用して String 型の文字列データをにコピーすると,それが PChar 型の文字列となります.



[備考]
NULL 終端文字列のコピー ルーチンの関数名に P があるのは String 型の文字列を PChar 型の文字列にコピーするための関数です.P がない StrCopy, StrLCopy は PChar 型の文字列を別の PChar 型の文字列にコピーする関数です.
L がある関数はコピーする文字数 (長さ) 指定がある関数です.
P は Pascal, L は Length の意味かも知れません.

String 型の文字列を Pascal の文字列,あるいは Pascal 形式の文字列と言うことがあります.
PChar 型の文字列を C の文字列と言うことがあります.



図15
PChar 型変数用のメモリを確保する方法
  • AllocMem 関数あるいは StrAlloc 関数を使用

リスト20
//=============================================================================
//  Windows API の文字列は PChar を使用する
//  string型の値をPCharの変数値にコピー
//
//  PCharの変数用メモリを確保する方法
//  AllocMem関数を使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrText   : string;
  LPCharText : PChar;
begin
  LStrText := '日本語 ABC 文字列' + sLineBreak + 'PChar 型変数のメモリ確保方法';
  //PCharの文字列を格納するメモリ領域を確保(+1はNull終端文字の分)
  //AllocMemは確保したメモリを0で初期化してくれる
  LPCharText := AllocMem((Length(LStrText) + 1) * SizeOf(Char));
  try
    StrPCopy(LPCharText, LStrText);
    Memo1.Lines.Add(LPCharText);
  finally
    FreeMem(LPCharText);
  end;
end;

//=============================================================================
//  Windows API の文字列は PChar を使用する
//  string型の値をPCharの変数値にコピー
//
//  PCharの変数用メモリを確保する方法
//  StrAlloc関数を使用する例
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LStrText   : string;
  LPCharText : PChar;
begin
  LStrText := '日本語 ABC 文字列' + sLineBreak + 'PChar 型変数のメモリ確保方法';
  //PCharの文字列を格納するメモリ領域を確保(+1はNull終端文字の分)
  LPCharText := StrAlloc(Length(LStrText) + 1);
  try
    StrPCopy(LPCharText, LStrText);
    Memo1.Lines.Add(LPCharText);
  finally
    StrDispose(LPCharText);
  end;
end;




10_String 型の先頭から指定文字数だけを PChar 型の文字列にする


StrPLCopy 関数を使用すると,String 型の指定文字数だけを PChar 型の文字列できます.ただし,変換先の PChar 型の文字例用のメモリの確保が必要です.
StrPLCopy 等の Null 終端文字列のコピールーチンの関数はメモリのデータをコピーする関数です.したがって,コピー先の変数用のメモリが確保されていないと正常に動作しません.



図16
String 型の指定文字数たけを PChar 型の文字列にした結果
  • 1 行目は元の文字列
  • 2 行目は変換後の PChar 型の文字列

リスト21
//=============================================================================
//  String型の文字列の指定文字数の文字をPChar型の文字列とする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrSrc : string;
  LPCharDst : PChar;
  LCopyCnt  : Integer;
begin
  Memo1.Lines.Clear;

  //テスト用の文字列
  LStrSrc := '日本語 ABC 123 文字列';

  //PChar型の文字列用のメモリを確保
  //Null終端文字の分が必要
  LCopyCnt := 7;
  LPCharDst := AllocMem((LCopyCnt + 1) * SizeOf(Char));
  try
    //String型の指定文字数をPChar型の文字列にする
    StrPLCopy(LPCharDst, LStrSrc, LCopyCnt);

    //結果の表示
    Memo1.Lines.Add(LStrSrc);
    Memo1.Lines.Add(LPCharDst);
  finally
    FreeMem(LPCharDst);
  end;
end;




11_String 型の部分文字列を PChar 型の文字列にする


MoveChars 関数を使用すると,String 型の文字列の任意の位置から任意の文字数だけを PChar 型の文字列にすることができます.この関数を使用する時も,変換先の PChar 型のメモリ領域が確保されている必要があります.



[備考]
2018年07月02日現在の MoveChars 関数のオンラインヘルプでは,第 3 引数の単位がバイトとなっていますが,実際には文字数です.バイト単位の指定は Move 関数の方です.


図17
String 型の文字列を PChar 型の文字列に変換
  • String 型の先頭から 7 文字分だけを PChar の文字列にした結果
図18
String 型の文字列を PChar 型の文字列に変換
  • String 型の 3 番目の文字から 7 文字分を PChar 型の文字列にした結果

リスト22
//=============================================================================
//  String型の文字列の先頭から指定文字数の文字をPChar型の文字列とする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrSrc   : string;
  LPCharDst : PChar;
  LCopyCnt  : Integer;
begin
  Memo1.Lines.Clear;

  //テスト用の文字列
  LStrSrc := '日本語 ABC 123 文字列';

  //PChar型の文字列用のメモリを確保
  //Null終端文字の分が必要
  LCopyCnt := 7;
  LPCharDst := AllocMem((LCopyCnt + 1) * SizeOf(Char));
  try
    //String型の先頭から指定文字数をPChar型の文字列にする
    MoveChars(LStrSrc[1], LPCharDst^, LCopyCnt);

    //結果の表示
    Memo1.Lines.Add(LStrSrc);
    Memo1.Lines.Add(LPCharDst);
  finally
    FreeMem(LPCharDst);
  end;
end;

//=============================================================================
//  String型の文字列の任意の値から指定文字数の文字をPChar型の文字列にする
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LStrSrc : string;
  LPCharDst : PChar;
  LCopyCnt  : Integer;
begin
  Memo1.Lines.Clear;

  //テスト用の文字列
  LStrSrc := '日本語 ABC 123 文字列';

  //PChar型の文字列用のメモリを確保
  //Null終端文字の分が必要
  LCopyCnt := 7;
  LPCharDst := AllocMem((LCopyCnt + 1) * SizeOf(Char));
  try
    //String型の文字列の任意の位置からPChar型の文字列にする
    MoveChars(LStrSrc[3], LPCharDst^, LCopyCnt);

    //結果の表示
    Memo1.Lines.Add(LStrSrc);
    Memo1.Lines.Add(LPCharDst);
  finally
    FreeMem(LPCharDst);
  end;
end;




12_PChar 型の文字列を String 型の文字列にする


下に示すコードのように,PChar 型の変数はそのま String 型の変数に代入すれば String 型の文字列に変換できます.ここまでのサンプルでも TMemo.Lines.Add メソッドの引数に PChar 型の変数を使用しています.つまり,PChar 型の文字列を自動で String 型の文字列にしているわけです.
必要であれば string でキャストします.



図19
PChar の文字列を String 型の文字列に変換

リスト23
//=============================================================================
//  PChar 型の文字列を String 型の文字列に変換
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrText : string;
  LResText : string;
  LPChar   : PChar;
begin
  //テスト用のPChar型の文字列を作成
  LStrText :='文字列の型変換 - DEF';
  LPChar   := PChar(LStrText);


  //PChar型の文字列をString型の文字列に変換
  LResText := LPChar;

  //確認表示
  Memo1.Lines.Add(LResText);
end;

//=============================================================================
//  PChar 型の文字列を String 型の文字列に変換
//  明示的にキャストする例
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LStrText : string;
  LResText : string;
  LPChar   : PChar;
begin
  //テスト用のPChar型の文字列を作成
  LStrText :='文字列の型変換 - DEF';
  LPChar   := PChar(LStrText);


  //PChar型の文字列をString型の文字列に変換
  LResText := string(LPChar);

  //確認表示
  Memo1.Lines.Add(LResText);
end;




13_PChar 型の先頭から指定文字数だけを String 型の文字列にする


SetString 関数を使用すると,PChar 型の文字列の指定文字数だけを String 型の文字列の変数の値とすることができます.
下図は,先頭から 10 文字分を取り出して表示しています.



図20
PChar型の文字列を String 型の文字列にする
  • 1 行目は元の PChar 型の文字列
  • 2 行目が String 型として取り出した文字列

リスト24
//=============================================================================
//  PChar型の文字列の先頭から指定文字数の文字を取得
//  それをString型の文字列に代入
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LString : string;
  LPChar  : PChar;
begin
  Memo1.Lines.Clear;

  //テスト用の文字列
  LPChar := '日本語 ABC 123 文字列';

  //LPCharの文字を指定数分LStringにコピー
  SetString(LString, LPChar, 10);

  //結果の表示
  Memo1.Lines.Add(LPChar);
  Memo1.Lines.Add(LString);
end;




14_PChar 型の部分文字列を String 型の文字列にする


上と同じ SetString 関数を使用して,PChar 型の文字列の指定位置から指定文字数だけを String 型の文字列の変数に代入する例です.
下図では,先頭から 6 文字目から 8 文字分を取り出して String 型の変数の値としています.
コピー元の PChar 型の変数のポインタを移動することで文字位置を指定しています.



図21
PChar型の文字列を String 型の文字列に変換
  • 1 行目は元の PChar 型の文字列
  • 2 行目が String 型として取り出した文字列

リスト25
//=============================================================================
//  PChar型の文字列の指定位置から指定文字数の文字をSTring型の文字列にする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LString : string;
  LPChar  : PChar;
begin
  Memo1.Lines.Clear;

  //テスト用の文字列
  LPChar := '日本語 ABC 123 文字列';

  //LPCharの文字の指定位置から指定文字数だけLStringにコピー
  SetString(LString, LPChar + 5, 8);

  //結果の表示
  Memo1.Lines.Add(LPChar);
  Memo1.Lines.Add(LString);
end;




15_String 型の 1 文字と Char 型の 1 文字


以下の Button1 クリックのコードは「あ」の 1 文字を PChar でキャストしていますが,このコードは実行すると例外が発生します.Button2 クリックのコードも例外となります.
この例のように,PChar でキャストする文字が 1 文字だけで,しかも変数ではなく直接与えられた場合,それは String 型の「文字列」ではなく,Char 型の「文字」として解釈されます.つまり,Button2 クリックのコードと同じ意味となります.
Char 型は 1 文字だけの文字の型です.



[備考]
Delphi 2009 より前の非ユニコード版の Delphi では Button1 クリックのコードはエラーとはなりませんが,半角の英数字の場合はエラーとなります.非ユニコード版の Delphi では文字をバイト単位で扱います.したがって「あ」は 2 バイト,つまり 2 文字となるので「文字列」となります.
一方,Delphi 2009 以降の Delphi では,文字は「文字」単位 (正確にはエレメント単位) で扱います.つまり「あ」は 1 文字となります.
当然,非ユニコード版の Delphi では Button2 クリックのコードはコンパイルエラーとなります.


リスト26
//=============================================================================
//  Delphi 2009 以降に(Unicode版)おける「文字」の注意
//  例外が発生するコード
//
//  1文字はChar型となり「文字列」としての情報がない
//  1文字ではなく2文字以上であれば「文字列」と認識するので問題ない
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
begin
  LPChar := PChar('あ');
  Memo1.Lines.Add(LPChar);
end;

//=============================================================================
//  Delphi 2009 以降に(Unicode版)おける「文字」の注意
//  例外が発生するコード
//
//  Char型は文字なのでPChar(文字列)にはできない
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LText  : Char;
  LPChar : PChar;
begin
  LText  := 'あ';
  LPChar := PChar(LText);
  Memo1.Lines.Add(LPChar);
end;


次のように,該当する「文字」に Null 文字である #0 を末尾に追加すれば,それは Null 終端の「文字列」となります.したがつて正常に動作します.PChar 型は Null 終端の文字列です.



リスト27
//=============================================================================
//  Char型の配列に文字を代入してNull終端文字にすればPCharに代入できる
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LCharArray : array [0..1] of Char;
  LPChar     : PChar;
begin
  LCharArray[0] := 'あ';
  LCharArray[1] := #0;
  LPChar := LCharArray;

  Memo1.Lines.Add(LPChar);
end;


以下に示すように,PChar でキャストしないで直接 PChar 型の変数に代入すれば「文字列」として解釈してくれます.PChar でキャストするのは「文字列」でなければなりません
したがって string 型でキャストしているその次のコードも.string 型の変数も,たとえ 1 文字であっても正常に PChar 型の文字列に変換できます.



図22
String 型の値であれば 1 文字でも PChar 型の文字列に変換できる

リスト28
//=============================================================================
//  文字列として認識可能なコード
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
begin
  LPChar := 'あ';
  Memo1.Lines.Add(LPChar);
end;

//=============================================================================
//  文字列として認識可能なコード
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LPChar : PChar;
begin
  LPChar := PChar(string('あ'));
  Memo1.Lines.Add(LPChar);
end;

//=============================================================================
//  文字列として認識可能なコード
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LText  : string;
  LPChar : PChar;
begin
  LText  := 'あ';
  LPChar := PChar(LText);
  Memo1.Lines.Add(LPChar);
end;




16_String 型と PChar 型の変数の文字数とバイト数


String 型の変数に格納されている文字数は Length 関数で取得できます.PChar 型の変数に格納されている文字数は StrLen 関数を使用して取得できます.
PChar 型の文字数は Length 関数でも取得できます.Length 関数の引数は String 型ですが,引数を PChar 型の変数にすると型変換が行われます.



[備考 1]
文字数のことを「文字列の長さ」と言うこともあります.
Length 関数では,サロゲートペアの文字等,複数のエレメントで構成されている文字が含まれている文字列の文字数は正しく取得できません.


[備考 2]
Char 型の静的配列で,配列要素のインデックスが 0 から始まる場合,実際に文字が格納されている要素数も StrLen 関数で取得できます.これは一般的に言う「文字数」と同じです.
Length 関数で取得した Char 型の配列の「長さ」は,要素数であって文字として有効な文字数ではありません.Null 文字等,初期化されていない分も含まれます.



図23
PChar 型の変数に格納されている文字数を取得
  • StrLen 関数または Length 関数で取得

リスト29
//=============================================================================
//  PCharに格納されている文字数を取得
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSrcStr : string;
  LPChar  : PChar;
begin
  LSrcStr := 'Char 型文字配列を String に';
  LPChar  := PChar(LSrcStr);

  Memo1.Lines.Add('文字数 : ' +  IntToStr(StrLen(LPChar)));
  Memo1.Lines.Add('文字数 : ' +  IntToStr(Length(LPChar)));
end;


Length 関数で取得した値を文字数と書いていますが,実際にはエレメント単位の値です.
下に示すコードで使用している文字列の 1 番目の文字は「𠮷野屋」の先頭の文字である「𠮷」で,部首は「つち」です.この文字はサロゲートペアな文字です.2 番目の文字は「吉 (キチ)」 という文字で部首は「さむらい」です.

この文字列の長さを Length 関数で取得すると 5 になります.MECSUtils の MecsLength 関数を使用すると表示通りの文字数が取得できます.Length 関数で取得する値は,サロゲートペアな文字や結合文字が含まれていなければ文字数となります.

String 型の文字列データが使用するバイト数は ByteLength 関数で取得できます.1 エレメントが使用するのは SizeOf(Char) バイトです.したがって,String 型の文字列データが使用するバイト数は,(エレメント数 x SizeOf(Char)) です.



図24
サロゲートペアな文字が含まれている文字列
  • 順番に
  • テストに使用した文字列
  • Length 関数で取得した値
  • MECSUtils.MecsLength 関数で取得した値
  • ByteLength 関数で取得した値

リスト30
implementation

uses
  MECSUtils;

{$R *.dfm}

//=============================================================================
//  String 型の文字列の長さ
//  サロゲートペアな文字がある場合
//
//  このコードの実行には uses に MECSUtils が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LString  : string;
  LInteger : Integer;
begin
  Memo1.Lines.Clear;

  LString := #$D842#$DFB7 + '吉' + #$4F60#$597D;
  Memo1.Lines.Add(LString);

  LInteger := Length(LString);
  Memo1.Lines.Add('文字列長さ : ' + IntToStr(LInteger) + ' エレメント');

  LInteger := MECSUtils.MecsLength(LString);
  Memo1.Lines.Add('文字列長さ : ' + IntToStr(LInteger) + ' 文字');

  LInteger := ByteLength(LString);
  Memo1.Lines.Add('文字列長さ : ' + IntToStr(LInteger) + ' バイト');
end;




17_PChar 型の文字列を結合する


String 型の文字列は + 演算子で結合できましたが,PChar 型の文字列は + 演算子では結合できません.StrCat 関数を使用します.
結合した結果の文字列を別に用意して,その文字列に必要なメモリ領域を確保します.文字列の結合は文字列単位で StrCat を,結合する文字列の数だけ実行します.
このコードではメモリの確保に StrAlloc 関数を使用しています.StrAlloc 関数は文字数を指定してメモリ領域を確保します.使用後は StrDispose 関数で確保したメモリ領域を破棄します.




図25
PChar 型の文字列の結合
  • StrCat 関数で結合する
  • 結合後の PChar 型のメモリ領域の確保が必要

リスト31
//=============================================================================
//  Pchar型の文字列の結合
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPCharA : PChar;
  LPCharB : PChar;
  LPChar  : PChar;
  LLen    : Integer;
begin
  LPCharA := 'PChar 型の文字列 ';
  LPCharB := '結合結果';

  //結果のPChar文字列に必要なメモリを確保
  //Null終端文字の分が必要
  LLen   := StrLen(LPCharA) + StrLen(LPCharB) + 1;
  LPChar := StrAlloc(LLen);
  try
    //PChar型の文字列を結合
    StrCat(LPChar, LPCharA);
    StrCat(LPChar, LPCharB);

    //確認表示
    Memo1.Lines.Add(LPChar);
  finally
    StrDispose(LPChar);
  end;
end;




18_PChar 型の文字列を 1 文字単位で処理


AnsiString 型の文字列では 1 文字単位で操作する際,1 バイトの文字か 2 バイトの文字であるかを判定する必要がありました.ここまでのサンプルで確認できるように,ユニコード版の String 型では文字単位での処理が可能です.これは PChar 型の文字列でも同じです.
以下のコードでは PChar 型の文字列を 1 文字づつ取り出して表示します.文字要素のインデックスで各文字を取得しています.



リスト32
//=============================================================================
//  PChar 型の各文字は文字のインデックスで取得できる
//  String 型の最初の文字のインデックスは 1 であるが PChar の場合は 0
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LPChar : PChar;
  LChr   : Char;
  LIndex : Integer;
begin
  Memo1.Lines.Clear;

  LPChar  := '文字列ABC';

  for LIndex := 0 to Length(LPChar) - 1 do begin
    LChr := LPChar[LIndex];
    Memo1.Lines.Add(LChr);
  end;
end;


上のサンプルで使用しているコードでは文字単位で文字を取得することはできますが,インデックスを指定して文字を代入することはできません.
次のコードのように,PChar 型の文字列を格納するメモリ領域を確保してから操作すれば,インデックスを指定して文字を代入できるようになります.



図26
PChar 型の文字列に 1 文字づつ文字を代入

リスト33
//=============================================================================
//  PChar 型の文字列は文字配列
//  文字単位で操作する例 (代入)
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
  LText  : string;
  LChr   : Char;
  LIndex : Integer;
begin
  Memo1.Lines.Clear;

  LText := 'PChar 型の文字列';

  // PChar 型の変数用のメモリを確保
  LPChar := AllocMem(100);
  try
    // string 型の文字列の最初の文字のインデックスは 1
    // PChar 型の文字列の最初の文字のインデックスは 0
    // LChr は 1 文字の型 (Char 型) の変数
    for LIndex := 1 to Length(LText) do begin
      LPChar[LIndex - 1] := LText[LIndex];
      LChr :=  LPChar[LIndex - 1];
      Memo1.Lines.Add(LChr);
    end;
  finally
    // 確保したメモリを開放
    FreeMem(LPChar);
  end;
end;




19_複数の Null 終端文字列を格納する PChar 型の文字列


Null 終端文字列を複数格納した PChar 型の文字列があります.文字列の最後は Null 文字 (#0) が 2 つあります.その例です.
この PChar 型の文字列から各々の文字列を取得するには,まず PChar 型の変数として取得します.すると最初の Null 終端文字までの文字列が取得できます.PChar はポインタです.そこでポインタを,取り出した文字数分だけ移動してまた PChar 型の変数として取得します.これを繰り返します.



[備考]
Null 終端文字列を複数結合した形式の文字列は,レジストリの REG_MULTI_SZ 型の値に使用されています.また,TSHFileOpStruct レコード型のファイル指定のメンバでも使われます.



図27
PChar 型の変数に複数の Null 終端文字列を格納

リスト34
//=============================================================================
//  Null終端文字列を複数格納したPChar型の文字列
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
begin
  LPChar := '北穂高岳' + #0+ '奥穂高岳' + #0 + '前穂高岳' + #0 + '涸沢岳' + #0#0;


  //Null終端文字列を複数格納したPCharの変数から各々のNull終端文字列を取得する
  while (StrLen(LPChar) > 0) do begin
    //取得した文字列を表示
    Memo1.Lines.Add(LPChar);
   //取得元のポインタを移動(+1はnull終端(#0)の分)
    //LPChar := LPChar + Length(LPChar) + 1; でもよい
    LPChar := LPChar + StrLen(LPChar) + 1;
  end;
end;




20_AnsiString 型の文字列と PAnsiChar 型の文字列の使用


Delphi 2009 以降の Unicode 版の Delphi でも Ansi 版の文字列を使用することができます.
Ansi 版の String 型は Unicode 版の Delphi では AnsiString 型に相当しますが,機能は拡張されています.Ansi 版の PChar 型は Unicode 版の Delphi では PAnsiChar 型に相当します.

以下は実際の使用例です.AnsiString 型の文字列を StrPCopy 関数を使用して PAnsiChar 型の文字列に変換します.この時,StrPCopy 関数は Ansi 版の関数を使用します.Ansi 版の関数類を使用するには uses に AnsiStrings を追加して明示的にユニットスコープ名を付けて使用します.
下のコードでは Length 関数を使用していわゆる「文字列の長さ」を取得していますが,Ansi 型の文字列の「長さ」はバイト数となります.このバイト数は,半角文字では 1 バイト,全角文字の時は 2 バイトになります.

TMemo に文字列を表示する際,string で型キャストしています.これは「W1057 文字列の暗黙のキャスト ('AnsiChar' から 'string')」という警告を表示しないようにするためです.



図28
AnsiString 型の文字列を PAnsiChar 型の文字列としてコピーする例

リスト35
implementation

uses
  AnsiStrings;

{$R *.DFM}

//=============================================================================
//  Unicode版のDelphiでAnsiString型とPAnsiChar型の文字列を使用
//
//  Ansi文字列の処理にはAnsiStringsユニット内の関数類を使用する
//  Ansi文字列のLength関数の実行結果はバイト数(半角は1バイト,全角は2バイト)
//
//  usesにAnsiStringsが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LString     : string;
  LAnsiString : AnsiString;
  LPAnsiChar  : PAnsiChar;
begin
  LString     := '日本語 ABC 文字列';
  LAnsiString := AnsiString(LString);
  LPAnsiChar  := AllocMem(Length(LAnsiString) + 1);
  try
    //AnsiString型の文字列をPAnsiChar型の文字列としてコピー
    AnsiStrings.StrPCopy(LPAnsiChar, LAnsiString);

    //結果の表示
    Memo1.Lines.Add(string(LPAnsiChar));

    //Length関数で「文字列の長さ」を取得して表示
    Memo1.Lines.Add('');
    Memo1.Lines.Add(IntToStr(Length(LString)));
    Memo1.Lines.Add(IntToStr(Length(LAnsiString)));
    Memo1.Lines.Add(IntToStr(AnsiStrings.StrLen(LPAnsiChar)));
  finally
    FreeMem(LPAnsiChar);
  end;
end;




21_String 型のプロパティとメソッド - ヘルパ TStringHelper


Delphi XE3 では,文字型,文字列型あるいは各種の数値の型と論理型に対して,レコードヘルパが実装されました.これらのヘルパの機能を利用すると,各種の変数にプロパティやメソッドが実装されたのと同じことになり便利です.
下のサンプルコードでは,String 型のヘルパである Length プロパティを使用して,文字列の長さを取得しています.その他のプロパティ,メソッド類はオンラインヘルプで確認してください.



図29
String 型のヘルパの使用例

リスト36
//=============================================================================
//  String 型のヘルパ
//  String 型に対してプロパティやメソッドが実装されている (Delphi XE3 以降)
//  String 型以外の型にもヘルパが実装されている
//
//  これは Length プロパティの使用例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LText : string;
  LLen  : Integer;
begin
  Memo1.Lines.Clear;

  LText := '日本語ABC';
  LLen  := LText.Length;
  Memo1.Lines.Add(IntToStr(LLen));
end;