C++ *stream | istream / ostream / iostream 详解

注:本文为 “C++ *stream” 相关文章合辑。
英文引文,机翻未校。
中文引文,略作重排,未整理去重。
如有内容异常,请看原文。


Understanding the Utility of Iostreams in C++

理解 C++ 中 iostream 的用途

By Manoj Debnath
February 27, 2020

The iostream classes are the first library classes we encounter when we begin with C++. The primary services that we deal with these classes is solving general I/O problems. After all, this is what the name apparently means. But the beauty is that it represents standard I/O, files, and even blocks of memory and they look the same with one interface. The article explores some of the facets of this incredible class with simple example.
当我们开始学习 C++ 时,iostream 类是我们遇到的第一个库类。我们使用这些类的主要目的是解决一般的输入输出问题。毕竟,这正是它的名字所表达的含义。然而,它的美妙之处在于,它不仅代表标准输入输出和文件,甚至还可以表示内存块,而且所有这些都通过一个统一的接口来实现。本文通过一个简单的例子探讨了这个令人惊叹的类的一些方面。

What is iostream in C++?

C++ 中的 iostream 是什么

The C++ language deals with I/O a bit differently than other languages. It is handled by a family of types prescribed in the standard library. The I/O to and from device files, consoles, and in-memory I/O, such as from string, are all handled by these types. The primary functions defined are read/write operations of the built-in types. Therefore, the I/O stream library provides formatted as well as unformatted buffered I/O of text as well as numeric values.
C++ 语言在处理输入输出时与其他语言有所不同。它是通过标准库中定义的一系列类型来处理的。这些类型负责处理来自设备文件、控制台以及内存中的输入输出(例如来自 string 的数据)。其主要功能是执行内置类型的读写操作。因此,输入输出流库提供了对文本和数值的格式化以及非格式化的缓冲输入输出。

What is a Stream in C++?

C++ 中的流是什么

When a sequence of bytes flows from one interface to another, it can be called a stream. This sequence of bytes represents information that may be further processed or used as is, depending upon the context. When the flow of bytes sequence is main memory inbound, such as from devices like a keyboard, disk, or network connection, it is called an input operation. And, when the flow of bytes is main memory outbound to a device such as printer, disk, network connection, or display screen, it is called an output operation. The sequence of bytes can have different meanings depending upon the context. It can represent characters, graphical images, raw data, audio/video information, or any other type depending upon the context of use.
当一系列字节从一个接口流向另一个接口时,可以将其称为一个 stream(流)。这一系列字节表示的信息可以根据上下文进一步处理或直接使用。当字节序列流入主内存时,例如来自键盘、磁盘或网络连接等设备,这被称为 input operation(输入操作)。而当字节序列从主内流出到打印机、磁盘、网络连接或显示器等设备时,这被称为 output operation(输出操作)。字节序列的含义可以根据上下文有所不同,它可以表示字符、图形图像、原始数据、音频/视频信息,或者根据使用上下文的任何其他类型。

The I/O mechanism must be able to transfer the sequence of bytes in a consistent and efficient manner. But, the problem is that the devices associated with the I/O are often slow in operation. For example, the mechanical rotation of a disk, input coming from keyboard strokes, or sequence of bytes from remote interface require considerably more time than the time required by the processor to process the data. Therefore, to mitigate the time gap so that it does not lag the overall performance of the I/O operation, the C++ stream library classes are fine tuned for optimal performance.
输入输出机制必须能够以一致且高效的方式传输字节序列。然而,问题在于与输入输出相关的设备通常操作速度较慢。例如,磁盘的机械旋转、来自键盘按键的输入,或者来自远程接口的字节序列,都需要比处理器处理数据所需的时间多得多。因此,为了减少这种时间差,以免拖慢整个输入输出操作的性能,C++ 的流库类经过了优化,以实现最佳性能。

Another problem is that data usually comes in different formats. Some come as an unformatted I/O format, which require low-level I/O processing, and some comes in formatted I/O format, which require high-level I/O capabilities. The unformatted I/O specified by sequence of bytes requires processing of individual bytes during its transfer from device to memory or memory to device. Such data often comes in high volume and require fast processing capabilities. In formatted I/O, the sequence of bytes forms a unit of meaningful combination of bytes such as characters, floating-point numbers, integers, strings, or any other user-defined types. Handling I/O with such data units is convenient but inefficient, especially when they come in high volume. The standard C++ stream library supports both the format and does both high- and low-level I/O processing, respectively.
另一个问题是,数据通常以不同的格式出现。有些是以非格式化的输入输出格式出现,需要进行低级输入输出处理;而有些是以格式化的输入输出格式出现,需要高级输入输出功能。以字节序列指定的非格式化输入输出在从设备传输到内存或从内存传输到设备时,需要逐字节处理。这种数据通常数据量很大,需要快速处理能力。在格式化输入输出中,字节序列形成了一个有意义的字节组合单元,例如字符、浮点数、整数、字符串或任何其他用户定义的类型。使用这种数据单元进行输入输出操作虽然方便,但效率较低,尤其是在数据量很大时。标准 C++ 流库同时支持这两种格式,并分别进行高级和低级输入输出处理。

Character Sets

字符集

Previously, the C++ library used to support only input and output of chars which carried only one byte of information, such as those we find with the ASCII (American Standard Code for information Interchange) (8 - bit/1 - byte) character set. But later, it introduced Unicode characters, which represent extensive international character sets, the I/O capabilities of C++ encompassed them within its framework. For example, the type wchar_t (4 - byte/32 - bit) is a later inclusion and can store Unicode characters. Also, C++11 introduced other types, such as char16_t and char32_t, to represent character types that require explicit sizes. The stream classes were redesigned as specialized templates for dealing with char and wchar_t, respectively.
以前,C++ 库仅支持单字节信息的字符输入输出,例如我们在 ASCII(美国信息交换标准代码)(8 位/1 字节)字符集中所见到的字符。但后来,它引入了 Unicode 字符,这些字符代表广泛的国际字符集,C++ 的输入输出功能将其纳入了其框架中。例如,类型 wchar_t(4 字节/32 位)是后来加入的,可以存储 Unicode 字符。此外,C++11 引入了其他类型,如 char16_tchar32_t,以表示需要明确大小的字符类型。流类被重新设计为专门的模板,分别用于处理 charwchar_t

Stream Classes and Objects

流类和对象

A C++ program includes a header to avail the basic service required for all stream I/O operations. There are four objects defined by iostream: cin, cout, cerr, and clog that correspond to the standard input stream, standard output stream, standard error stream (unbuffered), and standard error stream (buffered), respectively. Each of them provides both formatted and unformatted I/O services.
C++ 程序通过包含 头文件来获取所有流输入输出操作所需的基本服务。iostream 定义了四个对象:cincoutcerrclog,分别对应标准输入流、标准输出流、标准错误流(未缓冲)和标准错误流(缓冲)。它们每一个都提供格式化和非格式化的输入输出服务。

There is another header, called , which provides parameterized stream manipulators for performing formatted I/O such as setw, setbase, setprecision, setfill, setiosflags, and more.
另外还有一个名为 的头文件,它提供了用于执行格式化输入输出的参数化流操纵器,例如 setwsetbasesetprecisionsetfillsetiosflags 等。

The header is particularly used in association with file processing and provides services for file I/O.
头文件特别用于文件处理,提供文件输入输出服务。

The library stream classes form hierarchy as follows. Figure 1 gives you a quick idea on some of their traits.
库流类形成了如下层次结构。图 1 为您快速展示了它们的一些特征。

ios_base
basic_streambuf
basic_ios
basic_istream
basic_ostream
basic_iostream

Figure 1: The library stream classes’ hierarchy
图 1:库流类的层次结构

  • The ios_base class is the base class for all stream classes of the standard I/O library. It defines common properties of all stream objects, independent of their character type, such as functions for state and format flags.
    ios_base 类是标准输入输出库中所有流类的基类。它定义了所有流对象的通用属性,这些属性与其字符类型无关,例如用于状态和格式标志的函数。

  • The basic_ios is a template class derived from ios_base. It defines a stream component independent of input or output, but depends on character types and their traits. They provide the buffer definition, such as the object from the template class basic_streambuf. They are used for the read/write scheme.
    basic_ios 是从 ios_base 派生的模板类。它定义了一个与输入或输出无关的流组件,但依赖于字符类型及其特征。它们提供了缓冲区定义,例如来自模板类 basic_streambuf 的对象。它们用于读写方案。

  • The basic_istream and basic_ostream are template classes derived from basic_ios. They define the objects used for read and write operations, respectively. These classes too are parameterized with character types and its traits.
    basic_istreambasic_ostream 是从 basic_ios 派生的模板类。它们分别定义了用于读取和写入操作的对象。这些类也以字符类型及其特征为参数。

  • The basic_streambuf is the core of the I/O stream library. They provide the interface for read and write operations of the stream.
    basic_streambuf 是输入输出流库的核心。它们提供了流的读取和写入操作的接口。

We accept input via descendants of the istream classes and output via descendants of the ostream classes. The iostream classes combine both and provide an object to do input as well as output. Apart from these, the iostream library provides classes for ifstream file input, ofstream for file output, and fstream for both input as well as output operation on files. Similarly, there are classes, such as istringstream, ostringstream, and stringstream, for I/O operation with string classes. They almost have a same interface whether we work with a file, standard I/O, memory, or string object. The stream classes discussed above are the templatized versions to leverage generic type I/O operation.
我们通过 istream 类的派生类接收输入,通过 ostream 类的派生类进行输出。iostream 类结合了两者,提供了一个既可以输入也可以输出的对象。除了这些,iostream 库还提供了用于文件输入的 ifstream 类、文件输出的 ofstream 类,以及用于文件输入输出操作的 fstream 类。同样,还有用于与字符串类进行输入输出操作的类,例如 istringstreamostringstreamstringstream。无论我们是处理文件、标准输入输出、内存还是字符串对象,它们几乎都具有相同的接口。上述讨论的流类是模板化版本,用于实现通用类型的输入输出操作。

A Quick Example

一个快速示例

Here is a quick demonstration of object input/output by overloading ostream and istream operators. The properties of the rudimentary Date class object get inserted and extracted conveniently using stream operators.
下面是一个通过重载 ostreamistream 运算符来实现对象输入输出的快速示例。通过使用流运算符,可以方便地插入和提取基础的 Date 类对象的属性。

#ifndef DATE_H
#define DATE_H

#include <string>

class Date
{
   int day , month , year; // 私有成员变量,用于存储日期的天、月、年
public:
   Date(); // 构造函数声明,用于初始化日期对象
   friend std::ostream& operator<<(std::ostream&, const Date&); // 重载 << 运算符,用于输出 Date 对象
   friend std::istream& operator>>(std::istream&, Date&); // 重载 >> 运算符,用于输入 Date 对象
};

#endif   // DATE_H

#include "date.h"
#include <ctime>
#include <sstream>
#include <iomanip>

using namespace std;

Date::Date()
{
   time_t today = time(0); // 获取当前时间
   tm *lt = localtime(&today); // 将时间转换为本地时间结构
   year = lt->tm_year + 1900; // 设置年份(tm_year 是从 1900 年开始的年数)
   month = lt->tm_mon + 1; // 设置月份(tm_mon 是从 0 开始的月份,需要加 1)
   day = lt->tm_mday; // 设置日期
}

ostream& operator<<(ostream& os, const Date& d) {
   os << d.day << '/'<<d.month<<'/'<<d.year; // 以 dd/mm/yyyy 的格式输出日期
   return os; // 返回输出流对象
}

istream& operator>>(istream& is, Date& d) {
   is >> d.day >> d.month >> d.year; // 从输入流中读取日期的天、月、年
   return is; // 返回输入流对象
}

#include <iostream>
#include "date.h"
using namespace std;

int main()
{
   Date d; // 创建一个 Date 对象,默认为当前日期
   Date d2; // 创建另一个 Date 对象

   cout << d << endl; // 输出当前日期
   cout << "Enter Date (in dd/mm/yyyy):";
   cin >> d2; // 输入一个日期
   cout << d2 << endl; // 输出输入的日期

   return 0;
}

Conclusion

结论

The istream object provides formatted and unformatted input capabilities. There are member functions, like get and getline, that overcome the limitation of extraction operators (>>), which skip white - space characters such as blanks, tabs, and newline. Similarly, ostream provides formatted and unformatted output capabilities. There are member functions, such as put, along with the stream insertion operator (<<) to output standard data types. We, however, can overload these operators to give a new meaning as we have done in the example above. Note that here we have merely scratched the surface of iostream library. There are many other aspects. Stay tuned for more.
istream 对象提供了格式化和非格式化的输入功能。它有一些成员函数,如 getgetline,这些函数克服了提取运算符(>>)的限制,因为提取运算符会跳过空白字符,如空格、制表符和换行符。同样,ostream 提供了格式化和非格式化的输出功能。它有一些成员函数,如 put,以及流插入运算符(<<),用于输出标准数据类型。然而,正如我们在上面的示例中所做的那样,我们可以重载这些运算符以赋予它们新的含义。请注意,这里我们只是对 iostream 库进行了初步的探索。


The C++ I/O System

C++ 输入输出系统

I/O Class Hierarchy (simplified)

I/O 类层次结构(简化版)

           ios_base
              |
             ios
           /     \
    istream      ostream
     |    \      /   |
     |    iostream   |
     |       |       |
ifstream    fstream    ofstream

The C++ I/O system defined in the IOStreams library is quite complex, extraordinarily useful and flexible, and quite beautiful in design. The current system resides in the namespace std, but is the result of at least a decade of evolution fuelled by theory, experiment, and the ISO standardization process.

C++ I/O 系统在 IOStreams 库中定义,它非常复杂,极其有用且灵活,设计上也非常优美。当前的系统位于 std 命名空间中,但它是经过至少十年的理论研究、实验和 ISO 标准化过程演变而来的。

A comprehensive treatment of C++ IOStreams is well beyond the scope of these notes. In fact there is a very good 640 page treatise on the subject that is recommended for further study (see [Standard C++ IOStreams and Locales Advanced Programmers Guide and Reference, by Angelika Langer and Klaus Kreft, Addison Wesley, 2000]). What is intended here is a detailed introduction to the most useful instantiations in this library, sufficient for most programming course work. For professional levels of expertise, you are strongly encouraged to obtain the Langer & Kreft reference and keep it alongside your Stroustrup.

对 C++ IOStreams 的全面介绍超出了这些笔记的范围。实际上,有一本非常好的关于该主题的 640 页专著,推荐用于进一步学习(参见 [《Standard C++ IOStreams and Locales Advanced Programmers Guide and Reference》,作者:Angelika Langer 和 Klaus Kreft,Addison Wesley,2000])。这里的目标是详细介绍该库中最常用的实例化,足以满足大多数编程课程的需求。对于专业级别的知识,强烈建议您获取 Langer 和 Kreft 的参考书籍,并将其放在您的 Stroustrup 书籍旁边。

A portion of the iostreams class hierarchy is shown in the slide. This is the portion that we will discuss in some detail. A more complete hierarchy is as follows:

slide 中展示了 iostreams 类层次结构的一部分。我们将详细讨论这一部分。更完整的层次结构如下:

                             ios_base
                                |
                       basic_ios<charT, traits>
                       /                      \
basic_istream<charT, traits>                   basic_ostream<charT, traits>
   |                   \                      /            |
   |                 basic_iostream<charT, traits>         |
   |                              |                        |
basic_ifstream<charT, traits>     |           basic_ofstream<charT, traits>
                                  |
                      basic_fstream<charT, traits>

在这里插入图片描述

Notice that all of the classes except the base class ios_base are templates. The two template parameters represent the character type being used along with properties of that type. There are two predefined character types, the familiar 1 - byte ascii characters char and the 4 - byte wide characters wchar_t. The type char supports ASCII, EBCDIC, and ISO 8859 - 2 character sets. The type wchar_t supports Unicode and ISO 10646. The user can invent other character classes and associated traits and instantiate streams using that type.

请注意,除了基类 ios_base 外,所有类都是模板。两个模板参数表示正在使用的字符类型及其属性。有两种预定义的字符类型:熟悉的 1 字节 ASCII 字符 char 和 4 字节宽字符 wchar_tchar 类型支持 ASCII、EBCDIC 和 ISO 8859 - 2 字符集。wchar_t 类型支持 Unicode 和 ISO 10646。用户可以创建其他字符类及其相关特性,并使用该类型实例化流。

Even the diagram above does not illustrate the full generality of the IOStream library. There is support for internationalization in the form of Locales, and there is a hierarchy of buffer types that are used by iostreams. These are interesting and important features that are beyond the scope of these notes.

即使上面的图表也没有展示 IOStream 库的全部通用性。它支持以 Locales 形式的国际化,并且有一个被 iostreams 使用的缓冲区类型的层次结构。这些是超出这些笔记范围的有趣且重要的特性。

