このエントリーをはてなブックマークに追加

PASCAL入門

おことわり

ここではLinuxのFreePASCALを使っています。 参考にしている本が古いので(1981年著)現在では通用しない事を書いている場合があります。 また、私自身がPASCALにあまり詳しくないので、間違った事を書いている場合があります。

ここに書いてある事がもし間違っていても、鵜呑みにして悩まないよう、よろしくお願いします。 おかしいと思ったら、ご自身で検証しながらご利用ください。 また、お使いのコンパイラによっては通用しない事が書かれている場合もありますので、各自お使いのコンパイラに合わせて読み替えてください。

コメント

PASCALではコメントは { と } で囲われた部分がコメントとみなされます。
{ これはコメント文です }
{ があると、次に } が来るまでがコメントとみなされます。という事は、間に改行コードが入っても良いので、複数行に渡ってコメントを書くことも出来ます。
{
    これは
        コメント文
              です
}
という事は、CやPerlやPHPに慣れた人が、無意識に { って書いてしまうと、以降がコメントになってしまうので注意しましょう。え?そんな奴はいない?

変数

PASCALは、COBOLやCのように変数を宣言してから使います。
サイズ 意味  備 考
byte 8ビット 整数。 0~255 Cでいうところの unsigned char
integer 16ビット 整数。 -32768 ~ 32767 Cでいうところの(16ビットシステムで) int
longint 32ビット 整数。 -2147483648 ~ 2147483647 Cでいうところの(16ビットシステムで) long
real 64ビット 実数。有効数字15桁の浮動小数点 Cでいうところの(16ビットシステムで) double
char 8ビット 1バイトの文字 Cでいうところのchar
string 256バイト 文字列 PostgreSQLでいうところのvarchar(255)みたいなもの
boolean trueかfalse PostgreSQLでいうところのboolみたいなもの
stringが255文字までしか使えないのは、格納形式が先頭の1バイト目に文字列の長さ、2バイト目以降に文字列分のメモリーが固定長で確保されたためです。string型に256文字以上代入しようとすると、FreePascalでは、
Constant strings can't be longer than 255 chars
というエラーになります。

LinuxのCコンパイラ(cc、gcc)では、32ビットシステムでint型を指定すると32ビット長になるのですが、FreePascalではLinuxの32ビットシステムであってもintegerは16ビット、longintが32ビット長になります。(CentOS4 + FreePascalで確認)

変数の宣言はvarを使います。VisualBasicでいうところのDIM、JavaScriptでいうところのvar、COBOLでいうところのPICみたいなものです。
var text : string;
    moji1,moji2,moji3 : char;
    suuji1 : integer;
    suuji2 : real;
    baito : byte;
    rongu1,rongu2 : longint;
文法上では2行目から字下げする必要はありませんが、字下げした方が可読性が上がります。なので、慣習的に2行目から字下げする事になっています。

変数の宣言の書式が覚えにくい時は , を「と」 : を「は、」と置き換えて覚えてください。
moji1,moji2,moji3 : char;
       ↓
moji1とmoji2とmoji3 は、char;
変数名にはいくつかルールがあります。
  • 予約語と同じ変数名は使えない。
  • アンダーバー以外の記号を使ってはならない。
  • 数字から始まってはならない。
  • 変数名の長さが規定以上であってはならない。
予約語とかぶらないために一番良い方法は、ローマ字を使う事です。文字列を表すのに「string」と書くと予約語とかぶってしまいますが、「mojiretu」と書けば予約語とかぶりません。この手法はCOBOLでも良く使われています。

また、COBOLではハイフンが使えるため「TEXT-NO1-STR」みたいに長い変数名を使うという手法がよく用いられますが、同様にアンダーバーを使って「string_no1_char」みたいに長い変数にするという手もあります。参考にした本では「変数名は8文字まで」と書いてありますが、FreePascalでは8文字以上でも大丈夫のようです。(具体的に何文字まで大丈夫かという検証はしてません。)

booleanは1ビットあれば表すことができますが、内部的にはスカラ型で type boolean = (false,true);という風にスカラ型として定義してあるだけという説もあるため(この場合、内部ではfalse=0、true=1として扱われる)、ここではサイズを?としています。

プログラム名

プログラムの先頭に program プログラム名; という風に記述します。
program HELLO;
COBOLでいうところの「PROGRAM-ID. プログラム名.」みたいなものです。OpenCOBOLではPROGRAM-ID.を書かないとエラーになってしまうのですが、FreePASCALではprogram文がなくとも特にエラーにはならないようです。

定数

定数を定義するために、constを宣言します。いわゆる、Cでいうとろこの#define、PHPでいうところのdefineです。C++のconstとは違います。C++でconstというと参照専用とか、引数の参照渡しという意味になります。

const 定数名=値; 定数名=値; 定数名=値; ……
program const_test;

const tadano=65 ; saitoh=18;
var goukei:integer;

begin
	goukei := tadano+saitoh;
	writeln(goukei)
end.
この例では、tadanoを65、saitohを18と定義しておき、プログラム本体でtadano+saitohを計算して83を表示します。(斎藤ファンの方、石を投げないでください。)

定数は文字列であってもよく、その場合、 ham='Hokkaido Nipponham Fighters';のようにシングルクォートして文字列を設定します。当然ですが、文字列と数値の足し算はできません。 代入文では:=を使いますが、定数の設定では=です。

代入

代入は、 := を使います。 = ではありません。これは、条件式の「等しければ」という意味と区別するためです。定数を定義する時は := ではなく= じゃないか、と言うかもしれませんが、 const文は代入ではなくあくまで定数を宣言するためのものだからです。

このように、多くの言語では、代入と条件式の = は区別しています。
言語 代入 条件式 備考
Pascal := = 定数の宣言時には = を使う。
C、C++、PHP、Perl = == 誤ってif文で代入演算子を使ってもエラーにはならない。代入された値で真か偽かを判定される。
それを逆手にとって、関数のリターンコードを変数に代入しつつ、それが真か偽かを判定できる。
COBOL TO = ただし、COMPUTE演算子内での代入は = を用いる。
BASIC、VisualBasic = = コンパイラが文脈で判断する。古いBASICでは、代入を表すためにLETを宣言する。

文字列

文字列は、シングルクォートで囲って表します。文字列中にシングルウォーとが出てくる場合は、シングルクォートを2個続けて書きます。という事は、SQLと同じですね。もっとも、SQLとは違い\'ではエスケープできませんが。

例)
writeln('Merry X''mas');
出力結果)
Merry X'mas

