Назад | Начало урока | Вперед
Содержание

Часть 2

За полной документацией по всем функциям и классам стандартной библиотеки С++ обращайтесь прежде всего "Справочник по стандартным библиотекам С, С++" автор - П.Дж.Плаугер (P.J.Plauger) по адресу: http://www.dincumware.com. Это превосходный сборник электронной документации по стандартным библиотекам в формате html. Вы можете просматривать его в браузере при возникновении любых вопросов. Справочник можно изучать непосредственно в режиме подключения или приобрести его для локального просмотра. Он содержит документацию по библиотекам как С так и С++. Электронная документация хороша не только своей доступностью, но и тем, что в ней легко производить поиск.

Глава 3

Строки

Обработка строк в символьных массивах отнимала массу времени у программистов С. При работе с символьным массивом программисту приходилось различать статические строки в кавычках и массивы, созданные в стеке и в куче, а так же помнить, что в одних случаях передается char*, а в других приходится копировать целый массив.

С учетом того, что операции со строками так широко распространены, символьные массивы превращались в источник сплошных недоразумений и ошибок. Однако не смотря на это, созданеи собственных строковых классов в течение долгих лет оставалось распространенным упражнением для начинающих программистов С++. Класс string стандартной библиотеки С++ раз и навсегда решает проблему работы с символьными массивами, он правильно выделает память даже при конструировании копий и присваивании. Вам просто не придется думать об этих мелочах.

В этой главе рассматривается класс string стандартного языка С++. Сначала мы разберемся что же из себя представляют строки С++ и чем они отличаются от традиционных символьных массивов С. Вы узнаете какие операции выполняются с объектами string, и как класс string обеспечивает применение различных кодировок и преобразований строковых данных.

