C++ primer plus笔记 --- 第17章、输入、输出和文件

17.1、C++输入和输出概述

C++中的输入流和输出流是对程序进行输入和输出操作的主要方式。输入流用于从外部获取数据,输出流用于向外部输出数据。C++中的输入输出操作都是通过I/O流(输入/输出流)来完成的。I/O流是指stream,它是一个抽象类,派生出了继承了它的输入流istream和输出流ostream。

输入流和输出流分别和两个预定义的对象cin和cout绑定,它们在iostream头文件中定义。数据的输入和输出都是针对流对象进行操作。cin和cout对象依赖于系统,因此在不同的系统之间可能会有一些差异。

在输入和输出数据时,还需要考虑类型和格式的问题。例如,在输出浮点数时,需要指定小数点后的精度,而在输入字符串时,则需要注意空格和换行符等问题。

下面我们将详细介绍C++中的输入输出相关的知识点。

17.1.1、流和缓冲区

在介绍输入输出流之前,我们需要理解两个重要的概念——流和缓冲区。

1、流

流是一种数据传输通道,用于在程序和外部世界之间传输数据。流可以是输入流、输出流或二者兼备的流。流用于将数据从源传输到目标,从而实现数据的输入和输出。

2、缓冲区

缓冲区是一个临时存储区域,用于存放输入或输出数据。在C++中,缓冲区分为输出缓冲区和输入缓冲区。

输出缓冲区是指在执行输出操作时,数据先被送到输出缓冲区存放,当缓冲区满或刷新操作时,数据才会被实际发送到目标。

输入缓冲区是指在执行输入操作时,数据从输入流中获取,在输入流中查找换行符并将其删除后,再将数据存放在输入缓冲区中等待读取操作。

缓冲区可以提高I/O的效率,避免频繁地进行I/O操作,从而提高程序的运行效率。但是,在某些情况下,需要立即发送数据或立即读取数据,这时就需要清空缓冲区或刷新缓冲区。

在C++中,我们可以使用以下函数清空和刷新缓冲区:

  • flush():刷新输出流的缓冲区,将未写入目标设备的数据立即输出。

  • getline():从输入流中读取一行数据,在读取完一行数据后,自动清空输入缓冲区。

  • ignore():清空输入缓冲区中指定个数的字符,从而丢弃这些字符。

现在我们已经了解了流和缓冲区的基本概念,接下来我们将介绍数据的输入和输出操作。

17.1.2、流、缓冲区和iostream文件

在C++中,iostream是用于支持流输入输出操作的标准库。iostream库定义了三个流对象:cin表示标准输入流,cout表示标准输出流,cerr表示标准错误流。iostream还提供了一些类型和函数来支持输入输出操作。

iostream使用了缓冲区来提高输入输出操作的效率。当进行输出操作时,数据被先存放在缓冲区中,直到满或显式刷新才进行输出到目标设备,从而提高输出效率。类似地,在执行输入操作时,数据从输入流中获取,在输入缓冲区中查找换行符并将其删除后,再将数据存放在输入缓冲区中等待读取操作。

iostream库还提供了一些函数来操作文件。其中,fstream库提供了文件操作函数,包括打开、关闭、读取和写入文件。fstream库定义了两个类:ifstream类表示文件输入流,用于读取文件;ofstream类表示文件输出流,用于写入文件。

在进行文件输入输出操作时,需要先使用open()方法打开文件,然后使用流运算符(<<和>>)来读取和写入文件的数据,最后使用close()方法关闭文件。

下面是一个使用iostream库读写文件的简单示例:

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    ifstream inFile("input.txt"); //打开输入文件
    ofstream outFile("output.txt"); //打开输出文件

    int temp;
    while (inFile >> temp) //从输入文件读取内容并写入输出文件中
    {
        outFile << temp << " ";
    }

    inFile.close(); //关闭输入文件
    outFile.close(); //关闭输出文件
    return 0;
}

上面的代码中,我们首先使用ifstream和ofstream打开了输入和输出文件,然后使用流运算符(<<和>>)将输入文件中的内容读取并写入输出文件,最后再使用close()方法关闭文件。

17.1.3、重定向

在C++中,可以通过重定向来改变输入输出目标。重定向是指将输入源或输出目标从默认的键盘和屏幕分别改变为文件或其他设备。

