LoginSignup
4
2

More than 5 years have passed since last update.

無料の開発環境 Delphi Starter Edition で Object Pascal 言語を優しく学ぶ 第5回 「配列 と レコード」

Last updated at Posted at 2017-02-20

Delphi Starter Edition チュートリアルシリーズ シーズン2 第5回 「配列 と レコード」

image

2017年1月23日より 「Delphi / C++Builder Starter チュートリアルシリーズ」 シーズン2、全9回、3月27日まで、毎週月曜日、Delphiパートが 17時00分~17時20分、 リアルタイム放送スペシャルコンテンツが5分~10分、C++Builderパートが 17時30分~17時50分の時間割でお送りしています。(今回、第5回はDelphiの部の補講があり、18時30分終了となります)

無料でダウンロード & 利用できる開発環境のDelphi / C++Builder Starter エディションを使用して、プログラミング言語のDelphi (Object Pascal ), C++の基礎を学ぶオンラインセッションです。

使用したスライドなどの情報を掲載します。

Webセミナースライド

第5回 「配列 と レコード」のスライドは下記アドレスよりご参照いただけます。

アジェンダ

  • 今日のねらい
    • 配列とは何か、またその使い方を知る
    • レコードとは何か、またその使い方を知る
  • 実施内容
    • 配列の定義、宣言、使用例
    • 静的・動的配列の違いと参照型について学ぶ
    • レコードの定義、宣言、使用例
  • 補講
    • ポインタについて学ぶ
    • メソッド付きレコードの定義、宣言、使用例
    • ついでにバリアントを少々

開発環境インストール

Delphi / C++Builder Starter チュートリアルシリーズ シーズン1 第1回 ‟無料で始めよう アプリ作成„をご参考になり、開発環境をインストールしてください。

以下、Delphi Webセミナーの本筋

「配列」データ型

  • 配列データ型とは? どんなもので、何に使えるのか?
    • データ型をリスト化 (連続した列として取り扱える)
    • 「インデックス」と呼ばれるものを使用して配列内の一つを指定してアクセス
    • 多次元配列も可能

例:Integer型の : 変数識別子iDate の配列の図

識別子と
インデックス
iDate[1] iDate[2] iDate[3] iDate[4] iDate[5] iDate[6] iDate[7]
データ 28 26 23 30 32 31 26
  • 配列の種類には「静的配列」と「動的配列」

    • 静的配列: type内の定義やvar内の変数宣言時に配列数を決めている配列
    • 動的配列: 定義や宣言時には長さを決めず、実装時に配列の長さを指定
  • Low関数, High関数 ,Length関数, Copy関数が有効に使える

静的配列の定義と変数宣言方法

  • 静的配列の定義
    • type ブロックで配列データ型となる識別子と、どんなデータ型の配列(array)であるか宣言
    • 配列数がtype定義内でインデックスとして指定されるため、配列の数をあらかじめ知ることができる
//静的データ配列の定義文法
type  //typeブロック
配列データ型定義識別子 = array [インデックス(部分範囲指定で配列数指定)] of データ型;
//静的データ配列の定義コード例
type
TDayTemperaturs = array [1..24] of Integer; //[a..x]なども範囲指定で使用可能

配列データを実際に使用する前の配列変数宣言

var //静的データ配列定義を使用する際の変数宣言文法
データ配列変数識別子 : 定義した配列データ型識別子;
var //静的データ配列定義を使用する際のコード例
myDayTemp1 : TDayTemperaturs;

静的配列のアクセスと使用例

  • データ配列変数を宣言後…
    • [ ] でくくったインデックスを変数に付加してアクセス
    • インデックスはType定義の時に設定した部分範囲の値で指定
    • TDayTemperaturs=array[1..31] of Integer; なら[1]~[31]がインデックス
