Преобразование void* в переменные разного типа

Меню есть нескольких типов:

  1. Информационное меню. Отображает актуальные данные. ReadOnly
  2. Программное меню. Служит для настройки некоторых параметров. Часть из этих параметров пересекается с параметрами из меню 1.
  3. Расписание. Позволяет установить время включения/выключения нескольких каналов в течении суток/недели.

В каком формате хранить - не суть важно. Изначально вопрос стоял, как работать с указателями.

Я хочу по указателю на переменную получить значение переменной, изменить его, сохранив в исходной переменной.
Во всех статьях/учебниках описаны примеры, которые не несут никакой смысловой нагрузки. При попытке повторить их, я получил болт на всё лицо. Было обидно, поэтому и попросил помощи у опытных товарищей.
Я уже придумал пару обходных решений, но, хочется разобраться, чтобы потом использовать возможности языка программирования более полно и с бОльшим КПД.

Вам нужно двигаясь по структуре меню отображать определённые данные относящиеся к элементам этого меню?
С указателями работать то не сложно. Указатель, есть адрес на память, а объём данных определяется как раз типом данных и, соответственно операции допустимые с этими данными.
Тогда, я думаю, вам точно необходимо в свою структуру добавить тип данных элемента меню, а ещё возможно флаг чтение/запись. В обработчике структуры получаете ID элемента меню и в зависимости от типа данных, переходите в область кода, которая возвращает определённый тип данных или записывает по адресу в указателе. Но придётся заняться ручным приведением типов.

Посмотрите здесь про указатели и приведение
https://easyelectronics.ru/file/yazyk-programmirovaniya-s-spravochnik/124-2

Спасибо большое за статью. Пошел разбираться :slight_smile:

Понимаете, в чём Ваша беда. Вы придумали какой-то костыль и теперь упорно за него цепляетесь. Я постоянно пытаюсь понять, что Вам нужно, а Вы мне всё этим свичом в нос тычете. Я вот почти уверен, что Вам там вообще не нужны ни свич, ни указатели на void. Но Вы вместо того, чтобы ответить на мои вопросы, постоянно говорите “см. свич”.

Не хочу я смотреть на свич - он там костыль, как и указатель на void! Попробуйте точно и чётко ответить на мои вопросы. А то уже день святого Нуба заканчивается, а пример я Вам так и не написал. Завтра то уже не получится :frowning:

То есть у вас в свитче отдельная ветка НА КАЖДУЮ строку меню? - ужос…

Добавьте в структуру меню тип переменной(как уже посоветовал почти каждый в этой ветке) - и вам достаточно будет сделать по одной ветке свитча на каждый тип. То есть одна ветка - для переменных типа uint16_t, другая для float, третья - String… И все.

Вместо свитча на 127 веток будет свитч на три… ну или пять, если еще добавить пару типов.

Фу, блин. Да я это и говорил изначально! Не хочу я свитчами пользоваться. Это была просто начальная идея. Я сам знаю, что это мягко говоря, дерьмовая идея. Потому и заморочился с указателями. Как их разрулить по типам тоже давно уже понял. Я хочу разобраться с указателями, только и всего. Вот об этом я и спрашивал в своём начальном сообщении. Все пишут, зачем вам оно, кучу всего написали. А как с указателями работать никто не написал.

Впервом сообщении вы плачетесь о том, что вам не нравится свитч на 127 позиций. Я вам показал, как уменьшить это число до трех - что вас не устраивает?

Я вам еще в самом начале написал - какой-то механизм выбора будет нужен В ЛЮБОМ СЛУЧАЕ. Это может быть свитч, могут быть цепочки “if-else…”, могут быть шаблоны…
И свитч не хуже и не лучше любого другого.

Похоже, что вы считаете указатели какой-то магией, которая помогает от всех болезней. А на самом деле геморой все равно придется лечить!

Указатели не решат проблему выбора типа переменных.

Просто я меню привел как пример. Интересует сама суть. Я понимаю, что указатели нужны, чтобы не плодить в памяти копии данных, но, реализовать у меня не получилось: компилятор долго ругался на все мои попытки. В итоге, я привёл то, что компилятор проглотил, но, результат по итогу нулевой: значение переменной не передалось. Вот я спросил, что я не так делаю. Наставьте, так сказать, на путь истинный. Если это невозможно, то так и скажите, и закроем тему. Если возможно, то ткните носом, куда копать. Просто я уже запутался в конец.
Мне стыдно и обидно, что на работе я спец, программами мною написанными годами десятки тысяч человек пользуются. А элементарном, казалось бы, вопросе не смог разобраться.

если у вас что-то не выходит с указателями - так и спрашивайте конкретно про них. К чему эти меню, массивы, свитчи?

Напишите, в чем именно вопрос и приведите короткий код, иллюстрирующий проблему.

Только код должен быть полным, включая все описания переменных, setup() и loop()

