Перейти к содержимому

Как вывести массив в строку c

  • автор:

Как в c++ вывести массив строк

Если я правильно понимаю, указатель выходит за границу массива. Как это можно исправить Если я точно указываю кол-во строк, программа работает исправно, но как обойтись без подсчёта строк?

Отслеживать
51.4k 86 86 золотых знаков 267 267 серебряных знаков 505 505 бронзовых знаков
задан 10 мая 2014 в 13:12
Николай135326 Николай135326
1 1 1 золотой знак 1 1 серебряный знак 1 1 бронзовый знак

@Николай135326 а почему вы считаете, что ваш массив должен заканчиваться элементом ‘\0’? Если вам нужна итерация по элементам без учета индексов, то для этого имеет смысл исмользовать контейнеры

10 мая 2014 в 13:21

пользуюсь c++ а как это сделать (если возможно, то не указывать количество строк — чтобы можно было добавить 1-2 строки и не переписывать параметры?)

10 мая 2014 в 13:28

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

10 мая 2014 в 13:32

У вас есть два выхода. Простой, нормальный: откажитесь от нативных массивов и строк, перейдите на стандартные контейнеры и std::string , они для новичков попроще будут. Сложный: прочитайте книжку по C и разберитесь с тем, что такое массив и C-style-строка. Я бы советовал начать с первого, и постепенно переходить ко второму.

10 мая 2014 в 14:16

Про пустую строку в качестве ограничителя размера массива Вам уже рассказал @insolor. Еще пара способов. Первый — это граница в виде пустого указателя, я бы сказал, в стиле параметров командной строки (вспомните main(int ac, char *av[]) < . (как раз для Вашего кода)): char *mas[] = ; // запись тривиальна Второй -- вычислить размер массива: int n = sizeof(mas) / sizeof(mas[0]); for (int i = 0; i < n; i++) puts(mas[i]); Понятно, что второй способ применим только в области видимости определения массива (т.е. с параметром не пойдет).

Как вывести массив строк в Си?

Понимаю, что что — то не правильно делаю, но мне кажется второй кусок кода логичным :

printf(«%s/n», *a) ; //вывести строку.

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

  • Вопрос задан более трёх лет назад
  • 5436 просмотров

2 комментария

Простой 2 комментария

Работа с массивами. Часть 1

Массивы Си

Доброго времени суток, сегодня мы поговорим о массивах, разберём некоторые тонкости заполнения массивов в Си. Тема несложная, думаю, легко разберётесь. Итак, давайте начнём. Заполнить целочисленный массив array_int[N] (N = 34) числами из диапазона [-83; 90] и массив array_double[N] дробными числами из диапазона [-2.38; 6.57]. Вывести массивы на экран в две строки. Вывести массивы в файл в два столбца в формате «array_int[индекс] = значение; array_double[индекс]= значение». Код:

#include stdio.h> #include stdlib.h> #define N 34 int main(void)  FILE*file; file = fopen("file.txt","w"); int array_int[N], i, j; for (i = 1; i = N; i++) array_int[i] = -83 +rand() % (90 + 83 + 1); printf("%d ", array_int[i]); > printf("\n"); double array_double[N],t; for (j = 1; j = N;) t = rand() % 8 - 2 + (rand() % 95 - 38) / 100.0; if ((t = 6.57 ) && (t >= -2.38))  array_double[j] = t; printf("%.2lf ", array_double[j]); j++; > > for (i = 1; i = N; i++) fprintf(file, "array_int[%d] = %d\t array_double[%d] = %.2lf \n ", i, array_int[i], i, array_double[i]); > fclose(file); return EXIT_SUCCESS; > 

Вывод:

