C++文件操作

C++文件操作

Scroll Down

C++文件操作

简单的概念+记一些小操作+和同学讨论的一些细节
流程:
打开文件-写数据/读数据-关闭文件

文件流类型

注意包含头文件

#include<fstream>

ofstream ofs;输出
ifstream ifs;输入
fstream dataFile;文件流

打开文件

ifstream ifs;
ifs.open(文件名, 打开方式);

这里可以使用绝对路径,但要注意反斜杠转义,如:

ifs.open("c:\\file.txt");

如果就单一个文件名注意是要和源代码在同一目录下

处理文件名的几种方式

ifstream ifs;
char fileName[20];
strcpy(fileName, "file.txt");
ifs.open(fileName);
cout << "输入要打开的文件名字:" ;
cin.getline(fileName, 20);
fs.open(fileName, ios::out);
#define FILENAME "file.txt"
模式含义
ios::app追加模式,文件存在则保留原内容在尾部追加,默认情况下若文件不存在,将创建一个新文件
ios::ate若文件存在,将直接转到文件尾部
ios::binary二进制模式,当以二进制模式打开文件时,将以二进制格式进行读写
ios::in输入模式,从文件中读取数据,若不存在open将失效
ios::out输出模式,向文件写数据,若文件已经存在,文件的内容将被刷新
ios::trunc若文件已经存在,文件的内容将被删除,是ios::out的默认模式

注:模式可以通过 | 符号连接使用
栗子:

dataFile.open("file.txt", ios::in | ios::out);

以输入和输出模式打开该文件,这样文件的内容将会被保留

在定义流对象时打开文件

fstream dataFile("file.txt", ios::in | ios::out);;

测试文件是否打开成功

ifs.open("file.txt", ios::in);
if(!ifs){
    cout << "文件打开失败" << endl; 
    exit(0);
};

if(ifs.fail()){}

if(!ifs.is_open()){}

关闭文件

.close();
文件的最后一个字符是文件结束标记,当关闭文件时,系统会自动在文件的尾部写上这样一个标识文件的结束的字符,不同操作系统,文件结束标志也不相同

流操作符

写+关+追加

fstream dataFile;
dataFile.open("file.txt", ios::out);//打开后写
dataFile << "Word1\n";
dataFile<< "Word2\n";
dataFile.close();
dataFile.open("file.txt", ios::app);//打开后追加
dataFile<<"Word3\n";
dataFile.close();

运行结果

Word1
Word2
Word3

文件中的格式化输出

dataFile << num << endl;
dataFile.precision(5);
dataFile << num << endl;
dataFile.precision(4);
dataFile << num << endl;
dataFile.precision(3);
dataFile << num << endl;
dataFile.close();

将语句精度设置为2

dataFile << setprecision(2);

注:>>通过空白字符(空格、跳格、换行)进行区分数据的

检测文件结束

eof()检测"读指针"是否已经到达文件尾部,若已经达到文件尾部,将返回一个非零值,所以判断/循环语句可以这样写

if(!dataFile.eof()){
    //...
}

while(!dataFile.eof()){
    //...
}

若文件尾另起一个空行,有效的判断方式:

while(!dataFile.eof())
{
    dataFile >> buf;
    if(dataFile.fail()){//判断上一行读取是否失败
        break;
    }
}

这里还是容易产生一点混淆,所以做个实验记录一下结果
当文件中数据无空行且+fail判断
file1.png
当文件中数据有空行+fail判断
file2.png
当文件中数据无空行 无fail判断
file3.png
当文件中数据有空行 无fail判读
file4.png

当文件中有一个整数
图片是一起讨论的友链crux小朋友提供的。。懒得重新去截图了
filee.png
file.png
流先达到了结束符,后fail掉

以上的种种迹象表明文件真正到达结束符之前,还会再进入一次循环
关于EOF的解析:
http://www.ruanyifeng.com/blog/2011/11/eof.html

流对象作为参数