There is also support for exception handling built into the class hierarchy shown above, but we will not say much about that aspect either. Because we will assume the standard ASCII character set, there is no need to go into detail on the traits classes in these notes. Finally, there is a parallel set of classes for I/O into strings instead of streams.

在上述类层次结构中还内置了异常处理支持,但我们也几乎不会讨论这一方面。由于我们将假设使用标准 ASCII 字符集,因此这些笔记中无需详细讨论特性类。最后,还有一组平行的类用于对字符串而不是流进行输入输出。

For most purposes, it suffices to use the ASCII character set and stream I/O, and there are type definitions in the standard library associated with those assumptions:

对于大多数用途,使用 ASCII 字符集和流式 I/O 就足够了,标准库中有一些与这些假设相关的类型定义:

typedef basic_ios<char, char_traits<char>> ios;
typedef basic_istream<char, char_traits<char>> istream;
typedef basic_ostream<char, char_traits<char>> ostream;
typedef basic_iostream<char, char_traits<char>> iostream;
typedef basic_ifstream<char, char_traits<char>> ifstream;
typedef basic_ofstream<char, char_traits<char>> ofstream;
typedef basic_fstream<char, char_traits<char>> fstream;

In the remainder of this chapter we will discuss details of the classes ios_base (equivalently class ios), the derived classes istream, ostream, iostream, ifstream, ofstream, and the collection of IO manipulators defined with these classes.

在本章的其余部分,我们将讨论 ios_base 类(等同于 ios 类)、派生类 istreamostreamiostreamifstreamofstream 以及与这些类一起定义的 I/O 操纵符的细节。

Class ios_base

ios_base

namespace std {
    class ios_base {
        public:
            // ios_base status methods
            bool good() const; // true iff no error flag is set
            bool eof() const; // true iff stream is at end of file
            bool fail() const; // true iff badbit or failbit are set
            bool bad() const; // true if badbit is set
            operator void*() const; // null pointer if fail(), non-null otherwise
            void clear(iostate newstate = goodbit); // sets state to newstate
            void setstate(iostate addstate); // adds addstate to existing state
            enum iostate {
                goodbit = 0x0000, // everything's ok
                eofbit = 0x0001, // stream is at end of file
                failbit = 0x0002, // last I/O operation failed
                badbit = 0x0004 // serious error, stream unusable
            };
        protected:
            unsigned long state; // stores status bits
    };
}

I/O streams can be understood by exploring in detail the class ios_base, which contains all of the public member variables and most public methods in the hierarchy. This slide shows the basic organization of the class. We explore the specifics in more detail in the following slides.

通过详细研究 ios_base 类,可以理解 I/O 流。该类包含层次结构中的所有公共成员变量和大多数公共方法。此文展示了该类的基本组织结构。我们将在接下来的文中更详细地探讨具体细节。

Items to take note of at this point are:

需要注意的几点是:

  • All of the standard library classes are in the std namespace.
  • 标准库中的所有类都在 std 命名空间中。
  • The implementation details hinted at in this slide and elaborated later are not specified by the standard; we are illustrating one possible instantiation.
  • 本文中暗示的实现细节以及后续详细说明的内容并未由标准指定;我们展示的是一种可能的实例化方式。
  • Variables and methods related to exception handling are omitted.
  • 省略了与异常处理相关的变量和方法。

ios_base Status Methods

ios_base 状态方法

class ios_base {
    public:
        bool good() const; // true if no error flag is set
        bool eof() const; // true if eofbit is set
        bool fail() const; // true if badbit or failbit are set
        bool bad() const; // true if badbit is set
        operator void*() const; // null pointer if fail(), non-null otherwise
        void clear(iostate newstate = goodbit); // sets state to newstate
        void setstate(iostate addstate); // adds addstate to existing state
        enum iostate {
            goodbit = 0x0000, // everything's ok
            eofbit = 0x0001, // stream is at end of file
            failbit = 0x0002, // last I/O operation failed
            badbit = 0x0004 // serious error, stream unusable
        };
    protected:
        unsigned long state; // stores status bits
};

Some useful code techniques can be illuminated with this information. For example:

利用这些信息可以展示一些有用的编码技巧。例如:

std::ifstream in;
in.open(filename);
while (!in) {
    std::cout << "Cannot open file " << filename << "-try again:";
    std::cin >> filename;
    in.clear();
    in.open(filename);
}

The implementation of setstate uses bitwise operations. These are native operations of C that allow direct access to integral values at the bit level. Most current CPUs have hardware support for bitwise operations, making them extremely efficient, typically requiring one clock cycle to accomplish the entire operation.

setstate 的实现使用了位运算。这些是 C 的原生操作,允许直接在位级别访问整数值。大多数现代 CPU 都有硬件支持位运算,这使得它们极其高效,通常只需要一个时钟周期即可完成整个操作。

Here is a table showing the bitwise operations and their semantics:

以下表格展示了位运算及其语义:

OperationSymbolTypeInfix VersionAccumulator Version
and&binaryz = x & yz &= y
or|binaryz = x | yz |= y
xor^binaryz = x ^ yz ^= y
not~unaryz = ~y(na)
left shift<<binaryz = x << n(na)
right shift>>binaryz = x >> n(na)

ios_base Formatting Methods

ios_base 格式化方法

class ios_base {
    public:
        fmtflags flags() const; // returns current flags
        fmtflags flags(fmtflags newflags); // sets flags to newflags
        fmtflags setf(fmtflags setbits); // sets specified flags
        fmtflags setf(fmtflags setbits, fmtflags mask); // sets flags in mask
        fmtflags unsetf(fmtflags unsetbits); // clears specified flags
        enum fmtflags {
            boolalpha = 0x0001, // read/write bool values in alphabetic
            left = 0x0002, // left-justify output
            right = 0x0004, // right-justify output
            internal = 0x0008, // prefix left..fill..number right
            skipws = 0x0010, // skip white space before extraction
            dec = 0x0020, // decimal
            hex = 0x0040, // hexadecimal
            oct = 0x0080, // octal
            showbase = 0x0100, // show base indicator on output
            showpoint = 0x0200, // show decimal point for fixed point output
            showpos = 0x0400, // force show of sign for positive numbers
            fixed = 0x0800, // force decimal notation for float
            scientific = 0x1000, // force scientific notation for float
            unitbuf = 0x2000, // flush buffer after each insertion
            uppercase = 0x4000 // use upper case indicators for hex and e
        };
    protected:
        unsigned long flags; // stores flag bits
};

Meaning of Format Flags

格式标志的含义

Format FlagMeaning
boolalphaReads and writes bool values in alphabetic
leftLeft-justify output
rightRight-justify output
internalPrefix left…fill…number right
skipwsSkip white space before extraction
decDecimal
hexHexadecimal
octOctal
showbaseShow base indicator on output
showpointShow decimal point for fixed point output
showposForce show of sign for positive numbers
fixedForce decimal notation for float
scientificForce scientific notation for float
unitbufFlush buffer after each insertion
uppercaseUse upper case indicators for hex and e

ios_base Data Methods

ios_base 数据方法

class ios_base {
    public:
        char fill(char fillch); // sets fill character
        int precision() const; // returns precision value
        int precision(int val); // sets precision value
        int width() const; // returns width value
        int width(int val); // sets width value
    protected:
        int width_value; // initialized to 0
        int precision_value; // initialized to 0
        char fill_character; // initialized to ' '
};

Meaning of Width and Precision

宽度和精度的含义

  • Width:
    • n <= 0: No effect
    • n > 0: On output, sets the minimum number of characters output (filled with fill char). On input, sets the length of buffer for string extractions. Note: Reset to 0 after each insertion and extraction.
  • Precision:
    • n < 0: Default
    • n >= 0: If ios::fixed is set, determines the number of places displayed after the decimal point. If ios::fixed is not set, determines the number of significant digits displayed.

ios_base Binding Methods

ios_base 绑定方法

class ios_base {
    public:
        streambuf* rdbuf(); // returns ptr to stream's streambuf object
        ostream* tie(); // returns ptr to the tied ostream
        ostream* tie(ostream*); // ties current stream to specified ostream
        static bool sync_with_stdio(bool sync = true); // sync with C standard I/O
    protected:
        streambuf* streambuffer; // pointer to a streambuf object
        ostream* tied_ostream; // pointer to an ostream object
};

ios_base File Modes

ios_base 文件模式

class ios_base {
    public:
        enum open_mode {
            in = 0x0001, // open file for input
            out = 0x0002, // open file for output
            ate = 0x0004, // seek to end when file is opened
            app = 0x0008, // open file in append mode
            trunc = 0x0010, // truncate the file if it exists
            binary = 0x0020 // open file in binary mode
        };
        enum seek_dir {
            beg = 0x0100, // seek relative to beginning of file
            cur = 0x0200, // seek relative to current position
            end = 0x0400 // seek relative to end of file
        };
    protected:
        unsigned long mode; // stores open and seek mode bits
};

Meaning of File Modes

文件模式的含义

Open ModeEffectEffect with binary
inOpens text files for reading, initial position at beginning of fileInitial position at beginning of file
outTruncates file to empty, or creates file for write onlyNo effect on empty file
`outtrunc`Truncates file to empty, or creates file for write only
appAppends; opens or creates text file for writing at end of fileNo additional effect
`inout`Opens file for update (read or write), position at beginning of file
`inouttrunc`

Possible ios_base Method Implementations

可能的 ios_base 方法实现

ios_base::fmtflags ios_base::flags() const {
    return flags;
}

ios_base::fmtflags ios_base::flags(ios_base::fmtflags newflags) {
    ios_base::fmtflags oldflags = flags;
    flags = newflags;
    return oldflags;
}

ios_base::fmtflags ios_base::setf(ios_base::fmtflags setbits) {
    ios_base::fmtflags oldflags = flags;
    flags |= setbits;
    return oldflags;
}

ios_base::fmtflags ios_base::setf(ios_base::fmtflags setbits, ios_base::fmtflags mask) {
    ios_base::fmtflags oldflags = flags;
    flags = (flags & ~mask) | (setbits & mask);
    return oldflags;
}

ios_base::fmtflags ios_base::unsetf(ios_base::fmtflags unsetbits) {
    ios_base::fmtflags oldflags = flags;
    flags &= ~unsetbits;
    return oldflags;
}

char ios_base::fill(char newfill) {
    char oldfill = fill_character;
    fill_character = newfill;
    return oldfill;
}

int ios_base::precision() const {
    return precision_value;
}

int ios_base::precision(int val) {
    int oldprecision = precision_value;
    precision_value = val;
    return oldprecision;
}

int ios_base::width() const {
    return width_value;
}

int ios_base::width(int val) {
    int oldwidth = width_value;
    width_value = val;
    return oldwidth;
}

Class istream

istream

namespace std {
    class istream : public ios_base {
        public:
            friend istream& operator>>(istream&, char&);
            friend istream& operator>>(istream&, int);
            friend istream& operator>>(istream&, long);
            friend istream& operator>>(istream&, unsigned char);
            friend istream& operator>>(istream&, unsigned int);
            friend istream& operator>>(istream&, unsigned long);
            friend istream& operator>>(istream&, float);
            friend istream& operator>>(istream&, double);
            friend istream& operator>>(istream&, long double);
            friend istream& operator>>(istream&, char*);
            void get(char&);
            char get();
            char peek();
        // predefined object
        cin;
    };
}

Class ostream

ostream

namespace std {
    class ostream : public ios_base {
        public:
            friend ostream& operator<<(ostream&, char);
            friend ostream& operator<<(ostream&, int);
            friend ostream& operator<<(ostream&, long);
            friend ostream& operator<<(ostream&, unsigned char);
            friend ostream& operator<<(ostream&, unsigned int);
            friend ostream& operator<<(ostream&, unsigned long);
            friend ostream& operator<<(ostream&, float);
            friend ostream& operator<<(ostream&, double);
            friend ostream& operator<<(ostream&, long double);
            friend ostream& operator<<(ostream&, const char*);
            void put(char ch);
        // predefined objects
        cout, cerr, clog;
    };
}

Predefined Objects cin, cout, cerr, clog

预定义对象 cin, cout, cerr, clog

  • Predefined istream object: cin
    预定义的 istream 对象:cin
  • Predefined ostream objects: cout, cerr, clog
    预定义的 ostream 对象:cout, cerr, clog
  • Buffered ostreams: cout, clog
    缓冲的 ostreamcout, clog
  • Tied stream pair: cin, cout
    绑定的流对:cin, cout
  • cin.tie(&cout); // in file iostream
    cin.tie(&cout); // 在文件 iostream 中

Class ifstream

ifstream

class ifstream : public istream {
    public:
        ifstream* open(const char* filename, ios_base::open_mode mode = ios_base::in);
        ifstream* close();
};

Class ofstream

ofstream

class ofstream : public ostream {
    public:
        ofstream* open(const char* filename, ios_base::open_mode mode = ios_base::out | ios_base::trunc);
        ofstream* close();
};

I/O Manipulators - 1

I/O 操纵符 - 1

boolalpha // calls s.setf(ios::boolalpha)
noboolalpha // calls s.unsetf(ios::boolalpha)
noshowbase // calls s.unsetf(ios::showbase)
showbase // calls s.setf(ios::showbase)
showpoint // calls s.setf(ios::showpoint)
noshowpoint // calls s.unsetf(ios::showpoint)
showpos // calls s.setf(ios::showpos)
noshowpos // calls s.unsetf(ios::showpos)
uppercase // calls s.setf(ios::uppercase)
nouppercase // calls s.unsetf(ios::uppercase)
skipws // calls s.setf(ios::skipws)
noskipws // calls s.unsetf(ios::skipws)
unitbuf // calls s.setf(ios::unitbuf)
nounitbuf // calls s.unsetf(ios::unitbuf)
left // calls s.setf(ios::left, ios::adjustfield)
right // calls s.setf(ios::right, ios::adjustfield)
internal // calls s.setf(ios::internal, ios::adjustfield)
dec // calls s.setf(ios::dec, ios::basefield)
hex // calls s.setf(ios::hex, ios::basefield)
oct // calls s.setf(ios::oct, ios::basefield)
fixed // calls s.setf(ios::fixed, ios::floatfield)
scientific // calls s.setf(ios::scientific, ios::floatfield)
endl // flushes streambuf and inserts '\n'
ends // flushes streambuf and inserts '\0'
flush // flushes streambuf

I/O Manipulators - 2

I/O 操纵符 - 2

Prototype for 0 - parameter manipulators:
stream_type& manip_name(stream_type& s);
// User can define and use manipulators, e.g.:
ostream& beepbeep(ostream& os) {
    os << "lala";
    return os;
}

I/O Manipulators - 3

I/O 操纵符 - 3

setbase(int b); // sets base (radix) for numericals
setiosflags(ios::format_flags mask); // calls ios::setf(mask)
resetiosflags(ios::format_flags mask); // calls ios::unsetf(mask)
setfill(char ch); // calls ios::fill(ch)
setprecision(int n); // calls ios::precision(n)
setw(int n); // calls ios::width(n)
// User may define a more extensive collection

For complete details on all these features of C++ IOStreams, see the Langer and Kreft reference.

有关 C++ IOStreams 的所有这些特性的详细信息,请参阅 Langer 和 Kreft 的参考书籍。

For most purposes, it suffices to use the ASCII character set and stream I/O, and there are type definitions in the standard library associated with those assumptions:

对于大多数用途,使用 ASCII 字符集和流式 I/O 就足够了,标准库中有一些与这些假设相关的类型定义:

typedef basic_ios<char, char_traits<char>> ios;
typedef basic_istream<char, char_traits<char>> istream;
typedef basic_ostream<char, char_traits<char>> ostream;
typedef basic_iostream<char, char_traits<char>> iostream;
typedef basic_ifstream<char, char_traits<char>> ifstream;
typedef basic_ofstream<char, char_traits<char>> ofstream;
typedef basic_fstream<char, char_traits<char>> fstream;

Note that making these substitutions yields the hierarchy illustrated in the slide.

请注意,进行这些替换后,将得到文中展示的层次结构。

In the remainder of this chapter we will discuss details of the classes ios_base (equivalently class ios), the derived classes istream, ostream, iostream, ifstream, ofstream, and the collection of IO manipulators defined with these classes.

在本章的其余部分,我们将讨论 ios_base 类(等同于 ios 类)、派生类 istreamostreamiostreamifstreamofstream,以及与这些类一起定义的 I/O 操纵符的细节。

Class ios_base

ios_base

