cout源码浅析

news2024/11/18 3:27:09

目录

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,本人水平有限或许有纰漏,还望指正。

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法DAY52 动态规划10 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组

300.最长递增子序列 五部曲&#xff1a; 1、dp数组的含义&#xff1a; dp[ i ] : 代表 截至到nums[i] (包括 nums[i]) 的序列中&#xff0c;以nums[i] 结尾的&#xff0c;最长递增子序列的长度。这里强调以nums[i] 结尾&#xff0c;是因为还要跟nums[j]做对比&#xff0c;确定…

ACG-crcme1(★★★)

运行程序 info exit 查壳 没壳 载入OD分析 刚载入OD发现要使用 ACG.key 搜一下字符串看看 发现这有貌似成功相关的字符串 进去看看 可以找到关键跳 爆破的话直接在这就可以完成 上面就该是算法了 算法分析 开始先判断文件存在和文件内容大小 读取文件内容&am…

微前端 qiankun@2.10.5 源码分析(二)

微前端 qiankun2.10.5 源码分析&#xff08;二&#xff09; 我们继续上一节的内容。 loadApp 方法 找到 src/loader.ts 文件的第 244 行&#xff1a; export async function loadApp<T extends ObjectType>(app: LoadableApp<T>,configuration: FrameworkConfi…

uniapp - 实现微信小程序电子签名板,横屏手写姓名签名专用写字画板(详细运行示例,一键复制开箱即用)

效果图 实现了在uniapp项目中,微信小程序平台流畅的写字签名板(也可以绘图)功能源码,复制粘贴,改改样式几分钟即可搞定! 支持自动横屏、持预览,真机运行测试非常流畅不卡顿。 基础模板 如下代码所示。 <template><view class=

vue3.2+vite+vant4+sass搭建笔记

1、确定node版本 1、下载nvm安装包 官方下载地址&#xff1a;https://github.com/coreybutler/nvm-windows/releases 双击安装 2、在node官网下载安装多个node 3、切换node 2、创建项目 1、安装依赖 pnpm i 2、启动项目 npm run dev 3、配置指向src import { defineC…

FAST协议解析2 FIX Fast Tutorial翻译【PMap、copy操作符】

FIX Fast Tutorial FIX Fast教程 &#xff08;译注&#xff1a;本篇是对https://jettekfix.com/education/fix-fast-tutorial/翻译和解释&#xff0c;除了文本的直接翻译外&#xff0c;我还针对各点按我的理解进行了说明和验证&#xff0c;所以可以看到译文下会有很多译注&am…

虹科方案 | HK-Edgility:将 SASE 带到边缘

通过上期的文章&#xff0c;我们了解到虹科HK-Edgility软件系统《面向未来的安全SD-WAN》的解决方案。本篇文章&#xff0c;我们将带您了解虹科系统在SASE的方案简介。 一、时代背景 向软件即服务 (SaaS) 和云原生应用程序的过渡&#xff0c;加上越来越多的远程用户生成和访问公…

快来参与:2023全国大数据与计算智能挑战赛正在报名中

全国大数据与计算智能挑战赛是由国防科技大学系统工程学院大数据与决策实验室组织的年度赛事活动&#xff0c;旨在深入挖掘大数据应用实践中亟需破解的能力生成难题、选拔汇聚数据领域优势团队、促进大数据领域的技术创新和面向需求的成果生成、推动形成“集智众筹、联合攻关、…

Spring项目的创建与使用

一、创建Spring项目 这里使用Maven方式创建Spring项目&#xff0c;分为以下三步&#xff1a; 创建一个普通的Maven项目添加spring框架支持添加启动类 注&#xff1a;这里创建的是一个spring的core项目&#xff0c;不是web项目&#xff0c;只需要main方法&#xff0c;不需要t…

Ubuntu显示美化 优化 常用插件

Ubuntu显示美化 优化 常用插件 1. 安装 Extension Manager2. 网速显示&#xff08;不显示总流量记得关掉&#xff09;3. 顶部透明度4. 左侧dock导航透明度5. 过渡动画2022-01-22 毛玻璃效果 和 程序启动背景墙效果2022-01-23 窗口预览&#xff08;类windos多窗口&#xff09;20…

