【C++】コンストラクタ関数のオーバーロード
コンストラクタをオーバーロードするメリット
- 柔軟性を得る
- 配列のサポート
- コピーコンストラクタを作成すること
study145.cpp
#include <iostream> using namespace std; class myclass { int x; public: myclass() { x = 0; } myclass( int n ) { x = n; } int getx() { return x; } }; int main() { myclass o1(10); myclass o2; cout << "o1: " << o1.getx() << "\n"; cout << "o2: " << o2.getx() << "\n"; return 0; }
$ ./study145 o1: 10 o2: 0
stdudy149.cpp
#include <iostream> using namespace std; class myclass { int x; public: myclass() { x = 0; } myclass( int n ) { x = n; } int getx() { return x; } void setx( int n ) { x = n; } }; int main() { myclass *p; myclass ob(10); p = new myclass[10]; if( !p ) { cout << "メモリ割り当てエラー\n"; return 1; } for ( int i = 0; i < 10; ++i ) { p[i] = ob; } for ( int i = 0; i < 10; ++i ) { cout << "p[" << i << "]: " << p[i].getx(); cout << "\n"; } return 0; }
$ ./study149 p[0]: 10 p[1]: 10 p[2]: 10 p[3]: 10 p[4]: 10 p[5]: 10 p[6]: 10 p[7]: 10 p[8]: 10 p[9]: 10
- 動的配列の初期化に使用することができる
引用・参考はこちら
【C++】参照の返し
stdudy135.cpp
#include <iostream> using namespace std; int &f(); int x; int main() { f() = 100; cout << x << "\n"; return 0; } int &f() { return x; }
$ ./study135 100
- f() = 100;はf()によって返される参照に100を代入している
- return x;はxへの参照(xのアドレス)を返している。
- 要するに、戻り地に100を代入している。
study137.cpp
#include <iostream> #include <cstdlib> using namespace std; class array { int size; char *p; public: array( int num ); ~array() { delete [] p; } char &put( int i ); char get( int i ); }; array::array( int num ) { p = new char [num]; if( !p ) { cout << "メモリ割り当てエラー\n"; exit(1); } size = num; } char &array::put( int i ) { if( i < 0 || i > size-1 ) { cout << "境界エラー\n"; exit(1); } return p[i]; } char array::get( int i ) { if( i < 0 || i > size-1 ) { cout << "境界エラー\n"; exit(1); } return p[i]; } int main() { array a(10); a.put(3) = 'X'; a.put(2) = 'R'; cout << a.get(3) << a.get(2); cout << "\n"; a.put(11) = '!'; return 0; }
$ ./study137 XR 境界エラー
- array a(10);でa(0)からa(9)まで1文字ずつ代入できるオブジェクトを作成
- a.put(3) = 'X';で、put()の返り値のp[i]に文字Xを代入。a.put(4)はR.
- a.get(3)の返り値はp[i]なので、get()の引数に指定した数字のところの文字を取り出せる
- if( i < 0 || i > size-1 ) で指定した場所が範囲内にあるか確かめることができる。(代入するときも出力するときも)
- C/C++は配列のオーバーフローなどをチェックできないので、このプログラムでチェックできる。
引用・参考はこちら
【C++】オブジェクト参照の引き渡し
- こちらでは、オブジェクトを関数に渡した場合は、オブジェクトのコピーが作成されることを確認している。
- コピーなのでコンストラクタは呼ばれないがデストラクタは呼ばれる
- 動的メモリを2度以上開放することになり問題が発生することがある。
- オブジェクト参照によって解決できる
- オブジェクトを参照するとコピーが作成されないためデストラクタは実行されない
- ただし、関数内の変更が、引数に指定したオブジェクトにも反映される。
study132.cpp
#include <iostream> using namespace std; class myclass { int who; public: myclass( int n ) { who = n; cout << "コンストラクタ呼び出し" << who << "\n"; } ~myclass() { cout << "デストラクタ呼び出し" << who << "\n"; } int id() { return who; } }; void f( myclass &o ) { cout << "受け取り" << o.id() << "\n"; } int main() { myclass x(1); f(x); return 0; }
$ ./study132 コンストラクタ呼び出し1 受け取り1 デストラクタ呼び出し1 デストラクタ呼び出し1
こちらは以前と同様。デストラクタが2回呼びだされている。
study133.cpp
#include <iostream> using namespace std; class myclass { int who; public: myclass( int n ) { who = n; cout << "コンストラクタ呼び出し" << who << "\n"; } ~myclass() { cout << "デストラクタ呼び出し" << who << "\n"; } int id() { return who; } }; void f( myclass &o ) { cout << "受け取り" << o.id() << "\n"; } int main() { myclass x(1); f(x); return 0; }
$ ./study133 コンストラクタ呼び出し1 受け取り1 デストラクタ呼び出し1
19行目のみ変更している。
引用・参考はこちら
【C++】参照
参照(reference)
- すべての面で変数の別名として動作する暗黙的なポインタ(implict pointer)
用途
- 参照を関数に渡すことができる
- 関数から参照を返すことができる
- 独立した参照を作成することができる
study127_1.cpp
#include <iostream> using namespace std; void f( int *n ); int main() { int i = 0; f(&i); cout << "iの新しい値:" << i << "\n"; return 0; } void f( int *n ) { *n = 100; }
Cの場合。 これは、f()がiのアドレスを引き渡していて、そのアドレスのところに100を代入しているので、iの値が変わる。
study127_2.cpp
#include <iostream> using namespace std; void f( int &n ); int main() { int i = 0; f(i); cout << "iの新しい値:" << i << "\n"; return 0; } void f( int &n ) { n = 100; }
C++ではこのように完結に書くことができる。
study129.cpp
#include <iostream> using namespace std; void swapargs( int &x, int &y ); int main() { int i, j; i = 10; j = 19; cout << "i:" << i << "、"; cout << "j:" << j << "\n"; swapargs( i, j ); cout << "交換後:"; cout << "i:" << i << "、"; cout << "j:" << j << "\n"; return 0; } // 参照の場合 void swapargs( int &x, int &y ) { int t; t = x; x = y; y = t; } // ポインタの場合 /*void swapargs( int *x, int *y ) { int t; t = *x; *x = *y; *y = t; }*/
引用・参考はこちら
【C++】newとdelete
- C言語でメモリを動的割り当てするときはmalloc()を使用し、メモリを開放するときにfree()を使う
- C++ではnewを使用してメモリを割り当て、deleteを使用して開放する
- new演算子は自動的に十分なメモリを割り当てる
- sizeof()などを使用する必要がない
- new演算子は指定した方のポインタを自動的に返す
- malloc()関数を使用してメモリを当てるような明示的に型キャストは行わなくて良い
- オーバーロードできる
study120.cpp
#include <iostream> using namespace std; class samp { int i, j; public: void set_ij( int a, int b ) { i = a; j = b; } int get_product() { return i*j; } }; int main() { samp *p; p = new samp; if (!p) { cout << "メモリ割り当てエラー\n"; return 1; } p->set_ij( 4, 5 ); cout << "積は:" << p->get_product() << "\n"; return 0; }
$ ./study120 積は:20
study122.cpp
#include <iostream> using namespace std; class samp { int i, j; public: samp( int a, int b ) { i = a; j = b; } int get_product() { return i*j; } }; int main() { samp *p; p = new samp( 6, 5 ); if( !p ) { cout << "メモリ割り当てエラー\n"; return 1; } cout << "積は:" << p->get_product() << "\n"; delete p; return 0; }
$ ./study122 積は:30
動的に割り当てられたオブジェクトに初期値を渡している。
study125.cpp
#include <iostream> using namespace std; class samp { int i, j; public: void set_ij( int a, int b ) { i = a; j = b; } samp() { cout << "コンストラクタの呼び出し\n"; } ~samp() { cout << "デストラクタの呼び出し\n"; } int get_product() { return i*j; } }; int main() { samp *p; p = new samp [10]; if ( !p ) { cout << "メモリ割り当てエラー\n"; return 1; } for ( int i = 0; i < 10; ++i ) p[i].set_ij( i, i ); for ( int i = 0; i < 10; ++i ) { cout << "積[" << i << "]は:" << p[i].get_product() << "\n"; } delete [] p; return 0; }
$ ./study125 コンストラクタの呼び出し コンストラクタの呼び出し コンストラクタの呼び出し コンストラクタの呼び出し コンストラクタの呼び出し コンストラクタの呼び出し コンストラクタの呼び出し コンストラクタの呼び出し コンストラクタの呼び出し コンストラクタの呼び出し 積[0]は:0 積[1]は:1 積[2]は:4 積[3]は:9 積[4]は:16 積[5]は:25 積[6]は:36 積[7]は:49 積[8]は:64 積[9]は:81 デストラクタの呼び出し デストラクタの呼び出し デストラクタの呼び出し デストラクタの呼び出し デストラクタの呼び出し デストラクタの呼び出し デストラクタの呼び出し デストラクタの呼び出し デストラクタの呼び出し デストラクタの呼び出し
コンストラクタ、デストラクタがどのタイミングで呼ばれているかの確認
引用・参考はこちら
【C++】オブジェクトのポインタ
オブジェクトのポインタ
- オブジェクトポインタを使用した演算子は、他のデータ型のポインタ演算と同様に、そのオブジェクト型に関して行われる
- オブジェクトポインタをインクリメントすると次のオブジェクトを指すようになり、デクリメントするとその逆になる。
study112.cpp
#include <iostream> using namespace std; class samp { int a, b; public: samp( int n, int m ) { a = n; b = m; } int get_a() { return a; } int get_b() { return b; } }; int main() { samp ob[4] = { samp( 1, 2 ), samp( 3, 4 ), samp( 5, 6 ), samp( 7, 8 ) }; samp *p; p = ob; for ( int i = 0; i < 4; ++i ) { cout << p->get_a() << ' '; cout << p->get_b() << "\n"; p++; } cout << "\n"; return 0; }
$ ./study112 1 2 3 4 5 6 7 8
Thisポインタ
- すべてのメンバ関数の呼び出し時に自動的に渡されるポインタで、その呼び出しを行ったオブジェクトを指す
ob.f1();
- この場合、f1()関数にはobへのポインタが自動的に渡される
- このポインタをthisという名前で参照する
- フレンド関数には使用不可
study115.cpp
#include <iostream> #include <cstring> using namespace std; class inventory { char item[20]; double cost; int on_hand; public: inventory( char *i, double c, int o ) { strcpy( this->item, i ); this->cost = c; this->on_hand = o; } void show() { cout << this->item; cout << ":$" << this->cost; cout << " 在庫" << this->on_hand << "\n"; } }; int main() { inventory ob( "レンチ", 4.95, 4 ); ob.show(); return 0; }
$ ./study115 レンチ:$4.95 在庫4
これは以下のように略して書くこともできる
study114.cpp
#include <iostream> #include <cstring> using namespace std; class inventory { char item[20]; double cost; int on_hand; public: inventory( char *i, double c, int o ) { strcpy( item ,i ); cost = c; on_hand = o; } void show() { cout << item; cout << ":$" << cost; cout << " 在庫:" << on_hand << "\n"; } }; int main() { inventory ob( "レンチ", 9.45, 4 ); ob.show(); return 0; }
$ ./study114 レンチ:$4.95 在庫4
- 省略形のほうが楽なので、thisを書く人はいないが、原理を理解しておくことは大切。
引用・参考はこちら
【C++】オブジェクトの配列
- オブジェクトは変数を持っており、他の変数と同じ機能と属性を持っている
- オブジェクト配列へのアクセス方法も、ほかの変数配列と同じ
study108.cpp
#include <iostream> using namespace std; class samp { int a; public: samp( int n ) { a = n; } int get_a() { return a; } }; int main() { samp ob[4] = { -1, -2, -3, -4 }; for ( int i = 0; i < 4; ++i ) cout << ob[i].get_a() << ' '; cout << endl; return 0; }
実行
$ ./study108 -1 -2 -3 -4
この例では、-1から-4までの値をobコンストラクタ関数に渡している
samp ob[4] = { -1, -2, -3, -4 };
これは
samp ob[4] = { samp(-1), samp(-2), samp(-3), samp(-4) };
これと同じ。
study109.cpp
#include <iostream> using namespace std; class samp { int a; public: samp( int n ) { a = n; } int get_a() { return a; } }; int main() { samp ob[4][2] = { 1, 2, 3, 4, 5, 6, 7, 8 }; for ( int i = 0; i < 4; ++i ) { cout << ob[i][0].get_a() << ' '; cout << ob[i][1].get_a() << "\n"; } cout << endl; return 0; }
$ ./study109 1 2 3 4 5 6 7 8
このように二次元配列も扱うことができる。
study110.cpp
#include <iostream> using namespace std; class samp { int a, b; public: samp( int n, int m ) { a = n; b = m; } int get_a() { return a; } int get_b() { return b; } }; int main() { samp ob[4][2] = { samp( 1, 2), samp( 3, 4), samp( 5, 6), samp( 7, 8), samp( 9, 10), samp(11, 12), samp(13, 14), samp(15, 16) }; for ( int i = 0; i < 4; ++i ) { cout << ob[i][0].get_a() << ' '; cout << ob[i][0].get_b() << "\n"; cout << ob[i][1].get_a() << ' '; cout << ob[i][1].get_b() << "\n"; } cout << endl; return 0; }
$ ./study110 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
コンストラクタに複数の引数を渡すことができるが、その場合は長い初期化構文を使用しなければならない。