Comments
Description
Transcript
応用プログラミング 第11回 ∼ C++入門
本日の内容 応用プログラミング 第11回 ∼ C++入門その3 ∼ 1. クラスをもっと使う クラスの配列とポインタ クラスを引数で渡す 仮想関数 動的メモリ管理again 2. テンプレート関数 3. テンプレートクラス 電気通信大学電子工学専攻 Intelligent Electronic Systems Group 長井 隆行 2 using namespaceの例 名前空間 (Q)なぜstd::coutと"std::"が必要なのでしょうか? #include <iostream> using namespace std; //std名前空間を使うぜ! int main() { cout << "std::はいりませんね。¥n"; return 0; } (A)stdという名前空間のcoutという意味です C++ではクラス名の衝突をさけるために名前空間というのを設定できる 名前空間を設定すると 名前空間::何がし と指定する必要がある これによって 何がし の部分がかぶっても大丈夫! 但し常に名前空間を指定するのはめんどうなので using namespace std; などと書いておくと cout << "hogehoge! ¥n" と名前空間を指定する必要がなくなる 衝突を避けるために設定しているのに、それを面倒くさがってはずしてしま うのはいかがなものかという気はしますが^^; 3 4 クラスの配列 解答例 //Carクラスの定義 class Car{ public: int car_number; //ナンバー用のメンバ変数 double gas; //ガソリン量用のメンバ変数 void showNumber( ); //ナンバー表示用メンバ関数 void showGas( ); //ガソリン量表示用メンバ関数 void run( double distance); //走行用メンバ関数 }; クラスとは、構造体の拡張に過ぎない ⇒ 配列(クラスの配列)も使えるはず??? 試してみましょう 9-2.cppを改造して複数の車を走らせて見ます //Carクラスのメンバ関数の定義(ナンバー表示関数の本体) 省略 //Carクラスのメンバ関数の定義(ガソリン量表示関数の本体) void Car::showNumber() { std::cout << "車のナンバーは" << car_number << "です。¥n"; } //Carクラスのメンバ関数の定義(走行関数の本体) void Car::run( double distance ) { //燃費はリッター10km gas = gas -distance/10.0; if(gas < 0) { gas = 0; std::cout << "ガス欠で止まりました¥n"; } } もちろん複数の車を配列で表現します ⇒ Car cars[10]; Carクラス 車10台分 5 複数の車を走行させるメイン関数 6 ミニテスト(問1) 前の続き int main( ) { int i; Car cars[3]; for(i=0; i<3; i++){ cars[i].car_number = 1000+i; cars[i].gas = i*10; } for(i=0; i<3; i++){ cars[i].showNumber( ); } for(i=0; i<3; i++){ cars[i].run( i ); cars[i].showGas( ); } return 0; } p11-1.cppの実行結果はどうなるでしょうか? //3台の車というインスタンス(実体)を宣言 //車のナンバー登録(これ自体に意味はないので注意) //車のガソリン(給油) //私の車のナンバーを表 // i (km)走らせる //走行後の車のガソリン量を表示 p11-1.cpp 7 8 クラスのポインタ ポインタを使った例 クラスの定義はp11-1.cppと同じ もちろんクラスのポインタも使える Car my_car; Car* p_car; p_car = &my_car; //Carクラスのインスタンス //Carクラスのポインタ変数 //ポインタ変数へアドレスを代入 int main( ) { int i; Car cars[2]; Car* my_car; Car* your_car; 配列がからんでも同じ(シンタックスシュガーを思い出しましょ う) my_car = cars; your_car = cars+1; for(i=0; i<2; i++){ cars[i].car_number = 1000+i; } (*my_car).showNumber( ); (*your_car).showNumber( ); return 0; hoge が配列のとき、&hoge[0] は hoge と書ける *hoge は hoge[0]を意味する *(hoge+i)はhoge[i]を意味する //2台分のインスタンス(実体)を宣言 //ポインタ変数 //ポインタ変数 //ポインタ変数にアドレスを設定 //ポインタ変数にアドレスを設定 //車のナンバー登録(これ自体に意味はないので注意) //車のナンバーを表示 cars[0].showNumber()でもよい //車のナンバーを表示 p11-2.cpp } ポインタの場合、メンバを参照するのにアロー演算子が使える 9 ミニテスト(問2) 10 クラスを引数として関数に渡す (*my_car).showNumber( ); をアロー演算子で書き直しましょう 関数に引数としてクラスのインスタンスを渡すことが 可能です アドレス渡しももちろん可能 11 12 クラスを引数にする例 ミニテスト(問3) アドレス渡しに直しましょう //Carクラスの定義は省略 void runAndShow(Car m_car, double dist); //Carクラスの定義は省略 //関数のプロトタイプ宣言 void runAndShow( //メインの関数(車クラスを使用する) int main(void) { Car my_car; //関数のプロトタイプ宣言 //メインの関数(車クラスを使用する) int main(void) { Car my_car; my_car.car_number=1000; my_car.gas=50; runAndShow(my_car, 10); //車のナンバーを表示 //走らせてガソリンを表示する関数 my_car.car_number=1000; my_car.gas=50; runAndShow( , 10); return 0; } //車のナンバーを表示 //走らせてガソリンを表示する関数 return 0; void runAndShow(Car m_car, double dist) { m_car.run(dist); m_car.showGas(); } } void runAndShow( { //メンバ関数の呼び出し //メンバ関数の呼び出し 派生クラスでのポインタ利用 //Carクラスは省略 class Truck : public Car{ public: //ナンバー表示用メンバ関数(オーバーライド) void showNumber(); }; void Truck::showNumber() { std::cout << "車のナンバーは" << car_number << "です。¥n"; std::cout << "こいつはトラックだぜぃ! ¥n"; } //メインの関数(車クラスを使用する) int main(void) { Car my_car; Truck my_truck; Car* p_car[2]; my_car.car_number = 1000; my_truck.car_number = 99; p_car[0] = &my_car; p_car[1] = &my_truck; for(int i=0; i<2; i++) { p_car[i]->showNumber(); } return 0; , double dist) //メンバ関数の呼び出し //メンバ関数の呼び出し p11-4.cpp 13 } , double dist); } 14 派生クラスでのポインタ利用 派生クラスでポインタを 使う時にはちょっと注意 が必要 Truckクラス使ってみま しょう G:¥paperwork¥2006講義¥応用プログラミング¥no11>bcc32 p11-5.cpp Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland p11-5.cpp: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland G:¥paperwork¥2006講義¥応用プログラミング¥no11>p11-5 車のナンバーは1000です。 車のナンバーは99です。 //車のナンバーを登録 //トラックのナンバー登録 G:¥paperwork¥2006講義¥応用プログラミング¥no11> このような代入が可能 p11-6.cpp 15 Truckクラスの showNumber()ではない! 16 仮想関数 仮想関数を使った結果 //Carクラスの定義 class Car{ public: int car_number; //ナンバー用のメンバ変数 double gas; //ガソリン量用のメンバ変数 virtual void showNumber( ); //ナンバー表示用メンバ関数(仮想関数) void showGas( ); //ガソリン量表示用メンバ関数 void run(double distance); //走行用メンバ関数 }; class Truck : public Car{ public: //ナンバー表示用メンバ関数(オーバーライド) void showNumber(); }; void Truck::showNumber() { std::cout << "車のナンバーは" << car_number << "です。¥n"; std::cout << "こいつはトラックだぜぃ! ¥n"; } //メインの関数(車クラスを使用する) int main(void) { //全く同じなので省略 p11-7.cpp } G:¥paperwork¥2006講義¥応用プログラミング¥no11>bcc32 p11-7.cpp Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland p11-7.cpp: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland 仮想関数を使うと Truckクラスの showNumber() が呼ばれる! 17 動的メモリの確保再び G:¥paperwork¥2006講義¥応用プログラミング¥no11>p11-7 車のナンバーは1000です。 車のナンバーは99です。 こいつはトラックだぜぃ! G:¥paperwork¥2006講義¥応用プログラミング¥no11> *オーバーライドする関数を仮想関数としておく(virtualをつける)と ポインタによるメンバ呼び出しで問題がおきない 18 *virtualをつけるのは基本クラスの関数のみ 動的メモリの確保再び 続き newとdeleteの例 Cでは動的メモリの確保にmalloc(実はcallocというのもある)と開放に freeを使いました C++では、newとdeleteを使います newやdeleteは関数ではなく演算子なので( )はつきません mallocで確保したものをdeleteで開放したり、newで確保したものを freeで開放することはできません int main() { int* data; data = new int[100]; int *foo; foo = (int *) malloc(100,sizeof(int)); int *hoge; hoge = new int; ... delete hoge; int *foo; foo = new int[100]; ... delete [ ] foo; //int型ひとつ分 … //変数を開放 free(foo); for(int i=0; i<100; i++) { *(data+i)=i; //i番目にiを代入 std::cout << data[i] << "¥n"; } delete[ ] data; return 0; //int型100個分の配列 //配列を開放 //int 100個分のメモリを確保 19 } //メモリの開放 p11-8.cpp 20 動的メモリの確保再び 続き テンプレート関数(1) クラスも動的にメモリ確保できます int main( ) { int i; Car* p_ cars; p_cars = new Car[3]; for(i=0; i<3; i++){ cars[i].car_number = 1000+i; cars[i].gas = i*10; } for(i=0; i<3; i++){ cars[i].showNumber( ); } for(i=0; i<3; i++){ cars[i].run( i ); cars[i].showGas( ); } delete[] cars; return 0; } 関数を使っていると、引数の型だけが違い、その他の処 理などは全く同じ場合が多々あります オーバーロードを用いれば、同じ名前で引数の違う2つの関数を 作ることができました 処理内容が全く同一な場合は、関数テンプレートを用いる ことで、これらを一つにまとめてしまうことが可能です //車のナンバー登録(これ自体に意味はないので注意) //車のガソリン(給油) //車のナンバーを表 void function(int a); void function(double a); // i (km)走らせる //走行後の車のガソリン量を表示 double hoge; function(hoge); p11-9.cpp テンプレート関数(2) 21 classとしてもよい テンプレート利用 double hoge = maxdt(0.4, 10.4); int i_maxdt( int val1, int val2 ) { return ( val1 > val2 ? val1 : val2); } double d_maxdt( double val1, double val2 ) { return ( val1 > val2 ? val1 : val2); } 22 テンプレートクラス 2つの変数を比較し、大きい方を返す関数 Template <typename T> T maxdt( T val1, T val2 ) { return ( val1 > val2 ? val1 : val2); } int hoge = maxdt( 1, 2); function(int a){ //aの型以外下と同じ中身 function( a ){ } //同じ中身 } function(double a){ //aの型以外上と同じ中身 } //共にint //共にdouble もしCでやると int hoge = i_maxdt( 1, 2 ); double hoge = d_maxdt( 0.4, 10.4 ); 23 C++ではテンプレート関数という機能がありました クラスでも同様に、クラステンプレート(汎用クラス)を用いることで、様々な 型を扱うクラスを作成することが可能です 汎用クラスは次のように書きます。 template <typename 型の識別子> class クラス名 { //メンバ ・・・ }; メンバ関数の本体を定義するときがちょっと面倒ですが・・・ 実際にクラスを使う時には、型を指定することで宣言(インスタンスの生成) します クラス名<型> インスタンス名 (宣言でどの型でクラスを使うのかを指定しているところがポイント) 24 テンプレートクラスの例 (main関数) テンプレートクラスの例 Cmaxクラスをつかって、int型配列の最大値とdouble型配列の最 大値を計算している どちらの配列に対しても同じクラステンプレートを使っている //大きい値を返す(テンプレート関数) template< typename T > T tmax( T a, T b ) { return a >b ? a : b ; } //配列の最大値を探し保持するテンプレートクラスCmax //配列の型が自由に選べるようになっている template< typename T> class Cmax { public: T m_result ; void tbmax( T * in, int size ); }; int main() { int i_data[]={1, 5, 3, 7}; double d_data[]={3.3, 5.5, 8.8, 4.4, 1.1}; Cmax< int > max_i; Cmax< double > max_d ; //最大値を探すメンバ関数の定義 template< typename T> void Cmax<T>::tbmax(T* in, int size) { m_result = in[0] ; for ( int i=1 ; i<size ; ++i ) { m_result = tmax( m_result, in[i] ) ) ; } } max_i.tbmax(i_data, 4); max_d.tbmax(d_data, 5); std::cout << "最大値は" << max_i.m_result << "です。¥n"; std::cout << "最大値は" << max_d.m_result << "です。¥n"; return 0; } 25 p11-10.cpp 26