namespace std {
    class ios_base {
        public:
            // ios_base status methods
            bool good() const; // true if no error flag is set
            bool eof() const; // true if stream is at end of file
            bool fail() const; // true if badbit or failbit are set
            bool bad() const; // true if badbit is set
            operator void*() const; // null pointer if fail(), non-null otherwise
            void clear(iostate newstate = goodbit); // sets state to newstate
            void setstate(iostate addstate); // adds addstate to existing state
            enum iostate {
                goodbit = 0x0000, // everything's ok
                eofbit = 0x0001, // stream is at end of file
                failbit = 0x0002, // last I/O operation failed
                badbit = 0x0004 // serious error, stream unusable
            };
        protected:
            unsigned long state; // stores status bits
            unsigned long flags; // stores mode bits
            unsigned long mode; // stores flag bits
            int width_value; // initialized to 0
            int precision_value; // initialized to 0
            char fill_character; // initialized to ' '
            streambuf* streambuffer; // pointer to a streambuf object
            // plus other data, such as specifying tied streams
    };
}

I/O streams can be understood by exploring in detail the class ios_base, which contains all of the public member variables and most public methods in the hierarchy. This slide shows the basic organization of the class. We explore the specifics in more detail in the following slides.

通过详细研究 ios_base 类,可以理解 I/O 流。该类包含层次结构中的所有公共成员变量和大多数公共方法。此文展示了该类的基本组织结构。我们将在接下来的文中更详细地探讨具体细节。

Items to take note of at this point are:

需要注意的几点是:

  • All of the standard library classes are in the std namespace.
    • 标准库中的所有类都在 std 命名空间中。
  • The implementation details hinted at in this slide and elaborated later are not specified by the standard; we are illustrating one possible instantiation.
    • 本文中暗示的实现细节以及后续详细说明的内容并未由标准指定;我们展示的是一种可能的实例化方式。
  • Variables and methods related to exception handling are omitted.
    • 省略了与异常处理相关的变量和方法。

ios_base Status Methods

ios_base 状态方法

class ios_base {
    public:
        bool good() const; // true if no error flag is set
        bool eof() const; // true if eofbit is set
        bool fail() const; // true if badbit or failbit are set
        bool bad() const; // true if badbit is set
        operator void*() const; // null pointer if fail(), non-null otherwise
        void clear(iostate newstate = goodbit); // sets state to newstate
        void setstate(iostate addstate); // adds addstate to existing state
        enum iostate {
            goodbit = 0x0000, // everything's ok
            eofbit = 0x0001, // stream is at end of file
            failbit = 0x0002, // last I/O operation failed
            badbit = 0x0004 // serious error, stream unusable
        };
    protected:
        unsigned long state; // stores status bits
};

Some useful code techniques can be illuminated with this information. For example:
利用这些信息可以展示一些有用的编码技巧。例如:

std::ifstream in;
in.open(filename);
while (!in) {
    std::cout << "Cannot open file " << filename << "-try again:";
    std::cin >> filename;
    in.clear();
    in.open(filename);
}

The implementation of setstate uses bitwise operations. These are native operations of C that allow direct access to integral values at the bit level. Most current CPUs have hardware support for bitwise operations, making them extremely efficient, typically requiring one clock cycle to accomplish the entire operation.

setstate 的实现使用了位运算。这些是 C 的原生操作,允许直接在位级别访问整数值。大多数现代 CPU 都有硬件支持位运算,这使得它们极其高效,通常只需要一个时钟周期即可完成整个操作。

Here is a table showing the bitwise operations and their semantics:

以下表格展示了位运算及其语义:

OperationSymbolTypeInfix VersionAccumulator Version
and&binaryz = x & yz &= y
or|binaryz = x | yz |= y
xor^binaryz = x ^ yz ^= y
not~unaryz = ~y(na)
left shift<<binaryz = x << n(na)
right shift>>binaryz = x >> n(na)

ios_base Formatting Methods

ios_base 格式化方法

class ios_base {
    public:
        fmtflags flags() const; // returns current flags
        fmtflags flags(fmtflags newflags); // sets flags to newflags
        fmtflags setf(fmtflags setbits); // sets specified flags
        fmtflags setf(fmtflags setbits, fmtflags mask); // sets flags in mask
        fmtflags unsetf(fmtflags unsetbits); // clears specified flags
        enum fmtflags {
            boolalpha = 0x0001, // read/write bool values in alphabetic
            left = 0x0002, // left-justify output
            right = 0x0004, // right-justify output
            internal = 0x0008, // prefix left..fill..number right
            skipws = 0x0010, // skip white space before extraction
            dec = 0x0020, // decimal
            hex = 0x0040, // hexadecimal
            oct = 0x0080, // octal
            showbase = 0x0100, // show base indicator on output
            showpoint = 0x0200, // show decimal point for fixed point output
            showpos = 0x0400, // force show of sign for positive numbers
            fixed = 0x0800, // force decimal notation for float
            scientific = 0x1000, // force scientific notation for float
            unitbuf = 0x2000, // flush buffer after each insertion
            uppercase = 0x4000 // use upper case indicators for hex and e
        };
    protected:
        unsigned long flags; // stores flag bits
};

Meaning of Format Flags

格式标志的含义

Format FlagMeaning
boolalpha以字母形式读写布尔值
left左对齐输出
right右对齐输出
internal数字输出时,前缀、填充字符和数字分别对齐
skipws提取前跳过空白字符
dec十进制
hex十六进制
oct八进制
showbase在输出中显示基数指示符
showpoint在定点输出中显示小数点
showpos强制显示正数的符号
fixed强制浮点数以十进制形式输出
scientific强制浮点数以科学计数法输出
unitbuf每次插入后刷新缓冲区
uppercase在十六进制和科学计数法中使用大写字母

ios_base Data Methods

ios_base 数据方法

class ios_base {
    public:
        char fill(char fillch); // 设置填充字符
        int precision() const; // 返回精度值
        int precision(int val); // 设置精度值
        int width() const; // 返回宽度值
        int width(int val); // 设置宽度值
    protected:
        int width_value; // 初始化为 0
        int precision_value; // 初始化为 0
        char fill_character; // 初始化为空格
};

Meaning of Width and Precision

宽度和精度的含义

  • Width:
    • n <= 0: 无影响
    • n > 0: 在输出时,设置最小输出字符数(不足部分用填充字符填充)。在输入时,设置字符串提取的缓冲区长度。注意:每次插入和提取后宽度值会重置为 0。
  • Precision:
    • n < 0: 默认值
    • n >= 0: 如果设置了 ios::fixed,则确定小数点后显示的位数。如果没有设置 ios::fixed,则确定显示的有效数字位数。

ios_base Binding Methods

ios_base 绑定方法

class ios_base {
    public:
        streambuf* rdbuf(); // 返回指向流的 streambuf 对象的指针
        ostream* tie(); // 返回指向绑定的 ostream 的指针
        ostream* tie(ostream*); // 将当前流绑定到指定的 ostream
        static bool sync_with_stdio(bool sync = true); // 与 C 标准 I/O 同步
    protected:
        streambuf* streambuffer; // 指向 streambuf 对象的指针
        ostream* tied_ostream; // 指向 ostream 对象的指针
};

ios_base File Modes

ios_base 文件模式

class ios_base {
    public:
        enum open_mode {
            in = 0x0001, // 以输入模式打开文件
            out = 0x0002, // 以输出模式打开文件
            ate = 0x0004, // 打开文件时定位到文件末尾
            app = 0x0008, // 以追加模式打开文件
            trunc = 0x0010, // 如果文件存在则截断文件
            binary = 0x0020 // 以二进制模式打开文件
        };
        enum seek_dir {
            beg = 0x0100, // 从文件开头开始定位
            cur = 0x0200, // 从当前位置开始定位
            end = 0x0400 // 从文件末尾开始定位
        };
    protected:
        unsigned long mode; // 存储打开和定位模式的位
};

Meaning of File Modes

文件模式的含义

Open ModeEffectEffect with binary
in打开文本文件以供读取,初始位置在文件开头初始位置在文件开头
out如果文件存在则将其截断为空,或创建文件以供写入对空文件无影响
`outtrunc`截断文件为空,或创建文件以供写入
app以追加模式打开或创建文本文件,写入操作在文件末尾进行无额外影响
`inout`打开文件以供更新(读或写),位置在文件开头
`inouttrunc`

Possible ios_base Method Implementations

可能的 ios_base 方法实现

ios_base::fmtflags ios_base::flags() const {
    return flags;
}

ios_base::fmtflags ios_base::flags(ios_base::fmtflags newflags) {
    ios_base::fmtflags oldflags = flags;
    flags = newflags;
    return oldflags;
}

ios_base::fmtflags ios_base::setf(ios_base::fmtflags setbits) {
    ios_base::fmtflags oldflags = flags;
    flags |= setbits;
    return oldflags;
}

ios_base::fmtflags ios_base::setf(ios_base::fmtflags setbits, ios_base::fmtflags mask) {
    ios_base::fmtflags oldflags = flags;
    flags = (flags & ~mask) | (setbits & mask);
    return oldflags;
}

ios_base::fmtflags ios_base::unsetf(ios_base::fmtflags unsetbits) {
    ios_base::fmtflags oldflags = flags;
    flags &= ~unsetbits;
    return oldflags;
}

char ios_base::fill(char newfill) {
    char oldfill = fill_character;
    fill_character = newfill;
    return oldfill;
}

int ios_base::precision() const {
    return precision_value;
}

int ios_base::precision(int val) {
    int oldprecision = precision_value;
    precision_value = val;
    return oldprecision;
}

int ios_base::width() const {
    return width_value;
}

int ios_base::width(int val) {
    int oldwidth = width_value;
    width_value = val;
    return oldwidth;
}

Class istream

istream

namespace std {
    class istream : public ios_base {
        public:
            friend istream& operator>>(istream&, char&);
            friend istream& operator>>(istream&, int);
            friend istream& operator>>(istream&, long);
            friend istream& operator>>(istream&, unsigned char);
            friend istream& operator>>(istream&, unsigned int);
            friend istream& operator>>(istream&, unsigned long);
            friend istream& operator>>(istream&, float);
            friend istream& operator>>(istream&, double);
            friend istream& operator>>(istream&, long double);
            friend istream& operator>>(istream&, char*);
            void get(char&);
            char get();
            char peek();
        // 预定义对象
        cin;
    };
}

Class ostream

ostream

namespace std {
    class ostream : public ios_base {
        public:
            friend ostream& operator<<(ostream&, char);
            friend ostream& operator<<(ostream&, int);
            friend ostream& operator<<(ostream&, long);
            friend ostream& operator<<(ostream&, unsigned char);
            friend ostream& operator<<(ostream&, unsigned int);
            friend ostream& operator<<(ostream&, unsigned long);
            friend ostream& operator<<(ostream&, float);
            friend ostream& operator<<(ostream&, double);
            friend ostream& operator<<(ostream&, long double);
            friend ostream& operator<<(ostream&, const char*);
            void put(char ch);
        // 预定义对象
        cout, cerr, clog;
    };
}

Predefined Objects cin, cout, cerr, clog

预定义对象 cin, cout, cerr, clog

  • Predefined istream object: cin
    • 预定义的 istream 对象:cin
  • Predefined ostream objects: cout, cerr, clog
    • 预定义的 ostream 对象:cout, cerr, clog
  • Buffered ostreams: cout, clog
    • 缓冲的 ostreamcout, clog
  • Tied stream pair: cin, cout
    • 绑定的流对:cin, cout
  • cin.tie(&cout); // in file iostream
    • cin.tie(&cout); // 在文件 iostream 中

Class ifstream

ifstream

class ifstream : public istream {
    public:
        ifstream* open(const char* filename, ios_base::open_mode mode = ios_base::in);
        ifstream* close();
};

Class ofstream

ofstream

class ofstream : public ostream {
    public:
        ofstream* open(const char* filename, ios_base::open_mode mode = ios_base::out | ios_base::trunc);
        ofstream* close();
};

I/O Manipulators - 1

I/O 操纵符 - 1

boolalpha // calls s.setf(ios::boolalpha)
noboolalpha // calls s.unsetf(ios::boolalpha)
noshowbase // calls s.unsetf(ios::showbase)
showbase // calls s.setf(ios::showbase)
showpoint // calls s.setf(ios::showpoint)
noshowpoint // calls s.unsetf(ios::showpoint)
showpos // calls s.setf(ios::showpos)
noshowpos // calls s.unsetf(ios::showpos)
uppercase // calls s.setf(ios::uppercase)
nouppercase // calls s.unsetf(ios::uppercase)
skipws // calls s.setf(ios::skipws)
noskipws // calls s.unsetf(ios::skipws)
unitbuf // calls s.setf(ios::unitbuf)
nounitbuf // calls s.unsetf(ios::unitbuf)
left // calls s.setf(ios::left, ios::adjustfield)
right // calls s.setf(ios::right, ios::adjustfield)
internal // calls s.setf(ios::internal, ios::adjustfield)
dec // calls s.setf(ios::dec, ios::basefield)
hex // calls s.setf(ios::hex, ios::basefield)
oct // calls s.setf(ios::oct, ios::basefield)
fixed // calls s.setf(ios::fixed, ios::floatfield)
scientific // calls s.setf(ios::scientific, ios::floatfield)
endl // flushes streambuf and inserts '\n'
ends // flushes streambuf and inserts '\0'
flush // flushes streambuf

I/O Manipulators - 2

I/O 操纵符 - 2

Prototype for 0 - parameter manipulators:
stream_type& manip_name(stream_type& s);
// User can define and use manipulators, e.g.:
ostream& beepbeep(ostream& os) {
    os << "lala";
    return os;
}

I/O Manipulators - 3

I/O 操纵符 - 3

setbase(int b); // 设置数字的基数(进制)
setiosflags(ios::format_flags mask); // 调用 ios::setf(mask)
resetiosflags(ios::format_flags mask); // 调用 ios::unsetf(mask)
setfill(char ch); // 调用 ios::fill(ch)
setprecision(int n); // 调用 ios::precision(n)
setw(int n); // 调用 ios::width(n)
// 用户可以定义更广泛的操纵符集合

C++ 的 iostream 标准库介绍与使用详解

posted @ 2016-07-07 21:36 极客先锋

0 为何需要 iostream

在 C++ 编程中,输入输出操作是基础功能之一,而这些功能是由 iostream 库提供的。因此,深入理解 iostream 库的实现与使用是十分必要的。与 C 语言的 stdio 库不同,iostream 库是基于面向对象的设计理念,通过多重继承与虚拟继承构建的层次结构,并作为 C++ 标准库的一部分提供给程序员使用。

iostream 库为内置类型对象提供了输入输出支持,同时也支持文件的输入输出操作。此外,通过扩展 iostream 库,类的设计者可以为自定义类型添加输入输出支持。

以下通过一个示例说明为何需要扩展库来支持自定义类型的输入输出:

#include <stdio.h> 
#include <iostream> 
using namespace std;     

class Test 
{ 
public: 
    Test ( int a = 0 , int b = 0 ) 
    { 
        this -> a = a; 
        this -> b = b; 
    } 
    int a; 
    int b; 
}; 

int main() 
{ 
    Test t ( 100 , 50 ); 
    printf ( "%???" , t ); // 输出格式不明确 
    scanf ( "%???" , t );  // 输入格式不明确 
    cout << t << endl; // 输出格式不明确 
    cin >> t;          // 输入格式不明确 
    system ( "pause" ); 
}

由于自定义类的特殊性,上述代码中无论是使用 C 风格的输入输出,还是 C++ 的输入输出,都无法明确表示其格式。C 语言没有运算符重载机制,导致 stdio 库无法扩充以支持对自定义类对象的识别。而 C++ 通过运算符重载机制扩充 iostream 库,使系统能够识别自定义类型,从而明确输入输出的格式。

在上述示例中,printfcout 的对比展示了 C 与 C++ 处理输入输出的根本区别:C 语言的输入输出是基于函数调用的方式,而 C++ 则是基于对象模式,coutcin 分别是 ostream 类和 istream 类的对象。

1 iostream:istream 与 ostream

C++ 中的 iostream 库主要包含以下头文件:

IOSstream 库
fstreamiomanip
iosiosfwd
iostreamistream
ostreamsstream
streambufstrstream

我们熟悉的输入输出操作分别由 istream(输入流)和 ostream(输出流)这两个类提供。为了支持双向输入输出操作,由 istreamostream 派生出了 iostream 类。

类的继承关系如下图所示:
点击图片可在新窗口打开

iostream 库定义了以下三个标准流对象:

  1. cin:表示标准输入(standard input)的 istream 类对象,其默认输入设备为键盘。
  2. cout:表示标准输出(standard output)的 ostream 类对象,其默认输出设备为显示器屏幕。
  3. cerr:表示标准错误(standard error)的 ostream 类对象,其默认输出设备为显示器屏幕,仅用于输出错误信息。

输出主要由重载的左移操作符(<<)完成,输入主要由重载的右移操作符(>>)完成:

  1. >> a 表示将数据输入到对象 a 中。
  2. << a 表示将对象 a 中的数据输出。

