欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 会展 > 【C++】:浅析 “std::cout << std::endl”

【C++】:浅析 “std::cout << std::endl”

2025/1/8 5:42:41 来源:https://blog.csdn.net/weixin_39568531/article/details/144986007  浏览:    关键词:【C++】:浅析 “std::cout << std::endl”

一、引子

std::cout << "Hello, world" << std::endl;

以上,作为任何一名接触过C++编程的朋友们来说,基本应该都是自己写下的第一行代码吧(别钻牛角尖,说什么头文件啥的)。

也许你也曾心里疑惑过,std::cout这也不是函数的表示方式啊,为啥他能这么写?也许你的老师也跟你说过,记住这个固定句式,它就这么写!然后,没有然后。你就这样已知遵循着“谆谆教诲”,教条式的传用了整个职业生涯~

本篇,作为打破你的思维,开始刨根问题的第N次分享,我们尽量深入浅出,更多的了解下std::cout << "Hello, world" << std::endl;,做到知其然,知其所以然。

二、鱼游浅底

2.1 cout是什么?

extern _CRTDATA2_IMPORT ostream cout;

cout 是什么? 对,它ostream类型的实例对象,而且是extern修饰的全局对象。

所以,既然是实例对象,我们还可以这样使用:

使用示例

#include <iostream>int main() {std::ostream& out = std::cout; // std::cout is an instance of std::ostreamout << "Hello, World!" << std::endl;return 0;
}

2.2 cout为何能直接 接<<操作符 ?

对象后面 直接接 << 操作符,这个不难联想,自然是有实现 << 操作符。
所以,我们简单看看ostream

using ostream       = basic_ostream<char, char_traits<char>>;

ostreambasic_ostream<char, char_traits<char>> 的一个类型别名。我们简单了解 下basic_ostream 类模板及其相关组件。

  1. basic_ostream 类模板:

    • basic_ostream 是 C++ 标准库中的一个类模板,用于表示输出流。它是一个通用的流类,可以与不同的字符类型和字符特性(traits)一起使用。
    • 模板参数包括字符类型(如 charwchar_t)和字符特性类(如 char_traits<char>)。
  2. char:

    • char 是 C++ 中的基本数据类型,用于表示单字节字符。
    • basic_ostream<char, char_traits<char>> 中,char 指定了流处理的字符类型。
  3. char_traits<char>:

    • char_traits 是一个特性类模板,定义了如何处理特定字符类型的操作。
    • char_traits<char> 提供了一组静态成员函数和类型,用于描述 char 类型字符的特性和操作,如比较、复制、查找等。
  4. using 声明:

    • using ostream = basic_ostream<char, char_traits<char>>; 是一种类型别名声明。
    • 它将 basic_ostream<char, char_traits<char>> 类型重命名为 ostream,使得代码更简洁和易读。

2.3 basic_ostream

在上面2.2 中我们已经了解到,iostream 其实只是类模版basic_ostream特化类型basic_ostream<char, char_traits<char>> 的别名,那么回到我们最开始的问题,为何能直接使用<< 操作符连接使用,答案不言而喻,必然存在<<的操作符函数,而且是多个函数的重载!!

