Исторически работа со строками в языках С и С++ вызывает затруднения у новичков. Как мне кажется, во многом это связано с тем, что в этих языках строки значительно отличается от строк в языках типа Basic и Pascal. Непонимание базовых принципов приводит к тому, что код либо не работает вовсе, либо что еще хуже, работает, но не стабильно или непредсказуемо даже для его авторов. Основой для написания данной статьи по большей части послужили вопросы, часто задаваемые новичками. Надеюсь, хотя бы часть таких вопросов она снимет.
Это первая часть, в которой обсуждаются «традиционные» строки в С. В С++ существуют более удобные механизмы для работы со строковыми данными, эти механизмы рассматриваются во второй части статьи. А зачем вообще обсуждать неудобные С-строки, если есть С++? К сожалению, совсем забыть о строках в стиле С нельзя по двум причинам:
существует большое количество библиотек (например, API операционных систем) работающих именно с С-строками
строковые классы в С++ все равно основаны на традиционных С-строках, и если мы хотим разобраться в том, как они работают, нам придется понимать их основы.
Строковые литералы
Самая простая строковая сущность (под строковой сущностью я понимаю нечто, с чем можно работать как с привычной строкой) в С — это так называемый «строковый литерал». Он представляет собой последовательность символов, заключенную в двойные кавычки. Пример:
Пока я намеренно игнорирую вопрос о том, что есть строковый литерал с точки зрения компилятора или языка, ограничиваясь только его свойствами с точки зрения программиста.
Основное свойство строкового литерала — простота его использования. Не имея ни малейшего представления о том, чем он является на самом деле, мы можем использовать его практически везде, где от нас ждут строку. Например, в WinAPI-функцию SetWindowText (она задает текст, связанный с окном) нужно передать описатель окна и строку текста. И мы можем вызвать ее очень просто:
Но конечно, строковых литералов, естественно, фиксируемых при создании программы, будет маловато. Хотелось бы иметь возможность использовать переменные, не правда ли?
Строковые переменные
В С отсутствуют встроенные строковые типы в том смысле, в котором они есть в языках типа Basic и Pascal. И присущая этим языкам легкость оперирования строковыми переменными (присвоение, сравнение) в С недоступна. Что же такое строка в С?
Для начала разберемся, что такое строка в жизни. Очевидно, что строка – это последовательность символов . В С – как в жизни. С-строка — это последовательность символов. Как известно, последовательности в С представляются массивами или указателями. Между первыми и вторыми существует связь, однако природа этой связи выходит за рамки данной статьи. Предполагается, что читателю знакомы следующие особенности связи между массивами и указателями:
массив можно привести к указателю на его первый элемент, что неявно происходит при передаче массивов в функции, ожидающие указатели
информация о размере массива переданного таким образом в функцию теряется
в С не существует способа передать массив по значению с сохранением его размера
указателя на первый элемент массива достаточно для работы со всем массивом, при условии что нам известна его длина.
Если вышеприведенные высказывания вызывают у вас затруднения, рекомендую сначала разобраться с этим вопросом, а уже потом читать эту статью.
В дальнейшем для простоты я по большей части буду говорить о массивах, но почти все сказанное относится и к указателям.
Итак, тип строк в С – массив. Однако каков тип элементов этого массива? Вообще говоря, возможны варианты. Исторически символ занимает 1 байт, в этом случае он имеет тип char . При этом существуют и другие кодировки, в которых символ представляется, например, двумя байтами. Для работы с такими строками требуются специальные функции.
Слегка отвлечемся от строк и разберем понятие кодировки . По определению, кодировка — это способ представления чего либо, в нашем случае символов. Кодировки символов делятся на однобайтные — каждый символ представлен одним байтом и многобайтные, в которых одному символу соответствует несколько байтов. В свою очередь многобайтные кодировки можно разделить на кодировки с фиксированным количеством байтов — каждому символу соответствует одинаковое количество байтов, и «плавающие», в которых один символ может представляться разным количеством байтов в зависимости от его содержимого. К первым относятся кодировки типа Unicode, в которой каждый символ представлен двумя байтами, ко вторым — UTF-8 и др. Плавающие кодировки — отдельная тема, языки С/С++ не предлагают для них никакой поддержки.
Необходимость в многобайтных кодировках возникла из-за того, что одним стандартным байтом можно представить не так много символов, например восьмибитный байт способен принимать значения от 0 до 255, а значит в такой кодировке не может существовать более 256 различных символов. Учитывая, что, например, в японском языке около двух тысяч иероглифов, 256 символов японцам явно не хватит. Но уже двух восьмибитных байтов хватит для представления 65536 символов — вполне неплохо, хотя и недостаточно для представления всех символов на свете.
Между однобайтными и фиксированными многобайтными строками принципиальной разницы нет. В С/С++ существует специальный тип для многобайтных символов — wchar_t и специальные функции для работы со строками, состоящими из таких символов. Размер wchar_t не фиксирован в стандарте и определяется реализацией компилятора. На многих платформах и компиляторах это два байта, соответствующих кодировке Unicode. Каждой функции, работающей со строками из однобайтных символов, соответствует функция — «побратим», принимающая строки из многобайтных символов. Кроме того, существует специальная форма для записи строковых литералов, в которых символы представлены несколькими байтами: перед кавычками ставится буква L. Т.е., возвращаясь к нашему первому примеру, вызов функции SetWindowText в Unicode-программе будет выглядеть так:
Фактически, на этом с точки зрения программиста разница между строками из многобайтовых и однобайтовых символов заканчивается, поэтому в дальнейшем я буду рассматривать только традиционные строки из однобайтовых символов. При необходимости вы легко найдете многобайтовые аналоги всех упомянутых в статье функций.
ПРЕДУПРЕЖДЕНИЕ
Не забывайте пользоваться правильными функциями!
Очевидно, что последовательность должна быть как-то ограничена, то есть мы должны знать, где она заканчивается. Размер строки можно было бы счесть равным размеру массива… но тогда возникают две проблемы:
размер передаваемых в функцию массивов нужно указывать отдельно, получить его в функции нельзя
такой подход не работает при использовании указателей, размер которых не зависит от длины последовательности, на которую они указывают.
А значит, нужно поступить как-то по другому. Собственно, у нас есть два варианта – связать со строкой ее размер и где-то хранить его (например, в нулевом элементе массива или специальной переменной), либо ограничить строку (последовательность) специальным символом (элементом последовательности), дойдя до которого мы будем знать, что достигли конца. Именно этот способ и принят в С, причем в качестве терминатора (ограничивающего символа) выбран символ с кодом 0, как наиболее редко встречающийся в строках. То есть в С строка – это массив элементов типа char, ограниченный символом с кодом 0, называемом нуль-терминатором. Напомню, что c массивом элементов типа char связан указатель на char, поэтому С-строка– это еще и указатель типа char* на область памяти, заканчивающуюся символом с кодом 0.
ПРИМЕЧАНИЕ
У такого подхода есть как положительные, так и отрицательные стороны. Но положительных больше. К первым относятся. 1. Натуральное представление строки в том смысле, что начало строки совпадает с началом последовательности символов 2. Неограниченная длина строки — в случае хранения длины строки отдельно длина строки не может превышать значения, максимального для переменной этого типа. Например, при использовании однобайтовых строк и хранении длины строки в первом элементе последовательности (как в Pascal) длина строки не может превышать максимального значения переменной типа char — на многих платформах это 255. 3. «Сдвинув» начало строки вправо, мы все равно получаем абсолютно нормальную строку, заканчивающуюся нулем. Строка, размер которой хранится отдельно, этой особенности лишена. Недостатки: 1. Для определения длины строки всякий раз необходимо искать ее конец, сравнивая ее символы с нулевым. Чем длиннее строка — тем больше операций. Об этом следует помнить при использовании длины строки, например, в циклах. Если между итерациями строка не изменяется, ее длину следует вычислить заранее, а потом использовать полученное значение. 2. Строки, с которыми используются стандартные функции, не могут содержать в себе нулевые символы. В редких случаях это может доставить неудобство.
Теперь с высоты наших знаний вернемся к строковым литералам. Я думаю, никого не удивит, что их тип – const char*, т.е. указатель на константные символы, а в конце каждого строкового литерала компилятор автоматически подставляет нуль-терминатор.
Символы
Символы сами по себе тоже довольно интересны. Как вы уже знаете, символьная переменная — это переменная типа char, занимающая в памяти 1 байт. В отличие от строк, символ — это встроенный интегральный тип в С/C++, для него допустимы все операции, допустимые для интегральных типов. Существуют символьные литералы, они записываются в одинарных кавычках (прямых апострофах). Пример символьного литерала:
В вышеприведенном примере значением sym является 65 в кодовой таблице ASCII. В этом случае строка sym=’A’ абсолютно эквивалентна строке sym=65. Однако, в целях улучшения портируемости лучше всегда использовать запись в апострофах — рано или поздно программу может потребоваться скомпилировать на платформе, где у символа А другой код.
Для записи символьных литералов типа wchar_t используется запись, аналогичная записи для строковых литералов этого типа:
Существует специальный формат для записи символьных литералов – слеш, за которым идет код символа. Такая форма записи необходима, если мы хотим использовать элемент, не отображающийся в печатный символ, например нуль-терминатор, который представляется так: ’\0’.
ПРИМЕЧАНИЕ
строки (1), (2) и (3) имеют один и тот же эффект. Однако вторая и третья запись считаются нагляднее – мы сразу видим, что работаем именно с символом.
ПРЕДУПРЕЖДЕНИЕ
Не путайте ‘\0’, ‘0’ и “0”. Первое – символьный литерал, соответствующий символу с кодом 0. Второе – такой же литерал, но обозначающий цифру 0, ее код в ASCII-кодировке 48. Третий — это строковый литерал, содержащий два символа, цифру 0 и нуль-терминатор.
Как я уже упоминал тип char – интегральный, а значит для него определены все операции, определенные для интегральных типов, в том числе +, -, *, / и операции сравнения. Хотя не все они имеют смысл для символов (например, я плохо себе представляю смысл перемножения двух символов), однако их использование совершенно «законно» и их результат обычно именно такой, какой ожидался.
Применение же этих операций к строкам либо вообще запрещено, либо, что еще хуже, их результат в большинстве случаев далек от ожидаемого.
Операции со строками
Создание строк
Проиллюстрирую создание строк на фрагментах кода с комментариями.
Присваивание строк
Первый и самый очевидный способ присваивания строк – присваивание отдельных символов. Например,
Однако, это совершенно неудобно. Не зная о правильных способах, начинающие программисты часто «выдумывают» свои способы присваивания строк, конечно, неправильные. Приведу несколько примеров:
Эта ошибка относительно безопасна, так как приводит к сбою на этапе компиляции. Есть и гораздо более опасная ошибка.
Этот код откомпилируется, но, возможно, содержит «идеологическую» ошибку. Неправильно полагать, что в str2 теперь содержится копия str1. На самом деле этот указатель указывает не на копию, а на ту же самую строку. При любом изменении содержимого str2 изменяется str1. Однако, если именно это и требуется, то все в порядке.
Еще один вариант присваивания указателей – присваивание их строковым литералам. Как вы помните, тип строкового литерала – const char*, а значит такой код работает:
Опять же следует помнить, что str указывает на строковый литерал, а не на его копию. Но, к сожалению, такой код тоже сработает:
Здесь мы имеем дело с наследством C, в котором отсутствовал const. Поэтому стандарт С++ разрешает такое присваивание. Что может иметь неприятные последствия:
Результат работы такой программы непредсказуем. Компилятор может разместить константы в памяти только для чтения, и попытка их изменить приведет к сбою. Поэтому всегда объявляйте указатели, в которые вы собираетесь записывать адреса строковых литералов как const char*. В этом случае компилятор не позволит модифицировать данные и диагностирует ошибку, что поможет вам исправить логику программы.
Вопросы неправильного и рискованного присваивания строк мы рассмотрели. Пришла пора обсудить правильное присваивание или копирование строк.
Для копирования строк существуют несколько библиотечных функций, наиболее общеупотребительной из которых является функция
Функция посимвольно копирует содержимое строки, на которую указывает src в строку, на которую указывает dest и возвращает dest. Так как массив может быть преобразован в указатель, такой вызов функции абсолютно легален:
При использовании этой функции следует соблюдать осторожность. Опасность заключается в том, что даже если исходная строка окажется больше, чем память, выделенная для второй строки (программистом через malloc или компилятором при использовании массивов), функция strcpy никак про это узнать не сможет и продолжит копирование в невыделенную память. Разумеется, последствия будут катастрофическими.
Снизить риск такого развития событий способна функция
Последний параметр – максимальное количество копируемых символов. Таким образом, передавая туда размер приемника, вы гарантируете, что функция никогда не выйдет за пределы выделенной памяти. Однако помните, что если исходная строка будет скопирована не полностью, нуль-терминатор не появится в результирующей строке. Его придется записать самостоятельно.
ПРЕДУПРЕЖДЕНИЕ
Никогда не забывайте контролировать используемую память!
Сравнение строк
Для лексикографического сравнения строк используются функции strcmp и stricmp. Первая сравнивает строки с учетом регистра, вторая – без. Однако, все это относится только к латинице. Если вы хотите сравнивать без учета регистра кириллические строки, придется разобраться с локалями.
Прототипы этих функций таковы:
Обе функции возвращают число меньшее 0, если первая строка меньше второй, большее нуля если первая строка больше второй и 0, если строки лексикографически равны.
Полагаю, вам не придет в голову сравнивать строки, используя операции ‘<’ и ‘>’.
Длина строки
Для вычисления длины строки используется функция
Функция возвращает длину строки, не включая нуль-терминатор. Как всегда, следите за тем, чтобы в выделенной под string памяти все же нашелся такой символ. В противном случае функция выйдет за пределы выделенной памяти и все будет плохо. Напомню, что для определения длины строки функции придется последовательно обратиться ко всем ее символам до нуль-терминатора, а значит, потенциально эта операция довольно дорогая. Поэтому, не следует использовать эту функцию в циклах, то есть вместо
больше подойдет примерно такой код :
Преобразования строк
Зачастую требуется преобразовать число в строку и наоборот. Есть несколько способов сделать это.
Во-первых, такие преобразования совсем несложно делать самостоятельно. Оставляю это в качестве домашнего задания.
Во-вторых, можно использовать функции sprintf и sscanf. Например, так:
Эти функции очень похожи на printf и scanf, за исключением того, что они работают не с консолью, а со строковым буфером. Для дополнительной информации об этих функциях см. документацию.
Хотя sprintf и sscanf довольно удобны, у них есть несколько недостатков. Во-первых, они не всегда быстро работают, во-вторых не типобезопасны. Например, если в строке формата вы укажите, что передаете два целых, а вместо этого передадите два double, ошибка обнаружится только при выполнении программы и найти ее причину будет не так-то просто.
В-третьих, доступно целое семейство функций atof, atoi, atol и itoa, ltoa. Все они очень похоже между собой. Функции из первой группы преобразуют строку в число (float, int или long) в зависимости от окончания. Функции из второй группы выполняют обратное преобразование. Должен заметить, что функции из второй группы (равно как и ранее упомянутая stricmp) не входят в стандарт С, однако они весьма удобны и доступны на некоторых платформах.
Прототипы функций из первой группы:
Функции из второй группы могут создавать строковое представление чисел в любой системе (по любому основанию) от 2 до 36. Основание передается в третьем параметре. Чтобы получить строковое представление числа в десятичной системе, передайте 10. Функции возвращают указатель на строку.
ПРИМЕЧАНИЕ
При использовании этих функций не забывайте выделять память, достаточную для предоставления строкового предоставления числа. Например, максимальная длина десятичного строкового представления четырехбайтного беззнакового целого числа — 11 байт, включая нуль-терминатор («4294967295»).
Конкатенация (объединение) строк
Сначала простой вопрос – каков результат выполнения следующего кода:
Если ответ – ошибка на этапе компиляции, материал изложенный в статье вы усвоили (или знали это раньше). Если же вы полагаете, что в str3 будет хранится строка «Hello world!», то вероятно, мои предыдущих объяснений оказалось недостаточно. Нельзя складывать указатели (и имена массивов).
Для конкатенации следует использовать функции.
Есть две специальные функции:
Эти функции добавляют к строке, на которую указывает dest, символы из строки source. Первая версия добавляет все символы до нуль-терминатора, вторая – максимум size символов. Результирующая строка завершается нуль-терминатором.
Кроме того, можно воспользоваться общей функцией sprintf так:
Этот вариант удобнее, если нужно объединить более двух строк. Однако к его недостаткам относится типонебезопасность.
Заключение
На этом первая часть, посвященная С-строкам заканчивается. В качестве домашнего задания рекомендую реализовать упоминавшиеся здесь стандартные функции, за исключением sprintf и sscanf самостоятельно. Это нетрудно, и если вы справитесь, значит, вы отлично овладели материалом. Тем не менее, всегда используйте стандартные функции, а не ваши собственные. Это общее правило – реализовать стандартные функции имеет смысл только в качестве учебного задания.
Для использования строковых функций вам потребуется подключить к программе соответствующие стандартные заголовки. Это string.h для всех функций, кроме sprintf и sscanf, определенных в stdio.h и функций преобразования, определенных в stdlib.h.
В С++ вместо вышеупомянутых заголовочных файлов следует подключать cstring, cstdio и cstdlib соответственно.
Во второй части статьи я рассмотрю возможности C++, позволяющее значительно упростить работу со строками и сделать ее более удобной.
Строки в языке C
Строка — это последовательность ASCII или UNICODE символов.
Строки в C, как и в большинстве языков программирования высокого уровня рассматриваются как отдельный тип, входящий в систему базовых типов языка. Так как язык C по своему происхождению является языком системного программирования, то строковый тип данных в C как таковой отсутствует, а в качестве строк в С используются обычные массивы символов.
Исторически сложилось два представления формата строк:
формат ANSI;
cтроки с завершающим нулем (используется в C).
Формат ANSI устанавливает, что значением первой позиции в строке является ее длина, а затем следуют сами символы строки. Например, представление строки «Моя строка!» будет следующим:
11 ‘М’ ‘о’ ‘я’ ‘ ‘ ‘с’ ‘т’ ‘р’ ‘о’ ‘к’ ‘а’ ‘!’ В строках с завершающим нулем, значащие символы строки указываются с первой позиции, а признаком завершения строки является значение ноль. Представление рассмотренной ранее строки в этом формате имеет вид:
‘М’ ‘о’ ‘я’ ‘ ‘ ‘с’ ‘т’ ‘р’ ‘о’ ‘к’ ‘а’ ‘!’ 0
Объявление строк в C
Строки реализуются посредством массивов символов. Поэтому объявление ASCII строки имеет следующий синтаксис:
char имя[длина]; Объявление строки в С имеет тот же синтаксис, что и объявление одномерного символьного массива. Длина строки должна представлять собой целочисленное значение (в стандарте C89 – константа, в стандарте C99 может быть выражением). Длина строки указывается с учетом одного символа на хранение завершающего нуля, поэтому максимальное количество значащих символов в строке на единицу меньше ее длины. Например, строка может содержать максимально двадцать символов, если объявлена следующим образом: char str[21]; Инициализация строки в С осуществляется при ее объявлении, используя следующий синтаксис: char str[длина] = строковый литерал;
Строковый литерал – строка ASCII символов заключенных в двойные кавычки. Примеры объявления строк с инициализацией:
Так как строки на языке С являются массивами символов, то к любому символу строки можно обратиться по его индексу. Для этого используется синтаксис обращения к элементу массива, поэтому первый символ в строке имеет индекс ноль. Например, в следующем фрагменте программы в строке str осуществляется замена всех символов ‘a’ на символы ‘A’ и наоборот.
Массивы строк в С
Объявление массивов строк в языке С также возможно. Для этого используются двумерные массивы символов, что имеет следующий синтаксис: char имя[количество][длина];
Первым размером матрицы указывается количество строк в массиве, а вторым – максимальная (с учетом завершающего нуля) длина каждой строки. Например, объявление массива из пяти строк максимальной длиной 30 значащих символов будет иметь вид:
При объявлении массивов строк можно производить инициализацию: char имя[количество][длина] = <строковый литерал №1, . строковый литерал №N>;
Число строковых литералов должно быть меньше или равно количеству строк в массиве. Если число строковых литералов меньше размера массива, то все остальные элементы инициализируются пустыми строками. Длина каждого строкового литерала должна быть строго меньше значения длины строки (для записи завершающего нуля).
При объявлении массивов строк с инициализацией допускается не указывать количество строк в квадратных скобках. В таком случае, количество строк в массиве будет определено автоматически по числу инициализирующих строковых литералов. Например, массив из семи строк:
Функции для работы со строками в С
Все библиотечные функции, предназначенные для работы со строками, можно разделить на три группы:
ввод и вывод строк;
преобразование строк;
обработка строк.
Ввод и вывод строк в С
Для ввода и вывода строковой информации можно использовать функции форматированного ввода и вывода (printf и scanf). Для этого в строке формата при вводе или выводе строковой переменной необходимо указать спецификатор типа %s. Например, ввод и последующий вывод строковой переменной будет иметь вид:
Недостатком функции scanf при вводе строковых данных является то, что символами разделителями данной функции являются:
перевод строки,
табуляция;
пробел.
Поэтому, используя данную функцию невозможно ввести строку, содержащую несколько слов, разделенных пробелами или табуляциями. Например, если в предыдущей программе пользователь введет строку: «Сообщение из нескольких слов», то на экране будет выведено только «Сообщение». Для ввода и вывода строк в библиотеке stdio.h содержатся специализированные функции gets и puts.
Функция gets предназначена для ввода строк и имеет следующий заголовок: char * gets(char *buffer);
Между тем использовать функцию gets категорически не рекомендуется, ввиду того, что она не контролирует выход за границу строки, что может произвести к ошибкам. Вместо нее используется функция fgets с тремя параметрами:
char * fgets(char * buffer, int size, FILE * stream);
где buffer — строка для записи результата, size — максимальное количество байт, которое запишет функция fgets, stream — файловый объект для чтения данных, для чтения с клавиатуры нужно указать stdin. Эта функция читает символы со стандартного ввода, пока не считает n — 1 символ или символ конца строки, потом запишет считанные символы в строку и добавит нулевой символ. При этом функция fgets записывает в том символ конца строки в данную строку, что нужно учитывать.
Функция puts предназначена для вывода строк и имеет следующий заголовок: int puts(const char *string);
Простейшая программа: ввод и вывод строки с использованием функций fgets и puts будет иметь вид:
Для считывания одного символа можно использовать функцию fgetc(FILE * stream) . Она считывает один символ и возвращает значение этого символа, преобразованное к типу int, если же считывание не удалось, то возвращается специальная константа EOF, равная -1. Функция возвращает значение -1 для того, чтобы можно было обрабатывать ситуацию конца файла, посимвольное чтение до конца файла можно реализовать следующим образом:
int c; while ((c = fgetc(stdin)) != EOF) < // Обработка символа >
Для вывода одного символа можно использовать функцию int fputc(int c, FILE *stream); .
Помимо функций ввода и вывода в потоки в библиотеке stdio.h присутствуют функции форматированного ввода и вывода в строки. Функция форматированного ввода из строки имеет следующий заголовок:
Функции форматированного вывода в строку имеют следующие заголовки:
Преобразование строк
В С для преобразования строк, содержащих числа, в численные значения в библиотеке stdlib.h предусмотрен следующий набор функций: double atof(const char *string); // преобразование строки в число типа double int atoi(const char *string); // преобразование строки в число типа int long int atol(const char *string); // преобразование строки в число типа long int long long int atoll(const char *string); // преобразование строки в число типа long long int
Корректное представление вещественного числа в текстовой строке должно удовлетворять формату:
После символов E, e указывается порядок числа. Корректное представление целого числа в текстовой строке должно удовлетворять формату:
Помимо приведенных выше функций в библиотеке stdlib.h доступны также следующие функции преобразования строк в вещественные числа:
Аналогичные функции присутствуют и для преобразования строк в целочисленные значения:
Функции обратного преобразования (численные значения в строки) в библиотеке stdlib.h присутствуют, но они не регламентированы стандартом, и рассматриваться не будут. Для преобразования численных значений в строковые наиболее удобно использовать функции sprintf и snprintf.
Обработка строк
В библиотеке string.h содержаться функции для различных действий над строками. Функция вычисления длины строки: size_t strlen(const char *string);
Функции копирования строк:
Функции сравнения строк:
Функции осуществляют сравнение строк по алфавиту и возвращают:
положительное значение – если string1 больше string2; отрицательное значение – если string1 меньше string2; нулевое значение – если string1 совпадает с string2;
Функции объединения (конкатенации) строк:
Функции поиска символа в строке:
Функция поиска строки в строке: char * strstr(const char *str, const char *substr);
Функция поиска первого символа в строке из заданного набора символов: size_t strcspn(const char *str, const char *charset);
Функции поиска первого символа в строке не принадлежащему заданному набору символов: size_t strspn(const char *str, const char *charset);
Функции поиска первого символа в строке из заданного набора символов: char * strpbrk(const char *str, const char *charset);
Функция поиска следующего литерала в строке: char * strtok(char * restrict string, const char * restrict charset);
Как сложить строки в c
Для того, чтобы точно знать, сколько выделить памяти для хранения строки, часто нам нужно получить длину конкретной строки. Разного рода функции иногда предлагают не просто ввести строку, а указать какое количество символов из неё мы хотели бы использовать. Чаще всего мы хотим ввести всю строку, поэтому есть смысл не считать вручную, а просто измерить длину строки и подставить в функцию.
#include<iostream.h> #include<string.h>
> Функция strlen() возвращает количество символов в строке. Например для строки "машина" она вернёт число 6.
Вопрос: Как сложить две строки?
Помните мы писали программу, которая c нами здоровалась? Там была строка:
И умная программка добавляла в конец ваше имя.
А если мы хотим сложить две строки, которые заранее не известны? Для этого в программировании применяется операция "склеивания" — конкатенции (знаю — жуткое слово!), которая позволяет просто сложить две строки вместе и получить одну. В языке СИ такой операции нет, но зато в библиотеке string.h есть специальная функция strncat(), которая позволяет склеивать исходную строку с подстрокой.
Функция strcpy(to, from) копирует строку from в строку to. Кстати, это ещё один из способов присвоения строке значения. Функция strncat объединяет две строки, дописывая в конец строки kuda строку otkuda. Этот процесс можно сравнить с локомотивом, в конец которого на товарной станции прицепляют ещё один вагончик. Для каждого нового вагона нам понадобится функция strncat.
Обратите внимание, что у функции strncat три аргумента. Функция гибка и позволяет скопировать не всю строку, а только её часть. Если мы точно знаем сколько символов в строке otkuda, можно третьим аргументом задать число, но проще всего измерить длину строки с помощью известной нам уже функции strlen().
Вопрос: КАК из строки сделать число?
Для чего это нужно? Часто программа получает данные от пользователя в виде строки. Например, данные полученные из текстовых полей Windows — всегда строковые. И даже если пользователь ввёл число, оно всё равно будет представлено, как строка. Чтобы подставить это число в форумулу, вам придётся его конвертировать в строку.
#include <stdlib.h> #include <iosatream.h>
void main() < int n; char *str="123456"; //объявляем строку
n=atoi(str); //конвертируем в число
В некоторых классах Windows есть методы, позволяющие сразу получать целые числа из текстовых полей, но метода, позволяющего сразу считывать числа с плавающей запятой, нет, поэтому в любом случае вам понадобится конвертирование. Для преобразования строки в число с плавающей запятой существует функция atof(char *str), которая по своему применению аналогична.
В этом примере мы используем новую для нас библиотеку "stdlib.h" — в данной библиотеке содержится большинство функций конвертирования, а также множество других функций самого разного назначения. Это библиотека очень широкого профиля.
После получения строки из поля ввода, конвертирования в число, подстановки в формулу, или куда-то ещё, ответ зачастую придётся выводить в какое-нибудь окно. Естественно, напрямую число в окно мы не сможем — оно нас просто не поймёт, и заставит выполнять обратное преобразование — теперь уже из числа в строку. Иногда ответ должен сопровождаться каким-то текстом, например: "U = 127,24 v ". Для создания такого дружественного интерфейса удобнее использовать не саму операцию конвертирования, а записать в один буфер и число и текст.
Функция sprintf аналогична функции printf, с той лишь разницей, что вывод информации осуществляется не в какое-то устройство вывода, а в буфер данных. Используя эту функцию, мы сможем комбинировать различные типы данных в любой последовательности.
void main() < char buffer[25]; //строка, длиной 25 символов const double f=127.24; //число с плавающей точкой
sprintf(buffer, "U = %3.2f V\n", f);
Однако, если нам необходимо просто преобразовать число в строку, можно использовать функцию конвертирования, описанную в следующем разделе.
Конвертирование — очень важная задача, и в математических программах без неё никуда. Если ваша программа что-то считает, получая данные от пользователя, вы будете использовать конвертирование постоянно.
Вопрос: КАК что-то найти в строке?
Иногда нам нужно проверить строку на наличие в ней какого-нибудь символа. Например, если пользователь вводит свой E-mail хорошо бы проверить, есть ли в нём символ "at", который в России чаще называют собакой @? Кроме того, в каждом адресе электронной почты есть по крайней мере одна точка. Следующая программа последовательно проверяет наличие в строке знака "@" и точки.
void main() < char *ptr;
ptr = (char *)memchr(email, ‘@’, strlen(email));
ptr = (char *)memchr(email, ‘.’, strlen(email));
printf("\n%s не является E-mail адресом", email);
> else < printf("\nE-mail адрес: %s успешно введён!", email);
Переменная flag — своеобразный выключатель, который имеет два состояния "истина" и "ложь". Такие переменные программисты часто так и называют "флагами" или "флажками". Они обозначают состояние. Первоначально переменная flag = 0. Но если программа не обнаруживает в строке знака "@" или точки, значение flag поменяется на 1. Условий может быть и больше (например, мы могли бы проверить, есть ли какие-то символы до знака "@" и находится ли точка после знака @, а не до него), однако работать они будут по тому же принципу. В конце программы проверяется значение переменной flag, и если она изменила своё значение, E-mail адрес признаётся некорректным. При разработки больших программ подобные проверки необходимо производить буквально на каждом шагу, они делают программу более надёжной и отказоустойчивой, страхуя нас от недопустимого типа данных. Согласитесь, если мы попытаемся отправить письмо на некорректный адрес, приятного в этом будет мало! Однако во избежание увеличения кода, мы заключим процедуру проверки адреса в маленькую проверочную функцию вида:
int IsEmail(char *email)
которая будет возвращать 0, если E-mail адрес был введён недостаточно корректно.
Часто производители программного обеспечения требуют регистрации и предлагают пользователю заполнить форму, в которой в том числе есть просьба указать электронный адрес. Знать ваш E-mail адрес производителю жизненно важно, чтобы впоследствии заваливать вас своей рекламой. Поэтому если вы введёте свой адрес неправильно или вообще проигнорируете эту просьбу, электронная форма откажется вас регистрировать.
Функция поиска в строке незаменима при работе с файлами (а храним мы данные именно в них — лучшего пока ничего не придумали). Есть и другие применения поиска: когда программа предлагает пользователю сохранить файл, с которым он работал, необходимо ввести его имя. Однако пользователи попадаются с совершенно разным профессиональным уровнем: кто-то вводит имена файлов с расширениями: "picture.bmp" , а кто-то и не знает, что такое расширение. Нужно предусмотреть оба случая, и в случае, если пользователь просто ввёл имя: "моя картинка. " добавить к этому имени расширение. Пример решения этой проблемы приведён ниже.
Часто, когда вы получаете от пользователя не числовые данные, а строковые, вам надо сравнить строку с уже имеющимся эталоном. Допустим, вы хотите, чтобы пользователь зарегистрировал свою версию вашей программы и ввёл пароль. Для этого и нужна функция сравнения строк strncmp(). Она возвращает ноль, если строки одинаковы.
Следующая ниже программа вполе функциональна. Она ни за что не пропустит построннего на ваш компьютер. При желании, вы можете включить её в файл autoexec.bat. Доступ посторонним будет закрыт навеки! (Конечно если вы всё ещё пользуетесь Windows 98).
#include <stdio.h> #include <string.h>
char *password="BUSH"; //наш пароль char enter[256]; //строка, которую будет вводить пользователь
//сравниваем две строки по всей длине ptr = strncmp(enter, password, strlen(password));
> >while(ptr!=0); //цикл повторяется, пока ptr не будет =0. return 0; > Конечно в реальности никто не будет прописывать пароль в коде программы, иначе мало-мальски грамотный хаккер запросто его оттуда "достанет". Настоящие программы генерируют пароль из имени пользователя, таким образом, чтобы пароли никогда не повторялись. Также популярна онлайн регистрация, при которой пользователь получает пароль, регистрируясь на сайте фирмы. Получив такой пароль, программа преобразует его по сложному алгоритму и проверяет на соответствие какому-то правилу — например сумма кодов символов должна всегда быть равна 100 (или что-то в этом роде). Дружеский совет — никогда не давайте "говорящие" пароли, как в данном примере. Такие пароли разгадываются элементарно, путём простого перебора символов. Хороший пароль должен содержать цифры и буквенные символы разного регистра, также неплохо было бы включить в него специальные символы. В результате должно получиться нечто вроде: "$&weh!60Om17rv".