cout源码浅析

目录

cout源码浅析

那么对于没有定义在这之中的要怎么办呢?

实际使用

结语


首先来看我从cplusplus中截取的这张图:

注意最下面这一行字。cout其实是ostream的一个标准对象object。而上面则演示了一些继承关系。

好的,理解了之后,接下来就去观察一下源码实现吧!

cout源码浅析

测试环境:VS2019

对于初学C++的时候,我们常常直接这样去用:

#include <iostream>
using namespace std;
int main()
{
    cout << "Hello World" << endl;
}

 

那么理解了cout是一个object之后,我们就去查看一下它具体的实现:

在iostream头文件中,可以看到如下语句:

而该语句是包含在namespace std中的。

从这一句也可以看到cout的类型为ostream。

进一步查看前面的_CRTDATA2_IMPORT,可以看到宏定义:

#define _CRTDATA2_IMPORT _CRTIMP2_IMPORT

接着看:

#define _CRTIMP2_IMPORT __declspec(dllimport)

这里的__declspec是MSVC编译器的关键字, __declspec(dllimport)就表示这个东西是从别的DLL导入的。

这里多提一句extern(参考C++primer第5版41页):

为了支持分离式编译(separate compilation)机制,C++将声明和定义区分开来。

变量声明规定了变量的类型和名字,在这一点上定义与之相同。但是除此之外,定义还申请存储空间,也可能会为变量赋一个初始值。

如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显式地初始化变量:

extern int i; // 声明i而非定义i
int j;        // 声明并定义j

任何包含了显示初始化的声明即可成为定义。我们能给extern关键字标记的变量赋一个初始值,但是这么做也就抵消了extern的作用。extern语句如果包含初始值就不再是声明,而变成定义了:

extern double pi = 3.1416; // 定义

在函数体内部,如果试图初始化一个由extern关键字标记的变量,将引发错误。

变量能且只能被定义一次,但是可以被多次声明。

再来看cplusplus给出的cout注解:

着重注意这两句:

The object is declared in header<iostream>with external linkage and static duration: it lasts the entire duration of the program.

In terms ofstatic initialization order, cout is guaranteed to be properly constructed and initialized no later than the first time an object of typeios_base::Init is constructed, with the inclusion of<iostream>counting as at least one initialization of such objects with static duration.

也就是说,其初始化早在第一次构造 ios_base::Init 类型对象初始化的时候就完成了。而其声明在iostream中有,生命周期持续到整个程序结束。

好,那让我们继续往下分析ostream。可以在iosfwd中看到如下语句:

using语义在STL中应用非常广泛。事实上早期这些都是用typedef,C++2.0之后似乎鼓励用using。

可以发现,ostream类型实际上是basic_ostream,两个模板参数:一个char,一个char_traits.

traits,萃取机,其实是一种手法,充当中间层。把一些东西丢入萃取机,然后通过一些统一的接口,去得到相应的回答。

traits各式各样,有type traits、iterator traits、char traits、allocator traits、pointer traits、array traits

关于萃取机,推荐侯捷老师的《STL源码剖析》或侯老师的课程,讲的很好。

这里可以想象,char_trairts,反应的就是character的一些特性,可以通过这个萃取机去进行询问。比如是不是宽字符呐之类的。

接着往下,思考如何通过<<去把东西输出到屏幕上呢?其实就是通过操作符重载去根据逐个的类型实现,比如说针对int型的,代码如下:

    basic_ostream& __CLR_OR_THIS_CALL operator<<(int _Val) { // insert an 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());
            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_BEGIN
            if (_Nput_fac.put(_Iter(_Myios::rdbuf()), *this, _Myios::fill(), _Tmp).failed()) {
                _State |= ios_base::badbit;
            }
            _CATCH_IO_END
        }

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

至此,我们已经明白了cout的大致原理:

cout是一个ostream类型的对象,ostream其实是模板类basic_ostream,其内重载了针对各种各样的type的operator<<,然后通过返回自身,使得可以连续cout,例如cout << 1 << 2,cout << 1的返回类型仍然是*this,也就是cout,接下来便又会cout << 2

定义在basic_ostream中针对的类型有int、unsigned int等等等等.

那么对于没有定义在这之中的要怎么办呢?

回顾以前我们使用string的时候,不也能直接cout一个string类型吗?

观察string源码:

同样可以看到,一个string其实就是模板类basic_string,有对应的类型、萃取机、分配器。

而重载operator<<是在类外进行的操作:

到这里我们发现了差异:

在ostream中我们在类内重载了operator<<,其中参数为所需要针对的type,比如int。

而在string的实现中(想要cout一个string类型对象),我们在类外重载了operator<<,其带有两个参数:

basic_ostream<_Elem, _Traits>& _Ostr

与 const basic_string<_Elem, _Traits, _Alloc>& _Str

如果仅考虑cout而言,可以想象,前者为cout,后者为string本身。那么为何造成这样的差异呢?

