C++ primer plus读书笔记——第17章 输入、输出和文件

第17章 输入、输出和文件

1. 对键盘进行输入缓冲可以让用户在将输入传输给程序之前返回并更正。C++程序通常在用户按下回车键时刷新输入缓冲区。

2. 一些I/O类

streambuf类为缓冲区提供了内存,并提供了用于填充缓冲区、访问缓冲区、刷新缓冲区和管理缓冲区内存的类方法。

ios_base类表示流的一般特征,如是否可读取、是二进制流还是文本流等。

ios类基于ios_base类,其中包括了一个指向streambuf对象的指针成员。

ostream类从ios类派生而来,提供了输出方法。

istream类从ios类派生而来,提供了输入方法。

iostream类是基于istream类和ostream类的,因此继承了输入方法和输出方法。

3. C++11提供了I/O的char和wchar_t具体化。例如,istream和ostream都是char具体化的typedef。同样,wistream和wostream都是wchar_t具体化。例如,wcout对象用于输出宽字符流。头文件ostream中包含了这些定义。

4. 在程序中包含iostream文件将自动创建8个流对象(4个用于窄字符流,4个用于宽字符流)。

cin对象对应于标准输入流。wcin对象与此类似,但处理的是wchar_t类型。

cout对象与标准输出流相对应。wcout对象与此类似,但处理的是wchar_t类型。

cerr对象与标准错误流相对应,可用于显示错误信息。这个流没有被缓冲,这意味着信息将被直接发送给屏幕,而不会等到缓冲区填满或新的换行符。wcerr与此类似。

5. 对象代表流——这意味着什么呢?当iostream文件为程序声明一个cout对象时,该对象将包含存储了与输出有关的信息的数据成员,如显示数据是使用的字符宽度、小数位数、显示整数时使用的计数方法以及描述用来处理输出流缓冲区的streambuf对象的地址。

6. C++将输出看作是字节流(根据实现和平台的不同,可能是8位、16位或32位的字节,但都是字节)。因此,ostream类最重要的任务之一就是将数值类型(如int或float)转换为以文本表示的字符流。

7. cout字符串指针将显示字符串。对于其它类型的指针,C++打印地址的数值表示。如果要获得字符串的地址,则必须将其强制转换为其它类型。

int eggs = 12;

char *amount = “dozen”;

cout << &eggs;  //打印eggs变量的地址

cout << amount; //打印字符串“dozen”

cout << (void *)amount;//打印字符串“dozen”的地址

8. 其它ostream方法

除了各种operator<<()函数外,ostream类还提供了put()方法和write()方法,前者用于显示字符,后者用于显示字符串。

cout.put(‘w’).put(‘t’);

write()方法显示整个字符串,其模板原型如下:

basic_ostream<chart, traits> & write(const char_type *s, streamsize n);

write的第一个参数提供了要显示的字符串的地址,第二个参数指出了要显示多少个字符串。

还需要注意的是,write()方法并不会在遇到空字符时自动停止打印字符,而只是打印指定数目的字符,即使超出了字符串的边界。

但write()确实为将数据存储在文件中提供了一种简洁、准确的方式。

9. ostream类对cout对象处理的输出进行缓冲,所以输出不会立即发送到目标地址,而是被存储在缓冲区中,直到缓冲区填满。通常,缓冲区为512字节或其整数倍。在屏幕输出时,程序不必等到缓冲区被填满。例如,将换行符发送到缓冲区后,将刷新缓冲区。另外,正如前面指出的,多数C++实现都会在输入即将发生时刷新缓冲区。

假设有如下代码:

cout << “Enter a number: ”;

float num;

cin >> num;

它会立刻显示cout信息,即使输出字符串中没有换行符。

10. 有两个控制符可以强行刷新缓冲区。控制符flush刷新缓冲区,而控制符endl刷新缓冲区,并插入一个换行符。

cout << “Hello, good-looking!” << flush;

cout << “wait a moment, please” << endl;

事实上,控制符也是函数。可以直接使用flush来刷新缓冲区:

flush(cout);