複合文

Cでは、main() { (命令) } とか、if (条件){ (命令) } のように、一連の処理を { と } で囲いますが、Pascalでは一連の処理を begin と endで囲います。メインルーチンは、bein ~ end. 、if文やループのような条件を満たした時に実行される一連の文を、 begin~ end;で囲います。メインルーチンの終わりにはendの後にピリオドが入ります。

文の後ろにセミコロンがつくのはCと同じですが、Cの「文の終わり」という意味ではなく、Pascalでは「文の区切り」という意味です。なので、一番最後の命令(endの直前の命令)にはセミコロンはつきません。つけると、次に空文があるという意味になります。

ループ

ループには、while、repeat、forがあります。

while

while (条件) do
 begin
   (命令)
 end;
条件が真である限り、(命令)を繰り返し実行します。条件が最初から偽である時は、(命令)は1回も実行されません。

repeat

repeat
   (命令)
until (条件);
Cでいうところのdo{}while のようなもので、少なくとも(命令)は1回は実行されます。また、(条件)はwhileとは違い偽である限り繰り返されます。untilはCOBOLやPerlでいうところのuntil(条件)と同じで、条件が成立する「まで」という意味です。

repeatには「複合命令」という意味が含まれるので、repeat後をbegin~endでくくると増長になる、と参考にしている本に書いてあるので、従う事にします。

for

for カウンタ変数 := 初期値 to 終わり値 do
 begin
   (命令)
 end

for カウンタ変数 := 初期値 downto 終わり値 do
 begin
   (命令)
 end
カウンター変数を初期値から終わり値まで、toは1つずアップさせ、downtoは1ずつダウンさせながらループします。という事は、カウンター変数は1ずつしか上げ下げできません。じゃあ8ずつ上げたい時はどうするかというと、たとえば、「80、 88、 96、 104、 112、 120、 128 …」という値が欲しい時は、カウンターを0からカウントアップさせて、変数をもう1個用意して、

d := 80 + n*8;

という風にします。いや、特に検算とかしてないんで(←しろよ)、間違ってても鵜呑みにして悩んだりしないように。

条件式

if

条件式としてif文を使う事ができます。
if (条件) then (命令1);

if (条件) then (命令1) else (命令2);
(条件)が真なら(命令1)が、また、(条件)が偽なら(命令2)が実行されます。
if x>0 then
        a:=1
else
        a:=2;
thenやelseの後に複数の命令を書くときは、beginとendで囲ってください。
if x>0 then
        begin
                a:=1;
                b:=1
        end
else
        begin
                a:=2;
                b:=2
        end;
elseの直前の命令(上の例ではelseの直前のend)にはセミコロンはつけません。つけてしまうと、そこでif文が終わりという意味になってしまい、次のelseでエラーになってしまいます。

