PR

【C・C++】関数とは?【値渡し・参照渡し・ポインタ渡し】

アイキャッチ C++
この記事は約13分で読めます。

関数に関する記事です。
値渡し・参照渡し・ポインタ渡しなどについても解説しています。

のんびり丸

WEBアプリの開発等をしています。
日々の学習の備忘録として運営していこうと思います。

のんびり丸をフォローする

関数とは?

関数というのは、任意の処理をひとまとめにした機能のことです。
関数内の処理を行う際に必要となる入力値のことを【引数(ひきすう)】といい、返ってくる結果の値のことを【戻り値】or【返り値】と言います。

また、関数の機能を記述することを【関数を定義する】と言います。
戻り値は、【return】を用いて記述します。

記述方法

関数は以下のように記述します。
いくつか例を作ってみたので、参考にしてみてください。

戻り値の型 関数名(引数){
処理
return ○;
}

コーディング例①

  • 引数:円の半径
  • 戻り値:円の面積

円の半径から円の面積を算出するCircle関数を定義してみます。
関数の定義後、引数に10mmを入力した円の面積を出力します。

#include <stdio.h> // printf()を使用するために必要

/*************************
円の面積を求める関数の定義
a:円の半径【mm】
x:円の面積【mm²】
*************************/

int Circle(int a)
{
	int x;
	x = a * a * 3.14;
	return x;
}

/*************
関数の呼び出し
*************/

int main()
{
	int a;
	a = Circle(10);
	printf("%d\n", a);

	return 0;
}
出力
314

コーディング例②

  • 引数:身長・体重
  • 戻り値:BMI

身長・体重からBMIを算出するBMI関数を定義してみます。
関数の定義後、身長:176cm、体重:60kgの人間のBMIを出力します。

#include <stdio.h> // printf()を使用するために必要

/********************
BMIを求める関数の定義
height:身長【cm】
weight:体重【kg】
********************/

double BMI(double height, double weight)
{
	double x = weight / (height / 100) / (height / 100);
	return x;
}

/*************
関数の呼び出し
*************/

int main()
{
	double a;
	a = BMI(176, 60);
	printf("%f\n", a);

	return 0;
}
出力
19.369835

コーディング例③

  • 引数:なし
  • 戻り値:なし

"Happy Birthday"と表示するだけのHappyBirthday関数を定義してみます。
戻り値がないので、void型で作成します。

その後、関数の呼び出しを行います。

#include <stdio.h> // printf()を使用するために必要

/*********
関数の定義
*********/

void HappyBirthday()
{
	printf("Happy Birthday\n");
}

/*************
関数の呼び出し
*************/

int main()
{
	HappyBirthday();

	return 0;
}
出力
Happy Birthday

main()関数

main()関数は、プログラムの開始地点となる関数です。
main()関数の呼び出し前に、変数や関数の宣言をする必要があります。

標準ライブラリ関数

言語側があらかじめ用意してくれている関数を【標準ライブラリ関数】と言います。
適宜ヘッダーファイルをインクルードすることで、利用可能です。

標準ライブラリ関数の例としては、以下のようなものがあります。

  • printf()
  • strcpy()
  • atoi()

ローカル変数・グローバル変数

関数内で宣言した変数のことをローカル変数と言います。
ローカル変数の参照範囲【スコープ】は、関数内に限定されます。

関数外で宣言した変数のことをグローバル変数と言います。
グローバル変数の参照範囲【スコープ】は、変数の宣言以降となります。

コーディング例①

#include <stdio.h> // printf()を使用するために必要

int a;
int b;

void Func1(int x)
{
	int b;
	int c;
	a = x; // グローバル変数
	b = x; // ローカル変数
	c = x; // ローカル変数
}

int main()
{
	int c;
	a = 100; // グローバル変数
	b = 100; // グローバル変数
	c = 100; // ローカル変数
	printf("a=%d b=%d c=%d\n", a, b, c);
	Func1(50);
	printf("a=%d b=%d c=%d\n", a, b, c);

	return 0;
}
出力
a=100 b=100 c=100
a=50 b=100 c=100

