C++OpenSiv3D入門講座 Vol. 03 vectorの基本

C++OpenSiv3D入門講座

今回は動的配列vectorの基本について学ぶ。 演習では、OpenSiv3Dを用いて複数の敵を生成する。

vectorの基本的な使い方

C言語の配列は固定長だったが、C++には必要に応じて自動的にサイズを変更してくれる配列、vectorがある。 vectorの要素には配列と同じように[]でアクセス出来る。 vectorを使うときはvectorヘッダをインクルードする。(vector.hでないことに注意!)

#include <iostream>
#include <vector>

int main(){

    std::vector<int> vec;//int型の動的配列

    // 10個の要素を追加していく
    for(int i = 0; i < 10; i++ ){
        vec.emplace_back(i);
    }

    //出力
    for(int i = 0; i < vec.size(); i++ ){
        std::cout << vec[i] << " ";
    }
    std::cout << std::endl;

    std::cout << "現在のサイズ : " << vec.size() << std::endl;

    return 0;
}

書き方

std::vector<動的配列の中に入れる型> 動的配列の変数名

のように書く。

例:double型の動的配列ary1を作成する場合
std::vector<double> ary1;

追加の方法

動的配列名.emplace_back(追加するデータ)

のように書く。

例:int型の動的配列aryに 1を入れるとしたら
ary.emplace_back(1);

また、クラス名{ コンストラクタ引数 }でクラスの実体を作成できることを利用して、以下のように書くこともできる。

例:Vector2型の動的配列aryに(1, 3)のVector2を入れるとしたら
ary.emplace_back(Vector2{ 1, 3 });
(Vector2{ 1, 3 }でVector2型の実体を作成し、それをaryに入れている。)

配列のサイズの取得

動的配列名.size()

クラスのvector

#include <iostream>
#include <vector>

class MyClass{
public:
    int x;

    MyClass(int _x) :
        x{ _x }
    {
    }
};


int main(){

    std::vector<MyClass> vec;

    //要素を追加
    for(int i = 0; i < 10; i++){
        vec.emplace_back(MyClass{ rand()%10 });
    }

    //要素MyClassのxを表示
    for(int i = 0; i < vec.size(); i++){
        std::cout << vec[i].x << " ";
    }

    std::cout << std::endl;//改行

    return 0;
}

Tips:インスタンスの作成と同時に関数を呼ぶ例

以下の例では、色を表すMyColorクラス、円を表すMyCircleクラスを用意し、インスタンスを作成しそのまま関数の引数に入れる例と、インスタンスの作成と同時に関数を呼ぶ例を示している。このように、C++はCに比べ、エレガント(変態的ともいう)な表記が可能になる。

#include <iostream>
// 色を表すクラス (赤成分,緑成分,青成分):(r,g,b)
class MyColor {
public:
    int r, g, b;
    MyColor(int _r, int _g, int _b) :
        r{ _r },
        g{ _g },
        b{ _b }
    {
    }
};
// 円を表すクラス 座標:(x,y)、半径:r
class MyCircle {
public:
    int x, y, r;
    MyCircle( int _x, int _y, int _r ) :
        x{ _x },
        y{ _y },
        r{ _r }
    {
    }
    // 自分の情報と、引数にとったMyColorの情報を出力する関数
    void showSelfAndColor(const MyColor& color) {
        std::cout << "Circle:(x, y, r) = (" << x << "," << y << "," << r << ")"
            << " Color:(r, g, b) = (" << color.r << "," << color.g << "," << color.b << ")" << std::endl;
        std::cout << std::endl;
    }
};
int main() {
    // MyCircleのインスタンスがmyCircleA変数に、MyColorのインスタンスがmyColorA変数に入り、それをshowSelfAndColorで表示する
    MyCircle myCircleA{ 10, 10, 5 };
    MyColor myColorA{ 70, 80, 90 };
    myCircleA.showSelfAndColor(myColorA);
    // MyCircleのインスタンスがmyCircleB変数に入り、MyColorのインスタンスは生成とともにshowSelfAndColorの引数となり表示される
    MyCircle myCircleB{ 20, 20, 6 };
    myCircleB.showSelfAndColor(MyColor{ 100, 110, 120 });
    // MyCircleのインスタンスの生成と同時にshowSelfAndColor関数を呼ぶ。
    // またMyColorのインスタンスも生成とともにshowSelfAndColorの引数となり表示される
    MyCircle{ 30, 30, 7 }.showSelfAndColor(MyColor{ 200, 210, 220 });
    return 0;
}

