IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 《C++标准库》学习笔记 — STL —流 -> 正文阅读

[C++知识库]《C++标准库》学习笔记 — STL —流

一、操控器

所谓操控器是一种专门用来操控 stream 的一种对象,通常它只会改变输入的解释方式输出的格式化方式。带实参的操控器被定义在 文件中,如 setw 等;不带实参的操控器包括 endlflush

1、原理

操控器的实现原理其实是函数指针。在 ostream 中定义了 operator<< 的多种重载,其中包括:

__ostream_type& operator<<(__ostream_type& (*__pf)(__ostream_type&))
{
	return __pf(*this);
}

我们通过传入不同的操控器函数指针,即可触发不同的处理。以 endl 为例:

template<typename _CharT, typename _Traits>
inline basic_ostream<_CharT, _Traits>& endl(basic_ostream<_CharT, _Traits>& __os)
{ return flush(__os.put(__os.widen('\n'))); }

widen 函数用于国际化相关操作。

2、自定义操控器

参考标准库中操控器的定义,我们可以自定义操控器用于读取忽略一行中的剩余输入:

#include <iostream>
#include <limits>
using namespace std;

template<typename _CharT, typename _Traits>
inline basic_istream<_CharT, _Traits>& ignoreLine(basic_istream<_CharT, _Traits>& is)
{ 
	is.ignore(numeric_limits<streamsize>::max(), is.widen('\n'));
	return is;
}

int main(int argc, char* argv[])
{
	char ch;
	int numOfLinesStartWithA = 0;
	
	while((ch = cin.get()) != 'q')
	{
		if (ch == 'A')
		{
			numOfLinesStartWithA++;
		}
		
		cin >> ignoreLine;
	}
	
	cout << numOfLinesStartWithA << " lines start with A" << endl;

    return 0;
}

在这里插入图片描述

3、控制输入的宽度

我们可以用 setw 控制输出数据的宽度。此操作符同样可以用于输入上,主要作用在与C风格字符串共同使用:

char buffer[80];
cin >> setw(sizeof(buffer))>> buffer;

这样可以防止输入溢出。

二、自定义 I/O 操作符

假设我们有这样一个处理分数的类:

class Fraction
{
public:
	Fraction(int numerator, int denominator):
		numerator(numerator),
		denominator(denominator)
	{}

	int getDenominator() const
	{
		return denominator;
	}
	
	int getNumerator() const
	{
		return numerator;
	}
	
private:
	int numerator;
	int denominator;
}

1、重载输出操作符

一般情况下,我们可能想要这样实现:

inline ostream& operator<<(ostream& os, const Fraction& fraction)
{
	os << fraction.getNumerator() << "/" << fraction.getDenominator();
	return os;
}

这种实现存在两个问题:
① 这样的输出只适合使用 charstream(对本例来说当然没有什么区别)。
② 一次性的格式化标志仅对分子起作用。换句话说,如果我们尝试这样调用:

#include <iomanip>
#include <iostream>
int main(int argc, char* argv[])
{
	
	Fraction f(5,20);
	cout << setw(5) << f << endl;
	cout << setw(5) << 5 << endl;
    return 0;
}

得到的结果是:
在这里插入图片描述
这里的宽度仅对分子起作用。

简单修改即可解决上述问题:

#include <sstream>

template<typename charT, typename traits>
inline basic_ostream<charT, traits>& operator<<(basic_ostream<charT, traits> &os, const Fraction& fraction)
{
	basic_ostringstream<charT, traits> s;
	s.copyfmt(os);
	s.width(0);
	
	s << fraction.getNumerator() << "/" << fraction.getDenominator();
	
	os << s.str();
	
	return os;
}

在这里插入图片描述

2、输入操作符

简单的输入操作符实现如下:

inline istream& operator>>(istream& is, Fraction& fraction)
{
	int numerator;
	int denominator;
	
	is >> numerator;
	is.ignore();
	is >> denominator;
	
	fraction = Fraction(numerator, denominator);
	
	return is;
}