11. cout一个浮点数时,默认情况是,当指数大于等于6或小于等于-5时,将使用科学计数法来表示。

12. ostream类是从ios类派生而来的,而后者是从ios_base类派生而来的。ios_base类存储了描述格式状态的信息。由于ios_base类是ostream类的间接基类,因此可以将其方法用于ostream对象,如cout。

13. 修改计数系统

要控制整数以十进制、十六进制还是八进制显示,可以使用dec、hex和oct控制符。

hex(cout);

虽然控制符实际上是函数,但它们更通用的使用方式是:

cout << hex;

14. 调整字段宽度

cout.width();       //返回字段宽带的当前设置

cout.width(int i);  //将字段宽度设置为i,并返回以前的字段宽度值

width()方法只影响接下来显示的一个项目,然后字段宽度将恢复默认值。

默认的字段宽带为0,适用于所有的数据。因为C++永远不会截短数据,因此如果试图在宽度为2的字段中打印一个7位值,C++将增宽字段,以容纳该数据。C/C++的原则是:显示所有数据比保持列的整洁性更加重要。C++是内容终于形式。

15. 填充字符

在默认情况下,cout使用空格填充字段中未被使用的部分。

cout.fill(‘*’);  //将填充字符改为星号

这对于检查打印结果,防止接收方添加数据很有用。

与字段宽度不同的是,新的填充字符一直有效,知道更改它为止。

16. 设置浮点数的显示精度

浮点数的精度含义取决于输出模式。在默认模式下,它指的是显示的总位数。在定点模式和科学模式下,精度指的是小数点后面的位数。C++的默认精度是6位。

cout.precision(2);

新的精度设置会一直有效,知道被重新设置为止。

17. 打印末尾的0和小数点

cout.setf(ios_base::showpoint);

将显示末尾的小数点,还将导致末尾的0被显示出来。例如2,将会显示为2.000000。

showpoint是ios_base类声明中定义的类级静态常量。

18. setf()函数有两个原型。第一个为:

fmtflags setf(fmtflags);

ios_base定义了格式常量:

ios_base::boolalpha  输入和输出bool值,可以为true或false

ios_base::showbase   对于输出,使用C++基数前缀(0,0x)

ios_base::showpoint  显示末尾的小数点

ios_base::uppercase  对于16进制输出,使用大写字母

ios_base::showpos     在整数前面加上+

注意,仅当基数为10时,才使用加号或符号。C++将十六进制和八进制都视为无符号。

19. 第二个setf()原型接受两个参数,并返回以前的设置:

fmtflags setf(fmtflags, fmtflags);

第一个参数表示所需设置的fmtflags值,第二个参数表示要清除第一个参数中的哪些位。例如,将第3位设置为1表示以10为基数,将第4位设置为1表示以8为基数,将第5位设置为1表示以16为基数。假设输出是以10为基数的,而要将它设置为以16为基数,则不仅要将第5位置为1,还需要将第3为置为0。

cout.setf(ios_base::hex, ios_base::basefield);

第二个参数

第一个参数

含义

ios_base::basefield

ios_base::dec

使用基数10

ios_base::oct

使用基数8

ios_base::hex

使用基数16

ios_base::floatfield

ios_base::fixed

定点计数法

ios_base::scientific

科学计数法

ios_base::adjustfield

ios_base::left

左对齐

ios_base::right

右对齐

ios_base::internal

符号或基数前缀左对齐,值右对齐

20. 如果您熟悉C语言的printf()说明符,则可能知道,默认的C++模式对应于%g说明符,定点表示法对应于%f说明符,而科学表示法对应于%e说明符。

21. 在C++标准中,默认表示法精度是总位数,不显示末尾的0。而定点表示法和科学表示法都有下面的两个特征:

精度指的是小数位数,而不是总位数;

显示末尾的0。

22. 调用setf()的效果可以通过unsetf()消除,后者原型如下:

void unsetf(fmtflags mask);

cout.unsetf(ios_base::boolalpha);

