Web Analytics Made Easy - StatCounter

工業大学生ももやまのうさぎ塾

うさぎでもわかるをモットーに大学レベルの数学・情報科目をわかりやすく解説! 数式が読み込まれない場合は1回再読み込みしてみてください。

うさぎでもわかるC言語における関数の作り方

こんにちは、ももやまです。

今回は、C言語で「関数を作る方法」と「作った関数を使う方法」の2つについて説明していきたいと思います。

1.関数とは

関数を簡単に言うと、魔法の箱です。

もう少し具体的に言うと、何個かの値を箱に入れると、箱の中で何かしらの処理(魔法)がされ、1つの出力が出てくる箱です。

f:id:momoyama1192:20200505170829j:plain

関数(魔法)の具体例を1つ見てみましょう。

入力した2つの値を掛け、掛けた結果を返す関数があるとします。

この関数に、6と8という2つの値を入力します。

f:id:momoyama1192:20200505170824j:plain

すると、箱の中で処理が行われ、48という値が出力されます。


というわけで、皆さんにはこれから魔法の箱でかかる魔法、つまり関数の作り方について説明していきたいと思います。

2.どうして関数を作るの?

とは言っても、「本当に関数を作る必要なんてあるの?」と思う人も多いと思います。

というわけで、ここでは「なぜ関数が必要なのか」について説明していきたいと思います。


例えば、こんなプログラムがあったとします。

(英語と数学の平均点を出力するプログラム)

#include<stdio.h>
#define N 10

int main() {
    int data_eng[N] = {32,54,77,82,14,65,99,46,70,57,25}; // 英語
    int data_math[N] = {49,100,96,55,68,26,9,32,78,70};   // 数学

    double ave_eng, ave_math;
    int sum_eng = 0, sum_math = 0; // 0の初期化忘れずに
    int count_eng = 0, count_math = 0; // 0の初期化忘れずに

    // 各生徒の英語の点数の合計・人数を求める
    for(int i = 0; i < N; i += 1) {
        sum_eng += data_eng[i]; // 合計点
        count_eng += 1; // 人数
    }
    ave_eng = sum_eng * 1.0 / count_eng;

    // 各生徒の数学の点数の合計・人数を求める
    for(int i = 0; i < N; i += 1) {
        sum_math += data_math[i]; // 合計点
        count_math += 1; // 人数
    }
    ave_math = sum_math * 1.0 / count_math;

    printf("English Ave:%4.2f\n",ave_eng);
    printf("Math    Ave:%4.2f\n",ave_math);

    return 0;
}

このプログラムで、「英語の平均点」と「数学の平均点」を計算する際に同じ処理内容がmainの部分に書かれていますね。

// 英語の平均点計算部分
    for(int i = 0; i < 10; i += 1) {
        sum_eng += data_eng[i]; // 合計点
        count_eng += 1; // 人数
    }
    ave_eng = sum_eng * 1.0 / count_eng;

// 数学の平均点計算部分
    for(int i = 0; i < 10; i += 1) {
        sum_math += data_math[i]; // 合計点
        count_math += 1; // 人数
    }
    ave_math = sum_math * 1.0 / count_math;

今回は、両方とも5行の処理内容なのでそこまで関数にする必要はないかもしれませんが、

  • 何回も同じ処理をする
  • プログラムが長くなる

と、main部分にいちいち同じ処理を書くと、とてつもない長さのプログラムになってしまいます。

また、関数にすることで、

  • なんの操作が行われているか分かりやすくなる
  • 自分以外の人にとってもプログラムが見やすくなる
  • 後で見直し、デバッグするときに楽になる

などのメリットがあります。

そのため、関数の作成はプログラミングにおいて重要なのです!

3.関数を作ってみよう

ということで、実際に関数を作ってみましょう!

その1.2つの値を入力し、その積を返す関数

では、最初に出てきた「入力した2つの値(整数)を掛け、掛けた結果を返す関数」を作ってみましょう。

