《C++ Primer Plus》学习笔记 — 输入、输出和文件

本文详细介绍了C++中的输入输出流,包括流和缓冲区、iostream继承体系、iostream流对象和重定向。重点讲解了如何使用cout进行输出,如数字类型的插入运算符重载、字符串类型的插入运算符重载以及格式化输出。此外,还深入探讨了使用cin进行输入的操作,如抽取操作符、流状态、异常处理以及其他istream类方法。最后,文章阐述了文件输入输出的相关知识,包括简单文件IO、流状态检查、文件模式以及内核格式化。
摘要由CSDN通过智能技术生成

一、概述

1、流和缓冲区

C++把输入和输出看做字节流。字节可以构成字符或数值数据的二进制表示。C++程序只是检查字节流,而不需要知道字节来自何方,去往何处。这就是说,我们可以独立地进行流输入、流处理以及流输出。下面列出了管理和操作流的步骤:
在这里插入图片描述
我们使用缓冲区进行高效地输入输出处理。通常,内存是以块来进行数据读取和写入的,而程序是以字节为单位进行数据处理的。缓冲区可以帮助我们匹配二者的速度。

2、iostream继承体系

C++的I/O解决方案不同于C,是基于类(iostream)所实现的。下面是其继承体系:
在这里插入图片描述
这些类提供了对输入输出字节流的操作,其中:
ios_base类提供了流的一般特征,如格式化,流状态,本地化等。
basic_ios类是一个模板类,模板参数为字符类及其特征类。该类通过包含一个指向basic_streambuf对象的指针提供了使用缓冲区处理流的功能。basic_streambuf的工作方式如下:
在这里插入图片描述
basic_ostream通过重载 << 运算符提供了输出不同类型数据的方法。
basic_istream通过重载 >> 运算符提供了输入不同类型数据的方法。
basic_iostream同时支持输入和输出操作。
其余类继承了上面所说的三个基类,是针对文件和字符串输入输出流的特化处理。

3、iostream流对象

上面我们说过使用字节流处理文件的步骤。C++的iostream类库为我们自动创建了8个流对象:
cin用于处理char类型的输入。默认情况下被关联到标准输入,通常为键盘。
cout用于处理char类型的输出。默认情况下被关联到标准输出,通常为显示器。
cerr用于处理char类型的错误显示。默认情况下被关联到标准输出,通常为显示器。需要注意的是这个流没有缓冲区,因此打印的错误信息将会直接被推送给屏幕。
clog同样用于处理char类型的错误显示。默认情况下被关联到标准输出,通常为显示器。这个流有缓冲区。
wcinwcoutwcerrwclog分别为上述对象针对于宽字节的流处理对象。

那么流对象与标准输入输出是怎样绑定的呢?
以gcc代码作为参考:

new (&buf_cout_sync) stdio_sync_filebuf<char>(stdout);
new (&buf_cin_sync) stdio_sync_filebuf<char>(stdin);
new (&buf_cerr_sync) stdio_sync_filebuf<char>(stderr);

// The standard streams are constructed once only and never
// destroyed.
new (&cout) ostream(&buf_cout_sync);
new (&cin) istream(&buf_cin_sync);
new (&cerr) ostream(&buf_cerr_sync);
new (&clog) ostream(&buf_cerr_sync);
cin.tie(&cout);
cerr.setf(ios_base::unitbuf);
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 455. cerr::tie() and wcerr::tie() are overspecified.
cerr.tie(&cout);

我们可以看出cout与标准输出的绑定主要是通过系统接口将缓冲区与标准输出绑定。

4、重定向

我们在启动程序时可以通过命令行参数重定向标准输入输出,但是这种重定向的输出并不影响cerrclog

#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
   
	int totalLength = 0;
	string s;
	while (cin >> s)
	{
   
		totalLength += s.length();
	}

	cout << totalLength << endl;
}

我们使用程序可执行文件名 > 输出文件 < 输入文件名可以将输入文件中的字符数输出到输出文件中。在此过程中,屏幕中将不会得到任何输出。

二、使用cout进行输出

与执行位运算的 << 操作符不同,ostream重载的 << 操作符被称为插入运算符。

1、用于输出数字类型的插入运算符重载

uint为例:

basic_ostream& __CLR_OR_THIS_CALL operator<<(unsigned int _Val) {
    // insert an unsigned int
    ios_base::iostate _State = ios_base::goodbit;
    const sentry _Ok(*this);

    if (_Ok) {
    // state okay, use facet to insert
        const _Nput& _Nput_fac = _STD use_facet<_Nput>(this->getloc());

        _TRY_IO_BEGIN
        if (_Nput_fac.put(_Iter(_Myios::rdbuf()), *this, _Myios::fill(), static_cast<unsigned long>(_Val))
                .failed()) {
   
            _State |= ios_base::badbit;
        }
        _CATCH_IO_END
    }

    _Myios::setstate(_State);
    return *this;
}

(1)sentry 和 tie

sentryostream类的一个公有内部类:

explicit __CLR_OR_THIS_CALL sentry(basic_ostream& _Ostr) : _Sentry_base(_Ostr) {
   
    if (!_Ostr.good()) {
   
        _Ok = false;
        return;
    }

    const auto _Tied = _Ostr.tie();
    if (!_Tied || _Tied == &_Ostr) {
   
        _Ok = true;
        return;
    }

    _Tied->flush();
    _Ok = _Ostr.good(); // store test only after flushing tie
}

这个类的作用是刷新缓冲区。tie方法是用于给当前流对象(cout)设置或获取一个输出流对象(当二者的缓冲区管理的字符序列需要同步时,例如cincout),以保证当前流对象输入或输出之前,被管理的输出流对象缓冲区将会被刷新。我们以文件流举个例子(来源C++参考手册):

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

int main()
{
   
	ofstream os("test.txt");
	ifstream is("test.txt");
	string value("0");

	os << "Hello";
	is >> value;

	cout << "Result before tie(): \"" << value << "\"\n";
	is.clear();
	is.tie(&os);

	is >> value;

	cout << "Result after tie(): \"" << value << "\"\n";
}

在这里插入图片描述
我们简单解释下这段代码(这里test.txt文件一开始并不存在):首先我们使用文件输出流输出了hello。然而,文件输出流默认有缓冲区,只有遇到指定字符(默认为换行),才会刷新缓冲区。因此我们第一次从文件输入流中并不会得到任何数据。所以第一次我们打印的value为其初值。然后我们调用tie方法将文件输出流绑定到输入流上。这时我们再从文件输出流向value变量中输入字符时,根据我们前面的解释,会先调用绑定的输出流的flush方法。因此文件输出流的hello字符串就进入文件中了。所以我们第二次会将文件中的hello设置到value变量中。

(2)use_facet 和 locale

use_facet是一个模板方法,用于获取指定locale的某一组成部分:

template< class Facet >
const Facet& use_facet( const std::locale& loc )
{
   
	...
	const size_t _Id         = _Facet::id;
    const locale::facet* _Pf = _Loc._Getfacet(_Id);
    ...
    return static_cast<const _Facet&>(*_Pf); // should be dynamic_cast
}

这里是由模板参数的成员变量id找到对应的部分。我们输出数字时获取到的facet就是num_put类。关于id的宏定义如下:

// xlocalinfo.h
#define _X_ALL      LC_ALL  // 0
#define _X_COLLATE  LC_COLLATE  // 1
#define _X_CTYPE    LC_CTYPE  // 2
#define _X_MONETARY LC_MONETARY  // 3
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值