★リスト構造2(list)★


リスト構造はデータの挿入や削除が得意と書きました。

このデータ構造に合ったシチュエーションを考えてみます。


シューティングゲームの弾の扱いについてシミュレートしてみましょう。

自機の弾について↓のように設定します。

弾は連射可能

画面外に出るか敵に当たった場合消える

このように設定した場合、↓の状況が考えられます。

何発発射されるか分からない

先に撃った弾から消えるとは限らない

何発発射されるか分からない

この状況はベクタでもリストでも問題ありません。

データの個数が分からない時のためのコンテナですから、どちらも得意とするところです。

先に撃った弾から消えるとは限らない

こちらの状況は、リストが有利ですね。

ベクタは最後尾のデータを削除するのは得意ですが、途中のデータを消すのは不得意です。

と言う訳で、これをリストで作ってみましょう。

実際に発射は出来ませんから、状況を考えてシミュレートします。


弾の構造体は次のようにします。

struct Shot {
    int x;
    int y;
    bool bShoot;
};

X座標とY座標、発射中のフラグの3つです。

座標はデータを区別するために設定しました。

発射中フラグがfalseのデータは画面外に出たか敵に当たって消える弾を指しています。

発射した弾の中で、発射中フラグがfalseのデータを削除するプログラムを作ります。


まずは、ヘッダファイル、構造体宣言、main関数の一部から作ります。

<sample program cpp017-01>

#include <cstdlib>
#include <ctime>
#include <list>
#include <iostream>

struct Shot {
    int x;
    int y;
    bool bShoot;
};

int main()
{
    srand((unsigned int)time(NULL));

    std::list<Shot> lstShot;

    return 0;
}

ここから弾を発射(追加)していきますが、何発撃つか分からない状況を作るため乱数を使います。

また、座標と発射中フラグも乱数で設定して、リストに追加しましょう。

<sample program cpp017-02>

#include <cstdlib>
#include <ctime>
#include <list>
#include <iostream>

struct Shot {
    int x;
    int y;
    bool bShoot;
};

int main()
{
    srand((unsigned int)time(NULL));

    std::list<Shot> lstShot;

    Shot work;

    int shotMax = rand() % 6 + 5;

    for (int i = 0; i < shotMax; i++) {

        work.x = rand() % 100;
        work.y = rand() % 100;

        if (rand() % 2) {
            work.bShoot = true;
        }
        else {
            work.bShoot = false;
        }

        lstShot.push_back(work);
    }

    return 0;
}

乱数を使って、5発から10発の弾を発射するようにしました。

X、Y座標は0から99までの数値が入ります。

フラグはif文を使って、trueかfalseを入れるように作りました。


最後に、イテレータを使って発射後の状況を表示します。

<sample program cpp017-03>

#include <cstdlib>
#include <ctime>
#include <list>
#include <iostream>

struct Shot {
    int x;
    int y;
    bool bShoot;
};

int main()
{
    srand((unsigned int)time(NULL));

    std::list<Shot> lstShot;

    Shot work;

    int shotMax = rand() % 6 + 5;

    for (int i = 0; i < shotMax; i++) {

        work.x = rand() % 100;
        work.y = rand() % 100;

        if (rand() % 2) {
            work.bShoot = true;
        }
        else {
            work.bShoot = false;
        }

        lstShot.push_back(work);
    }

    std::list<Shot>::iterator it;

    std::cout << "削除前" << std::endl;

    for (it = lstShot.begin(); it != lstShot.end(); it++) {
        std::cout << "x = " << it->x
            << " : y = " << it->y
            << " : bShoot = " << it->bShoot
            << std::endl;
    }

    return 0;
}

<実行結果>

削除前
x = 47 : y = 4 : bShoot = 0
x = 29 : y = 79 : bShoot = 1
x = 11 : y = 38 : bShoot = 0
x = 4 : y = 38 : bShoot = 0
x = 46 : y = 16 : bShoot = 1
x = 93 : y = 54 : bShoot = 1
x = 93 : y = 81 : bShoot = 0
x = 7 : y = 20 : bShoot = 1
続行するには何かキーを押してください・・・

前にイテレータはポインタ変数だと説明しました。