使用重定向有以下三种方式:

  1. 在命令行中使用输入输出重定向符号来指定输入和输出文件。例如,在命令行中输入./a.out < input.txt > output.txt,就可以将程序的输入重定向为input.txt文件,输出重定向为output.txt文件。

  2. 使用freopen函数来进行输入输出重定向。freopen函数是C++中的标准库函数,它可以将文件指针关联到某个文件流,从而实现输入输出重定向。

  3. 使用iostream中的rdbuf函数来重定向输入输出流。rdbuf函数是指将指定的流对象的缓冲区替换为另一个缓冲区,从而实现输入输出的重定向。

下面是一个使用freopen函数进行重定向的示例:

#include <iostream>
#include <cstdio>

using namespace std;

int main()
{
    freopen("input.txt", "r", stdin); // 以只读方式打开文件
    freopen("output.txt", "w", stdout); // 以只写方式打开文件

    int a, b;
    cin >> a >> b;
    cout << "a + b = " << a + b << endl;

    return 0;
}

上面的示例将输入重定向为input.txt文件,输出重定向为output.txt文件,并从输入文件中读取a和b的值,输出它们的和到输出文件中。

需要注意的是,在输入输出重定向时,程序必须具有访问目标文件的权限。

17.2、使用cout进行输出

在C++中,使用cout对象进行输出操作是非常常见的操作。cout对象是一个ostream类对象,它是在头文件iostream中定义的。cout用于向标准输出流(通常指屏幕)输出数据。默认情况下,cout和标准输出流绑定,输出的内容会直接显示在屏幕上。

cout的输出数据类型包括:bool、char、字符串、数字、浮点数等。cout用于控制输出的格式、精度、对齐方式和填充字符等。

下面是一些常见的使用cout进行输出的示例:

cout << "Hello World!" << endl; // 输出字符串

int a = 10;
cout << "a = " << a << endl; // 输出变量a的值

double pi = 3.141592653589793;
cout << "pi = " << pi << endl; // 输出pi的值,默认小数点后6位

cout.precision(10); // 设置小数点后输出的精度为10位
cout << "pi = " << pi << endl; // 输出pi的值,小数点后10位

cout.width(10); // 设置输出宽度为10
cout.fill('*'); // 设置填充字符为*
cout << "a = " << a << endl; // 输出a的值,输出长度为10,右对齐,前面填充*

cout.width(10); // 设置输出宽度为10
cout.setf(ios::left); // 设置左对齐
cout << "a = " << a << endl; // 输出a的值,输出长度为10,左对齐,默认填充空格

上述代码中,输出的每一行都以endl结束,这个操作符用于在输出流中插入一个换行符并刷新缓冲区。

需要注意的是,cout的输出操作可以连续执行,可以在一行中输出多个内容,使用<<运算符进行连接。同时,我们可以使用控制符来控制输出的格式和对齐方式。比如,precision控制小数点后的位数,width控制输出宽度,fill控制填充字符,setf控制对齐方式等等。

17.2.1、重载的<<运算符

在C++中,cout对象通过重载<<运算符实现对不同数据类型的输出操作。在cout <<中,左边的cout对象为已经定义好的输出流对象,右边的操作数可以是任意数据类型,包括基本数据类型、自定义类对象等。在cout输出时,对于一些数据类型,重载<<运算符会自动根据数据类型进行特定的输出操作。

例如,我们可以定义一个自定义类MyClass,并在其中重载<<运算符,然后使用cout对象输出这个自定义类对象。示例代码如下:

#include <iostream>
using namespace std;

class MyClass {
private:
    int data;
public:
    MyClass(int d) : data(d) {}
    friend std::ostream& operator<< (std::ostream& os, const MyClass& obj) {
        os << "MyClass data: " << obj.data;
        return os;
    }
};

int main()
{
    MyClass obj(10);
    cout << obj << endl;
    return 0;
}

在这个示例中,我们定义了一个自定义类MyClass,并在其中重载了<<运算符。这里使用了友元函数的方式实现了<<运算符的重载,在重载中首先输出"MyClass data: "字符串,然后输出MyClass对象的成员变量data的值。

在main函数中,我们创建了一个MyClass对象obj,并使用cout对象输出这个对象。由于<<运算符被重载了,cout对象会自动将MyClass对象转换为输出字符串,并输出。

注意,在重载<<运算符时,需要使用友元函数,因为cout对象是已经定义好的ostream类对象,而我们无法直接在MyClass类中定义cout对象。同时在定义<<运算符时,需要显式地返回一个ostream类对象,以允许链式输出。

