Help us understand the problem. What is going on with this article?

値渡し、ポインタ渡し、参照渡しの違い

More than 3 years have passed since last update.

最近C++を扱っていて、上記3つの違いがわかったのでまとめます。
以下のサンプルコードを示します。

#include<iostream>

using namespace std;

class MyClass{
  public:
    int getI();
    void setI(int i);
  private:
    int i;
};

int MyClass::getI(){
  return i;
}

void MyClass::setI(int i){
  this->i = i;
}

void value(int i){
  i++;
  cout << "Value in value: " << i << "\n";    // この関数内のiの値、main関数内のものとは別物。
  cout << "Address in value: " << &i << "\n"; // この関数内のiのアドレス。上記同様main関数のものとは別物。
  cout << "\n";
}

void pointer(int *i){
  // *i++; できない
  *i += 1;
  cout << "Value in pointer: " << i << "\n";     // main関数内のiのアドレス。
  cout << "Address in pointer: " << &i << "\n";  // この関数内のmain関数内のiを指すポインタのアドレス。
  cout << "Pointer in pointer: " << *i << "\n";  // main関数内のiのアドレスに格納された値。
  cout << "\n";
}

void reference(int &i){
  i++;
  cout << "Value in reference: " << i << "\n";    // main関数内のiのアドレスに格納された値, すなわちpointerの1番下のやつと同じ
  cout << "Address in reference: " << &i << "\n"; // main関数内のiのアドレス。
  cout << "\n";
}

void objectValue(MyClass c){
  c.setI(c.getI()+1);
  cout << "Address in objectValue: " << &c << "\n";    // mainとは別物。
  cout << "Value of MyClass::i: " << c.getI() << "\n";
  cout << "\n";
}

void objectPointer(MyClass *c){
  c->setI(c->getI()+1);
  cout << "Address in objectPointer: " << &c << "\n";   // なぜかobjectValueと同じになる。
  // cout << "Pointer in objecPointer: " << *c << "\n"; できない。
  cout << "Value of MyClass::i: " << c->getI() << "\n"; // 逆参照演算子*がいらない。
  cout << "\n";
}

void objectReference(MyClass &c){
  c.setI(c.getI()+1);
  cout << "Address in objectReference: " << &c << "\n"; // main関数と同じ。
  cout << "Value of MyClass::i: " << c.getI() << "\n";
  cout << "\n";
}

int main(){
  int i = 10;
  cout << "Value in main: " << i << "\n";
  cout << "Address in main: " << &i << "\n";
  cout << "\n";
  value(i);
  pointer(&i);
  reference(i);

  MyClass c;
  c.setI(10);
  cout << "Object address in main: " << &c << "\n";
  cout << "Value of i in main: " << c.getI() << "\n";
  cout << "\n";
  objectValue(c);
  objectPointer(&c);
  objectReference(c);
  return 0;
}

実行結果

Value in main: 10
Address in main: 0x7fff5a163e78

Value in value: 11
Address in value: 0x7fff5a163e1c

Value in pointer: 0x7fff5a163e78
Address in pointer: 0x7fff5a163e18
Pointer in pointer: 11

Value in reference: 12
Address in reference: 0x7fff5a163e78

Object address in main: 0x7fff5a163e70
Value of i in main: 10

Address in objectValue: 0x7fff5a163e18
Value of MyClass::i: 11

Address in objectPointer: 0x7fff5a163e18
Value of MyClass::i: 11

Address in objectReference: 0x7fff5a163e70
Value of MyClass::i: 12

それぞれがどのような扱いになるかはソースコードのコメント部分に書きました。まとめると以下のようになると思う。

  • 値渡し
    渡された変数の値が別のアドレスにコピーされる。だから、関数内で変更しても、呼び出しもとの変数の値は変化しない。

  • ポインタ渡し
    渡された変数のアドレスを指すポインタが別のアドレスに割り当てられてそれを通じて呼び出し元の変数の値を操作できる。だから、関数内で変更すると、呼び出し元も変更される。

  • 参照渡し
    渡された変数は呼び出し元の変数と値、アドレス含め全く同じになる。これはコピーではないので当然関数ないで変更すると呼び出し元の値も変化する。

ポインタ渡しが極端に難しい。 ちなみにGolangでは3つ目の書き方ができなかった。

package main

import "fmt"

func value(i int) {
    fmt.Println("Value in value: ", i)
    fmt.Println("Address in value: ", &i)
    fmt.Println()
}

func pointer(i *int) {
    fmt.Println("Value in pointer: ", i)
    fmt.Println("Address in pointer: ", &i)
    fmt.Println("Pointer in pointer: ", *i)
    fmt.Println()
}

func main() {
    i := 10
    fmt.Println("Value in main: ", i)
    fmt.Println("Address in main: ", &i)
    fmt.Println()
    value(i)
    pointer(&i)
}

結果

Value in main:  10
Address in main:  0xc820072220

Value in value:  10
Address in value:  0xc820072270

Value in pointer:  0xc820072220
Address in pointer:  0xc820086020
Pointer in pointer:  10

まとめ

いままでポインタ渡しと参照渡しが混同していてすべて参照渡しと呼んでいたのでそれが間違いだったことに気がついた。プリミティブな方と、自作した型では状況が変わってくる。例えば、自作型ではポインタ渡しでも*なしで値変更出来たりする。あとよくわからないのが自作型の値渡しとポインタ渡しでオブジェクトのアドレスが同じになっている。(追記: これはただ単に値渡しで確保したアドレスが開放されてそこにポインタが割り当てられただけだと考えられる)また、自作型のポインタ渡しではそもそも*付きの変数を参照できない。
あとGolangでは固定長配列が値渡しになっています。それに対して可変長配列であるスライスは参照渡しです。

lon9
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away