создать длинный список 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";

, 👍0

Обсуждение

Используйте stncpy() и strncat() для защиты от переполнения массивов символов. Гуглите их. dropdownoptions имеет длину два байта, и вы копируете в него более длинные строки. Он должен быть достаточно большим, чтобы хранить в нем самую длинную строку, которую вы копируете., @romkey

@romkey Это немного вводит в заблуждение. dropdownoptions имеет длину два байта, так как это указатель, а не массив символов. Он не предназначен для удержания строки, поэтому было бы не лучшим решением просто увеличить его., @chrisl

@chrisl, я согласен :), @romkey


2 ответа


Лучший ответ:

1

Как объяснил Крисл в своем ответе, вы должны объявить 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


1

Причина, по которой ваш код дает сбой, заключается в том, что вы пытаетесь записать недействительный указатель.

С линией

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.

,