Си-строки (массивы символов)
В прошлом уроке мы разобрали динамические String-строки, а сейчас настало время “простых” статических строк – такая строка представляет собой массив символов типа char
(char array) и для неё работает такой же синтаксис, как и для остальных массивов (урок про массивы). Конец строки определяется нулевым символом \0
, за это такой тип строк называют null-terminated string. Также это стандартные строки языка Си и поэтому они называются cstring.
Си-строки
Основное отличие таких строк от String-строк: это обычный массив, размер которого известен заранее и не меняется в процессе работы. Можно объявить как массив и посимвольно задать текст:
char str[] = {'h', 'e', 'l', 'l', 'o', 'char str[] = {'h', 'e', 'l', 'l', 'o', '\0'}; // с нулевым символом на конце'}; // с нулевым символом на конце
Такой вариант записи максимально неудобный, поэтому строки в C/C++ можно задавать просто текстом в двойных кавычках (компилятор сам посчитает размер массива):
char str[] = "hello";
Любой написанный в двойных кавычках текст:
- Воспринимается компилятором как массив символов, а точнее – указателем на первый символ.
- Имеет тип
const char*
. - Хранится в оперативной памяти микроконтроллера.
- Компилятор автоматически добавляет в конец строки нулевой символ
'\0'
.
Полученный выше массив содержит 6 символов: 5 на слово hello и 1 на завершающий символ.
Си-строки можно складывать следующим образом:
char str[] = "Hello" ", " "World!";
И даже переносить сложение:
char str[] = "Hello" ", " "World!";
Сложение происходит на этапе компиляции, то есть в машинном коде это будет одна большая строка.
У нас есть доступ к символам строки, как к элементам массива:
char str[] = "hello"; str[0] = 'a'; // здесь str == "aello"
В отличие от String-строк, Си-строки:
char str[] = "hello"; char str2[] = "world"; str += str2; // НЕЛЬЗЯ складывать str = "text"; // НЕЛЬЗЯ присваивать после инициализации if (str == str2); // НЕЛЬЗЯ сравнивать
Для этого существуют специальные функции, о которых мы поговорим ниже.
Длина Си-строки
Для определения длины текста можно использовать оператор strlen()
, который возвращает количество символов в строке. Сравним его работу с оператором sizeof()
:
char str[100] = "World"; sizeof(str); // вернёт 100 strlen(str); // вернёт 5
Здесь оператор sizeof()
вернул количество байт, занимаемое массивом. Массив я специально объявил с размером бОльшим, чем содержащийся в нём текст. А вот оператор strlen()
посчитал и вернул количество символов, которые идут с начала массива и до нулевого символа в конце текста без его учёта. А вот такой будет результат при инициализации без указания размера массива:
char text[] = "Hello"; strlen(text); // вернёт 5 ("читаемых" символов) sizeof(text); // вернёт 6 (байт)
Массив строк
Можно создать один массив с несколькими строками и обращаться к ним по индексу, фактически это будет двухмерный массив. Выглядит следующим образом:
// объявляем массив строк const char *names[] = { "Period", // 0 "Work", // 1 "Stop", // 2 }; // выводим третий элемент Serial.println(names[2]); // выведет Stop
Таким образом удобно паковать строки для создания текстовых меню и прочего. Единственный большой минус – весь этот текст висит в оперативной памяти мёртвым грузом. Можно сохранить его во Flash – программной памяти (PROGMEM), об этом читайте в отдельном уроке.
F() macro
Есть готовый инструмент, позволяющий очень просто хранить статичные текстовые данные во Flash памяти для передачи в функции, которые могут с таким текстом работать. Этот способ хорош для последующего складывания строк или вывода текста в монитор порта или на дисплей. Достаточно обернуть строку в макрос F()
:
Serial.println(F("Hello, World!"));
Строка “Hello, World!” будет записана во Flash память и не займёт 14 байт (13 + нулевой) в оперативной.
Инструменты для Си-строк
Есть готовые функции, позволяющие конвертировать различные типы данных в строки:
itoa(int_data, str, base)
– записывает переменную типаint
int_data в строку str с базисом* base.ltoa (long_data, str, base)
– записывает переменную типаlong
long_data в строку str с базисом* base.ultoa (unsigned_long_data, str, base)
– записывает переменную типаunsigned long
unsigned_long_data в строку str с базисом* base.dtostrf(float_data, width, dec, str)
– записывает переменную типаfloat
float_data в строку str с количеством символов width и знаков после запятой dec.
* Примечание: base – основание системы счисления, тут всё как при выводе в Serial:
DEC
– десятичнаяBIN
– двоичнаяOCT
– восьмеричнаяHEX
– шестнадцатеричная
float x = 12.123; char str[10] = ""; dtostrf(x, 4, 2, str); // тут str == "12.12" int y = 123; itoa(y, str, DEC); // тут str == "123"
И наоборот, можно преобразовывать строки в численные данные, функция вернёт результат:
atoi(str)
– преобразование str вint
atol(str)
– преобразование str вlong
atof(str)
– преобразование str вfloat
float x; char str[10] = "12.345"; x = atof(str); // тут x == "12.345"
Массивы символов не так просты, как кажутся: их возможности сильно расширяет стандартная библиотека cstring. Использование всех доступных фишек по работе с массивами символов позволяет полностью избавить свой код от тяжёлых String-строк и сделать его легче, быстрее и оптимальнее. Подробно обо всех инструментах можно почитать в официальной документации. Очень интересный пример с манипуляцией этими инструментами можно посмотреть здесь. А мы вкратце рассмотрим самые полезные.
Важный момент: библиотека работает со строками как с указателями, и многие функции возвращают как результат именно указатель. Как это понимать, если вы не читали урок про указатели и/или тема слишком сложная? Указатель – первый символ в строке, работа со строкой начнётся с него. Последним символом является нулевой символ, и для программы строка существует именно в этом диапазоне. Если какая-то функция возвращает указатель на конкретный символ в строке – по сути она возвращает часть строки, начиная с этого символа и до конца строки. Например, мы искали символ ,
в строке "Hello, world!"
. Программа вернёт нам указатель на эту запятую, по сути это будет кусочек той же самой строки, содержащий ", world!"
. Просто “начало” строки сместится.
strcpy(str1, str2)
|
Копирует str2 в str1, включая NULL. Так как мы передаём указатель, цель и место назначения можно “подвинуть”: char str1[] = "hello world"; char str2[] = "goodbye"; // вставим bye после hello strcpy(str1 + 6, str2 + 4); // тут str1 == hello bye |
strncpy(str1, str2, num)
|
Копирует num символов из начала str2 в начало str1 char str1[] = "hello world"; char str2[] = "goodbye"; // вставим good после hello strncpy(str1 + 6, str2, 4); // тут str1 == hello goodd // вторая d осталась после "world" |
strcat(str1, str2)
|
Прибавляет str2 к str1, при этом str1 должна иметь достаточный для этого размер. NULL первой строки заменяется на первый символ из str2
char str1[15] = "hello "; char str2[] = "world"; strcat(str1, str2); // здесь str1 - "hello world" |
strncat(str1, str2, num)
|
Добавляет num символов из начала str2 к концу str1
|
strcmp(str1, str2)
|
Сравнивает str1 и str2. Возвращает 0, если строки одинаковы. Больше нуля, если str1 > str2. Меньше нуля, если str1 < str2.
|
strncmp(str1, str2, num)
|
Сравнивает первые num символов из строк str1 и str2. Возвращает 0, если эти участки одинаковы.
|
strchr(str, symb)
|
Ищет символ symb в строке str и возвращает указатель на первое совпадение.
|
strrchr(str, symb)
|
Ищет символ symb в строке str и возвращает указатель на последнее совпадение.
|
strcspn(str1, str2)
|
Выполняет поиск первого вхождения в строку str1 любого из символов строки str2 и возвращает количество символов до найденного первого вхождения.
|
strpbrk(str1, str2)
|
Выполняет поиск первого вхождения в строку str1 любого из символов строки str2 и возвращает указатель на найденный символ.
|
strspn(str1, str2)
|
Поиск символов строки str2 в строке str1. Возвращает длину начального участка строки str1, который состоит только из символов строки str2.
|
strstr(str1, str2)
|
Функция ищет первое вхождение подстроки str2 в строке str1.
|
strtok(str, delim)
|
Ищет символы-разделители delim в строке str, возвращает указатель на последний найденный. Как использовать – смотри тут.
|
strlen(str)
|
Возвращает длину строки str без учёта нулевого символа.
|
Библиотека
У меня есть библиотека для работы с Си-строками: их преобразования и парсинга на блоки данных. Некоторые инструменты реализованы гораздо эффективнее, чем в стандартной строковой библиотеке. Библиотека называется GParser, документацию и примеры смотрите на GitHub.
Видео
Полезные страницы
- Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
- Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
- Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
- Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
- Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
- Поддержать автора за работу над уроками
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту (alex@alexgyver.ru)