Освой Arduino играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

String

Строка в C
Строка в C++
08.StringConstructors
08.StringCaseChanges
StringCharacters
08.StringIndexOf
08.StringStartsWithEndsWith
Строковые функции языка C
Сконвертировать строку в int
Сконвертировать int в строку
Сконвертировать строку в массив символов

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

Для вывода в строке самих кавычек и некоторых других служебных символов используются экранирующий символ \. Например, \" используется для вывода кавычки, а \n для переноса на новую строку.


Serial.print("This\nis\na\ntest\n\nKitty said, \"How are you?\"\n");

Пока не проверял, но видел в сети вариант, как можно создать строку, которая содержит много текста с кавычками, при помощи магии, а именно конструкции R"=====( bla bla bla )====="; (для плат ESP8266).


const char MAIN_page[] = R"=====(
<!DOCTYPE html>
<html>
<head>
<title>ESP8266</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<style>
.imageDiv{
    padding: 4%;
 }
</style>
<body>
<div style="width:100%;">
<div style="width:50%;  margin: 0 auto;">
  <h1>ESP8266 Double quotes escape Demo</h1>
</div>
</div>
</body>
</html>
)=====";

Строка в C

В языке C строка должна завершаться нулевым символом ('\0'), поэтому при создании строки нужно указывать на один символ больше. Иногда компилятор может догадаться самостоятельно добавлять нулевой символ, но в некоторых случаях он не поможет.

Существует несколько способов объявить строку в виде символьного массива.


//char name[] = {'B', 'a', 'r', 's', 'i', 'k', '\0'};
//char name[7] = {'B', 'a', 'r', 's', 'i', 'k', '\0'};
//char name[] = {'B', 'a', 'r', 's', 'i', 'k'};
//char name[6] = {'B', 'a', 'r', 's', 'i', 'k'};
//char name[16] = {'B', 'a', 'r', 's', 'i', 'k'}; // массив с запасом

void setup() {
  // Попробуйте все варианты
  char name[] = {'B', 'a', 'r', 's', 'i', 'k', '\0'};
  Serial.begin(9600);
  Serial.print(name);
}

void loop() {}

Иногда массив строк записывают как указатель на символ (первый символ в массиве):


char *message = "Hello Kitty";

Строковые функции языка C

Существуют родные строковые функции самого языка C. Язык C++ также может их использовать в целях совместимости.

Длина строки (без завершающего нулевого символа) вычисляется при помощи функции strlen() (string length).


char name[7] = {'B', 'a', 'r', 's', 'i', 'k', '\0'};
Serial.println(strlen(name)); // 6

Функция strcpy (string copy) копирует одну строку в другую.


char cat[7] = {'B', 'a', 'r', 's', 'i', 'k', '\0'};
char copyCat[7];
strcpy(copyCat, cat);
Serial.println(copyCat);

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


char cat[7] = {'B', 'a', 'r', 's', 'i', 'k', '\0'};
char copyCat[4];
strncpy(copyCat, cat, 4);
Serial.println(copyCat); // Bars

"Кошачья" функция strcat (string concatenate) присоединяет одну строку к другой.


char cat[3] = {'t', 'y', '\0'};
char smallCat[7] = {'k', 'i', 't', '\0'};
strcat(smallCat, cat);
Serial.println(smallCat); // kitty

Функция strcmp() осуществляет лексикографическую проверку двух строк, оканчивающихся нулевыми символами, и возвращает целое число со следующим значением: меньше 0 - если первая строка меньше, чем вторая строка, 0 - если обе строки совпадают, больше 0 - если первая строка больше второй строки.

В примере сравним массив символов со строкой C++, так как имеется совместимость между ними, но можно сравнить и два строковых массива или две строки.


char cat[4] = {'c', 'a', 't', '\0'};

int result = strcmp(cat, "cat");
Serial.println(result);

Функция sprintf(), выполняет форматирование массивов символов.

Допустим, мы хотим вывести информацию на двух строках ЖК-дисплея:


char line1[17];
int tempC = 30;
sprint(line1, "Temp: %d C", tempC);

char line2[17];
int h = 12;
int m = 30;
int s = 5;
sprintf(line2, "Time: %2d:%02d:%02d", h, m, s);

Массив символов line1 — это строковый буфер, содержащий форматированный текст, который имеет ёмкость 17 символов, включая завершающий нулевой символ.

В первом параметре функции передаётся массив символов, в который должен быть записан результат. Следующий аргумент — строка формата, содержащая смесь простого текста, такого как "Temp:", и команд форматирования, например %d. В данном случае %d означает «десятичное целое со знаком». Остальные параметры будут подставлены в строку формата в порядке их следования на место команд форматирования.

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


Time: 12:30:05