Команд новых нет, но поговорим об изюминке во втором цикле (при заполнении дробного массива): Чтобы заполнить массив дробными «рандомными» числами из диапазона [-2.38; 6.57], необходимо сначала сгенерировать целые числа rand() % 8 — 2, где 8 сумма целого чисел у границы (-2.38 и 6.57), затем уже сгенерировать дробную часть (rand() % 95 — 38) / 100.0, где 95 сумма границ ( 38 + 57 ).
Мы используем условие if ((t = -2.38)), чтобы быть точно уверенным, что результат будет именно в этом диапазоне. А также в этом примере я показал, что у цикла for переменная j может быть изменена не только в объявлении цикла, но и посередине его.
Следующий: Заполнить массив array[20] степенями числа 2, начиная с минус пятой степени. Вывести массив на экран в строку. Код:

#include stdio.h> #include stdlib.h> #include math.h> #define N 20 int main(void)  int i,k; k = -5; double array[N]; for (i = 0; i  N; i++) array[i] = pow(2,k); k++; printf("%.3lf ", array[i]); > return EXIT_SUCCESS; > 

Как видите, ничего сложного. Если вы осилили предыдущие задачи в статьях, то это для вас будет так, пальцы размять =)
Едем дальше. Заполнить одномерный массив из 15 элементов случайными числами из диапазона [-30; 16]. Вывести на экран массив в строку. Найти первое с конца массива число, которое кратно 7, если таких чисел нет, то вывести сообщение о том, что чисел кратных семи не обнаружено. Использовать оператор break.

#include stdio.h> #include stdlib.h> #define N 15 int main(void)  int mas[N], i, k = 0; for (i = 0; i  N; i++) mas[i] = - 30 + rand() % (16 + 30 + 1); printf("%d ", mas[i]); > for (i = N - 1; i >= 0; i--) if (mas[i] % 7 == 0)  printf("\n%d", mas[i]); k = 1; break; > > if (k == 0) printf("не найдено"); return EXIT_SUCCESS; > 

Break — команда прерывания цикла и выход из него, запуск последующих операторов и инструкций. Алгоритм решения примера несложен.
И последний на сегодня: Заполнить одномерный массив из 20-ти элементов из файла input.txt и вывести его на экран. Изменить элементы массива, умножив четные элементы на 2, а нечетные — на 3. Вывести измененный массив на экран на новой строке.

#include stdio.h> #include stdlib.h> int main(void)  FILE*input; input = fopen("input.txt","r"); int mas[20],i; for (i = 1; i = 20; i++) fscanf(input, "%d", & mas[i]); printf("%d ", mas[i]); > printf("\n"); for (i = 1; i = 20; i++) if (mas[i] % 2 == 0) mas[i] *= 2; > else  mas[i] *= 3; > printf("%d ", mas[i]); > fclose(input); return EXIT_SUCCESS; > 

Снимок

Ввод/вывод:

Тут тоже ничего особенного, поэтому на этом и закончим.=) Вот и всё. Пишите вопросы, комментируйте, всем ответим. Все исходники без файлов txt.
Скачать исходники задачи — 1
Скачать исходники задачи — 2
Скачать исходники задачи — 3
Скачать исходники задачи — 4

Поделиться ссылкой:

Си-строки (массивы символов)

В прошлом уроке мы разобрали динамические String-строки в реализации Arduino, а сейчас настало время стандартных статических строк языка C/C++. Такая строка представляет собой массив символов типа char (char array) и для неё работает такой же синтаксис, как и для остальных массивов (урок про массивы). Конец строки определяется нулевым символом \0 (или целым число 0 ), за это такой тип строк называют null-terminated string: ноль на конце позволяет программе определять конец строки и её длину. Также это стандартные строки языка Си и поэтому называются cstring.

Текст в кавычках

Любой написанный в двойных кавычках текст «some text» :

  • Является строковой константойstring constant
  • Имеет тип данных const char* – то есть указывает на свой первый символ в памяти
  • Хранится и в программной, и в оперативной памяти микроконтроллера
  • Компилятор автоматически добавляет нулевой символ в конец строки ‘\0’ – то есть реальный размер строки всегда на 1 символ больше
  • Оптимизируется компилятором – об этом ниже

Оптимизация компилятором

