c++笔记(10) 文件输入输出
c++ 定义了ifstream, ofstream, fstream类用于文件处理和操作文件,这些类定义在头文件<fstream>中。
c++使用“流”来描述数据流动,数据流向程序,则为input stream(输入流),反之为output stream输出流。
1.文本文件的读写操作。
写入文件
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ofstream output;
output.open("score.txt"); // open a file
output << "zhangjun" << " " << 'S' << " " << 90 << endl;
output << "hehe" << " " << 'L' << " " << 88 << endl;
output.close(); // close the file
cout << "Done" << endl;
return 0;
}
如果文件已经存在,再次打开的话,文件的内容会被清除。
读取文件:
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream input;
input.open("score.txt");
string name;
char med;
int score;
input >> name >> med >> score;
cout << "name is " << name << " char is " << med << " score is " << score << endl;
input >> name >> med >> score;
cout << "name is " << name << " char is " << med << " score is " << score << endl;
input.close();
return 0;
}
关于空格:
读取的数据中是以空格作为分割。如果写入的也有空格,则会导致独处的数据有问题。
2. 检测文件是否存在
fail()函数用于检测文件是否存在!打开文件后可以用fail()函数判断
对上面的代码添加文件检测是否存在:
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream input;
input.open("score.txt");
// if the file exist
if(input.fail())
{
cout << "the file not exist";
return 0;
}
string name;
char med;
int score;
input >> name >> med >> score;
cout << "name is " << name << " char is " << med << " score is " << score << endl;
input >> name >> med >> score;
cout << "name is " << name << " char is " << med << " score is " << score << endl;
input.close();
return 0;
}
3.检测文件结束
在不知道文件有多少行又想读取读取全部数据的话,需要检测文件的结束位置
(1).eof()函数
ifstream input;
input.open("filename");
while(!input.eof())
{
// read data from file;
input >> number;
if(input.eof()) break; // 每读完一次数据,立即检测一次
// other operation .....
}
(2)通过input>>返回的值判断
while(input>>number)
{
// read data from file;
}
input>>number读入数据返回的是一个对象,否则返回的是NULL,据此可以判断文件末尾
注: c++中输入输出流的构造函数参数为c字符串,所以如果文件名问string,要转换c_str()
string filename;
cin >> filename;
input.open(filename.c_str());
4.函数getline, get, put
流提取运算符读取数据,只能以空格作为分隔符,如果读取的数据中含有空格,则应该怎么读取
getline() : <iostream> , 函数参数getline(ifstream, int/string data, delimitChar)
get() // 只能读写单个字符
put() // 只能读写单个字符
例如读取: New york#New Mexico# India
string city
while(!input.eof())
{
getline(input, city, '#'); // 注意delimitChar! 是Char!
cout << city << endl;
}
5.fstream和文件打开模式
fstream创建既能写入又能读出的文件
打开文件的模式:
ios::in 读取模式打开文件
ios::out 写入文件模式
ios::app 追加模式
ios::trunc 如果文件已经存在,丢弃文件内容
ios::binary 二进制输入输出
实例:创建一个文件,写入数据并关闭,再次打开追加数据
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
fstream inout;
// creat a file
inout.open("city.txt", ios::out); //打开文件的模式
// write cities
inout << "Dalloa" << " " << "Hoston" << " " << "Anlantas" << " ";
inout.close();
// open file
inout.open("city.txt", ios::out | ios::app);
// write a line
inout << "Swadas" << " " << "Austin" << " " << "Chicago" ;
inout.close();
// read the file
inout.open("city.txt", ios::in);
string city;
while(!inout.eof())
{
inout >> city;
cout << city << endl;
}
inout.close();
return 0;
}
检测流状态:
c++以提供的检测流状态的函数:
eof()
fail() 文件打开是否成功
bad()
good()
clear()
#include <iostream>
#include <fstream>
using namespace std;
void showState(fstream&);
int main()
{
fstream inout;
//creat an output file
inout.open("temp.txt", ios::out);
inout << "Dallas";
cout << "Normal operation." << endl;
inout.close();
showState(inout);
inout.open("temp.txt", ios::in);
string city;
inout >> city;
cout << "End of file" << endl;
showState(inout);
inout.close();
return 0;
}
void showState(fstream& a)
{
cout << "stream state: " << endl;
cout << "eof(): " << a.eof() << endl;
cout << "fail(): " << a.fail() << endl;
cout << "bad(): " << a.bad() << endl;
cout << "good: " << a.good() << endl;
}
6.二进制输入输出
二进制文件输入输出打开方式
文件可以分为文本文件和二进制文件两类
文本文件:能被文本编辑器处理的文件称为文本文件。 在c++中,扩展名为.txt
二进制文件:非文本文件都称为二进制文件。只能由计算机程序读取处理,在c++中扩展名为.dat, 处理二进制文件效率更高
为了读写二进制文件,必须对流对象使用read(), write()函数
1.write()函数
streamObject.write(const char*, int size): char*: 写入的字符数组 size:写入的字节数
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
fstream binaryio;
binaryio.open("city.dat", ios::out | ios::binary); // 二进制写入, 二进制文件 .dat
string s("Atlant");
binaryio.write(s.c_str(), s.size()); // 转化为c字符串
binaryio.close();
cout << "Done" << endl;
return 0;
}
非字符数据的写入,使用reinterpret_cast运算符实现将一个指针类型转化为与其不相关的指针类型,它只是进行指针值的二进制复制,并不改变指针指向的数据。
reinterpret_cast<datatype*>(address) 允许将任何指针转换为任何其他指针类型
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
fstream binaryio;
binaryio.open("temp.dat", ios::out | ios::binary); // 二进制写入, 二进制文件 .dat
int value = 199;
binaryio.write(reinterpret_cast<char*>(&value), sizeof(value));
binaryio.close();
cout << "Done" << endl;
return 0;
}
2 read()函数
streamObject.read(char* address, int size) // size指定可以读取的最大字节数, gcount() 获取实际读取字节数
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
fstream binaryio;
binaryio.open("city.dat", ios::in | ios::binary); // 二进制读取, 二进制文件 .dat
char s[10];
binaryio.read(s, 10);
s[binaryio.gcount()] = '\0'; // gcount()获取实际读取的字节数 // 设置字符串的末尾'\0'
cout << s << endl;
binaryio.close();
return 0;
}
读取整数;
文件temp.dat中存储的整数转变为二进制字节,现在读取这些字节,转化为整数
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
fstream binaryio;
binaryio.open("temp.dat", ios::in | ios::binary); // 二进制读取, 二进制文件 .dat
int value;
binaryio.read(reinterpret_cast<char*>(&value), sizeof(value));
cout << value << endl;
binaryio.close();
return 0;
}
3. 二进制数组IO
可以使用reinterpret_cast将任意类型的数据转变为二进制字节,也可以将二进制字节转化为任意类型的数据
如:将double数组写入二进制文件,然后再读取出来
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
const int SIZE = 5;
fstream binaryio;
// 想数组中写入数据
binaryio.open("array.dat", ios::out | ios::binary);
double array[SIZE] = {2.3, 4, 3.8, 3.0, 11.11};
binaryio.write(reinterpret_cast<char*>(&array), sizeof(array));
binaryio.close();
// 读取文件中的数据
binaryio.open("array.dat", ios::in | ios::binary);
double result[SIZE];
binaryio.read(reinterpret_cast<char*>(&result), sizeof(result));
binaryio.close();
// display array
for(int i=0; i<SIZE; i++)
{
cout << result[i] << " ";
}
cout << endl;
return 0;
}
二进制对象IO
如何向二进制文件写入对象,以及从二进制文件读出
定义一个Student类, 将类写入文件和从文件中读出
代码:
student.h
#include <iostream>
#include <string>
using namespace std;
#ifndef STUDENT_H
#define STUDENT_H
class Student
{
private:
char firstName[25];
char mid; // 中间名
char lastName[25];
int score;
public:
Student(); // 无参构造函数
Student(const string& firstName, const char mid, const string& lastName, int score); // 有参构造函数
// 定义访问器和更改器
void setFirstName(const string& firstName);
void setMid(const char mid);
void setLastName(const string& lastName);
void setScore(int score);
string getFirstName() const;
char getMid() const;
string getLastName() const;
int getScore() const;
};
#endif
student.cpp
#include <iostream>
#include <string>
#include <cstring>
#include "E:\back_up\code\c_plus_code\chapter5\external_file\student..h"
using namespace std;
Student::Student()
{
// all the data remain default value;
}
Student::Student(const string& firstName, const char mid, const string& lastName, int score)
{
setFirstName(firstName);
setMid(mid);
setLastName(lastName);
setScore(score);
}
// mutator
void Student::setFirstName(const string& firstName)
{
strcpy(this->firstName, firstName.c_str()); // string 类型的值复制到char[]
}
void Student::setMid(const char mid)
{
this->mid = mid;
}
void Student::setLastName(const string& lastName)
{
strcpy(this->lastName, lastName.c_str()); // strcpy()函数的参数:,char[], string.c_str()才可以
}
void Student::setScore(int score)
{
this->score = score;
}
string Student::getFirstName() const
{
return string(firstName); // 将char[]转换为string
}
char Student::getMid() const
{
return mid;
}
string Student::getLastName() const
{
return string(lastName); //
}
int Student::getScore() const
{
return score;
}
// accessor
main.cpp
#include <iostream>
#include <fstream>
#include <string>
#include "E:\back_up\code\c_plus_code\chapter5\external_file\student..h"
using namespace std;
void displayStudent(const Student&);
int main()
{
Student student1("Jhoon", 'K', "jerry", 90);
Student student2("Mary", 'L', "Charlotte", 100);
Student student3("Koeras", 'B', "Long", 78);
Student student4("Maksa", 'C', "short", 90);
fstream binaryio;
binaryio.open("student.dat", ios::out | ios::binary); // 创建文件,写入数据
binaryio.write(reinterpret_cast<char*>(&student1), sizeof(Student));
binaryio.write(reinterpret_cast<char*>(&student2), sizeof(Student));
binaryio.write(reinterpret_cast<char*>(&student3), sizeof(Student));
binaryio.write(reinterpret_cast<char*>(&student4), sizeof(Student));
binaryio.close();
binaryio.open("student.dat", ios::in | ios::binary); // 这种读取数据的方法,文件指针自动下移
Student student;
binaryio.read(reinterpret_cast<char*>(&student), sizeof(Student));
displayStudent(student);
binaryio.read(reinterpret_cast<char*>(&student), sizeof(Student));
displayStudent(student);
return 0;
}
void displayStudent(const Student& student)
{
cout << student.getFirstName() << " " << student.getMid() << " " << student.getLastName() << " " << student.getScore() << endl;
}
注:
student类中lastName和firstName的数据类型为固定字符长度的数组,char[25], 而不用string, 是因为在reinterpret(char*, size)时。size = sizeof(Student)保证每个学生记录大小相同,保证正确读取学生数据,如果采用string则不能保证。
char[] 与string类型之间的转化:
string(char[]) 类型转化
strcpy(string, char[].c_str()) // 值复制
4. 随机访问文件
使用seekg()函数和seekp()函数移动文件指针到任意位置。
文件由字节序列组成,操作系统中会维护一个文件指针的特殊标记,指向序列中的某个位置,读写操作都是从文件指针指向的位置处进行。
文件的访问分为:
1. 顺序访问文件 文件指针位于文件开始的地方。向后移动
2.随机访问文件 读取文件的数据时,如果想跳过前面的数据项,访问某一项数据,可以采用随机访问文件。‘
c++允许对流对象使用seekp()和seekg()函数,移动文件的指针到任意的位置
seekp() : seek put 用于输出流
seekg() : seek get 用于输入流
seekg(pos): 将指针移动到绝对位置pos
seekg(long a, pos): pos是相对位置, a是偏移量, 注意偏移量的单位是字节 pos: :ios:beg, ios::end, ios::cur
tellp:
tellg: 返回文件指针的当前位置
修改上面的student代码
只对main.cpp作如下修改:
#include <iostream>
#include <fstream>
#include <string>
#include "E:\back_up\code\c_plus_code\chapter5\external_file\student..h"
using namespace std;
void displayStudent(const Student&);
int main()
{
Student student1("Jhoon", 'K', "jerry", 90);
Student student2("Mary", 'L', "Charlotte", 100);
Student student3("Koeras", 'B', "Long", 78);
Student student4("zhuwen", 'F', "short", 90);
Student student5("xiang", 'F', "short", 90);
Student student6("gouhao", 'S', "short", 90);
Student student7("crappple", 'C', "short", 90);
fstream binaryio;
binaryio.open("student.dat", ios::out | ios::binary); // 创建文件,写入数据
binaryio.write(reinterpret_cast<char*>(&student1), sizeof(Student));
binaryio.write(reinterpret_cast<char*>(&student2), sizeof(Student));
binaryio.write(reinterpret_cast<char*>(&student3), sizeof(Student));
binaryio.write(reinterpret_cast<char*>(&student4), sizeof(Student));
binaryio.write(reinterpret_cast<char*>(&student5), sizeof(Student));
binaryio.write(reinterpret_cast<char*>(&student6), sizeof(Student));
binaryio.write(reinterpret_cast<char*>(&student7), sizeof(Student));
binaryio.close();
binaryio.open("student.dat", ios::in | ios::binary); // 打开文件,指针这时位于文件起始的位置
cout << "current position is " << binaryio.tellg() << endl; // 当前文件中指针的位置
Student student;
// 读取第三个数据,跳过前两个
binaryio.seekg(2*sizeof(Student)); // 将指针向后移动两个数据项
cout << "current position is " << binaryio.tellg() << endl; // 当前文件中指针的位置
binaryio.read(reinterpret_cast<char*>(&student), sizeof(Student)); // 读取的是第三项数据
displayStudent(student);
cout << "current position is " << binaryio.tellg() << endl; // 当前文件中指针的位置
// 指针的当前位置
binaryio.seekg(2*sizeof(Student), ios::cur); // 读取第七条数据 // 偏移量单位是字节,所以向后移动两项数据,偏移量应该是n*sizeof(Student)
cout << "Current position is " << binaryio.tellg() << endl;
binaryio.read(reinterpret_cast<char*>(&student), sizeof(Student));
displayStudent(student);
cout << "Current position is " << binaryio.tellg() << endl;
return 0;
}
void displayStudent(const Student& student)
{
cout << student.getFirstName() << " " << student.getMid() << " " << student.getLastName() << " " << student.getScore() << endl;
}
运行结果:
分析:
1. 首先将七个student对象依次写进二进制文件
2. 打开二进制文件,此时文件指针位于文件的开头,指向的是第一个数据, position = 0
3. 将文件指针向后移动两个数据项 binaryio.seekg(2*sizeof(Student)); position = 112
4.读取数据项,读到的是第三个数据项,可以看到读取完毕后文件指针后移到下一个数据项, position = 168
5.再将文件指针从当前位置向后移动两个位置,读取数据项
6. 更新文件:
可以使用组合模式打开文件。如:按照读写模式打开一个二进制文件,对文件进行更新
对上面的程序做出修改:
#include <iostream>
#include <fstream>
#include <string>
#include "E:\back_up\code\c_plus_code\chapter5\external_file\student..h"
using namespace std;
void displayStudent(const Student&);
int main()
{
fstream binaryio;
binaryio.open("student.dat", ios::out | ios::in | ios::binary); // 以读写模式打开二进制文件
Student student_new;
binaryio.seekg(sizeof(Student)); // 文件指针后移一个位置
binaryio.read(reinterpret_cast<char*>(&student_new), sizeof(Student));
displayStudent(student_new);
student_new.setFirstName("SHI");
student_new.setLastName("gououou");
student_new.setScore(100);
binaryio.seekg(sizeof(Student)); // 因为读完数据后文件指针下移,所以需要将文件指针拉回到原处再写入修改后的数据,达到文件更新的目的
binaryio.write(reinterpret_cast<char*>(&student_new), sizeof(student_new));
binaryio.close();
binaryio.open("student.dat", ios::in | ios::binary); // 打开文件,指针这时位于文件起始的位置
Student student;
binaryio.seekg(sizeof(Student)); // 读取修改后的数据 定位
//cout << "current position is " << binaryio.tellg() << endl; // 当前文件中指针的位置
binaryio.read(reinterpret_cast<char*>(&student), sizeof(Student)); // 读取的是第三项数据
displayStudent(student);
//cout << "current position is " << binaryio.tellg() << endl; // 当前文件中指针的位置
binaryio.close();
return 0;
}
void displayStudent(const Student& student)
{
cout << student.getFirstName() << " " << student.getMid() << " " << student.getLastName() << " " << student.getScore() << endl;
}
需要注意的是对文件修改时,必须保证指针指向数据的正确性,否则数据没有修改,却把别的数据给覆盖了