C++文件操作

在C++中,文件操作主要通过<fstream>头文件提供的类来实现,以下是详细介绍:

一、基本概念

1. 流(Stream)的概念

在C++中,文件被视为字节序列的流。输入流用于从文件读取数据,输出流用于向文件写入数据。这种流的概念使得对不同类型设备(如文件、控制台等)的输入输出操作具有统一的接口。

2. 文件类型

(1) 文本文件(Text File)

以人类可读的字符形式存储数据,每行以换行符('\0')结束。例如,使用文本编辑器创建的普通文件。

(2) 二进制文件(Binary File)

以二进制格式存储数据,直接存储变量的二进制表示,而不是将其转换为字符形式。适用于存储诸如图像、音频等非文本数据,或者需要精确存储数据结构的情况。

二、文件操作类

1. ofstream(Output File Stream)用于创建和写入文件。

(1) 创建和打开文件

可以通过构造函数或open方法打开文件。

#include <fstream>
#include <iostream>
int main() {
    std::ofstream outfile("example.txt");
    if (!outfile) {
        std::cerr << "Error opening file for writing" << std::endl;
        return -1;
    }
    std::cout << outfile << std::endl; // 好奇心,看看输出什么值。
    // 写入数据
    outfile << "Hello, World!" << std::endl;
    outfile.close();
    return 0;
}

也可以先创建ofstream对象,再使用open方法打开文件:

std::ofstream outfile;
outfile.open("example.txt");
//...
outfile.close();

(2) 写入数据

使用<<运算符向文件写入各种类型的数据,如整数、字符串等。

int num = 42;
std::string str = "This is a test";
outfile << num << " " << str << std::endl;

2. ifstream(Input File Stream)用于读取文件。

(1) 打开文件和读取数据

同样可以通过构造函数或open方法打开文件:

std::ifstream infile("example.txt");
if (!infile) {
    std::cerr << "Error opening file for reading" << std::endl;
    return -1;
}
std::string line;
while (std::getline(infile, line)) {
    std::cout << line << std::endl;
}
infile.close();

可以使用>>运算符按类型读取数据。

int num;
std::string str;
infile >> num >> str;
std::cout << "Number: " << num << ", String: " << str << std::endl;

3. fstream(File Stream)可以用于同时进行文件的读写操作。

(1) 打开文件和读写操作

std::fstream file("example.txt", std::ios::in | std::ios::out);
if (!file) {
    std::cerr << "Error opening file" << std::endl;
    return -1;
}
int num;
file >> num;
num++;
file.seekp(0, std::ios::beg);
file << num;
file.close();

三、文件操作的常用函数和状态检查

1. 打开模式(Open Modes)

当打开文件时,可以指定不同的打开模式。

(1) std::ios::in 用于以输入模式打开文件,即读取文件内容。

(2) std::ios::out 用于以输出模式打开文件,即写入文件内容。如果文件不存在,则创建新文件;如果文件存在,则清空文件内容。

(3) std::ios::app 以追加模式打开文件,新写入的数据将添加到文件末尾,不会覆盖原有内容。

(4) std::ios::binary 以二进制模式打开文件,用于处理二进制文件。

std::ofstream outfile("binary_file.bin", std::ios::out | std::ios::binary);

2. 状态检查

可以通过检查文件流对象的状态来确定文件操作是否成功。

(1) good() 如果文件流处于正常状态(没有错误发生),则返回true。

std::ifstream infile("example.txt");
if (infile.good()) {
    // 文件打开成功,可以进行读取操作
}

(2) fail() 如果发生了可恢复的错误(如格式错误等),则返回true。

(3) bad() 如果发生了严重的、不可恢复的错误(如磁盘故障等),则返回true。

(4) eof() 当文件读取到达文件末尾时,返回true。

std::ifstream infile("example.txt");
while (!infile.eof()) {
    std::string line;
    std::getline(infile, line);
    std::cout << line << std::endl;
}

3. 文件定位(Seek)

在文件读写过程中,可以使用seekg(用于输入流,设置读取位置)和seekp(用于输出流,设置写入位置)函数来定位文件指针。

