Как получить тип данных переменной?
Я использую Arduino и хотел бы знать, существует ли функция, которая возвращает тип данных переменной. То есть я хотел бы запустить что-то вроде следующего:
// Note: 'typeof' is a sample function that should return the data type.
Serial.println(typeof(myVar));
@user502052, 👍14
Обсуждение5 ответов
Лучший ответ:
В типичной программе на C++ вы бы использовали оператор typeid
, например:
std::cout << typeid(myVar).name();
Однако для этого требуется функция компилятора, называемая Информацией о типе среды выполнения (RTTI). Он отключен в среде IDE Arduino, предположительно, потому, что он увеличивает требования к памяти во время выполнения программы.
Вы можете получить более подробную информацию о стоимости ресурсов здесь: https://stackoverflow.com/questions/579887/how-expensive-is-rtti
Однако любой полнофункциональный компилятор C++ определенно будет поддерживать RTTI. Если вы хотите попробовать использовать стороннюю IDE (например, Eclipse с плагином Arduino), вы можете легко включить ее. Хотя, вероятно, это не стоит хлопот только из-за этого.
Альтернатива
Более эффективным (но менее гибким) решением было бы использование подхода класса признаков. Это включает в себя какое-то фанковое метапрограммирование шаблонов:
// Generic catch-all implementation.
template <typename T_ty> struct TypeInfo { static const char * name; };
template <typename T_ty> const char * TypeInfo<T_ty>::name = "unknown";
// Handy macro to make querying stuff easier.
#define TYPE_NAME(var) TypeInfo< typeof(var) >::name
// Handy macro to make defining stuff easier.
#define MAKE_TYPE_INFO(type) template <> const char * TypeInfo<type>::name = #type;
// Type-specific implementations.
MAKE_TYPE_INFO( int )
MAKE_TYPE_INFO( float )
MAKE_TYPE_INFO( short )
Вы можете добавить строки MAKE_TYPE_INFO(..)
для любого типа, который вы хотите, включая имена пользовательских классов. Затем вы могли бы использовать его следующим образом:
int myVar = 17;
Serial.println( TYPE_NAME(myVar) );
Все, что вы не определили с помощью MAKE_TYPE_INFO (..)
, будет отображаться как "unknown"
.
Это довольно продвинутый материал, поэтому я не буду пытаться объяснить, как все это работает здесь. В Интернете есть различные учебные пособия по программированию шаблонов на C++, если вам интересно.
ПРАВКА: Стоит отметить, что оператор typeof
не является стандартным C++, но поддерживается несколькими компиляторами, такими как GCC. По сути, это более старый эквивалент decltype
, который фигурирует в стандарте C++11.
очень информативный и обстоятельный ответ! был бы признателен за больше такого рода, @Omer
Я использую простой глупый подход...
// serial print variable type
void types(String a) { Serial.println("it's a String"); }
void types(int a) { Serial.println("it's an int"); }
void types(char *a) { Serial.println("it's a char*"); }
void types(float a) { Serial.println("it's a float"); }
void types(bool a) { Serial.println("it's a bool"); }
Это концепция полиморфизма, при которой создается несколько функций с разными типами параметров, но с одним и тем же именем функции. Во время выполнения будет вызвана функция, соответствующая нужному количеству аргументов и типу(типам) аргументов. Надеюсь, это объяснение поможет.
Не могли бы вы немного расширить свой ответ*, кратко объяснив, как это работает?, @Greenonline
мне пришлось переписать его вручную, потому что копипаст привел к проблемам, которые я не понимал. После перепечатки его вручную, как и на скетче, он сработал как заклинание. Весьма полезно..., @novski
Простой, понятный без знания шаблонов. Может быть расширен для возврата перечисления типа., @voidPointer
Основываясь на ответе @snakeNET (который я расцениваю как перегрузку функций, а не полиморфизм)...
Более общий способ-передать указатель на печать, позволяя использовать любой класс, связанный с печатью (не только последовательный); также передавать объекты по ссылке, например
void types(Print* p, const String&) { p->print("it's a String"); } // for each type
...
Serial.print("Type is "); types(&Serial, myStr); Serial.println();
Обратите внимание, что я изменил "println" на "print"; снова, сделав его более общим.
Я думаю, что значение термина **полиморфизм** несколько изменилось за десятилетия, чтобы теперь включать перегрузку функций *вне* классов ООП, а также перегрузку функций-членов *внутри* классов. Когда я начал изучать программирование, это касалось только переопределения методов виртуального класса в классах-потомках., @tim
Я протестировал форму шаблона, форму с длинными руками и макрос ___
_ _ _ _ _ _ _ _ и получил разные результаты. Различия приведены в последних двух строках таблицы.
Форма шаблона 1
Авторство: Питер Блумфилд
MAKE_TYPE_INFO( bool )
MAKE_TYPE_INFO( bool* )
MAKE_TYPE_INFO( char )
MAKE_TYPE_INFO( char* )
MAKE_TYPE_INFO( double )
MAKE_TYPE_INFO( double* )
MAKE_TYPE_INFO( float )
MAKE_TYPE_INFO( float* )
MAKE_TYPE_INFO( int )
MAKE_TYPE_INFO( int* )
MAKE_TYPE_INFO( short )
MAKE_TYPE_INFO( short* )
MAKE_TYPE_INFO( String )
MAKE_TYPE_INFO( String* )
MAKE_TYPE_INFO( char[5] )
MAKE_TYPE_INFO( char(*)[5] )
Форма Длинной Руки
Атрибуция:
- Змеиная сеть
- брюманц
- Изменено мной, чтобы быть более общим.
const char* TypeOf(const bool&) { static const char type[] = "bool"; return type; }
const char* TypeOf(const bool*) { static const char type[] = "bool*"; return type; }
const char* TypeOf(const char&) { static const char type[] = "char"; return type; }
const char* TypeOf(const char*) { static const char type[] = "char*"; return type; }
const char* TypeOf(const double&) { static const char type[] = "double"; return type; }
const char* TypeOf(const double*) { static const char type[] = "double*"; return type; }
const char* TypeOf(const float&) { static const char type[] = "float"; return type; }
const char* TypeOf(const float*) { static const char type[] = "float*"; return type; }
const char* TypeOf(const int&) { static const char type[] = "int"; return type; }
const char* TypeOf(const int*) { static const char type[] = "int*"; return type; }
const char* TypeOf(const String&) { static const char type[] = "String"; return type; }
const char* TypeOf(const String*) { static const char type[] = "String*"; return type; }
//const char* const TypeOf(const char[5]) { static const char* type = "char[5]"; return type; } // Decays to char* so generates compiler redefinition warning.
const char* const TypeOf(const char(*)[5]) { static const char* type = "char(*)[5]"; return type; }
Форма шаблона 2: __КРАСИВАЯ_ФУНКЦИЯ__
Макрос
Атрибуция:
- Оригинальная
string
версия Владимира Талыбина - Я изменил
версию массива символов.
Использование строки
template <class T>
String type_name(const T&)
{
String s = __PRETTY_FUNCTION__;
int start = s.indexOf("[with T = ") + 10;
int stop = s.lastIndexOf(']');
return s.substring(start, stop);
}
Использование
массива символов
Этот вариант имеет более низкие требования к памяти.
template <typename T>
char* type_name(const T&)
{
char* pf = __PRETTY_FUNCTION__;
char* begin = strstr(pf, "[with T = ") + 10;
char* end = strrchr(pf, ']');
*end = 0;
return begin;
}
Тестовый код
bool b = false;
char c = 'a';
double d = 1.2;
float f = 3.4;
int i = 5;
String s = "6789";
char chars[] = "1234";
Serial.println(F("TypeOf() Test"));
Serial.println(TypeOf(b));
Serial.println(TypeOf(&b));
Serial.println(TypeOf(c));
Serial.println(TypeOf(&c));
Serial.println(TypeOf(d));
Serial.println(TypeOf(&d));
Serial.println(TypeOf(f));
Serial.println(TypeOf(&f));
Serial.println(TypeOf(i));
Serial.println(TypeOf(&i));
Serial.println(TypeOf(s));
Serial.println(TypeOf(&s));
Serial.println(TypeOf(chars));
Serial.println(TypeOf(&chars));
Serial.println(F("TYPE_NAME() Test"));
Serial.println(TYPE_NAME(b));
Serial.println(TYPE_NAME(&b));
Serial.println(TYPE_NAME(c));
Serial.println(TYPE_NAME(&c));
Serial.println(TYPE_NAME(d));
Serial.println(TYPE_NAME(&d));
Serial.println(TYPE_NAME(f));
Serial.println(TYPE_NAME(&f));
Serial.println(TYPE_NAME(i));
Serial.println(TYPE_NAME(&i));
Serial.println(TYPE_NAME(s));
Serial.println(TYPE_NAME(&s));
Serial.println(TYPE_NAME(chars));
Serial.println(TYPE_NAME(&chars));
Serial.println(F("type_name() Test"));
Serial.println(type_name(b));
Serial.println(type_name(&b));
Serial.println(type_name(c));
Serial.println(type_name(&c));
Serial.println(type_name(d));
Serial.println(type_name(&d));
Serial.println(type_name(f));
Serial.println(type_name(&f));
Serial.println(type_name(i));
Serial.println(type_name(&i));
Serial.println(type_name(s));
Serial.println(type_name(&s));
Serial.println(type_name(chars));
Serial.println(type_name(&chars));
Результаты
TypeOf() | ИМЯ ТИПА() | имя типа() |
---|---|---|
бул | бул | бул |
бул* | бул* | бул* |
шар | шар | шар |
чар* | шар* | шар* |
двойной | двойной | двойной |
двойной* | двойной* | двойной* |
float | float | float |
float* | float* | float* |
инт | инт | инт |
инт* | int* | int* |
Строка | Строка | Строка |
Строка* | Строка* | Строка* |
шар* | чар[5] | чар [5] |
char(*)[5] | символ(*)[5] | символ (*)[5] |
Некоторые Выводы
У функции TypeOf()
есть проблема с затухающим указателем.TypeOf()
иTYPE_NAME()
должны иметь отдельные определения с длинной формой илиMAKE_TYPE_INFO
.type_name()
используетмакрос __PRETTY_FUNCTION__
для автоматического получения имени типа в функции шаблона, поэтому нет необходимости индивидуально определять различные типы.- Шаблон формы 1 имеет самые низкие требования к памяти.
Вопросы
- Поскольку форма шаблона 2 имеет более высокие требования к памяти, чем форма шаблона 1, как ее можно преобразовать из Формы 2 в Форму 1?
- Как
статический символ const *
будет инициализирован из подстроки массивасимволов
, полученного измакроса ___
__ _ _ _ _ _ _ и передан в качестве аргумента шаблона во время компиляции?
Что-то вроде этого псевдокода?
static const char * name = substr(__PRETTY_FUNCTION__, begin, end);
Возможно, это постоянное выражение, которое может быть присвоено члену структуры шаблона во время компиляции? (Я использовал цикл for, потому
что strstr
и strrchr
не являются constexpr
.)
template <typename T>
constexpr char* type_name(const T&)
{
char* pf = __PRETTY_FUNCTION__;
char* begin;
char* end;
begin = end = pf;
bool found_open_square_bracket = false;
for (; *pf != 0; pf++)
{
if (!found_open_square_bracket && *pf == '[')
{
begin = pf + 10;
found_open_square_bracket = true;
}
if (*pf == ']')
{
end = pf;
}
}
*end = 0;
return begin;
}
Обратите внимание, что: 1. Когда вы передаете массив в качестве аргумента функции, он распадается на указатель на его первый элемент. 2. char*[5]
- это массив указателей, тогда как &chars
- указатель на массив., @Edgar Bonet
@Juraj, Извини. Я думал, что первый абзац охватывает это в контексте этой темы. Я объясню это яснее., @tim
Это скорее ограничение версии C÷+ в Arduino. На ПК я бы вернул " std::string_view в этом случае. На Arduino вы можете вернуть структуру "const char*
и "size_t". Это решит вашу проблему с памятью., @Vladimir Talybin
Я бы также разделил часть шаблона и часть синтаксического анализа на 2 функции, чтобы компилятор генерировал меньше кода для каждого запрошенного типа., @Vladimir Talybin
Существует также трюк с использованием предопределенного макроса компилятора. Преимущество в том, что он может печатать любой тип.
template <class T>
String type_name(const T&)
{
String s = __PRETTY_FUNCTION__;
int start = s.indexOf("[with T = ") + 10;
int stop = s.lastIndexOf(']');
return s.substring(start, stop);
}
Используйте его вот так
double pi = 3.14;
const char* str = "test";
Serial.println(type_name(pi));
Serial.println(type_name(str));
ИЗМЕНИТЬ:
Некоторые улучшения основаны на анализе Тима.
Сначала у меня есть комментарий к варианту, который имеет более низкие требования к памяти. __PRETTY_ФУНКЦИЯ__
- это литеральная строка (константа), которую не следует изменять. Поэтому моя версия const, которая не использует кучу, следующая.
template <size_t N>
const char* extract_type(const char (&signature)[N])
{
const char* beg = signature;
while (*beg++ != '=');
++beg;
const char* end = signature + N;
for (; *end != ']'; --end);
static char buf[N];
char* it = buf;
for (; beg != end; ++beg, ++it)
*it = *beg;
*it = 0;
return buf;
}
template <class T>
const char* type_name(const T&)
{
return extract_type(__PRETTY_FUNCTION__);
}
Менее безопасная, но более эффективная версия, использующая тот факт, что смещение для типа, который мы хотим извлечь, является константой (всегда одинаковой). Если подпись функции изменится (например, поместите ее в пространство имен), смещение тоже изменится. Но давайте, установите его один раз, напишите комментарий, и он будет работать вечно :).
template <size_t N>
const char* extract_type(const char (&signature)[N])
{
const char* beg = signature + 42;
const char* end = signature + N - 2;
static char buf[N - 43];
char* it = buf;
for (; beg != end; ++beg, ++it)
*it = *beg;
*it = 0;
return buf;
}
template <class T>
const char* type_name(const T&)
{
return extract_type(__PRETTY_FUNCTION__);
}
Хотя вышесказанное, конечно, нормально, его можно улучшить и дальше. Вот моя версия функции constexpr, упомянутая @tim. Он использует постоянное смещение, как указано выше, чтобы сократить его. Этот код генерирует только извлеченную строку типа, доказательство здесь.
#include <utility>
template <class T, std::size_t... I>
const char* type_name(std::index_sequence<I...>)
{
static constexpr char name[] = { __PRETTY_FUNCTION__[I + 60]..., 0 };
return name;
}
template <class T>
const char* type_name(const T&)
{
return type_name<T>(
std::make_index_sequence<sizeof(__PRETTY_FUNCTION__) - 44>());
}
Минимальное требование для этого-C++14 из-за особенностей последовательности. Они могут быть повторно реализованы (см. Определение здесь), чтобы адаптировать его к C++11.
Это здорово, но " int stop = s.lastIndexOf (]'); "лучше, потому что в некоторых типах есть" []
., @tim
@тим Тру, спасибо тебе! Изменит это., @Vladimir Talybin
- Можно ли использовать последовательный порт в качестве переменной?
- Создание строк с символами UTF-8 из данных
- Не выводится на serial monitor, отправляя строку на serial.print
- String() против char для простого управления потоком
- Как анализировать многострочные последовательные данные с неизвестным количеством строк?
- Keyboard.write записывает целочисленное значение ASCII в виде строки
- Присвоение значения порядку байтов структуры
- В чем разница между типами данных CloudTemperature, CloudTemperatureSensor и Float?
Почему вам нужно это делать? тип переменной должен быть известен с момента ее объявления, @sachleen
@sachleen Неправда. Справочная информация по Arduino печально известна тем, что не говорит вам о типах возвращаемых функций. Я должен определить как auto, а затем использовать кучу перегрузок функций, чтобы узнать. RTTI было бы проще узнать., @brewmanz