这些标准流对象都有默认的设备,如下表所示:

C++ 对象名设备名称C 中标准设备名默认含义
cin键盘stdin标准输入
cout显示器屏幕stdout标准输出
cerr显示器屏幕stderr标准错误输出

上表表明,cin 对象的默认输入设备是键盘,cout 对象的默认输出设备是显示器屏幕。

那么,C++ 是如何利用 cin/cout 对象与左移和右移运算符重载来实现输入输出的呢?以下以输出为例,说明其实现原理:

  1. coutostream 类的对象,因为它所指向的是标准设备(显示器屏幕),所以它在 iostream 头文件中作为全局对象进行定义。
  2. ostream cout(stdout); // 其默认指向的 C 中的标准设备名,作为其构造函数的参数使用。
  3. 在 iostream 头文件中,ostream 类对应每个基本数据类型都有其友元函数对左移操作符进行了重载。
    • ostream& operator<<(ostream &temp, int source);
    • ostream& operator<<(ostream &temp, char *ps);
    • … 等等

一句输出语句:cout << "http://www.cppblog.com/andxie99";,事实上调用的就是 ostream& operator<<(ostream &temp, char *ps); 这个运算符重载函数。由于返回的是流对象的引用,引用可以作为左值使用,所以当程序中有类似 cout << "http://www.cppblog.com/andxie99" << "白纸人生"; 这样的语句出现的时候,就能够构成连续输出。

由于 iostream 库不仅支持对象的输入输出,同时也支持文件流的输入输出,所以在详细讲解左移与右移运算符重载之前,我们有必要先对文件的输入输出以及输入输出的控制符有所了解。

2 fstream:ifstream 和 ofstream

与文件相关的输入输出类主要在 fstream 头文件中被定义。在这个头文件中主要定义了三个类,由这三个类控制对文件的各种输入输出操作,它们分别是 ifstreamofstreamfstream。其中 fstream 类是由 iostream 类派生而来,它们之间的继承关系如下图所示:
点击图片可在新窗口打开

由于文件设备并不像显示器屏幕与键盘那样是标准默认设备,所以它在 fstream 头文件中是没有像 cout 那样预先定义的全局对象。因此,我们必须自己定义一个该类的对象。如果要以文件作为设备向文件输出信息(也就是向文件写数据),那么就应该使用 ofstream 类。

ofstream 类的默认构造函数原型为:

ofstream::ofstream ( const char *filename , int mode = ios::out , int openprot = filebuf::openprot );
  • filename:要打开的文件名。
  • mode:要打开文件的方式。
  • openprot:打开文件的属性。

其中 modeopenprot 这两个参数的可选项如下表所示:

mode 属性表
ios::app以追加的方式打开文件
ios::ate文件打开后定位到文件尾,ios::app 就包含有此属性
ios::binary以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文
ios::in文件以输入方式打开
ios::out文件以输出方式打开
ios::trunc如果文件存在,把文件长度设为 0

可以用“或”把以上属性连接起来,例如 ios::out | ios::binary

openprot 属性表
属性含义
0普通文件,打开访问
1只读文件
2隐含文件
4系统文件

可以用“或”或者“+”把以上属性连接起来,例如 31 | 2,表示以只读和隐含属性打开文件。

实例代码如下:

#include <fstream> 
using namespace std; 

int main()  
{ 
    ofstream myfile ( "c:\\1.txt" , ios::out | ios::trunc , 0 ); 
    myfile << "白纸人生" << endl << "网址:" << "www.cppblog.com/andxie99"; 
    myfile.close(); 
    system ( "pause" ); 
}

文件使用完后可以使用 close 成员函数关闭文件。

ios::app 为追加模式,在使用追加模式的时候同时进行文件状态的判断是一个比较好的习惯。示例如下:

#include <iostream> 
#include <fstream> 
using namespace std; 

int main()  
{ 
    ofstream myfile ( "c:\\1.txt" , ios::app , 0 ); 
    if ( !myfile ) // 或者写成 myfile.fail()
    { 
        cout << "文件打开失败,目标文件状态可能为只读!"; 
        system ( "pause" ); 
        exit ( 1 ); 
    } 
    myfile << "白纸人生" << endl << "网址:" << "www.cppblog.com/andxie99" << endl; 
    myfile.close(); 
}

在定义 ifstreamofstream 类对象的时候,我们也可以不指定文件。以后可以通过成员函数 open() 显式地把一个文件连接到一个类对象上。例如:

#include <iostream> 
#include <fstream> 
using namespace std; 

int main()  
{ 
    ofstream myfile; 
    myfile.open ( "c:\\1.txt" , ios::out | ios::app , 0 ); 
    if ( !myfile ) // 或者写成 myfile.fail()
    { 
        cout << "文件创建失败,磁盘不可写或者文件为只读!"; 
        system ( "pause" ); 
        exit ( 1 ); 
    } 
    myfile << "白纸人生" << endl << "网址:" << "www.cppblog.com/andxie99" << endl; 
    myfile.close(); 
}

以下是一个利用 ifstream 类对象,将文件中的数据读取出来,然后再输出到标准设备中的例子。代码如下:

#include <iostream> 
#include <fstream> 
#include <string> 
using namespace std; 

int main()  
{ 
    ifstream myfile; 
    myfile.open ( "c:\\1.txt" , ios::in , 0 ); 
    if ( !myfile ) 
    { 
        cout << "文件读错误"; 
        system ( "pause" ); 
        exit ( 1 ); 
    } 
    char ch; 
    string content; 
    while ( myfile.get ( ch ) ) 
    { 
        content += ch; 
        cout.put ( ch ); // cout << ch; 这么写也是可以的
    } 
    myfile.close(); 
    cout << content; 
    system ( "pause" ); 
}

在上述代码中,我们利用成员函数 get(),逐一读取文件中的有效字符,再利用 put() 成员函数,将文件中的数据通过循环逐一输出到标准设备(屏幕)上。get() 成员函数会在文件读到末尾的时候返回假值,所以我们可以利用它的这个特性作为 while 循环的终止条件。同时,我们在上述代码中引入了 C++ 风格的字符串类型 string,在循环读取的时候逐一保存到 content 中。要使用 string 类型,必须包含 <string> 头文件。

在简单介绍过 ofstream 类和 ifstream 类后,再来看一下 fstream 类。fstream 类是由 iostream 派生而来,fstream 类对象可以同时对文件进行读写操作。

示例代码如下:

#include <iostream> 
#include <fstream> 
using namespace std; 

int main()  
{ 
    fstream myfile; 
    myfile.open ( "c:\\1.txt" , ios::out | ios::app , 0 ); 
    if ( !myfile ) 
    { 
        cout << "文件写错误,文件属性可能为只读!" << endl; 
        system ( "pause" ); 
        exit ( 1 ); 
    } 
    myfile << "白纸人生" << endl << "网址:" << "www.cppblog.com/andxie99" << endl;   
    myfile.close(); 
    
    myfile.open ( "c:\\1.txt" , ios::in , 0 ); 
    if ( !myfile ) 
    { 
        cout << "文件读错误,文件可能丢失!" << endl; 
        system ( "pause" ); 
        exit ( 1 ); 
    } 
    char ch; 
    while ( myfile.get ( ch ) ) 
    { 
        cout.put ( ch ); 
    } 
    myfile.close(); 
    system ( "pause" ); 
}

由于 fstream 类可以对文件同时进行读写操作,所以对其对象进行初始化的时候一定要显式地指定 modeopenprot 参数。

3 strstream:ostrstream 与 istrstream

简单理解就是能够控制字符串类型对象进行输入输出的类。C++ 不仅可以支持 C++ 风格的字符串流控制,还可以支持 C 风格的字符串流控制。

我们先看看 C++ 是如何对 C 风格的字符串流进行控制的。C 中的字符串其实就是字符数组,字符数组内的数据在内存中的位置是连续的。我们通常用 char str[size] 或者 char *str 的方式声明创建 C 风格字符数组。为了能让字符数组作为设备并提供输入输出操作,C++ 引入了 ostrstreamistrstreamstrstream 这三个类。要使用它们创建对象,就必须包含 <strstream> 头文件。

  • istrstream 类用于执行 C 风格的串流的输入操作,也就是以字符串数组作为输入设备。
  • ostrstream 类用于执行 C 风格的串流的输出操作,也就是以字符串数组作为输出设备。
  • strstream 类同时可以支持 C 风格的串流的输入输出操作。

istrstream 类是从 istream(输入流类)和 strstreambase(字符串流基类)派生而来,ostrstream 是从 ostream(输出流类)和 strstreambase(字符串流基类)派生而来,strstream 则是从 iostream(输入输出流类)和 strstreambase(字符串流基类)派生而来。

它们的继承关系如下图所示:
点击图片可在新窗口打开

串流同样不是标准设备,不会有预先定义好的全局对象,所以不能直接操作,需要通过构造函数创建对象。

istrstream 类的构造函数原型如下:

istrstream::istrstream ( const char *str , int size );

参数 1 表示字符串数组,而参数 2 表示数组大小。当 size 为 0 时,表示 istrstream 类对象直接连接到由 str 所指向的内存空间并以 \0 结尾的字符串。

以下是一个利用 istrstream 类创建对象,指定流输入设备为字符串数组,通过它向一个字符型对象输入数据的示例代码:

#include <iostream> 
#include <strstream> 
#include <cstring> // 包含 strlen 函数
using namespace std; 

int main()  
{ 
    char *name = "www.cppblog.com/andxie99"; 
    int arraysize = strlen ( name ) + 1; 
    istrstream is ( name , arraysize ); 
    char temp; 
    is >> temp; 
    cout << temp; 
    system ( "pause" ); 
}

ostrstream 类用于执行串流的输出,它的构造函数如下所示:

ostrstream::ostrstream ( char *_Ptr , int streamsize , int Mode = ios::out );

第一个参数是字符数组,第二个是说明数组的大小,第三个参数是指打开方式。

以下是一个示例代码:

#include <iostream> 
#include <strstream> 
using namespace std; 

int main()  
{ 
    int arraysize = 100; 
    char *pbuffer = new char [ arraysize ]; 
    ostrstream ostr ( pbuffer , arraysize , ios::out ); 
    ostr << arraysize << ends; // 使用 ostrstream 输出到流对象的时候,要用 ends 结束字符串
    cout << pbuffer; 
    delete[] pbuffer; 
    system ( "pause" ); 
}

在上述代码中,我们创建一个 C 风格的串流输出对象 ostr,将 arraysize 内的数据成功地以字符串的形式输出到了 ostr 对象所指向的 pbuffer 指针的堆空间中。pbuffer 正是我们要输出的字符串数组。在结尾要使用 ends 结束字符串,否则会有溢出的危险。

4 stringstream

stringstream 用于 C++ 风格的字符串的输入输出。stringstream 的构造函数原型如下:

stringstream::stringstream ( string str );

示例代码如下:

#include <iostream> 
#include <sstream> 
#include <string> 
using namespace std; 

int main()  
{ 
    stringstream ostr ( "ccc" ); 
    ostr.put ( 'd' ); 
    ostr.put ( 'e' ); 
    ostr << "fg"; 
    string gstr = ostr.str(); 
    cout << gstr << endl; 
    
    char a; 
    ostr >> a; 
    cout << a; 
    
    system ( "pause" ); 
}

此外,stringstream 类的对象还常用于 string 与各种内置类型数据之间的转换。示例代码如下:

#include <iostream> 
#include <sstream> 
#include <string> 
using namespace std; 

int main()  
{ 
    stringstream sstr; 
    //--------int 转 string----------- 
    int a = 100; 
    string str; 
    sstr << a; 
    sstr >> str; 
    cout << str << endl; 

    //--------string 转 char[]-------- 
    sstr.clear(); // 如果你想通过使用同一 stringstream 对象实现多种类型的转换,请注意在每一次转换之后都必须调用 clear() 成员函数
    string name = "colinguan"; 
    char cname [ 200 ]; 
    sstr << name; 
    sstr >> cname; 
    cout << cname; 

    system ( "pause" ); 
}

5 io_state 输入 / 输出的状态标志

C++ 中负责输入 / 输出的系统包括了关于每一个输入 / 输出操作的结果的记录信息。这些当前的状态信息被包含在 io_state 类型的对象中。io_state 是一个枚举类型,以下便是它包含的值:

  • goodbit:无错误
  • eofbit:已到达文件尾
  • failbit:非致命的输入 / 输出错误,可挽回
  • badbit:致命的输入 / 输出错误,无法挽回

有两种方法可以获得输入 / 输出的状态信息。一种方法是通过调用 rdstate() 函数,它将返回当前状态的错误标记。例如,如果没有错误,则 rdstate() 会返回 goodbit。示例代码如下:

#include <iostream> 
using namespace std; 

int main()  
{ 
    int a; 
    cin >> a; 
    cout << cin.rdstate() << endl; 
    if ( cin.rdstate() == ios::goodbit ) 
    { 
        cout << "输入数据的类型正确,无错误!" << endl; 
    } 
    if ( cin.rdstate() == ios_base::failbit ) 
    { 
        cout << "输入数据类型错误,非致命错误,可清除输入缓冲区挽回!" << endl; 
    } 
    system ( "pause" ); 
}

另一种方法则是使用以下任何一个函数来检测相应的输入 / 输出状态:

bool bad();
bool eof();
bool fail();
bool good();

示例代码如下:

#include <iostream> 
using namespace std; 

int main()  
{ 
    int a; 
    cin >> a; 
    cout << cin.rdstate() << endl; 
    if ( cin.good() ) 
    { 
        cout << "输入数据的类型正确,无错误!" << endl; 
    } 
    if ( cin.fail() ) 
    { 
        cout << "输入数据类型错误,非致命错误,可清除输入缓冲区挽回!" << endl; 
    } 
    system ( "pause" ); 
}

如果错误发生,那么流状态既被标记为错误,你必须清除这些错误状态,以使你的程序能正确适当地继续运行。要清除错误状态,需使用 clear() 函数。此函数带一个参数,它是你将要设为当前状态的标志值。通常,只要将 ios::goodbit 作为实参即可。

示例代码如下:

#include <iostream> 
using namespace std; 

int main()  
{ 
    int a; 
    cin >> a; 
    cout << cin.rdstate() << endl; 
    cin.clear ( ios::goodbit ); 
    cout << cin.rdstate() << endl; 
    system ( "pause" ); 
}

通常,当我们发现输入有错又需要改正的时候,使用 clear() 更改标记为正确后,同时也需要使用 get() 成员函数清除输入缓冲区,以达到重复输入的目的。示例代码如下:

#include <iostream> 
using namespace std; 

int main()  
{ 
    int a; 
    while ( 1 ) 
    { 
        cin >> a; 
        if ( !cin ) // 条件可改写为 cin.fail()
        { 
            cout << "输入有错!请重新输入" << endl; 
            cin.clear(); 
            cin.get(); 
        } 
        else 
        { 
            cout << a; 
            break; 
        } 
    } 
    system ( "pause" ); 
}

最后再给出一个对文件流错误标记处理的例子,巩固学习。代码如下:

#include <iostream> 
#include <fstream> 
using namespace std; 

int main()  
{ 
    ifstream myfile ( "c:\\1.txt" , ios_base::in , 0 ); 
    if ( myfile.fail() ) 
    { 
        cout << "文件读取失败或指定文件不存在!" << endl; 
    } 
    else 
    { 
        char ch; 
        while ( myfile.get ( ch ) ) 
        { 
            cout << ch; 
        } 
        if ( myfile.eof() ) 
        { 
            cout << "文件内容已经全部读完" << endl; 
        } 
    } 
    system ( "pause" ); 
}

C++ 中头文件 iostream 介绍

fengbingchun 于 2017-03-19 16:13:04 发布

1. C++ 输入输出机制概述

C++ 语言并不直接处理输入输出,而是通过定义在标准库中的一簇类型来实现输入输出操作。这些类型支持从设备读取数据以及向设备写入数据,设备可以是文件、控制台窗口等。此外,还有一些类型允许内存输入输出,即从 string 读取数据或向 string 写入数据。

2. 头文件 iostream 的作用

在 C++/C++11 中,头文件 <iostream> 定义了标准输入/输出流对象。包含 <iostream> 时,也会自动包含 <ios><streambuf><istream><ostream><iosfwd>

3. iostream 中的对象

3.1 窄字符(char)

  • cin:标准输入流(对象)。
  • cout:标准输出流(对象)。
  • cerr:标准错误输出流(对象),用于输出错误信息。
  • clog:标准日志输出流(对象),用于记录日志信息。

3.2 宽字符(wchar_t)

  • wcin:标准输入流(宽字符)(对象)。
  • wcout:标准输出流(宽字符)(对象)。
  • wcerr:标准错误输出流(宽字符)(对象)。
  • wclog:标准日志输出流(宽字符)(对象)。
