入門書でのおさらい24・関数、演習6-1,6-2,6-3,6-4,6-5

今日は142ページの関数を勉強します

142ページ・関数

関数定義に関しての疑問を聞く

関数の引数の中で宣言した変数は関数の中ではもう宣言しなくていいんですか?

はい、その通りです。

引数(仮引数)として宣言した変数は、その関数の「中身(ブロック)」ですでに宣言されている変数と同じ扱いになります。そのため、関数内で改めて同じ名前の変数を宣言する必要はありません(二重に宣言するとエラーになります)。

関数の実行が始まるときに、呼び出し元から渡された値がその引数に自動的にセットされた状態で、関数の処理がスタートします。

なぜ引数でなく仮なんですか

「入れ物」としての役割を強調しているからです。

プログラムのソースコードに書かれている関数側の引数は、実際の中身(具体的な数値など)ではなく、「後で値が入るための場所(仮の変数)」にすぎません。

これに対して、関数を呼び出すときに実際に渡す具体的な値のことを「実引数(じつひきすう)」と呼びます。

  • 仮引数: 値を受け取るための「予約席」や「ラベル」のようなもの。
  • 実引数: その席に実際に座る「お客さん(具体的なデータ)」のこと。

「仮」という言葉を使うことで、呼び出し側から値をもらうまでは中身が確定していない、という性質を表しています。

仮引数で宣言した変数の文字はメイン関数で使えるのですか?

いいえ、使えません。

仮引数として宣言された変数は、その関数の中(ブロック内)だけで有効な変数です。これを「ローカルスコープ」と呼びます。 [1, 2]

したがって、ある関数で使っている仮引数の名前を main 関数の中で書いても、main 関数からはその変数を見ることはできず、コンパイルエラーになります。

逆に、main 関数の中で宣言した変数を他の関数から直接見ることもできません。だからこそ、「引数」を使って値を渡す必要があるのです。

そういう意味ではなく

#include<stdio.h>
int max2(int a, int b)
{
    if(a > b)
        return a;
    else
        return b;   
}
int main(void)
{
    int a, b;

    puts("2つの整数を入力せよ");
    printf("整数1:"); scanf("%d",&a);
    printf("整数2:"); scanf("%d",&b);

    printf("大きいほうの値は%dです。\n", max2(a, b));

    return 0;
}

このように仮引数でint a,bを宣言してますが、メイン関数で同じアルファベットの変数を使えるのかと聞いています

はい、それは可能です。

コードの中で、max2 関数の仮引数名(a, b)と、main 関数内の変数名(a, b)に同じ文字を使っていますが、これらは名前が同じだけの全く別の変数として扱われます。

  • max2a は、max2 という部屋の中だけで有効な変数。
  • maina は、main という部屋の中だけで有効な変数。

このように、それぞれの関数で独立して変数名を決めることができるので、その書き方はC言語として全く問題ありません。