std::fstream file("example.txt", std::ios::in | std::ios::out);
file.seekg(0, std::ios::end);
int size = file.tellg();
file.seekp(0, std::ios::beg);

其中tellg用于获取当前读取位置,tellp用于获取当前写入位置。

总结:C++中文件操作的基本流程如下:

第一步、包含头文件

在进行文件操作之前,需要包含<fstream>头文件,这个头文件包含了用于文件输入输出操作的类和相关函数。

、打开文件

1. 选择合适的文件流类

ofstream(输出文件流):用于创建和写入文件。如果文件不存在,它会创建新文件;如果文件存在,根据打开模式可能会覆盖原有内容。

ifstream(输入文件流):用于读取已存在的文件。

fstream(文件流):可以同时进行文件的读写操作。

2. 指定打开模式并打开文件

打开模式(Open Modes)

ios::in: 以输入模式打开文件,用于读取。

ios::out: 以输出模式打开文件,用于写入。如果文件已存在,默认会清空文件内容;若文件不存在,则创建新文件。

ios::app:  以追加模式打开文件,新写入的数据会添加到文件末尾,不会覆盖原有内容。

ios::binary:以二进制模式打开文件,适用于处理二进制数据,如图像、音频文件等。

打开文件的方式

通过构造函数打开

例如,使用ofstream创建并打开一个名为example.txt的文件用于写入:

#include <fstream>
int main() {
    std::ofstream outFile("example.txt");
    // 后续操作
    outFile.close();
    return 0;
}

先创建对象,再使用open函数打开

std::ofstream outFile;
outFile.open("example.txt");
// 后续操作
outFile.close();

、检查文件是否成功打开

1. 使用文件流对象的状态标志

可以通过检查文件流对象的状态来确定文件是否成功打开。

good()函数:如果文件流处于正常状态(没有错误发生),则返回true。例如:

std::ifstream inFile("example.txt");
if (inFile.good()) {
    // 文件打开成功,可以进行读取操作
} else {
    // 文件打开失败,可能是文件不存在或者权限问题等
}

除了good()函数,还有fail()、bad()等状态函数。

fail()在发生可恢复的错误(如格式错误等)时返回true;

bad()在发生严重的、不可恢复的错误(如磁盘故障等)时返回true。

、文件读写操作

