C++ 进阶基础之四

标准 I/O 流的介绍

I/O 流的概念

程序的输入指的是从输入文件将数据传送给程序,程序的输出指的是从程序将数据传送给输出文件。C++ 的输入输出包含以下三个方面的内容:

  • 对系统指定的标准设备的输入和输出:即从键盘输入数据,输出到显示器屏幕。这种输入输出称为标准的输入输出,简称 标准 I/O
  • 以外存磁盘文件为对象进行输入和输出:即从磁盘文件输入数据,数据输出到磁盘文件。以外存文件为对象的输入输出称为文件的输入输出,简称 文件 I/O
  • 对内存中指定的空间进行输入和输出:通常指定一个字符数组作为存储空间(实际上可以利用该内存空间存储任何信息)。这种输入和输出称为字符串输入输出,简称 串 I/O

I/O 流类库的结构

在 C 语言中,用 printfscanf 进行输入输出,往往不能保证所输入输出的数据是可靠的安全的。在 C++ 的输入输出中,编译系统对数据类型进行严格的检查,凡是类型不正确的数据都不可能通过编译。因此 C++ 的 I/O 操作是类型安全(Type Safe)的。C++ 的 I/O 操作是可扩展的,不仅可以用来输入输出标准类型的数据,也可以用于用户自定义类型的数据。C++ 通过 I/O 类库来实现丰富的 I/O 功能。这样使 C++ 的输人输出明显地优于 C 语言中的 printfscanf,但是也为之付出了代价,C++ 的 I/O 系统因此变得比较复杂,要掌握许多使用细节。C++ 编译系统提供了用于输入输出的 iostream 类库。iostream 这个单词是由 3 个部分组成的,即 i-o-stream,意为输入输出流。在 iostream 类库中包含许多用于输入输出的类,如下图所示:

cplus-plus-io-1

ios 是抽象基类,由它派生出 istream 类和 ostream 类,两个类名中第 1 个字母 i 和 o 分别代表输入(input)和输出(output)。istream 类支持输入操作,ostream 类支持输出操作,iostream 类支持输入输出操作。iostream 类是从 istream 类和 ostream 类通过多重继承而派生的类,其继承层次如下图所示:

cplus-plus-io-2

iostream 类库中不同的类的声明被放在不同的头文件中,用户在自己的程序中用 #include 命令包含了有关的头文件,这就相当于在本程序中声明了所需要用到的类。可以换 — 种说法:头文件是程序与类库的接口。iostream 类库的接口分别由不同的头文件来实现,常用的头文件如下:

  • strstream:用于字符串流 I/O
  • fstream:用于实现文件的 I/O 操作
  • iomanip:在使用格式化 I/O 时,应包含此头文件
  • iostream:包含了对输入输出流进行操作所需的基本信息
  • stdiostream:用于混合使用 C 语言和 C++ 的 I/O 机制,例如希望将 C 语言程序转变为 C++ 程序

iostream 头文件中定义的类有 ios,istream,ostream,iostream,istream_withassign,ostream_withassign,iostream_withassign 等。在 iostream 头文件中不仅定义了相关的类,还定义了 4 种标准 I/O 对象,如下所示:

cplus-plus-io-3

<<>> 本来在 C++ 中是被定义为左位移运算符和右位移运算符的,由于在 iostream 头文件中对它们进行了重载,使它们能用作标准类型数据的输入和输出运算符。所以,在使用到它们的程序中必须用 #include <iostream> 命令将其包含到程序中。在 iostream 中只对 <<>> 运算符用于标准类型数据的输入输出进行了重载,但未对用户声明的类型数据的输入输出进行重载。如果用户声明了新的类型,并希望用 <<>> 运算符对其进行输入输出,则需要按照 C++ 的运算符重载规则来做。

标准 I/O 流的使用

标准输入流的简单使用

标准输入流对象 cin 的常用函数如下:

  • cin.get(),一次只能读取一个字符
  • cin.get(一个参数),读一个字符
  • cin.get(多个参数),可以读字符串
  • cin.getline(),读取整行字符串,包括读取空格字符
  • cin.ignore(),用于忽略或清除输入缓冲区中的一个或多个字符
  • cin.putback(),将数据放回缓冲区
  • cin.peek(),返回值是一个 char 型的字符,即指针指向的当前字符,但它只是观测指针停留在当前的位置并不后移;如果要访问的字符是文件结束符,则函数的返回值是 EOF 或者 -1
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <iostream>

using namespace std;

void input1() {
int number;
cout << "请输入一个数字: ";
cin >> number;
cout << "输入的数字是: " << number << endl;
}

void input2() {
char buf[1024];
cout << "请输入字符串: ";
cin >> buf; // 当遇到空格符时,会停止接收数据输入
cout << "输入的字符串: ";
cout << buf << endl;
}

void input3() {
char ch;
cout << "请输入字符串: ";
while ((ch = cin.get()) != EOF) // 如果缓冲区没有数据,则程序会阻塞
{
cout << ch << " ";
}
}

