C言語において、関数のないプログラムはないと言えます。mainも関数ですから。mainだけでプログラムを完結させることも可能でしょう。
しかし、組み込みマイコンではmain関数だけでは完結できません。それは、組み込みマイコンでは割り込み処理を別途作成することが多いからです。
Windowsアプリケーションでもそうでしょう。マウスクリックやキーボードの入力があっときのイベントハンドラと呼ばれる処理もmain関数では実現できません。
ここでは、関数を上手に作成するためのテクニックについて書いていこうと思います。
関数名は処理の内容がわかるようにつける
関数を作るとき、まず、関数名を考えると思います。ここは、プログラムを読むときに重要になりますので、処理の内容がある程度わかるような名前をつけましょう。
なぜかというと、プログラムを解読していく立場になって、考えてみてもらえれば、わかると思いますが、関数化されている中身のプログラムをいちいち読もうと思いません。
なるべく時間をかけずにプログラムを読もうとします。しかし、関数名がいい加減な名前だと、何をやっているかを中身まで見なくてはなりません。
関数になっている部分は、たいていややこしいプログラムになっており、解読するのに時間がかかってしまいます。また、そういうプログラムを読もうという気が失せるかもしれません。
そういったことも考えて、関数名はきちんとつけるべきです。少々長くなっても構いません。長すぎると思ったら、意味が分かるように、うまく省略しましょう。これは関数名だけでなく、変数名でも同じことが言えます。
いきなり、完ぺきな関数名が浮かばないと思いますし、関数の内容をプログラムしていくうちに、始めにつけた関数名とプログラムの内容が合わなくなるかもしれません。
その場合は、関数内のプログラムが完成してから、それに似合った関数名に付け替えましょう。
いい加減な名前を付けて作った関数と、内容に沿った名前を付けて作った関数を見比べてみてください。
<いい加減な名前を付けて作った関数の例> int func(int a, int b, int c) { int t; if (c == 0) { t = a + b; } else if (c == 1) { t = a - b; } else if (c == 2) { t = a * b; } else if (c == 3) { if (b == 0) { t = 32767; } else { t = a / b; } } else { t = 0; ] return t; } <内容に沿った名前を付けて作った関数の例> int ArithmeticOperations(int val1, int val2, int selection) { int result; if (selection == 0) { result = val1 + val2; } else if (selection == 1) { result = val1 - val2; } else if (selection == 2) { result = val1 * val2; } else if (selection == 3) { if (val2 == 0) { result = 32767; } else { result = val1 / val2; } } else { result = 0; ] return result; }
どうでしょうか?算術演算をどれか1つだけ選択して、結果を返す関数です。前者より後者のほうが、わかりやすいと思いませんか?
思わないのなら、私の名前の付け方のセンスがないだけです。あなたはもう少しよい名前をつけてみてください。。
関数を作ったあとは、コメントで、関数名、引数、戻り値、内容を簡単に記述しておくと、ものすごくわかりやすくなりますので、意識してみてください。こんな感じです。
/****************************************/ /* 関数名 :定義した関数名 */ /* 第1引数:型 仮変数名 内容 */ /* : */ /* :引数があればあるだけ記述 */ /* : */ /* 戻り値 :戻り値の型 内容 */ /* 説明 :関数の内容 */ /****************************************/
先ほどの関数例に付け足してみるとこうなります。
/*******************************************************/ /* 関数名 :ArithmeticOperations */ /* 第1引数:int val1 演算に使用する変数1(作用される側) */ /* 第2引数:int val2 演算に使用する変数2(作用する側) */ /* 第3引数:int selection 演算選択変数 */ /* 0:足算 1:引算 2:掛算 3:割算 */ /* 戻り値 :int result 演算結果 */ /* 説明 :selectionで選択された演算を実施し、結果を */ /* 返す関数 */ /*******************************************************/ int ArithmeticOperations(int val1, int val2, int selection) { int result; if (selection == 0) { result = val1 + val2; } else if (selection == 1) { result = val1 - val2; } else if (selection == 2) { result = val1 * val2; } else if (selection == 3) { if (val2 == 0) { result = 32767; } else { result = val1 / val2; } } else { result = 0; ] return result; }
どうでしょうか?さらにわかりやすくなったと思いませんか?
これがあるのとないのとでは、内容把握に雲泥の差が出ます。ぜひ、活用してください。
関数の引数は4つ以内にする
関数の引数はいくつでも持てますが、4つ以内に収めるようにしましょう。引数を4つ以内にしておかないと、処理速度の低下、プログラムの見にくさが出てしまって、いいことがありません。
せっかく関数を作るのですから、引数の個数は意識しましょう。引数の多い関数は、使いまわしにくくなってしまいますし、そもそも、そんなに引数がたくさん必要なら、作ろうとしている関数にメリットがあるのかを考えてください。引数を多くしないといけないのなら、無理に関数にしようとしていませんか?
どうしても引数がたくさんいるというのであれば、構造体を作って、その構造体のポインタを引数にするというやり方をするとよいでしょう。
あまり意味のない構造体を作るのもよくないですが、関数の引数にするデータであれば、それなりに関係性を持っているものと思われますので、構造体を作っても違和感がないと思います。
やはり、引数が多い関数を見ると、読む気は失せますね。それに、関数を使おうと思っても、何番目の引数が何なんのかを把握するのに、プログラムを行ったきたりしないといけません。
また、関数宣言も横に長くなってしまい、見にくくなってしまいますよね。そういうプログラムをあなたは読みたいですか?
おそらく、引数4つでもきついんじゃないでしょうか?
実際に、引数が5つ以上のプログラムを見てみましょう。
/***************************************************************/ /* 関数名 :SetProtocol */ /* 第1引数:int chanel 設定チャンネル */ /* 第2引数:int data_bit データビット数 */ /* 第3引数:int party_bit パリティビット */ /* 第4引数:int stop_bit ストップビット */ /* 第5引数:long baudrate ボーレート */ /* 戻り値 :なし */ /* 説明 :指定されたチャンネルの通信プロトコルを設定する関数 */ /***************************************************************/ void SetProtocol(int chanel, int data_bit, int parity_bit, int stop_bit, long baudrate) { 設定するチャンネルの範囲チェック data_bitで1byteのデータが8ビットか7ビットかを設定 parity_bitでパリティなし、偶数パリティ、奇数パリティかを設定 stop_bitでストップビットなし、1ストップビット、2ストップビットかを設定 baudrateで通信ボーレートを設定 }
通信プロトコルの関数の中身を日本語で記載したものです。実際に、組み込みマイコンでは通信プロトコルの設定を行いますが、この中身は使用するマイコンによって、処理が異なるので、書けませんでした。
ただ、引数が5つ以上ある関数の例を示したかったので、中身を省略させてもらいました。実際の中身はものすごく長いものになるはずです。
話を戻すと、このように引数が5つもあると、プログラムが読みにくいことはないですか?
プログラムの中身がないので、それほど感じないかもしれないですが、プログラム全体の中で、このような関数が出てくると、解読はかなり辛いです。
これを構造体にすると、関数の引数部分はスッキリします。スッキリするだけでなく、処理速度も向上します。
struct st_protocol_data { int m_chanel; int m_data_bit; int m_party_bit; int m_stop_bit; long m_baudrate; }; /***************************************************************/ /* 関数名 :SetProtocol */ /* 第1引数:struct st_protocol_data* st_protcl */ /* プロトコルデータの構造体ポインタ */ /* 戻り値 :なし */ /* 説明 :指定されたチャンネルの通信プロトコルを設定する関数 */ /***************************************************************/ void SetProtocol(struct st_protocol_data* st_protcl) { 設定するチャンネルの範囲チェック data_bitで1byteのデータが8ビットか7ビットかを設定 parity_bitでパリティなし、偶数パリティ、奇数パリティかを設定 stop_bitでストップビットなし、1ストップビット、2ストップビットかを設定 baudrateで通信ボーレートを設定 }
引数を構造体のポインタにしたので、引数が1つで済んでいます。関数の呼び出し元で、各種データは構造体へ設定しておく必要が発生しますが、いちいち、関数を呼び出すときに変数を書く必要ないですね?
もし、構造体のデータが増えても関数への引数はポインタですので、関数で負荷が増えることはありません。
こうやって引数の数をできる限り減らして、最高でも引数4個までの関数を作るようにしましょう。
関数の戻り値が複数欲しいときはポインタを使う
関数の戻り値は1つです。しかし、関数実行後、2個以上の戻り値が必要なときがあります。そのときは、return文では1つしか返せないので、ポインタを渡して、必要なデータをポインタを介して受け取ります。
例えば、2つの変数の足算と引算の結果が欲しい場合は、このようにします。
/* 足算、引算をするための関数 */ int AddSubCalculation( int x, int y, int* sub ) { int add; add = x + y; *sub = x - y; return add; }
または、こういう風にします。
/* 足算、引算をするための関数 */ void AddSubCalculation( int x, int y, int* add int* sub ) { *add = x + y; *sub = x - y; }
先の例は、足算の結果はreturn文で返し、引算の結果はポインタでもらう形です。後の例は、足算も引算もポインタで結果をもらう形です。
どちらをしても構いません。両方とも演算なので、先の例は気持ち悪いかもしれませんが、何も問題ありません。
返したいものが、3つ4つと増えてくると、引数が増えますよね。そうなると、どうするか、もうおわかりですよね。
構造体を作って、ポインタでやり取りするです。または、返したいものが、同じ型であれば、配列のポインタでもOKです。
さっきの例は、足算と引算だけでしたが、掛算と割算と割算の余りも取得する関数を作ってみますね。
struct st_calc_data { int m_add; int m_sub; int m_mul; int m_div; int m_rem; }; /* 四則演算をするための関数 */ void ArithmeticOperations( int x, int y, struct st_calc_data* result ) { result->m_add = x + y; result->m_sub = x - y; result->m_mul = x * y; if ( y == 0 ) { result->m_div = 32767; result->m_rem = x; } else { result->m_div = x / y; result->m_rem = x % y; } }
構造体を使えば、このようになります。関数の呼び出し元でこの構造体変数を宣言しておく必要はありますよ。
構造体のポインタを引数にしておくと、構造体はメンバの型が異なっても問題ありませんから、幅広く扱うことが可能です。
次に配列を使った場合です。
/* 四則演算をするための関数 */ void ArithmeticOperations( int x, int y, int* result ) { result[0] = x + y; result[1] = x - y; result[2] = x * y; if ( y == 0 ) { result[3] = 32767; result[4] = x; } else { result[3] = x / y; result[4] = x % y; } }
関数の呼び出し元で、int型で要素数5の配列を確保しておく必要があるのは、言うまでもありませんね。
どちらの関数にも言えますが、引数のポインタがNULLポインタだと、不正なアクセスになり、プログラムが落ちてしまうでしょう。そうならないように引数のエラーチェックはしておくようにしましょう。
ポインタだけでなく、他の引数もエラーチェックをするようにしましょう。そうすることで、予想外のことが起きてもプログラムが暴走したり、落ちたりしない強いプログラムになります。
まとめ
C言語の関数を上手に作成するためのテクニックについて、書いてみました。組み込みマイコンを扱っているので、どうしてもそちら目線でのことになりましたが、処理速度やプログラムの読みやすさを意識することは、組み込みプログラムでなくても必要だと思います。
あなたも、普段から、関数名のつけ方、コメント文を意識して書く、処理速度のことも考えて引数の数を考える。ということをしていってください。
そうすれば、誰が見ても、読みやすい、理解しやすい関数が作れるようになります。