1. 写入操作(针对ofstream和fstream

使用<<运算符写入文本数据

例如,向文件写入字符串和整数:

std::ofstream outFile("example.txt");
if (outFile.is_open()) {
    outFile << "Hello, World!" << std::endl;
    int num = 42;
    outFile << num << std::endl;
    outFile.close();
}

写入二进制数据(使用write函数,针对ofstream和fstream在二进制模式下)

例如,将一个整数以二进制形式写入文件:

std::ofstream outFile("binary_example.bin", std::ios::out | std::ios::binary);
int num = 123;
outFile.write(reinterpret_cast<char*>(&num), sizeof(num));
outFile.close();

2. 读取操作(针对ifstream和fstream)

使用>>运算符按类型读取文本数据

例如,从文件读取整数和字符串:

std::ifstream inFile("example.txt");
if (inFile.is_open()) {
    int num;
    std::string str;
    inFile >> num;
    inFile >> str;
    inFile.close();
}

读取二进制数据(使用read函数,针对ifstream和fstream在二进制模式下)

例如,从二进制文件读取一个整数:

std::ifstream inFile("binary_example.bin", std::ios::in | std::ios::binary);
int num;
inFile.read(reinterpret_cast<char*>(&num), sizeof(num));
inFile.close();

第五、文件定位(可选)

1. 使用seekg(针对输入流)和seekp(针对输出流)函数

这些函数用于设置文件指针的位置。例如,将文件指针移动到文件开头或结尾等位置。

seekg示例

假设要从文件中间开始读取数据,可以先将文件指针移动到指定位置:

std::ifstream inFile("example.txt");
inFile.seekg(10, std::ios::beg); // 将文件指针从文件开头移动10个字节
// 然后进行读取操作
inFile.close();

seekp示例

在写入文件时,如果要修改文件中间的内容,可以先将文件指针移动到相应位置再写入:

std::fstream file("example.txt", std::ios::in | std::ios::out);
file.seekp(5, std::ios::beg);
file << "New Data";
file.close();

还可以使用tellg(获取输入流的当前位置)和tellp(获取输出流的当前位置)函数来获取文件指针的当前位置。

第六、关闭文件

1. 使用close函数

在文件操作完成后,需要使用close函数关闭文件。例如:

std::ofstream outFile("example.txt");
outFile << "Some data";
outFile.close();

关闭文件可以释放与文件相关的系统资源,确保数据被正确写入磁盘(对于写入操作),并且使文件可供其他程序或操作使用。

完整实例

以下是一个 C++文件操作的代码示例,包括写入文本文件、从文本文件读取内容以及写入和读取二进制文件:

#include <iostream>
#include <fstream>
#include <string>
// 写入文本文件
void writeTextFile() {
    std::ofstream outFile("text.txt");
    if (outFile.is_open()) {
        outFile << "This is a line of text." << std::endl;
        outFile << "Another line." << std::endl;
        outFile.close();
    } else {
        std::cerr << "Unable to open file for writing." << std::endl;
    }
}
// 读取文本文件
void readTextFile() {
    std::ifstream inFile("text.txt");
    if (inFile.is_open()) {
        std::string line;
        while (std::getline(inFile, line)) {
            std::cout << line << std::endl;
        }
        inFile.close();
    } else {
        std::cerr << "Unable to open file for reading." << std::endl;
    }
}
// 写入二进制文件
void writeBinaryFile() {
    std::ofstream outBinaryFile("binary.dat", std::ios::binary);
    if (outBinaryFile.is_open()) {
        int num = 42;
        outBinaryFile.write(reinterpret_cast<char*>(&num), sizeof(num));
        outBinaryFile.close();
    } else {
        std::cerr << "Unable to open file for writing binary data." << std::endl;
    }
}
// 读取二进制文件
void readBinaryFile() {
    std::ifstream inBinaryFile("binary.dat", std::ios::binary);
    if (inBinaryFile.is_open()) {
        int num;
        inBinaryFile.read(reinterpret_cast<char*>(&num), sizeof(num));
        std::cout << "Read number from binary file: " << num << std::endl;
        inBinaryFile.close();
    } else {
        std::cerr << "Unable to open file for reading binary data." << std::endl;
    }
}
int main() {
    writeTextFile();
    readTextFile();
    writeBinaryFile();
    readBinaryFile();
    return 0;
}

在这个示例中,有四个函数分别用于写入和读取文本文件以及写入和读取二进制文件。在main函数中依次调用这些函数来展示文件操作的过程。

C风格的文件操作库<cstdio>

<cstdio>是C++中的标准输入输出头文件,它提供了一系列用于文件操作和基本输入输出的函数。这个头文件实际上是C标准库<stdio.h>在C++中的兼容版本,包含了很多对文件和控制台进行操作的功能。

1. 文件打开

fopen函数(打开文件)

原型:FILE *fopen(const char *filename, const char *mode);

功能:打开一个名为filename的文件,根据mode指定的模式进行操作。

#include <cstdio>
int main() {
    FILE *fp = fopen("test.txt", "w");
    if (fp == NULL) {
        // 文件打开失败
        perror("fopen");
        return -1;
    }
    // 文件打开成功,后续操作
    fclose(fp);
    return 0;
}

打开模式(mode)有多种:

"r":以只读方式打开文件,文件必须存在。

"w":以只写方式打开文件,如果文件存在则清空内容,如果不存在则创建。

"a":以追加方式打开文件,在文件末尾添加内容,如果文件不存在则创建。

"r +":以读/写方式打开文件,文件必须存在。

"w +":以读/写方式打开文件,若文件存在则清空内容,若不存在则创建。

"a +":以读/写方式打开文件,在文件末尾添加内容,若文件不存在则创建。

2. 文件关闭

fclose函数(关闭文件)

原型:int fclose(FILE *stream);

功能:关闭之前由fopen打开的文件流stream。如果成功关闭,返回0;否则返回EOF(通常定义为 - 1)。

3. 文件读写操作

fgetc函数(读字符)

原型:int fgetc(FILE *stream);

功能:从指定的文件流stream中读取一个字符。返回值为读取到的字符(以unsigned char类型提升为int类型返回),如果到达文件末尾或发生错误则返回EOF。

#include <cstdio>
int main() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) {
        // 文件打开失败
        perror("fopen");
        return -1;
    }
    // 文件打开成功,后续操作
    int c;
    while ((c = fgetc(fp))!= EOF) {
        putchar(c);
    }
    fclose(fp);
    return 0;
}

