Строки


Строка (String), как понятно из названия, это совокупность символов. По сути строка это одномерный массив типа данных char, про массивы мы уже недавно говорили и вы должны понять, о чём идёт речь. Как и в случае с массивом символов, к каждому элементу строки можно обратиться при помощи квадратных скобок. Основным отличием строки от массива символов является тот факт, что строка – динамический массив, у которого не нужно указывать размер. Также строка является не просто типом данных, а объектом, объектом очень мощного класса String.

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

  String string0 = "Hello String";              // заполняем словами в кавычках
  String string1 = String("lol ") + String("kek");  // сумма двух строк
  String string2 = String('a');                 // строка из символа в одинарных кавычках
  String string3 = String("This is string");    // конвертируем строку в String
  String string4 = String(string3 + " more");   // складываем строку string3 с текстом в кавычках
  String string5 = String(13);                  // конвертируем из числа в String
  String string6 = String(20, DEC);             // конвертируем из числа с указанием базиса (десятичный)
  String string7 = String(45, HEX);             // конвертируем из числа с указанием базиса (16-ричный)
  String string8 = String(255, BIN);            // конвертируем из числа с указанием базиса (двоичный)
  String string9 = String(5.698, 3);            // из float с указанием количества знаков после запятой (тут 3)

  // строки можно складывать друг с другом
  String string10 = string0 + string1;          // string10 равна Hello Stringlol kek

  // можно формировать название из кусочков, например для работы с файлами. Даже из дефайнов
#define NAME "speed"
#define TYPE "-log"
#define EXT ".txt"

  // при сложении достаточно указать String 1 раз для первой строки
  String filename = String(NAME) + TYPE + EXT;  // filename будет равна speed-log.txt

  // доступ к элементу строки работает по такому же механизму, как массив
  string1[0] = "a";
  // теперь вместо Hello String у нас aello String

Как вы могли заметить, строки можно объявлять большим количеством способов, а также буквально складывать строки, как числа, оператором +. Я уже говорил, что строки являются объектами класса String, и у этого класса есть огромное количество удобных методов по работе со строками, далее мы их все рассмотрим с некоторыми примерами. Но для начала запомните вот что: строки – очень тяжёлый инструмент, очень медленный и занимающий кучу памяти: уже просто само наличие строк (от одной и более) в прошивке занимает +5% Flash памяти, т.к. подключается сам “инструмент” – класс String. Для небольших проектов это не страшно, памяти всегда будет навалом.

Инструменты для String


Итак, методы для работы со строками. Как и все методы, они применяются к своим объектам (к строкам) через точку. В рассмотренных ниже примерах строка называется myString.

charAt()

myString.charAt(index);

Возвращает элемент строки myString под номером index. Аналог – myString[index];

setCharAt()

myString.setCharAt(index, val);

Записывает в строку myString символ val на позицию index. Аналог – myString[index] = val;

compareTo()
myString.compareTo(myString2);
  • Возвращает отрицательное число, если myString идёт до myString2
  • Возвращает положительное число, если myString идёт после myString2
  • Возвращает 0, если строки одинаковы
concat()

myString.concat(value);

Присоединяет value к строке (value может иметь любой численный тип данных). Возвращает true при успешном выполнении, false при ошибке. Аналог – сложение, myString + value;

endsWith()

myString.endsWith(myString2);

Проверяет, заканчивается ли myString символами из myString2. В случае совпадения возвращает true

startsWith()

myString.startsWith(myString2);

Проверяет, начинается ли myString символами из myString2. В случае совпадения возвращает true

equals()

myString.equals(myString2);

Возвращает true, если myString совпадает с myString2. Регистр букв важен

equalsIgnoreCase()

myString.equalsIgnoreCase(myString2);

Возвращает true, если myString совпадает с myString2. Регистр букв неважен

indexOf()

myString.indexOf(val);
myString.indexOf(val, from);

Ищет и возвращает номер (позицию) значения val в строке, ищет слева направо, возвращает номер первого символа в совпадении. val может быть char или String, то есть ищем в строке другую строку или символ. Можно искать, начиная с позиции from. В случае, когда не может найти val в строке, возвращает -1.

lastIndexOf()

myString.lastIndexOf(val);
myString.lastIndexOf(val, from);

Ищет и возвращает номер (позицию) значения val в строке, ищет справа налево, возвращает номер последнего символа в совпадении. val может быть char или String, то есть ищем в строке другую строку или символ. Можно искать, начиная с позиции from. В случае, когда не может найти val в строке, возвращает -1.

length()

myString.length();

Возвращает длину строки в количестве символов

remove()

myString.remove(index);
myString.remove(index, count);

Удаляет из строки символы, начиная с index и до конца, либо до указанного count

replace()

myString.replace(substring1, substring2);

В строке myString заменяет последовательность символов substring1 на substring2.

String myString = "lol kek 4eburek";

// заменить чебурек на пельмень
myString.replace("4eburek", "pelmen");
reserve()

myString.reserve(size);

Зарезервировать в памяти количество байт size для работы со строкой

c_str()

myString.c_str();

Преобразовывает строку в “СИ” формат (null-terminated string) и возвращает указатель на полученную строку

trim()

myString.trim();

Удаляет пробелы из начала и конца строки. Действует со строкой, к которой применяется

substring()

myString.substring(from);
myString.substring(from, to);

