深入理解C++文件操作

一.C++文件操作基础

C++ 的文件操作主要基于(stream)机制,通过标准库中的 <fstream> 头文件来实现。相比于 C 语言的文件操作,C++ 的流式操作更加面向对象,也更安全易用。

1.什么是流

所谓流,是对数据传输过程的一种高度抽象,流就像一根数据管道,数据就像管道中的水流一样,具有方向性、有序且连续地从一端流向另一端。

从底层来看,流本质上是一个类,假如说,我们进行cout << 'A'操作,那么ostream 类的 << 运算符被调用,然后将A扔给其内部关联的 streambuf 对象的成员函数(sputc('A')),streambuf主要处理缓冲区相关,sputc 会先判断缓冲区满是否已经满了,没满则直接写入缓冲区,满了,则把整个缓冲区积攒的数据,通过一次性的系统调用全部刷入外部设备,在将A存放进去。

根据数据流动的方向,流主要分为两类:

输入流 :数据从外部设备(如键盘、文件)流向程序内存。核心操作符是提取运算符 >>。

输出流:数据从程序内存流向外部设备(如屏幕、文件)。核心操作符是插入运算符 <<。

在C++中,根据数据来源和去向的不同,标准库将流分成了三大类:

标准的I/O流:也就是我们经常使用的cin/cout。

文件流:ifstream/ofstream,就是C++文件操作的基础。

字符串流:istringstream / ostringstream,与文件流十分相似。

常用的文件打开方式:

ios::in:只读模式。文件不存在则打开失败。

ios::out:只写模式。文件不存在则创建;文件已存在则默认清空原有内容。

ios::app:追加模式。写入的内容会添加到文件末尾,不会清空原内容。

ios::ate:打开文件后,文件指针直接定位到文件末尾。

ios::trunc:打开文件时,如果文件存在,则清空其内容(ios::out 默认包含此模式)。

ios::binary:以二进制模式打开(默认是文本模式),不进行换行符等特殊字符的转换。

2.文件操作相关的流

都定义在<fstream>头文件中。

ifstream:输入流,默认使用ios::in打开方式

**ofstream:**输出流,默认使用ios::out打开方式

fstream:文件流,无默认模式,同时支持读和写操作

二.普通文件操作的具体流程

1.写文件

cpp 复制代码
//创建输出流对象并打开文件(如果文件不存在会自动创建)
ofstream outFile("test.txt", ios::out);//这里的打开方式也可以不加,使用默认的
///检查文件是否成功打开
if (!outFile.is_open())
{
	throw "文件打开失败";
}
//写文件
outFile << "ABCD" << endl;
//关闭文件
outFile.close();

当然这里我们也可以使用ofstream,但是这样的话不能省略打开方式。

2.读文件

cpp 复制代码
//创建输入流对象并打开文件
ifstream inFile("test.txt", ios::in);//这里的打开方式也可以不加,使用默认的
///检查文件是否成功打开
if (!inFile.is_open())
{
	throw "文件打开失败";
}
//读文件(按行读取)
string line;
while (getline(inFile, line)) {
	cout << line << endl;
}
//关闭文件
inFile.close();

类似的,我们也可以使用ofstream,但是这样的话不能省略打开方式。

关于这里的读取方式,其实存在许多种,除了使用(getline)按行读取,我们还可以使用>>操作符读取,但是>>操作符无法读取空格(空格会被当作分隔符):

cpp 复制代码
string word;
while (inFile >> word) {
	cout << word << endl;
}

我们还可以使用get,按字符读取,包括空格和换行符,但这样相较于getline,函数调用次数过多,效率较低:

cpp 复制代码
char c;
while (inFile.get(c)) {
	cout << c;
}

三.二进制文件的文件操作

对于复杂数据结构的文件读写操作,我们一般使用二进制的文件读写方式,相较于普通的文件读写方式,使用二进制,可以提高读写的速度,减少文件的大小,确保数据的绝对精确(主要对于浮点数,由于进制转换的先天缺陷)。

二进制的读写必须使用write与read函数,write(数据的地址, 数据的字节大小),read(存放数据的地址, 数据的字节大小)

例如,我们想要将一个Student对象的信息写进文件中:

cpp 复制代码
struct Student
{
	char _name[20];
	int _age;
};

int main()
{
	Student s1 {"zhangsan",18};
	//指定多种打开方式时,可以使用"|"
	ofstream outFile("Student.dat", ios::out | ios::binary);
	if (!outFile.is_open())
	{
		throw "文件打开失败";
	}
    //使用 reinterpret_cast<const char*> 将结构体地址转为字节指针
	outFile.write(reinterpret_cast<const char*>(&s1), sizeof(Student));
	outFile.close();

	Student s2;//准备一个空的结构体用来接收数据
	ifstream inFie("Student.dat", ios::in | ios::binary);
    //使用 reinterpret_cast<char*> 将结构体地址转为字节指针
	inFie.read(reinterpret_cast<char*>(&s2), sizeof(Student));
    //检查实际读取的字节数是否符合预期(防止文件损坏或读取不完整)
	if (inFie.gcount() == sizeof(Student))
	{
		cout << "name:" << s2._name << endl
			<< "age:" << s2._age<<endl;
	}
	else
	{
		cerr << "读取数据不完整" << endl;
	}
	inFie.close();
}

实际上对于二进制读写类,我们一般要求这个类其需要是POD类型(普通的旧式数据),POD类型要求这个类没有构造,析构,拷贝构造,赋值引用,不能有虚函数或虚基类,所有非静态数据成员必须有相同的访问控制权限(即与C语言的结构体相同)。

注意:对于包含动态数据的复杂类型(vector,string等),绝对不能直接写入文件,解决办法是先写入字符串的长度(size_t),再写入字符串的实际内容(data())。

相关推荐
王老师青少年编程1 小时前
2025年【江苏“信息与未来”编程思维】真题及题解(T2:坐标变换)
c++·题解·真题·坐标变换·编程思维·江苏·信息与未来
西门吹-禅4 小时前
java springboot N+1问题
java·开发语言·spring boot
skywalk81634 小时前
设计并实现段言的 C FFI 绑定机制 @Trae
c语言·开发语言·python·编程
2023自学中5 小时前
C++ 内存追踪器
linux·c++
IT笔记6 小时前
【Rust】Rust Match 模式匹配详解
java·开发语言·rust
2zcode6 小时前
免费开源项目文档:基于MATLAB卷积神经网络的口罩佩戴检测系统
开发语言·matlab·cnn
逝水无殇6 小时前
C# 运算符重载详解
开发语言·后端·c#
TPBoreas6 小时前
配置信息防泄露方案:.env 环境隔离详解(dotenv-java)
java·开发语言
敲代码的嘎仔7 小时前
实习日志day6--实习日志day6--title命名规范化&businessType纠正&补充缺失的@Log注解&报警与通信模块补充&产出阶段总结文档
java·开发语言·人工智能·git·python·实习·大二
江华森7 小时前
Python 实现高德地图找房(一):环境搭建与数据爬虫
开发语言·爬虫·python