関数の引数
第10課では、関数が引数を受け取ることができることを学びました。
しかし、パラメータは具体的にどのように渡されるのでしょうか?関数がパラメータを変更した場合、元の変数も変更されるのでしょうか?
今回のレッスンで取り上げるのは、まさにそのパラメータの渡し方です。
1. 値渡し
(1) 1.1 基本的な使い方
値渡しはC++のデフォルトの渡し方であり、引数の値がパラメータにコピーされます。
#include iostream
void modifyValue(int x) { // x is a copy
x = 100; // Modifying the copy — original is unaffected
std::cout << "Inside function: x = " << x << std::endl;
}
int main() {
int a = 5;
modifyValue(a); // Pass by value: a is copied to x
std::cout << "Inside main: a = " << a << std::endl; // a is still 5
return 0;
}
出力:
Inside function: x = 100
Inside main: a = 5
(2) 1.2 メリットとデメリット
| メリット | デメリット |
|---|---|
| 安全(関数が元のデータを変更できない) | 大きなオブジェクトのコピーにはコストがかかる(大きな配列、大きな構造体) |
| シンプルでわかりやすい | 1つの関数から複数の値を「返す」ことはできない |
2. ポインタ渡し
関数が元の変数を変更するようにしたい場合は、そのアドレス(ポインタ)を渡すことができます。
(1) 2.1 基本的な使い方
#include iostream
void modifyValue(int* x) { // x is a pointer
*x = 100; // Dereference: modify the value the pointer points to
std::cout << "Inside function: *x = " << *x << std::endl;
}
int main() {
int a = 5;
modifyValue(&a); // Pass the address of a
std::cout << "Inside main: a = " << a << std::endl; // a is now 100
return 0;
}
出力:
Inside function: *x = 100
Inside main: a = 100
(2) 2.2 なぜ変更できるのか?
| 手順 | 説明 |
|---|---|
| 1 | int a = 5; — a 用のメモリを割り当て、5 を格納 |
| 2 | modifyValue(&a); — aのアドレスを関数に渡す |
| 3 | int* x は、a のメモリを指すアドレスを受け取る |
| 4 | *x = 100; — ポインタを介して a の値を変更する |
3. 参照渡し
C++では、ポインタよりも安全で便利な変数の別名である参照が導入されました。
(1) 3.1 基本的な使い方
#include iostream
void modifyValue(int& x) { // x is a reference (alias)
x = 100; // Modifying x modifies the original variable
std::cout << "Inside function: x = " << x << std::endl;
}
int main() {
int a = 5;
modifyValue(a); // Pass by reference — looks like pass-by-value
std::cout << "Inside main: a = " << a << std::endl; // a is now 100
return 0;
}
出力:
Inside function: x = 100
Inside main: a = 100
(2) 3.2 参照とポインタ
| 比較 | 参照 | ポインタ |
|---|---|---|
| 構文 | int& x = a; |
int* x = &a; |
| null になることはありますか? | ❌ いいえ(初期化する必要があります) | ✅ はい(nullptr になる可能性があります) |
| 参照解除が必要ですか? | ❌ いいえ(直接使用) | ✅ はい(*x を使用) |
| 再割り当ては可能ですか? | ❌ いいえ(一度だけバインドされる) | ✅ はい(別の場所を指すようにできる) |
| 推奨 | ⭐⭐⭐⭐⭐(推奨) | ⭐⭐⭐(必要な場合) |
4. 3つのパッシング手法の比較
(1) ▶ サンプル:1:2つの変数の入れ替え(難易度 ⭐⭐)
方法 1:値渡し(失敗)
#include iostream
void swapFailed(int a, int b) { // ❌ Pass by value: modifies copies
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 3, y = 5;
swapFailed(x, y);
std::cout << "x = " << x << ", y = " << y << std::endl; // x=3, y=5 (no swap)
return 0;
}
方法 2:ポインタ渡し(動作確認済み)
#include iostream
void swapPointer(int* a, int* b) { // ✅ Pass by pointer
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 3, y = 5;
swapPointer(&x, &y);
std::cout << "x = " << x << ", y = " << y << std::endl; // x=5, y=3 (swapped)
return 0;
}
方法 3:参照渡し(動作確認済み、推奨)
#include iostream
void swapReference(int& a, int& b) { // ✅ Pass by reference (recommended)
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 3, y = 5;
swapReference(x, y); // Looks like pass-by-value but is pass-by-reference
std::cout << "x = " << x << ", y = " << y << std::endl; // x=5, y=3 (swapped)
return 0;
}
5. どちらをいつ使うべきか?
| シナリオ | 推奨 | 理由 |
|---|---|---|
| 関数は変更する必要がない | 値渡し または const 参照 | 安全で明確 |
| 関数を修正する必要がある | 参照渡し | 構文を整理し、安全にする |
| 複数の値を「返す」必要がある | 参照渡し | 複数の参照パラメータを変更する |
| 大きなオブジェクト(配列、構造体) | const参照による渡し | コピーのオーバーヘッドを回避 |
| 「なし」(例:見つかりませんでした)を表現する必要がある | ポインタ渡し | ポインタは nullptr である場合がある |
6. 定数への参照
関数がパラメータを変更しないようにしつつ、コピーも避けたい場合は、const 参照を使用してください。
#include iostream
#include string
void printName(const std::string& name) { // Const ref: can't modify name
std::cout << "Name: " << name << std::endl;
// name = "Alice"; // ❌ Error: can't modify a const reference
}
int main() {
std::string myName = "MOTO";
printName(myName); // No copy — efficient
return 0;
}
const reference は C++ において 最も一般的なパラメータ型のひとつ です。安全性と効率性の両方を兼ね備えています。
7. 引数としての配列
配列を関数に渡すと、それはポインタに変換される(これについては後で詳しく説明します)。
#include iostream
// Array parameter: need to pass the length separately
void printArray(int arr, int length) {
for (int i = 0; i < length; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
int myArray[5] = {1, 2, 3, 4, 5};
printArray(myArray, 5); // Pass array name (which is the address)
return 0;
}
8. 演習:複数の戻り値
(1) ▶ サンプル:2:商と余りを求める(難易度 ⭐⭐)
#include iostream
// Use reference parameters to "return" multiple values
void divide(int a, int b, int& quotient, int& remainder) {
quotient = a / b;
remainder = a % b;
}
int main() {
int a = 17, b = 5;
int q, r;
divide(a, b, q, r); // q and r will be modified
std::cout << a << " / " << b << " = " << q << " ... " << r << std::endl;
// Output: 17 / 5 = 3 ... 2
return 0;
}
▶ サンプル 1: function parametersデモ (難易度 ⭐)
#include <iostream>
int main() {
// function parametersの
std::cout << "function parametersをびましょう!" << std::endl;
return 0;
}
❓ よくある質問
Q: 参照とポインタの本当の違いは何ですか? A: 参照は変数の別名であり、ポインタはアドレスを格納する変数です。参照は初期化する必要があり、nullにはできず、再代入もできません。「何もない」ことを表現する必要がある場合を除き、参照を使用することをお勧めします(その場合はポインタを使用してください)。Q: 配列をパラメータとして渡す際、なぜ長さを指定する必要がないのですか? A: 配列はポインタに展開されるためです。
void foo(int arr)はvoid foo(int* arr)と同等です。arrパラメータは実際には配列ではなく、ポインタです。 Q: 値渡し、参照渡し、ポインタ渡しのうち、どれが最も効率的ですか?A: 小さなオブジェクト(intなど)の場合、値渡し(コピーコストが小さい)が最も効率的です。大きなオブジェクト(大きな配列、大きな構造体)の場合、定数参照渡し(コピーが発生しない)が最適です。パラメータを変更する必要がある場合は、参照渡し(構文が簡潔で、コピーが発生しない)が最も効率的です。
Q:function parametersについて最も重要なことは何ですか? A:まず核心概念を理解し、その後実践的な例で練習することが重要です。
Q:function parametersを効果的に練習するにはどうすればよいですか? A:簡単な例から始め、徐々に複雑さを増やし、常にコードをテストしてください。
📖 まとめ
- 値渡し:コピーを作成する — 関数内での変更は元の値には影響しない
- ポインタ渡し:アドレスを渡す。関数は元の変数を変更できる。
- 参照渡し:エイリアスを渡す — 関数による変更は元の値に影響する(推奨)
- 定数参照:変更はできないが、コピーを回避できる — 最も一般的な選択肢
- 配列は関数に渡されるとポインタに変換されるため、長さは別途渡す必要がある
📝 練習問題
-
初心者(難易度 ⭐): 渡された変数に10を加算する関数
void addTen(int& x)を作成してください。main()でテストしてください。 -
中級(難易度 ⭐⭐): 配列の最小値と最大値を求める関数
void minMax(int arr, int length, int& min, int& max)を作成してください(参照パラメータを使用してそれらを「返す」ようにしてください)。 -
上級(難易度 ⭐⭐⭐): 3つの数値を並べ替えるプログラムを作成してください。それらを昇順で並べ替える関数
void sortThree(int& a, int& b, int& c)を作成してください。ユーザーに3つの数値を入力してもらい、sortThreeを呼び出し、並べ替えられた結果を表示してください。
9. 🚀 次は
パラメータの渡し方について理解できたところで、次は再帰(第12課)について学びましょう。これは、関数が自分自身を呼び出すという驚くべきテクニックです!