CやPHPに慣れてしまうと、 ifの後の条件をカッコでくくりたくなりますが、特にカッコでくくらなくともエラーにはなりません。

case

caseとは、Cでいうところのswitch、COBOLでいうところのEVALUTEのようなものです。
case (変数) of
 値1: (命令1);
 値2: (命令2);
 値3: (命令3);
 値4: (命令4)
end
(変数)の値が値1だったら(命令1)が実行され、値2だったら(命令2)が実行され …… になります。(手を抜くなって?) また、値はカンマで区切る事によってOR条件を設定する事もできます。
case x of
        0: writeln('Xは0だ');
        1: writeln('Xは1だ');
        2: writeln('Xは2だ');
        3,4,5: writeln('Xは3か4か5だ')
else
        writeln('しらん')
end;
どの条件にも合致しなかった時は、else以降が実行されます。Cでいうところのdefault、COBOLでいうところのOTHERSみたいなものです。

goto

gotoを使うのに、CやCOBOLでは[goto ラベル]と書けばラベルに分岐してくれましたが、Pascalでは若干手続きが面倒です。まず、最初のbeginの前(宣言部で)使うラベルを宣言しなければなりません。
label 1,3,5;
また、使うラベルは整数でなければなりません。3というラベルを使いたい時は、 3: と書きます。その上で、3:に分岐したい所でgoto 3;という風に書きます。
3:
x:=4;
case x of
	0: writeln('Xは0だ');
	1: writeln('Xは1だ');
	2: writeln('Xは2だ');
	3,4,5: writeln('Xは3か4か5だ')
else
	writeln('しらん')
end;
goto 3;
この例では、goto 3;から3:に飛びます。という事は無限ループになります。

cと同様、Pascalでもgotoの使用はできるだけ避けるべきです。どうしても使う時は、エラー発生時にエラー処理に強制的に飛ばす場合や、ユーザーが強制中止キーを押した時に中止処理に飛ぶ場合など、通常以外の処理に限るべきです。

また、他の言語でもそうですが、ループの中からの脱出、ループの中への飛び込み、別のサブルーチン(というか、手続き (procedure))への分岐、サブルーチンからメインルーチンへの分岐(またはその逆)はしてはいけません。いずれもスタックがあふれてしまいます。

スカラ型

スカラ型とは、Perlでいうところのスカラー変数・・・ではありません。(Perlのスカラー変数は、いわゆる「ふつーの変数」です。)みなさんは、エクセルに「ユーザー設定リスト」というのがあるのをご存知でしょうか?これは、 アルファベット順ではなく、人間が決めた規則で並べてあるリストです。

たとえば、エクセルではデフォルトでSun、Mon、Tue、Wed、Thu、Fri、Satというリストが登録されています。これは、この順でソートすることもできます。Pascalでは、この、エクセルでいうところの「ユーザー設定リスト」を設定する事ができます。
type day_of_week = (Sun,Mon,Tue,Wed,Thu,Fri,Sat);
type というのはCでいうところのtypedefみたいなもので、ユーザー定義型のようなものです。
var wday : day_of_week;
というように、wdayという変数をday_of_week型で宣言します。これで変数wdayに対し、定数Sun Mon Tue Wed Thu Fri Satを代入する事ができるようになります。

ただし、これはあくまでコンパイラが内部的に
Sun=0
Mon=1
Tue=2
Wed=3
Thu=4
Fri=5
Sat=6
と置き換えてくれるだけです。ord関数を使って、Sunを0に戻すことはできますが、PerlやPHPの配列みたいに、0からSunという文字を得る事はできず、どうしてもしたい時は別途ifやcase文を使ったサブルーチンを作る必要があります。

スカラ型の値を直接writelnで表示させるとエラーになってしまうので、ord関数で数値に戻す必要があります。
wday := Sun;
writeln(ord(wday));

部分範囲型

スカラ型で定義したうちの一部分を別のタイプとして再定義できます。
type day_of_week = (Sun,Mon,Tue,Wed,Thu,Fri,Sat);
   heijitu = Mon..Fri;
とすると、heijitu(平日)型は、MonからFriまでという意味になります。ここで定義した範囲外の値を代入しようとすると、Error: range check error while evaluating constantsというエラーになります。

スカラ型はあくまでコンパイラが内部的に設定するだけなので、即値を書いても良いのですが、
type sebango = 0..99;
この場合、範囲外の値を直接代入してもコンパイルエラーとはならず、ワーニング Warning: range check error while evaluating constantsになるだけです。また、直接代入せずに計算した結果が範囲外になったとしてもエラーにはなりません。

配列

どの言語にも大抵はある配列です。Pascalは型を宣言しなければならない言語のため、PerlやPHPみたいに同じ配列に色々な型の値が入れられたりはしませんで、同じ配列には同じ型の値しか入りません。この辺はCに近いですね。

