• C++I/O流



    流和基本文件I/O

    流(stream) 是由字符(或其他类型的数据)构成的"流"(flow)。流向程序,称为 输入流(input stream)。流出程序,则称为 输出流(output stream)。如输入流来自键盘,表明程序要从键盘获取输入。如输入流来自文件,表明程序要从文件获取输入。类似地,输出流可发送给屏幕或文件。

    之前我们已经使用过 cin 作为连接到键盘的输入流,cout 作为连接到屏幕的输出流。下面主要讨论如何使用流对象来进行文件 I/O。

    // 从文件infile.dat读取3个数,求和,将结果写入文件outfile.dat
    
    #include 
    using namespace std;
    
    int main()
    {
        ifstream inStream;
        ofstream outStream;
    
        inStream.open("infile.dat");
        outStream.open("outfile.dat");
    
        int first, second, third;
        inStream >> first >> second >> third;
        outStream << "The sum of the first 3\n"
                  << "numbers in infile.dat\n"
                  << "is " << (first + second + third)
                  << endl;
        inStream.close();
        outStream.close();
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    cincout 流是系统已经为你声明好的。要将流连接到文件,必须像声明其他任何变量那样声明流。“输入文件流”(input-file stream)变量的类型名称是 ifstream;“输出文件流”(output-file stream)变量的类型名称是 ofstream。所以,要将 inStream 声明为用于文件的输入流,将 outStream 声明为用于另一个文件的输出流,需要使用如下所示的语句:

    ifstream inStream;
    ofstream outStream;
    
    • 1
    • 2

    ifstreamofstream 类型在头文件fstream的库中定义。所以,任何程序要想以这种方式声明流,都必须包含以下预编译指令(通常放在接近文件开头的位置):

    #include 
    
    • 1

    使用 ifstreamofstream 类型时,程序还必须包含以下语句:

    using namespace std;
    
    • 1

    流变量(例如前面声明的 inStreamoutStream)必须连接到一个文件。这称为打开文件,用 open 函数完成该操作。例如,假定希望输入流 inStream 连接到 infile.dat 文件,程序首先执行以下语句,然后才能从该文件读取输入:

    inStream.open("infile.dat");
    
    • 1

    这里有两个细节。首先,流变量名称和一个圆点(也就是句点符号)要放在函数名称 open 之前,文件名则被指定为 open 函数的参数。其次,文件名要使用一对双引号。

    一旦声明好输入流变量,并用 open 函数将其连接到文件,程序就可使用提取操作符 >> 从文件获取输入。例如,以下语句从连接到 inStream 的文件读取两个数,将它们分别放入变量 oneNumberanotherNumber 中:

    int oneNumber, anotherNumber;
    inStream >> oneNumber >> anotherNumber;
    
    • 1
    • 2

    输出流采用和输入流相同的方式来打开(也就是连接到)文件。例如,以下语句声明输出流 outStream 并将其连接到 outfile.dat 文件:

    ofstream outStream;
    outStream.open("outfile.dat");
    
    • 1
    • 2

    针对 ofstream 类型的一个流,成员函数 open 会新建一个尚不存在的输出文件。如输出文件存在,成员函数open会丢弃文件内容。所以在调用 open 函数之后,输出文件为空。

    一旦调用 open 将文件连接到 outStream 流,程序就可使用插入操作符 << 将输出发送到那个文件。例如,以下语句将两个字符串和两个变量 oneNumberanotherNumber 的内容写入和 outStream 流连接的文件:

    outStream << "oneNumber = " << oneNumber << " anotherNumber = " << anotherNumber;
    
    • 1

    程序使用的每个输入和输出文件都有两个名称。外部文件名是文件真实名称,但只在 open 函数调用中使用一次,该函数将文件连接到一个流。一旦调用了 open,就必须将流名称作为文件名使用。

    程序完成从文件输入或者向文件输出之后,应该将文件关闭。关闭文件导致流与文件断开。调用 close 函数关闭文件。

    inStream.close();
    outStream.close();
    
    • 1
    • 2

    注意 close 函数不获取任何参数。如程序正常终止,但没有关闭文件,系统会自动为你关闭。但最好养成主动关闭文件的好习惯,原因有二。其一,只有在程序正常终止的前提下,系统才会为你关闭文件。假如程序因为错误而异常终止,文件就不会关闭,并可能损坏。程序在结束文件处理后立即关闭文件,文件损坏的概率就大大降低。其二,你可能希望程序将输出发送给一个文件,以后又将那些输出读回程序。为此,程序应该在完成向文件的写入之后立即关闭文件,再用 open 函数将文件连接到一个输入流(也可以打开一个文件,并同时进行输入和输出,但方式稍有区别)。

    open 调用可能因许多原因而失败。例如,打开不存在的输入文件,open 调用就会失败。在这种情况下,可能不会报告错误信息,你的程序将在你不知情的情况下执行非预期的操作。因此,我们可以使用成员函数 fail 测试一个流操作是否成功。其返回一个 bool 值:

    inStream.open("stuff.dat");
    if (inStream.fail())
    {
    	cout << "Input file opening failed.\n";
    	exit(1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上面提到,如果用输出文件流打开文件,会使得文件清空,但有时我们需要在原有文件的基础上追加一些东西,那么我们可以使用下面这种方式打开文件:

    ofstream fout;
    fout.open("data.txt", ios::app);
    
    • 1
    • 2

    流 I/O 工具

    用流函数格式化输出

    对程序输出的布局进行调整称为对输出进行格式化。在 C++ 中,可以用一些命令来控制格式,且可为任何输出流使用这些格式化命令。
    其中一种使用 setf 成员函数来设置格式,其有以下标志:

    标志含义默认
    ios::fixed设置这个标志,就不用 e 记数法写浮点数(设置该标志会自动取消设置 ios::scientific 标志)未设置
    ios::scientific设置这个标志,会用 e 记数法写浮点数(设置该标志会自动取消设置 ios::fixed 标志) 如果没有设置 ios::fixed 也没有设置 ios::scientific,就由系统决定如何输出每个数字未设置
    ios::showpoint如果设置这个标志,就始终为浮点数显示小数点和尾随的0。如果没有设置这个标志,而且一个数字在小数点之后全是0,那么当这个数字输出时,就可能不会显示小数点和尾随的 0未设置
    ios::showpos如果设置这个标志,正整数之前会输出一个正号未设置
    ios::right如果设置这个标志,同时调用成员函数 width 指定了域宽,输出的下一项会对齐指定域的右侧(右对齐)。也就是说,在输出的项之前,会根据需要添加空格(设置该标志会自动取消设置ios::left标志)已设置
    ios::left如果设置这个标志,同时调用成员函数 width 指定域宽,输出的下一项会对齐指定域的左侧(左对齐)。也就是说,在输出的项之后,根据需要添加填充空格(设置该标志会自动取消设置ios::right标志)未设置

    比如设置格式为 定点记数法(fixed-point notation),且始终显示小数点和尾随的 0 :

    outStream.setf(ios::fixed);
    outStream.setf(ios::showpoint);
    
    • 1
    • 2

    要决定浮点型小数点后的位数,可以使用 precision 成员函数:

    outStream.precision(3);
    
    • 1

    也可以 width 成员函数设置域宽:

    cout << "Start Now";
    cout.width(4);
    cout << 7 << endl;
    
    • 1
    • 2
    • 3

    这段代码导致屏幕上显示以下输出:

    Start Now   7
    
    • 1

    在输出中,字母 w 与数字 7 之间有 3 个空格。 width 函数告诉流一个输出项需要占多少个字符位置(即域宽)。本例中,输出项(也就是数字 7)只占一个字符位置,而 width 要求使用 4 个字符位置,所以其他 3 个位置用空格填充。如果输出所需的字符位置数目超过了在 width 函数调用中指定的数目,就自动补足缺少的字符位置。总之,输出项始终都会完整输出,不会被截短,无论为 width 指定的参数是什么。

    需要注意的是,对 width 函数的调用只适用于下一个要输出的项。要输出 12 个数字,而且每个都占 4 个字符位置,就必须调用 12 次 width

    设置的任何标志都可用 unsetf 函数取消。例如,以下语句导致程序停止为输出到 cout 流的正整数显示正号:

    cout.unsetf(ios::showpos);
    
    • 1

    操纵元

    操纵元(manipulator) 是以非传统方式调用的函数。调用操纵元后,它本身又会调用一个成员函数。操纵员位于插入操作符 << 之后,好似它本身就是下一个要输出的项。

    之前已经见过一个操纵元,即 endl。这里介绍两个新的操纵元:setwsetprecision。操纵元 setw 和成员函数 width 所做的事情完全一样。要调用 setw 操纵元,在插入操作符 << 之后写上 setw 即可。这如同将该操纵元发送到输出流,后者随即调用成员函数 width 例如,以下语句用不同域宽输出数字 10,20 和 30:

    cout << "Start" << setw(4) << 10
         << setw(4) << 20 << setw(6) << 30;
    
    • 1
    • 2

    流操纵元 setprecision 所做的事情和成员函数 precision 完全一样。但 setprecision 调用要放在插入操作符 << 之后,这和调用 setw 操纵元是类似的。例如,以下语句为指定的数字输出小数部分,小数位数由 setprecision 调用来指定:

    cout.setf(ios::fixed);
    cout.setf(ios::showpoint);
    cout << "$" << setprecision(2) << 10.3 << endl
         << "$" << 20.5 << endl;
    
    • 1
    • 2
    • 3
    • 4

    使用 setprecision 操纵元设置小数位数时,和成员函数 precision 的情况一样,设置会一直生效,直到把它重设为其他数字(通过再次调用 setprecisionprecision)。要使用 setwsetprecision 操纵元,必须在程序中包含以下预编译指令:

    #include 
    
    • 1

    流作为函数实参

    流可作为函数实参使用。唯一限制就是形参必须传引用。流参数不能传值。下面为一实例:

    // 演示输出格式化命令
    // 读取 rawdata.dat 文件中的所有数字,然后采用美观的格式
    // 将数字写到屏幕,同时写到 neat.dat 文件
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    
    void makeNeat(ifstream& messyFile, ofstream& neatFile,
                int numberAfterDecimalpoint, int fieldWidth);
    // 前条件:messyFile 和 neatFile 这两个流已用 open 函数连接到文件
    // 后条件:与 messyFile流连接的那个文件中的数字写到屏幕,
    // 同时写到与neatFile流连接的那个文件
    // 每个数字单独占一行,并采用定点记数法(换言之,不采用e记数法),
    // 而且在小数点之后保留 numberAfterDecimalpoint 位小数;
    // 每个数字的前面要么添加一个正号,要么添加一个负号,
    // 而且每个数字都占用宽带为 fieldWidth 的一个域(该函数不关闭文件)
    int main()
    {
        ifstream fin;
        ofstream fout;
    
        fin.open("rawdata.dat");
        if (fin.fail())
        {
            cout << "Input file opening failed.\n";
            exit(1);
        }
    
        fout.open("neat.dat");
        if (fout.fail())
        {
            cout << "Output file opening failed.\n";
            exit(1);
        }
        makeNeat(fin, fout, 5, 12);
    
        fin.close();
        fout.close();
    
        cout << "End of program.\n";
        return 0;
    }
    
    // 使用iostream,fstream 和 iomanip:
    void makeNeat(ifstream& messyFile, ofstream& neatFile,
                int numberAfterDecimalpoint, int fieldWidth)
    {
        neatFile.setf(ios::fixed);
        neatFile.setf(ios::showpoint);
        neatFile.setf(ios::showpos);
        neatFile.precision(numberAfterDecimalpoint);
        cout.setf(ios::fixed);
        cout.setf(ios::showpoint);
        cout.setf(ios::showpos);
        cout.precision(numberAfterDecimalpoint);
    
        double next;
        while (messyFile >> next)
        {
            cout << setw(fieldWidth) << next << endl;
            neatFile << setw(fieldWidth) << next << endl;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    字符 I/O

    get 和 put 成员函数

    每个输入流都有名为 get 的成员函数,它读取一个输入字符。和提取操作符不同,无论下个输入字符是什么,get 都会读取。具体地说,无论下个输入字符时空白字符(空格、制表符等),还是换行符,get 都会读取它。get 函数获取 char 类型的变量作为参数。调用 get 时,会读取下一个输入字符,并把实参变量设为该字符:

    char nextSymbol;
    cin.get(nextSymbol);
    
    • 1
    • 2

    每个输出流都有名为 put 的成员函数,它获取一个 char 类型的参数。调用成员函数 put 后,它的参数的值被输出到输出流:

    cout.put(nextSymbol);
    cout.put('a');
    
    • 1
    • 2

    要使用 get 或者 put 成员函数,需要在程序中包含以下预编译指令:

    #include 
    
    • 1

    putback 成员函数

    有时需要知道输入流中的下个字符。但在读取了下个字符之后,却发现自己不想处理该字符,所以想把它重新放回输入流。例如,假定希望程序一直读取一个输入流中的字符,直到(但不包括)第一个空格。所以,程序必须读取第一个空格,否则不知道什么时候应该停止读取。另一方面,既然已读取了该空格,它就不再包含在输入流中。与此同时,程序其他部分可能需要读取和处理这个空格。有许多方案都能解决这个问题,但最简单的方案就是使用成员函数 putbackputback 是每个输入流都有的成员函数。它获取一个 char 参数,并将该参数的值放回输入流。该参数可以是能求值为 char 值的任何表达式。

    例如,以下代码从与输入流 fin 连接的文件读取字符,将它们写入与输出流 fout 连接的另一个文件。代码一直读取字符,直到(但不包括)第一个空格:

    fin.get(next);
    while (next != ' ')
    {
    	fout.put(next);
    	fin.get(next);
    }
    fin.putback(next);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意,执行完这段代码之后,已被读取的空格仍然包含在输入流 fin 中,因为代码在读取了这个空格之后,又把它放回原来的输入流中。还要注意,putback 是将一个字符放回输入流中,而 put 是将一个字符放到输出流中。被成员函数 putback 放回输入流的字符不一定是上次读取的字符,它可以是任意字符。putback 是将字符放回输入”流“而不是放回输入”文件“,原始输入文件的内容不发生任何改变。

    万用型流参数

    下面是一个检查输入的例子:

    // 演示输出格式化命令
    // 读取 rawdata.dat 文件中的所有数字,然后采用美观的格式
    // 将数字写到屏幕,同时写到 neat.dat 文件
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    
    void makeNeat(ifstream& messyFile, ofstream& neatFile,
                int numberAfterDecimalpoint, int fieldWidth);
    // 前条件:messyFile 和 neatFile 这两个流已用 open 函数连接到文件
    // 后条件:与 messyFile流连接的那个文件中的数字写到屏幕,
    // 同时写到与neatFile流连接的那个文件
    // 每个数字单独占一行,并采用定点记数法(换言之,不采用e记数法),
    // 而且在小数点之后保留 numberAfterDecimalpoint 位小数;
    // 每个数字的前面要么添加一个正号,要么添加一个负号,
    // 而且每个数字都占用宽带为 fieldWidth 的一个域(该函数不关闭文件)
    int main()
    {
        ifstream fin;
        ofstream fout;
    
        fin.open("rawdata.dat");
        if (fin.fail())
        {
            cout << "Input file opening failed.\n";
            exit(1);
        }
    
        fout.open("neat.dat");
        if (fout.fail())
        {
            cout << "Output file opening failed.\n";
            exit(1);
        }
        makeNeat(fin, fout, 5, 12);
    
        fin.close();
        fout.close();
    
        cout << "End of program.\n";
        return 0;
    }
    
    // 使用iostream,fstream 和 iomanip:
    void makeNeat(ifstream& messyFile, ofstream& neatFile,
                int numberAfterDecimalpoint, int fieldWidth)
    {
        neatFile.setf(ios::fixed);
        neatFile.setf(ios::showpoint);
        neatFile.setf(ios::showpos);
        neatFile.precision(numberAfterDecimalpoint);
        cout.setf(ios::fixed);
        cout.setf(ios::showpoint);
        cout.setf(ios::showpos);
        cout.precision(numberAfterDecimalpoint);
    
        double next;
        while (messyFile >> next)
        {
            cout << setw(fieldWidth) << next << endl;
            neatFile << setw(fieldWidth) << next << endl;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    如果函数要获取输入流实参,而且实参有时是 cin,有时是输入文件流,那么可以使用 istream(没有f)类型的形参。但即使作为 istream 类型的实参提供给函数,输入文件流也必须声明为 ifstream 类型(有 f)。

    类似,如果函数要获取输出流实参,而且实参有时是 cout,有时是输出文件流,那么可以使用 ostream(没有 f)类型的形参。但即使作为 ostream 类型的实参提供给函数,输出文件流也必须声明为 ofstream 类型(有 f)。不能打开或关闭 istreamostream 类型的对象。传给函数前打开这些对象,函数调用结束后关闭。

    比如我们重写 newLine 函数:

    // 使用iostream:
    void newLine(istream& inStream)
    {
    	char symbol;
    	do
    	{
    		inStream.get(symbol);
    	} while (symbol != '\n');
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    eof 成员函数

    每个输入文件流都有名为 eof 的成员函数,用于判断何时读完文件的全部内容,没有更多的输入。eofend of file 的缩写,表示文件尾。函数无参,对于名为 fin 的输入流,可像下面这样写 eof 函数调用:

    fin.eof()
    
    • 1

    文件末尾有一个特殊的“文件尾”标记。成员函数 eof 只有在读取了这个文件尾标记之后,才会从 false 变为 true,因此可以用于判断何时读完整个输入文件。

    预定义字符函数

    下面对 cctype 库常用的函数进行总结:

    函数说明
    toupper(Char_Exp)返回 Char_Exp 的大写形式
    tolower(Char_Exp)返回 Char_Exp 的小写形式
    isupper(Char_Exp)如果 Char_Exp 是大写字母,就返回true,否则返回false
    islower(Char_Exp)如果 Char_Exp 是小写字母,就返回true,否则返回false
    isalpha(Char_Exp)如果 Char_Exp 是字母表中的字母,就返回 true;否则返回false
    isdigit(Char_Exp)如果 Char_Exp 是’0’到’9’的数字,就返回true,否则返回false
    isspace(Char_Exp)如果 Char_Exp 是空白字符(比如空格或换行符),就返回true;否则返回false

    需要注意的是以下语句不会输出字母 ‘A’,而是输出为字母’A’分配的数字:

    cout << toupper('a');
    
    • 1

    这是因为 touppertolower 返回 int 类型的值,而不是 char 类型的值,因此需要将其赋给 char 类型的变量:

    char c = toupper('a');
    cout << c;
    
    • 1
    • 2
  • 相关阅读:
    6.0、C语言数据结构——链式存储结构 (1)
    如何全面升级spring-boot-2.x及Spring-security-oauth2
    文章翻译软件-批量免费翻译软件支持各大翻译接口
    【C++】string模拟实现
    【源码系列】短剧系统开发国际版短剧系统软件平台介绍
    【知识点】浅入线段树与区间最值问题
    CANoe-vTESTstudio之Waveform编辑器
    Docker安装pgAdmin4
    计算机网络 ——数据链路层(广域网)
    python:爬取网络小说,看这一篇就够了
  • 原文地址:https://blog.csdn.net/weixin_44491423/article/details/126328988