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

Да, это я уже сделал. Только не выложил. Простите.

#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;    // Наименование пункта меню
  uint8_t value_type;  // Тип перенной: 0 - char, 1 - float, 2 - uint8_t, 3 - uint16_t
  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", 0, &str_val },      // Ссылка на переменную типа String
  { 2, "Команда №2", 1, &float_val1 },   // Ссылка на переменную типа float
  { 3, "Команда №3", 1, &float_val2 },   // Ссылка на переменную типа float
  { 4, "Команда №4", 2, &unt8t_val1 },   // Ссылка на переменную типа uint8_t
  { 5, "Команда №5", 3, &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 - 1];
      break;
    case ProgramMode:
      item = menuPRG[MenuPos - 1];
      break;
  }
  MenuID = getMenuItem(item);
  return (MenuID > 0);
}

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

//Процедура вывода на дисплей. Кусок, отвечающий за вывод текущего пункта меню
void printDisplay() {
  char buff[10];
  String* str;
  float* flt;
  uint8_t* t8;
  uint16_t* t16;
  switch (menuItems[MenuID].value_type) {
    case 0:
      str = static_cast<String*>(menuItems[MenuID]._value);
      Serial.printf("t: %s\n", *str);
      sprintf(buff, "%s", *str);
      CurrentValue = String(buff);
      break;
    case 1:
      flt = static_cast<float*>(menuItems[MenuID]._value);
      Serial.printf("t: %f\n", *flt);
      sprintf(buff, "%.1f", *flt);
      CurrentValue = String(buff);
      break;
    case 2:
      t8 = static_cast<uint8_t*>(menuItems[MenuID]._value);
      Serial.printf("t: %d\n", *t8);
      sprintf(buff, "%d", *t8);
      CurrentValue = String(buff);
      break;
    case 3:
      t16 = static_cast<uint16_t*>(menuItems[MenuID]._value);
      Serial.printf("t: %d\n", *t16);
      sprintf(buff, "%d", *t16);
      CurrentValue = String(buff);
      break;
  }
  //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();
}

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

// Отказаться от этих переменных
  String* str;
  float* flt;
  uint8_t* t8;
  uint16_t* t16;
// И сделать что-нибудь так для каждого типа 
sprintf(buff, "%s", *(static_cast<String*>(menuItems[MenuID]._value);)));
// или 
sprintf(buff, "%.1f", *(static_cast<float*>(menuItems[MenuID]._value)));

но, понимаю, что это неправильно.

Почему неправильно? строка 9 из нижнего кода вполне рабочая.

А вот со String так не надо, %s sprintf это не для String

Но в основном коде строчки

так и остались.

Если 9 строчка правильная, то тогда Proteus меня обманывает:
выдает какую-то хрень в виде “…”

Вот тут у вас какой-то лютый бред написан:

Зачем String через sprint переводить в массив символов, чтобы потом снова сделать из него String?

Вот если бы вы привели пример, где вы банально читаете указатель из массива, преобразуете его к float* и передаете на печать - я бы поверил.

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

Давайте, ответьте мне, зачем в строке

!ReadMenuValue();

воскл знак - и я спать пойду.

Да. тут косяк на косяке!
Во-первых уже голова кругом идет - отдыхать иногда надо, во-вторых, это в спешке копипаст проклятый подвёл. А мозг не отреагировал.

так чего вы тогда на Протеус киваете?

то есть ответа не будет?

Да это опять копипаст. Это с рабочего кода кусок, не убрал “!”, а в рабочем коде цикл по перебору пунктов меню, пока не найдем поддерживаемую команду. Кривая попытка скрыть из меню неподдерживаемые команды.

то есть вы упорно уходите от ответа…

Ну а смысл вызова строчки без знака

можете обьяснить?

Даю подсказку - это почти такая же ошибка, как со знаком

Смысл - найти по MenuPos (номер активного пункта текущего меню) ID команды
Там изначально было много больше кода, выдернутого из старого проекта, но, понял, что это фигня, иначе придется городить монстра из switch (пост №18). Переделал по структуры текущий вариант, а процедура осталась. Потому что уперся в проблему указателей. Причёсывать код буду, когда заработает этот кусок.

Догадываюсь по Вашему комментарию, что там дичь какая-то, но, пока не могу переключиться с указателей. Мозг отключается…

