首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
260 阅读
2
nvim番外之将配置的插件管理器更新为lazy
140 阅读
3
2018总结与2019规划
137 阅读
4
从零开始配置 vim(15)——状态栏配置
134 阅读
5
PDF标准详解(五)——图形状态
110 阅读
软件与环境配置
读书笔记
编程
Thinking
FIRE
菜谱
翻译
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
读书笔记
emacs
VimScript
linux
elisp
文本编辑器
Java
投资理财
反汇编
OLEDB
数据库编程
Masimaro
累计撰写
378
篇文章
累计收到
32
条评论
首页
栏目
软件与环境配置
读书笔记
编程
Thinking
FIRE
菜谱
翻译
页面
归档
友情链接
关于
搜索到
378
篇与
的结果
2026-06-21
PDF标准详解(六)——线与线型
首先距离上一篇PDF标准详解已经过去了一年多的时间了,之前断更主要是因为我当初迷上了配置Emacs,想学习Emacs的相关内容。最近Emacs的内容更新完了,现在我想有始有终,继续更新关于pdf的文章。很遗憾我现在已经不干与pdf相关的工作,我被分配到去搞流式文档的排版去了。已经很长时间没有研究pdf标准相关的内容了,关于pdf的一些详细细节我可能也不太了解,只能写一些我当初研究阅读过的一个大概。前言言归正传,我们继续pdf标准之旅;在前面的系列中,我们大概了解pdf由页面组成,而页面由各种绘图状态来控制。我们可以使用绘图状态在各个坐标位置绘制各种图形并进入图形控制状态,图形绘制完成之后通过一些操作符回到页面状态。pdf的渲染是一个不断进入各种状态,并且回退到页面状态。很像一个状态机或者更通俗的说像一个函数调用,在函数内部进行各种操作,函数退出之后堆栈恢复到主函数的状态。我认为理解上一篇中给出的图形十分重要,它展示了pdf的分析框架。后面我们的内容就是针对整个框架完善其中的每一个边边角角。本节我们进入PathObj 路径对象的一个子部分,关于直线线条路径的描述我觉PathObj 是很形象的一个描述,我们想象一下在野外爬山或者旅游,我们会形成一个行走的路线,每个街角怎么走,怎么停。最终我们有可能会在一些地图APP上形成一个路径图。PathObj也是类似的,我们规定了从哪个坐标开始,到哪个坐标走直线,到哪个坐标走曲线。最终pdf解析器和渲染程序会根据我们指定的路径绘制一个图形。直线在上一章,我们说了如何画一条直线,总体上就是先定义一个起点,然后通过 m(moveTo) 将画笔移动到这个起点,然后给出下一个点的坐标,并且跟上 l (LineTo),最后用 s,显示出来,例如下面的一个例子3 0 obj % 页面内容流 << >> stream % 流的开始 400 400 m 100 100 l 800 400 l s endstream % 流结束 endobj本篇我们将以这个例子为基础,介绍关于线条的基本属性线宽线宽的操作为m,它的定义是“从路径的垂直距离,在用户空间,是小于或等于一半行宽的所有点”。这句话听起来很拗口,但是实际上你可以想象这样一个场景,我们在使用钢笔或者圆珠笔写字画画时,假设笔尖出墨是往两边均匀的出墨,那么笔在纸上划过时笔尖到笔迹某一个点的垂直距离就是线宽我们可以创造这么一个pdf来说明这个问题4 0 obj << >> stream % 流的开始 0.5 0.5 0.5 RG 20 w 100 100 m 200 200 l 400 100 l S 1 w 0 1 0 RG 100 100 m 200 200 l 400 100 l S endstream % 流结束 endobj 它的效果如下:我们可以将绿色部分当作笔真实走过的轨迹,而灰色部分则是笔的墨水往两边扩散的结果。这里定义的20线宽是线的边缘到绿色部分的垂直距离注意这里线宽定义的是实际线条宽度的一半。它的单位是一个非负的数字,如果我们采用设置线宽为0,那么此时的真实宽度是一个像素点。线帽它的英文是 line cap,它用来控制一条线在起点和终点(也就是没有和其他线条相交的开放端点)的边缘形状。它使用J来设置,它主要有三种样式0 J : 平头线帽,线条在端点处直接平切,不多出任何部分1 J : 圆头线帽,以线宽为半径,以起点或者终点为圆心画一个半圆2 J : 方头线帽,在起点或者终点位置多延伸出线宽一半的长度形成一个正方形的结尾。下面是我将上述的pdf线条改成了圆头线帽的样式,具体的其他类型读者可以自行修改观察4 0 obj << >> stream % 流的开始 0.5 0.5 0.5 RG 1 J 20 w 100 100 m 200 200 l 400 100 l S 0 J 1 w 0 1 0 RG 100 100 m 200 200 l 400 100 l S endstream % 流结束 endobj它的效果如下:线连接样式Line Join Style,线的连接样式是线相交或者接头处的样式,与上面的线帽类似,它使用j来作为操作符,它也是有三种样式0 j : 尖角连接,两条边的外侧直接延伸并相交,形成一个尖锐的角。这也是 PDF 的默认样式。1 j : 圆角连接,在转角处以一个圆弧进行平滑过渡,看起来比较柔和。2 j : 平角连接,把尖角直接“切掉”,在转角处形成一个平整的切面。下面我们看一个平角连接的例子,其余样式如果读者有兴趣可以自行修改查看4 0 obj << >> stream % 流的开始 0.5 0.5 0.5 RG %设置RGB颜色 1 J 2 j 20 w 100 100 m 200 200 l 400 100 l S 0 J 1 w 0 1 0 RG 100 100 m 200 200 l 400 100 l S endstream % 流结束 endobj它的效果如下:虚线虚线的操作符是 d 。我们可以简单的对上面的例子做一个改造,将实现改为虚线stream % 流的开始 0.5 0.5 0.5 RG 1 J 2 j 20 w [6 4] 0 d 100 100 m 200 200 l 400 100 l S 0 J 1 w [6 4] 0 d 0 1 0 RG 100 100 m 200 200 l 400 100 l S endstream % 流结束它的效果如下:从上面看,pdf的操作逻辑跟我们日常在纸张上写字画画一样,我们先选画笔的颜色、画笔的类型(这里是虚线型的画笔)、最后按照指定的轨迹(path) 来画图。默认的画笔是直线类型的。虚线的语法结构为:[线段长度 空白长度 ...] 相位 d对于上面的例子,我们需要解释两个问题:首先这个语法结构中的线段长度、空白长度以及相位都是什么意思为什么都是例子中都是虚线但是实际显示时为什么只有一条虚线我们先来解释第一个问题:我们假设在一个一维的线段中有一个原始坐标为0,坐标单位为pdf单位的向右无限延长的坐标系(往哪个方向延长实际上取决于我们的path)。每个坐标单位是一段的连续的区域,例如1 表示从原点到1单位的这个区间, 3表示从2到3单位的这个区间再想象一下,有一个存储了哪些区域有空白,哪些区域有线的列表。列表中存在数字的区域就有线。否则就没有线根据这个描述,我们可以推导出 [3, 5] d 最终得到的数字列表为: [0, 1, 2, 8, 9, 10, 16, 17, 18, ...]而相位则表示,从哪个区域块开始,如果是1相位,那么我们的列表就要在上述基础上舍弃一个元素,最终得到[1, 2, 8, 9, 10, 16, 17, 18, ...]如果我们按照[3,5] 这个数组来操作虚线,在不同相位下得到的结果如下:了解了这个问题,我们再来看上述的第二个问题?为什么上面的例子我们只看到了一条虚线。因为上面我们开启了线帽,线帽会往左右两边进行延长,各延长一半的线宽。对于上面的例子,如果线宽是20,那么左右两边各延长10,但是我们给的空白区间只有4,所以延长的线帽部分会覆盖空白区域导致看起来是一条实线。而线宽为1的那条线左右两边只延长0.5,所以仍然有空白区间显示成虚线。各位读者可以尝试去掉线帽,再次查看效果。利用上面的知识,我们再来分析一个相对复杂的虚线:stream % 流的开始 0.5 0.5 0.5 RG 20 w [1 2 5 7 8 1 4 5 12 4 5] 0 d 100 100 m 400 100 l S endstream % 流结束首先我们注意到,它里面的数字个数是奇数,无法凑成两两一对,对于这种情况,pdf会在后面将前面的所有数字再重新循环一遍也就是最终变成了[1 2 5 7 8 1 4 5 12 4 5 1 2 5 7 8 1 4 5 12 4 5]我们可以每两个一组计算哪些区域有内容、哪些区域是空白的,最后将所有结果组合起来。对于第一组: 第一个区域有内容,第二个、第三个是空白对于第二组: 第4个区域到第8个区域有内容,而从第9个到第15个区域是空白再往后就是后面的8个区域有内容、而1个区域是空白后面的就依次类推。最终形成了一个类似条形码的东西。各位读者可以自行查看效果,不过不同的pdf阅读器可能看到的效果不太一样,主要的区别在于阅读器是否会进行上述将数组中的数组重复一遍再解析。总结本文到此就结束了,本次介绍了一些关于线型的定义,包括线帽、线宽、线连接样式和虚线的定义。他们分别使用: J、w、j 、d 来定义。
2026年06月21日
3 阅读
0 评论
0 点赞
2026-06-20
读《just for fun》
最近1周读完了 《just for fun》 这本书。本书是Linux操作系统的作者 linus 的个人自传,书中没有什么技术术语和很深的技术描述,书中的语言轻松幽默,读起来还是很有趣很轻松的。书中的linus 自己是极度真诚的,比如说他提到自己因为鼻子大所以带了眼镜这样能显得鼻子小,这是我完全没有想到的,我一直以为他是沉迷编程把眼睛搞近视了(书中没有提到他是不是真的近视)。书中linus 经常提到他除了编程似乎是一个不记事的傻瓜,很多事情都需要他的妻子帮忙提醒。我觉得这可能是天才的通病,他们总是将个人宝贵的精力留在了最需要的地方。他父母养育他的方式很简单,他的母亲曾经说过,养育他十分简单,只需要把他丢入到一间有计算机的房子然后定时投喂意大利通心粉就好了。看到这里,我在想我现在作为孩子的父亲未来会不会也这么简单的养育我的孩子?放任她做自己的事,只提供基础的物质保证?在她沉浸在自己的世界和事业中时给予支持保持尊重。有在微软工作的朋友告诉他,微软将他的头像作为飞镖的靶子,他幽默的表示“我的大鼻子一定很容易射中”linus 也描述了他实现Linux的一些心路历程:首先他并没有在一开始就打算做一个操作系统,他开始仅仅希望做一个好的终端程序。只是在完善终端程序时顺便做了文件系统、输入输出设备的控制,当他意识到这其实可以是一个全新的操作系统的时候他才开始考虑实现一整套 posix 标准。linux 原生就是模块化的,符合Unix设计哲学的。每个模块只负责最重要最核心的功能,靠各个模块的组合来完成复杂的工作。在成功发布最初版本之后,Linus 也是决定开放源代码,他最初仅仅希望自己的劳动成果不被非法窃取,他只是希望能让世界上的其他黑客看到他的工作成果。并且希望吸引其他工程师来进行系统的完善,只是希望其他人来免费为自己打工。但是因为一些原因,Linux的版权和最终决定还是掌握在他的手里。但是这正好符合开源的基本宗旨:各尽所能,各取所需。书中有一些篇幅来描述Linus自己的人生观、金钱观等等:他个人活的比较洒脱,在成名之后拒绝包括苹果等一众科技公司做操作系统的邀请,仅仅以为他认为从技术层面上,他们的方案与自己的理念不合,是一种垃圾。同时他认为开源的作者不一定需要时时刻刻投入到开源中,书中他的原话是“我只是开放了源代码,又不是开放了自己”。开源软件的作者也好,开源软件的使用者也是,需要明白开源并不代表要满足用户的所有期待,开源并不意味着可以不劳而获。linus 自己并不富裕,但是他对金钱一直持有一种豁达的态度。如果不是red hat 和其他使用Linux 的商业共公司给他的股票期权,可能他还是与我们普通打工仔一样为钱所累。linus 对开源的态度持开放程度,他并不认为开源软件的商业化是邪恶的。他反对更严格的GPL3 协议,认为这是病态的开源宗教,所以Linux系统仍然采用相对宽泛的gpl协议。linus自己认为人生底层的追求是温饱和繁衍、再上一层就是社会稳定的秩序,最上层的就是自我追求和快乐。就像书名 《just for jun》。linus自身就是洒脱、自由、追求快乐的人
2026年06月20日
0 阅读
0 评论
0 点赞
2026-06-20
中国移动(600941)——初步分析
基本信息公司名称: 中国移动有限公司所处行业:通信——电信运营商核心业务:主营移动通信、宽带固网、政企算力、数据中心、数字化平台服务等,是国内算力网络与公共通信基建核心运营商。特点:国内三大运营商绝对龙头,用户基数、营收、利润规模行业第一;属于国家刚需基建行业,具备天然垄断属性,是所有互联网、数字化、AI产业的底层基础设施,经营极稳。商业模式靠什么挣钱:核心收入来自手机流量、语音、家庭宽带等个人基础通信业务;增量来自政企算力、IDC数据中心、数字化解决方案、智慧平台增值业务。护城河是什么:国内仅三张基础电信牌照,无新进入者,行业格局永久稳定;基站、光缆、算力网络重资产投入极高,完全无法复刻;个人上网、企业办公、AI算力、数字经济均依赖通信网络,属于国民刚需基建;用户体量超大,边际成本极低,现金流源源不断。未来5到10年分红会消失吗: 运营商是数字经济基建、卖铲子生意。互联网越卷、AI越发展、数字化越普及,网络与算力需求越刚性,公司收入越稳。5G、算力、6G迭代只会加固壁垒,不会颠覆行业,分红永续性极强。分红历史连续多少年分红不间断: 20+年持续派息近5年分红是否稳定: 近5年分红连续提升最近12个月股息率大概是多少: 5.13%分红率 (分红/净利润): 70% 以上,且逐年提升累计派现大于累计融资: 871.22% 满足累计派现大于融资的条件盈利稳定性5 年净利润: 近5年营收、净利润整体稳步抬升,近两年增速放缓、进入稳利润平台期5-10 年营业额: 最近5年持续提升,但是近2年增速放缓连续3年公司扣非净利润为正、净利润是否为正: 扣非净利润持续为正,盈利底盘扎实,且略有增长特点:行业已完全成熟,个人用户增长见顶,未来无高速增长,但依托庞大存量用户+算力新业务,可长期维持稳定利润底盘,属于稳存量、微增量的优质永续资产现金流经营活动现金流是否稳健: 经营活动的现金流近几年不稳定且有下降,但是仍然远大于净利润近5年营业现金流大于净利润: 经营活动的现金流大于净利润,利润现金含量充足连续3年自由现金流为正:自由现金流持续为正财务安全资产负债率: 稳定在32%、33%左右流动资产与流动负债的比: 0.8 左右,属于安全区间是否有大的风险:无即刻债务暴雷风险,债务处于安全范围内,不会对正常经营造成影响赚钱能力近3年ROE:维持在10%左右估值与安全边际股息率: 位于历史偏高水平PB: 位于中等偏下区间PE: 位于中等偏低区间整体估值:整体估值处于历史合理偏低水平核心风险政策性限价风险:通信、宽带属于民生基建,国家会持续压降资费,压制毛利率上行空间;资本开支压力:5G迭代、算力中心建设持续需要大额投入,长期消耗自由现金流;成长性缺失:用户饱和、传统业务见顶,未来只能赚分红钱,难赚业绩增长、估值爆发钱。结论中国移动作为国内通信行业绝对龙头,拥有国家级牌照垄断与底层网络基建壁垒,属于数字经济、AI产业的刚需底层基础设施,商业模式永续性极强。公司财务极度稳健、低负债、现金流充沛、盈利扎实,上市以来长期连续分红,近五年分红持续提升,股息率5.13%远超个人4%选股标准,累计派现大幅超融资,股东回报质量优秀。行业已进入成熟期,传统用户增长见顶,整体无高速成长空间,但存量盈利底盘稳固,叠加算力新业务稳步增量,未来可长期维持稳定经营与高分红。当前估值处于历史合理偏低区间,安全边际充足。移动没有大的风险,当前股息率大于本人4%的标准可以考虑买入
2026年06月20日
0 阅读
0 评论
0 点赞
2026-06-13
读《从0开始学价值投资》
本书真的就是如名称所说,从0开始了解价值投资的入门书籍。本书从财务指标、量化投资的角度对投资小白描述了价值投资理论中的好公司、好价格。财务指标上,可以分为偿债能力、运营能力、盈利能力、杜邦分析以及如何配置资产和最后买入时要考虑安全边际通过偿债能力考核企业资产负债率(总资产与总负债的比值):这条考察如果企业破产,企业的资产能否覆盖债务优质消费 / 轻资产企业<50%;制造行业<60%;常年高于 70% 直接剔除流动比率(流动资产与流动负债的比值):这条主要考察,公司的短期能快速变现的资产能否覆盖短期债务≥1.5 为健康,<1 存在短期偿债危机速动比例(流动资产 - 存货) / 流动负债:上面的流动比例包含了存货,如果遇到紧急情况需要变卖存货还债,存货是不可能按它自身的价值售卖的,肯定会打骨折甩卖。所以本指标在上面的基础上剔除了存货≥1,为标准现金比例:货币资金 ÷ 流动负债在极端经营情况下下,仅仅靠现金就能覆盖短期欠款标准为: ≥0.25产权比例总负债 ÷ 所有者权益标准 <1通过盈利能力考核企业毛利率:(营业收入 - 营业成本)÷ 营业收入书中标准:持续>30% 代表产品有定价权、品牌 / 技术护城河;逐年持续下滑警惕竞争恶化净利率归母净利润 ÷ 营业收入消费龙头稳定>10%,反映全链条费用管控能力ROA 总资产收益率净利润 ÷ 平均总资产 (平均总资产=期初总资产和期末总资产的平均值)连续多年>8%,衡量全部资产的整体盈利效率,规避靠高杠杆虚增利润的企业ROE 净资产收益率归母净利润 ÷ 平均净资产书中硬性标准:连续 3–5 年每年≥15%,波动幅度越小越好;仅靠加杠杆拉高 ROE 的公司不认定为好公司书中定位:衡量股东自有资本回报,巴菲特价值投资核心标尺扣非净利润、扣非净利率剔除政府补贴、变卖资产、投资收益等一次性收益,只看主营业务真实利润,规避 “纸面盈利”通过营运效率考核企业应收账款周转率营业收入 ÷ 平均应收账款高于行业均值、逐年稳定提升;持续下降代表货卖出去收不回钱,赊销风险上升存货周转率营业成本 ÷ 平均存货稳定走高,存货不积压;周转率骤降 = 产品滞销、存货减值风险总资产周转率营业收入 ÷ 平均总资产越高代表资产利用效率强,同等资产能创造更多营收通过现金流考核企业经营活动的现金流净额:要求经营现金流持续大于净利润,考核企业的利润是通过赊账或者财务造假的来的,利润需要持续反应到现金流中自由现金流自由现金流 = 经营现金流净额 - 维持性资本开支不同的公司维持性资本开支不同,如何确定维持性资本开支主要靠自身对企业经营的了解。简单处理的话可以等于购买固定资产、厂房等的投资现金流持续自由现金流为正,考核企业自身是否有持续造血能力。只有自由现金流为正,企业才能进行股份回购、分红等造福股东的事经营现金流/净利润比值书中阈值:>1.2 为优秀盈利质量,<0.8 为利润注水预警杜邦分析有了上面的筛选标准,我们可以淘汰那些不符合要求的垃圾公司,但是我们仍然无法从剩下的公司中区分平庸的公司和优秀的公司。我们可以针对ROE进行拆解,利用杜邦分析法来区分平庸的公司和优秀的公司ROE = 净利润率 × 资产周转率 × 权益乘数净利润率 = 净利润/营业收入资产周转率 = 营业收入/平均总资产权益乘数 = 平均总资产/平均股东权益;根据资产负债表的恒等式(资产=负债+股东权益),该公式可以变化为: 1 ÷ (1 - 资产负债率)它们分别用来衡量:每一元营收能转化为多少的净利润;每一元的总资产能转化为多少的营收;股东每一元的投入能转换成多少资产。ROE的升高应该靠提升净利润率和资产周转率,需要警惕借钱提高ROE的企业。负债增加会导致权益乘数曾佳佳也会推高ROE。安全边际筛选出来了好公司,应该以好的价格进行购买。我们应该以远低于估值的价格购买公司的股票,买入价格与估值之间的差价就是我们的安全边际。安全边际能保证即使我们分析错误,买入了烂公司也不至于亏损严重。书中给出的指标也是常用的估值指标:PE-TTM 滚动市盈率:对比自身历史分位、行业均值 。格雷厄姆给出的标准是当前市盈率小于25倍或者近5年平均市盈率小于20倍PB 市净率:重资产、金融行业重点参考。PEG 市盈率相对盈利增长比率:成长股估值标尺,PEG<1 具备安全边际总结我个人认为,本书从详细程度上来讲不如《聪明的投资者》,本书中的所有知识几乎全部来自《聪明的投资者》,本书算是它的基础入门版本或者说是作者对该书的阅读笔记。真正要了解价值投资、学习价值投资,《聪明的投资者》是我们绕不过去的坎。但是《聪明的投资者》这本书本身也并不难读
2026年06月13日
2 阅读
0 评论
0 点赞
2026-06-13
伊利股份(600887)初步分析
基本信息公司名称: 内蒙古伊利实业集团股份有限公司所处行业:食品加工 — 乳品行业核心业务:主营液态奶、酸奶、婴幼儿配方奶粉、奶酪、冷饮等各类乳制品,上游布局牧场养殖、饲料配套,下游覆盖全渠道经销与终端零售。特点:亚洲规模第一、全球前五乳制品龙头;上游自有牧场保障奶源,全国线下商超、便利店、母婴渠道全覆盖,品牌国民度极高,具备完整乳类全产业链布局。商业模式靠什么挣钱:核心收入来自液态奶(基础盘)、奶粉、奶酪、冷饮等多品类乳制品销售,上游养殖配套为辅,液态奶贡献主要营收,奶粉、奶酪提供增量利润。护城河是什么: 全产业链一体化,自主奶源平抑生鲜乳原料价格波动;全国极致下沉线下渠道,终端覆盖广度行业第一;国民级品牌认知,客户复购稳定;规模效应压低生产、物流单位成本,中小品牌难以竞争。未来5到10年分红会消失吗:不会,需求方面,乳制品消费目前属于刚需。而且当前我国的居民人均奶制品消耗远低于亚洲和全球的平均水平,未来有进一步提升的可能分红历史连续多少年分红不间断: 16年分红不间断近5年分红是否稳定:近5年分红逐渐提升最近12个月股息率大概是多少:5.37%分红率 (分红/净利润):近5年一直维持在70%以上,偏高区间,需持续跟踪是否挤压企业留存发展资金累计派现大于累计融资: 融资派现比为313.97%,满足累计派现大于融资的条件盈利稳定性5 年净利润: 净利润近几年呈增长趋势,但是24年下滑,25年又出新高5-10 年营业额: 营收近2年由高点回落、缓慢下行连续3年公司扣非净利润为正、净利润是否为正:扣非净利润持续为正,盈利底盘扎实,但24年出现大幅度下滑。未来仍然具有一定的成长性特点: 乳品行业已进入成熟阶段,行业总量增速放缓;伊利作为绝对龙头抗风险能力强,奶酪、奶粉第二增长曲线仍有增量空间,中长期仍具备温和成长性,业绩易受原料成本、消费景气影响产生阶段性波动。现金流经营活动现金流是否稳健:经营现金流常年充沛,但阶段性波动明显,近年出现大幅下滑,需深挖下滑诱因近5年营业现金流大于净利润: 经营活动的现金流大于净利润,利润现金含量充足连续3年自由现金流为正:自由现金流持续为正财务安全资产负债率: 近5年基本稳定流动资产与流动负债的比: 近几年有下降趋势,最近为0.729, 流动资产无法覆盖流动负债,短期偿债偏弱信号是否有大的风险:无即刻债务暴雷风险,但短期流动性偏弱,需持续跟踪现金流修复、负债结构变化赚钱能力近3年ROE:最新ROE为20.87%,近几年不稳定估值与安全边际股息率: 位于历史偏高水平PB: 位于偏低水平PE: 位于历史偏低水平整体估值:整体估值处于历史偏低水平核心风险国人对国产奶的质量一直存疑,某种程度上会影响产品销售经营虽然仍具有一定的成长性但是需要警惕债务扩大的风险各项财务指标不稳定,需要进一步分析下滑的原因生鲜乳周期涨价挤压毛利率、奶酪赛道行业竞争加剧。结论伊利股份作为国内乳制品绝对龙头,全产业链布局完善,奶源、渠道、品牌三重护城河深厚,产品刚需属性强,国内人均奶量提升打开中长期温和成长空间,长期被替代概率极低。分红层面连续 16 年稳定分红、逐年提升,股息率 5.37% 满足 4% 个人选股底线,累计派现大幅超过历史融资,分红基础扎实;当前 PE、PB 处于历史低位,估值安全边际充足。但公司存在多处不可忽视的潜在隐患:一是利润、经营现金流阶段性波动大,经营稳定性不及弱周期公用事业;二是流动比率仅 0.729,流动资产无法覆盖短期负债,短期流动性偏弱;三是分红率长期维持 70% 以上偏高区间,持续高比例分红会压缩内源留存资金;叠加消费景气、原料周期带来的业绩扰动,经营效率较疫情前有所下滑。综合风险把控要求,将伊利纳入股票池持续跟踪,优先梳理现金流下滑、流动性偏弱的核心诱因,确认风险无长期恶化趋势后,再评估分批买入机会。
2026年06月13日
4 阅读
0 评论
0 点赞
2026-06-07
读《人体简史》
这本书系统性的介绍了人体从细胞到组织到各个器官被发现的历史知识。其中有危险的人体实验,长期错误的医学认知和错误的治疗方案,以及有各种对人体着迷的先驱们孜孜不卷的研究。从中也看到现代医学基于科学原理从实践出发逐渐形成如今的体系和规模,挽救千千万万的患者。早期西方医学也停留在所谓的人体的四大基本组成元素并且形成了一套具有逻辑的原始认知,那个时候的人们认为人生病是因为血液脏了,再叠加从静脉放出的暗红色的血液,人们认为放血可以治疗各种疾病,放血之后病没有好是因为放血的量不够。一批对人体比较着迷的科学家(或者单纯的只是贵族),冒着被社会谴责甚至触犯法律的风险偷偷用死刑犯或者无家可归的死者进行解剖实验,逐渐纠正了曾今错误的认知。在大脑问题上,人们曾今迷信前额叶切除手术能治疗一切精神疾病。无数无辜的人被执行了这个手术,而手术也没有今天这么严谨,简单的设备,粗暴的手法。造成无数人的悲剧。书中也看到无数医生在疾病传播时冒着生命的危险深入一线,亲自研究生病的病人和尸体,试图找到病因并接触瘟疫的传播。医生始终怀揣着一颗救死扶伤的心。从书中我了解了医学从过去的愚昧到如今的谨慎和科学的发展,经历了漫长的,无数人的努力。我们现在生活离不开曾今为医学发展抛头颅,洒热血的人。书中也可以看到现代医学的不足,我在读这本书时经常看到我们现在对xx的认识仍然不足。每次读到这里我总是怀揣着对人体奇妙的敬畏,小到小小的细胞,大到细胞组成的人体这个复杂的工厂,有太多谜团我们无法解释,生命是如此的奇妙,人体进化出了太多复杂而奇妙的机制,它们仅仅是为了让你好好活着。以下是书中一些句子的摘录:与生活中的大部分事情一样,找平衡是一桩微妙的活计跟成年人的大脑相比,青少年的大脑里,神经连接的里程数更短。所以未成年人更加冲动,看起来更具激情,也容易满足和快乐。辅导小孩作业时感觉明明很简单的联想,但是他们就是想不到血型运作的原理是这样:所有的血细胞都有着相同的内部结构,但外部覆盖着不同种类的抗原(即从细胞表面往外突出的蛋白质),它们就是存在血型的关键;A型血细胞表面有A抗原,B型的有B抗原,AB型的有A和B抗原。把A型血输入B型血人体内,接受的一方会把输入的新型血视为入侵者并展开攻击。地球上存在的大多数最佳技术就在我们身体里,而且几乎所有人都认为这理所当然。运动能带来非同寻常的益处。经常散步可将心脏病发作或卒中的风险降低31%。事实上,吃大量的食物,可以立刻抵消掉大量的锻炼,而且我们大多数人都是这样做的长期的饥饿让志愿者变得易怒、嗜睡和抑郁,更容易生病很多食物的广告都标榜低盐、低脂肪、低糖,但制造商们降低这三者之一的时候,几乎总是会增加另外两种含量作为补偿既不含糖、又低脂肪、低盐、又不含多余油脂的食物一般不好吃睡眠与大量生物过程有关,如巩固记忆、恢复激素平衡、清除大脑中累积的神经毒素、重置免疫系统等睡眠周期一晚上重复4~5次。每个周期持续差不多90分钟,但也有所不同《睡眠革命》这本书中,提倡R90睡眠法,简单来说就是睡眠时间为90分钟的整数倍,尽量不要在深度睡眠中被叫醒显然,问题不在于你拥有什么样的基因,而在于你拥有的基因怎样表达,即基因得到了怎样的使用现代地球上的物种基因基本相同,人和黑猩猩有98%的基因相似,但是人之所以和黑猩猩有那么大的区别就是因为大量基因不表达或者表达方式不同致命性弱 传播性强的病毒才是最成功的病毒病毒想要长期存在一般也都会朝着这个方向演化你接受的教育越多,患阿尔茨海默病的概率就越小,拥有不断探索的活跃大脑(与年轻时在课堂上长时间地被动学习相对)几乎肯定可以阻挡阿尔茨海默病的侵袭。多用脑,多深度思考,少看手机,可以促进大脑健康;活到老学到老
2026年06月07日
4 阅读
0 评论
0 点赞
2026-06-06
股票投资学习记录(六)——公司财务基础
投资股票的前提是对公司有深刻的理解,而公司财报是公司按规定发布的经营情况的汇报。对于公司管理层来说通过年报、季报和半年报向公司股东汇报过去的经营成果,为来年的经营提供方向。对于普通投资者来说,它可以用来检验自己对公司商业模式、业务的分析是否准确、能够提前发现公司的潜在风险,最后根据财报上的经营成果对公司进行估值。对价值来说具备阅读公司财报的能力是投资长期盈利的必要条件企业的财务科目有哪些会计科目就是给公司的资产、负债、收入和支出进行“分门别类”的标签。如果盘点一下我们个人或者家庭的资产,我们可以分资产类:包括房子、车子(固定资产)、学位学历证书、技能证书(无形资产)、现金、短期理财、股票。负债类:房贷、车贷(长期借款)、花呗、信用卡(短期借债)对于个人的收支来讲,有收入: 工资收入(主营业务收入)、中彩票(营业外收入)、手中的股票涨了(投资收入)。对于支出来讲,我们有:水电燃气费、话费、宽带费、取暖费、买菜、通勤这类的固定支出、有偶尔遇到红白事、给亲戚还是过年发红包这种费用对于公司来说,公司财务科目有:资产类科目:银行存款、应收款、库存、固定资产等负债类科目:长/短期借款、应付款、预收款成本类科目:生产成本、制造费用、研发支出损益类科目:主营收入/成本、销售费用、销售费用、管理费用、财务费用所有者权益类科目:实收资本、资本公积、盈余公积、未分配利润公司如何做账与个人做账的一个不同是,个人做账注重结果,也就是注重现在还有剩余多少钱、哪些钱花在哪些地方、有哪些可以省略的,主要是为了清楚花钱的地方以及最终的余钱。而公司做账注重过程,它需要清楚的记录每一笔钱怎么来的,怎么花掉的,又去了哪里。公司做账主要是为了核算利润,供税务部门核查税务,以及供投资人分析它的业务往来和资金使用情况,判断它的价值。公司记账采用的是复试记账法,每一笔经济业务都需要至少在两个账户中同时记录。每一笔钱都有来龙去脉,而且来去相等。对于我们这个月发工资来说,个人记账我只需要记录本月工资收入xxx元。而公司需要记录这笔钱从哪来到哪去,首先月末会计提,员工干满了一个月,提供了一个月的劳动,公司此时相当于欠了员工一个月工资,此时公司的费用增加,根据员工所属部门不同,会记录近不同的科目:如果员工是工厂一线的生产工人,这笔钱会被计入生产成本;如果是销售人员,这笔钱会被计入销售费用;如果是管理人员,这笔钱会被计入管理费用。这笔钱来源自公司欠债,它被专门记录在“应付职工薪酬”这一科目。次月到发工资的日子,银行直接从公司账户扣款并往员工账户打款。此时公司之前欠员工的钱消失了,也就是“应付职工薪酬”这一科目相对应的数据减少,公司负债减少,而公司银行账户的钱减少对应着公司的资产减少。复试记账有两大基础基石:资产 = 负债 + 所有者权益有借必有贷,借贷相等第一个可以这样理解:购买资产的钱一部分来自与从别的地方借的钱(负债)、或者用股东的钱。公司靠自己赚的钱购买资产也属于用股东的钱,因为公司的利润属于股东,相当于股东投资自己的钱增加公司资产。第二个这里用借和贷来描述实在是令人费解,因为在我们日常用法中,借与贷其实是同一个意思。找银行借钱和找银行贷款是一个意思。而在会计术语中,借仅仅表示资金从哪来,而贷表示资金到哪去。下面通过公司真实业务场景来深刻理解复试记账。公司新成立,股东注资100万,此时公司所有者权益中的“实收资本”这一科目增加100万,而公司资产中的“现金”这一科目增加100万此时公司的“资产=负债+股东权益”这个等式为$$\text{资产}(100\text{万}) = \text{负债}(0) + \text{所有者权益}(100\text{万})$$对于资产类来说,借表示资金增加,而贷表示资金减少。在所有者权益类的科目来说借表示减少而贷表示增加。同时在负债类科目中,借也表示减少而贷表示增加。似乎有些矛盾不好记,但是我们想象一下上面的公司“资产=负债+股东权益”,借表示公司左边,而贷表示公式右边。借表示公式左边增加,而贷表示公式右边增加所以此时可以记为:借,货币资金 100万贷,股东权益 100万公司向银行申请了100万的为其1年的短期贷款,此时公司资产类科目“货币资金”增加100万,变为200万。而公司负债类短期负债为100万,仍然满足公式。“资产=负债+股东权益”这个等式为$$\text{资产}(200\text{万}) = \text{负债}(100\text{万}) + \text{所有者权益}此时可以记账:借,货币资金100万贷,短期借款100万公司使用50万购买生产产品的原材料,此时资产科目中的“存货”增加为50万,而现金部分减少了50万变成了150万这笔帐可以记为:借,存货资金50万贷,货币资金50万公司生产的产品卖了80万,并且客户当场付了钱,此时存货减少50万,而现金部分增加了80万,资产部分增加了30万,而所有者权益也增加30万。此时记账分两步,分别时存货减少50万,而现金增加80万。对于现金增加部分,可以记为:借:货币资金80万贷:营业收入80万而对于存货减少,会记为:借:营业成本50万贷:存货50万到月底结算时产生了 营业收入(80万)-营业成本(50万)=30万的利润,这部分利润规到了所有者权益公司向供应商采购了20万的原材料但是没有付款,此时资产科目的存货增加20万,而负债科目中的“应付账款”增加了20万。这个时候的帐可以记为:借:存货20万贷:应付账款20万公司卖出了价值20万的产品,价值30万,但是客户没有付款双方约定月底结账。此时公司资产科目中的存货减少20万,而资产科目中的应收账款为20万。此时第一步先确定营业收入为30万,此时记为:借:应收账款30万贷:营业收入30万然后存货应该减少20万借:营业成本20万贷:存货20万月底了,客户把欠款结清了,此时公司资产科目中的应收账款减少到0,而公司的资产中货币资金增加了30万。借:货币资金 30 万贷:应收账款 30 万或者到结账的日子,客户公司破产了,法院清算之后客户无法偿还这笔钱,因此这比应收账款产生了坏账。此时资产中的应收账款因为收不回来变成了0,而所有者权益科目中的“信用减值损失”增加了30万损失。此时上面的恒等式依然成立,资产少了30万,同时右侧的所有者权益少了30万这个时候这笔帐可以记为:借:信用减值损失 30万贷:应收账款 30万到月底,公司准备偿还供应商的20万原材料,此时对应着负债科目中的“应付账款”减少20万,而资产科目中公司的货币资金减少20万。这笔帐记为:借:应付账款20万贷:货币资金20万公司产品供不应求,决定花费50万购买新设备扩张生产线。此时公司固定资产增加50万,而货币资金减少50万。这笔帐可以记为:借:固定资产50万贷:货币资金50万假设新买的设备预计使用5年,那么会计上需要每年计提10万的资产减值损失。比如第一年固定资产价值50万,第二年固定资产的价值就只有40万了,此时资产中有10万减少了,这笔钱被记录在生产成本中,在使用这台机器生产的产品卖出之后它会转化为营业成本减少最终的利润。因此这笔帐可以记为:借:折旧费用10万贷:累计折旧 10万月底公司需要给工人发工资,假设需要10万元发工资。在月底时公司需要计提10万的费用,假设其中管理人员工资一共4万,一线工人的工资一共6万。一般工人的工资作为生产成本,而管理人员的工资被视为管理费用。所以此时在负债科目中有“应付职工工资” 10万元的增加。这笔钱可以记为:借:生产成本6万,管理费用4万贷:应付职工工资10万当发薪日到来,银行直接从公司账户扣除10万的货币资金,发放到公司员工的个人账户中,此时货币资金减少10万,而应付职工工资减少10万。所以可以记为:借:应付职工工资10万贷:货币资金10万假设公司年底核算时发现赚了不少钱,因此公司决定对股东进行分红,一共分红20万。此时会在所有者权益中计提应付股利20万,而公司资产中的货币资金需要减少20万。借:应付股利20万贷:货币资金20万总结上面的例子覆盖到了从公司成立到生产产品、产生运营费用、最后股东分红的大概流程。从上面的流程可以看到,记账时始终遵循资产=负债+所有者权限 这个横等式。在记账时发现每次的运营活动都会影响到公司的资产并且根据恒等式也会影响到负债和所有者权限。同时发现它也关联到了损益,有些钱是用到的经营中,而有些钱增加是因为经营盈利了。有了上面的过程,我们脑海中对经营过程中的金钱流动有了一个初步认识,后面学习如何解读财务三表,分析公司财报就比较容易了
2026年06月06日
4 阅读
0 评论
0 点赞
2026-05-30
农业银行(601288)初步分析
基本信息公司名称: 中国农业银行股份有限公司所处行业: 银行业 — 国有大型银行核心业务:主营存款、贷款、结算、银行卡、理财及托管等综合金融服务,深耕县域与三农金融市场,兼顾城市对公与零售业务。特点:全国性头部国有大行,县域网点覆盖最全、下沉深度最高;客户基数庞大、负债成本极低;经营风格保守稳健,风控严格,抗周期能力极强。商业模式盈利来源:核心依靠存贷款净息差赚取稳定收益,辅以理财代销、结算、银行卡、托管等中间业务增收,收入结构稳定。护城河:银行业强监管牌照壁垒,行业准入门槛极高,新竞争者极少;国有大行信用背书,揽储成本行业最低,负债端优势显著;全国海量线下网点+县域绝对垄断优势,用户粘性极强;经营风格保守,风控审慎,资产质量长期稳定。未来5到10年分红判断:银行属于国民经济刚需支柱,永续经营属性极强;农行风控保守、资本厚实、监管评级高,未来长期经营无暴雷风险。在监管约束与国企股东回报要求下,分红将长期持续,不会中断。分红历史连续分红: 连续分红16年不间断近 5 年分红: 近5年分红稳步提升近12个月股息率:3.95% 不满足个人4%股息率的买入条件分红率:30%,处于银行行业健康区间,未透支资本金融资派现比361.26%,回馈股东意愿强盈利稳定性5年净利润: 净利润低速稳健增长,无大幅波动5-10年营业额: 营业额稳定连续3年公司扣非净利润为正、净利润是否为正: 扣非净利润持续稳定低速增长,经营稳定特点:银行属于弱周期行业,叠加农行深耕县域、风控保守的特性,宏观经济波动对其冲击远小于股份行与中小银行,经济弱环境下仍能维持正增长,经营韧性极强。现金流经营活动现金流是否稳健: 经营活动现金流持续为正,但是呈现周期性变化近5年营业现金流大于净利润: 经营现金流远大于净利润连续3年自由现金流为正: 自由现金流持续为正财务安全资产负债率: 93.35% 为银行业正常水平,不代表高风险资本充足率: 17.93%,远超监管红线,自有资本厚实,分红与业务扩张均有保障不良贷款率: 1.27% 资产质量优质不良贷款拨备覆盖率:292.55% 资本充足总结:财务安全等级为顶级,无任何财务上的暴雷隐患。赚钱能力ROE: 近5年维持在10%~11%之间,且有小幅下滑趋势,整体偏平稳估值与安全边际股息率: 处于历史低位PE: 处于历史中等偏高水平PB: 处于历史中等偏高水平整体估值偏高,安全边际不足核心风险净息差进一步减少,未来盈利空间会进一步被压缩宏观经济波动可能导致不良贷款小幅上行银行业进入成熟期,整体无高增长,仅能赚取稳健分红收益。结论农业银行作为国有大型龙头银行,商业模式成熟稳定,牌照与国有背景构筑深厚壁垒,客户基础扎实、经营韧性极强。公司财务质量顶级,资本充足、不良率低、拨备储备丰厚,现金流充沛,连续多年稳定分红,股东回报意愿强,属于A股最稳的核心资产之一。但当前标的存在明显短板:股息率3.95%未达个人4%硬性买入标准,且PE、PB均处于历史中等偏高区间,整体无低估安全边际。最终操作策略:纳入核心观察股票池,不急于买入。等待后续股价回落,股息率回升至4%-5%以上、估值进入历史低分位、安全边际充足后,再择机分批配置。
2026年05月30日
6 阅读
0 评论
0 点赞
2026-05-30
双汇发展(000895)初步分析
基本信息公司名称: 河南双汇投资发展股份有限公司所处行业:食品饮料核心业务:公司聚焦肉类主业,构建了贯穿肉类全产业链的业务布局,主要产业涵盖饲料业、养殖业、屠宰业、肉制品加工业、外贸业、调味品业、包装业、商业等特点:国内生猪屠宰、肉制品绝对龙头,全产业链一体化(饲料 — 养殖 — 生猪屠宰 — 深加工肉制品 — 终端渠道),国内常温肉制品市占率断层领先。商业模式靠什么挣钱:生猪屠宰赚取屠宰加工费;火腿肠、低温熟食等肉制品产销赚取产品差价,配套包装、外贸为辅。护城河是什么: 覆盖饲料生产、牲畜饲养、宰杀到制作成食品的完整产业链,平抑未来猪价格变化;线上线下销售渠道广泛;产品的认可度未来5到10年分红会消失吗:不会,目前只要有火腿肠、火腿等肉食产品的需求,双汇的产品就不会受到影响。目前还没有一个同类的产品能达到双汇的规模、低价和全民认可度。但目前看高分红不一定能持续。分红历史连续多少年分红不间断: 29年分红不间断近5年分红是否稳定:近5年分红稳定,但是略有下降最近12个月股息率大概是多少:5.86%分红率 (分红/净利润):近5年一直维持在90%以上累计派现大于累计融资: 融资派现比为93.86%,不满足累计派现大于融资的条件盈利稳定性5 年净利润: 净利润常年 50 亿上下窄幅波动,无大幅涨跌;5-10 年营业额: 营收由高点回落、近年缓慢下行,稳定在 600 亿附近连续3年公司扣非净利润为正、净利润是否为正:扣非净利润持续为正,盈利底盘扎实,但成长性枯竭、业绩稳中趋弱;特点: 行业成熟期,存量市场竞争加剧,终端需求放缓,企业难以实现营收与利润持续增长现金流经营活动现金流是否稳健:经营现金流常年稳定在 50~70 亿区间,现金流稳健近5年营业现金流大于净利润: 双汇经营现金流长期显著高于净利润,利润现金含量充足连续3年自由现金流为正:自由现金流持续为正财务安全资产负债率: 近5年有扩大的趋势流动资产与流动负债的比: 近几年有下降趋势,最近为1.149, 刚好覆盖短期负债,安全边际偏弱是否有大的风险:目前没有大的偿债风险,但是需要警惕债务扩大的风险赚钱能力近3年ROE:24%, 高 ROE 一部分来自高杠杆 + 极致分红减少净资产,并非完全靠内生盈利扩张估值与安全边界股息率: 位于历史正常水平PB: 位于偏低水平PE: 位于历史偏低水平整体估值:整体估值处于合理偏低水平核心风险猪周期原料波动:全产业链对冲,影响弱于同行消费习惯转变:低温鲜肉、预制菜替代常温火腿肠,长期压制传统主业增长;食品安全黑天鹅为行业常态化风险;最近分红水平一直维持在90%以上,但是近几年债务水平上升,营业情况并未有明显提升,有透支未来经营的可能,未来高分红不一定能维持多次股权融资、累计分红不及募资,企业长期在向市场要钱,不符合优质现金奶牛标准。结论商业模式: 商业模式尚可,护城河优秀财务状况: 财务状况稳健,但是近年来债务水平有扩大的风险分红情况:分红连续稳定估值:估值水平在合理偏下双汇全产业链壁垒深厚、龙头地位稳固、现金流扎实、当前估值偏低、股息丰厚,从基本面和估值层面看似优质。但企业存在致命硬瑕疵:常年 90%+ 超高分红透支留存利润,内源积累缺失,只能依靠增发、举债补充经营资金,累计分红总额小于历史融资总额,违背个人选股硬性标准;叠加行业需求放缓、营收长期停滞,高分红难以永久维系,未来存在分红下调、业绩承压的潜在风险。综合风控要求,不纳入备选股票池,放弃配置。
2026年05月30日
3 阅读
0 评论
0 点赞
2026-05-25
C++ 20 协程的探索
C++ 20 中新增的协程特性可以说是近几年更新的最重要的一个特性。早在我使用python的时候,我就体会到python中的协程和它在处理list时使用yield 的优雅。现在C++ 也支持了协程,我们可以实现类似python中的功能了完整的协程例子在python中,我们可以实现如下的函数def fibonacci(n): a, b = 0, 1 for _ in range(n): yield a # 暂停函数,返回a a, b = b, a + b # 使用生成器 for num in fibonacci(5): print(num)早期的C++ 中我们只能使用std::vector<int> fibonacci(int n) { vector<int> res; int a = 0, b = 1; for(int i = 0; i < n; i++) { int c = a + b; a = b; b = c; res.push_back(c); } } for(auto i : fibonacci(5)) std::cout << i << std::endl;上面两个对比,python示例的特点在于它不是每次调用都计算指定范围内的所有结果,而是每调用一次接着上一次的进行调用,能极大的提升运行效率。一个好的消息是,在C++20 中新增了协程,我们也能实现类似的功能。而坏消息就是C++20 的协程设计成了底层的“语言特性骨架”,没有提供开箱即用的高层库,所以它的概念相对复杂。#include <coroutine> struct Generator { struct promise_type { int currentValue; std::suspend_always initial_suspend() { return std::suspend_always{}; } std::suspend_always final_suspend() noexcept { return std::suspend_always{}; } auto yield_value(int value) { currentValue = value; return std::suspend_always{}; } Generator get_return_object() { return Generator{ std::coroutine_handle<promise_type>::from_promise(*this) }; } void return_void() {} void unhandled_exception() { std::terminate(); } }; std::coroutine_handle<promise_type> h; Generator(std::coroutine_handle<promise_type> handle) : h(handle) {} ~Generator() { if (h) h.destroy(); } bool move_next() { if (!h || h.done()) return false; h.resume(); return !h.done(); } int get_value() { return h.promise().currentValue; } }; Generator fibonacci(int n) { int a = 0; int b = 1; int i = 0; while (i < n) { co_yield a; int next = a + b; a = b; b = next; i++; } } int _tmain(int argc, TCHAR* argv[]) { Generator generator = fibonacci(10); while (generator.move_next()) { std::cout << generator.get_value() << std::endl; } return 0; }比起python的版本,它确实复杂了不少。但是我们可以从逻辑上一一拆解协程实现原理首先我们知道,普通的函数是通过栈来存储数据的,在调用时首先经过参数压栈,然后在栈中初始化函数的局部变量,接着执行函数代码。在函数退出时要在接收返回值的内存处初始化返回值对象,最后执行出栈操作清理整个函数栈。协程函数是可以随时挂起并回退到调用处,或者由外部直接恢复执行的,所以在回到调用位置时并没有执行函数栈的清理操作,而且需要保存函数当前执行的上下文环境,在需要的时候进行恢复。C++的协程采用的是无栈的设计,也就是说协程函数本身并没有产生栈,它的所有信息由编译器生成处理。到这里我们知道针对每个协程,编译器会生成处理它的代码包括保存上下文环境,真正执行挂起和恢复。编译器中通过coroutine_handle 对象来管理每个协程对象。我们无需知道编译器具体是如何实现管理协程的,我们只需要获取它的对象,并调用对象的方法就能操作对象。这也是面向对象的体现。我们知道编译器是通过生成一个协程对象来管理协程的。那么它是什么时候生成这个对象的呢?答案是它在发现某个函数为协程函数时会生成一个协程对象,对象生成的时机早于协程函数开始执行的时候,主要是因为它需要接管协程函数的栈并保存上下文环境。那么编译器是如何知道某个函数是协程函数的呢?主要是通过识别里面是否有 co_await、co_yield、co_return 这么几个关键字。到此我们先梳理一下,首先编译器在编译时扫描到了函数中有 co_await、co_yield、co_return 这几个关键字,因此它会生成协程对象接管函数栈和上下文,并且不会为函数本身生成栈帧。对于协程函数,为什么不能直接返回具体的返回值类型,而需要利用结构体包装一层呢?例如对于 fibonacci,我们知道它实际返回的是int类型,为什么不能直接返回int 呢?如果我们直接返回int会得到这样的错误:error C2039: "promise_type": 不是 "std::coroutine_traits<int,int>" 的成员也就是说,协程的类型已经定义好了,它必须是 std::coroutine_traits<T> 的类型,我们可以在标准库中看到,这个类型的定定义如下:template <class _Ret, class = void> struct _Coroutine_traits {}; template <class _Ret> struct _Coroutine_traits<_Ret, void_t<typename _Ret::promise_type>> { using promise_type = typename _Ret::promise_type; }; _EXPORT_STD template <class _Ret, class...> struct coroutine_traits : _Coroutine_traits<_Ret> {};从上面可以看到,返回值必须要包含一个名为 promise_type 的结构。这个结构实际上是一个接口,在编译器帮我们管理协程,实现协程的创建、挂起、恢复、结束时它给我们提供了一系列的接口,方便我们在这些事件发生时执行一些自定义的代码。上面我们实现了这么两个接口:initial_suspend: 协程对象被创建时执行final_suspend: 协程对象被销毁时执行这两个函数的返回值也是一个接口,需要返回一个 awaiter(等待体)。这个 awaiter 我们也可以将它看作一个接口。它主要有三个函数需要实现:bool await_ready(): 如果返回 true,则表示已经就绪,无需挂起;否则表示需要挂起await_suspend(coroutine_handle<>) 当协程被挂起时,协程对象会保存当前协程的对象,此时 await_suspend 会被调用await_resume() 协程恢复执行时,该函数被调用需要注意,上面三个函数除了 await_ready 外,另外两个都没有给出返回值类型,标准并没有规定它们的返回值类型。这里我用了标准库实现的简单的等待体 std::suspend_always,它的实现如下:_EXPORT_STD struct suspend_always { _NODISCARD constexpr bool await_ready() const noexcept { return false; } constexpr void await_suspend(coroutine_handle<>) const noexcept {} constexpr void await_resume() const noexcept {} };还有一个类似的实现_EXPORT_STD struct suspend_never { _NODISCARD constexpr bool await_ready() const noexcept { return true; } constexpr void await_suspend(coroutine_handle<>) const noexcept {} constexpr void await_resume() const noexcept {} };到此为止,我们知道了4个接口,分别是协程对象被创建和销毁时调用,协程被挂起和恢复时调用。到这里,读者可能有疑问,这些接口为什么不设计到promise_type 里面,而非要放到两个结构里面,非要凭空多出一个概念呢?是不是这样更难理解,显得逼格更高呢?原因与难度和逼格无关,这是因为等待体主要是用来控制协程是挂起还是执行,以前我陷入了一个误区,我以前认为每次调用co_yield、co_await 一定会陷入等待。但是实际上它们只是操作等待体,与协程实际进行等待还是继续执行没有关系。我们是通过具体的等待体来实现此时协程是该继续执行还是等待。等待体与协程本身的控制应该分开,协程根据等待体的返回值来决定该等待还是继续执行。到这里我们再总结一次:编译器扫描到了协程函数,会定义一个协程对象来管理协程协程对象创建时执行 initial_suspend 函数,对象根据函数的返回值来决定是否要执行协程函数协程函数被执行时会依次执行,直到遇到 co_yield、co_await、co_return遇到上面的关键字之后它通过关键字操作的等待体中的 await_ready 的返回值来决定这个时候要不要接着执行协程如果需要挂起协程,那么它会调用等待体中的 await_suspend如果在外部恢复了协程,此时会调用等待体中的 await_resume如果协程函数结束了,会调用 final_suspend有读者可能会问了,你说 co_yield、co_await、co_return,我在上面的例子中也没看到有等待体呀。这是因为编译器对我们 co_yield a; 这个语句中执行了类型转换,将int转化为了等待体。我们在实现这个转化上有两种方式,第一个就是我自己定义一个等待体,然后通过构造函数实现int到等待体的转化,第二个就是实现接口, 对于co_yield 来说这个接口是一个名为 yield_value 的函数。编辑器在执行类型转化时,会进行下面的流程:如果在 promise_type 中定义了对应的转换函数,那么会调用转换函数来进行转换否则在全局查找能否有办法将后面的数据类型转化为一个等待体这里我没有自定义等待体,就采用最简单的 yeild_value了。对于 co_await 来说,可以通过 await_transform 函数来进行转化在上面的例子中,还有三个函数没有介绍到:get_return_objectreturn_voidunhandled_exception首先是 get_return_object,从名字上看,它是用来获取返回值对象的,我们是要用它来获取哪个返回值对象呢?是协程函数的吗?当然不是,这个函数是在协程对象自身被创建之后会调用,也就是说这里的返回对象其实是协程对象。return_void / return_value函数,这个函数是协程体结束之后如果协程函数自身返回时被调用,对于第一个函数,它调用的时机是在协程函数结束之前,而return_value 则是在调用 co_return 返回一个值。它们与 co_yield 类似,外部都是通过这种机制来实现获取协程执行到此的一些值,但是调用 return_void 和 return_value 之后意味着协程函数已经执行完毕,没有机会再恢复了。最后一个函数是:unhandled_exception, 这个函数表示,如果协程函数中发生异常,此函数将被调用。总结到此我们根据上述代码来总结一下协程的执行流程:编译器扫描到了协程函数,会定义一个协程管理对象来管理协程协程管理对象被创建之后,执行 get_return_object 此时外界有机会获取到管理对象,此时我们将handle 对象保存到了 Generator 对象中协程管理对象执行 initial_suspend 函数,对象根据函数的返回值来决定是否要执行协程函数。这里我们通过返回suspend_always 来将协程挂起外界通过执行 move_next 调用里面的 h.resume() 来恢复协程的执行,协程会按顺序执行直到遇到 co_yield、co_await、co_return这里遇到了关键字 co_yield 它会调用promise_type 中的 yield_value 执行整形数据到awaiter 的转化,这里返回suspend_always 来将协程挂起此时协程被挂起,那么它会调用等待体中的 await_suspend如果在下一次循环中又一次调用 move_next 恢复了协程,此时会调用等待体中的 await_resume就这样每调用一次 move_next 就执行一次计算,获取数列中的下一个数字循环结束,协程函数执行到函数体尾部或者执行了 co_return ,会调用 return_value或者 return_void 让外界有机会获取到协程函数本身的返回值,这个函数本身不返回值,所以这里会调用 return_void。协程函数结束了,执行 final_suspend,此时可以做一些清理工作。这里我们返回 suspend_always 来暂停协程,主要是为了协程结束之后,仍然可以获取协程计算的结果。主函数结束,Generator 对象被销毁,此时调用析构函数中的 h.destroy() 销毁协程管理对象
2026年05月25日
4 阅读
0 评论
0 点赞
1
2
...
38