見出し画像

C言語教室 第1回 - 関数呼び出し

いきなりですが、2つの整数の平均値を計算してみましょう。

#include <stdio.h>

void main() {
  int a;
  int b;
  int v;

  a = 2;
  b = 9;
  v = (a + b) / 2;
  printf("Ave(%d,%d)=%d\n", a, b, v);
}

最初の#include行はprintfを使うときには、おまじないとして必ず書く必要がる行です。実行すると以下のような出力が得られるはずです。

Ave(2,9)=5

平均値を求めるのに整数型同士の割り算なので、整数除算になります。整数除算がどう働くかは、代入する値から覚えてください(仕様としては切り捨てです)。

さて、他の数の平均値も求めてみましょう。

#include <stdio.h>

void main() {
  int a;
  int b;
  int c;
  int d;
  int v1, v2;

  a = 2;
  b = 9;
  v1 = (a + b) / 2;
  printf("Ave(%d,%d)=%d\n", a, b, v1);

  c = 19;
  d = 25;
 v2 = (c + d) / 2;
  printf("Ave(%d,%d)=%d\n", c, d, v2);
}

これを実行すると、結果が2つ出力されます。

Ave(2,9)=5
Ave(19,25)=22

同じことを2度やるのに、同じようなコードを2回書くのは面倒ですね。コピペすればいいので、別に構わないとか言わないでください。100回やるのに100個コード書くのは駄目です。

どうして駄目かというと、何回やっているかを知るのにコードを数えるなんていうのは駄目です。そして、もし修正が必要な時に、その数だけ直さないとならないからです。そして「すべて」がキチンと直っていることを確認するのは大変です。同じことを何回もするときこそ、コードは1個だけにして、何回やっているかわかるようにするのが基本です。


こういう時は、関数というものを作って、これを呼び出すように書きます。

#include <stdio.h>

void ave() {
  int v;
  v = (a + b) / 2;
  printf("Ave(%d,%d)=%d\n", a, b, v);
}

void main() {
  int a;
  int b;
  
  a = 2;
  b = 9;
  ave();
}

これをコンパイルすると

v = (a + b) / 2;
     ^
main.c:5:8 キーワード: 'a' が未定義です。スペルミスがないか再確認してください。

あれぇ、aveの中で変数aが宣言されていないというエラーがでますよね。具体的なメッセージはコンパイラによって違います。コンパイラによっては警告だけで実行できてしまうことも実はありますけど、普通はエラーになるはずです。
aveの中で変数宣言しないといけないのなら、宣言を追加しましょう。

#include <stdio.h>

void ave() {
  int a;
  int b;
  int v;
  v = (a + b) / 2;
  printf("Ave(%d,%d)=%d\n", a, b, v);
}

void main() {
  int a;
  int b;
  
  a = 2;
  b = 9;
  ave();
}

おや、エラーはなくなったけど、値がおかしい(0ではなくて、他の値が出ることもあります)。

Ave(0,0)=0

mainで設定したaやbはどうなったんだろう。順番がいけないのかな。関数を書く順序を入れ替えてみましょう。

#include <stdio.h>

void main() {
  int a;
  int b;
  
  a = 2;
  b = 9;
  ave();
}

void ave() {
  int a;
  int b;
  int v;
  v = (a + b) / 2;
  printf("Ave(%d,%d)=%d\n", a, b, v);
}

もしかしたら、入れ替えたことによって、mainでaveが未定義だと怒られるかもしれません。その場合は、mainの前に

extern void ave();

というおまじないの行をいれます(これを入れなくても怒られないこともあります)。

Ave(0,0)=0

やはり0以外の結果になるかもしれませんが、結果がおかしいことには違いません。

関数の中で宣言した変数は、その関数の中でしか有効ではない。

お約束1

つまりmainの中で、aveでも使う変数を宣言してはいけないのです。そこで両方の関数で使う変数は関数の外で宣言をします。

#include <stdio.h>

int a;
int b;

void ave() {
  int v;
  v = (a + b) / 2;
  printf("Ave(%d,%d)=%d\n", a, b, v);
}

void main() {
  a = 2;
  b = 9;
  ave();
}

これで最初に考えた通りの結果がでるはずです。

Ave(2,9)=5

関数の中で宣言した変数をローカル変数。外で宣言した関数をグローバル変数と呼びます。

お約束2