参考C++primer第5版494页,重载输出运算符<<:
通常情况下,输出运算符的第一个形参是一个非常量ostream对象的引用。之所以ostream是非常量的是因为向流写入内容会改变其状态;而该形参是引用是因为我们无法直接复制一个ostream对象。

第二个形参一般来说是一个常量的引用,该常量是我们想要打印的类类型。第二个形参是引用的原因是我们希望避免复制形参;而之所以该形参可以是常量是因为(通常情况下)打印对象不会改变对象的内容。

为了与其他输出运算符保持一致,operator<<一般要返回它的ostream形参。

因此我们可以想见,在ostream类内重载了operator<<时,成员函数默认第一个形参是this指针,因此此时我们只需要带一个int(或是别的type)型的参数即可。而在类外则必须把第一个参数给显示地指明出来。

实际使用

比如这里我自定义类,类内成员三个int的abc,输出这个类的时候想一并输出a b c:

#include <iostream>
using namespace std;

class MyData
{
public:
	int a, b, c;
public:
	MyData(int a, int b, int c) : a(a), b(b), c(c) {};
};

ostream& operator << (ostream& os, const MyData& my_data)
{
	os << my_data.a << " " << my_data.b << " " << my_data.c;
	return os;
}

int main()
{
	MyData Hbh(1, 2, 3);
	cout << Hbh << endl;
	return 0;
}

这里类外重载operator<<时我们严格遵守C++primer所述规则:两个参数都传引用,第二个形参为常量const,最后返回ostream形参。

但是这样在类外重载有一个弊端,就是为了类外去访问,我把类MyData的类内变量都定义为public的了。

但是在类内定义的时候,由于this指针并非ostream而是这个类本身,那么就会造成第一个参数非ostream对象,怎么办呢?办法如下:

#include <iostream>
using namespace std;

class MyData
{
	int a, b, c;
public:
	MyData(int a, int b, int c) : a(a), b(b), c(c) {};
	friend ostream& operator << (ostream& os, const MyData& my_data)
	{
		os << my_data.a << " " << my_data.b << " " << my_data.c;
		return os;
	}
};

int main()
{
	MyData Hbh(1, 2, 3);
	cout << Hbh << endl;
	return 0;
}

即写成友元的形式。这样既不会传入默认的this指针,又可以访问类内的成员。

结语

只是兴起分析了一下cout,本人水平有限或许有纰漏,还望指正。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/18339.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

造轮子系列】面试官问:你能手写Vuex吗(一)?

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 Vuex 是 Vue.js 的状态管理模式&#xff0c;它主要解决了组件之间共享状态时的问题。在本文…

【markdown工具配合图床】PicGo图床配置教程,一秒读懂配置

前言 看到这篇文章的大佬&#xff0c;我默认大家都会配置git&#xff0c;已经配置好ssh公钥。 此时你看到的这篇文章就是基于markdown工具&#xff08;VSCode&#xff0c;Typora&#xff09;编写的。 PicGo作为图床转换工具&#xff0c;并配合gitee作为图片服务器&#xff0…

搭建Serv-U FTP服务器共享文件并外网远程访问「无公网IP」

文章目录 1. 前言2. 本地FTP搭建2.1 Serv-U下载和安装2.2 Serv-U共享网页测试2.3 Cpolar下载和安装 3. 本地FTP发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 转载自内网穿透工具的文章&#xff1a;使用Serv-U搭建FTP服务器并公网访问【内网穿透】 1. 前言…

【Vue面试题】Vue2.x生命周期?

文章目录 1.有哪些生命周期&#xff08;系统自带&#xff09;?beforeCreate( 创建前 )created ( 创建后&#xff09;beforeMount (挂载前)mount (挂载后)beforeUpdate (更新前)updated (更新后)beforeDestroy&#xff08;销毁前&#xff09;destroy&#xff08;销毁后&#xf…

vue3:自定义指令

一、理解vue指令 1.1、指令 在 vue 中提供了一些对于页面和数据更为方便的输出&#xff0c;这些操作就叫做指令&#xff0c;以 v-xxx 表示&#xff0c;比如 html 页面中的属性 <div v-xxx ></div>。自定义指令很大程度提高了开发效率&#xff0c;提高了工程化水平…

git简介和使用、基础命令

文章目录 一、git的安装与配置二、Git工作区原理三、Git的使用和仓库的创建四、Git的常用操作五、配置公钥六、IDEA中配置Git 一、git的安装与配置 https://tortoisegit.org/ 下载对应版本安装即可 注意&#xff1a;配置中输入邮箱和密码一定要和自己的git账户一致 git的配置…

面试华为测试岗,收到offer后我却毫不犹豫拒绝了....

我大学学的是计算机专业&#xff0c;毕业的时候&#xff0c;对于找工作比较迷茫&#xff0c;也不知道当时怎么想的&#xff0c;一头就扎进了一家外包公司&#xff0c;一干就是2年。我想说的是&#xff0c;但凡有点机会&#xff0c;千万别去外包&#xff01; 在深思熟虑过后&am…

【模拟IC学习笔记】 反馈

