cocos2d-x を使うために C++ を学習したメモ

cocos2d-x

cocos2d-x でゲーム開発してみたいなーって思ったけど

これは C++ で書かれている。知らない言語だよ!

ということでC++入門を読んだ、そのメモ。

実行環境

とりあえず Xcode を使おうと思う。

cocos2d-x のプロジェクトを既に作ってあったので

/Classes/AppDelegate.cpp の

bool AppDelegate::applicationDidFinishLaunching

の中身にコードを書いて練習するというよくわからない

学習環境で実行することにした…まぁいいや。

Hello World

#include <iostream>
using namespace std;

int main()
{
    cout << "Hello World" << endl;
}

こんにちは。

#include <iostream>

bool AppDelegate::applicationDidFinishLaunching()
{
  // 標準出力というやつでしょうか
  cout
  // なんかこの << 記号は"押し込む"という説明がなされていた
  << "Hello World"
  // endl は改行文字らしい。 
  << endl
  // 改行をたくさんしてみた
  << endl << endl << endl;

  exit(1);
}

といことでこんなんを実行すると Xcodeのターミナルに Hello World と実行された。

やったね。

ここまでできればC++は攻略したも同然だ!

名前空間

AppDelegate.cpp の上のほうをみると

USING_NS_CC;

とあったのでこれが名前空間っぽかった。

USING_NS_CC の中身は

#define USING_NS_CC using namespace cocos2d

だったのでやっぱそうだった。

実際の名前空間の仕様はどうなってるのかよく知らないけど、

たぶん Java と大差ないだろう。

int main() {
    std::cout << "Hello World" << std::endl;
}

変数とか文字列とか

