Как сделать общие поля класса без наследования в с++

Рейтинг: 0Ответов: 4Опубликовано: 25.07.2025

Я делаю змейку в консоли (разделил на файлы) и в файле logic.cpp у меня такой код:

#include <iostream>
#include "logic.h"

using namespace std;

class Field
{
public:
    static const int LENGTH = 10;
    static const int WIDTH = 10;
    static char field[LENGTH][WIDTH];

    void Fill()
    {
        // заполянем поле 
        for (int i = 0; i < LENGTH; i++)
        {
            for (int j = 0; j < WIDTH; j++)
            {
                field[i][j] = '#';
            }
        }
    }
};

class Apple
{
private:
    int appleX, appleY;

    // Field* filed;

public:

    void FillApple()
    {

        for (int i = 0; i < 3; i++)
        {
            // генерируем координаты для яблок
            appleY = 0 + rand() % Field::LENGTH;
            appleX = 0 + rand() % Field::WIDTH;

            // суем яблоки в поле 
            Field::field[appleY][appleX] = '@';
        }
    }
};

(Другой класс и методы не вставил) Так вот, я хочу чтобы мои другие классы использовали поля из Field. Для этого я использую Field::, но для этого мои поля должны быть в public, хотя я хочу чтобы они были в private (для инкапсуляции), но не получается. Также использовал наследование, и мои поля были в protected, но такой способ плохой. Также можно использовать глобальные переменные, но это так себе, хочу чтобы все было там где надо. Можно использовать ссылки или указатель, и я использовал // Field* filed;, но не знаю насколько это правильно + когда передаю в метод, ничего не меняется. Помогите пожалуйста

Ответы

▲ 1

Не знаю как в c++, а в c# вложенный класс имеет доступ ко всем членам охватывающего класса, поэтому можно сделать так:

using System;
class Field {
  private int a = 3;
  public class Apple {
    public int GetField(Field f) { return f.a; } 
  }
}
static class Program {
  static void Main() {
    Console.WriteLine(new Field.Apple().GetField(new Field()));
  }
}

-->

3

Может, в c++ тоже так?

▲ 0

Вам не понадобятся подобные "извращения", если вы вспомните основы ООП. Там не только инкапсуляция, но также присутствует Абстракция, что на мой (наверное устаревший) взгляд первичнее и главнее.

Так вот:

  • класс Поле (Field) - создание игрового поля
  • класс Яблоко (Apple) - создание яблока
  • класс Игра (Game. Для простоты просто Игра, а не Змейка, например) - игровой движок, отвечающий за создание объектов игры (Поля, Яблок, Змейки), их взаимодействия (столкновения и поедания яблок), а также управления Змейкой
  • можно добавить класс Ячейка (Cell) для хранения позиции поля и какого-нибудь окрашивания (текстуры), и от него наследовать Яблоко.

В таком случае инкапсуляция и взаимодействие объектов станет намного проще и, что самое главное, понятнее.

Примерный код, крупными мазками:

class Game {
    Game() {
        field = Field(10, 10);
        // Создаем и размещаем змейку на игровом Поле 
        // randomCell - возвращает любую свободную ячейку        
        snake = Snake(field.randomCell());

        for(int i = 0; i < 3; i++) {
            // freeRandomCell - возвращает свободную ячейку без яблок
            Cell cell = field.freeRandomCell();
            // Добавляем яблоко с координатами cell на поле
            field.setCell(Apple(cell));
        }

        // Главный цикл игры 
        play();
    }

    // Метод вызывается в главном цикле игры
    move() {
        // Делаем условное перемещение. 
        // В реальном коде тут идет проверка считывания кода нажатой кнопки клавиатуры (если таковая была),
        // в соответствии с которой делается нужное изменение перемещения
        snake.move();
    
        // Проверяем позицию головы змейки  
        if ( !field.isFreeCell(snake.head()) ) {
            ...
        }
    }
}

Я разместил этот примерный код в конструкторе, но можно сделать отдельный метод, который вызывать в цикле, отвечающем за "Создать новую игру"

▲ 0

Можно воспользоваться ссылкой, но помнить, что время жизни этой ссылки будет меньше, чем время жизни объекта, на которую она ссылается.

#include <iostream>
#include <cstdlib>

using namespace std;

class Field
{
private:
    static constexpr int LENGTH = 10;
    static constexpr int WIDTH = 10;
    char field[LENGTH][WIDTH]; // убрал static (если не нужно)

public:
    void Fill()
    {
        for (int i = 0; i < LENGTH; i++)
        {
            for (int j = 0; j < WIDTH; j++)
            {
                field[i][j] = '#';
            }
        }
    }

    void setCell(int y, int x, char value)
    {
        field[y][x] = value;
    }

    char getCell(int y, int x) const
    {
        return field[y][x];
    }

    int getLength() const { return LENGTH; }
    int getWidth() const { return WIDTH; }
};

class Apple
{
private:
    Field& fieldRef;

public:
    Apple(Field& field) : fieldRef(field) {}

    void FillApple()
    {
        for (int i = 0; i < 3; i++)
        {
            int appleY = rand() % fieldRef.getLength();
            int appleX = rand() % fieldRef.getWidth();
            fieldRef.setCell(appleY, appleX, '@');
        }
    }
};
▲ -1

Без наследования особо никак, но наследование в этом случае - бесплатная вещь (не слушайте апостолов Торвальдса) и может быть сделана невидимой.

Можно поступить так:

class Apple
{
private:
    int appleX, appleY;

    // Field* filed;
    enum {eLength = Field::LENGTH, eWidth =  Field::WIDTH};
public:

    void FillApple()
    {
        auto& field = Field::field;
        
        for (int i = 0; i < 3; i++)
        {
            appleY = 0 + rand() % eLength; // может убрать это в Field 
            appleX = 0 + rand() % eWidth;

            // суем яблоки в поле 
            field[appleY][appleX] = '@';
        }

Не совсем то, и все равно много писать, оправдывает себя только если Field:: встречается очень часто.

// Так никто не узнает
class Apple : private Field {

С точки зрения памяти это может быть либо лучше, либо то же самое, что иметь Field field; в классе.

Вопрос в том, хотите ли, чтобы все фрукты использовали один и тот же регистр полей или у каждого свой интерфейс? Потому что возможна конструкция CRTP:

class Apple : private Field<Apple> {