Возвращает кусок строки, содержащейся в myString начиная с позиции from и до конца, либо до позиции to

String myString = "lol kek 4eburek";
String chebur = myString.substring(8);
// строка chebur содержит в себе "4eburek"
toCharArray()

myString.toCharArray(buf, len);

Раскидывает строку в массив – буфер buf (типа char []) с начала и до длины len

getBytes()

myString.getBytes(buf, len);

Копирует указанное количество символов len (вплоть до unsigned int) в буфер buf (byte [])

toFloat()

myString.toFloat();

Возвращает содержимое строки в тип данных float

toDouble()

myString.toDouble();

Возвращает содержимое строки в тип данных double

toInt()

myString.toInt();

Возвращает содержимое строки в тип данных int

String myString = "10500";
int val = myString.toInt();
// val теперь 10500
toLowerCase()

myString.toLowerCase();

Переводит все символы в нижний регистр. Было ААААА – станет ааааа

toUpperCase()

myString.toUpperCase();

Переводит все символы в верхний регистр. Было ааааа – станет ААААА

Длина строки


Небольшой комментарий по поводу длины строки: мы можем узнать длину строки двумя способами, при помощи оператора sizeof() и метода length(). Давайте разберём отличия между ними:

String textString = "Hello";
sizeof(textString);   // вернёт 6
textString.length();  // вернёт 5

Оператор sizeof вернёт размер строки в байтах. Строка содержит “нулевой символ” на конце, этот символ тоже весит один байт, соответственно оператор вернёт число 6. Метод length возвращает длину строки в количестве символов, не считая завершающий нулевой, поэтому результат будет 5.

F() macro


Строки являются очень тяжёлым с точки зрения использования памяти инструментом, ведь текст в строке хранится в оперативной памяти микроконтроллера, а её не так уж и много. Есть готовый инструмент, позволяющий удобно хранить текстовые данные во Flash памяти микроконтроллера. Этот способ хорош для вывода фиксированных текстовых данных, например, в монитор порта:

Serial.println(F("Hello, World!"));

Строка “Hello, World!” будет записана во Flash память и не займёт 14 байт (13 + нулевой) в оперативной.

Массивы символов


Массивы символов, они же char array, являются ещё одним способом работы с текстовыми данными. Этот вариант имеет гораздо меньше возможностей по работе с текстом, но зато занимает меньше места в памяти (не используется элемент String) и работает значительно быстрее. К массиву символов применяются те же правила, какие работают для обычных массивов. Рассмотрим пример, в котором объявим массив символов, поработаем с ним, и выведем в порт:

// объявить массив текста длиной 6 символов
// и задать текст
char helloArray[] = "Hello!";

// объявить массив текста длиной 100 символов
// и задать в его начало текст
char textArray[100] = "World";

Serial.println(helloArray); // выведет Hello!
Serial.println(textArray);  // выведет World

textArray[0] = 'L';         // заменим элемент
Serial.println(textArray);  // выведет Lorld

В отличие от строк, массивы символов нельзя:

helloArray += textArray;  // складывать
textArray = "new text";   // присваивать ТЕКСТ после инициализации

Длина строки


Для определения длины текста можно использовать оператор strlen, который возвращает количество символов в массиве. Сравним его работу с оператором sizeof:

char textArray[100] = "World";
sizeof(textArray);  // вернёт 100
strlen(textArray);  // вернёт 5

Здесь оператор sizeof вернул количество байт, занимаемое массивом. Массив я специально объявил с размером бОльшим, чем содержащийся в нём текст. А вот оператор strlen посчитал и вернул количество символов, которые идут с начала массива и до нулевого символа в конце текста.

Массив строк


Очень мощной фишкой массивов символов является возможность создать один массив с несколькими строками, и обращаться к ним по номеру. Выглядит это следующим образом:

// объявляем массив строк
const char *names[]  = {
  "Period",   // 0
  "Work",     // 1
  "Stop",     // 2
};

// выводим третий элемент
Serial.println(names[2]); // выведет Stop

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

Экономия памяти


“Строки” в массиве строк тоже хранятся в оперативной памяти, что не очень здорово. Ещё больше не здорово то, что применить рассмотренный F() macro к ним нельзя, потому что фактически это не строки. То есть вот такой код приведёт к ошибке:

const char *names[]  = {
  F("Period"),   // 0
  F("Work"),     // 1
  F("Stop"),     // 2
};

Как же быть? Массив строк можно сохранить в PROGMEM, программной памяти микроконтроллера, то есть во Flash. Вот такую конструкцию можно использовать как шаблон:

// объявляем наши "строки"
const char array_1[] PROGMEM = "Period";
const char array_2[] PROGMEM = "Work";
const char array_3[] PROGMEM = "Stop";

// объявляем таблицу ссылок
const char* const names[] PROGMEM = {
  array_1, array_2, array_3,
};

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

  char arrayBuf[10];  // создаём буфер

  // копируем в arrayBuf при помощи встроенного strcpy_P
  strcpy_P(arrayBuf, (char*)pgm_read_word(&(names[1])));

  Serial.println(arrayBuf); // выведет Work
}

Да, сложно и громоздко, но при большом объёме текстовых данных это может спасти проект!

Важные страницы


  • Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
  • Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
  • Полная документация по языку Ардуино, все встроенные функции и макро, все доступные типы данных
  • Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
  • Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
Последнее обновление Август 13, 2019
2019-08-13T00:25:38+03:00