普通の配列

普通の配列は、var 配列名: array[最小値..最大値] of 型;という風に宣言します。
var hairetu: array[0..10] of integer;
VBに慣れている人は、無意識に as integerって書いてしまいがちですが、Pascalではasではなくofです。(as integerだと「数値として」、of integerだと「数値の」でしょうか)

宣言した時の最小値~最大値の範囲外の値を添え字として指定すると、ワーニングWarning: range check error while evaluating constantsになりますが、エラーにはならないようです。Cだとメモリーを壊してセグメンテーションフォルトになるのですが、FreePascalではセグメンテーションフォルトにならないようです。

配列を使う時は、
hairetu[0] := 1;
みたいに、添字を[ ]カッコで囲って書きます。

スカラ型の添字

配列の添え字の範囲に、スカラ型を指定する事ができます。
type day_of_week = (Sun,Mon,Tue,Wed,Thu,Fri,Sat);
var callender: array[day_of_week] of integer;

begin
        callender[Sun] := 1;
        writeln(callender[Sun])
end.
とすると、コンパイラは内部的にcallenderという数値の配列を7個分用意します。ただし、使う時に添字に数字0~6を使うとエラー(Error: Incompatible types: got "LongInt" expected "day_of_week")になります。この場合、添字は Sun,Mon,Tue,Wed,Thu,Fri,Sat のいずれかを使います。

固定長文字列

Cでは可変長の文字列を確保する時は、char*(charポインター)で宣言しておいて、後で必要な分をmalloc()で動的に確保するわけですが、固定長の文字列を格納するメモリーを確保する時は、

char mojiretu[10];

という風に、コンパイル時にあらかじめ10バイト分を静的に確保すると思います。

同様に、Pascalでも静的にメモリーを確保するために配列を使う事ができます。
var mojiretu: array[0..9] of char;

begin
        mojiretu := 'abcdefghijklmnopqrstuvwxyz';
        writeln(mojiretu)
end.
出力結果
abcdefghij

Pascalのcharは、Cのcharと同じで1バイトです。なので、添字の範囲にarray[0..9]を指定すると10バイト分のメモリーが確保されます。添字は0から始めなくても良いのですが、途中から始めても(array[3..9] みたいに指定しても) あまり意味がないようです。

上の例であえて確保したメモリー以上に代入していますが、OpenPascalでは確保したメモリー以上に代入してしまってもセグメンテーションフォルトにはならず、指定した長さで切られるようです。また、Cみたいに0x00 (というか\0) が入るメモリーは考慮しなくて良いです。

二次元配列

二次元配列は、宣言する時も使う時も[]の中でカンマで区切って使います。
var hairetu: array[0..10,0..10] of integer;

begin
        hairetu[0,0]:=10;
        writeln(hairetu[0,0])
end.
上の例では、hairetu という二次元配列を宣言しています。上の例では、添字はともに0~10までです。

圧縮指定

array の前にpackedをつけると、内部で圧縮されるという意味になります。
var hairetu: packed array[0..10,0..10] of integer;
どういう圧縮がされるかはコンパイラごとに異なります。(コンパイラによってはまったく圧縮されないものもあるそうです。) packedが指定されると、内部でメモリーの圧縮/展開が行われるようになり、その分コンパイルや実行時間が遅くなりますが、その分使うメモリー容量は少なくて済みます。

レコード型

Cでは、typedefで構造体(struct)をユーザー定義型として定義しておき、その後実体を確保する事でユーザー定義型の変数を使用する事ができますが、Pascalでも同様にユーザーの定義した構造体を持った変数を使う事ができます。 ただし、ここでは構造体ではなくPascal用語でレコード型と呼ぶようにします。
type レコード名 = record
               要素の定義
                   :
                   :
           end
レコード名とは、要するにCでいうところのtypedefで定義した型の名前です。

では、野球選手型という型を定義してみます。
program record_test;

type senshu = record 
                                name: array[0..19] of char;
                                term: array[0..9] of char;
                                position: array[0..9] of char;
                                sebango: array[0..2] of char;
                                uchi: char;
                                nage: char;
                                nenpo: longint
                end;

var s: array[0..69] of senshu;

begin
        s[0].name:='Yoshio Itoi';
        s[0].term:='Fighters';
        s[0].position:='center';
        s[0].sebango:='7';
        s[0].uchi:='L';
        s[0].nage:='R';
        s[0].nenpo:=100000000;

        writeln(s[0].name);
        writeln(s[0].nenpo)

end.
選手型という型を定義し、
 名前: 20バイト
 チーム:10バイト
 ポジション: 10バイト
 背番号: 3バイト
 打ち: 1バイト(L/R/B)
 投げ: 1バイト(L/R)
 年俸 4バイト
