関数の実引数に変数を指定したとき、仮引数にどんな渡し方ができるかの言語別対応表と、関連用語の説明を書いてみました。
値渡しと参照渡しの理解の助けになりましたら幸いです。
言語別対応表
\ 渡し方 \ 言語 \ |
値渡し (値コピー) |
ポインタ渡し (ポインタ値コピー 値共有) |
参照の値渡し (参照値コピー オブジェクト共有) |
参照渡し (コピーなし 変数共有) |
---|---|---|---|---|
C# | できる ( int arg ) |
できる ( int *arg ) |
できる ( Object arg ) |
できる ( ref int arg ) |
PHP | できる ( int $arg ) |
できない | できる ( object $arg ) |
できる ( int &$arg ) |
C++ | できる ( int arg ) |
できる ( int *arg ) |
できない | できる ( int &arg ) |
C | できる ( int arg ) |
できる ( int *arg ) |
できない | できない |
Rust | できる ( arg: i32 ) |
できる ( arg: &i32 ) |
できない | できない |
Java JavaScript TypeScript |
できる ( int arg ) |
できない | できる ( Object arg ) |
できない |
Python | できない | できない | できる ( arg: int ) |
できない |
Ruby | できない | できない | できる ( arg: Integer ) |
できない |
「できる」の下の括弧は仮引数の書き方の一例です
一番左の「値渡し」の欄は値型変数の値渡しのことです。参照型変数の値渡しは「参照の値渡し」の欄に分けて記載しています。
C/C++の配列型変数は、ポインタ渡しや参照の値渡しのようなことができますが、配列サイズを指定して領域確保した配列型変数は後述の参照型変数(オブジェクト変数)のように参照値を保持していないので、参照の値渡しはできないと記載しました。
Rustの参照型変数は、本記事のポインタ型変数と同様に代入した参照(メモリアドレス値)を保持する値型変数なので、ポインタ渡しの欄に記載しました。
Pythonが参照渡しできないことは公式プログラミングFAQに記載されています。
出力引数のある関数 (参照渡し) はどのように書きますか?
Python では引数は代入によって渡されます。代入はオブジェクトへの参照を作るだけなので、呼び出し元と呼び出し先にある引数名の間にエイリアスはありませんし、参照渡しそれ自体はありません。
変数の種類
大きくは「値型変数」と「参照型変数」の2種類に分類されます。
値型変数
- 変数は値そのものを保持する
- 代入した値と変数が保持する値は同じ
値型変数(代入した値を保持)
ポインタ型変数(値型変数の一種)
- ポイントされる値(pointee)はメモリ上のどこかにあり、変数(pointer)は値へのポインタ値(例えばメモリアドレス値)を保持して値をポイントする
- 変数にはポインタ値を代入するので、代入した値と変数が保持する値は同じ(値型変数)
- 言語によってはポインタをインクリメントしたり配列アクセスして別の値をポイントすることもできる(危険)
ポインタ型変数(代入したポインタ値を保持) --ポイントする(point)--> 値(pointee)
参考: 人差し指(pointer finger) --指す(point)--> 人(pointee)
参照型変数(オブジェクト型変数)
- 参照されるオブジェクトはメモリ上のどこかにあり、変数は代入したオブジェクトへの参照値を保持してオブジェクトを参照する
- どんな参照値かは言語側が勝手に決める
- 代入した値と変数が保持する値は違う
参照型変数(参照値を保持) --参照する(reference)--> オブジェクト(代入した値)
Pythonではすべての変数が参照型変数で、参照値としてオブジェクトID(オブジェクト識別値)を保持します。
CPython(C言語で実装されたPythonインタプリタ)では、オブジェクトIDとしてオブジェクトのメモリアドレス値を使います。
同じデータ型でも言語によって値型変数か参照型変数かが異なります。
整数型(int型)は、CやJavaでは値型変数ですが、Pythonでは参照型変数です。
配列型は、Javaでは参照型変数ですが、PHPでは価型変数です。
C++では、参照渡しした仮引数(実引数変数を参照する変数)のことを「参照型」と定義しているようですが、この記事ではオブジェクト(代入値)を参照する変数を参照型変数と定義しています。
関数の引数の種類
実引数(変数か値)
- 関数を呼び出す側が引き渡す変数、もしくは値(数値、ポインタ値、オブジェクトなど)
仮引数(変数)
- 呼び出された関数が引き受ける変数
- 関数内で仮引数に代入することもできる
引数への変数の渡し方の種類
大きくは「値渡し」と「参照渡し」の2種類に分類されます。
実引数に変数を指定した場合の仮引数への渡し方の種類です。
実引数に値を指定した場合は値渡し相当で、参照渡しはコンパイルエラーや実行時エラーになります。
値渡し (call by value, pass by value)
- 変数が保持してる値(代入した値とは限らない)のコピーを仮引数に渡す
- 実引数と仮引数は独立変数
- 仮引数に代入しても実引数に影響しない
ポインタ渡し(値渡しの一種)
- ポインタ型変数が保持しているポインタ値(例えばメモリアドレス値)のコピーを仮引数に渡す
- 実引数と仮引数は独立変数
- 仮引数に代入しても実引数に影響しない
- ポイント先の値は共有
- ポイント先の値を変更すると呼び出し元に影響する
参照の値渡し(値渡しの一種、call by object, call by object-sharing)
- 参照型変数が保持している参照値のコピーを仮引数に渡す
- 実引数と仮引数は独立変数
- 仮引数に代入しても実引数に影響しない
- 参照先のオブジェクトは共有
- 参照先のオブジェクト内容を変更すると呼び出し元に影響する
参照渡し (call by reference, pass by reference)
- 実引数(呼出し元変数)に対する参照情報を渡して仮引数と変数共有する
- どのような参照情報を渡してどのように変数共有するかは言語側が勝手に決める
- 実引数と仮引数は同一変数(別名、エイリアス)
- 仮引数に代入すると実引数(呼出し元変数)に代入される
「参照を渡す」を英語にすると pass a reference です。
「参照渡し」は pass by reference で、例えば go by bicycle (自転車で行く)の by で行く手段として自転車を使うことを表現しているように、変数を渡す手段として参照を使うことを表現していると見ることができます。
参考: 引数 - Wiipedia
参照渡し(さんしょうわたし、call by reference)はその実装手段の一つ(と見ることもできる)。変数に対する参照(アドレス情報)を渡す方法である(これは言語側が勝手に行う。C言語のように明示的にアドレス演算子を使うものは参照渡しとは呼ばない)。
私の不確かな記憶では、ポインタ型変数も参照型変数もなかった時代のFORTRANでは関数の実引数に変数を指定するとすべて参照渡し(変数共有)していたので、言語毎の関数に対して「call by value/reference」という用語が使われ、現代の言語では引数毎に値渡しか参照渡しかを指定できるので、引数に対して「pass by value/reference」という用語が使われるようになったのではないかと憶測してます。
あくまで私の個人的な憶測です。
動作確認プログラム
値型変数の値渡し、ポインタ渡し、参照渡し
#include <stdio.h>
void increment(int w, int *x, int *y, int &z) { // wは値渡し、x,yはポインタ渡し、zは参照渡し
w++; // 仮引数(コピーされた値)をインクリメント
x++; // 仮引数(コピーされたメモリアドレス値)をインクリメント
(*y)++; // ポイント先の共有値をインクリメント
z++; // 仮引数(共有変数の値)をインクリメント
}
int main() {
int a = 1;
int b = 1;
int c = 1;
int d = 1;
increment(a, &b, &c, d); // a,dは変数指定、&b,&cは値(メモリアドレス値)指定
printf("%d %d %d %d\n", a, b, c, d); // 1 1 2 2
}
参照型変数の参照の値渡し、参照渡し
<?php
function increment(object $x, object $y, object &$z) { // x,yは参照の値渡し、zは参照渡し
$x->value = $x->value + 1; // 共有オブジェクト内部に代入
$y = (object)['value' => $y->value + 1]; // 仮引数(独立変数)に代入
$z = (object)['value' => $z->value + 1]; // 仮引数(共有変数)に代入
}
$a = (object)['value' => 1];
$b = (object)['value' => 1];
$c = (object)['value' => 1];
increment($a, $b, $c);
echo "{$a->value} {$b->value} {$c->value}\n"; // 2 1 2
情報処理試験問題例
情報処理試験では、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
まとめ
値やオブジェクトへの参照を渡すことを「参照渡し」と説明しているブログや記事が沢山ありますが、情報処理用語の「参照渡し」とは違う説明です。
情報処理用語の「値渡し」「参照渡し」は、関数の引数に変数を渡すときに使われる用語で、変数2種類と渡し方2種類を組み合わせて以下の4種類があります。
- 値型変数の値渡し(値コピー)
- 値型変数の参照渡し(変数共有)
- 参照型変数の値渡し(オブジェクト共有)
- 参照型変数の参照渡し(変数共有)
みなさんが使用している言語ではどれができるか正しく理解・説明できますでしょうか。
用語の本来の使い方を理解して、話し相手と話がかみ合わない時の参考にしていただけたら幸いです。