除了重载<<运算符外,我们还可以通过在类中定义一个toString或者to_string函数,在其中定义输出字符串的逻辑,并在输出时调用这个函数实现自定义类对象的输出。

17.2.2、其他ostream方法

除了重载<<运算符外,Ostream类还提供了其他一些方法来控制输出的格式。

  1. setprecision(n):设置输出浮点数的精度为n位。

  2. setw(n):设置输出域的宽度为n个字符。如果输出宽度小于n个字符,则在输出左侧填充空格,如果输出宽度大于n个字符,不会自动截断。

  3. setfill©:设置填充字符为c。如果输出的宽度大于输出字符串的长度,则会在输出字符串的左侧填充指定的填充字符。

  4. setiosflags(flag):设置输出标志位,例如左对齐、右对齐、十六进制输出等等。

下面是一个展示以上方法的示例代码:

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
    cout << setprecision(5) << 3.14159 << endl; // 输出浮点数,保留5位小数

    cout << setw(10) << 3 << endl; // 输出整数,输出宽度为10,自动右对齐,左侧填充空格
    cout << setw(10) << setfill('*') << 3 << endl; // 输出整数,输出宽度为10,填充字符为*,自动右对齐,左侧填充*
    cout << setw(5) << setiosflags(ios::left) << 3 << endl; // 输出整数,输出宽度为5,左对齐,右侧填充空格

    cout << setiosflags(ios::hex) << 33 << endl; // 输出33的十六进制形式
    cout << setiosflags(ios::uppercase) << 33 << endl; // 输出33的大写字母形式
    return 0;
}

在上述示例代码中,我们使用了setprecision、setw、setfill、setiosflags这些Ostream类的方法,来控制输出的格式和对齐方式。使用setw和setfill方法可以控制输出的宽度和填充字符,使用setprecision可以控制输出浮点数的小数位数,使用setiosflags可以设置输出标志位。需要注意的是,在使用setfill设置填充字符时,需要在setw之前调用,以保证填充字符的设置生效。

17.2.3、刷新输出缓冲区

输出缓冲区是指在向输出设备(如屏幕)输出数据时,先将数据存储在内存中,直到缓冲区满或者显式地刷新缓冲区才进行实际的输出操作。在C++中,cout对象也有一个输出缓冲区,使用<<运算符时,数据会先存储在cout对象的输出缓冲区中,直到缓冲区满或者使用endl等语句显式地刷新缓冲区才输出到屏幕上。

可以通过以下两种方法来刷新输出缓冲区:

  1. 使用endl语句或者"\n"(换行符)来显式地刷新输出缓冲区,将缓冲区中的内容向屏幕输出。

  2. 使用flush语句来显式地刷新输出缓冲区,但不会在输出内容后插入换行符。

下面是一个示例代码:

#include <iostream>
using namespace std;

int main() {
    cout << "Hello, world!" << endl; // 输出"Hello, world!"并刷新输出缓冲区
    cout << "What's your name?" << endl; // 输出"What's your name?"并刷新输出缓冲区

    cout << "I'm fine, thank you!" << flush; // 输出"I'm fine, thank you!"并刷新输出缓冲区,不插入换行符
    return 0;
}

在上述示例代码中,我们使用了endl、“\n"和flush这些语句显式地刷新输出缓冲区。每次输出一行内容后都要使用endl或者”\n"来刷新输出缓冲区,以便将缓冲区中的数据输出到屏幕上。flush语句可以在输出内容不需要换行的情况下使用,以显式地刷新输出缓冲区。

17.2.4、用cout进行格式化

在C++中,可以使用cout对象进行简单的格式化输出。cout对象提供了一些方法,例如setprecision、setw、setfill、setiosflags等,可以用来控制输出格式、数字精度、对齐方式等。

下面是一些使用cout进行格式化输出的示例:

#include <iostream>
#include <iomanip> // 头文件

using namespace std;

int main()
{
    double pi = 3.1415926535;
    cout << "默认输出: " << pi << endl; // 默认输出浮点数

    cout << fixed << setprecision(2) << "两位小数输出: " << pi << endl; // 输出浮点数,保留两位小数
    cout << scientific << setprecision(2) << "科学计数法输出: " << pi << endl; // 输出浮点数,科学计数法,保留两位小数

    cout << setw(10) << setfill('*') << "居中输出:" << "Hello, world!" << endl; // 输出字符串,宽度为10,居中,前后填充*

    cout << hex << "输出十六进制数:" << 42 << endl; // 输出十六进制数
    cout << uppercase << hex << "输出大写十六进制数:" << 42 << endl; // 输出大写十六进制数
    cout << dec << "输出十进制数:" << 42 << endl; // 输出十进制数

    return 0;
}

