280 likes | 403 Views
インタラクティブ・ゲーム制作 < プログラミングコース>. 第 4 回 スコープ・ポインタ・参照. 今日の内容. スコープの話 変数の寿命について コンストラクタと デストラクタの確認 クラスのスコープ. ポインタと参照 ポインタとは何ぞや 参照 と は何ぞや うまいお付き合いの 仕方. 変数やオブジェクトの寿命の話. スコープ. スコープとは. while( window.update () == true) { if( キーが押されてたら? ) { うごけー } else {
E N D
インタラクティブ・ゲーム制作<プログラミングコース>インタラクティブ・ゲーム制作<プログラミングコース> 第4回 スコープ・ポインタ・参照
今日の内容 • スコープの話 • 変数の寿命について • コンストラクタとデストラクタの確認 • クラスのスコープ • ポインタと参照 • ポインタとは何ぞや • 参照とは何ぞや • うまいお付き合いの仕方
変数やオブジェクトの寿命の話 スコープ
スコープとは while(window.update() == true) { if(キーが押されてたら?) { うごけー } else { 押されてなかったら これやっとけー(省略可) } if(違うキーが押されてたら?) { 違う感じにうごけー } } • 変数やオブジェクトの有効範囲のこと • {}で囲われた範囲を「ブロック」と呼ぶ • 右の例だとwhileループのブロック内に、条件分岐で更に3つブロックがある • もちろんwhileループの外側にも関数のブロックがある • スコープはブロックで決まる
変数が生まれる時・死ぬ時 • 処理がスコープに入って宣言された時、変数が生成される • 処理がスコープから抜ける時、変数は破壊される • 関数呼び出しで一時的にジャンプする時は大丈夫 • 今処理しているスコープから見えない変数は使えない • コンパイルエラーになる void func(void) { // ここで変数hogeが作られる inthoge = 0; if(適当な条件式) { // ここで変数hogehogeが作られる inthogehoge = 0; } // ここで変数hogehogeが破壊される return; // ここで変数hogeが破壊される }
じゃあこう書くしかないじゃない!! intmain(int argc, char *argv[]) { /* 凄まじい分量の変数宣言 */ /* 殺人的な分量の初期化処理 */ //メインループ while(true) { /* 目を覆いたくなるようなゲーム処理 */ } // ここに至るまでに数万行 return 0; }
なんでああなるのか? • 関数やクラスを覚えても、うまく使えずこう書く人は多い • スコープの問題を解決できないから • 今日取り扱う、ポインタや参照を理解すれば、こんなことはしなくて済む • 絶対にやめよう
スコープイン・アウトに際する重要イベント • 変数の場合は、単純に箱が確保され、消滅するだけ • クラスオブジェクトは、イン・アウトで次の関数が呼ばれる • スコープインでコンストラクタ • スコープアウトでデストラクタ #ifndef __SCOPR_CHECKER_H__ #define __SCOPR_CHECKER_H__ #include <iostream> // スコープの出入り時にメッセージを出すクラス class ScopeChecker { public: ScopeChecker(void) { std::cout << "こうして俺はこの世界に生まれた。" << std::endl; }; ~ScopeChecker() { std::cout << "そして俺は世界から抹殺された。" << std::endl; }; }; #endif
初期化と後片付け以外にも色々使い道がある • 自動で呼んでくれるという構造を利用して、闇の魔術を行使するC++erも多い • ScopeCheckerはその一例 • コンストラクタに引数を取ることで、実体生成時に名前を付けるようにした→ // さっきのクラスのコンストラクタを改造 // ※一部省略 class ScopeChecker{ private: std::string name; public: ScopeChecker(std::string argName) : name(argName) { std::cout << “俺の名は” << name << “……たった今生まれたところさ。” << std::endl; }; // デストラクタも同じように改造しよう };
トピック:初期化子リスト • コンストラクタで、メンバ変数に値をセットする最速で確実なやり方 • 引数リストの後ろに「: メンバ名(値)」 • 複数ある時はカンマ区切りで並べる • : hoge(1), fuge(2) • [上級]継承している場合は、親クラスのコンストラクタに引数を渡す時にも使う ScopeChecker(std::string argName) : name(argName) { } // 次のように書いても同じ ScopeChecker(std::string argName) { name = argName; } // できるだけ初期化子リストを使おう
クラス内のスコープってどうなってるの? • お品書きと中身を分けずに、まとめて書いた状態で考えると分かりやすい • クラスという枠で括られているので、メンバ関数からはメンバ変数が見える class HogeHoge { private: // メンバ変数はクラス内スコープ std::string name; public: HogeHoge() { // スコープ内だからメンバ変数が見える name = “ほげ~”; }; void func() { // メンバだから、見えま~す std::cout << name << std::endl; }; };
でもまとめて書くとしんどいので • お品書きと本体に分離して書く • 本体を書く時は「クラス名::関数名」と書くことで、「ここだけ一時的にクラスの中だよ!」ということを示す class HogeHoge { // 省略 // 関数の名前・返値の型・引数だけ書く void func(); // 省略 }; // CPP側では #include “HogeHoge.h” // ここだけHogeHogeの中ってことにして! void HogeHoge::func() { // 飛び地だけど、メンバだから見えま~す std::cout << name << std::endl; }
クラスが見えてるか?オブジェクトが見えているか?クラスが見えてるか?オブジェクトが見えているか? • クラスを利用するには、クラスの宣言がスコープ内に見えてないとダメ • なのでcppの冒頭でincludeする • 作ったオブジェクトを操作出来るのは、オブジェクトを作ったスコープ内だけ // HogeHogeクラスを使いたい #include “HogeHoge.h” void func() { // これはOK HogeHogehoge; hoge.func(); } void funcOther() { // hogeは飽くまでfunc()で作ったから // これはNG hoge.func(); }
ここまでのまとめ • ブロック{}によってスコープが決まる • スコープに出入りする時、クラスオブジェクトならコンストラクタとデストラクタが走る • クラスのメンバは、クラススコープという括りで括られていると考える
怖くない、ほら、怖くない ポインタと参照
画像を友達に送りたい • どんなやり方が考えられるだろうか? • 「画像を直接転送する」 • 「画像をアップロードしてURLを教える」 • 他にもやり方があるが、それぞれメリットとデメリットがある • ポインタは後者の考え方に基づく
現物渡しとURL渡しの違い • 現物の場合 • 転送に時間がかかる • お互いそれぞれ手元にファイルが残る • コピーしたことになる • 送り先の相手がその画像をいじっても、送り主には影響なし • その逆もまた同じ • URLの場合 • 転送は一瞬 • 送り先の相手は手元にコピーしなくても、URLから画像を見れる • コピーすることも可 • 送り主が画像を消したり、内容を変更したら送り先も影響を受ける • 重要
全ての変数にはアドレスがある • 変数名に&を付けると、アドレスになる • アドレスは「ポインタ変数」にしまうことができる • ポインタは「型名 *ポインタ名;」で宣言する • ポインタ変数に*を付けると、その中身を取り出せる // main()の中だとして // 適当に作った変数のアドレスを見るコード inthoge= 0; int *pHoge = NULL; pHoge = &hoge; std::cout << pHoge << std::endl; std::cout << *pHoge << std::endl;
関数の引数で利用してみる 関数の定義 呼び出し側 // main()の中だとして intnum = 10; cout << “before:” << num << endl; cantChangeValue(num); //changeValue(&num); cout << “after:” << num << endl; // 呼び出す側を切り替えて試してみよう // 渡されてきた値を2倍しようとする関数 void cantChangeValue(intarg_num) { arg_num*= 2; return; } // 渡されてきた値を2倍する関数 void changeValue(int*arg_pNum) { *arg_pNum *= 2; return; }
何が違うのか? • コピー先をいじっても意味が無い // 渡されてきた値を2倍しようとする関数 void cantChangeValue(intarg_num) { arg_num *= 2; return; } // main()の中だとして intnum = 10; cout << “before:” << num << endl; cantChangeValue(num); cout << “after:” << num << endl; 10 arg_num 10 コピー num 20
何が違うのか? • ポインタを通じて元の変数がいじれる // 渡されてきた値を2倍する関数 void changeValue(int *arg_pNum) { *arg_pNum *= 2; return; } // main()の中だとして intnum = 10; cout << “before:” << num << endl; changeValue(&num); cout << “after:” << num << endl; 10 arg_pNum 10 point! アドレスを渡す num 20 20
参照はポインタの簡易版 • 関数の引数リスト側で&を付けて宣言 • 関数の呼び出し側は、実体を渡せばよい • 後から指し示す対象を変えることはできない • 実体と同様に扱える • メリットでもあり、デメリットでもある // 渡されてきた値を2倍する関数(参照版) void changeRefValue(int &arg_pNum) { arg_pNum *= 2; return; } // main()の中だとして intnum = 10; cout<< “before:” << num << endl; changeRefValue(num); cout << “after:” << num << endl;
実体・ポインタ・参照の違い • 関数の引数として使う場合、オブジェクトは実体渡しだと不都合が生じる場合が多い • 読み出し専用は参照、いじくる場合はポインタ、という使い分けをすることが多い
ポインタだからできること • newとdeleteの使用 • スコープに左右されずに確保したいメモリやオブジェクトを宣言できる • 大きなサイズの配列など • ポリモフィズムを利用したオブジェクト生成 • 1つのポインタ変数が状況によって指す対象を切り替えることができる • 最初はとりあえずNULLにしておくことも可能
newとdelete • new 型名(); で実体を作成する • 配列ならnew 型名[個数];とする • 実体ができた場所のアドレスが得られるので、ポインタ型の変数で捕まえて利用する • newしたものは基本的に自分で片付けねばならない • 片付けるにはdelete アドレス;とする • 配列を作った場合にはdelete [] アドレス;とする • うかつなdeleteは死を招く • まだ利用しているものをdelete したりとか • かといってdeleteしないでいると、プログラムが、OSが、死ぬ
まとめ • ポインタはエロ画像のURLである • 参照はその簡易版である • ポインタ(参照)を使えば、スコープの枠を飛び越えて変数やオブジェクトを扱える
今日の課題 • 2変数の中身を入れ替える関数を作ろう • ポインタ・参照どちらでもよい • 入れ替えるのはint型かdouble型とする • それぞれのバージョンを作るとなおよし • 期限は来週の授業開始時まで • 超絶楽勝だから今回は期限厳守で!