создать длинный список x элементов для раскрывающегося списка gyverportal
Я пытаюсь сделать выбор в раскрывающемся списке с помощью gyverportal. Проблема в том, что я не знаю, сколько элементов, поэтому список не может быть жестко запрограммирован. Вот пример с гитхаба:
GP.SELECT("sel", "val 1,val 2,val 3", valSelect);
//где sel — имя поля, а valselect — текущий выбранный элемент.
Меня очень смущают различные типы текста (char, char*, const char, const char*, String, char array[10]... и это лишь некоторые из них), и операции с ними кажутся кошмаром. . Я думаю, что gyverportal ожидает "char*" для списка.
для контекста: Элементы загружаются из EEPROM, а число сохраняется в Length.
что мне нужно:
для 0 элементов: "нет"
для 5 элементов: "1,2,3,4,5,нет"
для 10 элементов: "1,2,3,4,5,6,7,8,9,10,нет"
int Length=5; //У меня есть пять элементов для выбора
char* dropdownoptions= "";
if (Length<1){
dropdownoptions="none";
}else{
char Answ[60]="1,";
for (int i=1; i<=Length; i++){
char part[3];
String str=String(i+1);
str.toCharArray(part, 3);
strcat(Answ,part);
strcat(Answ,",");
}
strcpy(dropdownoptions,Answ);
strcat(dropdownoptions,"none");
}
Serial.println (dropdownoptions);
Этот код был переписан около 30 раз и компилируется, но вызывает сбой платы (esp8266) или зацикливание загрузки
В идеале это то, что я хотел бы сделать
dropdownoptions= "";
for (int i=1; i<=Length; i++){
dropdownoptions+=i;
dropdownoptions+=",";
}
dropdownoptions+="none";
@Gerge, 👍0
Обсуждение2 ответа
Лучший ответ:
Как объяснил Крисл в своем ответе, вы должны объявить
dropdownoptions
в виде массива char
, достаточно большого, чтобы вместить все
строка, включая завершающий '\0'
. Если вы заранее знаете
максимальное значение Length
, вы можете соответствующим образом изменить размер массива.
В противном случае вы можете вычислить требуемый размер как функцию Длины
:
int str_length; // размер строки, включая последний '\0'
if (Length < 10)
str_length = 2 * Length + 5;
else
str_length = 3 * Length - 4; // предположим, что длина < 100
char dropdownoptions[str_length];
Затем вы должны записать содержимое в этот массив. Один из способов сделать это
было бы записать каждую отдельную часть во временный массив, затем
скопируйте его в массив dropdownoptions
, используя конкатенацию строк. Мой
однако предпочтительным способом заполнения массива является метод «нулевого копирования».
состоящий из записи частей прямо в нужное место
конечный массив. Это требует некоторых манипуляций с указателем. Если ты слишком
неудобно с указателями, вы можете пропустить этот ответ. Но если ты хочешь
чтобы узнать и попрактиковаться, читайте дальше.
Пусть p
будет указателем на текущую позицию массива, где вы хотите
записывать. Вы можете передать этот указатель в sprintf()
, чтобы написать
часть данных в буфер в нужном месте. sprintf()
возвращает
количество записанных байтов, не считая завершающего '\0'
, и
затем вы можете переместить указатель p
на эту точную величину, чтобы
знать, где написать следующий кусок. В коде:
char *p = dropdownoptions; // текущая позиция записи
for (int i = 1; i <= Length; i++) {
p += sprintf(p, "%d,", i);
}
sprintf(p, "none");
Однако у использования sprintf()
есть один недостаток: если вы получаете размер
неправильного и слишком короткого массива, вы в конечном итоге напишете за его конец
и испортить память. Если вы хотите защищаться, вы должны держать
отслеживать объем доступного оставшегося места и использовать функцию
snprintf()
. Эта функция принимает в качестве второго параметра
количество байтов, доступных в буфере, и он никогда не переполнится
этот буфер. Я считаю, что самый простой способ отслеживать
оставшееся место для инициализации указателя на конец массива
(технически, «один после конца» массива) и вычислить
доступное пространство как end-p
:
char *p = dropdownoptions; // текущая позиция записи
const char *end = dropdownoptions + str_length; // конец массива
for (int i = 1; i <= Length; i++) {
int count = snprintf(p, end - p, "%d,", i);
p += min(count, end - p);
}
snprintf(p, end - p, "none");
Причина использования здесь min()
заключается в том, что snprintf()
может возвращать
число больше, чем доступное пространство: если буфер слишком мал, это
функция возвращает количество символов, которое было бы записано
(не считая завершающего '\0'
), если бы буфер был достаточно большим.
Спасибо за подробное введение в указатели, мне нужно еще многому научиться.
В EEPROM хранится 20 элементов, поэтому теперь код работает с `
char dropdownoptions[60];`
Я не ожидаю, что он когда-нибудь переполнится (есть много проверок), поэтому я использовал sprintf., @Gerge
Причина, по которой ваш код дает сбой, заключается в том, что вы пытаетесь записать недействительный указатель.
С линией
char* dropdownoptions= "";
вы объявляете указатель на символ (имеется в виду 2-байтовый большой адрес памяти) и позволяете ему указывать на строковый литерал. Все строковые литералы помещаются компилятором в раздел оперативной памяти только для чтения и не могут быть изменены. Таким образом, с этой строкой у вас есть только указатель (адрес памяти), но не буфер/пространство памяти для записи. Позже вы пытаетесь
strcpy(dropdownoptions,Answ);
который хочет записать в память/адрес, на который указывает dropdownoptions
. Но он находится в разделе только для чтения, его нельзя записать, поэтому ваш код вылетает. И лучше, чтобы он вылетал, чем записывал данные в какую-то случайную ячейку памяти, где может быть что-то важное.
Выполнение
dropdownoptions="none"
это нормально, так как литеральная строка "none"
также помещается в раздел только для чтения. Таким образом, в этом случае изменяется только указатель, а не базовые данные.
Если вы хотите писать в dropdownoptions
, вам нужно объявить его как массив символов:
char dropdownoptions[70] = "";
Сделайте его достаточно большим, чтобы он мог содержать самую большую строку, которую вы ожидаете. Остальная часть вашего кода должна работать как есть. Хотя преобразование из целого числа i
в строку можно сделать короче:
char part[3];
itoa(i+1, part, 10);
Затем part
содержит число в виде строки с завершающим нулем, поэтому вы можете использовать для него функции strX
.
- Преобразование строки в IP-адрес
- Как составить URL-адрес HTTP-запроса GET с параметрами ключ/значение
- Какова цель F() и FPSTR() в ESP8266WebServer -> FSBrowser?
- Как исправить код утечки памяти в ESP8266/NodeMCU, вызванный концентрацией строк?
- Как заменить объекты String массивами символов, продолжая использовать строковые методы
- Утечка памяти, вызванная конкатенацией строк
- Как отсортировать строку с числом по возрастанию
- Чтение строки из Firebase и сохранение ее в виде CString
Используйте
stncpy()
иstrncat()
для защиты от переполнения массивов символов. Гуглите их.dropdownoptions
имеет длину два байта, и вы копируете в него более длинные строки. Он должен быть достаточно большим, чтобы хранить в нем самую длинную строку, которую вы копируете., @romkey@romkey Это немного вводит в заблуждение.
dropdownoptions
имеет длину два байта, так как это указатель, а не массив символов. Он не предназначен для удержания строки, поэтому было бы не лучшим решением просто увеличить его., @chrisl@chrisl, я согласен :), @romkey