C++流类

123 阅读5分钟

本文已参与[新人创作礼]活动,一起开启掘金创作之路。

image.png

(欢迎大家关注我的微信公众号——控制工程研习,上面会分享很多我学习过程中总结的笔记。)

一直以来对于C++的所谓流研究不深,由于近期的工作任务好好学习了一下。

    C++ 又可以称为“带类的 C”,即可以理解为 C++ 是 C 语言的基础上增加了面向对象(类和对象)。C语音有一套数据读写(I/O)的方案:

· 使用 scanf()、gets() 等函数从键盘读取数据,使用printf()、puts() 等函数向屏幕上输出数据;

· 使用fscanf()、fgets() 等函数读取文件中的数据,使用 fprintf()、fputs() 等函数向文件中写入数据。

    C 语言的这套 I/O 解决方案也适用于 C++ 程序,但 C++ 并没有“偷懒”,它自己独立开发了一套全新的 I/O 解决方案,其中就包含大家一直使用的 cin 和 cout。


    本质上来说,C++ 的这套 I/O 解决方案就是一个包含很多类的类库(作为 C++ 标准库的组成部分),这些类常被称为 “流类” 。C++ 的开发者认为数据输入和输出的过程也是数据传输的过程,数据像水一样从一个地方流动到另一个地方,所以 C++ 中将此过程称为“流”,实现此过程的类称为“流类”。

    C++中用于实现数据输入和输出的这些流类以及它们之间的关系如下图所示。箭头表示派生关系。

图片

    ios是所有流类的基类,它派生出 istream 和 ostream。特别需要指出的是,为了避免多继承的二义性,从 ios 派生出 istream 和 ostream 时,均使用了 virtual 关键字(虚继承)。

    这些流类各自功能分别为:

流类名**功能 **
istream常用于接收从键盘输入的数据;
ostream常用于将数据输出到屏幕上;
ifstream用于读取文件中的数据;
ofstream用于向文件中写入数据;
iostream继承自 istream 和 ostream 类,因为该类的功能兼两者于一身,既能用于输入,也能用于输出;
fstream兼 ifstream 和 ofstream 类功能于一身,既能读取文件中的数据,又能向文件中写入数据。

1. 输入输出流

    只要涉及输入或者输出数据,我们立马想到的就是 cin 和 cout。其实,cin 就是 istream 类的对象,cout 是 ostream 类的对象,它们都声明在 头文件中。

    除此之外, 头文件中还声明有 2 个 ostream 类对象,分别为 cerr 和 clog。它们的用法和 cout 完全一样,但 cerr 常用来输出警告和错误信息给程序的使用者,clog 常用来输出程序执行过程中的日志信息(此部分信息只有程序开发者看得到,不需要对普通用户公开)。

    cout、cerr和 clog 之间的区别如下(关于缓冲区上的区别,后面再做分析):


    · cout 除了可以将数据输出到屏幕上,通过重定向,还可以实现将数据输出到指定文件中;而 cerr 和 clog 都不支持重定向,它们只能将数据输出到屏幕上;

    · cout 和 clog 都设有缓冲区,即它们在输出数据时,会先将要数据放到缓冲区,等缓冲区满或者手动换行(使用换行符 '\n' 或者 endl)时,才会将数据全部显示到屏幕上;而 cerr 则不设缓冲区,它会直接将数据输出到屏幕上。


    除了以上 2 点特性上的不同之外,cerr、clog 和 cout 没有任何不同。之所以我们常用 cout,是因为 cerr 和 clog 有各自不同的适用场景。以 cerr 为例,一旦程序某处使用 cerr 输出数据,我们自然而然地会认为此处输出的是警告或者错误信息。

    值得一提的是,类似 cin、cout、cerr 和 clog 这样,它们都是 C++ 标准库的开发者创建好的,可以直接拿来使用,这种在 C++ 中提前创建好的对象称为内置对象

    一个简单的例子:

#include <iostream>
#include <string>
int main() {
    std::string test;
    std::cin >> test;
    std::cout << "cout:" << test << std::endl;
    std::cerr << "cerr:" << test << std::endl;
    std::clog << "clog:" << test << std::endl;
    return 0;
}

结果显示:

test
cout:test
cerr:test
clog:test

此外对于cin对象还有一些很有用的成员方法,之前都没有注意过:

成员方法名功能
getline(str,n,ch)从输入流中接收 n-1 个字符给 str 变量,当遇到指定 ch 字符时会停止读取,默认情况下 ch 为 '\0'。
get()从输入流中读取一个字符,同时该字符会从输入流中消失。
gcount() 返回上次从输入流提取出的字符个数,该函数常和  get()、getline()、ignore()、peek()、read()、readsome()、putback() 和 unget() 联用。
peek()返回输入流中的第一个字符,但并不是提取该字符。
putback(c) 将字符 c 置入输入流(缓冲区)。
ignore(n,ch)从输入流中逐个提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 个字符,或者当前读取的字符为 ch。
operator>>重载 >> 运算符,用于读取指定类型的数据,并返回输入流对象本身。

    类似地,cout、clog、cerr对象也有一些成员方法:

成员方法名功能
put()输出单个字符。
write()输出指定的字符串。
tellp()用于获取当前输出流指针的位置。
seekp()设置输出流指针的位置。
flush()刷新输出流缓冲区。
operator<<重载 << 运算符,使其用于输出其后指定类型的数据。

2. 输入输出缓冲区的理解

    cout、cerr、clog的一些区别是cerr没有缓冲区,而cout和clog是有缓冲区的。\

    cout是在终端显示器输出,cout流在内存中对应开辟了一个缓冲区,用来存放流中的数据,当向cout流插入一个endl,不论缓冲区是否满了,都立即输出流中所有数据,然后插入一个换行符。

    clog流也是标准错误流,作用和cerr一样,区别在于cerr不经过缓冲区,直接向显示器输出信息,而clog中的信息存放在缓冲区,缓冲区满或者遇到endl时才输出。

    endl 是一个特殊值,称为操纵符,将它写入输出流时,具有输出换行的效果,并刷新与设备相关联的缓冲区。通过刷新缓冲区,用户可立即看到写入到流中的输出。 程序员经常在调试过程中插入输出语句,这些语句都应该刷新输出流。忘记刷新输出流可能会造成输出停留在缓冲区中,如果程序崩溃,将会导致程序错误推断。

   关于缓冲区刷新的含义:

    以缓冲方式打开一个文件时,往文件里写几个字节,一般不会立即真正把这几个字节写入文件,只有当缓冲区满时才真正写盘。如果想在缓冲区满之前写盘保存,可以做冲刷缓冲区动作。

    下列行为引发冲刷动作:1)缓冲区满时;2)行输出时遇endl,cerr或cin时;3)执行冲刷函数;4)关闭文件。


3. 文件流的读与写

    文件流的读写实际就与输入输出流类似。

    写文件的步骤:

// 1.包含头文件
#include <fstream>
// 2.创建流对象
Ofstream ofs;
// 3.打开文件
ofs.open("文本路径", 打开方式)
// 4.写数据
Ofs << "写入的数据";
// 5.关闭文件
Ofs.close();

读文件的步骤:

// 1. 包含头文件
#include <fstream>
// 2.创建对象流
Ifstream ifs;
// 3.打开文件并判断文件是否打开成功
Ifs.open("文件路径", 打开方式)
// 4.读数据
四种方式读取
// 5.关闭文件
Ifs.close()

四种读文件的方式如下:

// 第一种
char buf[1024] = { 0 };
while (ifs >> buf) {
	cout << buf << endl;
}
ifs.close();

// 第二种
char buf[1024] = { 0 };
while (ifs.getline(buf, sizeof(buf))) {
	cout << buf << endl;
}

// 第三种
string buf;
while (getline(ifs, buf)) {
	cout << buf << endl;
}

// 第四种
char c;
while ((c = ifs.get()) != EOF) {
	cout << c;
}

4. 自定义流

    了解了这些流之后,我们可以做一些特别的事情,自己定义实际所需要的流,例如下面这个例子:

std::ostream& stream()
{
    return std::cout << "test:" << std::flush;
}

这个简单的函数实例的返回值是一个流,从函数内容可以看到整个返回的流设计好一些预定的打印消息,这样就会使原本简单的流满足自己的设计要求了。

    std::flush也是用于刷新缓冲区的。

参考网站:

[1] C++输入流和输出流(超级详细):c.biancheng.net/view/7559.h…

[2] c++里关于cerr,clog,cout三者的区别:www.cnblogs.com/eddyshn/p/4…