ということで、Javaに続いてまた何となく Delphiを始めてみました(何となくというのもおかしいのですが、実際のところDelphiの知識が特に必要とされているわけでもないですし、ちょっと興味がある程度、趣味の範囲でDelphiを始めたという意味では「何となく」になるでしょうか)。

今回使用したコンパイラ・開発環境はBorland社の「Delphi 6 Personal」です。 これは使用者登録等の簡単な手続きが必要になりますが、フリーでダウンロードできます。 ここではインストールの説明などは特にしません。

Javaに関してはC系の言語であるので、言語仕様自体の習得はそれほど難しいものではなかったのですが、Delphiは今まで使ったことのない Pascal系の言語(Object Pascal)であるので最初からかなり手こずりました。 他の言語と比較しつつ基本的なところから勉強していくことにします。

最初はHello, world!

何はなくとも、最初のプログラムは「Hello, world!」と決まっているので、コンソールアプリでこれを表示してみます。 「新規作成」メニューの「その他」をクリックし、表示されたダイアログから「コンソール」をクリックして、コンソールアプリケーション用のプロジェクトを生成します。 DelphiはよくVisual Basicと比較されますが、Delphiにはコンソールアプリを作れないと思いこんでいたので驚きです。 VBでもコンソールアプリが作れれば良かったのですが・・・

最初はHello
program HelloWorld;

{$APPTYPE CONSOLE}

uses
  SysUtils;

begin
  { TODO -oUser -cConsole Main : ここにプログラムコードを書いてください }

  Writeln('Hello, world!');
  Writeln('This is first delphi program.');

end.
実行結果
Hello, world!
This is first delphi program.

このプログラムの大半はテンプレートとして提供されたものです。 実際に追加したのは11,12行目だけです。 プログラムの詳細はこの次で説明するとして、ひとまず「Hello, world!」が無事表示されたことを喜ぶことにします。

Object Pascal のプログラム構造

といっても、「Hello, world!」が表示されただけではどうしようもないので、Object Pascalにおけるプログラムの構造はどのようなものなのかを見てみます。

プログラム構造

Object Pascalではこのようなプログラム構造になっています。 これはVBと言うよりはC/C++の構造に似ていると言えます。 続いて、部分毎に説明していきます。

まず、プログラムヘッダでプログラムの名前を決めます。 この名前とファイル名は一致している必要があります。 また、コンパイル命令などもここに記述します。 これはprogram文の前にあっても問題ないようです。

続いて、プログラムブロックですが、これはさらに細かいブロックに分かれます。 最初に来るuses文では使用するユニットを記述します。 ユニットとはヘッダーのようなもので、ヘッダーのインクルードや名前空間のインポートなどと似たようなことを行います。 続く変数宣言部ではvar文により変数を宣言します。 宣言すべき変数がない場合は省略できます。

最後にプログラム本体を記述します。 begin から end で囲まれる部分が Main関数 に相当する部分です。 Main関数以外の関数・手続き(プロシージャ)を記述するにはこの部分より上に記述する必要があります。

Object Pascal の文法

構造がわかったところで文法について見てみます。

Object Pascal の文法
{$APPTYPE CONSOLE}

program HelloWorld;

uses
    SysUtils;

var
    // 変数宣言部
    i: integer; // integer型の宣言
    j, k: integer;

begin
    // メイン関数に相当する部分

    {
    コメント文
    中括弧でくくられた部分がコメントとされる
    中括弧に続いて$マークをつけるとコンパイラに対する命令文となる
    例えばプログラム最初にある$APPTYPEなど
    }

    (*
        これもコメント文
        {}と同じような働き
    *)

    // C++スタイルのコメント

    i := 3; // 代入の例
    j :=
         5; // 複数行にまたがっていても問題ない
    k := i + j; i := 1; // 複数文含まれていても問題ない

    // printf(), WriteLine(), println()に相当する手続き(プロシージャ)
    Writeln( 'i = ', i );
    Writeln( 'j = ', j );
    Writeln( 'k = ', k );

    { 終了メッセージを表示 }
    Write( 'Press any key to continue' );
    Readln;