Я именно так и спросил, привёл кусок кода, в котором не получается. Привести весь код - затея не из лучших. Там почти на 480 кб кода для ESP32. Проект почти закончен. Решил добавить пульт на ЖК-экране, чтобы не только через WEB-интерфейс править настройки, но и через выносной пульт. Вот и столкнулся с невиданным мною доселе зверем в виде указателей. Проблему с меню я уже победил другим способом, практически сразу, как написал первый пост. Но, без указателей. Но, гештальт остался незакрытым, вот и спросил совета у гуру, а нарвался на “секс на площади”.

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

float* t = static_cast<float*>(menuItems[MenuID]._value); // Получаю ссылку на переменную
CurrentValue = String(*t); // Для вывода на экран или других целей...

Вот я и спросил, как правильно было сделать?

Давайте вы успокоитесь и перестанете нервничать. В первом сообщении вы ничего про указатели не спрашивали, вы как раз написали, что единственная проблема - это понять, какой тип у переменной.
Именно на этот вопрос вам уже 30 сообщений и отвечают.

И да, весь ваш код в 480К никому не нужен. Выделите из него пример строк на 30-50, где была бы видна ваша проблема. Но этот кусок должен быть ПОЛНЫМ СКЕТЧЕМ, который компилировался бы в Ардуино ИДЕ

Вот именно, что КУСОК! Он бесполезен без остального кода.

Если не верите - я могу это вам доказать.
Давайте так - если я покажу, что эти две строки у меня работают - вы переводите мне на телефон 1000 руб?

В первом посте я привел куски кода, которые работают с меню.
Мне полдня понадобится подогнать код для публикации здесь. СЕйчас займусь… Раз это так принципиально.

похоже вы все еще не понимаете сути.

Ну так что, готовы рискнуть?
Готов продемонстрировать что Ваши строчки в сообщении #32 полностью рабочие. Вот прямо как есть - беру их и вставляю в код. И получаю на печать значение переменной, а вовсе не ноль.

Может тогда до вас дойдет, что обсуждать пару строк кода, ВЫРВАННЫХ ИЗ КОНТЕКСТА - бред?

Я собрал тестовый скетч для тестирования вашего кода из первого сообщения за 3 минуты. И в нем чуть более 20 строк, включая пустые. В чем проблема то?

Вот кусок функционального кода (Наименования переменных и пунктов меню изменены, все совпадения с реальными именами - случайны!)

#define _keyMinus 0x01     // Кнопка "Минус"
#define _keyOk 0x02        // Кнопка "Ок"
#define _keyPlus 0x04      // Кнопка "Плюс"
#define _keyPRG 0x08       // Кнопка "Программа". Используется для установки параметров
#define _keyInfo 0x20      // Кнопка "Инфо". Используется для отображении информации текущих параметров
#define _keySchedule 0x80  // Кнопка "Расписание"

struct str_MenuItem {  // Структура для хранения элемента меню и привязки его к переменным
  byte Item_ID;        // ID-команды
  String Item_Name;    // Наименование пункта меню
  void* _value;        //Указатель на переменную, хранящую текущее значение команды
};

enum menuType : uint8_t {  // Режимы отображения информации на экране
  NormalMode,              // Нормальный режим (обычный)
  SchedulerMode,           // Режим программирования расписания
  InfoMode,                // Режим отображения подробной информации
  ProgramMode,             // Режим изменения параметров
};

enum menuINFO_namesItems : uint8_t {
  menuINFO_command1,
  menuINFO_command2,
  menuINFO_command3,
  menuINFO_command4,
  menuINFO_command5,
  menuINFO_command6,
  menuINFO_names_len,
};

enum menuPRG_namesItems : uint8_t {
  menuPRG_command1,
  menuPRG_command2,
  menuPRG_command3,
  menuPRG_command4,
  menuPRG_command5,
  menuPRG_command6,
  menuPRG_command7,
  menuPRG_names_len,
};

menuType MenuLevel = NormalMode;  // Тип меню, вызванного на экран

byte
  MenuPos = 0,  // Позиция в текущем меню
  MenuID = 0,   // ID ОТ-команды в позиции меню
  mnuNULL = 0,  // Заглушка для несуществующего пункта меню
  Key = 0;      // Код нажатой кнопки

float float_val1 = 100.4, float_val2 = 4.8;
uint8_t unt8t_val1 = 12, unt8t_val2 = 26;
uint16_t unt16t_val1 = 768, unt16t_val2 = 223;
String CurrentValue = "";  // Значение текущего пункта меню
String str_val = "--";
bool isNeed2Display = true;  // признак необходимости обновить информацию на экране
bool isMenuChanged = false;  // Сбросим признак, что были внесены изменения