反馈的作用&#xff1a;增益灵敏度降低 采用开环的方式实现一个精确的增益比较困难&#xff0c;但是可以实现高增益。 增益灵敏度衍生出来的另外两个特点 1、增加系统带宽。 2、改变输出阻抗&#xff0c;提高驱动能力。 反馈的作用&#xff1a;增加带宽 带宽的增加来源于…

ChatGPT实现markdown 格式与 emoji 表情

markdown 格式与 emoji 表情 书写文章时&#xff0c;巧妙的使用一些小图标&#xff0c;可以给文章增加不少的灵动感&#xff0c;读者也会感觉更加轻松。恰当的图标也能增进读者对内容的理解。ChatGPT 目前不能直接联网&#xff0c;但可以使用 emoji 表情文字来达到类似的效果。…

C++之数据对齐

目录 关于对齐数据对齐结构体对齐原则原理分析 关于对齐 对齐方式&#xff1a;表示的是一个类型的对象存放的内存地址应满足的条件好处&#xff1a;对齐的数据在读写上有性能优势对于不对齐的结构体&#xff0c;编译器会自动补齐以提高CPU的寻址效率 数据对齐 四个函数/描述…

机器学习实战:带你进入AI世界!

机器学习是人工智能领域的一个重要分支&#xff0c;可以帮助我们从大量数据中发现规律&#xff0c;进行预测和分类等任务。然而&#xff0c;想要真正掌握机器学习算法&#xff0c;并将其应用到实际问题中&#xff0c;还需要进行大量的实战练习。 本文将介绍几个常见的机器学习实…

6、Flutterr聊天界面网络请求

一、准备网络数据 1.1 数据准备工作 来到网络数据制造的网址,注册登录后,新建仓库,名为WeChat_flutter;点击进入该仓库,删掉左侧的示例接口,新建接口. 3. 接着点击右上角‘编辑’按钮,新建响应内容,类型为Array,一次生成50条 4. 点击chat_list左侧添加按钮,新建chat_list中的…

PAT A1032 Sharing

1032 Sharing 分数 25 作者 CHEN, Yue 单位 浙江大学 To store English words, one method is to use linked lists and store a word letter by letter. To save some space, we may let the words share the same sublist if they share the same suffix. For example, l…

如何利用ChatGPT进行论文润色-ChatGPT润色文章怎么样

ChatGPT润色文章怎么样&#xff1f; ChatGPT可以润色文章&#xff0c;使用其润色功能可以为用户提供更加整洁、清晰、文采动人的文本。但需要注意以下几点&#xff1a; 需要保持文本的一致性和完整性。当使用ChatGPT进行润色时&#xff0c;需要注意保持文本的一致性和完整性。…

和月薪5W的聊过后,才发现自己一直在打杂···

前几天和一个朋友聊面试&#xff0c;他说上个月同时拿到了腾讯和阿里的offer&#xff0c;最后选择了阿里。 我了解了下他的面试过程&#xff0c;就一点&#xff0c;不管是阿里还是腾讯的面试&#xff0c;这个级别的程序员&#xff0c;都会考察项目管理能力&#xff0c;并且权重…

Java程序设计入门教程---循环结构(while)

目录 思考 概念 语法 案例&#xff1a;求1到100的整数和&#xff1f; 案例分析 思考 1. 让你输出10000000000000000句“Hello,world!”&#xff0c;你怎么写代码&#xff1f; 2. 求1到100的整数和&#xff1f; 概念 循环结构程序多次循环执行相同或相近的任务。 while循环…

Windows在外远程桌面控制macOS 【macOS自带VNC远程】

文章目录 前言1.测试局域网内远程控制1.1 macOS打开屏幕共享1.2 测试局域网内VNC远程控制 2. 测试公网远程控制2.1 macOS安装配置cpolar内网穿透2.2 创建tcp隧道&#xff0c;指向5900端口 3. 测试公网远程控制4. 配置公网固定TCP地址4.1 保留固定TCP地址4.2 配置固定TCP端口地址…

什么?Python一行命令快速搭建HTTP服务器并公网访问?

文章目录 1.前言2.本地http服务器搭建2.1.Python的安装和设置2.2.Python服务器设置和测试 3.cpolar的安装和注册3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 转载自远程内网穿透的文章&#xff1a;【Python】快速简单搭建HTTP服务器并公网访问「cpolar内网穿透…

springboot第19集:权限

article 文章表sys_permission 后台权限表sys_role 后台角色表sys_role_permission 角色-权限关联表sys_user 用户表sys_user_role 用户-角色关联表 image.png image.png sys_user_role id user_id(用户id) role_id(角色id) sys_role id role_name(角色名) create_time(创建时间…

基于 EKS Fargate 搭建微服务性能分析系统

背景 近期 Amazon Fargate 在中国区正式落地&#xff0c;因 Fargate 使用 Serverless 架构&#xff0c;更加适合对性能要求不敏感的服务使用&#xff0c;Pyroscope 是一款基于 Golang 开发的应用程序性能分析工具&#xff0c;Pyroscope 的服务端为无状态服务且性能要求不敏感&…