строка+символ

ourlive
Offline
Зарегистрирован: 26.05.2012

возникла необходимость совершать различные операции со строковыми переменными.

пока операции с однозначно определёнными строками типа:

String text="123abc";
text=text+"32bc";

то проблем особых нет, а вот если возникает необходимость прибавить всего один символ, то начинается ужас.

text="123abc"+char(100);

судя по тому, что ничего похожего в примерах нет, это не правильно. преобразование

text="123abc"+String(char(100));

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

так вот всё же как правильно к строковой переменной прибавить один или несколько символов известных только по их коду?

При всём при этом задача усложняется тем, что данная строка выводится на TFT.LCD, в библиотеку которого засунута невесть какая таблица симвлов, с ASCII совпадающая только частично (например символ с кодом 1 отображается как кислый смайлик и т.п.).

 

 

 

 

 

 

 

leshak
Offline
Зарегистрирован: 29.09.2011
ourlive
Offline
Зарегистрирован: 26.05.2012

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

leshak
Offline
Зарегистрирован: 29.09.2011

 И поэтому решили попробовать вариант который в туториале описан в качестве примера "это работать не будет" :)

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

  1. Вы уверены что вам нужен именно String? Строка "как объект". Стандартная С-шная строка не подойдет?  (учитывая дальнешие использование TFT)
  2. Вы уверены что вам нужно именно "прибавить", а не "использовать в строке символ известный по коду"
  3. Код символа известен на момент компиляции программы, или он должен "вычислятся в процессе", а потом вставлятся в строку?
ourlive
Offline
Зарегистрирован: 26.05.2012

1. не уверен. всё что я о программировании знаю, это бессистемное самопознание. потому в строках, объектах и других умных словах частенько путаюсь

2. да, именно прибавлять, причём всё это происходит в процессе: строку разделить, ненужное выкинуть, нужное добавить, снова склеить в одну и тэдэ.

3. часть символов вычисляются, часть известны заранее.

данная строка выполняет роль буфера печати. т.к. вывод на экран по SPI больших массивов данных занимает довольно много времени, решил выводить инфу неспеша по таймеру (на данный момент 10 символов в секунду), чтобы высвободить временной ресурс для параллельных задач (обработка нажатий клавишь и т.п.)

в итоге строка имеет формат:

1. маркер начала, 2 координты (x;y), 3. размер шрифта, 4. текст. и снова маркер, новые координаты, шрифт, текст и т.д.

потому как очередь печати может ожидаться до 2х десятков строк (стока на экране минимальным шрифтом может влезать), то не хотелось плодить мегаколичество массивов различного назначения, а хотелось ограничится одной глобальной переменной для этих целей.

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

leshak
Offline
Зарегистрирован: 29.09.2011

 Честно говоря, даже чуть-чуть в затруднении что-же вам посоветовать.

С одной стороны объект String - проще в использовании. Особенно для новичка, всякие "разделить, выкинуть" и т.п.

С другой стороны, по сравнению с обычным string  (который суть массив заканчивающийся нулевым символом) он более "тяжеловесен". Это калька с "взрослых языков на компьютере", где "килобайтом туда-сюда" рояля не играет.

Экономии на "количестве массивов" у вас не произойдет. Скорее даже "наоборот". Все эти строковые операции каждый раз будут порождать, опять-таки новый объект строку (который внутри, как ни крути, будет выделять массив под хранение данных).

Ну и еще минус String - что это, вообщем-то "ардуинистая фишка". Мало где используются, многие библиотеки портированные от "обычного Amtel" - ничего про нее не знают.

Да и, опять-таки, "отправлять строку порциями" - проще обычными строками-указателями. Отправил 20-ть байт - сместил указатель и т.п.

Вообщем-то "сделать как вы хотите" конечно можно. Вот например:

void setup(){
  Serial.begin(57600);
  
  Serial.println("Ready");
  
  String str1="AAA NNN BBB";
  Serial.println(str1);
  
  //подменяем символы использую их коды
  str1[4]= 86 ; //V
  str1[5]= 87; // W
  str1[6]= 88; // X
  
  Serial.println(str1); // Выводит "AAA VWX BBB"
  
  str1+=" ZZZ";
  
  Serial.println(str1); // Выводит "AAA VWX BBB ZZZ"
  
  
  //похоже это то что вы хотели 
  str1+= (char[]){32,81,82,83,0} ; // Добавляет " QRS". 32 в начале это пробел, 0 в конце - символ конца строки

  Serial.println(str1); // Выводит "AAA VWX BBB ZZZ QRS"
  
  
  // а вот имитация вашей попытки
  str1+=(char)88; // доавляем X
  
  Serial.println(str1); // Выводит "AAA VWX BBB ZZZ QRSX"
  
  // или в таком виде как вы пробовали
  
  String str2=String("AAA")+(char)88;
  Serial.println(str2); // Выводит "AAAX"
}

void loop(){}

 

 

Но меня терзают сомнения что это "сильно окольный путь". "Разбирать строку" - а не проще изначально собирать ее из правильный кусков. да и вообще "все это упихивать в строку" не очень понятно зачем. Если нужно слать какие-то "байты", так байтами их и слать, SPI вроде это умеет.

Покажите весь свой скетч. Возможно вы пытаетесь выполнить "сложную работу" которую вообще не нужно делать.

 

ourlive
Offline
Зарегистрирован: 26.05.2012
/***************************************************
работает:
1. опрос часов раз в пол секунды
2. опрос кнопок нажатие(0.1 сек) и долгое нажатие (1+ сек)
3. индикация нажатия
4. стирание одного символа
5. печать текста

    LCD  
  _____________________128_________________________
  |
  |
  |
  |
  |
  |
  |
 160
  |
  |
  |
  |
  |
  |
  
  TextSize(1) Heigh 8    length 5+1
  TextSize(2) Heigh 16
  TextSize(3) Heigh 24
  
 ****************************************************/

// определение пинов
#define sclk 13
#define mosi 11
#define cs   8
#define dc   10
#define rst  7  // you can also connect this to the Arduino reset

#include <Adafruit_GFX.h>    // библиотека графики
#include <Adafruit_ST7735.h> // библиотека на экран
#include <SPI.h>

#include <Wire.h>                       // 1-wire
#include <DS1307new.h>                  // часы

// определение пинов экрана
Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, rst);

// определение переменных------------------------------------------------------------------------------------------
byte key_put=0;  // нажатая клавиша
byte menu=0;  // статус меню
unsigned long time=0;      // таймер работы дуины
unsigned long timer_key=0; // таймер клавиши
unsigned long timer_time=0; // таймер часов
unsigned long timer_menu=0; // таймер меню
unsigned long timer_my_print=0; // таймер меню
String my_text_print="";        // текстовая строка на печать
word i;
void setup(void) {
  Serial.begin(9600);
  pinMode(6,OUTPUT); 
  analogWrite(6, 10);        //подсветка
  tft.initR(INITR_REDTAB);   // инициализация ST7735R chip, red tab
  tft.fillScreen(ST7735_BLACK); // залить весь экран чёрным
  start1();
  RTC.getTime();
}

void start1() {
  tft.setTextColor(ST7735_WHITE);
  tft.setTextSize(2);
  tft.setCursor(1, 10);
  tft.println("   Nano");
  tft.println("  tester");
  tft.println("   v0.4");
  delay(2000);
  tft.fillScreen(ST7735_BLACK);
  }
  
void time_read() { //читать время раз в пол секунды
  if (time>timer_time) {
  timer_time=timer_time+500000;  
  RTC.getTime();      
  // RTC.year - RTC.month - RTC.day - RTC.dow -год, месяц, день, день недели
  // RTC.hour : RTC.minute : RTC.second - часы:минуты:секунды
  
  i++;
  if (i> 65535) {i=1;}
  my_print_tft(50,100,String(char(i))+" "+String(i),2);
}}