という風に内部要素を定義します。

背番号はintegerの方が良いのではないか?と思われるかもしれませんが、背番号には「00」というやっかいなものがあるので、ここではcharで3バイト確保しています。背番号順にソートする必要がある時はintegerで確保して「00」は内部では「-1」という風に記録しておいて、表示の時にif文で分けるという方法もあります。

実体はその後のvarで宣言し、sという選手型の配列を70個確保しています。実際使う時には、
s[0].name
みたいに変数名(配列の時は[]カッコでくくって添字も)、ピリオド、要素名という順序で書いてください。

ただし、いちいちs.[0]を頭につけるのが面倒な時は、
        with s[0] do
                begin
                        writeln(name);
                        writeln(nenpo)
                end
というふうに書く事もできます。このように、with 変数名 do begin ~ endでくくると、その間の変数はwithで指定されたレコード内の要素である事を表します。(配列の時は変数名の後に添字も)

集合型

C++入門の項目で、+記号をオーバーロードしてクラス同士を足し算するコンストラクタを作りました。こような処理がPascalでは+記号をオーバーロードせずともデフォルトで使えるようになっています。これが集合型です。

例えば、野球で残塁になったランナーを1塁ランナー(first)、2塁ランナー(second)、3塁ランナー(third)と定義しておき、1回から3回までの残塁を足し算したい場合に
program set_test;

type rui = (first, second, third);
	runner = set of rui;
var zanrui: array[1..9] of runner;
	zanrui_kei: runner;

begin
	zanrui[1] := [first];
	zanrui[2] := [first,second];
	zanrui[3] := [second,third];
	zanrui_kei := zanrui[1] + zanrui[2] + zanrui[3];

	if  first  in zanrui_kei then writeln('1塁残塁あり');
	if  second in zanrui_kei then writeln('2塁残塁あり');
	if  third  in zanrui_kei then writeln('3塁残塁あり')

end.
と言う風にすれば残塁の数が・・・え?残塁の数が足せてないって??

集合型の + というのはビット演算でいうところのORという意味でして、中身を本当に足すわけではありませんで、つまり、
[1塁ランナー] OR [1塁ランナー, 2塁ランナー] OR [2塁ランナー, 3塁ランナー]
は、[1塁ランナー, 2塁ランナー,3塁ランナー]という風に、ダブった分は1つになるわけです。ダブった分が2個になったりはしません。

集合型の掛け算はANDという意味になります。
program set_test;

type rui = (first, second, third);
	runner = set of rui;
var zanrui: array[1..9] of runner;
	zanrui_kei: runner;

begin
	zanrui[1] := [first];
	zanrui[2] := [first,second];
	zanrui[3] := [second,third];
	zanrui_kei := zanrui[1] * zanrui[2] * zanrui[3];

	if  first  in zanrui_kei then writeln('毎回1塁に残塁あり');
	if  second in zanrui_kei then writeln('毎回2塁に残塁あり');
	if  third  in zanrui_kei then writeln('毎回3塁に残塁あり')

end.
は何も表示されません。毎回残塁のある塁がありませんので。これが、2回までなら、
[1塁ランナー] AND [1塁ランナー, 2塁ランナー]
となるので、「毎回1塁に残塁あり」となるところですが、3回には1塁に残塁がありませんので。

あと、集合で引き算は集合から指定された要素を引くという意味です。
[1塁ランナー, 2塁ランナー] - [1塁ランナー]
では、[2塁ランナー]だけが残ります。1塁ランナーは牽制でアウトになったということで。

ここで、「if first in zanrui_kei then …」という構文が出てきますが、これはzanrui_kei(残塁計)の中にfirst(1塁)が含まれているかどうかっていう意味です。集合中に特定の要素が含まれているかどうかを判別する場合に使います。

また、ある人の守れるポジションを定義しておき、ある選手とある選手は守れるポジションが等しいか、同じかより多いか、より多いか、より少ないかという比較をする事ができます。
program set_test;

type shubiichi = (first, second, short, third, right, center, left, catcher);
        skill = set of shubiichi;

        var koyano,nioka,inada: skill;

begin
        koyano := [first, third, left];
        nioka := [first, short, third];
        inada := [first, third];

        if koyano >= inada then writeln('小谷野は稲田より守れる場所が同じか多い');
        if koyano >= nioka then writeln('小谷野は二岡より守れる場所が同じか多い');

end.
小谷野 の守れる場所 [1塁, 3塁, レフト]、二岡の守れる場所 [1塁, ショート, 3塁]、稲田の守れる場所 [1塁, 3塁]とすると (※注) 「if koyano >= inada」の条件式ですが、 >= は等しいか左辺の方が多い・・・別な言い方をすれば、左辺は右辺を含んでいる場合に真となります。したがって、 実行結果は「小谷野は稲田より守れる場所が同じか多い」となります。