end.
実行結果
i = 1
j = 5
k = 8
Press any key to continue

まず、コメントとなる部分は緑色に変えておきました。 コメントは様々な種類があります。 VB、C++と同じ感覚で使えるコメントが「//(ダブルスラッシュ)」です。 コメント文でコンパイル命令を記述することができます。 プログラムの一番はじめに記述した部分がコンパイル命令です。

Object Pascalでは大文字と小文字は区別されません。 つまり、「Integer」も「integer」も「INTEGER」も、そしてもちろん「iNtEgEr」なども同じと見なされます。 VBでは大文字小文字の区別はされなくとも自動的に単一の表記になるように調整されました。 しかし、 Delphiでは調整されることはありません。

文の終わりにはC/C++同様「;(セミコロン)」を付けます。 また、代入は「=」ではなく「:=」で行います。 VB, C/C++での代入は「=」で、C/C++で等価を表す「==」はObject Pascalでは「=」になります。 ここが注意すべきことだと思います。

また、 'と' の間が文字列リテラルになります。 文字列リテラル中にこのクォーテーションを含めたい場合はVBと同じように「''」と二つ続けることで一つのクォーテーションとして扱われます。 さらに、関数・プロシージャ呼び出しで渡すべき引数がないときは「Readln()」のように括弧を残してはならず、「Readln」としなければなりません。

最後に、変数の宣言なのですが、変数の宣言は必ずvar文で行わなければなりません。 それ以外の部分でローカル変数を宣言したりすることはできません。 ただし、プロシージャ・関数レベルで変数を宣言することはできます。

標準入出力

簡単な文字列型変数を使って標準入出力で入出力をしてみます。

標準入出力
program HelloWorld;

{$APPTYPE CONSOLE}

uses
    SysUtils;

var
    yourName: String;

begin

    Write( 'Please type your name here: ' );
    Readln( yourName );

    Writeln( 'Hello, ' + yourName + '.' );

    { 終了メッセージを表示 }
    Write( 'Press any key to continue' );
    Readln;

end.
実行結果
Please type your name here: santa marta
Hello, santa marta.
Press any key to continue

Write, Writelnは.NET FrameworkのConsole.Write, Console.WriteLineに相当するプロシージャです。 ReadlnもReadLineに相当するものです。 しかし、入力された文字列は戻り値ではなく引数に渡されます。 文字列を連結する際にはVBのように & ではなく + を用いることができます。

ちなみに、一番最後にあるReadlnは何らかの入力があるまで終了させないためのものです。 これを省くと名前を入力した瞬間に目にもとまらぬ速さでコンソールが閉じてしまいます。

算術演算

算術演算は基本的には他の言語とは大差ありません。

算術演算
program HelloWorld;

{$APPTYPE CONSOLE}

uses
    SysUtils;

var
    modValue, divValue: Integer;

begin

    Writeln( '算術演算のサンプル' );

    Writeln( '3 + 5 = ', 3 + 5 );
    Writeln( '3 - 5 = ', 3 - 5 );
    Writeln( '3 * 5 = ', 3 * 5 );
    Writeln( '3 / 5 = ', 3 / 5, ' = ', FloatToStr( 3 / 5 ) );

    Writeln( '切捨てと四捨五入' );
    Writeln( '5 / 3 = ', FloatToStr( 5 / 3 ) );
    Writeln( '5 / 3 = ', Trunc( 5 / 3 ) );
    Writeln( '5 / 3 = ', Round( 5 / 3 ) );

    Writeln( '商と剰余' );
    divValue := 5 div 3;
    modValue := 5 mod 3;

    Writeln( '5 ÷ 3 = ', divValue, '…', modValue );

    Writeln( '偶数・奇数' );
    Writeln( '2 is odd: ', Odd( 2 ) );
    Writeln( '3 is odd: ', Odd( 3 ) );

    { 終了メッセージを表示 }
    Write( 'Press any key to continue' );
    Readln;

end.
実行結果
算術演算のサンプル
3 + 5 = 8
3 - 5 = -2
3 * 5 = 15
3 / 5 =  6.00000000000000E-0001 = 0.6
切り捨てと四捨五入
5 / 3 = 1.66666666666667
5 / 3 = 1
5 / 3 = 2
商と剰余
5 ÷ 3 = 1…2
偶数・奇数
2 is odd: FALSE
3 is odd: TRUE
Press any key to continue