说这话,我必然是有依据的。这里我为大家附上部分basic_ostream 中的代码:

 basic_ostream& __CLR_OR_THIS_CALL operator<<(basic_ostream&(__cdecl* _Pfn)(basic_ostream&) ) { // call basic_ostream manipulatorreturn _Pfn(*this);}basic_ostream& __CLR_OR_THIS_CALL operator<<(_Myios&(__cdecl* _Pfn)(_Myios&) ) { // call basic_ios manipulator_Pfn(*this);return *this;}basic_ostream& __CLR_OR_THIS_CALL operator<<(ios_base&(__cdecl* _Pfn)(ios_base&) ) { // call ios_base manipulator_Pfn(*this);return *this;}basic_ostream& __CLR_OR_THIS_CALL operator<<(bool _Val) { // insert a booleanios_base::iostate _State = ios_base::goodbit;const sentry _Ok(*this);if (_Ok) { // state okay, use facet to insertconst _Nput& _Nput_fac = _STD use_facet<_Nput>(this->getloc());_TRY_IO_BEGINif (_Nput_fac.put(_Iter(_Myios::rdbuf()), *this, _Myios::fill(), _Val).failed()) {_State |= ios_base::badbit;}_CATCH_IO_END}_Myios::setstate(_State);return *this;}basic_ostream& __CLR_OR_THIS_CALL operator<<(short _Val) { // insert a shortios_base::iostate _State = ios_base::goodbit;const sentry _Ok(*this);if (_Ok) { // state okay, use facet to insertconst _Nput& _Nput_fac  = _STD use_facet<_Nput>(this->getloc());ios_base::fmtflags _Bfl = this->flags() & ios_base::basefield;long _Tmp;if (_Bfl == ios_base::oct || _Bfl == ios_base::hex) {_Tmp = static_cast<long>(static_cast<unsigned short>(_Val));} else {_Tmp = static_cast<long>(_Val);}_TRY_IO_BEGINif (_Nput_fac.put(_Iter(_Myios::rdbuf()), *this, _Myios::fill(), _Tmp).failed()) {_State |= ios_base::badbit;}_CATCH_IO_END}_Myios::setstate(_State);return *this;}// NOTE:// If you are not using native wchar_t, the unsigned short inserter// is masked by an explicit specialization that treats an unsigned// short as a wide character.// To read or write unsigned shorts as integers with wchar_t streams,// make wchar_t a native type with the command line option /Zc:wchar_t.basic_ostream& __CLR_OR_THIS_CALL operator<<(unsigned short _Val) { // insert an unsigned shortios_base::iostate _State = ios_base::goodbit;const sentry _Ok(*this);if (_Ok) { // state okay, use facet to insertconst _Nput& _Nput_fac = _STD use_facet<_Nput>(this->getloc());_TRY_IO_BEGINif (_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;}basic_ostream& __CLR_OR_THIS_CALL operator<<(int _Val) { // insert an intios_base::iostate _State = ios_base::goodbit;const sentry _Ok(*this);if (_Ok) { // state okay, use facet to insertconst _Nput& _Nput_fac  = _STD use_facet<_Nput>(this->getloc());ios_base::fmtflags _Bfl = this->flags() & ios_base::basefield;long _Tmp;if (_Bfl == ios_base::oct || _Bfl == ios_base::hex) {_Tmp = static_cast<long>(static_cast<unsigned int>(_Val));} else {_Tmp = static_cast<long>(_Val);}_TRY_IO_BEGINif (_Nput_fac.put(_Iter(_Myios::rdbuf()), *this, _Myios::fill(), _Tmp).failed()) {_State |= ios_base::badbit;}_CATCH_IO_END}_Myios::setstate(_State);return *this;}basic_ostream& __CLR_OR_THIS_CALL operator<<(unsigned int _Val) { // insert an unsigned intios_base::iostate _State = ios_base::goodbit;const sentry _Ok(*this);if (_Ok) { // state okay, use facet to insertconst _Nput& _Nput_fac = _STD use_facet<_Nput>(this->getloc());_TRY_IO_BEGINif (_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;}

basic_ostream& __CLR_OR_THIS_CALL operator<<(unsigned short _Val)
basic_ostream& __CLR_OR_THIS_CALL operator<<(int _Val)

这些我不必详说,当你想起自己曾经不止一次写过如下的代码:

std::cout << "我最帅" << 13 << 14 << std::endl;

你以前可能没仔细想过为啥能输出不同的数据类型,但是看到这里的朋友现在你明白了;所有发散性思维更好的朋友可能也就明白了,为啥使用 std::cout无法打印输出某些自定义的数据类型了。
譬如:

class MyClass {
public:MyClass(const std::string& name, int value) : name_(name), value_(value) {}
private:std::string name_;int value_;
};

如何直接打印呢? MyClass myObject("Example", 42); std::cout << myObject << std::endl;

其实在我们了解了cout的本质之后,解决方法顿上心头。So easy!

#include <iostream>
#include <string>// Define MyClass
class MyClass {
public:MyClass(const std::string& name, int value) : name_(name), value_(value) {}// Friend function to overload the << operatorfriend std::ostream& operator<<(std::ostream& os, const MyClass& obj);private:std::string name_;int value_;
};// Overload the << operator for MyClass
std::ostream& operator<<(std::ostream& os, const MyClass& obj) {os << "MyClass(Name: " << obj.name_ << ", Value: " << obj.value_ << ")";return os;
}int main() {MyClass myObject("Example", 42);std::cout << myObject << std::endl; // This will use the overloaded operator<<return 0;
}

这个实现只能说么,太普通,大家都这么用,显示不出你的特别。这里提供另一种思路:

#include <iostream>
#include <streambuf>
#include <string>// Define MyClass
class MyClass {
public:MyClass(const std::string& name, int value) : name_(name), value_(value) {}const std::string& getName() const { return name_; }int getValue() const { return value_; }private:std::string name_;int value_;
};// Custom stream buffer that writes to std::cout
class MyStreamBuf : public std::streambuf {
protected:virtual int overflow(int c) override {if (c != EOF) {std::cout.put(static_cast<char>(c));}return c;}
};// Custom ostream class
class MyOStream : public std::ostream {
public:MyOStream() : std::ostream(&myBuf) {}// Overload << operator for MyClassMyOStream& operator<<(const MyClass& obj) {*this << "MyClass(Name: " << obj.getName() << ", Value: " << obj.getValue() << ")";return *this;}private:MyStreamBuf myBuf;
};int main() {MyClass myObject("Example", 42);MyOStream myCout;myCout << myObject << std::endl; // Use custom cout to print MyClassreturn 0;
}

三、为啥能连续使用 <<

一句话解释,<< 函数 return *this; 返回cout对象的引用,所有你可以拆开了来理解,

std::cout << A << B << std::endl;

即:

std::cout& out_a = (std::cout << A);
std::cout& out_b = out_a << B;

所以,丝毫没有任何毛病。

四、我们讲讲 call basic_ostream manipulator 流控

以下,是我们之所以能操作流控的基础。没有下面的重载,显然是不支持 std::cout << std::endl;语法的。

basic_ostream& __CLR_OR_THIS_CALL operator<<(basic_ostream&(__cdecl* _Pfn)(basic_ostream&) ) { // call basic_ostream manipulatorreturn _Pfn(*this);}basic_ostream& __CLR_OR_THIS_CALL operator<<(_Myios&(__cdecl* _Pfn)(_Myios&) ) { // call basic_ios manipulator_Pfn(*this);return *this;}basic_ostream& __CLR_OR_THIS_CALL operator<<(ios_base&(__cdecl* _Pfn)(ios_base&) ) { // call ios_base manipulator_Pfn(*this);return *this;}

特别说下一下两个点:

  1. basic_ostream&(__cdecl* _Pfn)(basic_ostream&):
    • 这是一个函数指针参数,指向一个接受 basic_ostream& 参数并返回 basic_ostream& 的函数。__cdecl 是一个调用约定,指定函数如何接收参数和返回值。
    • _Pfn 是函数指针的名称。

2 { return _Pfn(*this); }:

  • 这是函数的实现部分。它调用传入的函数指针 _Pfn,并将当前对象(*this)作为参数传递给该函数。
  • 返回值是 _Pfn 函数的返回值,即一个 basic_ostream& 引用。

作用是允许 basic_ostream 对象使用流操作符 << 来调用特定的流操纵器(manipulator)。流操纵器是一些函数,它们可以改变流的状态或格式化输出,例如 std::endlstd::flush 等。

例如,当你写 std::cout << std::endl; 时,std::endl 是一个流操纵器,它被传递给这个重载的 operator<<,从而调用 std::endl 函数来在流中插入换行符并刷新缓冲区。

流控基础知识和使用示例

这里,给出一些流控操作的相关基础知识:

基本概念

  1. std::ostream:

    • std::coutstd::ostream 类的一个实例。std::ostream 是一个输出流类,提供了多种方法用于将数据写入流中。
  2. 流缓冲区:

    • std::cout 使用一个流缓冲区(std::streambuf)来暂存输出数据。数据首先被写入缓冲区,然后在适当的时候(如缓冲区满或显式刷新时)被写入到实际的输出设备(如控制台)。
  3. 流操纵器:

    • 流操纵器是一些特殊的函数或对象,用于改变流的状态或格式化输出。例如,std::endl 用于插入换行符并刷新缓冲区,std::setw 用于设置字段宽度。

常用流操纵器

  • std::endl: 插入一个换行符并刷新输出缓冲区。
  • std::flush: 刷新输出缓冲区,但不插入换行符。
  • std::setw: 设置输出字段的宽度。
  • std::setprecision: 设置浮点数的输出精度。
  • std::fixedstd::scientific: 设置浮点数的输出格式为定点或科学计数法。

流状态

  • std::ios::good(): 检查流是否处于良好状态。
  • std::ios::eof(): 检查流是否到达文件末尾。
  • std::ios::fail(): 检查流是否发生格式错误。
  • std::ios::bad(): 检查流是否发生不可恢复的错误。

缓冲区刷新

  • 自动刷新: std::cout 在输出换行符(\n)时通常会自动刷新缓冲区。
  • 显式刷新: 使用 std::flushstd::endl 可以显式刷新缓冲区。

同步与线程安全

  • 同步与 C 标准 I/O: 默认情况下,std::cout 与 C 标准 I/O(如 printf)是同步的,以确保输出顺序一致。可以通过 std::ios::sync_with_stdio(false) 来禁用这种同步以提高性能。
  • 线程安全: C++ 标准库的流对象在多线程环境中是线程安全的,意味着多个线程可以安全地使用 std::cout但输出顺序可能会混乱。

使用示例

#include <iostream>
#include <iomanip>int main() {std::cout << "Hello, World!" << std::endl; // 使用 std::endl 插入换行并刷新缓冲区double pi = 3.141592653589793;std::cout << "Pi: " << std::setprecision(4) << pi << std::endl; // 设置输出精度std::cout << std::setw(10) << 42 << std::endl; // 设置字段宽度return 0;
}

五、总结

看到这里,想必对于cout多了一些自己的理解,对于cin来说,反之亦然。各位自行探索~


君不见,黄河之水天上来;君不见,地下暗河趣更多…
—— 南山客《新年首札》

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com