Как "перебрать" циклом for структуру struct

спасибо, попробую понять, но наверное пока не мой уровень

Вы нас где-то обманываете. Если они имеют разный тип, то их нельзя поместить в массив.

Эту информацию, возможно, тоже есть смысл добавить в структуру, чтобы не “помнить”.

вот это похоже на то, что я ищу. буду изучать

Что-то я запутался. Из-за ответа #17 решил что @Smith2007 и ТС - это один и тот же человек.

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

Ну, в таком случае Вам нужен самостоятельный объект (оформить можно, например, как структуру) “датчик”. Внутри них должны быть прописаны их взаимосвязи и взаимовлияния (именно внутри, а не снаружи) и дальше работать с ним, как самостоятельными сущностями, через методы самой структуры, а не таскать их номера пинов и связи по всей программе. Всё, связанное с датчиком прячется в его описание и доступно только через методы. Если будете гуглить, эта концепция называется “инкапсуляцией”.

Вам тут очень правильную мысль подсказали:

Это частный случай более общего утверждения: всё, что связано с объектом должно быть собрано в его описании и меняться в одном месте. То, что при изменении чего-то в датчике, Вам надо изменять это в 100500 местах в программе – признак неправильного проектирования программы.

Попробуйте изолировать сущности (а не размазывать их по программе), программа сразу на порядок прибавит в организованности и управляемости.

Извините, протупил. Принял вас за ТС-а.)

Вот и я тоже…

Наследование. Предок - общая часть для всех типов структур. Именно по указателю на предка (базовый объект) структуры хранятся, передаются, перебираются. В базовом объекте хранится и описательная часть (указывающая место в процессе, название, другие общие для всех признаки). В наследниках добавлены уникальные поля, определены операторы копирования, сравнения, сериализации. Возможно, аксессоры для доступа к полям через общие базовые функции - тут простор для проектирования. Короче, классика ООП. Но без полного видения задачи советовать что-то конкретное сложно.

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

Не правильная организация структуры?

Эту же структуру записываю во флеш esp32 (псевдофлеш).

/*****************************************
функция записи данных во flash.
Перед загрузкой рассчитываем crc32
******************************************/ 
void flash_write(void) {
  config_t& conf = getConfig();
  // Обновляем CRC (рассчитываем для всех полей кроме crc)
  size_t dataSize = sizeof(config_t) - sizeof(uint32_t);
  conf.crc = calculateHardwareCRC32((uint8_t*)&conf, dataSize);
  EEPROM.put(0, conf);
  EEPROM.commit();
}

uint32_t calculateHardwareCRC32(const uint8_t* data, size_t length) {
 return esp_crc32_le(0xFFFFFFFF, data, (uint32_t)length);
}

Получается короткий код и легче читать

это плохая идея, подключи часики на DS3231 там 24c32 и памяти вагон и в цикле 100 тысяч. перезаписей кольцевого буфера, ну не знаю сколько у тебя там хранится, я ужался до 64 байт, так что есть надежда что при перезаписи раз в минуту проживёт = 97 лет )))

Не правильная организация структуры?

Эту же структуру записываю во флеш esp32 (псевдофлеш).


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

И структура уже 260 байт занимает. Это примерно 2/3 всех необходимых параметров.

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

Но пока задачи куда банальнее.

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

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

Следующий шаг нужно разобраться с подключением моторов по CAN.

Извиняюсь, что в этой теме поднял не профильный вопрос. Перехожу в профильную :slight_smile:

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

Именно массив для датчиков. И обрабатывать его именно в цикле. Но только для элементов различных типов будут вызываться предназначенные именно для них методы - для одного будет обрабатывать 2 переменные, а для другого - 10.

Можно, если типы унаследованы от общего.

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

struct SSensorValue {
	unsigned long timeStamp;  //	Время съёма показаний (мс от запуска МК)
	float temperture;         //	температура в градусах С
	float humidity;           //	влажность в %
	float pressure;           //	давление в мм.рт.ст.
};

void printSensorValue(const void * vv) {
	const SSensorValue * sv = (const SSensorValue *) vv;
	Serial.print("SENSOR VALUE: Time:");
	Serial.print(sv->timeStamp);
	Serial.print("ms; T:");
	Serial.print(sv->temperture);
	Serial.print("C; H:");
	Serial.print(sv->humidity);
	Serial.print("%; P:");
	Serial.print(sv->pressure);
	Serial.println("mm");
}

void setup() {
	Serial.begin(115200);
	Serial.println("Fun begins!");
	//
	//	Объявляем структуру и заполняем какими-то значениями
	//
	SSensorValue sv;
	sv.timeStamp = millis();
	sv.temperture = 36.6;
	sv.humidity = 93.2;
	sv.pressure = 755;
	//
	//	Печатаем структуру
	//
	printSensorValue(&sv);
}