上述代码中,使用了fixed、setprecision、scientific、setw、setfill、hex、uppercase、dec等方法,对输出进行了格式化,控制输出浮点数精度、字符串宽度、填充字符,控制输出数字的进制、大小写等。

需要注意的是,在使用setw和setfill控制字符串输出宽度和填充字符时,需要将setfill放在setw语句之前,否则填充字符设置不会生效。

17.3、使用cin>>进行输入

在C++中,可以使用cin对象进行输入操作。cin对象的>>运算符被重载,可以读取多种不同类型的数据。

以下示例展示了如何从标准输入流(通常是键盘)读取整数和字符串:

#include <iostream>
#include <string>

int main()
{
    int num;
    std::string str;

    std::cout << "请输入一个整数: ";
    std::cin >> num;

    std::cout << "请输入一个字符串: ";
    std::cin >> str;

    std::cout << "输入的整数是: " << num << std::endl;
    std::cout << "输入的字符串是: " << str << std::endl;

    return 0;
}

在上述示例中,我们先声明了一个int类型的变量num和一个std::string类型的变量str。然后使用std::cin输入流对象从标准输入流中读取整数和字符串。读取整数时,我们使用>>运算符将输入的整数存储到num变量中。读取字符串时,我们同样使用>>运算符将输入的字符串存储到str变量中。

需要注意的是,使用>>运算符读取字符串时,它会将空格符、制表符或回车符作为分隔符,只读取字符串中第一个分隔符之前的部分,然后将输入的分隔符留在输入缓冲区。如果需要读取一行完整的字符串,应该使用std::getline函数。

17.3.1、cin>>如何检查输入

在进行输入操作时,我们需要注意用户可能会输入错误的数据类型或格式,这时我们可以使用cin对象的fail、eof、bad和good等方法来检查输入状态。

  1. fail()方法:返回true表示输入的数据类型与预期不符或者输入结束符(如CTRL+D),或者输入错误。可以通过调用clear()方法将输入状态复位为正常状态。

  2. eof()方法:返回true表示已经读取到输入结束符(如CTRL+D)。

  3. bad()方法:返回true表示输入流发生了不可恢复的错误(如文件被删除或写入错误),在发生这种错误时,输入流将无法继续使用。

  4. good()方法:返回true表示输入状态正常,没有发生错误。

下面是一个示例代码:

#include <iostream>
#include <string>

int main() 
{
    int num;
    std::string str;

    std::cout << "请输入一个整数: ";
    std::cin >> num;

    if (std::cin.fail()) {
        std::cout << "输入的不是整数。" << std::endl;
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }

    std::cout << "请输入一个字符串: ";
    std::cin >> str;

    if (std::cin.fail()) {
        std::cout << "输入错误。" << std::endl;
    }

    return 0;
}

在上述代码中,我们使用fail()方法来检查用户输入的整数是否合法。如果输入的不是整数,则输出提示信息,使用clear()方法和忽略缓冲区中的数据,以达到复位输入流的目的。如果输入的字符串格式不正确,我们也会检查并输出相关提示信息。

需要注意的是,在清空输入流缓冲区时,需要使用std::cin.ignore(std::numeric_limitsstd::streamsize::max(), ‘\n’)来忽略缓冲区中的数据。这里使用std::numeric_limitsstd::streamsize::max()可以保证忽略缓冲区中的所有数据,'\n’表示忽略前一个换行符,以避免该字符对后续输入造成影响。

17.3.2、流状态

在进行输入输出操作时,流(如cin和cout)都会维护自己的状态信息。这些状态信息可以通过流对象的成员函数来获取和修改。流状态可通过以下四种标志来描述:

  1. goodbit:表示流状态正常。

  2. eofbit:表示已经读取到输入结束符(如CTRL+D)。

  3. failbit:表示输入操作遇到了不可恢复的错误。

  4. badbit:表示流发生了不可恢复的错误(如文件被删除或写入错误)。

这些标志可以通过流对象的flags()方法查询得到,并且可以通过流对象的clear()方法清除。flags()方法返回一个位掩码,其中每个位表示一个特定的流标志。clear()方法可以用来清除一些标志位。

下面是一个示例代码:

#include <iostream>