/ 演算子は除算を行いますが、オペランドが整数でもその結果は実数型になります。 FloatToStr関数はその名の通り、浮動小数点の数値を文字列に変換します。 ただし、指数表記ではなく読みやすい表記に変えてくれます。 Trunc関数は小数部を切り捨て、Round関数は小数部を丸めて整数に変換します。 div及びmodは商と剰余を求める演算子です。 VBの\とModに相当するものといえます。 Odd関数はその整数が奇数であるかを Boolean値で返します。

その他の算術演算

平方根、べき乗、対数などの算術演算も他の言語とあまり変わりません。 ただし、これらの関数を利用する場合はMathユニットをuses文に含める必要があります。

その他の算術演算
program HelloWorld;

{$APPTYPE CONSOLE}

uses
    SysUtils,
    Math;

var
    sinValue, cosValue, tanValue: Extended;

begin

    Writeln( '平方: 1.732050807 ^ 2 = ', Sqr( 1.732050807 ) );
    Writeln( '平方根: √2 = ', Sqrt( 2 ) );

    Writeln( 'べき乗: 2 ^ 3 = ', Power( 2, 3 ) );

    Writeln( '常用対数' );
    Writeln( '  Log10(100) = ', Log10( 100 ) );

    Writeln( '自然対数' );
    Writeln( '  Ln(2.718281828) = ', Ln( 2.718281828 ) );

    Writeln( '2を底とした対数' );
    Writeln( '  Log2(256) = ', Log2( 256 ) );

    Writeln( 'nを底とした対数' );
    Writeln( '  Log5(125) = ', LogN( 5, 125 ) );

    Writeln( 'Pi関数: π=', Pi );

    Writeln( 'Sin, Cos, Tan' );
    Writeln( '  Sin(π/4) = ', Sin( Pi / 4 ) );
    Writeln( '  Cos(π/3) = ', Cos( Pi / 3 ) );
    Writeln( '  Tan(π/6) = ', Tan( Pi / 6 ) );

    Writeln( '  Sin(60) = ', Sin( DegToRad( 60 ) ) );
    Writeln( '  Cos(30) = ', Cos( DegToRad( 30 ) ) );
    Writeln( '  Tan(45) = ', Tan( DegToRad( 45 ) ) );

    Writeln( 'SinCosプロシージャ' );
    SinCos( DegToRad( 60 ), sinValue, cosValue );
    tanValue := sinValue / cosValue;
    Writeln( '  Sin(60) = ', sinValue );
    Writeln( '  Cos(60) = ', cosValue );
    Writeln( '  Tan(60) = ', tanValue );


    { 終了メッセージを表示 }
    Write( 'Press any key to continue' );
    Readln;

end.
実行結果
算術演算のサンプル
3 + 5 = 8
3 - 5 = -2
3 * 5 = 15
3 / 5 =  6.00000000000000E-0001 = 0.6
切り捨てと四捨五入
5 / 3 = 1.66666666666667
5 / 3 = 1
5 / 3 = 2
平方: 1.732050807 ^ 2 = 2.99999999802935E+0000
平方根: √2 =  1.41421356237310E+0000
べき乗: 2 ^ 3 =  8.000000000000000E+0000
常用対数
  Log10(100) =  2.000000000000000E+0000
自然対数
  Ln(2.718281828) =  9.99999999831127E-0001
2を底とした対数
  Log2(256) =  8.00000000000000E+0000
nを底とした対数
  Log5(125) =  3.00000000000000E+0000
Pi関数: π= 3.14159265358979E+0000
Sin, Cos, Tan
  Sin(π/4) =  7.07106781186548E-0001
  Cos(π/4) =  5.00000000000000E-0001
  Tan(π/4) =  5.77350269189626E-0001
  Sin(60)  =  8.66025403784439E-0001
  Cos(60)  =  8.66025403784439E-0001
  Tan(60)  =  1.00000000000000E+0000
