12 | 2025/01 | 02 |
S | M | T | W | T | F | S |
---|---|---|---|---|---|---|
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 | 28 | 29 | 30 | 31 |
ブログは単なるメモ帳となる予定。
クラスの継承とコンストラクタ、デストラクタ
クラスを継承すれば、当然コンストラクタやデストラクタも継承される。つまり、派生クラスのオブジェクトを生成すれば、派生クラスのコンストラクタと基本クラスのコンストラクタの両方が実行される。デストラクタも同様である。
そのコンストラクタの実行順序は「基本クラス→派生クラス」の順である。派生クラスのコンストラクタは基本クラスの初期化も含んでいるため、基本クラスの後に実行される。何も記述しなくても基本クラスのコンストラクタは自動実行されるが、基本クラスのコンストラクタに引数を与えたい場合には、初期化リストの部分に記述する。
class Dimension2 : public Dimension1{ public: Dimension2() : Dimension1() { // ここで基本クラスのコンストラクタ呼び出し } }
一方、デストラクタの実行順序は「派生クラス→基本クラス」の順に実行される。これも、派生クラスの後処理が基本クラスのメンバに依存することがあることを考えれば自然。
クラスの継承について、最初に本で読んだときによく分からずじまいだった。しかも、その後、クラスの継承を使うこともなくここまできてしまった。今回ちょっと使おうかなと思ったので改めて本を読んでみたが、やはりよく分からない。ということでプログラムを作っていろいろやってみた。
今回は適当に以下のようなクラスを使ってみた。
class Dimension1{ private: int x; void privateSet(int a){ x = a; } protected: void protectedSet(int a){ x = a; } public: void publicSet(int a){ x = a; } int getX(){ return x; } }; class Dimension2 : public Dimension1{ private: int y; protected: public: void set(int a, int b){ publicSet(a); // protectedSet(a); // privateSet(a); y = b; } int getY(){ return y; } };
クラスの継承というのは、他のクラスを内包するようなクラスを作ることである。内包されているクラスを基本クラス、内包しているクラスを派生クラスという。
図:クラスの継承の概要
クラスの継承は以下のように記述する。
class Dimension2 : public Dimension1{};
クラスのメンバのアクセス制御と同様に、クラスの継承にもprivateな継承、protectedな継承、publicな継承がある。上記の継承はpublicな継承である。privateな継承は以下のように記述する。
class Dimension2 : private Dimension1{};
派生クラスのメンバ関数からは、基本クラスのprotectedなメンバ、およびpublicなメンバにアクセスできる。これは継承の種類によらない。
一方、派生クラス外からの基本クラスのメンバ関数へのアクセスは、継承の種類によってさらに制限される。
図・privateな継承
図・protectedな継承
図・publicな継承
いろいろ書いたけどあってんのかな? 次もクラスの継承を少し書く予定。
前のエントリで関数ポインタを少し理解したので、続いてメンバ関数ポインタについて少し学ぶ。
今回もとりあえず例を挙げておく。
class vec{ public: void resize(int); }; int main(void){ vec v; vec *w = new vec(); void (vec::*func)() = &vec::resize; (v.*func)(5); (w->*func)(5); return 0; }
メンバ関数ポインタの宣言は、
void ( vec ::* func )( ); 【返却値】(【クラス名】::*【変数名】)(【引数】);
メンバ関数のアドレス取得は、
& vec :: resize &【クラス名】::【関数名】で可能。メンバ関数のアドレス取得は&が必須なので、関数アドレス取得時には常につける癖をつけた方がよさそう。各変数固有のアドレス(上の例ではvec::vのresize関数)を取得するわけではないみたい。
ポインタを介した関数の実行は、
vec v; (v.*func)(5); vec* w = new vec; (w->*func)(5);の様に、.*演算子か->演算子を用いる。
ちなみに、メンバ関数が静的(static)である場合には、呼び出しは普通の関数の様に呼び出せる。
関数ポインタを使う必要が出てきた。OpenCVのcvSetMouseCallback関数とかで存在自体は知ってたけど、使うのは初めてなのでメモっておく。
参考:C++言語解説
以下に簡単な関数ポインタのプログラム例を挙げておく。
#include <iostream> void func1(void){ std::cout << "func1\n"; } void func2(void){ std::cout << "func2\n"; } int main(void){ int i; void (*func)(void); while(1){ std::cout << "1:func1 2:func2 0:quit\n"; std::cin >> i; switch(i){ case 1: func = &func1; break; case 2: func = func2; break; default: goto LOOP_EXIT; } func(); } LOOP_EXIT: return 0; }
これを見ると分かるように関数ポインタの宣言は、
void (* func )( void ); 【関数の戻り値】 (*【変数名】)(【関数の引数】);という形になる。
関数アドレスの取得は、
&func1 &【関数名】または単に、
func2 【関数名】で取得可能。
関数ポインタを介した関数の実行は、
(* func )( ); (*【関数ポインタ】)(【引数】);で実行可能。また、普通の関数のように、
func ( ); 【関数ポインタ】(【引数】);としても実行できる。
よく使うクイックソートのプログラムを一応張っておく。もう憶えたけど念のため。
void QuickSort(int data[], const int low, const int high){ if(low < high){ // ピボットの決定 int x = (data[low] + data[high] + data[(low+high)/2])/3; int left = low - 1, right = high + 1; while(left <= right){ while(data[++left] < x); while(data[--right] > x); if(left < right) Swap(&data[left], &data[right]); } QuickSort(data, low, right); QuickSort(data, left, high); } }アルゴリズムは「クイックソート:アルゴリズム」に分かりやすい説明がある。
C++ならSTLのalgorithmを利用したほうが早い。
#include <algorithm> int main(){ int array[10] = { 1, 6, 4, 3, 5, 9, 8, 2, 7, 0}; std::sort(array, array+10); for(int i = 0; i < 10; ++i){ std::cout << array[i] << ", "; } std::cout << std::endl; return 0; }
ちなみに、降順にソートするには、
sort(array, array+10, greater<int>());のようにすればいいらしい(参考)。