この記事では、c言語をプログラミングするうえで、効率を上げたりコードをわかりやすくまとめたりできる「関数」というものを初心者にもわかりやすく解説していきます。
関数とは?
まずは関数について簡単に説明します。
関数の仕組み
プログラミング言語を学んでいくうちに、複雑なコードを書いていくようになってくると思います。しかし、同じような処理が複数必要なとき、何度も何度もその処理を書くのはとっても面倒ですよね。
そこでC言語では、
一定の処理をまとめて記述する関数(function)
という機能が用意されています!
関数を利用すれば、一連の処理をまとめることができ、いつでも何回でも呼び出して使うことができます。便利!
C言語で関数を使うには、
- 関数の定義(関数を作成すること)
- 関数の呼び出し(関数を利用すること)
この2つの手順を踏むことが必要です。
関数の定義
まず最初に、関数を作成する作業として、コードの中で一定のまとまった処理を指定する必要があります。これを関数の定義と呼びます。
次に示すコードが関数の一般的なスタイルです。
戻り値の型 関数名 (引数リスト) { 処理; ... return 式; }
ここでは大まかなイメージがつかめればOK!
ただし、ブロックの最後に;(セミコロン)は付けてはいけないので注意しましょう!
「戻り値」や「引数」などのなかなか聞かないような用語が使われていますが、これについては後程説明します。
「関数名」は、変数名と同様に、識別子を使って付けた関数の名前です。
まだこれだけではどのような処理ができるのかわかりにくいと思うので、次は関数の例を挙げます。
void ask(void) { printf("返事がない、ただのしかばねのようだ\n"); }
askという関数名をつけて、ブロック中に1文だけ処理を記述していますね。これでaskという関数の定義ができました。
main関数
ここで勘のいい方ならもうお気づきかもしれませんが、実は今まで何気なく使っていた main から始まるブロックは、実は関数だったのです!
ざっくりとしたイメージで考えてみると、プログラム全体も「ひとまとまりの処理」です。したがって、C言語のプログラム全体も1つの関数となっています。この関数の名前が、もうおなじみのmainという関数名の関数です。
関数の呼び出し
関数を定義すると、その関数で定義したまとまった処理を利用することができます。この関数を利用することを関数の呼び出しといいます。
関数の呼び出しは、次に示すコードのように記述します。
関数名(引数リスト);
ここもだいたいのイメージがつかめればOKです。
関数の定義のときとは違い、;(セミコロン)をつけることを忘れずに!
実際呼び出すとき、例えば先ほど宣言したaskという関数を呼び出すには、
ask();
と記述すればいいのです。定義のときとは違って、ブロックを記述する必要はありません。
コードの中で関数の呼び出しが処理されると、前に定義した関数の処理がまとめて行われることになっています。詳しくは次の節を見てみてください。
関数の定義と呼び出しを行ってみる
それでは次のコードを入力して、関数の定義と関数の呼び出し方法を確かめてみましょう!
#include <stdio.h> //ask関数の定義 void ask(void) { printf("返事がない、ただのしかばねのようだ\n"); } //ask関数の呼び出し int main(void) { ask(); return 0; }
上記のコードをコンパイルし、実行してみると、次のような結果が表示されるはずです。
返事がない、ただのしかばねのようだ
このコードは、2つの部分に分かれていることに注意してください。
- main関数の部分
- ask関数の部分
C言語では、main関数から処理が開始されます。そして、ここでも今まで同様にプログラムが実行されると、まずmain関数の先頭部分から処理が始まります。
この後の処理について、次の図を見てください。
①まず最初に、main関数の中でask関数を呼び出します。
②ask関数内の処理を実行します。(今回の場合、printf関数が呼ばれ、文字列を出力します。)
③ask関数が終了すると、呼び出し元(今回の場合、main関数)に処理が戻ってきます。
そうして、main関数は今まで通り次の処理(今回の場合、「return 0;」が実行されます。)を行います。
関数に処理をまとめる
関数で定義できる処理は1つではなく、main関数内で書いているように、いくつも書くことができます。次に例を示します。
void status(void) { printf("村人Aは活動していません。\n"); printf("村人Bは活動していません。\n"); printf("村人Cは眠っています。\n"); printf("村人Dは遠出しています。\n"); printf("村人Eは魔物を倒しています。\n"); }
ここでは新たにstatus関数を定義しており、5つの処理を行うようにしています。
このように複数の処理をひとまとめにすることができます。あとはstatus関数を呼び出せば、5つの文が出力されます。
関数を複数回呼び出す
先ほどの例では一度しか関数を呼び出さなかったため、あまり利点が感じられなかったかもしれませんが、関数は複数回呼び出すことが可能です!
#include <stdio.h> //ask関数の定義 void ask(void) { printf("返事がない、ただのしかばねのようだ\n"); } //ask関数の呼び出し int main(void) { printf("村人Aが横たわっている。話しかけてみよう。\n"); ask(); printf("様子が変だ...あそこに座っている村人Bに聞いてみよう。\n"); ask(); return 0; }
上のコードを見てください。今回は 2回 ask関数を呼び出しています。これを実行してみると次のようになります。
村人Aが横たわっている。話しかけてみよう。 返事がない、ただのしかばねのようだ 様子が変だ...あそこに座っている村人Bに聞いてみよう。 返事がない、ただのしかばねのようだ
このように、関数は複数回呼び出すことができます。
同じ処理を何度も書かなければいけないとき、関数を定義しておけば後は呼び出すだけでその処理が行えるのです!
引数
引数とは、関数に渡す情報のことです。
関数は、呼び出し元から関数内に情報(値など)を渡し、その情報に応じた処理を行うことができます。
引数を受け取る、引数を渡す
引数を使う関数は、次のような形で定義します。
void born(int x) { printf("今月、村では%d人のこどもが生まれました。\n",x); }
このborn関数は、呼び出し元から呼び出されたときに、int型の値を一つ関数内に渡すように定義したものです。
関数の()内にある「int x」が引数と呼ばれるものであり、この引数xはこの関数内でのみ使用できるint型の変数となっています。
変数xは、呼び出し元から渡されるint型の値が格納されます。つまり、引数x=変数xとなっています。そのため、この値を関数内部で処理できるようになっているのです。
ちなみに、関数の()内がvoidのとき(または何もないとき)は、引数なしを定義しています。
ただし、この変数xはborn関数以外の場所では参照することができません。main関数内で変数がaとbしかない状態で、born関数のxの値を直接使用しようとしても、使うことはできないので注意してくださいね。
それでは実際に関数に値を渡すにはどのように呼び出せばいいのでしょうか。次のコードに例を示します。
#include <stdio.h> //born関数の定義 void born(int x) { printf("今月、村では%d人のこどもが生まれました。\n", x); } //born関数に値を渡して呼び出し int main(void) { printf("4月になりました\n"); born(13); printf("5月になりました\n"); born(10); return 0; }
これを実行してみると、次のような結果が表示されます。
4月になりました 今月、村では13人のこどもが生まれました。 5月になりました 今月、村では10人のこどもが生まれました。
関数の呼び出し時に値を渡すには、関数呼び出しの()内に値を記述します。こうすることで、born関数の引数に値を渡すことができるのです!
このmain関数内では2回born関数を呼び出していますが、一回目では13という値を、二回目では10という値を渡しています。そしてborn関数では、渡された値ごとに出力されている値が変わっていますね。
このように同じ関数でも、渡された引数の値によって別の処理が実行できることから、柔軟な関数を作成することが可能です。
仮引数、実引数
ここで、関数本体で定義されている引数を仮引数といいます。(born関数で定義された変数xのこと)
また、関数の呼び出し元に渡される引数を実引数といいます。(born関数を呼び出すときの13や10といった値のこと)
実引数には、値を直接記述するだけでなく、変数の値を渡すことも可能です。
複数の引数を持つ関数
引数は一つしか使えないわけではありません。複数個使って複雑な処理をすることも可能なのです!それでは次の例を見てみましょう。
#include <stdio.h> void born(int x, int y) { printf("この村での%d月の出生数は%d人でした。\n", x, y); } int main(void) { int month = 7; int num; printf("%d月に子供は何人生まれましたか?\n", month); scanf("%d", &num); born(month, num); printf("翌月は何人生まれましたか?\n"); scanf("%d", &num); born(month + 1, num); return 0; }
これの実行結果は次のようになります。(ただし、2行目の9、5行目の11は、実際にキーボードで入力した値。)
7月に子供は何人生まれましたか? 9 この村での7月の出生数は9人でした。 翌月は何人生まれましたか? 11 この村での8月の出生数は11人でした。
まず一度目の呼び出しでは、引数xに変数monthの値である「7」が渡され、引数yにキーボードで入力した値である「9」が入った変数numが渡されています。
また、二度目の呼び出しでは、引数xに変数monthに1を足した値「8」が渡され、引数yにキーボードで入力した値「11」が入った変数numが渡されています。
このように、複数の引数を用いた関数を使用することで、複雑な処理も一気に簡略化できます。さらに実引数に変数の値を渡すことができるため、処理によっては関数呼び出しのときの処理も簡略化が可能です。
この時点でもう関数を使わない手はない!と思うかもしれませんが、実は引数とは逆の機能を持つものがあります!そいつの名前は、戻り値っ...!!
戻り値
戻り値とは、関数の呼び出し元に、関数本体から特定の情報が返される情報のことです。つまり、引数の逆という感じですね。
今まで例を挙げた関数は、いずれも関数の定義時に一番左を「void」としていましたね。ここに型名を書くことで、戻り値を返すことができるようになります。例えば、「int」とすれば、戻り値はint型になります。
ただし注意点として、戻り値は引数とは違い、一つしか値を返すことができません。
return文
いままでmain関数ではよく使ってきたreturn文ですが、このreturnで指定した値が戻り値の値になります。つまり、
int ret(void) { return 3; }
とすれば、ここで定義したret関数の戻り値の型はint型であり、その値は「3」となります。
また、return文に値を何も指定せずに、
return;
とすると、何も値を返さずにその関数の処理が終了します。
関数の処理は、returnに値を指定していてもしていなくても、return文が実行されたらその時点で関数を終了します。
return文が存在しないときは、関数が最後の処理を実行し終えるまで関数は終了しません。そのため最後まで処理をさせたい場合は書かなくても平気ですが、return文を書かない場合には、戻り値の型はvoid型にしておきましょう。
ちなみに、戻り値の型をvoid型で宣言したのに、return文に値を指定した場合は、呼び出し元に値は返されずに関数が終了します。つまり、値を指定しなかったときと一緒です。
戻り値を返す、呼び出し元で受け取る
それでは実際に戻り値を返して、それを呼び出し元に渡してみましょう。以下に例を示します。
#include <stdio.h> int born(int x, int y1, int y2) { int z; printf("出生数は、%d月は%d人、%d月は%d人でした。\n", x, y1, x+1, y2); z = y1 + y2; return z; } int main(void) { int month = 9; int num1, num2, sum; printf("%d月に子供は何人生まれましたか?\n", month); scanf("%d", &num1); printf("翌月は何人生まれましたか?\n"); scanf("%d", &num2); sum = born(month, num1, num2); printf("合計で%d人生まれました。\n", sum); return 0; }
このコードを実行すると、次のようになります。(ただし、2行目の15、4行目の12は、実際にキーボードで入力した値。)
9月に子供は何人生まれましたか? 15 翌月は何人生まれましたか? 12 出生数は、9月は15人、10月は12人でした。 合計で27人生まれました。
まず、変数monthは9であり、9月に子供が生まれた人数として15を、変数num1に代入し、翌月(10月)に子供が生まれた人数として12を、変数num2に代入しています。
そしてこの変数monthとnum1、num2を関数bornに引数として渡し、それらの値を出力した後で、zという変数に引数のy1とy2の和を代入しています。(つまり、15+12=27)
この変数zを戻り値として、main関数内の変数sumに代入し、その値である27を出力している、といった流れになっています。
このように関数を呼び出すときに、変数名 = 関数名(引数); のように呼び出すことで、関数の呼び出し元の変数に、その関数の戻り値を代入することができます。
変数とスコープ、記憶寿命
今まで 変数 というものを何気なく使ってきたと思いますが、実は変数は2種類あります。簡単に言うと、こんな感じです。
- ローカル変数 :関数内で宣言した変数
- グローバル変数:関数外で宣言した変数
これだけ見ても、「何が何だかさっぱりわからん!」「だから何なの?」ってなると思います。
実はこの2つの変数は、挙動はほぼ一緒なのですが、少し使い勝手が変わってきます。そのことについては、次のスコープのところで説明します。
スコープとは
スコープとは、変数の名前が通用する範囲のことを言います。
・・・と言われても、正直パッとしないと思いますので、次のコードを見てください。
#include <stdio.h> //グローバル変数の定義 int a = 5; int function1(void) { //function1関数内のローカル変数の定義 int b; b = 3; printf("aは%dです。bは%dです。",a ,b) } int function2(void) { //function2関数内のローカル変数の定義 int a, b; a = 2; b = 4; printf("aは%dです。bは%dです。",a ,b) } int main(void) { //main関数内のローカル変数の定義 int b; a + 2; b = 1; printf("aは%dです。bは%dです。",a ,b) }
このソースコードの実行結果は、次のようになります。
aは5です。bは3です。 aは2です。bは4です。 aは7です。bは1です。
グローバル変数とローカル変数で色分けをしてみた。このように関数内で宣言した変数はその関数内でしか使うことができません。
まとめ
お疲れ様です。関数はシステム開発においてとても重要なものなのでぜひ使ってみてください!。