void key() { //определение нажатой кнопки
if (time>timer_key) {
  byte key_put_local=0;
  timer_key=time+10000;                          // ожидание нажатия клавиши 10 раз в секунду
  unsigned int key=analogRead(A3);
  tft.drawLine(0,0, 2,0,ST7735_BLACK);            // затереть индикацию
  if (key>1000) {key_put_local=0;}                // клавиша не нажата
  else if (key<470 && key>455) {key_put_local=1; timer_key=time+500000;} // клавиша нажата, следующая проверка через пол секунды
  else if (key<395 && key>385) {key_put_local=2; timer_key=time+500000;}
  else if (key<305 && key>290) {key_put_local=3; timer_key=time+500000;}
  else if (key<185 && key>165) {key_put_local=4; timer_key=time+500000;}
  else {key_put_local=5; timer_key=time+500000;}  //кнопка не определена
  if (key_put_local!=0) {
  if (key_put_local==key_put || key_put==key_put_local+5) {key_put=key_put_local+5;} // если клавиша нажата более 1 секунды(две проверки нажатия), установить статус +5 - долгое нажатие
  else {key_put=key_put_local;}
  if (key_put==1) {tft.drawLine(0,0, 1,0,ST7735_RED);}      // гкая индикация нажатия
  if (key_put==2) {tft.drawLine(0,0, 1,0,ST7735_YELLOW);}
  if (key_put==3) {tft.drawLine(0,0, 1,0,ST7735_GREEN);}
  if (key_put==4) {tft.drawLine(0,0, 1,0,ST7735_BLUE);}
  if (key_put==5) {tft.drawLine(0,0, 1,0,ST7735_WHITE);}
  if (key_put==6) {tft.drawLine(0,0, 2,0,ST7735_RED);}
  if (key_put==7) {tft.drawLine(0,0, 2,0,ST7735_YELLOW);}
  if (key_put==8) {tft.drawLine(0,0, 2,0,ST7735_GREEN);}
  if (key_put==9) {tft.drawLine(0,0, 2,0,ST7735_BLUE);}
  if (key_put==10) {tft.drawLine(0,0, 2,0,ST7735_WHITE);}}
  else {key_put=0;}                                         //коррекция статуса, если ничего не нажато.
}}

void clear_byte (byte x, byte y, byte s) { // стирает один символ курсор (х,у), размер шрифта (s)
  tft.setTextColor(ST7735_BLACK);
    tft.setTextSize(s); 
    for  (int z=-s; z<=6*s; z++) {
    tft.setCursor(x, y-z); tft.println("_"); }
  }

void my_print_tft (byte x, byte y, String text, byte s) { //х,у - курсор; text - текст на печать, s - size
  // при адекватных параметрах отправить данные на печать
  if (x>0 && x<128 && y>0 && y<160 && s>0 && s<5 && text!="") {
  my_text_print=text+char(x)+char(y)+char(s)+char(1);
  }}
  
void my_print_tft_t() {
  if (my_text_print!="") {
  if (time>=timer_my_print) {
    timer_my_print=time+100000; // печатать 10 символов в секунду
  byte l=my_text_print.length(); // выделение параметров из строки
  byte s= my_text_print.charAt(l-2);
  byte cur= my_text_print.charAt(l-1);
  byte x= my_text_print.charAt(l-4)+(cur-1)*s*6;
  byte y= my_text_print.charAt(l-3);
  char my_char=my_text_print.charAt(cur-1);
    cur++;
    my_text_print.setCharAt(l-1,char(cur));  // запись нового значения курсора
    if (cur>l-4) {my_text_print="";}
  clear_byte(x,y,s);
  tft.setTextColor(ST7735_WHITE);
  tft.setTextSize(s);
  tft.setCursor(x, y);
  tft.println(my_char);
}}}

void loop() {
 
  time=micros();
  key();
  time_read();
  my_print_tft_t();
  if (key_put==1) {}
  if (key_put==2) {}
  if (key_put==3) {}
  if (key_put==4) {}
}

это не последний вариант, в последнем ещё больше артефактов разных стадий тестов, отладок и ошибок (последний на недоступной щас машине), да и структура строки была изменена. здесь впринципе видно как я пытаюсь данные в строку запихать и как достать.

а по вашему примеру сразу вопрос (char)88  и char(88)  по сишному идентичные записи?

 

leshak
Offline
Зарегистрирован: 29.09.2011

 Конечно "делов вкуса", можно идти и вашим путем. Но IMHO вы тут делаете очень много лишнией работы.

Зачем запихивать данные в строку, а потом их оттуда выпарсивать (тем более таким загадочным способом как charAt(1-2))?

С жестким кодингом "позиции параметра" - вы же потом повеситесь если вам, через пол-годика потребуется добавить еще 5-ток параметров :)

