# C++基础6-文件

本文是《C++ Primer Plus》的笔记,本文中的案例均自己实践过,如需转发请在转发开头贴上原文地址,谢谢!

# 文件的输入和输出

# 基本操作

C++ 处理文件IO和标准IO很相似,定义了ifstream和ofstream类,此外C++还定义了一个fstream类用于同步I/O。这些类均从iostream派生而来,可以使用之前介绍的方法。

ifstream fin;

上面定义了一个ifstream,即文件输入流的管理器,这个对象可以打开多个文件,这样多个文件便可以使用一个缓冲区,节省系统资源。

    #include <fstream>

    string filename;
    string str1;
    char ch1[20];
    ofstream fout;
    cin >> filename;
    //filename.c_str();
    fout.open(filename);
    //if (!fout.fail()) {
    //if (fout.good()) {
    //if (fout) {
    //注意上面这些测试无法检测一种情形:试图以不合适的文件模式打开文件时失败。而is_open()能够检测到这种错误以及good()检测到的错误。
    if(fout.is_open()){
        fout << "hello world!" << endl;
        fout << "hi world!";
        fout.close();
    }
    //如果不使用fout.close(),上面的"hi world!"不会被写入到str1中,因为这个字符串还在输入缓存区中待着。
    ifstream fin;
    fin.open(filename);
    if (fin.is_open()) {
        fin.getline(ch1, 30, '\n');
        getline(fin, str1);
        cout << ch1 << endl;
        cout << str1 << endl;
        fin.close();
    }
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