#include <iostream>
bool AppDelegate::applicationDidFinishLaunching()
{
  string str;
  str = "覇王翔吼拳を";
  cin >> str;
  cout << str;

  exit(1);

NSString ではないので @"hogehoge" とか書くというような失敗は

しなかったものの、最初 string *str; とか書いて失敗した。

cin >> str は ユーザーの入力を str に入れるという機能だけど、

Xcode で実行するとここの部分は飛ばされてしまい覇王翔吼拳を使えなかった。

クラス

テキトーになんか書いて試した

#include <iostream>

typedef int TestInteger;

bool AppDelegate::applicationDidFinishLaunching()
{
  // とりあえずクラスを作ってみる。

  // メソッド内で クラスの定義ができるようなので
  // メソッド内で書いて試す。

  class Test {
    // 可視性の指定無しは private 扱い
    TestInteger number;

  public:
    string str;

    // コンストラクタ : 返り値の型が無いのはコンストラクタ
    // Objective-C だと init の部分だね
    Test(string str) {
      // this.str ってやったら怒られた。 this はポインタ。
      this->str = str;
    }

    void printfStr() {
      cout << str << endl;
    }
  };

  Test test = Test("コンストラクタの引数");

  // Ojbective-C ではないので当然プロパティではない
  test.str += "\nプロパティじゃないよ";

  test.printfStr();

  // 可視性の指定がないものは private になる
  test.number = 1; // コンパイルエラー

  exit(1);
}

C言語の型が分かればだいたいオッケーな気がする。

クラス変数・クラスメソッド

class Test {
public:
  static int num;
  static void test() {
    cout << Test::num << endl;
  }
};
// クラス変数の初期化
int Test::num = 100;

bool AppDelegate::applicationDidFinishLaunching()
{
  Test::test(); 

  Test::num = 12345;
  Test::test();

  exit(1);
}

仮想関数

なんかソースコードみてたら virtual とかいう見慣れない トークン を見かけた。

仮想関数 の定義に使うものらしい。これは派生クラスで再定義できるもの。

えーと、これは多分 Javaの抽象メソッドとかそのあたりをイメージすればよさそうか。

でもなんか違う気がしてきた。またあとで勉強しよう。

オブジェクトのデータを変えないメンバ関数

には const をつけるのが作法とのこと。

class Test {
public:
  int num;
  void test() const{
    // num = 10000; // これを書いたら XCodeでエラー出がでてビルド不能だった
    cout << num << endl;
  }  
};

インライン関数とかインラインじゃない関数とか

インライン関数はクラス定義の中に書き込まれた具体的なメソッド。

インライン関数じゃない関数は、クラスのメソッド定義ではメソッド名や引数などの宣言だけを行い、実際の定義はクラス外で行う。

cclass NotInlineMethod {

  string str;

public:
  NotInlineMethod(string s);
  void hoge(int n) const;
};

NotInlineMethod::NotInlineMethod(string s) : str(s){}
void NotInlineMethod::hoge(int n) const{
	cout << str << n ;
};

参考 : Ken Kobayashi CPP8

制御構文

どの言語でも大差ないだろうという予測のもと飛ばした。

オブジェクトはそのままだと値渡しになる

php や Java だと勝手に参照渡しになるし、

Objective-C だとポインタで渡すので勘違いしたけど、

C++ で引数にオブジェクトを渡しても参照渡しにならない。

class Test {

public:

  string str;

  void func1(Test test) {
    test.str = "foo/";
  }
  void func2(Test &test) {
    test.str = "bar/";
  }
};

bool AppDelegate::applicationDidFinishLaunching()
{
  string str = "";

  Test test = Test();

  test.str = "foobar/";
  str += test.str;

  test.func1(test);
  str += test.str;

  test.func2(test);
  str += test.str;

  cout << str;

  exit(1);
}

これを実行するとターミナルには

foobar/foober/bar/

と表示される。ということで 引数に渡すものは 値なのか 参照なのかきちんと意識しよう。

継承 / インターフェイス / 純粋仮想関数

C++ において明確にインターフェイスという機能はなく

virtual修飾子がつけられた再定義が必要なメソッドの定義があるクラスが

インターフェイスとしての機能を持っている。

この関数のことを 純粋仮想関数 という。

純粋仮想関数を持つクラスを抽象クラスという。

// インターフェイス
class ICreature {
protected:
  bool life;

  // 純粋仮想関数 / = 0 となっている
  // 仮想関数の一種
  virtual void birth() = 0;

  ICreature() : life(false){};
};

// インターフェイスを実装したクラス
class Human : ICreature {
  void birth () {
    this->life = true;
  }
  string name;
};

// 継承
class Woman : Human {
};

仮想関数 と オーバーライド

なんか仮想関数を知らないのに純粋仮想関数とかいうのが出てきたので、仮想関数についても調べる。

class Base {
public:
  int n;
  void test() {
    cout << n << endl;
  }

  Base func(){
    this->n = 0;
    return *this;
  };
  virtual Base virtualFunc(){
    this->n = 0;
    return *this;
  };
};

class Inherited : public Base {

public:
  Base func(){
    this->n = 12345;
    return *this;
  };
  virtual Base virtualFunc(){
    this->n = 67890;
    return *this;
  };
};

bool AppDelegate::applicationDidFinishLaunching()
{
  Inherited test1;
  test1.virtualFunc().test();
  test1.func().test();

  Base *test2 = new Inherited();
  (*test2).virtualFunc().test();
  (*test2).func().test();

  Base test3_1 = Inherited();
  Base *test3_2 = &test3_1;
  test3_1.virtualFunc().test();
  test3_1.func().test();
  test3_2->virtualFunc().test();
  test3_2->func().test();

  exit(1);

new 演算子使ってない場合がどうなってるか分からなかったりして頭がショートした。

オブジェクトの値として定義したものはそのオブジェクトとしてしか使えないとかそんな感じなのかな。まぁ値型として定義したものはポリモーフィズム使えないってことっすね。

結果

67890
12345
67890
0
0
0
0
0

デストラクタ

  class Test {
    // デストラクタ
  public:
    ~Test() {
      cout << "デストラクタがコールされました\n";
    }

    void DestructTest () {
      Test test;
    }
  };

  Test test;
  test.DestructTest();

  exit(1);
}
#include <iostream>

class Base {
protected:
  ~Base () {
    cout << "Baseインスタンスが破棄されました\n";
  };
};

class Inherited : public Base {
public:
  ~Inherited () {
    cout << "Inheritedインスタンスが破棄されました\n";
  };
};

// 継承するクラスに可視性つけるに気づかなくハマった…
bool AppDelegate::applicationDidFinishLaunching()
{
  Inherited inherited;
  return;
}

結果

Inheritedインスタンスが破棄されました
Baseインスタンスが破棄されました

という順番でデストラクタが実行される。

よーするに デストラクタ関数内の末尾でスーパークラスのメソッドが呼ばれているってことね。

コンストラクタはこの逆でコンストラクタ内の最初の部分でスーパークラスのコンストラクタをコールしている。

また、仮想関数を持つクラスには基本的には中身が空の仮想デストラクタを定義しておくべき。

new / delete

インスタンスを動的に生成するときは new を使用する。

new で確保したメモリを解放するときには delete を使う。

delete で解放しなければメモリリークする。

また、ヌルポインタに対して delete してもエラーにはならない。何もおこらない。

new は動的に配列を確保するときにも使用するし、オブジェクト以外のメモリも確保してポインタを返してくれる。

  // 配列のメモリを動的に生成
  int count = 100;
  int *intArray = new int[count];
  delete[] intArray;

  // intのメモリを動的に生成
  int *intPointer;
  intPointer = new int;
  delete intPointer;

  // ちょっと違和感があるけど
  // 以下のコードが処理された時点で
  // Inherited の配列には 引数のないコンストラクタが実行された
  // インスタンスがすべての配列に収まることになる
  Test *test;
  test = new Test[5];

継承した基底クラスのメソッドを呼び出す

#include <iostream>

class Base {
public:
  int n;
  virtual void func () {
    cout << "Baseのメソッドだよ\n";
  }

};

class Inherited : public Base {public:
  Inherited(){
    n = 123;
  }
  void func () {
    cout << "Inheritedのメソッドだよ\n";
  }
};

bool AppDelegate::applicationDidFinishLaunching()
{
  Base *test = new Inherited;
  test->func();
  test->Base::func();

  exit(1);
}

結果

Inheritedのメソッドだよ
Baseのメソッドだよ

コピーコンストラクタと代入演算子

C++入門

class Aru{
     int data;
public:
    Aru(int d) : data(d){}
    int get_data() const{ return data; }
};

class Betu
{
    Aru *a;  //Aruのポインタ
public:
    Betu();
    ~Betu();
    //コピーコンストラクタ
    Betu(const Betu&);  //宣言なので仮引数は省略した
    //代入演算子
    Betu& operator=(const Betu&); //宣言なので仮引数は省略した
    void input();
    void show() const;
    int get_data() const{ return a->get_data(); }  //新しく定義
};

//ポインタの0は「どこも指さない」という意味。後述。
Betu::Betu() : a(0){}

Betu::~Betu(){
    delete a;
}

//コピーコンストラクタ
Betu::Betu(const Betu& x){
    a = new Aru(x.get_data());
}

//代入演算子
Betu& Betu::operator=(const Betu& x){
    if(this == &x) return *this;   //自己代入
    delete a;
    a = new Aru(x.get_data());
    return *this;
}

void Betu::input(){
    int d;
    delete a;
    cout << "整数を入力してください:" << endl;
    cin >> d;
    a = new Aru(d);
}

void Betu::show() const{
    if(a == 0) return;  //ポインタがどこも指し示していなければ終了
    cout << "データ:" << a->get_data() << endl;
}

あとでもうちょい詳しく勉強しようと思う項目のメモ

ということで3日でC++の基本的なコードは書けるようになった。はず。

そういうことにしておこう。

次は Color Labyrinth を Cocos2dx に移植してみようと思う。

でもしっかり理解していない部分もあるのであとで勉強し直そう。

  • 可視性:
  • 仮想関数の再定義とか new 演算子とか
  • テンプレート
  • 多重継承
  • 演算子多重定義

なんか可視性とか継承とかクラス周りがやたら複雑で死ぬ可能性が見えてきた。

もうちょっと知りたいかなと思ったときに参考にしようと思ったサイトのメモ

Share
関連記事