根据文件中数据的组织方式,可以将文件分为文本文件和二进制文件。
比如:数字 64 在内存中表示为 0100 0000,若将其保存为 文本文件,则要分别存放十位 6 和个位 4 的 ASCII 码,为 0011 0110 0011 0100,占用两个字节;若将其保存为二进制文件,则按内存中形式直接输出,为 0100 0000,占用一个字节。
文本文件中数据与字符一一对应,一个字节代表一个字符,可以直接在屏幕上显示或打印出来,这种方式使用方便,比较直观,便于阅读,但一般占用存储空间较大,而且输出时要将二进制转化为 ASCII 码比较花费时间。
二进制文件,输出时不需要进行转化,直接将内存中的形式输出到文件中,占用存储空间较小,但一个字节并不对应一个文件,不能直观显示文件中的内容。
文件流是以外存文件未输入输出对象的数据流。输出文件流是从内存流向外存文件的数据,输入文件流是从外存文件流向内存的数据。每一个文件流都有一个内存缓冲区与之对应。
C++有三个用于文件操作的文件类:
ofstream //文件的写操作(输出),主要是从内存写入存储设备(如磁盘),继承了istream类
ifstream //文件的读操作(输入),主要是从存储设备中读取数据到内存,继承了ostream类
fstream //文件的读写操作,对打开的文件可进行读写操作,继承了iostream类
操作的过程:要以磁盘文件(外存文件)为对象进行输入输出,必须定义一个文件流类的对象,通过文件流对象将数据从内存输出到磁盘文件,或者将磁盘文件输入到内存。
定义文件流对象后,我们还需要将文件流对象和指定的磁盘文件建立关联,以便使文件流流向指定的磁盘文件,并确定文件的工作方式(是输入还是输出,二进制还是文本)。我们可以在定义流对象的时候指定参数来调用构造函数,或者通过成员函数open()
来进行文件流对象和指定文件的关联(后面会讲)。
以上,箭头表示继承关系。
以istream
,ostream
,iostream
三者为例,看一下具体的继承关系,如下:
template<typename _CharT, typename _Traits>
class basic_istream : virtual public basic_ios<_CharT, _Traits>;
template<typename _CharT, typename _Traits>
class basic_ostream : virtual public basic_ios<_CharT, _Traits>;
template<typename _CharT, typename _Traits>
class basic_iostream
: public basic_istream<_CharT, _Traits>,
public basic_ostream<_CharT, _Traits>;
/// Base class for @c char streams.
typedef basic_ios<char> ios; //基础类
/// Base class for @c char buffers.
typedef basic_streambuf<char> streambuf;
/// Base class for @c char input streams.
typedef basic_istream<char> istream;
/// Base class for @c char output streams.
typedef basic_ostream<char> ostream;
/// Base class for @c char mixed input and output streams.
typedef basic_iostream<char> iostream;
/// Class for @c char memory buffers.
typedef basic_stringbuf<char> stringbuf;
/// Class for @c char input memory streams.
typedef basic_istringstream<char> istringstream;
/// Class for @c char output memory streams.
typedef basic_ostringstream<char> ostringstream;
/// Class for @c char mixed input and output memory streams.
typedef basic_stringstream<char> stringstream;
/// Class for @c char file buffers.
typedef basic_filebuf<char> filebuf;
/// Class for @c char input file streams.
typedef basic_ifstream<char> ifstream;
/// Class for @c char output file streams.
typedef basic_ofstream<char> ofstream;
/// Class for @c char mixed input and output file streams.
typedef basic_fstream<char> fstream;
可以看到basic_istream
和basic_ostream
都是虚继承于basic_ios
,basic_iostream
是继承于basic_istream
和basic_ostream
,注意这里继承于basic_ios
的时候之所以要用虚拟继承,是为了防止多重继承时,多个父类共用基类产生二义性。
打开文件操作主要是把我们的文件流类对象和一个文件相关联起来,这样这个被打开的文件可以用类对象表示,之后我们对文件流类对象所做的输入和输出操作其实就是对这个文件所做的操作。
open
函数打开一个文件在每个文件流的类中都定义了一个打开文件的成员函数open
,函数原型如下
void open(const char* filename,ios_base::openmode mode);
//参数的含义:
filename: 要打开的文件名
mode: 要打开文件的方式
其中mode
定义在所有IO
的基类中:即ios
类,它包括如下几种方式:
//文件的输入和输出是从内存的角度看的:数据载入内存叫输入,数据从内存到其他地方叫输出。
os::app //追加模式,即所有写入都追加到文件末尾
ios::ate //文件打开后定位到文件尾
ios::binary //以二进制方式打开文件,缺省的方式是文本方式
ios::in //文件以输入方式打开(文件数据输入到内存)(ifstream对象默认方式就是这个)
ios::out //文件以输出方式打开(内存数据输出到文件)(ofstream对象默认的打开方式)
ios::nocreate //不建立文件,所以文件不存在时打开失败
ios::noreplace//不覆盖文件,所以打开文件时如果文件存在失败
ios::trunc //如果文件存在,把文件长度设为0
mode
参数可以组合起来使用,但是两个参数之间必须要用操作符|
隔开,如下
ofstream out; //声明一个ofstream对象out
out.open("text.txt",ios::out|ios::app); //往text.txt文件中输入内容,输入方式在文件的末尾追加内容,且不清空原有的内容
或者
//这个声明方式是调用了ofstream有参构造函数,该构造函数会自动调用open函数。
ofstream out("text.txt",ios::out|ios::app);
类ofstream
, ifstream
和 fstream
的对象所进行的第一个操作通常都是打开文件,这些类都有一个构造函数可以直接调用open 函数,并拥有同样的参数。这样,我们就可以通过以下方式进行与上面同样的定义对象和打开文件的操作:
ofstream file ("example.bin", ios::out | ios::app | ios::binary);
//例如:以二进制输入方式打开文件c:\config.sys
fstream file1;
file1.open("c:\\config.sys",ios::binary|ios::in,0);
//如果open函数只有文件名一个参数,则是以读/写普通文件打开,即:
file1.open("c:\\config.sys");<=>file1.open("c:\\config.sys",ios::in|ios::out,0);
//另外,fstream还有和open()一样的构造函数,对于上例,在定义的时侯就可以打开文件了:
fstream file1("c:\\config.sys");
当我们完成对文件的操作后,需要调用成员函数close
来关闭我们的文件流,close
函数的作用其实就是清空该类对象在缓存中的内容并且关闭该对象和文件的关联关系,那个该对象可以和其他文件进行关联。
ofstream file; //声明一个ofstream对象file
file.open("text.txt",ios::out|ios::app);
file.close(); //关闭"text.txt"文件
为了防止一个类对象被销毁后,还和某个文件保留关联关系,所以文件流类的析构函数都会自动调用close
函数。
对文件读写操作要分两种:文本文件的读写、二进制文件的读写
文本文件的读写很简单:用插入器(<<)向文件输出;用析取器(>>)从文件输入。
插入器(<<) 向流输出数据。比如说系统有一个默认的标准输出流(cout),一般情况下就是指的显示器,所以,cout<<“Write
Stdout”<<‘n’;就表示把字符串"Write Stdout"和换行字符(‘n’)输出到标准输出流。析取器(>>)
从流中输入数据。比如说系统有一个默认的标准输入流(cin),一般情况下就是指的键盘,所以,cin>>x;就表示从标准输入流中读取一个指定类型(即变量x的类型)的数据
比如读取写入txt文件,如下
#include "iostream"
#include
void main()
{
std::fstream f("d:\\test.txt", ios::out);//定义了一个对象f,只写,"d:\\test.txt"文件不存在则创建,存在则清空原内容
f << 1234 << ' ' << 3.14 << 'A' << "How are you"; //写入数据
f.close();//关闭文件以使其重新变为可访问,函数一旦调用,原先的流对象就可以被用来打开其它的文件
f.open("d:\\try.txt", ios::in);//打开文件,只读
int i;
double d;
char c;
char s[20];
f >> i >> d >> c; //读取数据
f.getline(s, 20);
std::cout << i << std::endl; //显示各数据
std::cout << d << std::endl; //endl是一种格式,表示输出一个换行符,并刷新此流
std::cout << c << std::endl;
std::cout << s << std::endl;
f.close();
}
还有一些其他的格式如下
操纵符 | 功能 |
---|---|
dec | 格式化为十进制数值数据 |
endl | 输出一个换行符并刷新此流 |
ends | 输出一个空字符 |
hex | 格式化为十六进制数值数据 |
oct | 格式化为八进制数值数据 |
setpxecision(int p) | 设置浮点数的精度位数 |
也可以利用文件流对象的成员函数 get, put
等,其用法如下:
①put()
put()
函数向流写入一个字符,其原型是ofstream &put(char ch)
,使用也比较简单,如file1.put('c');
就是向流写一个字符'c'
。
②get()
get()
函数比较灵活,有3种常用的重载形式:
一种就是和put()
对应的形式:ifstream &get(char &ch);
功能是从流中读取一个字符,结果保存在引用ch
中,如果到文件尾,返回空字符。如file2.get(x);
表示从文件中读取一个字符,并把读取的字符保存在x
中。
另一种重载形式的原型是: int get();
这种形式是从流中返回一个字符,如果到达文件尾,返回EOF
,如x=file2.get();
和上例功能是一样的。
还一种形式的原型是:
ifstream &get(char *buf,int num,char delim='n');
这种形式把字符读入由buf
指向的数组,直到读入了num
个字符或遇到了由 delim
指定的字符,如果没使用 delim
这个参数,将使用缺省值换行符'n'
。例如:
file2.get(str1,127,'A');
//从文件中读取字符到字符串str1,当遇到字符’A’或读取了127个字符时终止。
二进制文件的操作需要在打开文件的时候指定打开方式为ios::binary
,并且还可以指定为既能输入又能输出的文件,我们通过成员函数 read
和 write
来读写二进制文件。
//这里 buffer 是一块内存的地址,用来存储或读出数据。参数size 是一个整数值,表示要从缓存(buffer)中读出或写入的字符数。
istream& read ( char * buffer, streamsize size);
ostream& write (char * buffer, streamsize size);