関数

関数の定義

C/C++ 言語では、関数を呼び出す前に、その関数を定義する必要がある。関数を定義するとき、関数が受け取る引数とその変数型、そして関数の戻り値の変数型も合わせて定義する必要がある。例えば、整数 a と整数 b の和を計算して返す関数 add を定義し、main 関数の中で add 関数を呼び出して実行するには次のようにする。この場合、main 関数の中から add 関数を呼び出している。そのため、add 関数の定義を main 関数の前に置く必要がある。

#include <stdio.h>

int add(int x, int y) {
    int s = x + y;
    return s;
}

int main() {

    int a = 10;
    int b = 20;

    int s = add(a, b);

    printf("%d", s);

    return 0;
}

add 関数を main 関数の後に記述する場合は、main の前にプロトタイプ宣言を行う必要がある。例えば、上のサンプルコードは下のサンプルコードと同じである。

#include <stdio.h>

int add(int, int);

int main() {

    int a = 10;
    int b = 20;

    int s = add(a, b);

    printf("%d", s);

    return 0;
}

int add(int x, int y) {
    int s = x + y;
    return s;
}

関数のオーバーロード

C/C++ では関数を定義するときに、関数の中で使われる引数の変数型も合わせて定義する必要がある。例えば、int add(int x, int y) で定義された関数を呼び出して使うとき、xy に小数を代入すると、その小数が整数に丸められて使われる。そのため、引数に複数の変数型が想定される場合は、同じ関数の名前で、異なる変数型の引数で定義する必要がある。

#include <stdio.h>

float add(int, int);
float add(float, float);
float add(int, float);
float add(float, int);

int main() {

    float a = 1.1;
    int b = 2;

    float s = add(a, b);

    printf("%f", s);

    return 0;
}

float add(int x, int y) {
    float s = (float) x + y;
    return s;
}

float add(float x, float y) {
    float s = x + y;
    return s;
}

float add(int x, float y) {
    float s = x + y;
    return s;
}

float add(float x, int y) {
    float s = x + y;
    return s;
}

このように、同じ機能を持つ関数で、引数の変数型の違いによって複数回定義する必要がる場合、C++ のテンプレートを使用すると便利である。

値渡し・ポインタ渡し・参照渡し

C/C++ 言語では、関数の引数に値を渡すとき、値渡しとポインタ渡しの 2 種類が存在する。値渡しの場合は、その値のコピーを関数に渡すため、関数の内部でその値を変更しても、関数を抜けた後にその影響が残らない。これに対して、ポインタ渡しの場合は、値のポインタ(メモリ上の住所)を関数に渡すため、関数の内部でそのメモリ上の値を変更すると、関数を抜けた後にはその影響が残ったままとなる。

C++ 言語では、値渡しとポインタ渡しの他に参照渡しも存在する。参照渡しの場合、ポインタ渡しと同様に、関数内部で値を変更すると、関数を抜けた後にもその影響が残る。

値渡し

#include <stdio.h>

void f(int x) {
    x += 1;
}

int main(void) {
    int x = 10;

    printf("%d\n", x); // 10
    f(x);
    printf("%d\n", x); // 10

    return 0;
}

ポインタ渡し

#include <stdio.h>

void g(int* x) {
    *x += 1;
}


int main(void) {
    int x = 10;

    printf("%d\n", x); // 10
    g(&x);
    printf("%d\n", x); // 11

    return 0;
}

参照渡し

#include <stdio.h>

void h(int& x) {
    x += 1;
}

int main(void) {
    int x = 10;

    printf("%d\n", x); // 10
    h(x);
    printf("%d\n", x); // 11

    return 0;
}

関数の引数と戻り値に配列を指定する方法

関数の引数と配列

C/C++ の関数では、引数として配列を受け取ることができない。そのため、関数に配列を代入したい場合、配列のポインタを関数に渡す必要がある。配列の名前が、その配列のポインタとなることに注意すると、引数の定義は次のように行うことができる。

#include <stdio.h>

int sum(int* arr, int arr_size) {
    int sum = 0;
    for (int i = 0; i < arr_size; i++) {
        sum += arr[i];
    }
    return sum;
}


int main(void) {
    int arr[] = {1, 2, 3, 4, 5};
    int s;

    s = sum(arr, sizeof(arr) / sizeof(arr[0]));
    printf("%d\n", s);

    return 0;
}

関数を定義するとき、引数の定義は次のようにしても同じくポインタ渡しとなる。

int sum(int arr[], int arr_size) {
    int sum = 0;
    for (int i = 0; i < arr_size; i++) {
        sum += arr[i];
    }
    return sum;
}

入力した配列に対して何らかの変更を行なって、その変更結果を返すような関数を作る場合は、配列のポインタを引数として受け取り、何も返さない関数を作成すれば良い。例えば、次の関数は入力された配列中の各値を 1000 倍している例を表している。

#include <stdio.h>

void times1000(int* arr, int arr_size) {
    for (int i = 0; i < arr_size; i++) {
        arr[i] = arr[i] * 1000;
    }
}

int main(void) {
    int arr[] = {1, 2, 3, 4, 5};

    times1000(arr, sizeof(arr) / sizeof(arr[0]));

    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
        printf("%d\n", arr[i]);
    }
    // 1000
    // 2000
    // 3000
    // 4000
    // 5000

    return 0;
}

関数の戻り値と配列

関数の戻り値に配列を指定することができない。そのため、配列を返したい場合は、その配列のポインタを返すことになる。関数の内部で定義したローカル変数を関数の外へ返すためにグローバル変数的な扱い方をしたいので、関数の内部では static で配列の宣言を行なっている。

#include <stdio.h>

int* calc(int x, int y) {
    static int arr[6];

    arr[0] = sizeof(arr) / sizeof(arr[0]);
    arr[1] = x + y;
    arr[2] = x - y;
    arr[3] = x * y;
    arr[4] = x / y;
    arr[5] = x % y;

    return arr;
}

int main(void) {
    int* arr;

    arr = calc(10, 20);

    for (int i = 1; i < arr[0]; i++) {
        printf("%d\n", arr[i]);
    }
    // 30
    // -10
    // 200
    // 0
    // 10

    return 0;
}