您可能注意到了,没有专门指示浮点数默认显示模式的标记。系统的工作原理如下:仅当只有定点为被设置时使用定点表示法;仅当只有科学位被设置时使用科学表示法;如没有被设置或两位都被设置时,使用默认表示法。因此,启用默认模式的方法如下:

cout.setf(0, ios_base::floatfield);

cout.unsetf(ios_base::floatfield);

23. 使用setf()不是进行格式化、对用户最为友好的方法,C++提供了多个控制符,能够调用setf(),并自动提供正确的参数。P751-P752

24. 头文件iomanip

使用iostream工具来设置一些格式值不太方便。为简化工作,C++在头文件iomanip中提供了一些控制符,它们能够提供前面讨论过的服务,但表示起来更方便。3个最常用的控制符分别是setprecision()、setfill()、setw(),与前面讨论的控制符不同的是,这三个控制符都带参数。

#include <iostream>

#include <iomanip>

#include <math>

int main()

{

using namespace std;

int n = 10;

cout << fixed << right;

cout << setw(6) << setfill(.) << n << setfill(‘ ’)

<< setw(12) << setprecision(3) << sqrt(root) << endl;

return 0;

}

25. 典型的运算符函数的原型如下:

istream & operator>>(int &);

参数和返回值都是引用。由于参数是引用,因此cin能够修改用作参数的变量的值。返回调用对象的引用,使得输入能够拼接起来。

26. 顺便说一句,可以将hex、oct和dec控制符与cin一起使用,来指定将整数输入解释为十六进制、八进制还是十进制格式。cin >> hex;

27. istream还为字符指针重载了>>抽取运算符。

抽取运算符将读取输入中的下一个单词,将它放置到指定的地址,并加上一个空值字符,使之称为一个字符串。

char name[20];

cin >> name;

28. while(cin >> input)如果istream对象的错误状态被设置,if或while语句将判定该对象为false。

29. cin或cout包含一个描述溜状态的数据成员,从ios_base类那里继承的。流状态(被定义为iostate类型,而iostate是一种bitmask类型)由3个ios_base元素组成:eofbit、badbit或failbit,其中每个元素都是1位。

30. 当cin操作到达文件尾时,它将设置eofbit;当cin操作未能读取到预期的输入时,它将设置failbit。I/O失败(如试图读取不可访问的文件或试图写入写保护的磁盘),也可能将failbit设置为1。在一些无法诊断的失败破坏流时,badbit元素将被设置。P756很多流状态相关的函数

31. 流状态位被设置后,流将对后面的输入或输出关闭,直到位被清除。

while(cin >> input)

{

sum += input;

}

if(cin.fail() && !cin.eof())

{

cin.clear();                 //reset stream state

while(!isspace(cin.get()))//get rid of bad input

continue;

}

else

{

cout << “I can not go on!\n”;

exit(1);

}

cout << “Now enter a new number: ”;

cin >> input;

32. 单字符输入

在使用char参数或没有参数的情况下,get()方法读取下一个输入字符,即使该字符是空格、制表符或换行符。get(char &)版本将输入字符赋给其参数,并返回istream对象的引用,到达文件尾时,返回值判定为false;而get(void)版本将输入字符转换为整形(通常是int),并将其返回,到达文件尾时,返回值EOF。

while(cin.get(ch))

{
}

while((ch = cin.get()) != EOF)

{
}

在get()方法中,get(char &)的接口更佳。get(void)的主要优点是,它与标准C语言中的getchar()函数极其相似,这意味着可以通过包含iostream(而不是stdio.h),并用cin.get()替换所有的getchar(),用cout.put(ch)替换所有的putchar(ch),来将C程序转换为C++。

33. 通过键盘可以仿真文件尾,对于DOS和Windows命令提示符模式,为按下Ctrl+Z;对于Unix,是在行首按下Ctrl+D。

34. 字符串输入

istream & get(char *, int, char);

istream & get(char *, int);

istream & getline(char *, int, char);

istream & getline(char *, int);

第一个参数是用于放置输入字符串的内存单元地址。第二个参数比要读取的最大字符数大1。第3个参数指定用作分界符的字符,只有两个参数的版本将换行符用作分界符。