文件流对象必须通过引用的方式传递给函数,不能值传递
原因:随着读写操作的进行,流对象的内部状态不断发生变化,为了保持其内部状态的一致性,应当向函数传递引用

应用:将文件操作弄成函数。。

读写

读文件

采用>>操作符直接读取时会略过空白字符

11 11
12

直接利用>>读出来会变为

111112

getline读文件

getline一次读取文件中的一行字符,包括空白字符

ifs.getline(str, 81, '\n');

解读:字符数组名/指向内存空间的字符指针 从文件中最多读取81-1个字符,若在读满最大字符个数之前遇到界符\n将停止继续读,默认界符也是这个

while(!ifs.eof())
{
    ifs.getline(buf, 81);
    if(ifs.fail())
    {
        break;
    }
    cout << buf << endl;
}

应用:若是要读取有分隔符的,含有空白字符或是一句话的长串数据,可以同个设定特殊的界符来读取

get读文件

从文件中一次读取一个字符,包括空白字符

ifs.get(ch);

所以一般也是配合循环使用了

while(!ifs.eof())
{
    ifs.get(ch);
    if(ifs.fail())
    {
        break;
    }
    cout << ch;
}

写文件

鸡操

ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "blablablac" << endl;
ofs.close();

put写文件

假设按!结束

while(true)
{
    cin.get(ch);
    if(ch == '!')//注意顺序,不会存储到文件中
        break;
    ofs.put(ch);
}

多文件操作

同时打开一个输入文件和输出文件,可以将这种程序看作一种过滤器,处理第一个文件的内容,再写到第二个文件中

实际操作就略掉了

二进制文件

二进制文件中的数据是非格式化的,按照在内存中的存储形式存储,而不是按照ASCII纯文本的形式存储

打开二进制文件

ifs.open("file.dat", ios::out | ios::binary)

通常使用write、read成员函数写数据

ostream& write(const char * buffer,int len);

字符指针buffer指向内存中一段存储空间,len是读写的字节数
read同理