まとめ

  • クラス名{ コンストラクタ引数 }でクラスの実体を作成できる
  • std::vector<動的配列の中に入れる型> 動的配列の変数名のようにしてvectorを作成する
  • 動的配列名.emplace_back(追加するデータ)のようにしてvectorに要素を追加する。
  • 動的配列名.size()のようにしてvectorのサイズを取得する。

演習問題(コンソール)

  1. int型のvectorを用意し、ランダムに0~9の数を10個入れて、すべて表示せよ。 ヒント:ランダムな0~9の数を生成するにはrand()%10とすればよい。
  2. 以下のようなクラスMyClassを用意した。MyClassのvectorを用意し、vectorにMyClassを10個追加し、すべての要素のshow()関数を呼び出せ。xの値はランダムな数でよい。
    class MyClass{
    public:
        int x;
    
        MyClass(int _x) :
            x{ _x }  
        {
        }
    
        void show(){
            std::cout << "xは:" << x << std::endl;
        }
    };
    

演習問題(OpenSiv3D)

今回は、前回のプロジェクトからそのままEnemyクラスを書き換えてもいいし、1から作り直しても良い。

  1. 以下の様なEnemyクラスを用意した。Enemyクラスのインスタンスを作り、動作を確認せよ。
    #pragma once
    #include <Siv3D.hpp>
    
    class Enemy {
    public:
        Vec2 pos;
        Vec2 velocity;
        Enemy(Vec2 _pos);
        void update();
        void draw();
    };
    
    #include "Enemy.h"
    Enemy::Enemy(Vec2 _pos):
        pos{ _pos },
        velocity{ 0.0, 50.0 }
    {
    }
    
    void Enemy::update() {
        pos += Scene::DeltaTime() * velocity;
    }
    
    //エネミー(円)を描画
    void Enemy::draw() {
        Circle{ pos, 30.0 }.draw(Color{ 255, 0, 0 });
    }
    
  2. Enemyのvectorを作り、Enemyが複数出られるようにせよ。

  3. Zキーが押されたらランダムな座標に敵が出るようにせよ。 ヒント:以下のようにしてシーン内のランダムな座標を取得することができる。
    RandomVec2(Scene::Size().x, Scene::Size().y)
    

  4. Enemyを管理するEnemyManagerクラスを作る。

    現在、メインループ内でEnemyのvectorをfor文で回していると思うが、メインループ内はできるだけすっきりさせたい。そこで、Enemyのvectorをメンバに持ち、updateを呼ぶとメンバのEnemyのupdateをすべて呼ぶEnemyManagerクラスを作り、メインループ内ではEnemyManagerのupdateを呼ぶだけでEnemyすべてが更新され、EnemyManagerのdrawを呼ぶだけですべての敵が描画されるようにする。また、add関数を持ち、それが呼ばれると敵を追加する。

    EnemyManagerクラスを作り、敵を管理せよ。
    #pragma once
    #include <vector>
    #include <Siv3D.hpp>
    #include "Enemy.h"
    
    class EnemyManager {
    public:
        std::vector<Enemy> enemies;
        void update();
        void draw();
        void add(Vec2 pos);
    };
    
    #include "EnemyManager.h"
    
    void EnemyManager::update()
    {
        // ここを実装
    }
    
    void EnemyManager::draw()
    {
        // ここを実装
    }
    
    void EnemyManager::add(Vec2 pos)
    {
        // ここを実装
    }
    

Tips

OpenSiv3Dにはvectorと同じように使える動的配列、Arrayがある。本資料ではC++STLしか使えない極限の環境を想定して今後もvectorを使うが、Arrayはvectorより要素の削除が簡単に行えるので、Arrayも確認してみよう。

zenn.dev

C++OpenSiv3D入門講座