f:id:momoyama1192:20200505170824j:plain

まずは、関数の外(中身以外)を作ります。具体的には、

  • どんな変数の型が何個入力されるか
  • どんな変数の型を出力するか
  • 関数の名前

を決めます。

今回は、整数型の2変数を入力し、整数型*1を出力すればいいので、関数の外は下のようになります。

f:id:momoyama1192:20200505174830g:plain

次に、関数の中身(処理内容)を作ります。

出力には return を使います。例えば、return 10; とすれば、10が出力されます。

なお、出力される値のことを返り値と呼ぶ人も多いです。


今回は、2つの入力 a, b の積を返せばいいので、

int mult2(int a, int b) {
    return a * b; // return x; とすると x が出力される
}

とすれば積が返されます!

もちろん、

int mult2(int a, int b) {
    int ans = a * b
    return ans;
}

のように1クッションおいてもOKです!

(余談)入力・出力変数がない場合

入力、出力変数はなくてもOKです。ない場合は、void と書きます。例えば、

// void print1() でもOK
void print1(void) {
    printf("1\n");
}

とすれば、入力、出力する変数が一切ない(1を表示する)関数の出来上がりです。

ちなみに、皆さんがいつも書いている

int main(void) {
    // 処理内容
    return 0; // 0が返されたらプログラムが正常終了(だからmain関数の出力は int )
}

void は、まさに入力引数なしを表している void です。

その2.入力された変数が3の倍数か判定

つぎに、入力された整数が3の倍数であれば1を、それ以外であれば0を返す関数を作りましょう。


今回は、入力されるのは整数型1変数(3の倍数かどうか判定する変数)、出力されるのは0, 1だけなので整数型ですね。

なので、関数の外は

int div3(int a) {

}

となりますね。(名前はいい感じに決めましょう。)


次に中身を書いていきましょう。

int div3(int a) {
    if(a % 3 == 0) {
        return 1; // 3の倍数は1を返す
    }
    else {
        return 0; // それ以外は0を返す
    }
}

return は1つの関数に2つ以上あってもOKなのがポイントです!

その3.配列を入力してみよう

入力変数に配列を指定することもできます。

配列を指定する場合は、下のように記述します。

f:id:momoyama1192:20200505184025g:plain

上の場合、整数型配列 a と、ただの整数型変数 n が入力されます。


もちろん下のように配列の要素数を省略せずに書いてもOKです。

f:id:momoyama1192:20200505184013g:plain

というわけで、実際に配列を入力とした関数をいくつか作っていきましょう。

(1) 要素数Nの配列の中身を出力する関数

要素数が N 個(defineで与えられる)の配列の中身をすべて表示する関数を作ってみましょう。

入力、出力は

  • 入力:中身を出力する配列 a (要素数はdefineで与えるため不要)
  • 出力:なし(printfで表示させるだけなので)

なので、関数の外は、

// void printArray(int a[N]) としてももちろんOK
void printArray(int a[]) { 

}

となります。あとは中身を完成させていきましょう!

#define N 5

void printArray(int a[]) { 
    for(int i = 0; i < N; i += 1) {
        printf("%d ",a[i]); // 各データを出力
    }
    printf("\n"); // すべてのデータを出力したら改行
}
(2) 配列の要素の平均を出す関数

最初に出てきた「配列内の要素の平均を求める」処理を関数化してみましょう。

注意が必要なのは、要素が int 型でも平均は小数になる可能性があるので出力は double 型 にする必要がある点です。
(例:50, 53, 51 の平均は51.333…)

入力、出力は

  • 入力:中身を出力する配列 a (要素数はdefineで与えるため不要)
  • 出力:実数型

なので、関数の外は、

// double ave(int a[N]) としてももちろんOK
double ave(int a[]) { 

}

となります。

あとは関数の中身を完成させていきましょう!

