【问题标题】:Superclass method with subclass data具有子类数据的超类方法
【发布时间】:2021-04-05 09:48:00
【问题描述】:

早上好。 我正在学习一些关于继承和控制台操作的概念。 如您所见,我是初学者。

所以我试图在控制台上绘制一个字符,我希望更新它的位置。 现在请注意,我知道我的代码可能在多种方面都非常糟糕,并且可能有数百种更好的完全替代方法可以做到这一点,但我想首先了解一些继承概念以及为什么它不能按原样工作。

所以,我在控制台上绘制了我的玩家角色“X”,然后我通过调用特定的成员方法来更新它的位置来移动它。 现在,因为我让 Player 类扩展了 DrawConsole 类,所以我想在 Player 实例上调用 drawConsole。

当我这样做时,我让 playerA 实例的位置坐标实际更新,但对播放器实例的引用现在有两个名为“位置”的成员,如您在图像上看到的那样。 我怎么能说选择 playerA 而不完全重新编写代码或使用完全不同的方法? 或者也许只是我不能,我实际上已经完全改变了方法? 希望我能够表达我的疑问。

这里是代码

#include <ctime>
#include <cstdlib>
#include "windows.h"

#define width 100
#define height 15

class StaticBuffer
{
public:
    StaticBuffer() { srand(time(0)); }

    void loadBackGround(CHAR_INFO *backGround, int swidth, int sheight)
    {

        for (int y = 0; y < sheight; y++)
        {
            int rnd = rand() % 100 + 1;
            for (int x = 0; x < swidth; x++)

                if (y == 0 || y == sheight - 1)
                {
                    backGround[y * swidth + x].Char.AsciiChar = (unsigned char)127;
                    backGround[y * swidth + x].Attributes = (unsigned char)23;
                }
                else if (x > 4 * rnd && x < (4 * rnd) + 5 || x > 4 * rnd / 2 && x < (4 * rnd / 2) + 5)
                {
                    backGround[y * swidth + x].Char.AsciiChar = (unsigned char)178;
                    backGround[y * swidth + x].Attributes = (unsigned char)12;
                }
                else
                {
                    backGround[y * swidth + x].Char.AsciiChar = 32;
                    backGround[y * swidth + x].Attributes = (unsigned char)3;
                }
        }

    }

private:
};

class DrawConsole
{
public:
    DrawConsole()
    {
        wConsole = GetStdHandle(STD_OUTPUT_HANDLE);
        rConsole = GetStdHandle(STD_INPUT_HANDLE);
        windowSizeInit = {0, 0, 30, 10};
        windowSize = {0, 0, bufferSize.X - 1, bufferSize.Y - 1};
        backGround = new CHAR_INFO[bufferSize.X * bufferSize.Y];
        obstacle = new CHAR_INFO[bufferSize.X * bufferSize.Y];
        inputBuffer = new INPUT_RECORD[4];
        drawBackGround.loadBackGround(backGround, bufferSize.X, bufferSize.Y);
        nInputWritten = 0;
        nOutputWritten = 0;
        playerString[0] = L'X';
        charLenght = 1;
        position = {10,13};
    }

    void drawConsole()
    {
        wConsole = GetStdHandle(STD_OUTPUT_HANDLE);
        rConsole = GetStdHandle(STD_INPUT_HANDLE);

        SetConsoleWindowInfo(wConsole, TRUE, &windowSizeInit);
        wConsole = GetStdHandle(STD_OUTPUT_HANDLE);

        SetConsoleScreenBufferSize(wConsole, bufferSize);
        SetConsoleWindowInfo(wConsole, TRUE, &windowSize);

        WriteConsoleOutputA(wConsole, backGround, bufferSize, {0,0}, &windowSize);
        WriteConsoleOutputCharacterW(wConsole, playerString, charLenght, position, &nOutputWritten);
    }

    void drawChar()
    {
        WriteConsoleOutputA(wConsole, backGround, bufferSize, {0,0}, &windowSize);
        WriteConsoleOutputCharacterW(wConsole, playerString, charLenght, position, &nOutputWritten);
    }


protected:
    HANDLE wConsole;
    HANDLE rConsole;

    COORD bufferSize{width, height};
    SMALL_RECT windowSizeInit;
    SMALL_RECT windowSize;

    CHAR_INFO *backGround;
    CHAR_INFO *obstacle;

    INPUT_RECORD *inputBuffer;
    DWORD nInputWritten;
    DWORD nOutputWritten;
    DWORD charLenght;

    StaticBuffer drawBackGround;
    wchar_t playerString[2];


    COORD position;
};

class Player :public DrawConsole
{
public:
    Player()
    {
        position.X = 20;
        position.Y = height - 2;
    }

    void movePlayerRight()
    {
        for (int i = 0; i < 3; i++)
            position.X += 1;
    }
    COORD getPositionC() { return position; }

private:

    COORD position;
};
Player *playerA = new Player;
DrawConsole *myConsole = new DrawConsole;

