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 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;}
至此,我们已经明白了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,本人水平有限或许有纰漏,还望指正。
相关文章:
cout源码浅析
目录 cout源码浅析 那么对于没有定义在这之中的要怎么办呢? 实际使用 结语 首先来看我从cplusplus中截取的这张图: 注意最下面这一行字。cout其实是ostream的一个标准对象object。而上面则演示了一些继承关系。 好的,理解了之后…...
发送Ajax get请求详解
发送AJAX get请求,前端代码: <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title>ajax get请求</title> </head> <body> <script type"text/java…...
SQL语句
创建及删除数据库和表 CREATE DATABASE 数据库名; CREATE DATABASE school; 创建新的表 CREATE TABLE 表名(字段1 数据类型,字段2 数据类型[,...] [,PRIMARY KEY (主键名)]); #主键一般选择能代表唯一性的字段,不允许取空值(NULL),值也不允许重复&…...
Mysql 学习(八)单表查询方法二
复杂查询 上一节说了5种访问类型的查询,这一节就来说说关于这些比较复杂的查询 情况一:多个二级索引查询 sql:SELECT * FROM index_value_table WHERE value1 abc AND value2 > 1000;搜索条件: value1 等于 abcvalue2 大于…...
安卓系统下的截屏和录屏
可以抓取手机屏幕画面(屏幕截图),也可以录制屏幕画面视频。拍摄屏幕后,可以查看、编辑和分享所拍的图片或视频。 抓取屏幕截图 打开要抓取的屏幕。视手机情况执行下列一个操作,3种方法看你手机有效的: 同…...
行为型模式-中介者模式
中介者模式 概述 一般来说,同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,即不利于类的复用,也不稳定。例如在下左图中…...
辅助驾驶功能开发-功能规范篇(16)-2-领航辅助系统NAP-功能ODD定义
1.系统定义 智能驾驶系统包含行车场景功能和泊车场景功能,行车场景功能包括安全ADAS功能、基础ADAS功能和高阶ADAS功能三大类,本文档定义高阶ADAS功能中的导航辅助驾驶功能用例。 1.1.高阶ADAS功能列表 功能需求ID 功能分类 功能名称...
PMP/高项 06-项目成本管理
项目成本管理 概念 项目成本管理 项目成本管理又被称为项目造价管理,是有关项目成本和项目价值两个方面的管理,是为保障以最小的成本实现最大的项目价值而开展的项目专项管理工作。 确保在批准的项目预算内完成项目 成本管理内容 规划成本管理 制定项目…...
XXL-JOB中间件【实现分布式任务调度】
目录 1:XXL-JOB介绍 2:搭建XXL-JOB 2.1:调度中心 2.2:执行器 2.3:执行任务 3:分片广播 1:XXL-JOB介绍 XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学…...
Vue3+Element Plus环境搭建和一键切换明暗主题的配置
Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。而Element Plus是一款基于Vue3面向设计师和开发者的组件库。 最终效果: 环境搭建 已安装 16.0 或更高版本的 Node.js,终端: npm init vuelatest这一…...
Leetcode326. 3 的幂
Every day a leetcode 题目来源:326. 3 的幂 相似题目:342. 4的幂 解法1:递归 代码: /** lc appleetcode.cn id326 langcpp** [326] 3 的幂*/// lc codestart class Solution { public:bool isPowerOfThree(int n){if (n <…...
【运动规划算法项目实战】如何在栅格地图中实现Dijkstra算法
文章目录 简介一、算法介绍1.1 Dijkstra算法流程1.2 Dijkstra算法伪代码二、代码实现2.1 ROS实现2.2 RVIZ演示三、总结简介 Dijkstra算法是一种用于图中单源最短路径的贪心算法。在计算机科学和网络设计中广泛应用。该算法从起点开始,通过优先选择距离起点最近的未标记节点来…...
【算法】一文彻底搞懂ZAB算法
文章目录 什么是ZAB 算法?深入ZAB算法1. 消息广播两阶段提交ZAB消息广播过程 2. 崩溃恢复选举参数选举流程 ZAB算法需要解决的两大问题1. 已经被处理的消息不能丢2. 被丢弃的消息不能再次出现 最近需要设计一个分布式系统,需要一个中间件来存储共享的信息…...
【软考高级】2022年系统分析师综合知识
1.( )是从系统的应用领域而不是从系统用户的特定需要中得出的,它们可以是新的功能性需求,或者是对已有功能性需求的约束,或者是陈述特定的计算必须遵守的要求。 A.功能性需求 B. 用户需求 C.产品需求 D.领域需求 2.对于安全关键系…...
关于AI未来的思考和应用场景
关于AI未来的思考和应用场景 AI(人工智能)是当今最热门的技术领域之一,它已经在多个领域产生了深远的影响,如医疗、金融、制造业等。未来,AI将继续发展,并在更多领域产生重要的影响。 AI的未来发展方向有…...
智慧城市规划数字化管理:数字孪生技术的创新应用
随着智能城市的不断发展,数字孪生技术也开始在智慧城市的建设中得到了广泛应用。数字孪生作为一种数字化的复制技术,它可以模拟真实世界中的实体和过程。 在城市规划方面,数字孪生可以帮助城市规划师更加直观地了解城市的整体规划和发展趋势&…...
开心档之C++ 指针
C 指针 学习 C 的指针既简单又有趣。通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。 正如您所知…...
零基础搭建私人影音媒体平台【远程访问Jellyfin播放器】
文章目录 1. 前言2. Jellyfin服务网站搭建2.1. Jellyfin下载和安装2.2. Jellyfin网页测试 3.本地网页发布3.1 cpolar的安装和注册3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5. 结语 转载自内网穿透工具的文章:零基础搭建私人影音媒体平台【远程访问Jelly…...
Abstract Expressionist
古董地图画集 10大最有名的抽象艺术家 抽象表现主义是现代许多不同艺术思想和表达流派中最奇特的艺术运动之一。这场运动开始从社会变革中涌现出来,恰逢第二次世界大战的最后几周和几个月。 这一次,来自世界各地的人们开始欢迎在经历了多年有史以来最致…...
【郭东白架构课 模块二:创造价值】24|节点四:如何减少语义上的分歧?
你好,我是郭东白。上节课我们通过一个篇幅比较长的电商案例,详细展示了为什么在架构活动中会出现语义分歧。同时也描述了,架构师在统一语义这个环节中所要创造的真正价值是什么。即,看到不同角色之间语境的差异,然后通…...
C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...
