class emulZ80cpu { // класс эмулятора процессора
private:
// переменные
unsigned char * _pROMzxs; // указатель на начало физической памяти эмулатора ROM
unsigned short _zxsROMsize; // размер ROM
unsigned char * _pRAMzxs; // указатель на начало физической памяти эмулатора RAM
regSetZ80 _mainRegisters;
regSetZ80 _shadowRegisters;
t32bool _useMainRegisters = true32t;
regPair_16t _regPC;
unsigned short _regSP;
t32bool _IFF1; // Флаг разрешения прерывания;
t32bool _IFF2; // Флаг, копия IFF1 во время обработки NMI;
unsigned char _regR; // Регистр регенерации памяти, 8 бит. Увеличивается на 1 после каждой выборки команды, но инкремент затрагивает только младшие 7 бит, старший бит не меняется и может быть использован в программах
unsigned char _intI; // Старший байт адреса вектора прерывания в режиме IM 2;
unsigned char _modeIM; // Режим обработки прерываний. Устанавливается командами IM 0/1/2.
protected:
// внутренние функции
// установить/прочитать указатель текущей команды // установка отдельно старшего и младшего байта
void z80setPC(const unsigned short inPC) {_regPC.valuePair = inPC;};
void z80setPCH(const unsigned char inPCH) {_regPC.highByte = inPCH;};
void z80setPCL(const unsigned char inPCL) {_regPC.lowByte = inPCL;};
unsigned short z80getPC(void) {return _regPC.valuePair;};
// увеличение PC регистра на единичку / сдвиг вперед/назад на нужное количество позиций
void z80addPC(void) {++_regPC.valuePair;};
void z80movePC(const signed short posMove) {_regPC.valuePair += posMove;};
// установить/прочитать стэковый регистр SP
void z80setSP(const unsigned short inSP) {_regSP = inSP;};
unsigned short z80getSP(void) {return _regSP;};
// поменять местами основной и теневой набор регистров
void z80changeMainShadowSetRegs(void) {_useMainRegisters = !_useMainRegisters;};
// вернуть указатель на текущий набор регистров
regSetZ80 * _z80getCurrentSetRegs(void);
// прочитать/записать регистры и регистровые пары
unsigned char z80getOneReg(const tOneRegs selOneReg);
void z80setOneReg(const tOneRegs selOneReg, const unsigned char valOneReg);
unsigned short z80getPairReg(const tPairRegs selPairReg);
void z80setPairReg(const tPairRegs selPairReg, const unsigned short valPairReg);
// получить байт по адресу спектрума
unsigned char z80getByteByAddr(unsigned short srcAddr);
// получить байт по адресу PC // увеличивается PC На 1
unsigned char z80fetchByteByPC(void);
unsigned char _ticksByCmd; // длительнойсть текущей команды в тактах z80
public:
// внешние функции
// прописывание указателей на физическую память МК
void z80setMemoryPointers(const unsigned char * pROMzxs, const unsigned short zxsROMsize, const unsigned char * pRAMzxs) {
_pROMzxs = (unsigned char *)pROMzxs; _zxsROMsize = zxsROMsize; _pRAMzxs = (unsigned char *)pRAMzxs;
}
// сброс процессора
void z80reset(void) {
z80setPC(0); _IFF1 = false32t; _IFF2 = false32t; _regR = 0; _intI = 0; _modeIM = 0;
}
// работа процессора
void z80run(void);
};
к нему будет куча функций, обращающихся к компонентам класса, но поскольку их много, хотел бы их отделить от описания класса, т е сделать их типа внешними.
Например функция:
void z80_cmd_00_NOP(emulZ80cpu * cpu) { // пустая команда
cpu->_ticksByCmd = 4; // длительность команды в тактах z80
}
компилятор конечно ругается:
../Src/z80commands.cpp: In function ‘void z80_cmd_00_NOP(emulZ80cpu*)’:
../Src/z80commands.cpp:11:14: error: ‘unsigned char emulZ80cpu::_ticksByCmd’ is protected within this context
11 | cpu->_ticksByCmd = 4; // длительность команды в тактах z80
| ^~~~~~~~~~~
можно конечно все в public описать и тогда будет работать, но как то это не красиво…
Есть ли возможность как то или внешние функции подцепить или что то другое?
Ну, вариантов-то over-8k, смотря что на самом деле нужно.
можно все внешние функции, которым нужен доступ к внутренней кухне класса, описать как его друзей (кл. слово friend);
можно их все собрать в некий отдельный класс (как статические) и описать этот внешний класс другом основного класса;
можно их все собрать в некий класс (как статические) и основной класс пронаследовать от того;
можно все поля, которые требуются внешним функциям, снабдить public геттерами (и, если надо, сеттерами).
Могу предложить ещё 100500 вариантов. Ту как угодно можно делать. Вопрос – что удобнее и насколько параноидальна иде спрятать всё внутрь и чужих не пущать.
Обязательно обратите внимание вот на что: для тех полей, которые по жизни могут изменяться внутренними функциями класса, но не должны изменяться внешними, лучше объявлять их (поля) private, а доступ к ним посторонних функций оформлять public- геттерами у которых указан атрибут const. Это гарантирует, что ни одна зараза не сможет изменить поля извне.
в моем конкретном случае как бы не стоит задача сильно ограничить доступы к полям и/или делать потом наследуемые классы.
Основная задача разделить команды и основной объект по разным файлам, что бы читабельно было, не в одной куче.
Остановился на варианте объявления friend класса.
Т е этот дружественный клас может и должен иметь доступ ко всем полям/данным основного класса, мне не нужно его в чем то ограничивать.
как бы один из самых очевидных методов вытекает из сообщения компилятора.
Если вы хотели иметь доступ к члену класса, нафига вы его protected описали?
А так, в дополнение к описанному Евгением, можно просто накидать кучу функций, обращающихся к методам и членам класса. Например по типу, как вы работаете с любой библиотекой ардуино
Просьба разьяснить про экземпляр класса, дружеский класс я сделал, все работает.
Но как к нему обратиться из функций основного объекта не создавая экземпляр?
class emulZ80cmd { // класс команд процессора
private:
static void z80_cmd_00_NOP(emulZ80cpu * cpu); // пустая команда
static void z80_cmd_01_11_21_31_LD_RP_nn(emulZ80cpu * cpu); // Загрузить в регистровую пару значение следующих двух байт после команды
protected:
public:
static void z80executeCMD(emulZ80cpu * cpu); // выполнить команду из таблицы команд
};
//extern emulZ80cmd commandsZ80;
../Src/z80cpu.cpp: In member function ‘void emulZ80cpu::z80run()’:
../Src/z80cpu.cpp:85:9: error: ‘z80executeCMD’ was not declared in this scope
85 | z80executeCMD(this);
| ^~~~~~~~~~~~~
вы что-то странное создаете, не очень похожее на ООП… чешете правой рукой за левым ухом…
Зачем вам класс, если вы не собираетесь создавать даже одного экземпляра?
И хочу заметить, что довольно неудобно разбираться в том, что вы делаете, по двум заголовкам функций.
“Покажите код полностью” (с)
Так, просьба разъяснить, кто на ком стоял и кому куда надо обращаться. Кто у Вас основной и т.п.
Давайте уйдём от всех этих “основных”, а будем пользоваться именами и приводить полный код.
Я это понимал вот так, если Вы имели в виду что-то другое, объясняйте:
//
// Это класс функций-утилит, которые имеют право обращаться
// к private функциям класса Kaka (возможно, и други, гже он тоже
// объявлен другом.
// Здесь только объявление функций этого класса,
// определение будет отдельно
//
class Kaka;
struct Utils {
static void util1(Kaka & k);
static void util2(Kaka & k);
static void util3(Kaka & k);
};
//
// Это класс к private функциям которого зачем-то нужно
// обращаться посторонним функциям, которые мы собрали
// в классе Utils, который здесь объявлен другом
class Kaka {
void func1(void) { Serial.println("func1 called"); }
void func2(void) { Serial.println("func2 called"); }
void func3(void) { Serial.println("func3 called"); }
friend class Utils;
};
//
// Определение функция класса Utils
//
void Utils::util1(Kaka & k) { k.func1(); }
void Utils::util2(Kaka & k) { k.func2(); }
void Utils::util3(Kaka & k) { k.func3(); }
//
// Пример использования.
// Создаём экземпляр Kaka
// А экземпляра Utils не создаём, статические методы так вызываем.
//
void setup(void) {
Serial.begin(9600);
Kaka kaka;
Utils::util1(kaka);
Utils::util3(kaka);
Utils::util2(kaka);
}
void loop(void) {}