首页 > 解决方案 > 将对象声明为数组,然后将同一数组中的某个值的类型声明为子类

问题描述

[已解决]这是我的全部代码都完成了工作:

#include <ncurses.h>
#include <random>
#include <ctime>
using namespace std;

//VARIABLES
///////////
    int tick_count = 0;
    char key;
    int width = 10, height = 10;

    class Entity {
    public:
        class Common {
        public:
            char icon;
            int x, y;
            char collide;
            int power = 0, speed = 0, defense = 0, health = 0;
            int hp;

            virtual void move(int dir) {
                switch (dir) {
                    case 1:
                        y--;
                        break;
                    case 2:
                        y++;
                        break;
                    case 3:
                        x--;
                        break;
                    case 4:
                        x++;
                        break;
                }
            }
            virtual ~Common() {}
        };

        class Player: public Common {
            public:
                int target, room;
                Player() {
                    icon = '@';
                    x = 1, y = 1;
                    collide = 'n';
                    power = 1, speed = 1, defense = 1, health = 10;
                    hp = health;
                }
        };
        class Kobold: public Common {
            public:
                Kobold() {
                    icon = 'K';
                    collide = 'n';
                    power = 1, speed = 0, defense = 1, health = 2;
                    hp = health;
                }
        };
        class Spider: public Common {
            public:
                Spider() {
                    icon = 'S';
                    collide = 'n';
                    power = 4, speed = 2, defense = 0, health = 3;
                    hp = health;
                }
        };
    };

    class Room {
    public:
        int width, height, difficulty, enemies;
    };

    Room room[1];
    Entity::Player player;
    Entity::Common * enemy[2];
    int enemy_ID, enemy_count = 2;
///////////

void INIT() {
    room[0].width = 10, room[0].height = 10, room[0].enemies = 0;
    player.x = player.y = 1, player.collide = 'n', player.room = 0;
    enemy[0] = new Entity::Kobold();
    enemy[1] = new Entity::Spider();
    enemy[0]->x = enemy[0]->y = 5;
    enemy[1]->x = enemy[1]->y = 8;
}

void DRAW() {
    clear();
    printw("ATK: %d, DEF: %d, HP: %d \n", player.power, player.defense, player.hp);
    printw("#");
    for (int i = 0; i < room[player.room].width - 1; i++) {
        printw("--");
    }
    printw("-#\n");
    for (int j = 0; j < room[player.room].height; j++) {
        printw("|");
        for (int i = 0; i < room[player.room].width; i++) {
            if (i == room[player.room].width - 1) {
                if (i == player.x and j == player.y) {
                    printw("%c", player.icon);
                }
                else if (i == enemy[enemy_ID]->x and j == enemy[enemy_ID]->y) {
                    printw("%c", enemy[enemy_ID]->icon);
                }
                else {
                    printw(" ");
                }

            }
            else {
                if (i == player.x and j == player.y) {
                    printw("%c ", player.icon);
                }
                else if (i == enemy[enemy_ID]->x and j == enemy[enemy_ID]->y) {
                    printw("%c ", enemy[enemy_ID]->icon);
                }
                else {
                    printw("  ");
                }
            }
        }
        printw("|\n");
    }
    printw("#");
    for (int i = 0; i < width - 1; i++) {
        printw("--");
    }
    printw("-#\n");

    //Debug
    mvprintw(1, 30, "%d %s %c %c", tick_count, typeid(enemy[0]).name(), player.collide, enemy[0]->collide);
}