Обработка текста относится к числу старейших задач программирования. Вполне понятно, что класс string в значительной степени основан на идеях и терминологии, традиционно использовавшейся в С и других языках. Это обстоятельство придаст вам уверенности во время изучения string. Какую бы идиому программирования вы не выбрали, основные операции с классом string делятся на три категории:

  • создание или модификация (изменение) последовательности символов, хранящейся в string
  • проверка наличия или отсутствия элементов в string
  • преобразование между различными схемами представления символов в string

    Вы увидите как каждая из этих операций выполняется средствами строковых объектов string в С++.

    Вверх

    Что такое строка?

    В языке С строка представляет собой массив символов, последним элементом которого всегда является двоичный ноль (часто называемый нуль-терминатором). Между строками С++ и их прототипами С существуют заметные различия. Первое и самое важное состоит в том, что строковые объекты С++ скрывают физическое представление содержащихся в них символов. Вам не придется беспокоиться о размерах массива или о нуль-терминаторах. Объект string так же содержит служебную информацию о размере и местонахождении буфера данных. Говоря точнее, чтроковый объект С++знает свой начальный адрес в памяти, свое содержимое, свою длину в символах, а так же максимальную длину в символах, до которой он может увеличиться без увеличения внутреннего буфера данных.

    Строки С++ существенно снижают вероятность самых распространенных и опасных ошибок программирования С: выхода за границы массива, попытки обращения к массиву через неинициализированный или ошибочный указатель, появление "висячих указателей" после освобождения блока памяти, в котором ранее хранился массив.

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

    В частности в сатндарте не определяются точные условия выделения памяти для хранения данных. Правила сформулированы таким образом, что они допускают реализацию на базе подсчета ссылок, но не делают ее обязательной.

    Впрочем независимо от того, используется ли в реализации подсчет ссылок или нет, семантика должна сохраниться. Так в языке С каждый символьный массив всегда занимает уникальный физический блок памяти. В С++ отдельные объекты string могут занимать или не занимать уникальные физические блоки памяти, но даже если хранение лишних копий данных предотвращается благодаря подсчету ссылок, с точки зрения программиста отдельные объекты должны работать так, словно каждый из них хранится в отдельном блоке.

    Пример:


    //: C03:StringStorage.h
    #ifndef STRINGSTORAGE_H
    #define STRINGSTORAGE_H
    #include <iostream>
    #include <string>
    #include "../TestSuite/Test.h"
    using std::cout;
    using std::endl;
    using std::string;

    class StringStorageTest : public TestSuite::Test {

    public: void run() {
    string s1("12345"); // Первая строка может быть скопирвоана во вторую
    // или копирование может имитироваться подсчетом ссылок
    string s2 = s1;
    test_(s1 == s2);
    // В любом случае эта команда должна изменять ТОЛЬКО s1
    s1[0] = '6';
    cout << "s1 = " << s1 << endl;
    cout << "s2 = " << s2 << endl;
    test_(s1 != s2);
    }
    };
    #endif // STRINGSTORAGE_H ///:~

    //: C03:StringStorage.cpp
    //{L} ../TestSuite/Test
    #include "StringStorage.h"

    int main() {

    StringStorageTest t;
    t.run();
    return t.report();
    } ///:~


    Результат:

    Анализ:

    Говорят, что в реализации, при которой уникальные копии создаются только при модификации строк, используется стратегия копирования при записи. Такое решение экономит время и память в тех случаях, когда строки только передается по значению (а так же в других ситуациях с доступом только для чтения).

    Пользователю класса string должно быть безразлично, основана реализация библиотеки на подсчете ссылок или нет. К сожалению это правило выполняется далеко не всегда. В многопоточных программах практически невозможно безопасно использовать реализацию с подсчетом ссылок.

    АВ: Здесь речь идет о мелком и глубоком копировании. То есть о создании двух разных ссылок, указывающих на один объект, или о создании двух разных объектов (идентичных по содержанию) и двух ссылок, каждая из которых указывает на свой объект.

    В указанной выше программе фрагмент кода:

    string s1("12345"); // Первая строка может быть скопирвоана во вторую
    // или копирование может имитироваться подсчетом ссылок
    string s2 = s1;

    сначала создает и инициализирует объект s1, затем создается объект s2 и он инициализируется тем же содержимым, которое было в s1. Таким образом происходит глубокое копирование. Создание двух разных объектов с идентичным содержимым и двух ссылок на эти объекты, каждая ссылка указывает на свой объект. Далее в программе происходит тестирование (проверка) этого факта. Перепиши эту программу, чтобы проверить этот факт. Программа не откомпилировалась потому, что не был найден заголовочный файл.

    Фраза - "практически невозможно безопасно использовать реализацию с подсчетом ссылок" говорит о том, что глубокое копирование предпочтительней и безопаснее.

    Вверх

    Создание и инициализация строк C++
    Создание и инициализация строк - вполне очевидные операции, обладающие достаточно гибкими возможностями. В приведенном ниже примере первая строка imBlank объявляется, но не содержит исходного значения. В отличие от симольных массивов С, которые до момента инициализации содержат случайный и бессмысленных набор битов, imBlank содержит полезную информацию (!). Этот объект string инициализируется "пустой строкой" (!), он может правильно сообщить о своей нулевой длине и отсутствии элементов через функции класса.

    Следующая строка .. инициализируется литералом .. При такой форме инициализации конструктору string передается символьный массив, заключенный в кавычки. С другой стороны .. инициализируется простым присваиванием. Последняя строка группы .. инициализируется существующим строковым объектом С++. Другими словами этот пример демонстрирует следующие способы инициализации объектов string:

  • создание пустого объекта string (инициализация объекта символьными данными откладывается на будущее) АВ:Создание пустого объекта со свойствами - хорошая идея!
  • инициализация объекта string с передачей конструктору литерала - символьного массива, заключенного в кавычки.
  • инициализация объекта string с помощью знака равенства (=)
  • использование объекта string для инициализации другого объекта


    //: C03:SmallString.cpp
    #include <string>
    using namespace std;

    int main() {

    string imBlank;
    string heyMom("Where are my socks?");
    string standardReply = "Beamed into deep "
    "space on wide angle dispersion?";
    string useThisOneAgain(standardReply);
    } ///:~


    Результат:

    Нет вывода на экран

    Анализ:

    Таковы простейшие формы инициализации объектов string. Их разновидности обладают большей гибкостью и лучше поддаются контролю. В частности возможно:

  • использование подмножества символьного массива С или строкового объекта С++
  • использование нескольких источников инициализационных данных оператором +
  • выделение подстроки функцией substr() объекта string

    Следующая программа демонстрирует перечисленные возможности:

    Вверх


    //: C03:SmallString2.cpp
    #include <string>
    #include <iostream>
    using namespace std;

    int main() {

    string s1
    ("What is the sound of one clam napping?");
    string s2
    ("Anything worth doing is worth overdoing.");
    string s3("I saw Elvis in a UFO");
    // Копирование первых 8 символов
    string s4(s1, 0, 8);
    cout << s4 << endl;
    // Копирование 6 символов из середины источника
    string s5(s2, 15, 6);
    cout << s5 << endl;
    // Копирование из середины в конец
    string s6(s3, 6, 15);
    cout << s6 << endl;
    // Комбинированное копирование
    string quoteMe = s4 + "that" +
    // substr() копирует 10 символов начиная с элемента 20
    s1.substr(20, 10) + s5 +
    // substr() копирует 100 символов или остаток до конца
    // строки, начиная с элемента 5.
    "with" + s3.substr(5, 100) +
    // Функция substr() также может копировать отдельные символы
    s1.substr(37, 1);
    cout << quoteMe << endl;
    } ///:~


    Результат:

    What is
    doing
    Elvis in a UFO
    What is that one clam doing with Elvis in a UFO?

    Анализ:

    В первом аргументе функции substr() класса string передается начальная позиция, а во втором - длина подстроки в символах. У обоих аргументов имеются значения по умолчанию. Функция substr() с пустым списком аргументов возвращает копию всего объекта string: это удобный способ копирования строк в С++.

    Обратите внимание на последнюю строку примера. С++ позволяет объединять разные способы инициализации string в одной команде, что очень удобно. Так же стоит заметить, что последний инициализатор копирует всего один символ исходного объекта string.

    Другая, более изощренная методика инициализации основана на применении строковых итераторов string::begin() и string::end(). Строка интерпретируется как объект-контейнер, в котором начало и конец последовательности символов обозначаются при помощи итераторов (мы уже встречались с контейнерами на примере векторов). В этом варианте конструктору string передаются два итератора, и он копирует символы из одного объекта в другой:

    Вверх


    //: C03:StringIterators.cpp
    #include <string>
    #include <iostream>
    #include <cassert>
    using namespace std;

    int main() {

    string source("xxx");
    string s(source.begin(), source.end());
    assert(s == source);
    } ///:~


    Операторы с итераторами не ограничиваются вызовами begin() и end(). Итераторы можно увеличивать и уменьшать, а так же прибавлять к ним целочисленные смещения, чтобы извлечь подмножество символов из исходной строки.

    Строки в С++ не могут инициализироваться одиночными символами, ASCII-кодами или другими целочисленными значениями. Впрочем строка может инициализироваться несколькими экземплярами одного символа:

    Вверх


    //: C03:UhOh.cpp
    #include <string>
    #include <cassert>
    using namespace std;

    int main() {

    // Ошибка: инициализация одиночным символом недопустима
    //! string nothingDoing1('a');
    // Ошибка: инициализация целочисленными кодами недопустима
    //! string nothingDoing2(0x37);
    // Следующий вариант допустим:
    string okay(5, 'a');
    assert(okay == string("aaaaa"));
    } ///:~


    Анализ:

    Первый аргумент определяет количество экземпляров второго аргумента в строке. Второй аргумент может быть только одиночным символом (char), но не символьным массивом.


    Назад | Начало урока | Вверх | Вперед
    Содержание

    Hosted by uCoz