\section{Строки} \subsection{Основные понятия} Получив в предыдущих разделах представление об указателях и массивах, и вскользь несколько раз упомянув строки, пришла пора изучить их подробнее. Итак, что же такое \textbf{строка}. В повседневной жизни строка \textit{это набор или последовательность символов}. Так вот в языке С строка - это тоже последовательность символов. А последовательности значений, в том числе символьных, в языке С представлены, как вы, уже знаете, массивами и указателями. Никакого примитива \code{string} в языке С нет. Как бы нам этого не хотелось - его нет. Но есть и хорошая новость, примитива \code{string} не существует и в других языках (здесь имеются ввиду, конечно, си-подобные языки, то есть примерно треть вообще всех языков программирования, известных сегодня). Раз строка - это массив или указатель, это всегда ссылочный тип данных. Например, строку можно объявить двумя способами: \begin{figure}[h!] \begin{lstlisting}[language=C,style=CCodeStyle] // строка char str[50] = {'h','e','l','l','o',' ','w','o','r','l','d','\0'}; // литерал char* str = "Hello world"; \end{lstlisting} \end{figure} Раз строка - это набор символов, давайте немного вспомним что такое сиволы и как с ними работать. Как вам уже известно, символьная переменная это переменная типа \code{char}. В отличие от строки это примитивный, числовой, тип данных, и к нему применимы все операции допустимые для примитивов, такие как присваивание, сложение, вычитание, умножение, деление, хотя не все имеют смысл, так автор, например с трудом может представить ситуацию в которой необходимо умножать или делить коды символов, кроме, пожалуй, криптографии. Обратим внимание на следующую запись: \begin{figure}[h!] \begin{lstlisting}[language=C,style=CCodeStyle] char c0 = 75; сhar с1 = ‘К’; \end{lstlisting} \end{figure} Здесь значением \code{c0} является \code{75}, что абсолютно эквивалентно значению переменной \code{c1}, равной символу \code{K}. То есть можно сказать, что преобразование чисел в символы и наоборот согласно таблицы, наподобие ASCII, частично приведённой на стр. \hyperref[table:ascii]{\pageref{table:ascii}}, встроена в работу компилятора языка С. Однако для улучшения читаемости кода лучше использовать вариант \code{sym = 'K'}. \begin{figure}[h!] \begin{tabular}{||c|c|c||c|c|c||c|c|c||c|c|c||} \hline dec & hex & val & dec & hex & val & dec & hex & val & dec & hex & val \\ \hline 000 & 0x00 & (nul) & 032 & 0x20 & \textvisiblespace & 064 & 0x40 & @ & 096 & 0x60 & \textquoteleft \\ 001 & 0x01 & (soh) & 033 & 0x21 & ! & 065 & 0x41 & A & 097 & 0x61 & a \\ 002 & 0x02 & (stx) & 034 & 0x22 & " & 066 & 0x42 & B & 098 & 0x62 & b \\ 003 & 0x03 & (etx) & 035 & 0x23 & \# & 067 & 0x43 & C & 099 & 0x63 & c \\ 004 & 0x04 & (eot) & 036 & 0x24 & \$ & 068 & 0x44 & D & 100 & 0x64 & d \\ 005 & 0x05 & (enq) & 037 & 0x25 & \% & 069 & 0x45 & E & 101 & 0x65 & e \\ 006 & 0x06 & (ack) & 038 & 0x26 & \& & 070 & 0x46 & F & 102 & 0x66 & f \\ 007 & 0x07 & (bel) & 039 & 0x27 & \textquotesingle & 071 & 0x47 & G & 103 & 0x67 & g \\ 008 & 0x08 & (bs) & 040 & 0x28 & ( & 072 & 0x48 & H & 104 & 0x68 & h \\ 009 & 0x09 & (tab) & 041 & 0x29 & ) & 073 & 0x49 & I & 105 & 0x69 & i \\ 010 & 0x0A & (lf) & 042 & 0x2A & * & 074 & 0x4A & J & 106 & 0x6A & j \\ 011 & 0x0B & (vt) & 043 & 0x2B & + & 075 & 0x4B & K & 107 & 0x6B & k \\ 012 & 0x0C & (np) & 044 & 0x2C & \textquoteright & 076 & 0x4C & L & 108 & 0x6C & l \\ 013 & 0x0D & (cr) & 045 & 0x2D & - & 077 & 0x4D & M & 109 & 0x6D & m \\ 014 & 0x0E & (so) & 046 & 0x2E & . & 078 & 0x4E & N & 110 & 0x6E & n \\ 015 & 0x0F & (si) & 047 & 0x2F & / & 079 & 0x4F & O & 111 & 0x6F & o \\ 016 & 0x10 & (dle) & 048 & 0x30 & 0 & 080 & 0x50 & P & 112 & 0x70 & p \\ 017 & 0x11 & (dc1) & 049 & 0x31 & 1 & 081 & 0x51 & Q & 113 & 0x71 & q \\ 018 & 0x12 & (dc2) & 050 & 0x32 & 2 & 082 & 0x52 & R & 114 & 0x72 & r \\ 019 & 0x13 & (dc3) & 051 & 0x33 & 3 & 083 & 0x53 & S & 115 & 0x73 & s \\ 020 & 0x14 & (dc4) & 052 & 0x34 & 4 & 084 & 0x54 & T & 116 & 0x74 & t \\ 021 & 0x15 & (nak) & 053 & 0x35 & 5 & 085 & 0x55 & U & 117 & 0x75 & u \\ 022 & 0x16 & (syn) & 054 & 0x36 & 6 & 086 & 0x56 & V & 118 & 0x76 & v \\ 023 & 0x17 & (etb) & 055 & 0x37 & 7 & 087 & 0x57 & W & 119 & 0x77 & w \\ 024 & 0x18 & (can) & 056 & 0x38 & 8 & 088 & 0x58 & X & 120 & 0x78 & x \\ 025 & 0x19 & (em) & 057 & 0x39 & 9 & 089 & 0x59 & Y & 121 & 0x79 & y \\ 026 & 0x1A & (eof) & 058 & 0x3A & : & 090 & 0x5A & Z & 122 & 0x7A & z \\ 027 & 0x1B & (esc) & 059 & 0x3B & ; & 091 & 0x5B & [ & 123 & 0x7B & \char`\{ \\ 028 & 0x1C & (fs) & 060 & 0x3C & < & 092 & 0x5C & \char`\\ & 124 & 0x7C & | \\ 029 & 0x1D & (gs) & 061 & 0x3D & = & 093 & 0x5D & ] & 125 & 0x7D & \char`\} \\ 030 & 0x1E & (rs) & 062 & 0x3E & > & 094 & 0x5E & \^{} & 126 & 0x7E & \~{} \\ 031 & 0x1F & (us) & 063 & 0x3F & ? & 095 & 0x5F & \char`\_ & 127 & 0x7F & \DEL \\ \hline \end{tabular} \caption{Фрагмент таблицы ASCII} \label{table:ascii} \end{figure} Таких таблиц кодировок несколько, а может, даже и несколько десятков. Разные операционные системы и разные приложения используют разные кодировки, например, в русскоязычной версии ОС Windows по-умолчанию используется cp1251, в то время как в командной строке этой же ОС используется cp866. Файлы можно сохранить в Unicode или ANSI. UNIX-подобные ОС, такие как Linux и Mac OS X обычно используют UTF-8 или UTF-16, а более ранние операционные системы и интернет пространства в русскоязычном сегменте использовали KOI8-R. \subsection{Особенности} Немного вспомнив, что такое символы, переходим к строкам. Строками мы пользуемся с самых первых страниц этого документа: написав в двойных кавычках <<Привет, Мир>>, мы использовали строку, а если точнее, строковый литерал. Строки иногда называют типом данных, но в языке С строка это указатель на последовательно записанный набор символов, поэтому работать с ним можно, как с массивами. Строки в языке С можно описать двумя способами: как указатель и как массив из переменных типа char. \frm{Объявление строки как указателя на символы в языке С++ полностью заменили на указатель на константный набор символов, чтобы подчеркнуть неизменяемость литерала. То есть, если в языке С считается нормальной запись \code{char* s = "Hello";} то в С++ это можно записать \textbf{только} как \code{const char* s = "Hello";} при этом в обоих языках поведение такого указателя будет обинаковым.} Давайте создадим строку в виде массива из \code{char} назовем ее \code{string1} и запишем внутрь литерал \code{This is a string!} - это строка. Также создадим указатель, назовем его \code{string2} и запишем в него литерал \code{This is also a string!} – это тоже строка. Выведем наши строки в консоль и убедимся, что автор не ошибся и их действительно можно так объявлять. \begin{figure}[h!] \begin{lstlisting}[language=C,style=CCodeStyle] char string1[256] = "This is a string!"; char* string2 = "This is also a string!"; printf("%s \n", string1); printf("%s \n", string2); \end{lstlisting} \end{figure} У каждого из способов есть свои особенности. Так, например в \textit{массиве} из переменных типа \code{char} мы можем изменять символы. Всё работает. Попробовав изменить какой-нибудь символ в \code{string1}, например, пятый, на символ \code{X} можно будет убедиться, что объявленная таким образом строка изменяемая, в отличие от строкового литерала, попытка изменить который приведёт к ошибке во время исполнения программы. Указатель на \code{char} нам не даёт возможности частично менять содержимое внутри строки, и, получается, представляет собой \textbf{immutable string} – неизменяемую строку. \begin{figure}[h!] \begin{lstlisting}[language=C,style=CCodeStyle] string1[5] = 'X'; printf("%s\n", string1); string2[5] = 'X'; printf("%s\n", string2); \end{lstlisting} \end{figure} Обратите внимание, что компилятор не считает такую запись неверной и ошибка проявляет себя только во время исполнения программы. Таким образом, становится очевидно, что программисту недостаточно просто убедиться в том, что его код компилируется, но необходимо и проверить его работу во время исполнения. Часто в этом помогают Unit-тесты. Среди нерадивых программистов бытует мнение, что вообще все тесты должны писать специалисты в тестировании, но на примере выше, когда код компилируется, а программа всё равно не работает должным образом, мы видим, что некоторая более тщательная проверка своей работы должна быть выполнена именно программистом, а тестирование - это лишь инструмент, помогающий автоматизировать такие проверки. \begin{verbatim} $ ./program Hello, world! Hello, world! HelloX world! zsh: bus error ./program $ \end{verbatim} \subsection{Строки и функции} Но довольно об ограничениях. Указатели на строки не такие уж бесполезные, мы можем, например, возвращать их из функций. То есть, мы можем объявить тип возвращаемого из функции значения как указатель \code{char*}, вернуть из нее строку и, например, вывести в консоль. Это открывает перед нами широчайшие возможности по работе с текстами. \begin{figure}[h!] \begin{lstlisting}[language=C,style=CCodeStyle] char* helloFunction() { return "Hello!"; } int main() { printf("%s \n",* helloFunction ()); } \end{lstlisting} \end{figure} Этот код выведет в консоль ожидаемое приветствие. Параллельно с написанием функции, приветствующей мир, предлагаю изучить некоторые стандартные возможности языка С для работы со строками. Например, специальную функцию, которая призвана выводить строки в консоль: \code{puts();} работает она очень похожим на \code{printf();} образом, но может выводить только строки, без каких-то других параметров, и всегда добавляет символ конца строки. Также изучим специальную функцию \code{gets();} которая призвана считывать строки из консоли и записывать их в переменные. \frm{Функция \code{gets();} некоторыми компиляторами признана небезопасной, её использование рекомендуется заменить на \code{gets\_s();} В С11 и позднее небезопасная функция была вовсе удалена из стандартной библиотеки языка и перестала поддерживаться всеми производителями компиляторов.} Создадим изменяемую строку типа \code{char[]}, назовём её \code{name}, передадим эту строку в функцию \code{gets();} и выведем на экран результат, полученный из консоли. Это будет очень полезная заготовка для дальнейшего общения с пользователем. \begin{figure}[h!] \begin{lstlisting}[language=C,style=CCodeStyle] char name[255]; gets(name); puts(name); \end{lstlisting} \end{figure} Теперь, мы можем поприветствовать пользователя нашей программы как следует, по имени. В нашей существовавшей функции приветствия внесём небольшие изменения. Создадим строку, в которой будем хранить приветственное слово, и в которую будет дописываться имя пользователя. Применим функцию склеивания строк. Поскольку склеивание - ненаучный термин, будем использовать слово \textbf{конкатенация}. И именно это слово подсказывает нам название функции, которую мы будем использовать: \code{strcat();} \frm{Для использования функции \code{strcat();} необходимо подключить в программу заголовочный файл, содержащий функции, работающие со строками \code{\#include }} Функция принимает на вход два параметра - строку, \textit{к которой} нужно что-то прибавить, и строку, \textit{которую} нужно прибавить. Логично предположить, что первая строка должна быть изменяемой (то есть являться массивом символов, а не литералом). Функция прибавит все символы второй строки в первую (если в массиве хватит места) и вернёт указатель на изменённую строку. Очень удобно. Запустим наш проект, введем имя и убедимся что всё \textbf{сломалось}. \begin{lstlisting}[language=C,style=CCodeStyle] char* helloFunction(char* name) { char welcome[255] = "Hello, "; return strcat(welcome, name); } int main(int argc, const char* argv[]) { char name[256]; gets(name); puts(helloFunction(name)); return 0; } \end{lstlisting} Что же случилось? Мы можем возвращать из функции только фиксированные строки, как в предыдущем примере. То есть, получается, нужно писать кейс, в котором содержатся все возможные имена, и оператором вроде \code{switch()} перебирать все возможные варианты ввода, и описывать все возможные приветствия пользователей, иначе мы устраиваем утечку памяти, создавая болтающийся в воздухе указатель, который никак не удалим? Нет, нас это, естественно, не устраивает. Что делать? Какой бы мы ни создали указатель в функции - он перестанет существовать, как только мы выйдем из области видимости этой функции. \frm{В некоторых случаях может показаться, что никакой проблемы нет, поскольку написанная таким образом программа благополучно поприветствует пользователя, но такое поведение не гарантируется ни одним компилятором и ни одной операционной системой, поскольку возвращаемый таким образом указатель может быть переписан абсолютно любой следующей инструкцией кода. Такое \textit{исчезающее} значение называется \textbf{xvalue}.} Выход очень простой: раз указатель не идёт в \code{int main (int argc, char *argv[])}, надо чтобы \code{int main (int argc, char *argv[])} дал нам указатель. Добавим в параметры функции указатель на выходную строку, и напишем что для начала сложить строки и положить в локальный массив \code{strcat(welcome, name)}. Добавим в основную функцию массив \code{char result[]}, который будет хранить результат и передадим в функцию \code{helloFunction} аргументы \code{name} и \code{result}. А раз функция больше ничего не возвращает, вполне легально сделать её \code{void}. \begin{lstlisting}[language=C,style=CCodeStyle] void helloFunction(char* name, char* out) { char welcome[255] = "Hello, "; strcat(welcome, name); out = welcome; } int main(int argc, const char* argv[]) { char name[256]; char result[256]; gets(name); helloFunction(name, result); puts(result); return 0; } \end{lstlisting} Запускаем, и \textbf{снова не работает}, да ещё и как интересно, смотрите! Предупреждение, ладно, понятно, мы о нём говорили, но дальше, когда мы вводим имя на выходе получается какая-то совсем уж непонятная строчка, совсем не похожая на приветствие. \begin{verbatim} $ ./program warning: this program uses gets(), which is unsafe. Ivan ??: $ \end{verbatim} А все дело в том, что строк в языке С за время повествования не появилось, и все манипуляции со строками - это довольно сложные алгоритмы работы с памятью и массивами символов. Поэтому, работая со строками, мы должны использовать библиотечные функции, например, есть функция \code{strcpy()}, которая не просто перекладывает указатель в определенную переменную, а копирует строку. \begin{figure}[h!] \begin{lstlisting}[language=C,style=CCodeStyle] void helloFunction (char* name, char* out) { char welcome[255] = “Hello, ”; strcat(welcome, name); strcpy(out, welcome); } int main(int argc, const char* argv[]) { char name[256]; char result[256]; gets(name); helloFunction(name, result); puts(result); return 0; } \end{lstlisting} \end{figure} Если присмотреться, то можно заметить, что все функции работающие со строками, именно так и делают - запрашивают источник данных и конечную точку, куда данные нужно положить. А кто мы такие, чтобы спорить со стандартными библиотечными функциями? Обратите внимание на то, что функции \code{strcat();} и \code{strcpy();} возвращают указатель на получившуюся строку. Мы перестали возвращать указатель на получившуюся строку, поскольку никто не гарантирует, что он просуществует достаточно долго, и тут встаёт вопрос о вызывающем функцию контексте, нужен ли этот указатель вызывающему. В случае необходимости, конечно, его можно вернуть. Работа со строками в С до сих пор является очень и очень актуальной темой на программистских форумах, можете удостовериться в этом самостоятельно. Раз уж заговорили о стандартной библиотеке, рассмотрим ещё пару-тройку интересных функций. Например, сравнение строк: функция \code{strcmp();} допустим, я хочу, чтобы именно меня программа приветствовала как-то иначе. Функция возвращает отрицательные значения, если первая строка меньше второй, положительные, если первая больше второй, и ноль, если строки равны. Это функция, которую удобно применять в условиях. Если строки будут действительно равны, мы скопируем в строку с именем слово \code{Creator}. \begin{lstlisting}[language=C,style=CCodeStyle] void helloFunction (char* name, char* out) { char welcome[255] = “Hello, ”; if (strcmp("Ivan", name) == 0) strcpy(name, "Creator"); strcat(welcome, name); strcpy(out, welcome); } \end{lstlisting} Из всех функций для работы со строками чрезвычайно часто используются \code{atoi();} и \code{atof();} переводящие написанные в строке цифры в численные переменные внутри программы. \code{atoi();} переводит в \code{int}, а \code{atof();} во \code{float}, соответственно. \frm{Существует несколько десятков функций, разнообразно преобразующих одни типы данных в другие и обратно, например, функции \code{atoi();} и \code{atof();} имеют ответные \code{itoa();} и \code{ftoa();}. Такие функции пишутся и для высокоуровневых библиотек, например, преобразование строковой записи в понятный компьютеру для соединения IP-адрес.} Для примера объявим переменную \code{num}, предложим пользователю ввести цифру, естественно в виде строки. Будем принимать ее при помощи небезопасной функции \code{gets();}, хотя как мы помним, могли бы и \code{scanf();} который сразу бы преобразовал строку согласно использованного заполнителя. Заведем переменную \code{int number} для хранения результата работы функции преобразования. Затем, давайте умножим результат сам на себя, чтобы убедиться, что это и правда число, причём именно то, которое мы ввели, и выведем окончательное число в консоль. \begin{lstlisting}[language=C,style=CCodeStyle] char num[64]; puts("Enter a number: "); gets(num); int number = atoi(num); number *= number; printf("We powered your number to %d", number); \end{lstlisting} Полный список функций для работы со строками можно посмотреть в заголовочном файле \code{string.h}. Описание и механика их работы легко гуглится, документации по языку очень много. \subsection{Работа с символами} В завершение беседы о строках и манипуляциях с ними, скажем ещё пару слов об обработке символов. Функции для работы с символами содержатся в заголовочном файле \code{stdlib.h}. Естественно, наша программа может получить от пользователя какие-то значения в виде строк. Не всегда же есть возможность использовать \code{scanf();} например, считывание из графических полей ввода или потоковый ввод данных из сети даёт нашей программе значения в виде строк. Стандартная библиотека языка С предоставляет нам функции для работы с каждым символом строки, например: \begin{itemize} \item \code{isalpha();} – возвращает истину, если символ в аргументе является символом из алфавита; \item \code{isdigit();} – возвращает истину, если символ в аргументе является цифрой; \item \code{isspace();} – проверяет, является ли переданный в аргументе символ пробельным; \item \code{isupper();} \code{islower();} – находится ли переданный в аргументе символ в верхнем или нижнем регистре; \item \code{toupper();} \code{tolower();} – переводят символ в верхний или нижний регистр, соответственно. \end{itemize} Можем использовать одну из них соответственно нашей задаче, допустим, пользователь может вводить своё имя как с заглавной буквы, так и всеми строчными. Уравняем оба варианта для нашей проверки одной строкой \code{name[0] = tolower(name[0]);} а после проверки вернём заглавную букву на место \code{name[0] = toupper(name[0]);} и удостоверимся что даже если мы напишем своё имя с маленькой буквы - программа напишет его с большой. \begin{lstlisting}[language=C,style=CCodeStyle] void helloFunction (char* name, char* out) { char welcome[255] = “Hello, ”; name[0] = tolower(name[0]); if (strcmp("ivan", name) == 0) strcpy(name, "Creator"); name[0] = toupper(name[0]); strcat(welcome, name); strcpy(out, welcome); } \end{lstlisting}