[前へ]   [目次へ]   [次へ]

参照(C++)


今回は、参照型についてです。
参照型はC++で追加された機能なので、C++でしか使えません。
これまで、式の評価のところで一部演算子の処理の結果、 & がついた型がしばしば出てきました。
& がついた型は「参照型」と呼び、参照対象の変数(オブジェクト)と同一視するというものです。

一言で参照型について説明してしまうと、「縛られたポインタ型」と言えます。
本質的に参照型はポインタ型であり、ポインタ型の一部機能を無効化したものです。

どう無効化されているかというと、以下のようなところです。
●ポインタ型は参照先を変更することができるが、参照型はできない
●ポインタ型は参照先を意味するために演算子を使うが、参照型は不要(というか使えない)
●ポインタ型は参照先不定で作成できるが、参照型は参照先を指定しないと作成できない

簡単な例を見てみます。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
< 19>
< 20>
< 21>
< 22>
< 23>
< 24>
< 25>
< 26>
< 27>
#include <stdio.h>

int main(void){
   
int *a;//int*型のaを作成
   //int &b;//コンパイルエラー「参照先を指定しなければいけません」
   
int c=10,e=20;
   
int &d=c;//参照先を代入するように定義することで参照先を指定できる。
            //この時cの中身が代入されるわけではなく、「dがcと同一視される」ことを示す。
   
   //ポインタ型
   
a=&e;
   printf(
"*a=%d\n",*a);
   a=&c;
//ポインタ型は後から参照先を変更可能
   
printf("*a=%d\n",*a);//ポインタ型は参照先(c)を意味するために*演算子が必要
   
   //参照型
   
printf("d=%d\n",d);//単にdと書くだけで、参照先(c)を意味する
   
d=30;//同様の理由により、代入すると参照先(c)が変更される
   
printf("c=%d\n",c);
   
   a=&d;
//dへの絶対IDを取得しようとしても、参照先(c)の絶対IDが返されます
   
printf("*a=%d\n",*a);//そのため、ここで表示されるのはcの値となります
   
   //終了待ち
   
getchar();
   
return 0;
実行結果:
*a=20
*a=10
d=10
c=30
*a=30


基本的に、参照型はコンパイラにとってはポインタ型です。
コンパイラは参照型に出くわすと、ポインタ型で言うところの * ポインタ参照演算子を強制実行するようになっています。
(式中で作られる参照型は必ずしもポインタ型実体を持つわけではありませんが概念上は同等です)

上のコードは、コンパイラが以下のように書き換えたとして扱われると、考えることができます。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
< 19>
< 20>
< 21>
< 22>
< 23>
< 24>
< 25>
#include <stdio.h>

int main(void){
   
int *a;
   
int c=10,e=20;
   
int *d=&c;//参照先へのポインタで初期化するように・・・
   
   //ポインタ型
   
a=&e;
   printf(
"*a=%d\n",*a);
   a=&c;
   printf(
"*a=%d\n",*a);
   
   
//参照型
   
printf("d=%d\n",(*d));//強制的に*dとされ、さらに優先順位に惑わされないよう()が付きます
   (*d)=30;//同様の理由により、代入すると参照先(c)が変更されるようになります
   
printf("c=%d\n",c);
   
   a=&(*d);
//dへの絶対IDを取得しようとしても、参照先(c)の絶対IDが返されます
   
printf("*a=%d\n",*a);//そのため、ここで表示されるのはcの値となります
   
   //終了待ち
   
getchar();
   
return 0;
実行結果:
*a=20
*a=10
d=10
c=30
*a=30


これらのコードでは参照型の存在意義が良くわかりませんが、
参照型はもっぱら、関数の引数型などに使うために存在しています。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
#include <stdio.h>

void func(int &n){//(1)
   
n=10;//(2)
}

int main(void){
   
int n2;
   
   func(n2);
//(3)
   
printf("n2=%d\n",n2);

   
//終了待ち
   
getchar();
   
return 0;
実行結果:
n2=10

(1)3行目の引数型を参照型とすると、その関数の呼び出し時((3)10行目)に与える実引数は
「参照型の参照先の実体の指定」に変化します。
一見すると(3)10行目は未初期化の変数の値を使って関数を呼び出しているように見えますが、
実際は n2 を参照先に指定して呼び出しているので、不定の結果に陥ることはありません。

(2)4行目の代入も、 n に指定されている参照先の実体(この場合は main 関数の n2 )への代入です。
ポインタ同様、参照先として指定されている実体のネームスペースは無視して操作されます。

実際には、上記コードはコンパイラが以下のように置き換えて解釈されると考えることができます。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
#include <stdio.h>

void func(int *n){//(1)ポインタ型に変更
   (*n)=10;//(2)強制的に参照する
}

int main(void){
   
int n2;
   
   func(&n2);
//(3)呼び出し元は絶対ID(ポインタ値)を渡すように変更
   
printf("n2=%d\n",n2);

   
//終了待ち
   
getchar();
   
return 0;
実行結果:
n2=10

参照型を用いると、ポインタ型よりもソースがスッキリし、
ポインタ操作におけるミスをかなりの程度抑止することができます。
しかし上記の例のように、暗黙的な置き換えが発生するため、
ソース上、意外なところに出力が発生することがあります。
(上記参照型版の関数の仕様はバグの原因となるため、通常はできるだけ避けた方がよいでしょう)

もちろん参照型も const による書込禁止を付けることができますが、
ポインタと比べると見た目がさっぱりしているため、配列なのか単体なのかといった点や、
const 属性の有無の確認がおろそかになり、バグが気付きにくくなるといった問題があります。

まさに一長一短な関係にあるため、ポインタ型、参照型を状況に応じて使い分けるということが必要です。
以下に一例を示します。

●ポインタ型を使用すべき状況
   ● NULL (無効)を許可する場合:参照型には NULL を意図したように渡すことができないので、強制です。
   ●不定数の要素を持つ配列を渡す場合:配列の参照型は要素数が固定されてしまうため、強制です。
                              この条件に合致する最も良く使うものは、文字列です。
   ●単体情報として出力する場合:一個の情報を出力する場合、参照型にするとソース上で紛らわしいためです。
   ●複雑なデータ構造を直接扱う場合:ポインタ型の使用そのものをもって、慎重に扱うべきという意味を与えます。
                              (当然、プログラマがそのような意識を持つことが前提です)

●参照型を使用すべき状況
   ● NULL (無効)を禁止する場合:ポインタ型の条件の反対です。
                                    ただし、単体情報を出力する場合はポインタ型にすべきです。
   ●構造を扱うクラスオブジェクトを渡す場合:渡すクラスの仕様にもよりますが、
                                    参照型で扱った方が自然に見えるクラス仕様があります。
   ●固定数の要素の配列または単体を入力専用とする場合:出力しない場合は参照型のデメリットがほとんどありません。
                                    忘れずに const を付けて、参照型を選んだ方が有用です。


次回は多次元配列についての予定です。

[前へ]   [目次へ]   [次へ]

プログラミング講座 総合目次

最終更新 2009/04/03