void LOGIC() {
    //GAME LOGIC
    ////////////
        tick_count += 1;
        srand(time(NULL));
    ////////////

    //BATTLE LOGIC
    //////////////
        if (player.collide == 'e' or enemy[enemy_ID]->collide == 'p') {
            for (int i; i < enemy_count; i++) {
                if (enemy[enemy_ID]->collide == 'p')
                    player.target = i;
            }

            if (player.speed > enemy[player.target]->speed) {

            }
            else if (player.speed < enemy[player.target]->speed) {

            }
            else if (player.speed == enemy[player.target]->speed) {
                if (player.collide = 'e') {
                    if (rand() % 2) {
                        enemy[player.target]->hp -= player.power - enemy[player.target]->defense;
                        mvprintw(14, 0, "You hit %s with %d damage", enemy[player.target], player.power - enemy[player.target]->defense);
                        clrtoeol();
                    }
                    else {

                    }
                }
                else if (player.collide = 'n') {
                    player.hp -= enemy[player.target]->power - player.defense;
                    mvprintw(14, 0, "%s hit you with %d damage", enemy[player.target], enemy[player.target]->power - player.defense);
                    getch();
                    clrtoeol();
                }
            }
        }
    //////////////

    //PLAYER LOGIC
    //////////////
        if (player.x - 1 == enemy[enemy_ID]->x and player.y == enemy[enemy_ID]->y and key == 'a'
        or player.x + 1 == enemy[enemy_ID]->x and player.y == enemy[enemy_ID]->y and key == 'd'
        or player.y - 1 == enemy[enemy_ID]->y and player.x == enemy[enemy_ID]->x and key == 'w'
        or player.y + 1 == enemy[enemy_ID]->y and player.x == enemy[enemy_ID]->x and key == 's'
        )
            player.collide = 'e';
        else
            player.collide = 'n';

        if (player.x < 0)
            player.x = 0;
        else if (player.x == width)
            player.x = width - 1;
        else if (player.y < 0)
            player.y = 0;
        else if (player.y == height)
            player.y = height - 1;
    //////////////

    //ENEMY LOGIC
    /////////////
        for (enemy_ID = 0; enemy_ID < enemy_count - 1; enemy_ID++)
            enemy_ID++;

        if (enemy[enemy_ID]->x == player.x and enemy[enemy_ID]->y > player.y and enemy[enemy_ID]->y - 1 != player.y)
            enemy[enemy_ID]->move(1);
        else if (enemy[enemy_ID]->x == player.x and enemy[enemy_ID]->y < player.y and enemy[enemy_ID]->y + 1 != player.y)
            enemy[enemy_ID]->move(2);
        else if (enemy[enemy_ID]->y == player.y and enemy[enemy_ID]->x > player.x and enemy[enemy_ID]->x - 1 != player.x)
            enemy[enemy_ID]->move(3);
        else if (enemy[enemy_ID]->y == player.y and enemy[enemy_ID]->x < player.x and enemy[enemy_ID]->x + 1 != player.x)
            enemy[enemy_ID]->move(4);

        if (enemy[enemy_ID]->x - 1 == player.x and player.y == enemy[enemy_ID]->y
        or enemy[enemy_ID]->x + 1 == player.x and player.y == enemy[enemy_ID]->y
        or enemy[enemy_ID]->y - 1 == player.y and player.x == enemy[enemy_ID]->x
        or enemy[enemy_ID]->y + 1 == player.y and player.x == enemy[enemy_ID]->x
        )
            enemy[enemy_ID]->collide = 'p';
        else
            enemy[enemy_ID]->collide = 'n';
    /////////////
}

void INPUT() {
    key = getch();
    switch (key) {
        case 'w':
            player.move(1);
            break;
        case 's':
            player.move(2);
            break;
        case 'a':
            player.move(3);
            break;
        case 'd':
            player.move(4);
            break;
    }
}

int main() {
    initscr();
    cbreak();
    noecho();
    INIT();
    DRAW();
    while (key != '\n') {
        //Main Loop
        ///////////////////////
            INPUT();
            LOGIC();
            DRAW();
        ///////////////////////
        refresh();
    }
    endwin();
    return 0;
}

