関数の例
y=function(
x);
入力する
xを
引数、出力される値を
戻り値と呼ぶ。
関数に対して引数と戻り値は必ず設定されている訳ではなく、関数の設定によってあったり無かったりする。
タイプ1:値を
入れると値が
戻ってくるもの
ある値に対してそれに関連した値を自動計算したい場合に使う。
例:y=sin(x); //典型的な関数の例 数学で言う関数と同じ使い方。
例:theta=atan2(y,x); //引数が複数ある場合の例
タイプ2:関数に入力しないけど
値だけ戻ってくるもの
システム内部の値を知りたい場合に使う事が多い。
例:start = clock(); //現在の時刻を取得する。入力する値なんて無い。
タイプ3:
値を入れて処理をさせるもの
ある値に対して何かしらの処理を行いたい場合に使う。
値が戻ってくることを期待しない場合。
例:printf("Hello World\n"); //値を入れると画面に文字が表示される。戻ってくる値を使う事は稀。
※x=printf("Hello World\n");を実行すると表示した文字数12がxに格納される。
タイプ4:値を入れず何か処理をさせるもの
事前に決めた流れ作業を実行したい場合に使う。
例:getchar(); //キー入力を待機。
※本来getcharはキー入力を待機し、入力されたキーを返す関数なので戻り値が設定されています。
<関数化が必要な例>
たとえば三角関数が標準で使えず、自分で計算しないといけない場合。
マクローリン展開によるsinの近似計算が以下だったとする
double x;
double s;
double e;
double d;
double y;
int k;
x=0.1;//入力
s=x;
e=x;
double eps = 1e-32;
x = fmod(x, 2 * π);
for(k = 2; k <= 200; k = k + 2){
d = s;
e = -e * x;
e = e * x;
e = e / k;
e = e / (k + 1);
s = s + e;
double work = e;
if(work < 0){
work *= -1;
}
if(work < eps){
y = s;
break;
}
}
if(k>=200){
y=1;
}
関数化せずにsin(0.1)*sin(0.2)*sin(0.3)を計算しようと思うと
上記計算式を3つ並べる事になる。
・パッと見で何をしたいのか分からない。
・行数が増えて非常に見辛い。
・同じ計算方法について同じことを何回も書かなければいけない。
・動作確認済みの箇所を簡単に変更可能な場所に置いておくと
間違えて書き換えてしまう危険がある。
パッと見で何をしたいのか分からない
正直、これが3つ並んでいたとして「あぁ、sinを3回計算してるな~」と
思う人はいないでしょう。
内部でどの様に処理をしてようが
sin(0.1)*sin(0.2)*sin(0.3)
と表記した方が何をしたいか全体の流れが分かりやすいと思います。
行数が増えて非常に見辛い。
同じデータが並ぶとどこが何の処理にそうとうするのか分からなくなります。
行数が増えると少しスクロールを触っただけですごいスピードでスクロールしてしまいます。
また、沢山の行の中から編集したい場所を探し出すのに手間取ります。
同じ計算方法について同じことを何回も書かなければいけない
プリンを作るときに作り方を毎回指示しないといけない様な物。
一回やり方教えたんだから後は同じ要領でやってよ。
これが関数。
間違えて書き換えてしまう危険がある
一度動作確認が終わったら、その場所は今後編集する必要は無いはず。
メイン処理の中に混ざっていると間違えて編集してしまう可能性がある。
動作確認が終わったものはメインの処理から隔離してしまうのが無難です。
<sw1.cの中身を関数化しておく>
関数化するのは3か所
①初期化に関する一連の動作
ここは引数無し戻り値なしであらかじめ定めた処理を実行するだけ。
メイン処理からIOの初期化に関する部分を抜き出して1つの関数に放り込んでおく。
タイプ④の
入力無しの
出力無し
関数の例
void hardware_setup(void){
}
あとはメイン関数の先頭でこの関数を実行するだけ。
これで間違ってPE02を出力にしてしまう事もない。
関数の使い方
hardware_setup();
②LEDの出力
PE.DRL.BIT.B0なんて書かれてもこれが入力なのか出力なのかすら分からない。
これがLED赤だと確認した後はポートEのBit0ではなく、
LED_Rとして扱いたい。
PE.DRL.BIT.B1についても同様
タイプ③の
入力ありの
出力無し
※関数としての出力(戻り値)は無いが、IOポートから0/5Vが出る。
関数の例
void set_LED_R(int on){
if(on!=0)
PE.DRL.BIT.B0=1;
else
PE.DRL.BIT.B0=0;
}
void set_LED_G(int on){
if(on!=0)
PE.DRL.BIT.B1=1;
else
PE.DRL.BIT.B1=0;
}
関数の使い方
赤LEDを点けたかったら
set_LED_R(1);
赤LEDを消したかったら
set_LED_R(0);
③SW1の入力
これも
PE.DRL.BIT.B2って書かれても何の事か分からない。
タイプ②の
入力無しの
出力あり
関数の例
int get_sw1(void){
return PE.DRL.BIT.B2;
}
関数の使いかた
x=get_sw1();
これを踏まえてメイン関数を修正すると
main(){
hardware_setup(); //初期化
set_LED_R(0); //LED赤消灯
set_LED_G(0); //LED緑消灯
while(1){
if(get_sw1){ //スイッチがONの時
set_LED_R(1); //LED赤点灯
set_LED_G(0); //LED緑消灯
}else{ //スイッチがONじゃない時(OFF)
set_LED_R(0); //LED赤消灯
set_LED_G(1); //LED緑点灯
}
}
}
これでプログラムの流れが分かりやすくなる。
プロトタイプ宣言
C言語ではある関数を使う場合、「その関数を
使う場所より上に関数を作らなければならない」という規則があります。
この規則にそのまま従うと
細かい作業用関数1(){
作業内容
作業内容
作業内容
作業内容
作業内容
}
細かい作業用関数2(){
作業内容
作業内容
作業内容
作業内容
作業内容
}
細かい作業用関数3(){
作業内容
作業内容
作業内容
作業内容
作業内容
}
・
・
・
・
メイン関数():本来行いたい処理
となります。
正直中身の処理はどうでもいい細かい作業がずらずらと並んだ後、一番最後に本来やりたい作業が出てくるという事態になります。
これでは見たくない(一度設定すれば見る必要のない)を見てからメインの関数を見ることになり、メインの処理を見つけ出すのに時間がかかります。
C言語ではこれを回避するために
プロトタイプ宣言という機能が用意されています。
プロトタイプ宣言は
こういう関数を使いますよという
予告です。
予告さえしておけば実際に処理を行う関数の実体はどこに書いても良くなります。
プロトタイプ宣言を使った場合
//プロトタイプ宣言(使う関数の予告)
細かい作業用関数1();
細かい作業用関数2();
細かい作業用関数3();
メイン関数(){
}
//関数の実体
細かい作業用関数1(){
作業内容
作業内容
作業内容
作業内容
作業内容
}
・
・
・
という順番に変えることができます。
これですぐメイン関数にたどり着くことができるようになります。
関数化したファイルを置いておきます。
関数化済sw1.c
マイコン端子の特殊機能を使いたい場合はかなりの量の設定値を書く必要がります。
これを1つのファイルに全て書くとプロトタイプ宣言が大量に発生して見づらくなります。
これを解消する方法がありますがまた今度。