プロトタイプ宣言

関数も変数と同様に、事前に宣言をした後に処理を定義することが出来ます。
このことを、【プロトタイプ宣言】と言います。
こうすることで、main()の後に処理を定義してもエラーが出ません。

また、引数を記述する際は、データ型だけでなく変数を書くことも可能です。

宣言のみ、ヘッダーファイルに書くことも多いです。

コーディング例①

#include <stdio.h> // printf()を使用するために必要

/***************
プロトタイプ宣言
***************/

int Circle(int);
double BMI(double height, double weight);
void HappyBirthday();

/*********
関数の定義
*********/

int main()
{
	int a;
	double b;
	a = Circle(10);
	b = BMI(176, 60);
	HappyBirthday();
	printf("%d\n", a);
	printf("%f\n", b);

	return 0;
}

// 円の面積を求める
int Circle(int a)
{
	int x;
	x = a * a * 3.14;
	return x;
}

// BMIを求める
double BMI(double height, double weight)
{
	double x = weight / (height / 100) / (height / 100);
	return x;
}

void HappyBirthday()
{
	printf("Happy Birthday\n");
}
出力
Happy Birthday
314
19.369835

デフォルト引数

関数の引数には、デフォルトの値を設定することが出来ます。
宣言時、定義時のどちらでも設定可能です。

デフォルト引数を設定する場合は、後ろの引数から設定する必要があります。
例えば、第一引数にデフォルト値を設定して、第二引数にデフォルト値を設定しないといったことは出来ません。

また、ある引数を省略した場合には、それ以降の引数は自動的に省略されます。
なので、第一引数を省略して、第二引数を指定するといった記述は出来ません。

コーディング例①

#include <iostream>

/*******************
円の面積を求める関数
r:円の半径【mm】
p:円周率
*******************/
double Circle(int r = 10, double p = 3.14) {
	return r * r * p;
}

int main() {
	double A, B, C;
	A = Circle();		// Circle(10, 3.14)
	B = Circle(5);		// Circle(5, 3.14)
	C = Circle(5, 3);	// Circle(5, 3)
	std::cout << A << std::endl;
	std::cout << B << std::endl;
	std::cout << C << std::endl;

	return 0;
}
出力
314
78.5
75

オーバーロード

引数の型や数が異なる、同じ名前の関数を複数定義することが出来ます。
このことをオーバーロードと呼びます。

オーバーロードを用いて同じ名前の関数を複数定義してある場合には、引数の型・数を判断材料に、どの関数を呼び出すか決定されます。

また、戻り値の型だけ違う関数の場合は、オーバーロードすることが出来ません。

オーバーロードを使用することで、演算子や代入演算子の機能を変更することも出来ます。

コーディング例①

#include <iostream>

// 円の面積
double Circle(int r, double p) { // ①
	return r * r * p;
}
double Circle(double r, double p) { // ②
	return r * r * p;
}
double Circle(int r) { // ③
	return r * r * 3.14;
}

int main() {
	double A, B, C;
	A = Circle(10, 3.14); // ①
	B = Circle(10.0, 3.14); // ②
	C = Circle(10); // ③
	std::cout << A << std::endl;
	std::cout << B << std::endl;
	std::cout << C << std::endl;

	return 0;
}
出力
314
314
314

実引数・仮引数

関数の定義側の引数を仮引数と言います。
下記のコードでは、height・weightが該当します。

関数の呼び出し側の引数を実引数と言います。
下記のコードでは、176・60が該当します。

コーディング例①

#include <stdio.h> // printf()を使用するために必要

// BMIを求める
double BMI(double height, double weight)
{
	double x = weight / (height / 100) / (height / 100);
	return x;
}

int main()
{
	double a;
	a = BMI(176, 60);
	printf("%f\n", a);

	return 0;
}
出力
19.369835

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

実引数の値を仮引数に渡す標準的な方法を【値渡し】と言います。
【値渡し】の場合、関数内で引数の値を変更しても、実引数の値には影響がありません。