Ладно, в общем пора заканчивать.

По итогам… не могу не повторить, что я очень удивляюсь, что вы могли когда-то писать какие-то программы. Может наврали? - писали не вы, а ассистенты, а вы только “руководили”?
Вы не имеете не малейшего представления о логике написания программы и об отладке. И не важно, на каком языке вы якобы писали раньше.

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

А так вы впустую отняли у меня 4 часа воскресного вечера, причем , что обидно, мы с вами совершенно не продвинулись к решению. И виноваты в этом вы.
Реально, мои 4 часа стоят значительно дороже тех 1000р, про которые я писал в начале.

Спокойной ночи.

а что если вы опишите структуру, которая будет содержать указатель на значение, а также идентификатор типа данных этого значения? А к пунктам меню будете вязать указатели на такие структуры?

От хранения идентификатора типов данных, мне кажется, вы никак не уйдете. Указатель хранит адрес памяти, ему же невдомек, что и какой размерности по этому адресу содержится?

Как зачем? А зачем вообще восклицательный знак? Понятное дело, что он выполняет интонационно-экспрессивную роль и ставится для выражения изумления, сильного чувства, волнения и тому подобного.

А Вы что подумали?

Так делалось в Си. Сейчас на дворе 2025 год, а не 1975 и за последние полвека для таких задач появились совершенно другие средства. Вчера (в честь дня Св. Нуба) я искренне хотел написать пример для ТС без свичей, указателей на void и прочих костылей, но так и не дождался от него ответов на простые вопросы, без которых моя писанина могла оказаться нахрен никому не нужной :frowning:

Что касается ТС - ему Ваша лекция вряд ли зашла бы, он, судя по вчерашней дискуссии, даже базовых конструкций языка не знает.
А вот постоянным участникам здесь. думаю, почитать было бы интересно.

по какому запросу гуглить?

В общем так. Коротко я уже писал выше, что я имею в виду – наследование и шаблоны, которые позволяют обойтись без void (сохранив таким образом все выгоды строгой типизации) и без switch, т.к. компилятор сам разберётся какую функцию в какой элемент меню вставлять. Вот, разродился на пример.

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

Тем не менее, для примера я сделал примитивное меню, элементы которого имеют значения. Значение может быть типа: int, float и String. Во всех случаях оно считает сколько раз этот элемент выбрали. В случае String счёт ведётся текстом (“один”, “два” и т.д.). Меню совсем примитивное, одноуровневое, но по нему можно ходить вверх и вниз и выбирать элементы.

Как всегда – последняя версия IDE искаропки, Uno и ему подобные.

Файл .ino
#include "MenuItem.h"

//
//	Cобственно меню состоит из имён трёх известных поросят
//	(четвёртый поросёнок Нах-Нах опять куда-то смылся)
//
MenuItem<int> nifnif("Ниф-Ниф", 0);
MenuItem<float> nufnuf("Нуф-Нуф", 0, & nifnif);
MenuItem<String> nafnaf("Наф-Наф", "ноль", & nufnuf);
//
//	Первый и текущий элементы меню, пригодятся, чтобы ходить по нему
MenuItemBase * const first = & nifnif;
MenuItemBase * current = first;

//
//	Возможные команды меню и функция чтения команды
//	из UART. Команда может быть введена в любом регистре
//	всё, что не является правильной командой - игнорируется.
//
enum Command { CMD_UP, CMD_DOWN, CMD_ACTIVATE };
//
Command readCommand(const bool waitForUp, const bool waitForDown) {
	while (true) {
		while(! Serial.available());
		const char ch = toupper(Serial.read());
		if (waitForUp && ch == 'U') return CMD_UP;
		if (waitForDown && ch == 'D') return CMD_DOWN;
		if (ch == 'A') return CMD_ACTIVATE;
	}
}

// 
//	
void setup(void) {
	Serial.begin(9600);
	Serial.println("Поехали !!!");
}

//
//	Функция печати одной строки (одного элемента) меню.
//	Пеачатеся знак > если данный элемент активен, затем
//	название элемента и, после : текущее значение
//
void printmenuItem(MenuItemBase * mi) {
	Serial.print((mi == current) ? "> " : "  ");
	Serial.println(mi->toString());
}