fputc函数(写字符)

原型:int fputc(int c, FILE *stream);

功能:将字符c(unsigned char类型提升为int类型)写入指定的文件流stream中。如果写入成功,返回写入的字符;如果发生错误则返回EOF。

#include <cstdio>
int main() {
    FILE *fp = fopen("test.txt", "w");
    if (fp == NULL) {
        // 文件打开失败
        perror("fopen");
        return -1;
    }
    // 文件打开成功,后续操作
    fputc('A', fp);
    fclose(fp);
    return 0;
}

fgets函数(读字符串)

原型:char *fgets(char *s, int n, FILE *stream);

功能:从文件流stream中读取最多n - 1个字符到字符数组s中,并在末尾添加'\0'字符。如果读取到换行符' '或者到达文件末尾,读取操作停止。返回值为s(如果成功读取),如果发生错误或者到达文件末尾则返回NULL。

#include <cstdio>
int main() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) {
        // 文件打开失败
        perror("fopen");
        return -1;
    }
    // 文件打开成功,后续操作
    char str[100];
    fgets(str, 100, fp);
    fclose(fp);
    return 0;
}

fputs函数(写字符串)

原型:int fputs(const char *s, FILE *stream);

功能:将字符串s写入到文件流stream中,不自动添加换行符。如果写入成功,返回一个非负整数;如果发生错误则返回EOF。

#include <cstdio>
int main() {
    char str[] = "Hello, World!";
    FILE *fp = fopen("test.txt", "w");
    if (fp == NULL) {
        // 文件打开失败
        perror("fopen");
        return -1;
    }
    // 文件打开成功,后续操作
    fputs(str, fp);
    fclose(fp);
    return 0;
}

fread函数(读二进制数据)

原型:size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

功能:从文件流stream中读取nmemb个大小为size的数据块到ptr指向的内存区域。返回值为实际读取到的数据块数量,如果小于nmemb可能表示到达文件末尾或者发生错误。例如,读取一个整数数组:

#include <cstdio>
int main() {
    FILE *fp = fopen("data.bin", "rb");
    int arr[10];
    size_t n = fread(arr, sizeof(int), 10, fp);
    fclose(fp);
    return 0;
}

fwrite函数(写二进制数据)

原型:size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

功能:将ptr指向的内存区域中的nmemb个大小为size的数据块写入到文件流stream中。返回值为实际写入的数据块数量。例如,写入一个整数数组:

#include <cstdio>
int main() {
    FILE *fp = fopen("data.bin", "wb");
    int arr[] = {1, 2, 3, 4, 5};
    size_t n = fwrite(arr, sizeof(int), sizeof(arr)/sizeof(int), fp);
    fclose(fp);
    return 0;
}

4. 文件定位操作

fseek函数(文件指针定位)

原型:int fseek(FILE *stream, long int offset, int whence);

功能:将文件流stream的文件指针移动到相对于whence指定的位置偏移offset个字节的地方。whence可以取以下值:

SEEK_SET:文件开头,此时offset为从文件开头的偏移量。

SEEK_CUR:文件当前位置,offset为相对于当前位置的偏移量。

SEEK_END:文件末尾,offset为相对于文件末尾的偏移量(偏移量可以为负,表示向前移动)。

#include <cstdio>
int main() {
    FILE *fp = fopen("test.txt", "r+");
    fseek(fp, 5, SEEK_SET);
    fclose(fp);
    return 0;
}

ftell函数(获取文件指针位置)

原型:long int ftell(FILE *stream);

功能:返回文件流stream的文件指针当前相对于文件开头的偏移量(以字节为单位)。如果发生错误,返回 - 1L。

