C++OpenSiv3D入門講座 Vol. 05 関数オーバーロード・range-based-for・constメンバ関数
今回は関数オーバーロードと、range-based-forについて説明する。
関数オーバーロード
C++では、引数が異なる同じ名前の関数を定義できる。そのように関数を多重定義することを関数オーバーロードという。 実行中に、引数によって呼ばれる関数が選択される。
#include <iostream> //絶対値を返す関数、myAbsを3通りにオーバーロードする int myAbs(int n){ std::cout << "int型のmyAbsが呼ばれました" << std::endl; if(n > 0){ return n; }else{ return -n; } } float myAbs(float n){ std::cout << "float型のmyAbsが呼ばれました" << std::endl; if(n > 0){ return n; }else{ return -n; } } double myAbs(double n){ std::cout << "double型のmyAbsが呼ばれました" << std::endl; if(n > 0){ return n; }else{ return -n; } } int main(){ int a = -10; float b = -2.71f; double c = -3.14; std::cout << myAbs(a) << std::endl << std::endl; std::cout << myAbs(b) << std::endl << std::endl; std::cout << myAbs(c) << std::endl << std::endl; return 0; }
以下は、引数の数によって呼ばれる関数が変わるパターンである。
#include <iostream> void myFunc(int a){ std::cout << "引数1つのmyFuncが呼ばれました。引数は " << a << std::endl; } void myFunc(int a, int b){ std::cout << "引数2つのmyFuncが呼ばれました。引数は " << a << " " << b << std::endl; } int main(){ myFunc(1); myFunc(2, 3); return 0; }
関数オーバーロードと同じ要領で、コンストラクタも複数定義できる。
#include <iostream> class MyClass { public: int value; MyClass() : value{ 0 } { std::cout << "引数なしのコンストラクタが呼ばれました" << std::endl; } MyClass(int _value) : value{ _value } { std::cout << "引数ありのコンストラクタが呼ばれました" << std::endl; } }; int main() { MyClass obj1{ 10 }; std::cout << obj1.value << std::endl; MyClass obj2; std::cout << obj2.value << std::endl; return 0; }
Tips:range-based-for
これまでvectorに対して、for文で用いる時、毎回始めのイテレータと終わりのイテレータを書いていたが、これは面倒である。C++にはrange-based-forがあり、これを用いると、配列のそれぞれの要素に対して行う処理が簡単に書ける。以下に書き方を示す。
for(配列の要素の型 受け取る変数 : 配列名){
}
以下にサンプルコードを用意した。今回は、自分で用意したMyClass型のvectorのそれぞれの要素のaを表示している。配列の要素の型はautoで推論している。constを使うことで値を間違って変更することを防いでおり、参照を用いて、無駄なコピーをが発生しないようにしている。
#include <iostream> #include <vector> class MyClass { public: int a; MyClass(int _a) : a{ _a } { } }; int main() { std::vector<MyClass> vec; for (int i = 0; i < 5; i++) { vec.emplace_back(MyClass{ i }); } for (const auto& i : vec) { std::cout << i.a << std::endl; // コメントを外すと値を書き換えることになるのでコンパイルエラー // i.a = 99; } return 0; }
以下はMyClassのaを全て2倍にする例。参照を用いて、vectorの中身を書き換えている。auto&&と、&が1つではなく2つあるがタイプミスではない。今回の場合auto&でも問題ないが、auto&&を用いると右辺値にも対応できるようになり、より汎用的な書き方になるので、range-based-forを用いる際は基本的にこちらを推奨する。(ちなみに、VisualStudio上でマウスカーソルをiの上に持っていくとわかるが、iはちゃんとMyClass&と推論されていることがわかる)詳しくは、range-based for loopsの要素の型について - Qiita参照。
#include <iostream> #include <vector> class MyClass { public: int a; MyClass(int _a) : a{ _a } { } }; int main() { std::vector<MyClass> vec; for (int i = 0; i < 5; i++) { vec.emplace_back(MyClass{ i }); } // 参照を用いてそれぞれのMyClassのaを2倍に for (auto&& i : vec) { i.a *= 2; } // 中身を表示 for (const auto& i : vec) { std::cout << i.a << std::endl; } return 0; }
Tips:constメンバ関数
constメンバ関数を使うと、メンバ変数を変更できないメンバ関数を作ることができる。これを用いることで、誤ってメンバを変更することを防ぐことができ、また多人数開発の際にメンバ変数を変更していないことを明示できる。
#include <iostream> class MyClass { public: int a; MyClass(int _a); void func() const; }; MyClass::MyClass(int _a) : a{ _a } { } void MyClass::func() const { // コメントを外すとメンバ変数を変更してしまうのでコンパイルエラー // a = 100; std::cout << a << std::endl; } int main() { MyClass obj{ 10 }; obj.func(); return 0; }
まとめ
- 引数が異なる同じ名前の関数を定義でき、実行中に引数によって呼ばれる関数が選択される(関数オーバーロード)
- C++にはrange-based-forがあり、配列のそれぞれの要素に対して行う処理が簡単に書ける。
- constメンバ関数は、メンバ変数を変更できないメンバ関数である。