//
//	Функция печати всего меню и подсказки по командам. 
//	Печатаются только доступные в данный момент команды.
//
void printMenu(void) {
	Serial.println("======================");
	for (MenuItemBase * mi = first; mi; mi = mi->next()) printmenuItem(mi);
	Serial.println("------------");
	Serial.println("нажмите: ");
	if (! current->isFirst()) Serial.println("  U - шаг вверх");
	if (! current->isLast()) Serial.println("  D - шаг вниз");
	Serial.println("  A - выбрать текущий элемент");
	Serial.println();
}

//
//	Ну, печатаем меню, читаем команду, если это команда перехода, то переходим по меню
//	а если команда активации, то инкрементируем значение элемента меню. И так по кругу.
//
void loop(void) {
	printMenu();
	Command command = readCommand(current->previous(), current->next());
	if (command == CMD_UP) current = current->previous();
	else if (command == CMD_DOWN) current = current->next();
	else ++(*current);
}

и

MenuItem.h
#ifndef	MENU_ITEM_H
#define	MENU_ITEM_H

//
//	Базовый (абстрактный) класс для элемента меню.
//	Делает всё, кроме работы со значениями.
//	Последняя представлена виртуальными функциями toString и предекремент.
//
struct MenuItemBase {
protected:
	const char * m_name;
	MenuItemBase * m_previous;
	MenuItemBase * m_next;
public:
	MenuItemBase(const char * nm, MenuItemBase * prev) : 
		m_name(nm), m_previous(prev), m_next(nullptr) {
			m_previous->m_next = this;
		}

	MenuItemBase * next(void) const { return m_next; }
	MenuItemBase * previous(void) const { return m_previous; }
	const char * name(void) const { return m_name; }
	bool isLast(void) const { return m_next == nullptr; }
	bool isFirst(void) const { return m_previous == nullptr; }
	virtual String toString(void);
	virtual void operator ++(void);
};

//
//	Функция инкрементирует переданное значение val
//	тип которого задан параметром шаблона
//	Для всех типов, кроме String выполняет val += 1;
//	А для String "прибавляет 1" в текстовом виде.
//
template<typename TValueType> void _pluplus(TValueType & val) { val += 1; }
template<> void _pluplus<String>(String & val) {
	if (val == "ноль") val = "один";
	else if (val == "один") val = "два";
	else if (val == "два") val = "три";
	else  val = "дохрена";
}

//
//	Класс для элемента меню.
//	Целиком наследует всё от MenuItemBase, от себя добавляет только
//	свойство m_value (значение) тип которого указан параметром шаблона
//	и два метода работы со значением "toString" и "++" (предекремент)
//
template<typename TValueType>
struct MenuItem : public MenuItemBase {
protected:
	TValueType m_value;
public:
	MenuItem(const char * nm, const TValueType defValue, MenuItemBase * prev = nullptr) :
		MenuItemBase(nm, prev), m_value(defValue) {}

	String toString(void) { return String(m_name) + ": " + String(m_value); }
	void operator ++(void) { _pluplus(m_value); }
};

#endif	//	MENU_ITEM_H

Выглядит это примерно так, хотя, сами запускайте:

Монитор порта
======================
  Ниф-Ниф: 3
> Нуф-Нуф: 2.00
  Наф-Наф: дохрена
------------
нажмите: 
  U - шаг вверх
  D - шаг вниз
  A - выбрать текущий элемент

Там я постарался прокомментировать, но если будут вопросы …

Да ладно О_о. Какие средства позволяют отказаться от указателей? Сделайте программный стек или динамическую очередь в памяти без указателей. Да и вообще динамическая память без указателей как реализуется.
Да и опять же, если в каком то конкретном случае есть одни решения, то в другом случае не будет. Язык программирования даёт вам инструменты, а уж реализацию задаёт автор. Если бы не было вариативности, программы давно бы писались самими компьютерами. Что бы стать хорошим программистом, необходимо изучить как можно больших вариантов, подходов, реализаций. Это как словарный запас у человека.

Да ладно О_о. Я где-то что-то говорил про отказ от указателей? Не припомню. Более того, не припомню, чтобы я вообще что-то говорил про отказ от чего-либо (goto и пр.). Вы меня с кем-то перепутали.

надо сначала в предметной области стать специалистом, товарищ, разве нет?