Отправляет email-рассылки с помощью сервиса Sendsay

C и C++ для начинающих

  Все выпуски  

C и C++ для начинающих - Выпуск 14. Строки


Служба Рассылок Subscribe.Ru проекта Citycat.Ru
Архив рассылки Подписаться Статистика рассылки Форум

C и C++ для начинающих. Выпуск 14.

Доброго времени суток! Сегодня речь пойдет о символьных строках.

Сразу хочу извиниться за достаточно большую задержку. Причины две - сперва родной вуз отключил интернет, а когда его восстановили - началась сессия. Но все когда-нибудь заканчивается, и теперь, удачно сдав эту самую сессию, я снова с вами. Также я не ответил на некоторые письма - просьба запастись терпением, залежи писем я обязательно разгребу в ближайшее время.

Также я получил одно интересное письмо с просьбой увеличить объем домашнего задания. Что ж, нет препятствий патриотам! Увеличу.

Программирование

Итак, разберемся, что из себя представляют в C символьные строки. Что такое вообще строка? Я, например, считаю, что это последовательность символов конечной длины, то есть набор из некоторого количества символов. А как в C реализуется набор из некоторого количества однотипных элементов? Правильно, массивами. Таким образом, строка может быть представлена как массив символов.

А теперь давайте зададимся таким вопросом. Пусть у нас есть две строки:
 char str1 [] = {'H','e','l','l','o'};
 char str2 [] = {'w','o','r','l','d'};
Где заканчивается первая строка? Ответ на этот вопрос не так прост, как хотелось бы. Ну для нас все ясно: длина строки str1 равна str2 - str1. Но компилятору-то это неясно! Да и мы перестанем их различать, как только передадим строку в некоторую функцию: ведь вы помните, что при передаче массива в функцию не передается информации о его размере. Так что нам необходимо как-то задать размер нашей строки.

Делаться это может двумя способами. Первый реализован в Паскале и Бейсике и состоит в том, что мы в строке наряду с собственно массивом символов храним ее длину как число. преимуществом такого метода является достаточно быстрая обработка строк. Но у нее есть и серьезный недостаток: во-первых, это необходимость хранить длину строки где-то отдельно, а во-вторых (и в самых главных), размер переменной под длину накладывает ограничения на длину строки. Конечно, можно хранить длину в четыхехбайтовой переменной (unsinged long) - тогда максимально допустимая длина строки станет равной 4 Гб, но тогда для хранения строки длиной 4 символа потребуется 8 байт - это неэкономно.

Второй способ состоит в том, чтобы в строку включить некоторый маркер - признак ее конца. Фактически в роли этого маркера выступает некоторый символ, который гарантированно не встретится в строке. Тогда для того, чтобы узнать длину строки, потребуется пересчитать все символы, пока не встретится этот самый маркер. Главное достоинство такого метода - неограниченная длина строки при достаточно экономном распределении памяти (строка из n символов занимает n+1 байт памяти). Недостатки - медленная обработка (узнать длину строки - целая проблема) и гарантированное отсутствие в любой строке некоторого фиксированного символа.

Тем не менее, в С реализован именно второй способ. В качестве терминирующего символа выбран символ с кодом 0 (не путайте его с символом '0'! Почему-то некоторые считают, что символ цифры и его код должны совпадать - отнюдь). Таким образом, определение
 char HelloStr [] = "Hello, world";
фактически интерпретируется как
 char HelloStr [] = {'H', 'e', 'l', 'l', 'o', ' ', ',', 'w', 'o', 'r', 'l', 'd', 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 (конечно, односторонней - то есть каким-то образом вашу строку можно было бы читать при помощи стандартных функций С).

До встречи!

Ведущий рассылки, av

http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться Рейтингуется SpyLog

В избранное