Как сравнить char строки
Перейти к содержимому

Как сравнить char строки

Строковые переменные

Встроенный тип данных «строка» в C/C++ отсутствует. С точки зрения компилятора и стандартной библиотеки, строка — это массив элементов типа char , последним элементом которого является двоичный ноль (то есть символ, код которого равен нулю). Допускается присваивание таким массивам так называемых строковых литералов — последовательностей символов, заключенных в двойные кавычки. В конец каждого такого литерала компилятор автоматически добавляет символ с кодом 0. Рассмотрим небольшой пример:

Все три объявления в приведенном выше примере абсолютно равнозначны; заметим, что в случае «формальной» инициализации (переменная str2 ) требуется явное указание завершающего нулевого символа. В соответствии с тем, что компилятор C/C++ воспринимает имя массива как адрес его первого элемента, синтаксически допустимым и правильным является объявление переменной str3 как указателя. Именно эта форма и используется чаще всего; более того, все функции стандартной библиотеки, предназначенные для работы со строками, используют именно формальный тип «указатель на char ».

Необходимо отметить, что компилятор от Microsoft помещает строковые литералы в область, доступную только для чтения, поэтому выполнение следующего примера завершается с ошибкой «access violation»:

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

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

Управляющие последовательности

Для вставки в строку «непечатных» (с кодами меньше 32) или имеющих в C/C++ специальное значение символов, необходимо использовать так называемые управляющие последовательности , начинающиеся с символа «обратный слэш»:

\a — звонок;
\t — горизонтальная табуляция;
\r — возврат каретки;
\n — перевод строки;
\b — «забой» (backspace);
\’ — апостроф;
\» — двойная кавычка;
\\ — обратный слэш;
\0 — символ с кодом 0.

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

\ooo — символ с восьмеричным кодом ooo;
\xHH — символ с шестнадцатеричным кодом HH.

Следующие две строки эквивалентны:

Функции обработки строк

Заголовочный файл #include <string.h>

Еще раз подчеркнем, что все функции обработки строк, предоставляемые стандартной библиотекой, считают признаком конца строки первый символ с кодом 0, который присутствует в этой строке. Таким образом, при выполнении любых действий над строковой переменной, объявленной как

слово «City» будет игнорироваться. Рассмотрим основные функции обработки строк.

strcpy

Копирует строку src в dest и возвращает dest . Пример использования:

strcat

«Дописывает» строку src в конец строки dest и возвращает dest . Пример использования:

strlen

Возвращает длину строки src , то есть количество символов до завершающего нуля. Пример использования:

strchr

Ищет первое вхождение символа chr в строку src и возвращает указатель на часть строки, начинающуюся с искомого символа. Если символ chr отсутствует в строке src , функция возвращает NULL. Пример использования:

strrchr

Ищет последнее вхождение символа chr в строку src и возвращает указатель на часть строки, начинающуюся с искомого символа. Если символ chr отсутствует в строке src , функция возвращает NULL. Пример использования:

strstr

Ищет первое вхождение строки substr в строку str и возвращает указатель на часть строки, начинающуюся с искомой подстроки. Если строка substr отсутствует в строке str , функция возвращает NULL. Пример использования:

strcmp

Сравнивает строки str1 и str2 . Если эти строки эквивалентны, функция возвращает 0; в противном случае, возвращается отрицательное значение, если строка str1 «меньше» строки str2 , или положительное значение, если строка str1 «больше» строки str2 . «Меньше» и «больше» в данном случае определяется разницей кодов первых несовпадающих символов. Пример использования:

_stricmp

Сравнивает строки str1 и str2 аналогично функции strcmp , но без учета регистра символов. Данная функция не входит в стандарт ANSI и относится к категории «Microsoft specific». Пример использования:

strtok

Последовательно разбивает строку src на лексемы (токены), считая разделителями все символы строки seps . При каждом вызове возвращается указатель на очередную найденную лексему или NULL, если достигнут конец строки src . Отметим, что данная функция модифицирует исходную строку. Пример использования:

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

Текстовый ввод/вывод

Заголовочный файл #include <stdio.h>

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

Выводит на экран строку src , переводит курсор в начало следующей строки экрана и возвращает количество выведенных символов.

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

Функции преобразования данных

Заголовочный файл #include <stdlib.h>

