なんで入出力を>>と<<で出来るんだ?
私が初めて勉強したプログラミング言語でした。その後、C++を学習しようとして行き詰った(疑問に思った)事があります。それが「なんで入出力を>>と<<で出来るんだ?」って事です。
この記事では、C++の入出力に関して私なりに考察をしていこうと思います。最後まで読んでいただければ幸いです。
参考までに、整数の入力を受け、それをそのまま出力するプログラムをC言語とC++で書いてみます。まずはC言語。
#include <stdio.h>
int main(void){
int a = 0;
scanf("%d", &a);
printf("%d", a);
}
scanf関数でスキャンした内容を、printf関数で出力する。実に分かりやすいですね!
続いてC++
#include <iostream>
int main(void){
int a = 0;
std::cin >> a;
std::cout << a;
}
えーっと。なんで「>>」で入力、「<<」で出力になるんです?
本題に入る前に
演算子のオーバーロード
虚数を扱うクラスを作ってみた
入出力について語る前に、演算子のオーバーロードというものについて話す必要があります。まずは以下のサンプルを見てください。
このサンプルでは虚数を扱うクラス「CNumber」というものを定義しています。実部を扱う「r_」と虚部を扱う「i_」というメンバーがあり、str関数ではそれを「a+bi」の形のテキストに変換しています。
#include <iostream>
#include <string>
class CNumber {
public:
CNumber(int r, int i) {
r_ = r;
i_ = i;
}
inline std::string str();
private:
int r_;
int i_;
};
inline std::string CNumber::str() {
if (i_ >= 0)
return std::to_string(r_) + "+" + std::to_string(i_) + "i";
else
return std::to_string(r_) + "-" + std::to_string(-i_) + "i";
}
int main(void) {
CNumber a(1, 2);
CNumber b(3, 4);
std::cout << "a=" << a.str() << "\n";
std::cout << "b=" << b.str() << "\n";
return 0;
}
実行結果は以下のようになります。
a=1+2i
b=3+4i
注意事項:constにすべきでは?
str関数に関して、「これはconstにすべきでは?」と思ったそこの貴方、その通りです。
これは本来以下のようにするべきです。
inline std::string CNumber::str() const {
return std::to_string(r_) + "+" + std::to_string(i_) + "i";
}
しかしながら、この記事では出来る限りサンプルコードを短くすべく、constは付けない方針で行こうと思います。予めご了承ください。
虚数の掛け算をしたい
では、今作ったCNumber
型の変数同士の掛け算をしたいときはどうすればいいでしょうか? こうすればいいでしょうか?
int main(void) {
CNumber a(1, 2);
CNumber b(3, 4);
CNumber c = a * b; // 掛け算!
std::cout << "a=" << a.str() << "\n";
std::cout << "b=" << b.str() << "\n";
std::cout << "c=" << c.str() << "\n";
return 0;
}
これをコンパイルするとエラーになります。当然ですよね、だってCNumber
同士の掛け算のやり方をコンパイラは知りませんから。
ではどうしましょうか? 仕方がないので、専用の関数「mul」を定義してみましょうか。
#include <iostream>
#include <string>
class CNumber {
public:
CNumber(int r, int i) {
r_ = r;
i_ = i;
}
inline std::string str();
CNumber mul(CNumber& value);
private:
int r_;
int i_;
};
inline std::string CNumber::str() {
if (i_ >= 0)
return std::to_string(r_) + "+" + std::to_string(i_) + "i";
else
return std::to_string(r_) + "-" + std::to_string(-i_) + "i";
}
CNumber CNumber::mul(CNumber& value) {
int r = r_ * value.r_ - i_ * value.i_;
int i = r_ * value.i_ + i_ * value.r_;
return CNumber(r, i);
}
int main(void) {
CNumber a(1, 2);
CNumber b(3, 4);
CNumber c = a.mul(b);
std::cout << "a=" << a.str() << "\n";
std::cout << "b=" << b.str() << "\n";
std::cout << "c=" << c.str() << "\n";
return 0;
}
実行結果は以下のようになります。
a=1+2i
b=3+4i
c=-5+10i
想定通りの結果になりました! これで虚数同士の掛け算を行う事が出来ますね。
しかしながら、毎回掛け算するときにa.mul(b)
のような書き方をしないといけないのは面倒です。「*」を使えたらいいのになあ……。そこで登場するのが演算子のオーバーロードです。
オーバーロードは難しくない!
演算子のオーバーロードというものを使えば、a.mul(b)と書く代わりに「a * b」と書けるようになります。
その方法はいたってシンプルで、先ほどmul
と書いていた部分をoperator*
に書き換えるだけです。
そのコードを以下に示します。
#include <iostream>
#include <string>
class CNumber {
public:
CNumber(int r, int i) {
r_ = r;
i_ = i;
}
inline std::string str();
CNumber operator*(CNumber& value);
private:
int r_;
int i_;
};
inline std::string CNumber::str() {
if (i_ >= 0)
return std::to_string(r_) + "+" + std::to_string(i_) + "i";
else
return std::to_string(r_) + "-" + std::to_string(-i_) + "i";
}
CNumber CNumber::operator*(CNumber& value) {
int r = r_ * value.r_ - i_ * value.i_;
int i = r_ * value.i_ + i_ * value.r_;
return CNumber(r, i);
}
int main(void) {
CNumber a(1, 2);
CNumber b(3, 4);
CNumber c = a * b;
std::cout << "a=" << a.str() << "\n";
std::cout << "b=" << b.str() << "\n";
std::cout << "c=" << c.str() << "\n";
return 0;
}
実行結果は以下のようになります。
a=1+2i
b=3+4i
c=-5+10i
想定通りの結果になりました! これで虚数同士の掛け算を普通の数字のように行えるようになりました!
関数のオーバーロード
printfに代わる関数が欲しい
突然ですが、printf関数に関して"%d"
とか%f
とか使い分けるのって、慣れるまでは難しくないですか?
そこであなたは「"%d"
とか覚えなくてもいい感じに表示してくれる関数があれば、初心者にとって分かりやすいんじゃない?」と思ったとしましょう。
こんなプログラムを書いてみました。
#include <stdio.h>
void print_int(int a) { printf("%d", a); }
void print_float(float a) { printf("%f", a); }
必要かどうかはさておき、プログラムとして成立はしています。
もっとシンプルに
C言語ならこれでもOKなのですが、C++だともっとシンプルにこんな事が出来ちゃいます。
#include <stdio.h>
void print(int a) { printf("%d", a); }
void print(float a) { printf("%f", a); }
そうです、なんと同名の関数を複数定義できてしまいます。
このように、同名の関数(引数は異なる)を定義することを関数のオーバーロードと言います。
やっと本題
ここまで「演算子のオーバーロード」と「関数のオーバーロード」を見てきました。この知識があれば、coutの内部で起こっている事を、自前で再現できちゃいます!
coutを自作してみよう!
ベースとなるプログラム
まずはこんなプログラムを書いてみました。
#include <stdio.h>
class OutPut {
public:
void operator<<(int a);
void operator<<(float a);
void operator<<(const char* a);
};
void OutPut::operator<<(int a) { printf("%d", a); }
void OutPut::operator<<(float a) { printf("%f", a); }
void OutPut::operator << (const char* a) { printf(a); }
OutPut cout;
int main(void) {
cout << 1;
cout << "\n";
cout << 3.14f;
return 0;
}
実行結果
1
3.140000
三種類のoperator<<
を作りました。演算子のオーバーロードについて、関数のオーバーロードをしている訳ですね!
「ちょっと待った、operator<<
って何?」
そう思った方もいるのではないでしょうか? これは「シフト演算子」という演算子でして、a<<b
と書くことで「$a×2^b$」を計算できます。これはC言語にも備わっている演算子ですね。
実行結果も想定通りです。しかしながら、このままではcout << 1 << "\n" << 3.14f
のような書き方を出来ません。どうすればいいでしょうか?
自身への参照
それを実現するためには、OutPutクラスを以下のように書き替えます。
class OutPut {
public:
OutPut& operator<<(int a);
OutPut& operator<<(float a);
OutPut& operator<<(const char* a);
};
OutPut& OutPut::operator<<(int a) {
printf("%d", a);
return *this;
}
OutPut& OutPut::operator<<(float a) {
printf("%f", a);
return *this;
}
OutPut& OutPut::operator<<(const char* a) {
printf(a);
return *this;
}
<<
演算子を行うと、自身への参照を返すようにしました。参照とは「自身そのもの」です。コピーではなく、別名を作っているイメージです。
試しに以下のようなコードを動かすと、coutとout1~4が同じアドレス(メモリ上の空間)を指している事を確認できます。
OutPut& out1 = cout << 1;
OutPut& out2 = cout << "\n";
OutPut& out3 = cout << 3.14f;
OutPut& out4 = cout << "\n";
printf("Address of cout is 0x%p.\n", &cout);
printf("Address of out1 is 0x%p.\n", &out1);
printf("Address of out2 is 0x%p.\n", &out2);
printf("Address of out3 is 0x%p.\n", &out3);
printf("Address of out4 is 0x%p.\n", &out4);
coutとout1~3
が全く同じものなら、以下のように書いてもOKな訳です。
int main(void) {
OutPut& out1 = cout << 1;
OutPut& out2 = out1 << "\n";
OutPut& out3 = out2 << 3.14f;
OutPut& out4 = out3 << "\n";
return 0;
}
これを一行で書くと、こうなります。
(((cout << 1) << "\n") << 3.14f) << "\n";
そして不要な括弧を外すと見慣れたあの形になる訳です。
cout << 1 << "\n" << 3.14f << "\n";
全ソースコード
#include <stdio.h>
class OutPut {
public:
OutPut& operator<<(int a);
OutPut& operator<<(float a);
OutPut& operator<<(const char* a);
};
OutPut& OutPut::operator<<(int a) {
printf("%d", a);
return *this;
}
OutPut& OutPut::operator<<(float a) {
printf("%f", a);
return *this;
}
OutPut& OutPut::operator<<(const char* a) {
printf(a);
return *this;
}
OutPut cout;
int main(void) {
cout << 1 << "\n" << 3.14f << "\n";
return 0;
}