__PURE_APPDOMAIN_GLOBAL extern _CRTDATA2 istream cin, *_Ptr_cin;
__PURE_APPDOMAIN_GLOBAL extern _CRTDATA2 ostream cout, *_Ptr_cout;
__PURE_APPDOMAIN_GLOBAL extern _CRTDATA2 ostream cerr, *_Ptr_cerr;
__PURE_APPDOMAIN_GLOBAL extern _CRTDATA2 ostream clog, *_Ptr_clog;
__PURE_APPDOMAIN_GLOBAL extern _CRTDATA2 wistream wcin, *_Ptr_wcin;
__PURE_APPDOMAIN_GLOBAL extern _CRTDATA2 wostream wcout, *_Ptr_wcout;
__PURE_APPDOMAIN_GLOBAL extern _CRTDATA2 wostream wcerr, *_Ptr_wcerr;
__PURE_APPDOMAIN_GLOBAL extern _CRTDATA2 wostream wclog, *_Ptr_wclog;

4. IO 库的组成

4.1 输入输出流类型

  • istream:输入流类型,提供输入操作。
  • ostream:输出流类型,提供输出操作。

4.2 标准输入输出流对象

  • cin:一个 istream 对象,标准输入流,用于从标准输入读取数据。
  • cout:一个 ostream 对象,标准输出流,用于向标准输出写入数据。输出可以重定向(使用 “>` 或 “1>”)到指定文件中,通常用于程序的正常输出内容。
  • cerr:一个 ostream 对象,标准错误流,用于输出程序错误信息或其他不属于正常逻辑的输出内容。写入到 cerr 的数据默认不经过缓冲区,直接输出到显示器,可以通过 “2>” 重定向到指定文件。
  • clog:一个 ostream 对象,标准日志流,关联到标准错误。与 cerr 的区别在于,cerr 的输出不经过缓冲区,而 clog 的输出默认会存放在缓冲区,缓冲区满或遇到 endl 时才会输出。clog 通常用于报告程序的执行信息,可存入日志文件。

4.3 输入输出操作符

  • >>:从 istream 对象读取输入数据的操作符。
  • <<:向 ostream 对象写入输出数据的操作符。

4.4 其他相关函数

  • getline:从给定的 istream 读取一行数据,并存入给定的 string 对象中。

5. IO 库类型与头文件的关系

  • <iostream>:定义了用于读写流的基本类型。
  • <fstream>:定义了读写命名文件的类型。
  • <sstream>:定义了读写内存 string 对象的类型。

img

6. 宽字符支持

为了支持使用宽字符的语言,标准库定义了一组类型和对象来操作 wchar_t 类型的数据。宽字符版本的类型和函数的名字以字母 “w” 开始,例如,wcinwcoutwcerr 分别是 cincoutcerr 的宽字符版本。宽字符版本的类型和对象与其对应的普通 char 版本的类型定义在同一个头文件中。

7. 测试代码

7.1 使用标准输出流 cout

#include <iostream>

int test_iostream_cout()
{
    char str[] = "Hello C++";
    std::cout << "Value of str is : " << str << std::endl;
    return 0;
}

7.2 使用标准输入流 cin

#include <iostream>

int test_iostream_cin()
{
    char name[50];
    std::cout << "Please enter your name: ";
    std::cin >> name;
    std::cout << "Your name is: " << name << std::endl;
    return 0;
}

7.3 使用标准日志流 clog

#include <iostream>

int test_iostream_clog()
{
    char str[] = "Unable to read....";
    std::clog << "Error message : " << str << std::endl;
    return 0;
}

7.4 使用标准错误流 cerr

#include <iostream>

int test_iostream_cerr()
{
    char str[] = "Unable to read....";
    std::cerr << "Error message : " << str << std::endl;
    return 0;
}

7.5 宽字符版本的输入输出

#include <iostream>

static void TestWide()
{
    int i = 0;
    std::wcout << L"Enter a number: ";
    std::wcin >> i;
    std::wcerr << L"test for wcerr" << std::endl;
    std::wclog << L"test for wclog" << std::endl;
}

int test_iostream_w()
{
    int i = 0;
    std::cout << "Enter a number: ";
    std::cin >> i;
    std::cerr << "test for cerr" << std::endl;
    std::clog << "test for clog" << std::endl;
    TestWide();
    return 0;
}

8. iostream 的本质

iostream 是 C++ 标准库的一个头文件。标准库的 “标准” 之意在于,每个 C++ 编译器都必须自带该库,无论使用何种 C++ 编译器,其用法和行为都是一致的。头文件中仅包含标准库中类和函数的声明,而标准库的实际代码是预编译的(或者是类模板,这种情况下会直接写在头文件中)。通过 #include 该头文件,告知编译器使用其中声明的内容,编译器则负责将实际的库函数与代码一起生成可执行文件。

头文件 <iostream> 本身没有扩展名,它会进一步包含其他头文件。通过逐层查找,可以了解 cincout 等对象的具体实现。

9. IO 的广泛含义

在计算机领域,IO 通常指输入(Input)和输出(Output),其含义较为广泛,不仅限于 iostream,而是泛指计算机的输入和输出操作。


C++ 中的 iostream 标准库

瑞 新 于 2019-03-28 10:18:43 发布

1. iostream 标准库概述

C++ 提供了一个强大的输入输出(Input/Output,简称 I/O)标准库,用于处理各种输入和输出操作。该库的核心是 iostream,其名称由 “i-o-stream” 组成,意为输入输出流。iostream 标准库是 C++ 标准库的一部分,提供了一套面向对象的输入输出机制,支持对标准输入输出设备(如键盘和显示器)以及文件的读写操作。

2. iostream 类库的层次结构

iostream 类库的层次结构基于面向对象的继承机制,其核心类包括:

  • ios:抽象基类,定义了输入输出流的基本接口和状态管理功能。
  • istream:从 ios 派生,支持输入操作。
  • ostream:从 ios 派生,支持输出操作。
  • iostream:通过多重继承从 istreamostream 派生,支持双向输入输出操作。

此外,为了支持文件的输入输出操作,iostream 类库还提供了以下类:

  • ifstream:从 istream 派生,用于从文件中读取数据。
  • ofstream:从 ostream 派生,用于向文件中写入数据。
  • fstream:从 iostream 派生,支持对文件的双向输入输出操作。

3. 使用 iostream 标准库

在 C++ 程序中,要使用 iostream 标准库的功能,需要在程序开头包含相应的头文件:

#include <iostream>

#include 是一个预处理指令,用于将指定的头文件内容插入到当前文件中。<iostream> 是 C++ 标准库中的一个头文件,它声明了 iostream 类库中所有类和函数的接口。

3.1 标准输入输出

iostream 标准库提供了两个全局对象,用于处理标准输入和输出:

  • cinistream 类的对象,用于从标准输入设备(通常是键盘)读取数据。
  • coutostream 类的对象,用于向标准输出设备(通常是显示器)写入数据。

3.2 文件输入输出

对于文件操作,可以使用以下类:

  • ifstream:用于打开文件并从中读取数据。
  • ofstream:用于打开文件并向其中写入数据。
  • fstream:用于打开文件并进行双向读写操作。

3.3 标准库的特性

iostream 标准库具有以下特性:

  • 面向对象:通过类和对象的形式,提供了一套统一的输入输出接口。
  • 类型安全:支持对各种数据类型的输入输出操作,并通过重载运算符(如 <<>>)实现类型安全的输入输出。
  • 可扩展性:用户可以通过继承和重载机制,扩展 iostream 类库的功能,以支持自定义类型的输入输出。

C++——IOStream

鹅一百已于 2025-04-14 15:01:08 修改

1. 什么是 IO?

在 C 语言和 C++ 中,我们已经接触到了两种 IO(输入/输出)的概念。例如,以下代码分别展示了 C 语言和 C++ 中的 IO 库:

#include <stdio.h>  // C 语言标准输入输出库
#include <iostream>  // C++ 输入输出流库

iostream 是 C++ 中的 IO 流库,其中 I 代表输入(Input),O 代表输出(Output),用于实现用户与程序之间的交互。在早期的 C++ 程序中,我们通常使用固定的测试用例来验证程序结果,而较少关注输入输出的动态交互。然而,在实际应用中,大多数程序需要与用户进行实时交互,因此 IO 流是程序设计中不可或缺的部分。

img

2. 什么是流?

流(Stream)是一种连续且具有方向的概念,类似于水流或电流。在 IO 流中,输入(In)是指用户向内存输入数据,而输出(Out)是指程序将数据从内存输出到终端或其他设备。尽管流是连续的,但数据的捕获过程并非如此。用户可以持续向内存输入数据,但程序只会根据需要捕获特定的数据片段,而未捕获的数据则暂时存储在缓冲区中,等待下一次程序的读取。

img

3. C++ 中的 IO 流

在 C 语言中,最常用的 IO 函数是 printfscanf,它们分别用于输出和输入。然而,C++ 引入了 cincout 等新的 IO 流对象,以替代传统的 C 语言 IO 函数。C 语言的 IO 函数需要使用特定的格式化占位符来指定数据类型,这在处理复杂数据时容易出错,尤其是在涉及泛型编程时,数据类型可能不明确,导致格式化错误。

img

为了解决这一问题,C++ 利用面向对象的特性重新实现了 IO 流。其最大的改进是能够自动识别数据类型,无需使用占位符来指定数据类型。虽然 C++ 的 IO 流在某些情况下可能不如 C 语言的 printf 灵活,但 C++ 的 IO 流提供了更高的安全性和易用性。此外,C++ 并未禁用 printfscanf,在需要时仍可使用。

img

3.1 标准 IO 流

C++ 标准库提供了四个全局的 IO 流对象:

  • cin:标准输入流,用于从键盘输入数据到内存。
  • cout:标准输出流,用于将数据从内存输出到控制台。
  • cerr:标准错误输出流,用于输出错误信息。
  • clog:标准日志输出流,用于记录日志信息。

其中,cin 的输入并非直接从键盘获取,而是通过缓冲区进行。用户输入的数据首先存储在缓冲区中,cin 从缓冲区中提取所需的数据,未提取的数据仍保留在缓冲区中,等待下一次读取。此外,当输入对象为字符或字符串时,空格和回车符无法通过 cin 输入,因为它们被用作分隔符,表示输入数据的结束。

在实际编程中,我们经常遇到以下代码:

while (cin >> a) {
    // ...
}

这段代码的循环终止条件是如何判断的呢?我们知道,流插入操作(如 cin >> a)的返回值是一个 istream 对象,而流提取操作的返回值是一个 ostream 对象。编译器无法直接将这些对象转换为布尔值,因此 C++ 标准库引入了一种新的语法——类型转换运算符重载。

3.2 类型转换运算符重载

类型转换运算符重载允许程序员自定义对象在特定类型转换时的行为。例如,以下代码展示了如何通过重载 operator bool() 来改变对象的布尔值判断逻辑:

class A {
public:
    A(int a) : _a(a) {}

    operator bool() {
        if (_a > 10)
            return false;
        else
            return true;
    }

private:
    int _a;
};

int main() {
    A a1(20);
    A a2(1);

    cout << (bool)a1 << endl;  // 输出:false
    cout << (bool)a2 << endl;  // 输出:true
}

通过重载 operator bool(),我们改变了对象在布尔上下文中的行为。这种机制不仅适用于布尔类型,还可以用于其他类型的转换,从而大大提高程序的灵活性。

3.3 文件 IO 流

除了标准输入输出流,C++ 还提供了文件 IO 流,用于处理文件的读写操作。文件 IO 流包含三个主要对象:

  • ifstream:用于文件输入(读取)。
  • ofstream:用于文件输出(写入)。
  • fstream:用于同时支持文件输入和输出。

文件的读写有两种方式:二进制文件和文本文件。二进制文件类似于浅拷贝,直接将数据的二进制形式原封不动地写入文件;而文本文件类似于深拷贝,只将有效数据以文本形式写入文件。

img

例如,对于一个单链表,如果使用二进制文件存储,则会直接复制链表节点的二进制数据,包括指针的值。然而,由于程序每次运行时的内存地址不同,这种存储方式会导致指针失效,读取到的数据可能无效。而使用文本文件存储时,需要手动遍历链表节点,将节点的值以字符串形式写入文件,并在读取时手动将字符串转换为合适的数据类型,重新构建链表。

文本文件操作的函数在不同库中可能存在差异,因此在需要时应查阅相关文档或自行实现。


C++ iostream、ostream、istream 等标准库详解

YoungGeeker 于 2022-08-28 19:04:46 发布

在编写 C++ 代码时,iostream 库是几乎每个程序都会用到的标准库,尽管有些人可能会选择使用 cstdio。当我们深入查看 iostream 库的源码时,会发现其中包含大量的 include、预处理、externnamespace 等内容,同时还引入了 iosostreamistreamstreambuf 等头文件。本文将为您详细揭秘这些内容。

iostream

iostream 是 C++ 的标准输入输出流库,其名称由 “输入(in)”、“输出(out)” 和 “流(stream)” 组合而成。

组成

iostream 库的基础是两种类型:istreamostream,分别表示输入流和输出流。流是指从某种 I/O 设备上读取或写入的字符序列,强调字符是随着时间顺序生成或消耗的。

标准库定义了 4 个 IO 对象:

  • cinistream 类型):标准输入。
  • coutostream 类型):标准输出。
  • cerrclogostream 类型):分别用于输出错误信息和程序执行的一般信息。

基本类模板

iostream 库基于类模板的层级结构,以一种与类型无关的方式提供大部分功能。基本类模板包含两个参数:字符类型(charT)决定处理的元素类型,而特性参数为每个特定的元素类型提供额外的特征。

类层级结构中的类模板实例名称通常在类名前带有 basic_ 前缀,例如:

  • istream 对应的类模板为 basic_istream
  • fstream 对应的类模板为 basic_fstream

唯一的例外是 ios_base,它本身是类型无关的,因此不是一个类模板,而是一个普通类。

类模板实例

名称和关系

iostream 库中包含两组标准的类模板层级结构实例:

  • 面向单字节(char 类型)的实例,例如 iosistreamofstream
  • 面向宽字节(wchar_t 类型)的实例,其命名规则与单字节实例相同,但所有类和对象名称前带有 w 前缀,例如 wioswistreamwofstream

标准对象

作为 iostream 库的一部分,头文件声明了一些用于标准输入输出设备的对象。这些对象分为两组:

  • 面向单字节的对象:cincoutcerrclog
  • 面向宽字节的对象:wcinwcoutwcerrwclog

类型

iostream 库中的类很少直接使用基本类型作为成员的原型,而是通常使用根据其实例的特性定义的类型。对于默认的 charwchar_t 类型的实例,streamposstreamoffstreamsize 分别用于表示位置、偏移和大小。

操纵符

操纵符是与流对象的插入(<<)和提取(>>)运算符一同使用的全局函数,用于变更流的属性和格式设置。例如,endlhexscientific 是常见的操纵符。

源码

以下是 iostream 库的源码:

// -*- C++ -*-
//===--------------------------- iostream ---------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is dual licensed under the MIT and the University of Illinois Open
// Source Licenses. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
 
#ifndef _LIBCPP_IOSTREAM
#define _LIBCPP_IOSTREAM
 
 
 
#include <ios>
#include <streambuf>
#include <istream>
#include <ostream>
 
namespace std {
 
extern istream cin;
extern ostream cout;
extern ostream cerr;
extern ostream clog;
extern wistream wcin;
extern wostream wcout;
extern wostream wcerr;
extern wostream wclog;
 
}  // std
 
 
#include <__config>
#include <ios>
#include <streambuf>
#include <istream>
#include <ostream>
 
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#endif
 
_LIBCPP_BEGIN_NAMESPACE_STD
 
#ifndef _LIBCPP_HAS_NO_STDIN
extern _LIBCPP_FUNC_VIS istream cin;
extern _LIBCPP_FUNC_VIS wistream wcin;
#endif
#ifndef _LIBCPP_HAS_NO_STDOUT
extern _LIBCPP_FUNC_VIS ostream cout;
extern _LIBCPP_FUNC_VIS wostream wcout;
#endif
extern _LIBCPP_FUNC_VIS ostream cerr;
extern _LIBCPP_FUNC_VIS wostream wcerr;
extern _LIBCPP_FUNC_VIS ostream clog;
extern _LIBCPP_FUNC_VIS wostream wclog;
 
_LIBCPP_END_NAMESPACE_STD
 
#endif  // _LIBCPP_IOSTREAM

ostream

ostream 是专为窄字节设计的输出库。以下是其源码:

// Output streams -*- C++ -*-
 
// Copyright (C) 1997-2019 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library.  This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
 
// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.
 
// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
// <http://www.gnu.org/licenses/>.
 
/** @file include/ostream
 *  This is a Standard C++ Library header.
 */
 
//
// ISO C++ 14882: 27.6.2  Output streams
//
 
#ifndef _GLIBCXX_OSTREAM
#define _GLIBCXX_OSTREAM 1
 
#pragma GCC system_header
 
#include <ios>
#include <bits/ostream_insert.h>
 
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
 
  /**
   *  @brief  Template class basic_ostream.
   *  @ingroup io
   *
   *  @tparam _CharT  Type of character stream.
   *  @tparam _Traits  Traits for character type, defaults to
   *                   char_traits<_CharT>.
   *
   *  This is the base class for all output streams.  It provides text
   *  formatting of all builtin types, and communicates with any class
   *  derived from basic_streambuf to do the actual output.
  */
  template<typename _CharT, typename _Traits>
    class basic_ostream : virtual public basic_ios<_CharT, _Traits>
    {
    public:
      // Types (inherited from basic_ios):
      typedef _CharT			 		char_type;
      typedef typename _Traits::int_type 		int_type;
      typedef typename _Traits::pos_type 		pos_type;
      typedef typename _Traits::off_type 		off_type;
      typedef _Traits			 		traits_type;
 
      // Non-standard Types:
      typedef basic_streambuf<_CharT, _Traits> 		__streambuf_type;
      typedef basic_ios<_CharT, _Traits>		__ios_type;
      typedef basic_ostream<_CharT, _Traits>		__ostream_type;
      typedef num_put<_CharT, ostreambuf_iterator<_CharT, _Traits> >
      							__num_put_type;
      typedef ctype<_CharT>	      			__ctype_type;
 
      /**
       *  @brief  Base constructor.
       *
       *  This ctor is almost never called by the user directly, rather from
       *  derived classes' initialization lists, which pass a pointer to
       *  their own stream buffer.
      */
      explicit
      basic_ostream(__streambuf_type* __sb)
      { this->init(__sb); }
 
      /**
       *  @brief  Base destructor.
       *
       *  This does very little apart from providing a virtual base dtor.
      */
      virtual
      ~basic_ostream() { }
 
      /// Safe prefix/suffix operations.
      class sentry;
      friend class sentry;
 
      //@{
      /**
       *  @brief  Interface for manipulators.
       *
       *  Manipulators such as @c std::endl and @c std::hex use these
       *  functions in constructs like "std::cout << std::endl".  For more
       *  information, see the iomanip header.
      */
      __ostream_type&
      operator<<(__ostream_type& (*__pf)(__ostream_type&))
      {
	// _GLIBCXX_RESOLVE_LIB_DEFECTS
	// DR 60. What is a formatted input function?
	// The inserters for manipulators are *not* formatted output functions.
	return __pf(*this);
      }
 
      __ostream_type&
      operator<<(__ios_type& (*__pf)(__ios_type&))
      {
	// _GLIBCXX_RESOLVE_LIB_DEFECTS
	// DR 60. What is a formatted input function?
	// The inserters for manipulators are *not* formatted output functions.
	__pf(*this);
	return *this;
      }
 
      __ostream_type&
      operator<<(ios_base& (*__pf) (ios_base&))
      {
	// _GLIBCXX_RESOLVE_LIB_DEFECTS
	// DR 60. What is a formatted input function?
	// The inserters for manipulators are *not* formatted output functions.
	__pf(*this);
	return *this;
      }
      //@}
 
      //@{
      /**
       *  @name Inserters
       *
       *  All the @c operator<< functions (aka <em>formatted output
       *  functions</em>) have some common behavior.  Each starts by
       *  constructing a temporary object of type std::basic_ostream::sentry.
       *  This can have several effects, concluding with the setting of a
       *  status flag; see the sentry documentation for more.
       *
       *  If the sentry status is good, the function tries to generate
       *  whatever data is appropriate for the type of the argument.
       *
       *  If an exception is thrown during insertion, ios_base::badbit
       *  will be turned on in the stream's error state without causing an
       *  ios_base::failure to be thrown.  The original exception will then
       *  be rethrown.
      */
 
      //@{
      /**
       *  @brief Integer arithmetic inserters
       *  @param  __n A variable of builtin integral type.
       *  @return  @c *this if successful
       *
       *  These functions use the stream's current locale (specifically, the
       *  @c num_get facet) to perform numeric formatting.
      */
      __ostream_type&
      operator<<(long __n)
      { return _M_insert(__n); }
 
      __ostream_type&
      operator<<(unsigned long __n)
      { return _M_insert(__n); }
 
      __ostream_type&
      operator<<(bool __n)
      { return _M_insert(__n); }
 
      __ostream_type&
      operator<<(short __n);
 
      __ostream_type&
      operator<<(unsigned short __n)
      {
	// _GLIBCXX_RESOLVE_LIB_DEFECTS
	// 117. basic_ostream uses nonexistent num_put member functions.
	return _M_insert(static_cast<unsigned long>(__n));
      }
 
      __ostream_type&
      operator<<(int __n);
 
      __ostream_type&
      operator<<(unsigned int __n)
      {
	// _GLIBCXX_RESOLVE_LIB_DEFECTS
	// 117. basic_ostream uses nonexistent num_put member functions.
	return _M_insert(static_cast<unsigned long>(__n));
      }
 
#ifdef _GLIBCXX_USE_LONG_LONG
      __ostream_type&
      operator<<(long long __n)
      { return _M_insert(__n); }
 
      __ostream_type&
      operator<<(unsigned long long __n)
      { return _M_insert(__n); }
#endif
      //@}
 
      //@{
      /**
       *  @brief  Floating point arithmetic inserters
       *  @param  __f A variable of builtin floating point type.
       *  @return  @c *this if successful
       *
       *  These functions use the stream's current locale (specifically, the
       *  @c num_get facet) to perform numeric formatting.
      */
      __ostream_type&
      operator<<(double __f)
      { return _M_insert(__f); }
 
      __ostream_type&
      operator<<(float __f)
      {
	// _GLIBCXX_RESOLVE_LIB_DEFECTS
	// 117. basic_ostream uses nonexistent num_put member functions.
	return _M_insert(static_cast<double>(__f));
      }
 
      __ostream_type&
      operator<<(long double __f)
      { return _M_insert(__f); }
      //@}
 
      /**
       *  @brief  Pointer arithmetic inserters
       *  @param  __p A variable of pointer type.
       *  @return  @c *this if successful
       *
       *  These functions use the stream's current locale (specifically, the
       *  @c num_get facet) to perform numeric formatting.
      */
      __ostream_type&
      operator<<(const void* __p)
      { return _M_insert(__p); }
 
#if __cplusplus >= 201703L
      __ostream_type&
      operator<<(nullptr_t)
      { return *this << "nullptr"; }
#endif
 
      /**
       *  @brief  Extracting from another streambuf.
       *  @param  __sb  A pointer to a streambuf
       *
       *  This function behaves like one of the basic arithmetic extractors,
       *  in that it also constructs a sentry object and has the same error
       *  handling behavior.
       *
       *  If @p __sb is NULL, the stream will set failbit in its error state.
       *
       *  Characters are extracted from @p __sb and inserted into @c *this
       *  until one of the following occurs:
       *
       *  - the input stream reaches end-of-file,
       *  - insertion into the output sequence fails (in this case, the
       *    character that would have been inserted is not extracted), or
       *  - an exception occurs while getting a character from @p __sb, which
       *    sets failbit in the error state
       *
       *  If the function inserts no characters, failbit is set.
      */
      __ostream_type&
      operator<<(__streambuf_type* __sb);
      //@}
 
      //@{
      /**
       *  @name Unformatted Output Functions
       *
       *  All the unformatted output functions have some common behavior.
       *  Each starts by constructing a temporary object of type
       *  std::basic_ostream::sentry.  This has several effects, concluding
       *  with the setting of a status flag; see the sentry documentation
       *  for more.
       *
       *  If the sentry status is good, the function tries to generate
       *  whatever data is appropriate for the type of the argument.
       *
       *  If an exception is thrown during insertion, ios_base::badbit
       *  will be turned on in the stream's error state.  If badbit is on in
       *  the stream's exceptions mask, the exception will be rethrown
       *  without completing its actions.
      */
 
      /**
       *  @brief  Simple insertion.
       *  @param  __c  The character to insert.
       *  @return  *this
       *
       *  Tries to insert @p __c.
       *
       *  @note  This function is not overloaded on signed char and
       *         unsigned char.
      */
      __ostream_type&
      put(char_type __c);
 
      /**
       *  @brief  Core write functionality, without sentry.
       *  @param  __s  The array to insert.
       *  @param  __n  Maximum number of characters to insert.
      */
      void
      _M_write(const char_type* __s, streamsize __n)
      {
	const streamsize __put = this->rdbuf()->sputn(__s, __n);
	if (__put != __n)
	  this->setstate(ios_base::badbit);
      }
 
      /**
       *  @brief  Character string insertion.
       *  @param  __s  The array to insert.
       *  @param  __n  Maximum number of characters to insert.
       *  @return  *this
       *
       *  Characters are copied from @p __s and inserted into the stream until
       *  one of the following happens:
       *
       *  - @p __n characters are inserted
       *  - inserting into the output sequence fails (in this case, badbit
       *    will be set in the stream's error state)
       *
       *  @note  This function is not overloaded on signed char and
       *         unsigned char.
      */
      __ostream_type&
      write(const char_type* __s, streamsize __n);
      //@}
 
      /**
       *  @brief  Synchronizing the stream buffer.
       *  @return  *this
       *
       *  If @c rdbuf() is a null pointer, changes nothing.
       *
       *  Otherwise, calls @c rdbuf()->pubsync(), and if that returns -1,
       *  sets badbit.
      */
      __ostream_type&
      flush();
 
      /**
       *  @brief  Getting the current write position.
       *  @return  A file position object.
       *
       *  If @c fail() is not false, returns @c pos_type(-1) to indicate
       *  failure.  Otherwise returns @c rdbuf()->pubseekoff(0,cur,out).
      */
      pos_type
      tellp();
 
      /**
       *  @brief  Changing the current write position.
       *  @param  __pos  A file position object.
       *  @return  *this
       *
       *  If @c fail() is not true, calls @c rdbuf()->pubseekpos(pos).  If
       *  that function fails, sets failbit.
      */
      __ostream_type&
      seekp(pos_type);
 
      /**
       *  @brief  Changing the current write position.
       *  @param  __off  A file offset object.
       *  @param  __dir  The direction in which to seek.
       *  @return  *this
       *
       *  If @c fail() is not true, calls @c rdbuf()->pubseekoff(off,dir).
       *  If that function fails, sets failbit.
      */
       __ostream_type&
      seekp(off_type, ios_base::seekdir);
 
    protected:
      basic_ostream()
      { this->init(0); }
 
#if __cplusplus >= 201103L
      // Non-standard constructor that does not call init()
      basic_ostream(basic_iostream<_CharT, _Traits>&) { }
 
      basic_ostream(const basic_ostream&) = delete;
 
      basic_ostream(basic_ostream&& __rhs)
      : __ios_type()
      { __ios_type::move(__rhs); }
 
      // 27.7.3.3 Assign/swap
 
      basic_ostream& operator=(const basic_ostream&) = delete;
 
      basic_ostream&
      operator=(basic_ostream&& __rhs)
      {
	swap(__rhs);
	return *this;
      }
 
      void
      swap(basic_ostream& __rhs)
      { __ios_type::swap(__rhs); }
#endif
 
      template<typename _ValueT>
	__ostream_type&
	_M_insert(_ValueT __v);
    };
 
  /**
   *  @brief  Performs setup work for output streams.
   *
   *  Objects of this class are created before all of the standard
   *  inserters are run.  It is responsible for <em>exception-safe prefix and
   *  suffix operations</em>.
  */
  template <typename _CharT, typename _Traits>
    class basic_ostream<_CharT, _Traits>::sentry
    {
      // Data Members.
      bool 				_M_ok;
      basic_ostream<_CharT, _Traits>& 	_M_os;
 
    public:
      /**
       *  @brief  The constructor performs preparatory work.
       *  @param  __os  The output stream to guard.
       *
       *  If the stream state is good (@a __os.good() is true), then if the
       *  stream is tied to another output stream, @c is.tie()->flush()
       *  is called to synchronize the output sequences.
       *
       *  If the stream state is still good, then the sentry state becomes
       *  true (@a okay).
      */
      explicit
      sentry(basic_ostream<_CharT, _Traits>& __os);
 
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
      /**
       *  @brief  Possibly flushes the stream.
       *
       *  If @c ios_base::unitbuf is set in @c os.flags(), and
       *  @c std::uncaught_exception() is true, the sentry destructor calls
       *  @c flush() on the output stream.
      */
      ~sentry()
      {
	// XXX MT
	if (bool(_M_os.flags() & ios_base::unitbuf) && !uncaught_exception())
	  {
	    // Can't call flush directly or else will get into recursive lock.
	    if (_M_os.rdbuf() && _M_os.rdbuf()->pubsync() == -1)
	      _M_os.setstate(ios_base::badbit);
	  }
      }
#pragma GCC diagnostic pop
 
      /**
       *  @brief  Quick status checking.
       *  @return  The sentry state.
       *
       *  For ease of use, sentries may be converted to booleans.  The
       *  return value is that of the sentry state (true == okay).
      */
#if __cplusplus >= 201103L
      explicit
#endif
      operator bool() const
      { return _M_ok; }
    };
 
  //@{
  /**
   *  @brief  Character inserters
   *  @param  __out  An output stream.
   *  @param  __c  A character.
   *  @return  out
   *
   *  Behaves like one of the formatted arithmetic inserters described in
   *  std::basic_ostream.  After constructing a sentry object with good
   *  status, this function inserts a single character and any required
   *  padding (as determined by [22.2.2.2.2]).  @c __out.width(0) is then
   *  called.
   *
   *  If @p __c is of type @c char and the character type of the stream is not
   *  @c char, the character is widened before insertion.
  */
  template<typename _CharT, typename _Traits>
    inline basic_ostream<_CharT, _Traits>&
    operator<<(basic_ostream<_CharT, _Traits>& __out, _CharT __c)
    { return __ostream_insert(__out, &__c, 1); }
 
  template<typename _CharT, typename _Traits>
    inline basic_ostream<_CharT, _Traits>&
    operator<<(basic_ostream<_CharT, _Traits>& __out, char __c)
    { return (__out << __out.widen(__c)); }
 
  // Specialization
  template <class _Traits>
    inline basic_ostream<char, _Traits>&
    operator<<(basic_ostream<char, _Traits>& __out, char __c)
    { return __ostream_insert(__out, &__c, 1); }
 
  // Signed and unsigned
  template<class _Traits>
    inline basic_ostream<char, _Traits>&
    operator<<(basic_ostream<char, _Traits>& __out, signed char __c)
    { return (__out << static_cast<char>(__c)); }
 
  template<class _Traits>
    inline basic_ostream<char, _Traits>&
    operator<<(basic_ostream<char, _Traits>& __out, unsigned char __c)
    { return (__out << static_cast<char>(__c)); }
  //@}
 
  //@{
  /**
   *  @brief  String inserters
   *  @param  __out  An output stream.
   *  @param  __s  A character string.
   *  @return  out
   *  @pre  @p __s must be a non-NULL pointer
   *
   *  Behaves like one of the formatted arithmetic inserters described in
   *  std::basic_ostream.  After constructing a sentry object with good
   *  status, this function inserts @c traits::length(__s) characters starting
   *  at @p __s, widened if necessary, followed by any required padding (as
   *  determined by [22.2.2.2.2]).  @c __out.width(0) is then called.
  */
  template<typename _CharT, typename _Traits>
    inline basic_ostream<_CharT, _Traits>&
    operator<<(basic_ostream<_CharT, _Traits>& __out, const _CharT* __s)
    {
      if (!__s)
	__out.setstate(ios_base::badbit);
      else
	__ostream_insert(__out, __s,
			 static_cast<streamsize>(_Traits::length(__s)));
      return __out;
    }
 
  template<typename _CharT, typename _Traits>
    basic_ostream<_CharT, _Traits> &
    operator<<(basic_ostream<_CharT, _Traits>& __out, const char* __s);
 
  // Partial specializations
  template<class _Traits>
    inline basic_ostream<char, _Traits>&
    operator<<(basic_ostream<char, _Traits>& __out, const char* __s)
    {
      if (!__s)
	__out.setstate(ios_base::badbit);
      else
	__ostream_insert(__out, __s,
			 static_cast<streamsize>(_Traits::length(__s)));
      return __out;
    }
 
  // Signed and unsigned
  template<class _Traits>
    inline basic_ostream<char, _Traits>&
    operator<<(basic_ostream<char, _Traits>& __out, const signed char* __s)
    { return (__out << reinterpret_cast<const char*>(__s)); }
 
  template<class _Traits>
    inline basic_ostream<char, _Traits> &
    operator<<(basic_ostream<char, _Traits>& __out, const unsigned char* __s)
    { return (__out << reinterpret_cast<const char*>(__s)); }
  //@}
 
  // Standard basic_ostream manipulators
 
  /**
   *  @brief  Write a newline and flush the stream.
   *
   *  This manipulator is often mistakenly used when a simple newline is
   *  desired, leading to poor buffering performance.  See
   *  https://gcc.gnu.org/onlinedocs/libstdc++/manual/streambufs.html#io.streambuf.buffering
   *  for more on this subject.
  */
  template<typename _CharT, typename _Traits>
    inline basic_ostream<_CharT, _Traits>&
    endl(basic_ostream<_CharT, _Traits>& __os)
    { return flush(__os.put(__os.widen('\n'))); }
 
  /**
   *  @brief  Write a null character into the output sequence.
   *
   *  <em>Null character</em> is @c CharT() by definition.  For CharT
   *  of @c char, this correctly writes the ASCII @c NUL character
   *  string terminator.
  */
  template<typename _CharT, typename _Traits>
    inline basic_ostream<_CharT, _Traits>&
    ends(basic_ostream<_CharT, _Traits>& __os)
    { return __os.put(_CharT()); }
 
  /**
   *  @brief  Flushes the output stream.
   *
   *  This manipulator simply calls the stream's @c flush() member function.
  */
  template<typename _CharT, typename _Traits>
    inline basic_ostream<_CharT, _Traits>&
    flush(basic_ostream<_CharT, _Traits>& __os)
    { return __os.flush(); }
 
#if __cplusplus >= 201103L
  template<typename _Ch, typename _Up>
    basic_ostream<_Ch, _Up>&
    __is_convertible_to_basic_ostream_test(basic_ostream<_Ch, _Up>*);
 
  template<typename _Tp, typename = void>
    struct __is_convertible_to_basic_ostream_impl
    {
      using __ostream_type = void;
    };
 
  template<typename _Tp>
    using __do_is_convertible_to_basic_ostream_impl =
    decltype(__is_convertible_to_basic_ostream_test
	     (declval<typename remove_reference<_Tp>::type*>()));
 
  template<typename _Tp>
    struct __is_convertible_to_basic_ostream_impl
    <_Tp,
     __void_t<__do_is_convertible_to_basic_ostream_impl<_Tp>>>
    {
      using __ostream_type =
	__do_is_convertible_to_basic_ostream_impl<_Tp>;
    };
 
  template<typename _Tp>
    struct __is_convertible_to_basic_ostream
    : __is_convertible_to_basic_ostream_impl<_Tp>
    {
    public:
      using type = __not_<is_void<
        typename __is_convertible_to_basic_ostream_impl<_Tp>::__ostream_type>>;
      constexpr static bool value = type::value;
    };
 
  template<typename _Ostream, typename _Tp, typename = void>
    struct __is_insertable : false_type {};
 
  template<typename _Ostream, typename _Tp>
    struct __is_insertable<_Ostream, _Tp,
			   __void_t<decltype(declval<_Ostream&>()
					     << declval<const _Tp&>())>>
				    : true_type {};
 
  template<typename _Ostream>
    using __rvalue_ostream_type =
      typename __is_convertible_to_basic_ostream<
	_Ostream>::__ostream_type;
 
  /**
   *  @brief  Generic inserter for rvalue stream
   *  @param  __os  An input stream.
   *  @param  __x  A reference to the object being inserted.
   *  @return  os
   *
   *  This is just a forwarding function to allow insertion to
   *  rvalue streams since they won't bind to the inserter functions
   *  that take an lvalue reference.
  */
  template<typename _Ostream, typename _Tp>
    inline
    typename enable_if<__and_<__not_<is_lvalue_reference<_Ostream>>,
			      __is_convertible_to_basic_ostream<_Ostream>,
			      __is_insertable<
				__rvalue_ostream_type<_Ostream>,
				const _Tp&>>::value,
		       __rvalue_ostream_type<_Ostream>>::type
    operator<<(_Ostream&& __os, const _Tp& __x)
    {
      __rvalue_ostream_type<_Ostream> __ret_os = __os;
      __ret_os << __x;
      return __ret_os;
    }
#endif // C++11
 
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std
 
#include <bits/ostream.tcc>
 
#endif	/* _GLIBCXX_OSTREAM */

istream

istream 是 C++ 标准库中的窄字节输入库,主要用于处理输入操作。由于代码较长且未在原文中提供,此处略去详细代码展示。

fstream

fstream 库是 C++ 标准库中用于文件操作的头文件,提供了文件输入输出流的功能,支持对文件的读写操作。

ios

ios 库是 C++ 标准库中用于管理输入输出流的基础库,类似于 Java 中的 iostream 库。它为输入输出操作提供了底层支持,并定义了流的状态标志、格式化标志等。需要注意的是,ios 并非用于 iOS 开发的库,而是 C++ 标准库的一部分。

本文完。


C++ 流(stream)总结

浩世轩宇 于 2014-11-10 17:22:27 发布

一、C++ 中流的概念

在程序设计中,数据输入/输出(I/O)操作是必不可少的,C++语言的数据输入/输出操作是通过 I/O 流库来实现的。C++ 中把数据之间的传输操作称为流,流既可以表示数据从内存传送到某个载体或设备中(即输出流),也可以表示数据从某个载体或设备传送到内存缓冲区变量中(即输入流)。

C++ 流涉及以下概念:

  • 标准 I/O 流:内存与标准输入输出设备之间信息的传递;
  • 文件 I/O 流:内存与外部文件之间信息的传递;
  • 字符串 I/O 流:内存变量与表示字符串流的字符数组之间信息的传递。

STL 中定义的流类

流类分类流类名称流类作用
流基类ios所有流类的父类,保存流的状态并处理错误
输入流类istream输入流基类,将流缓冲区中的数据作格式化和非格式化之间的转换并输入
ifstream文件输入流类
istream_withassigncin 输入流类,即操作符 >> 输入流
istrstream串输入流类,基于 C 类型字符串 char* 编写
istringstream串输入流类,基于 std::string 编写
输出流类ostream输出流基类,将流缓冲区中的数据作格式化和非格式化之间的转换并输出
ofstream文件输出流类
ostream_withassigncoutcerrclog 的输出流类,即操作符 << 输出流
ostrstream串输入流类,基于 C 类型字符串 char* 编写
ostringstream串输入流类,基于 std::string 编写
输入/输出流类iostream多目的输入/输出流类的基类
fstream文件流输入/输出类
strstream串流输入/输出类,基于 C 类型字符串 char* 编写
stringstream串流输入/输出类,基于 std::string 编写

:对于串流,提供了两套类,一个基于 C 类型字符串 char* 编写(定义于头文件 <strstream>),一个基于 std::string 编写(定义于 <sstream>),后者是 C++ 标准委员会推荐使用的。

二、C++ 中读取 string 对象

  1. 标准输入读取cin >> string

    • 忽略开头所有的空白字符(空格、换行、制表符等);
    • 读取字符直至再次遇到空白字符,读取终止。
  2. 读取整行文本getline(istream, string)

    • 不忽略开头的空白字符;
    • 读取字符直至遇到换行符,如果第一个字符是换行符,则返回空 string
    • 返回时丢弃换行符,换行符不存储在 string 中。

三、对 sstream(经常用作格式转换)库的讲解

使用 stringstream 对象简化类型转换

如果你已习惯了 <stdio.h> 风格的转换,也许你首先会问:为什么要花额外的精力来学习基于 <sstream> 的类型转换呢?也许对下面一个简单的例子的回顾能够说服你。假设你想用 sprintf() 函数将一个变量从 int 类型转换到字符串类型。为了正确地完成这个任务,你必须确保证目标缓冲区有足够大空间以容纳转换完的字符串。此外,还必须使用正确的格式化符。如果使用了不正确的格式化符,会导致非预知的后果。下面是一个例子:

int n = 10000;
char s[10];
sprintf(s, "%d", n); // s 中的内容为 "10000"

到目前为止看起来还不错。但是,对上面代码的一个微小的改变就会使程序崩溃:

int n = 10000;
char s[10];
sprintf(s, "%f", n); // 看!错误的格式化符

在这种情况下,程序员错误地使用了 %f 格式化符来替代了 %d。因此,s 在调用完 sprintf() 后包含了一个不确定的字符串。要是能自动推导出正确的类型,那不是更好吗?

进入 stringstream

由于 ns 的类型在编译期就确定了,所以编译器拥有足够的信息来判断需要哪些转换。<sstream> 库中声明的标准类就利用了这一点,自动选择所必需的转换。而且,转换结果保存在 stringstream 对象的内部缓冲中。你不必担心缓冲区溢出,因为这些对象会根据需要自动分配存储空间。

注意<sstream> 使用 string 对象来代替字符数组。这样可以避免缓冲区溢出的危险。而且,传入参数和目标对象的类型被自动推导出来,即使使用了不正确的格式化符也没有危险。

string 到 int 的转换

string result = "10000";
int n = 0;
stringstream stream;
stream << result;
stream >> n; // n 等于 10000

重复利用 stringstream 对象

如果你打算在多次转换中使用同一个 stringstream 对象,记住在每次转换前要使用 clear() 方法。在多次转换中重复使用同一个 stringstream(而不是每次都创建一个新的对象)对象最大的好处在于效率。stringstream 对象的构造和析构函数通常是非常耗费 CPU 时间的。

在类型转换中使用模板

你可以轻松地定义函数模板来将一个任意的类型转换到特定的目标类型。例如,需要将各种数字值,如 intlongdouble 等等转换成字符串,要使用以一个 string 类型和一个任意值 t 为参数的 to_string() 函数。to_string() 函数将 t 转换为字符串并写入 result 中。使用 str() 成员函数来获取流内部缓冲的一份拷贝:

template <class T>
void to_string(string &result, const T &t) {
    ostringstream oss; // 创建一个流
    oss << t;          // 把值传递入流中
    result = oss.str(); // 获取转换后的字符串并将其写入 result
}

这样,你就可以轻松地将多种数值转换成字符串了:

to_string(s1, 10.5); // double 到 string
to_string(s2, 123);  // int 到 string
to_string(s3, true); // bool 到 string

可以更进一步定义一个通用的转换模板,用于任意类型之间的转换。函数模板 convert() 含有两个模板参数 out_typein_value,功能是将 in_value 值转换成 out_type 类型:

template <class out_type, class in_value>
out_type convert(const in_value &t) {
    stringstream stream;
    stream << t; // 向流中传值
    out_type result; // 这里存储转换结果
    stream >> result; // 向 result 中写入值
    return result;
}

这样使用 convert()

double d;
string salary;
string s = "12.56";
d = convert<double>(s); // d 等于 12.56
salary = convert<string>(9000.0); // salary 等于 "9000"

stringstream 通常是用来做数据转换的。相比 C 库的转换,它更加安全、自动和直接。

四、STL 中 stringifstreamstringstream 的使用

1. 从标准输入接受字符串,然后进行相关处理

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

int main() {
    string s; // 定义一个 string 对象,从标准输入接受一个字符串
    cout << "请输入一行字符串:" << endl;
    getline(cin, s);
    stringstream ss(s); // 定义一个 string 流(使用 s 实例化)
    cout << "处理后的字符串为:" << endl; // 将 string 流里的东西输出
    for (string s1; ss >> s1; cout << s1 << endl);
    return 0;
}

运行结果如下:

请输入一行字符串:
you are a good boy
处理后的字符串为:
you
are
a
good
boy

根据前文所说“忽略开头空白字符,读取字符直至再次遇到空白字符为止”,这样的结果不难理解。

2、getline 函数原型

template <class E, class T, class A> basic_istream<E, T>& getline(
    basic_istream<E, T>& is, basic_string<E, T, A>& str);
template <class E, class T, class A> basic_istream<E, T>& getline(
    basic_istream<E, T>& is, basic_string<E, T, A>& str, E delim);

第二个重载函数很有意思,结尾是 charwchar 型的一个分隔符。如果设为 '\n',则为以换行符为分隔;如果设为 ',',则为以逗号为分隔。由此,虽然 C++ 中的字符串没有分割函数,如果是从文件中读取出被特定分隔符分隔的文本,那么就可以用此方法,如:

std::ifstream file;
file.open("tt.txt");
std::string s, t;
while (std::getline(file, s)) // 按行读取
{
    std::stringstream strs(s); // 把行装到另一个流中
    while (std::getline(strs, t, ',')) // 把流按分隔符实现分割
        std::cout << t << std::endl;
}
file.close();

上面的程序相当于将整个文本先按行分割,再按分隔符分割,也可以变换一下,只按分隔符分割,然后过滤掉按行符,换行符与某元素连在了一起,并处于开头。

五、C++ 的流操作复制文件

.wrl.obj 文件格式转换时用到,记录一下相关方法。

使用 C++ 标准程序库的输入输出流(I/O Stream)复制文件,存在许多的方法。

方法一:逐个字符复制

#include <fstream>
std::ifstream input("in", ios::binary);
std::ofstream output("out", ios::binary);
char ch;
while (input.get(ch)) output << ch;

注意:如果使用 input >> ch 读取字符,则必须先调用 input.unsetf(ios::skipws) 取消输入流默认的跳过空白符(空格、换行、制表符等)的输入格式,因为换行符是空白符的一种。另外,对于 ofstream 对象,默认的操作方式是 ios_base::out | ios_base::trunc,即输出和文件清空!

方法二:逐行复制

#include <fstream>
#include <string>
std::ifstream input("in", ios::binary);
std::ofstream output("out", ios::binary);
std::string line;
while (getline(input, line)) output << line << "\n";

注意:这里的代码有一个小小的缺陷,如果文件不是纯文本格式的文件,或者文本文件的最后没有换行符,那么会导致复制后的文件末尾添加了一个多余的换行符。

方法三:迭代器复制

#include <fstream>
#include <iterator>
#include <algorithm>
std::ifstream input("in", ios::binary);
std::ofstream output("out", ios::binary);
input.unsetf(ios::skipws);
copy(istream_iterator<char>(input), istream_iterator<char>(), ostream_iterator<char>(output, ""));

同样这里也有一个小技巧,输入流的格式默认为跳过空白字符,因此调用 unsetf 取消这个格式,才可保证正确的复制。

方法四:缓冲区复制

#include <fstream>
std::ifstream input("in", ios::binary);
std::ofstream output("out", ios::binary);
output << input.rdbuf();

这里直接使用了输入流的缓冲区,因此没有引入额外的临时对象。

很显然,上述四种方法中,最后一种方法最简洁,由于直接操作输入流的缓冲区,从运行效率上来说,也比其他方法有着略微的优势(当然,由于操作系统可能提供了额外的基于设备的文件缓冲机制,也许你无法证实这一点)。因此,除非要对输入内容进行处理,直接复制文件推荐最后一种方法,既不容易出错,又能获得良好的性能。

另外,对文件进行更改、删除、插入等操作,可以直接用以上方法也可以先把文件读入 vector<string>,处理后再输出,不过当文件很大的时候,vector 占用内存空间较大,而且输出时会破坏原文件格式,尽量不使用。

以上 是几种同种流(文件流)之间的数据复制的方式,字符串流与文件流之间也可以以此方式进行复制。另外,再看一下 get 函数:

int_type get();
basic_istream& get(E& c);
basic_istream& get(E *s, streamsize n);
basic_istream& get(E *s, streamsize n, E delim);
basic_istream& get(basic_streambuf<E, T> *sb);
basic_istream& get(basic_streambuf<E, T> *sb, E delim);

delim 表示结束符,前文中讨论的流分割函数还记得吧?又多了种方法,估计 get 与全局函数 getline(不是那个成员函数,成员函数要求给出 streamsize,而全局的就不用)实现得差不多。默认的也是 '\n',按行分隔。

六、C++ 流缓冲区的应用——输出文件内容的方法举例

简单讨论 C++ 流对象的底层缓冲区,并举例介绍如何使用该缓冲区进行文件内容的输出。

C++ 标准库封装了一个缓冲区类 streambuf,以供输入输出流对象使用。每个标准 C++ 输出输出流对象都包含一个指向 streambuf 的指针,用户可以通过调用 rdbuf() 成员函数获得该指针,从而直接访问底层 streambuf 对象。因此,可以直接对底层缓冲区进行数据读写,从而跳过上层的格式化输入输出操作。

template <class Elem, class Traits> class basic_ios : public ios_base {
    basic_streambuf<_Elem, _Traits>* _Mystrbuf;
    // C++ 标准库封装了一个缓冲区类 streambuf。
    _Mysb* rdbuf() const { // return stream buffer pointer
        return (_Mystrbuf);
    }
    // 使调用者与参数(流缓冲指针)关联,
    // 返回自己当前关联的流缓冲区指针,可用来重定向等
    _Mysb* rdbuf(_Mysb* _Strbuf) { // set stream buffer pointer
        _Mysb* _Oldstrbuf = _Mystrbuf;
        _Mystrbuf = _Strbuf;
        return (_Oldstrbuf);
    }
};

对象通过调用 rdbuf() 获得了底层 streambuf 对象的指针,也就可以通过该指针调用 streambuf 支持你各种操作进行输入输出。在这里主要介绍如何利用该指针实现文件内容的输出。

对于文件流类和字符串流类,分别派生了相应的流缓冲区类型:

template <class _Elem, class _Traits> class basic_streambuf;
typedef basic_streambuf<char, char_traits<char>> streambuf;
typedef basic_streambuf<wchar_t, char_traits<wchar_t>> wstreambuf;

template <class Elem, class Tr = char_traits<Elem>, class Alloc = allocator<Elem>>
class basic_stringbuf : public basic_streambuf<Elem, Tr>;
typedef basic_stringbuf<char, char_traits<char>, allocator<char>> stringbuf;
typedef basic_stringbuf<wchar_t, char_traits<wchar_t>, allocator<wchar_t>> wstringbuf;

template <class Elem, class Tr = char_traits<Elem>> class basic_filebuf : public basic_streambuf<Elem, Tr>;
typedef basic_filebuf<char, char_traits<char>> filebuf;
typedef basic_filebuf<wchar_t, char_traits<wchar_t>> wfilebuf;

输出流提供了一个重载版本 operator<<,它以 streambuf 指针为参数,实现把 streambuf 对象中的所有字符输出到输出流出中;输入流也提供了一个对应的 operator>> 重载版本,把输入流对象中的所有字符输入到 streambuf 对象中。输入流的 get 成员重载版本中还有以 streambuf 指针为参数的版本,也可以用来把输入流的东西写入到输出流缓冲区中。

下面用三种方法实现把一个文件的内容输出标准输出(当然还可以通过普通的标准格式化输入输出完成):

方法一:通过 operator<<

#include <iostream>
#include <fstream>
using namespace std;

int main() {
    ifstream fin("source.dat");
    cout << fin.rdbuf();
    return 0;
}

方法二:利用 get 成员函数

ifstream fin("source.dat");
while (!fin.get(*cout.rdbuf()).eof()) { // extract a line input
    if (fin.fail()) // blank line
        fin.clear();
    cout << char(fin.get()); // extract '\n'
}

代码解释:由于上面代码中的 get 版本在遇到 '\n' 字符时,结束提取,所以需要用循环实现整个文件内容的输出。另外,当此版本 get 函数遇到空行时,因为没有提取到任何字符(注意:get 不提取回车符),注意会设置失败标志 ios::failbit,所以此时应当调用 clear() 函数清除错误标志。同样,因为该版本 get 不会提取回车符,所以需要用另一版本的 get() 提取回车符。不同版本的 Get 函数参见前文。此处还使用了 *cout.rdbuf()coutostream 类的对象,当然就有缓冲区,可以用 rdbuf 返回缓冲区对象的指针。最后,关于 fin.clear,需要特别注意的是:要清空流类对象的内存,不能用 clear 方法,那只是设置了错误标志位。

方法三:利用重载的 get 函数

ifstream fin("main.cpp");
fin.get(*cout.rdbuf(), EOF);

代码解释:这个版本的 get 成员函数可以自定义提取终止符。这里通过设置为文件结束符(EOF)来达到一下提取整个文件的目的。

当然,你可以把上面的 cout 换成任意的输出流,比如文件输出流,从而可以实现文件的拷贝功能。

另外,上面代码中并没有使用输入流的 >> 操作符,因为 >><< 是相对的,只是把操作数交换一下位置罢了。因此,你可以把上面代码中用 out << in.rdbuf() 的地方换成 in >> out.rdbuf(),代码的功能完全一样,效果也一样。

七、关于缓冲区

1. 缓冲类型

标准库提供缓冲是为了减少对 readwrite 的调用。提供的缓冲有三种类型(整理自 APUE):

  • 全缓冲:在这种情况下,实际的 I/O 操作只有在缓冲区被填满了之后才会进行。对驻留在磁盘上的文件的操作一般是有标准 I/O 库提供全缓冲。缓冲区一般是在第一次对流进行 I/O 操作时,由标准 I/O 函数调用 malloc 函数分配得到的。flush 描述了标准 I/O 缓冲的写操作。缓冲区可以由标准 I/O 函数自动 flush(例如缓冲区满的时候);或者我们对流调用 fflush 函数。

  • 行缓冲:在这种情况下,只有在输入/输出中遇到换行符的时候,才会执行实际的 I/O 操作。这允许我们一次写一个字符,但是只有在写完一行之后才做 I/O 操作。一般的,涉及到终端的流——例如标准输入(stdin)和标准输出(stdout)——是行缓冲的。

  • 无缓冲:标准 I/O 库不缓存字符。需要注意的是,标准库不缓存并不意味着操作系统或者设备驱动不缓存。

ISO C 要求:

  • 当且仅当不涉及交互设备时,标准输入和标准输出是全缓存的。
  • 标准错误绝对不是全缓存的。

但是,这并没有告诉我们当标准输入/输出在涉及交互设备时,它们是无缓存的还是行缓存的;也没有告诉我们标准错误应该是行缓存的还是无缓存的。不过,大多数实现默认的缓存类型是这样的:

  • 标准错误总是无缓存的。
  • 对于所有的其他流来说,如果它们涉及到交互设备,那么就是行缓存的;否则是全缓存的。

2. 改变默认缓存类型,即自定义缓冲区

a. 对于 C 中文件操作

可以通过下面的函数改变缓存类型(摘自 APUE):

void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);

这些函数必须在流打开之后、但是未对流做任何操作之前被调用(因为每个函数都需要一个有效的文件指针作为第一个参数)。

利用 setbuf,可以打开或者关闭缓存。为了打开缓存,buf 参数必须一个大小为 BUFSIZ 的缓存,BUFSIZ 是定义在 stdio.h 中的常量。<ISO/IEC 9899> 要求:BUFSIZ 至少为 256。如果要关闭缓存,可以将 buf 设成 NULL

利用 setvbuf,我们可以设定缓存类型。这是通过 mode 参数指定的。

b. 对于 C++ 中流
virtual basic_streambuf* setbuf(E *s, streamsize n);

第一个参数指向自定义缓冲区空间,第二个为缓冲区大小。

八、stringstringBuffer 的区别

String 对象建立之后不能再改变,如果经常对字符串进行各种各样的修改,那么使用 String 来代表字符串的话会引起很大的内存开销。StringBuffer 允许修改,不是每个不同的字符串都要生成一个新的对象,两种类的对象转换十分容易。

在我以前的了解中,String 是一个 final ClassStringBuffer 不是。所以对于 String a = "yacht"String b = "yacht1"String c = a + b;存在一个对象拷贝构造和解析的消耗问题;对于一个 StringBuffer 来说,StringBuffer sb = new StringBuffer()sb.append("yacht")sb.append("yacht1");因为 StringBuffer 是一个可以实例化的类,而且它的内建机制是维护了一个 capacity 大小的字符数组,所以它的 append 操作不存在对象的消耗问题,所以我觉得如果存在 String 连接这种事情,StringBuffer 来做会好很多。

但事情并不是这么简单,看下面代码:

String a = "yacht1" + "yacht2" + "yacht3" + "yacht4";
StringBuffer sb = new StringBuffer();
sb.append("yacht1");
sb.append("yacht2");
sb.append("yacht3");
sb.append("yacht4");
String a = sb.toString();

如果按照我先前说的看法,红色的效率肯定比蓝色的低,但经过测试不是这样,为什么?这里,我们需要理解程序过程的两个时期,一个是编译时,一个是运行时,在编译时,编译器会对你的程序做出优化,所以红色的 String a 会被优化成 yacht1yacht2yacht3yacht4,而蓝色的 StringBuffer 只会在运行时才处理。所以效率是不一样的。

如果代码是这样的:

String a;
for (int i = 0; i < 100000; i++) {
    a += String.valueOf(i);
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 100000; i++) {
    sb.append(i);
}
String a = sb.toString();

如果是这种情况的话,红色的效率就大大不如蓝色,区别在哪里,就在于运行时和编译时的优化问题上!

前面看到有人写 StringstringBuffer 的区别是前者是不能改写的,后者是可以改写的。

我觉得说 String 的字符串不能改变话是不错,但是例子要举好。

看看下面这个简单的例子:

public class xx {
    public static void main(String[] args) {
        String s1 = "You are hired!";
        String s2 = "You are hired!";
        if (s1 == s2) {
            System.out.println("一个内存空间");
        } else {
            System.out.println("不是一个内存空间");
        }
    }
}

打印的结果是:一个内存空间

这里 == 的意义是两个操作数是否指向同一个对象。

可见 s2 在不用 new 创建的情况下会自动检索到具有相同内容的内存空间中共享,那么既然 s1s2 共享了同一个对象。

再看下面的代码:

public class xx {
    public static void main(String[] args) {
        String s1 = "You are hired!";
        String s2 = "You are hired!";
        s1 = s1.replace('h', 'f');
        System.out.println(s1);
        if (s1 == s2) {
            System.out.println("一个内存空间");
        } else {
            System.out.println("不是一个内存空间");
        }
    }
}

代码结果是:

You are fired!
不是一个内存空间

可见,Strings1 的内容虽然被改写,但是已经不在是原来第一次分配到的那个内存空间,也就是 String 类的内容能被改变,但一旦改变系统将为其分配新的内存。

说到与 stringBuffer 的区别,从根本上来说应该是:

stringBuffer 在做字符长度变动的时候将继续使用原来的内存空间,不新分配。

String 的长度一旦变动,就如上面的例子一样,其内部将分配新的内存空间。

九、streambuf 相关的讲解

顺序访问流中的字符。如果用一个数组模拟出流,代码将是下面样子的。

char sgetc() { return buff[i]; }
char sbumpc() { return buff[i++]; } // 先取当前字符,然后指针移向后一个
char snextc() { return buff[++i]; } // 先移动指针,然后取值

逐个字符的打印出 abcdefg

stringstream ss;
ss << "abcdefg";
streambuf* pbuf = ss.rdbuf();
do {
    char ch = pbuf->sgetc();
    cout << ch;
} while (pbuf->snextc() != EOF);

验证缓冲区在起作用,pbuf->in_avail() 返回的值在变化:

fstream fs;
fs.open("C:\\test.log");
streambuf* pbuf = fs.rdbuf();
while (pbuf->sgetc() != EOF) {
    char ch = pbuf->sbumpc(); // 弹出一个字符
    cout << ch;
    streamsize size = pbuf->in_avail(); // 查询缓冲区内有效数据个数(把缓存区看做一个容器)
}

实现文件大小的查询(C 函数 fseekftell):

fstream filestr("C:\\test.log");
streambuf* pbuf = filestr.rdbuf();
size = pbuf->pubseekoff(0, ios_base::end);

设置程序员选择的任意缓冲区(C 函数 setvbuf):

fstream ss("C:\\test.log");
streambuf* pbuf = ss.rdbuf();
char mybuffer[2048] = {0};
pbuf->pubsetbuf(mybuffer, 2048);

读取出来的一个字符,可以做退回操作:

ch = pbuf->sbumpc();
int r = pbuf->sputbackc(ch);
ch = pbuf->sgetc();

十、C++ 中控制文件读写位置

C++ 输出控制

如果要在输出流中加入格式控制符则要加载头文件:#include <iomanip>。这里面 iomanip 的作用比较多,主要是对 cincout 之类的一些操纵运算子,比如 setfillsetwsetbasesetprecision 等等。它是 I/O 流控制头文件,就像 C 里面的格式化输出一样。以下是一些常见的控制函数:

  • dec:置基数为 10,相当于 "%d"
  • hex:置基数为 16,相当于 "%X"
  • oct:置基数为 8,相当于 "%o"

示例:

cout << 12 << hex << 12 << oct << 12 << 12; // output 12c1414
  • setprecision(n):设显示小数精度为 n 位。

示例:

setf(ios::fixed);
cout << setprecision(2) << 2.345 << endl; // output 2.34
  • setw(n):设域宽为 n 个字符。这个控制符的意思是保证输出宽度为 n。如:
cout << setw(3) << 1 << setw(3) << 10 << setw(3) << 100; // 输出结果为 1 10100

当输出长度大于 3 时(<<1000),setw(3) 不起作用。

  • setfill(c):设填充字符为 c

  • setiosflags(ios::fixed):固定的浮点显示。

  • setiosflags(ios::scientific):指数表示。

示例:

cout << setiosflags(ios::fixed) << setprecision(2) << 2.345 << endl; // output 2.34
  • setiosflags(ios::left):左对齐。
  • setiosflags(ios::right):右对齐。
  • setiosflags(ios::skipws):忽略前导空白。
  • setiosflags(ios::uppercase):16 进制数大写输出。
  • setiosflags(ios::lowercase):16 进制小写输出。
  • setiosflags(ios::showpoint):强制显示小数点。
  • setiosflags(ios::showpos):强制显示符号。

示例:

cout << setiosflags(ios::uppercase) << hex << 12 << 15 << endl; // output CF
cout << setiosflags(ios::showpoint) << x << endl; // 若 float x = 1, 则 output 1.000000
cout << setiosflags(ios::showpos) << 1 << endl; // output +1

其他流函数

  • bits:指定的格式标志位,返回旧的格式标志。
  • long fill(char c):设置填充字符,缺省条件下是空格。
  • char fill():返回当前填充字符。
  • int precision(int val):设置精确度为 val,控制输出浮点数的有效位,返回旧值。
  • int precision():返回旧的精确度值。
  • int width(int val):设置显示数据的宽度(域宽),返回旧的域宽。
  • int width():只返回当前域宽,缺省宽度为 0。这时插入操作能按表示数据的最小宽度显示数据。

预定义的操纵算子

使用成员函数控制格式化输入输出时,每个函数调用需要写一条语句,尤其是它不能用在插入或提取运算符的表达式中,而使用操纵算子,则可以在插入和提取运算符的表达式中控制格式化输入和输出。在程序中使用操纵算子必须嵌入头文件 <iomanip.h>

  • dec:十进制的输入输出。
  • hex:十六进制的输入输出。
  • oct:八进制的输入输出。
  • ws:提取空白字符。
  • ends:输出一个 nul 字符。
  • endl:输出一个换行字符,同时刷新流。
  • flush:刷新流。
  • resetiosflags(long):清除特定的格式标志位。
  • setiosflags(long):设置特定的格式标志位。
  • setfill(char):设置填充字符。
  • setprecision(int):设置输出浮点数的精确度。
  • setw(int):设置域宽格式变量。

错误处理

在对一个流对象进行 I/O 操作时,可能会产生错误。当错误发生时,错误的性质被记录在 ios 类的一个数据成员中。ios 类中定义的描述错误状态的常量:

  • goodbit:没有错误,正常状态。
  • eofbit:到达流的结尾。
  • failbit:I/O 操作失败,清除状态字后,可以对流继续进行操作。
  • badbit:试图进行非法操作,清除状态字后,流可能还可以使用。
  • hardfail:致命错误,不可恢复的错误。

ostream 类的成员函数

  • ostream& put(char ch):向流中输出一个字符 ch,不进行任何转换。
  • ostream& write(char*, int):向流中输出指定长度的字符串,不进行转换。
  • ostream& flush():刷新流,输出所有缓冲的但还未输出的数据。
  • ostream& seekp(streampos):移动流的当前指针到给定的绝对位置。
  • ostream& seekp(streamoff, seek_dir):流的当前指针类似与文件的当前指针。
  • streampos tellp():返回流的当前指针的绝对位置。

istream 类的成员函数

  • int get():读取并返回一个字符。
  • istream& get(char& c):读取字符并存入 c 中。
  • istream& get(char* ptr, int len, char delim = ''):读取指定的字符到缓冲区中,直到遇到指定的分界符为止,分界符不填入缓冲区。
  • istream& getline(char* ptr, int len, char delim = ''):与 get(char*, int, char) 类似,但将分界符填入缓冲区。
  • istream& putback():将最近读取的字符放回流中。
  • istream& read(char*, int):读取规定长度的字符串到缓冲区中。
  • int gcount():返回读取的字节数。
  • int peek():返回流中下一个字符,但不移动文件指针。
  • istream& seekg(streampos):移动当前指针到一绝对地址。
  • istream& seekg(streampos, seek_dir):移动当前指针到一相对地址。
  • streampos tellg():返回当前指针。
  • istream& ignore(int n = 1, delim = EOF):跳过流中几个字符,或直到遇到指定的分界符为止。

其他函数

  • getch():函数用于从键盘输入一个字符,不回显,包含头文件 <conio.h> 中。

via :

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值