Presentation is loading. Please wait.

Presentation is loading. Please wait.

インタラクティブ・ゲーム制作 <プログラミングコース>

Similar presentations


Presentation on theme: "インタラクティブ・ゲーム制作 <プログラミングコース>"— Presentation transcript:

1 インタラクティブ・ゲーム制作 <プログラミングコース>
第4回 スコープ・ポインタ・参照

2 今日の内容 スコープの話 ポインタと参照 変数の寿命について コンストラクタと デストラクタの確認 クラスのスコープ ポインタとは何ぞや
参照とは何ぞや うまいお付き合いの 仕方

3 変数やオブジェクトの寿命の話 スコープ

4 スコープとは スコープはブロックで決まる 変数やオブジェクトの 有効範囲のこと {}で囲われた範囲を 「ブロック」と呼ぶ
右の例だとwhileループのブロック内に、条件分岐で更に3つブロックがある もちろんwhileループの 外側にも関数のブロックがある スコープはブロックで決まる while(window.update() == true) {   if(キーが押されてたら?) {     うごけー   } else {     押されてなかったら     これやっとけー(省略可)   }   if(違うキーが押されてたら?) {     違う感じにうごけー }

5 変数が生まれる時・死ぬ時 処理がスコープに 入って宣言された時、 変数が生成される 処理がスコープから 抜ける時、 変数は破壊される
関数呼び出しで一時的に ジャンプする時は大丈夫 今処理している スコープから見えない変数は使えない コンパイルエラーになる void func(void) { // ここで変数hogeが作られる int hoge = 0; if(適当な条件式) { // ここで変数hogehogeが作られる int hogehoge = 0; } // ここで変数hogehogeが破壊される return; // ここで変数hogeが破壊される

6 じゃあ こう書くしかないじゃない!! int main(int argc, char *argv[]) { /* 凄まじい分量の変数宣言 */ 殺人的な分量の初期化処理 // メインループ while(true) { 目を覆いたくなるようなゲーム処理 } // ここに至るまでに数万行 return 0;

7 なんでああなるのか? 関数やクラスを覚えても、 うまく使えずこう書く人は多い
スコープの問題を解決できないから 今日取り扱う、ポインタや参照を 理解すれば、こんなことはしなくて済む 絶対にやめよう

8 スコープイン・アウトに 際する重要イベント
変数の場合は、 単純に箱が確保され、消滅するだけ クラスオブジェクトは、イン・アウトで次の関数が呼ばれる スコープインで コンストラクタ スコープアウトで デストラクタ #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

9 初期化と後片付け以外にも 色々使い道がある
自動で呼んでくれるという構造を利用して、闇の魔術を行使するC++erも多い ScopeCheckerは その一例 コンストラクタに 引数を取ることで、 実体生成時に名前を付けるようにした→ // さっきのクラスのコンストラクタを改造 // ※一部省略 class ScopeChecker { private: std::string name; public: ScopeChecker(std::string argName) : name(argName) { std::cout << “俺の名は” << name << “……たった今生まれたところさ。” << std::endl; }; // デストラクタも同じように改造しよう

10 トピック:初期化子リスト コンストラクタで、 メンバ変数に値をセットする最速で確実なやり方 引数リストの後ろに 「: メンバ名(値)」
複数ある時はカンマ区切りで並べる : hoge(1), fuge(2) [上級] 継承している場合は、 親クラスのコンストラクタに引数を渡す時にも使う ScopeChecker(std::string argName) : name(argName) { } // 次のように書いても同じ ScopeChecker(std::string argName) name = argName; // できるだけ初期化子リストを使おう

11 クラス内のスコープって どうなってるの? お品書きと中身を 分けずに、まとめて書いた状態で考えると分かりやすい
クラスという枠で 括られているので、メンバ関数からは メンバ変数が見える class HogeHoge { private: // メンバ変数はクラス内スコープ std::string name; public: HogeHoge() { // スコープ内だからメンバ変数が見える name = “ほげ~”; }; void func() // メンバだから、見えま~す std::cout << name << std::endl;

12 でもまとめて書くとしんどいので お品書きと本体に 分離して書く
本体を書く時は 「クラス名::関数名」 と書くことで、 「ここだけ一時的にクラスの中だよ!」ということを示す class HogeHoge { // 省略 // 関数の名前・返値の型・引数だけ書く void func(); }; // CPP側では #include “HogeHoge.h” // ここだけHogeHogeの中ってことにして! void HogeHoge::func() { // 飛び地だけど、メンバだから見えま~す std::cout << name << std::endl; }

13 クラスが見えてるか? オブジェクトが見えているか?
クラスを利用するには、クラスの宣言が スコープ内に見えてないとダメ なのでcppの冒頭でincludeする 作ったオブジェクトを操作出来るのは、オブジェクトを作ったスコープ内だけ // HogeHogeクラスを使いたい #include “HogeHoge.h” void func() { // これはOK HogeHoge hoge; hoge.func(); } void funcOther() // hogeは飽くまでfunc()で作ったから // これはNG

14 ここまでのまとめ ブロック{}によってスコープが決まる スコープに出入りする時、 クラスオブジェクトならコンストラクタとデストラクタが走る
クラスのメンバは、クラススコープという括りで括られていると考える

15 怖くない、ほら、怖くない ポインタと参照

16 画像を友達に送りたい どんなやり方が考えられるだろうか? 「画像を直接転送する」 「画像をアップロードしてURLを教える」
他にもやり方があるが、それぞれメリットと デメリットがある ポインタは後者の考え方に基づく

17 現物渡しとURL渡しの違い 現物の場合 URLの場合 転送に時間がかかる お互いそれぞれ手元にファイルが残る
コピーしたことになる 送り先の相手が その画像をいじっても、 送り主には影響なし その逆もまた同じ URLの場合 転送は一瞬 送り先の相手は手元に コピーしなくても、URLから画像を見れる コピーすることも可 送り主が画像を 消したり、内容を 変更したら送り先も 影響を受ける 重要

18 全ての変数にはアドレスがある 変数名に&を付けると、 アドレスになる アドレスは 「ポインタ変数」に しまうことができる
ポインタは 「型名 *ポインタ名;」 で宣言する ポインタ変数に*を 付けると、 その中身を取り出せる // main()の中だとして // 適当に作った変数のアドレスを見るコード int hoge = 0; int *pHoge = NULL; pHoge = &hoge; std::cout << pHoge << std::endl; std::cout << *pHoge << std::endl;

19 関数の引数で利用してみる 関数の定義 呼び出し側
// 渡されてきた値を2倍しようとする関数 void cantChangeValue(int arg_num) { arg_num *= 2; return; } // 渡されてきた値を2倍する関数 void changeValue(int *arg_pNum) *arg_pNum *= 2; // main()の中だとして int num = 10; cout << “before:” << num << endl; cantChangeValue(num); //changeValue(&num); cout << “after:” << num << endl; // 呼び出す側を切り替えて試してみよう

20 何が違うのか? コピー先をいじっても意味が無い arg_num num 10 10 コピー 20 // 渡されてきた値を2倍しようとする関数
void cantChangeValue(int arg_num) { arg_num *= 2; return; } // main()の中だとして int num = 10; cout << “before:” << num << endl; cantChangeValue(num); cout << “after:” << num << endl; 10 arg_num 10 コピー num 20

21 何が違うのか? ポインタを通じて元の変数がいじれる num 10 point! 10 arg_pNum アドレスを渡す 20 20
// 渡されてきた値を2倍する関数 void changeValue(int *arg_pNum) { *arg_pNum *= 2; return; } // main()の中だとして int num = 10; cout << “before:” << num << endl; changeValue(&num); cout << “after:” << num << endl; 10 arg_pNum point! 10 アドレスを渡す num 20 20

22 参照はポインタの簡易版 関数の引数リスト側で&を付けて宣言 関数の呼び出し側は、実体を渡せばよい 後から指し示す対象を変えることはできない
実体と同様に扱える メリットでもあり、 デメリットでもある // 渡されてきた値を2倍する関数(参照版) void changeRefValue(int &arg_pNum) { arg_pNum *= 2; return; } // main()の中だとして int num = 10; cout << “before:” << num << endl; changeRefValue(num); cout << “after:” << num << endl;

23 実体・ポインタ・参照の違い 関数の引数として使う場合、オブジェクトは実体渡しだと不都合が生じる場合が多い
宣言の仕方 Hoge hoge; Hoge *a; Hoge &a = other; 種類 実体 ポインタ 参照 代入できるもの 実体・定数 アドレス 実体を宣言時に 必ず代入 メンバアクセス .(ピリオド) ->(アロー演算子) &を付けると アドレスになる ポインタの *を付けると エラーになる 実体が取れる 関数の引数として使う場合、オブジェクトは実体渡しだと不都合が生じる場合が多い 読み出し専用は参照、いじくる場合はポインタ、という使い分けをすることが多い

24 ポインタだからできること newとdeleteの使用 1つのポインタ変数が状況によって指す 対象を切り替えることができる
スコープに左右されずに確保したいメモリやオブジェクトを宣言できる 大きなサイズの配列など ポリモフィズムを利用したオブジェクト生成 1つのポインタ変数が状況によって指す 対象を切り替えることができる 最初はとりあえずNULLにしておくことも可能

25 newとdelete 片付けるには delete アドレス;とする うかつなdeleteは死を招く
実体ができた場所の アドレスが得られるので、ポインタ型の変数で 捕まえて利用する newしたものは 基本的に自分で 片付けねばならない 片付けるには delete アドレス;とする 配列を作った場合にはdelete [] アドレス;とする うかつなdeleteは死を招く まだ利用しているものをdelete したりとか かといってdelete しないでいると、 プログラムが、OSが、死ぬ

26 まとめ ポインタはエロ画像のURLである ポインタ(参照)を使えば、スコープの枠を飛び越えて変数やオブジェクトを扱える
参照はその簡易版である ポインタ(参照)を使えば、スコープの枠を飛び越えて変数やオブジェクトを扱える

27 今日の課題 2変数の中身を入れ替える関数を作ろう 期限は来週の授業開始時まで ポインタ・参照どちらでもよい
入れ替えるのはint型かdouble型とする それぞれのバージョンを作るとなおよし 期限は来週の授業開始時まで 超絶楽勝だから今回は期限厳守で!

28 To be continued…


Download ppt "インタラクティブ・ゲーム制作 <プログラミングコース>"

Similar presentations


Ads by Google