イテレータ経由で構造体のメンバにアクセスするためには「->演算子」が必要です。

上の結果では、4発の弾が削除候補になっています。

削除後のデータも確認出来るように、表示のプログラムをコピーしておきます。

<sample program cpp017-04>

#include <cstdlib>
#include <ctime>
#include <list>
#include <iostream>

struct Shot {
    int x;
    int y;
    bool bShoot;
};

int main()
{
    srand((unsigned int)time(NULL));

    std::list<Shot> lstShot;

    Shot work;

    int shotMax = rand() % 6 + 5;

    for (int i = 0; i < shotMax; i++) {

        work.x = rand() % 100;
        work.y = rand() % 100;

        if (rand() % 2) {
            work.bShoot = true;
        }
        else {
            work.bShoot = false;
        }

        lstShot.push_back(work);
    }

    std::list<Shot>::iterator it;

    std::cout << "削除前" << std::endl;

    for (it = lstShot.begin(); it != lstShot.end(); it++) {
        std::cout << "x = " << it->x
            << " : y = " << it->y
            << " : bShoot = " << it->bShoot
            << std::endl;
    }

    std::cout << "削除後" << std::endl;

    for (it = lstShot.begin(); it != lstShot.end(); it++) {
        std::cout << "x = " << it->x
            << " : y = " << it->y
            << " : bShoot = " << it->bShoot
            << std::endl;
    }

    return 0;
}

<実行結果>

削除前
x = 83 : y = 90 : bShoot = 1
x = 90 : y = 57 : bShoot = 0
x = 60 : y = 81 : bShoot = 0
x = 87 : y = 20 : bShoot = 1
x = 34 : y = 79 : bShoot = 0
x = 95 : y = 50 : bShoot = 0
x = 98 : y = 35 : bShoot = 0
x = 54 : y = 27 : bShoot = 1
削除後
x = 83 : y = 90 : bShoot = 1
x = 90 : y = 57 : bShoot = 0
x = 60 : y = 81 : bShoot = 0
x = 87 : y = 20 : bShoot = 1
x = 34 : y = 79 : bShoot = 0
x = 95 : y = 50 : bShoot = 0
x = 98 : y = 35 : bShoot = 0
x = 54 : y = 27 : bShoot = 1
続行するには何かキーを押してください・・・

ここからが本番です。

データを削除するには、erase関数を使います。

erase関数の引数はイテレータです。

引数で指定したデータを削除してくれます。

繰り返しの中で、発射中フラグがfalseのデータを消してみましょう。

<sample program cpp017-05>

#include <cstdlib>
#include <ctime>
#include <list>
#include <iostream>

struct Shot {
    int x;
    int y;
    bool bShoot;
};

int main()
{
    srand((unsigned int)time(NULL));

    std::list<Shot> lstShot;

    Shot work;

    int shotMax = rand() % 6 + 5;

    for (int i = 0; i < shotMax; i++) {

        work.x = rand() % 100;
        work.y = rand() % 100;

        if (rand() % 2) {
            work.bShoot = true;
        }
        else {
            work.bShoot = false;
        }

        lstShot.push_back(work);
    }

    std::list<Shot>::iterator it;

    std::cout << "削除前" << std::endl;

    for (it = lstShot.begin(); it != lstShot.end(); it++) {
        std::cout << "x = " << it->x
            << " : y = " << it->y
            << " : bShoot = " << it->bShoot
            << std::endl;
    }

    for (it = lstShot.begin(); it != lstShot.end(); it++) {
        if (!it->bShoot) {
            lstShot.erase(it);
        }
    }

    std::cout << "削除後" << std::endl;

    for (it = lstShot.begin(); it != lstShot.end(); it++) {
        std::cout << "x = " << it->x
            << " : y = " << it->y
            << " : bShoot = " << it->bShoot
            << std::endl;
    }

    return 0;
}

<実行結果>



エラーになりました・・・

メッセージには、イテレータが増加できないとか書いてあります。

このエラーの原因を簡単に書くと、↓のようになります。

  erase関数でデータを消した場合、次のデータのポインタが分からなくなってしまっている。

これを防ぐにはerase関数の戻り値を使います。

