先週は、派生・継承について説明を行ったが、今日はその続き。
void main() { Person saitoh( "saitoh" , 45 ) ; Jyoshi ashida( "ashida" , 60 ) ; ashida.buka( &saitoh ) ; Person* list[2] ; list[0] = &saitoh ; list[1] = &ashida ; for( int i = 0 ; i < 2 ; i++ ) list[ i ]->print() ; }
この例では、list[1]=&ashida ができることが注目点。 派生クラスのポインタは、基底クラスのポインタに格下げが許される。 この例であれば、list[1]->print() では、list[1]=&ashida とする時点で、 Jyoshi型がPerson型になっているため、Person型のprint()が実行され、 部下の情報は表示されない。
しかし、実際のプログラムでは、list[1]->print() にて、Jyoshi::print() が呼び出して 欲しい場合も多い。こういった場合には、仮想関数を用いる。 virtual 宣言されたメソッドが宣言されると、データ構造の構築時に、 型情報を覚えるためのID(型情報)も保存される。 また、仮想関数の呼び出しでは、型情報に応じてメソッドが呼び出される。
class Person { : public: virtual void print() { printf( "%s %d\n" , name , age ) ; } } ; class Jyoshi : public Person { private: int size ; Person* table[ 5 ] ; public: virtual void print() { Person::print() ; for( int i = 0 ; i < size ; i++ ) table[i]->print() ; } } ;
パソコンでプログラムを打ち込みながら、受講している人がいたので、 Person型のサイズsizeof( Person )を確認すると、 virtual 宣言した場合、データサイズが4byte拡大することが確認できた。 実際には、型情報として「仮想関数の関数ポインタ配列へのポインタ」が 使われることが多いので、そのポインタ分の4byte増加と思われる。
int main(int argc,char**argv)
Cの授業でも、間違いとは説明するものの"void main(){}" でプログラムを書いていたり するけれど、C++だと明確にエラーになるため、argc,argvも含めて解説する。
int main( int argc , char*argv[] ) { for( int i = 0 ; i < argc ; i++ ) printf( "%s\n" , argv[i] ) ; // プログラムを a.out Hello World と実行すれば、 // argv[0]=a.out,argv[1]=Hello,argv[2]=World になる。 return 0 ; // mainの正常終了(プロセスのリターン値でOSに渡される) }
new delete
上記の仮想関数のプログラム例は、実プログラムでは、 以下のようなコードでインスタンスを生成する場合も多い。
Person *saitoh = new Person( "斉藤" , 45 ) ;
と簡単に説明しようとしたら、new,delete の説明をしてなかった。 new, delete は動的にメモリを確保するC言語のmalloc,freeに相当する演算子。
// C言語の場合 int *p ; if ( (p = (int*)malloc( sizeof(int) * 100 ) != NULL ) { // p[i] を整数配列として使える free( p ) ; } // C++のnew,deleteの場合 int *p = new int[ 100 ] ; // new演算子はメモリ確保の失敗には例外を使うので // NULLチェックは不要。 // p[i]を整数配列として使える delete [] p ; // Personの例で説明 Person* p = new Person( "斉藤" , 45 ) ; // new Personでは、コンストラクタも実行される。 p->print() ; delete p ; // delete では、デストラクタ呼び出しも行われる。