fstream file("bfile.dat", ios::out | ios::binary);
int buffer[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
//写入
file.write((char *)buffer, sizeof(buffer));
file.close();

file.open("bfile.dat", ios::in);
if(file.fail())
{
    cout << "打开文件失败";
    exit(0);
}
//读
file.read((char *) buffer, sizeof(buffer));
for(int count = 0; count < 10;count++)
{
    cout << buffer[count] << " ";
}
cout << endl;
file.close();

运行示例(省略了一些提示词)

首先向文件中写数据...
写数据成功!
打开文件读取数据!
1 2 3 4 5 6 7 8 9 10

这里要注意,如果改成文本文件是会溢出的
假设整数123,当使用<<存储至文件后,也将会转换成文本,即'1','2','3'
所以当企图将上面的代码改造成文本文件方式时,是要注意一些细节的
现在bfile中的内容:

12345678910

坑:
有个小同学获得了这样的奇怪结果

首先向文件中写数据...
写数据成功!
打开文件读取数据!
2147483647 2 3 4 5 6 7 8 9 10

看到这个214783647就很容易联想到溢出了
分析主要代码

while(!file.eof()){
    for(int count=0;count<10;count++){
		file>>buffer[count];
		cout<<buffer[count]<<" ";
	}
}

<<是由空白字符来区分文本的,那么这样读的时候就会当成数字12345678910来读,超出了int的范围,直接拉满值抛出,所以到后面cout出来的2 3 4 5。。。都是取出的原数组的值

补一个读取类内容的操作(在另一篇写了直接copy的)

//1.包含头文件
#include <fstream>
#include <string>

class Person
{
public:
    char m_Name[64];
    int m_Age;
};
//写文件
void test01()
{
    //2.创建输出流对象
    ofstream ofs("person.txt", ios::out | ios::binary);
    //3.打开文件   
    ofs.open("peson.txt", ios::out | ios::binary);
    
    Person p = {"ccc", 18};
    //4.写文件
    ofs.write((const char *)&p, sizeof(p));
    //5.关闭文件
    ofs.close();
}
//读文件
void test02()
{
    ifstream ifs("person.txt", ios::in | ios::binary);
    if(!ifs.is_open())
    {
        cout << "文件打开失败" << endl;
    }
    
    Person p;
    ifs.read((char * )&p, sizeof(p));//&p会返回Person类型,所以需要墙砖
    
    cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;
}

读写结构体

注意:结构体可以是不同数据类型的混合体,必须采用二进制方式操作文件
可以使用 定长块
直接附上一篇例题(感谢上文踩坑的小同学)(没错就是太懒了)

//结构体数据读写文件操作举例
#include<iostream>
#include<fstream>
#include<stdlib.h>
#include<ctype.h>
using namespace std;

struct Info{
	char name[21];
	int age;
	char address[51];
	char phone[14];
	char mail[51];
};

int main()
{
	fstream people("people.dat",ios::out|ios::binary);
	Info person;
	char again;
	
	if(people.fail()){
		cout<<"打开文件people.dat出错!\n";
		exit(0);
	}
	do{
		cout<<"请输入下面的数据:\n";
		cout<<"姓名:";
		cin.getline(person.name,21);
		cout<<"年龄:";
		cin>>person.age;
		cin.ignore();
		cout<<"联系地址:";
		cin.getline(person.address,51);
		cout<<"联系电话:";
		cin.getline(person.phone,14);
		cout<<"E-mail:";
		cin.getline(person.mail,51);
		people.write((char *)&person,sizeof(person));
		cout<<"还要再输入一个学生的数据吗?";
		cin>>again;
		cin.ignore(); 	//略过换行符 
	}while(toupper(again)=='Y');
	people.close();	//关闭文件
	 
	//下面是再次打开文件进行读取数据
	cout<<"\n\n***下面显示所有人的数据***\n";
	people.open("people.dat",ios::in|ios::binary);
	if(people.fail()){
		cout<<"打开文件peole.dat出错!\n";
		exit(0);
	}
	people.read((char *)&person,sizeof(person));
	while(!people.eof()){
		cout<<"姓名:";
		cout<<person.name<<endl;
		cout<<"年龄:";
		cout<<person.age<<endl;
		cout<<"联系地址:";
		cout<<person.address<<endl;
		cout<<"联系电话:";
		cout<<person.phone<<endl;
		cout<<"E-mail:";
		cout<<person.mail<<endl;
		cout<<"按任意键,显示下一个人的记录!\n";
		cin.get(again);
		people.read((char *)&person,sizeof(person));		
	} 
	cout<<"显示完毕!\n";
	people.close();
	return 0;
}

随机访问文件

根据需要访问文件中的数据

定位函数seekp和seekg

找到一篇文章有一些讲解
https://blog.csdn.net/qq_16209077/article/details/52058917

3种随机访问模式的含义

模式含义
ios::beg从文件头开始计算偏移量
ios::end从文件尾开始计算偏移量
ios::cur从当前位置开始计算偏移量

QQ截图20200323154011.png

栗子:
文本内容

abcdefg1234567890
file.seekg(5L, ios::beg);
file.get(ch);
cout << "从文件头开始,5号字节位置上的字符:"<< ch << endl;//f
file.seekg(-10L, ios::end);
file.get(ch);
cout << "从文件尾开始,10号字节位置上的字符:"<< ch << endl;//1
file.seekg(3L, ios::cur);
file.get(ch);
cout << "从当前位置偏移3个字节后,字符:"<< ch << endl;//5

注意:在文件中,开始位置0,20号字节的位置就是21个字节的位置
每当从文件中读取一个字符以后(例如get),文件的读位置指针将自动向前移动一个字节

返回位置函数tellp和tellg

获取当前写指针的位置

long pos = ofs.tellp();

获取当前读指针的位置

long pos = ifs.tellg();