Строки в С++
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. Это может быть очень полезно при склеивании большого количества
строк.
Ведущий рассылки, av