Chapter 17 输入、输出和文件
本章内容包括:
- C++角度的输入和输出
- iostream类系列
- 重定向
- ostream类方法
- 格式化输出
- istream类方法
- 流状态
- 文件IO
- 使用ifstream类从文件输入
- 使用ofstream类输出到文件
- 使用fstream类进行文件输入和输出
- 命令行处理
- 二进制文件
- 随机文件访问
- 内核格式化
C++使用了很多较为高级的语言特性来实现输入和输出,其中包括类、派生类、函数重载、虚函数、模板和多重继承。
17.1 C++输入和输出概述
多数计算机语言的输入和输出是以语言本身为基础实现的。诸如Basic和Pascal等语言的关键字列表可知,PRINT语句、Writeln语句以及其他类似的语句都是语言词汇表的组成部分,但C和C++都没有将输入和输出建立在语言中。
C++依赖于C++的I/O解决方案,而不是C语言的I/O解决方案。C++的I/O方案在头文件iostream和fstream中定义的一组类。
17.7.1 流和缓冲区
C++程序把输入和输出看作字节流。输入时,程序从输入流中抽取字节;输出时,程序将字节插入到输出流中。对于面向文本的程序,每个字节代表一个字符,字节可以构成字符或者数值数据的二进制表示。
输入流的字节可能来自键盘、存储设备或者其他程序。
输出流的字节可以刘翔屏幕、打印机、存储设备或其他程序。
流充当了程序和流源或流目标之间的桥梁,这使得C++程序可以以相同的方式来对待来自键盘的输入和来自文件的输入。C++程序只检查字节流,而不需要直到字节来自何方
管理输入包含两步:
- 将流与输入去向的程序关联起来
- 将流与文件连接起来
输入流需要两个连接,每端一个,文件端部连接提供流的来源,程序端连接将流的流出部分转储到程序中。
通常会使用缓冲区来更高效的处理输入和输出。缓冲区是用作中介的内存块,它是将信息从设备传输到程序或者从程序传输给设备的临时存储工具。通常,磁盘驱动器设备以512字节的块为单位来传输信息,而程序通常每次只能处理一个字节的信息。
缓冲区帮助匹配这两种不同的信息传输速率。
非缓冲方法:程序可以从文件中读取一个字符,处理它,再从文件中读取下一个字符,在处理,从硬盘文件中每次读取一个字符需要大量的硬件活动,速度非常慢;
缓冲方法是从磁盘读取大量信息,将这些信息存储在缓冲区中,每次从缓冲区读取一个字节,从内存读取单个字节的速度要比从硬盘上快许多,这种方法更方便,也更快。
输出时,程序首先填满缓冲区,然后把整块数据传输给硬盘,并清空缓冲区,称为刷新缓冲区。
键盘输入每次提供一个字符,因此这种情况下程序无需缓冲区来帮助匹配不同的数据传输速率。然而,对键盘输入进行缓冲可以让用户将输入传输给程序之前进行返回并更正。C++程序通常在用户按下回车键时刷新输入缓冲区,对于屏幕输出,C++程序通常在用户发送换行符时刷新输出缓冲区。
17.1.2 流、缓冲区和iostream文件
管理流和缓冲区的工作优点复杂,头文件iostream中包含一些专门设计用来管理流和缓冲区的类。C++98版本C++I/O定义了一些类模板,以支持char和wchar_t数据;C++11添加了char16_t和char32_t具体化。通过使用typedef工具,C++使得这些模板char具体化能够模仿传统的非模板IO实现。下面时其中的一些类:
- streambuf类为缓冲区提供了内存,并提供了用于填充缓冲区、访问缓冲区内容、刷新缓冲区和管理缓冲区内存的类方法;
- ios_base类表示流的一般特征,如是否可读取、是二进制流还是文本流等;
- ios类基于ios_base,其中包括了一个指向streambuf对象的指针成员;
- ostream类是从ios类派生而来的,提供了输出方法;
- istream类也是从ios类派生而来,提供了输入方法;
- iostream类是基于istream和ostream类的,因此继承了输入方法和输出方法。
要使用这些工具,必须使用适当的类对象。
C++的iostream类库管理了很多细节。例如,在程序中包含iostream文件将自动创建8个流对象(4个用于窄字符流,4个用于宽字符流)。
- cin对象对应于标准输入流,默认情况下关联到标准输入设备(通常为键盘),wcin对象与此类似,处理的是wchar_t类型。
- cout对象与标准输出流相对应,默认情况下关联到输出设备(通常为显示器),wcout对象与此类似,但处理的是wchar_t类型。
- cerr对象与标准错误流相对应,可用于显示错误消息。默认情况下,这个流被关联到输出设备(通常为显示器),这个流没有被缓冲,这意味着信息将被直接发送给屏幕,而不会等到缓冲区被填满或新的换行符。
- clog对象也对应标准错误流。在默认情况下,这个流被关联到标准输出设备(通常为显示器)。这个流被缓冲。
- 对象代表流——该对象将包含存储了与输出有关的信息的数据成员,如显示数据时使用的字段宽度、小数位数、显示整数时采用的计数方法以及描述用来处理输出流的缓冲区的streambuf对象的地址。
cout对象凭借streambuf对象的帮助,管理着流中的字节流。
17.1.3 重定向
标准输入和输出流通常连接着键盘和屏幕,但很多操作系统(UNIX、Linux和Windows)都支持重定向,这个工具使得能够改变标准输入和输出。
输入重定向(<),输出重定向(>),例如,有一个名为counter.exe的可执行的C++程序,可以计算输入中的字符数,并报告结果。样例如下:
cow_cnt file:
C>counter <oklahoma >cow_cnt
C>
命令行中的<oklahoma将标准输入与oklahoma文件关联起来,使cin从该文件读取输入,>cow_cnt将标准输出与cow_cnt文件关联起来,cout将输出发送给文件。
标准重定向并不会影响cerr或者clog,因此输出被重定向,屏幕也会输出错误信息。
17.2 使用cout进行输出
C++将输出看作字节流(根据实现和平台的不同,可能是8位、16位或32位的字节,但都是字节)。在程序中,很多数据被组织成比字节更大的单位,double由64位的二进制数据表示,但将字节流发送给屏幕时,希望每个字节表示一个字符值。ostream类最重要的任务之一是将数值类型(int或float)转换为以文本形式表示的字符流。
17.2.1 重载的<<运算符
<<运算符的默认含义是按位左移运算符,x<<3的含义是将x的二进制表示中所有的位向左移动3位。ostream类重新定义了运算符,将其重载为输出。这种情况下<<叫插入运算符。插入运算符被重载,使之能识别C++中的所有基本类型:
- char、 unsigned char、 signed char
- short、 unsigned short
- int、unsigned int
- long、unsigned long、long long、unsigned long long
- float、double、long double
对于每种数据类型,ostream类都提供了operator<<()函数的定义,C++程序将其对应于有相应特征标的运算符函数。
1.输出和指针
ostream类为下面的指针类型定义了插入运算符函数:
- const signed char*;
- const unsigned char*;
- const char*;
- void *
方法使用终止空字符'\0'来确定何时停止显示字符。
对于其他类型的指针,C++将其对应于void*,打印地址的数值表示。
2.拼接输出
插入运算符的返回类型都是ostream &,这特性使得可以通过插入连接输出。
17.2.2 其他ostream方法
ostream类提供了put()方法和write()方法。
put方法也返回指向调用对象的引用,因此cout.put('I').put('t')是可以的。
cout.put(65) // display the A character
cout.put(66.3) // display the B character
write()方法显示整个字符串,有两个参数,第一个参数提供了显示字符串的地址,第二个参数指出显示多少个字符,返回类型为调用方法的对象。
17.2.3 刷新输出缓冲区
对于文件输出,ostream类对cout对象处理的输出进行缓冲,所以不会立刻发送到目标地址,而是被存储在缓冲区中,知道缓冲区填满;然后程序将刷新缓冲区,把内容发送出去,清空缓冲区,以存储新的数据,这样效率很高。
对于屏幕输出,填充缓冲区的重要性要低得多。换行符送到缓冲区后,就刷新缓冲区。多数C++实现都会在输入即将发生时刷新缓冲区。
控制符flush和endl都可以刷新缓冲区,区别在于flush只刷新缓冲区显示,endl会插入一个换行符。
17.2.4 用cout进行格式化
ostream插入运算符将值转换为文本格式,默认情况下格式化的值如下:
- char值,如果它代表可打印字符,则将被作为一个字符显示在宽度为一个字符的字段中。
- 对于数值类型,将以十进制方式显示在一个刚好容纳该数字及负号(如果有的话)的字段中。
- 字符串被显示在宽度等于该字符串长度的字段中
- 新式:浮点类型被显示为6为,末尾的0不显示(显示的位数与存储时的精度没有任何关系)数字以定点表示法还是科学计数法表示,取决于其值。当指数大于等于6或小于等于-5时,将使用科学计数法表示。字段宽度恰好容纳数字和负号。
每个值的显示宽度都等于它的长度。
1.修改显示时使用的计数系统
ostream类是从ios类派生而来,后者从ios_base类派生而来。
通过使用ios_base的成员函数,可以控制字段宽度和小数位数。
dec、hex、oct控制符控制了输出按什么进制显示。
2.调整字段宽度
width()方法可以改变字段宽度,只影响下一个项目,而且由于其返回值的是字段宽度值,因此不可以通过插入连接输出。C++永远不会截短数据。
3.填充字符
默认情况使用空格填充字段未被使用的部分,可以使用fill(ch)来改变填充字符,新的填充字符一直有效,直至修改它为止
4.设置浮点数的显示精度
C++的默认显示精度为6位(末尾的0将不显示),precision()成员函数可以选择其他值。新的精度设置将一直有效,直至被重新设置。
5.打印末尾的零和小数点
ios_base()类提供了一个setf()函数,用于set标记,能够控制多种格式化特性。使用下面的方法将使cout显示末尾小数点:
cout.setf(ios_base::showpoint);
showpoint是类级静态常量。
6.再谈setf()
setf()方法控制了小数点被显示时的其他几个格式选项。
setf()有两个原型,第一个为:
fmtflags set(fmtflags);
fmtflahs是bitmask类型的typedef名。返回值指出所标记位置以前的设置,用于恢复原始设置。
格式化常量
常量 | 含义 |
---|---|
ios_base::boolalpha | 输入和输出bool值,可以为true和false |
ios_base::showbase | 对于输出,使用C++基数前缀(0,0x) |
ios_base::showpoint | 显示末尾的小数点 |
ios_base::uppercase | 对于16进制输出,使用大写字母,E表示法 |
ios_base::showpos | 在整数前面加上+(仅基数为10才使用+) |
第二个setf()原型接受两个参数,并返回以前的设置:
fmtflags setf(fmtflags, fmtflags);
setf(long,long)的参数
第二个参数 | 第一个参数 | 含义 |
---|---|---|
ios_base::field | ios_base::dec | 使用基数10 |
ios_base::field | ios_base::oct | 使用基数8 |
ios_base::field | ios_base::hex | 使用基数16 |
ios_base::floatfield | ios_base::fixed | 使用定点计数法 |
ios_base::floatfield | ios_base::scientific | 使用科学计数法 |
ios_base::adjustfield | ios_base::left | 使用左对齐 |
ios_base::adjustfield | ios_base::right | 使用右对齐 |
ios_base::adjustfield | ios_base::internal | 符号或基数前缀左对齐,值右对齐 |
setf()的第二参数清除一批相关的位,然后将第一参数的一位设置为1。
精度3让默认的浮点显示总共显示3位,定点模式和科学模式只显示3位小数。
setf()的效果可以通过unsetf()消除,后者的原型如下:
void unsetf(fmtflags mask);
7.标准控制符
C++提供了多个控制符,能够调用setf()
一些标准控制符
控制符 | 调用 |
---|---|
boolalpha | setf(ios_base::boolalpha) |
noboolalpha | unsetf(ios_base::boolalpha) |
showbase | setf(ios_base::showbase) |
noshowbase | unsetf(ios_base::showbase) |
showpoint | setf(ios_base::showpoint) |
noshowpoint | unsetf(ios_base::showpoint) |
showpos | setf(ios_base::showpos) |
noshowpos | unsetf(ios_base::showpos) |
uppercase | setf(ios_base::uppercase) |
nouppercase | unsetf(ios_base::uppercase) |
internal | setf(ios_base::internal, ios_base::adjustfield) |
left | setf(ios_base::left, ios_base::adjustfield) |
right | setf(ios_base::right, ios_base::adjustfield) |
dec | setf(ios_base::dec, ios_base::basefield) |
hex | setf(ios_base::hex, ios_base::basefield) |
oct | setf(ios_base::oct, ios_base::basefield) |
fixed | hex |
scientific | setf(ios_base::scientific, ios_base::floatfield) |
8.头文件iomanip
在头文件中包含三个常用的控制符:
setprecision()、setfill()、setw()
17.3 使用cin进行输入
cin对象脚标准输入表示为字节流,通过键盘来生成这样字节流。如果键入序列2011,cin对象将从输入流中抽取这几个字符。输入可以是字符串的一部分、int值等其他类型,抽取过程涉及了类型转换。
cin对象根据接受值的变量类型,使用其方法将字符序列转换为所需的类型。istream类重载了抽取运算符>>,使之能够识别下面这些基本类型:
- signed char &; unsigned char &; char &;
- short &; unsigned short &;
- int &; unsigned int &;
- long &; unsigned long &; long long &; unsigned long long &;
- float &;
- double &; long double &;
抽取运算符重载的参数和返回值都是引用,引用可以抽取运算符可以处理变量本身。
cin >> hex; 将输入解释为十六进制。
istream类为字符指针类型重载了>>抽取运算符:
- signed char *;
- unsigned char *;
- char *;
对于这种类型的参数,抽取运算符将读取输入中的下一个单词,将它放到指定的位置,并加上一个空字符'\0',使之称为一个字符串。
每个抽取运算符都返回调用对象的引用,这使得可以将输入拼接起来。
17.3.1 cin>>如何检查输入
抽取运算符查看输入流的方法是相同的,跳过空白(空格、换行符和制表符),直到遇到非空白字符。
17.3.2 流状态
cin或cout对象包含一个描述流状态的数据成成员(从ios_base类继承的)。
流状态(被定义为iostate类型,iostate是一种bitmask类型)由3个ios_base元素组成:eofbit、badbit或failbit,每个元素都是1位,可以设置1或清除0。
流状态
成员 | 描述 |
---|---|
eofbit | 如果到达文件尾,则设置为1 |
badbit | 如果流被破坏,则设置为1;例如,文件读取错误 |
failbit | 如果输入操作未能读取预期的字符或输出操作没有写入预期的字符,则设置为1 |
goofbit | 另一种表示0的方法 |
good() | 如果流可以使用(所有的位都被清楚),则返回true |
eof() | 如果eofbit被设置,则返回true |
bad() | 如果badbit被设置,则返回true |
fail() | 如果badbit或failbit被设置,则返回true |
rdstate() | 返回流状态 |
exceptions() | 返回一个位掩码,指出哪些标记导致异常被引发 |
exceptions(iostate ex) | 设置哪些状态异常导致clear()引发异常;例如,如果ex是eofbit,则如果eofbit被设置,clear()将引发异常 |
clear(iostate s) | 将流状态设置为s;s的默认值是0(goodbit);如果(restate()& exceptions())!=0,则引发异常basic_ios::failure |
setstate(iostate s) | 调用clear(rdstate()|s)。这将设置与s中设置的位对应的流状态位,其他流状态位保持不变 |
1.设置状态
clear(); // 将清除全部3个状态位
clear(eofbit); // eofbit被设置,其他两个状态位被清除
setstate(eofbit) // eofbit被设置,请他两个状态位不影响
2.I/O和异常
exceptions()方法返回一个位字段,它包含3位,分别对应于eofbit、failbit和badbit。
修改流状态涉及clear()和setstate(),这都将使用clear()。
cin.exceptions(badbit); // setting badbit causes exception to be thrown
cin.exceptions(badbit | eofbit) // eofbit随后被设置,下面的语句将引发异常
3.流状态的影响
只有在流状态良好(所有的位都被清除)的情况下,下面的测试才会返回true。
cin >> input;
设置流状态位有一个非常严重的后果:流将后面的输入或输出关闭,直到位被清除。
输入关闭后的重置流状态方法:
cin.clear(); // reset stream state
while (!isspace(cin.get()))
continue; // get rid of bad input
17.3.3 其他的istream类方法
1.单字符输入
(1) 成员函数get(char &);
get(char &)成员函数返回一个指向调用它的istream对象的引用,这意味着可以拼接get(char &)后面的其他抽取。
cin.get(char &)到达文件尾,它都不会给参数赋值。
(2)成员函数get(void)
get(void)成员函数还读取空白,但使用返回值来传递给程序。返回值类型位int或更大的整形。
由于其返回值不是类对象,因此不能对它用成员运算符。
cin.get(ch)于cin.get()
特征|cin.get(ch)|ch=cin.get()
传输输入字符的方法|赋给参数ch|将函数返回值赋给ch
字符输入时函数的返回值|指向istream对象的引用|字符编码(int值)
达到文件尾时函数的返回值|转换位false|EOF
2.采用哪种单字符输入形式
首先,是否希望跳过空白,希望跳过空白,使用抽取运算符>>;如提供菜单选项;
如果希望程序检查每个字符,使用get()方法,计算字数的程序可以使用空格判断单词何时结束。get(char &)的接口更好,cin.get(void)的主要优点是可以替换标准c语言所有的getchar(),cout.put(ch)替换多所有的putchar(ch)。
3.字符串输入:getline()、get()和ignore()
getline()成员函数和get()的字符串版本都读取字符串,它们的函数特征标相同:
istream & get(char *, int, char);
istream & get(char *, int);
istream & getline(char *, int, char);
istream & getline(char *, int);
第一个参数是字符串的内存单元的地址,第二个参数比要读取的最大字符数大1,第三个参数是用作分节符的字符。
get()和getline()的区别是,get()将换行符留在输入流中,getline()将换行符抽取并被丢弃。
get()将分界符留在输入队列中,而getline()不保留。
4.意外字符串输入
get(char *, int)和getline()的某些输入形式将影响流状态。与其他输入函数一样,遇到文件尾时设置eofbit,遇到流破坏时将设置badbit。另外两种特殊情况是无输入及输入到达或超过函数调用指定的最大字符数。
char temp[80]
while (cin.get(temp,80)) // terminates on empty line
空行不会导致getline()设置failbit,因为getline()会抽取换行符,不保存它。
输入行为
方法 | 行为 |
---|---|
getline(char *,int) | 如果没有读取任何字符(换行符被视为读取了一个字符),则设置failbit 如果读取了最大数目的字符,且行中还有其他字符,则设置failbit |
get(char*,int) | 如果没有读取任何字符,则设置failbit |
17.3.4 其他istream方法
read():该方法最大的特点是不会在输入后加空值字符,该方法与ostream write()结合使用,来完成文件输入和输出。
peek()查看输入流中下一个字符,但不抽取。
gcount()返回最后一个非格式化读取的字符数,字符不能是由抽取运算符读取。
putback()将一个字符插入到输入流中,被插入的字符是下一条输入语句读取的第一个字符。
17.4 文件输入和输出
大多数计算机程序使用了文件:字处理程序创建文档文件;数据库程序创建和搜索信息文件;编译器读取源代码文件并生成可执行文件。文件本身是存储在某种设备(磁带、光盘、软盘或硬盘)上的一系列字节。操作系统管理文件,跟踪它们的位置、大小、创建时间等。除非在操作系统级别上编程,否则不必担心这些事情。需要的只是将程序与文件相连的途径、让程序读取文件内容的途径以及让程序创建和写入文件的途径。
重定向可以提供一些文件支持,但他的局限性更大,重定向来自操作系统,而非C++。
C++I/O类软件包处理文件输入和输出的方式与处理标准输入和输出的方式非常相似。
17.4.1 简单的文件I/O
要程序写入文件:
- ofstream fout; fout.open(filename-cstr); fout << contents << flush
- ofstream fout(filename-cstr); fout << contents << flush
ofstream是ostream的派生类,可以使用基类的所有方法,包括插入运算符定义、格式化方法和控制符。
程序读取文件:
- ifstream fin; fin.open(filename-cstr); fin >> ch;
- ifstream fin(filename-cstr); fin >> ch;
输入和输出流对象过期时,到文件的连接将自动关闭,也可以使用close()方式显式地关闭到文件地连接关闭这样地连接并不会删除流,而只是断开流到文件地连接
17.4.2 流状态检查和is_open()
fstream类从ios_base类那里继承类一个流状态成员,该成员存储了流状态地信息。
可以通过检查流状态来判断最后一个流操作是否成功。打开一个不存在地文件进行输入时,将设置failbit位,ifstream对象和istream对象一样,被放在需要bool值地地方,将被转换为bool值。
C++实现提供了一种更好地方法——is_open()方法,该方法可以检测到一种例外情形,程序试图以不合适地文件模式代开文件时失败。
17.4.3 打开多个文件
程序可能需要打开多个文件,一次处理一组文件时,可以使用以下代码实现:
ifstream fin("fat.txt");
...
fin.close();
fin.clear();
fin.open("rat.txt");
...
17.4.4 命令行处理计数
Windows、UNIX或Linux系统中计算文件包含的字数,可以在命令行输入如下命令:
wc report1 report2 report3
wc 是程序名, report1 report2 report3是作为命令行参数传递程序地文件名
C++有一种让在命令行环境中运行地程序能够访问命令行参数地机制,方法是使用下面地main()函数:
int main(int argc, char * argv[])
for (int i = 1; i < argc; i++ )
argv[i]; // 处理文件
17.4.5 文件模式
文件模式描述的是文件如何被使用:读、写、追加等。将流与文件关联时,可以提供指定文件模式的第二个参数。ios_base定义了一个openmode类型,用于表示模式,它也是一种bitmask类型。
文件模式常量
常量 | 含义 |
---|---|
ios_base::in | 打开文件,以便读取 |
ios_base::out | 打开文件,以便写入 |
ios_base::ate | 打开文件,并移到文件尾 |
ios_base::app | 追加到文件尾 |
ios_base::trunc | 如果文件存在,则截短文件 |
ios_base::binary | 二进制文件 |
ifstream和ofstream的构造函数和open()方法为指定文件模式的参数提供了默认值。
ifstream使用的模式为ios_base::in
ofstream使用的模式为ios_base::out|ios_base::trunc(打开文件并截短文件)
位运算符OR(|)用于将两个位值合并成一个可用于设置两个位的值。
fstream类不提供默认模式的值,创建fstream对象时,必须显式的提供模式
C++和C的文件打开模式
C++模式 | C模式 | 含义 |
---|---|---|
ios_base::in | "r" | 打开以读取 |
ios_base::out | "w" | 等价于ios_base::out|ios_base::trunc |
ios_base::out|ios_base::trunc | "w" | 打开文件以写入,如果已存在,则截短文件 |
ios_base::out|ios_base::app | "a" | 打开文件以写入,只追加 |
ios_base::in|ios_base::out | "r+" | 打开文件以读写,在文件允许的位置写入 |
ios_base::in|ios_base::out|ios_base::trunc | "w+" | 打开文件以读写,如果已经存在,则首先截短文件 |
c++mode|ios_base::binary | "cmodeb" | 以C++mode(或相应的cmode)和二进制模式打开;例如, ios_base::in|ios_base::binary成为"rb" |
c++mode|ios_base::ate | "cmode" | 以指定的模式打开,并移到文件尾。C使用一个独立的函数 调用,而不是模式编码。例如,ios_base::in|ios_base::ate 被转换为"r"模式和C函数调用fseek(file, 0, SEEK_END) |
ios_base::ate和ios_base::app都将文件指针指向打开的文件尾,区别在于,ios_base::app模式只允许将数据添加到文件尾,而ios_base::ate模式将指针放到文件尾
1.追加文件
2.二进制文件
将数据存储在文件中,可以将其存储尾文本格式或二进制格式。
文本格式便于读取,可以使用编辑器或字处理器来读取和编辑文本文件,方便在系统间转移。
二进制格式对于数字来说比较精确,不会有转换误差或舍入误差,二进制格式保存数据的速度更快,因为不需要转换,可以大块的存储数据,二进制格式通常占用的空间较小,这取决于数据的特征。
要以二进制格式保存,可以使用计算机的内部数据表示,将结构作为一个整体保存。方法如下:
- 使用二进制模式
- 使用成员函数write((char*) & pl, sizeof pl)
将结果存储在二进制文件中,同样的方法也适用于不适用虚函数的类。存储类时,只有数据成员被保存,而方法不会被保存。read()和write()成员函数的功能是相反的。
内嵌函数的形式:
inline void eatline() { while (std::cin.get() != '\n') continue; }
配合cin.get(char *, int len)使用,效果很好
17.4.6 随机存取
随机存取指的是直接移动(不是一次移动)到文件的任何位置。随机存取常被用于数据库文件中,程序维护一个独立的索引文件,该文件指出数据在主数据中的位置。
文件记录对应于程序结构或类。
要实现随机存取,需要使用fstream对象,模式选择:
ios_base::in|ios_base::out|iso_base::binary
在文件中移动的方式,fstream类继承了两个方式seekg()和seekp(),前者是输入指针移动到指定文件位置,后世是输出指针移到指定的文件位置(实际上指针指向的是缓冲区中的位置,而不是实际的文件)。
istream & seekg(streamoff, ios_base::seekdir);
istream & seekg(streampos);
第一个原型定位到离第二个参数指定的文件位置的特定距离(单位为字节)的位置;第二个原型定位到文件开头的特定距离(单位为字节的位置)。
seek_dir参数是ios_base类中定义的另一种整形,有三个可能的位置:
- ios_base::beg 文件开始处;
- ios_base::cur 当前位置;
- ios_base::end 文件尾
streamoff可以是正值,可以是负值,也可以是0。
streampos类型的值定位到文件中的一个位置,看作是相对于文件开始处的位置。
如果要检查文件指针的当前位置,可以使用tellg()方法和tellp()方法,它们都返回一个表示当前位置的streampos值(以字节为单位,从文件开始处算起)。
使用临时文件
开发应用程序时,经常需要使用临时文件,这种文件的存在是短暂的,必须受程序控制。头文件cstdio中的tmpnam()标准函数可以创建不同名的名字。
17.5 内核格式化
istream族支持程序与终端之间的I/O,fstream族使用相同的接口提供程序与文件之间的I/O,C++库还提供了sstream族,使用相同的接口提供程序和string对象之间的I/O。
读取string对象中的格式化信息或将格式化信息写入string对象中被称为内核格式化(incore formatting)
头文件sstream定义了一个从ostream类派生而来的ostringstream类,如果创建了一个ostringstream对象,可以将信息写入其中,他将存储这些信息,可以将cout的方法用于ostringstream对象。
使用str()方法可以冻结该对象,不能将信息再写入该对象中。
istringstream允许使用istream方法读取istringtream对象中的数据,istringstream对象可以使用string对象进行初始化。
17.6 复习题
1.isotream文件在C++I/O中扮演何种角色?
iostream文件在C++提供了输入流和输出流的类定义和类方法,提供了连接程序和终端的字节流。
iostream文件定义了用于管理输入和输出的类、常量和操作符,这些对象管理用于处理I/O的流和缓冲区。该文件还创建了一些标准对象(cin、cout、cerr和clog以及对应的宽字符对象),用于处理与每个程序相连的标准输入和输出流
2.为什么键入数字(如121)作为输入程序要求进行转换?
键入121是字节流,是字符1、2、1三个字符编码,而程序内存储是以值的二进制存储信息的,因此要求程序进行转换。
3.标准输出与标准错误之间的区别?
标准错误cerr会直接显式,不会等待刷新缓冲区,而标准输出会等待刷新缓冲区,在进行文件输出时,cerr信息会显示在终端上,而不是文件上。默认情况下,标准输出和标准错误都将输出发送给标准输出设备(通常是显示器)。如果要求操作系统将输出重定向到文件,则标准输出将与文件(而不是显示器)相连,但标准错误仍与显示器相连
4.为什么在不为每个类型提供明确指示的情况下,cout仍能够显示不同的C++类型?
C++插入控制符对所有基本的数据类型提供了重载函数,cout会根据C++的数据类型,自动选择相应的插入控制符重载函数。
5.输出方法的定义的哪一个特征能让您能够拼接输出?
输出方法都定义的返回调用方法的对象引用使我们可以用插入运算符拼接输出。
6.编写一个程序,要求用户输入一个整数,然后以十进制、八进制和十六进制显式该整数。在宽度为15个字符的字段中显示每种形式,并将它们显示在同一行,同时使用C++数基前缀。
程序如下:
// ex6.cpp -- cout a integer in dec oct and hex
#include <iostream>
#include <iomanip>
int main()
{
using namespace std;
int n;
cout << "Enter an integer: ";
cin >> n;
// use ios_base::fmtflags
cout.setf(ios_base::showbase);
cout.width(15);
cout << n;
// cout.setf(ios_base::oct); // is not work, add ios::field, can't find the field
cout.width(15);
cout << oct << n;
cout.width(15);
cout.setf(ios_base::hex);
cout << hex << n << endl;
cout.unsetf(ios_base::showbase);
// use iomanip
cout << showbase << setw(15) << dec << n
<< setw(15) << oct << n
<< setw(15) << hex << n << endl << noshowbase;
return 0;
}
7.编写一个程序,请求用户输入下面的信息,并按下面的格式显示它们:
Enter your name: Billy Gruff
Enter your hourly wages: 12
Enter numbers of hours: 7.5
First format:
Billy Gruff: $ 12.00: 7.5
Second format:
Billy Gruff : $12.00 :7.5
// ex7.cpp -- set output format
#include <iostream>
#include <iomanip>
int main()
{
using namespace std;
string name;
double wages_h;
double hours;
cout << "Enter your name: ";
getline(cin, name);
cout << "Enter your hourly wages: ";
cin >> wages_h;
cout << "Enter numbers of hours: ";
cin >> hours;
cout << "First format:\n";
cout << setw(30) << name << ": $" << fixed
<< setw(10) << setprecision(2) << wages_h << ":"
<< setw(8) << setprecision(1) << hours << endl;
cout << "Second format:\n";
cout << left << setw(30) << name << ": $" << fixed
<< setw(10) << setprecision(2) << wages_h << ":"
<< setw(8) << setprecision(1) << hours << endl;
return 0;
}
8.对于下面的程序:
// ex8.cpp -- rq17-8.cpp
#include <iostream>
int main()
{
using namespace std;
char ch;
int ct1 = 0;
cin >> ch;
while (ch != 'q')
{
ct1++;
cin >> ch;
}
int ct2 = 0;
cin.get(ch);
while (ch != 'q')
{
ct2++;
cin.get(ch);
}
cout << "ct1 = " << ct1 << "; ct2 = " << ct2 << "\n";
return 0;
}
如果输入如下内容,该程序将打印什么内容?
I see a q<Enter>
I see a q<Enter>
其中,<Enter>表示按回车键。
输出5和8,输出ct1 = 5; ct2 = 9
程序的后半部分从第一个q后面的换行符开始读取,将换行符计算在内。
9.下面的两条语句都读取并丢弃行尾之前的所有字符(包括行尾)。这两条语句的行为在哪些方面不同?
while (cin.get() != '\n')
continue;
cin.ignore(80, '\n');
第一个语句会抛弃行尾之前的所有字符,第二个语句会抛弃80个字符。当输入行超过80个字符,igonre()将不能正常工作
cin.get()是按字符抽取,cin.ignore()是。