- 概述
一、何为I/O
外围设备分为,存储设备和输入/输出设备;
存储设备用于存储信息,如磁盘、U盘、光盘、磁带等,数据以文件形式保存在这些存储设备中;
输入/输出设备分为,输入设备和输出设备;输入设备指计算机接收数据的来源设备,如键盘、鼠标、扫描仪等;输出设备指计算机处理完毕的数据送往外部设备,如显示器、打印机等;
I/O的中心是内存;在内存中,数据往外输送,即为输出;数据从外面进来,即为输入;
二、标准I/O流cin和cout
头文件iostream中,定义了两个流类:输入流类istream和输出流类ostream,且用这两个类定义了流对象cin和cout:
istream cin; ostream cout;
cin和cout分别是输入数据流和输出数据流;在默认情况下,cin与键盘关联,键盘中输入的数据进入cin;cout与显示器关联,cout中的数据输出到显示屏上;
举例:
int someInt; float someFloat; char someChar; cin>>someInt>>someFloat>>someChar; // 通过键盘键入12 3.14 9<回车> cout<<"The answer is: "<<someInt*someFloat;
分析:
cin和cout都只能装载字符,因此输入操作符">>"和输出操作符"<<"都要负责转化工作:
a 对于cin,键盘键入的字符逐个进入cin。若接收这些数据的变量不是字符型变量或字符串变量,则">>"将根据变量类型,把cin的字符组合转化为该变量类型的值,再赋值给该变量。如该例第一个输入的变量是int类型,">>"把"1"和"3"这样的字符组合转化为整型值13,再赋值给someInt;第二个输入">>"把'3'、'.'、'1'和'4'转化为double值3.14,再赋值给someFloat;
b cin中每个字符都是”平等“的,都是按先后顺序等待着每次输入">>"消化掉前面一些字符,则每次输入是消化掉cin中哪些字符呢?每次">>"都是一次输入,且多次输入可连续进行。输入由两部分组成,键盘的数据进入cin和cin的数据进入变量,两个部分均完成才结束由多次输入组成的整个输入状态。每次输入在碰到空白符(空格、制表符、换行符),或碰到第一个非法字符(该数据类型中不能出现的字符)时结束。整个输入状态在键入了回车且已输入的数据足以满足所有输入时结束;
一般,整形变量接收"0"~"9"十个字符;浮点型变量接收这十个字符外,还接收".";字符型变量接收非空白符;
c 对于cout,输出操作符"<<"把数据项转换为字符组合,再置入cout中。如该例要输出的是double值40.82,"<<"将其转换为'4'、'0'、'.'、'8'和'2',再输出到显示器上;
三、文件I/O流
文件I/O也称读文件(输入)和写文件(输出);C++标准库中提供两个类ifstream和ofstream,分别用于文件输入和输出;
举例1:
事先准备好一个纯文本文件source.txt,保存在与下面fileIO.cpp同一个文件夹中,其内容为:12 3.14 9;编写程序,把该文件的内容读入一个int变量、一个float变量和一个char变量中;
/* 利用文件流实现文件I/O */ #include <fstream> // ① using namespace std; int main() { int someInt; float someFloat; char someChar; ifstream inFile; // ② ofstream outFile; // ② inFile.open("source.txt"); // ③ outFile.open("result.txt"); // ④ inFile >> someInt >> someFloat >> someChar; // ⑤ outFile << "The answer is: " << someInt*someFloat << endl; // ⑥ inFile.close(); // ⑦ outFile.close(); // ⑦ system("pause"); return 0; }
执行结果保存在result.txt中:The answer is: 40.82;
分析:
a 由于类ifstream和ofstream定义在fstream中,在文件头需要加上预编译指令①;
b ②语句定义了两个对象inFile和outFile,称为文件流对象,inFile负责文件输入,outFile负责文件输出;
c ③和④语句文件流对象与就具体的文件关联起来,使后续的具体读写操作作用于这些文件上。③把输入文件流对象inFile与文件sourcce.txt关联起来,后面从inFile输入数据便是从文件里读取数据;同理,④把输出文件流对象outFile与文件result.txt关联起来,后面输出的结果放入outFile中最终就会保存在result.txt里;
d 对于输入流,若相关联的文件不存在,则下面e中所介绍的都操作将不起任何作用;对于输出流,若相关联的文件不存在,将创建该文件;若该文件存在,先自动清空文件的原有内容,再e中介绍的写操作再把新内容写入文件;
e ⑤和⑥是具体的读写操作,⑤是从输入文件流inFile中读取数据,置入变量中,⑥是把要输出的内容放入输出文件流中,最终保存到文件result.txt中;
举例2:
假设已有纯文本文件1.txt,先编写程序将1.txt复制到另一个纯文本文件2.txt中;
/* 利用文件流实现文本文件的复制 */ #include <fstream> using namespace std; int main() { char ch; ifstream inFile; ofstream outFile; inFile.open("1.txt"); outFile.open("2.txt"); inFile >> ch; // ① while (inFile) // ② { outFile << ch; inFile >> ch; // ③ } inFile.close(); outFile.close(); system("pause"); return 0; }
本段程序利用循环,逐个读取1.txt的字节,并输送给2.txt,达到复制目的;
利用inFile是否进入失效状态来判断1.txt是否已经读取完毕!!!
在inFile中装载的是一个一个字符,通过①和③的读取,每次从inFile中消耗掉一个字符,此时inFile一直处于正常状态;当读取完最后一个字符后,假如再次企图读取,此时inFile中已无字符,这个读取的大动作会使inFile进入失效状态。因此通过②的判断,得知文件是否读取完毕;
假如1.txt内容是:
I am in Nanjing.
I like baking and sports.
运行上述程序后,将得到2.txt,打开该文件其内容是:
IaminNanjing.Ilikebakingandsports.
可以看到,1.txt中的换行符和空格符都没有复制到2.txt中,因为输入符">>"会忽略掉空白字符!!!
为使1.txt中内容完整复制到2.txt中,修改程序如下:
/* 利用文件流实现文本文件的完整复制 */ #include <fstream> using namespace std; int main() { char ch; ifstream inFile; ofstream outFile; inFile.open("1.txt"); outFile.open("2.txt"); inFile.get(ch); // ① while (inFile) // ② { outFile << ch; inFile.get(ch); // ③ } inFile.close(); outFile.close(); system("pause"); return 0; }
分析:
语句①中的get函数是输入流的公有成员函数,作用是按顺序获取输入流中的一个字符,包括空白字符都会被获取,并存放在参数变量ch中。
- 二进制文件I/O
文本文件I/O不是文件I/O的主流,而是二进制文件I/O;
一、文本文件I/O VS 二进制文件I/O
文本文件输出,需要先将输出值转化为字符序列,再存储到文件中的内容是这些字符相应地ASCII码;文本文件输入,先将字符序列转化为相应的数值,再进入变量;
二进制文件输出,是直接将内存数据(二进制形式)输出到文件中保存;二进制文件输入,直接将文件内容输入内存中即可,无需转化过程。
一般,二进制文件比文本文件体积小,且二进制文件I/O省去”转化“,大大节约文件I/O的时间。
但文本文件I/O有一个优点:方便打开文本文件、直接看到结果,因此一般用于保存一些简单的结果数据用以方便查阅。
二、二进制文件I/O
1 利用ifstream和ofstream进行二进制文件I/O
举例:
/* 利用ifstream和ofstream进行二进制文件I/O */ #include <iostream> #include <fstream> using namespace std; int main() { int a[10] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; int b[10]; int i; ifstream inFile; // ① ofstream outFile; // ① outFile.open("1.dat", ios::binary); // ② for (i = 0; i < 10; i++) { outFile.write((char*)&a[i], sizeof(a[i])); // ③ } outFile.close(); // ④ inFile.open("1.dat", ios::binary); // ② for (i = 0; i < 10; i++) { inFile.read((char*)&b[i], sizeof(b[i])); // ⑤ } inFile.close(); // ④ for (i = 0; i < 10; i++) cout << b[i] << " "; cout << endl; system("pause"); return 0; }
运行程序屏幕显示:10 20 30 40 50 60 70 80 90 100
且在该cpp同目录下将出现一个共有40字节的1.dat,表示数组a个元素已写入该文件中,在程序中又将该文件的内容读取到b数组中;
分析:
a 利用语句①创建输出文件流或输入文件流对象,并利用语句②使得文件流对象与某个文件相关联;ifstream和ofstream类的open函数原型分别如下,
void open(const char* szName, int nMode=ios::in, int nProt=filebuf::openprot); void open(const char* szName, int nMode=ios::out, int nProt=filebuf::openprot);
对于输入文件流,szName是输入文件名;对于输出文件流,szNAme是输出文件名。
nMode指定文件的打开模式,由ios类中定义的一组枚举常量表示;在默认情况下,输入文件流的nMode为ios::in,输出文件流的nMode为ios::out,表示文件I/O方式为文本文件I/O;若进行二进制文件I/O,将其设置为ios::binary。
nProt指定文件的保护方式,如只读、隐含等,一般情况下使用默认值即可。
b 语句③和⑤进行文件写和读的具体操作。对于write函数,第一个参数表示要输出的数据在内存中的存放地址,第二个参数表示要输出的数据占据内存空间的大小(字节);对于read函数,第一个参数表示数据输入后存放的地址,第二个参数表示输入数据占据内存空间的大小(字节)。
由于读写均以字节为单位进行传输,因此地址应该是指向自己的指针,即char*,因此需要把地址强制转化为char*类型。
c 文件I/O完毕,需关闭文件。
d 对于文件输出,若文件已经存在,默认情况下,open函数将清除文件中的原有数据。若希望以追加形式写文件,即不清除文件中的原有数据,而是从原有数据结尾处写入新数据,需在调用open函数时利用ios::app指定写方式为追加:
outFile.open("1.dat", ios::binary | ios::app);
位或操作|表示枚举常量的组合,如上述的ios::binary | ios::app表示以追加方式打开二进制文件。
2 文件的定位
默认情况下,对文件的读写是按顺序从头到尾进行。我们可以认为每个文件中有两个位置指针,指出在文件中进行读写操作的位置。每读/写完一个数据项后,读/写指针自动移动到下一个位置上。但有时,我们可能需要控制这两个指针的位置,以一种我们自己设计的顺序来读/写文件,这就是文件的定位。在对文件进行读/写操作时可利用seekg/seekp函数进行文件定位。
举例:
假设在下面程序中,希望隔个读取文件1.dat里面的数值,即只读取10、30、50、70、90。在读取10后,文件读指针自动移动到20的位置上,这时就应该调用seekg,改变指针位置使其移动到30的位置上,便可略过20。
/* 利用函数seekg进行输入文件读指针的定位 */ #include <iostream> #include <fstream> using namespace std; int main() { int a[10] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; int b[10] = { 0 }; int i; ifstream inFile; ofstream outFile; outFile.open("1.dat", ios::binary); for (i = 0; i < 10; i++) { outFile.write((char*)&a[i], sizeof(a[i])); } outFile.close(); inFile.open("1.dat", ios::binary); for (i = 0; i < 10; i++) // ① { inFile.read((char*)&b[i], sizeof(b[i])); // ② inFile.seekg(sizeof(int), ios::cur); // ③ } inFile.close(); for (i = 0; i < 10; i++) cout << b[i] << " "; cout << endl; system("pause"); return 0; }
运行程序屏幕显示:10 30 50 70 90 0 0 0 0 0
分析:
在循环①中,并没有将1.dat的数据完全读入数组b中,因为在每次执行语句②读入一个int型数据后,语句③利用函数seekg移动了读指针的位置,使得读指针跳过一个int型数据,指向再下一个int型数据。函数seekg调用方式如下:
输入文件流对象.seekg (位移量, 起始点);
其含义是使得位置指针从当前位置移动到距离”起始点“为”位移量“的那个位置上,位移量按字节计数。起始点可以使ios::beg、ios::cur、ios::end,分别表示文件开始处、当前位置、文件末尾处。若位移量为正整数,则向文件结尾方向移动;若为负整数,则向文件开头方向移动。
对于输出文件,可以类似使用seekp函数进行文件写指针的定位。