深入理解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())。

相关推荐
ShoreKiten2 小时前
cpp考前急救
数据结构·c++·算法
Byron Loong2 小时前
【基础】c,c++编译过程
c语言·c++
l1t2 小时前
DeepSeek辅助编写埃拉托斯特尼筛法和Atkin筛法求质数程序比较
开发语言·人工智能·python
Hesionberger2 小时前
LeetCode79:单词搜索DFS回溯详解
java·开发语言·c++·python·算法·leetcode·c#
skywalk81633 小时前
下载安装 Temurin® JDK JDK 21 - LTS 速度很慢,有办法加速吗?
java·开发语言
Kiling_07043 小时前
Java方法引用与排序算法精讲
开发语言·python
xyq20243 小时前
AppML 案例未来:探索移动应用机器学习的新篇章
开发语言
MZ_ZXD0013 小时前
springboot音乐播放器系统-计算机毕业设计源码76317
java·c语言·c++·spring boot·python·flask·php
Emberone3 小时前
C++ list 详解:从入门到模拟实现,彻底搞懂双向链表
c++·list