Главная страница
Навигация по странице:

  • Упражнение 7.54. Должны ли члены класса Debug, начинающиеся с set_, быть объявлены как constexpr Если нет, то почему

  • Упражнение 7.56. Что такое статический член класса Каковы преимущества статических членов Чем они отличаются от обычных членов

  • Язык программирования C Пятое издание


    Скачать 1.85 Mb.
    НазваниеЯзык программирования C Пятое издание
    Дата15.07.2019
    Размер1.85 Mb.
    Формат файлаpdf
    Имя файла620354-www.libfox.ru.pdf
    ТипДокументы
    #84130
    страница22 из 54
    1   ...   18   19   20   21   22   23   24   25   ...   54

    Что будет при явном конструкторе Sales_data()?
    Упражнение 7.49. Объясните по каждому из следующих трех объявлений функции combine(),
    что происходит при вызове i.combine(s), где i — это объект класса Sales_data, a s — строка:
    (a) Sales_data &combine(Sales_data);
    (b) Sales_data &combine(Sales_data&);
    Page 382/1103

    (c) Sales_data &combine(const Sales_data&) const;
    Упражнение 7.50. Определите, должен ли какой-либо из конструкторов вашего класса Person быть явным.

    Упражнение 7.51. Как, по вашему, почему вектор определяет свой конструктор с одним аргументом как явный, а строка нет?
    7.5.5. Агрегатные классы
    Агрегатный класс (aggregate class) предоставляет пользователям прямой доступ к своим членам и имеет специальный синтаксис инициализации. Класс считается агрегатным в следующем случае.
    • Все его переменные-члены являются открытыми (public).
    • Он не определяет конструкторы.
    • У него нет никаких внутриклассовых инициализаторов (см. раздел 2.6.1).
    • У него нет никаких базовых классов или виртуальных функций, связанных с классом средствами, которые рассматриваются в главе 15.
    Например, следующий класс является агрегатным: struct Data { int ival; string s;
    };
    Для инициализации переменных-членов агрегатного класса можно предоставить заключенный в фигурные скобки список инициализаторов для переменных-членов:
    // val1.ival = 0; val1.s = string("Anna")
    Data val1 = { 0, "Anna" };
    Инициализаторы должны располагаться в порядке объявления переменных-членов. Таким образом, сначала располагается инициализатор для первой переменной-члена, затем для второй и т.д. Следующей пример ошибочен:
    // ошибка: нельзя использовать "Anna" для инициализации ival или 1024
    // для инициализации s
    Data val2 = { "Anna" , 1024 };
    Page 383/1103

    Как и при инициализации элементов массива (см. раздел 3.5.1), если в списке инициализаторов меньше элементов, чем переменных-членов класса, последние переменные-члены инициализируются значением по умолчанию. Список инициализаторов не должен содержать больше элементов, чем переменных-членов у класса.
    Следует заметить, что у явной инициализации переменных-членов объекта класса есть три существенных недостатка.
    • Она требует, чтобы все переменные-члены были открытыми.
    • Налагает дополнительные обязанности по правильной инициализации каждой переменной-члена каждого объекта на пользователя класса (а не на его автора). Такая инициализация утомительна и часто приводит к ошибкам, поскольку достаточно просто забыть инициализатор или предоставить неподходящее значение.
    • Если добавляется или удаляется переменная-член, придется изменить все случаи инициализации. Упражнения раздела 7.5.5
    Упражнение 7.52. На примере первой версии класса Sales_data из раздела 2.6.1 объясните следующую инициализацию. Найдите и исправьте возможные ошибки.
    Sales_data item = {"978-0590353403", 25, 15.99};
    7.5.6. Литеральные классы
    В разделе 6.5.2 упоминалось, что параметры и возвращаемое значение функции constexpr должны иметь литеральные типы. Кроме арифметических типов, ссылок и указателей,
    некоторые классы также являются литеральными типами. В отличие от других классов, у классов, являющихся литеральными типами, могут быть функции-члены constexpr. Такие функции-члены должны отвечать всем требованиям функций constexpr. Эти функции-члены неявно константные (см. раздел 7.1.2).
    Агрегатный класс (см. раздел 7.5.5), все переменные-члены которого имеют литеральный тип, является литеральным классом. Неагрегатный класс, соответствующий следующим ограничениям, также является литеральным классом.
    • У всех переменных-членов должен быть литеральный тип.
    • У класса должен быть по крайней мере один конструктор constexpr.
    • Если у переменной-члена есть внутриклассовый инициализатор, инициализатор для переменной-члена встроенного типа должен быть константным выражением (см. раздел
    2.4.4). Если переменная-член имеет тип класса, инициализатор должен использовать его собственный конструктор constexpr.
    • Класс должен использовать заданное по умолчанию определение для своего деструктора —
    функции-члена класса, удаляющего объекты типа класса (см. раздел 7.1.5). Конструкторы constexpr
    Хотя конструкторы не могут быть константными (см. раздел 7.1.4), в литеральном классе они могут быть функциями constexpr (см. раздел 6.5.2). Действительно, литеральный класс должен предоставлять по крайней мере один конструктор constexpr.
    Page 384/1103

    Конструктор constexpr может быть объявлен как = default (см. раздел 7.1.4) или как удаленная функция, которые будут описаны в разделе 13.1.6. В противном случае конструктор constexpr должен отвечать требованиям к конструкторам (у него не может быть оператора return) и к функциям constexpr (его исполняемый оператор может иметь единственный оператор return)
    (см. раздел 6.5.2). В результате тело конструктора constexpr обычно пусто. Определению конструктора constexpr предшествует ключевое слово constexpr: class Debug { public: constexpr Debug(bool b = true): hw(b), io(b), other(b) { } constexpr Debug(bool h, bool i, bool o): hw(h), io(i), other(o) { } constexpr bool any() { return hw || io || other; } void set_io(bool b) { io = b; } void set_hw(bool b) { hw = b; } void set_other(bool b) { hw = b; } private: bool hw; // аппаратная ошибка, отличная от ошибки IO bool io; // ошибка IO bool other; // другие ошибки
    };
    Конструктор constexpr должен инициализировать каждую переменную-член. Инициализаторы должны либо использовать конструктор constexpr, либо быть константным выражением.
    Конструктор constexpr используется и для создания объектов constexpr, и для параметров или типов возвращаемого значения функций constexpr: constexpr Debug io_sub(false, true, false); // отладка IO if (io_sub.any()) // эквивалент if (true) cerr << "print appropriate error messages" << endl; constexpr Debug prod(false); //
    Page 385/1103
    при выпуске без отладки if (prod.any()) // эквивалент if (false) cerr << "print an error message" << endl; Упражнения раздела 7.5.6
    Упражнение 7.53. Определите собственную версию класса Debug.

    Упражнение 7.54. Должны ли члены класса Debug, начинающиеся с set_, быть объявлены как constexpr? Если нет, то почему?
    Упражнение 7.55. Является ли класс Data из раздела 7.5.5 литеральным? Если нет, то почему? Если да, то почему он является литеральным.
    7.6. Статические члены класса
    Иногда классы нуждаются в членах, ассоциированных с самим классом, а не с его индивидуальными объектами. Например, класс банковского счета, возможно, нуждается в переменной-члене, представляющей базовую процентную ставку. В данном случае мы хотели бы ассоциировать процентную ставку с классом, а не с каждым конкретным объектом.
    С точки зрения эффективности нет никаких причин хранить процентную ставку для каждого объекта. Однако важней всего то, что если процентная ставка изменится, каждый объект сразу использует новое значение. Объявление статических членов
    Чтобы сделать член класса статическим, его объявление следует предварить ключевым словом static. Статические члены, как и любые другие, могут быть открытыми или закрытыми.
    Статическая переменная-член может быть константой, ссылкой, массивом, классом и т.д.
    В качестве примера определим класс, представляющий банковскую учетную запись: class Account { public: void calculate() { amount += amount * interestRate; } static double rate() { return interestRate; } static void rate(double); private: std::string owner; double amount; static double interestRate; static double initRate();
    };
    Статические члены класса существуют вне конкретного объекта. Объекты не содержат
    Page 386/1103
    данные, связанные со статическими переменными-членами. Таким образом, каждый объект класса Account будет содержать две переменные-члена — owner и amount. Есть только один объект interestRate, совместно используемый всеми объектами Account.
    Аналогично статические функции-члены не связаны с конкретным объектом; у них нет указателя this. В результате статические функции-члены не могут быть объявлены константами и к указателю this нельзя обратиться в теле статического члена класса. Это ограничение применимо и к явному использованию указателя this, и к неявному, при вызове не статического члена класса. Использование статических членов класса
    К статическому члену класса можно обратиться непосредственно, используя оператор области видимости: double r; r = Account::rate(); // доступ к статическому члену при помощи
    // оператора области видимости
    Даже при том, что статические члены не являются частью отдельных объектов, для доступа к статическому члену класса можно использовать объект, ссылку или указатель на тип класса:
    Account ac1;
    Account *ac2 = &ac1;
    // эквивалентные способы вызова статической функции rate r = ac1.rate(); // через объект класса Account или ссылку r = ac2->rate(); // через указатель на объект класса Account
    Функции-члены могут использовать статические члены непосредственно, без оператора области видимости: class Account { public: void calculate() { amount += amount * interestRate; } private: static double interestRate; // остальные члены как прежде
    }; Определение статических членов
    Page 387/1103

    Подобно любой другой функции-члену, статическую функцию-член можно определить как в,
    так и вне тела класса. Когда статический член класса определяется вне его тела класса,
    ключевое слово static повторять не нужно, оно присутствует только в объявлении в теле класса: void Account::rate(double newRate) { interestRate = newRate;
    }
    При обращении к статическому члену класса вне тела класса, подобно любому другому члену класса, необходимо указать класс, в котором он определен. Но ключевое слово static используется только при объявлении в теле класса. В определении ключевое слово static не используется.
    Поскольку статические переменные-члены не принадлежат индивидуальным объектам класса, они не создаются при создании объектов класса. В результате они не инициализируются конструкторами класса. Кроме того, статическую переменную-член вообще нельзя инициализировать в классе. Каждую статическую переменную-член следует определить и инициализировать вне тела класса. Как и любой другой объект, статическая переменная-член может быть определена только однажды.
    Как и глобальные объекты (см. раздел 6.1.1), статические переменные-члены определяются вне любой функции. Следовательно, сразу после определения они продолжают существовать, пока программа не завершит работу.
    Статические члены определяют точно так же, как и функции-члены класса вне класса.
    Указывается тип объекта, затем имя класса, оператор области видимости и собственное имя члена:
    // определить и инициализировать статический член класса double
    Account::interestRate = initRate();
    Этот оператор определяет статический объект по имени interestRate, который является членом класса Account и имеет тип double. Подобно другим членам класса, определение статического находится в области видимости того класса, где определено его имя. В
    результате статическую функцию-член initRate() можно использовать для инициализации переменной rate непосредственно, без уточнения класса. Обратите внимание: несмотря на то, что функция-член initRate() является закрытой, ее можно использовать для инициализации объекта interestRate. Определение переменной-члена interestRate, подобно любому другому определению, находится в области видимости класса, а следовательно,
    имеет доступ к закрытым членам класса.
    Наилучший способ гарантировать, что объект будет определен только один раз, —
    разместить определение статических переменных-членов в том же файле, который содержит определение не встраиваемых функций-членов класса. Инициализация статических переменных-членов в классе
    Обычно статические переменные-члены не могут быть инициализированы в теле класса. Но можно предоставить внутриклассовые инициализаторы для тех статических переменных-членов, которые имеют тип целочисленных констант, или статических членов constexpr литерального типа (см. раздел 7.5.6). Инициализаторы должны быть константными
    Page 388/1103
    выражениями. Такие члены сами являются константными выражениями; они могут быть использованы там, где ожидается константное выражение. Например, инициализированную статическую переменную-член можно использовать для определения размерности члена типа массива: class Account { public: static double rate() { return interestRate; } static void rate(double); private: static constexpr int period = 30; // period - константное выражение double daily_tbl[period];
    };
    Если член класса используется только в контекстах, где компилятор может подставить его значение, то инициализированная константа или статическое константное выражение не следует определять отдельно. Но если член класса используется в контексте, где значение не может быть подставлено, то определение для этого члена необходимо.
    Например, если переменная period используется только для определения размерности массива daily_tbl, нет никакой необходимости определять ее за пределами класса Account. Но если пропустить определение, то даже, казалось бы, тривиальное изменение в программе может привести к отказу компиляции. Например, если передать переменную-член
    Account::period функции, получающей параметр типа const int&, то переменную period следует определить.
    Если инициализатор предоставляется в классе, определение члена класса не должно задавать исходного значения:
    // определение статического члена без инициализатора constexpr int Account::period; // инициализатор предоставлен в
    // определении класса
    Даже если константная статическая переменная-член инициализируется в теле класса, она должна определяться вне определения класса. Статические члены можно применять так, как нельзя применять обычные
    Как уже упоминалось, статические члены существуют независимо от конкретного объекта. В
    результате они применимы такими способами, которые недопустимы для нестатических переменных-членов. Например, у статической переменной-члена может быть незавершенный тип (см. раздел 7.3.3). В частности, статическая переменная-член может иметь тип,
    Page 389/1103
    совпадающий с типом класса, членом которого она является. Нестатическая переменная-член может быть только указателем или ссылкой на объект собственного класса:
    class Bar { public:
    // ... private: static Bar mem1; // ok: тип статического члена может быть
    // незавершенным
    Bar *mem2; // ok: тип указателя-члена может быть незавершенным
    Bar mem3; // ошибка: тип переменной-члена должен быть
    // завершенным
    };
    Еще одно различие между статическими и обычными членами в том, что статический член можно использовать как аргумент по умолчанию (см. раздел 6.5.1): class Screen { public:
    // bkground ссылается на статический член класса
    // объявлено позже, в определении класса
    Screen& clear(char = bkground); private: static const char bkground;
    };
    Нестатическая переменная-член не может использоваться как аргумент по умолчанию,
    поскольку ее значение является частью объекта, которому она принадлежит. Использование
    Page 390/1103
    нестатической переменной-члена как аргумента, по умолчанию не предоставляющего объект,
    которому она принадлежит, также является ошибкой. Упражнения раздела 7.6

    Упражнение 7.56. Что такое статический член класса? Каковы преимущества статических членов? Чем они отличаются от обычных членов?
    Упражнение 7.57. Напишите собственную версию класса Account.
    Упражнение 7.58. Какие из следующих объявлений и определений статических переменных-членов являются ошибочными? Объясните почему.
    // example.h class Example { public: static double rate = 6.5; static const int vecSize = 20; static vector<double> vec(vecSize);
    };
    // example.C
    #include "example.h" double Example::rate; vector<double> Example::vec;
    Резюме
    Классы — это фундаментальный компонент языка С++. Классы позволяют определять новые типы, наилучшим образом приспособленные к задачам конкретного приложения и позволяющие сделать их короче и проще в модификации.
    Основой классов являются абстракция данных (способность определять данные и функции-члены) и инкапсуляция (способность защитить члены класса от общего доступа).
    Инкапсуляция класса достигается определением членов его реализации закрытыми. Классы могут предоставить доступ к своему не открытому члену, объявив другой класс или функцию дружественной.
    Классы могут определять конструкторы — специальные функции-члены, контролирующие инициализацию объектов. Конструкторы могут быть перегружены. Для инициализации всех переменных-членов конструкторы должны использовать список инициализации конструктора.
    Классы позволяют объявлять переменные-члены изменяемыми (mutable) или статическими
    Page 391/1103

    (static). Изменяемая переменная-член никогда не становится константой — ее значение может быть изменено даже в константной функции-члене. Статической может быть как функция, так и переменная-член. Статические члены класса существуют независимо от объектов данного класса.
    Классы могут также определить изменяемые (mutable) и статические (static) члены.
    Изменяемая переменная-член никогда не становится константой; ее значение может быть изменено даже в константной функции-члене. Статический член может быть функцией или переменной; статические члены существуют независимо от объектов типа класса.
    Термины
    = default. Синтаксис, используемый после списка параметров объявления стандартного конструктора класса, чтобы сообщить компилятору о необходимости создать конструктор,
    даже если у класса есть другие конструкторы.
    Абстрактный тип данных (abstract data type). Структура данных, инкапсулирующая
    (скрывающая) свою реализацию.
    Абстракция данных (data abstraction). Технология программирования, сосредоточенная на интерфейсе типа. Абстракция данных позволяет программистам игнорировать детали реализации типа, интересуясь лишь его возможностями. Абстракция данных является основой как объектно-ориентированного, так и обобщенного программирования.
    Агрегатный класс (aggregate class). Класс только с открытыми переменными-членами, без внутриклассовых инициализаторов или конструкторов. Члены агрегатного класса могут быть инициализированы заключенным в фигурные скобки списком инициализаторов.
    Делегирующий конструктор (delegating constructor). Конструктор со списком инициализации,
    один элемент которого определяет другой конструктор того же класса для инициализации.
    Дружественные отношения (friend). Механизм, при помощи которого класс предоставляет доступ к своим не открытым членам. Дружественные классы и функции имеют те же права доступа, что и члены самого класса. Дружественными могут быть объявлены как классы, так и отдельные функции.
    Закрытый член класса (private member). Члены, определенные после спецификатора доступа private; доступный только для друзей и других членов класса. Закрытыми обычно объявляют переменные-члены и вспомогательные функции, используемые классом, но не являющиеся частью интерфейса типа.
    Изменяемая переменная-член (mutable data member). Переменная-член, которая никогда не становится константой, даже когда является членом константного объекта. Значение изменяемой переменной-члена вполне может быть изменено в константной функции.
    Инкапсуляция (encapsulation). Разделение реализации и интерфейса. Инкапсуляция скрывает детали реализации типа. В языке С++ инкапсуляция предотвращает доступ обычного пользователя класса к его закрытым членам.
    Интерфейс (interface). Открытые (public) операции, поддерживаемые типом. Обычно интерфейс не включает переменные-члены.
    Класс (class). Механизм языка С++, позволяющий создавать собственные абстрактные типы
    Page 392/1103
    данных. Классы могут содержать как данные, так и функции. Класс определяет новый тип и новую область видимости.
    Ключевое слово class. Следующие после ключевого слова class объявления класса считаются по умолчанию закрытыми (private).
    Ключевое слово struct. Следующие после ключевого слова struct объявления структуры считаются по умолчанию открытыми (public).
    Константная функция-член (const member function). Функция-член, которая не может изменять обычные (т.е. нестатические и неизменяемые) переменные-члены объекта. Указатель this константного члена класса является указателем на константу. Функция-член может быть перегружена на основании того, является ли она константной или нет.
    Конструктор (constructor). Специальная функция-член, обычно инициализирующая объекты.
    Конструктор должен присвоить каждой переменной-члену хорошо продуманное исходное значение.
    Конструктор преобразования (converting constructor). Неявный конструктор, который может быть вызван с одиночным аргументом. Конструктор преобразования используется для неявного преобразования типа аргумента в тип класса.
    Спецификатор доступа (access specifier). Ключевые слова public и private определяют,
    доступны ли данные члены для пользователей класса или только его друзьям и членам.
    Спецификаторы могут присутствовать многократно в пределах класса. Каждый спецификатор устанавливает степень доступа для последующих членов до следующего спецификатора.
    Незавершенный тип (incomplete type). Тип, который уже объявлен, но еще не определен.
    Использовать незавершенный тип для определения члена класса или переменной нельзя.
    Однако ссылки или указатели на незавершенные типы вполне допустимы.
    Область видимости класса (class scope). Каждый класс определяет область видимости.
    Область видимости класса сложнее, чем другие области видимости, поскольку определенные в теле класса функции-члены могут использовать имена, которые появятся уже после определения.
    Объявление класса (class declaration). Ключевое слово class (или struct), сопровождаемое именем класса и точкой с запятой. Если класс объявлен, но не определен, то это незавершенный тип.
    Открытый член класса (public member). Члены, определенные после спецификатора доступа public; доступны для любого пользователя класса. Обычно в разделах public определяют только те функции, которые определяют интерфейс класса.
    Поиск имени (name lookup). Процесс поиска объявления используемого имени.
    Предварительное объявление (forward declaration). Объявление имени еще не определенного класса. Как правило, используется для ссылки на объявление класса до его определения.
    См . незавершенный тип.
    Реализация (implementation). Как правило, закрытые (private) члены класса, определяющие данные и все операции, которые не предназначены для использования кодом, применяющим тип.
    Синтезируемый стандартный конструктор (synthesized default constructor). Компилятор самостоятельно создает (синтезирует) стандартный конструктор для классов, у которых не
    Page 393/1103
    определено никаких конструкторов. Этот конструктор инициализирует переменные-члены типа класса, запуская их стандартные конструкторы, а переменные-члены встроенных типов остаются неинициализированными.
    Список инициализации конструктора (constructor initializer list). Перечень исходных значений переменных-членов класса. Инициализация переменных-членов класса значениями списка осуществляется прежде, чем выполняется тело конструктора. Переменные-члены класса,
    которые не указаны в списке инициализации, инициализируются неявно, своими значениями по умолчанию.
    Стандартный конструктор (default constructor). Конструктор без параметров.
    Указатель this. Значение, неявно передаваемое как дополнительный аргумент каждой нестатической функции-члену. Указатель this указывает на объект, функция которого вызывается.
    Функция-член (member function). Член класса, являющийся функцией. Обычные функции-члены связаны с объектом класса при помощи неявного указателя this. Статические функции-члены с объектом не связаны и указателя this не имеют. Функции-члены вполне могут быть перегружены; если это так, то неявный указатель this участвует в подборе функции.
    Явный конструктор (explicit constructor). Конструктор с одним аргументом, который, однако, не может быть использован для неявного преобразования. Объявление явного конструктора предваряется ключевым словом explicit.
    Часть II
    Библиотека С++
    С каждым выпуском новой версии языка С++ росла также его библиотека. На самом деле библиотеке посвящено больше двух третей текста нового стандарта. Хоть мы и не можем рассмотреть каждое средство библиотеки подробно, ее основные средства каждый программист С++ должен знать. Эти основные средства мы и рассмотрим в данной части.
    Начнем в главе 8 с базовых средств библиотеки IO. Кроме потоков чтения и записи,
    связанных с окном консоли, библиотека определяет типы, позволяющие читать и писать в именованные файлы и строки в оперативной памяти.
    Основную часть библиотеки составляют многочисленные классы контейнеров и семейство обобщенных алгоритмов, позволяющих писать компактные и эффективные программы.
    Чтобы разработчик программы мог сосредоточиться на решении фактических проблем,
    библиотека берет на себя все подробности управления памятью.
    В главе 3 мы познакомились с контейнером типа vector. Подробней мы рассмотрим его и другие типы последовательных контейнеров в главе 9, а также изучим больше операций,
    предоставленных типом string. Строку типа string можно считать специальным контейнером,
    который содержит только символы. Тип string поддерживает многие, но не все операции контейнеров.
    В главе 10 представлены обобщенные алгоритмы. Обычно они работают с диапазоном элементов в последовательном контейнере или с другой последовательностью. Библиотека алгоритмов предоставляет эффективные реализации различных классических алгоритмов,
    Page 394/1103
    такие как сортировка и поиск, а также другие общие задачи. Например, есть алгоритм copy,
    который копирует элементы из одной последовательности в другую; алгоритм find, который ищет указанный элемент; и так далее. Алгоритмы обобщены двумя способами: они могут быть применены к различным видам последовательностей, и эти последовательности могут содержать элементы различных типов.
    Библиотека предоставляет также несколько ассоциативных контейнеров, являющихся темой главы 11. Доступ к элементам в ассоциативном контейнере осуществляется по ключу.
    Ассоциативные контейнеры имеют много общих операций с последовательными контейнерами, а также определяют операции, являющиеся специфическими для ассоциативных контейнеров.
    Завершается часть главой 12, рассматривающей средства управления динамической памятью, предоставляемые языком и библиотекой. В этой главе рассматриваются одни из самых важных новых библиотечных классов, являющихся стандартизированными версиями интеллектуальных указателей. Используя интеллектуальные указатели, можно сделать намного надежней код, который использует динамическую память. Эта глава завершается расширенным примером, в котором используются библиотечные средства, представленные во всей части II.
    Глава 8
    Библиотека ввода и вывода
    Язык С++ не имеет дела с вводом и выводом непосредственно. Вместо этого ввод и вывод обрабатываются семейством типов, определенных в стандартной библиотеке. Они обеспечивают взаимосвязь с устройствами, файлами, окнами и консолью. Есть также типы,
    обеспечивающие ввод и вывод в оперативную память и строки.
    Библиотека ввода и вывода определяет также операции чтения и записи значений встроенных типов. Кроме того, такие классы, как string, обычно определяют подобные операции ввода и вывода для работы с объектами данного класса.
    В этой главе представлены основные принципы библиотеки IO. В последующих главах рассматриваются дополнительные возможности: создание собственных операторов ввода и вывода (глава 14), контроль формата и осуществление произвольного доступа к файлам
    (глава 17).
    В предыдущих программах использовалось немало средств библиотеки IO, большинство из них было представлено в разделе 1.2.
    • Тип istream (input stream — поток ввода) обеспечивает операции ввода.
    • Тип ostream (output stream — поток вывода) обеспечивает операции вывода.
    • Объект cin класса istream читает данные со стандартного устройства ввода.
    • Объект cout класса ostream записывает данные на стандартное устройство вывода.
    • Объект cerr класса ostream записывает данные на стандартное устройство сообщений об ошибке. Объект cerr, как правило, используется для сообщений об ошибках в программе.
    • Оператор >> используется для чтения данных, передаваемых в объект класса istream.
    Page 395/1103

    • Оператор << используется для записи данных, передаваемых в объект класса ostream.
    • Функция getline() (см. раздел 3.2.2) получает ссылку на объект класса istream и ссылку на объект класса string, а затем читает слово из потока ввода в строку.
    8.1. Классы ввода-вывода
    Типы и объекты ввода-вывода, которые мы использовали до сих пор, манипулируют символьными данными. По умолчанию эти объекты связаны с окном консоли пользователя.
    Конечно, реальные программы не могут быть ограничены вводом и выводом исключительно в окно консоли. Программам нередко приходится читать или писать в именованные файлы.
    Кроме того, операции ввода-вывода зачастую удобно использовать для обработки символов в строке. Приложениям также, вероятно, понадобится читать и писать на языках, которые требуют поддержки расширенных символов.
    Для поддержки столь разных видов обработки ввода-вывода, кроме уже использованных ранее типов istream и ostream, библиотека определяет целую коллекцию типов ввода-вывода.
    Эти типы (табл. 8.1) определены в трех отдельных заголовках: заголовок iostream определяет базовые типы, используемые для чтения и записи в поток, заголовок fstream определяет типы, используемые для чтения и записи в именованные файлы, заголовок sstream определяет типы, используемые для чтения и записи в строки, расположенные в оперативной памяти.
    Таблица 8.1. Типы и заголовки библиотеки ввода-вывода Заголовок Тип iostream istream,
    wistream — читают данные из потока ostream, wostream — записывают данные в поток iostream, wiostream — читают и записывают данные в поток fstream ifstream, wifstream —
    читают данные из файла оfstream, wofstream — записывают данные в файл fstream, wfstream
    — читают и записывают данные в файл sstream istringstream, wistringstream — читают данные из строки ostringstream, wostringstream — записывают данные в строку stringstream,
    wstringstream — читают и записывают данные в строку
    Для поддержки языков, использующих расширенные символы, библиотека определяет набор типов и объектов, манипулирующих данными типа wchar_t (см. раздел 2.1.1). Имена версий для расширенных символов начинаются с буквы w. Например, объекты wcin, wcout и wcerr соответствуют обычным объектам cin, cout и cerr, но для расширенных символов. Такие объекты определяются в том же заголовке, что и типы для обычных символов. Например,
    заголовок fstream определяет типы ifstream и wifstream. Взаимоотношения между типами ввода и вывода
    Концептуально ни вид устройства, ни размер символов не влияют на операции ввода-вывода.
    Например, оператор >> можно использовать для чтения данных из окна консоли, из файла на диске или из строки. Точно так же этот оператор можно использовать независимо от того, читаются ли символы типа char или wchar_t.
    Используя наследование (inheritance), библиотека позволяет игнорировать различия между потоками различных видов. Подобно шаблонам (см. раздел 3.3), связанные наследованием классы можно использовать, не вникая в детали того, как они работают. Более подробная информация о наследовании в языке С++ приведена в главе 15 и в разделе 18.3.
    Page 396/1103

    Если говорить коротко, то наследование позволяет сказать, что некий класс происходит от другого класса. Обычно объект производного класса можно использовать так, как будто это объект класса, от которого он происходит.
    Типы ifstream и istringstream происходят от класса istream. Таким образом, объект типа ifstream или istringstream можно использовать так, как будто это объект класса istream.
    Объекты этих типов можно использовать теми же способами, что и объект cin. Например,
    можно вызвать функцию getline() объекта ifstream или istringstream либо использовать их оператор >> для чтения данных. Точно так же типы ofstream и ostringstream происходят от класса ostream. Следовательно, объекты этих типов можно использовать теми же способами, что и объект cout.
    Все, что рассматривается в остальной части этого раздела, одинаково применимо как к простым, файловым и строковым потокам, а также к потокам для символов типа char или wchar_t.
    8.1.1. Объекты ввода-вывода не допускают копирования и присвоения
    Как упоминалось в разделе 7.1.3, объекты ввода-вывода не допускают копирования и присвоения: ofstream out1, out2; out1 = out2; // ошибка: нельзя присваивать потоковые объекты ofstream print(ofstream); // ошибка: нельзя инициализировать параметр
    // типа ofstream out2 = print(out2); // ошибка: нельзя копировать потоковые объекты
    Поскольку объекты типа ввода-вывода нельзя копировать, не может быть параметра или типа возвращаемого значения одного из потоковых типов (см. раздел 6.2.1). Функции,
    осуществляющие ввод-вывод, получают и возвращают поток через ссылки. Чтение или запись в объект ввода-вывода изменяет его состояние, поэтому ссылка не должна быть константой.
    8.1.2. Флаги состояния
    В связи с наследованием классов ввода-вывода возможно возникновение ошибок. Некоторые из ошибок исправимы, другие происходят глубоко в системе и не могут быть исправлены в области видимости программы. Классы ввода-вывода определяют функции и флаги,
    Page 397/1103
    перечисленные в табл. 8.2, позволяющие обращаться к флагам состояния (condition state) потока и манипулировать ими.
    Таблица 8.2. Флаги состояния библиотеки ввода-вывода strm ::iostate strm — один из типов ввода-вывода, перечисленных в табл. 8.1. iostate —
    машинно-зависимый целочисленный тип, представляющий флаг состояния потока strm ::badbit Значение флага strm ::iostate указывает, что поток недопустим strm ::failbit Значение флага strm ::iostate указывает, что операция ввода- вывода закончилась неудачей strm ::eofbit Значение флага strm ::iostate указывает, что поток достиг конца файла strm ::goodbit Значение флага strm ::iostate указывает, что поток не находится в недопустимом состоянии. Это значение гарантированно будет нулевым s .eof() Возвращает значение true, если для потока s установлен флаг eofbit s .fail() Возвращает значение true, если для потока s установлен флаг failbit s .bad() Возвращает значение true, если для потока s установлен флаг badbit s .good() Возвращает значение true, если поток s находится в допустимом состоянии s .clear() Возвращает все флаги потока s в допустимое состояние s .clear( флаг ) Устанавливает определенный флаг (флаги) потока s в допустимое состояние.
    Флаг имеет тип strm ::iostate
    Page 398/1103
    s .setstate( флаг ) Добавляет в поток s определенный флаг. Флаг имеет тип strm ::iostate s .rdstate() Возвращает текущее состояние потока s как значение типа strm ::iostate
    В качестве примера ошибки ввода-вывода рассмотрим следующий код: int ival; cin >> ival;
    Если со стандартного устройства ввода ввести, например, слово Boo, то операция чтения потерпит неудачу. Оператор ввода ожидал значение типа int, но получил вместо этого символ
    В. В результате объект cin перешел в состояние ошибки. Точно так же объект cin окажется в состоянии ошибки, если ввести символ конца файла.
    Как только произошла ошибка, последующие операции ввода-вывода в этом потоке будут терпеть неудачу. Читать или писать в поток можно только тогда, когда он находится в неошибочном состоянии. Поскольку поток может оказаться в ошибочном состоянии, код должен проверять его, прежде чем использовать. Проще всего определить состояние потокового объекта — это использовать его в условии: while (cin >> word)
    // ok: операция чтения успешна ...
    Условие оператора while проверяет состояние потока, возвращаемого выражением >>.
    Если данная операция ввода успешна, состояние остается допустимым и условие выполняется. Опрос состояния потока
    Использование потока в условии позволяет узнать только то, допустим ли он. Это ничего не говорит о случившемся. Иногда необходимо также узнать причину недопустимости потока.
    Например, действия после достижения конца файла, вероятно, будут отличаться от таковых после ошибки на устройстве ввода-вывода.
    Библиотека ввода-вывода определяет машинно-зависимый целочисленный тип iostate,
    используемый для передачи информации о состоянии потока. Этот тип используется как коллекция битов, подобно переменной quiz1 в разделе 4.8. Классы ввода-вывода определяют четыре значения constexpr (разделе 2.4.4) типа iostate, представляющие конкретные битовые схемы. Эти значения используются для указания конкретных видов состояний ввода-вывода.
    Они используются с побитовыми операторами (см. раздел 4.8) для проверки или установки нескольких флагов за раз.
    Флаг badbit означает отказ системного уровня, такой как неисправимая ошибка при чтении или записи. Как только флаг badbit установлен, использовать поток обычно больше невозможно. Флаг failbit устанавливается после исправимой ошибки, такой как чтение
    Page 399/1103
    символа, когда ожидались числовые данные. Как правило, такие проблемы вполне можно исправить и продолжить использовать поток. Достижение конца файла устанавливает флаги и eofbit и failbit. Флаг goodbit, у которого гарантированно будет значение 0, не означает отказа в потоке. Если любой из флагов badbit, failbit или eofbit будет установлен, то оценивающее данный поток условие окажется ложным.
    Библиотека определяет также набор функций для опроса состояния этих флагов. Функция good() возвращает значение true, если ни один из флагов ошибок не установлен. Функции bad(), fail() и eof() возвращает значение true, когда установлен соответствующий бит. Кроме того, функция fail() возвращает значение true, если установлен флаг badbit. Корректный способ определения общего состояния потока подразумевал бы использование функции good() или fail(). На самом деле код проверки потока в условии эквивалентен вызову !fail().
    Функции bad() и eof() оповещают только о конкретной ошибке. Управление флагами состояния
    Функция-член rdstate() возвращает значение типа iostate, соответствующее текущему состоянию потока. Функция setstate() позволяет установить указанные биты состояния, чтобы указать возникшую проблему. Функция clear() перегружена (см. раздел 6.4): одна ее версия не получает никаких аргументов, а вторая получает один аргумент типа iostate.
    Версия функции clear(), не получающая никаких аргументов, сбрасывает все биты отказа.
    После ее вызова функция good() возвращает значение true. Эти функции-члены можно использовать следующим образом:
    // запомнить текущее состояние объекта cin auto old_state = cin.rdstate(); cin.clear(); // сделать объект cin допустимым process_input(cin); // использовать объект cin cin.setstate(old_state); // вернуть объект cin в прежнее состояние
    Версия функции clear(), получающая аргумент, ожидает значение типа iostate,
    представляющее новое состояние потока. Для сброса отдельного флага используется функция-член rdstate() и побитовые операторы, позволяющие создать новое желаемое состояние.
    Например, следующий код сбрасывает биты failbit и badbit, а бит eofbit оставляет неизменным:
    // сбросить биты failbit и badbit, остальные биты оставить неизменными cin.clear(cin.rdstate() &

    cin.failbit & cin.badbit); Упражнения раздела 8.1.2
    Упражнение 8.1. Напишите функцию, получающую и возвращающую ссылку на объект класса
    Page 400/1103
    istream. Функция должна читать данные из потока до тех пор, пока не будет достигнут конец файла. Функция должна выводить прочитанные данные на стандартное устройство вывода.
    Перед возвращением потока верните все значения его флагов в допустимое состояние.
    Упражнение 8.2. Проверьте созданную функцию, передав ей при вызове объект cin в качестве аргумента.
    Упражнение 8.3. В каких случаях завершится следующий цикл while? while (cin >> i) /* ... */
    8.1.3. Управление буфером вывода
    Каждый объект ввода-вывода управляет буфером, используемым для хранения данных,
    которые программа читает или записывает. Например, при выполнении следующего кода литеральная строка могла бы быть выведена немедленно или операционная система могла бы сохранить данные в буфере и вывести их позже: os << "please enter a value: ";
    Использование буфера позволяет операционной системе объединить несколько операций вывода данной программы на системном уровне в одну операцию. Поскольку запись в устройство может занять много времени, возможность операционной системы объединить несколько операций вывода в одну может существенно повысить производительность.
    Существует несколько условий, приводящих к сбросу буфера, т.е. к фактической записи на устройство вывода или в файл.
    • Программа завершается нормально. Все буфера вывода освобождаются при выходе из функции main().
    • В некий случайный момент времени буфер может оказаться заполненным. В этом случае перед записью следующего значения происходит сброс буфера.
    • Сброс буфера можно осуществить явно, использовав такой манипулятор, как endl (см.
    раздел 1.2).
    • Используя манипулятор unitbuf, можно установить такое внутреннее состояние потока,
    чтобы буфер освобождался после каждой операции вывода. Для объекта cerr манипулятор unitbuf установлен по умолчанию, поэтому запись в него приводит к немедленному выводу.
    • Поток вывода может быть связан с другим потоком. В таком случае буфер привязанного потока сбрасывается при каждом чтении или записи другого потока. По умолчанию объекты cin и cerr привязаны к объекту cout. Следовательно, чтение из потока cin или запись в поток cerr сбрасывает буфер потока cout. Сброс буфера вывода
    В приведенных ранее программах уже не раз использовался манипулятор endl, который записывает символ новой строки и сбрасывает буфер. Существуют еще два подобных манипулятора: flush и ends. Манипулятор flush используется для сброса буфер потока без добавления символов в вывод. Манипулятор ends добавляет в буфер нулевой символ, а затем сбрасывает его. cout << "hi!" << endl; //
    Page 401/1103
    выводит hi, новую строку и сбрасывает буфер cout << "hi!" << flush; // выводит hi и сбрасывает буфер, ничего не
    // добавляя cout << "hi!" << ends; // выводит hi, нулевой символ
    // и сбрасывает буфер Манипулятор unitbuf
    Если сброс необходим при каждом выводе, лучше использовать манипулятор unitbuf, который сбрасывает буфер потока после каждой записи. Манипулятор nounitbuf восстанавливает для потока использование обычного управляемого системой сброса буфера: cout << unitbuf; // при любой записи буфер будет сброшен немедленно
    // любой вывод сбрасывается немедленно, без всякой буферизации cout << nounitbuf; // возвращение к обычной буферизации Внимание! При сбое программы буфер не сбрасывается
    Буфер вывода не сбрасывается , если программа завершается аварийно. При сбое программы вполне вероятно, что выводимые ею данные могут остаться в буфере, ожидая вывода.
    При попытке отладить аварийно завершающуюся программу необходимо гарантировать, что любой подозрительный вывод будет сброшен сразу. Программист может впустую потратить множество часов на отслеживание вовсе не того кода только потому, что фактически последний буфер вывода просто не сбрасывается.Связывание потоков ввода и вывода
    Когда поток ввода связан с потоком вывода, любая попытка чтения данных из потока ввода приведет к предварительному сбросу буфера, связанного с потоком вывода. Библиотечные объекты cout и cin уже связаны, поэтому оператор cin >> ival; заставит сбросить буфер,
    связанный с объектом cout.
    В интерактивных системах потоки ввода и вывода обычно связаны. При выполнении программы это гарантирует, что приглашения к вводу будут отображены до того, как система перейдет к ожиданию ввода данных пользователем.
    Существуют две перегруженные (см. раздел 6.4) версии функции tie(): одна не получает никаких аргументов и возвращает указатель на поток вывода, к которому в настоящее время
    Page 402/1103
    привязан данный объект, если таковой вообще имеется. Функция возвращает пустой указатель, если поток не связан.
    Вторая версия функции tiе() получает указатель на объект класса ostream и связывает себя с ним. Таким образом, код x.tie(&o) связывает поток x с потоком вывода o.
    Объект класса istream или ostream можно связать с другим объектом класса ostream: cin.tie(&cout); // только для демонстрации: библиотека
    // автоматически связывает объекты cin и cout
    // old_tie указывает на поток (если он есть),
    // в настоящее время связанный с объектом cin ostream *old_tie = cin.tie(nullptr); // объект cin больше не связан
    // связь cin и cerr; не лучшая идея, поскольку объект cin должен быть
    // привязан к объекту cout cin.tie(&cerr); // чтение в cin сбрасывает объект cerr, а не cout cin.tie(old_tie); // восстановление обычной связи между cin и cout
    Чтобы связать данный поток с новым потоком вывода, функции tie() передают указатель на новый поток. Чтобы разорвать существующую связь, достаточно передать в качестве аргумента значение 0. Каждый поток может быть связан одновременно только с одним потоком. Однако несколько потоков могут связать себя с тем же объектом ostream.
    8.2. Ввод и вывод в файл
    В заголовке fstream определены три типа, поддерживающие операции ввода и вывода в файл: класс ifstream читает данные из указанного файла, класс ofstream записывает данные в файл, класс fstream читает и записывает данные в тот же файл. Использование того же файла для ввода и вывода рассматривается в разделе 17.5.3.
    Page 403/1103

    Эти типы поддерживают те же операции, что и описанные ранее объекты cin и cout. В
    частности, для чтения и записи в файлы можно использовать операторы ввода-вывода
    (<< и >>), можно использовать функцию getline() (см. раздел 3.2.2) для чтения из потока ifstream. Материал, изложенный в разделе 8.1, относится также и к этим типам.
    Кроме поведения, унаследованного от типа iostream, определенные в заголовке fstream типы имеют в дополнение члены для работы с файлами, связанными с потоком. Эти операции перечислены в табл. 8.3, они могут быть вызваны для объектов классов fstream, ifstream или ofstream, но не других типов ввода-вывода.
    Таблица 8.3. Операции, специфические для типов заголовка fstream fstream fstrm; Создает несвязанный файловый поток, fstream — это один из типов, определенных в заголовке fstream fstream fstrm( s ); Создает объект класса fstream и открывает файл по имени s . Параметр s может иметь тип string или быть указателем на символьную строку в стиле С (см. раздел
    3.5.4). Эти конструкторы являются явными (см. раздел 7.5.4). Заданный по умолчанию режим файла зависит от типа fstream fstream fstrm( s , режим ); Подобен предыдущему конструктору, но открывает файл s в указанном режиме fstrm.open( s ) fstrm.open( s , режим ) Открывает файл s и связывает его с потоком fstrm. Параметр s может иметь тип string или быть указателем на символьную строку в стиле С. Заданный по умолчанию режим файла зависит от типа fstream . Возвращает тип void fstrm.close() Закрывает файл, с которым связан поток fstrm.
    Возвращает тип void fstrm.is_open() Возвращает значение типа bool, указывающее, был ли связанный с потоком fstrm файл успешно открыт и не был ли он закрыт
    8.2.1. Использование объектов файловых потоков
    Page 404/1103

    Когда необходимо читать или писать в файл, определяется объект файлового потока (file stream), который связывается с файлом. Каждый класс файлового потока определяет функцию-член open(), которая выполняет все системные операции,
    необходимые для поиска указанного файла и его открытия для чтения или записи.
    При создании файлового потока можно (но не обязательно) указать имя файла. При предоставлении имени файла функция open() вызывается автоматически: ifstream in(ifile); // создать объект ifstream и открыть указанный файл ofstream out; // файловый поток вывода, не связанный с файлом
    Этот код определяет in как входной поток, инициализированный для чтения из файла,
    указанного строковым аргументом ifile. Код определяет out как поток вывода, который еще не связан с файлом. По новому стандарту имена файлов могут быть переданы как в переменной библиотечного типа string, так и в символьном массиве в стиле С (см. раздел 3.5.4).
    Предыдущие версии библиотеки допускали только символьные массивы в стиле С.
    Использование fstream вместо iostream&
    Как упоминалось в разделе 8.1, объект производного типа можно использовать в тех местах,
    где ожидается объект базового типа. Благодаря этому факту функции, получающие ссылку
    (или указатель) на один из типов iostream, могут быть вызваны от имени соответствующего типа fstream (или sstream). Таким образом, если имеется функция, получающая ссылку ostream&, то ее можно вызвать, передав объект типа ofstream, то же относится к ссылке istream& и типу ifstream.
    Например, функции read() и print() (см. раздел 7.1.3) можно использовать для чтения и записи в именованный файл. В этом примере подразумевается, что имена файлов ввода и вывода передаются как аргументы функции main() (см. раздел 6.2.5): ifstream input (argv[1]); // открыть файл транзакций продаж ofstream output(argv[2]); // открыть файл вывода
    Sales_data total; // переменная для хранения текущей суммы if (read(input, total)) { // прочитать первую транзакцию
    Sales_data trans; // переменная для хранения данных следующей
    //
    Page 405/1103
    транзакции while(read(input, trans)) { // читать остальные транзакции if (total.isbn() == trans.isbn()) // проверить isbn total.combine(trans); // обновить текущую сумму else { print(output, total) << endl; // отобразить результат total = trans; // обработать следующую книгу
    }
    } print (output, total) << endl; // отобразить последнюю транзакцию
    } else // ввода нет cerr << "No data?!" << endl;
    Кроме использования именованных файлов, этот код практически идентичен версии программы сложения, приведенной в разделе 7.1.1. Важнейшая часть — вызов функций read() и print(). Этим функциям можно передать объекты типа fstream, хотя типами их параметров определены istream& и ostream& соответственно. Функции-члены open()
    и close()
    Когда определяется пустой объект файлового потока, вызвав функцию open(), его впоследствии можно связать с файлом: ifstream in(ifile); // создать объект ifstream и открыть указанный файл ofstream out; // файловый поток вывода, не связанный ни с каким
    // файлом
    Page 406/1103
    out.open(ifile + ".copy"); // открыть указанный файл
    При неудаче вызова функции open() устанавливается бит failbit (см. раздел 8.1.2). Поскольку вызов функции open() может потерпеть неудачу, имеет смысл проверить ее успешность: if (out) // проверить успешность вызова функции open
    // вызов успешен, файл можно использовать
    Это подобно использованию объекта cin в условии. При неудаче вызова функции open()
    условие не выполняется и мы не будем пытаться использовать объект in.
    Как только файловый поток будет открыт, он остается связанным с определенным файлом.
    На самом деле вызов функции open() для файлового потока, который уже открыт, приводит к установке бита failbit. Последующие попытки использования этого файлового потока потерпят неудачу. Чтобы связать файловый поток с другим файлом, необходимо сначала закрыть существующий файл. Как только файл закрывается, его можно открыть снова: in.close(); // закрыть файл in.open(ifile + "2"); // открыть другой файл
    Если вызов функции open() успешен, поток устанавливается в такое состояние, что функция good() возвратит значение true. Автоматическое создание и удаление
    Рассмотрим программу, функция main() которой получает список файлов для обработки (см.
    раздел 6.2.5). У такой программы может быть следующий цикл:
    // для каждого переданного программе файла for (auto p = argv + 1; p != argv + argc; ++p) { ifstream input(*p); // создает input и открывает файл if (input) { // если ошибки с файлом нет, обработать его process(input);
    } else cerr << "couldn't open: " + string(*p);
    } //
    Page 407/1103
    input выходит из области видимости и удаляется при каждой итерации
    При каждой итерации создается новый объект класса ifstream по имени input и открывается файл для чтения. Как обычно, проверяется успех вызова функции open(). Если все в порядке,
    этот файл передается функции, которая будет читать и обрабатывать ввод. В противном случае выводится сообщение об ошибке.
    Поскольку объект input является локальным для цикла while, он создается и удаляется при каждой итерации (см. раздел 5.4.1). Когда объект fstream выходит из области видимости,
    файл, к которому он привязан, автоматически закрывается. На следующей итерации объект input создается снова.
    Когда объект класса fstream удаляется, автоматически вызывается функция close().
    Упражнения раздела 8.2.1
    Упражнение 8.4. Напишите функцию, которая открывает файл и читает его содержимое в вектор строк, сохраняя каждую строку как отдельный элемент вектора.
    Упражнение 8.5. Перепишите предыдущую программу так, чтобы каждое слово сохранялось в отдельном элементе.
    Упражнение 8.6. Перепишите программу книжного магазина из раздела 7.1.1 так, чтобы читать транзакции из файла. Передавайте имя файла как аргумент функции main() (см.
    раздел 6.2.5).
    8.2.2. Режимы файла
    Каждый поток обладает режимом файла (file mode), определяющим возможный способ использования файла. Список режимов файла и их значений приведен в табл. 8.4.
    Таблица 8.4. Режимы файла in Открывает файл для ввода out Открывает файл для вывода app Переходит в конец файла перед каждой записью ate Переходит в конец файла непосредственно после открытия trunc Усекает существующий поток при открытии binary
    Осуществляет операции ввода-вывода в бинарном режиме
    Режим файла можно указать при каждом открытии файла, будь то вызов функции open() или косвенное открытие файла при инициализации потока именем файла. У режимов, которые можно задать, есть ряд ограничений.
    • Режим out может быть установлен только для объектов типа ofstream или fstream.
    • Режим in может быть установлен только для объектов типа ifstream или fstream.
    • Режим trunc может быть установлен, только если устанавливается также режим out.
    • Режим app может быть установлен, только если не установлен режим trunc. Если режим app установлен, файл всегда открывается в режиме вывода, даже если это не было указано явно.
    • По умолчанию файл, открытый в режиме out, усекается, даже если не задан режим trunc.
    Page 408/1103

    Чтобы сохранить содержимое файла, открытого в режиме out, необходимо либо задать также режим app, тогда можно будет писать только в конец файла, либо задать также режим in,
    тогда файл откроется и для ввода, и для вывода. Использование того же файла для ввода и вывода рассматривается в разделе 17.5.3.
    • Режимы ate и binary могут быть установлены для объекта файлового потока любого типа и в комбинации с любыми другими режимами.
    Для каждого типа файлового потока задан режим файла по умолчанию, который используется в случае, если режим не задан. Файлы, связанные с потоками типа ifstream,
    открываются в режиме in; файлы, связанные с потоками типа ofstream, открываются в режиме out; а файлы, связанные с потоками типа fstream, открываются в режимах in и out. Открытие файла в режиме out удаляет существующие данные
    По умолчанию при открытии потока типа ofstream содержимое файла удаляется.
    Единственный способ воспрепятствовать удалению данных файла подразумевает установку режима app:
    // file1 усекается в каждом из следующих случаев ofstream out("file1"); // out и trunc установлены неявно ofstream out2("file1", ofstream::out); // trunc установлен неявно ofstream out3("file1", ofstream::out | ofstream::trunc);
    // для сохранения содержимого файла следует явно задать режим app ofstream app("file2", ofstream::app); // out установлен неявно ofstream app2("file2", ofstream::out | ofstream::app);
    Единственный способ сохранить существующие данные в файле, открытом потоком типа ofstream, — это явно установить режим app или in. Режим файла устанавливается при каждом вызове функции open()
    Режим файла некоего потока может изменяться при каждом открытии файла. ofstream out; // режим файла не установлен out.open("scratchpad"); // неявно заданы режимы out и trunc out.close(); //
    Page 409/1103
    out закрыт, его можно использовать для другого файла out.open("precious", ofstream::app); // режимы out и app out.close();
    Первый вызов функции open() не задает режим вывода явно; этот файл неявно открывается в режиме out. Как обычно, режим out подразумевает также режим trunc. Поэтому файл scratchpad, расположенный в текущем каталоге, будет усечен. Когда открывается файл precious, задается режим добавления. Все данные остаются в файле, а запись осуществляется в конец файла.
    Режим файла устанавливается при каждом вызове функции open() явно или неявно. Когда режим не устанавливается явно, используется значение по умолчанию. Упражнения раздела
    8.2.2
    Упражнение 8.7. Пересмотрите программу книжного магазина из предыдущего раздела так,
    чтобы вывод записывался в файл. Передайте имя этого файла как второй аргумент функции main().
    Упражнение 8.8. Пересмотрите программу из предыдущего упражнения так, чтобы добавить ее вывод в заданный файл. Запустите программу для того же выходного файла по крайней мере дважды и удостоверьтесь, что данные сохраняются.
    8.3. Строковые потоки
    Заголовок sstream определяет три типа, поддерживающие операции ввода-вывода в оперативной памяти; эти типы обеспечивают чтение или запись в строку, как будто она является потоком ввода-вывода.
    Объект класса istringstream читает строку, объект класса ostringstream записывает строку, а объект класса stringstream читает и записывает строку. Подобно типам заголовка fstream,
    типы, определенные в заголовке sstream, происходят от типов, используемых заголовком iostream. Кроме унаследованных операций, типы, определенные в заголовке sstream, имеют дополнительные члены для работы со строками, связанными с потоком. Эти операции перечислены в табл. 8.5. Они могут быть выбраны для объектов класса stringstream, строковых потоков (string stream), но не других типов ввода-вывода.
    Обратите внимание на то, что хотя заголовки fstream и sstream имеют общий интерфейс к заголовку iostream, никакой другой взаимосвязи у них нет. В частности, нельзя использовать функции open() и close() для объектов класса stringstream, а функцию str() нельзя использовать для объектов класса fstream.
    Таблица 8.5. Операции, специфические для класса stringstream sstream strm; strm — несвязанный объект класса stringstream. sstream — это один из типов, определенных в заголовке sstream
    Page 410/1103
    sstream strm(s); sstream содержит копию строки s. Этот конструктор является явным (см. раздел 7.5.4).
    strm.str() Возвращает копию строки, которую хранит объект strm strm.str(s) Копирует строку s в объект strm. Возвращает тип void
    8.3.1. Использование класса istringstream
    Класс istringstream зачастую используют тогда, когда некую работу следует выполнить со всей строкой и другую работу с отдельными словами в пределах строки.
    Предположим, например, что имеется файл, содержащий список людей и номеров их телефонов. У одних людей есть только один номер, а у других несколько — домашний телефон, рабочий, мобильный и т.д. Наш исходный файл может выглядеть следующим образом: morgan 2015552368 8625550123 drew 9735550130 lee 6095550132 2015550175 8005550000
    Каждая запись в этом файле начинается с имени, затем следует один или несколько номеров телефонов. Для начала определим простой класс, представляющий исходные данные:
    // по умолчанию члены являются открытыми; см. раздел 1.2 struct PersonInfo { string name; vector<string> phones;
    };
    Один член класса PersonInfo будет представлять имя человека, а вектор будет содержать переменное количество его номеров телефонов.
    Наша программа будет читать файл данных и создавать вектор объекта класса PersonInfo.
    Каждый элемент вектора будет соответствовать одной записи в файле. Ввод обрабатывается в цикле, который читает запись, а затем извлекает имя и номера телефона каждого человека:
    string line, word; // будут содержать строку и слово из ввода vector<PersonInfо> people; // будет содержать все записи из ввода
    //
    Page 411/1103
    читать ввод по строке за раз, пока не встретится конец файла
    //
    (или другая ошибка) while (getline(cin, line)) {
    PersonInfo info; // создать объект для содержания данных записи istringstream record(line); // связать запись с читаемой строкой record >> info.name; // читать имя while (record >> word) // читать номер телефона info.phones.push_back(word); // и сохранить их people.push_back(info); // добавить эту запись в people
    }
    Здесь для чтения всей записи со стандартного устройства ввода используется функция getline(). Если вызов функции getline() успешен, то переменная line будет содержать запись из входного файла. В цикле while определяется локальный объект PersonInfo для содержания данных из текущей записи.
    Затем с только что прочитанной строкой связывается поток istringstream. Теперь для чтения каждого элемента в текущей записи можно использовать оператор ввода класса istringstream.
    Сначала читается имя, затем следует цикл while, читающий номера телефонов данного человека.
    Внутренний цикл while завершается, когда все данные в строке прочитаны. Этот цикл работает аналогично другим, написанным для чтения из объекта cin. Различие только в том,
    что этот цикл читает данные из строки, а не со стандартного устройства ввода. Когда строка прочитана полностью, встретившийся "конец файла" свидетельствует о том, что следующая операция ввода в объект record потерпит неудачу.
    Внешний цикл while завершается добавлением в вектор только что обработанного объекта класса PersonInfo. Внешний цикл while продолжается, пока объект cin не встретит конец файла. Упражнения раздела 8.3.1
    Упражнение 8.9. Используйте функцию, написанную для первого упражнения 8.1.2, для вывода содержимого объекта класса istringstream.
    Упражнение 8.10. Напишите программу для сохранения каждой строки из файла в векторе
    Page 412/1103
    vector<string>. Затем используйте объект класса istringstream для чтения каждого элемента из вектора по одному слову за раз.
    Упражнение 8.11. Программа этого раздела определила свой объект класса istringstream во внешнем цикле while. Какие изменения необходимо внести, чтобы определить объект record вне этого цикла? Перепишите программу, перенеся определение объекта record во вне цикла while, и убедитесь, все ли необходимые изменения внесены.

    1   ...   18   19   20   21   22   23   24   25   ...   54


    написать администратору сайта