int main()
{
    std::cout.flags(std::ios::showbase | std::ios::hex); //设置输出标志

    int num;
    std::cout << "请输入一个十六进制数: ";
    std::cin >> num;

    if (std::cin.fail()) {
        std::cout << "输入错误。" << std::endl;
        std::cin.clear(); //清除错误标志
    }

    std::cout << "输入的数为: " << num << std::endl;

    std::cout.flags(std::ios::dec); //清除输出标志
    return 0;
}

在上述代码中,我们使用std::cout.flags()方法设置输出标志,以控制后面的输出内容。在输入结束后,我们使用std::cin.fail()方法检查输入状态,如果输入错误,则输出提示信息,使用std::cin.clear()方法清楚错误标志,以使输入流正常工作。在输出后,我们使用std::cout.flags()方法清除输出标志,以避免影响之后的输出。

需要注意的是,在使用流状态标志时,我们应该根据需要正确设置和清除,避免出现错误的输出或输入行为。

17.3.3、其他istream类方法

在C++中,除了>>运算符外,istream类还提供了一些其他方法,可以帮助我们更好地控制输入。

  1. get()方法:从输入流中获取一个字符。可以通过重载此方法来读取不同类型的数据,例如get(char&)、get(int&)、get(double&)等。

  2. getline()方法:用于读取一行文本。可以通过重载此方法来读取不同类型的数据,例如getline(char*)、getline(char[], int)、getline(std::string&)等。

  3. ignore()方法:用于从输入流中忽略一定数量的字符。可以通过重载此方法来指定不同的忽略条件,例如ignore(int)、ignore(int, char)、ignore(std::streamsize, int)等。

  4. putback()方法:用于向输入流中压回一个字符,将其放回到读取位置的前面。

下面是一个示例代码:

#include <iostream>
#include <string>

int main()
{
    int num1;
    std::cout << "请输入一个整数: ";
    std::cin >> num1;

    std::string str;
    std::cout << "请输入一行文本: ";
    std::getline(std::cin, str);

    int num2;
    std::cout << "请输入另一个整数: ";
    std::cin >> num2;

    std::cout << "你输入的整数分别是: " << num1 << ", " << num2 << std::endl;
    std::cout << "你输入的文本是: " << str << std::endl;

    std::cin.ignore(); //忽略一个字符
    char ch;
    std::cout << "请输入一个字符: ";
    std::cin.get(ch);
    std::cout << "你输入的字符是: " << ch << std::endl;

    return 0;
}

在上述代码中,我们首先使用std::cin>>运算符读取输入流中的整数。然后使用std::getline()方法读取一行文本。在读取第二个整数之前,需要先使用std::cin.ignore()方法忽略输入流中的一个字符。然后使用std::cin.get()方法读取输入流中的一个字符。最后输出所有读取到的数据。

需要注意的是,在使用ignore()方法时,我们需要指定忽略的字符数量,可以使用std::numeric_limitsstd::streamsize::max()来指定忽略所有字符。在使用get()方法时,需要注意清除输入流中的换行符,可以使用ignore()方法或者std::cin.getline()方法。

17.3.4、其他istrean方法

除了上面提到的方法以外,istream还提供了其他一些用于输入的方法,下面是一些例子:

  1. peek():查看下一个字符,但是并不取走它。

  2. unget():把上一个读进来的字符送回到输入流中。

  3. putback():与unget()类似,也可以重新放回一个字符,不过它还可以用于把多个字符一次性推回流中。

  4. read():可以读入指定数量的字节到一个缓冲区中。

  5. gcount():返回最近一次读入的字符数。

  6. getline():从流中读取一行字符,可以指定分隔符。和前面提到的getline()方法类似。

  7. get(), getchar():用于从流中读取单个字符,和前面提到的get方法类似。

需要注意的是,在使用这些方法时,需要根据实际需要选择合适的方法,并且正确处理输入流中可能出现的各种异常情况。

17.4、文件输入和输出

在C++中,我们可以使用文件输入输出操作来读取和写入文件。文件输入输出操作需要使用fstream头文件中定义的输入输出流对象ifstream和ofstream。

1、ifstream流(输入流)用于读取文件。

ifstream streamName;
streamName.open(“fileName”);

打开文件时,需要指定一个文件名作为参数。如果文件不存在,会返回错误。

在使用ifstream对象读取文件时,可以使用>>运算符,或者getline()方法。

2、ofstream流(输出流)用于写入文件。

