Лек11: Строки.
В лекциях 11 и 12 описаны два типа данных, значения которых представляют строки символов. Первый из них — массив базового типа char, в котором хранится последовательность символов строки, а конец строки отмечается нулевым символом '\0'. Это старый способ представления строк, унаследованный языком C++ от языка С. Строки данного типа, называемые строками С, все еще широко используются, и вам часто придется с ними сталкиваться. Например, строковые константы в кавычках, такие как "Hello", реализуются в C++ как строки С.
Стандарт ANSI/ISO языка C++ включает более современный способ представления строк в виде объектов класса string. Это второй строковый тип, рассматриваемый в Лек15.
11.1. Массивы для хранения строк
В этом вопросе описывается способ представления строк символов, унаследованный языком C++ от языка С. Хотя строки С несколько «старомодны», они по-прежнему широко используются и являются неотъемлемой частью языка C++.
Строковые значения и строковые переменные С
Одним из способов представления строк является использование массива базового типа char. Например, строку "Hello" удобно представить как массив из шести индексированных переменных: пяти букв слова "Hello" и одного нулевого символа '\0', служащего маркером конца строки. Символ '\0' называется нуль-символом или нулевым символом, а когда он используется в качестве маркера конца строки — нуль-терминатором. При использовании таких маркеров программа может считывать массивы посимвольно и знать, когда следует остановиться. Строка, хранящаяся в описанном формате, называется строкой С.
В программе C++ нуль-символ записывается как ' \0', то есть в виде двух символов, но на самом деле, подобно символу новой строки '\n', он является одним символом. Как и любое другое символьное значение, он может храниться в переменной типа char или элементе массива с базовым типом char.
Нуль-символ '\0'
Нуль-символ '\0' отмечает конец строки С, хранящейся в символьном массиве. Такой массив часто называют строковой переменной С. Хотя нуль-символ записывается в виде двух символов, это один символ, который может храниться в переменной типа char или элементе массива базового типа char.
Вам уже приходилось пользоваться строками С. Например, литеральная строка, подобная "Hello", хранится в виде строки С, хотя это редко имеет значение для программы.
Строковая переменная С представляет собой просто массив символов. Так, следующее объявление массива:
char s[10];
создает строковую переменную С, в которой может храниться строковое значение С, состоящее из десяти или менее символов.
Массив длиной десять символов вмещает строку из девяти символов и нуль-символ '\0', отмечающий ее конец.
Строковая переменная С является частично заполненным массивом символов. Подобно другим частично заполненным массивам, она содержит данные в идущих подряд элементах, начиная с нулевого. И в ней занято столько позиций, сколько нужно для хранения данных. Однако для отслеживания количества заполненных элементов массива ей не требуется отдельная переменная типа int. Информация о месте окончания строки содержится в ней самой — после последнего символа строки в строковой переменной С располагается специальный символ '\0'. Поэтому, если в переменной s содержится строка "Hi Mom!", элементы массива заполнены следующим образом:
s[0] s[l] s[2] s[3] s[4] s[5] s[6] s[7] s[8] s[9]
H |
i |
|
M |
o |
m |
! |
\0 |
? |
? |
Символ '\0' используется в качестве сигнального значения, отмечающего конец строки С. Если считывать символы строки, начиная с элемента s[0], затем s[l], s[2] и т. д., то дойдя до символа ' \0', вы будете знать, что достигли конца строки. Поскольку этот символ всегда занимает один элемент массива, максимальная длина строки, которую может вместить массив, на единицу меньше объявленного размера этого массива.
Еще раз подчеркнем: в конце строковой переменной С обязательно должен располагаться нуль-символ ' \0'. Отличие этого типа массива от остальных заключается не в структуре, а состоит в том, что он, представляя собой обычный массив символов, используется особым образом.
Как видно из следующего примера, строковую переменную С можно инициализировать при объявлении:
char my_message[20] = "Hi there.";
Обратите внимание на то, что строка С, присваиваемая строковой переменной С, не обязательно заполняет весь массив.
Объявление строковой переменной С
Строковая переменная С — это обычный массив символов, используемый особым образом. Она объявляется так же, как любой другой массив символов.
Синтаксис
char имя_массива[максимальный_размер_строки_С + 1];
Пример char my_c_string[11];
Единица прибавляется для того, чтобы массив вмещал нуль-символ ' \0', отмечающий конец хранящейся в массиве строки. В частности, строковая переменная my_c_string в приведенном выше примере вмещает строку С длиной в десять или менее символов.
Инициализируя строковую переменную С, можно опустить размер массива. C++ автоматически присвоит ей размер, который будет на единицу больше, чем длина заключенной в кавычки строки (один дополнительный элемент массива для символа '\0'). Например, объявление
char short_string[] = "abc";
эквивалентно объявлению
char short_string[4] = "abc";
Однако не путайте следующие две инициализации:
char short_string[] = "abc";
и
char short_string[] = {'a', 'b', 'c'};
Они не равнозначны. Первая помещает после символа 'с' символ '\0', а вторая этого не делает (она вообще не помещает в массив символ ' \0' — ни после 'с', ни в каком-либо другом месте).
Поскольку строковая переменная С является массивом, она состоит из набора элементов, с которыми можно работать по отдельности. Предположим, что программа содержит такое объявление строковой переменной С:
char our_string[5] = "Hi";
Это объявление и инициализация массива символов, включающего следующие элементы: our_string[0], our_string[l], our_string[2], our_string[3] и our_string[4]. Для примера рассмотрим следующий фрагмент программы:
int index = 0;
while (our_string[index] != '\0')
{
our_string[index] = 'X';
index++; }
Данный код изменяет строковое значение, хранящееся в переменной our_string, помещая в нее строку С, состоящую только из символов 'X'.
Инициализация строковой переменной С
Строковую переменную С можно инициализировать при объявлении, как в следующем примере:
char rny_string[] = "DoBeDo";
Эта инициализация автоматически помещает в конец строки С символ '\0'.
Если в квадратных скобках не задано число, создается массив, длина которого на единицу больше длины помещаемой в него строки. Так, приведенный оператор объявляет массив my_string из девяти индексированных переменных (восемь для символов строки "Do Be Do" и один для нуль-символа '\0').
При работе с такими индексированными переменными нужно внимательно следить, чтобы символ ' \0' не был заменен каким-нибудь другим значением, поскольку при отсутствии этого символа массив перестанет вести себя, как строковая переменная С. Например, код
char happy_string[7] = "DoBeDo";
happy_string[6] = ‘Z’;
изменяет массив happystring так, что в нем больше не содержится строка С.
После выполнения приведенного кода массив happy_string будет по-прежнему содержать шесть букв строки "DoBeDo", но в нем не будет нуль-символа, отмечающего конец строки С (он заменен символом 'Z'). Многие функции для работы со строками требуют обязательного наличия нуль-символа ' \0' и без него работают неправильно. В качестве еще одного примера можно рассмотреть приведенный выше цикл while, изменяющий символы строковой переменной ourstring. Цикл заменяет символы до тех пор, пока не встретит символ ' \0', а в случае его отсутствия он может «обработать» большой фрагмент памяти с непредсказуемыми последствиями. Цикл while можно переписать следующим образом:
int index = 0;
while ( (our_string[index] != '\0') && (index < SIZE) )
{
our_string[index] = 'X';
index++; }
чтобы он не полагался на наличие символа ' \0' и ни в коем случае не выходил за переделы массива.
В этом примере SIZE — именованная константа, равная объявленному размеру массива our_string.
Оператор
char *str = "Vasia"
создает не строковую переменную, а указатель на строковую константу.
При работе со строками часто используются указатели.
Рассмотрим процесс копирования строки srcв строку dest.
#include <iostream.h>
int main(){
char *src = new char [10];
char *dest = new char [10], *d = dest;
cin << src;
while ( *d++ = *src++);
cout << dest;}
11.2. Ловушка: использование операторов = и == со строками С
Строковые значения и переменные С отличаются от значений и переменных других типов данных, и многие операции языка C++ к ним неприменимы. Так, нельзя использовать строковую переменную С в операторе присваивания. Если же попытаться сравнить две строки С посредством оператора = =, результат будет не таким, как ожидалось. Это объясняется тем, что строки С являются массивами.
Присваивание значения строковой переменной С выполняется не так просто, как другим переменным C++. Скажем, следующий оператор присваивания:
char a_string[10];
a_string = "Hello";
недопустим.
Хотя в объявлении такой переменой можно пользоваться знаком равенства для ее инициализации, больше нигде в программе это не разрешается. Знак равенства в объявлении переменной
char happy_string[7] = "DoBeDo";
означает именно инициализацию, а не присваивание, которое выполняется иначе.
Существуют разные способы присваивания значения строковой переменной С, простейший из них заключается в вызове стандартной функции strcpy:
strcpy(a_string, "Hello");
Этот вызов присваивает переменной a_string строку "Hello". К сожалению, данная версия функции strcpy не проверяет, превышает ли размер строки размер строковой переменной.
Во многих реализациях C++ имеется более безопасная версия данной функции, называемая strncpy (с буквой n). У нее есть третий аргумент, в котором задается максимальное количество копируемых символов. Например:
char another_string[10];
strncpy(another_string, a_string_variable, 9);
Вызов копирует максимум девять символов из строковой переменной a_string_variable (независимо от ее длины) в строковую переменную another_string.
Проверку эквивалентности двух строковых переменных С нельзя выполнять обычным способом (то есть с помощью оператора ==). Данный оператор можно использовать со строками С для других целей. Поэтому если применить его для сравнения двух строк, результат окажется неверным, и при этом даже не будет выведено сообщение об ошибке. Сравнение двух строк С на эквивалентность выполняется с помощью стандартной функции strcmp. Вот так:
if (strcmp(c_stringl, c_string2))
cout << "The strings are NOT the same.";
else
cout << "The strings are the same.";
Обратите внимание на то, что функция strcmp работает несколько необычно — она возвращает значение true, когда строки не равны. Эта функция сравнивает две строки посимвольно, и если для очередной пары символов код символа из строки c_string1 оказывается меньше кода символа из строки c_string2, она прекращает проверку и возвращает отрицательное число. В случае же когда код символа из строки cstringl оказывается больше кода символа из строки c_string2, она возвращает положительное число. Ну а если строки одинаковы, функция strcmp возвращает 0. При сравнении символов двух строк используется лексикографический (словарный) порядок. Когда обе строки содержат символы одного регистра, этот порядок совпадает с алфавитным.
Таким образом, функция strcmp возвращает отрицательное число, положительное число или 0 в зависимости от того, окажется первая из переданных ей строк меньше, больше или равной второй с точки зрения их лексикографического порядка. Если использовать возвращаемый ею результат в качестве логического выражения в операторе if или в цикле для проверки равенства двух строк С, ненулевое значение будет преобразовано в true, a 0 — в false. При тестировании программ, выполняющих такую проверку, не забывайте об этой «логике наоборот».
Компиляторы C++, соответствующие стандарту ANSI/ISO, поддерживают более безопасную версию функции strcmp с третьим аргументом, в котором задается максимальное количество сравниваемых символов.
Функции strcpy и strcmp располагаются в библиотеке с заголовочным файлом <cstring>. Для их использования нужно добавить в начало программы следующую директиву:
#include <cstring>
Обе эти функции не требуют приведенной ниже (или подобной ей) директивы:
using namespace std;
но она может понадобиться в других частях программы.
Библиотека cstring
Для объявления и инициализации строк С не требуется никакой директивы include или using. Однако при обработке строк вы наверняка будете применять те или иные предопределенные функции из библиотеки cstring. Поэтому, решив воспользоваться строками С, лучше сразу включите в начало файла программы директиву
#include <cstring>