void loop() {}

но я не совсем понимаю что происходит в этих строках

void printSensorValue(const void * vv) {
	const SSensorValue * sv = (const SSensorValue *) vv;

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

Тогда элементы массива - указатели на базовый тип, а это один и тот же тип, а не разные. Я решил не путать ТСа разными уточнениями. Можно и в std::any завернуть всё, если новые стандарты юзать.

Если что выше сказали - не понятно, вот ещё гляньте - простой способ. Массив указателей на функции. Можно использовать в цикле. Функции должны быть одного типа.

Спойлер
void Print_time();
void  Print_sens();
void Print_moto();
void (*Print_func[])() = { Print_time, Print_sens, Print_moto};

struct time {
  uint16_t year;
  uint8_t month;
  uint8_t date;
  uint8_t hours;
  uint8_t minuts;
};

struct sens {
  uint8_t n;
  float t;
};

struct moto {
  bool ON;
  uint32_t change_time;
};

struct time t;
struct sens s;
struct moto m;


void setup() {
  t.year = 2026;
  t.month = 6;
  t.hours = 12;
  t.minuts = 33;

  s.n = 4;
  s.t = 43.00;

  m.ON = true;
  m.change_time = 2335;

  Serial.begin(115200);

  for (uint8_t i = 0; i < 3; i++)
    Print_func[i]();

}

void Print_time()
{
  Serial.print(" Time:   ");
  Serial.print(t.year);
  Serial.print(" ");
  Serial.print(t.month);
  Serial.print("  ");
  Serial.print(t.hours);
  Serial.print(".");
  Serial.println(t.minuts);
}

void Print_sens()
{
  Serial.print(" Sensor number ");
  Serial.print(s.n);
  Serial.print("  val = ");
  Serial.println(s.t);
}

void Print_moto() {
  Serial.print(" moto is ON = ");
  Serial.print(m.ON);
  Serial.print("  change_time = ");
  Serial.print(m.change_time);
  Serial.println(" min");
}


void loop() {

}
Спойлер

P.S. Данные в структурах - лишь бы что-то было, для примера

P.P.S. При копировании, затёрлась первая строчка - исправил.

Тот пример был, видимо про “указатели на указатели” а не про написание структур.

Вот это:

идеологически неверно. Всё, что касается структуры, лучше держать в ней самой, внутри неё.

Вот Ваш пример (я его скопировал), но печать убрана внутрь структуры, сравните.

struct SSensorValue : public Printable {
	unsigned long timeStamp;  //	Время съёма показаний (мс от запуска МК)
	float temperture;         //	температура в градусах С
	float humidity;           //	влажность в %
	float pressure;           //	давление в мм.рт.ст.

	size_t printTo(Print& p) const {
		size_t res = p.print("SENSOR VALUE: Time:");
		res += p.print(timeStamp);
		res += p.print("ms; T:");
		res += p.print(temperture);
		res += p.print("C; H:");
		res += p.print(humidity);
		res += p.print("%; P:");
		res += p.print(pressure);
		res += p.print("mm");
		return res;
	}
};

void setup() {
	Serial.begin(9600); // я поменял скорость, если надо, то поменяйте обратно
	Serial.println("Fun begins!");
	//
	//	Объявляем структуру и заполняем какими-то значениями
	//
	SSensorValue sv;
	sv.timeStamp = millis();
	sv.temperture = 36.6;
	sv.humidity = 93.2;
	sv.pressure = 755;
	//
	//	Печатаем структуру
	//
	Serial.println(sv);
}

void loop() {}

Т.е. печать сидит внутри структуры и не торчит наружу. Подробнее про эту технику вот здесь.

Кстати, здесь Вам таже нетрудно сделать персонифицированную печать. Сейчас сделаю пример

— продолжение следует —

---- продолжение —

Вот смотрите, теперь у датчиков есть названия, а функция печати всегда печатает о каком датчике речь

struct SSensorValue : public Printable {
	const char * sensorName; // это будет название датчика
	unsigned long timeStamp;  //	Время съёма показаний (мс от запуска МК)
	float temperture;         //	температура в градусах С
	float humidity;           //	влажность в %
	float pressure;           //	давление в мм.рт.ст.

	// зададим название, а заодно обнулим все значения, нечего им болтаться в случайных значениях
	SSensorValue(const char * name = "") : sensorName(name), timeStamp(0), temperture(0), humidity(0), pressure(0) {}

	size_t printTo(Print& p) const {
		size_t res = p.print("SENSOR \"");
		res += p.print(sensorName);
		res += p.print("\" VALUE: Time:");
		res += p.print(timeStamp);
		res += p.print("ms; T:");
		res += p.print(temperture);
		res += p.print("C; H:");
		res += p.print(humidity);
		res += p.print("%; P:");
		res += p.print(pressure);
		res += p.print("mm");
		return res;
	}
};

void setup() {
	Serial.begin(9600);
	Serial.println("Fun begins!");
	//
	//	Объявляем структуру и заполняем какими-то значениями
	//
	SSensorValue sv("Vozle vhoda");
	sv.timeStamp = millis();
	sv.temperture = 36.6;
	sv.humidity = 93.2;
	sv.pressure = 755;

	SSensorValue sv1("Na Vyhode");
	//
	//	Печатаем структуры
	//
	Serial.println(sv);
	Serial.println(sv1);
}

void loop() {}

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

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

Пример
// В этой структуре есть три поля a, b и c
struct ABC {
	int a;
	int b;
	int c;
	int sumABC(void) const { return a + b + c; }
};

// А в этой тоже три поля: d, e и f
struct DEF {
	int d;
	int e;
	int f;
	int sumDEF(void) const { return d + e + f; }
};

// А вот в этой структуре будут все 6 полей, т.к. она НАСЛЕДУЕТ,
//	всё, что есть в "struct DEF" и в "struct ABC"  и ещё свое поле g
struct SevenMembers : 
	public ABC,	// Это значит, что в этой структуре будут все поля структуры ABC
	public DEF	// Это значит, что в этой структуре будут все поля структуры DEF
{
	int g;

	int sum() const { return sumABC() + sumDEF() + g; }
};

//
//	Очеь важное свойство наследования - полиморфизм.
//	ПРостыми словами это означает, что любой экземпляр
//	SevenMembers будет также полноценным экземпляром 
//	тех структур, от которых SevenMembers пронаследована,
//	т.е. ABC и DEF. Сейчас мы это продемонстрируем.
//

// Эта функция ожидает паратетра struct ABC
int getSum1(const ABC & str) {
	return str.sumABC();
}

// Эта функция ожидает паратетра struct DEF
int getSum2(const DEF & str) {
	return str.sumDEF();
}

void setup(void) {
	Serial.begin(9600);

	//	Объявляем и инициализируем экземпляр структуры SevenMembers
	SevenMembers sm;
	sm.a = 1;
	sm.b = 2;
	sm.c = 3;
	sm.d = 4;
	sm.e = 5;
	sm.f = 6;
	sm.g = 7;

	// Функция getSum1 ожидает параметр типа ABC.
	//	У нас есть только SevenMembers sm
	// Но мы уже говорили, что SevenMembers - наследник ABC,
	//	а. значит, sm - полноправный экземпляр типа ABC
	Serial.println(getSum1(sm));
	
	// Опять же!
	// Функция getSum2 ожидает параметр типа DEF.
	//	У нас есть только SevenMembers sm
	// Но мы уже говорили, что SevenMembers - наследник DEF,
	//	а. значит, sm - полноправный экземпляр типа DEF
	Serial.println(getSum2(sm));
}
void loop(void) {}

Могу, конечно ответить на изначальный вопрос темы – показать, как перебирать поля структуры (если они одного типа, разумеется) циклом, только повторяю - это через задницу. В реальности я так не сразу даже могу придумать где бы это могло быть реально нужно. Чаще всего, если Вам это нужно – это признак того, что в проектировании / архитектуре программы что-то не так.

Сейчас подготовлю пример.

Пример череззадничного решения вопроса из стартового топика
//
//	Определим структуру с тремя полями
//
struct SKaka {
	SKaka (const int _a=0, const int _b=0, const int _c=0) : a(_a), b(_b), c(_c) {}
	int a;
	int b;
	int c;

	int errorValue = -1;

	int length = 3;

	//
	//	Введём оператор взятия по индексу
	//	При индексе 0, речь будет идти о поле a
	//	при индексе 1 - о поле b
	//	при индексе 2 - о поле c
	//
	int & operator [](const int index) {
		switch (index) {
			case 0: return a;
			case 1: return b;
			case 2: return c;
			default: return errorValue;
		}
	}
};

void setup(void) {
	Serial.begin(9600);

	// Обявляем экземпляр м назначаем начальные значения
	//	kaka.a=1, kaka.b=2, kaka.c=3
	SKaka kaka(1, 2, 3);

	// Выполним арифметическое действие
	//	над всеми полями по индексу
	for (int i = 0; i < kaka.length; i++) {
		kaka[i] *= kaka[i];
	}

	// А теперь распечатаем все поля,
	//	опять же по индексу.
	for (int i = 0; i < kaka.length; i++) {
		Serial.print("kaka[");
		Serial.print(i);
		Serial.print("]=");
		Serial.println(kaka[i]);
	}
}
void loop(void) {}