実引数の参照を仮引数に渡す方法を【参照渡し】と言います。
【参照渡し】の場合、関数内で引数の値を変更すると、実引数の値に影響があります。

実引数のポインタを仮引数に渡す方法を【ポインタ(アドレス)渡し】と言います。
【ポインタ(アドレス)渡し】の場合、関数内で引数の値を変更すると、実引数の値に影響があります。

コーディング例①

#include <iostream>

// 値渡し
void ConvertValue(int x, int y)
{
	int z = x;
	x = y;
	y = z;
}

// 参照渡し
void ConvertReference(int &x, int &y)
{
	int z = x;
	x = y;
	y = z;
}

// ポインタ渡し
void ConvertAddress(int *x, int *y)
{
	int z = *x;
	*x = *y;
	*y = z;
}

int main()
{
	// 値渡し
	int a = 5, b = 10;
	ConvertValue(a, b);
	printf("a=%d b=%d\n", a, b);

	// 参照渡し
	a = 5; b = 10;
	ConvertReference(a, b);
	printf("a=%d b=%d\n", a, b);

	// ポインタ渡し
	a = 5; b = 10;
	ConvertAddress(&a, &b);
	printf("a=%d b=%d\n", a, b);

	return 0;
}
出力
a=5 b=10
a=10 b=5
a=10 b=5

使い分け

【値渡し】と【参照渡し】は呼び出し方が同じで見分けにくいため、実引数を変更する場合は、【ポインタ渡し】にすることが多いようです。
なので、引数の使い分けとしては、次のようになります。

データのサイズ実引数を変更しない実引数を変更する
小さいデータ値渡しポインタ渡し
大きいデータ【const】参照渡しポインタ渡し

再起呼び出し

関数は、自分自身を呼び出すことが可能です。
自分自身を呼び出すことを【再起呼び出し】と言います。

コーディング例①

#include <stdio.h> // printf()を使用するために必要

// xの階乗を求める
int Factorial(int x)
{
	if (x == 0)
		return 1;
	else
		return (x * Factorial(x - 1));
}

int main()
{
	int a = 5, b;
	b = Factorial(a);
	printf("%dの階乗は%dです。\n", a, b);

	return 0;
}
出力
5の階乗は120です。

終了条件を指定しないと、無限ループになってしまいます。

インライン関数

関数の先頭に【inline】を記述すると、コンパイル時に呼び出し箇所へ関数の定義が展開されるようになります。
このような関数を【インライン関数】と呼びます。

インライン関数は、どのファイルからもアクセス出来るように、ヘッダファイルに定義します。

inline データ型 関数名(){
// 処理の記述
}

イメージ

コンパイル時に呼び出し箇所へ関数の定義が展開されると言われても、正直イメージしにくいです。
なので、出来るだけイメージしやすいように、具体例(コーディング例②)を作成してみました。

今回は一つのファイルに関数の定義と呼び出しを書いていますが、本来はヘッダファイルにインライン関数の定義を記述します。

コーディング例①

#include <iostream>

// インライン関数を定義
inline void Hello() {
	std::cout << "Hello" << std::endl;
}

int main() {
	for (int i = 0; i < 5; i++)
		Hello();

	return 0;
}
出力
Hello
Hello
Hello
Hello
Hello

コーディング例②

コーディング例①の場合は、Hello()を呼び出しているだけでしたが、実際の処理は次のようになっています。

#include <iostream>

inline void Hello() {
	std::cout << "Hello" << std::endl;
}

int main() {
	for (int i = 0; i < 5; i++) {
		// Hello()の展開
		std::cout << "Hello" << std::endl;
	}

	return 0;
}
出力
Hello
Hello
Hello
Hello
Hello

メリット・デメリット

インライン関数にすると、プログラムの実行速度が速くなるというメリットがあります。
その代わり、プログラムのサイズが大きくなるというデメリットがあります。

インライン関数に向いている関数は、次のような関数です。

  • 処理内容の少ない関数
  • 呼び出し回数の多い関数
タイトルとURLをコピーしました