Команда sprintf() не только подставила числа в нужные места, но и добавила ведущий ноль перед цифрой 5. В примере между символами : находятся команды форматирования трёх компонентов времени. Часам соответствует команда %2d, которая выводит двузначное десятичное число. Команды форматирования для минут и секунд немного отличаются (%02d). Эти команды также выводят двузначные десятичные числа, но добавляют ведущий ноль, если это необходимо. Однако имейте в виду, что этот приём предназначен для значений типа int.

На языке C можно воспользоваться функциями atoi() ( ASCII to int) и atol() (ASCII to long) для конвертации массива символов в int и long.


char x[10] = "450";
int result = atoi(x);

В C++ можно использовать toInt(). В классе Arduino Serial есть функция parseInt().

Строка в C++

Строка как отдельный тип относится уже к языку C++. Вам не нужно указывать массив отдельных символов, достаточно просто окружить строку двойными кавычками. У класса String есть несколько удобных функций для работы со строками.

Только помните, что класс String требует больше ресурсов, чем при работе с массивами символов. Проведём эксперимент. Напишем первый скетч с использованием String.


void setup() {
  Serial.begin(9600);
  String firstName = "Alexander";
  String lastName = "Klimov";
  String fullName = firstName + " " + lastName;
  Serial.println(fullName);
}

void loop() {}

При компиляции вывелось следующее: Sketch uses 3212 bytes (9%) of program storage space. Maximum is 32256 bytes. Global variables use 216 bytes (10%) of dynamic memory, leaving 1832 bytes for local variables. Maximum is 2048 bytes.

Перепишем пример.


void setup() {
  Serial.begin(9600);
  char myName[16] = "Alexander ";
  char lastName[] = "Klimov";
  strcat(myName, lastName);
  Serial.println(myName);
}
void loop() {}

На этот раз выводится Sketch uses 1618 bytes (5%) of program storage space. Maximum is 32256 bytes. Global variables use 210 bytes (10%) of dynamic memory, leaving 1838 bytes for local variables. Maximum is 2048 bytes.

Второй скетч использует 5% памяти вместо 9%, т.е. почти в два раза меньше. Для сложных проектов подобное использование строк может стать решающим фактором.

Функции из C могут работать со строками C++. Например, определим длину строки.


strlen("cat"); // вернёт 3

Сконвертировать строку в int


String val = "1234";
int result = val.toInt();

Сконвертировать int в строку

Обратная задача конвертации int в строку есть в примере 08.StringConstructors

08.StringConstructors

Пример File | Examples | 08.Strings | StringConstructors - манипуляции со строками: объединить две строки, конвертировать символ в строку, конвертировать число в строку в разных системах счисления.


void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  // send an intro:
  Serial.println("\n\nString Constructors:");
  Serial.println();
}

void loop() {
  // обычная строка
  String stringOne = "Hello String";
  Serial.println(stringOne);      // prints "Hello String"

  // конвертируем символ в строку:
  stringOne =  String('a');
  Serial.println(stringOne);       // prints "a"

  // конвертируем строку в объект String:
  String stringTwo =  String("This is a string");
  Serial.println(stringTwo);      // prints "This is a string"

  // соединяем две строки:
  stringOne =  String(stringTwo + " with more");
  // prints "This is a string with more":
  Serial.println(stringOne);

  // число в строку:
  stringOne =  String(13);
  Serial.println(stringOne);      // prints "13"

  // число строку, выбирая формат, например, десятичный:
  stringOne =  String(analogRead(A0), DEC);
  // prints "453" or whatever the value of analogRead(A0) is
  Serial.println(stringOne);

  // шестнадцатеричный:
  stringOne =  String(45, HEX);
  // prints "2d", which is the hexadecimal version of decimal 45:
  Serial.println(stringOne);

  // бинарный
  stringOne =  String(255, BIN);
  // prints "11111111" which is the binary value of 255
  Serial.println(stringOne);

  // большое число типа long:
  stringOne =  String(millis(), DEC);
  // prints "123456" or whatever the value of millis() is:
  Serial.println(stringOne);

  // число с плавающей точкой с тремя разрядами после запятой:
  stringOne = String(5.698, 3);
  Serial.println(stringOne);

  // с округлением до двух знаков после запятой:
  stringOne = String(5.698, 2);
  Serial.println(stringOne);

  // do nothing while true:
  while (true);

}

08.StringCaseChanges

Переводим строку в верхний или нижний регистр с помощью функций toUpperCase() и toLowerCase() в примере File | Examples | 08.Strings | StringCaseChanges.


void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  // send an intro:
  Serial.println("\n\nString case changes:");
  Serial.println();
}

void loop() {
  // toUpperCase() преобразует все символы в верхний регистр:
  String stringOne = "<html><head><body>";
  Serial.println(stringOne);
  stringOne.toUpperCase();
  Serial.println(stringOne);

  // toLowerCase() преобразует все символы в нижний регистр:
  String stringTwo = "</BODY></HTML>";
  Serial.println(stringTwo);
  stringTwo.toLowerCase();
  Serial.println(stringTwo);


  // do nothing while true:
  while (true);
}

StringCharacters

