2.12.8. 関数#
2.12.8.1. 関数の定義#
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;
}
2.12.8.2. 関数のオーバーロード#
C / C++ では、関数を定義するときに引数の型も含めて指定します。例えば、int add(int x, int y) として定義された関数を呼び出す際に、x と y に小数を代入すると、小数は整数に変換されてから処理されます。
そのため、引数に複数の型が想定される場合は、同じ関数名で異なる引数型を持つ関数を複数定義する必要があります。これを関数のオーバーロードと呼びます。
#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++ のテンプレート機能を使用すると便利です。
2.12.8.3. 値渡し・ポインタ渡し・参照渡し#
C / C++ 言語では、関数の引数の渡し方として値渡しとポインタ渡しの 2 種類があります。値渡しでは、変数の値のコピーが関数に渡されるため、関数内部で値を変更しても、関数終了後に元の変数へ影響はありません。
一方、ポインタ渡しでは変数のアドレス(メモリ上の場所)が渡されます。そのため、関数内部でそのメモリ上の値を変更すると、関数終了後も変更が反映されます。
C++ 言語では、これらに加えて参照渡しも利用できます。参照渡しの場合も、関数内部で値を変更すると、関数終了後に変更が反映されます。
2.12.8.3.1. 値渡し#
#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;
}
2.12.8.3.2. ポインタ渡し#
#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;
}
2.12.8.3.3. 参照渡し#
#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;
}
2.12.8.4. 関数の引数と戻り値に配列を指定する方法#
2.12.8.4.1. 関数の引数と配列#
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;
}
関数を定義するとき、引数の定義を次のように記述しても、同様にポインタ渡しとなります。
#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;
}
入力した配列に対して変更を加え、その結果を反映させたい場合は、配列のポインタを引数として受け取り、戻り値を持たない関数を作成します。次の例では、配列中の各値を 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;
}
2.12.8.4.2. 関数の戻り値と配列#
関数の戻り値として配列を直接指定することはできません。そのため、配列を返したい場合は、その配列のポインタを返します。次の例では、関数内部で 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;
}