导图社区 C++第17章:输入、输出和文件
《C Primer Plus》第17章同步思维导图,介绍了有关C 输入、输出及文件的内容。包括istream、ostream类及其类对象cin、cout。详细讲解了C 输入输出的原理以及细节操作,同时涉及C 中文件的数据传输内容。
编辑于2021-01-19 08:32:55第17章 输入、输出和文件
17.1 C++输入和输出概述
17.1.1 流和缓冲区
C++程序只是检查字节流,而不需要知道字节来自何方。同理,通过使用流,C++程序处理输出的方式将独立于其去向。因此管理输入包含两步: · 将流与输入去向的程序关联起来。 · 将流与文件连接起来。
17.1.2 流、缓冲区和iostream文件
1. 管理流和缓冲区的类
· streambuf类
为缓冲区提供了内存,并提供了用于填充缓冲区、访问缓冲区内容、刷新缓冲区和管理缓冲区内存的类方法
· ios_base类
表示流的一般特征,如是否可读取、是二进制流还是文本流等
· ios类
基于ios_base,其中包括了一个指向streambuf对象的指针成员
· ostream类
从ios类派生而来的,提供了输出方法
· istream类
从ios类派生而来的,提供了输入方法
· iostream类
基于istream和ostream类的,因此继承了输入方法和输出方法
2. iostream的8个流对象
· cin对象对应于标准输入流。在默认情况下,这个流被关联到标准输入设备(通常为键盘)。wcin对象与此类似,但处理的是wchar_t 类型
· cout对象与标准输出流相对应。在默认情况下,这个流被关联到标准输出设备(通常为显示器)。wcout对象与此类似,但处理的是wchar_t类型
· cerr对象与标准错误流相对应,可用于显示错误消息。在默认情况下,这个流被关联到标准输出设备(通常为显示器)。这个流没有被缓冲,这意味着信息将被直接发送给屏幕,而不会等到缓冲区填满或新的换行符。wcerr对象与此类似,但处理的是wchar_t类型
· clog对象也对应着标准错误流。在默认情况下,这个流被关联到标准输出设备(通常为显示器)。这个流被缓冲。wclog对象与此类似,但处理的是wchar_t类型
· 对象代表流——这意味着什么呢?当iostream文件为程序声明一个cout对象时,该对象将包含存储了与输出有关的信息的数据成员,如显示数据时使用的字段宽度、小数位数、显示整数时采用的计数方法以及描述用来处理输出流的缓冲区的streambuf对象的地址。下面的语句通过指向的streambuf对象将字串“Bjarna free”中的字符放到cout管理的缓冲区中: cout << "Bjarne free";
17.1.3 重定向
重定向得能够改变标准输入和标准输出。 例如,假设有一个名为counter.exe的、可执行的Windows命令提示符C++程序,它能够计算输入中的字符数,并报告结果。该程序的运行情况如下: C>counter Hello and goodbye! Control-Z << simulated end-of-file Input contained 19 characters. C> 其中的输入来自键盘,输出的被显示到屏幕上。通过输入重定向(<)和输出重定向(>),可以使用上述程序计算文件oklahoma中的字符数,并将结果放到cow_cnt文件中: cow_cnt file: C>counter <oklahoma >cow_cnt C> 命令行中的<oklahoma将标准输入与oklahoma文件关联起来,使cin从该文件(而不是键盘)读取输入。换句话说,操作系统改变了输入流的流入端连接,而流出端仍然与程序相连。命令行中的>cow_cnt将标准输出与cow_cnt文件关联起来,导致cout将输出发送给文件(而不是屏幕)。也就是说,操作系统改变了输出流的流出端连接,而流入端仍与程序相连。DOS、Windows命令提示符模式、Linux和UNIX能自动识别这种重定向语法
cout代表的标准输出流是程序输出的常用通道。标准错误流(由cerr和clog代表)用于程序的错误消息。默认情况下,这3个对象都被发送给显示器。但对标准输出重定向并不会影响cerr或clog,因此,如果使用其中一个对象来打印错误消息,程序将在屏幕上显示错误消息,即使常规的cout输出被重定向到其他地方。例如,请看下面的代码片段: if (success) std::cout << "Here come the gooodies!\n"; else { std::cerr << "Something horrible has happened.\n"; exit(1); } 如果重定向没有起作用,则选定的消息都将被显示在屏幕上。然而,如果程序输出被重定向到一个文件,则第一条消息(如果被选定)将被发送到文件中,而第二条消息(如果被选定)将被发送到屏幕。顺便说一句,有些操作系统也允许对标准错误进行重定向。例如,在UNIX和Linux中,运算符2>重定向标准错误
17.2 使用cout进行输出
17.2.1 重载的<<运算符
1. 输出和指针
ostream类为下面的指针定义了插入运算符函数: · const signed char* · const unsigned char* · const char* · void*
C++用指向字符串存储位置的指针来表示字符串。指针的形式可以是char数组名、显式的char指针或用引号括起的字符串。因此,下面所有的cout语句都显示字符串: char name[20] = "Dudly Diddlemore"; char* pn = "Violet D'Amore"; cout << "Hello!"; cout << name; cout << pn; 方法使用字符串中的终止空字符来确定何时停止显示字符
对于其他类型的指针,C++将其对应于void *,并打印地址的数值表示。如果要获得字符串的地址,则必须将其强制转换为其他类型,如下面的代码片段所示: int eggs = 12; char* amount = "dozen"; cout << &eggs; // prints the address of eggs variable cout << amount; // prints the string "dozen" cout << (void*) amount; // prints the address of the "dozen" string
2. 拼接输出
插入运算符的所有化身的返回类型都是ostream &。也就是说,原型的格式如下: ostream&<< operator(type); (其中,type是要显示的数据的类型)返回类型ostream &意味着使用该运算符将返回一个指向ostream对象的引用。哪个对象呢?函数定义指出,引用将指向用于调用该运算符的对象。换句话说,运算符函数的返回值为调用运算符的对象。例如,cout << “potluck”返回的是cout对象。这种特性使得能够通过插入来连接输出。例如,请看下面的语句: cout <<"We have" << count << " unhatched chickens.\n"; 表达式cout << “We have”将显示字符串,并返回cout对象。至此,上述语句将变为: cout << count << " unhatched chickens.\n";
17.2.2 其他ostream方法
1. cout.put()
(1)原型:
ostream& put(char);
(2)使用
· 被模板化,可以用于wchar_t
· 可拼接: cout.put('I').put('t');
· 在原型合适的情况下,可以将数值型参数(如int)用于put(),让函数原型自动将参数转换为正确char值: cout.put(65); // display the A character cout.put(66.3); // display the B character
2. cout.write()
(1)原型
basic_ostream<charT, treaits>& write(const char_type* s, streamsize n);
(2)使用
· write()的第一个参数提供了要显示的字符串的地址,第二个参数指出要显示多少个字符。使用cout调用write()时,将调用char具体化,因此返回类型为ostream &
· 可拼接: cout.write(state2, i) << endl;
· write()方法并不会在遇到空字符时自动停止打印字符,而只是打印指定数目的字符,即使超出了字符串的边界!
· write()方法也可用于数值数据,您可以将数字的地址强制转换为char *,然后传递给它: long val = 560031841; cout.write((char*) &val, sizeof(long)); 这不会将数字转换为相应的字符,而是传输内存中存储的位表示。例如,4字节的long值(如560031841)将作为4个独立的字节被传输。输出设备(如显示器)将把每个字节作为ASCII码进行解释。因此在屏幕上,560031841将被显示为4个字符的组合,这很可能是乱码(也可能不是,请试试看)。然而,write()确实为将数值数据存储在文件中提供了一种简洁、准确的方式
17.2.3 刷新输出缓冲区
1. 缓冲区的特性
· 由于 ostream类对cout对象处理的输出进行缓冲,所以输出不会立即发送到目标地址,而是被存储在缓冲区中,直到缓冲区填满。然后,程序将刷新(flush)缓冲区,把内容发送出去,并清空缓冲区,以存储新的数据。通常,缓冲区为512字节或其整数倍。当标准输出连接的是硬盘上的文件时,缓冲可以节省大量的时间
· 多数C++实现都会在输入即将发生时刷新缓冲区。也就是说,假设有下面的代码: cout << "Enter a number:"; float num; cin >> num; 程序期待输入这一事实,将导致它立刻显示cout消息(即刷新“Enter a number:”消息),即使输出字符串中没有换行符。如果没有这种特性,程序将等待输入,而无法通过cout消息来提示用户
2. 用flush和endl刷新
flush:刷新缓冲区。 cout << "Hello" << flush; flush(cout);
cout:刷新缓冲区,并插入一个换行符。 cout << "Hello" << endl; endl(cout);
17.2.4 用cout进行格式化
格式化效果一直持续 的方式用xxx表示, 否则用xxx表示
1. cout默认情况下格式化值的方式
· char
如果它代表的是可打印字符,则将被作为一个字符显示在宽度为一个字符的字段中
· 数值整型
将以十进制方式显示在一个刚好容纳该数字及负号(如果有的话)的字段中
· 字符串
被显示在宽度等于该字符串长度的字段中
· 浮点数
浮点类型被显示为6位,末尾的0不显示(注意,显示的数字位数与数字被存储时精度没有任何关系)。数字以定点表示法显示还是以科学计数法表示,取决于它的值。具体来说,当指数大于等于6或小于等于−5时,将使用科学计数法表示。另外,字段宽度恰好容纳数字和负号(如果有的话)。默认的行为对应于带%g说明符的标准C库函数fprintf()
2. 修改显示时使用的计数系统
(1)dec(十进制)
dec(cout); cout << dec;
(2)hex(十六进制)
hex(cout); cout << hex;
(3)oct(八进制)
oct(cout); cout <<oct;
3. 调整字段宽度
cout.width()
· 原型
int width(); int width(int i);
第一种格式返回字段宽度的当前设置; 第二种格式将字段宽度设置为i个空格,并返回以前的字段宽度值。这使得能够保存以前的值,以便以后恢复宽度值时使用
· 使用
cout << '#'; cout.width(12); cout << 12 << "#" << 24 << "#\n"; 输出如下: # 12#24#
4. 填充字符
cout.fill()
cout.fill('*'); cout.width(7); cout << "What?"; 输出如下: **What?
5. 设置浮点数的显示精度
cout.precision()
浮点数精度的含义取决于输出模式。在默认模式下,它指的是显示的总位数。在定点模式和科学模式下(稍后将讨论),精度指的是小数点后面的位数。已经知道,C++的默认精度为6位(但末尾的0将不显示) cout.precision(4); cout << 1/3 << endl; cout << 12.3 << endl; 输出如下: 0.333 12.3
6. 打印末尾的0和小数点
cout.setf(ios_base::showpoint)
ios_base类提供了一 个setf()函数(用于set标记),能够控制多种格式化特性。这个类还定义了多个常量,可用作该函数的参数。 showpoint是ios_base类声明中定义的类级静态常量。类级意味着如果在成员函数定义的外面使用它,则必须在常量名前面加上作用域运算符(::)。因此 ios_base::showpoint指的是在ios_base类中定义的一个常量
7. 再谈setf()
(1)原型
fmtflags setf(fmtflags);
参数是一个fmtflags值,指出要设置哪一位。返回值是类型为fmtflags的数字,指出所有标记以前的设置。如果打算以后恢复原始设置,则可以保存这个值
fmtflags setf(fmtflags, fmtflags);
型接受两个参数,并返回以前的设置。第一参数和以前一样,也是一个包含了所需设置的fmtflags值。第二参数指出要清除第一个参数中的哪些位
例如,将第3位设置为1表示以10为基数,将第4位设置为1表示以8为基数,将第5位设置为1表示以16为基数。假设输出是以10为基数的,而要将它设置为以16为基数,则不仅需要将第5位设置为1,还需要将第3位设置为0——这叫作清除位(clearing the bit)
(2)介绍
· setf()
setf()方法控制了小数点被显示时其他几个格式选项。ios_base类有一个受保护的数据成员,其中的各位(这里叫作标记)分别控制着格式化的各个方面,例如计数系统、是否显示末尾的0等。打开一个标记称为设置标记(或位),并意味着相应的位被设置为1。位标记是编程开关,相当于设置DIP开关以配置计算机硬件。例如,hex、dec和oct控制符调整控制计数系统的3个标记位。setf()函数提供了另一种调整标记位的途径
· fmtflags
fmtflags是bitmask类型的typedef名,用于存储格式标记。该名称是在ios_base类中定义的
· bitmask
bitmask类型是一种用来存储各个位值的类型。它可以是整型、枚举,也可以是STL bitset容器。这里的主要思想是,每一位都是可以单独访问的,都有自己的含义。iostream软件包使用bitmask来存储状态信息
(3)格式常量
ios_base::boolalpha
输入和输出bool值,可以为true或false
ios_base::showbase
对于输出,使用C++基数前缀(0,0x)
ios_base::showpoint
显示末尾的小数点
ios_base::uppercase
对于16进制输出,使用大写字母,E表示法
ios_base::showpos
在正数前面加上+
(4)setf(long, long)的参数
(5)setf()的说明
定点表示法和科学表示法都有下面两个特征: · 精度指的是小数位数,而不是总位数; · 显示末尾的0
没有专门指示浮点数默认显示模式的标记。系统的工作原理如下: · 仅当只有定点位被设置时使用定点表示法; · 仅当只有科学位被设置时使用科学表示法; · 对于其他组合,如没有位被设置或两位都被设置时,将使用默认模式
(6)unsetf()
调用setf()的效果可以通过unsetf()消除,后者的原型如下: void unsetf(fmtflags mask); 其中,mask是位模式。mask中所有的位都设置为1,将使得对应的位被复位。也就是说,setf()将位设置为1,unsetf()将位恢复为0。例如: cout.setf(ios_base::showpoint); // show trailing decimal point cout.unsetf(ios_base::showpoint); // don't show trailing decimal point cout.setf(ios_base::boolalpha); // display true, false cout.unsetf(ios_base::boolalpha); // display 1, 0
(7)使用举例
· 修改和恢复设置
setf()函数是ios_base类的一个成员函数。由于这个类是ostream类的基类,因此可以使用cout对象来调用该函数。例如,要左对齐,可使用下面的调用: ios_base::fmtflags old = cout.setf(ios_base::left, ios_base::adjustfield); 要恢复以前的设置,可以这样做: cout.setf(old, ios::adjustfield);
· 设置浮点数默认显示模式
启用默认模式的方法之一如下: cout.setf(0, ios_base::floatfield); // go to default mode 第二个参数关闭这两位,而第一个参数不设置任何位。一种实现同样目标的简捷方式是,使用参数ios::floatfield来调用函数unsetf(): cout.unsetf(ios_base::floatfield); // go to default mode
8. 标准控制符
使用setf()不是进行格式化的、对用户最为友好的方法,C++提供了多个控制符,能够调用setf(),并自动提供正确的参数。前面已经介绍过dec、hex和oct,这些控制符(多数都不适用于老式C++实现)的工作方式都与hex相似。例如,下面的语句打开左对齐和定点选项: cout << left << fixed;
9. 头文件iomanip
(1)setprecision()
接受一个指定精度的整数参数
(2)setfill()
接受一个指定填充字符的char参数
(3)setw()
接受一个指定字段宽度的整数参数
17.5 内核格式化
1. 介绍
iostream族(family)支持程序与终端之间的I/O,而fstream族使用相同的接口提供程序和文件之间的I/O。C++库还提供了sstream族,它们使用相同的接口提供程序和string对象之间的I/O。也就是说,可以使用于cout的ostream方法将格式化信息写入到string对象中,并使用istream方法(如getline())来读取string对象中的信息。读取string对象中的格式化信息或将格式化信息写入string对象中被称为内核格式化(incore formatting)
2. ostringstream类
头文件sstream定义了一个从ostream类派生而来的ostringstream类(还有一个基于wostream的wostringstream类,这个类用于宽字符集)。如果创建了一个ostringstream对象,则可以将信息写入其中,它将存储这些信息。可以将可用于cout的方法用于ostringstream对象。也就是说,可以这样做: ostringstream outstr; double price = 380.0; char* ps = " for a copy of the ISO/EIC C++ standard!"; outstr.precision(2); coutstr << fixed; oustr << : "Pay only CHF " << price << ps << endl; 格式化文本进入缓冲区,在需要的情况下,该对象将使用动态内存分配来增大缓冲区。ostringstream类有一个名为str()的成员函数,该函数返回一个被初始化为缓冲区内容的字符串对象: string mesg = outstr.str(); // returns string with formatted information 使用str( )方法可以“冻结”该对象,这样便不能将信息写入该对象中
3. istringstream类
istringstream类允许使用istream方法族读取istringstream对象中的数据,istringstream对象可以使用string对象进行初始化。 假设facts是一个string对象,则要创建与该字符串相关联的istringstream对象,可以这样做: istringstream instr(facts); // use facts to initialize stream 如果instr包含大量字符格式的整数,则可以这样读取它们: int n; int sum = 0; while (instr >> n) sum += n;
17.4 文件输入和输出
17.4.1 简单的文件I/O
1. 写入和读取文件
(1)写入
#include <fstream> int main() { ofstream fout; fout.open("jar.txt"); // ofstream fout("jar.txt"); fout << "Hey\n"; ... }
(2)读取
#include <fstream> int main() { ifstream fin; fin.open("jar.txt"); // ofstream fin("jar.txt"); char ch; fin >> ch; ... }
2. 说明
(1)由于ostream是ofstream类的基类,因此可以使用所有的ostream方法,包括各种插入运算符定义、格式化方法和控制符。ofstream类使用被缓冲的输出,因此程序在创建像fout这样的ofstream对象时,将为输出缓冲区分配空间。如果创建了两个ofstream对象,程序将创建两个缓冲区,每个对象各一个。像fout这样的ofstream对象从程序那里逐字节地收集输出,当缓冲区填满后,它便将缓冲区内容一同传输给目标文件
(2)以默认模式打开文件进行输出将自动把文件的长度截短为零,这相当于删除已有的内容
(3)当输入和输出流对象过期(如程序终止)时,到文件的连接将自动关闭。另外,也可以使用close()方法来显式地关闭到文件的连接: fout.close(); // close output connection to file fin.close(); // close input connection to file 关闭这样的连接并不会删除流,而只是断开流到文件的连接。然而,流管理装置仍被保留。例如,fin对象与它管理的输入缓冲区仍然存在
17.4.2 流状态检查和is_open()
1. 文件检查
C++文件流类从ios_base类那里继承了一个流状态成员。正如前面指出的,该成员存储了指出流状态的信息:一切顺利、已到达文件尾、I/O操作失败等。如果一切顺利,则流状态为零(没有消息就是好消息)。其他状态都是通过将特定位设置为1来记录的。文件流类还继承了ios_base类中报告流状态的方法,可以通过检查流状态来判断最后一个流操作是否成功。对于文件流,这包括检查试图打开文件时是否成功
2. 举例
例如,试图打开一个不存在的文件进行输入时,将设置failbit位,因此可以这样进行检查: fin.open(argv[file]); if (fin.fail()) // or if (!fin) { ... } 然而,较新的C++实现提供了一种更好的检查文件是否被打开的方法——is_open()方法: if (!fin.is_open()) // open attempt failed { ... }
3. cin.is_open()与其他方法的区别
当试图以不合适的文件模式打开文件时失败。方法is_open()能够检测到这种错 误以及good()能够检测到的错误,而fin.fail()、fin.good()、!fin等方法无法检测
17.4.3 打开多个文件
要计算某个名称在10个文件中出现的次数。在这种情况下,可以打开一个流,并将它依次关联到各个文件。这在节省计算机资源方面,比为每个文件打开一个流的效率高。使用这种方法,首先需要声明一个ifstream对象(不对它进行初始化),然后使用open()方法将这个流与文件关联起来
17.4.4 命令行处理技术
C++有一种让在命令行环境中运行的程序能够访问命令行参数的机制,方法是使用下面的main()函数: int main(int argc, char* argv[]) argc为命令行中的参数个数,其中包括命令名本身。argv变量为一个指针,它指向一个指向char的指针。这过于抽象,但可以将argv看作一个指针数组,其中的指针指向命令行参数,argv[0]是一个指针,指向存储第一个命令行参数的字符串的第一个字符,依此类推。也就是说,argv[0]是命令行中的第一个字符串,依此类推。例如,假设有下面的命令行: wc report1 report2 report3 则argc为4,argv[0]为wc,argv[1]为report1,依此类推。下面的循环将把每个命令行参数分别打印在单独一行上: for (int i = 1; i < argc; i++) cout << argv[i] << endl; 以i = 1开头将只打印命令行参数;以i = 0开头将同时打印命令名
17.4.5 文件模式
概述
文件模式描述的是文件将被如何使用:读、写、追加等。将流与文件关联时,都可以提供指定文件模式的第二个参数: ifstream fin("banjo", mode1); // constructor with mode argument ofstream fout(); fout.open("harp", mode2); // open() with mode arguments ios_base类定义了一个openmode类型,用于表示模式;与fmtflags和iostate类型一样,它也是一种bitmask类型。可以选择ios_base类中定义的多个常量来指定模式 ifstream open()方法和构造函数用ios_base::in(打开文件以读取)作为模式参数的默认值; 而ofstream open()方法和构造函数用ios_base::out | ios_base::trunc(打开文件,以读取并截短文件)作为默认值
1. 追加文件(*)
2. 二进制文件
1. 格式存储
(1)文本格式的优点
· 便于读取,可以很方便地将文本文件从一个计算机系统传输到另一个计算机系统
· 对于不同的计算机系统,数据转移相对可靠
(2)二进制格式的优点
· 存储数字比较精确,不会有转换误差或舍入误差
· 保存数据的速度更快
· 可以大块地存储数据
2. 与文本存储的代码比较
· 给定条件
cosnt int LIM = 20; struct planet { char name[LIM]; // name of planet double population; // its population double g; // its acceleration of gravity }; planet pl;
(1)文本存储
要将结构pl的内容以文本格式保存,可以这样做: ofstream fout("planets.dat", ios_base::out | ios_base::app); fout << pl.name << " " << pl.population << " " << pl.g << "\n"; 必须使用成员运算符显式地提供每个结构成员,还必须将相邻的数据分隔开,以便区分。如果结构有30个成员,则这项工作将很乏味
(2)二进制存储
要用二进制格式存储相同的信息,可以这样做: ofstream fout("planets.dat", ios_base::out | ios_base::app | ios_base::binary); fout.write((char*) &pl, sizeof pl); 上述代码使用计算机的内部数据表示,将整个结构作为一个整体保存。不能将该文件作为文本读取,但与文本相比,信息的保存更为紧凑、精确。它确实更便于键入代码。这种方法做了两个修改: · 使用二进制文件模式 · 使用成员函数write()
(3)说明
要以二进制格式(而不是文本格式)存储数据,可以使用write()成员函数。这种方法将内存中指定数目的字节复制到文件中,它只逐字节地复制数据,而不进行任何转换。例如,如果将一个long变量的地址传递给它,并命令它复制4个字节,它将复制long值中的4个字节,而不会将它转换为文本。唯一不方便的地方是,必须将地址强制转换为指向char的指针。也可以用同样的方式来复制整个planet结构。要获得字节数,可以使用sizeof运算符: fout.write((char*) &pl, sizeof pl); 要使用文件恢复信息,请通过一个ifstream对象使用相应的read()方法: ifstream fin("planets.dat", ios_base::in | ios_base::binary); fin.read((char*) &pl, sizeof pl); 这将从文件中复制sizeof pl个字节到pl结构中。同样的方法也适用于不使用虚函数的类。在这种情况下,只有数据成员被保存,而方法不会被保存。如果类有虚方法,则也将复制隐藏指针(该指针指向虚函数的指针表)。由于下一次运行程序时,虚函数表可能在不同的位置,因此将文件中的旧指针信息复制到对象中,将可能造成混乱
(4)注意
该程序是否可以使用string对象而不是字符数组来表示planet结构的name成员? 答案是否定的,至少在不对设计做重大修改的情况下是否定的。问题在于,string对象本身实际上并没有包含字符串,而是包含一个指向其中存储了字符串的内存单元的指针。因此,将结构复制到文件中时,复制的将不是字符串数据,而是字符串的存储地址。当您再次运行该程序时,该地址将毫无意义
17.4.6 随机存取
1. 文件打开模式
fstream finout(file, ios_base::in | ios_base::out | ios_base::binary); 同时使用in模式和out模式将得到读/写模式,因此只需添加二进制元素即可。如前所述,要使用|运算符来组合模式
2. 定位方法
(1)finout.seekg()
· 功能
将输入指针移到指定的文件位置
· 原型
istream& seekg(streamoff, ios_base::seekdir);
istream& seekg(streampos);
第一个原型定位到离第二个参数指定的文件位置特定距离(单位为字节)的位置; 第二个原型定位到离文件开头特定距离(单位为字节)的位置
· 说明
streamoff值被用来度量相对于文件特定位置的偏移量(单位为字节)。streamoff参数表示相对于三个位置之一的偏移量为特定值(以字节为单位)的文件位置(类型可定义为整型或类)。seek_dir参数是ios_base类中定义的另一种整型,有3个可能的值: · ios_base::beg:相对于文件开始处的偏移量 · ios_base::cur:相对于当前位置的偏移量 · ios_base::end:相对于文件尾的偏移量 streampos值表示文件中的绝对位置(从文件开始处算起)。可以将streampos位置看作是相对于文件开始处的位置(以字节为单位,第一个字节的编号为0)。因此下面的语句将文件指针指向第112个字节,这是文件中的第113个字节: fin.seekg(112);
(2)finout.seekp()
同上类似
3. 获取指针位置
(1)方法
finout.tellg()
对应输出流
finout.tellp()
对应输入流
(2)功能
返回一个表示当前指针位置的streampos值(以字节为单位,从文件开始处算起)
(3)说明
· 创建fstream对象时,输入指针和输出指针将一前一后地移动,因此tellg()和tellp()返回的值相同。 · 使用istream对象来管理输入流,而使用ostream对象来管理同一个文件的输出流,则输入指针和输出指针将彼此独立地移动,因此tellg()和tellp()将返回不同的值
17.3 使用cin进行输入
概述
· cin的进制输入
可以将hex、oct和dec控制符与cin一起使用,来指定将整数输入解释为十六进制、八进制还是十进制格式。例如,下面的语句将输入12或0x12解释为十六进制的12或十进制的18,而将ff或FF解释为十进制的255: cin >> hex;
· 字符指针的输入
istream类还为下列字符指针类型重载了>>抽取运算符: · signed char* · char* · unsigned char* 对于这种类型的参数,抽取运算符将读取输入中的下一个单词,将它放置到指定的地址,并加上一个空值字符,使之成为一个字符串
17.3.1 cin>>如何检查输入
1. cin输入的特点
不同版本的抽取运算符查看输入流的方法是相同的。它们跳过空白(空格、换行符和制表符),直到遇到非空白字符。即使对于单字符模式(参数类型为char、unsigned char或signed char),情况也是如此。也就是说,它读取从非空白字符开始,到与目标类型不匹配的第一个字符之间的全部内容
2. cin输入失败的情况
输入有时可能没有满足程序的期望。例如,假设有下列一段代码: int num; cin >>num; 而程序输入的是Zcar,而不是−123Z。在这种情况下,抽取运算符将不会修改elevation的值,并返回0(如果istream对象的错误状态被设置,if或while语句将判定该对象为false)。返回值false让程序能够检查输入是否满足要求
17.3.2 流状态
概述
cin或cout对象包含一个描述流状态(stream state)的数据成员(从ios_base类那里继承的)。流状态(被定义为iostate类型,而iostate是一种bitmask类型)由3个ios_base元素组成:eofbit、badbit或failbit,其中每个元素都是一位,可以是1(设置)或0(清除): · eofbit:当cin操作到达文件末尾时,它将设置eofbit · failbit:当cin操作未能读取到预期的字符时,它将设置failbit;I/O失败(如试图读取不可访问的文件或试图写入写保护的磁盘),也可能将failbit设置为1 · badbit:在一些无法诊断的失败破坏流时,badbit元素将被设置(实现没有必要就哪些情况下设置failbit,哪些情况下设置badbit达成一致) 当全部3个状态位都设置为0时,说明一切顺利。程序可以检查流状态,并使用这种信息来决定下一步做什么
1. 设置状态
(1)clear()
clear()方法将状态设置为它的参数。因此,下面的调用将使用默认参数0,这将清除全部3个状态位(eofbit、badbit和failbit): clear();
同样,下面的调用将状态设置为eofbit;也就是说,eofbit将被设置,另外两个状态位被清除: clear(eofbit);
(2)setstate()
而setstate()方法只影响其参数中已设置的位。因此,下面的调用将设置eofbit,而不会影响其他位: setstate(eofbit);
2. I/O和异常
exceptions()方法返回一个位字段,它包含3位,分别对应于eofbit、failbit和badbit。修改流状态涉及clear()或setstate(),这都将使用clear()。修改流状态后,clear()方法将当前的流状态与exceptions()返回的值进行比较。如果在返回值中某一位被设置,而当前状态中的对应位也被设置,则clear()将引发ios_base::failure异常。如果两个值都设置了badbit,将发生这种情况。如果exceptions()返回goodbit,则不会引发任何异常。ios_base::failure异常类是从std::exception类派生而来的,因此包含一个what()方法。
exceptions()的默认设置为goodbit,也就是说,没有引发异常。但重载的exceptions(iostate)函数使得能够控制其行为: cin.exception(badbit); // setting badbit causes exception to be thrown
位运算符OR使得能够指定多位。例如,如果badbit或eofbit随后被设置,下面的语句将引发异常: cin.exception(badbit | eofbit);
3. 流状态的影响
(1)问题
设置流状态位有一个非常重要的后果:流将对后面的输入或输出关闭,直到位被清除。如果希望程序在流状态位被设置后能够读取后面的输入,就必须将流状态重置为良好。这可以通过调用clear()方法来实现。注意,这还不足以重新设置流状态。导致输入循环终止的不匹配输入仍留在输入队列中,程序必须跳过它
(2)解决方案
· 一种方法是一直读取字符,直到到达空白为止。isspace()函数是一个cctype函数,它在参数是空白字符时返回true: cin.clear(); while (!isspace(cin.get())) continue; // get rid of bad input cin >> input; // will work now
· 另一种方法是,丢弃行中的剩余部分,而不仅仅是下一个单词: while (cin.get() != '\n') continue; // get rid of the rest of the line
17.3.3 其他istream类方法
1. 单字符输入
(1)cin.get(char&)
在使用char参数或没有参数的情况下,get()方法读取下一个输入字符,即使该字符是空格、制表符或换行符。get(char& ch)版本将输入字符赋给其参数
如果cin.get(char &)到达文件尾——无论是真正的文件尾,还是通过键盘仿真的文件尾(对于DOS和Windows命令提示符模式,为按下Ctrl + Z;对于UNIX,是在行首按下Ctrl + D),它都不会给其参数赋值。这是完全正确的,因为如果程序到达文件尾,就没有值可供赋给参数了。另外,该方法还调用setstate(failbit),导致cin的测试结果为false: char ch; while (cin.get(chh)) { // process input } 只要存在有效输入,cin.get(ch)的返回值都将是cin,此时的判定结果为true,因此循环将继续。到达文件尾时,返回值判定为false,循环终止
(2)cin.get(void)
get(void)成员函数还读取空白,但使用返回值来将输入传递给程序。get(void)成员函数的返回类型为int(或某种更大的整型,这取决于字符集和区域)。
到达文件尾后(不管是真正的文件尾还是模拟的文件尾),cin.get(void)都将返回值EOF——头文件iostream提供的一个符号常量。这种设计特性使得可以这样来读取输入: int ch; while ((ch = cin.get) != EOF) { // process input } 这里应将ch的类型声明为int,而不是char,因为值EOF可能无法使用char类型来表示。
2. 采用哪种单字符输入形式
(1)跳过空白
>>
(2)检查每个字符
cin.get(char&)
(3)接口C语言
cin.get(void)
3. 字符串输入
(1)cin.get()
· 函数原型
istream& get(char*, int, char);
istream& get(char*, int);
· 第1个参数:用于放置输入字符串的内存单元的地址。 · 第2个参数:比要读取的最大字符数大1(额外的一个字符用于存储结尾的空字符, 以便将输入存储为一个字符串)。 · 第3个参数:指定用作分界符的字符,只有两个参数的版本将换行符用作分界符。 上述函数都在读取最大数目的字符或遇到换行符后为止
· 特点
cin.get()将换行符留在输入流中,这样接下来的输入操作首先看到是将是分界符(默认为换行符)
(2)cin.getline()
· 函数原型
istream& getline(char*, int, char);
istream& getline(char*, int);
(参数列表即作用同上)
· 特点
gerline()抽取并丢弃输入流中的分界符(默认为换行符)
(3)cin.ignore()
· 函数原型
istream& ignore(int = 1, int = EOF);
该函数接受两个参数:一个是数字,指定要读取的最大字符数;另一个是字符,用作输入分界符
· 用法
默认参数值EOF导致ignore()读取指定数目的字符或读取到文件尾。 而下面的函数调用读取并丢弃接下来的255个字符或直到到达第一个换行符: cin.ignore(255, '\n');
4. 意外字符串输入
(1)两种方法都遵循的普遍情况
· 遇到文件尾时将设置eofbit
· 遇到流被破坏(如设备故障)时将设置badbit
· 无法读取字符时设置failbit
(2)关于无法读取字符的区别
cin.getline()由于会舍弃分界符,因此在读取空行('\n')时不会设置failbit,而是读取后丢弃
cin.get()不会丢弃分界符,因此遇到空行('\n')时会停住。若此时没有读取任何字符,则会设置failbit,并且清除异常状态不做任何处理后下一个读取的字符仍然是'\n'
17.3.4 其他istream方法
1. cin.read()
read()不会在输入后加上空值字符,因此不能将输入转换为字符串。read()方法不是专为键盘输入设计的,它最常与ostream write()函数结合使用,来完成文件输入和输出。该方法的返回类型为istream &,因此可以像下面这样将它拼接起来: char gross[144]; char score[20]; cin.read(gross, 144).read(score, 20);
2. cin.peek()
peek()函数返回输入中的下一个字符,但不抽取输入流中的字符。也就是说,它使得能够查看下一个字符。假设要读取输入,直到遇到换行符或句点,则可以用peek()查看输入流中的下一个字符,以此来判断是否继续读取: char great_input[80]; char ch; int i = 0; while ((ch = cin.peek()) != '.' && ch != '\n')) cin.get(great_input[i++]); great_input[i] = '\0';
3. cin.gcount()
gcount()方法返回最后一个非格式化抽取方法读取的字符数。这意味着字符是由get()、getline()、ignore()或read()方法读取的,不是由抽取运算符(>>)读取的,抽取运算符对输入进行格式化,使之与特定的数据类型匹配。例如,假设使用cin.get(myarray,80)将一行读入myarray数组中,并想知道读取了多少个字符,则可以使用strlen()函数来计算数组中的字符数,这种方法比使用cin.gcount()计算从输入流中读取了多少字符的速度要快
4. cin.putback()
putback()函数将一个字符插入到输入字符串中,被插入的字符将是下一条输入语句读取的第一个字符。putback()方法接受一个char参数——要插入的字符,其返回类型为istream &,这使得可以将该函数调用与其他istream方法拼接起来。使用peek()的效果相当于先使用get()读取一个字符,然后使用putback()将该字符放回到输入流中。然而,putback()允许将字符放到不是刚才读取的位置