🙄

値渡し、ポインタ渡し、オブジェクト渡し、参照渡しの言語別対応表と用語説明

に公開
2

関数の実引数に変数を指定したとき、仮引数にどんな渡し方ができるかの言語別対応表と、関連用語の説明を書いてみました。
値渡しと参照渡しの理解の助けになりましたら幸いです。

言語別対応表

\ 渡し方
  \
言語 \
値渡し
(値コピー)
 
ポインタ渡し
(ポインタ値コピー
値共有)
参照の値渡し
(参照値コピー
オブジェクト共有)
参照渡し
(コピーなし
変数共有)
C# できる
(int arg)
できる
(int *arg)
できる
(Object arg)
できる
(ref int arg)
Swift できる
(arg: Int)
できる できる できる
(arg: inout Int)
PHP できる
(int $arg)
できない できる
(object $arg)
できる
(int &$arg)
C++ できる
(int arg)
できる
(int *arg)
できない できる
(int &arg)
C できる
(int arg)
できる
(int *arg)
できない できない
Rust できる
(arg: i32)
できる
(arg: &i32)
できない できない
Go できる
(arg int)
できる
(arg *int)
できる
(arg []int)
できない
Java
JavaScript
TypeScript
できる
(int arg)
できない できる
(Object arg)
できない
Python できない できない できる
(arg: int)
できない
Ruby できない できない できる
(arg: Integer)
できない

変数の種類

大きくは「値型変数」と「参照型変数」の2種類に分類されます。

値型変数

  • 変数は値そのものを保持する
  • 代入した値と変数が保持する値は同じ

ポインタ型変数(値型変数の一種)

  • ポイントされる値(pointee)はメモリ上のどこかにあり、変数(pointer)は値へのポインタ値(例えばメモリアドレス値)を保持して値をポイントする
  • 変数にはポインタ値を代入するので、代入した値と変数が保持する値は同じ(値型変数)
  • 言語によってはポインタをインクリメントしたり配列アクセスして別の値をポイントすることもできる(危険)

参照型変数(オブジェクト型変数)

  • 参照されるオブジェクトはメモリ上のどこかにあり、変数は代入したオブジェクトへの参照値を保持してオブジェクトを参照する
    • どんな参照値かは言語側が勝手に決める
  • 代入した値と変数が保持する値は違う

関数の引数の種類

実引数(値か変数)

  • 関数を呼び出す側が引き渡す値(数値、ポインタ値、オブジェクトなど)、もしくは変数。
function(123);    // 値を渡す
int value = 123;
function(value);  // 変数を渡す

変数を渡す場合に、変数の値を渡す「値渡し」と、変数そのものを渡す(呼び出し元変数を参照する)「参照渡し」があります。

仮引数(変数)

  • 呼び出された関数が引き受ける変数
  • 関数内で仮引数に代入することもできる

引数への変数の渡し方の種類

実引数に変数を指定した場合、「値渡し」と「参照渡し」の2種類に分類されます。
変数の「値」を渡すのが「値渡し」、「変数」自体を渡す(呼出し元変数を参照する)のが「参照渡し」です。
実引数に値を指定した場合は値渡し相当で、参照渡しはコンパイルエラーや実行時エラーになります。

値渡し (call by value, pass by value)

  • 変数が保持してる値を仮引数に渡す
    • 値型変数は値のコピーを渡す
      • 関数内で値を変更しても呼出し元に影響しない
    • 参照型変数は参照値のコピーを渡して値を共有する(参照の値渡し)
      • 関数内で値を変更すると呼出し元に影響する
  • 実引数と仮引数は独立変数
    • 仮引数に代入しても実引数に影響しない

ポインタ渡し(値渡しの一種、call by adress)

  • ポインタ型変数が保持しているポインタ値(例えばメモリアドレス値)のコピーを仮引数に渡す
  • 実引数と仮引数は独立変数
    • 仮引数に代入しても実引数に影響しない
  • ポイント先の値は共有
    • ポイント先の値を変更すると呼び出し元に影響する

参照の値渡し(値渡しの一種、call by object, call by object-sharing)

  • 参照型変数が保持している参照値のコピーを仮引数に渡す
  • 実引数と仮引数は独立変数
    • 仮引数に代入しても実引数に影響しない
  • 参照先のオブジェクトは共有
    • 参照先のオブジェクト内容を変更すると呼び出し元に影響する

参照渡し (call by reference, pass by reference)

  • 実引数(呼出し元変数)に対する参照情報を渡して仮引数と変数共有する
    • どのような参照情報を渡してどのように変数共有するかは言語側が勝手に決める
  • 実引数と仮引数は同一変数(別名、エイリアス)
    • 仮引数に代入すると実引数(呼出し元変数)に代入される

動作確認プログラム

値型変数の値渡し、ポインタ渡し、参照渡し

C++
#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
}

ポインタ型変数の値渡し、参照渡し

C++
#include <stdio.h>

void increment(int *x, int *&y) {  // xは値渡し、yは参照渡し
    (*x)++;  // 共有されたint型変数の値をインクリメント
    x++;     // コピーされたポインタ型変数をインクリメント
    (*y)++;  // 共有されたint型変数の値をインクリメント
    y++;     // 共有されたポインタ型変数をインクリメント
}

int main(void) {
    int values[] = {1, 22, 333};
    int *p0 = &values[0];
    int *p1 = &values[1];
    increment(p0, p1);
    printf("%d %d\n", values[0], *p0);  // 2 2
    printf("%d %d\n", values[1], *p1);  // 23 333
}

参照型変数の参照の値渡し、参照渡し

PHP
<?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 を「参照呼出し」と表記しています。

https://www.ap-siken.com/kakomon/25_haru/q20.html

結果検証プログラム

C++
#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種類があります。

  1. 値型変数の値渡し
  2. 値型変数の参照渡し
  3. 参照型変数の値渡し
  4. 参照型変数の参照渡し

みなさんが使用している言語ではどれができるか正しく理解・説明できますでしょうか。
用語の本来の使い方を理解して、話し相手と話がかみ合わない時の参考にしていただけたら幸いです。

Discussion

junerjuner

mdn に念押しされていますね。

引数は常に値渡しであり、決して参照渡しではありません。これは、関数が引数に再代入を行っても、関数外では値が変更されないということです。より正確には、オブジェクト引数は共有渡しです。つまり、オブジェクトのプロパティが変更される場合、その変更は関数外にも影響するということです。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions#引数の受け渡し

shiracamusshiracamus

@juner さん、
記事からリンクさせていただきました。ありがとうございます。