C++--文件操作

文件概念

  • 文件原理: 文件打开都有一个文件指针,该指针的初始位置由I/O方式指定,每次读写都从文件指针的当前位置开始。每读入一个字节,指针就后移一个字节。当文件指针移到最后,就会遇到文件结束EOF(文件结束符也占一个字节,其值为-1),此时流对象的成员函数eof的值为非0值(一般设为1),表示文件结束 了。

  • 文件是程序的一个重要部分。文件一般是存储在外部介质上数据的集合。

  • 操作系统以文件为单位对数据进行管理。

  • 常见文件分类:

    1. 程序文件
      源程序文件(.cpp),目标文件(.obj),可执行文件(.exe)。
    2. 数据文件
      在程序运行时,常常需要将一些数据输出到磁盘上存放起来,以后需要时在读取到计算机内存。
  • 根据文件中数据的组织形式:

    1. ASCII文件
      文本文件或字符文件,每一个字节放一个ASCII代码。
    2. 二进制文件
      字节文件,把内存中的数据按照在内存中的存储形式原样输出在磁盘上存放.
       1. 对于字符来说,在内存中以ASCII码存放,每一个字节放一个ASCII代码,因此无论用ASCII文件输出还是二进制文件输出,其数据形式一样。
       2. 对于数值数据,二者不同。
        比如:整数100000,在内存中占有4个字节,若按内部格式直接输出,在磁盘中占有4个字节,但是转换成ACSII形式输出,6个字符占有6个字节。
      

      在这里插入图片描述

    c++提供了二种I/O:

    1. 高级I/O:
      把若干个字节组合为一个有意义的单位,然后以ASCII字符形式输入输出。

      例如将内存中的数据送到显示器上输出,先把内存中的数据转换成ASCII字符,然后按照整型,浮点型等形式输出。

    2. 低级I/O:
      以字节为单位输入输出,在输入输出过程中不进行格式的转换。是以二进制形式进行的。通常用于在内存和设备之间传输一些字节。

文件流类

  • c++中的cin和cout只能处理标准设备为对象的输入输出,而不能处理以磁盘文件为对象的输出输入。必须另外定义以磁盘文件为对象的输入输出。
  • 文件流:以外存文件为输入输出对象的数据流。
    输出文件流是从内存流向外存文件的数据,
    输入文件流是从外存文件流向内存的数据。
    每一个文件流都有一个内存缓存区与之对应。
  • 文件流本身不是文件,而是以文件为输入输出对象的流。对磁盘文件的读取必须通过文件流实现。
  • 以磁盘文件为对象的输入输出,必须定义文件流对象。文件流对象是文件流类定义的,而不是iostream定义的。