get()和getline()之间的区别在于,get()将换行符留在输入流中,这样接下来的输入操作首先看到的将是换行符;而getline()抽取并丢弃输入流中的换行符。

istream & ignore(int n = 1, int ch = EOF);

cin.ignore(255, ‘\n’);丢弃接下来的255个字符或直到到达第一个换行符。

方法

行为

getline(char *, int)

遇到文件尾,则设置failbit;

如果读取了最大数目的字符,且下一个字符不是换行符,则设置failbit;

get(char *, int)

遇到文件尾,则设置failbit;

遇到空行,则设置failbit;

35. 其它istream方法read()、peek()、gcount()、putback()

read()读取指定数目的字节,并将它们存储在指定的位置中。

char gross[144];

cin.read(gross, 144);

与getline()和get()不同的是,read()不会在输入后加上空值字符。

peek()返回输入中的下一个字符,但不抽取输入流中的字符。

gcount()方法返回最后一个非格式化抽取方法读取的字符数。这意味着字符是由get()、getline()、ignore()或read()方法读取的,不是由抽取运算符读取的(>>),抽取运算符对输入进行格式化,使之与特定的数据类型匹配。

putback()函数将一个字符插入到输入字符串中,被插入的字符将是下一条输入语句的第一个字符。putback()方法接受一个char参数,返回istream &。

36. 用于文件输入的ifstream类和用于文件输出的ofstream类都是从头文件iostream中的类派生而来的,因此这些新类的对象可以使用前面介绍过的方法。

37. 文件I/O要包含头文件fstream。对于大多数实现来说,包含该文件便自动包含iostream头文件,因此不必显式包含iostream。

38. 要将文件输出流对象与特定的文件关联,可以使用open()方法:

ofstream fout;

fout.open(“jar.txt”);

可以使用另一个构造函数将这两步合并成一个语句:

ofstream(“jar.txt”);

类似地,声明一个ifstream对象,将它与文件名关联起来,可以这么做:

ifstream fin;

fin.open(“jellyjar.txt”);

ifstream fin(“jellyjar.txt”);

39. 当输入或输出流对象过期时,文件的连接将自动关闭,另外,也可以使用close()方法来显示地关闭文件的连接:

fout.close();

fin.close();

关闭这样的连接并不会删除流,而只是断开流到文件的连接。例如,fin对象与它管理的输入缓冲区仍然存在。可以将流重新连接到同一个文件或另一个文件。

40. 小技巧:

string filename;

cin >> filename;

ofstream fout(filename.c_str());

41. C++文件流类从ios_base类那里继承了一个流状态成员。以前,检查文件是否打开的常见方式如下:

if(fin.fail())…

if(!fin.good())…

if(!fin)…

上面三种方式等价,然而,这些测试无法检测这样一种情形:试图以不合适的文件模式打开文件失败时。方法is_open()能够检测到这种错误以及good()能够检测到的错误。然而,老式C++没有实现is_open()。

42. 有些C++实现要求在重新关联文件时使用fin.clear(),有些则不要求,这取决于将文件与ifstream对象关联起来时,是否自动重置流状态。使用fin.clear()是无害的,即使在不必使用它的时候。

43. 将流与文件关联时,都可以提供第二个参数指定文件模式。

ifstream fin(“banjo”, mode1);

  • ofstream fout;

fout.open();

fout.open(“banjo”, mode2);

常量

含义

ios_base::in

打开文件,以便读取

ios_base::out

打开文件,以便写入

ios_base::ate

打开文件,并移到文件尾

ios_base::app

追加到文件尾

ios_base::trunc

如果文件存在,则截断文件

ios_base::binary

二进制文件

注意,ios_base::ate和ios_base::app都将文件指针指向打开的文件尾。二者的区别在于,ios_base::app模式只允许将数据添加到文件尾,而ios_base::ate模式将指针放到文件尾。

44. 文本文件和二进制文件。对于字符来说,二进制表示和文本表示是一样的,即字符的ASCII码表示。对数字来说,二进制文件与文本文件有很大区别。

