5,8K подписчиков

Введение в работу со строками на языке программирования C (Си)

814 прочитали

Начнем с задачки по строкам :) Попробуйте сделать её без компилятора. Ответ напишите в комментариях
Начнем с задачки по строкам :) Попробуйте сделать её без компилятора. Ответ напишите в комментариях

Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?

Обсуждение этой задачи в telegram-канале

Обсуждение этой задачи в VK группе

Что такое строки в языке C ?

В языке C, как и в компьютере в целом, строки представлены набором чисел, где на каждую букву приходится некоторый числовой код, по которому операционная система определяет какой символ нарисовать пользователю. Происходит это в соответствии с таблицами кодировок ( ASCII или UTF-8, или другие). Хранить строки можно в массивах из переменных типа char. Каждый символ типа char хранит 1 байт ( 8 бит данных ). Вспоминаем формулу для мощности алфавита, получается, что если на один символ выделяется 8 бит, то мы можем закодировать 256 символов (еще в 1963 году в ASCII было 7 бит, но потом её расширили до 8 бит).

 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-2

Как компилятор определяет конец строки?

Строка в C заканчивается терминальным символом '\0'. Да, здесь как бы два символа, но для компилятора это один символ, числовое значение которого 0, т.е. массив типа char заканчивается нулем.

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

Рассмотрим другой пример заполнения. В готовую переменную word присвоим строковый литерал. Строка "Math" заканчивается терминальным символом '\0'.

Еще один вариант посимвольного присваивания также заканчивается нулевыми байтами. Так как всего память выделяется на 100 элементов, заполняются только первые 7 (с 0-го по 6-й), остальные элементы, начиная с 8-го по счету (7-го по индексу), заполняются нулями.

В итоге строки выводятся компилятором корректно.

 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-4

А как считать строку с консоли в языке C ?

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

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

 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-5

Как самостоятельно определить длину строки?

Напишем небольшой код, который будет считывать введенную строку и находить её длину.

Нахождение длины строки, введенной в консоль
Нахождение длины строки, введенной в консоль

Это прекрасно работает в онлайн-компиляторах. Также этот код легко запустится, если написать его в любом текстовом редакторе, сохранить с расширением .c или .cpp, а потом запустить через терминал (или командную строку) с помощью компилятора GCC.

Но проблема может возникнуть в том случае, если вы попытаетесь запустить такой код в Microsoft Visual Studio (MVS), начиная с 2015 года выпуска. Дело в том, что там напихали своих безопасных версий этих функций, которые добавляют проблем начинающим. Вместо того, чтобы думать о логике кода, приходится бороться с ошибками IDE.

Самое интересное, что ни одно из представленных решений в поисковике (на первой странице) не сработало. То ли я дурак, то ли сани не едут... Поправьте, если знаете конкретное решение для 2015 MVS.

Прикрепляю код, единственный вариант, который удалось довести до работы без ошибок конкретно в 2015-й студии...

Работает только такая версия: scanf_s("%19s", word, (unsigned)_countof(word)); 
НЕ работает версия: scanf_s("%19s", word);
#define _CRT_SECURE_NO_WARNINGS НЕ РАБОТАЕТ(!) 
Решение с отключением SDL НЕ РАБОТАЕТ(!)
Решение с дополнением в свойствах проекта -> свойства конфигурации -> C/C++ -> Препроцессор -> Определение препроцессора НЕ РАБОТАЕТ (!)
Редактирование и определение директивы в заголовочных файлах stdafx и stdio также НЕ РАБОТАЮТ (!)
Работает только такая версия: scanf_s("%19s", word, (unsigned)_countof(word)); НЕ работает версия: scanf_s("%19s", word); #define _CRT_SECURE_NO_WARNINGS НЕ РАБОТАЕТ(!) Решение с отключением SDL НЕ РАБОТАЕТ(!) Решение с дополнением в свойствах проекта -> свойства конфигурации -> C/C++ -> Препроцессор -> Определение препроцессора НЕ РАБОТАЕТ (!) Редактирование и определение директивы в заголовочных файлах stdafx и stdio также НЕ РАБОТАЮТ (!)

А можно ли сравнить два слова в языке C ?

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

 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-8

Как удалить элемент из массива символов или строки?

Есть такая полезная функция memcpy, полное определение которой будет следующим:
void
* memcpy( void * destptr, const void * srcptr, size_t num );

Эта функция копирует num первых байтов, на которые ссылается указатель srcptr (source pointer) в блок памяти, на который указывает destptr(destination pointer). Будьте аккуратны, данная функция не проверяет есть ли символ завершения в источнике srcptr. Поэтому возможно переполнение.

  • destptr
    Указатель на блок памяти назначения (куда будут копироваться байты данных), имеет тип данных void.
  • srcptr
    Указатель на блок памяти источник (т. е., откуда будут копироваться байты данных), имеет тип данных void.
  • num
    Количество копируемых байтов.
 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-9

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

