文件读写操作

作者:jicanmeng

时间:2017年06月07日


  1. 文件写入
  2. 文件读取
  3. 文件操作模式: 只读,只写,可读可写
  4. 文件指针

c语言中,对文件的读写操作使用fopen(), fread(), fwrite(), fclose()等函数。c++中,在兼容这种方式的同时,提供了另外的方式进行文件的读写操作:

1. 文件写入

先看一个程序:

#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib> // for exit()
using namespace std;

int main()
{
    string file_name = "sample.dat";

    // ofstream is used for writing files
    // We'll make a file called sample.dat
    ofstream outf(file_name);

    // If we couldn't open the output file stream for writing
    if (!outf) {
        cerr << "error: " << file_name <<
         " could not be opened for writing!" << endl;
        exit(1);
    }

    outf << "This is line 1" << endl;
    outf << "This is line 2" << endl;

    return 0;

    // When outf goes out of scope, the ofstream
    // destructor will close the file
}

上面的程序会创建一个名称为sample.dat的文件。learncpp.com上对文件的操作的描述是:

To open a file for reading and/or writing, simply instantiate an object of the appropriate file I/O class, with the name of the file as a parameter. Then use the insertion (<<) or extraction (>>) operator to read/write to the file. Once you are done, there are several ways to close a file: explicitly call the close() function, or just let the file I/O variable go out of scope (the file I/O class destructor will close the file for you).

可以在创建了文件后,调用了close()方法后,再调用open()方法打开。learncpp.com上面的描述如下:

Just like it is possible to explicitly close a file stream using close(), it’s also possible to explicitly open a file stream using open(). open() works just like the file stream constructors -- it takes a file name and an optional file mode.

#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib> // for exit()
using namespace std;

int main()
{
    ofstream outf("sample.dat");
    outf << "This is line 1" << endl;
    outf << "This is line 2" << endl;
    outf.close(); // explicitly close the file

    // Oops, we forgot something
    outf.open("Sample.dat", ios::app);
    outf << "This is line 3" << endl;
    outf.close();

    return 0;
}

打开sample.dat文件后,发现文件中有三行,就是我们刚才写入的三行。

2. 文件读取

下面的程序则会从sample.dat文件中读取数据:

#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib> // for exit()
using namespace std;

int main()
{
    string file_name = "sample.dat";

    // ifstream is used for reading files
    // We'll read from a file called Sample.dat
    ifstream inf(file_name);

    // If we couldn't open the output file stream for reading
    if (!inf) {
        cerr << "error: " << file_name <<
         " could not be opened for reading!" << endl;
        exit(1);
    }

    // While there's still stuff left to read
    while (inf) {
        std::string strInput;
        //inf >> strInput;
        getline(inf, strInput);
        cout << strInput << endl;
    }

    return 0;

    // When inf goes out of scope, the ifstream
    // destructor will close the file
}

以下是运行结果:

[jicanmeng@andy tmp]$ ./a.out
				This is line 1
                This is line 2
			[jicanmeng@andy tmp]$

注意上面的第26行代码,如果我们注释掉这一行,打开25行,那么运行结果如下:

[jicanmeng@andy tmp]$ ./a.out
                This
                is
                line
                1
                This
                is
                line
                2
            [jicanmeng@andy tmp]$

3. 文件操作模式: 只读,只写,可读可写

fstream创建的文件,可以读取,也可以写入。

在使用c语言打开文件的时候,可以指定模式,比如"r", "w", "w+", "a+"等。在c++中,同样可以指定。下表是可以指定的标志位(from learncpp.com):

Ios file mode Meaning
app Opens the file in append mode
ate Seeks to the end of the file before reading/writing
binary Opens the file in binary mode (instead of text mode)
in Opens the file in read mode (default for ifstream)
out Opens the file in write mode (default for ofstream)
trunc Erases the file if it already exists

It is possible to specify multiple flags by bitwise ORing them together (using the | operator). Note that ios::in and ios::out are already defaults for the ifstream and ofstream classes respectively. If you opt to use fstream (which can do both input and output), you explicitly have to pass in ios::in and/or ios::out depending on which mode you’d like to use.
默认情况下,ios::app会设置,即创建一个文件时,如果这个文件存在,那么会清空这个文件,从头开始写入。

下面的例子,会打开sample.dat文件,然后在文件后面添加两行:

#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib> // for exit()
using namespace std;

int main()
{
    string file_name = "sample.dat";

    // We'll make a file called sample.dat.
    // But if file exists, only append data.
    ofstream outf(file_name, ios::app);

    // If we couldn't open the output file stream for writing
    if (!outf) {
        cerr << "error: " << file_name <<
         " could not be opened for writing!" << endl;
        exit(1);
    }

    outf << "This is line 3" << endl;
    outf << "This is line 4" << endl;

    return 0;

    // When outf goes out of scope, the ofstream
    // destructor will close the file
}

4. 文件指针

learncpp.com上面的描述:

Each file stream class contains a file pointer that is used to keep track of the current read/write position within the file. When something is read from or written to a file, the reading/writing happens at the file pointer’s current location. By default, when opening a file for reading or writing, the file pointer is set to the beginning of the file. However, if a file is opened in append mode, the file pointer is moved to the end of the file, so that writing does not overwrite any of the current contents of the file.

打开一个文件用于读取时,可以使用seekg函数来移动文件指针;打开一个文件用于写入时,可以使用seekp函数来移动文件指针。调用函数时需要指定两个参数,第一个是移动几个字节,第二个是从哪里开始移动。第二个参数有三个选项:文件开始位置(ios::beg), 当前的指针位置(ios::cur), 文件的结束位置(ios::end)。举例说明:

#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib> // for exit()
using namespace std;

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

    // If we couldn't open iofile, print an error
    if (!iofile) {
        // Print an error and exit
        cerr << "Uh oh, sample.dat could not be opened!" << endl;
        exit(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, 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(), ios::beg);

                break;
        }
    }

    return 0;
}

假设我们有一个文件,里面内容为abcd,执行完上面的程序后,文件的内容就会变为#bcd。注意,我们并没有调用seekp函数,那么为什么执行文件写入操作会覆盖a呢?

答案是:seekp()函数的作用是设置输出文件流的文件流指针位置,seekg()函数的作用是设置输入文件流的文件流指针位置。fstream创建的这个文件既是输出文件流,也是输入文件流,所以seekg和seekp都会改变文件流指针的位置。不像c语言,分为读指针和写指针。

参考资料

  1. <<C++实用教程>> 电子工业出版社 郑阿奇 主编 丁有和 编著
  2. The C++ Tutorial:
    http://www.learncpp.com/cpp-tutorial/186-basic-file-io/
  3. misc:
    http://www.cnblogs.com/onlyan/archive/2012/08/02/2620726.html