Как сделать общие поля класса без наследования в с++
Я делаю змейку в консоли (разделил на файлы) и в файле 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;, но не знаю насколько это правильно + когда передаю в метод, ничего не меняется. Помогите пожалуйста
Ответы (4 шт):
Без наследования особо никак, но наследование в этом случае - бесплатная вещь (не слушайте апостолов Торвальдса) и может быть сделана невидимой.
Можно поступить так:
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> {
Вам не понадобятся подобные "извращения", если вы вспомните основы ООП. Там не только инкапсуляция, но также присутствует Абстракция, что на мой (наверное устаревший) взгляд первичнее и главнее.
Так вот:
- класс Поле (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()) ) {
...
}
}
}
Я разместил этот примерный код в конструкторе, но можно сделать отдельный метод, который вызывать в цикле, отвечающем за "Создать новую игру"
Не знаю как в 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++ тоже так?
Можно воспользоваться ссылкой, но помнить, что время жизни этой ссылки будет меньше, чем время жизни объекта, на которую она ссылается.
#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, '@');
}
}
};