CPP中cout输出问题研究

此生再无相见时 提交于 2020-02-28 17:12:09

一直以来都喜欢用百度知道,最近有网友在百度知道上提问:

他的C++教学课程中有这样一段话:

【在定义流对象时,系统会在内存中开辟一段缓冲区,用来暂存输入输出流的数据,在执行cout语句时,先把插入的数据顺序存放在输出缓冲区中,直到输出缓冲区满或遇到cout语句中的endl(或‘’,ends,flush)为止,此时将缓冲区中已有的数据一起输出,并清空缓冲区,输出流中的数据在系统默认的设备(一般为显示器)输出。】

网友的疑问:上面的这段话对不对?如果是对的,那他的意思是不是就是说如果没有遇到endl或者缓冲区没有满的时候是不是就不能进行输出了呢?

网友的意思是,如果有如下一段代码:

int a;
cin>>a; 
cout<<a;

上面的代码是不是存在不会输出的问题,因为按照教学课程的意思,上面的代码并未调用endl或flush,所以不会进行输出。但是实际上是有输出的,那么为什么?是不是教学课程有错误?

为此我查看了CPP标准库的源代码,从cout的实现看起。cout是ostream类的实例对象,查看该类的说明可以看到许多重载的<<操作符,<<操作符其实也是ostream类的成员函数,其后的a就是参数。那么在本例中其实就是调用

ostream& operator<< (int val);

这个操作符的重载函数。我们来看看这个重载的实现代码,如下:

 basic_ostream& operator<<(unsigned int __x)
     { return _M_put_num(this, static_cast<unsigned long>(__x)); }

我们看到,该重载调用了一个_M_put_num函数,并且对参数进行了转换。我们再看看_M_put_num函数的实现代码:

template <class _CharT, class _Traits, class _Number> 
 basic_ostream<_CharT, _Traits>&
 _M_put_num(basic_ostream<_CharT, _Traits>* __this, _Number __x)
 {
   typename basic_ostream<_CharT,_Traits>::sentry __sentry(*__this);
   if (__sentry) {
     bool __failed = true;
     __STL_TRY {
       typedef num_put<_CharT, ostreambuf_iterator<_CharT, _Traits> > 
         _NumPut;
       __failed
         = use_facet<_NumPut>(__this->getloc()).put(*__this, *__this, 
                                                    __this->fill(),
                                                    __x).failed();
     }
     __STL_CATCH_ALL {
       __this->_M_handle_exception(ios_base::badbit);
     }
     
     if (__failed)
       __this->setstate(ios_base::badbit);
   }
   return *__this;
 }

_M_put_num是一个模板函数,我们可以看到该函数调用了一个put函数来进行操作,那就再找找这个put函数:

template <class _CharT, class _Traits>
 basic_ostream<_CharT, _Traits>&
 basic_ostream<_CharT, _Traits>::put(char_type __c)
 {
   sentry __sentry(*this);
   bool __failed = true;
 
   if (__sentry) {
     __STL_TRY {
       __failed = _S_eof(this->rdbuf()->sputc(__c));
     }
     __STL_CATCH_ALL {
       this->_M_handle_exception(ios_base::badbit);
     }
   }
 
   if (__failed)
     this->setstate(ios_base::badbit);
 
   return *this;
 }

同样一个模板函数,可以看到put函数是调用了rdbuf()->sputc(__c)来进行操作的,咱们看看这个sputc是什么,

sputc函数是streambuf的成员函数,下面是CPP官网的说明:

int sputc (char c);

Store character at current put position and increase put pointer

The charactercis stored at the current position of the controlled output sequence, and then advances the position indicator to the next character.

Internally, the function calls the virtual protected member overflow if there are no write positions available at the put pointer ( pptr). Otherwise, the function uses the put pointer ( pptr) directly, without calling virtual member functions.

大意是在将字符存储到控制输出流的pptr位置,并将指示器加1。

也就是说成员操作符<<最终调用了sputc函数实现了想输出流缓冲区写入字符。我们找到了写缓冲区的关键,那么写入缓冲区的字符是如何输出的呢?我们再看看put函数的实现代码。注意第一行:

 sentry __sentry(*this);

这个sentry是什么?看看CPP官网是如何说的:

std::ostream::sentry

class sentry;

Prepare stream for output

Member class that performs a series of operations before and after each output operation:

Its constructor performs the following operations on the stream object passed as its argument (in the same order):
  • If any of its internal error flags is set, the function may set its failbit flag and return.

  • If it is a tied stream, the function flushes the stream it is tied to (if its output buffer is not empty), unless the function determines that synchronization is not necessary.
    On failure, it may set the failbit flag.

该类是ostream的嵌套类,每一个ostream类对象都会实例化sentry类。该类在输出操作的前后都要进行一系列的操作。主要是设置流对象的标志,并刷新相关流。ostream的所有成员函数都会构造该类的实例,从而实现对缓冲区的检测。这里有两点需要我们注意:一是sentry对象构造时刷线缓冲区是在put函数完成写入缓冲区操作之前,显然输出操作不是在sentry对象构造时进行的;二是实例的对象__sentry是put函数的局部变量。所以在put函数完成输出操作返回时,是要析构销毁该对象的。重点就在对象销毁时的析构函数中,来看看sentry类的定义:

explicit sentry(basic_ostream<_CharT, _Traits>& __str)
       : _M_str(__str), _M_ok(false), _M_buf(__str.rdbuf())
     {
       if (_M_str.good()) {
         if (!_M_buf)
           _M_str.setstate(ios_base::badbit);
         if (_M_str.tie())
           _M_str.tie()->flush();
         _M_ok = _M_str.good();      
       }
     }
     
     ~sentry() {
       if (_M_str.flags() & ios_base::unitbuf)
 #ifdef _UNCAUGHT_EXCEPTION
         if (!uncaught_exception())
 #endif
           _M_str.flush();
     }

可以看到,sentry类对象析构时flush了。这时我们通过put函数写入缓冲区的内容就被flush输出了。

所以教学课程的内容是有问题的,不严谨或者错误的。

【如有问题,请批评指正!】

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!