SinCosプロシージャ
  Sin(60)  =  8.66025403784439E-0001
  Cos(60)  =  5.00000000000000E-0001
  Tan(60)  =  1.73205080756888E+0000
Press any key to continue

注意すべきことは、Sqrが平方を求め、Sqrtが平方根を求める関数であるということです。 双方非常に似た名前なので注意が必要です。 また、特殊なプロシージャにSinCosがあります。 これはあるラジアン値の正弦と余弦を同時に求めるというものです。 ヘルプによるとSinとCosを一度ずつ呼び出すよりは高速に動作するそうです。 ちなみに、Extended型は拡張精度実数型と呼ばれるもので、これらの関数の戻り値はExtended型になっています。

for文

for文の動作自体は他の言語とほとんど変わりありません。 ただ、カウンタ変数の増分は±1に固定されています。 Stepを利用したりすることができないので、それ以外の値は指定できません。 表記自体はVBのFor文と非常に似ています。

for文
program HelloWorld;

{$APPTYPE CONSOLE}

uses
    SysUtils;

var
    i, j: integer;

begin

    for i := 0 to 9 do
    begin
        Write( i, ' ' );
    end;

    Writeln;

    for i := 9 downto 0 do
    begin
        Write( i, ' ' );
    end;

    Writeln;

    for i := 0 to 4 do
    begin
        for j := 0 to 4 do
        begin
            Write( '(', i, ', ', j, ') ' );
        end;
        Writeln;
    end;

    { 終了メッセージを表示 }
    Write( 'Press any key to continue' );
    Readln;

end.
実行結果
0 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 0
(0, 0) (0, 1) (0, 2) (0, 3) (0, 4)
(1, 0) (1, 1) (1, 2) (1, 3) (1, 4)
(2, 0) (2, 1) (2, 2) (2, 3) (2, 4)
(3, 0) (3, 1) (3, 2) (3, 3) (3, 4)
(4, 0) (4, 1) (4, 2) (4, 3) (4, 4)
Press any key to continue

カウンタをインクリメントする場合はtoを用い、ディクリメントする場合はdowntoを用います。 繰り返す文をdoに続けて記述します。 繰り返す文が一行の場合はdoの後にそのまま記述し、複数行の場合はbeginとendの間に記述します。 この記述方法はC/C++に似ていると言えます。 また、二重ループなどをすることもできます。

while文, repeat文

while文は他の言語同様、前置条件判断の無限ループ、repeat文は後置条件判断の無限ループです。 for文で1以外の増分を指定したいときはwhile/repeatを使うことができます。

while文
program HelloWorld;

{$APPTYPE CONSOLE}

uses
    SysUtils;

var
    i: integer;

begin

    i := 10;
    while 0 < i do
    begin
        Write( i, ' ' );
        i := i - 2;
    end;

    Writeln;

    i := 12;
    repeat
        Write( i, ' ' );
        i := i - 3;
    until i < 0;

    Writeln;

    { 終了メッセージを表示 }
    Write( 'Press any key to continue' );
    Readln;

end.
実行結果
10 8 6 4 2
12 9 6 3 0
Press any key to continue

while文はfor文同様beginからendまでのブロックを、条件式の値が真の間繰り返します。 逆に、repeat文は条件式が真になるまで(式が偽の間)繰り返します。 repeat文ではブロック構造を成さず、repeatからuntilまでを繰り返します。 これもまたfor文同様に入れ個にして二重ループを形成することもできます。

if文

例によってif文も他の言語と大差ありません。 ただ、前述の通り、等価を表す演算子にはVB同様 = 演算子を用います。 また、一つだけ注意として、「elseはそれ単体では文になり得ない」ということからelseの前には ;(セミコロン) をつけません。 つけてしまうと構文エラーになります。

if文
program HelloWorld;

{$APPTYPE CONSOLE}

uses
    SysUtils;

var
    i, j: integer;
    s: string;

begin

    for i := 1 to 9 do
    begin
        for j := 1 to 9 do
        begin

            if i = j then
                Write( '   ' )
            else
            begin
                s := IntToStr( i * j );
                SetLength( s, 3 );
                Write( s );
            end;

        end;
        Writeln;
    end;

    { 終了メッセージを表示 }
    Write( 'Press any key to continue' );
    Readln;