Компилятор оптимизирует строковые константы, но не во всех случаях. Если создать несколько строк как массивы (которые можно изменять) и присвоить им одинаковые строки, то они займут место в памяти как разные строки, т.е. столько, сколько в них суммарно символов:

char s1[] = "hello"; char s2[] = "hello";

Если создать несколько одинаковых строк как указатели – то компилятор их оптимизирует и они займут место в памяти как одна строка!

const char* s1 = "hello"; const char* s2 = "hello";

Если при выводе в Serial или передаче в другие функции мы используем одинаковые строки, то они также будут оптимизироваться и занимать место как одна строка:

Serial.println("hello"); lcd.print("hello"); String s("hello");

В то же время F() – строки (подробнее в уроке про PROGMEM) не оптимизируются компилятором и занимают в программной памяти каждая своё место:

Serial.println(F("hello")); lcd.print(F("hello"));

Сложение

Строковые константы можно складывать через пробелы:

char str[] = "Hello" ", " "World!";

Сложение происходит на этапе компиляции, то есть в скомпилированной программе это будет одна общая строка.

Перенос строк (в программе)

Длинную строку можно переносить для удобства чтения и редактирования программы. Есть два способа:

Первый способ – работает как сложение строк в предыдущей главе. Каждая строка в своих кавычках пишется с новой строки:

char str[] = "Hello" ", " "World!";

Второй способ – использование символа обратный слэш \ для переноса строки. Кавычки в этом случае нужны только в начале и конце:

char str[] = "Hello\ , \ World!";

Примечание: результирующий текст в переменной str в обоих случаях не имеет переносов, то есть в обоих примерах получится строка «Hello, World!» .

Перенос строк (текст)

Для человека текст с новой строки – это текст с новой строки. Чтобы перенести текст на новую строку, мы нажимаем на клавишу Enter на клавиатуре. В то же время текст в текстовых файлах не хранится в разных “строках”, он лежит в памяти одной длинной строкой. Когда мы открываем файл, компьютер читает текст и ищет в нём специальные невидимые символы, которые называются управляющими символами. Одним из таких символов является перенос строки – \n , именно его добавляет клавиша Enter. Чтобы компьютер при выводе строки перенёс её – нужно добавить этот символ в текст. В программе мы будем видеть этот символ, а вот в результирующем тексте он автоматически превратится в перенос строки. Примеры (без переноса в программе и с переносом двумя способами):

char str1[] = "Строка1\nСтрока2\nСтрока3"; char str2[] = "Строка1\ \nСтрока2\ \nСтрока3"; char str3[] = "Строка1\n" "Строка2\n" "Строка3";

Во всех трёх случаях получится текст

Строка1 Строка2 Строка3

Примечание: ставить символ переноса строки можно как в начале новой строки (см. str2 ), так и в конце предыдущей (см. str3 ).

Кавычки внутри строки, экранирование

На практике довольно часто бывает нужно иметь строку, которая содержит символы двойных кавычек, например для вёрстки html:

Строка ограничивается символами двойные кавычки » : с них начинается и заканчивается. В программе не может быть просто отдельно стоящих двойных кавычек » – это приведёт к ошибке компиляции, так как они являются частью синтаксиса, который обязывает использовать их «парами» . Соответственно нельзя просто так взять и задать строку, содержащую этот символ внутри себя:

// приведёт к ошибке компиляции char str1[] = "Символ " кавычка"; // приведёт к ошибке компиляции char str2[] = "Этот "текст" в кавычках"; // скомпилируется, получится Этот текст в кавычках (сложение трёх строк) char str3[] = "Этот ""текст"" в кавычках";

Эту проблему можно решить двумя способами: экранированием и использованием инструмента компилятора raw string literal (С++ 11). Экранирование кавычек во многих языках программирования осуществляется при помощи обратного слэша \ . Таким образом просто кавычки » – это оператор, часть синтаксиса языка, а вот так – \» – это печатный символ кавычек, который может входить в состав строки:

// скомпилируется, получится Этот "текст" в кавычках char str4[] = "Этот \"текст\" в кавычках";

“Сырые” строки

“Сырые” строки – очень удобный инструмент компилятора, позволяющий задать любой текст просто в виде текста, включая кавычки и переносы строк без дополнительного экранирования. Синтаксис следующий R»(ваш текст)» или R»метка(ваш текст)метка» , где метка – любой текст длиной до 16 символов без пробелов, должен быть одинаковым в начале и конце. Нужна для того, чтобы компилятор мог корректно определить конец сырой строки, если внутри самой строки есть )» . Например строка R»()» приведёт к ошибке, т.к. компилятор решит что она закончилась после слова func ! Добавим метку R»raw()raw» и компилятор без ошибки найдёт конец сырой вставки. Примеры:

// вывод: текст "с кавычками" - удобно Serial.println(R"(текст "с кавычками" - удобно)"); char str1[] = R"(текст с переносами строки)"; Serial.println(str1); // метку rawliteral часто можно встретить в примерах для esp8266/32. char str2[] = R"rawliteral( )rawliteral"; Serial.println(str2);

Примечание: перенос строки внутри экранированной строки в программе станет переносом строки в итоговой строке в переменной!

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

Объявление как массив

Основное отличие таких строк от String -строк: это обычный массив, размер которого известен заранее и не меняется в процессе работы. Можно объявить строку как массив и посимвольно задать текст:

char str[] = ; // с нулевым символом на конце

Такой вариант записи не очень удобный, поэтому строки в C/C++ можно задавать просто текстом в двойных кавычках – компилятор сам посчитает размер массива:

char str[] = "hello";

Полученный выше массив содержит 6 символов: 5 на слово hello и 1 на завершающий символ. Текст в данном массиве можно изменять в процессе работы программы, потому что с точки зрения программы мы создали обычный массив и заполнили его буквами. Изменим первую букву на прописную: str[0] = ‘H’; . Выведем в монитор порта:

Serial.println(str);

Serial умеет работать с такими данными и с радостью их выведет.

Объявление как указатель

Также строку можно объявить как указатель на const char* – то есть сам текст в кавычках хранится где то в программе, а мы получаем на него “ссылку”:

const char* str = "hello";

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

Serial.println(str);

Примечание: можно объявить и как char* str = «hello»; и пользоваться дальше точно так же как массивом, но компилятор выдаст предупреждение что строковая константа (текст в кавычках) приравнивается к неконстантному типу.

Массив строк

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

// объявляем массив строк const char* names[] = < "Period", // 0 "Work", // 1 "Stop", // 2 >; // выводим третий элемент Serial.println(names[2]); // выведет Stop

Таким образом удобно паковать строки для создания текстовых меню и прочего. Единственный большой минус – весь этот текст висит в оперативной памяти мёртвым грузом. Можно сохранить его во Flash – программной памяти (PROGMEM), об этом читайте в отдельном уроке.

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

char arr[к-во строк][макс. длина];

По сути это будет двухмерный массив. Копирование другой строки в массив может выглядеть так: strcpy(arr[0], str); . Об этом читайте ниже.

Длина строки

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

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

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

char text[] = "Hello"; strlen(text); // вернёт 5 ("читаемых" символов) sizeof(text); // вернёт 6 (байт)

Отличия от String

В отличие от String-строк, Си-строки:

char str[] = "hello"; char str2[] = "world"; str += str2; // НЕЛЬЗЯ складывать str = "text"; // НЕЛЬЗЯ присваивать после инициализации if (str == str2); // НЕЛЬЗЯ сравнивать

Для этого существуют специальные функции, о которых мы поговорим ниже.

Оптимизация памяти

Как я писал выше – “текст в кавычках” хранится и в памяти программы, и в оперативной памяти, то есть после запуска микроконтроллера строка загружается в оперативную память, и уже там мы имеем к ней доступ. Как правило, объём программной памяти микроконтроллера в несколько раз больше, чем оперативной. Есть несколько возможностей хранения строк только в программной памяти, об этом очень подробно поговорим в уроке про PROGMEM.

