Доброго времени суток! Сегодня речь пойдет о символьных строках.
Сразу хочу извиниться за достаточно большую задержку. Причины две - сперва родной
вуз отключил интернет, а когда его восстановили - началась сессия. Но все когда-нибудь
заканчивается, и теперь, удачно сдав эту самую сессию, я снова с вами. Также я не
ответил на некоторые письма - просьба запастись терпением, залежи писем я обязательно
разгребу в ближайшее время.
Также я получил одно интересное письмо с просьбой увеличить объем домашнего задания.
Что ж, нет препятствий патриотам! Увеличу.
Программирование
Итак, разберемся, что из себя представляют в C символьные строки. Что такое вообще
строка? Я, например, считаю, что это последовательность символов конечной длины, то
есть набор из некоторого количества символов. А как в C реализуется набор из некоторого
количества однотипных элементов? Правильно, массивами. Таким образом, строка может
быть представлена как массив символов.
А теперь давайте зададимся таким вопросом. Пусть у нас есть две строки:
Где заканчивается первая строка? Ответ на этот вопрос не так прост, как хотелось бы.
Ну для нас все ясно: длина строки str1 равна str2 - str1. Но
компилятору-то это неясно! Да и мы перестанем их различать, как только передадим
строку в некоторую функцию: ведь вы помните, что при передаче массива в функцию
не передается информации о его размере. Так что нам необходимо как-то задать размер
нашей строки.
Делаться это может двумя способами. Первый реализован в Паскале и Бейсике и состоит
в том, что мы в строке наряду с собственно массивом символов храним ее длину как число.
преимуществом такого метода является достаточно быстрая обработка строк. Но у нее есть
и серьезный недостаток: во-первых, это необходимость хранить длину строки где-то
отдельно, а во-вторых (и в самых главных), размер переменной под длину накладывает
ограничения на длину строки. Конечно, можно хранить длину в четыхехбайтовой переменной
(unsinged long) - тогда максимально допустимая
длина строки станет равной 4 Гб, но тогда для хранения строки длиной 4 символа
потребуется 8 байт - это неэкономно.
Второй способ состоит в том, чтобы в строку включить некоторый маркер - признак ее
конца. Фактически в роли этого маркера выступает некоторый символ, который
гарантированно не встретится в строке. Тогда для того, чтобы узнать длину строки,
потребуется пересчитать все символы, пока не встретится этот самый маркер.
Главное достоинство такого метода - неограниченная длина строки при достаточно
экономном распределении памяти (строка из n символов занимает n+1
байт памяти). Недостатки - медленная обработка (узнать длину строки - целая проблема)
и гарантированное отсутствие в любой строке некоторого фиксированного символа.
Тем не менее, в С реализован именно второй способ. В качестве терминирующего символа
выбран символ с кодом 0 (не путайте его с символом '0'!
Почему-то некоторые считают, что символ цифры и его код должны совпадать - отнюдь).
Таким образом, определение
Это просиходит при инициализации массива строкой. Во всех остальных случаях
встретившаяся строка интерпретируется как еще не созданный безымянный массив
соответствующей длины. То есть выражение
printf ("Hello, world\n");
на самом деле интерпретируется как
char str1 [] = "Hello, world\n";
printf (str1);
Теперь мы можем переписать задачу 6.2, используя только один цикл:
#include <stdio.h>
int main (void)
{
char *str;
for (str = "$$$$$$$"; *str; str++)
printf ("%s\n", str);
return 0;
}
В этой программе не хватает той злосчастной ступеньки, которая была в условии задачи,
но это не важно. Попытаемся разобраться в программе. У нас есть указатель на
char. Мы ему присваиваем адрес строки
"$$$$$$$". Теперь каждый раз мы продвигаемся на одну
позицию вперед и печатаем ее. То есть во второй раз строка будет напечатана со второго
символа, в следующий раз - с третьего, и т.д.
Теперь о действиях со строками. Раз в C нет предопределенного типа для строки, то нет
и столь привычных многим операций, как сравнения и склеивания строк, реализованных
во многих языках как операторы сложения и сравнения. Здесь сложение массивов
недопустимо, а при сравнении будут сравниваться не сами строки, а только указатели
на них, что нам, конечно же, неинтересно. Для манипуляций со строками существует
набор функций, объявленных в файле <string.h> (те,
кто пишет под Windows, могут включать вместо него файл
<windows.h>). Наиболее важные функции:
int strcmp (char
*string1, char *string2) - осуществляет сравнение
двух строк. Возвращает отрицательное число, если первая строка меньше второй,
0, если строки равны и положительное число, если первая строка больше второй.
Более детально, функция возвращает разницу между кодами первых встретившихся
неодинаковых символов (если строки неодинаковы по длине, то когда-то ненулевой
символ будет сравниваться с нулем).
char *strcat (char
*string1, char *string2) - осуществляет склеивание
двух строк. Вторая строка добавляется в конец первой. Функция не проверяет (да и не
может проверять технически) наличие необходимого количества памяти в конце первой
строки - об этом должны позаботиться вы. Функция возвращает указатель на первую
строку.
char *strcpy (char
*dest, char *source) - осуществляет копирование
строки source на место строки dest. Опять-таки позаботьтесь о том,
чтобы вся строка поместилась в отведенном для нее месте. Функция возвращает
указатель на строку-приемник.
int strlen (char
*string) - возвращает длину строки string (не считая нулевого символа).
char *strdup (char
*string) - создает дубликат строки string и возвращает указатель на
него. Учтите, что в отличие от остальных функций, strdup сама создает
строку и поэтому после того, как она стала вам ненужна, не забывайте ее освободить.
char *strncpy (char
*dest, char *source, int
count) и char *strncat (
char *string1, char *string2,
int count) - аналогично strcpy и strcat, но копируются
только первые count символов. Функции не добавляют к строке завершающего
нуля - вам придется сделать это самим.
char *strchr (char
*string, int c) и char
*strstr (char *string, char
*substring) - ищут первое вхождение в строку string соответственно
символа c и подстроки substring. Обе функции возвращают адрес
первого вхождения или NULL, если такового не найдено.
Как вводить строки выводить их на экран? Для ввода строки существует функция
char *gets (char *string),
которая считывает строку с клавиатуры и помещает ее в буфер string, указатель
на который и возвращает. Выводиться строка может или уже известной вам функцией
printf со спецификатором ввода "%s", либо
специальной функцией int puts (char
*string), которая выводит строку string на экраз и возвращает
некоторое ненулевое значение в случае успеха.
Зачем нужен спецификатор "%s"? Это делается для того, чтобы
можно было выводить строки с любыми символами. Сравните:
char str [] = "Захотелось мне вывести %d...";
printf ("%s", str); /* Правильный вариант */
printf ("\n"); /* Разделитель новой строки */
printf (str); /* Неправильный вариант */
В чем разница? В первом случае функция напечатает именно то, что от нее требуется.
А вот во втором случае printf, встретив в строке str спецификатор
"%d" (ведь теперь эта строка - первая, значит, она
задает формат вывода), сделает вывод, что за ней должно следовать число. А так как
оно не следует, то вместо "%d" будет напечатан некоторый
мусор - число, находящееся в тот момент в стеке. Последствия могут быть и более
серьезными, если в строке находится последовательность "%s"
- printf сочтет ее за строку и будет выводить ее до тех пор, пока не встретит
нулевой символ. А где она его встретит, сколько успеет напечатать и не crash'нется ли
из-за обращения не к своей памяти - не знает никто.
Есть еще одна очень полезная функция - int sprintf
(char *dest, char *format, ...).
Эта функция работает точно так же, как и printf, но она не выводит получившуюся
строку на экран, а помещает ее в dest. Это может быть очень полезно при
склеивании большого количества строк.
Ну вот вроде и все на сегодня. Домашнее задание:
14.1. Реализуте описанные функции работы со строками
(кроме gets и puts) сами. 14.2. Создайте функцию для ввода строки с клавиатуры.
Строка считается введенной, если ее длина превысила 63 символа, или если был нажат
пробел, клавиша табуляции ('\t') или Enter. 14.3. Создайте функцию, которая ищет в данной строке
первых символ, принадлежащий заданному множеству (множество задается строкой). Если
символ найден, функция должна возвращать указатель на него, если не найден -
NULL. 14.4. Как будет себя вести функция strcat,
если в качестве обоих аргументов передать один и тот же указатель? 14.5. Как уже упоминалось, строки в C имеют достаточно
низкую скорость обработки. Поэтому для ряда задач, требующих очень быстрой обработки
строк, представляется целесообразным пожертвовать тремя байтами для существенного
ускорения обработки строк - хранить в строках их длину. Разработайте соответствующую
таким строкам структуру данных и функции для работы с ней. Было бы желательно наличие
совместимости со стандартными строками C (конечно, односторонней - то есть каким-то
образом вашу строку можно было бы читать при помощи стандартных функций С).