Поэтому аналогично можно удалить букву из слова:

 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-10

Что еще можно сделать с помощью функции копирования участка памяти из одной области в другую? Можно написать функцию, которая меняет местами две переменные:

 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-11

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

 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-12

А что делать, если области памяти пересекаются?

Здесь тоже есть выход. Мы можем использовать следующую функцию:
void* memmove (void * destination, const void * source, size_t num);

Данная функция копирует num байт из источника source в пункт назначения destination. При этом области могут пересекаться. При копировании используется промежуточный буфер, который справится с проблемой перекрытия областей.

 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-13

Функция для копирования одной строки в другую

char* strcpy (char * destination, const char* source );

Копирует одну строку в другую, вместе с нулевым символом. Также возвращает указатель на destination.

 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-14

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

char* strncpy (char* destination, const char* source, size_t num);

Вызов данной функции скопирует только num первых букв строки. Терминальный символ \0 автоматически НЕ добавляется. При копировании из строки в эту же строку не должны пересекаться области. Если это происходит, то нужно использовать ранее рассмотренную функцию memmove().

Конкатенация строк: как склеить две строки в одну?

Существуют две функции из стандартной библиотеки <string.h>

char* strcat (char * destination, const char * source);

Добавляет в конец destination строку source, при этом затирая первым символом нулевой. Возвращает указатель на destination.

char* strncat (char * destination, const char * source, size_t num);

Добавляет в конец строки destination num символов второй строки. В конец добавляется нулевой символ.

 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-15

Сравнение строк через библиотечную функцию

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

int strcmp (const char * str1, const char * str2);

Возвращает 0, если строки равны, больше нуля, если первая строка больше, меньше нуля, если первая строка меньше. Сравнение строк происходит посимвольно, сравниваются численные значения. Для сравнения строк на определённом языке используется strcoll

int strcoll (const char * str1, const char * str2);

int strncmp (const char * str1, const char * str2, size_t num);

сравнение строк по первым num символам.

Пример - сортировка массива строк по первым трём символам:

 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-16

Работа с локалью или что такое locale.h ?

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

 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-17

Поиск в строке

Рассмотрим следующую функцию:
void * memchrvoid * memptr, int val, size_t num );

Функция memchr описана в заголовочном файле string.h (для C) или в cstring (для C++) и находит в блоке памяти, на который указывает memptr первое вхождение символа val, а после возвращает указатель на найденный символ. Ищет среди первых num байтов.

 Привет, друзья! Сегодня немного покодим. Попробуйте подумать без использования компилятора. Какой код правильно справится с данной задачей?-18

Если нужно работать только со строками, а не с произвольным наборов байт, то можно использовать функцию:

char* strchr (char * str, int character); - выполняет поиск первого вхождения символа character в строку str. При этом учитывается нулевой терминальный символ. То есть он также может быть найден, чтобы получить указатель на конец строки.

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

size_t strcspn ( const char * str1, const char * str2 );

Возвращает адрес первого вхождения любой буквы из строки str2 в строке str1. Если ни одно включение не найдено, то возвратит длину строки.

Поиск положение всех гласных в строке

Поиск гласных букв по позициям
Поиск гласных букв по позициям

Стоит обратить внимание на строку i++ после printf. Если бы её не было, то strcspn возвращал бы всегда 0, потому что в начале строки стояла бы гласная, и произошло зацикливание.

Для решения этой задачи гораздо лучше подошла функция, которая возвращает указатель на первую гласную:

char* strpbrk (char * str1, const char * str2) - эта функция похожа на strcspn(), но возвращает указатель на первый символ из строки str1, который есть в строке str2. Аналогичная задача по поиску гласных реализовывалась бы так:

Поиск гласных букв через указатели
Поиск гласных букв через указатели

Получается, что для работы со строками в языке C могут понадобиться следующие заголовочные файлы:

stdlib.h — заголовочный файл стандартной библиотеки языка Си, который содержит в себе функции, занимающиеся выделением памяти, контролем процесса выполнения программы, преобразованием типов и другие. Заголовок вполне совместим с C++ и известен в нём как cstdlib. Название «stdlib» расшифровывается как «standard library» (стандартная библиотека).

string.h — заголовочный файл стандартной библиотеки языка Си, содержащий функции для работы со строками, оканчивающимися на 0, и различными функциями работы с памятью.

Изучить содержимое стандартной библиотеки можно в документации или в википедии (тут будьте аккуратны, много ошибок)

Понравилась заметка? Поставьте лайк, подпишитесь на канал, поделитесь заметкой в социальных сетях! Вам не сложно, а мне очень приятно :)

Если Вам нужен репетитор по физике, математике или информатике/программированию, Вы можете написать мне или в мою группу Репетитор IT mentor в VK
Библиотека с книгами для физиков, математиков и программистов
Репетитор IT mentor в Instagram
Репетитор IT mentor в telegram