C++ 通过以下几个类支持文件的输入输出:

  1. ifstream类 (从istream类派生的。用于支持从磁盘文件的输入。
  2. ofstream类,从ostream类派生的。用于支持从磁盘文件的输出。
  3. fstream类,从iostream类派生的。用于支持从此磁盘文件的输入输出。

文件的打开

  • 文件输入输出方式设置值
    在这里插入图片描述
    a. 新版本I/O不提供iOS:nocreate和ios::noreplace.
    b. 可以用位或运算符“|”进行输入输出方式的组合。

    ofstream, ifstream 和 fstream所有这些类的成员函数open 都包含了一个默认打开文件的方式,这三个类的默认方式各不相同: 类 参数的默认方式 
    		ofstream ios::out | ios::trunc 
    		ifstream ios::in 
    		fstream  ios::in | ios::out
    只有当函数被调用时没有声明方式参数的情况下,默认值才会被采用。如果函数被调用时声明了任何参数,默认值将被完全改写,而不会与调用参数组合。
    
  • 打开磁盘文件
    打开文件是指在文件读写前做好准备工作:

    1. 为文件流对象和指定的磁盘文件建立关联,以便使文件流流向指定的磁盘文件。
    2. 指定文件的工作方式。是输出还是输入,操作的是二进制文件还是ASCII文件。

    以上工作通过二种方式实现:

    1. 调用文件流的成员函数open。
      一般调用方式:
      文件流对象.open(磁盘文件名,输入输出方式)
      磁盘文件名可以包含路径,若缺省则默认当前目录下的文件。
      若打开失败则open函数返回值为0.
      例如:
      ofstream outfile;   //建立一个文件输出类对象
      outfile.open("f1.dat",ios::out); //使文件流与f1.dat建立联系
      outfile.open("C:\c++\f1.dat",iso::out);
                         iso::out:是I/O的一种,表示以输出方式打开文件。
      
    2. 在定义文件流对象时指定参数
      由于对类ofstream, ifstream 和 fstream 的对象所进行的第一个操作通常都是打开文件,这些类都有一个构造函数可以直接调用open 函数,并拥有同样的参数。这样,我们就可以通过以下方式进行与上面同样的定义对象和打开文件的操作。
      调用形式:
      文件流类 文件流对象名(“文件路径”,输入输出方式)
      若打开失败,流对象返回值为0。
      例如:
      ostream outfile("f1.dat",ios::out);
      
  • 检查一个文件是否已经被顺利的打开
    bool is_open();
    它返回一个布尔(bool)值,为真(true)代表文件已经被顺利打开,假( false )则相反。



关闭磁盘文件

当文件读写操作完成之后,我们必须将文件关闭以使文件重新变为可访问的。关闭文件需要调用成员函数close(),它负责将缓存中的数据排放出来并关闭文件。

这个函数一旦被调用,原先的流对象(stream object)就可以被用来打开其它的文件了,这个文件也就可以重新被其它的进程(process)所有访问了。

为防止流对象被销毁时还联系着打开的文件,析构函数(destructor)将会自动调用关闭函数close。

  1. 调用成员函数,解除关联。
    文件流对象名.close();
  2. 解除当前文件关联,建立与其他文件的关联。
    文件流对象名.close(“新关联文件路径”,输入输出方式);

对ASCII文件的操作

文件的每个字节都以ACSII形式存放,此文件为ASCII文件。
对ASCII读写操作有两种:

  1. 用流插入运算符“<<”和流提取“>>"运算符输入输出标准类型的数据。
    由于ofstream和ifstream从ostream和istream类继承了公用的重载函数,所以对磁盘文件的操作,可以通过文件流对象和流插入,流提取运算符对文件进行读写。

    写入文件内容:

    #include <fstream>
    #include <iostream>
    using namespace std;
    int main() {
    	int a[10];
    	ofstream outfile("f1.dat", ios::out);   //定义文件流对象,打开磁盘文件f1.dat
    	if (!outfile) {                         //若打开失败,返回0
    		cerr << "open error!" << endl;
    		exit(1);
    	}
    	cout << "please enter 10 integer number:" << endl;
    	for (int i = 0; i < 10; i++) {
    		cin >> a[i];
    		outfile << a[i] << " ";            //将键盘输入的数据输出到磁盘文件
    	}
    	outfile.close();                       //关闭文件流
    	return 0;
    

    文件建立在了当前程序存放空间下的地方。若不存在此文件,会新建一个f1.dat文件,若存在,则直接打开此文件并覆盖其内容。
    在这里插入图片描述

    读取文件内容

    #include <fstream>
    #include <iostream>
    using namespace std;
    int main() {
    	int a[10];
    	ifstream infile("f1.dat",ios::in|ios::_Nocreate);    //定义文件流对象,以输入方式打开磁盘文件f1.dat若不存在也不会新建此文件。
    	if (!infile) {
    		cerr << "open error!" << endl;
    		exit(1);
    	}
    	for (int i = 0; i < 10; i++) {
    		infile>> a[i];      //从磁盘读取10个字符,顺序存放在数组中。
    		cout << a[i]<<" ";
    	}
    	cout << endl;
    	infile.close();
    	return 0;
    }
    

    在这里插入图片描述

  2. 用文件流的put,get,getline等成员函数进行字符的输入输出。

    #include <fstream>
    #include <iostream>
    using namespace std;
    //此函数从键盘读取一行字符并把字母存入磁盘文件
    void save_to_file() {
    	ofstream outfile("f2.txt", ios::out);//定义输出流文件对象,以输出方式打开磁盘文件f2
    	if (!outfile) {
    		cerr << "open error!"<<endl;
    		exit(1);
    	}
    	char c[100];
    	cin.getline(c, 100);   //从键盘上读取一行字符
    	for (int i = 0; c[i] != 0; i++) {
    		if (c[i] >= 65 && c[i] <= 90 || c[i] >= 97 && c[i] <= 122) {//判断是否为字母
    			outfile.put(c[i]);//若为字母保存在文件f2中
    			cout << c[i];     //并显示在屏幕上
    		}
    	}
    	cout << endl;
    	outfile.close();
    }
    //此函数读取文件中所以字母,并把所以小写字母转化为大写字母,全部存入f3文件中
    void get_from_file() {
    	char ch;
    	ifstream infile("f2.txt", ios::in | ios::_Nocreate);
    	if (!infile) {
    		cerr << "open f2.txt  error!" << endl;
    		exit(1);
    	}
    	ofstream outfile("f3.txt", ios::out);
    	if (!outfile) {
    		cerr << "open f3.txt error!" << endl;
    		exit(1);
    	}
    	while (infile.get(ch)) {//从磁盘文件读取字符,直到文件结束
    		if (ch >= 97 && ch <= 122)
    			ch -= 32;
    			outfile.put(ch);//读取的单个字符存入f3文件
    			cout << ch;
    	}
    		cout << endl;
    		infile.close();
    		outfile.close();
    }
    //读取磁盘文件内容
    void display_file(string s) {
    	ifstream infile(s,ios::in);
    	if (!infile) {
    		cerr << "open error" << endl;
    		exit(1);
    	}
    	char ch;
    	while (infile.get(ch))
    		cout.put(ch);
    	cout << endl;
    	infile.close();
    }
    int main() {
    	save_to_file();
    	get_from_file();
    	display_file("f3.txt");
    	return 0;
    }
    

    在这里插入图片描述

  • ofstream流对象:
    ofstream 类支持磁盘文件输出
    1. 如果在构造函数中指定一个文件名,当构造这个文件时该文件是自动打开的
      ofstream myFile (“filename”);
    2. 可以在调用默认构造函数之后使用 open 成员函数打开文件
      ofstream myFile ; // 声明一个静态文件输出流对象
      myFile.open (“filename”);// 打开文件,使流对象与文件建立联系
    3. 在构造对象或用 open 打开文件时可以指定模式
      ofstream myFile (“filename”,ios_base ::out | ios_base ::binary);

状态标志符的验证(Verification of state flags)

一些验证流的状态的成员函数(所有都返回bool型返回值):

  • bad()
    如果在读写过程中出错,返回 true 。
    例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。
  • fail()
    除了与bad() 同样情况下会返回 true 以外,加上格式错误时也返回true 。
    例如:当想要读入一个整数,而获得了一个字母的时候。
  • eof()
    如果读文件到达文件末尾,返回true。
  • good()
    这是最通用的:如果调用以上任何一个函数返回true 的话,此函数返回 false 。
    要想重置以上成员函数所检查的状态标志,你可以使用成员函数clear(),没有参数。



对二进制文件的读取

  • 二进制文件将内存中数据存储行式不加转换的存入磁盘文件。称为内存文件的印像文件。内存中数据二进制形式存放,又称字节文件。
  • 读取前的操作:先打开文件,打开时要用iOS::binary指定为以二进制形式传送和存储。二进制文件可以作即输入又输出的文件。这点和ASCII文件不同。
  1. 用成员函数read和write读写二进制文件
    对二进制文件的读写主要用istream类的成员函数read和write来实现

    istream& read(char *buffer,int len);
                       //  read函数的功能是从输入流中顺序读取len个字符,并存入到buffer所指向的字符数组中。它与get函数、getline函数的区别是,不在读取的这些字符的尾部添加一个空字符(’\0’)
       ostream& write(const char * buffer,int len);
       
       buffer 是一块内存的地址,用来存储或读出数据。参数len是一个整数值,表示要从缓存(buffer)中读出或写入的字符数
    

    存二进制文件

      #include <fstream>
      #include <iostream>
      using namespace std;
      struct Student {
      	char name[20];
      	int age;
      	char sex;
      };
      int main() {
      	Student stu[3] = { {"Tom",18,'f' },{"Sun",17,'f'},{"Rain",13,'f'} };
      	ofstream outfile("student.dat", ios::binary);
      	if (!outfile) {
      		cerr << "open error " << endl;
      		abort();
      	}
      	//此处if语句可以改写成write.((char*)&stu[0],sizeof(stu));
      	for (int i = 0; i < 3; i++) {
      		outfile.write((char *)&stu[i], sizeof(stu[i]));
      	}
      	outfile.close();
      	return 0;
      }
    

    在这里插入图片描述

    读取二进制文件

      #include <fstream>
      #include <iostream>
      using namespace std;
      struct Student {
      	char name[20];
      	int age;
      	char sex;
      };
      int main() {
      	Student stu[3];
      	ifstream infile("student.dat", ios::binary);
      	if (!infile) {
      		cerr << "open error " << endl;
      		abort();
      	}
      	for (int i = 0; i < 3; i++) {
      		infile.read((char *)&stu[i], sizeof(stu[i]));
      	}
      	infile.close();
      	for (int i=0; i < 3; i++) {
      		cout << stu[i].name << " " << stu[i].age << " " << stu[i].sex << endl;
      	}
      	return 0;
      	/*
      	Tom 18 f
      	Sun 17 f
      	Rain 13 f
      
      	*/
      }
      ```
    
    

文件指针有关的流成员函数

  • 在磁盘文件中有一个文件读写位置标记来指定当前应进行读写的位置。在从文件输入时每读入一个字节,该位置就往后移动一个字节,该位置就向后移动一个字节。在输出时每向文件输出一个字节,位置标记也向后移动一个字节,随着输出文件中字节不断增加,位置不断后移。

    所有输入/输出流对象(i/o streams objects)都有至少一个流指针:
    ifstream, 类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。 
    ofstream, 类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。 
    fstream, 类似 iostream, 同时继承了get 和 put 
    

    我们可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针:

成员函数 作用
gcount() 得到最后一次输入所读入的字节数
tellg() 不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准) ,就是一个整数,代表当前get 流指针的位置
tellp() 不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准) ,就是一个整数,代表当前put 流指针的位置
seekg(文件中的位置) 流指针被改变为指向从文件开始计算的一个绝对位置。要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同
seekg(位移量,参照位置) 可以指定由参照位置决定的一个具体的指针开始计算的一个位移(offset)
seekp(文件中的位置) 将输出文件位置标记移到指定的位置
seekp(位移量,参照位置)) 以参照位置为基础移动若干字节

参照位置

ios::beg 文件开头(默认)
ios::cur 位置标记当前的位置
ios::end 文件的末尾
  infile.seekg(100): 输入文件位置标记向前移到100字节位置
  infile.seekg(-50,ios::cur):输入文件位置标记从当前位置后移50字节
  infile.seekg(-50,ios::end):输入文件位置标记从文件尾后移50字节
  • 流指针 get 和 put 的值对文本文件(text file)和二进制文件(binary file)的计算方法都是不同的,因为文本模式的文件中某些特殊字符可能被修改。由于这个原因,建议对以文本文件模式打开的文件总是使用seekg 和 seekp的第一种原型,而且不要对tellg 或 tellp 的返回值进行修改。对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。

随机访问二进制数据文件

一般文件的读写时顺序进行的,逐个字节读写。但是对于二进制文件来说,可用成员函数移动指针,随机访问任意位置的数据,还可以修改文件中的数据。

	#include <fstream>
	#include <iostream>
	using namespace std;
	struct Student {
		char name[20];
		int age;
	};
	int main() {
		Student stu[5] = {"A",12,"df",23,"wq",21,"sds",32,"ed",23};
		fstream iofile("student.dat",ios::in|ios::out|ios::binary);
		if (!iofile) {
			cerr << "open error " << endl;
			abort();
		}
		for (int i = 0; i < 5; i++) {
			iofile.write((char*)&stu[i], sizeof(stu[i]));
		}
		Student stu1[5];
		for(int i=0;i<5;i+=2){
	        iofile.seekg(i*sizeof(stu[i]),ios::beg);//定位于0,2,4学生数据开头
			iofile.read((char*)&stu1[i / 2], sizeof(stu1[0]));//先后读入三个学生的数据,存入stu1[0],stu1[1],stu1[2]
			cout << stu1[i/2].name << "   " << stu1[i/2].age << endl;
		}
		cout << endl;
		strcpy_s(stu[2].name, "yj");
		stu[2].age = 18;
		iofile.seekp(2 * sizeof(stu[0]), ios::beg);//定位第3个学生数据开始处
		iofile.write((char*) &stu[2], sizeof(stu[2]));//更新第3个学生数据
		iofile.seekg(0, ios::beg);
		for (int i = 0; i < 5; i++) {
		   iofile.read((char*)&stu[i], sizeof(stu[i]));
		   cout << stu[i].name << "   " << stu[i].age << endl;
	    }
		iofile.close();
		return 0;
		/*
	        A   12
			wq   21
			ed   23
	
			A   12
			df   23
			yj   18
			sds   32
			ed   23
	
		*/
	}
	```
	
举例:
	

```c
#include <iostream>
#include <string>
using namespace std;
#include <fstream>

class Teacher
{
public:
	Teacher() {}
	Teacher(int sno, const char name[]){
		this->sno= sno;
		strcpy_s(this->name, name);
	}
	void prinf(){
		cout << "Teacher name:" << this->name << "   sno:" << this->sno << endl;
	}
private:
	int sno;
	char name[20];
};

int main()
{
	Teacher t1( 1001 ,"Tom");
	Teacher t2( 1002 , "Rain");
	Teacher t3( 1003 , "Sun");
	Teacher t4( 1004 , "Jain");
	fstream fs("teacher", ios::binary | ios::out);
	if (!fs)
	{
		cout << "文件打开失败" << endl;
	}
	fs.write((char *)&t1, sizeof(Teacher));
	fs.write((char *)&t2, sizeof(Teacher));
	fs.write((char *)&t3, sizeof(Teacher));
	fs.write((char *)&t4, sizeof(Teacher));
	fs.flush();
	fs.close();

	fstream fs2("teacher", ios::binary | ios::in);
	if (!fs)
	{
		cout << "文件打开失败" << endl;
	}
	Teacher t;

	fs2.read((char *)&t, sizeof(Teacher));
	t.prinf();
	fs2.read((char *)&t, sizeof(Teacher));
	t.prinf();
	fs2.read((char *)&t, sizeof(Teacher));
	t.prinf();
	fs2.read((char *)&t, sizeof(Teacher));
	t.prinf();
	fs2.close();

	system("pause");
	return 0;
}

在这里插入图片描述


缓存和同步(Buffers and Synchronization)

当我们对文件流进行操作的时候,它们与一个streambuf 类型的缓存(buffer)联系在一起。这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。例如,对于一个输出流, 每次成员函数put (写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中。

当缓存被排放出来(flush)时,它里面的所有数据或者被写入物理媒质中(如果是一个输出流的话),或者简单的被抹掉(如果是一个输入流的话)。这个过程称为同步(synchronization),它会在以下任一情况下发生:
当文件被关闭时: 在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。
当缓存buffer 满时:缓存Buffers 有一定的空间限制。当缓存满时,它会被自动同步。
控制符明确指明:当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush 和endl。

明确调用函数sync(): 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败。

更多: https://www.cnblogs.com/fengliu-/p/7218970.html

猜你喜欢

转载自blog.csdn.net/qq_41498261/article/details/83276784
今日推荐