Функция charAt() позволяет узнать символ в указанной позиции строки. Функция setCharAt() позволяет заменить символ в указанной позиции.

Возьмём пример File | Examples | 08.Strings | StringCharacters и немного переделаем его немного.


void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
}

void loop() {
  String reportString = "Земля держится на 3 китах";
  Serial.println(reportString);

  // получим символ в позиции 33:
  char mostSignificantDigit = reportString.charAt(33);

  String message = "Символ в указанной позиции: ";
  Serial.println(message + mostSignificantDigit);

  Serial.println();

  // Меняем символ и на о
  reportString.setCharAt(38, 'о');
  Serial.println(reportString);

  // do nothing while true:
  while (true);
}

Этот пример показывает, что нужно быть осторожным при работе с символами, которые не входят в состав ANSI, например, русскими. Если вы посчитаете, то увидите, что символ '3' находится на 19 позиции. Старайтесь работать с английским текстом, чтобы избежать путаницы.

charAt

08.StringIndexOf

Пример показывает применение функций indexOf() и lastIndexOf(). Функции находят первое вхождение символа в строке с начала или конца строки. Также можно указать смещение, с которого следует начинать поиск вхождения.


void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  Serial.println("\n\nString indexOf() and lastIndexOf()  functions:");
  Serial.println();
}

void loop() {
  // indexOf() возвращает позицию (индекс) указанного символа
  String stringOne = "<HTML><HEAD><BODY>";
  int firstClosingBracket = stringOne.indexOf('>'); // ищем первую закрывающую угловую скобку
  Serial.println("The index of > in the string " + stringOne + " is " + firstClosingBracket);

  stringOne = "<HTML><HEAD><BODY>";
  int secondOpeningBracket = firstClosingBracket + 1;
  int secondClosingBracket = stringOne.indexOf('>', secondOpeningBracket); // вторая закрывающая скобка
  Serial.println("The index of  the second > in the string " + stringOne + " is " + secondClosingBracket);

  // можно также использовать indexOf() для поиска строк:
  stringOne = "<HTML><HEAD><BODY>";
  int bodyTag = stringOne.indexOf("<BODY>");
  Serial.println("The index of the body tag in the string " + stringOne + " is " + bodyTag);

  stringOne = "<UL><LI>item<LI>item<LI>item</UL>";
  int firstListItem = stringOne.indexOf("<LI>");
  int secondListItem = stringOne.indexOf("<LI>", firstListItem + 1);
  Serial.println("The index of the second list tag in the string " + stringOne + " is " + secondListItem);

  // lastIndexOf() находит последнее вхождение символа или строки
  int lastOpeningBracket = stringOne.lastIndexOf('<');
  Serial.println("The index of the last < in the string " + stringOne + " is " + lastOpeningBracket);

  int lastListItem  = stringOne.lastIndexOf("<LI>");
  Serial.println("The index of the last list tag in the string " + stringOne + " is " + lastListItem);


  // lastIndexOf() может искать строку:
  stringOne = "<p>Lorem ipsum dolor sit amet</p><p>Ipsem</p><p>Quod</p>";
  int lastParagraph = stringOne.lastIndexOf("<p");
  int secondLastGraf = stringOne.lastIndexOf("<p", lastParagraph - 1);
  Serial.println("The index of the second to last paragraph tag " + stringOne + " is " + secondLastGraf);

  // do nothing while true:
  while (true);
}

08.StringStartsWithEndsWith

Функции startsWith() и endsWith() позволяют узнать, начинается или заканчивается строка на заданный символ (подстроку).


void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  Serial.println("\n\nString startsWith() and endsWith():");
  Serial.println();
}

void loop() {
  // startsWith() проверят, начинается ли строка со слов HTTP/1.1:
  String stringOne = "HTTP/1.1 200 OK";
  Serial.println(stringOne);
  if (stringOne.startsWith("HTTP/1.1")) {
    Serial.println("Server's using http version 1.1");
  }

  // также можно использовать startsWith() с небольшим смещением:
  stringOne = "HTTP/1.1 200 OK";
  if (stringOne.startsWith("200 OK", 9)) {
    Serial.println("Got an OK from the server");
  }

  // endsWith() проверяет, заканчивается ли строка на заданный символ
  String sensorReading = "sensor = ";
  sensorReading += analogRead(A0);
  Serial.print(sensorReading);
  if (sensorReading.endsWith("0")) {
    Serial.println(". This reading is divisible by ten");
  } else {
    Serial.println(". This reading is not divisible by ten");
  }

  // do nothing while true:
  while (true);
}

Сконвертировать строку в массив символов

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


String str = "Hello Kitty"; 
 
// Length (with one extra character for the null terminator)
int str_len = str.length() + 1; 
 
// Prepare the character array (the buffer) 
char char_array[str_len];
 
// Copy it over 
str.toCharArray(char_array, str_len);

Дополнительная информация

Arduino Reference: String() - официальная документация

Реклама