「if koyano >= nioka」 の条件式ですが、小谷野と二岡は要素数は同じですが、中身が異なる(レフトとショート)ので・・・つまり、左辺は右辺を含んでいないので真にはなりません。

※注: 「稲田はセカンドもできるだろ」と言いたいところですが、他に良い例が思いつかなかったもので、すいません。

ポインタ型

Cでは動的にメモリーを確保する時には、いったんポインタを確保しておき必要に応じてmallocで確保してfreeで開放すると思います。同様に、Pascalでもポインタを使って動的にメモリーを確保する事ができます。

var p: ↑integer;

と宣言します・・って、あれ?↑ってどうやって出すんだ!?いや、参考にしている本が古いもので・・・。(昭和56年って書いてあるし)

FreePascalでは、
var p: ^integer;
と言う風に↑の代わりに^を使うみたいです。 (参考文献 freepascal wiki)
program pointer_test;
var p: ^integer;
begin
        new(p);
        p^:=5;
        writeln(p^);
        dispose(p)
end.
ポインター変数には実体がありません。そこでnewを使って実体を確保します。Cでいうところのmallocです。実体を確保せずに値を代入すると、Cではセグメンテーションフォルトになりますが、FreePascalではRuntime errorになります。

p^:=5は、ポインタpが示す場所に5を代入するという意味です。同様に、writeln(p^);は、ポインタpの示す場所の値を表示するという意味です。Cでいうところの*pに代入するようなものです。

dispose(p)は、newで確保したメモリーを解放します。Cでいうところのfreeです。

でも、実体が1個しか確保できなかったらポインタにする意味なくね?と思って色々やってみたのですが、new(p*20)とかnew(p[1..20])ってのはいずれもコンパイルエラーになります。 Cだとmallocで指定するのはバイト数なので融通が利くんですけど。なので、これはCみたく構造体のメモリーを複数個動的に確保するような使い方はできないっぽいです。

これは、どちらかというとISAMのような線形データーベースを作るために用意されている機能のようです。
program pointer_test;

type    p_senshu = ^senshu;
                senshu = record 
                                name: array[0..19] of char;
                                term: array[0..9] of char;
                                position: array[0..9] of char;
                                sebango: array[0..2] of char;
                                uchi: char;
                                nage: char;
                                nenpo: longint;
                                next: p_senshu;
                end;

var s: array[0..20] of senshu;
        p: ^senshu;

begin

        s[0].name:='Yoshio Itoi';
        s[0].term:='Fighters';
        s[0].position:='center';
        s[0].sebango:='7';
        s[0].uchi:='L';
        s[0].nage:='R';
        s[0].nenpo:=100000000;
        s[0].next:=@s[1];

        s[1].name:='Sinji Takahashi';
        s[1].next:=nil;

        p:=s[0].next;
        writeln(p^.name)

end.
この場合senshu型の要素の一番最後の項目が、次の選手へのポインタになっています。次の選手は@s[1]を示しているので、次はs[1]となります。例えば、s[1]のSinji Takahashi が退団(;ω; ウッ・・・)した時にs[0].nextのポインタを @s[2]にする事でメモリーの内容はそのままでs[1]の情報を事実上削除(;ω; ウッ・・)する事ができます。

s[1].next:=nil;というのは、ポインターに ぬるぽ NULLを入れるという意味で、つまり次のポインターはないという意味です。このデーターで最後と言う場合に使います。Cに慣れてしまうと、頭でわかってるつもりでも手が勝手にnulと書いてしまうので注意しましょう。

ファイル

ファイルを扱う前に、バイナリファイル、テキストファイルともにファイルディスクリプタを宣言する必要があります。

バイナリファイルの場合
var ファイルディスクリプタ: file of 型名

テキストファイルの場合
var ファイルディスクリプタ: text

ここでファイルディスクリプタといいましたが、それはCでいうところの名前で、Pascalではファイル型と呼びます。ファイルのオープンは、reset、rewrite、があります。resetが読み込み用、rewriteが書き込み用のオープンです。FreePascalではオープンする前にassignでファイル名と紐付けしておく必要があります。

assign
ファイル型の変数とディスクに読み書きする時のファイル名を紐付け(assign)します。 assign(ファイル型の変数, ファイル名) とします。ファイル名は実際にOSで管理しているファイル名です。

reset
ファイルを読み込み用にオープンします。reset(ファイル型の変数)とします。

rewrite
ファイルを書き込み用にオープンします。rewrite(ファイル型の変数)とします。