erase関数は削除したデータの次のデータの場所を戻します。

それをイテレータで受け取ってやれば良いのです。

<sample program cpp017-06>

#include <cstdlib>
#include <ctime>
#include <list>
#include <iostream>

struct Shot {
    int x;
    int y;
    bool bShoot;
};

int main()
{
    srand((unsigned int)time(NULL));

    std::list<Shot> lstShot;

    Shot work;

    int shotMax = rand() % 6 + 5;

    for (int i = 0; i < shotMax; i++) {

        work.x = rand() % 100;
        work.y = rand() % 100;

        if (rand() % 2) {
            work.bShoot = true;
        }
        else {
            work.bShoot = false;
        }

        lstShot.push_back(work);
    }

    std::list<Shot>::iterator it;

    std::cout << "削除前" << std::endl;

    for (it = lstShot.begin(); it != lstShot.end(); it++) {
        std::cout << "x = " << it->x
            << " : y = " << it->y
            << " : bShoot = " << it->bShoot
            << std::endl;
    }

    for (it = lstShot.begin(); it != lstShot.end(); it++) {
        if (!it->bShoot) {
            it = lstShot.erase(it);
        }
    }

    std::cout << "削除後" << std::endl;

    for (it = lstShot.begin(); it != lstShot.end(); it++) {
        std::cout << "x = " << it->x
            << " : y = " << it->y
            << " : bShoot = " << it->bShoot
            << std::endl;
    }

    return 0;
}

<実行結果>



上手く終わるケースもあればエラーで止まるケースもあります・・・

実は、もう1段階対応しなければならない事があります。

erase関数は消した次のデータの場所を返していると書きました。

と言う事は、イテレータは勝手に次へ進んでいるのです。

for文の最後に「it++」と書いてありますから、

  データを削除した場合、次のデータに移っているにも関わらずさらに1つ先へ進んでいる

のです。


そこで↓のように対処します。

<sample program cpp017-07>

#include <cstdlib>
#include <ctime>
#include <list>
#include <iostream>

struct Shot {
    int x;
    int y;
    bool bShoot;
};

int main()
{
    srand((unsigned int)time(NULL));

    std::list<Shot> lstShot;

    Shot work;

    int shotMax = rand() % 6 + 5;

    for (int i = 0; i < shotMax; i++) {

        work.x = rand() % 100;
        work.y = rand() % 100;

        if (rand() % 2) {
            work.bShoot = true;
        }
        else {
            work.bShoot = false;
        }

        lstShot.push_back(work);
    }

    std::list<Shot>::iterator it;

    std::cout << "削除前" << std::endl;

    for (it = lstShot.begin(); it != lstShot.end(); it++) {
        std::cout << "x = " << it->x
            << " : y = " << it->y
            << " : bShoot = " << it->bShoot
            << std::endl;
    }

    for (it = lstShot.begin(); it != lstShot.end(); ) {
        if (!it->bShoot) {
            it = lstShot.erase(it);
        }
        else {
            it++;
        }
    }
    
    std::cout << "削除後" << std::endl;

    for (it = lstShot.begin(); it != lstShot.end(); it++) {
        std::cout << "x = " << it->x
            << " : y = " << it->y
            << " : bShoot = " << it->bShoot
            << std::endl;
    }

    return 0;
}

<実行結果>

削除前
x = 89 : y = 46 : bShoot = 0
x = 57 : y = 1 : bShoot = 1
x = 66 : y = 13 : bShoot = 0
x = 97 : y = 38 : bShoot = 1
x = 19 : y = 4 : bShoot = 1
x = 73 : y = 8 : bShoot = 0
x = 11 : y = 56 : bShoot = 1
削除後
x = 57 : y = 1 : bShoot = 1
x = 97 : y = 38 : bShoot = 1
x = 19 : y = 4 : bShoot = 1
x = 11 : y = 56 : bShoot = 1
続行するには何かキーを押してください・・・

for文の増分を消している事に注意してください。

erase関数によってデータを消した場合は、イテレータを進めないようにしました。

これで完成です!


2Dグラフィックスが扱えるようになったら、もう一度やってみましょう。


次へ

戻る

目次へ