同时读取文件:

    string filename="hello.txt";
    char ch1[100];
    fstream fout;
    fout.open(filename, ios_base::in | ios_base::out | ios_base::app);
    if (fout.is_open()) {
        fout << "hello world xie!";
        fout.seekp(0);
        //fout.seekg(0);
        //fstream类使用缓存区来存储中间数据,因此指针指向的是缓存区中的位置,由于文件只有一个缓存区,seekg和seekp都会改变文件指针的位置。
        fout.get(ch1, 100, EOF);
        cout << ch1;
        fout.close();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

# 文件模式

文件模式描述的是文件将被如何使用:读,写,追加等。一般是文件流对象和具体文件关联时的第二个参数:

打开文件的方法:

方法一:
ifstream fin("file1",mode1);
方法二:
ofstream fout();
fout.open("file2",mode2);

上面mode1,mode2可以用 ios_base::out | ios_base::app 来表示同时使用多个文件模式。
1
2
3
4
5
6
7

ios_base 定义了一个openmode类型,也是一种bitmask类型,选择多个这样的常量来指定模式:

文件模式常量 含义
ios_base::in 打开文件准备读取
ios_base::out 打开文件准备写入
ios_base::ate 将文件指针设置在文件末尾,默认是文件开头
ios_base::app 所有输出操作只在文件末尾执行
ios_base::trunc 如果文件存在,截短文件
ios_base::binary 以二进制的方式打开文件

C语言的打开模式对应于C++模式解析

打开模式 读写状态 目标文件不存在 打开文件后文件指针位置 是否清空原有内容 读取位置 写入位置 C++模式
r 只读 打开失败 开头 任意位置 —— in
w 只写 新建 开头 —— 任意位置 out 或者 out | trunc
a 只写 新建 结尾 —— 尾部写入 out | app
r+ 读写 打开失败 开头 任意位置 任意位置 in | out
w+ 读写 新建 开头 任意位置 任意位置 in | out | trunc
a+ 读写 新建 结尾 任意位置 尾部写入 in | out | app

注意:

  1. 对于只写的流,out | trunc 等同于 out。对于读写的流,要打开文件时就截断文件,必须明确指定,即 in | out | trunc。
  2. app模式,即便是重新将文件指针指向头,也不起作用。ate模式将指针指向头,可将文件指针重新指向到文件头。
  3. 上面的所有操作加上b即为二进制文件操作

# 二进制文件

将数据存储在文件中时,可存储为文本格式或二进制格式。上面我们所说的都是文本格式,即将所有内容都存储为文本,这就需要在存储前将数据转换下。如果内容是文本的话,用文本格式存取比较方便,使用编辑器或字处理来读取和编辑文本文件,从一个计算机系统传输到另一个计算机系统。

而二进制格式对于数字来说比较精确,因为它存储的是值得内部表示,不会有转换误差或者舍入误差,以二进制保存数据更快因为有不需要转换。但是同一个系统上不同的编译器可能使用不同的内部存储结构,这就可能导致不兼容的问题。

以二进制格式存储数据,要使用write()成员函数,它只是逐字节地复制数据而不进行任何转换,唯一不方便的是需要将地址强制转换为指向char的指针。从二进制文件中读数据的话要使用read()函数,下面请看实例:

    struct person {
        //string name;如果改成string的话就会有问题
        char name[20];
        int age;
    }p1,p2,p3,p4;

    cout << "Input Person1 name:" ;
    cin >> p1.name;
    cout << "Input Person1 age:" ;
    cin >> p1.age;
    cout << "Input Person2 name:" ;
    cin >> p2.name;
    cout << "Input Person2 age:" ;
    cin >> p2.age;


    string filename = "testbin";
    ifstream fromfile;
    ofstream tofile;
    fromfile.open(filename,ios_base::in|ios_base::binary);
    tofile.open(filename,ios_base::out|ios_base::binary);
    if (tofile.is_open()) {
        tofile.write((char *) &p1,sizeof(p1));
        tofile.write((char *) &p2,sizeof(p2));
        tofile.close();
    }

    if (fromfile.is_open()) {
        fromfile.read((char *) &p3,sizeof(p3));
        fromfile.read((char *) &p4,sizeof(p4));
        fromfile.close();
    }
    cout << p3.name << ";" << p3.age << endl;
    cout << p4.name << ";" << p4.age << 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

# 随机存取

移动输入指针的位置使用seekg,它的原型为:

istream& seekg (streampos pos);
istream& seekg (streamoff off, ios_base::seekdir way);

获取输入指针的位置使用tellg,它的原型为:

streampos tellg();

移动输出指针的位置使用seekp,它的原型为:

ostream& seekp (streampos pos);
ostream& seekp (streamoff off, ios_base::seekdir way);

获取输出指针的位置使用tellp,它的原型为:

streampos tellp();

  1. 只获取一个偏移值的原型,偏移值是从开头算起。
  2. 指定偏移量的起始位置,seekdir可以取值:ios_base::beg (开头),ios_base::cur (中间),ios_base::end (结尾)。
  3. fstream类使用缓存区来存储中间数据,因此指针指向的是缓存区中的位置,当使用in | out 打开文件时,由于文件只有一个缓存区,seekg和seekp都会改变文件指针的位置。

实例:

    struct person {
        //string name;如果改成string的话就会有问题
        char name[20];
        int age;
    }p1, p2, p3, p4;

    cout << "Input Person1 name:";
    cin >> p1.name;
    cout << "Input Person1 age:";
    cin >> p1.age;
    cout << "Input Person2 name:";
    cin >> p2.name;
    cout << "Input Person2 age:";
    cin >> p2.age;


    string filename = "testbin";
    ifstream fromfile;
    ofstream tofile;
    fromfile.open(filename, ios_base::in | ios_base::binary);
    tofile.open(filename, ios_base::out | ios_base::binary);
    if (tofile.is_open()) {
        tofile.write((char *)&p1, sizeof(p1));
        tofile.write((char *)&p2, sizeof(p2));
        tofile.close();
    }

    if (fromfile.is_open()) {
        fromfile.seekg(sizeof(person));
        fromfile.read((char *)&p3, sizeof(p3));
        fromfile.seekg(0);
        fromfile.read((char *)&p4, sizeof(p4));
        fromfile.close();
    }
    cout << p3.name << ";" << p3.age << endl;
    cout << p4.name << ";" << p4.age << 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

# 内核格式化

iostream相关类支持程序与终端之间的I/O,而fstream相关类使用相同的接口提供程序和文件之间的I/O。此外还有sstream相关类,使用相同的接口提供程序和string对象之间的I/O。也就是说可以使用cout的ostream的方法将格式化信息写入到string对象中,并使用istream方法(比如getline())来读取string对象中的信息。这种读取string对象中的格式化信息或将格式化信息写入string对象中称为内核格式化(incore formatting)。

    #include <sstream>

    ostringstream outStr;
    string str1;
    int age;
    cout << "Input your name:";
    getline(cin, str1);
    cout << "Input your age:";
    cin >> age;
    outStr << str1 << "现在" << age << "岁了!" <<endl;

    string result = outStr.str();
    cout << result;

    string word;
    istringstream inStr("this is a apple");
    while (inStr >> word) {
        cout << word << endl;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

上面就是从标准输入到字符串,和从字符串到标准输出。怎么方便记忆3大IO的输入输出方向呢?

输入:从 输入 get信息
输出:put信息 到 输出

比如文件,ifstream 是从文件中获取信息,ofstream是将信息推送到文件。
比如stream,istringstream 是从string中获取信息,ostringstream是将信息推送到string中。

# C++程序命令行接收参数

定义主程序:
int main(int argc,char *argv[])
{
    for (int i = 0; i < argc; i++) {
        cout << argv[i] << endl;
    }
}

在命令行输入:
.\BasicPrimer.exe h1 h2 h3
得到输出:
h1
h2
h3
1
2
3
4
5
6
7
8
9
10
11
12
13
14