C++之“流”-第2课-C++和C标准输入输出同步

为什么C++和C的标准输入输出不同步时,数据会混乱?同步会带来多大性能损失?为什么说这个损失通常不用太在乎?

0. 课堂视频

C++之“流”-第2课:和C输入输出的同步

1. 理解cin和cout的类型与创建过程

std::cout 是std::ostream类型的一个变量;而 std::ostream是std::basic_ostream<char>模板类的类型别名( typedef )。std::cin是std::isteram类型的一个变量,而std::istream是std::back_istream<cahr>模板类的类型别名。两个模板类中的 “char” 参数,表明二者都是基于普通 字符(char)作为最小输出或输入单位。如果改为 wchar_t,则以UNICODE字符串作为基本输入输出单位。

正如上一节课所说,std::ostream和std::istream都是抽象概念的流,无法直接创建对应的输出或输入流对象。

注意,C++中的“抽象”概念,和 Java 这样更加纯粹的“面向对象”的编程语言有所不同。Java 中的“抽象”,通常使用:“什么实事都不做,只负责定要求” 的接口(interface)表达。C++中有更多不同的方式来表达抽象概念,可以同样“什么事都不做,只负责定要求”的纯虚类,也可以是“做了很多基础的事,但禁用了特定构建方法”的方式。两种方式的共同表现是:不让用户直接创建对象。

std::ostream和std::istream对外开放的构造方法,都要求一个“流缓存区/stream_buf”入参。以输出为例,我们可以:

  • 设计并实现一个内存输出缓存区,传入后以得到一个内存输出流的基本功能;
  • 设计并实现一个文件输出缓存区,传入后以得到一个文件输出流的基本功能;
  • 设计并实现一个网络输出缓存区,传入后以得到一个网络输出流的基本功能;

那么,为std::istream的构造函数传入一个键盘输入流缓存区,就能得到一个标准输入流,即std::cin;而为std::ostream传入一个屏幕输出流缓存区,就能得到一个标准输出流,即std::cout。但实际上,C++程序中的std::cin和std::cout对象,都是C++库自动创建出来的,并且不允许用户手工创建二者。为什么呢?因为对一个程序来说,标准输入设施应该只有一个,标准输出设施也应该只有一个;如果用户自己创建,就挡不住有用户创建出一打标准输入流或标准输出流了。

在Windows的控制台(console)或Linux下的终端(terminal)里,键盘被称为程序的标准输入设备,屏幕被称为程序的标准输出。并且,无论一台电脑接多少个键盘(少见),在逻辑上都会被当作一个键盘;同理,无论一台电脑接多少个屏幕(常见),在逻辑上也都会被当作一个屏幕。因此,cin 和 cout 本质上是一种“单例”,即整个程序中,只能一个标准输入流,一个标准输出流。

这种“一个程序里,某种类型的对象只有一个”的逻辑的实现,有专门的,称为“单例模式”的设计模式来实现。C++实现 cin 和 cout 的单例保障倒很简单:使用默认构造函数(没有任何入参)来创建特定对象,再把该默认构造函数的访问权限设置为私有(private)或保护的(protected),在gc++的实现中使用的是后者。标准库内部可以通过 “友元”加“派生”的方式,实现对基类受保护的默认构造函数的调用。

一旦调用std::ostream的默认构造函数,由于没有入参,也就没有外部传入的输出缓存区,此时标准库将自动创建标准输出流的缓存区,从而创建出标准输出流,即:std::cout对象。标准输入流的创建过程与此类型,同样是调用默认构造函数,然后自行创建、关联和键盘输入缓存区,从而创建出 std::cin。

以上调用过程都是在程序主函数 main() 开始之前,就执行完毕,因此我们的程序在一开始就能够方便地使用std::cin和std::cout。事实上,在 main() 之前我们就可以使用了。如果在 main() 函数之前就开始执特定代码,这是C++的另一个知识点,不在此讲解。

2. 数据输入输出次序冲突问题的出现

到现在,一切看起来很完美:cin和cout是自动创建的,并且各自只会有一份,不会冲突……但是,考虑到C++的一个重要的历史使命:兼容C语言,问题就来了——

C 语言有自己的输入输出机制,并且本质上,底层也需要用到输入或输出缓存区。上一节课我们说过,这个缓存区本质是一个数据队列,一个“有次序保障”的数据队列。C++尽管做到了一个程序只有一个C++输入流或一个C++输出流,但加上C的队列,现在,一个C++程序会有两个输入队列、两个输出队列。

C/C++两套输入输出队列

这就有点像现实生活中的某种排队现象:入口或出口只有一个,但人们排了两条队,两条队伍各自的内部数据都有次序保障,但是,当门就在眼前,两条队伍如何通过一个门呢?无论是互相礼让,还是争先抢后,都无法保障复原原始的数据次序。

3. 混合输入,同步对比不同步

代码:

#include <cstdio> // C 语言的标准输入输出库
#include <iostream>

using namespace std;

int main()
{
    ios_base::sync_with_stdio(false); // 不同步!!!

    int i, j;

    scanf("%d", &i); //用C的方式输入  i
    cin >> j; // 用C++的方式输入 j

    cout << i << ", " << j << endl;

    return 0;
}

4. 混合输出,同步对比不同步

代码:

#include <cstdio>
#include <iostream>

using namespace std;

int main()
{
	ios_base::sync_with_stdio(false);

	for (int i=0; i<3; ++i)
	{
		printf("hello from printf!\n");
		cout << "hello from cout.\n";
	}

	return 0;
}

5. 同步与不同步性能对比

代码:

#include <ctime>
#include <cstdio>
#include <iostream>

using namespace std;

int main()
{
    ios::sync_with_stdio(false);

    clock_t beg = clock();

    for (int i=0; i<30000; ++i)
    {
        cout << "hello world.";
    }


    clock_t end = clock();
    cout << "\n" << (end - beg) * 1000 / CLOCKS_PER_SEC << "ms." <<endl;


    return 0;
}

注意,程序使用 sync_with_stdio(false) 取消 C++和C的标准输入输出同步,该操作是不可逆的,即后续无法通过 sync_with_stdio(true) 恢复 同步。

6. 为什么不用太在乎C++标准输入输出的性能?

C++常用以写以下程序:

类型典型应用描述大致占比输入输出性能
后台服务或底层组件网络服务、防火墙不直接面向用户,不使用标准输入输出25%不在乎
GUI程序Photoshop、Office、游戏使用系统GUI作为输入输出20%不在乎
基础工具命令行文件处理工具:压缩、图片处理虽然在命令行运行,但几乎没有输入输出15%不在乎
简单命令行工具各类命令行客户端程序:libcurl,文件列表低频使用标准输入输出20%不在乎
非性能敏感的控制台应用用户开发的简单命令行应用,比如处理excel表格性能不敏感15%不在乎
性能敏感的控制台应用信息学竞赛程序、远程日志监控等性能敏感,大量标准输入输出操作会影响程序性能5%在乎
  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南郁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值