转载请注明出处:http://blog.csdn.net/enyusmile/article/details/48679147
本章内容包括:
- C++角度的输入和输出.
- iostream类系列
- 重定向
- ostream类方法
- 格式化输出
- istream类方法
- 流状态
- 文件I/O
- 使用ifstream类从文件输入
- 使用ofstream类输出到文件
- 使用fstream类进行文件输入和输出
- 命令行处理
- 二进制文件
- 随机文件访问
- 内核格式化
用于文件输入和输出的C++工具都是基于cin和cout所基于的基本类定义.因此本章以对控制台I/O(键盘和屏幕)的讨论为跳板,来研究文件I/O.
17.1 C++输入和输出概述
- C++依赖于C++的I/O解决方案,而不是C语言的I/O解决方案,前者是在头文件iostream(以前为iostream.h)和fstream(以前为fstream.h)中定义一组类.这个类库不是正式语言定义的组成部分(cin和istream不是关键字);
17.1.1 流和缓冲区
- C++程序把输入和输出看作字节流.流充当了程序和流源或流目标之间的桥梁.
- C++程序只是检查字节流,而不需要知道字节来自何方.同理,通过使用流,C++程序处理输出的方式将独立于其去向.因此管理输入包含两步:
- 将流与输入去向的程序关联起来
- 将流与文件连接起来.
- 通常,通过使用缓冲区可以更高效得处理输入和输出.
- 键盘输入每次提供一个字符,因此在这种情况下,程序无需缓冲区来帮助匹配不同的数据传输速率.然而,对键盘输入进行缓冲可以让用户在将输入传输给程序之前返回并更正.C++程序通常在用户按下回车键时刷新输入缓冲区.
17.1.2 流、缓冲区和iostream文件
- 通过使用typedef工具,C++使得这些模板char具体化能够模仿传统的非模板I/O实现.
- 重定义I/O:ISO/ANSI标准C++98对I/O做了两方面的修订.首先是从ostream.h到ostream的变化,用ostream将类放在std名称空间中.其次I/O类被重新编写.为成为国际语言,C++bixu能够处理需要16位的国际字符集或更宽的字符类型.因此,该语言在传统的8位char(“窄”)类型的基础上添加了wchar_t(“宽”)字符类型;而C++11tianjia了类型char16_t和char32_t.每种类型都需要有自己的I/O工具.标准委员会并没有开发两套(现在为4套)独立的类,而是开发了1套I/O类模板,其中包括basic_istream<charT,traits<charT>>和basic_ostream<charT,traits<charT>>.traits<charT>模板是一个模板类,为字符类型定义了具体特性,如如何比较字符是否相等以及字符的EOF值等.该C++11标准提供了I/O的char和wchar_t具体化.例如,istream和ostream都是char具体化的typedef.同样,wistream和wostream都是wchar_t具体化.例如,wcout对象用于输出宽字符流.头文件ostream中包含了这些定义.ios基类汇总的一些独立于类型的信息被移动到新的ios_base类中,这包括各种格式化常量,例如ios:fixed(现在为ios_base::fixed).另外,ios_base还包含了一些老式ios中没有的选项.
- cout << “Bjarne free”; 这个语句通过指向的streambuf对象将字符串”Bjarne free”中的字符放到cout管理的缓冲区中;ostream类定义了上述语句中使用的operator<<()函数,ostream类还支持cout数据成员以及其他大量的类方法.另外,C++注意到,来自缓冲区的输出被引导到标准输出(通常是显示器,由操作系统提供).总之,流的一端与程序相连,另一端与标准输出相连,cout对象凭借streambuf对象的帮助,管理着六种的字节流.
17.1.3 重定向
- 这个工具使得能够改变标准输入和标准输出.通过输入重定向(<)和输出重定向(>)
- 换句话说,操作系统改变了输入流的流入端连接,而流出端仍然与程序相连.
17.2 使用cout进行输出
17.2.1 重载的<<运算符
- 在C++中,与C一样,<<运算符的默认含义是按位左移运算符.但ostream类重新定义了<<运算符,方法是将其重载为输出.在这种情况下,<<叫做插入运算符.
- 1.输出和指针
- ostream类还未下面的指针类型定义了插入运算符函数:
- const signed char *;
- const unsigned char *;
- const char *;
- void *;
- 方法使用字符串中的终止空字符来确定何时停止显示字符.
- ostream类还未下面的指针类型定义了插入运算符函数:
- 2.拼接输出
- 插入运算符的所有化身的返回类型都是ostream&.也就是说,原型的格式如下:ostream & operator<<(type);
- 函数定义指出,引用将指向用于调用该运算符的对象.换句话说,运算符函数的返回值为调用运算符的对象.
17.2.2 其他ostream方法
- ostream类还提供了put()方法和write()方法,前者用于显示字符,后者用于显示字符串.
- 一些老式编译器错误地为char,unsigned char和signed char 3种参数类型重载了put().这使得将int参数用于put()时具有二义性,因为int可被转换为这3种类型中的任何一种.
- 程序清单17.1 write.cpp
- write()方法并不会在遇到空字符时自动停止打印字符,而只是打印制定数目的字符,即使超出了字符串的边界!
17.2.3 刷新输出缓冲区
- 如果程序使用cout将字节发送给标准输出,情况如何?由于ostream类对cout对象处理的输出进行缓冲,所以输出不会立即发送到目标地址,而是被存储在缓冲区中,知道缓冲区填满.然后,程序将舒心flush缓冲区,把内容发送出去,并清空缓冲区,以存储新的数据.通常,缓冲区为512字节或其整数倍.
- 可以用更为方便的插入表示法来成功的进行刷新.
17.2.4 用cout进行格式化
- ostream插入运算符将值转换为文本格式.
- 注意:并非所有的编译器都能生成符合当前C++标准格式的输出.另外,当前标准允许区域性变化.例如,欧洲实现可能遵循欧洲人的风格:使用逗号而不是句点来表示小数点.也就是说,2.54将被写成2,54.区域库(头文件locale)提供了用特定的风格影响imbuing输入或输出流的机制,所以同一个编译器能够提供多个区域选项.本章使用美国格式.
- 程序清单17.2 defaults.cpp
- 注意,1.200末尾的0没有显示出来,但末尾0的浮点值后面将有6个空格.
- 1.修改显示时使用的计数系统
- ostream类是从ios类派生而来的,而后者是从ios_base类派生而来的.ios_base类存储了描述格式状态的信息.
- 注意:ios_base类中的成员和方法以前位于ios类中.现在,ios_base是ios的基类.在新系统中,ios是包含char和wchar_t具体化的模板,而ios_base包含了非模板特性.
- 程序清单17.3 manip.cpp
- 2.调整字段宽度
- 可以使用width成员函数将长度不同的数字放到宽度相同的字段中.
- width()方法只影响将显示的下一个项目,然后字段宽度将恢复为默认值.
- C++永远不会截短数据,因此如果视图在宽度为2的字段中打印一个7位值,C++将增宽字段,以容纳该数据(在有些语言中,如果数据长度与字段宽度不匹配,将用星号填充字段.C/C++的原则是:显示所有的数据比保持列的整洁更重要.C++视内容重于形式).
- 程序清单17.4 width.cpp
- 3.填充字符
- 在默认情况下,cout用空格填充字段中未被使用的部分,可以用fill()成员函数来改变填充字符.
- 程序清单17.5 fill.cpp
- 注意,与字段宽度不同的是,新的填充字符将一直有效,直到更改它为止.
- 4.设置浮点数的显示精度
- 浮点数精度的含义取决于输出模式.在默认模式下,它值得是显示的总位数.
- 已经知道,C++的默认精度为6位(但末尾的0将不显示)
- precision()成员函数使得能够选择其他值.
- 和width()的情况不同,但与fill()类似,新的精度设置将一直有效,直到被重新设置.
- 程序清单17.6 precise.cpp
- 5.打印末尾的0和小数点
- iostream系列类没有提供专门用于完成这项任务的函数,但ios_base类提供了一个setf()函数(用于set标记),能够控制多种格式化特性.
- 程序清单17.7 showpt.cpp
- 6.再谈setf()
- setf()函数有两个原型.
- fmtflags setf(fmtflags);
- 其中,fmtflags是bitmask类型的typedef名,用于存储格式标记.该名称是在ios_base类中定义的.
- 注意:bitmask类型是一种用来存储各个位值的类型.它可以是整型,枚举,也可以是STL bitset容器.这里的主要思想是,每一位都是可以单独访问的,都有自己的含义.iostream软件包使用bitmask来存储状态信息.
- 使用它们时,必须加上作用域解析运算符.也就是说,应使用ios_base::uppercase,而不是uppercase.如果不想使用using编译指令或using声明,可以使用作用域运算符来指出这些名称位于名称空间std中.修改将一直有效,直到被覆盖为止.
- 程序清单17.8 setf.cpp
- 注意,仅当基数为10时才使用加号.C++将十六进制和八进制都视为无符号的,因此对它们,无需使用符号(然而,有些C++实现可能仍然会显示加号).
- fmtflags setf(fmtflags , fmtflags);
- 这种重载格式用于设置由多位控制的格式选项.
- 遗憾的是,C++没有提供自对齐模式
- 程序清单17.9 setf2.cpp
- fmtflags setf(fmtflags);
- 调用setf()的效果可以通过unsetf()消除.后者的原型是:void unsetf(fmtflags mask);其中,mask是位模式.mask中所有的位都设置为1,将使得对应的位被复位.也就是说,setf()将位位置为1,unsetf()将位恢复为0.
- 系统的工作原理如下:仅当只有定点位被设置时使用定点表示法;仅当只有科学位被设置时使用科学表示法;对于其他组合,如没有位被设置或两位都被设置时,将使用默认模式.
- setf()函数有两个原型.
- 7.标准控制符
- 8.头文件iomanip
- 3个最常用的控制符分别是setprecision(),setfill()和setw(),它们分别用来设置精度,填充字符和字段宽度.
- 程序清单17.10 iomanip.cpp
17.3 使用cin进行输入
- cin解释输入的方式取决于value_holder的数据类型.
17.3.1 cin>>如何检查输入
- 不同版本的抽取运算符查看输入流的方法是相同的.
- 程序清单17.11 check_it.cpp
17.3.2 流状态
- 1.设置状态
- 为什么需要重新设置流状态呢?对于程序员来说,最常见的理由是,在输入不匹配或到达文件尾时,需要使用不带参数的clear()重新打开输入.是否有意义,取决于程序要执行的任务.
- 2.I/O和异常
- 假设某个输入函数设置了eofbit,这是否会导致异常被引发呢?在默认情况下,答案是否定的.但可以使用exceptions()方法来控制异常如何被处理.
- 程序清单17.12 cinexcp.cpp
- 3.流状态的影响
17.3.3 其他istream类方法
- 1.单字符输入
- (1).成员函数get(char &)
- 2.采用哪种单字符输入形式
- 3.字符串输入:getline(),get()和ignore()
- 程序清单17.13 get_fun.cpp
- 4.意外字符串输入
17.3.4 其他istream方法
- 程序清单17.14 peeker.cpp
- 程序清单17.15 truncate.cpp
17.4 文件输入和输出
17.4.1 简单的文件I/O
- 警告:以默认模式打开文件进行输出将自动把文件的长度截短为零,这相当于删除已有的内容.
- 读取文件的要求与写入文件相似:
- 创建一个ifstream对象来管理输入流
- 将该对象与特定的文件关联起来
- 以使用cin的方式使用该对象
- 当输入和输出流对象过期(如程序终止)时,到文件的连接将自动关闭.另外,也可以使用close()方式来显式地关闭到文件的连接.关闭这样的连接并不会删除流,而只是断开流到文件的连接.然而,流管理装置仍被保留.
- 程序清单17.16 fileio.cpp
17.4.2 流状态检查和is_open()
- 警告:以前,检查文件是否成功打开的常见方式如下:
if(fin.fail()) ... //failed to open
if(!fin.good()) ... //failed to open
if(!fin) ... //failed to open
- fin对象被用于测试条件中时,如果fin.good()为false,将被转换为false;否则将被转换成true.因此上面三种方式等价.然而,这些测试无法检测到这样一种情形:试图以不适合的文件模块打开文件时失败.方法is_open()能够检测到这种错误以及good()能够检测到的错误.然而,老式C++实现没有is_open();
17.4.3 打开多个文件
- 在这种情况下,可以打开一个流,并将它依次关联到各个文件.
17.4.4 命令行处理技术
- C++有一种让在命令行环境中运行的程序能够访问命令行参数的机制,方法是使用下面的main()函数:int main(int argc,char *argv[])
- 程序清单17.17 count.cpp注意:
- 有些C++实现要求在该程序末尾使用fin.clear(),有些则不要求,这取决于将文件与ifstream对象关联起来时,是否自动重置流转太.使用fin.clear()是无害的,即使在不必使用它的时候使用.
17.4.5 文件模式
- ios_base类定义了一个openmode类型,用于表示模式;与fmtflags和iostate类型一样,它也是一种bitmask类型(以前,其类型为int).可以选择ios_base类中定义的多个常量来指定模式.
- 1.追加文件
- 注意:在早起,文件I/O可能是C++最不标准的部分,很多老式编译器都不遵守当前的标准.例如,有些编译器使用诸如nocreate等模式,而这些模式不是当前标准的组成部分.另外,只有一部分编译器要求在第二次打开同一个文件进行读取之前调用fin.clear().
- 程序清单17.18 append.cpp
- 2.二进制文件
- 二进制格式对于数字来说比较精确,因为它存储的是值的内部表示,因此不会有转换误差或舍入误差.以二进制格式保存数据的熟读更快,因为不需要转换,并可以大块地快出数据.二进制格式通常占用的空间较小,这取决于数据的特征.
- 二进制文件和文本文件:使用二进制文件模式时,程序将数据从内存传输给文件(反之亦然)时,将不会发生任何隐藏的转换,而默认的文本模式并非如此.例如,对于Windows文本文件,它们使用两个字符的组合(回车和换行)表示换行符;Macintosh文本文件使用回车来表示换行符;而UNIX和Linux文件使用换行(linefeed)来表示换行符.C++是从UNIX系统上发展而来的,因此也使用换行(linefeed)来表示换行符;为增加可一致性,Windows C++程序在写文本模式文件时,自动将C++换行符转换为回车和换行;Macintosh C++程序在写文件时,将换行符转换为回车.在读取文本文件时,这些程序将本地换行符转换为C++格式.对于二进制数据,文本格式会引起问题,因此double值中间的字节可能与换行符的ASCII码有相同的位模式.另外,在文件尾的检测方式也有区别.因此以二进制格式保存数据时,应使用二进制文件模式(UNIX系统只有一种文件模式,因此对于它来说,二进制模式和文本模式是一样的).
- 提示:read()和write()成员函数的功能是相反的.请用read()来恢复用write()写入的数据.
- 注意:虽然二进制文件概念是ANSI C的组成部分,但一些C和C++实现并没有提供对二进制文件模式的支持.原因在于:有些系统只有一种文件类型,因此可以将二进制操作(如read()和write())用于标准文件格式.因此,如果实现认为ios_base::binary是非法常量,只要删除它即可.如果实现不支持fixed和right控制符,则可以使用cout.setf(ios_base::fixed,ios_base::floatfield)和cout.setf(ios_base::right,ios_base::adjustfield).另外,也可能必须用ios替换ios_base.其他编译器(特别是老式编译器)可能还有其他特征.
- 程序清单17.19 binary.cpp
17.4.6 随机存取
- 随机存取指的是直接移动(不是依次移动)到文件的任何位置.随机存取常被用于数据库文件,程序维护一个独立的索引文件,该文件指出数据在主数据文件中的位置.这样,程序便可以直接跳到这个位置,读取(还可能修改)其中的数据.
- 如果文件由长度相同的记录组成,这种方法实现起来最简单.
- 类型升级:在C++早起,seekg()方法比较简单.Streamoff和streampos类型是一些标准整型(如long)的typedef.但为创建可移植标准,必须处理这样的现实情况:对于有些文件系统,整数参数无法提供足够的信息,因此streamoff和streampos允许是结构或类类型,条件是它们允许一些基本的操作,如使用整数值作为初始值等.随后,老版本的istream类被basic_istream模板取代,streampos和streamoff被basic_istream模板取代.然而,streampos和streamoff继续存在,作为pos_type和off_type的char的具体化.同样,如果将seekg()用于wistream对象,可以使用wstreampos和wstreamoff类型.
- 注意:实现越旧,与C++标准相冲突的可能性越大.一些系统不能识别二进制标记,fixed和right控制符以及ios_base.
- 程序清单17.20 random.cpp
// random.cpp -- random access to a binary file
#include <iostream> // not required by most systems
#include <fstream>
#include <iomanip>
#include <cstdlib> // (or stdlib.h) for exit()
const int LIM = 20;
struct planet
{
char name[LIM]; // name of planet
double population; // its population
double g; // its acceleration of gravity
};
const char * file = "planets.dat"; // ASSUMED TO EXIST (binary.cpp example)
inline void eatline() { while (std::cin.get() != '\n') continue; }
int main()
{
using namespace std;
planet pl;
cout << fixed;
// show initial contents
fstream finout; // read and write streams
finout.open(file,
ios_base::in | ios_base::out | ios_base::binary);
//NOTE: Some Unix systems require omitting | ios::binary
int ct = 0;
if (finout.is_open())
{
finout.seekg(0); // go to beginning
cout << "Here are the current contents of the "
<< file << " file:\n";
while (finout.read((char *) &pl, sizeof pl))
{
cout << ct++ << ": " << setw(LIM) << pl.name << ": "
<< setprecision(0) << setw(12) << pl.population
<< setprecision(2) << setw(6) << pl.g << endl;
}
if (finout.eof())
finout.clear(); // clear eof flag
else
{
cerr << "Error in reading " << file << ".\n";
exit(EXIT_FAILURE);
}
}
else
{
cerr << file << " could not be opened -- bye.\n";
exit(EXIT_FAILURE);
}
// change a record
cout << "Enter the record number you wish to change: ";
long rec;
cin >> rec;
eatline(); // get rid of newline
if (rec < 0 || rec >= ct)
{
cerr << "Invalid record number -- bye\n";
exit(EXIT_FAILURE);
}
streampos place = rec * sizeof pl; // convert to streampos type
finout.seekg(place); // random access
if (finout.fail())
{
cerr << "Error on attempted seek\n";
exit(EXIT_FAILURE);
}
finout.read((char *) &pl, sizeof pl);
cout << "Your selection:\n";
cout << rec << ": " << setw(LIM) << pl.name << ": "
<< setprecision(0) << setw(12) << pl.population
<< setprecision(2) << setw(6) << pl.g << endl;
if (finout.eof())
finout.clear(); // clear eof flag
cout << "Enter planet name: ";
cin.get(pl.name, LIM);
eatline();
cout << "Enter planetary population: ";
cin >> pl.population;
cout << "Enter planet's acceleration of gravity: ";
cin >> pl.g;
finout.seekp(place); // go back
finout.write((char *) &pl, sizeof pl) << flush;
if (finout.fail())
{
cerr << "Error on attempted write\n";
exit(EXIT_FAILURE);
}
// show revised file
ct = 0;
finout.seekg(0); // go to beginning of file
cout << "Here are the new contents of the " << file
<< " file:\n";
while (finout.read((char *) &pl, sizeof pl))
{
cout << ct++ << ": " << setw(LIM) << pl.name << ": "
<< setprecision(0) << setw(12) << pl.population
<< setprecision(2) << setw(6) << pl.g << endl;
}
finout.close();
cout << "Done.\n";
// keeping output window open
// cin.clear();
// eatline();
// cin.get();
return 0;
}
- 使用临时文件:开发应用程序时,经常需要使用临时文件,这种文件的存在是短暂的,必须受程序控制.您是否考虑过,在C++中如何使用临时文件呢?创建临时文件,复制另一个文件的内容并删除文件棋士都很简单.首先,需要为临时文件制定一个命名方案,但如何确保每个文件都被制定了独一无二的文件名呢?cstdio中声明的tmpnam()标准函数可以帮助您.char* tmpnam(char* pszName);tmpnam()函数创建一个临时文件名,将它放在pszName指向的C-风格字符串中.常量L_tmpnam和TMP_MAX(二者都是在cstdio中定义的)限制了文件名包含的字符数以及在确保当前目录中不生成重复文件名的情况下tmpnam()可被调用的最多次数.
17.5 内核格式化
- string的sstream族支持取代了char数组的strstream.h族支持.
- 程序清单17.21 strout.cpp
- 程序清单17.22 strin.cpp
- 总之,istringstream和ostringstream类使得能够使用istream和ostream类的方法来管理存储在字符串中的字符数据.
17.6 总结
- 流是进出程序的字节流.缓冲区是内存中的临时存储区域,是程序与文件或其他I/O设备之间的桥梁.信息在缓冲区和文件之间传输时,将使用设备(如磁盘驱动器)处理效率最高报的尺寸以大块数据的方式进行传输.信息在缓冲区和程序之间传输时,是逐字节传输的,这种方式对于程序中的处理操作更为方便.C++通过讲一个被缓冲流同程序及其输入源相连来处理输入.同样,C++也通过将一个被缓冲流与程序及其输出目标相连来处理输出.iostream和fstream文件构成了I/O类库,该类库定义了大量用于管理流的类.包含了iostream文件的C++程序将自动打开8个流,并使用8个对象管理它们.cin对象管理标准输入流,后者默认与标准输入设备(通常为键盘)相连;cout对象管理标准输出流,后者默认与标准输出设备(通常为显示器)相连;cerr和clog对象管理与标准错误设备(通常为显示器)相连的未被缓冲的流和被缓冲的流.这4个对象有都有用于宽字符的副本,它们是wcin,wcout,wcerr和wclog.
- I/O类库提供了大量有用的方法.istream类定义了多个版本的抽取运算符(>>),用于识别所有基本的C++类型,并将字符输入转换为这些类型.get()方法族和getline()方法为档子符输入和字符串输入提供了进一步的支持.同样,ostream类定义了多个版本的输入运算符(<<),用于识别所有的C++基本类型,并将他们转换为相应的字符输出.put()方法对单字符输出提供了进一步的支持.sistream和sostream类对款字符提供了类似的支持.
- 使用ios_base类方法以及文件iostream和iomanip中定义的控制符(可与插入运算符拼接的函数),可以控制程序如何格式化输出.这些方法和控制符是的能够控制计数系统,字段宽度,小数位数,显示浮点变量时采用的计数系统以及其他元素.
- fstream文件提供了将iostream方法扩展到文件I/O的类定义.ifstream类侍从istream类派生而来的.通过将ifstream对象与文件关联起来,可以使用所有的istream方法读取文件.同样,通过将ofstream对象与文件关联起来,可以使用ostream方法来写文件;通过将 fstream对象与文件关联起来,可以将输入和输出方法用于文件.
- 要将文件与流关联起来,可以在初始化文件流对象时提供文件名,也可以先创建一个文件流对象,然后用open()方法将这个流与文件关联起来.close()方法终止流与文件之间的连接.类构造函数和open()方法接收可选的第二个参数,该参数提供文件模式.文件模式决定文件是否被读和/或写,打开文件以便写入时是否截短文件,试图打开不存在的文件时是否会导致错误,是使用二进制模式还是文本模式等.
- 文本文件以字符格式存储所有的信息,例如,数字值将被转换为字符表示.常规的插入和抽取运算符以及get()和geline()都支持这种模式.二进制文件使用计算机内部使用的二进制表示来存储信息.与文本文件相比,二进制文件存储数据(尤其是浮点值)更为精确,简洁,但可移植性较差.read()和write()方法都支持二进制输入和输出
- seekg()和seekp()函数提供对文件的随机存取.这些类方法使得能够将文件指针放置到相对于文件开头,文件尾和当前位置的某个位置.tellg()和tellp()方法报告当前的文件位置.
- sstream头文件定义了istringstream和ostringstream类,这些类使得能够使用istream和ostream方法来抽取字符串中的信息,并对要放入到字符串汇总的信息进行格式化.
17.7 复习题
17.8 编程练习
本章源代码下载地址