C++における参照を説明する.基本的に参照は変数名を変える機能であり,現代的なC++において決定的な役割を果たす.実用的なプログラムを作りたいものは覚えるべきである.に例を示す.
参照による変数名の変更
#include <iostream>
using namespace std;
int main(){
int a = 1;
int &b = a;
b = 2;
std::cout << a << "\n";
return 0;
}
ref.cpp として保存し実行した結果を以下に示す.
6行目のbへの操作によりaの値が変更を受けていることがわかる.aの値にこの変数名で変更を加えたのは4行目だけでこれだけみれば1が出力されるはずであるが,6行目ではaの別名として5行目で宣言されたbに変更を加えているため,この結果が反映される.出力はこの仕組みが反映されたものである.
グローバル領域などの大きな範囲に宣言された変数は,長い変数名として識別しやすいことが望まれる場面が多い.このようば場合でも,みずからのコードでは短い変数名に変更して利用することが,この参照を用いれば可能となる.以下に例を示す.日本語に対応するために,コンパイルにはg++の10版以上が必要である.
#include <iostream>
using namespace std;
int main(){
int 東京都立大学システムデザイン学部情報科学科柴田 = 1;
int &s = 東京都立大学システムデザイン学部情報科学科柴田;
s = 2;
std::cout << s << "\n";
return 0;
}
日本語で書いてあるので,このような狂気じみた変数名が使われるはずがない,と思われるかも知れないが,英単語で似たような程度の変数名は頻繁に見かけるから,この例はそれほど大げさではないと思う.
参照が違う変数名でありながら別の変数を操作できる,という性質は,違う変数同士で同じ記憶域を参照していると解釈することもできる.実際にこの2つの考え方は同じことである.一応覚えておくと良い.この解釈に従えば,参照を用いれば,プログラムを実行するための記憶量を削減することができる.また事実そうである.
参照を使えば,関数の呼び出しにおいて,仮引数を操作することで実引数の値を変更することができる.以下に例を示す.
#include <iostream>
using namespace std;
void F(int& c){
c = c + 2;
}
int main(){
int a = 1;
cout << a << "\n";
F(a);
cout << a << "\n";
F(a);
cout << a << "\n";
return 0;
}
この例では,Fの仮り引数のcは3行目に記載の通り&により参照を取るように定義されているため,Fを呼び出すたびに,与えられた実引数のaの値が2増える.実行結果を次に示す.確かに呼び出すごとに値が増えていることを確認できる.
逆に,以下のGを使った例では,aの値は変更を受けない.細かくて気が付きにくいが,3行目のintの後に&が記載されていないことに注意してもらいたい.
#include <iostream>
using namespace std;
void G(int c){
c = c + 2;
}
int main(){
int a = 1;
cout << a << "\n";
G(a);
cout << a << "\n";
G(a);
cout << a << "\n";
return 0;
}
参照をクラスにおいて次の例のように使うこともできる.
#include <iostream>
using namespace std;
struct A{
int x;
A(){
x = 0;
}
A& inc(){
x = x + 1;
return (*this);
}
};
int main(){
A a;
a.inc();
cout << a.x << "\n";
a.inc().inc().inc().inc().inc();
cout << a.x << "\n";
return 0;
}
8行目にある通り,メンバ関数incは呼び出されたときに所属しているオブジェクトの参照を返すようになっている.このため,メンバ関数の呼び出しが実行された場所には,オブジェクトが存在すると見ることができる.そのため,続けてメンバ関数を呼び出すことが可能となる.以下に実行結果を示す.
確かに,inc と書かれた回数だけ,aに対して操作が行われていることを確認できる.これはすべてのincの呼び出しが,aに対して行われていることを意味する.よって意図したとおり動くことが確認できる.
参考までに,自明であるが次の例も示す.この例の通り,連続して呼び出す関数は別の物であっても構わない.
#include <iostream>
using namespace std;
struct A{
int x;
A(){
x = 0;
}
A& inc(){
x = x + 1;
return (*this);
}
A& add100(){
x = x + 100;
return (*this);
}
};
int main(){
A a;
a.inc();
cout << a.x << "\n";
a.inc().add100().inc().inc().add100();
cout << a.x << "\n";
return 0;
}
上記方法を演算子オーバーロードと併用すれば,次のように cout の << のような実装が可能となる.引数の型により呼び出される関数を変えることができるので,bool型の値が渡された場合に別の処理を行えることも示してある.
#include <iostream>
using namespace std;
struct A{
int x;
A(){
x = 0;
}
A& operator<<(int i){
x = x + i;
return (*this);
}
A& operator<<(bool i){
x = x + 100;
return (*this);
}
};
int main(){
A a;
a << 1 << 1 << 1 << true << 1 << 2;
cout << a.x << "\n";
return 0;
}
実行結果を以下に示す.意図した動作をしているとわかる.
cout など ostream の型のオブジェクトに,自作のクラスのオブジェクトを渡して何かしら出力を行いたい場合,以下のように記載すると良い.
#include <iostream>
using namespace std;
struct A{
void print(ostream& os){
os << "(This is an object of class A!)";
}
};
ostream& operator<<(ostream& os, A& a){
a.print(os);
return os;
}
int main(){
A a;
cout << a << "\n";
return 0;
}
14行目を見てわかるように,自作のクラスは普通<<の右側に来る.一方クラスのメンバ関数はクラスのオブジェクトが左側に来るように記載しなければならない.よって,自作のクラスのオブジェクトをcoutに渡したい場合,外部に演算子を定義する必要がある.それが8行目の定義である.8行目の関数の中で,別途用意しておいたaの表示用のメンバ関数に出力先のオブジェクトosを渡して呼び出している.以下に実行結果を示す.このように,ここで説明したことを用いれば,オブジェクトの状態をわかりやすく,14行目を見てわかるように簡潔かつ一貫性のあるコードで出力できるようになる.