Cpp 参照

提供:yonewiki
2022年9月26日 (月) 11:02時点におけるYo-net (トーク | 投稿記録)による版 (ページの作成:「C++に戻る <table class="mbox-small" style="border:1px solid #aaa; background-color:#f9f9f9; width:22em;" id="RealTitleBanner"> <tr> <td style="width:1px;"></td> <td class="mbox-text plainlist" style="">本来の表記は「<b><span id="RealTitle" style="font-size:large;">C++(Cpp) 参照</span></b>」です。この記事に付けられた題名は{{記事名の制約}}から不正確なものとなっています。</td> </tr>…」)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

C++に戻る


本来の表記は「C++(Cpp) 参照」です。この記事に付けられた題名はテンプレート:記事名の制約から不正確なものとなっています。

※このページではC++にのみ存在する機能として、記事タイトルがC++ 参照になっています。

参照

ポインタはアドレスの中身を保持するアドレス変数でしたが、C++では参照型と呼ばれるアドレス中身を直接アクセスするための型が定義できます。 参照型には型名の後ろに&記号をつけて宣言します。宣言の記述形式はポインタと変わりません。具体的な使用例は以下のようになります。

int nSize = 256
int& refnSize = nSize;

printf("nSize    Addres->%x Value->%d",&refnSize,refnSize);
printf("refnSize Addres->%x Value->%d",&refnSize,refnSize);

出力結果

nSize    Addres->50f840 Value->256
refnSize Addres->50f840 Value->256

変数のアドレスも値も、元の変数と同じものになります。これは関数の引数としても使えます。書き換えられることが分かっていれば、問題ないですが、特に意識しない場合は予期しない動作にも感じ取れます。


関数の引数の型に参照を示すint&のように参照を示す&記号が付いてるかどうかは使う関数の宣言や定義部分を確認するまでは、知ることができません。頻繁に使っていれば、事前に認識もしている可能性はあります。もしくはVisualStudioのようなコードインテリセンス機能が働いて、引数の型が明示されることで、認識するパターンもあると思います。


参照の引数を取る関数は具体的には以下のようなプログラムになります。

#include "stdafx.h"
#include "Ref001.h"

void RefPrint(int& refnInt){

  printf("\n");
  printf("********************************************************\n");
  printf("* RefPrint(int& refnInt)参照型\n");
  printf("* RefPrint関数 refnInt Addres->%x refnInt->%d\n",&refnInt,refnInt);
  
  refnInt = 512;
  printf("* RefPrint関数内で引数の値変更を実施 →512へ\n");

  printf("*\n");
  printf("* RefPrint(int& refnInt)\n");
  printf("* RefPrint関数 refnInt Addres->%x refnInt->%d\n",&refnInt,refnInt);
  printf("********************************************************\n");
}

void ValPrint(int valnInt){

  printf("\n");
  printf("********************************************************\n");
  printf("* ValPrint(int valnInt)通常型\n");
  printf("* ValPrint関数 valnInt Addres->%x valnInt->%d\n",&valnInt,valnInt);
  
  valnInt = 1024;
  printf("* ValPrint関数内で引数の値変更を実施 →1024へ\n");

  printf("*\n");
  printf("* ValPrint(int valnInt)\n");
  printf("* ValPrint関数 valnInt Addres->%x valnInt->%d\n",&valnInt,valnInt);
  printf("********************************************************\n");
}

void PointPrint(int* pnInt){

  printf("\n");
  printf("********************************************************\n");
  printf("* PointPrint(int* pnInt)通常型\n");
  printf("* PointPrint関数 pnInt Addres->%x pnInt->%d &pnInt->%x\n",pnInt,*pnInt,&pnInt);
  
  *pnInt = 2048;
  printf("* PointPrint関数内で引数の値変更を実施 →2048へ\n");

  printf("*\n");
  printf("* PointPrint(int* pnInt)\n");
  printf("* PointPrint関数 pnInt Addres->%x pnInt->%d &pnInt->%x\n",pnInt,*pnInt,&pnInt);
  printf("********************************************************\n");
}