Поскольку Windows-приложения имеют возможность обмениваться с пользователем только текстовой информацией, периодически возникает необходимость преобразования чисел в их строковое представление и обратно — для обработки в программе уже двоичных данных. Ниже мы рассмотрим функции, предназначенные для выполнения таких преобразований.

Преобразует строковое представление целого числа str в двоичное и возвращает его. Преобразование прекращается на первом недопустимом символе; если такой символ окажется самым первым в строке, функция вернет значение 0. Пример использования:

Под Win32 эта функция полностью аналогична atoi .

strtol

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

которая преобразует строковое представление целого числа str в системе счисления с основанием radix в двоичное и возвращает его. При этом, в переменную по адресу end_ptr будет записан указатель на символ, который прервал обработку строки str . Пример использования:

Преобразует строковое представление дробного числа str в двоичное и возвращает его. Преобразование прекращается на первом недопустимом символе; если такой символ окажется самым первым в строке, функция вернет значение 0.0. Заметим, что исходная строка может содержать как десятичное, так и экспоненциальное представление дробного числа.

strtod

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

которая преобразует строковое представление дробного числа str в двоичное и возвращает его, записывая по адресу end_ptr указатель на символ, который прервал обработку строки str . Использование этой функции аналогично strtol .

_itoa

Записывает по адресу dest строковое представление целого числа number по основанию radix и возвращает dest . Пример использования:

_ltoa

Под Win32 эта функция полностью аналогична _itoa .

_ultoa

Аналогична функции _ltoa , но предназначена для преобразования беззнаковых целых чисел.

_gcvt

Записывает по адресу dest строковое представление дробного числа number и возвращает dest . Через параметр num_dig необходимо передать требуемое число знаков строкового представления. Заметим, что если данная функция не сможет представить исходное число в десятичной форме с требуемым количеством знаков, то будет выбрана экспоненциальная форма. При преобразовании в десятичную форму, функция отбрасывает незначащие нули.

_fcvt

Возвращает адрес буфера, содержащего строковое представление дробного числа number в десятичной форме; заметим, что этот буфер перезаписывается при каждом вызове функции. Через параметр num_dec необходимо передать требуемое количество десятичных знаков; при необходимости исходное число будет округлено или дополнено нулями. В переменную по адресу dec_pos будет записана требуемая позиция десятичной точки в возвращенной строке; при этом, если целая часть числа равна 0, то по этому адресу будет записано отрицательное или нулевое значение. В переменную по адресу has_sign записывается ненулевое значение для отрицательного исходного числа и 0 — в противном случае.

Таким образом, возвращаемая данной функцией строка не содержит ни знака, ни десятичной точки; ниже рассматривается несколько примеров:

_ecvt

Данная функция полностью аналогична _fcvt , за исключением того, что исходное число представляется в экспоненциальной форме. Заметим, что эта функция использует тот же буфер, что и _fcvt .

sprintf

Записывает в буфер по адресу dest строку, сформированную на основании форматирующей строки fmt и произвольного количества необязательных аргументов. Строка fmt , помимо обычных символов, может содержать так называемые форматирующие последовательности . Каждая такая последовательность соответствует одному необязательному аргументу; она начинается с символа «%» и имеет в общем случае форму

Здесь t — это один символ, определяющий тип аргумента, строковое представление которого должно быть подставлено на место данной форматирующей последовательности, и вид выполняемого преобразования. Это обязательная составляющая форматирующей последовательности; допустимо использование следующих символов:

t ожидаемый
тип аргумента
вид преобразования
c char
d
i
int или long в десятичной системе
u unsigned int в десятичной системе
o unsigned int в восьмеричной системе
x unsigned int в шестнадцатеричной системе, буквы a…f строчные
X unsigned int в шестнадцатеричной системе, буквы A…F заглавные
e double в экспоненциальной форме, буква e строчная
E double в экспоненциальной форме, буква E заглавная
f double в десятичной форме
g double в наиболее компактной форме, буква e строчная
G double в наиболее компактной форме, буква E заглавная
p void* в шестнадцатеричной системе, буквы A…F заглавные
s char* параметр интерпретируется как строка C/C++

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

