跳至正文
wyaaaattwhoAI RESEARCH & ENGINEERING
← 返回首页

I/O

输入/输出(I/O)概述

  • 输入/输出(简称I/O)是程序的一个重要组成部分:
    • 程序运行所需要的数据往往要从外设(如:键盘、磁盘等)得到 程序的运行结果通常也要输出到外设(如:显示器、打印机、磁盘等)中去。
  • 在C++中,输入/输出操作不是语言定义的成分,而是由具体的实现作为标准库的功能来提供。
  • 在C++中,输入/输出是一种基于字节流的操作
  • 在C++的标准库中,提供了:
    • 基于字节的I/O操作 基于C++基本数据类型数据的I/O操作(在这些操作的内部实现了基本数据类型与字节流之间的转换)
  • 输入/输出操作往往是带内存缓冲区的:
    • 输出时,输出的数据先放缓冲区中,缓冲区满了之后再实际输出到外设。 输入时,先从外设输入一大块内容放入缓冲区,然后从缓冲区中逐步输入数据。

C++输入输出的实现途径

  • 过程式——通过从C语言保留下来的函数库中的输入/输出函数来实现。例如: 输出:printf、...... 输入:scanf、......
  • 面向对象——通过C++的I/O类库中的类/对象以及重载的操作符“<<”和“>>”来实现。例如: 输出:ostream、cout、<< ...... 输入:istream、cin、>> ......