始まりカッコの問題ですが関数の場合始まりカッコは改行するんですか?下図参照。forやdoがfor{、do{、とすぐあとから始まるためずっとメイン関数の始まりカッコも前者のように書いてましたが今日改めてみたら解説書はずっと後者でした

/////////////始まりカッコをかいぎょうしない///////////////
int main(void){
}


/////////////始まりカッコをかいぎょうする///////////////
int main(void)
{
}

結論から言うと、どちらでも正解です。プログラムの動作に違いはありません。

C言語には「どこで改行すべき」という厳格なルールがないため、書き手の好みやプロジェクトの方針(コーディング規約)によって分かれます。

  • ){ (同じ行に書く):JavaやJavaScriptなどで主流のスタイルです。行数が節約できます。
  • ) の次の行に {:『新・明解C言語』の著者(柴田望洋先生)が推奨しているスタイルです。関数の区切りが視覚的にハッキリするというメリットがあります。

学習中はテキストの書き方を真似しておくと、コードの構造を把握しやすくなるのでおすすめです。

ちなみに関数の定義?宣言?はメイン関数の外でするのが決まりなんですか?

C言語では、関数の「定義(中身を書くこと)」は他の関数の外側で行うのが決まりです。関数のなかに別の関数の定義を書く(入れ子にする)ことはできません。

ただし、「宣言(「こういう関数があるよ」という紹介)」だけであれば、メイン関数の中でも可能です。

一般的には、あなたの書いたコードのように、main の上に定義をまるごと書いてしまうか、上に宣言だけ書いて下に定義を書くスタイルが一般的です

「宣言」を main の中で行うパターン
#include <stdio.h>

int main(void)
{
    // 関数の「宣言」を main の中で行う
    int min2(int a, int b); 

    int x = 10, y = 20;
    printf("小さい方は %d\n", min2(x, y));

    return 0;
}

// 関数の「定義(中身)」は外に書く
int min2(int a, int b)
{
    return (a < b) ? a : b;
}

このように、「中身(定義)」は外に、 「紹介状(宣言)」は中へ、 という書き方もできます。ただ、実務では宣言も外(main の上など)にまとめることが多いです。

解説書は関数は2個以上の値を返却できないとありますが、つまり1個か0個ということですか?

その通りです。

C言語の関数が return 文で呼び出し元に返せる値は、「1個」または「なし(0個)」のどちらかだけです。

  • 1個返す場合:戻り値の型(intdouble など)を指定します。
  • 0個の場合:戻り値の型に void を指定します。

もし2つ以上の値を返したい場合は、後ほど学習する「配列」や「構造体」、「ポインタ」といった技術を使う必要がありますが、基本の return 文で返せるのは常に1つだけと覚えておいて間違いありません。

演習6-1

これは簡単です。returnが一つになるように書くのがいいらしいのでそれを意識して関数を作ります。

#include<stdio.h>
int min2(int a, int b)
{
    int min = a;
    if(b < min) min = b;
    return min;
}
int main(void)
{
    int n1, n2;

    puts("2つの整数を入力せよ");
    printf("整数1:"); scanf("%d",&n1);
    printf("整数2:"); scanf("%d",&n2);

    printf("小さいほうの値は%dです。\n", min2(n1, n2));

    return 0;
}

演習6-1、一発合格です!
int min = a; で一旦初期化してから比較するスタイル、コードがスッキリしていて読みやすいですね。main関数での呼び出しも完璧です。

「return文は一箇所にまとめる」という設計思想(単一出口の原則)をしっかり意識されているのが素晴らしいです。

将来的にプログラムが複雑になったとき、出口が一つだとデバッグがしやすくなるという大きなメリットがあります。

演習6-2

#include<stdio.h>
int min3(int a, int b,int c)
{
    int min = a;
    if(b < min) min = b;
    if(c < min) min = c;
    return min;
}
int main(void)
{
    int n1, n2, n3;

    puts("3つの整数を入力せよ");
    printf("整数1:"); scanf("%d",&n1);
    printf("整数2:"); scanf("%d",&n2);
    printf("整数3:"); scanf("%d",&n3);

    printf("一番小さい値は%dです。\n", min3(n1, n2, n3));

    return 0;
}

演習6-2、お疲れ様です。

3つの値の比較も、min という変数を使って一つずつ判定していくことで、return を一箇所にまとめた綺麗な構造になっていますね。

動作も問題ありません。

演習6-3

#include<stdio.h>
int cube(int x)
{
    return x * x * x;
}

int main(void)
{
    int x;

    puts("整数を入力せよ");
    printf("整数:"); scanf("%d",&x);

    printf("xの3乗は%dです。\n", cube(x));

    return 0;
}

演習6-3、お疲れ様です。

x * x * x をそのまま返す形ですね。非常にシンプルで分かりやすいです。
こちらも動作に問題ありません。

演習6-4

#include<stdio.h>
int sqr(int n)
{
    return n * n;
}
int pow4(int x)
{
    return sqr(x) * sqr(x);
}

int main(void)
{
    int n1;

    puts("整数を入力せよ");
    printf("整数:"); scanf("%d",&n1);

    printf("nの4乗は%dです。\n", pow4(n1));

    return 0;
}

演習6-4、お疲れ様です。

関数の中で別の関数(sqr)を呼び出して利用する形、しっかり書けていますね。pow4 の中で sqr(x) * sqr(x) とすることで、計算がスッキリまとまっています。

動作も問題ありません。

このプログラム組んだ時うっかり関数名をpow4でなくpowにしちゃったんだけどエラーが出ました。使っちゃいけない名前でしたか?

はい、その通りです。pow という名前はC言語の標準ライブラリ(数学計算用の math.h など)ですでに予約されている関数の名前と重なるため、エラーや衝突の原因になります。

C言語には「予約語(intifなど)」の他に、標準ライブラリで使われている有名な関数名がいくつかあり、それらと同じ名前を自分で付けるのは避けるのが一般的です。

なるほど、気を付けます。

演習6-5

#include<stdio.h>
int  sumup(int n)
{
    int sum = 0;
    for(int i = 1; i <= n; i++){
        sum += i;
    }
    return sum;
}

int main(void)
{
    int n;
    puts("1から入力された数までの全整数の和を求めます");
    do{
        printf("整数を入力:"); scanf("%d",&n);
        if(n < 1) puts("1以上の整数を入力してください");
    }while(n < 1);

    printf("1から%dまでの全整数の和は%dです。\n", n, sumup(n));

    return 0;
}

演習6-5、お疲れ様です!

main関数側で、do文を使って「1未満が入力されたら再入力させる」というエラーチェックの処理まで組み込んでいますね。プログラムの堅牢性が上がっていて良いと思います。

関数のループ処理も、return sum; まで過不足なく書けており、動作も完璧です。

今日のところまではまあまあすいすい解けたと思います。また明日。

お疲れ様でした!
確かに、関数の定義から呼び出し、さらには標準ライブラリとの名前の衝突への気づきなど、非常にスムーズで中身の濃い学習内容だったと思います。

「returnを一つにまとめる」や「入力チェックを入れる」といった丁寧なコードの書き方が印象的でした。