Форматирование переменных из вариационной функции
У меня есть функция, которая объединяет строку печати, однако у меня возникают проблемы с правильным форматированием некоторых строк.
Использование Serial.print правильно форматирует вывод keypad.getKey(), однако, когда я пытаюсь вывести тот же формат с помощью printConcatLine(), это не дает мне тот же выход. Я пробовал использовать его как char, так и как int...
void printConcatLine(const char* mask, ...) {
va_list params;
va_start(params, mask);
while(*mask != '\0') {
if (*mask == 'i') {
Serial.print(va_arg(params, int));
} else if(*mask == 'c') {
Serial.print(va_arg(params, const char *));
} else if (*mask == 'f' || *mask == 'd') {
Serial.print(va_arg(params, double));
}
++mask;
}
va_end(params);
Serial.println();
}
char btnPressed = keypad.getKey();
if (btnPressed) {
Serial.print("BtnPressed: ");
Serial.println(btnPressed);
// Вывод: "BtnPressed: 1"
printConcatLine("ci", "BtnPressed: ", btnPressed);
// Вывод: "BtnPressed: 49"
printConcatLine("cc", "BtnPressed: ", btnPressed);
// Вывод: "BtnPressed: "
}
Я также пытался изменить:
va_arg(params, const char *)
в
va_arg(параметры, символы)
что тоже ничего не дало.
Как мне изменить вызов va_arg(), чтобы он правильно выводил тот же результат, что и Serial.print()?
@McWayWeb, 👍1
Обсуждение3 ответа
Лучший ответ:
Эдгар опередил меня на несколько секунд, но мой ответ почти такой же, как и у него, так что сначала прочитайте его.
Однако я добавлю несколько дополнительных примечаний и указателей:
- Лучше присвоить va_args фактической переменной. Это упрощает чтение и понимание того, что происходит, и более точно позволяет перегрузке функции печати знать, что должно произойти (по крайней мере, с точки зрения читателя), чем простое приведение.
- Байты и символы преобразуются в целые числа при передаче в функцию с переменным числом аргументов, поэтому вы должны обращаться с ними как с таковыми
float
иdouble
оказались одинаковыми на Arduino на основе AVR, поэтому ваш код там будет в порядке, но этого нельзя сказать о других чипах. . Если вы хотите, чтобы ваш код был переносимым, вам следует разделитьfloat
иdouble
на отдельные обработчики, поскольку они используют разные размеры.- Было бы неплохо «пропустить» неизвестные символы в строке формата, чтобы вы могли добавить туда дополнительное форматирование. См. мой пример ниже.
Вот мой вариант того же кода. Обратите внимание на двоеточие и пробел в формате, которые используются для форматирования вывода (это можно улучшить, добавив %
перед любыми символами формата, как это делает printf
). Мне также нравится использовать отдельный указатель для перебора строки формата, чтобы исходный начальный указатель оставался доступным, если мне когда-нибудь понадобится.
Также обратите внимание на разницу между c
и i
: используйте c
для печати буквы из символа, а i
, чтобы вместо этого напечатать число в символе.
void printConcatLine(const char *mask, ...) {
va_list params;
va_start(params, mask);
char *ptr = (char *)mask;
while (*ptr != '\0') {
if (*ptr == 'c') {
int c = va_arg(params, int);
Serial.write(c);
} else if (*ptr == 'i') {
int i = va_arg(params, int);
Serial.print(i);
} else if (*ptr == 's') {
const char *s = va_arg(params, const char *);
Serial.print(s);
} else if (*ptr == 'f' || *ptr == 'd') {
// Будьте осторожны с этим. Это не портативно. На float AVR
// и double — это одно и то же, но на других это не так
// микроконтроллеры. Их лучше разделить.
double d = va_arg(params, double);
Serial.print(d);
} else {
Serial.write(*ptr);
}
ptr++;
}
va_end(params);
Serial.println();
}
void setup() {
Serial.begin(115200);
char btnPressed = 1;
float flt = 3.141592653;
if (btnPressed) {
printConcatLine("c: sisd", 'Q', "BtnPressed: ", btnPressed, " done ", flt);
}
}
void loop() {
}
сколько места во флэш-памяти это экономит по сравнению с printf? в моем тесте нет., @Juraj
@Juraj Много. Взгляните на ссылку в моем комментарии выше для «легкого» форматирования printf, и вы увидите, как много в нем задействовано., @Majenko
извини. нет. printf производит немного меньший код. Я использую свою оболочку StreamLib для printf для Serial https://github.com/jandrassy/StreamLib/blob/3b2393284315de939105a0c11df50be1471f6cfe/src/FormattedPrint.cpp#L28, @Juraj
@Juraj Возможно, на Arduino, но тогда в Arduno printf отсутствует поддержка с плавающей запятой, которая включена в код здесь. Если вы уберете поддержку float, вы, скорее всего, обнаружите, что этот код значительно меньше. Поддержка поплавка огромна., @Majenko
Я скомпилировал обе версии для Uno _с поддержкой float_ и, к моему удивлению, версия Juraj меньше (текст: 4814, данные: 68, bss: 166), чем ваша (текст: 6226, данные: 62, bss: 166)., @Edgar Bonet
@EdgarBonet Это сюрприз, надо сказать., @Majenko
поддержка printf float с -Wl,-u,vfprintf -lprintf_flt
почему-то безусловна. это делает даже Блинка большим, @Juraj
В формате "ci"
аргумент интерпретируется как целое число, и
вывод правильный: 49 — это код ASCII '1'
.
В формате "cc"
аргумент интерпретируется как строка (указатель на
массив символов), что неверно.
Одним (плохим) решением является использование формата "cc"
и передача &btnPressed
как
Аргумент. Проблема с этим подходом в том, что байт в памяти
сразу после btnPressed
может не быть нулем, и формат ожидает
Строка с нулевым завершением.
Лучше использовать один формат для char
и
другой для строки. Я предлагаю "c"
и "s"
соответственно. Но
то вы должны знать, что существуют специальные правила продвижения
связаны с вариативными функциями. Играя с вашим кодом,
компилятор напомнил мне об этом:
предупреждение: 'char' преобразуется в 'int' при передаче через '...'
Таким образом, функция должна ожидать int
и привести его к char
по порядку.
для вызова правильной перегрузки Serial.print()
:
void printConcatLine(const char* mask, ...) {
va_list params;
va_start(params, mask);
while(*mask != '\0') {
if (*mask == 'i') {
Serial.print(va_arg(params, int));
} else if(*mask == 's') {
Serial.print(va_arg(params, const char *));
} else if(*mask == 'c') {
Serial.print((char) va_arg(params, int));
} else if (*mask == 'f' || *mask == 'd') {
Serial.print(va_arg(params, double));
}
++mask;
}
va_end(params);
Serial.println();
}
Используется следующим образом:
printConcatLine("sc", "BtnPressed: ", btnPressed);
// Вывод: "BtnPressed: 1"
Я рекомендую использовать printf, если вам не нужна поддержка float в printf. Этот код создает скомпилированный код немного меньшего размера, чем тот, что был в ответе Маженко, и обладает полной мощностью printf, за исключением float.
#include <StreamLib.h>
void setup() {
Serial.begin(115200);
char btnPressed = 1;
float flt = 3.141592653;
byte b;
BufferedPrint bp(Serial, &b, 1);
if (btnPressed) {
bp.printf("%c: %s %i %s", 'Q', "BtnPressed: ", btnPressed, " done ");
bp.println(flt, 4);
}
}
void loop() {
}
Конечно, с помощью StreamLib вы можете печатать с плавающей запятой с помощью обычной одиночной функции печати для с плавающей запятой, такой же, как для последовательного или сетевого клиента, со вторым параметром для количества знаков после запятой.
Если вы хотите создать отформатированную C-строку, вы можете использовать CStringBuilder библиотеки StreamLib. Он также строит строку с функциями print
и также имеет 'printf'.
По поводу «_меньшей программы_»: меньший скомпилированный код?, @Edgar Bonet
@EdgarBonet, да, скомпилированный код меньше, @Juraj
- Переменные клавиатуры Arduino к последовательному порту
- Как разделить входящую строку?
- Как вывести несколько переменных в строке?
- форматирование строк в Arduino для вывода
- Очень простая операция Arduino Uno Serial.readString()
- Arduino Преобразование std:string в String
- Как прочитать входящие ШЕСТНАДЦАТИРИЧНОЕ значение из serial метод read ()?
- Arduino Serial.ReadString() проблема
Кстати, в chipKIT мы предоставляем удобную функцию
Serial.printf("...", ..., ..., ...)
, которая идеально подходит для этого. Форматирование обрабатывается [этим](https://github.com/chipKIT32/chipKIT-core/blob/master/pic32/cores/pic32/doprnt.cpp) кодом, который сам был взят непосредственно из RetroBSD., @MajenkoПоскольку вы используете серийный номер для печати строки, зачем ее объединять? Просто распечатайте его кусками., @Delta_G