символ значение
преобразованный аргумент выравнивается по левому краю (по умолчанию — по правому)
+ знак отображается при любом значении аргумента (по умолчанию — только при отрицательном)
0 «лишние» позиции заполняются символом «0» (по умолчанию — пробелом)
пробел при положительном значении аргумента на месте знака выводится пробел
# o к преобразованному аргументу добавляется префикс «0»
x к преобразованному аргументу добавляется префикс «0x»
X к преобразованному аргументу добавляется префикс «0X»
e
E
f
преобразованный аргумент будет содержать десятичную точку даже при отсутствии дробной части
g
G
преобразованный аргумент будет содержать десятичную точку даже при отсутствии дробной части;
при необходимости дробная часть дополняется незначащими нулями

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

Необязательная составляющая p определяет точность представления аргумента; ее интерпретация зависит от типа этого аргумента:

тип p
e
E
f
требуемое количество знаков после десятичной точки; при необходимости выполняется
округление аргумента или дополнение его дробной части незначащими нулями
g
G
максимальное количество значащих цифр
s максимальное количество символов аргумента, которое следует использовать

Необязательная составляющая s «уточняет» размер целочисленного аргумента и может быть одним из следующих символов:

символ размер аргумента
l long
h short

Если в формируемую строку необходимо вставить символ «%», то его следует написать два раза подряд. Ниже приведен пример использования функции sprintf :

Как сравнить char строки

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

#include<iostream.h>
#include<string.h>

>

Функция strlen() возвращает количество символов в строке. Например для строки "машина" она вернёт число 6.

Вопрос: Как сложить две строки?

Помните мы писали программу, которая c нами здоровалась? Там была строка:

И умная программка добавляла в конец ваше имя.

А если мы хотим сложить две строки, которые заранее не известны? Для этого в программировании применяется операция "склеивания" — конкатенции (знаю — жуткое слово!), которая позволяет просто сложить две строки вместе и получить одну. В языке СИ такой операции нет, но зато в библиотеке string.h есть специальная функция strncat(), которая позволяет склеивать исходную строку с подстрокой.

void main()
<
char kuda[20];
char *otkuda="Федерация";

strcpy(kuda, "Российская "); //копирование в строку kuda

strncat(kuda, otkuda, strlen(otkuda)); //склеиваем две строки, результат — в строке kuda

cout<<kuda; //на экране фраза "Российская Федерация".

Функция 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);

Однако, если нам необходимо просто преобразовать число в строку, можно использовать функцию конвертирования, описанную в следующем разделе.

Вопрос: КАК из числа сделать строку?

#include<stdio.h>

void main()
<
int number = 123456;
char *string;

itoa(number, string, 10); //число 10 заботится о добавлении минуса отрицательными числам
printf("Число = %d Строка = %s", number, string);
>

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

Вопрос: КАК что-то найти в строке?

Иногда нам нужно проверить строку на наличие в ней какого-нибудь символа. Например, если пользователь вводит свой 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" , а кто-то и не знает, что такое расширение. Нужно предусмотреть оба случая, и в случае, если пользователь просто ввёл имя: "моя картинка. " добавить к этому имени расширение. Пример решения этой проблемы приведён ниже.

#include<stdio.h>
#include<string.h>
#include<conio.h>

void main()
<
char *ptr;

printf("Введите имя файла: ");
scanf("%s", filename);

ptr = (char *)memchr(filename, ‘.w’, strlen(filename));

printf("\nИмя файла: %s", filename);

Вопрос: КАК сравнить две строки?

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

Следующая ниже программа вполе функциональна. Она ни за что не пропустит построннего на ваш компьютер. При желании, вы можете включить её в файл autoexec.bat. Доступ посторонним будет закрыт навеки! (Конечно если вы всё ещё пользуетесь Windows 98).

#include <stdio.h>
#include <string.h>

char *password="BUSH"; //наш пароль
char enter[256]; //строка, которую будет вводить пользователь

//сравниваем две строки по всей длине
ptr = strncmp(enter, password, strlen(password));

//если строки одинаковы
if (ptr == 0)
<
printf("Доступ открыт");
return(0);
>

else <
printf("Введён неверный пароль!");

>
>while(ptr!=0); //цикл повторяется, пока ptr не будет =0.
return 0;
>