close
ファイルを閉じます。close(ファイル型の変数)とします。

read
read(ファイル型の変数, 変数)とすると、ファイルから変数に読み込まれます。第一引数を省略するとコンソールからの入力になります。

write
write(ファイル型の変数, 内容)とすると、内容がファイルに書き出されます。第一引数を省略するとコンソールへの出力となり、いわゆるPHPやPerlでいうところのprintと同じ役割をします。

seek
seek(ファイル型の変数,飛ばす量)とすると、ファイルのポインタを先頭から指定位置に移動(シーク)します。先頭を0とみなします。

eof
eof(ファイル型の変数)とすると、ファイルがeofに達していれば真、達していなければ偽となります。

eoln
eoln(ファイル型テキストタイプの変数)とすると、ファイル読み込みポインタの示す文字が改行コード、またはファイルがeofに達していれば真、それ以外に偽となります。

put、get
変数を使わずにファイルバッファ内だけで読み書きしますが、FreePascalでは使えないみたいです。というか、そもそもFreePascalではファイルバッファの操作自体できないようでして。

readln、writeln
readlnは行単位で読み込み、writelnは書いた後に後ろに改行コードをつけます。その他はread、writeと同じです。readln、writelnが使えるのは、テキストファイルとコンソールだけです。(他にもあるかも!?いや、少なくともバイナリファイルにはできないって事で・・・)

ファイル型の変数を指定せずに、引数を1つだけでreadln、writelnをするとコンソールに対しての入出力になります。という事は、writelnをPerlやPHPでいうところのprint文として使う事ができるわけです。といっても、これまでのサンプルでも暗黙で使ってましたけど。

バイナリファイルの書き込みのサンプル
program binary_file_test;

type senshu = record 
                                name: array[0..19] of char;
                                term: array[0..9] of char;
                                position: array[0..9] of char;
                                sebango: array[0..2] of char;
                                uchi: char;
                                nage: char;
                                nenpo: longint
                end;
	var f: file of senshu;
	var a: senshu;

begin
	assign(f,'test.dat');
	rewrite(f);
	a.name:='Yoshio Itoi';
	a.term:='Fighters';
	a.position:='center';
	a.sebango:='7';
	a.uchi:='L';
	a.nage:='R';
	a.nenpo:=100000000;
	write(f,a);
	close(f)
end.
senshu型のレコード(構造体ね)を定義しておき、ファイル型の変数fと、バッファaを宣言します。各要素に代入した後、write(f,a)で書き込んでいます。

テキストファイルの書き込みのサンプル
program text_file_test;

        var f: text;
        var a: string;

begin
        assign(f,'test.dat');
        rewrite(f);
        a:='Yoshio Itoi as Hokkaido Nipponham Fighters No.7 Center Fielder.';
        writeln(f,a);
        close(f)
end.
テキストファイルfとバッファaを宣言します。バッファに書き込む文字列を入れた後、writelnで書いています。

バイナリファイルの読み込みのサンプル
program binary_file_test;

type senshu = record 
                                name: array[0..19] of char;
                                term: array[0..9] of char;
                                position: array[0..9] of char;
                                sebango: array[0..2] of char;
                                uchi: char;
                                nage: char;
                                nenpo: longint
                end;
	var f: file of senshu;
	var a: senshu;

begin
	assign(f,'test2.dat');
	reset(f);

	while not eof(f) do
		begin
			read(f,a);
			writeln(a.name)
		end;

	close(f)
end.
バイナリファイルを順読み込みします。ファイルは1レコードがsenshu型の固定長になっています。eof(f)はファイルが終わりに達している時に真になりますので、 while not eof(f)はファイルがeofに達していない限りループという意味になります。
	seek(f,2);
	read(f,a);
	writeln(a.name);

	seek(f,1);
	read(f,a);
	writeln(a.name);

	seek(f,0);
	read(f,a);
	writeln(a.name);
この例では、ファイルを乱読み込み(ランダムリード)しています。(読み込みと表示部以外は省略しています)

ここで、seekはファイルの読み込みポインタを先頭から指定位置に移動します。先頭を0とみなしますので、上の例では、先頭から3番目、2番目、1番目のレコードを読みます。存在しないレコード位置にシークしようとすると、Runtime errorになります。

テキストファイルの読み込みのサンプル
program text_file_test;

	var f: text;
	var a: string;

begin
	assign(f,'test3.dat');
	reset(f);
	while not eof(f) do
		begin
			readln(f,a);
			writeln(a)
		end;
	close(f)
end.
テキストファイルを順読み込みして表示します。上の例ではreadlnでファイルから行単位で読み込みます。テキストファイルに対しては順読み込みしかできません。テキストファイルに対してseekを実行すると、call by var parameters have to match exactly: Got "Text" expected "File"というコンパイルエラーになります。
program text_file_test;

	var f: text;
	var a: char;