[问题](抱歉,标题可能令人困惑)我正在制作一个老式的基于 ascii 的 Rogue-like。这是一些代码:

    class Entity {
    public:
        class Player {
        public:
            int x, y, target;
            char collide;
            int power = 1, speed = 1, defense = 1, health = 10;
            int hp = health;
            
            void move(int dir);
        };
        class Kobold {
        public:
            int x, y;
            char collide;
            const int power = 2, speed = 1, defense = 1, health = 3;
            int hp = health;

            void move(int dir);
        };

我的目标是命名一个数组enemy,并让每个数组值(或任何它的名称)都有一个不同的类作为其类型。例如:enemy[0]可以是一个对象,Kobold它是 的成员Entity,haveenemy[5]是一个对象,Spider它是Entity等的成员。这样就更容易拥有更容易在for循环中声明和引用的敌人。我要问的是如何以这种方式声明数组enemy。拥有并回答我的要求会很好,但我不确定这是否可能以一种简单的方式实现,所以如果有人有更简单或更容易的方法来实现我的目标,我也希望看到它。提前致谢。

编辑:示例代码(忽略它不是真实的):

//Declaring an array with 8 values with no type
void enemy[8];

//Assigning the Kobold class to the first enemy as it's type
enemy[0] = Entity::Kobold;

//Assigning the Spider class to the 3rd enemy as it's type
enemy[2] = Entity::Spider;

//Player attacks 3rd enemy (spider) and inflicts damage
enemy[2].hp -= player.power;

//Checks if any enemy is dead, and if so then delete it
for (int i = 0; i < 8; i++) {
    if (enemy[i].hp <= 0) {
        //Removing enemy from it's class, therefore deleting it
        void enemy[i];
    }
}

//First enemy (Kobold) attacks player, inflicting damage
player.hp -= enemy[0].power;

//Player isn't dead, and attacks first enemy (Kobold) and kills it
enemy[0].hp -= player.power;

//Checks if any enemy is dead, and if so then delete it (again)
for (int i = 0; i < 8; i++) {
    if (enemy[i].hp <= 0) {
        //Removing enemy from it's class, therefore deleting it
        void enemy[i];
    }
}

printw("You Win!");

我知道它无效并且可能有一些错误,但它只是为了帮助可视化我正在尝试做的事情。

编辑:

这类似于我要问的问题: 是否有可能拥有来自同一基类的不同对象的数组?

我正在尝试将数组作为对象,Entity但我想让任何数组值成为Entity. 所以是这样的:

Entity enemy[5];
enemy[0] = new Kobold;
enemy[4] = new Spider;

然后我可以参考这样更容易的:

for (int i = 0; i < 5; i++) {
    if (array[i].x == player.x and array[i].y == player.y) {
        printw("player and an enemy are in the same spot");
    }
}

所以重点是一次轻松地访问每个敌人(实际上)的变量。

另一个例子(检查任何敌人是否有 0 hp):

for (int i = 0; i < amount_of_enemies; i++) {
    if (enemy[i].hp <= 0) {
        //some code to delete the enemy and give player XP etc.
    }
}

标签: c++arraysclassobject

解决方案


第1部分

这里可以使用两种流行的方法,一种是使用 C++17 的std::any。另一种方法是使用virtual方法和接口。

std::any方法允许在同一个数组中存储任何不相关的类std::vector<std::any>。std::any 允许将不同类型存储在仅支持单一类型的单个容器中。

稍后当从该数组访问实体时,您必须检查它们的类型并将其转换为特定类型并处理if子句内的每个类型。每个if都会有特定于这种类型的处理代码。

对于任何处理的类,您都可以拥有完全不同的方法和字段,例如下面代码中的方法.player_shout()(for Player) 和.kobold_defense()(for Kobold)。

数组也可能有一些你无法处理或不知道的类型,那么你可以跳过它们或以其他方式处理,见下面的代码:

在线尝试!

#include <iostream>
#include <vector>
#include <any>

class Entity {
public:        
    class Player {
    public:
        int x, y, target;
        char collide;
        int power = 1, speed = 1, defense = 1, health = 10;
        int hp = health;
        
        void move(int dir) { std::cout << "Moved Player to dir " << dir << std::endl; }
        void player_shout() { std::cout << "Player shouted!" << std::endl; }
    };
    class Kobold {
    public:
        int x, y;
        char collide;
        const int power = 2, speed = 1, defense = 1, health = 3;
        int hp = health;

        void move(int dir) { std::cout << "Moved Kobold to dir " << dir << std::endl; }
        void kobold_defense() { std::cout << "Kobold defensed!" << std::endl; }
    };
};

void ProcessEntities(std::vector<std::any> & entities) {
    for (std::size_t i = 0; i < entities.size(); ++i) {
        auto & e = entities[i];
        if (e.type() == typeid(Entity::Player)) {
            Entity::Player & player = *std::any_cast<Entity::Player>(&e);
            player.move(2);
            player.player_shout();
        } else if (e.type() == typeid(Entity::Kobold)) {
            Entity::Kobold & kobold = *std::any_cast<Entity::Kobold>(&e);
            kobold.move(1);
            kobold.kobold_defense();
        } else
            std::cout << "Unknown entity " << i << " of type "
                << e.type().name() << std::endl;
    }
}

struct BadType {};

int main() {
    std::vector<std::any> entities = {
        Entity::Player(), Entity::Kobold(), Entity::Player(),
        Entity::Player(), BadType(), Entity::Kobold(), BadType()};
    ProcessEntities(entities);
}

输出:

oved Player to dir 2
Player shouted!
Moved Kobold to dir 1
Kobold defensed!
Moved Player to dir 2
Player shouted!
Moved Player to dir 2
Player shouted!
Unknown entity 4 of type 7BadType
Moved Kobold to dir 1
Kobold defensed!
Unknown entity 6 of type 7BadType

第2部分

您还可以使用虚拟方法来表示具有某些属性(字段)和所有实体共有的方法的公共类。这称为虚拟接口方法。

所有实体都应该从这个通用接口继承并实现所有继承的方法。通用类本身不实现接口方法,但如果需要它可以。

您可以拥有任意深度的虚拟类层次结构,一切皆有可能。以下示例显示了简单Common类的实现以及所有实体对它的继承。

您还可以将虚拟类继承与类型转换方法(std::any在我的答案的第 1 部分中完成)混合使用。以下示例调用通用虚拟方法move()并显示通用属性speed。其余的逻辑是通过使用type()方法的类型检查和使用dynamic_cast的类型转换来完成的,在转换之后,您可以使用每个实体的特定(非继承)属性。

因此,您可以以任意方式将虚拟继承与动态类型转换混合。

在线尝试!

#include <iostream>
#include <vector>
#include <memory>

class Entity {
public:
    class Common {
    public:
        int x, y;
        char collide;
        int power = 1, speed = 1, defense = 1, health = 10;
        int hp = health;

        virtual void move(int dir) = 0;
        virtual std::type_info const & type() const = 0;
        virtual ~Common() {}
    };

    class Player : public Common {
    public:
        int player_attr = 123;

        std::type_info const & type() const { return typeid(*this); }
        void move(int dir) { std::cout << "Moved Player to dir "
            << dir << " with health " << health << std::endl; }
        void player_shout() { std::cout << "Player shouted with attr "
            << player_attr << "!" << std::endl; }
    };
    class Kobold : public Common {
    public:
        int kobold_attr = 456;

        std::type_info const & type() const { return typeid(*this); }
        void move(int dir) { std::cout << "Moved Kobold to dir "
            << dir << " with health " << health << std::endl; }
        void kobold_defense() { std::cout << "Kobold defensed with attr "
            << kobold_attr << " !" << std::endl; }
    };
};

void ProcessEntities(std::vector<std::shared_ptr<Entity::Common>> & entities) {
    for (std::size_t i = 0; i < entities.size(); ++i) {
        auto & e = entities[i];
        std::cout << "Entity " << i << ": Speed " << e->speed << ". ";
        e->move(i % 3); // Common method
        if (e->type() == typeid(Entity::Player)) {
            Entity::Player & player = dynamic_cast<Entity::Player &>(*e);
            player.player_shout();
            std::cout << "Player attr " << player.player_attr << std::endl;
        } else if (e->type() == typeid(Entity::Kobold)) {
            Entity::Kobold & kobold = dynamic_cast<Entity::Kobold &>(*e);
            kobold.kobold_defense();
            std::cout << "Kobold attr " << kobold.kobold_attr << std::endl;
        } else
            std::cout << "Unknown entity " << i << std::endl;
    }
}

int main() {
    std::vector<std::shared_ptr<Entity::Common>> entities = {
        std::make_shared<Entity::Player>(), std::make_shared<Entity::Kobold>(),
        std::make_shared<Entity::Player>(), std::make_shared<Entity::Player>(),
        std::make_shared<Entity::Kobold>()};
    ProcessEntities(entities);
}

输出:

Entity 0: Speed 1. Moved Player to dir 0 with health 10
Player shouted with attr 123!
Player attr 123
Entity 1: Speed 1. Moved Kobold to dir 1 with health 10
Kobold defensed with attr 456 !
Kobold attr 456
Entity 2: Speed 1. Moved Player to dir 2 with health 10
Player shouted with attr 123!
Player attr 123
Entity 3: Speed 1. Moved Player to dir 0 with health 10
Player shouted with attr 123!
Player attr 123
Entity 4: Speed 1. Moved Kobold to dir 1 with health 10
Kobold defensed with attr 456 !
Kobold attr 456

如果您需要更简单的虚拟继承版本,为了让您更容易理解,我创建了这个更简单的代码:

在线尝试!

class Entity {
public:
    class Common {
    public:
        int x, y;
        char collide;
        int power = 0, speed = 0, defense = 0, health = 0;
        int hp = health;
        
        virtual void move(int dir) = 0;
        virtual ~Common() {}
    };
    class Player : public Common {
    public:
        Player() { power = 1; speed = 1; defense = 1; health = 10; hp = health; }
        void move(int dir) {}
    };
    class Kobold : public Common {
    public:
        Kobold() { power = 2; speed = 1; defense = 1; health = 3; hp = health; }
        void move(int dir) {}
    };
    class Spider : public Common {
    public:
        Spider() { power = 3; speed = 2; defense = 2; health = 5; hp = health; }
        void move(int dir) {}
    };
};

int main() {
    Entity::Common * enemies[3] = {
        new Entity::Kobold(), new Entity::Spider()};
    enemies[0]->move(3);
    enemies[0]->hp -= enemies[1]->power;
}

第 3 部分

如果两个第一个解决方案(来自我的答案的第 1 部分和第 2 部分)不适合您,那么我可以建议基于std::variantstd::visit的第三个解决方案。

我从你的问题中提取了你如何处理敌人和玩家的例子。并对其进行了一些修改,以使用我使用 std::variant 的第三个想法。请参阅下面的代码。

对您的敌人类进行了一些微小的修改,例如添加复制构造函数(请参阅 参考资料CONSTR(Kobold);)和实现Empty一种实体来存储空值。

您可以在以下代码中看到我创建了两个宏ENEN2. 基本上,如果您将敌人存储为 std::variant ,那么您必须使用这两个宏之一才能访问和/或修改敌人或与玩家互动。因为您不能直接使用 std::variant 类型。这些宏用于std::visit访问底层的特定类型。

为了开始使用像下面这样的 std::variant 解决方案,您只需定义一次特殊类型Enemy,只需枚举所有可能的敌人类using Enemy = std::variant<Entity::Kobold, Entity::Spider, Entity::Dragon, Entity::Empty>;,请参阅代码。

我创建Empty了将空值存储在数组中的类。您可以通过特殊宏检查元素是否为空EMPTY(enemy)

在线尝试!

#include <variant>
#include <vector>
#include <iostream>

class Entity {
public:
    #define COPY(self, o) { self.x = o.x; self.y = o.y; self.collide = o.collide; self.power = o.power; self.speed = o.speed; self.defense = o.defense; self.health = o.health; self.hp = o.hp; }
    #define CONSTR(T) \
        T() {} \
        T(T const & o) { COPY((*this), o); } \
        T & operator =(T const & o) { COPY((*this), o); return *this; }

    class Player {
    public:
        int x, y, target;
        char collide;
        int power = 1, speed = 1, defense = 1, health = 10;
        int hp = health;
        
        void show_hp() const { std::cout << "Player health " << hp << std::endl; }
        void move(int dir) { std::cout << "Moved Player to dir " << dir << std::endl; }
    };
    class Kobold {
    public:
        int x, y;
        char collide;
        int power = 2, speed = 1, defense = 1, health = 1;
        int hp = health;

        CONSTR(Kobold);

        void show_hp() const { std::cout << "Kobold health " << hp << (hp <= 0 ? ", Dead!!!" : "") << std::endl; }
        void move(int dir) { std::cout << "Moved Kobold to dir " << dir << std::endl; }
    };
    class Spider {
    public:
        int x, y;
        char collide;
        int power = 3, speed = 1, defense = 1, health = 5;
        int hp = health;

        CONSTR(Spider);

        void show_hp() const { std::cout << "Spider health " << hp << (hp <= 0 ? ", Dead!!!" : "") << std::endl; }
        void move(int dir) { std::cout << "Moved Spider to dir " << dir << std::endl; }
    };
    class Dragon {
    public:
        int x, y;
        char collide;
        int power = 7, speed = 1, defense = 1, health = 20;
        int hp = health;

        CONSTR(Dragon);

        void show_hp() const { std::cout << "Dragon health " << hp << (hp <= 0 ? ", Dead!!!" : "") << std::endl; }
        void move(int dir) { std::cout << "Moved Dragon to dir " << dir << std::endl; }
    };
    class Empty {
    public:
        int x, y;
        char collide;
        int power = 0, speed = 0, defense = 0, health = 1;
        int hp = health;

        CONSTR(Empty);

        void show_hp() const { std::cout << "Empty!!!" << std::endl; }
        void move(int dir) { std::cout << "Empty!!!" << std::endl; }
    };
    #define EMPTY(x) (typeid(x) == typeid(Entity::Empty))
};

using Enemy = std::variant<
    Entity::Kobold, Entity::Spider, Entity::Dragon, Entity::Empty>;

#define EN(_pos, code) \
    std::visit([&](auto & enemy) code, enemies[_pos]);

#define EN2(_pos0, _pos1, code) \
    std::visit([&](auto & enemyA) { \
        std::visit([&](auto & enemyB) code, enemies[_pos1]); \
    }, enemies[_pos0]);

void Process(std::vector<Enemy> & enemies) {
    Entity::Player player;

    EN(2, {
        // Player attacks 3rd enemy (spider) and inflicts damage
        enemy.show_hp();
        enemy.hp -= player.power;
        enemy.show_hp();
    });

    //Checks if any enemy is dead, and if so then delete it
    for (int i = 0; i < enemies.size(); ++i) {
        EN(i, {
            if (!EMPTY(enemy) && enemy.hp <= 0) {
                //Removing enemy from it's class, therefore deleting it
                enemies[i] = Entity::Empty();
                std::cout << "Enemy " << i << " is dead and removed." << std::endl;
            }
        });
    }

    EN(0, {
        player.show_hp();
        //First enemy (Kobold) attacks player, inflicting damage
        player.hp -= enemy.power;
        player.show_hp();

        enemy.show_hp();
        //Player isn't dead, and attacks first enemy (Kobold) and kills it
        enemy.hp -= player.power;
        enemy.show_hp();
    });

    //Checks if any enemy is dead, and if so then delete it
    for (int i = 0; i < enemies.size(); ++i) {
        EN(i, {
            if (!EMPTY(enemy) && enemy.hp <= 0) {
                //Removing enemy from it's class, therefore deleting it
                enemies[i] = Entity::Empty();
                std::cout << "Enemy " << i << " is dead and removed." << std::endl;
            }
        });
    }

    EN2(3, 4, {
        enemyA.show_hp();
        enemyA.hp -= enemyB.power;
        enemyA.show_hp();

        enemyB.show_hp();
        enemyB.hp -= enemyA.power;
        enemyB.show_hp();
    });

    //Checks if any enemy is dead, and if so then delete it
    for (int i = 0; i < enemies.size(); ++i) {
        EN(i, {
            if (!EMPTY(enemy) && enemy.hp <= 0) {
                //Removing enemy from it's class, therefore deleting it
                enemies[i] = Entity::Empty();
                std::cout << "Enemy " << i << " is dead and removed." << std::endl;
            }
        });
    }

    std::cout << "You win!" << std::endl;
}

int main() {
    std::vector<Enemy> enemies = {
        Entity::Kobold(), Entity::Dragon(), Entity::Spider(),
        Entity::Spider(), Entity::Dragon(), Entity::Kobold()};
    Process(enemies);
}

输出:

Spider health 5
Spider health 4
Player health 10
Player health 8
Kobold health 1
Kobold health 0, Dead!!!
Enemy 0 is dead and removed.
Spider health 5
Spider health -2, Dead!!!
Dragon health 20
Dragon health 17
Enemy 3 is dead and removed.
You win!

推荐阅读