ofstream streamName;
streamName.open(“fileName”);

打开文件时,需要指定一个文件名作为参数。如果文件不存在,会创建一个新文件。

在使用ofstream对象写入文件时,可以使用<<运算符。

在使用ifstream和ofstream时,需要在使用完毕后,用close()方法关闭文件。也可以使用构造函数在打开文件的同时进行初始化,并在写入或读取完成后自动关闭文件。

3、fstream流(可读写流)可以读写文件,它既可以作为输入流,也可以作为输出流。

在使用fstream流时,打开文件的方式和ifstream、ofstream相同。

fstream streamName;
streamName.open("fileName", ios::in | ios::out);

打开文件时,可以指定文件打开模式,例如只读、只写、同时读写等模式。具体模式可以在ios命名空间中找到,例如:

ios::in —— 以只读方式打开文件
ios::out —— 以只写方式打开文件
ios::app —— 以只写方式打开文件,并在文件末尾追加新内容
ios::ate —— 以读写方式打开文件,并将文件指针移到文件末尾
ios::trunc —— 如果文件已经存在,先删除文件,然后重新创建

在使用fstream对象读写文件时,也可以使用>>、<<、getline()等方法。

需要注意的是,如果同时使用输入和输出操作,必须在输入和输出操作之间使用seekg()和seekp()方法控制文件指针的位置。另外,在使用文件流时,需要确保文件操作的安全性,如判断文件是否正确打开、关闭文件等操作,以避免可能出现的各种错误。

17.4.1、简单的文件I/O

下面是一个简单的示例代码,展示了如何使用ifstream读取文件,以及如何使用ofstream写入文件:

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

using namespace std;

int main()
{
    /* --- 使用 ifstream 读取文件 --- */

    ifstream fin("input.txt");
    if (!fin)
    {
        cout << "读取文件失败!" << endl;
        return 1;
    }

    int num;
    fin >> num;
    cout << "从文件中读取到的整数为:" << num << endl;

    string str;
    getline(fin, str);
    cout << "从文件中读取到的字符串为:" << str << endl;

    fin.close();


    /* --- 使用 ofstream 写入文件 --- */

    ofstream fout("output.txt");
    if (!fout)
    {
        cout << "写入文件失败!" << endl;
        return 1;
    }

    fout << "Hello world!" << endl;
    fout << "这是一个测试文件。" << endl;

    fout.close();

    return 0;
}

在上述代码中,我们使用了ifstream和ofstream分别读取和写入文件。首先,使用ifstream读取input.txt文件中的一些数据,然后使用 输出语句 在控制台上输出这些数据。紧接着,使用ofstream将一些文本数据写入output.txt文件。

需要注意的是,无论是读取还是写入文件,都需要对文件的打开状态进行检查,以避免出现不必要的错误。在读取数据时,我们使用了两种不同的方法,一种是使用>>运算符读取整数,另一种是使用getline()方法读取字符串。在写入数据时,我们使用<<运算符向文件写入数据。

17.4.2、流状态检查和is_open()

在使用文件输入输出操作时,我们需要考虑各种文件操作可能出现的错误,并进行相应的处理。C++提供了流状态检查和is_open()方法来判断文件操作是否成功。

1、流状态检查

在使用文件流进行操作时,可以通过流对象的状态标志来监测已经发生的错误。流对象有一些标志,用于标记流的状态。其中常用的有三个:

  • eofbit:流上已经到达了文件末尾(End-Of-File),读取操作不能再继续。此时可以使用clear()方法清除eofbit标志。

  • failbit:发生了一个格式化错误。例如,尝试读取一个整数但是输入了一个字符,或者尝试读取一个布尔值但是输入了一个字符串。此时也可以使用clear()方法清除failbit标志。

  • badbit:发生了一个无法恢复的错误。例如,尝试进行文件读取操作,但是文件不存在。此时只能关闭文件,不能再进行其它操作。

我们可以使用good()、eof()、fail()、bad()方法来检查流的状态。

  • good: 如果之前未出现过任何错误,则返回 true。

  • eof: 如果已到流末尾,返回 true。

  • fail: 如果出现了格式化错误,则返回 true。

  • bad: 如果在流操作中出现了严重的错误,则返回 true。

例如:

ofstream outfile("example.txt");
if (outfile.good()) // good()检查文件流是否没有错误
{
    outfile << "Hello world!" << endl;
}
else 
{
    cout << "文件写入失败!" << endl; //可以捕捉到异常或者直接退出程序
}