end.
実行結果
   2  3  4  5  6  7  8  9
2     6  8  10 12 14 16 18
3  6     12 15 18 21 24 27
4  8  12    20 24 28 32 36
5  10 15 20    30 35 40 45
6  12 18 24 30    42 48 54
7  14 21 28 35 42    56 63
8  16 24 32 40 48 56    72
9  18 27 26 45 54 63 72
Press any key to continue

まずはじめにif文意外の注意点として、SetLengthは配列要素の長さを指定するものですが、この例のように文字列に対しても有効です。 つまり、文字列の長さを設定することができます。 この例では3バイト(Object Pascalの文字列はAsciiとして扱われるため。ちなみにVBはUnicode。)に設定して書式を併せています。

if文ではVBのEnd Ifに相当するものはありません。 形式的にはC/C++に似たものと言えます。 ただし、条件式を()でくくる必要はありません。 また、for文同様に、実行する文が一行だけの場合はbegin-endブロックを作る必要がありません。 else ifはこの次の例で使用します。

break, continue

Object Pascalではbreakとcontinueが採用されています。 その用法はC/C++と同じです。

break
program HelloWorld;

{$APPTYPE CONSOLE}

uses
    SysUtils;

var
    i: integer;

begin

    for i := 1 to 9 do
    begin

        if (i = 1) or (i = 4) or (i = 6) then
            continue
        else if i = 8 then
            break;

        Write( i, ' ' );

    end;

    Writeln;

    { 終了メッセージを表示 }
    Write( 'Press any key to continue' );
    Readln;

end.
実行結果
2 3 5 7
Press any key to continue

この例を見てわかるとおり else if の前であっても ;(セミコロン) をつけてはいけません。 continueとbreakがC/C++と同じ動作であることは実行結果を見れば納得がいくと思います。

case文

case文はswitch文やSelect Caseステートメントと同じような働きを持つ構文です。 caseは値の分類には用いられません。

case文
program HelloWorld;

{$APPTYPE CONSOLE}

uses
    SysUtils;

var
    i: integer;

begin

    for i := 0 to 9 do
    begin

        case i of

        0, 1, 4, 6, 8, 9: Writeln( i, 'は素数ではない' );

        2,
        3,
        5,
        7: Writeln( i, 'は素数である' );

        end;

    end;

    Writeln;

    { 終了メッセージを表示 }
    Write( 'Press any key to continue' );
    Readln;

end.
実行結果
0は素数ではない
1は素数ではない
2は素数である
3は素数である
4は素数ではない
5は素数である
6は素数ではない
7は素数である
8は素数ではない
9は素数ではない
Press any key to continue

この例では二つの表記方法を使っていますが、どちらでも問題ないと言うことだけを示している以外は特に意味はありません。 case文は「case 値 of」というように用います。 また、breakなどを用いなくても下の文に流れていくことがないので、動作はVBのSelect Caseステートメントに似ていると言えます。 case文の終わりにはend;をつけます。

さらに、複数の連続した値や、いずれの値にも当てはまらない場合などを記述することもできます。

case文
program HelloWorld;

{$APPTYPE CONSOLE}

uses
    SysUtils;

var
    s: string;
    i: integer;

begin

    for i := 0 to 9 do
    begin

        case i of

        0..4:
            s := ' is less than 5.';

        5:
            s := ' equals to 5.';

        else
            s := ' is greater than 5';

        end;

        Writeln( i, s );

    end;

    Writeln;

    { 終了メッセージを表示 }
    Write( 'Press any key to continue' );
    Readln;

end.
実行結果
0 is less than 5.
1 is less than 5.
2 is less than 5.
3 is less than 5.
4 is less than 5.
5 equals to than 5.
6 is greater than 5.
7 is greater than 5.
8 is greater than 5.
9 is greater than 5.
Press any key to continue

配列(静的配列)

配列という要素はほとんどの言語に存在しますが、Object Pascalにおける配列の動作はVBに近く、表記方法などはC++に似ています。

配列(静的配列)
program HelloWorld;