#include <cstdio>
int main() {
    FILE *fp = fopen("test.txt", "r");
    fseek(fp, 0, SEEK_END);
    long int size = ftell(fp);
    fclose(fp);
    return 0;
}

rewind函数(文件指针重置)

原型:void rewind(FILE *stream);

功能:将文件流stream的文件指针重新设置到文件开头,等效于fseek(stream, 0, SEEK_SET)。

#include <cstdio>
int main() {
    FILE *fp = fopen("test.txt", "r +");
    fseek(fp, 5, SEEK_SET);
    // 一些操作后
    rewind(fp);
    fclose(fp);
    return 0;
}

C++文件操作的高级特性

1. 文件缓冲

缓冲的作用:文件缓冲是一种优化文件 I/O 操作的技术,通过在内存中设置缓冲区,将数据先暂时存储在缓冲区中,当缓冲区满或进行特定的操作时,再将缓冲区中的数据一次性写入文件或从文件中读取到缓冲区,减少了频繁的磁盘读写操作,从而提高文件操作的效率。

设置缓冲区大小:在 C++ 中,可以使用 setbuf 或 setvbuf 函数来设置文件的缓冲区大小和模式。

setbuf 函数原型:void setbuf(FILE *stream, char *buf);

其中 stream 是文件指针,buf 是指向缓冲区的指针,如果 buf 为 NULL,则系统会自动分配一个缓冲区。

setvbuf 函数原型: int setvbuf(FILE *stream, char *buf, int mode, size_t size);

mode 可以是 _IOFBF(全缓冲)、_IOLBF(行缓冲)或 _IONBF(无缓冲),size 是缓冲区的大小。

char buffer[1024];
FILE *fp = fopen("file.txt", "w");
setvbuf(fp, buffer, _IOFBF, sizeof(buffer));

2. 文件状态和错误处理

检查文件是否成功打开:在打开文件后,应该检查文件是否成功打开。

例如,使用 ifstream 或 ofstream 打开文件后,可以检查文件流对象的 fail、bad 或 good 函数的返回值来确定文件是否打开成功。如果文件打开失败,可以根据具体情况抛出异常或进行错误处理。

获取文件的状态信息:fstream 类提供了一些成员函数来获取文件的状态信息,如 is_open 函数用于检查文件是否处于打开状态,eof 函数用于检查是否到达文件末尾。在进行文件读取或写入操作时,可以使用这些函数来判断文件的状态,以便正确地处理文件操作。

3. 文件的序列化和反序列化

序列化:将数据结构或对象转换为可以存储或传输的格式(通常是字节流)的过程称为序列化。在 C++ 中,可以通过将数据以特定的格式写入文件来实现序列化。例如,可以定义一个结构体或类,然后将其成员变量的值写入文件。为了方便序列化和反序列化操作,可以重载 << 和 >> 运算符,以便直接使用 ofstream 和 ifstream 进行写入和读取。

反序列化:与序列化相反,反序列化是将存储或传输的格式(字节流)转换回数据结构或对象的过程。在 C++ 中,通过从文件中读取数据并将其填充到数据结构或对象的成员变量中来实现反序列化。在读取数据时,需要按照序列化时的格式进行读取,以确保数据的正确性。

4. 文件的并发访问控制(多线程环境下)

互斥锁和文件锁:在多线程环境下,如果多个线程同时对同一个文件进行操作,可能会导致数据的不一致性或文件损坏。为了避免这种情况,可以使用互斥锁或文件锁来实现对文件的并发访问控制。互斥锁用于保护关键代码段,确保在同一时刻只有一个线程可以访问文件。文件锁则是操作系统提供的一种机制,用于对文件进行加锁和解锁操作,以保证文件的一致性。

异步文件操作:在一些高性能的应用程序中,可以使用异步文件操作来提高文件 I/O 的效率。异步文件操作允许程序在进行文件操作时继续执行其他任务,而不需要等待文件操作完成。C++ 中可以使用操作系统提供的异步 I/O 接口或第三方库来实现异步文件操作。

C++编程语言基础

C++文件操作