C++11实现线程池

1.所有权的传递 适用移动语义可以将一个unique_lock赋值给另一个unique_lock,适用move实现。 void myThread1() {unique_lock<mutex> myUnique (testMutex1,std::defer_lock);unique_lock<mutex>myUnique1(std::move(myUnique));//myUnique 则实效 myUnique1 相当…

在Linux中进行Jenkins部署(maven-3.9.1+jdk11)

Jenkins部署在公网IP为x.x.x.x的服务器上 maven-3.9.1要安装在jdk11环境中 环境准备 第一步&#xff0c;下载jdk-11.0.19_linux-x64_bin.tar.gz安装包。 登录地址&#xff1a;Java Downloads | Oracle 下载jdk-11.0.19_linux-x64_bin.tar.gz安装包&#xff0c;然后使用Win…

电子温湿度记录仪

电子温湿度记录仪&#xff1a;实时监测环境温度和湿度电子温湿度记录仪是一种用于实时监测环境温度和湿度的设备。它广泛应用于医疗、制药、食品加工、仓储、博物馆、实验室等领域&#xff0c;以确保环境温湿度处于合适的范围内&#xff0c;以保持物品和设备的稳定性和安全性。…

信号的产生——tripuls函数

信号的产生——tripuls函数, 功能&#xff1a;产生非周期三角波信号&#xff0c;其调用格式如下&#xff1a; &#xff08;1&#xff09;ytripuls(t)&#xff0c; &#xff08;2&#xff09;ytripuls(t,w)&#xff0c; &#xff08;3&#xff09;ytripuls(t,w,s)&#xff0…

Java多线程入门到精通学习大全?深入了解线程:生命周期、状态和优先级!(第二篇:线程的基础知识学习)

本文详细介绍了线程的基础知识&#xff0c;包括什么是线程、线程的生命周期、线程的状态和线程优先级等。在了解这些知识后&#xff0c;我们能够更好地掌握线程的使用方式&#xff0c;提高程序的并发性和效率。如果您对线程有更深入的问题&#xff0c;也欢迎向我们提问。 1. 什…

华为MPLS跨域——后门链路实验配置

目录 配置PE与CE设备对接命令&#xff08;通过OSPF对接&#xff09; 配置后门链路 可以使用任意方式来跑跨域MPLS&#xff08;A、B、C1、C2都可以&#xff09;&#xff0c;不过关于传递Vpnv4路由的配置此处不做介绍&#xff1b;此处只介绍关于PE和CE对接的配置和关于后门链路…

node.js的核心模块

node的核心模块由一些精简而高效的库组成 文章目录 全局对象全局对象和全局变量processcosole utilutils.inheritsutils.inspect 事件机制事件发射器error 事件继承EventEmitter 文件系统访问fs.readFile(filename,[encoding],[callback(err,data)])fs.readFileSync(filename,…

NSSCTF (2)

[GKCTF 2020]cve版签到 查看响应头发现有几个可能利用的信息 1.hint 里面的Flag in localhost 2.apache 2.4.38 3. php 7.3.15 我们发现第一个是提示 第二个apache版本没有什么漏洞 但是php 7.3.15 存在cve漏洞 这里题目规定 *.ctfhub.com 所以我们 要以这个结尾 在 p…

05-事务管理

概念&#xff1a; 事务是一组操作的集合&#xff0c;它是不可分割的工作单位&#xff0c;这些操作要么同时成功&#xff0c;要么同时失败 操作&#xff1a; 开启事务&#xff08;一组操作开始前&#xff0c;开启事务) : start transaction / begin ; 提交事务&#xff08;这组操…

【LeetCode】1000题挑战(230/1000)

1000题挑战 没有废话&#xff0c;直接开刷&#xff01; 目录 1000题挑战 没有废话&#xff0c;直接开刷&#xff01; 第一题&#xff1a;242. 有效的字母异位词 - 力扣&#xff08;Leetcode&#xff09; 题目接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; …