この記事の要点
- 関数とは、処理のまとまりに名前を付け、別のところから呼び出せるようにしたもの
- 関数を使うことで、処理の再利用が簡単になり、保守性が向上する
- 同じ関数を繰り返して処理したい場合、再帰呼び出しを使うとシンプルに記述できる
以前、C言語とはどんな言語なのか、また副業について解説していました。
C言語での開発において、関数は必ず必要になります。
関数を使いこなせれば、コード記述量は減り、保守性も向上します。
当記事では、関数の定義方法や呼び出し方について初心者向けにわかりやすく解説します。
また、再帰呼び出しや関数形式マクロについても解説しますので、それらについて知りたい方もぜひ目を通してみてください。
C言語における「関数」
C言語の「関数」とは、処理のまとまりに名前を付け、別のところから呼び出せるようにしたものです。
関数を使うことで、同じ処理を簡単に再利用でき、処理の修正も楽になります。
具体例を用いて解説します。
例えば、100行にも渡る一連の処理、「処理A」があるとしましょう。
この処理Aを使いたいところが他に2か所あったので、それぞれにコピー&ペーストしました。
int main(void){ // 処理A(1) // 何か別の処理 // 処理A(2) // 何か別の処理 // 処理A(3) return 0; }
すると、この時点でソースコードは200行も増えてしまいます。
また、後日処理Aのバグが発覚して修正が必要になったとします。
この場合、処理Aをコピー&ペーストした箇所全てに修正が必要になります。
このように、同じ処理をコピー&ペーストで量産すると、コード量が多くなり、後から修正するのも大変になっていきます。
これは、関数を使うことで改善できます。
int funcA(void){ // 処理A } int main(void){ funcA(); // (1) // 何か別の処理 funcA(); // (2) // 何か別の処理 funcA(); // (3) return 0; }
こうすると、処理Aの記述が一回で済むためコードが短くなる上に、処理Aの修正箇所も1カ所になります。
これが関数を使うメリットです。
関数の定義と呼び出し
具体的な関数の利用方法について解説します。
関数の定義
関数を利用するためには、まず関数を定義する必要があります。
構文は以下の通りです。
戻り値の型 関数名(引数の型1 引数名1, 引数の型2 引数名2, …){ // 処理 }
戻り値や引数については後述するので、まずは形式を覚えましょう。
例えば、足し算の結果を返す関数の定義は、以下のようになります。
int sum(int a, int b){ return a + b; }
関数の呼び出し
関数呼び出しの構文は、以下の通りです。
関数名(引数1, 引数2, …);
引数の数や型は、関数の定義時に指定した通りに設定します。
関数が戻り値を返す場合は、呼び出し元でその戻り値を利用できます。
先ほどのsum関数を呼び出し、コンソールに出力してみましょう。
int sum(int a, int b){ return a + b; } int main(void){ printf("sum関数の戻り値は%dです。", sum(5, 7)); return 0; }
実行結果は、「sum関数の戻り値は12です。」となります。
引数と戻り値
引数とは、関数に渡す値のこと、
戻り値とは、関数から返ってくる値のことです。
引数で受け取った値に応じて処理を行い、処理結果を戻り値として返すことで、関数は役割を果たしています。
引数のルール
- 1つの関数に複数の引数を定義可能
- 引数が不要な関数では、「void」を指定する
- 引数に渡された値は、関数の中で利用できる
戻り値のルール
- 1つの関数に1つのみ定義可能
- 戻り値が不要な関数では、「void」を指定する
- 戻り値で返した値は、関数の呼び出し元で利用できる
- return文に記述した値が戻り値となる
引数、戻り値の定義例
引数なし、戻り値なし
void funcA(void){ }
引数あり、戻り値なし
void funcA(int num){ }
引数なし、戻り値あり
int funcA(void){ return 0; }
引数あり、戻り値あり
int funcA(int a, int b){ return a + b; }
再帰呼び出し
再帰呼び出しとは、関数の中で、その関数自身を呼び出すことです。
少しややこしいので、まず具体例を見てみましょう。
int funcA(int num){ if(num % 2 != 0){ // ① return num; // ② } return funcA(num / 2); // ③ }
この例では、funcA関数内でfuncA関数を呼び出しています。
呼び出されたfuncAはさらにfuncAを呼び、さらにそのfuncAが…とfuncAの呼び出しが続いていきます。
では、この関数の引数に20を渡すと、どのように処理されるか見てみましょう。
- 引数numに20を渡して、funcAを呼び出す。(1回目)
- (1回目のfuncA内)num(=20)を2で割った余りは0なので、if文の中に入らない。…①
- (1回目のfuncA内)引数numにnum / 2 (=10)を渡して、funcAを呼び出す。(2回目)…③
- (2回目のfuncA内)num(=10)を2で割った余りは0なので、if文の中に入らない。…①
- (2回目のfuncA内)引数numにnum / 2 (=5)を渡して、funcAを呼び出す。(3回目)…③
- (3回目のfuncA内)num(=5)を2で割った余りは1なので、if文の中に入る。…①
- (3回目のfuncA内)num(=5)を戻り値として返す。3回目のfuncA終了。…②
- (2回目のfuncA内)3回目のfuncAの実行結果(=5)を戻り値として返す。2回目のfuncA終了。…③
- (1回目のfuncA内)2回目のfuncAの実行結果(=5)を戻り値として返す。1回目のfuncA終了。…③
ということで、funcAの実行結果は5となります。
funcAは、割り切れなくなるまで2で除算するという関数でした。
このように、何度も同じ処理を実行する際、シンプルに記述できるのが再帰呼び出しです。
ただし、コードを読む人が処理をイメージしにくくなる点がデメリットです。
関数形式マクロとは
マクロとは、コード内の文字列をあらかじめ定義した値や処理に置換する機能のことです。
マクロは引数を取れるため、関数のように使用できます。
構文は以下の通りです。
#define マクロ名(引数) 置き換えられる処理
例えば、配列の要素数を取得する処理はよく使うので、関数形式マクロとして定義されることがあります。
#define ARRAY_SIZE(array) sizeof(array) / sizeof(array[0]) int main(void){ int array[10] = {}; printf("%d", ARRAY_SIZE(array)); return 0; }
関数形式マクロには、次のようなメリットがあります。
関数の場合、実行時に呼び出しされるため、わずかながら処理時間がかかります。
一方、関数形式マクロの場合、コンパイル前に置換されるため実行時の呼び出しコストはかかりません。
また、関数形式マクロは型チェックしないため、どんな型にも対応できます。
多用する処理や、シビアな実行速度が求められる処理では、関数形式マクロが有効になります。
C言語標準ライブラリ関数一覧
多くの人に様々なシーンで使われるような処理は、予め標準ライブラリとして提供されており、自作する必要がありません。
例えば、当記事でも多用しているprintf関数などです。
C言語の標準ライブラリは数が多く、網羅的にまとめることは困難です。
しかし、よく使われるものについては、以下のサイトにまとまっているので、ご参照ください。
まとめ
関数の使い方が上手な人が書いたコードは、非常に読みやすく、メンテナンスも簡単です。
これは、特に複数人で開発する案件や、長期間運用するシステムでは重要なことです。
さらに、必要に応じて再帰呼び出しや関数形式マクロを使いこなせるようになれば、C言語初心者は脱したといっても過言ではないでしょう。
関数は非常によく使うテクニックなので、当記事を何度も読み返してしっかりマスターしておきましょう。
また、C言語を学習する中で、if文を先に学ぶことが多く、大抵の場合if文のみで対処できるため、switch文の使いどころがわからない初心者の方も多いのではないでしょうか。
switch文の使い方や、if文との使い分け、また応用テクニックとして、enumやフォールスルーをご紹介していますので、参考にしてください。