• Функции работы со строками с. Операции со строками

    Пожалуйста, приостановите работу AdBlock на этом сайте.

    Итак, строки в языке Си. Для них не предусмотрено отдельного типа данных, как это сделано во многих других языках программирования. В языке Си строка – это массив символов. Чтобы обозначить конец строки, используется символ "\0" , о котором мы говорили в прошлой части этого урока. На экране он никак не отображается, поэтому посмотреть на него не получится.

    Создание и инициализация строки

    Так как строка – это массив символов, то объявление и инициализация строки аналогичны подобным операциям с одномерными массивами.

    Следующий код иллюстрирует различные способы инициализации строк.

    Листинг 1.

    Char str; char str1 = {"Y","o","n","g","C","o","d","e","r","\0"}; char str2 = "Hello!"; char str3 = "Hello!";

    Рис.1 Объявление и инициализация строк

    В первой строке мы просто объявляем массив из десяти символов. Это даже не совсем строка, т.к. в ней отсутствует нуль-символ \0 , пока это просто набор символов.

    Вторая строка. Простейший способ инициализации в лоб. Объявляем каждый символ по отдельности. Тут главное не забыть добавить нуль-символ \0 .

    Третья строка – аналог второй строки. Обратите внимание на картинку. Т.к. символов в строке справа меньше, чем элементов в массиве, остальные элементы заполнятся \0 .

    Четвёртая строка. Как видите, тут не задан размер. Программа его вычислит автоматически и создаст массив символов нужный длины. При этом последним будет вставлен нуль-символ \0 .

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

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

    Листинг 2.

    #include int main(void) { char str; char str1 = {"Y","o","n","g","C","o","d","e","r","\0"}; char str2 = "Hello!"; char str3 = "Hello!"; for(int i = 0; i < 10; i = i + 1) printf("%c\t",str[i]); printf("\n"); puts(str1); printf("%s\n",str2); puts(str3); return 0; }


    Рис.2 Различные способы вывода строки на экран

    Как видите, есть несколько основных способов вывести строку на экран.

    • использовать функцию printf со спецификатором %s
    • использовать функцию puts
    • использовать функцию fputs , указав в качестве второго параметра стандартный поток для вывода stdout .

    Единственный нюанс у функций puts и fputs . Обратите внимание, что функция puts переносит вывод на следующую строку, а функция fputs не переносит.

    Как видите, с выводом всё достаточно просто.

    Ввод строк

    С вводом строк всё немного сложнее, чем с выводом. Простейшим способом будет являться следующее:

    Листинг 3.

    #include int main(void) { char str; gets(str); puts(str); return 0; }

    Функция gets приостанавливает работу программы, читает строку символов, введенных с клавиатуры, и помещает в символьный массив, имя которого передаётся функции в качестве параметра.
    Завершением работы функции gets будет являться символ, соответствующий клавише ввод и записываемый в строку как нулевой символ.
    Заметили опасность? Если нет, то о ней вас любезно предупредит компилятор. Дело в том, что функция gets завершает работу только тогда, когда пользователь нажимает клавишу ввод. Это чревато тем, что мы можем выйти за рамки массива, в нашем случае - если введено более 20 символов.
    К слову, ранее ошибки переполнения буфера считались самым распространенным типом уязвимости. Они встречаются и сейчас, но использовать их для взлома программ стало гораздо сложнее.

    Итак, что мы имеем. У нас есть задача: записать строку в массив ограниченного размера. То есть, мы должны как-то контролировать количество символов, вводимых пользователем. И тут нам на помощь приходит функция fgets :

    Листинг 4.

    #include int main(void) { char str; fgets(str, 10, stdin); puts(str); return 0; }

    Функция fgets принимает на вход три аргумента: переменную для записи строки, размер записываемой строки и имя потока, откуда взять данные для записи в строку, в данном случае - stdin . Как вы уже знаете из 3 урока, stdin – это стандартный поток ввода данных, обычно связанный с клавиатурой. Совсем необязательно данные должны поступать именно из потока stdin , в дальнейшем эту функцию мы также будем использовать для чтения данных из файлов.

    Если в ходе выполнения этой программы мы введем строку длиннее, чем 10 символов, в массив все равно будут записаны только 9 символов с начала и символ переноса строки, fgets «обрежет» строку под необходимую длину.

    Обратите внимание, функция fgets считывает не 10 символов, а 9 ! Как мы помним, в строках последний символ зарезервирован для нуль-символа.

    Давайте это проверим. Запустим программу из последнего листинга. И введём строку 1234567890 . На экран выведется строка 123456789 .


    Рис.3 Пример работы функции fgets

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

    Листинг 5.

    #include int main(void) { char str; fgets(str, 10, stdin); puts(str); int h = 99; printf("do %d\n", h); scanf("%d",&h); printf("posle %d\n", h); return 0; }

    Вот результат её работы.


    Рис.4 Непустой буфер stdin

    Поясню произошедшее. Мы вызвали функцию fgets . Она открыла поток ввода и дождалась пока мы введём данные. Мы ввели с клавиатуры 1234567890\n (\n я обозначаю нажатие клавиша Enter ). Это отправилось в поток ввода stdin . Функция fgets , как и полагается, взяла из потока ввода первые 9 символов 123456789 , добавила к ним нуль-символ \0 и записала это в строку str . В потоке ввода осталось ещё 0\n .

    Далее мы объявляем переменную h . Выводим её значение на экран. После чего вызываем функцию scanf . Тут-то ожидается, что мы можем что-то ввести, но т.к. в потоке ввода висит 0\n , то функция scanf воспринимает это как наш ввод, и записывается 0 в переменную h . Далее мы выводим её на экран.

    Это, конечно, не совсем такое поведение, которое мы ожидаем. Чтобы справиться с этой проблемой, необходимо очистить буфер ввода после того, как мы считали из него строку, введённую пользователем. Для этого используется специальная функция fflush . У неё всего один параметр – поток, который нужно очистить.

    Исправим последний пример так, чтобы его работа была предсказуемой.

    Листинг 6.

    #include int main(void) { char str; fgets(str, 10, stdin); fflush(stdin); // очищаем поток ввода puts(str); int h = 99; printf("do %d\n", h); scanf("%d",&h); printf("posle %d\n", h); return 0; }

    Теперь программа будет работать так, как надо.


    Рис.4 Сброс буфера stdin функцией fflush

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

    Второй. Не забывайте очищать буфер ввода, если используете функцию fgets .

    На этом разговор о вводе строк закончен. Идём дальше.

    Хабра, привет!

    Не так давно у со мной произошел довольно-таки интересный инцидент, в котором был замешан один из преподавателей одного колледжа информатики.

    Разговор о программировании под Linux медленно перешел к тому, что этот человек стал утверждать, что сложность системного программирования на самом деле сильно преувеличена. Что язык Си прост как спичка, собственно как и ядро Linux (с его слов).

    У меня был с собой ноутбук с Linux, на котором присутствовал джентльменский набор утилит для разработки на языке Си (gcc, vim, make, valgrind, gdb). Я уже не помню, какую цель мы тогда перед собой поставили, но через пару минут мой оппонент оказался за этим ноутбуком, полностью готовый решать задачу.

    И буквально на первых же строках он допустил серьезную ошибку при аллоцировании памяти под… строку.

    Char *str = (char *)malloc(sizeof(char) * strlen(buffer));
    buffer - стековая переменная, в которую заносились данные с клавиатуры.

    Я думаю, определенно найдутся люди, которые спросят: «Разве что-то тут может быть не так?».
    Поверьте, может.

    А что именно - читайте по катом.

    Немного теории - своеобразный ЛикБез.

    Если знаете - листайте до следующего хэдера.

    Строка в C - это массив символов, который по-хорошему всегда должен заканчиваться "\0" - символом конца строки. Строки на стеке (статичные) объявляются вот так:

    Char str[n] = { 0 };
    n - размер массива символов, то же, что и длина строки.

    Присваивание { 0 } - «зануление» строки (опционально, объявлять можно и без него). Результат такой же, как у выполнения функций memset(str, 0, sizeof(str)) и bzero(str, sizeof(str)). Используется, чтобы в неинициализированных переменных не валялся мусор.

    Так же на стеке можно сразу проинициализировать строку:

    Char buf = "default buffer text\n";
    Помимо этого строку можно объявить указателем и выделить под нее память на куче (heap):

    Char *str = malloc(size);
    size - количество байт, которые мы выделяем под строку. Такие строки называются динамическими (вследствие того, что нужный размер вычисляется динамически + выделенный размер памяти можно в любой момент увеличить с помощью функции realloc()).

    В случае со стековой переменной, для определения размера массива я использовал обозначение n, в случае с переменной на куче - я использовал обозначение size. И это прекрасно отражает истинную суть отличия объявления на стеке от объявление с аллоцированием памяти на куче, ведь n как правило используется тогда, когда говорят о количестве элементов. А size - это уже совсем другая история…

    Нам поможет valgrind

    В своей предыдущей статье я также упоминал о нем. Valgrind ( , два - небольшой how-to) - очень полезная программа, которая помогает программисту отслеживать утечки памяти и ошибки контекста - как раз те вещи, которые чаще всего всплывают при работе со строками.

    Давайте рассмотрим небольшой листинг, в котором реализовано что-то похожее на упомянутую мной программу, и прогоним ее через valgrind:

    #include #include #include #define HELLO_STRING "Hello, Habr!\n" void main() { char *str = malloc(sizeof(char) * strlen(HELLO_STRING)); strcpy(str, HELLO_STRING); printf("->\t%s", str); free(str); }
    И, собственно, результат работы программы:

    $ gcc main.c $ ./a.out -> Hello, Habr!
    Пока ничего необычного. А теперь давайте запустим эту программу с valgrind!

    $ valgrind --tool=memcheck ./a.out ==3892== Memcheck, a memory error detector ==3892== Copyright (C) 2002-2015, and GNU GPL"d, by Julian Seward et al. ==3892== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info ==3892== Command: ./a.out ==3892== ==3892== Invalid write of size 2 ==3892== at 0x4005B4: main (in /home/indever/prg/C/public/a.out) ==3892== Address 0x520004c is 12 bytes inside a block of size 13 alloc"d ==3892== at 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== by 0x400597: main (in /home/indever/prg/C/public/a.out) ==3892== ==3892== Invalid read of size 1 ==3892== at 0x4C30BC4: strlen (vg_replace_strmem.c:454) ==3892== by 0x4E89AD0: vfprintf (in /usr/lib64/libc-2.24.so) ==3892== by 0x4E90718: printf (in /usr/lib64/libc-2.24.so) ==3892== by 0x4005CF: main (in /home/indever/prg/C/public/a.out) ==3892== Address 0x520004d is 0 bytes after a block of size 13 alloc"d ==3892== at 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== by 0x400597: main (in /home/indever/prg/C/public/a.out) ==3892== -> Hello, Habr! ==3892== ==3892== HEAP SUMMARY: ==3892== in use at exit: 0 bytes in 0 blocks ==3892== total heap usage: 2 allocs, 2 frees, 1,037 bytes allocated ==3892== ==3892== All heap blocks were freed -- no leaks are possible ==3892== ==3892== For counts of detected and suppressed errors, rerun with: -v ==3892== ERROR SUMMARY: 3 errors from 2 contexts (suppressed: 0 from 0)
    ==3892== All heap blocks were freed - no leaks are possible - утечек нет, и это радует. Но стоит опустить глаза чуть пониже (хотя, хочу заметить, это лишь итог, основная информация немного в другом месте):

    ==3892== ERROR SUMMARY: 3 errors from 2 contexts (suppressed: 0 from 0)
    3 ошибки. В 2х контекстах. В такой простой программе. Как!?

    Да очень просто. Весь «прикол» в том, что функция strlen не учитывает символ конца строки - "\0". Даже если его явно указать во входящей строке (#define HELLO_STRING «Hello, Habr!\n\0»), он будет проигнорирован.

    Чуть выше результата исполнения программы, строки -> Hello, Habr! есть подробный отчет, что и где не понравилось нашему драгоценному valgrind. Предлагаю самостоятельно посмотреть эти строчки и сделать выводы.

    Собственно, правильная версия программы будет выглядеть так:

    #include #include #include #define HELLO_STRING "Hello, Habr!\n" void main() { char *str = malloc(sizeof(char) * (strlen(HELLO_STRING) + 1)); strcpy(str, HELLO_STRING); printf("->\t%s", str); free(str); }
    Пропускаем через valgrind:

    $ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==3435== ==3435== HEAP SUMMARY: ==3435== in use at exit: 0 bytes in 0 blocks ==3435== total heap usage: 2 allocs, 2 frees, 1,038 bytes allocated ==3435== ==3435== All heap blocks were freed -- no leaks are possible ==3435== ==3435== For counts of detected and suppressed errors, rerun with: -v ==3435== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    Отлично. Ошибок нет, +1 байт выделяемой памяти помог решить проблему.

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

    Однако, знаете, (strlen(str) + 1) - такое себе решение. Перед нами встают 2 проблемы:

    1. А если нам надо выделить память под формируемую с помощью, например, s(n)printf(..) строку? Аргументы мы не поддерживаем.
    2. Внешний вид. Строка с объявлением переменной выглядит просто ужасно. Некоторые ребята к malloc еще и (char *) умудряются прикручивать, будто под плюсами пишут. В программе где регулярно требуется обрабатывать строки есть смысл найти более изящное решение.
    Давайте придумаем такое решение, которое удовлетворит и нас, и valgrind.

    snprintf()

    int snprintf(char *str, size_t size, const char *format, ...); - функция - расширение sprintf, которая форматирует строку и записывает ее по указателю, переданному в качестве первого аргумента. От sprintf() она отличается тем, что в str не будет записано байт больше, чем указано в size.

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

    Одна из описанных мною проблем использования strlen связана с функциями sprintf() и snprintf(). Предположим, что нам надо что-то записать в строку str. Конечная строка содержит значения других переменных. Наша запись должна быть примерно такой:

    Char * str = /* тут аллоцируем память */; sprintf(str, "Hello, %s\n", "Habr!");
    Встает вопрос: как определить, сколько памяти надо выделить под строку str?

    Char * str = malloc(sizeof(char) * (strlen(str, "Hello, %s\n", "Habr!") + 1)); - не прокатит. Прототип функции strlen() выглядит так:

    #include size_t strlen(const char *s);
    const char *s не подразумевает, что передаваемая в s строка может быть строкой формата с переменным количеством аргументов.

    Тут нам поможет то полезное свойство функции snprintf(), о котором я говорил выше. Давайте посмотрим на код следующей программы:

    #include #include #include void main() { /* Т.к. snprintf() не учитывает символ конца строки, прибавляем его размер к результату */ size_t needed_mem = snprintf(NULL, 0, "Hello, %s!\n", "Habr") + sizeof("\0"); char *str = malloc(needed_mem); snprintf(str, needed_mem, "Hello, %s!\n", "Habr"); printf("->\t%s", str); free(str); }
    Запускаем программу в valgrind:

    $ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==4132== ==4132== HEAP SUMMARY: ==4132== in use at exit: 0 bytes in 0 blocks ==4132== total heap usage: 2 allocs, 2 frees, 1,041 bytes allocated ==4132== ==4132== All heap blocks were freed -- no leaks are possible ==4132== ==4132== For counts of detected and suppressed errors, rerun with: -v ==4132== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) $
    Отлично. Поддержка аргументов у нас есть. Благодаря тому, что мы в качестве второго аргумента в функцию snprintf() передаем ноль, запись по нулевому указателю никогда не приведет к Seagfault. Однако, несмотря на это функция все равно вернет необходимый под строку размер.

    Но с другой стороны, нам пришлось завести дополнительную переменную, да и конструкция

    Size_t needed_mem = snprintf(NULL, 0, "Hello, %s!\n", "Habr") + sizeof("\0");
    выглядит еще хуже, чем в случае с strlen().

    Вообще, + sizeof("\0") можно убрать, если в конце строки формата явно указать "\0" (size_t needed_mem = snprintf(NULL, 0, «Hello, %s!\n\0 », «Habr»);), но это возможно отнюдь не всегда (в зависимости от механизма обработки строк мы можем выделить лишний байт).

    Надо что-то сделать. Я немного подумал и решил, что сейчас настал час воззвать к мудрости древних. Опишем макрофункцию, которая будет вызывать snprintf() с нулевым указателем в качестве первого аргумента, и нулем, в качестве второго. Да и про конец строки не забудем!

    #define strsize(args...) snprintf(NULL, 0, args) + sizeof("\0")
    Да, возможно, для кого-то будет новостью, но макросы в си поддерживают переменное количество аргументов, и троеточие говорит препроцессору о том, что указанному аргументу макрофункции (в нашем случае это args) соответствует несколько реальных аргументов.

    Проверим наше решение на практике:

    #include #include #include #define strsize(args...) snprintf(NULL, 0, args) + sizeof("\0") void main() { char *str = malloc(strsize("Hello, %s\n", "Habr!")); sprintf(str, "Hello, %s\n", "Habr!"); printf("->\t%s", str); free(str); }
    Запускаем с valgrund:

    $ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==6432== ==6432== HEAP SUMMARY: ==6432== in use at exit: 0 bytes in 0 blocks ==6432== total heap usage: 2 allocs, 2 frees, 1,041 bytes allocated ==6432== ==6432== All heap blocks were freed -- no leaks are possible ==6432== ==6432== For counts of detected and suppressed errors, rerun with: -v ==6432== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    Да, ошибок нет. Все корректно. И valgrind доволен, и программист наконец может пойти поспать.

    Но, напоследок, скажу еще кое-что. В случае, если нам надо выделить память под какую-либо строку (даже с аргументами) есть уже полностью рабочее готовое решение .

    Речь идет о функции asprintf:

    #define _GNU_SOURCE /* See feature_test_macros(7) */ #include int asprintf(char **strp, const char *fmt, ...);
    В качестве первого аргумента она принимает указатель на строку (**strp) и аллоцирует память по разыменованному указателю.

    Наша программа, написанная с использованием asprintf() будет выглядеть так:

    #include #include #include void main() { char *str; asprintf(&str, "Hello, %s!\n", "Habr"); printf("->\t%s", str); free(str); }
    И, собственно, в valgrind:

    $ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==6674== ==6674== HEAP SUMMARY: ==6674== in use at exit: 0 bytes in 0 blocks ==6674== total heap usage: 3 allocs, 3 frees, 1,138 bytes allocated ==6674== ==6674== All heap blocks were freed -- no leaks are possible ==6674== ==6674== For counts of detected and suppressed errors, rerun with: -v ==6674== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    Все отлично, но, как видите, памяти всего было выделено больше, да и alloc"ов теперь три, а не два. На слабых встраиваемых системах использование это функции нежелательно.
    К тому же, если мы напишем в консоли man asprintf, то увидим:

    CONFORMING TO These functions are GNU extensions, not in C or POSIX. They are also available under *BSD. The FreeBSD implementation sets strp to NULL on error.

    Отсюда ясно, что данная функция доступна только в исходниках GNU.

    Заключение

    В заключение я хочу сказать, что работа со строками в C - это очень сложная тема, которая имеет ряд нюансов. Например, для написания «безопасного» кода при динамическом выделении памяти рекомендуется все же использовать функцию calloc() вместо malloc() - calloc забивает выделяемую память нулями. Ну или после выделения памяти использовать функцию memset(). Иначе мусор, который изначально лежал на выделяемом участке памяти, может вызвать вопросы при дебаге, а иногда и при работе со строкой.

    Больше половины моих знакомых си-программистов (большинство из них - начинающие), решивших по моей просьбе задачу с выделением памяти под строки, сделали это так, что в конечном итоге это привело к ошибкам контекста. В одном случае - даже к утечке памяти (ну, забыл человек сделать free(str), с кем не бывает). Собственно говоря, это и сподвигло меня на создание сего творения, которое вы только что прочитали.

    Я надеюсь, кому-то эта статья будет полезной. К чему я это все городил - никакой язык не бывает прост. Везде есть свои тонкости. И чем больше тонкостей языка вы знаете, тем лучше ваш код.

    Я верю, что после прочтения этой статьи ваш код станет чуточку лучше:)
    Удачи, Хабр!

    Объявление строк

    Строка в языке Си представляет собой одномерный массив символов, последним элементом которой является символ конца строки – нуль (строка, завершающаяся нулем, то есть NULL terminated string).

    Объявление переменной типа строка в языке Си возможно тремя способами, два из которых инициализируют строку во время объявления.

    Первый способ:

    Объявления массива символов (не забудьте добавить место для завершающего нуля):

    Char s;

    Второй способ:

    Присвоить строковой переменной начальное значение (при этом длину строки компилятор может вычислить сам):

    Char s = "Пример инициализации строки";

    Справа от знака присваивания записана строковая константа. В конце строки автоматически добавляется ноль (‘\0’). Константы символьных строк помещаются в класс статической памяти.

    Третий способ:

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

    Char *s="Второй вариант инициализации";

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

    Char *s;

    Ввод строки со стандартного устройства ввода (клавиатуры)

    Для работы со строками есть набор функций. Для ввода со стандартного устройства ввода (клавиатуры) чаще всего используются библиотечные функциями из модуля стандартного ввода-вывода: scanf и gets .

    Для ввода строки с помощью функции scanf , использует формат «%s » , причем обратите внимание на то, что перед идентификатором строки не используется знак адреса «& » , так как одномерный массив уже представлен указателем на его начало:

    Scanf("%s", s);

    Функция gets() считывает символы до тех пор, пока не достигнет символа перехода на новую строку. Функция принимает все символы вплоть до символа перевода строки, но не включает его. К концу строки добавляется завершающий ноль (‘\0’). Функция gets() помещает считанную с клавиатуры последовательность символов в параметр типа строка и возвращает указатель на эту строку (если операция завершилась успешно), или NULL (в случае ошибки). В приведенном ниже примере при успешном завершении операции, на экран будет выведено две одинаковые строки:

    #include int main() { char s; char *p; p=gets(s); printf(" \n Введена строка %s. ",s); if (p) printf(" \n Введена строка %s. ",p); return 0; }

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

    #include #include #include int main() { char s; int x, err; do { printf(" \n Введите целое число -> "); gets(s); err=sscanf(s, "%d",&x); if (err!=1) printf(" \n Ошибка ввода. "); } while (err!=1); printf("\n Введено целое число -> %d", x); return 0; }

    Вывод строк на стандартное устройство вывода (экран монитора)

    Для вывода строк на стандартное устройство вывода (экран монитора) можно использовать две функции printf и puts . В функции printf в качестве формата передается «%s». Удобство использования этой функции заключается в том, что помимо строки можно сразу выводит данные других типов. Особенность функции puts заключается в том, что после вывода строки автоматически происходит переход на следующую строку.

    Функции для работы со строками

    Для преобразования строк в языке Си предусмотрена библиотека string. Каждая из функций имеет свой формат записи (прототип).

    Наиболее используемые функции рассмотрены в этой статье. — читать

    Пример программ(листинг) работающей со строками

    В программе строки могут определяться следующим образом:

    • как строковые константы;
    • как массивы символов;
    • через указатель на символьный тип;
    • как массивы строк.

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

    Любая последовательность символов, заключенная в двойные кавычки «» , рассматривается как строковая константа .

    Для корректного вывода любая строка должна заканчиваться нуль-символом "\0" , целочисленное значение которого равно 0. При объявлении строковой константы нуль-символ добавляется к ней автоматически. Так, последовательность символов, представляющая собой строковую константу, будет размещена в оперативной памяти компьютера, включая нулевой байт.

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

    Для помещения в строковую константу некоторых служебных символов используются символьные комбинации. Так, если необходимо включить в строку символ двойной кавычки, ему должен предшествовать символ «обратный слеш»: ‘\»‘ .

    Строковые константы размещаются в статической памяти. Начальный адрес последовательности символов в двойных кавычках трактуется как адрес строки. Строковые константы часто используются для осуществления диалога с пользователем в таких функциях, как printf() .

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

    char m;

    Компилятор также может самостоятельно определить размер массива символов, если инициализация массива задана при объявлении строковой константой:

    char m2=;
    char m3={"Т","и","х","и","е"," ","д","о","л","и","н","ы"," ","п","о","л","н","ы"," ","с","в","е","ж","е","й"," ","м","г","л","о","й","\0" };

    В этом случае имена m2 и m3 являются указателями на первые элементы массивов:

    • m2 эквивалентно &m2
    • m2 эквивалентно ‘Г’
    • m2 эквивалентно ‘o’
    • m3 эквивалентно &m3
    • m3 эквивалентно ‘x’

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

    char m2="Горные вершины спят во тьме ночной." ;

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

    char *m4;

    В этом случае объявление массива переменной m4 может быть присвоен адрес массива:

    m4 = m3;
    *m4 эквивалентно m3="Т"
    *(m4+1) эквивалентно m3="и"

    Здесь m3 является константой-указателем. Нельзя изменить m3 , так как это означало бы изменение положения (адреса) массива в памяти, в отличие от m4 .

    Для указателя можно использовать операцию увеличения (перемещения на следующий символ):

    Массивы символьных строк

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

    char *poet = {"Погиб поэт!", "- невольник чести -" ,
    "Пал," , "оклеветанный молвой…" };

    В этом случае poet является массивом, состоящим из четырех указателей на символьные строки. Каждая строка символов представляет собой символьный массив, поэтому имеется четыре указателя на массивы. Указатель poet ссылается на первую строку:
    *poet эквивалентно "П" ,
    *poet[l] эквивалентно "-" .

    Инициализация выполняется по правилам, определенным для массивов.
    Тексты в кавычках эквивалентны инициализации каждой строки в массиве. Запятая разделяет соседние
    последовательности.
    Кроме того, можно явно задавать размер строк символов, используя описание, подобное такому:

    char poet;

    Разница заключается в том, что такая форма задает «прямоугольный» массив, в котором все строки имеют одинаковую длину.

    Свободный массив

    Описание

    сhar *poet;


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

    Операции со строками

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

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

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

    char *name;
    name = (char *)malloc(10);
    scanf("%9s" , name);

    Для ввода строки использована функция scanf() , причем введенная строка не может превышать 9 символов. Последний символ будет содержать "\0" .

    Функции ввода строк

    Для ввода строки может использоваться функция scanf() . Однако функция scanf() предназначена скорее для получения слова, а не строки. Если применять формат "%s" для ввода, строка вводится до (но не включая) следующего пустого символа, которым может быть пробел, табуляция или перевод строки.

    Для ввода строки, включая пробелы, используется функция

    char * gets(char *);


    или её эквивалент

    char * gets_s(char *);

    В качестве аргумента функции передается указатель на строку, в которую осуществляется ввод. Функция просит пользователя ввести строку, которую она помещает в массив, пока пользователь не нажмет Enter .

    Функции вывода строк

    Для вывода строк можно воспользоваться рассмотренной ранее функцией

    printf("%s" , str); // str - указатель на строку

    или в сокращенном формате

    printf(str);

    Для вывода строк также может использоваться функция

    int puts (char *s);

    которая печатает строку s и переводит курсор на новую строку (в отличие от printf() ). Функция puts() также может использоваться для вывода строковых констант, заключенных в кавычки.

    Функция ввода символов

    Для ввода символов может использоваться функция

    char getchar();


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

    Функция вывода символов

    Для вывода символов может использоваться функция

    char putchar(char );


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    #include
    #include
    #include
    int main() {
    char s, sym;
    int count, i;
    system("chcp 1251" );
    system("cls" );
    printf("Введите строку: " );
    gets_s(s);
    printf("Введите символ: " );
    sym = getchar();
    count = 0;
    for (i = 0; s[i] != "\0" ; i++)
    {
    if (s[i] == sym)
    count++;
    }
    printf("В строке\n" );
    puts(s); // Вывод строки
    printf("символ " );
    putchar(sym); // Вывод символа
    printf(" встречается %d раз" , count);
    getchar(); getchar();
    return 0;
    }

    Результат выполнения

    Основные функции стандартной библиотеки string.h

    Основные функции стандартной библиотеки string.h приведены в таблице.

    Функция Описание

    char *strcat(char *s1, char *s2)

    присоединяет s2 к s1, возвращает s1

    char *strncat(char *s1, char *s2, int n)

    присоединяет не более n символов s2 к s1, завершает строку символом "\0", возвращает s1

    char *strсpy(char *s1, char *s2)

    копирует строку s2 в строку s1, включая "\0", возвращает s1
    );
    strncpy(m3, m1, 6); // не добавляет "\0" в конце строки
    puts("Результат strncpy(m3, m1, 6)" );
    puts(m3);
    strcpy(m3, m1);
    puts("Результат strcpy(m3, m1)" );
    puts(m3);
    puts("Результат strcmp(m3, m1) равен" );
    printf("%d" , strcmp(m3, m1));
    strncat(m3, m2, 5);
    puts("Результат strncat(m3, m2, 5)" );
    puts(m3);
    strcat(m3, m2);
    puts("Результат strcat(m3, m2)" );
    puts(m3);
    puts("Количество символов в строке m1 равно strlen(m1) : " );
    printf("%d\n" , strlen(m1));
    _strnset(m3, "f" , 7);
    puts("Результат strnset(m3, "f" , 7)" );
    puts(m3);
    _strset(m3, "k" );
    puts("Результат strnset(m3, "k" )" );
    puts(m3);
    getchar();
    return 0;
    }

    Результат выполнения

    Библиотека функций языков С и C++ включает богатый набор функций обработки строк и символов. Строковые функции работают с символьными массивами, завершающимися нулевыми символами. В языке С для использования строковых функций необходимо включить в начало модуля программы заголовочный файл , а для символьных - заголовочный файл . В языке C++ для работы со строковыми и символьными функциями используются заголовки и соответственно. В этой главе для простоты изложения используются имена С-заголовков.

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

    В языках С и C++ печатаемыми являются символы, отображаемые на терминале. В ASCII-средах они расположены между пробелом(0x20) и тильдой(OxFE). Управляющие символы имеют значения, лежащие в диапазоне между нулем и Ox1F; к ним также относится символ DEL(Ox7F).

    Исторически сложилось так, что аргументами символьных функций являются целые значения, из которых используется только младший байт. Символьные функции автоматически преобразуют свои аргументы в тип unsigned char. Безусловно, вы вольны вызывать эти функции с символьными аргументами, поскольку символы автоматически возводятся в ранг целых в момент вызова функции.

    В заголовке определен тип size_t, который является результатом применения оператора sizeof и представляет собой разновидность целого без знака.

    В версии С99 к некоторым параметрам нескольких функций, первоначально определенных в версии С89, добавлен квалификатор restrict. При рассмотрении каждой такой функции будет приведен ее прототип, используемый в среде С89(а также в среде C++), а параметры с атрибутом restrict будут отмечены в описании этой функции.

    Список функций

    Проверка на принадлежность

    isalnum - Проверка на принадлежность символа к алфавитно-цифровым
    isalpha - Проверка на принадлежность символа к буквам
    isblank - Проверка пустого символа
    iscntrl - Проверка на принадлежность символа к управляющим
    isdigit - Проверка на принадлежность символа к цифровым
    isgraph - Проверка на принадлежность символа к печатным но не к пробелу
    islower - Проверка на принадлежность символа к строчным
    isprint - Проверка на принадлежность символа к печатным
    ispunct - Проверка на принадлежность символа к знакам пунктуации
    isspace - Проверка на принадлежность символа к пробельным
    isupper - Проверка на принадлежность символа к прописным
    isxdigit - Проверка на принадлежность символа к шестнадцатеричным

    Работа с символьными массивами

    memchr - Просматривает массив чтобы отыскать первое вхождение символа
    memcmp - Сравнивает определённое количество символов в двух массивах
    memcpy - Копирует символы из одного массива в другой
    memmove - Копирует символы из одного массива в другой с учётом перекрытия массивов
    memset - Заполняет определённое количество символов массива заданным

    Манипуляции над строками

    strcat - Присоединяет копию одной строки к заданной
    strchr - Возвращает указатель на первое вхождение младшего байта заданного параметра
    strcmp - Сравнивает в лексикографическом порядке две строки
    strcoll - Сравнивает одну строку с другой в соответствии с параметром setlocale
    strcpy - Копирует содержимое одной строки в другую
    strcspn - Возвращает строку в которой отсутствуют заданные символы
    strerror - Возвращает указатель на строку содержащую системное сообщение об ошибке
    strlen - Возвращает длину строки с завершающим нулевым символом