Зачем мешать "мухи и котлеты"? Данные отдельно, тексты - отдельно. Если хочется что-бы они были "жестко связанны между собой" (данные и текст)  можно упихать их "в общий контекнер", только "контейнером" использовать не строку, а struct или классы. Которые как-бы для того и придуманы, что-бы разнотипные данные "объеденять" и было удобно с ними работать.

typedef struct TText{
   byte x;
   byte y;
   byte fontSize;
   String text; 
} ;


  TText mytext;  //  переменная-контейнер для данных и текста

void setup(){
  Serial.begin(57600);


 
 // заполняем текст и данные
 
 // данные
 mytext.x=5;
 mytext.y=10;
 mytext.fontSize=16;

 //тексе
 mytext.text="Hello text";
 
 printText();
 
 // чуток меняем данные
 
 mytext.x+=3;
 mytext.fontSize*=2;
 mytext.text+=" and appended word";
 
 // и опять выводим
 
  printText();
}


void printText( ){

  //выводим координаты

  Serial.print("X,Y=");
  Serial.print(mytext.x,DEC);Serial.print(",");Serial.print(mytext.y,DEC);
  Serial.println();

  Serial.print("Font Size:");Serial.println(mytext.fontSize);
  Serial.print("Text:");Serial.println(mytext.text);

  Serial.println();  Serial.println();
}


void loop(){
}

 

 

 

 

leshak
Offline
Зарегистрирован: 29.09.2011

ourlive пишет:


а по вашему примеру сразу вопрос (char)88  и char(88)  по сишному идентичные записи?

 

Близкие, но не идентичный. Хотя во многих ситуациях будет работать "одинаково".

(char) - это "приведение типа". То есть на этапе компиляции жесткое указание компилятору "то что дальше считай char-ром". Как-бы некое локальное "переопределение типа". Но никаких новым переменных это не образует.

char() - это "конвертация типа". То есть у нас есть переменная какого-то типа, и а мы хотим попрбовать ее преобразовать в char

mixail844
Offline
Зарегистрирован: 30.04.2012

извините если сильно вмешиваюсь,eсли нужно добавлять строку к строке может воспользоваться функцией strcat(char* a,char* b)?

