Тут не понял, а почему два раза?
Без делений и разного рода/размера библиотек - специально для 8 битных AVR:
void print_with_leading_zero(byte value) {
byte tens;
for (tens = 0; value > 9; value -=10, tens++);
Serial.print(tens);
Serial.print(value);
}
Вы совсем не про эту звездочку писали.
Для закрепления материала можете обьяснить, почему звездочка в Сериал.принт() мешает?
ну вы код совсем читать не умеете, что ли?
Вот этот кусок:
первый раз itoa вызывается при вычислении concat, а второй - при (hours<10)
Он не читатель, он писатель !!!
Не знал, что в условиях, вычисляются сразу все переменные.
не пишите бреда, это тут совершенно не при чем.
Похоже вы смотрите в код и не понимаете ни буквы…
Давайте по пунктам - то что в первой строке (при инициализации concat) itoa вычисляется всегда - есть возражения?
Возражений нет)
ну а в этой строке - если условие (hours<10) ложно - itoa вызывается вторично.
или не согласны?
Очень даже согласен!
ну вот и выходит, что если (hours<10) ложно - itoa вызывается дважды
А теперь про звездочку давайте.
Я бы вот так сделал:
char *GetTimeStr(const byte hours, const byte minute, char *_str, const char *_pfx = "") {
sprintf(_str, "%02d:%02d", hours, minute);
return strcat(_str, _pfx);
}
void setup() {
char str[20];
Serial.begin(115200);
Serial.println(GetTimeStr(5, 4, str, "Pm"));
}
void loop() {
// put your main code here, to run repeatedly:
}
Буфер под строку вне функции заводим…
Ну, вот смотрите, Вас же ничего не смущает, например вот в таком примере,
int summ(const int a, const int b) {
int s = a + b;
return s;
}
Вы же не говорите, что переменная s
локальная и это создаст проблемы при использовании результата функции?
Чтобы никогда не путаться, нужно твёрдо усвоить несколько простых понятий и применять их буквально - точно как сказано.
О буквальности, чтобы каждый раз не возвращаться
Это то, почему я тут частенько маниакально настаиваю на точности формулировок, чем вызываю бешенство новичков типа: “чего придираешься, всё же понятно”. Ни хрена не понятно, если копнуть чуть глубже.
Например, если функция возвращает значение типа char *
- это означает, что она возвращает указатель на символ. Не строку, не массив, не ссылку, не указатель на массив - ничего подобного - указатель на символ - всё!
Другой пример, если мы говорим, что функция возвращает значение некоторого типа, это надо именно так и понимать - выражение return abс
; возвращает не переменную abc
, а её значение, т.е. создаёт её копию и именно копию возвращает. Ни в коем случае не саму переменную.
Часто приходится читать, что мол, если мы хотим вернуть не копию, а “саму переменную”, то надо вернуть указатель на неё. Это абсолютно неправильная терминология. Она вводится с благими намерениями облегчиться понимание новичков, но на самом деле даёт им ложную установку, исключающую всякое понимание. На самом деле, если функция возвращает указатель на некоторую переменную, значит она возвращает именно указатель на эту переменную, а вовсе не “её саму”.
Т.е. тексты воспринимаем и используем буквально - без додумок!
Теперь простые истины.
№1
Указатель - физически это просто число. Пронумеруйте все ячейки памяти компьютера последовательными числами, начиная с нуля. Указатель -это номер ячейки памяти с которой расположен “указуемый” объект. Т.е. запомнили - это число - номер ячейки памяти и не ищите там никакой магии.
№2
Функция всегда возвращает значение выражения, указанного в операторе return
. Именно значение, а, ни в коем случае, не само выражение. Например, return s;
означает “вернуть значение s
” а вовсе не вернуть саму s
!!! На практике это значит, что создаётся копия s
и именно эта копия возвращается. Именно поэтому в примере из начала поста всё правильно - возвращается копия и ей без разницы, что оригинал уже уничтожен, т.к. копия была сделана до уничтожения оригинала.
Кстати
это (создание копий) поднимает большой ворох проблем в случае, если возвращается значение экземпляра класса. Дело в том, что при создании копии будет вызываться конструктор, а при её последующем уничтожении - деструктор. И это может вызвать массы неожиданных эффектов. Но это отдельная и очень непростая тема, выходящая долеко за рамки этого поста. Если захотите гуглить, то ключевые слова: “правило трёх”, “правило пяти”, “семантика перемещения”, “конструктор копирования”, “конструктор перемещения” и т.п., но, повторяю, это большая и сложная тема.
Я понимаю, о какой проблеме Вы говорили. Потому я приведу несколько примеров, где она возникает и где не возникает с рассуждениями по вышеприведённым правилам, чтобы у Вас всё устаканилось.
Пример №1
char buffer[39];
char * f1(void) {
strcpy(buffer, "In God we trust! Anyone else pay cash!");
return buffer;
}
Возвращается значение правильного адреса (помним, что это просто число - номер ячейки) нулевого байта массива buffer
.
Сам по себе массив buffer
объявлен глобально, потому время его жизни бесконечно, никуда он со своего адреса не денется. Никаких проблем!
Пример №2
char * f2(void) {
static char buffer[39];
strcpy(buffer, "In God we trust! Anyone else pay cash!");
return buffer;
}
Возвращается значение правильного адреса нулевого байта массива buffer
.
Сам по себе массив buffer
объявлен статическим, потому время его жизни бесконечно, никуда он со своего адреса не денется. Никаких проблем!
Пример №3
char * f3(void) {
char * buffer = new char[39];
if (! buffer) return nullptr;
strcpy(buffer, "In God we trust! Anyone else pay cash!");
return buffer;
}
Возвращается значение правильного адреса нулевого байта динамически запрошенного массива buffer
(или ноль, если на запрос не хватило памяти).
Память под массив buffer
выделена динамически. Её никто не освобождал (и не освободит пока Вы сами это явно не сделаете при помощиdelete
). Потому, до тех пор, пока Вы его сами не освободите, никуда он со своего адреса не денется. Никаких проблем, только не забываем освобождать, когда больше не нужен!
Пример №4
char * f4(void) {
char buffer[39];
strcpy(buffer, "In God we trust! Anyone else pay cash!");
return buffer;
}
Возвращается значение правильного адреса нулевого байта массива buffer
.
Сам по себе массив buffer
объявлен локально, поэтому здесь возникает проблема - сразу же после выхода из функции память, которую занимал массив, будет объявлена свободной. Значение, которое вернула функция, разумеется останется правильным адресом нулевого байта массива buffer
(а чего ему быть неправильным-то?), только вот массива там уже формально нет, т.к. память объявлена свободной. Будет ли по возвращённому адресу записанное в функции значение? А как повезёт! Память-то свободна. Если никто не успел разместить там новую(ые) переменную и записать что-нибудь новое, то будет - куда ж оно денется? А если успел - то хренушки.
Кстати
И, кстати, компилятор нас честно предупредит: “warning: address of local variable 'buffer' returned
”. Если мы не игнорируем его предупреждения, то тут же исправим ошибку, ну а если у нас стоят опции IDE по умолчанию (или если мы не читаем предупреждений), то сами себе злобные Буратины.
Ещё кстати
Именно поэтому я категорически не рекомендую использовать небрежно написанные библиотеки типа iarduino_xxx
. С них валится столько предупреждений, что, привыкнув, что их всегда много, можно запросто пропустить в своём куске кода такое вот важное предупреждение. Грамотно написанная библиотека не должна при компиляции засыпать пользователя предупреждениями.
Более сложный пример №5
А давайте попробуем вернуть литерал!
char * f5(void) {
return "In God we trust! Anyone else pay cash!";
}
void loop(void) {
char *s = f5();
...
}
Компилятору это не понравится, но, понимая, что альфа-самец здесь программист, а не он, он не станет совсем уж отказываться компилировать, а просто выдаст предупреждение: “warning: ISO C++ forbids converting a string constant to 'char*
'”
Не счесть сколько раз я видел в сети советы “гур” забивать на это предупреждение. Однако, забивать не него весьма опасно. Ведь в итоге у нас неконстантный указатель s
теперь указывает на литерал! Значит, нам никто и ничто не мешает случайно засрать изменить значение литерала, а потом удивляться, почему у нас вместо “пидар@с” печатается “экстремист”,
как вот в этом примере
char * f5(void) {
return "pidaras!!!";
}
void setup(void) {
Serial.begin(9600);
char *s = f5();
strcpy(s, "extremist");
//
// Ну, мы же, блин, явно говорим ему что печатать, а он :(
Serial.println("pidaras!!!");
}
void loop(void) {}
Можно, конечно, запретить себе менять литерал, объявив указатель s
константным.
char * f5(void) {
return "In God we trust! Anyone else pay cash!";
}
void loop(void) {
const char *s = f5();
...
}
Но сделаем мы это или нет, зависит целиком от нашей дисциплины и “незабывчивости”. И, если не сделаем, рискуем налететь прелести, описанные выше.
Гораздо грамотнее здесь таки сделать всё правильно, а именно объявить, что функция возвращает не char *
, а const char *
. Так мы одним махом и от предупреждения избавимся и запретим присваивать результат этой функции неконстантному указателю (если попытаемся - вылезет предупреждение).
Вот так это надо делать - и никаких предупреждений:
const char * f5(void) {
return "In God we trust! Anyone else pay cash!";
}
void loop(void) {
const char *s = f5();
...
}
Если я правильно понял, то тут задействован “хак” что строковые константы (литералы) используются по одному разу, если они идентичные… Интересный пример)
Не ожидал такого.
Ну, понятно, если бы он печатал extremist
при
Serial.println(f5());
А тут вроде ж явно задаём. А может это уже другой pidaras!!!
?
Наверное , оптимизатор , ИМХО
P.S.Позже, если никто не разъяснит, проверю в Proteus, там знаю как отключить.
Таки нет. При отключённом - всё также.
Более того, он их совмещает даже если литералы разные но оканчиваются одинаково. У нас тут уже была тема про это и там был хороший пример.
И, кстати, в той же теме, можете другой пример глянуть.
Спасибо
Что бы константы не затирались ни умышленно, ни случайным распахиванием- пишите их в PROGMEM !!!
Можно ещё их на бумажке писать (или на обоях) и камеру для считывания присобачить. Несомненный плюс такого похода - если захочется поменять, это можно сделать без программатора.