{$APPTYPE CONSOLE}

uses
    SysUtils;

var
    arr: array[0..4] of integer;
    upperBound, lowerBound: integer;
    i: integer;

begin

    arr[0] := 2;
    arr[1] := 3;
    arr[2] := 5;
    arr[3] := 7;
    arr[4] := 11;

    lowerBound :=  Low( arr );
    upperBound := High( arr );

    Writeln( 'Lower bound of arr is ', lowerBound );
    Writeln( 'Upper bound of arr is ', upperBound );

    Writeln( 'Values of array.' );

    for i := lowerBound to upperBound do
    begin
        Writeln( '  arr[', i, '] = ', arr[i] );
    end;

    Writeln( 'Out of range.' );

    for i := lowerBound - 3 to lowerBound - 1 do
    begin
        Writeln( '  arr[', i, '] = ', arr[i] );
    end;

    for i := upperBound + 1 to upperBound + 3 do
    begin
        Writeln( '  arr[', i, '] = ', arr[i] );
    end;

    { 終了メッセージを表示 }
    Write( 'Press any key to continue' );
    Readln;

end.
実行結果
Lower bound of arr is 0
Upper bound of arr is 4
Values of array.
  arr[0] = 2
  arr[1] = 3
  arr[2] = 5
  arr[3] = 7
  arr[4] = 11
Out of range
  arr[-3] = 9372868
  arr[-2] = 9372920
  arr[-1] = 0
  arr[5] = 4
  arr[6] = 0
  arr[7] = 0
Press any key to continue

宣言方法は見ての通りです。 「arrはinteger型の配列(添え字0〜4)」のように読むことができます。 Low関数とHigh関数は配列の下限と上限を取得するものです。 VBにおけるLBound関数とUBound関数に相当するものといえます。 また、配列の長さを求めるLength関数というものも存在します。

配列への値の代入と参照は特に問題ないのですが、興味深いことに、配列の添え字の範囲外の部分を参照しようとしてもエラーにはなりませんでした。 ただ、その値は必ずしも初期化されていない意味不明な数値です。

配列(動的配列)

Object Pascalには動的に長さを変えられる動的配列も存在します。

配列(動的配列)
program HelloWorld;

{$APPTYPE CONSOLE}

uses
    SysUtils;

var
    arr: array of integer;
    upperBound, lowerBound: integer;
    i: integer;

begin

    SetLength( arr, 10 );

    lowerBound :=  Low( arr );
    upperBound := High( arr );

    Writeln( 'Length of arr is ', Length( arr ) );
    Writeln( 'Lower bound of arr is ', lowerBound );
    Writeln( 'Upper bound of arr is ', upperBound );

    for i := lowerBound to upperBound do
    begin
        arr[i] := i;
    end;

    for i := lowerBound to upperBound do
    begin
        Writeln( 'arr[', i, '] = ', arr[i] );
    end;


    SetLength( arr, 5 );
    lowerBound :=  Low( arr );
    upperBound := High( arr );

    Writeln( 'Length of arr is ', Length( arr ) );
    Writeln( 'Lower bound of arr is ', lowerBound );
    Writeln( 'Upper bound of arr is ', upperBound );

    for i := lowerBound to upperBound do
    begin
        Writeln( 'arr[', i, '] = ', arr[i] );
    end;


    { 終了メッセージを表示 }
    Write( 'Press any key to continue' );
    Readln;

end.
実行結果
Length of arr is 10
Lower bound of arr is 0
Upper bound of arr is 9
  arr[0] = 0
  arr[1] = 1
  arr[2] = 2
  arr[3] = 3
  arr[4] = 4
  arr[5] = 5
  arr[6] = 6
  arr[7] = 7
  arr[8] = 8
  arr[9] = 9
Length of arr is 5
Lower bound of arr is 0
Upper bound of arr is 4
  arr[0] = 0
  arr[1] = 1
  arr[2] = 2
  arr[3] = 3
  arr[4] = 4
Press any key to continue

宣言直後には長さがなく、値が割り当てられていないので、SetLengthによって配列に長さを設定します。 また、長さを変えるときもSetLenghtを用います。