zl程序教程

您现在的位置是:首页 >  后端

当前栏目

C/C++学习笔记十二 Input and Output (I/O)(6)

C++笔记学习 and input 十二 output
2023-09-14 09:15:03 时间

一、随机文件 I/O

1、文件指针

        每个文件流类都包含一个文件指针,用于跟踪文件中的当前读/写位置。 当从文件中读取或写入某些内容时,读取/写入发生在文件指针的当前位置。 默认情况下,当打开文件进行读取或写入时,文件指针设置为文件的开头。 但是,如果以追加模式打开文件,则文件指针将移动到文件末尾,因此写入不会覆盖文件的任何当前内容。

2、使用 seekg() 和 seekp() 进行随机文件访问

        到目前为止,我们所做的所有文件访问都是顺序的——也就是说,我们已经按顺序读取或写入文件内容。 但是,也可以进行随机文件访问——即,跳到文件中的各个点以读取其内容。 当您的文件充满记录并且您希望检索特定记录时,这可能很有用。 您可以直接跳到要检索的记录,而不是阅读所有记录直到找到您想要的记录。

        随机文件访问是通过使用 seekg() 函数(用于输入)和 seekp() 函数(用于输出)操作文件指针来完成的。 如果您想知道,g 代表“get”,p 代表“put”。 对于某些类型的流, seekg() (改变读取位置)和 seekp() (改变写入位置)独立操作——但是,对于文件流,读取和写入位置总是相同的,所以 seekg 和 seekp 可以 互换使用。

        seekg() 和 seekp() 函数采用两个参数。 第一个参数是确定要移动文件指针多少字节的偏移量。 第二个参数是一个 Ios 标志,它指定偏移参数应该从什么偏移。

         正偏移量意味着将文件指针移向文件末尾,而负偏移量意味着将文件指针移向文件开头。

        这里有些例子:

inf.seekg(14, std::ios::cur); // move forward 14 bytes
inf.seekg(-18, std::ios::cur); // move backwards 18 bytes
inf.seekg(22, std::ios::beg); // move to 22nd byte in file
inf.seekg(24); // move to 24th byte in file
inf.seekg(-28, std::ios::end); // move to the 28th byte before end of the file

        移动到文件的开头或结尾很容易:

inf.seekg(0, std::ios::beg); // move to beginning of file
inf.seekg(0, std::ios::end); // move to end of file

        让我们使用 seekg() 和我们在上一节中创建的输入文件做一个示例。 该输入文件如下所示:

This is line 1
This is line 2
This is line 3
This is line 4

        这是示例:

#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::ifstream inf{ "Sample.txt" };

    // If we couldn't open the input file stream for reading
    if (!inf)
    {
        // Print an error and exit
        std::cerr << "Uh oh, Sample.txt could not be opened for reading!\n";
        return 1;
    }

    std::string strData;

    inf.seekg(5); // move to 5th character
    // Get the rest of the line and print it
    getline(inf, strData);
    std::cout << strData << '\n';

    inf.seekg(8, std::ios::cur); // move 8 more bytes into file
    // Get rest of the line and print it
    std::getline(inf, strData);
    std::cout << strData << '\n';

    inf.seekg(-14, std::ios::end); // move 14 bytes before end of file
    // Get rest of the line and print it
    std::getline(inf, strData);
    std::cout << strData << '\n';

    return 0;
}

        结果输出以下:

is line 1
line 2
This is line 4

        注意:某些编译器在与文本文件结合使用时(由于缓冲),在 seekg() 和 seekp() 的实现中存在错误。 如果您的编译器是其中之一(您会知道,因为您的输出与上面的不同),您可以尝试以二进制模式打开文件:

std::ifstream inf("Sample.txt", std::ifstream::binary);

        另外两个有用的函数是tellg() 和tellp(),它们返回文件指针的绝对位置。 这可用于确定文件的大小:

std::ifstream inf("Sample.txt");
inf.seekg(0, std::ios::end); // move to end of file
std::cout << inf.tellg();

        打印如下:

64

        这是 sample.txt 的字节长度(假设最后一行后有回车)。

3、使用 fstream 同时读取和写入文件

        fstream 类能够同时读取和写入文件!这里不可能在读写之间随意切换。一旦发生读取或写入,在两者之间切换的唯一方法是执行修改文件位置的操作(例如查找)。如果你真的不想移动文件指针(因为它已经在你想要的位置),可以寻找到当前位置:

// assume iofile is an object of type fstream
iofile.seekg(iofile.tellg(), std::ios::beg); // seek to current file position

        如果你不这样做,任何奇怪的事情都可能发生。

        (注意:虽然看起来 iofile.seekg(0, std::ios::cur) 也可以工作,但似乎一些编译器可能会优化它)。

        另一个棘手的问题:与 ifstream 不同,我们可以说 while (inf) 来确定是否还有更多要阅读的内容,这不适用于 fstream。

        让我们使用 fstream 做一个文件 I/O 示例。 我们将编写一个程序来打开一个文件,读取其内容,并将它找到的任何元音更改为“#”符号。

int main()
{
    // Note we have to specify both in and out because we're using fstream
    std::fstream iofile{ "Sample.txt", std::ios::in | std::ios::out };

    // If we couldn't open iofile, print an error
    if (!iofile)
    {
        // Print an error and exit
        std::cerr << "Uh oh, Sample.txt could not be opened!\n";
        return 1;
    }

    char chChar{}; // we're going to do this character by character

    // While there's still data to process
    while (iofile.get(chChar))
    {
        switch (chChar)
        {
            // If we find a vowel
            case 'a':
            case 'e':
            case 'i':
            case 'o':
            case 'u':
            case 'A':
            case 'E':
            case 'I':
            case 'O':
            case 'U':

                // Back up one character
                iofile.seekg(-1, std::ios::cur);

                // Because we did a seek, we can now safely do a write, so
                // let's write a # over the vowel
                iofile << '#';

                // Now we want to go back to read mode so the next call
                // to get() will perform correctly.  We'll seekg() to the current
                // location because we don't want to move the file pointer.
                iofile.seekg(iofile.tellg(), std::ios::beg);

                break;
        }
    }

    return 0;
}

        其他有用的文件功能

        要删除文件,只需使用 remove() 函数。

        此外,如果流当前打开,is_open() 函数将返回 true,否则返回 false。

4、关于将指针写入磁盘的警告

        虽然将变量流式传输到文件非常容易,但在处理指针时事情会变得更加复杂。请记住,指针只是保存它所指向的变量的地址。虽然可以在磁盘上读写地址,但这样做非常危险。这是因为变量的地址可能因执行而异。因此,尽管当您将该地址写入磁盘时,变量可能已经存在于地址 0x0012FF7C,但当您重新读取该地址时,它可能不再存在于该地址!

        例如,假设您有一个名为 nValue 的整数,它位于地址 0x0012FF7C。您为 nValue 分配了值 5。您还声明了一个名为 *pnValue 的指针,该指针指向 nValue。 pnValue 保存 nValue 的地址 0x0012FF7C。您想保存这些以供以后使用,因此您将值 5 和地址 0x0012FF7C 写入磁盘。

        之后,您再次运行程序并从磁盘读回这些值。您将值 5 读入另一个名为 nValue 的变量,该变量位于 0x0012FF78。您将地址 0x0012FF7C 读入一个名为 *pnValue 的新指针。因为当 nValue 位于 0x0012FF78 时 pnValue 现在指向 0x0012FF7C,所以 pnValue 不再指向 nValue,尝试访问 pnValue 会导致读取不到。

        所以不要将地址写入文件。当您从磁盘读回它们的值时,最初位于这些地址的变量可能位于不同的地址,并且这些地址将无效。