2、is_open()

除了流状态检查外,我们还可以使用is_open()方法来判断文件是否正确打开。

ifstream fin("example.txt");
if (fin.is_open())
{
    //文件打开成功,进行读取操作
}
else
{
    cout << "文件打开失败!" << endl;
}

在上述代码中,我们首先使用ifstream打开example.txt文件,并使用is_open()方法判断文件是否打开成功。如果打开成功,则可以进行读取操作,否则输出错误信息。在使用完文件输入输出操作后,需要使用close()方法关闭文件。

17.4.3、打开多个文件

在C++中,我们可以打开多个文件,对它们进行读取和写入操作。例如,我们可以同时读取两个文件,将它们的数据进行加和,并将结果写入到一个输出文件中。

我们可以使用独立的输入流和输出流来读写不同的文件,也可以使用同一个文件流来读写多个文件。在对多个文件进行操作时,需要在使用完一个文件后,关闭它并打开下一个文件。

下面是一个简单的示例代码,演示了如何对多个文件进行读写操作:

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    ifstream fin1("input1.txt"); // 打开第一个输入文件
    if (!fin1)
    {
        cout << "第1个输入文件打开失败!" << endl;
        return 1;
    }

    ifstream fin2("input2.txt"); // 打开第二个输入文件
    if (!fin2)
    {
        cout << "第2个输入文件打开失败!" << endl;
        return 1;
    }

    ofstream fout("output.txt"); // 打开输出文件
    if (!fout)
    {
        cout << "输出文件打开失败!" << endl;
        return 1;
    }

    int x, y;
    fin1 >> x;
    fin2 >> y;

    while (!fin1.eof() || !fin2.eof())
    {
        fout << x + y << endl;

        fin1 >> x;
        fin2 >> y;
    }

    fin1.close();
    fin2.close();
    fout.close();

    return 0;
}

在上述代码中,我们首先使用两个ifstream打开input1.txt和input2.txt两个输入文件,使用一个ofstream打开output.txt输出文件。然后,我们先从两个输入文件中获取第一个整数,将它们计算并写入到输出文件中。紧接着,我们使用>>运算符逐行读取文件数据,并将它们相加写入到输出文件中。

在使用完文件后,我们使用close()方法关闭文件。需要注意的是,如果在使用文件输入输出操作过程中出现了任何错误,需要及时关闭文件并处理异常情况,以避免可能出现的各种问题。

17.4.4、命令行处理技术

命令行处理技术是指通过命令行(Command Line)来与计算机进行交互操作的技术。在C++中,我们可以通过命令行参数来控制程序的行为,实现诸如文件输入输出、调试输出等常见操作。

在C++中,main()函数是程序的入口函数,可以接受命令行参数。main()函数的原型为:

int main(int argc, char *argv[]);

其中,argc表示参数的数量,argv是参数的字符串数组。第一个参数argv[0]代表程序本身的名称,后面的参数依次排列。例如,执行以下命令:

$ ./myprog arg1 arg2 arg3

则argc为4,argv数组包含4个元素,分别是"./myprog"、“arg1”、“arg2”、“arg3”。

我们可以使用命令行参数来控制程序的行为。例如,以下是一个简单的示例程序,接受一个文件名参数,读取并打印文件中的内容:

#include <iostream>
#include <fstream>

using namespace std;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Usage: " << argv[0] << " <filename>" << endl;
        return 1;
    }

    ifstream fin(argv[1]);
    if (!fin)
    {
        cout << "无法打开文件 " << argv[1] << endl;
        return 1;
    }

    char ch;
    while (fin.get(ch))
    {
        cout << ch;
    }

    fin.close();

    return 0;
}

在上述代码中,我们首先检查命令行参数的数量,判断是否满足要求。随后,我们使用一个ifstream打开argv[1]指定的文件。如果无法打开文件,程序返回错误并退出。最后,我们使用while循环逐个字符读取文件内容,并将其输出到标准输出中。

通过命令行参数,我们可以方便地控制程序的行为,避免了在程序内部手动输入参数的麻烦。此外,命令行参数还可以在编写自动化脚本等场景中发挥作用,提高了程序的灵活性和可扩展性。

17.4.5、文件模式

在文件输入输出操作时,可以使用不同的文件模式来控制文件的打开方式。