这种实现的考虑也很不周到:
① 没有考虑 char 类型的 stream,同时没有考虑分子分母之间输入的是否为 /(如果不是,应该报告格式错误)。
② 如果输入的为字符等其他无效输入,会引发格式异常。此异常不应传递进入 fraction 中,而是通过设置 failbit 提醒用户。
③ 未考虑部分读取错误或全部错误情况下对 fraction 的修改不应该进行。

优化后的代码如下:

template<typename charT, typename traits>
inline basic_istream<charT, traits>& operator>>(basic_istream<charT, traits>& is, Fraction& fraction)
{
	int numerator;
	int denominator;
	
	is >> numerator;
	
	if (is.peek() == '/')
	{
		is.ignore();
		is >> denominator;
	}
	else 
	{
		denominator = 1;
	}
	
	if (denominator == 0)
	{
		is.setstate(std::ios_base::failbit);
		return is;
	}
	
	if (is)
	{
		fraction = Fraction(numerator, denominator);
	}	
	
	return is;
}

我们简单测试一下:

int main(int argc, char* argv[])
{
	Fraction f(1,1);
	for (int i = 0; i < 2; ++i)
	{
		while (true)
		{
			cout << "请输入分数:";
			if (!(cin >> f))
			{
				break;
			}
			cout << "输入的分数为:" << f << endl;
		}
		cout << "无效分数,原分数为:" << f << endl;
		cin.clear();
		cin.get();
	}
	
    return 0;
}

在这里插入图片描述

三、自定义格式化标志

我们可以借助 iwordpword 两个函数用于设置和获取格式化标志,其参数均为 int 索引,返回类型为 long&void&* 指针。我们可以使用 ios_base 中的惊跳函数 xalloc 来取得一个尚未被用于此目的的索引:

static const int iword_index = std::ios_base::xalloc();

template<typename charT, typename traits>
inline basic_ostream<charT, traits>& operator<<(basic_ostream<charT, traits>& os, const Fraction& fraction)
{
	if (os.iword(iword_index))
	{
		os << fraction.getNumerator() << " / " << fraction.getDenominator();
	}
	else
	{
		os << fraction.getNumerator() << "/" << fraction.getDenominator();
	}

	return os;
}

ostream& fraction_spaces(std::ostream& strm)
{
	strm.iword(iword_index) = true;
	return strm;
}

int main(int argc, char* argv[])
{

	Fraction f(5, 20);
	cout << f << endl;
	cout << fraction_spaces << f << endl;
	cout << f << endl;
	return 0;
}

int main(int argc, char* argv[])
{

	Fraction f(5, 20);
	cout << f << endl;
	cout << fraction_spaces << f << endl;
	cout << f << endl;
	return 0;
}

在这里插入图片描述
注意这种状态不是一次性状态。除非我们后续重新设置,此标志位不会自动清除。

我们使用 copyfmt 函数会拷贝所有的格式信息,包括使用 iwordpword 设置的格式数组。对于 pword 来讲,这可能会导致浅拷贝的问题。因为 pword 仅保存了格式对象的地址。这可能会导致对一个格式对象的修改影响到多个流对象。ios_base 定义了一个回调,用以支持 必要时执行深拷贝销毁 stream 时销毁格式对象
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

void eventCb(ios_base::event e, ios_base& base, int userdata)
{
	cout << e << " " << userdata << endl;
}

int main(int argc, char* argv[])
{
	Fraction f(5, 20);
	cout.register_callback(eventCb, 12345);
	ostringstream str;
	str.copyfmt(cout);
}

在这里插入图片描述
copy_format 被调用后,将会先后触发 copyfmt_eventerase_event 两个回调。

四、连接 Input 和 Output Stream

1、以 tie 完成松耦合

