C++ 使用了很多较为高级的语言特性来实现输入和输出,其中包括类、派生类、函数重载、虚函数、模板和多重继承。换句话说,C++ 输入和输出技术实际上是 C++ 类技术的应用而已,所以在学习这方面的东西时应该有意地往这方面去想。
用于文件输入喝输出的 C++ 工具都是基于 cin 和 cout 所基于的基本类定义。但 C 和 C++ 都没有将输入和输出建立在语言中。 C 语言最初把 I/O 留给了编译 器实现人员。这样做的一个原因是为了让实现人员能够自由的设计 I/O 函数,使之最适合于目标计算机的硬件要求。然而, C++ 依赖于 C++ 的 I/O 解决方案, 而不是 C 语言的解决方案。C++ 自带了一个标准类库,实现了输入和输出的类库,所以要详细了解输入和输出方面的东西请参考其帮助文件。这里这是讲一下 基本的思想而已,不会过多涉及到细节。
流和缓冲区
C++ 程序把输入和输出看作字节流。输入时,程序从输入流中抽取字节;输出时,程序将字节插入到输出流中。对于面相文本的程序,每个字节代表一个字符。 更通俗地说,字节可以构成字符或数值的二进制表示。输入流中的字节可以来自键盘,也可能来自存储设备(如硬盘)或其他程序。同样,输出流中的字节可以 流向屏幕、打印机、存储设备或其他程序。流充当了程序和流源或流目标之间的桥梁。这使得 C++ 程序可以以相同的方式对待来自键盘的输入和来自文件的输入。 C++ 程序只是检查字节流,而不需要知道字节来自何方。同理,通过使用流,C++ 程序处理输出的方式将独立于其去向。因此管理输入包括两步:
- 将流与输入去向的程序关联起来
- 将流与文件连接起来
换句话说,输入流需要两个连接,每端各一个。文件端连接提供了流的来源,程序端连接将流的流出部分转储到程序中(文件端连接可以是文件、也可以是设备, 如键盘)同样,对输出的管理包括将输出流连接到程序以及将输出目标与流关联起来。这方面的理解请看后面的例子。
流处理
C++ 将输出(输入)看作字节流(根据实现平台不同,可能是 8 位,16 位或 32 位 的字节,但都是字节),但在程序中,很多数据被组织成比字节更大的单位, 例如,int 类型由 16 位或 32 位的二进制值表示;double 值由 64 位的二进制表示。但在将字节流发送给屏幕时,希望每个字节表示一个字符值。 也就是说,要在屏幕上显示数字 -2.34,需要将 5 个字符(-, 2, ., 3, 4),而不是这个值得 64 位内部浮点表示发动到屏幕上。
因此, ostream 类最重要的任务之一是将数值类型转换位以文本形式表示的字符流。事实上输入也是类似的处理,本质上是一种翻译,道理类似源程序到机器码 的编译。
缓冲区
通常,通过使用缓冲区可以更高效地处理输入和输出。缓冲区是用作中介的内存块,它是将信息从设备传输到程序或从程序传输给设备的临时存储工具。 主要是解决字符设备和块设备之间的高效传输问题,这可以匹配总线、内存、硬盘、CPU等的工作方式,从而最大程度地利用这些设备。
iostream 文件
管理流和缓冲区的工作有点复杂,但 iostream 文件中包含了一些专门设计用来实现、管理流和缓冲区的类。C++ 提供了 I/O 管理方面的类模板。通过使用 typedef 工具,C++ 使得这些模板 char 具体化能够模仿传统的非模板 I/O 实现。
- 管理标准的输入/输出流的类为:
istream(输入)、ostream(输出)、iostream(输入输出),其中 istream 和 ostream 直接从 ios 中继承,iostream 多重继承了 istream 和 otream。
而 cin 是 STL 中定位的一个用于输入的 istream 对象,cout、cerr、clog 是三个用于输出的 ostream 对象。其中 cout 对象也被称为标准输出,用于正常的输出, cerr 用来输出警告和错误信息,因为被称为标准错误,而 clog 用来输出程序运行时的一般性信息。 cerr 和 clog 之间的不同之处在于 cerr 是不经过缓冲区直接向显示器输出有关信息,而 clog 则是先把信息放在缓冲区,缓冲区满后或遇上 endl 时向显示器输出。
- 管理文件流的类为:
ifstream(文件输入)、ofstream(文件输出)和 fstream(文件的输入/输出)。其中 ifstream 是从 istream 中继承的类,ofstream 是从 ostream 中继承的类, fstream 是从 iostream 继承的类。
- 管理字符串流的类为:
istringstream(字符串输入)、ostringstream(字符串输出)和stringstream(字符串的输入/输出)。其中 istringstream 是从 istream 中继承的类, ostringstream 是从 ostream 中继承的类,stringstream 是从 iostream 继承的类。
1
要使用这些工具,必须使用适当的类对象。
例如,使用 ostream 对象 (如 cout)来处理输出。创建这样的对象将打开一个流,自动创建缓冲区,并将其与流关联起来,同时使得能够使用类成员函数。
C++ 的 iostream 类库管理了很多细节。例如,在程序中包含 iostream 文件将自动创建 8 个流对象(4 个用于窄字符流,4 个用于宽字符流)。现在列举 如下:
- cin:
标准输入流对象,键盘为其对应的标准设备。带缓冲区的,缓冲区由streambuf类对象来管理。
- cout:
标准输出流对象,显示器为标准设备。带缓冲区的,缓冲区由streambuf类对象来管理。
- cerr 和 clog :
标准错误输出流,输出设备是显示器。为非缓冲区流,一旦错误发生立即显示。
重定向
由于标准输入和输出流默认连接着键盘和屏幕,所以当换成其他输入输出设备时,必须进行重定向。这里推荐两种方法:
- 在源代码中,将设备与流进行关联,然后正常使用流就可以了
- 只有可执行文件,使用操作系统命令行工具的重定向命令
cin
1
输入操作原理
程序的输入都建有一个缓冲区,即输入缓冲区。一次输入过程是这样的,当一次键盘输入结束时会将输入的数据存入输入缓冲区, 而 cin 函数直接从输入缓冲区中取数据。正因为 cin 函数是直接从缓冲区取数据的,所以有时候当缓冲区中有残留数据时, cin 函数会直接取得这些残留数据而不会请求键盘输入,这就是例子中为什么会出现输入语句失效的原因!
cin 输入结束的条件:Enter、Space、Tab。cin 对这些结束符的处理:丢弃缓冲区中这些字符。与 cin.get()不同。
- cin »
- 该操作符是根据后面变量的类型读取数据。
- 输入结束条件 :遇到Enter、Space、Tab键。
- 对结束符的处理 :丢弃缓冲区中使得输入结束的结束符(Enter、Space、Tab)
- com.get(字符数组名, 提取长度, 结束符)
其中结束符为可选参数,读入的字符个数最多为(长度-1)个,结束符规定结束字符串读取的字符,默认为 ENTER,
若要读取字符,直接cin.get(char ch)或ch=cin.get()即可
- ch=cin.get() 与 cin.get(ch)
- 输入结束条件:Enter 键
- 对结束符处理:不丢弃缓冲区中的 Enter
- cin.getline()
cin.getline(数组名,长度,结束符) 大体与 cin.get(数组名,长度,结束符)类似.
1
区别在于
cin.get()当输入的字符串超长时,不会引起cin函数的错误,后面的cin操作会继续执行,只是直接从缓冲区中取数据。 但是cin.getline()当输入超长时,会引起cin函数的错误,后面的cin操作将不再执行。
- C 语言中 getchar、get、gets、getline
它们都能识别空格,读取输入直到回车后,getline 会丢弃回车,而 getchar、get和gets 不丢弃保留在输入缓冲中! 这个特性导致如果 get 下面还有一句 get 或 gets,必须用 get() 或 getchar() 接受掉这个回车, 否则下面的 get 或 gets 就当已经收到了回车字符而跳过了。因此在循环中使用 get 或 gets 要注意。 而 getchar 的话虽然回车也留在缓冲中,但因为循环条件是 !=’\n’,所以就无所谓了。
流状态
C++ 的输入输出实际上就是对流的操作,所以了解流的状态时非常必要的,比如可以根据流的状态做不同的输入输出处理,有时候检测 错误输入等。
设置状态
上边中提供了两种方法。clear() 和 setstate() 很相似,它们都重置状态,但采取的方式不同。
- clear()
clear() 方法将状态设置为它的参数,因此,下面的调用将使用默认参数 0.这将清除全部 3 个状态位(eofbit, badbit, 和 failbit):
1
clear();
同样,下面的调用将状态设置为 eofbit:也就是说, eofbit 将被设置,另外两个状态位被清除:
1
clear(eofbit);
为何需要重设流状态
对于程序员来说,最常见的理由是,在输入不匹配或到达文件尾时,需要使用不带参数的 clear() 重新打开输入。这样做是否有意义, 取决于程序要执行的任务。setstate() 的主要用途是当出现致命错误时为输入和输出函数提供一种修改状态的途径。
1
2
设置流状态位有一个非常重要的后果:
流将对后面的输入或输出关闭,直到位被清除
输出格式控制
setprecision(n) 可控制输出流显示浮点数的数字个数。C++ 默认的流输出数值有效位是6,所以不管数据是多少,都只输出六位。 如果 setprecision(n) 与 setiosflags(ios::fixed) 或者 setiosflags(ios_base::fixed) 合用,可以控制小数点右边的数字个数。 setiosflags(ios::fixed) 是用定点方式表示实数。 如果与 setiosnags(ios::scientific) 合用,可以控制指数表示法的小数位数。 setiosflags(ios::scientific) 是用指数方式表示实数。
I/O 常用控制符
使用控制符时,在程序开头加投文件#include
- 用格式控制符;
- 成员函数
在新版本的 c++中 头文件已经用 iomanip 取代了 iomanip.h。
1
可以不使用#include<iomanip>的
文件的输入和输出
文件本身是存储在某种设备(光盘、硬盘等)上的一系列字节。通常,操作系统管理文件,跟踪它们的位置、大小、创建时间等。除非在操作系统级别上 编程,否则通常不必担心这些事情。需要的只是将程序与文件相连的途径、让程序读取文件内容的途径以及程序创建和写入文件的途径。重定向可以提供一些 文件支持,但它比显式程序中的文件 I/O 局限性更大。另外,重定向来自操作系统,而非 C++ ,因此并非所有系统都有这样的功能。
C++ I/O 类软件包处理文件输入和输出方式与处理标准输入和输出的方式非常相似。
- 要写入文件,需要创建一个 ofstream 对象,并使用 ofstream 方法,如 « 插入运算符或 write()。
- 要读取文件,需要创建一个 ifstream 对象,并使用 ifstream 方法,如 » 抽取运算符或 get().
然而,与标准输入和输出相比,未见管理更为复杂。例如,必须将新打开的文件和流关联起来(标准输入输出已经默认关联了),要设置 访问模式,操作文件内指针位置等、
简单的文件 I/O
要让程序写入文件,必须这样做:
- 包含头文件 fstream;
- 创建一个 ofstream 对象来管理输出流;
- 将该对象与特定的文件关联起来;
- 以使用 cout 的方式使用该对象,唯一的区别是输出将进入文件,而不是屏幕。
因为 ostream 是 ofstream 类的基类,因此可以使用所有的 ostream 方法,包括各种输入运算符定义、格式化方法和控制符。ofstream 类使用被缓冲 的输出,因此程序在创建像 fout 这样的 ofstream 对象时,将为输出缓冲区分配空间。如果创建两个 ofstream 对象,程序将创建两个缓冲区,每个对象 一个。
1
以默认模式打开文件进行输出将自动把文件的长度截短为零,这相当于删除已有的内容。
读取文件的要求与写入文件相似:
- 包含头文件 fstream
- 创建一个 ifstream 对象来管理输入流(输入和输出相对于操作系统或程序)
- 将该对象与特定的文件关联起来
- 以使用 cin 的方式使用该对象。
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// fileio.cpp -- saving to a file
#include <iostream> // not needed for many systems
#include <fstream>
#include <string>
int main()
{
using namespace std;
string filename;
cout << "Enter name for new file: ";
cin >> filename;
// create output stream object for new file and call it fout
ofstream fout(filename.c_str());
fout << "For your eyes only!\n"; // write to file
cout << "Enter your secret number: "; // write to screen
float secret;
cin >> secret;
fout << "Your secret number is " << secret << endl;
fout.close(); // close file
// create input stream object for new file and call it fin
ifstream fin(filename.c_str());
cout << "Here are the contents of " << filename << ":\n";
char ch;
while (fin.get(ch)) // read character from file and
cout << ch; // write it to screen
cout << "Done\n";
fin.close();
// std::cin.get();
// std::cin.get();
return 0;
}
流状态价差和 is_open()
C++ 文件流类从 ios_base 类哪里继承了一个流状态成员。正如前面指出的,该成员存储了指出流状态的消息:一切顺利、已到文件尾、I/O 操作失败等。 这一节详细内容强参考情面的“流状态”,下面是“流状态”中的一张图
文件模式
文件模式描述的是文件将被如何使用:读、写、追加等。将流与文件关联时(无论是使用文件名初始化文件流对象,还是使用 open()方法),都可以 提供指定文件模式的第二个参数,例如
1
2
3
4
5
ifstream fin("banjo",model);
// constructor with mode argument
ofstream fout();
fout.open("harp",mode2);
// open() with mode arguments
ios_base 类定义了一个 openmode 类型,用于表示模式;与 fmtflags 和 iostate 类型一样,它也是一种 bitmask 类型,可以使用 ios_base 类中定义的 多个常熟来指定模式。现列表如下:
- ios_base::in 打开文件做读操作
- ios_base::out 打开文件做写操作
- ios_base::app 在每次写之前找到文件尾
- ios_base::ate 打开文件后立即定位在文件尾
- ios_base::trunc 打开文件时清空已存在的文件流
- ios_base::binary 以二进制模式进行IO操作
注意:ios_base::ate 和 ios_base::app 都将文件指针指向打开的文件尾。二者的区别在于,ios_base::app 模式只允许将数据添加到文件尾,而 ios_base::ate 模式将指针放到文件尾。
文件格式
- 文本文件以字符格式存储所有的信息,
例如,数字值将被转换为字符表示。常规的插入和抽取运算符以及 get() 和 getline() 都支持这种模式。
- 二进制文件
二进制文件使用计算机内部使用的二进制表示来存储信息。与文本文件相比,二进制文件存储数据(尤其是浮点值)更为精确、简洁,但可移植性较差。 read() 和 write() 方法都支持二进制输入和输出。
随机存取
seekg() 和 seekp() 函数提供对文件的随机存取。这些类方法使得能够将文件指针放置到相对于文件开头、文件尾和当前位置的某个位置。tellg() 和 tellp() 方法报告当前的文件位置。
1
顺序存取请参考文件模式。