ITエンジニア日記 ~NO SKILL, NO LIFE~

学んだ技術や、気になることをアウトプットしていきます。プログラミング, インフラ, etc...

値渡し と 参照渡し の違い

「値渡し」と「参照渡し」はプログラミングにおいて、メソッドや関数への引数の渡し方の種類を表す用語です。

今回は「値渡し」と「参照渡し」のそれぞれの仕組みをC++で紹介します。

※用語として、メソッド/関数の引数は仮引数、メソッド/関数を呼び出す際に渡す変数は実引数といいます。

[目次]

1. 値渡し

値渡しは実引数の値を仮引数にコピーして渡す方法です。

f:id:masakiXX0:20191031222640j:plain
foo : 実引数 bar : 仮引数

下記の例ではhogeという関数の引数 a と b に、変数 x と y の値をコピーした値が渡されます。

void hoge(int a, int b)
{
    std::cout << "[hoge]_before\n";
    std::cout << "a=" + std::to_string(a) + "\n";
    std::cout << "b=" + std::to_string(b) + "\n\n";

    a += 1;  // 引数aに1を足す
    b *= 2;  // 引数bに2を掛ける

    std::cout << "[hoge]_after\n";
    std::cout << "a=" + std::to_string(a) + "\n";
    std::cout << "b=" + std::to_string(b) + "\n\n";
}

int main()
{
    int x = 10;
    int y = 5;

    std::cout << "[main]_befor\n";
    std::cout << "x=" + std::to_string(x) + "\n";
    std::cout << "y=" + std::to_string(y) + "\n\n";

    hoge(x, y);

    std::cout << "[main]_after\n";
    std::cout << "x=" + std::to_string(x) + "\n";
    std::cout << "y=" + std::to_string(y) + "\n\n";
}

実行結果は次の通りです。

[main]_befor
x=10
y=5

[hoge]_before
a=10
b=5

[hoge]_after
a=11
b=10

[main]_after
x=10
y=5

関数hogeの中では引数 a と b に 、mainから渡された値 10 と 5 のコピーが渡されます。([hoge]_before

hogeの中では渡された引数を使って正しい計算が行われていることがわかります。([hoge]_after

しかし、関数hogeからmainに戻ってきて変数 x と y の値を確認すると、値が変わっていないことがわかります。([main]_after

このことから、値渡しでは「仮引数を変更しても実引数には反映されない」ということがわかります。

2. 参照渡し

参照渡しは仮引数が、実引数のメモリ領域を指し示すようになります。

f:id:masakiXX0:20191031223608j:plain
foo : 実引数 bar : 仮引数

下記の例ではhogeという関数の引数 a は、変数 x と同じメモリ領域を指し示し、引数 b は、変数 y と同じメモリ領域を指し示しめしています。

void hoge(int& a, int& b)
{
    std::cout << "[hoge]_before\n";
    std::cout << "a=" + std::to_string(a) + "\n";
    std::cout << "b=" + std::to_string(b) + "\n\n";

    a += 1;  // 引数aに1を足す
    b *= 2;  // 引数bに2を掛ける

    std::cout << "[hoge]_after\n";
    std::cout << "a=" + std::to_string(a) + "\n";
    std::cout << "b=" + std::to_string(b) + "\n\n";
}

int main()
{
    int x = 10;
    int y = 5;

    std::cout << "[main]_befor\n";
    std::cout << "x=" + std::to_string(x) + "\n";
    std::cout << "y=" + std::to_string(y) + "\n\n";

    hoge(x, y);

    std::cout << "[main]_after\n";
    std::cout << "x=" + std::to_string(x) + "\n";
    std::cout << "y=" + std::to_string(y) + "\n\n";
}

実行結果は次の通りです。

[main]_befor
x=10
y=5

[hoge]_before
a=10
b=5

[hoge]_after
a=11
b=10

[main]_after
x=11
y=10

関数hogeの引数 a と b は、実引数 x と y のメモリ領域を指し示すため、それぞれ 10 と 5 が表示されています。([hoge]_before

hogeの中計算が行われると、a と b の値は更新されます。([hoge]_after

関数hogeからmainに戻ってきたとき変数 x と y の値を確認すると、値が更新されていることがわかります。([main]_after

このことから、参照渡しでは「仮引数を変更すると、実引数にも反映される」ということがわかります。

3. (おまけ) ポインタ渡し

C言語C++にはポインタ渡し(アドレス渡し)という渡し方があります。これは、実引数が指し示すアドレスを仮引数にコピーして渡す方法です。

f:id:masakiXX0:20191031225523j:plain
foo : 実引数 bar : 仮引数

ポインタ渡しを使用した例は次のコードです。

void hoge(int* a, int* b)
{
    std::cout << "[hoge]_before\n";
    std::cout << "a=" + std::to_string(*a) + "\n";
    std::cout << "b=" + std::to_string(*b) + "\n\n";

    *a += 1;  // 引数aに1を足す
    *b *= 2;  // 引数bに2を掛ける

    std::cout << "[hoge]_after\n";
    std::cout << "a=" + std::to_string(*a) + "\n";
    std::cout << "b=" + std::to_string(*b) + "\n\n";
}

int main()
{
    int x = 10;
    int y = 5;

    std::cout << "[main]_befor\n";
    std::cout << "x=" + std::to_string(x) + "\n";
    std::cout << "y=" + std::to_string(y) + "\n\n";

    hoge(&x, &y);

    std::cout << "[main]_after\n";
    std::cout << "x=" + std::to_string(x) + "\n";
    std::cout << "y=" + std::to_string(y) + "\n\n";
}

mainの中で関数hogeに渡している実引数についている" & "アドレス演算子、関数hogeの仮引数についている" * "ポインタ宣言子といいます。

実行結果は次の通りです。

[main]_befor
x=10
y=5

[hoge]_before
a=10
b=5

[hoge]_after
a=11
b=10

[main]_after
x=11
y=10

参照渡しと同様に、「仮引数を変更すると、実引数にも反映される」ということがわかります。

C++ではポインタ渡しと参照渡しの使い分けとして、値がnullになる可能性があるものはポインタ渡しを使い、それ以外は参照渡しを使います。(参照渡しにnullを渡すことはできないため。)

よって、ポインタ渡しで渡ってきたときは、nullチェックを実施するという運用が必要です。

bool foo(int* a)
{
    // aに対してNULLチェックを実施
    if (a == nullptr)
    {
        return false;
    }

    *a += 1;  // 引数aに1を足す

    return true;
}

int main()
{
    int x = 10;

    foo(&x);

    std::cout << "x=" + std::to_string(x) + "\n";
}

4. まとめ

「値渡し」と「参照渡し」について紹介しました。言語によって多少差はありますが、基本的な考え方は同じなのでぜひ覚えておいてください。