begin
	assign(f,'test3.dat');
	reset(f);

	while not eoln(f) do
		begin
			read(f,a);
			write(a)
		end;

	close(f)
end.
のように、readlnを使わずに1文字単位で読み込む事もできます。この場合、読み込む変数をcharで確保しておきます。eolnで行の終わを判別する事ができます。

サブルーチン

CやPerlやPHPと同様、Pascalもサブルーチンを作る事が出来ます。

procedure サブルーチン名(引数: 型; 引数:型 … )

ポインタ渡し 値渡し

program procesure_test;

var a,b:integer;

procedure sub1 (aa:integer; var bb:integer);
	begin
			aa:=1;
			bb:=1;
	end;

begin
	a:=0;
	b:=0;
	sub1(a,b);
	writeln(a,b)
end.
この場合、 変数aは0のままですが、変数bは1になります。varをつけない場合は引数として変数だけでなく即値も許されます。という事は、引数を宣言する際にvarをつけないと値が渡され、つけるとポインタが渡されるみたいです。みたいっていうのは、参考にした本にはそこまで書かれてなかったんで、結果から推測した域を出ないのですが・・・。

「ポインタが渡される」といっても、引数をポインタ変数として扱う必要はありません。他の変数と同様に扱ってください。ただし、呼び出し元の変数と同じポインタを示しているため、サブルーチン内で変更すると呼び出し元の変数も影響してしまうという事です。

参考にした本によると、varをつけない時は「値パラメタが手続き内で複写される」と書いてあったので、今ふうの言い方に直すと、varをつけない時は引数の値がスタックを介してサブルーチンに渡されるのではないかと思われます。

ローカル変数

program procesure_test;

var a,b:integer;

procedure sub1 (aa:integer; var bb:integer);
	var c,d:integer;
	begin
		c:=3;
		d:=4;
		writeln(c,d);
	end;

procedure sub2 (aa:integer; var bb:integer);
	var c,d:integer;
	begin
		c:=5;
		d:=6;
		writeln(c,d);
	end;

begin
	a:=1;
	b:=2;
	sub1(a,b);
	sub2(a,b);
	writeln(a,b);
end.
即興で作った例なので、あんまり意味のないプログラムなんですが(引数とか全然使ってないし・・・)、sub1とsub2で同じ名前の変数cとdを使っています。このように、procedure内で宣言した変数はローカル変数になります。参考にしている本によると、ローカル変数は局所的変数、グローバル変数は広域的変数と呼ぶそうです。

サブルーチン内でグローバル変数として既に使っている名前をローカル変数として使っても良く、その場合はローカル変数が優先されます。つまり、グローバル変数と同じ名前のローカル変数を使うと、グローバル変数は隠蔽されるわけですね。

関数

リターンコードを返す事ができるサブルーチンを関数と呼びます。CやPHPではリターンコードを返さない関数がサブルーチンでしたし、Perlでは頭に&をつけて呼べばサブルーチン、後ろに引数()をつけて呼べば関数という風に分けていましたが、Pascalではサブルーチンと関数を明確に区別しています。

function 関数名 (引数:型; 引数:型; …): リターンコードの型;
program function_test;

var a,b,c:integer;

function func1 (var aa:integer; var bb:integer) : integer;
	var d:integer;
	begin
		d:=aa*bb;
		func1:=d;
	end;

begin
	a:=5;
	b:=6;
	c:=func1(a,b);
	writeln(c);
end.
このように、func1という名の、与えられた2つの引数を掛け算して返すという関数を作ったとします。

関数の中でaa*bbをローカル変数dに入れ、最後に関数名=dとしてリターンコードを返しています。このように、Pascalではリターンコードは関数名に代入をします。VBみたいですね。(注)

(注)関数名に代入するというのはVB6と同じですが、VB.NETではCみたいにreturn文でリターンコードを返す事を推奨しているようです。

最後に

Pascalに詳しくないので、あまりこみいった話ができずに申し訳ありません。一応、他の言語(CとかPerlとか)を知っている人が見てわかるように書いたつもりですが、いかがだったでしょうか?

今まで他言語を扱った人でPascalを全く知らない人が見て、さわりの部分がわかるようには書いているつもりなので、この後さらに詳しい情報が必要であればGoogle等で検索してより詳しい情報を発見してみてください。このサイトは、その前の段階の基礎を理解するためのお役に立てれば幸いです。

参考文献
情報処理シリーズ2 PASCAL
1981年5月20 K.イェンゼン:著 原田賢一:訳 株式会社培風館:発行
このページの先頭へ
  広告