ユニットとはVBにおける標準モジュールのようなものです。 ただ、標準モジュールとは異なり、Object Pascalではインターフェイス部と呼ばれる外部に公開するプロシージャ・関数・定数・型などを記述する部分があります。 これはC/C++でいうプロトタイプ宣言によく似たものです。 また、ユニットはメインプログラムのあるファイルとは別のファイルに記述されます。 簡単なユニットとそれを用いたサンプルを次に示します。
{$APPTYPE CONSOLE} program LearningDelphi; uses SysUtils, SampleUnit in 'SampleUnit.pas'; begin SampleProcedure; Writeln( SampleFunction ); // 終了メッセージを表示 Write( 'Press any key to continue' ); Readln; end.
unit SampleUnit; interface uses SysUtils; procedure SampleProcedure; function SampleFunction : string; implementation var sampleString: string; procedure SampleProcedure; begin Writeln( sampleString ); end; function SampleFunction : string; begin result := sampleString; end; initialization begin Writeln( '初期化処理がなされます' ); sampleString := 'SampleUnit'; end; finalization begin // メインプログラム終了後に呼び出されます end; end.
初期化処理がなされます SampleUnit SampleUnit
まず、ユニットを利用する側ではuses文で使用するユニットとそれが記述されているファイルを指定します。 それ以外に特別な記述は必要ありません。 後はユニットにあるプロシージャや型などを利用するだけです。 そして、それらの要素を実際に記述するのがユニットの方です。 ユニットは次のような構造になっています。
まず、ユニットヘッダではユニットの名前を指定します。 Delphiではここで指定した名前とファイル名が一致している必要があります。 ユニット本体の記述では、インターフェイス部と実現部の二つの部分を記述します。 実現部は実装部と言い換えてもいいかもしれません。 インターフェイス部でユニット外に公開するプロシージャ・関数の宣言部だけを記述し、実現部でその実装を行います。
また、実装部にはinitialization文とfinalization文をオプションで記述することができます。 ユニットをインスタンスかできないクラスと考えれば、この部分はコンストラクタ・デストラクタに相当するといえます。 この例ではusesをインターフェイス部で使用しましたが、ユニット同士の循環参照が起こる場合にはいずれかのusesを実現部に移すことで解決できます。 また、usesはできるだけ実現部で使用するべきとされています。
実行結果を検証してみると、メインプログラムから呼び出されたユニットのプロシージャ・関数は適切に動作しています。 また、すべてのプロシージャ・関数などが使用される前にユニットの初期化部が動作しています。 この例の終了処理部にWritelnなどを記述しても、アプリケーション終了直前に呼び出されるため、実際には表示された結果を見ることはできません。
Object Pascalにおけるクラスの概念もC/C++のクラスと大域的には同じといえます。 また、記述方法もC/C++と非常によく似ています。
unit SampleUnit; // インターフェイス部 interface // 型宣言 type TMusume = class // プライベートスコープ private // フィールド m_Age: integer; m_BirthDate: TDateTime; // メソッド function CalcAge: integer; // プロパティプロシージャ function getAge: integer; function getBirthDate: TDateTime; procedure setBirthDate( const birthDate: TDateTime ); // パブリックスコープ public // フィールド Name: string; // プロパティ property Age: integer read getAge; property BirthDate: TDateTime read getBirthDate write setBirthDate; // メソッド procedure Introduce; // コンストラクタ constructor Create( nam: String ); // デストラクタ //destructor Destroy; end; // 実装部 implementation uses SysUtils; // コンストラクタ constructor TMusume.Create( nam: String ); begin Name := nam; end; // プロパティプロシージャ function TMusume.getAge: integer; begin result := m_Age; end; procedure TMusume.setBirthDate( const birthDate: TDateTime ); begin m_BirthDate := birthDate; m_Age := CalcAge; end; function TMusume.getBirthDate: TDateTime; begin result := m_BirthDate; end; // メソッド procedure TMusume.Introduce; var y, m ,d: Word; begin DecodeDate( m_BirthDate, y, m, d ); Write( Name, ' ' ); Write( y, '年', m, '月', d, '日生まれ ' ); Writeln( m_Age, '歳' ); end; function TMusume.CalcAge: integer; var age: Word; by, bm, bd: Word; ny, nm, nd: Word; begin DecodeDate( m_BirthDate, by, bm, bd ); DecodeDate( Now, ny, nm, nd ); age := ny - by; if ( bm > nm ) or ( ( bm = nm ) and ( bd > nd ) ) then age := age - 1; result := age; end; end.nm ) or ( ( bm = nm ) and ( bd > nd ) ) then age := age - 1; result := age; end; end.]]>
まず、type文で「識別子 = class」としてクラス型を宣言します。 さらに、そのクラスのフィールド・インターフェイスを定義します。 各メンバはスコープを持つことができ、基本的にはC/C++と同じ「private, proteced, public」の三つです。 これ以外にもいくつか特殊なものがありますがここでは省略します。 プロシージャ・関数・フィールドのインターフェイスは今までどおりです。 また、Object Pascalのクラスはプロパティを持つことができます。 プロパティとなるフィールドはpropertyで宣言し、別途プロパティプロシージャを用意してある必要があります。
プロパティプロシージャの名前は任意ですが、書き込み用プロシージャは値をひとつだけ取り戻り値なし、読み取り用関数は引数なしで戻り値ありのものを用意します。 これらのプロパティプロシージャはプロパティ宣言部より前で宣言されている必要があります。 また、プロパティは読み取り専用にも書き込み専用にもすることができ、そうする場合はreadないしwriteを省略します。 この例ではプロパティプロシージャはprivateスコープですが、publicにすることで普通のプロシージャ同様に呼び出すことも可能です。 また、コンストラクタおよびデストラクタを実装することもできます。 その場合は「constructor 識別子(引数リスト);」ないし「destructor 識別子;」などとします。
インターフェイスの宣言が完了したら、実現部でその実装を行います。 これはC/C++のクラスにおけるメンバ関数の外部定義と同様の方法で行います。 つまり、「procedure クラス名.プロシージャ名」、「function クラス名.関数名」のように、関数・プロシージャの名前の前にクラスの名前を指定します。 また、これらのプロシージャの中はクラスの中と同様なので、クラスのメンバにクラス名を付加する必要はありません。 こうしてできたクラスは次のようにして使用します。
{$APPTYPE CONSOLE} program LearningDelphi; uses SysUtils, SampleUnit in 'SampleUnit.pas'; var // クラス型変数を宣言 aibon: TMusume; begin // インスタンスを生成 aibon := TMusume.Create( '加護亜依' ); // プロパティ値を設定 aibon.BirthDate := EncodeDate( 1988, 2, 7 ); // メソッドを呼び出し aibon.Introduce; // インスタンスを破棄 aibon.Free; // 終了メッセージを表示 Write( 'Press any key to continue' ); Readln; end.
まず、クラス型変数の宣言は普通の変数宣言と変わりありません。 インスタンスを生成するには「クラス型.コンストラクタ(引数)」とします。 このときコンストラクタに渡すべきパラメータがないときは省略できますし、クラスにコンストラクタ宣言がないときは暗黙の基底クラスであるTObjectから継承したCreateメソッドを利用することができます。 また、デストラクタも同様で、特にクラスで宣言されていない場合はFreeまたはDestroyメソッドを使用することができます。 その他メソッドの呼び出しやフィールド・プロパティへのアクセスはC/C++やVBと変わりありません。 このコードを実行すると次のようになります。
加護亜依 1988年2月7日生まれ 15歳 Press any key to continue
参考までに、これと同様の動作をするクラスとそれを利用するコードを、VB.NETで組んだものを例示しておきます。 見た目もできる限り似せています。
Imports System Class TMusume Private m_Age As Integer Private m_BirthDate As DateTime Public Name As String ' コンストラクタ Private Sub New(ByVal name As String) Me.Name = name End Sub Public Shared Function Create(ByVal name As String) As TMusume Return New TMusume(name) End Function ' プロパティ Public ReadOnly Property Age() As Integer Get Return m_Age End Get End Property Public Property BirthDate() As DateTime Get Return m_BirthDate End Get Set(ByVal Value As DateTime) m_BirthDate = Value m_Age = CalcAge() End Set End Property ' メソッド Public Sub Introduce() Dim y, m, d As Integer y = m_BirthDate.Year : m = m_BirthDate.Month : d = m_BirthDate.Day Console.Write(Name + " ") Console.Write("{0}年{1}月{2}日生まれ ", y, m, d) Console.WriteLine("{0}歳", m_Age) End Sub Private Function CalcAge() Dim age As Integer Dim by, bm, bd As Integer Dim ny, nm, nd As Integer by = m_BirthDate.Year : bm = m_BirthDate.Month : bd = m_BirthDate.Day ny = Now.Year : nm = Now.Month : nd = Now.Day age = ny - by If (bm > nm) OrElse ((bm = nm) AndAlso (bd > nd)) Then age -= 1 CalcAge = age End Function End Class Class SampleClass Shared aibon As TMusume Public Shared Function Main(ByVal args() As String) As Integer aibon = TMusume.Create("加護亜依") aibon.BirthDate = New DateTime(1988, 2, 7) aibon.Introduce() ' 解放処理は無用 Return 0 End Function End Classnm) OrElse ((bm = nm) AndAlso (bd > nd)) Then age -= 1 CalcAge = age End Function End Class Class SampleClass Shared aibon As TMusume Public Shared Function Main(ByVal args() As String) As Integer aibon = TMusume.Create("加護亜依") aibon.BirthDate = New DateTime(1988, 2, 7) aibon.Introduce() ' 解放処理は無用 Return 0 End Function End Class]]>
Object Pascalのクラスでは、当然継承もサポートされます。
unit SampleUnit; // インターフェイス部 interface // 型宣言 type TMusume = class // プライベートスコープ private // フィールド m_Age: integer; m_BirthDate: TDateTime; // メソッド function CalcAge: integer; // プロパティプロシージャ function getAge: integer; function getBirthDate: TDateTime; procedure setBirthDate( const birthDate: TDateTime ); // パブリックスコープ public // フィールド Name: string; // プロパティ property Age: integer read getAge; property BirthDate: TDateTime read getBirthDate write setBirthDate; // メソッド procedure Introduce; virtual; // コンストラクタ constructor Create( nam: string ); end; // TMusumeの派生クラス TDerived = class(TMusume) public // フィールド BloodType: string; // メソッド(オーバーライド) procedure Introduce; override; // コンストラクタ constructor Create( nam: string; bldType: string ); end; // 実装部 implementation uses SysUtils; // コンストラクタ constructor TMusume.Create( nam: string ); begin Name := nam; end; // プロパティプロシージャ function TMusume.getAge: integer; begin result := m_Age; end; procedure TMusume.setBirthDate( const birthDate: TDateTime ); begin m_BirthDate := birthDate; m_Age := CalcAge; end; function TMusume.getBirthDate: TDateTime; begin result := m_BirthDate; end; // メソッド procedure TMusume.Introduce; var y, m ,d: Word; begin DecodeDate( m_BirthDate, y, m, d ); Write( Name, ' ' ); Write( y, '年', m, '月', d, '日生まれ ' ); Writeln( m_Age, '歳' ); end; function TMusume.CalcAge: integer; var age: Word; by, bm, bd: Word; ny, nm, nd: Word; begin DecodeDate( m_BirthDate, by, bm, bd ); DecodeDate( Now, ny, nm, nd ); age := ny - by; if ( bm > nm ) or ( ( bm = nm ) and ( bd > nd ) ) then age := age - 1; result := age; end; // 派生クラスでのコンストラクタ constructor TDerived.Create( nam: string; bldType: string ); begin Name := nam; BloodType := bldType; end; // オーバーライドされたメソッド procedure TDerived.Introduce; var y, m ,d: Word; begin DecodeDate( m_BirthDate, y, m, d ); Write( Name, ' ' ); Write( y, '年', m, '月', d, '日生まれ ' ); Write( m_Age, '歳 ' ); Writeln( BloodType, '型' ); end; end.nm ) or ( ( bm = nm ) and ( bd > nd ) ) then age := age - 1; result := age; end; // 派生クラスでのコンストラクタ constructor TDerived.Create( nam: string; bldType: string ); begin Name := nam; BloodType := bldType; end; // オーバーライドされたメソッド procedure TDerived.Introduce; var y, m ,d: Word; begin DecodeDate( m_BirthDate, y, m, d ); Write( Name, ' ' ); Write( y, '年', m, '月', d, '日生まれ ' ); Write( m_Age, '歳 ' ); Writeln( BloodType, '型' ); end; end.]]>
{$APPTYPE CONSOLE} program LearningDelphi; uses SysUtils, SampleUnit in 'SampleUnit.pas'; var // クラス型変数を宣言 aibon: TDerived; musume: TMusume; begin // インスタンスを生成 aibon := TDerived.Create( '加護亜依', 'AB' ); // プロパティ値を設定 aibon.BirthDate := EncodeDate( 1988, 2, 7 ); // メソッドを呼び出し aibon.Introduce; // 基底クラス型へキャスト musume := aibon; // メソッドを呼び出し musume.Introduce; // インスタンスを破棄 aibon.Free; // 終了メッセージを表示 Write( 'Press any key to continue' ); Readln; end.
加護亜依 1988年2月7日生まれ 15歳 AB型 加護亜依 1988年2月7日生まれ 15歳 AB型 Press any key to continue
あるクラスを基底クラスとしてクラスを派生するにはtype文で「派生クラス = class(基底クラス)」とします。 Delphiにおける継承は、.NET FrameworkのCLSにおける継承と同じく、クラスの多重継承は認められていないものの、インターフェイスは複数実装することができます。 この例ではTMusumeを継承してTDerivedという派生クラスを作成しています。 まず、Introduceメソッドをオーバーライドするために、 TDerivedでのIntroduceメソッドの宣言の最後にoverrideを付加します。 当時に、オーバーライドされる基底クラスの Introduceメソッドにもvirtualを付加します。 後は各メンバの実装を行うだけです。
ちなみに、実行結果を見ると、TDerived型のインスタンスをTMusume型に代入しても、当然TDerived型の Introduceメソッドが呼び出されます。 しかし、派生クラスでのメソッド宣言でoverrideを取り去ると、派生クラスのメソッドは基底クラスのメソッドをシャドウしたかのような動作になります。
{ 途中省略 } // TMusumeの派生クラス TDerived = class(TMusume) public // フィールド BloodType: string; // メソッド(シャドウ) procedure Introduce; // コンストラクタ constructor Create( nam: string; bldType: string ); end;
加護亜依 1988年2月7日生まれ 15歳 AB型 加護亜依 1988年2月7日生まれ 15歳 Press any key to continue
ただし、このままだと「'Introduce'メソッドが基本型'TMusume'の仮想メソッドを隠しました」という警告が発生します。 これを解決するにはさっき消し去ったoverrideの変わりにreintroduceを付け替えます。
Object Pascalのクラスは、暗黙的にTObjectを継承します。
{$APPTYPE CONSOLE} program LearningDelphi; uses SysUtils, SampleUnit in 'SampleUnit.pas'; var // クラス型変数を宣言 aibon: TDerived; begin // インスタンスを生成 aibon := TDerived.Create( '加護亜依', 'AB' ); // クラス名 Writeln( aibon.ClassName ); // 基底クラス名 Writeln( aibon.ClassParent.ClassName ); // インスタンスのサイズ Writeln( aibon.InstanceSize ); // インスタンスを破棄 aibon.Free; // 終了メッセージを表示 Write( 'Press any key to continue' ); Readln; end.
TDerived TMusume 28 Press any key to continue
ここで呼び出したメソッドは全てTObjectから継承したメソッドです。 このほかにもいくつかメソッドが存在しますが、その多くは特別な場合を除いてあまり使用しないでしょう。
Delphiが賞賛される要因、そしてVisual Basicと比較される要因の一つが、RADにあると思います。 コンソールアプリで遊ぶのをそろそろ切り上げて、簡単なWindowsアプリケーションを作ってみます。 ところで一般的にはよくRAD環境と言いますけど、RADとはRapid Application Developmentの略なので「RAD環境」はRapid Application Development Developmentと冗長な表現になっています。 SerializableAttribute属性とか言っているのと同じ気がするのですがいいのでしょうか。 (確かに日本語の語感からするとRAD環境の方がしっくりくるのですが・・・)
よけいな話しになりましたが、まずはじめは生成されるテンプレートについて見てみることにします。
program FirstWindowsApplication; uses Forms, MainForm in 'MainForm.pas' {Form1}; {$R *.res} begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end.
unit MainForm; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) private { Private 宣言 } public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} end.
メインプログラムの方は特に問題ありません。 アプリケーションを初期化して、フォームを作成して、実行してメッセージループに入るだけです。 ただ、 MainFormユニットの方はフォームの実装を記述する部分ですが、VBとかなり異なります。 まず注目すべきは、フォームはTForm型から継承したクラス型であることです。 VBでもフォームは一種のクラスでしたが、Delphiでのフォームは継承が可能です。
それでは早速ログインダイアログのようなものを作ってみます。 このアプリケーションはエディットボックスに入力された文字をメッセージボックスを利用して表示するだけの単純なアプリケーションです。
unit MainForm; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Edit1: TEdit; Button1: TButton; Label1: TLabel; procedure Button1Click(Sender: TObject); procedure Edit1KeyPress(Sender: TObject; var Key: Char); private { Private 宣言 } public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} // ボタンのクリック procedure TForm1.Button1Click(Sender: TObject); begin MessageDlg( 'Hello, ' + Edit1.Text + '!', mtInformation, [mbOK], 0 ); end; // Enterキーによる入力 procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char); begin if Key = #13 then Button1Click( nil ); end; end.
灰色の部分はデザイン時に自動的に追加された部分と、イベントハンドラとして作成された部分です。 青いところが実際に記述したコードです。 これを実行するとこんな感じになります。