45. 文本格式便于读取,可以使用编辑器或字处理器来读取和编辑文本文件,可以很方便地将文本文件从一个计算机系统传输到另一个计算机系统。二进制格式对于数字来说比较精确,不会有转换误差或舍入误差,而且保存数据的速度快,占用空间小。

46. 将数据以文本文件或二进制文件保存的方法:

文本文件:

cons tint LIM = 20;

struct planet

{

char name[LIM];

double population;

double g;

};

planet pl;

ofstream fout(“planet.dat”, ios_base::out | ios_base::app);

fout << pl.name << “ “ << pl.population << “ “ << pl.g << endl;

必须使用成员运算符显式的提供每个结构成员,还必须将相邻的数据分隔开,以便区分。

要用二进制格式存储相同的信息,可以这样做:

ofstream fout(“planet.dat”, ios_base::out | ios_base::app | ios_base::binary);

fout.write((char *)&pl, sizeof pl);

与文本文件相比,信息的保存更加紧凑,精确。

47. 将struct数据以二进制保存,这种方法适用于不使用虚函数的类。在这种情况下,只有数据成员被保存,而方法不会被保存。如果类有虚方法,则也将复制隐藏指针(该指针指向虚函数的指针表)。由于下一次运行程序时,虚函数表可能在不同位置,因此将文件中的旧指针信息复制到对象中,将可能造成混乱。

48. string对象本身实际上并没有包含字符串,而是包含了一个指向其中存储了字符串内存单元的指针。

49. 随机存取常用于数据库文件,程序跳到数据在文件中的位置,读取其中的数据。为了能够读取和修改记录,可以创建一个fstream对象,以读写模式打开文件。fstream类是从iostream类派生而来的,而后者基于istream和ostream两个类。因此,它继承了它们的方法。还继承了两个缓冲区,一个用于输入,一个用于输出,并能同步化这两个缓冲区的处理。

fstream finout;

finout.open(file, ios_base::in | ios_base::out | ios_base::binary);

50. fstream类为此继承了两个方法:seekg()将输入指针移到指定的文件位置,后者将输出指针移到指定的文件位置(实际上,由于fstream使用缓冲区来存储中间数据,因此指针指向的是缓冲区的位置,而不是实际的文件)。也可以将seekg()用于ifstream对象,将seekp()用于ofstream对象。

seekg()的原型如下:

basic_istream<charT, traits>& seekg(off_type, ios_base::seekdir);

basic_istream<charT, traits>& seekg(pos_type);

由于char具体化,上面两个原型等同于下面的代码:

istream & seekg(streamoff, ios_base::seekdir);

istream & seekg(streampos);

第一个原型定位到离第二个参数指定的文件位置特定距离的位置;第二个原型定位到离文件头特定距离的位置。

fin.seekg(30, ios_base::beg);

fin.seekg(-1, ios_base::cur);

fin.seekg(0, ios_base::end);

fin.seekg(123);

P785-P787完整程序。

tellg()和tellp()方法报告当前的文件位置。

51. 生成临时文件名char * tmpnam(char *pszName);P787-P788

52. iostream族支持程序与终端之间的I/O,而fstream族提供程序和文件之间的I/O。C++还提供了sstream族,它们使用相同的接口提供程序和string对象之间的I/O。

53. 读取string对象中的格式化信息或将格式化信息写入string对象中被称为内核格式化(incore formatting)。

54. 头文件sstream定义了一个从ostream类派生而来的ostringstream类(还有一个基于wostream的wostringstream类,用于宽字符集)。如果创建了一个ostringstream对象,则可以将信息写入其中,它将存储这些信息。

55. ostringstream类有一个名为str()的成员函数,该函数返回一个被初始化为缓冲区内容的字符串对象:

string mesg = outstr.str();

使用str()方法可以“冻结”该对象,这样便不能将信息写入该对象中。

56. istringstream类允许使用istream方法族读取istringstream对象中的数据,istringstream对象可以使用string对象进行初始化。

57. istringstream和ostringstream类使得能够使用istream和ostream类的方法来管理存储在字符串中的字符数据。

上一篇:HTML学习-1网页基础知识


下一篇:mysql之select(一)