これまで作成してきた簡単計算ソフトですが、文字列から数値への変換にこれまでDelphi標準の関数 StrToInt、StrToFloat を使用してきましたが、この関数では、英文字や日本語文字を使ってしまうと例外を発生してしまったりして多少使いにくい、という事がありました。仮に、
if (入力した文字が英字である) then
結果 := 0
else
if (入力した文字が日本語である) then
結果 := 0
else
結果 := StrToInt(入力した文字) ; |
又は、例外処理を使用して
try
結果 := StrToInt(入力した文字) ;
except
結果 := 0 ; // エラー時の処理
end; |
のように行う事も可能であろうと思われますが、多少、変換のためだけにここまでせねばならないのかという思いも出てきてしまいます。ですので、StrToInt関数や StrToFloat関数の代わりに別の手法を使う事にします。と、そのまえに、例外処理について少し書いておきます。
◆例外処理
プログラミングを行い、作成したプログラムを実行していると、どうしても、例外(エラー)が発生してしまう場合があります。例えば、数値計算での「ゼロでの除算」をしてしまって例外が出てしまう場合、通常であれば、ゼロで除算しないように if文で判断させて問題回避を行うようにします。
↓
if (c <> 0) then
a := b div c
else
a := 0 ; |
しかし、計算式がかなり複雑で予測が出来ない場合があったり、どこをどうすれば問題回避出来るのか分からない場合もあるやもしれません。例外が発生すると、その行で実行は中断され、エラーメッセージが表示されます。そういった場合には、例外処理を行うようにします。例外処理には2つの種類があります。
try
(例外が起きるかもしれない文)
finally
(例外が起きても起きなくても
最終的に確実に実行したい文)
end; |
例外が起きるかもしれない文(複数の文でも可。begin〜endは必要ありません)の実行中、例外が発生すると、エラーメッセージが表示され、try 〜 finally内はその箇所で実行中断されますが、finally 〜 end内の文は実行継続されます。しかし、try〜finally〜end 以降の行は実行中断され、そのイベントハンドラ等は終了します。
try 〜 finally内で例外が発生しなかった場合は、try 〜 finally内の文を実行し、finally 〜 end内の文を実行し、try〜finally〜end 以降の行へと実行は続きます。
エラーメッセージを表示したくない/独自のエラーメッセージを表示したい、イベントハンドラ等ブロックの実行中断をさせたくない場合には、下記の try〜except〜end を利用します。
try
(例外が起きるかもしれない文)
except
(例外が起きた場合に実行したい文)
end; |
例外が起きるかもしれない文(複数の文でも可。begin〜endは必要ありません)の実行中、例外が発生すると、エラーメッセージは表示されず、try 〜 except内はその箇所で実行中断されますが、except 〜 end内の文が実行され、try〜except〜end 以降の行へと実行は継続されます。
try 〜 except内で例外が発生しなかった場合は、try 〜 except内の文を実行し、except 〜 end内の文は実行されず、try〜except〜end 以降の行へと実行は続きます。
例外処理の内容が違いますので、状況に応じて使い分けて下さい。
◆Val手続き
文字列から数値への変換にDelphi標準の関数 StrToInt、StrToFloatを利用してきましたが、Val手続きを利用する事によって例外からのわずらわしさから解放されます。Val手続きは、空の文字列であっても、英字であっても、日本語であっても、例外は発生せず、結果 0 を返します。但しこれは関数ではありませんので「a := Val(Edit1.Text)」のような使い方は出来ません。「Val(Edit1.Text,a,code);」のようになります。
procedure Val(S; var V; var Code: Integer); |
S:(入力)変換したい文字列値
V:(出力)変換後の変数 整数でも実数でも可
Code:(出力)文字列が無効である場合,
問題のある文字の位置が格納される。
有効な場合は 0 が格納される。 |
整数の変数 a で「Val(Edit1.Text, a, code);」のようにすると、文字列 Edit1.Text を整数変換して、変数 a に整数値が入ります。
実数の変数 e で「Val(Edit1.Text, e, code);」のようにすると、文字列 Edit1.Text を実数変換して、変数 e に実数値が入ります。
整数の変数を指定している場合、実数値を入力すると、自動的に整数値に丸められて、変数に変換結果が入ります。
関数は、0個以上の引数を受け渡して処理が行われ、1つの値を返す事が出来ます。
手続きは、0個以上の引数を受け渡して処理が行われ、値を返す事は出来ませんが、var指定された引数に値を返す事が出来ますので、結果的には複数の値を返す事が出来ます。
◆関数を自作
Val手続きを使うとしても、これまでの「A := ○○(Edit1.Text);」のように関数として利用したいですね。そういう場合には、関数を自作してしまいます。
まずは、関数の名前を考えます。
文字列(String)から整数(Integer)に変換ですのでDelphiでは「StrToInt」という名前になっていました。同じ名前は使えませんので別の名前で考えてみます。例えばC言語では「atoi」ですね。当方はこれまで「IVal」とか「SInt」のような名前を付けたりしていました。自分で分かり易い名前にして下さい。余り長すぎる名前も書くのが大変ですので問題ですが、省略し過ぎるのも余りよくありません。
ここでは「IVal」としておきます。
function IVal(S:String) : Integer ;
var
a : Integer ;
Code : Integer ;
begin
Val(S, a, Code) ;
Result := a ;
end; |
という具合になり、
のようにして関数を呼び出して利用する事が出来ます。関数は、それを呼び出す側より前に(先に;上に)記述する必要がありますので注意して下さい。
同様に、実数値用の関数 DVal も作っておきます。
function DVal(S:String) : Double ;
var
a : Double ;
Code : Integer ;
begin
Val(S, a, Code) ;
Result := a ;
end; |
という具合になり、
のようにして関数を呼び出して利用する事が出来ます。
自作関数内で利用する変数 a, Code はローカル変数ですので、この関数内でのみ有効であり、他のイベントハンドラや関数・手続きの中で使用される変数とは全く無関係です。関数の実行が終了されると破棄されます。ですので、関数内だけに絞って集中してプログラミングが出来るという事になります。先頭のIVal、DValの次の括弧内は、関数へ受け渡す引数;パラメータです。何個でも構いません。仮に引数が無い場合には「function IVal : Integer ;」のように括弧ごと無くなります。引数が複数ある場合、同じ型の場合には、
「function IVal(S,S1,S2,S3,S4:String) : Integer ;」
のようにカンマ[,]で区切ります。型が異なる場合には、
「function IVal(S:String;V:Integer) : Integer ;」
のようにセミコロン[;]で区切ります。複合する場合には、
「function IVal(S,S1,S2:String;V1,V2:Integer) : Integer ;」
のような感じです。呼び出し側で利用する変数名とここで定義する引数の変数の名前は同じである必要はありません。但し、変数の型、個数は同じである必要があります。
引数の後に、返り値の型をコロン[:]で付加します。
「function IVal(S:String) : Integer ;」であれば整数を返す関数、「function DVal(S:String) : Double ;」であれば倍精度実数を返す関数、となります。
返り値は、関数内での特殊な名前「Result」を変数のようにして値を入れます。「Result」の代わりに、その関数の名前、上記であれば「IVal」「DVal」を使う事も出来ます。どちらでも構いません。当方では勘違いをしない為に「Result」を利用しています。
先のプログラムを自作関数を使って書き換えてみます。
// 計算
procedure TForm1.Button1Click(Sender: TObject);
var
a , b , c , d : integer ;
e , f , g : double ;
i : integer ;
s : string ;
begin
e := StrToFloat(Edit1.Text) ;
f := StrToFloat(Edit2.Text) ;
a := Round(e) ;
b := Round(f) ;
if not(CheckBox1.Checked) then begin
// 整数計算
if (RadioButton1.Checked) then begin
c := a + b ;
s := IntToStr(c) ;
end
else begin
if (RadioButton2.Checked) then begin
c := a - b ;
s := IntToStr(c) ;
end
else begin
if (RadioButton3.Checked) then begin
c := a * b ;
s := IntToStr(c) ;
end
else begin
if (RadioButton4.Checked) then begin
if not(b = 0) then begin
c := a div b ;
d := a mod b ;
s := IntToStr(c) + ' ... ' + IntToStr(d) ;
end
else
s := 'ERROR!';
end
else begin
if (b >= 0) then begin
c := 1 ;
for i:=1 to b do
c := c * a ;
s := IntToStr(c) ;
end
else
s := 'ERROR!';
end;
end ;
end ;
end ;
end
else begin
// 実数計算
if (RadioButton1.Checked) then begin
g := e + f ;
s := FloatToStr(g) ;
end
else begin
if (RadioButton2.Checked) then begin
g := e - f ;
s := FloatToStr(g) ;
end
else begin
if (RadioButton3.Checked) then begin
g := e * f ;
s := FloatToStr(g) ;
end
else begin
if (RadioButton4.Checked) then begin
if not(f = 0.0) then begin
g := e / f ;
s := FloatToStr(g) ;
end
else
s := 'ERROR!';
end
else begin
b := Round(f) ; // 何乗かは整数とする
if (b >= 0) then begin
g := 1.0 ;
for i:=1 to b do
g := g * e ;
s := FloatToStr(g) ;
end
else
s := 'ERROR!';
end;
end ;
end ;
end ;
end ;
Edit3.Text := s ;
end; |
↓
// 文字列→整数
function IVal(S:String) : Integer ;
var
a : Integer ;
Code : Integer ;
begin
Val(S, a, Code) ;
Result := a ;
end;
// 文字列→実数
function DVal(S:String) : Double ;
var
a : Double ;
Code : Integer ;
begin
Val(S, a, Code) ;
Result := a ;
end;
// 計算
procedure TForm1.Button1Click(Sender: TObject);
var
a , b , c , d : integer ;
e , f , g : double ;
i : integer ;
s : string ;
begin
a := IVal(Edit1.Text) ;
b := IVal(Edit2.Text) ;
e := DVal(Edit1.Text) ;
f := DVal(Edit2.Text) ;
if not(CheckBox1.Checked) then begin
// 整数計算
if (RadioButton1.Checked) then begin
c := a + b ;
s := IntToStr(c) ;
end
else begin
if (RadioButton2.Checked) then begin
c := a - b ;
s := IntToStr(c) ;
end
else begin
if (RadioButton3.Checked) then begin
c := a * b ;
s := IntToStr(c) ;
end
else begin
if (RadioButton4.Checked) then begin
if not(b = 0) then begin
c := a div b ;
d := a mod b ;
s := IntToStr(c) + ' ... ' + IntToStr(d) ;
end
else
s := 'ERROR!';
end
else begin
if (b >= 0) then begin
c := 1 ;
for i:=1 to b do
c := c * a ;
s := IntToStr(c) ;
end
else
s := 'ERROR!';
end;
end ;
end ;
end ;
end
else begin
// 実数計算
if (RadioButton1.Checked) then begin
g := e + f ;
s := FloatToStr(g) ;
end
else begin
if (RadioButton2.Checked) then begin
g := e - f ;
s := FloatToStr(g) ;
end
else begin
if (RadioButton3.Checked) then begin
g := e * f ;
s := FloatToStr(g) ;
end
else begin
if (RadioButton4.Checked) then begin
if not(f = 0.0) then begin
g := e / f ;
s := FloatToStr(g) ;
end
else
s := 'ERROR!';
end
else begin
b := Round(f) ; // 何乗かは整数とする
if (b >= 0) then begin
g := 1.0 ;
for i:=1 to b do
g := g * e ;
s := FloatToStr(g) ;
end
else
s := 'ERROR!';
end;
end ;
end ;
end ;
end ;
Edit3.Text := s ;
end; |
◆手続きを自作
関数は、数学での関数のように、ある引数を受け渡して、目的の演算等を行って結果を返し、その結果を利用して更に別の処理を、という具合で、機能的には単機能である場合が多いです。
上記のプログラムを見ると、整数計算部分と実数計算部分がだらーっと続いてプログラムの見通しも余りよくありません。整数計算部分と実数計算部分を分離したくなります。手続きは、そういった処理の分散をしたい場合によく利用されます。
手続きは、関数と同様に引数を持ちますが、返り値がありませんので、引数の後の「:」+型と、内部での特別な名前「Result」はありません。引数のルールは関数と同じです。
procedure 手続き名(引数) ;
begin
・・・・・
end; |
呼び出し側は、
のようにして利用する事が出来ます。手続きは関数と同様、それを呼び出す側より前に(先に;上に)記述する必要がありますので注意して下さい。
手続きで値を返したい場合には、引数に「var」を付けます。
procedure ABCDE(a,b,c:integer) ;
begin
・・・・・
end; |
のうち、aの値を返り値として利用したい場合は、
procedure ABCDE(var a:integer; b,c:integer) ;
begin
・・・・・
end; |
のようにして、値を返さない引数とは別のものとして記述する必要があります。
procedure ABCDE(var a,b,c:integer) ;
begin
・・・・・
end; |
と記述すると、引数 a , b , c 全て、値を返す引数となります。呼び出し側では、いずれも
のようになります。引数として手続きABCDEに、x値、y値、z値を受け渡し、手続きABCDEではそれを、変数 a,b,c として受け取り、処理を行い、var指定した引数には返り値として必要な値を入れ、元の呼び出し側のイベントハンドラ・関数・手続きへ戻ります。
先のプログラムを自作手続きを使って書き換えてみます。整数計算部分、実数計算部分を自作手続きにします。
// 文字列→整数
function IVal(S:String) : Integer ;
var
a : Integer ;
Code : Integer ;
begin
Val(S, a, Code) ;
Result := a ;
end;
// 文字列→実数
function DVal(S:String) : Double ;
var
a : Double ;
Code : Integer ;
begin
Val(S, a, Code) ;
Result := a ;
end;
// 整数計算部分
procedure CalcInt(var s:string;a,b:integer;r1,r2,r3,r4:Boolean);
var
c , d : integer ;
i : integer ;
begin
if (r1) then begin
c := a + b ;
s := IntToStr(c) ;
end
else begin
if (r2) then begin
c := a - b ;
s := IntToStr(c) ;
end
else begin
if (r3) then begin
c := a * b ;
s := IntToStr(c) ;
end
else begin
if (r4) then begin
if not(b = 0) then begin
c := a div b ;
d := a mod b ;
s := IntToStr(c) + ' ... ' + IntToStr(d) ;
end
else
s := 'ERROR!';
end
else begin
if (b >= 0) then begin
c := 1 ;
for i:=1 to b do
c := c * a ;
s := IntToStr(c) ;
end
else
s := 'ERROR!';
end ;
end ;
end ;
end
end;
// 実数計算部分
procedure CalcDbl(var s:string;e,f:double;r1,r2,r3,r4:Boolean);
var
g : double ;
b , i : integer ;
begin
if (r1) then begin
g := e + f ;
s := FloatToStr(g) ;
end
else begin
if (r2) then begin
g := e - f ;
s := FloatToStr(g) ;
end
else begin
if (r3) then begin
g := e * f ;
s := FloatToStr(g) ;
end
else begin
if (r4) then begin
if not(f = 0.0) then begin
g := e / f ;
s := FloatToStr(g) ;
end
else
s := 'ERROR!';
end
else begin
b := Round(f) ; // 何乗かは整数とする
if (b >= 0) then begin
g := 1.0 ;
for i:=1 to b do
g := g * e ;
s := FloatToStr(g) ;
end
else
s := 'ERROR!';
end;
end ;
end ;
end ;
end;
// 計算
procedure TForm1.Button1Click(Sender: TObject);
var
a , b : integer ;
e , f : double ;
s : string ;
begin
a := IVal(Edit1.Text) ;
b := IVal(Edit2.Text) ;
e := DVal(Edit1.Text) ;
f := DVal(Edit2.Text) ;
if not(CheckBox1.Checked) then begin
// 整数計算
CalcInt(s,a,b, RadioButton1.Checked, RadioButton2.Checked,
RadioButton3.Checked, RadioButton4.Checked);
end
else begin
// 実数計算
CalcDbl(s,e,f, RadioButton1.Checked, RadioButton2.Checked,
RadioButton3.Checked, RadioButton4.Checked);
end;
Edit3.Text := s ;
end; |
プログラムの量としてはさほど変わりません。しかし、今後、更に改良してプログラムサイズが大きくなるとすると、内部を分離している方が分かり易いという場合は多いでしょうし、他のプログラムを作る時に利用出来る場合もあるかもしれません。イベントハンドラ1つだけだったものが、イベントハンドラ1つに、関数2つ、手続き2つ、と合計5個のブロックになりましたので、見る所も5箇所になった、とはいってもそれが不都合になるという事はさほどありません。利用する変数の数も、各ブロックで必要なものを必要なだけ定義し利用しているので、分かり易くなります。
プログラムを理解するという事は、このイベントハンドラ・関数・手続きは、どういう事をしているのか、この変数はどういう目的で使っているのか、なぜそういう処理を行っているのか、という事に大きく関わってきますので、関数・手続きの利用、そして必要なコメント記述は重要となります。
一度にプログラムを作り込んでしまった後で、関数・手続きを作って分散させようとすると、ややこしい状況に陥る場合もあります。プログラミングの最中にうまく処理を分散させながら、よく利用する関数等は予め作り溜め込んでおく等を行いつつ、効率的にプログラミングして下さい。
|