На этом шаге мы рассмотрим особенности использования потоковых классов char*.
Потоковые классы char* поддерживаются только в целях обратной совместимости. Их интерфейс иногда порождает ошибки, с этими
классами часто работают неправильно. Тем не менее они продолжают широко использоваться, поэтому далее приводятся их краткие описания.
Учтите, что в описываемой здесь стандартной версии прежний интерфейс был слегка изменен.
Далее вместо термина строка будет использоваться термин последовательность символов. Дело в том, что
последовательность символов, поддерживаемая потоковыми классами char*, не всегда заканчивается символом завершения строки (а
следовательно, не является строкой в обычном понимании).
Потоковые классы char* определены только для символьного типа char. К этой категории относятся следующие классы:
- istrstream для чтения из последовательностей символов;
- ostrstream для записи в последовательности символов;
- strstream для чтения из последовательностей символов и записи в последовательности символов;
- strstreambuf используется как потоковый буфер для потоков char*.
Потоковые классы char* определяются в заголовочном файле <strstream>.
Поток данных istrstream может инициализироваться последовательностью символов (типа char*), либо завершенной символом \0,
либо имеющей длину, переданную в аргументе. Типичный пример чтения и обработки строк:
char buffer[1000]; // Буфер, в которой могут храниться до 999 символов // Чтение строки std::cin.get(buffer,sizeof(buffer)); // Чтение/обработка строки как потока std::istrstream input(buffer); . . . . . input >> x;
Поток данных char*, предназначенный для записи, поддерживает последовательность символов, растущую по мере необходимости, или
инициализируется буфером фиксированного размера. При помощи флагов ios::app и ios::ate можно дописывать выводимые символы
к последовательности, уже хранящейся в буфере.
При строковой интерпретации потока данных char* необходима осторожность. В отличие от строковых потоков данных потоки типа char*
не всегда следят за использованием памяти, в которой хранится последовательность символов.
Функция str() предоставляет вызывающей стороне доступ к последовательности символов вместе с ответственностью за управление
соответствующей памятью. Если поток данных не был инициализирован буфером фиксированного размера (за который поток никогда не отвечает),
должны соблюдаться следующие трн правила.
- Поскольку права владения памятью передаются вызывающей стороне (если поток данных не был инициализирован буфером фиксированного
размера), символьная последовательность должна освобождаться. Но так как не гарантируется, что память выделялась, вызов delete[] не
всегда безопасен. Лучше всего вернуть память в поток данных вызовом функции freeze с аргументом false (пример приводится далее). - Вызов str() запрещает потоку данных дальнейшее изменение последовательности символов. Функция неявно вызывает функцию freeze(),
которая фиксирует ("замораживает") последовательность символов. Это делается для того, чтобы избежать трудностей при недостаточном размере
буфера и необходимости выделять новую память. - Функция str() не присоединяет символ завершения строки ('\0'). Это символ приходится отдельно присоединять к потоку
данных для завершения последовательности символов. Задача решается при помощи манипулятора ends. Некоторые реализации присоединяют
символ завершения строки автоматически, но это поведение нарушает переносимость программы.
Пример использования потока char*:
float x; // Создание и заполнение потока char* // - не забывайте о ends или '\0'!!! std::ostrstream buffer; // Динамический потоковый буфер buffer << "float х: " << х << std::ends; // Передача полученной С-строки функции foo() и возвращение памяти в буфер char* s = buffer.str(); foo(s); buffer.freeze(false);
Зафиксированный поток char* можно привести в обычное состояние для дополнительной обработки. Для этого следует вызвать функцию
freeze() с аргументом false. При выполнении этой операции право владения последовательностью символов снова возвращается
объекту потока данных. Это единственный безопасный способ освобождения памяти для последовательности символов. В следующем примере
показано, как это делается:
float х; std::ostrstream buffer; // Динамический поток char* // Заполнение потока char* buffer << "float x: " << x << std::ends; // Передача итоговой С-строки функции foo() // - фиксация потока char* foo(buffer.str()); // Снятие фиксации с потока char* buffer.freeze(false); // Установка позиции записи в начало buffer.seekp (0,ios::beg); // Повторное заполнение потока char* buffer << "once more float x: " << x << std::ends; // Повторная передача полученной С-строки функции foo() // - фиксация потока char*; foo(buffer.str()); // Возвращение памяти в буфер buffer.freeze(false);
Проблем, возникающих из-за фиксации потока данных, нет в классах строковых потоков данных. В основном это объясняется тем, что использование
памяти при копировании находится под контролем строкового класса.
Со следующего шага мы начнем рассматривать операторы ввода-вывода для пользовательских типов.