// 平均を求める関数ave
double ave(int a[]) {
    int sum = 0, count = 0;
    for(int i = 0; i < N; i += 1) {
        sum += a[i]; // 合計点数を求める 
        count += 1;  // 合計人数を求める
    }

    // 平均 = 合計 ÷ 人数
    return sum * 1.0 / count; // キャストを忘れずに(整数 ÷ 整数 = 整数となるため)
}

その4.2次元配列を入力してみよう

2次元配列も入力していきましょう。

ただし、普通の配列ではできてた配列の省略が、2次元配列 a[M][N] では2つ目の要素(Nの部分)が省略できないので注意しましょう。

f:id:momoyama1192:20200505184019g:plain

上の例の場合、

int printMat(int a[M][N]) {
    
}

もしくは

int printMat(int a[][N]) {
    
}

とする必要があります。(int a[][]int a[M][] はダメ)

要素数 M×N の2次元配列を表示してみよう

では、実際に要素数がM×Nとなっている2次元配列を表(行列?)の形で表示してみましょう。

#define M 5
#define N 5

int printMat(int a[M][N]) {
    for(int i = 0; i < M; i += 1) { // 縦のループ
        for(int j = 0; j < N; j += 1) { // 横のループ
            printf("%2d ",a[i][j]); // 各要素の出力
        }
        printf("\n"); // 1行分のループが終わったら改行
    }
}

4.作った関数を使ってみよう

では、実際に作った関数を使ってみましょう。

例として、先程作った「2つの積を求める関数 mult2」を使用します。

int mult2(int a, int b) {
    return a * b; // return x; とすると x が出力される
}

例えば、5と7の積を求めた結果を変数 ans に代入するのであれば、

    ans = mult2(5,7); // 5と7の積をansに代入

とすることで関数の出力結果が ans に代入されます。

mult2(5,7) のように入力する変数だけを順番にコンマ区切りで書きましょう。)


また、

void printArray(int a[]) { 
    for(int i = 0; i < N; i += 1) {
        printf("%d ",a[i]); // 各データを出力
    }
    printf("\n"); // すべてのデータを出力したら改行
}

のように出力がない関数であれば、

    printArray(data); // data型の配列の中身を表示
    // 配列を入力するときは data[] のように余計な [] は書かないこと

とすることで関数を使うことができます。

さらに、関数の入力に

    ans = mult(x,y);

のように変数を入れたり、

    printf("ans = %2d\n",mult(5,7) ); 
    mlut( mult(4,6),mult(3,8) ); // 関数の中に関数を入れてもOK!

のように出力結果を変数として使うことも可能です。

5.最初に出てきたプログラムを関数を使って表そう

最初に出てきた英語と数学の平均点を表示するプログラムを関数を使ってより見やすくしましょう。

// before
#include<stdio.h>
#define N 10

int main() {
    int data_eng[N] = {32,54,77,82,14,65,99,46,70,57,25}; // 英語
    int data_math[N] = {49,100,96,55,68,26,9,32,78,70};   // 数学

    double ave_eng, ave_math;
    int sum_eng = 0, sum_math = 0; // 0の初期化忘れずに
    int count_eng = 0, count_math = 0; // 0の初期化忘れずに

    // 各生徒の英語の点数の合計・人数を求める
    for(int i = 0; i < N; i += 1) {
        sum_eng += data_eng[i]; // 合計点
        count_eng += 1; // 人数
    }
    ave_eng = sum_eng * 1.0 / count_eng;

    // 各生徒の数学の点数の合計・人数を求める
    for(int i = 0; i < N; i += 1) {
        sum_math += data_math[i]; // 合計点
        count_math += 1; // 人数
    }
    ave_math = sum_math * 1.0 / count_math;

    printf("English Ave:%4.2f\n",ave_eng);
    printf("Math    Ave:%4.2f\n",ave_math);

    return 0;
}

平均点を求める部分を関数化すると…

// after
#include<stdio.h>
#define N 10