Инструменты для Си-строк

Массивы символов не так просты, как кажутся: их возможности сильно расширяет стандартная библиотека cstring. Использование всех доступных фишек по работе с массивами символов позволяет полностью избавить свой код от тяжёлых String-строк и сделать его легче, быстрее и оптимальнее. Подробно обо всех инструментах можно почитать в официальной документации. Очень интересный пример с манипуляцией этими инструментами можно посмотреть здесь. А мы вкратце рассмотрим самые полезные.

Конвертирование

Есть готовые функции, позволяющие конвертировать различные типы данных в строки:

  • itoa(int_data, str, base) – записывает переменную типа int int_data в строку str с базисом* base.
  • utoa(uint_data, str, base) – записывает переменную типа unsigned int uint_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"

Внимание! Функции конвертирования, работающие с типом float, являются очень тяжёлыми: их “подключение” занимает ~2 кБ Flash памяти!! Максимально избегайте их применения в крупном проекте. Для преобразования можно сделать свою функцию, практически готовые варианты для всех типов данных можно найти в стандартной ардуиновской Print.cpp (ссылка на файл на гитхабе Arduino).

Работа с байтовым буфером

Очень часто в реальных задачах встречается ситуация, когда текстовые данные приходят в виде массива byte : по какому-нибудь каналу связи (MQTT, UDP, Bluetooth…), при чтении из файлов и так далее. Например приём по MQTT во многих библиотеках выглядит так:

void callback(byte* payload, uint16_t len)

Пришёл поток байтов известной длины. Что с ними делать, если это текст и нам в программе он нужен как строка? Во многих примерах в Интернете предлагают преобразовать данные в String , просто как String s = (char*)payload . Делать так категорически нельзя, если переданный текст не оканчивается нулевым символом, а в большинстве случаев это как раз так. Дело в том, что свободная оперативная память во время работы микроконтроллера содержит не нули, а фактически случайные значения, оставшиеся от выгруженных переменных в разных местах программы. И если у нас приходит массив, который не оканчивается нулём, то в памяти после него тоже не обязан быть ноль, и при преобразовании в строку пойдёт вся память по порядку, пока не встретится ноль. Простой пример:

char str0[] = ; char str1[] = ; char str2[] = ; Serial.println(str0); // abc Serial.println(str1); // defabc Serial.println(str2); // ghidefabc

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

Через String

Если нужна String-строка, то нужно её создать, зарезервировать место под текст (чтобы избежать лишних аллокаций) и переписать в неё данные. К сожалению в реализации Arduino функция для переписывания массива в строку сделана приватной, поэтому придётся просто прибавить данные в цикле. Этот способ делает в два раза больше действий, чем могло бы быть, но для String-строки это единственный способ:

void callback(byte* payload, uint16_t len)

Через cstring

Здесь алгоритм будет такой: создать массив char с запасом под нулевой символ, переписать в него данные и нулевой символ в конце:

void callback(byte* payload, uint16_t len)

Данный способ сильно быстрее и эффективнее чем String. Дальше можно работать с созданной строкой как обычно.

Прочее

Инструменты для копирования, поиска и сравнения

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, возвращает указатель на последний найденный. Как использовать – смотри тут.

Возвращает длину строки str без учёта нулевого символа.

Дублирует указанную str строку, динамически выделяя память под новую строку, возвращает указатель на новую строку. Внимание! Новая строка будет в динамической памяти, чтобы удалить такую строку – нужно использовать оператор delete или free .

Библиотека

У меня есть библиотека для удобной работы с Си-строками, по возможностям схожая со String, но гораздо легче и эффективнее. Библиотека называется mString, документацию и примеры смотрите на GitHub.

Видео

Полезные страницы

  • Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
  • Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
  • Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
  • Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
  • Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
  • Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
  • Поддержать автора за работу над уроками
  • Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *