関数の実引数に変数を指定したとき、仮引数にどんな渡し方ができるかの言語別対応表と、関連用語の説明を書いてみました。
値渡しと参照渡しの理解の助けになりましたら幸いです。
タイトルにある「オブジェクト共有渡し」(call by object sharing) は、Wikipediaにある「参照の値渡し」で記載しています。
言語別対応表
| \ 渡し方 \ 言語 \ |
値渡し (値コピー) |
ポインタ渡し (ポインタ値コピー 値共有) |
参照の値渡し (参照値コピー オブジェクト共有) |
参照渡し (コピーなし 変数共有) |
|---|---|---|---|---|
| C# | できる ( int arg) |
できる ( int *arg) |
できる ( Object arg) |
できる ( ref int arg) |
| Swift | できる ( arg: Int) |
できる | できる | できる ( arg:inout Int) |
| PHP | できる ( int $arg) |
できない | できる ( object $arg) |
できる ( int &$arg) |
| VB.NET | できる ( ByVal argAsInteger) |
できない | できる ( ByVal argAs Object) |
できる ( ByRef argAs Integer) |
| Rust | できる ( arg: i32) |
できる ( arg: *i32) |
できない | できる1 ( arg: &i32) |
| C++ | できる ( int arg) |
できる ( int *arg) |
できない2 | できる3 ( int &arg) |
| C | できる ( int arg) |
できる ( int *arg) |
できない2 | できない |
| Go | できる ( arg int) |
できる ( arg *int) |
できる ( arg []int) |
できない |
| Java JavaScript TypeScript |
できる ( int arg) |
できない | できる ( Object arg) |
できない |
| Python | できない | できない | できる ( arg: int) |
できない |
| Ruby | できない | できない | できる ( arg: Integer) |
できない |
| Dart | できない | できない | できる ( int arg) |
できない |
一番左の「値渡し」の欄は値型変数の値渡しのことです。参照型変数の値渡しは「参照の値渡し」の欄に分けて記載しています。
言語名のリンクをクリックすると「参照渡しはない」というドキュメントが表示されます。
「できる」の下の括弧は仮引数の書き方の一例です
変数の種類
大きくは「値型変数」と「参照型変数」の2種類に分類されます。
値型変数
- 変数は値そのものを保持する
- 代入した値と変数が保持する値は同じ
- 変数から変数への代入では代入値がコピーされる
値型変数(代入した値を保持)
ポインタ型変数(値型変数の一種)
C言語などにある「ポインタ型変数」も「値型変数」ですが、参照型変数と誤解している人がいるので説明します。
- ポイントされる値(pointee)はメモリ上のどこかにあり、変数(pointer)は値へのポインタ値を保持して値をポイントする
- アドレス演算子などを使って明示的なポインタ値を代入・保持する
- 代入した値と変数が保持する値は同じ(値型変数)
- 変数から変数への代入ではポインタ値がコピーされ、ポイント先の値を共有する
- 言語によってはポインタをインクリメントしたり配列アクセスで別の値をポイントすることができる(危険)
ポインタ型変数(代入したポインタ値を保持) --ポイントする(point)--> 値(pointee)
参考: 人差し指(pointer finger) --指す(point)--> 人(pointee)
参照型変数(オブジェクト型変数)
- 参照されるオブジェクトはメモリ上のどこかにあり、変数は代入したオブジェクトへの参照値を保持してオブジェクトを参照する
- どんな参照値かは言語側が暗黙的に決める(ポインタ値、変数番号、変数名など)
- 代入した値と変数が保持する値は違う
- 変数から変数への代入では参照値だけがコピーされて代入値を共有する
参照型変数(参照値を保持) --参照する(reference)--> オブジェクト(代入した値)
Pythonではすべての変数が参照型変数で、参照値としてオブジェクトID(オブジェクト識別値)を保持します。
CPython(C言語で実装されたPythonインタプリタ)では、オブジェクトIDとしてオブジェクトのメモリアドレス値(ポインタ)を使います。
C++では、参照渡しした仮引数(実引数変数を参照する変数)のことを「参照型」と定義していますが、本記事ではオブジェクト(代入値)を参照する変数を参照型変数と定義しています。
データ型による変数の分類
どんなデータ型が値型変数か参照型変数かは言語によって異なります。
| 言語\データ型 | 数値 | 配列 | ポインタ | 構造体 | オブジェクト |
|---|---|---|---|---|---|
| C, C++ | 値型 | どちらでもない | 値型 | 値型 | なし |
| C# | 値型 | 参照型 | 値型 | 値型 | 参照型 |
| PHP | 値型 | 値型 | なし | なし | 参照型4 |
| Java, JavaScript | 値型 | 参照型 | なし | なし | 参照型 |
| Python, Ruby | 参照型 | 参照型 | なし | なし | 参照型 |
関数の引数の種類
実引数(値か変数)
- 関数を呼び出す側が引き渡す即値、または、変数
- 即値(immediate value): 数値、ポインタ値、オブジェクトなど
- 変数(variable): 変数名
function(123); // 即値を渡す
int value = 123;
function(value); // 変数を渡す
変数を渡す場合に、変数の値を渡す「値渡し」と、変数そのものを渡す(呼び出し元変数を参照する)「参照渡し」があります。(後述)
仮引数(変数)
- 呼び出された関数が引き受ける変数
- 関数内で仮引数に代入することもできる
引数への変数の渡し方の種類
大きくは「値渡し」と「参照渡し」の2種類に分類されます。
実引数に「変数」を指定した場合の仮引数への渡し方の種類です。
実引数に値 (immediate value) を指定した場合は値渡し相当で、値を参照渡しするとコンパイルエラーや実行時エラーになります。
値渡し (call by value, pass by value)
- 変数が保持してる値を仮引数に渡す
- 変数から変数への代入と同じ(代入渡し、pass by assignment)
- 値型変数は 値のコピー を渡す
- 関数内で値を変更しても呼出し元に影響しない
- 参照型変数は 参照値のコピー を渡して 値を共有 する(参照の値渡し)
- 関数内で値を変更すると呼出し元に影響する
- 実引数と仮引数は独立変数
- 仮引数に代入しても実引数に影響しない
ポインタ渡し(値渡しの一種、call by pointer)
- ポインタ型変数が保持しているポインタ値(例えばメモリアドレス値)のコピーを仮引数に渡す
- 実引数と仮引数は独立変数
- 仮引数に代入しても実引数に影響しない
- ポイント先の値は共有
- ポイント先の値を変更すると呼び出し元に影響する
参照の値渡し(値渡しの一種、call by object, call by object-sharing)
- 「オブジェクト渡し」や「オブジェクト共有渡し」ともいわれる
- 参照型変数が保持している参照値のコピーを仮引数に渡す
- 実引数と仮引数は独立変数
- 仮引数に代入しても実引数に影響しない
- 参照先のオブジェクトは共有
- 参照先のオブジェクト内容を変更すると呼び出し元に影響する
参照渡し (call by reference, pass by reference)
- 実引数に指定した変数そのものを仮引数に渡す
- 仮引数から実引数を参照する渡し方
- 実引数と仮引数は同一変数(エイリアス)、変数共有
- 仮引数に代入すると実引数(呼出し元変数)に代入される
「参照を渡す」を英語にすると pass a reference です。
「参照渡し」は pass by reference で、例えば go by bicycle (自転車で行く)の by で行く手段として自転車を使うことを表現しているように、変数を渡す手段として参照を使うことを表現していると見ることができます。
「手を渡す」と「手渡し」(手を使って何かを渡す、手は渡さない)のような違いです。
参考: 引数 - Wikipedia
参照渡し(さんしょうわたし、call by reference)はその実装手段の一つ(と見ることもできる)。変数に対する参照(アドレス情報)を渡す方法である(これは言語側が暗黙的に行う。C言語のように明示的にアドレス演算子を使うものは参照渡しとは呼ばない)。
私の不確かな記憶では、ポインタ型変数も参照型変数もなかった時代のFORTRANでは関数の実引数に変数を指定するとすべて参照渡し(変数共有)していたので、言語毎の関数に対して「call by value/reference」という用語が使われ、現代の言語では引数毎に値渡しか参照渡しかを指定できるので、引数に対して「pass by value/reference」という用語が使われるようになったのではないかと憶測してます。
あくまで私の個人的な憶測です。
動作確認プログラム
値型変数の値渡し、参照渡し
#include <iostream>
void call_by_value(int arg) { // 値型変数の値渡し(独立変数)
arg = 2; // 仮引数への代入は呼び出し元に影響しない
}
void call_by_reference(int &arg) { // 値型変数の参照渡し(共有変数)
arg = 3; // 仮引数への代入が呼び出し元に影響する
}
int main(void) {
int data = 1;
std::cout << data << std::endl; // 1
call_by_value(data);
std::cout << data << std::endl; // 1
call_by_reference(data);
std::cout << data << std::endl; // 3
}
ポインタ型変数の値渡し、参照渡し
#include <iostream>
void call_by_value(int *arg) { // ポインタ型変数の値渡し(独立変数)
*arg = 2; // ポイント先への代入は呼び出し元に影響する
arg = (int *)3; // 仮引数への代入は呼び出し元に影響しない
}
void call_by_reference(int *&arg) { // ポインタ型変数の参照渡し(共有変数)
*arg = 4; // ポイント先への代入は呼び出し元に影響する
arg = (int *)5; // 仮引数への代入が呼び出し元に影響する
}
int main(void) {
int data = 1;
int *ptr = &data;
std::cout << data << ", " << ptr << std::endl; // 1, 0x7ffe62f265cc
call_by_value(ptr);
std::cout << data << ", " << ptr << std::endl; // 2, 0x7ffe62f265cc
call_by_reference(ptr);
std::cout << data << ", " << ptr << std::endl; // 4, 0x5
}
参照型変数の値渡し、参照渡し
<?php
function call_by_value(object $arg) { // 参照型変数の値渡し
$arg->value = 2; // オブジェクト内への代入は呼び出し元に影響する
$arg = (object)['value' => 3]; // 仮引数への代入は呼び出し元に影響しない
}
function call_by_reference(object &$arg) { // 参照型変数の参照渡し
$arg->value = 4; // オブジェクト内への代入は呼び出し元に影響する
$arg = (object)['value' => 5]; // 仮引数への代入も呼び出し元に影響する
}
$data = (object)['value' => 1]; // 参照型変数data
echo "{$data->value}\n"; // 1
call_by_value($data);
echo "{$data->value}\n"; // 2
call_by_reference($data);
echo "{$data->value}\n"; // 5
情報処理試験問題例
情報処理試験では、call by value を「値呼出し」、call by reference を「参照呼出し」と表記しています。
情報処理試験問題の実行結果確認プログラム
#include <stdio.h>
void add(int X, int& Y) { // 値型変数、仮引数Xは値呼出し、仮引数Yは参照呼出し
X = X + Y;
Y = X + Y;
}
int main(){
int X, Y; // 値型変数
X = 2;
Y = 2;
add(X, Y);
printf("%d %d\n", X, Y);
}
2 6
まとめ
- 値型変数: 代入した値をそのまま保持する
- 変数から変数への代入では代入値をコピーする
- 参照型変数: 代入した値の参照値を保持して代入値を参照する
- 変数から変数への代入では参照値だけがコピーされて代入値を共有する
- 参照値はポインタ、オブジェクト識別値、変数名など言語実装側が暗黙的に決める
- 値渡し: 変数が保持してる値を渡す
- 実引数と仮引数は別変数
- 変数から変数への代入と同じ動作(代入値をコピーするとは限らない)
- 参照渡し: 変数そのものを渡す
- 実引数と仮引数は同一変数
- 値型変数も参照型変数も参照渡しできる
値やオブジェクトへの参照を渡すことを「参照渡し」と説明しているブログや記事が沢山ありますが、情報処理用語的には正しくありません。
- 「値型は値渡し、参照型は参照渡し」ではない
- 「参照を渡す」と「参照渡し」は別物
- 値(オブジェクト)への「参照を渡す」(pass a reference) と「値共有」する
- 関数引数に変数を「参照渡し」(pass by reference) すると「変数共有」する
情報処理用語の「値渡し」「参照渡し」は、関数の引数に変数を渡すときに使われる用語で、変数2種類と渡し方2種類を組み合わせて以下の4種類があります。
- 値型変数の値渡し(保持値コピー)
・仮引数に代入しても呼び出し元に影響しない
・ポインタ型変数の場合はポイント先に代入すると呼び出し元に影響する - 値型変数の参照渡し(変数共有)
・仮引数に代入すると呼び出し元に影響する - 参照型変数の値渡し(値共有、オブジェクト共有)
・仮引数に代入しても呼び出し元に影響しない
・オブジェクト内容を変更すると呼び出し元に影響する - 参照型変数の参照渡し(変数共有)
・仮引数に代入すると呼び出し元に影響する
みなさまが使用している言語ではどれができるか正しく理解・説明できますでしょうか。
| 渡し方\言語 | C | C++ | C# PHP |
Java JavaScript |
Python Ruby |
|---|---|---|---|---|---|
| 1. 値型変数の値渡し | できる | できる | できる | できる | できない |
| 2. 値型変数の参照渡し | できない | できる | できる | できない | できない |
| 3. 参照型変数の値渡し | できない | できない | できる | できる | できる |
| 4. 参照型変数の参照渡し | できない | できない | できる | できない | できない |
用語の本来の使い方を理解して、話し相手と話がかみ合わない時の参考にしていただけましたら幸いです。
-
Rustは「参照渡し」ではなく「借用」という用語を使用しています。仮引数に直接代入することはできずデリファレンスして代入するので参照渡しとは違う印象ですが、ポインタ型のようにnullにはならないなど参照渡しの特徴を持っています。 ↩
-
C/C++の配列型変数は、ポインタ渡しや参照の値渡しのようなことができますが、配列サイズを指定して領域確保した配列型変数は後述の参照型変数(オブジェクト変数)のように参照値を保持していないので、参照の値渡しはできないと記載しました。 ↩ ↩2
-
C++の参照型変数やPHPのリファレンス変数は、変数を参照して変数を共有(エイリアス)します。本記事で説明している参照型変数とは定義が異なります。 ↩
-
PHP4以前のオブジェクトは値型です。 ↩