void input4() {
char a, b, c;
cout << "请输入字符串: ";
cin.get(a); // 如果缓冲区没有数据,则程序会阻塞
cin.get(b);
cin.get(c);
cout << a << b << c;
}

void input5() {
char buf[256];
cout << "请输入字符串: ";
cin.getline(buf, 256); // 当遇到空格符时,不会停止接收数据输入
cout << buf << endl;
}

void input6() {
char buf1[256];
char buf2[256];
cout << "请输入字符串:"; // 例如输入:abc efghi
cin >> buf1;
cin.ignore(2); // 忽略缓冲区的数据
cin.getline(buf2, 256);
cout << buf1 << endl;
cout << buf2 << endl;
}

void input7() {
char buf1[256];
char buf2[256];
cout << "请输入字符串:"; // 例如输入:abc efghi
cin >> buf1;
cin.ignore(2);
int num = cin.peek(); // 查看缓冲区是否有数据
cout << num << endl;
cin.getline(buf2, 256);
cout << buf1 << endl;
cout << buf2 << endl;
}

void input8() {
// 分开处理输入的整数和字符
cout << "Please, enter a number or a word: ";
char c = std::cin.get();

if ((c >= '0') && (c <= '9'))
{
int n;
cin.putback(c); // 将数据放回缓冲区
cin >> n;
cout << "You entered a number: " << n << '\n';
}
else
{
char ch;
cin.putback(c); // 将数据放回缓冲区
cin.get(ch);
cout << "You entered a character: " << ch << '\n';
}
}

标准输出流的简单使用

标准输出流对象 cout 的常用函数如下:

  • cout.flush()
  • cout.put()
  • cout.write()
  • cout.width()
  • cout.fill()
  • cout.setf()
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
36
37
38
39
40
41
42
43
#include <iostream>
#include <iomanip>

using namespace std;

void output1() {
cout.put('h').put('e').put('l').put('l').put('o').put('\n');
}

void output2() {
char* str = "hello world\n";
cout.write(str, strlen(str));
}

void output3() {
// 第一种方式:使用流对象的成员函数
cout << "<Start>";
cout.width(30);
cout.fill('*');
cout.setf(ios::showbase);
cout.setf(ios::internal);
cout << hex << 123 << "<End>\n";
}

void output4() {
// 第二种方式:使用控制符
cout << "<Start>"
<< setw(30)
<< setfill('*')
<< setiosflags(ios::showbase)
<< setiosflags(ios::internal)
<< hex
<< 123
<< "<End>\n";
}

int main() {
output1();
output2();
output3();
output4();
return 0;
}

程序运行输出的结果如下:

1
2
3
4
hello
hello world
<Start>0x**************************7b<End>
<Start>0x**************************7b<End>

文件 I/O 流的简单使用

以普通的方式读写文件

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
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>
#include "fstream"

using namespace std;

void writeFile() {
// 打开文件
char* fname = "D:/file.txt";
ofstream fout(fname);
if (fout) {
fout << "Hello World" << endl;
fout.flush();
fout.close();
}
}

void readFile() {
// 读取文件
char ch;
char* fname = "D:/file.txt";
ifstream fin(fname);
if (fin) {
while (fin.get(ch)) {
cout << ch;
}
fin.close();
}
}

void writeFileApp() {
// 以追加的方式打开文件
char* fname = "D:/file.txt";
ofstream fout(fname, ios::app);
if (fout) {
fout << "What" << endl;
fout.flush();
fout.close();
}
}

int main() {
writeFile();
readFile();
writeFileApp();
readFile();
return 0;
}

程序运行输出的结果如下:

1
2
3
Hello World
Hello World
What

以二进制的方式读写文件

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include "fstream"

using namespace std;

class Teacher {

public:
Teacher()
{
age = 33;
strcpy(name, "");
}

Teacher(int _age, char* _name)
{
age = _age;
strcpy(name, _name);
}

void print()
{
cout << "age:" << age << ", name:" << name << endl;
}

private:
int age;
char name[32];
};

int main() {
char* fname = "D:/file.dat";
ofstream fout(fname, ios::binary);
if (!fout) {
cout << "打开文件失败" << endl;
return 0;
}

// 将类对象写入二进制文件(序列化)
Teacher t1(23, "Jim");
Teacher t2(26, "Tom");
fout.write((char*)&t1, sizeof(Teacher));
fout.write((char*)&t2, sizeof(Teacher));
fout.flush();
fout.close();

ifstream fin(fname);
if (!fin) {
cout << "打开文件失败" << endl;
return 0;
}

// 从二进制文件读取类对象(反序列化)
Teacher tmp;
fin.read((char*)&tmp, sizeof(Teacher));
tmp.print();
fin.read((char*)&tmp, sizeof(Teacher));
tmp.print();
fin.close();

return 0;
}

程序运行输出的结果如下:

1
2
age:23, name:Jim
age:23, name:Jim