重读《C++ Primer Plus》笔记 之 cout与endl

本文深入探讨了C++中的cout、'<<'运算符和endl的用法。cout是一个iostream头文件中定义的ostream类型对象,其地址可能位于堆中。'<<'是运算符重载,既可以用作成员方法,也可以是非成员方法。endl并非特定字符串,而是一个函数,通过'<<'重载实现输出换行,是C++中的manipulators之一。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

cout << "Hello World" << endl; 这几乎要成为所有刚学C++同学的第一行代码;可是回头来看,这一句里的问题可真不少,我先列一下我想到的:

  1. cout是啥,关键字么?不是。那是普通变量?那它在哪定义的?是什么类型?在哪赋值的?
  2. '<<'是啥?哦,可能是运算符重载
  3. endl是啥?是个特定字符串么?

这些问题咱们来一个一个解决。

cout是啥

首先跳到cout的定义,在iostream头文件中:略去不重要的,加上点注释,代码如下(不同环境代码也可能不一样)

_LIBCPP_BEGIN_NAMESPACE_STD    // 在 __config 头文件中可以看到它的定义:#define _LIBCPP_BEGIN_NAMESPACE_STD namespace std {inline namespace _LIBCPP_NAMESPACE {

#ifndef _LIBCPP_HAS_NO_STDIN
extern _LIBCPP_FUNC_VIS istream cin;    // cin 在这里 类型为 istream
extern _LIBCPP_FUNC_VIS wistream wcin;
#endif
#ifndef _LIBCPP_HAS_NO_STDOUT
extern _LIBCPP_FUNC_VIS ostream cout;    // cout 在这里 类型为 ostream
extern _LIBCPP_FUNC_VIS wostream wcout;
#endif
extern _LIBCPP_FUNC_VIS ostream cerr;
extern _LIBCPP_FUNC_VIS wostream wcerr;
extern _LIBCPP_FUNC_VIS ostream clog;
extern _LIBCPP_FUNC_VIS wostream wclog;

_LIBCPP_END_NAMESPACE_STD    // 在 __config 头文件中可以看到它的定义:#define _LIBCPP_END_NAMESPACE_STD  } }

那大概cout应该就是在栈里(具体在内存中的位置还有待考究,目前通过gdb发现cout的地址和栈中普通int变量的地址相差挺远的,不过和一个新new出来的char[]地址很接近,难道是在堆中?它又不是指针,为什么呢?请高人指教!研究变量在内存中位置时发现了这篇文档这个回答,感觉很赞。)的一个ostream类型的对象。(关于extern关键字,可以看这个回答,解释挺清晰的。extern的常用方法,参考这个问答这个问答)关于cout这个对象在哪里被初始化的,据本人无根据的猜测应该是在glibc里边的某处,不过我还没找到对应的源码,有高人了解这里细节的话,请多多指教~(可以顺便去这个问题赚20积分)

'<<'是啥,确实是运算符重载

不过对'<<'的重载方法,有的是作为成员方法,有的并没有放在成员方法中。

作为成员方法的有:

    basic_ostream& operator<<(basic_ostream& (*__pf)(basic_ostream&));    // 这个方法注意一下,它是在 cout << endl; 时使用的
    basic_ostream& operator<<(basic_ios<char_type, traits_type>&
                              (*__pf)(basic_ios<char_type,traits_type>&));
    basic_ostream& operator<<(ios_base& (*__pf)(ios_base&));
    basic_ostream& operator<<(bool __n);
    basic_ostream& operator<<(short __n);
    basic_ostream& operator<<(unsigned short __n);
    basic_ostream& operator<<(int __n);
    basic_ostream& operator<<(unsigned int __n);
    basic_ostream& operator<<(long __n);
    basic_ostream& operator<<(unsigned long __n);
    basic_ostream& operator<<(long long __n);
    basic_ostream& operator<<(unsigned long long __n);
    basic_ostream& operator<<(float __f);
    basic_ostream& operator<<(double __f);
    basic_ostream& operator<<(long double __f);
    basic_ostream& operator<<(const void* __p);
    basic_ostream& operator<<(basic_streambuf<char_type, traits_type>* __sb);

非成员方法的典型(Hello World里最常用的输出字符串那个'<<'的重载就是非成员方法):

template<class _Traits>
basic_ostream<char, _Traits>&
operator<<(basic_ostream<char, _Traits>& __os, const char* __str)
{
    return _VSTD::__put_character_sequence(__os, __str, _Traits::length(__str));
}

关于为什么不把它全部作为成员方法,这个问答这篇文章给了一些解释,《Effective C++》中第23条也有相应的说明(我自己还要再消化一下)。

endl是啥?是个特定字符串么?不,是个函数!

来看一下endl的定义:

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

是不是和想象中相去甚远?第一次看到这里估计都会和我一样一头雾水,一个函数怎么能交给'<<'来输出呢?

这就要看一下'<<'的这一个重载:

template <class _CharT, class _Traits>
inline _LIBCPP_INLINE_VISIBILITY
basic_ostream<_CharT, _Traits>&
basic_ostream<_CharT, _Traits>::operator<<(basic_ostream& (*__pf)(basic_ostream&))
{
    return __pf(*this);
}

这个重载方法接受一个函数指针,然后去执行这个函数(一个看似很简单的cout里居然还有这么骚的操作)。因为有了这个方法,所以像endl这样的方法又有了一个新的名字:manipulators(本人随便翻译下,就叫它操作符吧)。这里列出了所有的操作符。



----------低调的分割线----------


到这里大概就把之前我想到的几个问题回答得差不多了。有问题或者有指教欢迎在评论区讨论~



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值