const byte menuItemsCount = 127;
str_MenuItem menuItems[] = {
  // Массив со структурами элементов меню
  { 1, "Команда #1", &str_val },      // Ссылка на переменную типа String
  { 2, "Команда №2", &float_val1 },   // Ссылка на переменную типа float
  { 3, "Команда №3", &float_val2 },   // Ссылка на переменную типа float
  { 4, "Команда №4", &unt8t_val1 },   // Ссылка на переменную типа uint8_t
  { 5, "Команда №5", &unt16t_val1 },  // Ссылка на переменную типа uint16_t
                                      // Тут еще куча пунктов меню
};

const byte menuINFO[] = {
  // Меню для просмотра
  1, 3, 4, 6, 7, 10  // ID пунктов меню из массива структур
};

const byte menuPRG[] = {  // Меню для корректировки параметров
  2, 3, 5, 7, 9, 10, 11
};

//Поиск пункта меню по ID команды
byte getMenuItem(byte ID) {
  for (byte i = 0; i < menuItemsCount; i++) {
    if (ID == menuItems[i].Item_ID) return i;
  }
  return 0;
}

// Чтение текущего пункта активного меню
bool ReadMenuValue() {
  byte item = 0;
  switch (MenuLevel) {
    case SchedulerMode:
      break;
    case InfoMode:
      item = menuINFO[MenuPos];
      break;
    case ProgramMode:
      item = menuPRG[MenuPos];
      break;
  }
  MenuID = getMenuItem(item);
  return (MenuID > 0);
}

void menuExit() {
  // Тут идет сохранение переменных в EEPROM и часть из них передается обратно в главный контроллер
}

//Процедура вывода на дисплей. Кусок, отвечающий за вывод текущего пункта меню
void printDisplay() {
  float* t = static_cast<float*>(menuItems[MenuID]._value);  // Тут значение t = 0
  CurrentValue = String(*t);
  //display.setTextSize(1);
  //display.setTextSize(2);
  //display.setCursor(0, 0);
  //display.print(utf8rus(menuItems[MenuID].Item_Name));  // Название пункта меню выводится без проблем
  //display.setCursor(63 - CurrentValue.length() * 6, 34);
  //display.print(CurrentValue);  // Значение пункта меню
  isNeed2Display = false;
}

void setup(){
 // Тут куча кода по инициализации кнопок, LSD и много еще чего. 
}

void loop() {
  // Допустим кнопку нажали 
  Key = _keyInfo;
  
  if (Key > 0) {                        // Если нажата, то
    switch (Key) {                      // Обработка нажатых кнопок
      case _keyInfo:                    // Меню ИНФО
        if (MenuLevel == NormalMode) {  // Если только входим в меню
          MenuLevel = InfoMode;         // Установить режим ИНФО
          MenuPos = 1;                  // Первый пункт меню
          ReadMenuValue();
        } else {                   // Если уже в меню ИНФО
          MenuLevel = NormalMode;  // выходим из ИНФО
          MenuPos = 0;             // Сброс позиции меню
        }
        isNeed2Display = true;  // Обновляем дисплей
        break;
      case _keyPRG:                 // кнопка Программирование (PRG)
        if (MenuPos == 0) {         // И еще не в режиме "Программирование"
          isMenuChanged = false;    // Сбросим признак, что были внесены изменения
          MenuLevel = ProgramMode;  // Включим режим "Программирование"
          MenuPos = 1;              // Меню на первый пункт
          ReadMenuValue();
        } else {
          menuExit();  // Если были в режиме "Программирование", то выходим
        }
        break;
      case _keyMinus:
        if (MenuPos > 1) MenuPos--;
        else MenuPos = (MenuLevel == InfoMode) ? menuINFO_names_len : (MenuLevel == ProgramMode)   ? menuPRG_names_len
                                                                    : (MenuLevel == SchedulerMode) ? 1
                                                                                                   : 0;
        ReadMenuValue();
        break;
      case _keyPlus:
        if (MenuPos == ((MenuLevel == InfoMode) ? menuINFO_names_len : (MenuLevel == ProgramMode)   ? menuPRG_names_len
                                                                     : (MenuLevel == SchedulerMode) ? 1
                                                                                                    : 0)) MenuPos = 1;
        else MenuPos++;
        ReadMenuValue();
        break;
    }
    Key = 0;
  }
  if (isNeed2Display) printDisplay();
}

Переменные изначально получают свои значения с другого контроллера и отображаются на дисплее через menuINFO, могут изменяться пользователем через menuPRG.
У меня на экране названия пунктов выводятся, при нажатии кнопок “+” и “-” меняются пункты меню (названия пунктов), а значения во всех пунктах 0.00. Хотя в Serial значения переменных выводятся правильно. Следовательно ошибка в работе с указателями в строке:

float* t = static_cast<float*>(menuItems[MenuID]._value);

Отличный вывод :slight_smile:
Логика так и прет.
Ну так что, готовы поставить 1000, что строчка рабочая?

Если честно, я никак не думал, что попал в казино, или лотерею. Если есть ошибка, будьте так любезны, указать тупому бездарю, где она и в чем я ошибся. Если нет, то я не в том возрасте, чтобы играть “верю-не верю”.