int main()
{
    myConsole->drawConsole();
    while (true)
    {
        //Sleep(5000);
        playerA->movePlayerRight();
        playerA->drawChar();
    }
} 

【问题讨论】:

  • 只需从派生类中删除位置成员即可。
  • 我需要它在 Player 实例上调用 movePlayerRight()。
  • "我需要它" 你需要一个职位或两个不同的职位。如果您需要一个职位,则需要一个名为“职位”的变量。删除另一个。如果您需要两个不同的位置,则需要两个变量,但是将它们都命名为“位置”是一个非常糟糕的主意。重命名另一个。如果你不能决定是想要一个职位还是两个不同的职位,那是设计问题,而不是 C++ 问题。

标签: c++ oop inheritance


【解决方案1】:

这取决于你真正想要什么。如果想法是两个变量代表相同的概念,则不必在派生类中重新定义它,因为它在基类中受到“保护”,因此派生类能够访问它。

如果变量代表不同的事物,但它们恰好具有相同的名称(顺便说一句,这不是一个好主意),您可以使用定义变量的类来限定它。因此,例如,你可以这样做:

DrawConsole::position.X += 1;

要修改DrawConsole中声明的position变量和:

Player::position.X += 1;

修改Player中声明的position变量

但是,正如我之前所说,我会尽量避免两个变量同名,因为这很容易导致错误。


更新:

如果您想保持继承不变,只需从Player 中删除属性position。原因如下:

当前,当您调用drawChar 时,您正在执行DrawConsole 类中的代码(Player 本身没有定义drawChar 方法)。此代码无法访问Player::position,因为父类中的方法无法访问子类中的属性(即使您从子类的实例调用该方法),所以它只能看到DrawConsole::position,那就是它正在使用的变量。

但是当您在Player 的实例中调用movePlayerRigth 时,正在执行的代码是Player 类中的一个方法。此方法尝试访问位置属性,发现有两种可能性:DrawConsole::positionPlayer::position。在这种情况下,它选择Player::position,因为它是在同一个类中定义的。

因此,您有一个基于DrawConsole::position 绘制控制台的方法和另一个修改Player::position 的方法。这是行不通的,事实上如果你运行它,你会看到 X 没有移动。

如果您从Player 中删除position 变量,在movePlayerRight 中当您尝试访问变量position 时,代码会看到Player 没有定义位置属性,但它意识到它的父类 (DrawConsole) 确实定义了一个 position 属性,并且具有受保护的访问权限。受保护意味着子类中的代码可以直接访问它,因此movePlayerRight 将修改DrawConsole::position。在这种情况下,drawCharmovePlayerRight 都将访问同一个变量,并且将按预期工作。

因此,如果您希望这样,请从 Player 类中删除以下行:

COORD position;

您将看到代码按预期编译和工作(X 向右移动),因为现在Player 中的代码和DrawConsole 中的代码正在访问同一个变量 (DrawConsole::position)。

【讨论】:

  • 首先感谢您的回复。我只想更新我的 playerA 实例上的坐标位置,然后调用 drawChar 使其出现在屏幕上的另一个位置。它们代表相同的东西,但问题是我必须在这个概念中同时拥有两个“位置”变量。在 playerA 实例中,我需要它,因为我希望它代表要更新的当前玩家位置。在 drawChar 我必须拥有它,因为 WriteConsoleOutputCharacterW 需要它在它的主体中。那么问题来了。
  • 好吧,我的回答试图解决在继承中访问两个具有相同名称的变量的想法,但主要问题是你有一个从“DrawConsole”类继承的 Player 类,它实际上代表一个控制台(您甚至可以将其命名为“myConsole”)。但是播放器不是控制台,控制台包含播放器,所以 Player 应该是 DrawConsole 的一个属性,而不是子级。无论如何,如果这只是为了测试,请从 Player 中删除 position 属性,您会看到代码仍然可以正确编译,因为它将访问 DrawConsole 中的 position 属性。
  • 我的目标是有一个在屏幕上“绘制”东西的类和另一个代表我想在屏幕上绘制的东西的类。例如,我可以制作传递参数的东西,但我想体验一些继承,这就是重点。所以我的目标是有一个可以绘制的类,但使用来自另一个类的数据,我在其他地方以某种方式操作。所以,通过这种方式,让 Player 成为 Draw 的孩子对我来说似乎不是错误的方式。假设我想在不传递参数的情况下执行此操作。我该做什么?甚至可能吗?无论如何,再次感谢您的帮助。
  • 如果你想保持继承,那么解决方案是从 Player 中删除位置。我已经更新了答案来解释这背后的原因。
  • 是的,您可以从子节点访问和操作父节点的数据。但请记住,只有数据标记为public(因为那时每个人都可以访问它)或protected(因为你是一个子类)。如果标记为private,则无法访问。
猜你喜欢
  • 2019-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
  • 1970-01-01
  • 2011-10-24
  • 2012-01-22
  • 2020-06-11
相关资源
最近更新 更多