【学習内容とねらい】
今、あるテストの成績処理プログラムを作成する場合を考えましょう。前章の制御命令をマスターしていれば、基本的にこのプログラムを作成することはできます。しかし、「まずデータをファイルから読み込む部分を作って、それから平均値と標準偏差を計算しよう。おっと平均値を計算するには、得点の総合計と受験者総数が必要だから、それらを先に求めておかないと・・・。そうそう、標準偏差を計算するには偏差の2乗の和を求めておかなければならなかった・・・。」等々と、必要になる処理を逐一思いつくままに書き連ねて行くとミスも多く、また全体構造が分かりずらいプログラムになってしまいます。
そこで、以下のように2段階に分けてプログラミングできたらどうでしょうか?
T.このプログラムは以下の処理から成る。 |
U.各処理の詳細は以下の通り。 |
(1) 成績データの読み込み (2) 統計量(平均値と標準偏差)の計算 (3) 成績順の並べ替え(ソート) (4) 結果の表示 |
(1) ・・・ (2) ・・・ (3) ・・・ (4) ・・・ |
こうしておけば、Tを見ればプログラム全体の流れが分かります。また、もしソート結果が一部間違っていたとすると、Uの(3)の部分のみを点検すればよく、エラーの発見や修正が容易になります。また、のちに、偏差値も計算することになった場合には、Uの(2)の部分に計算部分を追加するだけですみ、拡張が容易になります。
この(1)〜(4)のような部分処理を「モジュール」といいます。Delphiでは、モジュールとして「関数」と「手続き」がありますが、本章では「関数」と「手続き」の用い方の学習を通じ、全体のプログラムを「モジュールの組み合わせ」という形で(上の例のように)作成する術を学びます。ここでは、モジュールとは「関数・手続き」の総称ととらえてください。
プログラミング学習の成果は、ある処理を自分でモジュールに分割できるかどうか、にかかっていると言われています。その意味で本章はプログラミング入門編の集大成になります。
<本章の構成>
![]() |
5−2 モジュール (2) −1つのボタンですます−
5−3 モジュール (3) −押せないボタンはいらないのか−
5−4 モジュール (4) −構造のはっきりしたプログラム−
5−5 モジュール (5) −Button1 と Button1 は本当にいらないのか−
5−6 モジュール (6) −プログラムの構造−
5−7 モジュール (7) −「手続き」を作ろう−
5−8 関数 (1) −値を返すモジュール−
5−9 関数 (2) −絶対値関数を使う−
5−10 関数 (3) −関数を使う−
5−11 関数 (4) −Random関数−
5−12 ローカル変数とグローバル変数
5−13 メソッド
文法と会話の得点合計を求めてから合否を判定するプログラムを作りましょう。
ここに後の説明の都合上、二つの「ボタン」コンポーネントのNameプロパティはそれぞれ「Button1」および「Button2」として下さい。
右のようなフォームを持つプログラムを作って下さい。
文法と会話の得点を入力してから「合計点を計算する」ボタンを押すと
合計点が計算され、さらに「合否を判定する」ボタンを押すと
150点以上か否かに応じて、合否を判定する。
このプログラムは今までの学習範囲内で作成できるはずです。
ボタンを2回押さずに、1回ですます方法があります。フォームの下の方を少しだけ大きくして、第三のボタン「両方のボタンを押す」を付け加えて下さい(Nameプロパティは「Button3」)。
第三のボタンを押したときの処理(イベントハンドラ)は、次のように書いて下さい。
少し腑に落ちないかもしれませんが、とりあえず実行してみましょう。
「文法」と「会話」の得点を入力してから、第三のボタンを押してみて、プログラムの動作を確かめましょう。
「合計点を計算する」ボタンも「合否を判定する」ボタンも使わなくてすみました。
それなら、この2つのボタンを見えなくしたらどうなるでしょう。
「合計点を計算する」ボタンおよび「合否を判定する」ボタンのVisibleプロパティをFalseにして下さい。
予想 さて、ここで実行ボタンを押す前に、予想をして下さい。
1. エラーが出て実行できない
2. エラーは出ないが、目的通りの動作はしない
3. エラーは出ず、目的通りの動作をする
あなたの予想は 。
予想をしたら、実行ボタンを押してプログラムの動作を確かめましょう。
「合計点を計算する」ボタンと「合否を判定する」ボタン を両方見えなくしても、プログラムは目的通りに動いと思います。
それならば、この2つのボタンをなくしてしまったらどうなるでしょう。
消したいボタンを選択して、
DELキーを押すと
ボタンが消えます。
もう一つのボタンも消しましょう。
予想 さて、ここで実行ボタンを押す前に、予想をして下さい。
1. エラーが出て実行できない
2. エラーは出ないが、目的通りの動作はしない
3. エラーは出ず、目的通りの動作をする
あなたの予想は 。
予想をしたら、実行ボタンを押してプログラムの動作を確かめましょう。
今度は、3科目の合計点を計算してから、合否を判定するプログラムを作ります。
下のようなフォームを持つプログラムを作って下さい。
主なコンポーネントのNameプロパティは、次のようにして下さい。
コンポーネント |
Name |
ボタン「合計点を計算する」 |
Button1 |
ボタン「合否を判定する」 |
Button2 |
まず、それぞれのボタンに対して、それを押したときのイベントハンドラを書いて、プログラムが正しく動作するようにして下さい。
正しく動作することが確認できたら、第三のボタンを付け加えて、そのボタンを押したときの処理を以下のように書いてください。
最後に、フォームの上にある「合計点を計算する」ボタンと「合否を判定する」ボタンを削除してから、動作を確認してください。
前節同様、うまく動作するはずです。
コードエディタを見てみましょう。
「Button1Click」と「Button2Click」が、それぞれ3か所ずつ出てきているはずです。
ところが、Button1もButton2も、もう存在しないのです。
それでは、すべてのButton1Clickを「Keisan」に、すべてのButton2Clickを「Hantei」に変えてみるとどうなるでしょう。
予想 さて、ここで実行ボタンを押す前に、予想をして下さい。
1. エラーが出て実行できない
2. エラーは出ないが、目的通りの動作はしない
3. エラーは出ず、目的通りの動作をする
あなたの予想は 。
予想をしたら、実行ボタンを押してプログラムの動作を確かめましょう。
もう一度コードエディタを見てみましょう。
(1)
このプログラムは、
という3つの部分からできています(右図の(1)部分参照)。
このような、プログラムの中の小さな独立部分を、「モジュール」といいます。
Delphiではこれを「手続き」と呼びます。
(2)
プログラムの中にどんな「手続き」があるかは、コードエディタの上の方に書きます(右図の(2)部分参照)。
コラム Senderとは? 例えば、Button1をクリックしてもButton2をクリックしてもButton3をクリックしてもKeisan手続きに飛ぶとします。そして、スピンエディットのValueプロパティの値を、
にするとしましょう。このようなとき、どのボタンがクリックされたかという情報を保存しておくことによって、処理の記述が容易になるという利点があるのです。 |
(3) このプログラムでの、3つのモジュールの関係は、次の図のようになっています。
このように、メインプログラムはシンプルに、細かな処理の部分はサブプログラムにまかせるように書くのが、見やすいプログラムといえます。
これまで何度も目にしてきた、「手続き」を定義する
という部分は、Delphiが自動的につくってくれました。少し、この書式をみてみましょう。Delphiでは、手続き定義する際の書き方は次のようになっています。
これを上の例に当てはめると、対応は次のようになっています。
手続き名 |
引数 |
少し補足説明しておきましょう。
@ 手続き名の前に、「TForm1.」がついていますが、これは、「Form1に所属している」という意味です。「TForm1.Button1Click」は、Form1上に定義されたButton1Click手続きという風に読みます。とりあえず、今は必ず「フォーム名.」が先頭につくと考えておいて良いでしょう。
A 引数とは、この手続きが呼びされた時、呼び出し元のプログラムから(呼び出された)手続きの定義プログラムへ受け渡す変数のことです。次節で引数を使用する例を学習します。
さて、上の「手続き」定義部分は、自分で書くこともできます。その練習をしてみましょう。
この部分は、自分で書くこともできます。その練習をしてみましょう。
下のようなフォームを持つプログラムを作って下さい。
主なコンポーネントのNameプロパティは、次のようにして下さい。
コンポーネント |
Name |
ボタン「計算と判定」 |
ButtonMain |
「パソコン本体の値段」と「ディスプレイの値段」を入力してから「計算と判定」ボタンを押すと、
1. 合計金額を表示し、
2. 合計金額が300000以下なら「買える。」そうでなければ「買えない。」と表示する
プログラムを作ります。
まず、ButtonMainを押したときの処理(イベントハンドラ:これも手続きです)を、以下のように書いて下さい。
ここに、手続き「keisan」と「Hantei」にとっては、引数Senderは必要ないので、引数なしとしています。
次に、上に書いた「Keisan」と「Hantei」手続きを、自分で次のように書きます。
最後に、「Keisan」と「Hantei」という手続きを自分で作ったことをDelphiに知らせるために、コードエディタの上の方で宣言をします。下図の、枠で囲った2行を追加して下さい。
これまでみてきた様に、一般にプログラムは複数のモジュールからなります。そして、メインモジュールにあたる「手続き」はサブモジュールである「手続き」に実行命令を出し、サブモジュールの方は決められた仕事をこなす、という処理の流れになっていました。
しかし、プログラミングの中では、
1. メインモジュールがいくつかの値(引数)を渡し、
2. サブモジュールがそれをもとに値(答)を計算して、メインモジュールに返す
という場面が必要になる場合があります。
引数
以下では、このような場合について考えて行きます。
3つの数x,y,zの値を渡すと平均値を計算して送り返すサブモジュールを作成・利用することで、以下のプログラムを完成させましょう。
まず、下のようなフォームのプログラムを作って下さい。
主なコンポーネントのNameプロパティは、次のようにして下さい。
コンポーネント |
Name |
上のスピンエディット |
SpinEditX |
中のスピンエディット |
SpinEditY |
下のスピンエディット |
SpinEditZ |
ボタン |
ButtonMain |
エディット |
EditHeikin |
ButtonMainをクリックしたときの処理(イベントハンドラ)は、次のように書いて下さい。
Averageモジュールをつくります。下のように書いて下さい。
解説
· Averageは、このモジュールの名前です。
· x,y,zは、メインモジュールからサブモジュールに渡される値です。
· Integerは、x,y,zがInteger型の変数であることを表しています。
· Realは、AverageがReal型の変数であることを表しています。
そして、Averageモジュールをつくったことを宣言するのを忘れないで下さい。
下のように、Averageモジュールがあることを宣言する必要があります。
処理の流れ(イメージ図)
それでは、実行してみましょう。
このAverageモジュールのように、値を受け取って値(答)を |
数の絶対値を求める関数を作ります。
下のようなフォームを持つプログラムを作って下さい。
主なコンポーネントのNameプロパティは、次のようにして下さい。
コンポーネント |
Name |
左のスピンエディット |
SpinEditFrom |
右のスピンエディット |
SpinEditTo |
ボタン「絶対値を求める」 |
ButtonMain |
まず、ButtonMainをクリックしたときの処理を、次のように書いてください。
次に、Zettaichi関数をつくります。
その前に、「絶対値」とはなんだったか、復習しておきましょう。
−3の絶対値は、3 Zettaichi(-3) =3
5の絶対値は、 5 Zettaichi(5)=5
0の絶対値は、
aの絶対値は、
aが正の数のとき、
aが負の数のとき、
右の空欄を埋めて、関数Zet
taichi
を完成させてください。
最後に、
関数Zet
taichiをつくったことを、コードエディタの上の方で宣言してください。
動作を確認してみましょう。
コラム 「関数(function)」と「手続き(procedure)」 functionとprocedureはどちらも、プログラム全体の一部という意味で、モジュールといえます。両者の違いは、値を返すかどうかという点にあります。 値を返すことができないprocedureは、functionの特殊なものと考えることもできます。 |
前ページで作った絶対値を求めるプログラムを改変します。まず、新しい名前でプログラムを保存し直してください。
まず、関数Zettaichiの宣言の部分を削除して下さい。
さらに、関数Zettaichiの定義部分を削除してください。
最後に、「Zettaichi」を「Abs」に変えてください。
すべて保存してからプログラムを実行して、動作を確かめてください。
・・・ 先ほどと同様うまく動作するはずです。
「Zettaichi」は、数の絶対値を返す関数でした。
「Abs」も、数の絶対値を返す関数として機能しています。
でも少し腑に落ちませんね。
「Zettaichi」は、関数の定義部分
function TForm1.Zettaichi(a:
Integer): Integer;
begin
・・・
end;
が必要だったのに、Abs に関してはわれわれは何もしていません。
Absは、どうして関数の定義部分がいらないのでしょうか?
実は、Abs関数の定義部分はDelphiの“内部”にあるのです。Delphi(正確に言うとPASCAL言語)は、Abs関数の他にも様々な関数を定義しています。これらは「組み込み関数」と呼ばれます。われわれは、それらの関数を、定義することなく使うことができるのです。
定義せずに使える関数の例
関数名 |
例 |
Abs()(絶対値関数) |
Abs(-3)=3 |
Sqr() |
Sqr(4)=16 |
Sqrt() |
Sqrt(9)=3 |
Sin()(サイン関数) |
Sin(0)=0 |
Cos()(コサイン関数) |
Cos(0)=1 |
Ln()(対数関数) |
Ln(2.71828)=0.999999327347282 |
Exp()(指数関数) |
Exp(1)=2.71828182845905 |
Delphiが定義している関数でよく使うものに、Random関数があります。Random関数は、ランダムな数(乱数)を返す関数です。
下のようなフォームを持つプログラムを作って下さい。
コンポーネントのNameプロパティは、次のようにして下さい。
コンポーネント |
Name |
スピンエディット |
SpinEditNumber |
ボタン「乱数発生」 |
Button |
ボタンをクリックしたときの命令を、次のように書いてください。
解説
それでは、実行して動作を確かめてみてください。
右のようなフォームを持つプログラムを作って下さい。
ボタンを押すと3つのスピンエディットに0から99までの乱数を発生させるようにしてください。
右のようなフォームを持つプログラムを作って下さい。
ボタンを押すと3つのスピンエディットに0から2までの乱数を発生させるようにしてください。そして、3つの数がそろったら「大当たり!」と表示して下さい。
3つの数がそろっていないときは「はずれ!」と表示して下さい。
下のようなフォームをもつ、パネルの色が赤/青/黄にランダムに変わるカラースロットマシンをつくります。
色の変わるパネルは、「パネルコンポーネント」を使います。コンポーネントパレットのStandardタブにあるパネルを、フォームに3つ貼ります。
パネルの色を変えるには、Colorプロパティの値を変えます。図の枠で囲った部分をダブルクリックするか、下向きの三角形をクリックして、左のパネルを赤に、中央のパネルを青に、右のパネルを黄色に変えてください。
パネルの色をランダムに変えるプログラムを書きましょう。色々な方法がありますが、例えば、0、1、2の乱数を発生させて、
0のとき赤、1のとき青、2のとき黄
にするようにしましょう。
Panel1の色を赤、青、黄色に変える命令は、それぞれ次のように書きます。
最後に、色がそろったときには「大当たり!」、そろわなかったときには「はずれ!」のメッセージを出すのも忘れないでください。
足し算だけができる簡単な電卓を作ってみましょう。
|
エディットに適当な数を入力して |
|
「+」ボタンを押すと画面には現れない場所 (変数) に値が保存され、エディットは空欄になり、 |
|
その後、エディットに別な数を入力して「=」ボタンを押すと |
|
和が求まる、というプログラムです。 |
まずは、フォームにコンポーネントを配置しましょう。
コンポーネント |
Name |
左のエディット |
EditNumber |
右上のボタン |
ButtonPlus |
右下のボタン |
ButtonEqual |
このプログラムでは、整数型変数 PrevNumber を用意して、「+」ボタンが押されたらそこに値を格納するようにします (下のプログラムを参照)。
まず、「+」ボタンのイベントハンドラを書きましょう。
procedure TForm1.ButtonPlusClick(Sender: TObject);
var
PrevNumber: Integer;{ここで PrevNumber を用意}
begin
PrevNumber := StrToInt(EditNumber.Text);{値を格納する}
EditNumber.Text := '';{エディットを空欄にする}
end;
次に、「=」ボタンのイベントハンドラを書きましょう。
procedure TForm1.ButtonEqualClick(Sender: TObject);
begin
EditNumber.Text := IntToStr(PrevNumber
+ StrToInt(EditNumber.Text));
end;
実行してみましょう。 すると・・・
「未定義の識別子 : 'PrevNumber'」というエラーが出ましたね。これは、「'PrevNumber' という変数 (か何か) を使いたいのだろうけど、そんなものは知らないよ」というエラーです。このエラーをダブルクリックすると、
エラーが発生した行が茶色で、エラーが発生した場所 (Delphi が解釈できなくなった場所) が水色で示されます。
PrevNumber は宣言したはずなのに、未定義エラーが出たのはなぜでしょうか?
実は、Delphi(PASCAL言語) には、「関数・手続きの中で用意された変数は、その関数・手続きの中だけで有効である」という決まりがあります。具体的に示すと次のようになります。
では、どうしたらよいのでしょう? この場合は、どの「関数・手続き」からでも利用できるように、PrevNumber を用意しましょう。コードエディタのずっと上のほうを見て、「type」で始まる次の部分を探してください。
この end; の直前で変数を宣言します。下線部を付け加えてください。(この「type」の中で宣言する場合、「var」はいりません。)
type
TForm1 = class(TForm)
ButtonPlus: TButton;
ButtonEqual: TButton;
EditNumber: TEdit;
procedure ButtonPlusClick(Sender: TObject);
procedure ButtonEqualClick(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
PrevNumber: Integer;
end;
また、これで先ほどの変数宣言はいらなくなりました。下線部を削除してください。
procedure TForm1.ButtonPlusClick(Sender: TObject);
var
PrevNumber: Integer;{ここで PrevNumber を用意}
begin
PrevNumber := StrToInt(EditNumber.Text);{値を格納する}
EditNumber.Text := '';{エディットを空欄にする}
end;
実行してみましょう。 今度はちゃんと動作するはずです。
このようにどの「関数・手続き」からも利用することができる変数を、「グローバル変数 (大域変数) 」と呼びます。一方、「関数・手続き」の中で宣言された変数は「ローカル変数 (局所変数)」 と呼ばれます。変数の有効範囲をスコープと言います。
コラム 全てグローバル変数でもいいのでは?
「それなら、どんな変数もグローバル変数にするほうが便利なのでは?」と考える人もいるかもしれません。すべての変数をグローバル変数にするとどうなるでしょう?
3. プログラミングでは、複数の関数・手続きで共有する必要のない限りは「ローカル変数」を使うのが鉄則とされています。 |
【基礎課題 1-7-5】で調べたように、各コンポーネントには、多くのプロパティがあります。例えばエディットなら、 Text プロパティを筆頭に、Top, Visibleなど様々なプロパティがあります。
絶対値を求める abs 関数と同様に、これらのプロパティは Delphi に最初から備わっているものです*)。「プロパティはコンポーネントに最初から備わっている変数である」と言い換えることもできます。
* ) 厳密に考えると、Delphi のもととなった PASCAL 言語の規格が定めているものと、Delphi の開発者が Delphi の利用者のために作ってくれたもの (ライブラリと呼ばれます) は別物ですが、ここではその違いを考えません。
そして実は、コンポーネントには変数 (プロパティ) だけでなく関数・手続きも最初からいくつか備わっているのです。このような関数・手続きは,メソッドと呼ばれます。ここでは、エディットに備わっている Clear メソッドを使って、【基礎課題 4-3-1】で作った検索プログラムを改良してみましょう。
このプログラムは、キーワードを入力しないで「検索」ボタンを押すと「検索用キーワードを入力してください。」という警告が出るというものでした。しかし、一度警告が表示されると、その後キーワードを入力して「検索」ボタンを押しても警告は消えません。そこで、一度警告が出ても、その後ちゃんとキーワードを入力すると警告が消えるように、プログラムを改良しましょう。まずは、今までの知識の範囲で改良します。プログラムを次のように変えてください。(コンポーネントの名前があなたのものと違う場合は適宜読み替えてください。)
procedure TForm1.ButtonSearchClick(Sender: TObject);
begin
if EditKeyWord.Text = ''
then
begin
EditMessage.Text := '検索用キーワードを入力してください。';
end
else
begin
EditMessage.Text := '';
end
;
end;
正しく動く (警告が1度出た後でもキーワードを入れると警告が消える) ことを確かめたら、プログラムを次のように変更してください。
procedure TForm1.ButtonSearchClick(Sender: TObject);
begin
if EditKeyWord.Text = ''
then
begin
EditMessage.Text := '検索用キーワードを入力してください。';
end
else
begin
EditMessage.Clear;
end
;
end;
先ほどのプログラムと同じように動くことを確かめましょう。
メソッドは、「オブジェクト名.メソッド」という形で呼び出します。この例
EditMessage.Clear
は、EditMessage の Clear メソッドを呼び出しています。
これまで、フォームやEditなどのコンポーネントには、プロパティがあることを学習して来ました。プロパティは、色や高さや幅等々、そのコンポーネントの「性質」を指定するためのものでした。実は、これらコンポーネントには、プロパティという“静的性質”に加えて「メソッド」と呼ばれる“動的操作”が定義されています。上の例でいうと、Clearはエディットに対して定義されているメソッドであるという訳です。
ところで、メソッドとプロパティを有するモノを“オブジェクト”と呼びます。そしてオブジェクトを組み合わせてプログラムを作成するという考え方を“オブジェクト指向プログラミング”と呼びます。オブジェクト指向プログラミングの世界では、プロパティやメソッドを
オブジェクト名.プロパティ オブジェクト名.メソッド
という形式で表します(ドット記法と言います)。予め、このようにプロパティやメソッドが定義されていればプログラムの開発効率が上がる事は皆も容易に理解できるでしょう。
さて、ここまで説明すると皆は、コンポーネントがオブジェクトであったこと、そしてそれを組み合わせてプログラミングを行うDelphiは“オブジェクト指向プログラミング”に則ったプログラム開発環境である事に気づいたと思います。きわめて簡略化した説明ですが、とりあえず、“オブジェクト指向プログラミング”という言葉の意味をこのように大まかに捉えておいてください。
エディットに予め備わっているメソッドは、Clear 以外にも何十個もありますが、その全てを紹介すると紙面も時間もかかるので、この教科書では今後も代表的なものしか紹介しません。そこで、「他にはどういうメソッドがあるのだろう?」と興味を持ったあなたのために、メソッドの調べ方を説明しておきましょう。
コンポーネントパレットの中から、調べたいものをクリックし、「F1」を押します。(下の画面はエディットについて調べる例です。)
ここで、次の画面が現れるので、下方の「Tedit(VCLリファレンスヘルプ)」を選択して[表示]ボタンをクリックしてください。
|
|
|
|
すると、エディットのメソッド一覧が表示されるので、目的のメソッドをクリックします。 |
|
こうして、目的のメソッドの説明が表示されます。説明は難しい内容であることが多いですが、この教科書を読み進んでいくとある程度分かるようになります。 |
|
|
|
メソッドだけでなくプロパティについても同様の方法で調べることができます。