関数の間で共通で使う変数をグローバル変数として、やり取りすることは出来るのですが、aveを使うときにはaとbというグローバル変数を使うのだよというのを覚えておかないといけません。関数の中を見ても宣言していない変数がいきなり出てくるだけです。ここで、ちょっと変なことをしてaveの中でaを宣言してみましょう。

#include <stdio.h>

int a;
int b;

void ave() {
  int v;
  int a;
  v = (a + b) / 2;
  printf("Ave(%d,%d)=%d\n", a, b, v);
}

void main() {
  a = 2;
  b = 9;
  ave();
}

また、先程のように値がおかしなことになります。

Ave(0,9)=4

この場合も0のところが他の値になることもあります。その結果、答えも変わってきます。いずれにせよmainでaに入れた値はどこかにいってしまっています。

グローバル変数とローカル変数の名前が同じ時はローカル変数が優先される。

お約束3

うっかりグローバル変数と同じ名前を使うだけでバグってしまうわけです。プログラムが小さいうちは気をつければ何とかなりますが、大きくなるとグローバル変数をきちんと把握していないと、この手のバグが頻発します。


さてC言語の関数は呼び出す際に「引き数」と呼ばれる値を渡すことが出来ます。関数を呼び出す時に関数名の後ろに、関数の中で使いたい変数を書くことで、呼び出し元の変数を呼び出した関数で使うことが出来ます。

#include <stdio.h>

void ave(int a, int b) {
  int v;
  v = (a + b) / 2;
  printf("Ave(%d,%d)=%d\n", a, b, v);
}

void main() {
  int a;
  int b;
  
  a = 2;
  b = 9;
  ave(a, b);
}

ちゃんと答えは出ていますね。

Ave(2,9)=5

aveを書く時に、受け取る変数の型と、この関数の中で使う変数名を書きます。呼び出す方では、aveの後ろに、この型の変数を必要な数だけ書くわけです。ちなみに両方の関数で同じ名前を使う必要はありません。

#include <stdio.h>

void ave(int x, int y) {
  int v;
  v = (x + y) / 2;
  printf("Ave(%d,%d)=%d\n", x, y, v);
}

void main() {
  int a;
  int b;
  
  a = 2;
  b = 9;
  ave(a, b);
}

同じ結果がちゃんと出ます。

Ave(2,9)=5

mainのaがaveのxになり、bがyになるわけです。

どの変数がどの変数になるかは、呼び出す時に書く順番で決まります。

お約束4

これでグローバル変数を使わなくても良くなりました。


さて平均値を出力するのをaveの方で行っていますが、これをmainでやるようにしたいです。簡単なのは計算結果であるvをグローバル変数にすれば、printfをmainに持ってきても大丈夫です。でも、またグローバル変数を使うことになってしまいます。関数は「戻り値」という値をひとつ持つことができます。計算結果を戻り値として返すことができるようにしてみましょう。

#include <stdio.h>

int ave(int x, int y) {
  int v;
  v = (x + y) / 2;
  return v;
}

void main() {
  int a;
  int b;
  int v;
  
  a = 2;
  b = 9;
  v = ave(a, b);
  printf("Ave(%d,%d)=%d\n", a, b, v);
}

答えは大丈夫ですね?

Ave(2,9)=5

今までaveの前に書いていたvoidは「戻り値が無いよ」ということを書いていたのです。ここには戻り値の「型」を書きます。これで関数の戻り値を変数に入れることができます。

関数名の前に戻り値の型を書く。戻り値は return 文で設定する(reutrn は関数ではない)。このときの変数の型が関数名の前に書いた型と同じでないといけない。

お約束5

どうですか。これで繰り返し使うような決まった処理を関数という形で書くことが出来るようになりました。

補足

コードをコピペしたり、打ち込んでいると、空白に「見える」部分が全角の空白など、実はいわゆる空白でないことがあります。こういうところがわかりにくいエラーとなるので、気をつけてください。プログラミング用のエディタなどでは全角空白をわかりやすく表示してくれる機能があることがあります。


課題

  • 2つの数の平均ではなくて、合計を求めるsumという関数を書いてみる。

  • 2つの整数の平均を整数で返す関数を、2つの整数の平均を小数で返す関数にしてみる。型指定とprintfの書式指定を調べること。

最初はこんな感じで、いかがでしょうか。引き数は、この後、配列を渡してみて参照渡しを理解することですかね。

なお、グローバル変数に関しては、ファイルが分かれたときのあれこれは、まだ触れないでおきます。その時はstaticもやらないといけないので。ではでは次回をお楽しみに。


この記事が気に入ったらサポートをしてみませんか?