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
89
90
91
92
93
#include <iostream>

using namespace std;

/**
* cin.get() 读取输入的单个字符, 包括换行符
*
* <p> 比如输入 as, 缓冲区存放的是 a, s, 换行符
*/
void test01() {
char c = cin.get();
cout << "c = " << c << endl; // 第一次读取到 a

c = cin.get();
cout << "c = " << c << endl; // 第二次读取到 s

c = cin.get();
cout << "c = " << c << endl; // 第三次读取到换行符

c = cin.get();
cout << "c = " << c << endl; // 第四次等待下次输入
}

/**
* cin.get(arg1, arg2) 读取输入的多个字符, 不包括换行符
*/
void test02() {
char buffer[1024];
cin.get(buffer, 1024); // 不会读取换行符, 换行符仍然留在缓冲区
cout << buffer << endl;

char c = cin.get();
if (c == '\n') {
cout << "换行符还在缓冲区";
} else {
cout << "换行符不在缓冲区";
}
}

/**
* cin.getline(arg1, arg2) 读取输入的一行字符, 包括换行符
*/
void test03() {
char buffer[1024];
cin.getline(buffer, 1024); // 会读取换行符并舍弃掉, 换行符不会留在缓冲区
cout << buffer << endl;

char c = cin.get();
if (c == '\n') {
cout << "换行符还在缓冲区";
} else {
cout << "换行符不在缓冲区";
}
}

/**
* cin.ignore(arg1) 忽略读取一个或多个字符
*
* <p> 比如, 输入 abcd, 最终只会输出 c = d
*/
void test04() {
cin.ignore(); // 忽略读取一个字符
cin.ignore(2); // 忽略读取两个字符
char c = cin.get();
cout << "c = " << c << endl;
}

/**
* cin.peek() 查看缓冲区是否有数据
*
* <p> 比如, 输入 ab, 第一次输出 c = a, 第二次也是输出 c = a
*/
void test05() {
char c = cin.peek(); // 检测缓冲区是否有字符, 如果有则返回第一个字符, 且不会清理掉缓冲区中的任何字符
cout << "c = " << c << endl;

c = cin.get();
cout << "c = " << c << endl;
}

/**
* cin.putback(arg1) 将读取到的字符放回缓冲区
*
* <p> 比如, 输入 abcd, 最终输出的是 abcd
*/
void test06() {
char c = cin.get();
cin.putback(c); // 将读取到的字符放回缓冲区

char buffer[1024];
cin.getline(buffer, 1024);
cout << buffer << endl;
}

标准输入流的使用案例二

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <cstring>
#include <iomanip>

using namespace std;

/**
* cout.put() 向缓冲区写入字符
*/
void test01() {
cout.put('a');
}

/**
* cout.write() 从缓冲区中写 N 个字符到当前输出流中
*/
void test02() {
char buf[1024] = "hello world";
cout.write(buf, strlen(buf));
}

/**
* 通过流对象的成员函数实现格式化输出
*/
void test03() {
int number = 99;
cout.width(20);
cout.fill('*');
cout.setf(ios::left); // 设置对齐的格式,比如左对齐
cout.unsetf(ios::dec); // 卸载十进制格式
cout.setf(ios::hex); // 设置十六进制格式
cout.setf(ios::showbase); // 强制输出整数的基数(八进制以 0 开头,十六禁止以 0x 开头)
cout.unsetf(ios::hex); // 卸载十六进制格式
cout.setf(ios::oct); // 设置八进制格式
cout << number;
}

/**
* 使用控制符的方式显示
*/
void test04() {
int number = 30;
cout << setw(20)
<< setfill('*')
<< setiosflags(ios::showbase) // 强制输出整数的基数(八进制以 0 开头,十六禁止以 0x 开头)
<< setiosflags(ios::left) // 设置对齐的格式,比如左对齐
<< hex // 使用十六进制格式
<< number;
}

int main() {
cout << endl << "------- test01() -------" << endl;
test01();
cout << endl << "------- test02() -------" << endl;
test02();
cout << endl << "------- test03() -------" << endl;
test03();
cout << endl << "------- test04() -------" << endl;
test04();
return 0;
}

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

1
2
3
4
5
6
7
8
------- test01() -------
a
------- test02() -------
hello world
------- test03() -------
0143****************
------- test04() -------
0x1e****************

标准输出流的使用案例二

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
48
49
50
#include <iostream>
#include <fstream>

using namespace std;

void writeFile() {
// 写入文件
char *fname = "/tmp/file.txt";
ofstream fout(fname);
if (fout) {
fout << "Hello World 1" << endl;
fout << "Hello World 2" << endl;
fout << "Hello World 3" << endl;
fout.flush();
fout.close();
}
}

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

void readFile() {
// 按单个字符读取文件
char ch;
char *fname = "/tmp/file.txt";
ifstream fin(fname);
if (fin) {
while (fin.get(ch)) {
cout << ch;
}
fin.close();
}
}

int main() {
writeFile();
readFile();
cout << "-----------------------" << endl;
writeFileAppend();
readFile();
return 0;
}

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

1
2
3
4
5
6
7
8
Hello World 1
Hello World 2
Hello World 3
-----------------------
Hello World 1
Hello World 2
Hello World 3
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <iostream>
#include <fstream>

using namespace std;

// 写文件
void test01() {
ofstream ofs;
ofs.open("/tmp/file.txt", ios::out | ios::trunc);
if (!ofs.is_open()) {
cout << "文件打开失败" << endl;
return;
}
ofs << "Hello World 1" << endl;
ofs << "Hello World 2" << endl;
ofs << "Hello World 3" << endl;
ofs.flush();
ofs.close();
cout << "文件写入完成" << endl;
}

// 第一种读取文件的方式(不推荐使用):按行读取, 会忽略空白字符(如空格、换行符)
void test02() {
ifstream ifs;
ifs.open("/tmp/file.txt", ios::in);
if (!ifs.is_open()) {
cout << "文件打开失败" << endl;
return;
}

// 按行读取, 会忽略空白字符(如空格、换行符), 这样会导致整行内容无法被完整读取
char buffer[1024];
while (ifs >> buffer) {
cout << buffer << endl;
}

ifs.close();
}

// 第二种读取文件的方式(推荐使用):按行读取
void test03() {
ifstream ifs;
ifs.open("/tmp/file.txt", ios::in);
if (!ifs.is_open()) {
cout << "文件打开失败" << endl;
return;
}

// 按行读取
string line;
while (getline(ifs, line)) {
cout << line << endl;
}

ifs.close();
}

// 第三种读取文件的方式(不推荐使用):按单个字符读取
void test04() {
ifstream ifs;
ifs.open("/tmp/file.txt", ios::in);
if (!ifs.is_open()) {
cout << "文件打开失败" << endl;
return;
}

// 按单个字符读取
char c;
while ((c = ifs.get()) != EOF) {
cout << c;
}

ifs.close();
}

int main() {
cout << "------ test01() ------" << endl;
test01();
cout << "------ test02() ------" << endl;
test02();
cout << "------ test03() ------" << endl;
test03();
cout << "------ test04() ------" << endl;
test04();
return 0;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
------ test01() ------
文件写入完成
------ test02() ------
Hello
World
1
Hello
World
2
Hello
World
3
------ test03() ------
Hello World 1
Hello World 2
Hello World 3
------ test04() ------
Hello World 1
Hello World 2
Hello World 3

以二进制的方式读写文件

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 = "/tmp/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