//どこかのprocedure 内 (一か月の気温を保持する配列例)
var //静的データ配列定義を使用する際のコード例
myDayTemp1 : TDayTemperaturs; //配列変数識別子 : データ配列定義識別子
begin
  myDayTemp[1] := 30; //並列変数識別子 [インデックス番号] でアクセス
  myDayTemp[2] := 32;
  //省略…
  myDayTemp[24] := 29;
  //以下はダメな例  
 { myDayTemp[32] := 28;  //これはインデックスの範囲外なのでコンパイル時にエラーとなる }
end;

動的配列の定義と変数宣言方法

  • 動的配列の定義
    • type ブロックで配列データ型となる識別子と、どんなデータ型の配列(array)であるか宣言
    • type ブロック内の定義ではインデックス数を指定しない
//動的データ配列の定義文法
type  //typeブロック
配列データ型識別子 = array of データ型; //配列のインデックスを指定しない
//動的データ配列の定義コード例
type
TDayTemperaturs = array of Integer; 

配列データを実際に使用する前の変数宣言

var //動的データ配列定義を使用する際の変数宣言文法
データ配列変数識別子 : 定義した配列データ型;
var //動的データ配列定義を使用する際のコード例
myDayTemp1 : TDayTemperaturs;

動的配列のアクセスと使用例

  • データ配列変数を宣言後…
    • SetLength(配列変数, 配列数)を指定して配列の長さ決定
    • インデックスは 0 から 配列の長さ -1 の値となる
    • SetLength(myDayTemp1,28); なら[0]~[27]がインデックスとなる
//どこかのprocedure 内
var //動的データ配列定義を使用する際のコード例
  myDayTemp1 : TDayTemperaturs;
  I : Integer;
begin                      //SetLength (動的配列変数識別子, セットしたい長さ);
  SetLength(myDayTemp1,28); //長さを28に設定。動的配列のインデックスは[0]~[27]となる
  for I := Low(myDayTemp1) to High(myDayTemp1) do //配列にはLow, Highはとても有効
    myDayTemp1[I] := [I]; //配列変数識別子 [インデックス]でアクセス
end;

静的配列と動的配列の変数識別子の扱いの違い

  • 静的配列と動的配列で異なる配列変数識別子の意味と扱われ方

    • 静的配列の変数の識別子は配列に入っている値の変数そのもの(いままでの 変数の扱いと同じ)
    • 動的配列の変数の識別子は、その配列のデータが入っているポインタを(入れ物の場所・アドレス)示している「参照型」
  • 配列変数識別子に[インデックス]を付加して要素にアクセスした場合には、動的・静的配列変数識別しでも配列に入っている値にアクセスできる

参照型とは?

  • 静的・動的配列の変数の識別子の中身は…
    • 動的配列の変数は「参照型」で、配列データの在る「場所」を示している
    • 静的配列は、値そのもの image

続・参照型

  • 静的配列と動的配列のコピーをしたときにその違いが明確に表れる…
    • データコピーするために静的配列と動的配列の変数識別子をvarブロックを宣言
    • 代入演算子 := でそれぞれの変数に 代入すると… image

続々・参照型

  • 静的配列と動的配列をコピーした変数に対して操作すると…
    • コピー用静的配列変数名にインデックスを使用して新たな値を代入した場合
    • コピー用動的配列変数名にインデックスを使用して新たな値を代入した場合 image

関数・手続きの引数(パラメータ)として配列を使用

  • 配列の数を指定せずにルーチンのパラメータとして使用可能(オープン配列パラメータと呼ばれる)
  • 動的配列変数の参照を変えないように パラメータにconstキーワードを付けて宣言
  • ルーチンのパラメータにconstキーワードを付加することにより変更不可となる
  • ルーチン呼び出し時に自由に配列数を決定できる
function Goukei(const Ai: array of Integer): Integer; //オープン配列をパラメータに設定
var
  I : Integer;
begin
  Result := 0;
  for I := Low(Ai) to High(Ai) do //引数として配列数が未定なのでLow/Highを使用しておく         
    Result := Result + Ai[I];  //配列のすべてを合計する処理
end;
//オープン配列パラメータを持つルーチンの使用例
Goukei([10, Y, 27*I]); //[]でくくることにより配列扱いとなる (Y,IはInteger型の変数)
Goukei(di); //動的配列変数を引数とすることもできる(di は array of Integer の動的配列変数)

「レコード」データ型

  • 複数のデータ型の集めて、データの型として再定義したもの
  • typeブロックで定義してvarで識別子を宣言してから使用
  • 定義したレコードをさらに配列として定義することもできる
//レコード定義の構文
type
  レコードデータ型定義識別子 = record //レコードデータ型識別子を定義
    レコード内に保持するデータ識別子1: データ型; //レコード内のフィールド定義(保持データ)
    レコード内に保持するデータ識別子2: データ型;
    レコード内に保持するデータ識別子3: データ型;
  end; //レコード定義はend; で閉じる
//レコード定義のコード例
type
  TMyDate= record //TMyDate というレコードのデータ型識別子定義
    Year: Integer; //Yearというフィールド名とそのデータ型(Integer)を定義
    Month: Byte; //Monthというフィールド名とそのデータ型(Byte)を定義
    Day: Byte; //Dayというフィールド名とそのデータ型(Byte)を定義
  end;

「レコード」データ型 の使用例

  • 定義したレコード使用する場合のコード例
    • レコード変数名.フィールド識別子で各フィールドにアクセス
    • 代入演算子で同じレコードデータ型の値をコピーできる(参照型ではなく、値がコピーされる)
//レコードデータ型の変数宣言
var
  Birthday, ADay : TMyDate; //TMyDateレコードデータ型の変数識別子 Birthday, Adayを宣言
//レコードデータ変数の使用例
begin
  Birthday.Year := 1995; //レコード変数.(ドット)フィールド名でアクセス可能
  Birthday.Month := 2;
  Birthday.Day := 14;

  Aday := BirthDay; //レコードを代入演算子で代入すると、値がそのままコピーされる
  Aday.Year := 2017;
  ShowMessage(Birthday.Year.ToString); //”1995”が表示される – Adayの変更に影響されない
  ShowMessage(Aday.Year.ToString); //”2017”が表示される
end;

レコード配列の使用

  • 定義したレコードを配列として使用可能
    • 動的配列、静的配列いずれも可能
    • 参照型、値型の扱いは配列データ型と同じ
//レコードデータ型の配列変数識別子宣
type
  TListMyDate = array of TMyDate; //TMyDateレコードデータ型の(動的)配列を定義
var
  ListMyDate : TListMyDate; //レコードデータ型の(動的)配列の変数識別子を宣言
{typeブロックで定義をせずにvarブロックで ListMyDate: array of TMyDate と宣言してもOK }
//レコード配列変数の使用例(動的配列の使用例)
begin
  SetLength(ListMyDate,3); //SetLengthで3つの配列をセット(Indexは[0]~[2])
  ListMyDate[0].Year := 1995; //使用法は配列と同じインデックスで配列の要素にアクセス
  ListMyDate[0].Month := 2; //インデックス後にフィールド名を指定して各フィールドにアクセス
  ListMyDate[0].Day := 14;
end;

配列、レコードにまつわる機能について参考となるリンク

補講

ここから下は初級者編から少しレベルアップした内容です。

補講で使用しているサンプルコード

サンプルコードを実際に見れるようにgithubにアップロードしています。

続々々・参照と配列のコピーの参考コード

image

上の図を使って、静的配列変数識別子と動的配列変数識別子のコピー動作(代入演算子:=を使った代入)の違いを説明しましたが、実際にこのコピー動作を行うサンプルコードは以下のようになります。

var
  DoutekiArray, DoutekiArrayCopy: array of Integer; //動的配列変数宣言
  SeitekiArray, SeitekiArrayCopy: Array [0 .. 1] of Integer;  //静的配列変数宣言
  I: Integer;
begin
  SetLength(DoutekiArray, 2); //動的配列の元となるものにSetLengthで実領域確保
  //動的配列の元、静的配列の元にそれぞれインデックスと同じ数値を入れておく
  for I := Low(DoutekiArray) to High(DoutekiArray) do
    DoutekiArray[I] := I;
  for I := Low(SeitekiArray) to High(SeitekiArray) do
    SeitekiArray[I] := I;
  //動的配列と静的配列をコピー先の変数に代入
  DoutekiArrayCopy := DoutekiArray;
  SeitekiArrayCopy := SeitekiArray;
  //コピー先の配列変数のインデックス[0]の値を変更
  SeitekiArrayCopy[0] := 100;
  DoutekiArrayCopy[0] := 100;
  //コピー元、コピー先の配列変数のインデックス[0]が示す値を確認
  Show('動的配列元[0]:' + DoutekiArray[0].ToString + '/ 動的配列コピー[0]:' +
    DoutekiArrayCopy[0].ToString);
  Show('静的配列元[0]:' + SeitekiArray[0].ToString + ' / 静的配列コピー[0]:' +
    SeitekiArrayCopy[0].ToString);
end;

これを実行すると下記の結果が得られます
image

ポインタ(ポインタ型)

  • データが入っている場所(アドレス)情報が「ポインタ」
  • ポインタ「型」は、そのポインタ情報を保持するデータ型
    • 通常の値を扱う変数も、そのデータ型のアドレスを扱うためのポインタ型がある
    • ポインタ型を定義、宣言するにはそのデータ型の先頭に ^ を付ける
    • 変数識別子のポインタ(アドレス)は先頭に@を付けるとそのアドレスを意味する
var
  IPointer: ^Integer; //Integer型のポインタを保持する変数識別子を宣言
  MyInt: Integer;   //Integer型の変数識別子を宣言
begin
  MyInt := 100; //Integer型変数に100を代入

 //変数識別子の先頭に@をつけることで、その変数のポインタを意味する
  IPointer := @MyInt; //Integer型のポインタ変数に、Integer型変数のポインタを代入

 //ポインタを保持する変数識別子の後ろに ^  をつけることで実際の値の場所を意味する
  IPointer^ := 20;  //ポインタ変数が参照している実データの場所に 値20を代入
  ShowMessage(MyInt.ToString); //メッセージとして 20 が表示される
end;

動的配列の操作

  • 動的配列変数に対してSetLengthを使わず直接代入で配列設定可能
  • + 演算子を使って 動的配列の連結が可能
  • Insert, Delete 関数で挿入、削除が可能
  • 動的配列の値をコピーしたい場合には Copy
var
  di, diCopy: array of Integer; //動的配列変数 di の宣言
begin
  di := [1, 2, 3];      //動的配列変数 diへの直接代入(Index 0~2の順に数値 1,2,3代入)
  di := di + di;        //di に di を連結(配列の長さは6となり、中身は1,2,3,1,2,3)
  di := di + [4, 5];    //di の配列の後部に さらに2つ直接代入(中身は1,2,3,1,2,3,4,5) 
  Insert([8, 9], di, 3);//diのIndex[3]に8,9を代入追加(中身は1,2,3,8,9,1,2,3,4,5)
  Delete(di, 0, 5);     //diのIndex[0]から、配列を5つ削除(1,2,3,8,9,1,2,3,4,5)
  diCopy := Copy(di,low(di),Length(di));//Copy(動的配列変数,開始インデックス,コピー長さ)
                                        //diCopyの中身が、1,2,3,4,5となる  
end;

ルーチンのパラメータに型可変オープン配列パラメータを使用

  • array of const をパラメータの型として設定することにより、配列のデータ型を指定せずルーチンを宣言、実装できる
    • array of const の引数は `TVerRecというレコード型の配列として受け渡される
    • TVerRecはデータ型情報を持った「レコード」なので、これでどんなデータ型か判断して処理する
function Goukei(const Args: array of const): Extended; //オープン配列をパラメータに設定
var
  I : Integer;
begin
  Result := 0;
  for I := Low(Args) to High(Args) do //引数として配列数が未定なのでLow/Highを使用しておく         
    case Args[I].Vtype of
      vtInteger: //Integer型の時の処理…
      vtChar: //Char型の時の処理…
 //省略
end;

メソッド付きレコード

レコード内に紐付けされたfunctionprocedure を持てる
レコードに紐付けられたルーチンは「メソッド」と呼ばれる
レコード内のフィールドに他ユニットからのアクセス制限を付加
他ユニットからアクセスできないようにするにはprivateキーワード
他ユニットからアクセスさせる場合にはpublic キーワード (キーワードを付けていなければ、publicとして扱われる)

// メソッド付き レコード定義の構文
type //Typeブロック内にて定義
  レコード方定義識別子 = record
  private //他unitからのアクセスを制限するキーワード private
    フィールド識別子定義: データ型定義;  //これらフィールドアは他unitからアクセスできない
    //…
    procedure ルーチン識別子定義(パラメータ:データ型); //このprocedure も他unitからアクセスできない
  public //外部unitから使用させる場合のキーワード public
    // type定義内ではメソッドの識別子定義のみ.メソッドの実装はimplementationブロックで行う
    Procedure ルーチン識別子定義(パラメータ:データ型); //このprocedure は外部からアクセス可能
    function ルーチン識別子定義(パラメータ:データ型): 戻り値データ型; //外部からアクセス可能
end;

メソッド付きレコード定義の例

  • メソッド付きレコード定義の例
    • レコード定義内のメソッドは定義のみ。実装はimplementationブロックで行う
type
  TMyDate = record //TMyDateというレコード識別子の定義
  private //他unitからのアクセスを制限するキーワード private
    Year: Integer; //内部データ保持用のフィールド の定義。外部unitからのアクセスを制限
    Month: Integer;
    Day: Integer;
    myDateTime: TDateTime;
    procedure SetDatetime; //内部処理用のprocedureの宣言外部unitからのアクセスを制限
  public //外部unitから使用させる場合のキーワード public
  // type定義内ではメソッドの識別子定義のみ.メソッドの実装はimplementationブロックで行う
    procedure SetValue(Y, M, D: Integer); //レコードの値セット用のProcedure識別子定義
    function GetValue: TDateTime; //レコードの値取得用のfunction識別子定義
  end;

メソッド付きレコード実装と使用の例

implementation //implementation ブロック
{$R *.fmx}
procedure TMyDate.SetDatetime; //レコード定義識別子.(ドット)メソッド定義識別子の形式で実装。TMyDataレコード定義のSetDateimeメソッド実装
begin
  //フィールドのYear, Month, Day データを myDateTime(TDateTime型)にエンコードして代入
  myDateTime := EncodeDate(Year, Month, Day); //メソッド実装内ではレコードの定義識別子なしでフィールドを使用可能
end;

procedure TMyDate.SetValue(Y, M, D: Integer);//TMyDateレコードのSetValueメソッドの実装
begin
  Year := Y; //SetValueの引数として渡されたデータをレコード内のフィールドに代入
  Month := M; //メソッド実装内ではレコードの定義識別子なしでフィールド名を使用可能
  Day := D;
  SetDatetime; //privateキーワードで宣言されていた内部メソッドを呼び出し。メソッド実装内ではレコード定義識別子なしでメソッドを使用可能
end;

function TMyDate.GetValue: TDateTime; //TMyDateレコードのGetValueメソッドの実装
begin
  Result := myDatetime; //myDateTimeフィールドを戻り値として戻しているだけ。メソッド実装内ではレコード定義識別子成してフィールドを使用可能
end;

procedure TForm1.Button1Click(Sender: TObject); //メソッド付きレコードの使用例。ここではボタンのクリック時に使用している
var
  MyBirthday: TMyDate; //上記で定義、実装しているメソッド付きTMyDataレコード型の変数識別子MyBirthdayを宣言
begin
  MyBirthday.SetValue(1995, 2, 14); //メソッド使用時はレコード変数識別子.メソッド名 で使用。  ShowMessage(DateTimeToStr(MyBirthday.GetValue)); //メソッド使用時は レコード変数識別子.メソッド名で使用。

こんな結果が出ます
image

バリアント

強い型付け言語のdelphi (Object Pascal)にも型なしデータ型があります

  • 型なし Variant
    • 様々な型の値を代入できるデータ型
    • 実行時変換が妥当な場合には、変換を自動で行う。変換できない場合には「例 外」が発生する
    • 実行速度は遅い
var
  V: Variant; //バリアント型変数識別子 V の宣言
  Str : string; //文字列型変数識別子 Strの宣言
begin
  V:=10; //値は整数の10としてバリアント変数Vに代入される
  V:='Hello'; //値は文字列の’Hello’としてバリアント変数Vに代入される
  V:=12.34; //値は浮動小数点の12.34としてバリアント変数Vに代入される

  Str := V; //Strには文字列としての’12.34’が代入される
  V := V+20; //Vには 浮動小数点12.34 + 20 の合計 32.34が浮動小数点として代入される
  V:= V + Str; //Vには32.34と、’12.34’の浮動小数点としての合計 44.68が代入される
  V:= 'Hello'; //Vには文字列’Hello’が代入される
  V := V + Str; //Vには文字列’Hello’と文字列’12.34’の連結’Hello12.34’が代入される
end;

おわりに

第5回 2月20日分の Delphiパート「配列 と レコード」は以上です。
<<シーズン2 第4回の記事はこちら

シーズン2 第6回の記事はこちら>>

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2