Конечно в реальности никто не будет прописывать пароль в коде программы, иначе мало-мальски грамотный хаккер запросто его оттуда "достанет". Настоящие программы генерируют пароль из имени пользователя, таким образом, чтобы пароли никогда не повторялись. Также популярна онлайн регистрация, при которой пользователь получает пароль, регистрируясь на сайте фирмы. Получив такой пароль, программа преобразует его по сложному алгоритму и проверяет на соответствие какому-то правилу — например сумма кодов символов должна всегда быть равна 100 (или что-то в этом роде). Дружеский совет — никогда не давайте "говорящие" пароли, как в данном примере. Такие пароли разгадываются элементарно, путём простого перебора символов. Хороший пароль должен содержать цифры и буквенные символы разного регистра, также неплохо было бы включить в него специальные символы. В результате должно получиться нечто вроде: "$&weh!60Om17rv".

Работа со строками на этапе компиляции в современном C++

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

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

Зачем все это нужно

В одном из проектов, над которым я работал, было принято использовать std::string в качестве строковых констант. В проекте было несколько модулей, в которых были определены глобальные строковые константы:

Думаю, вы уже догадались, что случилось в один прекрасный день. SAMPLE_PLUGIN_PATH приняла значение «sample.so» , несмотря на то, что PLUGIN_PATH имела значение «/usr/local/lib/project/plugins/» , как и ожидалось. Как это могло произойти? Все очень просто, порядок инициализации глобальных объектов не определен, в момент инициализации SAMPLE_PLUGIN_PATH переменная PLUGIN_PATH была пуста.

Кроме того, такой подход имеет еще ряд недостатков. Во-первых, брошенное при создании глобального объекта исключение не отлавливается. Во-вторых, инициализация происходит при выполнении программы, что тратит драгоценное процессорное время.

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

В этой статье рассмотрим строки, операции над которыми можно проводить на этапе компиляции. Назовем такие строки статическими.

Все реализованные операции были включены в библиотеку для работы со статическими строками. Исходные коды библиотеки доступны на github, ссылка в конце статьи.

Для использования библиотеки требуется как минимум C++14.

Определение статической строки

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

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

Создание статической строки

Посмотрите на определение строки hello выше, оно просто ужасно. Во-первых, нам нужно заранее вычислять длину массива. Во-вторых, нужно не забыть записать нулевой символ в конец. В-третьих, все эти запятые, скобки и кавычки. Определенно, с этим нужно что-то делать. Хотелось бы написать как-нибудь так:

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

Уже лучше, но индексы все же приходится писать руками. Здесь также отметим, что если указывать не все индексы, то можно получить подстроку строкового литерала, а если записать их в обратном порядке, то и его инверсию:

Это соображение очень пригодится нам в дальнейшем.

Теперь нам нужно как-то сгенерировать последовательность индексов строки. Для этого применим трюк с наследованием. Определим пустую структуру (нужно же что-то наследовать) с набором искомых индексов в качестве шаблонных параметров:

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

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

В итоге, функция создания статической строки будет выглядеть так:

Напишем аналогичную функцию для статической строки, она пригодится нам далее:

В дальнейшем, для каждой функции, принимающей строковый литерал foo(const char (& str)[Size]) будем писать аналогичную функцию, принимающую статическую строку foo(const static_string<Size>& str) . Но я, для краткости, упоминать об этом не буду.

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

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

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

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

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

Вывод статической строки в поток

Здесь все просто. Так как наша строка оканчивается нулевым символом, достаточно вывести в поток данные массива:

Преобразование статической строки в std::string

Здесь тоже ничего сложного. Инициализируем строку данными массива:

Сравнение статических строк

Будем сравнивать строки посимвольно, пока не выявим различия, либо не достигнем конца хотя бы одной из строк. Поскольку constexpr for еще не изобрели, воспользуемся рекурсией и тернарным оператором:

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

Такая версия компаратора позволит нам сравнивать не только строки целиком, но и отдельные подстроки.

Конкатенация статических строк

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

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

Операции поиска в статической строке

Рассмотрим операции поиска символа и подстроки в статической строке.

Поиск символа в статической строке

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

Константа static_string_npos указывает на то, что поиск не увенчался успехом. Определим ее следующим образом:

Аналогично реализуем поиск в обратном направлении:

Определение вхождения символа в статическую строку

Для определения вхождения символа достаточно попробовать поискать его:

Подсчет количества вхождений символа в статическую строку

Подсчет количества вхождений реализуется тривиально:

Поиск подстроки в статической строке

Так как предполагается, что статические строки будут относительно небольшими, не будем здесь реализовывать алгоритм Кнута-Морриса-Пратта, реализуем простейший квадратичный алгоритм:

Аналогично реализуем поиск в обратном направлении:

Определение вхождения подстроки в статическую строку

Для определения вхождения подстроки достаточно попробовать поискать ее:

Определение, начинается/кончается ли статическая строка с/на заданной подстроки

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

Аналогично для окончания статической строки:

Работа с подстроками статической строки

Здесь рассмотрим операции, связанные с подстроками статической строки.

Получение подстроки, префикса и суффикса статической строки

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

Реализуем получение подстроки с проверкой начала и конца подстроки с помощью static_assert :

Префикс — это подстрока, начало которой совпадает с началом исходной статической строки:

Аналогично для суффикса, только совпадает конец:

Разделение статической строки на две части по заданному индексу

Чтобы разделить статическую строку по заданному индексу, достаточно вернуть префикс и суффикс:

Реверсирование статической строки

Для реверсирования статической строки напишем генератор индексов, который генерирует индексы в обратном порядке:

Теперь реализуем функцию, которая реверсирует статическую строку:

Вычисление хэша статической строки

Вычислять хэш будем по следующей формуле:

H(s) = (s0 + 1) ⋅ 33 0 + (s1 + 1) ⋅ 33 1 +… + (sn — 1 + 1) ⋅ 33 n — 1 + 5381 ⋅ 33 n mod 2 64

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

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

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

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

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

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

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

Следует также учесть случай, когда первоначальное число равно нулю, в этом случае нужно вернуть нулевой символ, иначе нуль будет преобразован в пустую последовательность символов, а потом и в пустую строку:

Реализованный генератор прекрасно работает для положительных чисел, но не пригоден для отрицательных. Определим новый генератор, добавив в начало еще один шаблонный параметр — знак преобразуемого числа:

Будем обрабатывать число также, как показано выше, но с учетом знака:

Здесь есть один тонкий момент, обратите внимание на -(Value % 10) . Здесь нельзя -Value % 10 , так как диапазон отрицательных чисел на одно число шире диапазона положительных и модуль минимального числа выпадает из множества допустимых значений.

Отбрасываем число после обработки, если оно отрицательно, добавим символ знака минуса:

Отдельно позаботимся о преобразовании нуля:

Наконец, реализуем функции преобразования:

Преобразование статической строки в число

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

Для преобразования знаковых чисел, нужно учесть, что отрицательные числа начинаются с символа знака минуса:

Вопросы удобства использования библиотеки

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

Объект статической строки

Упакуем строку и реализованные методы в объект. Это позволит использовать более короткие имена методов, а также реализовать операторы сравнения:

Операторы сравнения

Использование компаратора в виде функции неудобно и нечитаемо. Определим глобальные операторы сравнения:

Аналогично реализуем остальные операторы > <= >= == !=, для всех вариаций аргументов статических строк и строковых литералов. Здесь приводить их нет смысла из-за тривиальности.

Макросы работы с числами

Для удобства преобразования числа в статическую строку и обратно определим соотвествующие макросы:

Примеры использования библиотеки

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

Конкатенация статических строк и строковых литералов:

Конкатенация статических строк, строковых литералов и чисел:

Итерация по символам в обоих направлениях:

Ссылки

Библиотеку, реализующую все вышеперечисленное, можно взять в моем github

Спасибо за внимание, замечания и дополнения приветствуются.

Update

Реализовал пользовательский литерал _ss для создания статических строк из строковых литералов:

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

Добавил шаблонный параметр Char вместо char:

Сделал специализации для char и whar_t, нижние используются в качестве неймспейсов, чтобы дергать статическую concat, которую внес в структуру статической строки:

Теперь все работает и для «широких» литералов:

Поправил метод size(), теперь size() и length() возвращают длину строки без учета нулевого символа, для получения размера массива нужно использовать sizeof():

Обновленная версия лежит на github
Спасибо всем за полезные комментарии.

Update 2

В ходе обсуждения с AndreySu появился еще один способ реализации статических строк, где символы передаются как шаблонные параметры:

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

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

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