/* strcat example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[80];
  strcpy (str,"these ");
  strcat (str,"strings ");
  strcat (str,"are ");
  strcat (str,"concatenated.");
  puts (str);
  return 0;
}

результат : 

these strings are concatenated. 

важно заметить что длинна str должна быть больше чем длинна добавляемых в нее строк + учитывать байт окончания '\0'.

 

 

 

leshak
Offline
Зарегистрирован: 29.09.2011

mixail844 пишет:

извините если сильно вмешиваюсь,eсли нужно добавлять строку к строке может воспользоваться функцией strcat(char* a,char* b)?

Что-вы, какое "вмешиваюсь"? Наоборот. "Молчащих в тряпочку" или приходящих на форум только когда у них проблема - и так хватает.

Да и кто-бы что не советовал - всегда есть шанс что он ошибается или не увидел более оптимальный путь. Поэтому "еще один помошник" - это всегда здорово.

Теперь по сути вашего предложения. То что вы описали - совершенно верно. Но это работа с обычными сишными строками. Это те строки которы string. А топик-стартер использует объект String - ардуиновскую "альтернативу". Предположительно более простую в использовании для новичка (хотя, лично я, тоже предпочел бы ваш подход).

А если глубже смотреть - то как раз вызывает глубокое сомнение, что "конкатенация строк" тут вообще нужна. На мой взгляд тут явная попытка использовать строки "не по назначению".

ourlive
Offline
Зарегистрирован: 26.05.2012

leshak, ваш примет, как и мой может печатать только одну строку. Например функция индикации времени отправляет на печать (т.е. в буфер) свои данные, через пол секунды срабатывает условие вывода данных в функции измерения температуры. соответственно буфер ещё не опусташен, печать ещё не завершена (строка времени это минимум 5 символов). В итоге данные в буфере должны накапливаться(координаты печати1+шрифт1+текст1+координаты2+шрифт2+текст2+и т.д.) и по мере печати на экран напечатанные символы должны исключаться из буфера высвобождая для будущих данных.

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

Но тут возникает вопрос. как происходит высвобождение памяти?

пример: берём строку

String text="123";

сейчас трока занимает 3 байта. если к строке прибавить.

text=text+"456";

будет 6 байт, далее

text="0";

в итоге реально на железе после укорочения строки память высвободилась?

вероятно я глупые вопросы задаю, ну уж какие есть..

да, и аналогично для структуры

typedef struct TText{
  byte x;
  byte y;
  byte fontSize;
  String text;
 };

TText text[];

боюсь, что такой подход с накоплением данных в неограниченном буфере может захавать всю немногочисленную память дуинки

Borland
Offline
Зарегистрирован: 17.05.2012

Согласен со всеми но выскажу свою точку зрения

Класс String замечательная штука, но мне кажется для Дуин и прочих контроллеров излишняя

Как только вы говорите  String text="123"; то вы сжираете у контроллера память ну думаю минимум килобайт на подгрузку всех методов класса String обьявленных в WString.cpp

как только вы создаете второй String на 3 байта, то вы сжираете навскидку байт 200 на обьявление обьекта класса String

для Меги это в общем то не проблема, но для контроллеров с небольшим RAM я бы пользоволся обычным string с использованием обычных strcpy  strcat и тд , а буфер можно иметь один, ну два, ну три

 для промежуточных вычислений складываний и переписываний строк.

ourlive
Offline
Зарегистрирован: 26.05.2012
void clear_byte (byte x, byte y, byte s) { // стирает один символ курсор (х,у), размер шрифта (s)
  tft.setTextColor(ST7735_BLACK);
  tft.setTextSize(s); 
  tft.setCursor(x, y); 
  tft.println(char(218)); 
  }

void my_print_tft (byte x, byte y, String text, byte s) { //х,у - курсор; text - текст на печать, s - size
  // при адекватных параметрах отправить данные на печать и наличии места в буфере
  if (x>0 && x<128 && y>0 && y<160 && s>0 && s<5 && text!="" && my_text_print.length()+text.length()<256) {
      if (my_text_print!="") {
      my_text_print=my_text_print+String(char(218))+String(char(x))+String(char(y))+String(char(s))+text;} // если печать уже идёт
      else {my_text_print=String(char(1))+String(char(218))+String(char(x))+String(char(y))+String(char(s))+text;} // если буфер пуст (курсто установить на единицу)
  }}
  
void my_print_tft_t() {
  if (my_text_print!="") {
  if (time>=timer_my_print) {
    timer_my_print=time+100000; // печатать 10 символов в секунду
    label1:
  byte l=my_text_print.length(); // выделение параметров из строки
  byte s= my_text_print.charAt(4);
  byte cur= my_text_print.charAt(0);
  word x= my_text_print.charAt(2)+(cur-1)*s*6;
  byte y= my_text_print.charAt(3);
  char my_char=my_text_print.charAt(5);
  //если достигнут ключевой символ стереть старые координаты и записать новые
  if (my_char==char(218) &&l>6) {
  my_text_print=String(char(1))+my_text_print.substring(5); 
  goto label1;}
    label2:
  if (x>122 && s==1) {x-=122;y+=8*s;goto label2;} // при выходе строки за экран перейти на следующую------------кривое исполнение
  if (x>116 && s==2) {x-=116;y+=8*s;goto label2;}
  if (x>110 && s==3) {x-=110;y+=8*s;goto label2;}
  if (x>104 && s==4) {x-=104;y+=8*s;goto label2;}
  if (y<153) { // при выходе строки за экран, игнорировать печать
  clear_byte(x,y,s);  //очистка позиции и печать символа
  tft.setTextColor(ST7735_WHITE);
  tft.setTextSize(s);
  tft.setCursor(x, y);
  tft.print(my_char); }
  my_text_print=my_text_print.substring(0,5)+my_text_print.substring(6); //удаление из буфера напечатанного символа
  cur++;
  my_text_print.setCharAt(0,char(cur));
  if (l<7) {my_text_print="";}// напечатан последний символ
}}}

во, вот этот блок работает как задумывалось. смысла оптимизировать его уже нет. правильнее в будущем переписать библиотеку на экран, что бы была возможность печати по аналогичному методу