int _tmain(int argc, _TCHAR* argv[])
{
  int nSize = 256;
  int& refnSize = nSize;
  printf("Main\n");
  printf("nSize    Addres->%x Value->%d\n",&nSize,nSize);
  printf("refnSize Addres->%x Value->%d\n",&refnSize,refnSize);

  RefPrint(nSize);

  printf("\n");
  printf("Main\n");
  printf("nSize    Addres->%x Value->%d\n",&nSize,nSize);
  printf("refnSize Addres->%x Value->%d\n",&refnSize,refnSize);

  ValPrint(nSize);

  printf("\n");
  printf("Main\n");
  printf("nSize    Addres->%x Value->%d\n",&nSize,nSize);
  printf("refnSize Addres->%x Value->%d\n",&refnSize,refnSize);

  PointPrint(&nSize);

  printf("\n");
  printf("Main\n");
  printf("nSize    Addres->%x Value->%d\n",&nSize,nSize);
  printf("refnSize Addres->%x Value->%d\n",&refnSize,refnSize);
  return 0;
}

結果出力

Main
nSize    Addres->37f82c Value->256
refnSize Addres->37f82c Value->256

********************************************************
* RefPrint(int& refnInt)参照型
* RefPrint関数 refnInt Addres->37f82c refnInt->256
* RefPrint関数内で引数の値変更を実施 →512へ
*
* RefPrint(int& refnInt)
* RefPrint関数 refnInt Addres->37f82c refnInt->512
********************************************************

Main
nSize    Addres->37f82c Value->512
refnSize Addres->37f82c Value->512

********************************************************
* ValPrint(int valnInt)通常型
* ValPrint関数 valnInt Addres->37f74c valnInt->512
* ValPrint関数内で引数の値変更を実施 →1024へ
*
* ValPrint(int valnInt)
* ValPrint関数 valnInt Addres->37f74c valnInt->1024
********************************************************

Main
nSize    Addres->37f82c Value->512
refnSize Addres->37f82c Value->512

********************************************************
* PointPrint(int* pnInt)通常型
* PointPrint関数 pnInt Addres->37f82c pnInt->512 &pnInt->37f74c
* PointPrint関数内で引数の値変更を実施 →2048へ
*
* PointPrint(int* pnInt)
* PointPrint関数 pnInt Addres->37f82c pnInt->2048 &pnInt->37f74c
********************************************************

Main
nSize    Addres->37f82c Value->'''2048'''
refnSize Addres->37f82c Value->2048

上記のような結果になります。


通常型の関数では、引数を受け取ると、新しくint型の変数のメモリを確保し、そのアドレスの中に引数の値を格納していて、値を関数内で書き換えても呼び出し元の変数には影響しないことがわかると思います。参照型の変数はそのまま使うので、関数内での書き換えであっても元の値が直接変化します。関数の戻り値がないvoid型の関数であるにも関わらず、関数内での処理結果を返すことが出来ているとも言えます。ポインタ型の引数でも参照と同じように元の値のアドレスを受け取るので、元の値の中身を書き換えることができます。但し、呼び出しの際には、引数に&nSizeとしてアドレスを渡しているので、関数を呼び出す側は書き換えられる可能性の意識を明確にして関数を利用します。そして関数の動作自体もポインタを引数にした場合は通常の引数と同様に新しくint型のポインタ変数のメモリを確保して、そのアドレスの中にアドレスを格納しています。したがって関数内で使われている引数のアドレス&pnIntは37f74cとなっていて、pnIntは37F82cが入っています。そして、その中身が*pnIntで確認できるという仕組みになっています。


なぜ、このような一見、危険とも思える参照という引数の型を使えるのか?それにはおそらくそのもっと先の意識しないでも書き換えして欲しいケースを実現することにあるのだと思います。ちゃんと関数の意味を知っていれば便利な引数なので、よく使われてもいると思います。


次のCpp 右辺値参照という型にも発展していくものになっています。


C++に戻る