関数の作り方はわかったけれど実際どう利用するか分からない。あるいは、利用価値がピンと来ないという人もいると思います。
以下のプログラムを例にとって関数を作るときの考え方を身に着けてみようと思います。
関数化するためのサンプル「デジタルフォント」
実行イメージ
キーボードから入力した数値をドット絵フォント風に表示するプログラムです。
xFont0.c
/* xFont0.c : デジタルフォント このプログラムを関数化してみる Created by dianxnao.com on 2018/10/16. */ #include <stdio.h> int main(void) { int i, n; char moji; printf("数値をいれてね: "); scanf("%c", &moji); n = moji - '0'; /* 文字を数値化する */ /* 数値のドット絵を表示する */ printf("\n"); switch(n){ case 0: printf("■■■\n"); printf("■ ■\n"); printf("■ ■\n"); printf("■ ■\n"); printf("■■■\n"); break; case 1: printf(" ■ \n"); printf("■■ \n"); printf(" ■ \n"); printf(" ■ \n"); printf("■■■\n"); break; case 2: printf("■■■\n"); printf(" ■\n"); printf("■■■\n"); printf("■ \n"); printf("■■■\n"); break; case 3: printf("■■■\n"); printf(" ■\n"); printf(" ■■\n"); printf(" ■\n"); printf("■■■\n"); break; case 4: printf("■ ■\n"); printf("■ ■\n"); printf("■■■\n"); printf(" ■\n"); printf(" ■\n"); break; case 5: printf("■■■\n"); printf("■ \n"); printf("■■■\n"); printf(" ■\n"); printf("■■■\n"); break; case 6: printf("■■■\n"); printf("■ \n"); printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); break; case 7: printf("■■■\n"); printf("■ ■\n"); printf(" ■\n"); printf(" ■\n"); printf(" ■\n"); break; case 8: printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); break; case 9: printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); printf(" ■\n"); printf("■■■\n"); break; default: /* 0 ~ 9以外の文字が入力された場合の表示 */ printf(" \n"); printf(" \n"); printf(" ■ \n"); printf(" \n"); printf(" \n"); } return 0; }
解説
switch文で単純に数値によって表示を変えているだけなので、特に説明は不要ですが、以下の部分は説明しておきます。
入力をchar型の文字として行っています。
char moji;
printf(“数値をいれてね: “);
scanf(“%c”, &moji);
この時点では文字のため、「7」という文字の場合、ASCIIコード表から「55」という数値ということになります。
このままでもswitch分岐が出来ないわけではないのですが、直接その数値で分岐させたかったので「0」という文字を引いて
という計算式になおして数値を求めています。
入力値は文字のため
‘7’ – ‘0’
となる。これを数値(アスキーコード番号)に置き換えると
55 – 48
となり結果として「7」という数値を得ることが出来ます。
それではこのプログラムを実際に関数に分割してみましょう。
サンプルを関数分割してみる(例1)
このプログラムをぱっと見た時、switch文が長いなぁと思いました。
switch文の部分だけ関数に分割してみます。
この部分です。
switch(n){ case 0: printf("■■■\n"); printf("■ ■\n"); printf("■ ■\n"); printf("■ ■\n"); printf("■■■\n"); break; case 1: printf(" ■ \n"); printf("■■ \n"); printf(" ■ \n"); printf(" ■ \n"); printf("■■■\n"); break; case 2: printf("■■■\n"); printf(" ■\n"); : [ 中 略 ] default: /* 0 ~ 9以外の文字が入力された場合の表示 */ printf(" \n"); printf(" \n"); printf(" ■ \n"); printf(" \n"); printf(" \n"); }
単純に関数化するなら適当に名前を決めて
void showNumber(void){
/* switch文の内容 */
}
となりますが、switch文には変数nという変数によって分岐させているため変数nの値を関数側に受け渡さなければなりません。
その場合、関数の引数を使います。
変数nはint型で定義されているので、関数の定義は
void showNumber(int x){
/* switch文の内容 */
}
とするのが適切です。
もちろんmain関数で利用しているint nという同じ名前の変数名を利用しても構わないが、その場合main関数のint nと関数showNumber側の引数int nはメモリ上では別の領域となる。
switch部分を関数showNumberに分割してmainから呼び出した状態のプログラムを示します。
xFont1.c
/* xFont1.c : デジタルフォント xFont0.c:main関数のswitch部分を関数に分割した Created by dianxnao.com on 2018/10/16. */ #include <stdio.h> void showNumber(int); int main(void) { int i, n; char moji; printf("数値をいれてね: "); scanf("%c", &moji); n = moji - '0'; /* 文字を数値化する */ showNumber(n); /* 数値のドット絵を表示する */ return 0; } /* 数値のドット絵を表示する関数 */ void showNumber(int n) { printf("\n"); switch(n){ case 0: printf("■■■\n"); printf("■ ■\n"); printf("■ ■\n"); printf("■ ■\n"); printf("■■■\n"); break; case 1: printf(" ■ \n"); printf("■■ \n"); printf(" ■ \n"); printf(" ■ \n"); printf("■■■\n"); break; case 2: printf("■■■\n"); printf(" ■\n"); printf("■■■\n"); printf("■ \n"); printf("■■■\n"); break; case 3: printf("■■■\n"); printf(" ■\n"); printf(" ■■\n"); printf(" ■\n"); printf("■■■\n"); break; case 4: printf("■ ■\n"); printf("■ ■\n"); printf("■■■\n"); printf(" ■\n"); printf(" ■\n"); break; case 5: printf("■■■\n"); printf("■ \n"); printf("■■■\n"); printf(" ■\n"); printf("■■■\n"); break; case 6: printf("■■■\n"); printf("■ \n"); printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); break; case 7: printf("■■■\n"); printf("■ ■\n"); printf(" ■\n"); printf(" ■\n"); printf(" ■\n"); break; case 8: printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); break; case 9: printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); printf(" ■\n"); printf("■■■\n"); break; default: /* 0 ~ 9以外の文字が入力された場合の表示 */ printf(" \n"); printf(" \n"); printf(" ■ \n"); printf(" \n"); printf(" \n"); } }
main関数がかなりすっきりしたことが分かります。
プログラムの見通しが良くなった、という言い方をすることもあります。
関数化する際の考え方としてmain関数はそのプログラムの大枠(入力、表示、出力やメニューによる分岐など)を考慮して作り、main関数からそれぞれの作業を他の関数に任せるといった感じでしょうか。会社や学校の役割分担みたいなものです。
関数mainが見通しが良くなったので、ちょっとプログラムを改良してみます。
プログラムを改良したときの関数のメリットを体験してみる
最初のプログラムxFont0.cは、数値1文字の入力にしか対応していませんでした。
複数の数値入力に対応するようにしてみます。
イメージはこんな感じです。
プログラムから示します。
xFont2.c
/* xFont2.c : デジタルフォント xFont1.cを改良:複数文字入力に対応させた Created by dianxnao.com on 2018/10/16. */ #include <stdio.h> void showNumber(int); int main(void) { int i, n; char moji[256]; printf("数値をいれてね: "); scanf("%s", moji); /* 入力した文字数分ドット絵を表示する */ for(i=0; moji[i] != '\0'; i++){ n = moji[i] - '0'; /* 文字を数値化する */ showNumber(n); /* 数値のドット絵を表示する */ } return 0; } /* 数値のドット絵を表示する関数 */ void showNumber(int n) { printf("\n"); switch(n){ case 0: printf("■■■\n"); printf("■ ■\n"); printf("■ ■\n"); printf("■ ■\n"); printf("■■■\n"); break; case 1: printf(" ■ \n"); printf("■■ \n"); printf(" ■ \n"); printf(" ■ \n"); printf("■■■\n"); break; case 2: printf("■■■\n"); printf(" ■\n"); printf("■■■\n"); printf("■ \n"); printf("■■■\n"); break; case 3: printf("■■■\n"); printf(" ■\n"); printf(" ■■\n"); printf(" ■\n"); printf("■■■\n"); break; case 4: printf("■ ■\n"); printf("■ ■\n"); printf("■■■\n"); printf(" ■\n"); printf(" ■\n"); break; case 5: printf("■■■\n"); printf("■ \n"); printf("■■■\n"); printf(" ■\n"); printf("■■■\n"); break; case 6: printf("■■■\n"); printf("■ \n"); printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); break; case 7: printf("■■■\n"); printf("■ ■\n"); printf(" ■\n"); printf(" ■\n"); printf(" ■\n"); break; case 8: printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); break; case 9: printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); printf(" ■\n"); printf("■■■\n"); break; default: /* 0 ~ 9以外の文字が入力された場合の表示 */ printf(" \n"); printf(" \n"); printf(" ■ \n"); printf(" \n"); printf(" \n"); } }
解説
入力部分に変更があります。入力に使っていた変数mojiを配列にしました。
char moji[256];
printf(“数値をいれてね: “);
scanf(“%s”, moji);
配列の先頭から1つずつデータを取り出して表示するためfor文による繰り返し処理を作りました。
for文の終了条件は、文字列の終わりを表すヌル文字を利用して
moji[i] != ‘\0’
としました。
関数showNumberはプログラムを全く変えていません。
関数はうまく作ると再利用できるということです。
これも関数化のメリットです。
さらに関数分割できないか考えてみる
xFont2.cをさらに関数分割できないか考えてみます。
次に注目したのは、関数showNumberについてです。
printf関数が沢山あるため非常に長くなっています。そこでcase文毎に関数化することを考慮しそれぞれの関数名を考えます。
void font_0(void); /* 0を表示 */ void font_1(void); /* 1を表示 */ void font_2(void); /* 2を表示 */ void font_3(void); /* 3を表示 */ void font_4(void); /* 4を表示 */ void font_5(void); /* 5を表示 */ void font_6(void); /* 6を表示 */ void font_7(void); /* 7を表示 */ void font_8(void); /* 8を表示 */ void font_9(void); /* 9を表示 */ void font_error(void); /* 0~9以外が入力されたときの表示 */
例えば7を表示する関数font_7の定義はこうなります。
/* 7を表示 */ void font_7(void){ printf("■■■\n"); printf("■ ■\n"); printf(" ■\n"); printf(" ■\n"); printf(" ■\n"); }
するとswitch文がすっきりとします。
/* 数値のドット絵を表示する関数 */ void showNumber(int n){ printf("\n"); switch(n){ case 0: font_0(); break; case 1: font_1(); break; case 2: font_2(); break; case 3: font_3(); break; case 4: font_4(); break; case 5: font_5(); break; case 6: font_6(); break; case 7: font_7(); break; case 8: font_8(); break; case 9: font_9(); break; default: /* 0 ~ 9以外の文字が入力された場合の表示 */ font_error(); break; } }
全体を次に示します。
最終的に関数化を突き詰めたプログラム全体
xFont3.c
/* xFont3.c : デジタルフォント xFont2.cを改良:さらに関数に分割してみた Created by dianxnao.com on 2018/10/16. */ #include <stdio.h> void showNumber(int); void font_0(void); void font_1(void); void font_2(void); void font_3(void); void font_4(void); void font_5(void); void font_6(void); void font_7(void); void font_8(void); void font_9(void); void font_error(void); int main(void) { int i, n; char moji[256]; printf("数値をいれてね: "); scanf("%s", moji); /* 入力した文字数分ドット絵を表示する */ for(i=0; moji[i] != '\0'; i++){ n = moji[i] - '0'; /* 文字を数値化する */ showNumber(n); /* 数値のドット絵を表示する */ } return 0; } /* 0を表示 */ void font_0(void){ printf("■■■\n"); printf("■ ■\n"); printf("■ ■\n"); printf("■ ■\n"); printf("■■■\n"); } /* 1を表示 */ void font_1(void){ printf(" ■ \n"); printf("■■ \n"); printf(" ■ \n"); printf(" ■ \n"); printf("■■■\n"); } /* 2を表示 */ void font_2(void){ printf("■■■\n"); printf(" ■\n"); printf("■■■\n"); printf("■ \n"); printf("■■■\n"); } /* 3を表示 */ void font_3(void){ printf("■■■\n"); printf(" ■\n"); printf(" ■■\n"); printf(" ■\n"); printf("■■■\n"); } /* 4を表示 */ void font_4(void){ printf("■ ■\n"); printf("■ ■\n"); printf("■■■\n"); printf(" ■\n"); printf(" ■\n"); } /* 5を表示 */ void font_5(void){ printf("■■■\n"); printf("■ \n"); printf("■■■\n"); printf(" ■\n"); printf("■■■\n"); } /* 6を表示 */ void font_6(void){ printf("■■■\n"); printf("■ \n"); printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); } /* 7を表示 */ void font_7(void){ printf("■■■\n"); printf("■ ■\n"); printf(" ■\n"); printf(" ■\n"); printf(" ■\n"); } /* 8を表示 */ void font_8(void){ printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); } /* 9を表示 */ void font_9(void){ printf("■■■\n"); printf("■ ■\n"); printf("■■■\n"); printf(" ■\n"); printf("■■■\n"); } /* 0~9以外が入力されたときの表示 */ void font_error(void){ printf(" \n"); printf(" \n"); printf(" ■ \n"); printf(" \n"); printf(" \n"); } /* 数値のドット絵を表示する関数 */ void showNumber(int n){ printf("\n"); switch(n){ case 0: font_0(); break; case 1: font_1(); break; case 2: font_2(); break; case 3: font_3(); break; case 4: font_4(); break; case 5: font_5(); break; case 6: font_6(); break; case 7: font_7(); break; case 8: font_8(); break; case 9: font_9(); break; default: /* 0 ~ 9以外の文字が入力された場合の表示 */ font_error(); break; } }
まとめ
関数化のメリット
わたしの知っているSEの方で、ソースコードがディスプレイ一杯になったら関数分割を考える、ということを言った人がいました。まあ、企業でない限り、関数化の基準はプログラマ自身が決めていけばいいと思います。
基本は、自信が見やすいプログラムを作ることだと思います。
コメント