前面我们学习 《C++ Primer Plus》曾经学习过这个方法。tie 用于将某个 stream 连接到另一个 stream 上:
在这里插入图片描述

2、以 Stream 缓冲区完成紧耦合

通过函数 rdbuf,可以使不同的 stream 共享同一个缓冲区,从而实现 stream 的紧耦合。其声明如下:
在这里插入图片描述
成员函数 rdbuf 允许多个 stream 对象从同一个缓冲区中读取或写入信息,而不必困扰与 I/O 次序。由于 I/O 操作被施以缓冲措施,所以同时使用多个 stream 缓冲区是很麻烦的。因为,对同一个 I/O 通道使用不同的 stream,而这些 stream 的缓冲区又不相同,意味着 I/O 得传递给其他 I/O。

basic_istreambasic_ostream 支持以缓冲区为参数的构造函数:

#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
	ostream hexout(cout.rdbuf());
	hexout.setf(ios::hex, ios::basefield);
	hexout.setf(ios::showbase);

	hexout << "hexout: " << 177 << " ";
	cout << "cout: " << 177 << " ";
	hexout << "hexout: " << -49 << " ";
	cout << "cout: " << -49 << " ";
	hexout << endl;
}

在这里插入图片描述
这里二者能共享缓冲区,是因为 basic_istreambasic_ostream 对象并不会在析构时释放对应的缓冲区。其他 stream 类对象都会释放它们最初分配的缓冲区,但它们不会销毁以 rdbuf 设置的缓冲区。这是因为 basic_istreambasic_ostream 使用 basic_ios 中提供的缓冲区对象,该对象是在堆上构建的;而如 stringstream 的类保存了自己的缓冲区对象,并重载了 rdbuf 方法已返回该对象。该对象在栈上分配,随着流对象的销毁而销毁。

此外,由于输出的格式保存在流对象而非缓冲区中,因此二者的输出格式不会互相影响。

3、重定向标准流

使用缓冲区绑定,我们可以实现标准流的重定向。需要注意的是,如果我们将标准流的输出重定向到 filestreamstringstream 的缓冲区上,需要在相应的流对象被释放后复原缓冲区(一个好的选择是使用智能指针):

#include <iostream>
#include <fstream>
#include <memory>
using namespace std;

void redirect(ostream& os);

int main(int argc, char* argv[])
{
	cout << "before redirect" << endl;
	redirect(cout);
	cout << "after redirect" << endl;
}

void redirect(ostream& os)
{
	auto delFunc = [&](streambuf* buf)
	{
		os.rdbuf(buf);
	};
	unique_ptr<streambuf, decltype(delFunc)> bufPtr(os.rdbuf(), delFunc);

	ofstream fs("redirect.txt");
	if (fs.is_open())
	{
		os.rdbuf(fs.rdbuf());
		fs << "one row from fs" << endl;
		os << "one row from os" << endl;
	}
}

在这里插入图片描述
在这里插入图片描述

4、可读可写的流

如果我们使用 fstream 声明一个对象,指定 ios::inios::out 两个标识,那么该流就是可读写的。我们也可以对于一个输出流对象和输入流对象使用相同的缓冲区达到相同的效果:

#include <iostream>
#include <fstream>
using namespace std;

int main(int argc, char* argv[])
{
	filebuf buffer;
	ostream os(&buffer);
	istream is(&buffer);
	buffer.open("test.txt", ios::in | ios::out | ios::trunc);

	for (int i = 0; i < 4; ++i)
	{
		os << i << ". line" << endl;

		is.seekg(0);
		char c;
		while (is.get(c))
		{
			cout.put(c);
		}
		cout << endl;

		is.clear();
	}
}

在这里插入图片描述
每次我们读取时都需要使用 seekg 将读取流指针定位到流开头。就像我们前面所学,读取流和写入流指针是不同的。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-09 19:17:43  更:2021-11-09 19:19:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:29:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码