::Главная страница :: С++/Си :: Статьи

Строки в С++

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>). Наиболее важные функции:
Как вводить строки выводить их на экран? Для ввода строки существует функция 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

Тематические ссылки
Ваша ссылка Ваша ссылка

Обмен кнопками, ведение статистики, реклама.