C++中提供的文件模式有以下几种:

  • ios::in:输入模式,用于以读取的方式打开文件。例如,使用ifstream读取文件时使用。

  • ios::out:输出模式,用于以写入的方式打开文件。例如,使用ofstream写入文件时使用。

  • ios::app:追加模式,用于在已有内容的文件尾部追加内容。例如,使用ofstream在文件末尾添加新的内容时使用。

  • ios::binary:二进制模式,用于以二进制形式读写文件。这种模式下,读入和输出的都是二进制数据。

  • ios::ate:文件指针定位在文件尾。使用该模式前,文件指针先移到文件尾,然后进行读写操作。例如,下面的代码将文件指针定位到文件尾,然后输出文件的大小:

ifstream fin("example.txt", ios::ate); // 以"ate"模式打开文件
if (!fin)
{
    cout << "打开文件失败!" << endl;
    return 1;
}

cout << "文件大小为:" << fin.tellg() << " 字节" << endl; // tellg()方法返回指针当前位置距离文件开头的字节数

fin.close();
  • ios::trunc:如果文件存在则清空其内容,如果文件不存在则创建一个新的空文件。

我们可以将多个文件模式用“|”连接起来,表示打开文件时所使用的多个模式。例如,下面的代码表示以输出模式打开文件,并在文件末尾添加新的内容:

ofstream fout("example.txt", ios::out|ios::app);
if (!fout)
{
    cout << "打开文件失败!" << endl;
    return 1;
}

fout << "添加一行新的内容" << endl;

fout.close();

在使用文件模式时,需要注意不同的模式之间的相互制约关系。例如,输出模式ios::out需要清空文件内容,而追加模式ios::app则会保存原有内容而追加新内容。因此,使用多个文件模式时要慎重,确保模式之间没有冲突,以避免程序出现意外行为。

17.4.6、随机存取

随机存取是一种读写文件的方式,可以访问文件中的任意位置而不是仅仅在文件的末尾添加或读取数据。在C++中,我们可以使用流指针来实现随机存取文件。流指针是一种指向流中某个位置的对象,可以通过控制流指针的位置来访问文件中的数据。

C++中提供了以下三种类型的流指针:

  • get指针(istream::pos_type):用于读取文件。

  • put指针(ostream::pos_type):用于写入文件。

  • fpos对象:可用于读取和写入文件。

我们可以使用ifstream、ofstream或者fstream类来创建一个文件流对象,然后使用seekg()和seekp()方法来定位流指针位置,同时使用tellg()和tellp()方法来获取流指针的当前位置。

seekg()和seekp()方法的第一个参数表示要设置的指针位置,而第二个参数则表示查找的起始位置(可选):

//定位get指针
ifstream fin("example.txt");
fin.seekg(0, ios::beg); //将get指针移到文件开头
fin.seekg(10, ios::cur); //将get指针移动10个字节
fin.seekg(0, ios::end); //将get指针移到文件结尾

//定位put指针
ofstream fout("example.txt");
fout.seekp(0, ios::beg); //将put指针移到文件开头
fout.seekp(10, ios::cur); //将put指针移动10个字节
fout.seekp(0, ios::end); //将put指针移到文件结尾

//定位fpos对象
fstream file("example.txt", ios::in | ios::out);
fpos pos = 10; //设置fpos对象的位置为10
file.seekg(pos); //设置get指针的位置为10
file.seekp(pos); //设置put指针的位置为10

使用tellg()和tellp()方法可以分别获得get指针和put指针的当前位置:

ifstream fin("example.txt");
cout << fin.tellg() << endl; //输出get指针当前位置
fin.seekg(10, ios::cur);
cout << fin.tellg() << endl; //输出get指针当前位置
fin.seekg(0, ios::end);
cout << fin.tellg() << endl; //输出get指针当前位置

ofstream fout("example.txt");
fout << "hello, world!" << endl;
cout << fout.tellp() << endl; //输出put指针当前位置
fout.seekp(10, ios::cur);
cout << fout.tellp() << endl; //输出put指针当前位置
fout.seekp(0, ios::end);
cout << fout.tellp() << endl; //输出put指针当前位置

在使用随机存取时,需要注意以下几点:

  1. 文件必须以二进制模式打开,否则在使用随机存取时会出现意外的行为。

  2. 对于get指针和put指针来说,当指针位置超过文件结尾时,文件流将自动调整指针位置,但是对于fpos对象来说,必须手动判断指针位置是否越界。

  3. 在使用随机存取时,要确保读写的数据类型与文件中的数据类型一致,否则将会导致数据损坏或数据类型转化错误。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值