// 平均を求める関数ave
double ave(int a[]) {
    int sum = 0, count = 0;
    for(int i = 0; i < N; i += 1) {
        sum += a[i]; // 合計点数を求める 
        count += 1;  // 合計人数を求める
    }

    // 平均 = 合計 ÷ 人数
    return sum * 1.0 / count; // キャストを忘れずに(整数 ÷ 整数 = 整数となるため)
}

int main() {
    int data_eng[N] = {32,54,77,82,14,65,99,46,70,57,25}; // 英語
    int data_math[N] = {49,100,96,55,68,26,9,32,78,70};   // 数学

    // ave(data_eng) → 英語の平均点,  ave(data_math) → 数学の平均点
    printf("English Ave:%4.2f\n",ave(data_eng)); // ave(data_eng) はdouble型
    printf("Math    Ave:%4.2f\n",ave(data_math)); // ave(data_math) はdouble型

    return 0;
}

このようにそれぞれの処理を関数で表すことができるので、全部の処理がmainにあるよりも見やすくなりましたね!

6.いろんな関数を作る練習をしてみよう!

とは言ってもまだまだ関数を作るのには慣れていないと思うので、もう少し関数を作る練習ができる記事を作成しております。

さらに練習をしたい人は下のリンクに演習問題を載せているのでぜひチャレンジしてみてください!

www.momoyama-usagi.com

7.関数の中で変数の値を変えると…(値渡し・参照渡し)

関数の中で変数の値を変える際には注意が必要です。

例えば次のようなプログラムを実行してみましょう。

#include<stdio.h>

void f1(int a, int b) {
    a += 1;
    b -= 1;
}

void f2(int c[]) {
    c[0] += 1;
    c[1] -= 1;
    c[2] *= 2;
}

int main() {
    int a = 10, b = 20;
    int c[3] = {50,80,120};

    // before
    printf("---before---\n");
    printf("a = %d, b = %d\n",a,b); // a = 10, b = 20
    printf("c[0] = %d, c[1] = %d, c[2] = %d\n\n",c[0],c[1],c[2]);
    // c[0] = 50, c[1] = 80, c[2] = 120

    f1(a,b); // 関数f1を実行
    f2(c);   // 関数f2を実行

    // after
    printf("---after---\n");
    printf("a = %d, b = %d\n",a,b); // a = 10, b = 20
    printf("c[0] = %d, c[1] = %d, c[2] = %d\n",c[0],c[1],c[2]);
    // c[0] = 51, c[1] = 79, c[2] = 240
}

ここで、関数 f1 はただの変数 a, b の値を変えようとしているプログラム、f2 は配列変数 c の値を変えようとしているプログラムに見えますよね。

しかし、いざ実行してみると、

  • 関数 f1 で書き換えた変数(配列じゃない)の値はmainに反映されていない
  • 関数 f2 で書き換えた変数(配列)の値はmainに反映されている

状態が発生し、「えええ?」と思う人もおおいと思います。


実は、普通の変数の場合、関数内で値を変更してもmainには影響しない値渡しと呼びます)のに対し、配列変数を関数内で変更するとmainに影響する参照渡しと呼びます)のです。

なので、たとえ関数内で変数の値を変えようと思っていない場合でも

  • 普通の変数であれば関数内で値を変えてもmainには影響しない(値渡し)
  • 配列であれば関数内で値を変えるとmainに影響する(参照渡し)

の2つの違いは頭にいれておきましょう。


値渡しや参照渡しについて詳しく知りたい人はこちらの記事をご覧ください。

少し難しめな内容な(ポインタなどが出てくる)ので、ある程度プログラミングができるようになってから見るのをお勧めします。

www.momoyama-usagi.com

8.さいごに

今回は、C言語において関数の作り方、作った関数の利用の仕方について説明しました。

この記事を見て、関数の作り方が少しでも理解していただけたら幸いです。

*1:整数×整数 = 整数なので