printf、scanf的缺陷

  • printf和scanf是两个带可变参数的函数,它们的第一个参数为一个格式串,指出后面要输入/输出的数据的类型和个数(格式串的说明参见教材<表8-1>)。例如:
    • 不是强类型:参数个数和类型不固定,编译时刻无法进行参数类型检查,会导致与类型相关的运行错误!(类型不安全)
    • 编译时刻一般不对格式串中指定的数据类型与所提供的数据的实际类型之间是否一致进行的检查(vs2015开始检查了),当格式串描述与数据不一致时会导致运行时刻的错误。例如:

      cout、cin的优势

      • 不需要专门指定数据的类型和个数,编译时刻根据数据本身来决定数据的类型和个数,这样可避免与类型和个数相关的错误!例如:

        I/O的分类

        • 面向控制台的I/O 从标准输入设备(如:键盘)获得数据 把程序结果从标准输出设备(如:显示器)输出
        • 面向文件的I/O 从外存(磁盘)获得数据 把程序结果保存到外存(磁盘)中
        • 面向字符串变量的I/O 从程序中的字符串变量中获得数据 把程序结果保存到字符串变量中

        C++的I/O类库中基本的类

        • C++标准类库提供了以下的类来实现各种输入/输出功能,其中,面向字符串变量的I/O现在已被标准库中的string所替代。
          • notion image
        • 在进行输入/输出时,首先创建某个I/O类的一个对象,然后,调用该对象类的成员函数进行基于字节流的输入/输出操作。例如
          • istream类和ostream类以及它们的派生类还分别重载了操作符“>>”(抽取)和“<<”(插入),用它们可以进行C++基本类型数据的输入/输出操作。
            • 用操作符>>进行输入时,它把输入看成是一个字符串,会自动进行从字符串到基本数据类型的转换。例如:
              • 用操作符<<进行输出时,它会把基本数据类型的数据转换成字符串输出。例如:
                • 注意:如果上述的x和y是char或char类型的数组,则输入/输出过程中不进行转换,直接进行输入/输出!

              面向控制台的I/O

              • 在I/O类库中预定义了四个I/O对象,可以直接用来进行控制台的输入/输出操作:
                • cin(istream类的对象):对应着计算机系统的标准输入设备。(通常为键盘)
                • cout(ostream类的对象):对应着计算机系统的标准输出设备。(通常为显示器)
                • cerr和clog(ostream类的对象):对应着计算机系统用于输出特殊信息(如程序错误信息)的设备。(通常也对应着显示器,但不受输出重定向的影响)。cerr为不带缓冲的,clog为带缓冲的。
              • 在进行控制台输入/输出时,程序中需要有下面的包含命令: #include <iostream>

              控制台输出操作

              • 用插入操作符(<<)进行基本数据类型数据的输出。
                • 特殊情况:输出指向字符的指针时,并不是输出指针的值,而是输出它指向的字符串。

                  输出格式控制

                  • 为了对输出格式进行控制,可以通过输出一些操纵符(manipulator)来实现。
                    • 常用操作符
                      notion image
                      对于浮点数(float、double和long double)
                      当输出格式为ios::scientific或ios::fixed时,操纵符setprecision用于设置浮点数小数点后面的位数;当输出格式为自动方式(既不是ios::scientific也不是ios::fixed,或两者都设了)时,操纵符setprecision用于设置浮点数有效数字的个数,这时的输出格式根据有效数字自动确定。
                  • 除了用操作符<<进行基本数据类型的输出外,也可以用ostream类的成员函数进行基于字节的控制台输出操作。

                    控制台输入操作

                    • 用抽取操作符(>>)进行基本数据类型数据的控制台输入。
                      • 用抽取操作符>>进行输入时,各个数据之间一般要用空白符(空格、\t、\n)分开
                        • 输入一个数据前,先跳过空白符
                        • 输入一个数据的过程中,碰到空白符或当前数据类型不允许的字符时结束当前数据的输入
                      • 可以通过一些操纵符来控制输入的行为,例如
                        • 除了用操作符>>进行基本数据类型的输入外,也可以用istream类的成员函数进行基于字节的控制台输入操作。
                          • 🚨
                            注意:用istream类的成员函数进行输入时,空白符也会作为字符输入
                        • 另外,可以用下面的成员函数跳过输入缓存中的若干字符
                          • 注意:用getline输入一行时,当输入串的长度超出指定大小,这会导致cin进入错误状态,后续输入不能正常进行,这时,可采用下面方法处理

                            操作符“>>”和“<<”的重载

                            • 标准库中重载的操作符“>>”和“<<”只能对基本数据类型的数据进行输入/输出。
                            • 可以针对自定义的类进一步重载操作符“>>”和“<<”,从而能用它们对某个类的对象进行输入/输出操作。
                            • 用操作符“<<”实现复数的输出

                              面向文件的I/O

                              文件的位置指针

                              • 在C++中,把文件看成是由一系列字节所构成的字节串,对文件中数据的操作(输入/输出)通常是逐个字节顺序进行,因此称为流式文件。
                              • 每个打开的文件都有一个内部(隐藏)的位置指针,它指出文件的当前读写位置。
                              • 进行读/写操作时,每读入/写出一个字节,文件位置指针会自动往后移动一个字节的位置。
                                • notion image
                              • 在利用I/O类库中的类进行文件的输入/输出时,程序中需要包含下面的头文件:
                                • #include <iostream>
                                  #include <fstream>

                              文件的输出操作

                              • 打开文件:创建ofstream类的一个对象,并建立它与外部某个文件之间的联系。
                                • 直接方式
                                  • 创建对象时建立与外部文件的联系 ofstream out_file(<文件名> [,<打开方式>]);
                                  • 例如,下面创建的对象out_file对应文件myfile.txt: ofstream out_file("d:\\myfile.txt",ios::out);
                                • 间接方式
                                  • 先用默认构造创建一个对象,然后用open建立与外部文件的联系 ofstream::open(<文件名> [,<打开方式>]);
                                  • 例如,让对象out_file对应文件myfile.txt: ofstream out_file; //用默认构造函数创建对象out_file out_file.open("d:\\myfile.txt",ios::out);
                              • 打开方式
                                • ios::out 打开一个外部文件用于写操作。 如果外部文件已存在,则首先把它的内容清除;否则,先创建该外部文件(内容为空)。 ios::out是默认打开方式。
                                • ios::app 打开一个外部文件用于添加操作。(不清除文件内容,文件位置指针在末尾) 如果外部文件不存在,则先创建该外部文件(内容为空)。
                                • ios::out | ios::binary 或 ios::app | ios::binary
                                  • 按二进制方式打开文件。(默认的是文本方式)
                                  • 对以文本方式打开的文件,当输出的字符为'\n'时,在某些平台上(如:Windows平台)将会自动把它转换成'\r'和'\n'两个字符写入外部文件。
                                  • 对以二进制方式打开的文件,对输出的字节不做任何转换,原样输出。
                              • 判断打开操作是否成功
                                • 打开文件时,必须要对文件打开操作的成功与否进行判断。
                                • 判断文件是否成功打开可以采用以下方式
                                • 输出数据
                                  • 文件成功打开后,可以使用插入操作符“<<”或ofstream类的一些成员函数来进行文件数据的输出操作
                                    • 文件输出操作结束时,要使用ofstream的成员函数close关闭文件: out_file.close();
                                    • 关闭文件的目的: 把文件内存缓冲区的内容写到磁盘文件中! 归还打开文件时申请的资源
                                    • 程序正常结束时,系统也会自动关闭程序打开的文件。

                                  文件的输入操作

                                  • 打开方式 ios::in打开一个外部文件用于读操作。(默认) ios::in | ios::binary按二进制方式打开文件。(默认为文本方式)
                                  • 对以文本方式打开的文件,当连续读入的两个字符是'\r'和'\n'时,在某些平台上(如:Windows平台)将自动转换成一个字符'\n'。
                                  • 对以二进制方式打开的文件,对输入的字节不做任何转换,原样输入。
                                  • 打开文件时要判断打开是否成功。判断方式与文件输出打开操作的判断一样
                                  • 文件成功打开后,可以使用抽取操作符“>>”或ifstream类的一些成员函数来进行文件的输入操作,例如
                                    • 读取数据过程中有时需要判断是否正确读入了数据(尤其是在文件末尾处)。
                                    • 判断是否正确读入了数据,可以调用ios类的成员函数fail来实现:
                                      • bool ios::fail() const; 该函数返回true表示文件操作失败;返回false表示操作成功。
                                    • 文件输入操作结束后,要使用ifstream的一个成员函数close关闭文件: in_file.close();
                                    • 有关文件读写的几点注意 以文本方式输出的文件要以文本方式输入;以二进制方式输出的文件要以二进制方式输入! 以文本方式读写的文件要以文本方式打开;以二进制方式读写的文件要以二进制方式打开!

                                      既能输入、又能输出的文件

                                      • 如果需要打开一个既能读入数据、也能输出数据的文件,则需要创建一个fstream类的对象。
                                      • 文件内部有两个位置指针,一个用于读,另一个用于写。
                                      • 在创建fstream类的对象并建立与外部文件的联系时,文件打开方式应为下面之一: ios::in|ios::out(可在文件任意位置写) ios::in|ios::app(只能在文件末尾写)
                                      • 文件的随机存取 为了能够随机读写文件中的数据,可以显示地指出读写的位置。 下面的操作用来指定文件内部读指针的位置:
                                        • 下面的操作来指定文件内部写指针的位置:
                                          • <参照位置>可以是:ios::beg(文件头),ios::cur(当前位置)和ios::end(文件尾)。 文件的随机存取一般用于以二进制方式存贮的文件。