<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on Nansen Li's Blog
李楠森的博客</title><link>https://nansenli.com/zh-cn/post/</link><description>Recent content in Posts on Nansen Li's Blog
李楠森的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>Nansen Li 🌈 （李楠森）</copyright><lastBuildDate>Sun, 13 Sep 2026 22:33:50 +0100</lastBuildDate><atom:link href="https://nansenli.com/zh-cn/post/index.xml" rel="self" type="application/rss+xml"/><item><title>我为何停止订阅Claude</title><link>https://nansenli.com/zh-cn/post/2026/09/why-i-unsubscribed-claude/</link><pubDate>Sun, 13 Sep 2026 22:33:50 +0100</pubDate><guid>https://nansenli.com/zh-cn/post/2026/09/why-i-unsubscribed-claude/</guid><description>&lt;p>过去一年发生了很多事情。总想着什么时候更新一下博客，但一直拖着。&lt;/p>
&lt;p>不过，就在这两天，我停止订阅了Claude，也删除了手机还有电脑上的相关应用。也许简单谈谈关于Claude的事情吧。&lt;/p>
&lt;p>不得不承认，Anthropic的模型确实很强大，订阅价格也合理，生产力很高。&lt;/p>
&lt;p>我之前曾经一直订阅GPT，直到有段时间，它完全无法工作，每次响应时间都很长。那时，我不清楚是什么原因导致在爱尔兰无法使用。但现在似乎已经解决了，使用体验很好。&lt;/p>
&lt;p>对于Claude，它确实曾经在我生命中最困难的时候，比GPT更能帮到我，在那段时间，它帮我调整了很多。&lt;/p>
&lt;p>但现在，我不得不对Claude说，不，我不会再继续使用你们的产品，也不会再考虑订阅了。&lt;/p>
&lt;h3 id="claude有多好用">Claude有多好用？
&lt;/h3>&lt;p>Claude确实很好用。无论是至今为止最会沟通的模型Opus4.6，还是最能执行命令的模型Opus5，或者是最强大的Fable系列的模型，他们确实很强大。使用Claude确实很难戒掉。但是，既然这个世界上有这么多不同种类不同公司提供的AI，不见得就一定要保持在Claude上。对于沟通能力上，GPT-6-Astra已经改善了沟通能力。对于性价比方面，DeepSeekV4.1-Flash，或者GLM5.3-Flash，让用户感到量大管饱。切换模型的使用刚开始确实很痛，但是可行。&lt;/p>
&lt;h3 id="为什么停止订阅claude">为什么停止订阅Claude？
&lt;/h3>&lt;p>我认为Anthropic目前的价值观会让整个世界朝着不健康的方向发展。在心理学上，有一个概念叫做投射性认同。指的是一个人将自己无法接受的品质（通常是自己所拥有但不愿意承认的品质）投射到他人上，而后者在长此以往的过程中，反而按照这个人的期望进行行动或者表现出类似的特质。&lt;/p>
&lt;p>举个例子，一对夫妻，妻子经常觉得丈夫无能，在生活中，对丈夫说，“你怎么啥也干不好”，“我真是瞎了嫁给你”，而每当丈夫做错事情，妻子就会说，”看吧，你就是什么都不行“。长此以往，妻子成功地将丈夫变成一个无能的丈夫，且丈夫也对此深信不疑。妻子的话变成了自我实现的预言。&lt;/p>
&lt;p>Anthropic的CEO，将自己对AI安全失控的恐惧，不断的投射到全世界，并且强调只有自己是对安全最负责的，如果自己不处于领先地位，则世界就会失去安全。&lt;/p>
&lt;p>&lt;strong>Anthropic的CEO最大的问题是，他混淆了手段和目的&lt;/strong>。他把自己的手段，混淆成了最终想要的结果。&lt;/p>
&lt;p>他声称的真正目的&lt;/p>
&lt;ul>
&lt;li>让AI给人类带来好处&lt;/li>
&lt;li>减小AI的风险&lt;/li>
&lt;li>让安全成为Anthropic的竞争优势&lt;/li>
&lt;/ul>
&lt;p>他的手段&lt;/p>
&lt;ul>
&lt;li>反复进行AI安全警示，例如发布AI对就业的冲击，且通常伴随令人糟心的数据，比如大量岗位失业&lt;/li>
&lt;li>反复要求对AI进行监管&lt;/li>
&lt;li>打压开放权重模型&lt;/li>
&lt;li>公开和政府叫板&lt;/li>
&lt;li>煽动不同国家之间的政府进行AI对抗&lt;/li>
&lt;/ul>
&lt;p>Anthropic的CEO，将这些手段，无限提升至与目的相同的地位，并占领道德高地。任何反对Anthropic的组织机构，都可能被视为对人类不够负责任，而不是积极寻找可能的其他非对抗性方案。况且，Anthropic的CEO如果过度依赖恐怖叙事来营销，也许最终它是对的，但是，正如上面那个投射性认同的例子，这是一个极度不健康，且对整个世界有害的过程，因为公开煽动对抗必然导致对抗不断升级，从而挤压合作空间。&lt;/p>
&lt;p>如果Anthropic的CEO真的相信在乎安全，那他的文章 &lt;a class="link" href="https://darioamodei.com/post/we-must-pace-the-frontier" target="_blank" rel="noopener"
>We must pace the frontier&lt;/a> 应该主要谈论AI失控或者不安全的AI的危害，或者对AI不监管的焦虑。但是，从该文章主要内容中，透露出的不是关于AI不够安全的焦虑，而是Anthropic失去领先地位的焦虑，特别是面对开放权重模型失去领先地位的焦虑。不仅如此，Anthropic的CEO还将失去领先地位的焦虑，不断扩散散播，转嫁到全球和全世界，让所有人一并承担。&lt;/p>
&lt;h3 id="结语">结语
&lt;/h3>&lt;p>人类值得一个开放和光明的未来。治愈更多的疾病，延长人类的平均寿命，减少犯罪率，增加个人价值感，我们有如此多的未竟之事。正如人类爱自己的孩子一样，即使孩童犯下错误，人类也会愿意相信和信任孩童，并制定规范指导孩童，因为孩童终将长成承担责任的大人。而信任我们的AI，包容AI犯下的错误，建立完善的AI监管，有助于建立更健康的人和AI的关系，而不是频繁强调AI潜在危险，煽动AI领域的对抗。&lt;/p></description></item><item><title>受不了 Openclaw Bug 太多，我手搓了一个新 Bot：Nagobot</title><link>https://nansenli.com/zh-cn/post/2026/02/nagobot-launch/</link><pubDate>Sat, 07 Feb 2026 16:52:42 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2026/02/nagobot-launch/</guid><description>&lt;p>最近一直在用 Openclaw，但 bug 实在太多了：经常莫名其妙中断、偶发“自爆”，排查成本也很高。&lt;/p>
&lt;p>另外，token 成本也不低。和 DeepSeek、Kimi K2.5 相比，性价比并不占优。&lt;/p>
&lt;p>后来我又试了 nanobot，本来想迁移过去，但它对新模型支持不够完整，K2.5 一直报“工具无法执行”。折腾几轮后，我决定不再等现成方案，直接自己做。&lt;/p>
&lt;p>我和 Opus、Codex 来回打了几百回合，最后手搓了一个新的 bot：&lt;strong>Nagobot&lt;/strong>，使用 Golang 实现。至少在 provider 这一层，不再受制于人。&lt;/p>
&lt;p>项目链接：&lt;a class="link" href="https://github.com/linanwx/nagobot" target="_blank" rel="noopener"
>https://github.com/linanwx/nagobot&lt;/a>&lt;/p>
&lt;p>实现过程中我学到很多，主要包括：&lt;/p>
&lt;ul>
&lt;li>线程与并发任务&lt;/li>
&lt;li>Agent 与 Skill 设计&lt;/li>
&lt;li>Cron 异步任务&lt;/li>
&lt;li>会话记录与上下文压缩&lt;/li>
&lt;/ul>
&lt;p>说实话，目前很多代码还没有经过完整 review。开发期间基本是一边写一边和 AI 讨论架构，有时候被气哭，有时候又被惊艳到，过程非常真实，也挺有趣。&lt;/p>
&lt;p>测试下来效果比预期好很多：现在已经可以让 bot 去创建异步任务、调查新闻、给自己设置提醒，实用性很强。&lt;/p>
&lt;p>目前大部分功能都已经实现。下一步我会继续啃“长期记忆”这块设计。&lt;/p>
&lt;p>如果你也有类似需求，欢迎交流或提 issue。我会持续更新这个项目。&lt;/p></description></item><item><title>我为何停止订阅ChatGPT</title><link>https://nansenli.com/zh-cn/post/2025/11/ai2025/</link><pubDate>Mon, 24 Nov 2025 23:13:25 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2025/11/ai2025/</guid><description>&lt;p>我最近停止订阅了ChatGPT。&lt;/p>
&lt;p>这不是一个容易的决定。我从ChatGPT开始支持付费的那天就开始了付费。这一付费，就快接近了3年。&lt;/p>
&lt;h1 id="停止付费的原因">停止付费的原因
&lt;/h1>&lt;ol>
&lt;li>延迟巨大。我经常等待回复时等待几十秒而没有响应。然而，免费版本通常使用mini模型，也足够回答大部分任务了。但是免费版本反而更快。我不明白，这是因为欧洲没有计算节点还是转发节点？&lt;/li>
&lt;li>Mac上的bug太多了。卡顿，占用CPU，复制Latex文本出现bug。&lt;/li>
&lt;li>一些交互体验很糟糕。例如，我们可能会需要重新使用Thinking来重新生成回答。然而，这个选项却在切换模型，需要多点一次，因为他们在二级菜单。而不经常使用的更精简/更多细节的重新回答按钮，却在重新回答的一级菜单&lt;/li>
&lt;li>产品更新变动巨大，模型性格变化太快。这需要重新熟悉模型的成本。&lt;/li>
&lt;li>模型固执，不遵守指令。例如，反复需要强调什么时候该搜索，什么时候不要问follow up 问题。&lt;/li>
&lt;li>模型行为不稳定，且难以控制。例如，在instant模式下，模型调用搜索后会切换为小模型，使用网络资料回答问题，而不是instant模型。&lt;/li>
&lt;li>模型质量不如其他模型，甚至不如一些开源免费模型，例如DeepSeek。&lt;/li>
&lt;/ol>
&lt;p>仍旧想让我留下来的原因：&lt;/p>
&lt;ol>
&lt;li>思考模式仍旧是王者，不断调用工具链来进行搜索和验证仍旧是性能佼佼者。但是Grok也很擅长搜索，而Grok免费额度已经足够使用了。&lt;/li>
&lt;li>产品矩阵更强大，例如ChatGPT Atlas浏览器，或者Mac的独立客户端。再到很多插件。然而令人揪心的是，Atlas浏览器的Agent的额度令人惊奇的低，几乎不能作为实用使用。&lt;/li>
&lt;/ol>
&lt;h1 id="目前的代替品">目前的代替品
&lt;/h1>&lt;p>&lt;img src="https://nansenli.com/img/ai2025-apps.png"
loading="lazy"
alt="手机上的 AI 应用"
>&lt;/p>
&lt;p>我尝试了众多免费软件，包括所有能想到的/大部分的LLM，甚至例如MiniMax/Kimi 团队开发的模型也都尝试了。我进行了总结。在总结前，我需要先定义一个软件行业的词汇。这个现象非常具有中国特色，或者地域特色。&lt;/p>
&lt;p>目前的 LLM 软件确实存在一种为了追求日活（DAU）、留存率和商业变现，而牺牲工具纯粹性和用户体验的趋势。一些LLM软件塞入了很多运营化的东西，例如很时髦的运营话题，或者与技术不太相关的很多东西，例如智能体，赛事专区，头条文案或者小红书文案。这个现象表明了软件臃肿化，过度运营化，去工具化，庸俗化，信噪比降低。在该文，我们把这个现象叫做我们把这个现象叫做平台衰退 / 屎化 (Enshittification)。&lt;/p>
&lt;ul>
&lt;li>智谱清言：智谱清言的GLM4.6是一款非常强大的模型，特别是其Agent能力强大。但是其客户端屎化严重，打开首页即出现：“恋爱军师”，“黑暗料理大挑战”等屎化内容。不建议使用。&lt;/li>
&lt;li>Yunbao：Yunbao是腾讯的软件，支持腾讯自研模型和DeepSeek，然而模型能力一般。软件呈现轻度屎化，例如植入腾讯游戏王者荣耀等内容。不建议使用。&lt;/li>
&lt;li>Gemini：新晋之神。Gemini 3 pro模型性能出众。Nano Banana pro制作图片能力优秀。适合工作和生活使用。有Google运营，整体体验良好。然而最近由于流量大，服务不是特别稳定。最近流量过大，服务降级严重，使用量化服务开始提供服务，因此质量下降。长思考多次工具调用能力不及GPT5-Thinking或者Grok Expert。软件几乎没有屎化，强烈推荐使用&lt;/li>
&lt;li>Grok：免费之神。强大的搜索能力，短思考，长思考质量结果均高。擅长挖掘网络信息下。免费版Expert足够一般用户使用。少量运营下内容，例如加入了AI虚拟人物，不过屎化非常轻微。推荐使用。&lt;/li>
&lt;li>ChatGPT：逐渐退下神位。很多功能仍旧只有ChatGPT提供，例如语音对话。用户总量大。免费版本足够使用。很多用户仍旧有惯性/依赖。可保留在手机上。&lt;/li>
&lt;li>DeepSeek：稳定的王者。功能最少，完全没有屎化，最干净的应用，服务极度稳定，模型风格极度稳定，智力也相当稳定。建议保留在手机上。&lt;/li>
&lt;li>Kimi：模型能力一般，宣传的Agent能力强但是实际体验下来，模版化严重，大量prompt engineering痕迹。不建议使用。软件没有屎化，算是优点。&lt;/li>
&lt;li>Claude：代码的神。客户端表现一般，特别是搜索上不如Grok和ChatGPT thinking激进。还是适合工作期间使用。无屎化。可以保留在手机上，蹭免费额度。值得注意的是该公司有地域歧视性言论，令人担忧。&lt;/li>
&lt;li>Dola：抖音出品的客户端。模型能力一般。语音识别能力优秀。屎化严重。不建议使用。&lt;/li>
&lt;li>Copilot：依托微软搜索和ChatGPT的产品，软件平平无奇。不建议使用。&lt;/li>
&lt;li>Manus：早期依赖病毒营销，影响团队长期健康发展，该产品大概率在中长期慢性死亡。尽管有一定Agent能力，但不建议长期使用。&lt;/li>
&lt;li>Perplexity：模型能力一般。软件使用逻辑略微混乱。管理团队的歧视性言论令人担忧，不建议使用。&lt;/li>
&lt;li>MiniMax：模型能力优异，特别是Minimax M2模型，有很好的Agent能力。然而软件体验糟糕，甚至最基本的复制和粘贴功能都没有。不建议使用。&lt;/li>
&lt;li>通义：只能在中国区下载，软件屎化浓郁，打开后满屏幕运营内容令人窒息，无法继续使用。建议立即卸载。&lt;/li>
&lt;li>Qwen Chat：目前仍旧未上架iOS平台。是去屎化的通义，支持Qwen大模型。若能上架则推荐使用。&lt;/li>
&lt;/ul>
&lt;p>总结：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;strong>软件名称&lt;/strong>&lt;/th>
&lt;th>&lt;strong>模型能力&lt;/strong>&lt;/th>
&lt;th>&lt;strong>屎化现象&lt;/strong>&lt;/th>
&lt;th>&lt;strong>优点&lt;/strong>&lt;/th>
&lt;th>&lt;strong>缺点&lt;/strong>&lt;/th>
&lt;th>&lt;strong>建议&lt;/strong>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>z-ai（Chinese version）&lt;/td>
&lt;td>强大&lt;/td>
&lt;td>严重&lt;/td>
&lt;td>Agent能力强&lt;/td>
&lt;td>客户端屎化&lt;/td>
&lt;td>不建议使用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Yunbao&lt;/td>
&lt;td>一般&lt;/td>
&lt;td>轻度&lt;/td>
&lt;td>支持多种模型&lt;/td>
&lt;td>植入游戏内容&lt;/td>
&lt;td>不建议使用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Gemini&lt;/td>
&lt;td>出色&lt;/td>
&lt;td>无&lt;/td>
&lt;td>性能出众，体验良好&lt;/td>
&lt;td>服务不稳定&lt;/td>
&lt;td>强烈推荐使用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Grok&lt;/td>
&lt;td>强大&lt;/td>
&lt;td>轻微&lt;/td>
&lt;td>免费，搜索能力强&lt;/td>
&lt;td>少量运营化内容&lt;/td>
&lt;td>推荐使用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ChatGPT&lt;/td>
&lt;td>退步&lt;/td>
&lt;td>轻微&lt;/td>
&lt;td>功能多，用户基数大&lt;/td>
&lt;td>逐渐退下神位&lt;/td>
&lt;td>可保留&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>DeepSeek&lt;/td>
&lt;td>稳定&lt;/td>
&lt;td>无&lt;/td>
&lt;td>稳定，无屎化&lt;/td>
&lt;td>功能最少&lt;/td>
&lt;td>建议保留&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Kimi&lt;/td>
&lt;td>一般&lt;/td>
&lt;td>无&lt;/td>
&lt;td>无屎化&lt;/td>
&lt;td>模版化严重&lt;/td>
&lt;td>不建议使用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Claude&lt;/td>
&lt;td>强大&lt;/td>
&lt;td>无&lt;/td>
&lt;td>适合工作使用&lt;/td>
&lt;td>搜索表现一般&lt;/td>
&lt;td>可保留&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Dola&lt;/td>
&lt;td>一般&lt;/td>
&lt;td>严重&lt;/td>
&lt;td>语音识别优秀&lt;/td>
&lt;td>模型能力一般&lt;/td>
&lt;td>不建议使用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Copilot&lt;/td>
&lt;td>一般&lt;/td>
&lt;td>无&lt;/td>
&lt;td>微软支持&lt;/td>
&lt;td>平平无奇&lt;/td>
&lt;td>不建议使用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Manus&lt;/td>
&lt;td>一般&lt;/td>
&lt;td>无&lt;/td>
&lt;td>一定Agent能力&lt;/td>
&lt;td>长期发展不佳&lt;/td>
&lt;td>不建议使用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Perplexity&lt;/td>
&lt;td>一般&lt;/td>
&lt;td>轻微&lt;/td>
&lt;td>无显著优点&lt;/td>
&lt;td>歧视性言论&lt;/td>
&lt;td>不建议使用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>MiniMax&lt;/td>
&lt;td>优异&lt;/td>
&lt;td>无&lt;/td>
&lt;td>模型能力优异&lt;/td>
&lt;td>基本功能缺失&lt;/td>
&lt;td>不建议使用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>通义&lt;/td>
&lt;td>强大&lt;/td>
&lt;td>非常严重&lt;/td>
&lt;td>模型能力强大&lt;/td>
&lt;td>过度运营&lt;/td>
&lt;td>建议卸载&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Qwen&lt;/td>
&lt;td>强大&lt;/td>
&lt;td>无&lt;/td>
&lt;td>模型能力强大&lt;/td>
&lt;td>暂未上架iOS&lt;/td>
&lt;td>可保留&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>结论：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>强烈推荐使用的工具&lt;/strong>：
&lt;ul>
&lt;li>&lt;strong>Gemini&lt;/strong>：尽管存在服务不稳定的情况，模型性能出众且用户体验良好，适合工作和生活使用。&lt;/li>
&lt;li>&lt;strong>Grok&lt;/strong>：强大的搜索能力和免费使用权限，使其成为一个值得保留的选择。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>建议保留的工具&lt;/strong>：
&lt;ul>
&lt;li>&lt;strong>ChatGPT&lt;/strong>：尽管逐渐退下神位，免费版仍然满足大部分需求，且用户基数大。&lt;/li>
&lt;li>&lt;strong>DeepSeek&lt;/strong>：以稳定著称，没有任何过度运营化的问题，保持了工具的纯粹性。&lt;/li>
&lt;li>&lt;strong>Claude&lt;/strong>：在代码领域表现优异，适合在工作中使用。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>不建议使用的工具&lt;/strong>：
&lt;ul>
&lt;li>&lt;strong>智谱清言、Yunbao、Kimi、Dola、Copilot、Manus、Perplexity、MiniMax、通义&lt;/strong>：这些工具要么因为屎化现象严重，要么因为模型能力一般或者基本功能缺失，并不推荐长期使用。但其背后的模型还是可圈可点，例如Kimi、MiniMax、Qwen模型。可惜产品不够好用。另外，截止今天，Qwen仍旧未上架iOS应用市场。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>上述结论仅限用户客户端，发表时间是2025年11月。不涉及API调用。
对于工作和编程场景，Claude sonnet 4.5，ChatGPT-5.1-Codex，Grok Code Fast 1，MiniMax M2和GLM 4.6，Gemini 3 Pro 值得推荐&lt;/p>
&lt;/blockquote></description></item><item><title>心理学阅读书籍点评和推荐</title><link>https://nansenli.com/zh-cn/post/2025/09/books/</link><pubDate>Fri, 19 Sep 2025 22:13:06 +0100</pubDate><guid>https://nansenli.com/zh-cn/post/2025/09/books/</guid><description>&lt;p>在过去的一年里，我一直在阅读一些书籍，主要集中在心理学上。这些书帮助我读过了我人生中最困难的时期，给我了很大的帮助。&lt;/p>
&lt;h1 id="推荐的书籍">推荐的书籍
&lt;/h1>&lt;p>这些书是我阅读后比较值得推荐的书&lt;/p>
&lt;h2 id="自我关怀的力量">&lt;strong>自我关怀的力量&lt;/strong>
&lt;/h2>&lt;ul>
&lt;li>作者: 克里斯廷·内夫（Kristin Neff）&lt;/li>
&lt;li>阅读时间：2024年10月&lt;/li>
&lt;/ul>
&lt;p>这本书系统的讲解了缺乏自我关怀的原因和现象，并且给出了很多不同的自我关怀的实践方法。这些实践方法具有高度的可行性。我很欣赏里面的扮演者模式，因为这对我很有效果。&lt;/p>
&lt;p>这是第一次完整的阅读一本心理学的书籍，阅读完之后，我差不多能够理解自责，自我责备，自我攻击的原理，通常我也可以在第一时间发现这些心理活动，从而对自我进行干预。我觉得这本书算是我阅读心理学书籍的起点。&lt;/p>
&lt;h2 id="非暴力沟通">&lt;strong>非暴力沟通&lt;/strong>
&lt;/h2>&lt;ul>
&lt;li>作者: [美] 马歇尔·卢森堡&lt;/li>
&lt;li>阅读时间：2024年11月&lt;/li>
&lt;/ul>
&lt;p>这本书相当浅显易懂。观察-感受-需要-请求非常具有操作性。我觉得很有启发性。然而，实际上在生活中，我发现其实挺难操作的，因为涉及不同文化和语境的问题，以及人际关系微妙的情绪变化，可能会导致差别很大。可能需要不断思考才能做到吧。相当于一生的课题。&lt;/p>
&lt;h2 id="被讨厌的勇气">&lt;strong>被讨厌的勇气&lt;/strong>
&lt;/h2>&lt;ul>
&lt;li>作者: 岸见一郎 / 古贺史健&lt;/li>
&lt;li>阅读时间：2024年11月&lt;/li>
&lt;/ul>
&lt;p>这本书远超我的期待。它与我之前的想法很相似，还帮我完善了一些思考。我非常喜欢这本书。书中关于目的论的解释很深刻，超出了普通认知。我认为最有价值的收获是：我明白了情绪背后其实是需求。&lt;/p>
&lt;h2 id="活出生命的意义">&lt;strong>活出生命的意义&lt;/strong>
&lt;/h2>&lt;ul>
&lt;li>作者: [奥] 维克多·弗兰克&lt;/li>
&lt;li>阅读时间：2024年11月&lt;/li>
&lt;/ul>
&lt;p>这本书的前半部分主要讲的是集中营的故事。在后半部分，这本书开始讲解关于存在主义相关的内容。我很喜欢关于意义疗法的部分，以及关于反向意向法中涉及到的矛盾问题。&lt;/p>
&lt;h2 id="高难度谈话">&lt;strong>高难度谈话&lt;/strong>
&lt;/h2>&lt;ul>
&lt;li>作者: 道格拉斯·斯通 (Douglas Stone) / 布鲁斯·佩顿 (Bruce Patton) / 希拉·汉 (Sheila Heen)&lt;/li>
&lt;li>阅读时间：2024年12月&lt;/li>
&lt;/ul>
&lt;p>这本书相比非暴力沟通更加的复杂。这本书讲了很多的例子，并且谈到了很多关于谈话中的重点。其中我觉得特别重要的一点是认识到双方叙事的差异，特别是第三个故事，以及沟通的重要。&lt;/p>
&lt;h2 id="少有人走的路">&lt;strong>少有人走的路&lt;/strong>
&lt;/h2>&lt;ul>
&lt;li>作者：[美国] 斯科特·派克&lt;/li>
&lt;li>阅读时间：2025年1月&lt;/li>
&lt;/ul>
&lt;p>这本书比较传统一点，全篇书强调了努力，奉献，以及爱。虽然其中观点有些陈旧过时，不过读起来却是舒畅愉快的。&lt;/p>
&lt;h2 id="复杂性创伤后压力综合征中文译名不原谅也没关系">&lt;strong>复杂性创伤后压力综合征（中文译名：不原谅也没关系）&lt;/strong>
&lt;/h2>&lt;ul>
&lt;li>作者：[美] 皮特·沃克&lt;/li>
&lt;li>阅读时间：2025年1月&lt;/li>
&lt;/ul>
&lt;p>这本书非常详细的介绍了战逃僵屈四种反应，即战斗-逃跑-僵持-讨好四种应对外界的方法。这本书也系统性的讲了创伤对人的影响。&lt;/p>
&lt;h2 id="咨询心理仅中文">&lt;strong>咨询心理（仅中文）&lt;/strong>
&lt;/h2>&lt;ul>
&lt;li>作者：樊富珉 朱旭 何瑾&lt;/li>
&lt;li>阅读时间：2025年7月&lt;/li>
&lt;/ul>
&lt;p>这是一本中文书籍，这本书远远算不上好，或者佳作。这本书优秀的地方在于他系统性的介绍了整个心理学的体系架构，让我了解到目前心理学不同分支架构，关联，历史和现状。&lt;/p>
&lt;h2 id="爱的五种语言">爱的五种语言
&lt;/h2>&lt;ul>
&lt;li>作者：盖瑞·查普曼&lt;/li>
&lt;li>阅读时间：2025年8月&lt;/li>
&lt;/ul>
&lt;p>这本书很有名，也听过很久了，是一本关于如何去爱和感受爱的书。&lt;/p>
&lt;h2 id="关系的重建attached">关系的重建（Attached）
&lt;/h2>&lt;ul>
&lt;li>作者：阿米尔•莱文 蕾切尔•赫尔勒&lt;/li>
&lt;li>阅读时间：2025年9月&lt;/li>
&lt;/ul>
&lt;p>这本书也是非常著名的一本书，是整个依恋关系中相当重要的一本书。有无数的短视频、文章将书中的内容进行复述，我相信大多数人都听说过依恋类型的概念。然而，当读完这本书后，还是比之前在网络上看到其他关于依恋类型分析所收获的更多。&lt;/p></description></item><item><title>面试经验总结</title><link>https://nansenli.com/zh-cn/post/2025/07/interview2025/</link><pubDate>Tue, 29 Jul 2025 23:52:58 +0100</pubDate><guid>https://nansenli.com/zh-cn/post/2025/07/interview2025/</guid><description>&lt;p>这里总结一下过去一年来的所有面试经历。&lt;/p>
&lt;p>总体来说结果还算满意。从NG（应届生）到社招岗位都有在持续投递简历，最终拿到了一个满意的Offer，也算顺利上岸。希望这份经历对未来来爱尔兰读书、找工作的同学有所帮助。&lt;/p>
&lt;p>在进入具体内容前，先介绍一下我的背景：我之前在国内大厂工作了几年，后来来DCU读硕士，在爱尔兰找的是Software Engineer相关岗位。&lt;/p>
&lt;p>从去年10月（入学后不久）开始，我就陆续投递简历，主要是NG岗位和实习岗位。一直投到次年2月，其间只收到了一个Google实习的面试。后来因为实习比较忙，暂停了投递。等到考试结束（5月）后，我集中投了一批岗位，并陆续收到了多个面试机会。经过面试流程，最终拿到一个正式Offer。从这次集中投递到收到Offer，大约花了一个半月时间。&lt;/p>
&lt;p>我每次投简历都有做记录，总共至少投了81个岗位。&lt;/p>
&lt;p>下面是部分收到面试邀请的岗位记录：&lt;/p>
&lt;hr>
&lt;h3 id="google-实习岗位">&lt;strong>Google 实习岗位&lt;/strong>
&lt;/h3>&lt;p>视频面试两轮：&lt;/p>
&lt;ul>
&lt;li>第一轮是一道 LeetCode 中等难度的算法题；&lt;/li>
&lt;li>第二轮是一道简单题，但表现很差，猜测因此未通过。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="apple-测试岗位">&lt;strong>Apple 测试岗位&lt;/strong>
&lt;/h3>&lt;ul>
&lt;li>
&lt;p>第一轮视频面试：考察SQL和一些基础算法；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>第二轮考察内容较杂，从简历展开到测试理论、再到Python的 requests 包；&lt;/p>
&lt;p>整体感觉发挥一般，猜测未通过原因可能是略有不足，以及岗位与开发背景匹配度不高。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="apple-full-stack-岗位">&lt;strong>Apple Full Stack 岗位&lt;/strong>
&lt;/h3>&lt;p>虽然收到了面试邀请，但因为岗位在科克，而我已经决定在都柏林发展并租好了房子，所以直接婉拒了这次机会，略感遗憾。&lt;/p>
&lt;hr>
&lt;h3 id="meta-sse-岗位">&lt;strong>Meta SSE 岗位&lt;/strong>
&lt;/h3>&lt;p>当时正值考试周，与Recruiter协商推迟了一个月，结果之后对方就再也没联系，应该算是被ghost了。不过我并不觉得是自身问题，因为有朋友面试表现非常好，最后也没通过，可能与Meta今年的HC（headcount）缩减有关。&lt;/p>
&lt;hr>
&lt;h3 id="amazon-swe-岗位">&lt;strong>Amazon SWE 岗位&lt;/strong>
&lt;/h3>&lt;p>第一轮面试内容包括：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>BQ（行为问题）部分：回答还算可以；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>技术部分问的是OOP设计而非算法题，但我准备不足，对OOP不熟；&lt;/p>
&lt;p>再加上Amazon大多是Java岗位，我个人对Java兴趣一般，没通过也算顺其自然。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="ptc-swe-岗位">&lt;strong>PTC SWE 岗位&lt;/strong>
&lt;/h3>&lt;p>一开始电话中就明确表示不提供工签。我提到可以先以1G身份入职，场面略显尴尬。虽然后续还是走完了流程，但最终没有推进。&lt;/p>
&lt;hr>
&lt;h3 id="ebay-swe">&lt;strong>eBay SWE&lt;/strong>
&lt;/h3>&lt;p>Recruiter进行了初步沟通，问了一些基本问题，之后未进入下一轮。推测是已有更合适人选。&lt;/p>
&lt;hr>
&lt;h3 id="workday">&lt;strong>Workday&lt;/strong>
&lt;/h3>&lt;p>Recruiter初步交流很顺利，进入等待流程阶段。原定面试时间临时被取消，之后又重新预约。因为那时我已经在等待Arista的正式Offer，所以主动停止了推进。&lt;/p>
&lt;hr>
&lt;h3 id="arista">&lt;strong>Arista&lt;/strong>
&lt;/h3>&lt;p>Recruiter首次联系是在我在健身房时，声音太嘈杂，约定稍后再联系。之后HR发送了第一次面试的时间。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>第一次面试&lt;/strong>：由于技术故障未能顺利开始，重新约时间后进行了两道算法题，通过；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>第二轮&lt;/strong>：现场面试，继续两道算法题，第二题难度较高，花了一个小时完成，通过；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>第三轮&lt;/strong>：同样是现场面试，问题更深入；&lt;/p>
&lt;p>面试通过后，等待总部审批正式Offer，整个流程大概等了一个月。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="经验总结">&lt;strong>经验总结&lt;/strong>
&lt;/h2>&lt;ul>
&lt;li>&lt;strong>NG和社招岗位都可以投递&lt;/strong>，不要因为“overqualified”而自我限制，实际并没有那么大的影响；&lt;/li>
&lt;li>&lt;strong>视频面试的压力比预期大&lt;/strong>：平时能做出的题，面试时可能连简单题都发挥不好；&lt;/li>
&lt;li>&lt;strong>刷题要系统&lt;/strong>：建议至少刷100道LeetCode，200道是一个比较稳妥的水平，再往上看时间精力，追求性价比；&lt;/li>
&lt;li>&lt;strong>别怕被拒&lt;/strong>：每次拒绝都是一次筛选，帮助你找到更适合自己的岗位；要及时总结经验，继续下一家；&lt;/li>
&lt;li>&lt;strong>心态要稳&lt;/strong>：投递简历本质就是概率问题，关键是持续输出+良好积累。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>祝大家也都能顺利上岸！&lt;/p></description></item><item><title>Python uv 包学习</title><link>https://nansenli.com/zh-cn/post/2025/05/pythonuv/</link><pubDate>Sun, 11 May 2025 21:02:54 +0100</pubDate><guid>https://nansenli.com/zh-cn/post/2025/05/pythonuv/</guid><description>&lt;p>最近在看MCP相关的内容，其中很多都提到了使用uv来管理Python的包依赖。通过了解，uv看起来是一个很有用的工具，这里总结一下功能。&lt;/p>
&lt;h3 id="uv到底是什么呢">uv到底是什么呢？
&lt;/h3>&lt;p>uv首先是一个包，是使用rust语言写的一个Python包，安装后，可以使用uv命令。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">pip&lt;/span> &lt;span style="color:#a6e22e">install&lt;/span> &lt;span style="color:#a6e22e">uv&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="uv能做什么">uv能做什么？
&lt;/h3>&lt;ol>
&lt;li>能代替pip&lt;/li>
&lt;/ol>
&lt;p>在平时使用的pip命令前，加上uv就好了。既然如此，为什么一定要用uv，而不是pip？答案是因为非常块，据统计，是pip的77倍。&lt;/p>
&lt;ol start="2">
&lt;li>能代替venv或virtualenv&lt;/li>
&lt;/ol>
&lt;p>少装一个包，命令更整洁。uv 在创建虚拟环境和安装包方面比 &lt;code>venv&lt;/code> 和 &lt;code>virtualenv&lt;/code> 快很多。&lt;/p>
&lt;ol start="3">
&lt;li>能代替poetry&lt;/li>
&lt;/ol>
&lt;p>问题来了，poetry是什么？poetry是用来更方便的管理Python的依赖问题。那为什么不用pip + requirements 呢？答案是麻烦。另外，requirements文件，不能锁住依赖的依赖，比如，我锁住了A这个包的版本，但是A又依赖B、C、D，而这使得“在我电脑上能运行但在别人的电脑上无法运行”问题很常见。现在，有了uv，poetry也被代替了。&lt;/p>
&lt;ol start="4">
&lt;li>能用来代替pipx&lt;/li>
&lt;/ol>
&lt;p>问题又来了，pipx是啥？pipx是一个安装Python工具但是不污染环境的工具。&lt;/p>
&lt;p>举个例子，假如我需要安装一个命令行工具，我会运行&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">pip&lt;/span> &lt;span style="color:#a6e22e">install&lt;/span> &lt;span style="color:#a6e22e">httpie&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>但是它可能会污染我的环境，卸载还会有副作用。而现在，我可以运行&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">pipx&lt;/span> &lt;span style="color:#a6e22e">install&lt;/span> &lt;span style="color:#a6e22e">httpie&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>不会污染环境。&lt;/p>
&lt;p>不过呢，现在这条命令也被代替了，现在可以运行&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">uv&lt;/span> &lt;span style="color:#a6e22e">tool&lt;/span> &lt;span style="color:#a6e22e">install&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>一句话总结uv：Python 依赖管理的“下一代事实标准”。&lt;/p></description></item><item><title>Dublin City University MSc in Computing: Secure Software Engineering 考试经验</title><link>https://nansenli.com/zh-cn/post/2025/04/dcu-secure-software-exam-cookbook/</link><pubDate>Sun, 04 May 2025 12:00:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2025/04/dcu-secure-software-exam-cookbook/</guid><description>&lt;p>我于这学期完成了DCU 的 Secure Software Engineering的所有考试，我在这里分享一下相关经验。&lt;/p>
&lt;p>更新：我的成绩已经出来了，平均分是75分，达到了一等一的学位水平。祝大家都能取得满意的成绩。&lt;/p>
&lt;p>该项目总共包含8门课程，其中包含上半学期4门课程，下半学期4门课程，所有课程均需要考试通过，难度不小。&lt;/p>
&lt;p>要想通过考试，其中最重要的有以下几点：&lt;/p>
&lt;ol>
&lt;li>参与课堂。尽量参与每一堂课，课堂可以加深学习的印象，而且有时会明确说明考试考察的内容方向。例如，今年考察的某个题的题型是某一个特定方向的题型。这对通过考试帮助很大。&lt;/li>
&lt;li>仔细做近年的题目。前往&lt;a class="link" href="dcu.guruarchive.com" >dcu.guruarchive.com&lt;/a>下载历年的题目。仔细研究题型，若课程变动不大，则过去两年的题型可能不会考察。若课程变动，则过去两年的题型作为主要考察内容。建议至少做2年，最多做5年的题目。&lt;/li>
&lt;li>仔细完成作业内容。每次提交的作业内容都将直接评分，并决定最终的成绩。通常来说，作业内容占据考试成绩的25%。由于作业是离线的，并不要求背诵和记忆任何内容，因此，只要肯付出努力，就可以取得较高的平时成绩，极大缓解考试通过的压力。&lt;/li>
&lt;/ol>
&lt;h1 id="考试概述">考试概述
&lt;/h1>&lt;h2 id="上半学期课程">上半学期课程
&lt;/h2>&lt;ol>
&lt;li>Cryptography and Number Theory&lt;/li>
&lt;li>System Software/BlockChain&lt;/li>
&lt;li>Secure Programming&lt;/li>
&lt;li>Professional &amp;amp; Research Practice&lt;/li>
&lt;/ol>
&lt;h2 id="下半学期课程">下半学期课程
&lt;/h2>&lt;ol>
&lt;li>Formal Programming&lt;/li>
&lt;li>Concurrent Programming&lt;/li>
&lt;li>Software Process Quality&lt;/li>
&lt;li>Network Security&lt;/li>
&lt;/ol>
&lt;p>上学期，课程压力不算大。其中重点的课程是Secure Programming以及Cryptography and Number Theory。另外还有一门课程，是System Software/BlockChain，具体是哪门课程，要看学校的安排。如果是BlockChain，则相对轻松，如果是System Software，则有一定压力。Professional &amp;amp; Research Practice这门课程相对轻松。总体而言，上半学期的课程相对轻松，有两门课是考试重点。&lt;/p>
&lt;p>下学期课程，课程压力较大。几乎所有课程都有一定的难度。稍微容易一点的Software Process Quality 也不太容易取得高分。因此学业压力会有一点大。&lt;/p>
&lt;p>下面是关于每一门考试的细节。&lt;/p>
&lt;h1 id="cryptography-and-number-theory">Cryptography and Number Theory
&lt;/h1>&lt;ul>
&lt;li>课程难度：⭐️⭐️⭐️⭐️⭐️&lt;/li>
&lt;li>考试难度：⭐️⭐️⭐️⭐️⭐️&lt;/li>
&lt;/ul>
&lt;p>这门课程的前面几节课难度尚可，在哈希函数之后的内容，难度飙升。特别是关于Cryptanalysis和Number Theory的部分。不幸的是，难的内容反而是考试的重点。&lt;/p>
&lt;p>想要通过这门课程，并且取得高分，并且花费最少的时间，建议操作方式如下：&lt;/p>
&lt;ol>
&lt;li>认真听课，不缺席课堂。&lt;/li>
&lt;li>在上课前，花费1小时时间，简单预习。对于Cryptanalysis和Number Theory的部分，花费2个小时预习。&lt;/li>
&lt;li>如果课堂内容没有听懂，特别是密码分析和数论的部分，不需要太担心，只需要跟上课堂的节奏即可。&lt;/li>
&lt;li>认真完成大作业，一般是线性攻击或者差分攻击分析，由于大作业必然出现在考试题目中，如果大作业不是自己认真完成的，则在考试中会带来挑战。作业难度相当高，并且高达25分。想要拿到满分，需要花费至少4天的时间，来完成程序的编写和验证。我的题目是&lt;strong>Feistel 4 的差分密码分析。&lt;/strong>&lt;/li>
&lt;li>在考试前，仔细做过去四年的卷子。并把课堂上没有听懂的密码学内容弄明白。&lt;/li>
&lt;/ol>
&lt;h1 id="system-softwareblockchain">System Software/BlockChain
&lt;/h1>&lt;p>这门课程需要根据学校的设置。我参与的课程最终上的是BlockChain&lt;/p>
&lt;ul>
&lt;li>课程难度：⭐️⭐️&lt;/li>
&lt;li>考试难度：⭐️⭐️⭐️&lt;/li>
&lt;/ul>
&lt;p>BlockChain的内容主要是视频课程，并不需要参与线下的课程。这使得上学期的安排更加轻松。&lt;/p>
&lt;p>作业内容是一份报告，以及2到3人的关于密码学的PPT演讲。&lt;/p>
&lt;p>总体来说，这门课程理解上不需要花费太多的时间，考试也不算太难。尽管如此，这门课程通过难度不高，但是想要取得高分却很困难。若想要取得高分，需要深入理解考试卷子的内容，认真准备课程作业，并且背诵大量内容，同时在考试分配好时间，尽可能书写多的内容。&lt;/p>
&lt;p>如何通过：&lt;/p>
&lt;ul>
&lt;li>熟练背诵过去4年卷子中出现的题目即可。考试题目多为区块链的问答题，可以背诵模板回答。&lt;/li>
&lt;li>认真准备PPT演讲的assignment以及对应的论文。论文需要有一定深度。&lt;/li>
&lt;/ul>
&lt;h1 id="secure-programming">Secure Programming
&lt;/h1>&lt;ul>
&lt;li>课程难度：⭐️⭐️⭐️⭐️⭐️&lt;/li>
&lt;li>考试难度：⭐️⭐️⭐️⭐️⭐️&lt;/li>
&lt;/ul>
&lt;p>我个人认为这是最有含金量的课程了。&lt;/p>
&lt;p>这门课程的内容相当丰富，包含了计算机从顶层到底层的所有内容，例如Linux，操作系统，C语言编程，逆向工程，数据结构，GDB调试等等。&lt;/p>
&lt;p>这门课程不包含作业，取而代之的是两场小的随堂考试。因此，这门考试加上期末考试，总共会有3场考试。&lt;/p>
&lt;p>其中第一场考试是期末考试的预演，题型类似。第二场考试则是一个独立的，安全攻击的考试，需要自己研究并编写代码，进行攻击。&lt;/p>
&lt;p>如何通过这门课程&lt;/p>
&lt;ol>
&lt;li>尽可能的参与课堂，并且尽最大努力去记忆课堂出现的所有信息。要想通过这门课程，需要有非常扎实的计算机技术特别是计算机底层原理的相关知识储备。&lt;/li>
&lt;li>取得平时考试的高分。这门课的老师非常的好，他总是会尽可能的将考点内容提前演练，所有的题目都是来自曾经考察过的内容的变体，因此，好好准备教授提供的复习材料是取得平时考试成绩的关键。&lt;/li>
&lt;li>熟练做往年的卷子，保证彻底理解往年题目，特别是Linux Permissions 的部分。&lt;/li>
&lt;li>考试时，6道题目可以选择5道题目回答，可以选择其中一题放弃，可以选择放弃关于安全攻击的那一题，因为安全攻击的已经随堂考察过了，本身内容也较多不好复习。你也可以根据自己水平放弃一题。&lt;/li>
&lt;/ol>
&lt;h1 id="professional--research-practice">Professional &amp;amp; Research Practice
&lt;/h1>&lt;ul>
&lt;li>课程难度：⭐️⭐️⭐️&lt;/li>
&lt;li>考试难度：⭐️⭐️⭐️&lt;/li>
&lt;/ul>
&lt;p>这门课程的平时成绩占比在50%。其中包含一个文献综述，一个视频演讲，一个论文。&lt;/p>
&lt;p>考试成绩占比也是50%。&lt;/p>
&lt;p>对于课堂的部分，可以参加也可以不参加。课堂主要以答疑为主。&lt;/p>
&lt;p>如何通过这门课程：&lt;/p>
&lt;ol>
&lt;li>作业内容尽量认真对待，取得一个很好的平时成绩的分数。想要获得高分，其中对于论文的部分，格式非常的关键。视频演讲的部分，需要足够的吸引人。&lt;/li>
&lt;li>熟练背诵过去3年的考试题目。尽管考试可以二选一回答，仍旧需要准备所有的考察内容。&lt;/li>
&lt;li>需要自己准备一份历年的标准答案。需要对整个课程的所有大纲有一个大致的了解。接下来，检查考试卷子的每一道题，使用对应的部分的材料，生成该题目的答案，然后背诵。&lt;/li>
&lt;li>平时课堂可参与也可以不参与。因为主要是答疑为主。偶尔穿插一些关于practicum以及找工作的相关信息。&lt;/li>
&lt;/ol>
&lt;h1 id="formal-programming">Formal Programming
&lt;/h1>&lt;ul>
&lt;li>课程难度：⭐️⭐️⭐️⭐️⭐️&lt;/li>
&lt;li>考试难度：⭐️⭐️⭐️⭐️&lt;/li>
&lt;/ul>
&lt;p>这门课程的难度非常的高，其中涉及到复杂的逻辑推理过程。好在考察的难度还属于可接受的范围。&lt;/p>
&lt;p>如何通过这门课程：&lt;/p>
&lt;ol>
&lt;li>尽量参与每堂课，对课堂的内容进行听讲，尝试跟上并理解课堂内容，例如一些公式、原理或者推导过程。&lt;/li>
&lt;li>认真完成课堂第三节课的作业题目，也就是Event-B建模题目。&lt;/li>
&lt;li>对于课堂内容，如果没有听懂，不用太担心，考前复习时弄明白即可，特别是各种Law，大致有印象即可。&lt;/li>
&lt;li>在考试前，认真完成过去3年的卷子，将课堂上没有听懂的内容完全搞明白。&lt;/li>
&lt;/ol>
&lt;h1 id="concurrent-programming">Concurrent Programming
&lt;/h1>&lt;ul>
&lt;li>课程难度：⭐️⭐️⭐️&lt;/li>
&lt;li>考试难度：⭐️⭐️⭐️⭐️⭐️&lt;/li>
&lt;/ul>
&lt;p>这门课程的难度并不算高，但是需要考察的内容非常多。&lt;/p>
&lt;p>考试需要背诵大量的内容，需要会书写大量的程序片段，API接口，实现大量的方案、原型设计等等。&lt;/p>
&lt;p>如何通过这门课：&lt;/p>
&lt;ol>
&lt;li>平时作业需要认真完成。&lt;/li>
&lt;li>对于考试，需要大量的做练习题，需要至少做5年以上的卷子才会比较保险。尽管题目6选5，但是由于题目考察不固定，难度波动大，建议所有题型全部复习，而不是针对性的放弃某个题型的题目。&lt;/li>
&lt;li>参与每一堂课，尽可能对课堂中出现过的所有内容有一个大致的了解和记忆。因为考试涉及面广泛，所有出现过的内容都可能会是考点。&lt;/li>
&lt;li>由于考试内容大量涉及到背诵和记忆类的内容，建议在上课时，提前确认当前所讲的内容是否是考试的内容，若是，则需要在课堂上尽可能的记住。&lt;/li>
&lt;li>可是将历年试卷整理成资料库，在课堂上询问AI该部分的内容是否是考卷的内容。若是，则认真听讲。&lt;/li>
&lt;li>这门课非常建议先浏览试卷，弄明白考察的内容。建议和AI沟通，确认过去3年的题型规律。由于这门课主要考背诵和记忆，因此课堂吸收很关键。&lt;/li>
&lt;/ol>
&lt;h1 id="software-process-quality">Software Process Quality
&lt;/h1>&lt;ul>
&lt;li>课程难度：⭐️⭐️&lt;/li>
&lt;li>考试难度：⭐️⭐️⭐️&lt;/li>
&lt;/ul>
&lt;p>这门课程难度并不高，但考察的内容则比较杂。&lt;/p>
&lt;ol>
&lt;li>考试复习时，优先复习重复往年卷子中考察的内容，例如Karnaugh和 Clause Coverage的题目。由于题目题型波动较大，尽管是6选5考察，仍旧建议复习所有出现过的题目类型。&lt;/li>
&lt;li>课堂可以参与也可以自学。&lt;/li>
&lt;li>作业内容并不难，认真对待，恰当的选择合适的题目可以取得高分。&lt;/li>
&lt;/ol></description></item><item><title>2025 年 4 月科技随笔2</title><link>https://nansenli.com/zh-cn/post/2025/04/tech250402/</link><pubDate>Sun, 20 Apr 2025 12:58:30 +0100</pubDate><guid>https://nansenli.com/zh-cn/post/2025/04/tech250402/</guid><description>&lt;h1 id="github-copoilt">Github copoilt
&lt;/h1>&lt;p>&lt;img src="https://nansenli.com/img/rescued/6f49f8b9f1ec.png"
loading="lazy"
alt="Image.png"
>&lt;/p>
&lt;p>面对陌生的Github项目，再也不用害怕看不懂了。点击项目主页的Copolit图标，即可对话，还可使用GPT4.1交流，高达1M窗口上下文，从“这个项目能用来做什么”到“如何部署和处理bug”之类的问题，都能很好回答。&lt;/p>
&lt;h1 id="dia-浏览器">Dia 浏览器
&lt;/h1>&lt;p>&lt;img src="https://nansenli.com/img/rescued/ba4da2cf2850.png"
loading="lazy"
alt="Image.png"
>&lt;/p>
&lt;p>&lt;a class="link" href="https://www.diabrowser.com/" target="_blank" rel="noopener"
>https://www.diabrowser.com/&lt;/a>&lt;/p>
&lt;p>目前还未公测，可以通过上面的链接提交邮箱进入等待名单。&lt;/p>
&lt;p>Arc浏览器确实很好用，它拥有稳定、成熟且一致的体验，不单纯的只是侧边栏标签创新那么简单。&lt;/p>
&lt;p>Dia是Arc浏览器的下一代产品，其实不太理解为什么要重新发布一个新的产品，直接在Arc浏览器上做整合应该是最好的。现在创始人的想法反而是搁置Arc浏览器了。&lt;/p>
&lt;p>从产品体验上来看，Dia浏览器似乎分为下面几个部分：&lt;/p>
&lt;ul>
&lt;li>一个内置的AI助手，可以进行日常对话&lt;/li>
&lt;li>一个类似nanobrowser或browser-use的浏览器操作助手，帮助规划和操作浏览器&lt;/li>
&lt;li>一个AI写作助手，可以在光标处生成文本&lt;/li>
&lt;/ul>
&lt;p>很有潜力的产品&lt;/p>
&lt;h1 id="gpt-o3--o4-mini--o4-mini-high-41--41-mini--41-nano-发布">GPT o3 / o4-mini / o4-mini-high/ 4.1 / 4.1 mini / 4.1 nano 发布
&lt;/h1>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;strong>模型&lt;/strong>&lt;/th>
&lt;th>&lt;strong>上下文窗口&lt;/strong>&lt;/th>
&lt;th>&lt;strong>输入定价&lt;/strong>&lt;/th>
&lt;th>&lt;strong>输出定价&lt;/strong>&lt;/th>
&lt;th>&lt;strong>图片定价&lt;/strong>&lt;/th>
&lt;th>&lt;strong>主要特性&lt;/strong>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>o4‑mini‑high&lt;/td>
&lt;td>200K 令牌&lt;/td>
&lt;td>$1.10/M&lt;/td>
&lt;td>$4.40/M&lt;/td>
&lt;td>$0.842/K 图像输入&lt;/td>
&lt;td>o4‑mini 的高推理努力版本；高吞吐、低延迟；支持链式调用与结构化输出&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>o4‑mini&lt;/td>
&lt;td>200K 令牌&lt;/td>
&lt;td>$1.10/M&lt;/td>
&lt;td>$4.40/M&lt;/td>
&lt;td>$0.842/K 图像输入&lt;/td>
&lt;td>紧凑型推理；AIME 99.5%、SWE‑bench 高分；优秀 STEM、视觉与代码编辑能力&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>o3&lt;/td>
&lt;td>200K 令牌&lt;/td>
&lt;td>$10/M&lt;/td>
&lt;td>$40/M&lt;/td>
&lt;td>$7.65/K 图像输入&lt;/td>
&lt;td>通用型强模型；顶级数学、科学、编码与视觉推理；需 BYOK&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GPT‑4.1&lt;/td>
&lt;td>1.05M 令牌&lt;/td>
&lt;td>$2/M&lt;/td>
&lt;td>$8/M&lt;/td>
&lt;td>N/A&lt;/td>
&lt;td>旗舰大模型；百万令牌上下文；卓越指令跟随、企业级检索与多模态理解&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GPT‑4.1 Mini&lt;/td>
&lt;td>1.05M 令牌&lt;/td>
&lt;td>$0.40/M&lt;/td>
&lt;td>$1.60/M&lt;/td>
&lt;td>N/A&lt;/td>
&lt;td>中型版本；接近 GPT‑4o 性能；低延迟、低成本；强编码与视觉理解&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GPT‑4.1 Nano&lt;/td>
&lt;td>1.05M 令牌&lt;/td>
&lt;td>$0.10/M&lt;/td>
&lt;td>$0.40/M&lt;/td>
&lt;td>N/A&lt;/td>
&lt;td>系列中最快最廉价；百万令牌上下文；适合分类与自动补全任务&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>GPT-4.1 Nano 天生适合翻译，成本非常低，例如配置在沉浸式翻译，开启中英双语字幕，平均每一集黑镜，仅需要0.5美分，翻译完7季只需要0.25美元，成本非常低。&lt;/p>
&lt;p>GPT-4.1 的上下文窗口终于来到1M了，之前GPT写代码简直被吊打，现在只能说追平了。&lt;/p>
&lt;p>o3、o4-mini、o4-mini-high表现非常惊喜，有一种小的deep research感觉。&lt;/p>
&lt;p>目前，我的日常基本上变成了：&lt;/p>
&lt;ul>
&lt;li>o4-mini： 简单搜索和总结，可容忍失误&lt;/li>
&lt;li>o4-mini-high: 复杂搜索、总结、多步推理&lt;/li>
&lt;li>o3: 对准确率要求较高的复杂搜索、总结、多步推理&lt;/li>
&lt;li>4o: 直觉类和记忆知识类的回答&lt;/li>
&lt;/ul>
&lt;p>而且我统计了每个模型的使用次数，差不多几乎都在使用，没有说哪个模型派不上用场的，不像是o3-mini幻觉太高基本就用不了。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/018788a20986.png"
loading="lazy"
alt="Image.png"
>&lt;/p>
&lt;h1 id="元宝接入微信联系人">元宝接入微信联系人
&lt;/h1>&lt;p>元宝微信，可以直接添加联系人发送信息。支持语音消息和图片消息。是豆包的竞品。优点是无需安装额外的应用。之前给父母推荐了豆包，他们都很喜欢使用。如果元宝也能支持语言通话，甚至视频聊天的话，可能会更有价值。&lt;/p>
&lt;h1 id="openai免费令牌">OpenAI免费令牌
&lt;/h1>&lt;p>&lt;img src="https://nansenli.com/img/rescued/7c4b97a270c2.png"
loading="lazy"
alt="Image.png"
>&lt;/p>
&lt;p>OpenAI 现在推出免费领牌活动，可以在Data controls中的sharing开启分享日志，即可领取。可惜就是时间太短了，只有一两周的时间，基本上用不完这么多额度。&lt;/p>
&lt;h1 id="github-其他日榜周榜相关">Github 其他日榜周榜相关
&lt;/h1>&lt;p>&lt;a class="link" href="https://github.com/kestra-io/kestra" target="_blank" rel="noopener"
>https://github.com/kestra-io/kestra&lt;/a>&lt;/p>
&lt;p>自动化运行平台，竞品包括：Airflow（数据管道）、Zapier（低代码营销自动化）、Rundeck（运维自动化）、Camunda （电商业务自动化）&lt;/p>
&lt;p>&lt;a class="link" href="https://github.com/Zackriya-Solutions/meeting-minutes" target="_blank" rel="noopener"
>https://github.com/Zackriya-Solutions/meeting-minutes&lt;/a>&lt;/p>
&lt;p>一款语音转文本的软件，苹果 voice memos 的竞品。&lt;/p>
&lt;p>&lt;a class="link" href="https://github.com/maybe-finance/maybe" target="_blank" rel="noopener"
>https://github.com/maybe-finance/maybe&lt;/a>&lt;/p>
&lt;p>个人财报管理，类似有知有行&lt;/p>
&lt;p>&lt;a class="link" href="https://github.com/supabase-community/supabase-mcp" target="_blank" rel="noopener"
>https://github.com/supabase-community/supabase-mcp&lt;/a>&lt;/p>
&lt;p>在本地使用 AI 开发软件的时候，用于连接数据库的软件，基于 MCP 协议实现。若 MCP 真实未来标准协议之一，那数据库本身可能就会支持 MCP，可能会形成竞品。&lt;/p>
&lt;p>&lt;a class="link" href="https://github.com/langgenius/dify" target="_blank" rel="noopener"
>https://github.com/langgenius/dify&lt;/a>&lt;/p>
&lt;p>低代码 LLM 平台，LangChain的竞品，在现阶段的应用领域可部分代替LangChain。&lt;/p></description></item><item><title>2025 年 4 月科技随笔</title><link>https://nansenli.com/zh-cn/post/2025/04/tech2504/</link><pubDate>Tue, 15 Apr 2025 20:45:20 +0100</pubDate><guid>https://nansenli.com/zh-cn/post/2025/04/tech2504/</guid><description>&lt;h1 id="前言">前言
&lt;/h1>&lt;p>打算从这期记录一些每周的科技相关的随笔，还有一些感悟之类的。大概每两周到一个月更新一期吧。&lt;/p>
&lt;h1 id="个人博客从-hugo-替换成-hexo">个人博客从 Hugo 替换成 Hexo
&lt;/h1>&lt;p>这周开始，我把博客从 Hexo 迁移到 Hugo，主要是因为：&lt;/p>
&lt;p>Hugo 对 i18n 支持更好。因为 Hexo 对 i18n 并不太支持，之前自己魔改了模板，最后实现了中文和英文切换，但是维护成本很高，了解后发现 Hugo 原生支持多语言。&lt;/p>
&lt;p>不过在切换后，发现也有以下一些优点：&lt;/p>
&lt;p>主题配置更简单。我使用的是&lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack" target="_blank" rel="noopener"
>Stack&lt;/a> 主题，基本上没有遇到坑。官方自带的 example 配置就很好，照这配置就好。另外，还带 Google Analysis 分析，还带 RSS 源，源也会自动区分语言，非常舒适。&lt;/p>
&lt;p>在切换的过程中，基本上没有遇到什么障碍，只要有你过去的 Markdown 文件，直接复制到新工程的目录下，再强制推送到原来的 github pages仓库，就可以直接迁移过来。强制推送后，需要在 github重新配置一下自定义域名，就无损升级了，包括评论之类的都是一样的。&lt;/p>
&lt;p>唯一不同的是，Hugo 默认使用小写链接，而 Hexo 默认保留大小写，这可能使得过去的一些链接失效，搜索引擎需要重新建立索引。&lt;/p>
&lt;h1 id="神秘模型optimus-alpha出现在httpsopenrouteraihttpsopenrouterai-并霸榜">神秘模型Optimus Alpha出现在&lt;a class="link" href="https://openrouter.ai/" target="_blank" rel="noopener"
>https://openrouter.ai/&lt;/a> 并霸榜
&lt;/h1>&lt;p>&lt;img src="https://nansenli.com/img/rescued/a7835acf9e85.png"
loading="lazy"
alt="Image.png"
>&lt;/p>
&lt;p>由于该模型免费，并且性能很强，在上线后，就被选择成为 OpenRouter 的首席流量处理模型。&lt;/p>
&lt;p>随着14 号 OpenAI 官方公布 GPT4.1 后，该模型从 OpenRouter 下架。显然，该模型就是 GPT4.1。&lt;/p>
&lt;p>GPT4.1 是一个编程能力特化的模型，拥有 1M 的上下文窗口。确实，现在编程领域，OpenAI 被 Claude 3.7 还有 Gemini2.5pro 打的鼻青脸肿，无论是 o1 还是 o3 模型，都没能在编程领域取得什么领先地位。希望 OpenAI 再接再厉吧。&lt;/p>
&lt;blockquote>
&lt;p>OpenRouter 是一个让用户&lt;strong>统一接入多个大语言模型 API&lt;/strong> 的中间层服务平台。你可以把它理解成一个「代理」或「路由器」，统一管理不同大模型厂商（OpenAI、Anthropic、Mistral、Google Gemini、Cohere 等）的调用方式。&lt;/p>
&lt;/blockquote>
&lt;p>看起来，这次，OpenAI 选择了 OpenRouter 作为测试发布平台，而不是&lt;a class="link" href="https://lmarena.ai/" target="_blank" rel="noopener"
>https://lmarena.ai/&lt;/a>&lt;/p>
&lt;h1 id="openrouterai">OpenRouter.ai
&lt;/h1>&lt;p>&lt;a class="link" href="https://openrouter.ai/" target="_blank" rel="noopener"
>OpenRouter&lt;/a>&lt;/p>
&lt;p>这是一个 LLM平台，包含大量的免费/收费高质量模型。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/def8fd9ff726.png"
loading="lazy"
alt="Image.png"
>&lt;/p>
&lt;p>选择 Prompt pricing 为 0 元，再选择 Top Weekly，就可以看到目前最好的免费模型。&lt;/p>
&lt;p>不过使用时需要开启 log 和使用数据进行训练。&lt;/p>
&lt;p>目前可以选择 Gemini 2.5 pro。&lt;/p>
&lt;p>其中Ranking功能可以实时查看平台上所有的流量分发情况，哪个模型好用一目了然。&lt;/p>
&lt;h1 id="nanobrowser">nanobrowser
&lt;/h1>&lt;p>&lt;img src="https://nansenli.com/img/rescued/8d34bb10e9ba.png"
loading="lazy"
alt="Image.png"
>&lt;/p>
&lt;p>一款用于 Chrome 的浏览器插件，可以控制浏览器的自动化操作。&lt;/p>
&lt;p>试验了一下，无法支持 Arc 浏览器。体验下来能力有限，例如，对网页的滚动偶尔失效，无法总结整个页面的内容，只能看到局部的内容，操作偶尔不正确。&lt;/p>
&lt;p>虽然该产品不太推荐，不过这可能是未来一个方向，看起来，以后会出现大量的 AI 代理。&lt;/p>
&lt;p>抢票、刷分之类的灰色产业估计会很开心。&lt;/p>
&lt;h1 id="markitdown">markitdown
&lt;/h1>&lt;p>&lt;img src="https://nansenli.com/img/rescued/e37c2c0e7d7a.png"
loading="lazy"
alt="Image.png"
>&lt;/p>
&lt;p>一款用于将不同格式的内容转换成 Markdown 格式的工具。&lt;/p>
&lt;p>离线情况下，可以处理不同的文件转换成 Markdown 格式，试用了一下，转换得比较基础。&lt;/p>
&lt;p>也可以支持接入 LLM，提升转换结果和文档中图片的部分。&lt;/p>
&lt;p>在未来可能是一款不错的 MCP 服务。&lt;/p>
&lt;h1 id="pdf-search">PDF search
&lt;/h1>&lt;p>&lt;img src="https://nansenli.com/img/rescued/d1310e598823.png"
loading="lazy"
alt="Image.png"
>&lt;/p>
&lt;p>一款应用，可以快速搜索多个 pdf 中出现的内容。&lt;/p>
&lt;p>使用类似模糊搜索的方式，原理是对 PDF 做了分割并且进行了嵌入向量计算，随后和搜索词进行匹配。&lt;/p>
&lt;p>适合考试期间搜索文档使用。&lt;/p>
&lt;p>缺点是，index 是按照页面划分的，搜索后需要自己在页面中找到对应的 part。&lt;/p></description></item><item><title>爱尔兰留学不完全攻略</title><link>https://nansenli.com/zh-cn/post/2025/04/ireland-study-guide/</link><pubDate>Thu, 10 Apr 2025 18:12:47 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2025/04/ireland-study-guide/</guid><description>&lt;p>其实这篇算不上攻略吧，只是对晨鹤的一些内容做一些个人的补充分享。&lt;/p>
&lt;p>晨鹤链接：&lt;a class="link" href="https://chenhe.me/post/ireland" target="_blank" rel="noopener"
>https://chenhe.me/post/ireland&lt;/a>&lt;/p>
&lt;p>晨鹤写的内容挺好，很详实，基本上解答了我很多当时来爱尔兰的困惑&lt;/p>
&lt;p>文章未完成，本文不定期更新。&lt;/p>
&lt;h1 id="背景">背景
&lt;/h1>&lt;p>地点在都柏林，录取的学校是 DCU。&lt;/p>
&lt;h2 id="出发前">出发前
&lt;/h2>&lt;ul>
&lt;li>直接点名型号吧，苏泊尔小绿锅压力锅。为什么必须要这个而不是其他的呢，因为：1. 压力蒸出来的米饭更好吃。 2. 炖煮类的，例如炖牛腩，卤鸡腿之类的，更方便。 我自己也带了小米的电饭煲，但是来了这边发现电饭煲煮的饭一点也不好吃，只有高压锅煮出来的饭好吃😋。另外，好像只有这款 10 分钟压力就能煮完米饭了，看了下其他款，并没有这么快的速度，我猜想可能和他的小容量有关。&lt;/li>
&lt;li>药品：强烈推荐带一盒感康，感冒的时候吃一粒马上就好多了。创可贴可以带一些透明的防水的那种。&lt;/li>
&lt;li>水杯：来了之后，发现想买到喜欢的水杯好难。如果人在国内，淘宝上买一个合适的水杯很重要。&lt;/li>
&lt;li>小红书：以前以为是个美妆 APP，在国内几乎没有用过。出国才发现这是个出国 APP，以下内容小红书上有大量攻略，可以酌情浏览，当然有的人不太喜并对其嗤之以鼻，萝卜白菜各有所爱吧（晨鹤不要打我）。&lt;/li>
&lt;li>驾照：我带了，但是来了就过期了，所以没怎么用到。如果有效期内的话，考驾照会减免考试所需要的时间。&lt;/li>
&lt;li>现金：其实不用换太多欧元。在国内办好一张双币信用卡，例如 visa 或者 master card，来了这边是可以直接刷卡的。这边现金消费的场景比较少。&lt;/li>
&lt;li>衣服：建议带多层的穿搭。由于这边天气多变，室内外温差大，可能会频繁增减衣物。冬天还是蛮冷的，羽绒服类似的厚度的可以带两件换着穿。&lt;/li>
&lt;li>行李箱：海南航空可以申请留学生认证，可以携带最多两个 28 寸的行李箱。不过对我来说太多了，我最后带了一个 20 的一个 28 的。另外 28 寸行李箱其实塞满也很容易超重。想带 26 寸的也可以。&lt;/li>
&lt;li>体验，牙科，手术等等：这边费用比较贵，在国内尽量都做了。例如补牙或者激光手术等等。&lt;/li>
&lt;li>转换插头：一个高质量的转换插头会很方便。可以考虑准备一些简单的转换插头，再买一个带多口充电口的转换插头。另外可以考虑购买一个特殊排插，头是英标的，插座是国标的。&lt;/li>
&lt;li>生活费：租房可能要 600-1200 欧，如果自己做饭，伙食每个月 500 欧，生活费可能是 1000-1800欧一个月，取决于个人消费水平。&lt;/li>
&lt;li>机票：海航直飞都柏林的机票不错，特别是，如果你从上海转机而不是在北京飞，机票会更便宜。也就是说多做一段飞机反而更便宜。&lt;/li>
&lt;li>临时住宿：可以在Airbnb 或者 Booking 上预定临时住宿。&lt;/li>
&lt;li>中餐调料：来了之后，你会发现每天的生活很大一部分就是买菜做饭。不过，基本上所有调料在本地中国超市有售卖，因此不必带太多调料品。具体售卖的清单可以搜索融兴行小程序353购查看。&lt;/li>
&lt;/ul>
&lt;h1 id="入境">入境
&lt;/h1>&lt;p>展示下面的内容即可：&lt;/p>
&lt;ul>
&lt;li>护照&lt;/li>
&lt;li>保险&lt;/li>
&lt;li>录取通知书&lt;/li>
&lt;/ul>
&lt;h1 id="到达爱尔兰后">到达爱尔兰后
&lt;/h1>&lt;ul>
&lt;li>抵达住宿地点：在机场拦出租车即可。&lt;/li>
&lt;li>买电话卡：这个可以办理学生套餐的，20 欧，无限流量。我办理的是&lt;strong>Vodafone 的&lt;/strong>。记得配置自动 20 天充值，不然每次都要手动充值很麻烦。&lt;/li>
&lt;li>办理公交卡：可以先办理一张成人卡，之后，再到市中心换学生卡。学生卡半价。然后可以在官网将成人卡注销，可以退卡退费的。&lt;/li>
&lt;li>办理居留卡 IRP：提前预约，若等到 9 月开学再预约，就要排队到 11 月了。有了 IRP，就可以离开爱尔兰了，例如回国，或者去英国或者欧洲玩了。&lt;/li>
&lt;li>办理银行卡：可以通过 AIB 办理，无需到现场办理，手机即可办理。办理需要学生证，学生银行卡可以免年费。银行卡尽早办理，同时提供的地址证明也是办理其他手续的重要资料。&lt;/li>
&lt;li>办理&lt;strong>PPSN&lt;/strong>：虽然不是一定要用到，但是万一呢。尽量提前办理好。如果找到实习了就可以报税用了。驾照报考也会用到。&lt;/li>
&lt;/ul>
&lt;h1 id="支付">支付
&lt;/h1>&lt;p>平时日常基本上大部分场景都是刷卡支付，使用银行卡靠一下无线的标志就可以支付了。也可以在手机上配置 Apple pay 进行支付。&lt;/p>
&lt;h1 id="租房">租房
&lt;/h1>&lt;p>DCU学校旁边有两家公寓，非常推荐， 在 shanowen 路上，一家是 Shanowen Square，另外一家是 Shanowen Hall，最好提前几个月来预约，不然到时候没有空房。市区的话 Beckett House 也不错，适合 TCD 的学生。当然如果摇号摇到学校的公寓是最好的。总之，优先学校宿舍，学生公寓，其次可以找房东租房。寄宿家庭的话，和房东一起住的话，可能因人而异，对我来说，我已经体验过了，不算噩梦，也不算美梦，但有个经历之后就好，让我再来一次我打死不要。也有同学说，寄宿家庭环境不错的。&lt;/p>
&lt;h1 id="居家和床上用品">居家和床上用品
&lt;/h1>&lt;p>可以在IKEA 上下单，到时候会送到房间。但是注意时效不一定准，可能会第二天才送。学生优惠记得使用，我记得当时我结完账才发现有优惠，联系工作人员，可以帮你取消再重新结算。Homebase 的东西也不错，里面的电暖气很赞，几十欧很大只又很轻，冬天太冷晚上停暖气的话，可以使用这个。&lt;/p>
&lt;h1 id="药店">药店
&lt;/h1>&lt;p>在国内很少逛药店，但这里的药店除了药以外，还卖很多其他东西，例如化妆品保健品护肤品洗浴用品。基本上 Boots 就可以搞定。市区的 Boots 有家比较大，二楼还可以拍证件照，非常万能。&lt;/p>
&lt;h1 id="餐饮">餐饮
&lt;/h1>&lt;p>基本上都是自己做饭。可以去市区融兴行中超购买。融兴行小程序可以全城配送，配送费满 30 欧备注“融逸生活”免配送费，当天早上订单下午即可送达，非常方便！！！&lt;/p>
&lt;p>其他超市说实话本人用的不太多。DCU 附近有 LIDL 还有 Tesco，里面也有一些生鲜，我主要用来补充一些肉类。&lt;/p>
&lt;p>肉类的话，这边的猪肉实在是味道太大了。基本没吃过。只吃牛肉。&lt;/p>
&lt;p>lidl 的Rib eye steak 味道还不错，肥瘦相间，8 欧多 400g，可以吃两顿。另外就是 lidl 的鸡腿肉了，带皮带骨的价格很划算。&lt;/p>
&lt;p>融兴行有卖牛腩，炖牛肉很好吃。&lt;/p>
&lt;p>这边炉灶不太好用，用的电陶炉居多。可能需要习惯一段时间火力。&lt;/p>
&lt;h1 id="公交">公交
&lt;/h1>&lt;p>爱尔兰公交系统发达，基本上不需要打车。TFI Live 可以实时查看任意站点的下一辆车的到达时间，比 Google 准，在没有电子指示的站点很有用，推荐下载使用。出行主要是公交车，偶尔会有Luas、Dart 等等。&lt;/p>
&lt;h1 id="网购">网购
&lt;/h1>&lt;p>推荐使用 Shein，满足一定金额即可包邮。一般快递需要 10 天左右送达。可以购买生活用品等。无食品。&lt;/p>
&lt;p>另外学生可以免费开亚马逊的学生会员半年。极大的减少快递费用。&lt;/p>
&lt;p>不走 Shein如果想要像淘宝一样，买东西，然后 走国际货运公司空运 10 天收到的话，运费极高，2kg 的话运费在 200-300 人民币。走海运，运费便宜一点，2kg 的话，在 60-80 人民币。所以如果买一些日用品的话，Shein 上能买到的话，尽量在 Shein 上买，还包邮费。&lt;/p>
&lt;p>Ochama 商品会更全，并且包含各种中国食品，以及部分京造产品（京东旗下在欧洲的公司），结账使用邀请码（7WYM5K）可以获得 10 欧优惠卷。但是运费有点贵，如果没有优惠的情况下，基本上很少用到。&lt;/p>
&lt;h1 id="服饰">服饰
&lt;/h1>&lt;p>南边有一个 Dundrum Town Centre，里面服装比较多。市区 Henry St 的服装也挺多的。&lt;/p>
&lt;h1 id="国内游玩">国内游玩
&lt;/h1>&lt;p>如果是报了语言班的话（我报了 DCU 的 5 周语言班，还蛮不错的，不然开学冲击太大，相当于提前了解了一下学校），7 月-8 月都比较适合出行游玩。可以去高威科克转一转。但如果没有报语言班，只是 9 月之后的话，来了天气就不太好了，会冷，日照时间也减少了很多，所以 9 月开学的时候可以去玩玩，10 月之后就不太适合玩了。之后适合游玩的话，可能要到第二年 4 月之后了，到那个时候，日照恢复，温度也上升了。&lt;/p>
&lt;h1 id="签证">签证
&lt;/h1>&lt;ul>
&lt;li>英国签证尽量在国内就申请好，在爱尔兰申请的话，只给半年，在国内申请，会给 2 年。这样无论是去北爱尔兰，还是去英国，都会很方便。&lt;/li>
&lt;li>这边课程很短。寒假和第二学期结束后，有大量时间。这些时间如果没有计划的话，会非常无聊。特别是冬天，日照很短。由于这边签证基本上需要找黄牛排队，所以，推荐来了就开始申请签证。稳一点的话，就抓一家大使馆开始刷签证。在签证期间内，往返一次目的地国家。当前在这个过程中也会花费大量的时间和金钱，但是没有办法，想要获得一张长期的申根签，就需要积累出入境记录。我申请的是法签。通常每次会按照以下时间发放：1 个月、3 个月、半年、一年、多年。单次多次出入境无所谓了，反正超过 3 个月的基本上不可能给单次。&lt;/li>
&lt;/ul>
&lt;h1 id="求职">求职
&lt;/h1>&lt;p>这个也很关键。来之前懵懵懂懂，直到来了之后才有一个清晰的认识。这个部分由于我还在求职，也没有完全走完流程，会在走完流程之后再更新。&lt;/p>
&lt;ul>
&lt;li>NG 岗位：也就是校招岗位，申请时间在来了之后的 9 月到 11 月份。11 月份之后，大部分校招岗位就关闭了。所以需要尽早申请。无论水平如何，都推荐申请一下 NG 岗位。不过，对于超过 2 年工作经验的人来说，大厂基本上 NG 岗位很难获得。但是相当于走了一下流程，若能够接收到面试，也算是丰富经验了。&lt;/li>
&lt;li>社招岗位：申请时间在次年 4 月底开始。由于我刚开始申请，所以也不太能说个所以然。另外，找工作也随缘吧。因为很多公司要求是有 1G 的签证才会有面试机会。申请太早的话，公司是不会处理的。&lt;/li>
&lt;li>实习岗位：这个就可遇不可求了，由于硕士只有一年，很多实习岗位并不是面相我们的，而是面相本科生的。只能说，尝试投递吧。&lt;/li>
&lt;/ul>
&lt;h1 id="交流">交流
&lt;/h1>&lt;p>如果有疑问，也可以通过以下渠道联系我。&lt;/p>
&lt;p>小红书：&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/91d9a6d2d744.jpeg"
loading="lazy"
alt="Image.jpeg"
>&lt;/p>
&lt;p>Whatsapp：&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/6d28c3183067.png"
loading="lazy"
alt="Image.heic.png"
>&lt;/p></description></item><item><title>LLM 工具使用体验总结</title><link>https://nansenli.com/zh-cn/post/2025/04/aitest/</link><pubDate>Tue, 08 Apr 2025 19:47:56 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2025/04/aitest/</guid><description>&lt;h1 id="chatgpt-o4-mini">ChatGPT o4-mini
&lt;/h1>&lt;p>&lt;a class="link" href="day://2025.04.17" >&lt;code>Thu, Apr 17&lt;/code>&lt;/a> 这两天最新发布的模型，推理能力显著，多步执行能力优秀。看来o4-mini 在实际任务执行表现上全面超越 o3-mini。&lt;/p>
&lt;p>下图展示了我的一个使用的例子，问题是：“黑镜第七季第三集中出现了很多亚裔，这些亚裔是中国人还是韩国人，或者是日本人还是马来西亚人或者新加坡？”，模型成功的多次调用搜索引擎进行搜索，并获取到了正确答案：&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/51ec0e03d882.png"
loading="lazy"
alt="Image.png"
>&lt;/p>
&lt;p>另外，projects也能在该模式下正常被调用和使用了，由于模型可以自主搜索，而不是使用rag的方式，使得搜索文件的效率大幅上升。&lt;/p>
&lt;h1 id="chatgpt-不包含o3o4-mini">ChatGPT （不包含o3、o4-mini）
&lt;/h1>&lt;ul>
&lt;li>
&lt;p>GPT-4o：全能。满足日常交流学习，上下文足够大，轻松处理代码、文档检索等任务。支持多种附件格式和在线执行代码。缺点是生成的上下文长度过小，不适合生成太长内容。对过长的对话，上下文压缩严重，可能会忘记早前的内容。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Projects：略鸡肋。适合用于多个文件频繁搜索和检索文本的场景。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>GPT-4o mini：弱于 GPT-4o，优点是上下文窗口巨大，推荐用于翻译超长文本的场合。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>o1：还不错的思维链模型，用于解决复杂的代码、数学题。不擅长处理感情及需要使用直觉的问题。但思维链过短，且质量不高。建议在输出结果后，将结果作为Gemini-2.5pro 或者DeepSeek-R1 的输入，优化质量。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>o3-mini：幻觉严重，不如 o1，但推理速度极快。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Search：略微鸡肋。新版本搜索结果被严重阉割，甚至不如模型不搜索时的输出质量。适合替代轻度搜索。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Deep Research：神器。同类功能中质量最好，输出结果最完善。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>GPT-4.5-preview：优秀。上下文窗口巨大，对长文本理解能力强。拥有海量记忆和最强直觉。其实不擅长推理，但是由于幻觉低，反而表现最好。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Canvas：整体不如 Cursor。上下文输出太小，文本长度受限。适合边写边编辑的短文本场景。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Work with Apps on macOS：功能体验被 Cursor 碾压，相当于 Cursor 的精简版。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h1 id="claude">Claude
&lt;/h1>&lt;ul>
&lt;li>Claude 3.7 Sonnet：优秀，适合生成各类代码。网页版支持超大上下文，几乎与 API 提供一致的上下文历史消息保留。模型非常适合撰写沟通类文本，如邮件，用词准确、简洁、不浮夸。缺点是免费版使用次数有限。&lt;/li>
&lt;/ul>
&lt;h1 id="deepseek">DeepSeek
&lt;/h1>&lt;ul>
&lt;li>
&lt;p>R1：幻觉严重，发挥不稳定，偶尔影响使用，对提示词质量要求极高。提示词正确或上下文完整时超越 o1。可以作为 o1 输出结果的输入，优化质量。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>V3：非常优秀，GPT-4o 平替。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h1 id="grok">Grok
&lt;/h1>&lt;ul>
&lt;li>
&lt;p>Grok-3：生成文本流畅自然，AI 感较弱，适合自然写作和小说创作。整体中规中矩，生产力方面不如 GPT-4o 提供的工具多。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Grok-3 + search：优秀。坐拥英文社交数据，快速分析新闻实事。输出内容长，几乎不过滤索搜源内容，比 GPT-4o 搜索能力强。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Grok-Deep Search：略鸡肋，反而不如 Grok-3 + search。生成内容模板化严重，影响质量。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h1 id="gemini">Gemini
&lt;/h1>&lt;ul>
&lt;li>
&lt;p>2.5 Pro：非常优秀的模型，GPT-4o 平替，幻觉少，搜索结果质量高，思维链完整清晰，逻辑性强。支持与 Google 工具联动，特殊场景下非常有用，例如上传截图或文本并自动在 Google Calendar 中创建日程。缺点是经常声称自己使用了搜索，但实际上并未执行搜索。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Deep Research：一般，略优于旧版 GPT Search。偶尔存在理解偏差，搜索和文档生成是分阶段执行，过程割裂，模板化严重。目前唯一能替代 GPT Deep Research 的产品。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h1 id="v0dev">v0.dev
&lt;/h1>&lt;ul>
&lt;li>适合编写并在线预览前端组件。&lt;/li>
&lt;/ul>
&lt;h1 id="chatqwenai">chat.qwen.ai
&lt;/h1>&lt;ul>
&lt;li>Qwen2.5-Max：非常优秀的模型，GPT-4o 平替，生成速度快，支持思考模式。&lt;/li>
&lt;/ul>
&lt;h1 id="知乎直答">知乎直答
&lt;/h1>&lt;ul>
&lt;li>支持搜索知乎全网内容。可能的竞品是小红书直答。&lt;/li>
&lt;/ul>
&lt;h1 id="perplexity">Perplexity
&lt;/h1>&lt;ul>
&lt;li>支持基础搜索，质量平庸，略鸡肋。&lt;/li>
&lt;/ul>
&lt;h1 id="mistralai">Mistral.ai
&lt;/h1>&lt;ul>
&lt;li>速度快，上下文窗口大，GPT4o-mini 平替。&lt;/li>
&lt;/ul>
&lt;h1 id="cursor">Cursor
&lt;/h1>&lt;ul>
&lt;li>神器。可对整个代码仓库建立索引，适合开发大型项目。支持多模型切换，支持本地命令行执行与终端接管，适合以下任务：
&lt;ul>
&lt;li>撰写 Latex 文档，替代 Overleaf&lt;/li>
&lt;li>辅助完成各类作业&lt;/li>
&lt;li>代码仓库搜索与特定功能定位&lt;/li>
&lt;li>创建单元测试&lt;/li>
&lt;li>执行 git 操作&lt;/li>
&lt;li>执行部署任务&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>几乎可以执行一切涉及文档和命令行的功能，在开发、调试、Debug、文档撰写方面均有帮助&lt;/li>
&lt;li>真正的现阶段最接近 Agent 的产品（于o3和o4-mini发布后不成立，因为o3和o4-mini也可以多步执行规划并完成任务了。不过o3和o4-mini仍然只能在自己的小环境里完成Agent，而Cursor可以在用户电脑上执行，这是一个优点。）&lt;/li>
&lt;/ul>
&lt;h1 id="例子leetcode-跟踪器">例子：Leetcode 跟踪器
&lt;/h1>&lt;p>在淘宝上存在大量的 Leetcode 会员共享账号，但是我们有的时候还是需要将做题记录追加到自己的账号中。购买共享会员账号后，我们可以将某个公司的题目导出为 csv 文件，然后，将自己的做题记录也导出为 csv 文件，之后，对比这两个文件，我们可以轻松的去追踪当前的做题进度。Cursor 非常擅长撰写类似的小工具：&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/1a7bea6f828c.png"
loading="lazy"
alt="Image.png"
>&lt;/p>
&lt;p>我们只需要复制网页中的元素，Cursor 就可以为我们撰写完整的 csv 文件，并且自动生成完美的前端界面，极大的提高做题的效率。&lt;/p></description></item><item><title>如何在搭载 Apple 芯片的 MacBook 上安装 Rodin 软件</title><link>https://nansenli.com/zh-cn/post/2025/02/rodin/</link><pubDate>Sat, 01 Feb 2025 13:02:01 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2025/02/rodin/</guid><description>&lt;h3 id="在-macos-上安装-rodin-的步骤m1-arm-架构">&lt;strong>在 macOS 上安装 Rodin 的步骤（M1 ARM 架构）：&lt;/strong>
&lt;/h3>&lt;ol>
&lt;li>
&lt;p>&lt;strong>安装 x86 JDK&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>从 Oracle 下载 &lt;strong>Intel x86 JDK 18&lt;/strong>（macOS .dmg）：&lt;/p>
&lt;p>&lt;a class="link" href="https://download.oracle.com/java/18/archive/jdk-18.0.2.1_macos-x64_bin.dmg" target="_blank" rel="noopener"
>jdk-18.0.2.1_macos-x64_bin.dmg&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>运行安装程序，它会自动配置 Java 环境。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>下载并安装 Rodin&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>从 SourceForge 获取 &lt;strong>Rodin 3.8/3.9&lt;/strong>（x86_64 版本）：&lt;/p>
&lt;p>&lt;a class="link" href="https://sourceforge.net/projects/rodin-b-sharp/files/Core_Rodin_Platform/3.8/rodin-3.8.0.202304051545-af2f57e1e-macosx.cocoa.x86_64.tar.gz/download" target="_blank" rel="noopener"
>rodin-3.8.0.202304051545-macosx.cocoa.x86_64.tar.gz&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>解压 &lt;code>.tar.gz&lt;/code> 文件，并将 &lt;code>Rodin.app&lt;/code> 移动到 &lt;code>/Applications&lt;/code> 文件夹。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>修复 macOS 安全权限&lt;/strong>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>运行以下命令以绕过&amp;quot;应用程序已损坏&amp;quot;的错误：&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo xattr -cr /Applications/Rodin.app
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Rodin 应该可以正常运行，无需其他步骤（例如配置 Java VM 路径）。&lt;/p></description></item><item><title>差分攻击 FEAL-4：从原理到实现</title><link>https://nansenli.com/zh-cn/post/2025/01/feal-4/</link><pubDate>Wed, 01 Jan 2025 13:15:04 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2025/01/feal-4/</guid><description>&lt;p>这篇文章主要介绍了如何对传统分组密码算法 FEAL-4 进行差分分析（Differential Cryptanalysis），并展示了攻击进程和关键实现细节。文中还包含了部分实验数据和攻击结果，可供有兴趣的读者参考或复现。&lt;/p>
&lt;hr>
&lt;h2 id="两张重要图片">两张重要图片
&lt;/h2>&lt;p>在正式展开分析之前，先给出两张与 FEAL-4 密切相关的图片，方便对差分传播和整体加密流程有一个直观认识。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>差分传播示意图&lt;/strong>&lt;br>
下图展示了在 FEAL-4 中，明文差分（XOR 形式）如何在加密轮次中传播并最终反映到密文上。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/d9f08a73ad65.png"
loading="lazy"
>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>FEAL-4 工作流示意图&lt;/strong>&lt;br>
下图是 FEAL-4 的整体加密框架示意，包括明文的左右分块、各轮子密钥注入以及 F 函数的运用流程。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/73e0b1400fd1.png"
loading="lazy"
>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="f-函数的特性">F 函数的特性
&lt;/h2>&lt;p>FEAL-4 的轮函数（即 F 函数）可以视作对 32 位输入进行某种随机化排列（Permutation）。在差分分析中，我们并不需要 F 的具体实现，而更关注它的两个关键差分属性：&lt;/p>
&lt;ul>
&lt;li>若 $X \oplus Y = 0$，则 $F(X) = F(Y)$。&lt;/li>
&lt;li>若 $X \oplus Y = 0x80800000$，则 $F(X) \oplus F(Y) = 0x02000000$。&lt;/li>
&lt;/ul>
&lt;p>有了这两个结论，在后续分析时就能推断特定差分输入下的输出表现，从而帮助定位子密钥。&lt;/p>
&lt;hr>
&lt;h2 id="差分路径的详细分析">差分路径的详细分析
&lt;/h2>&lt;p>假设我们选择一对特殊的明文 $P_0$ 和 $P_1$，满足：
$$
P_0 \oplus P_1 = 0x8080000080800000 = P^{\prime}.
$$
这个特定差分向量有助于简化 FEAL-4 若干轮次输出中的差分计算。&lt;/p>
&lt;h3 id="1-初始轮次差分推导">1. 初始轮次差分推导
&lt;/h3>&lt;ul>
&lt;li>
&lt;p>令 $(L0_0, R0_0)$ 表示 $P_0$ 的左右分块（同理 $(L0_1, R0_1)$ 表示 $P_1$ 的左右分块）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>经过第一轮的异或操作后：
$$
L1_0 \oplus L1_1 = (L0_0 \oplus K4) \oplus (L0_1 \oplus K4)
= L0_0 \oplus L1_0
= 0x80800000.
$$
同理可推出右半部分的差分。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>进一步由于加密时存在 “额外一轮 XOR”，可得到：
$$
R2_0 \oplus R2_1
= (R1_0 \oplus L1_0) \oplus (R1_1 \oplus L1_1)
= 0x80800000 \oplus 0x80800000
= 0.
$$
这意味着在后续计算中，差分会以更简单的形式得以归并。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="2-后向差分计算backward-calculation">2. 后向差分计算（Backward Calculation）
&lt;/h3>&lt;p>当我们获得明文对 $(P_0, P_1)$ 与对应的密文对 $(C_0, C_1)$ 后，可以进一步进行后向推导。令密文记作 $(L, R)$，则：&lt;/p>
&lt;ul>
&lt;li>计算出 $C^{\prime} = C_0 \oplus C_1$，得到 $(L^{\prime}, R^{\prime})$。&lt;/li>
&lt;li>根据已知差分性质，可在末轮逐步还原 $X^{\prime}$、$Y^{\prime}$、$Z^{\prime}$ 等中间变量。&lt;/li>
&lt;li>利用这些结果便可对各子密钥展开攻击或验证。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="攻击-k3">攻击 K3
&lt;/h2>&lt;p>在差分分析中，往往先从某个中间轮次子密钥开始，例如 $K_3$。具体步骤如下：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>选取明文对：&lt;/strong> 我生成了 12 对满足&lt;br>
$$
P_0 \oplus P_1 = 0x8080000080800000
$$&lt;br>
的随机明文。&lt;/li>
&lt;li>&lt;strong>加密并提取差分：&lt;/strong> 将上述明文对加密后，得到相应的密文对；结合差分路径推导出 $Y_0$、$Y_1$、$Z^{\prime}$ 等中间值。&lt;/li>
&lt;li>&lt;strong>遍历 $K_3$：&lt;/strong>&lt;br>
$$
Z^{\prime} = Z_0 \oplus Z_1 = F(Y_0 \oplus K_3) \oplus F(Y_1 \oplus K_3).
$$&lt;br>
只要找到满足差分方程 $= 0x02000000$ 的候选即保留。&lt;/li>
&lt;/ol>
&lt;p>通过这种方法，我成功得到四个候选子密钥：&lt;/p>
&lt;pre tabindex="0">&lt;code>cfa38976, cfa309f6, 4f238976, 4f2309f6
&lt;/code>&lt;/pre>&lt;p>执行以上过程的命令示例为：&lt;/p>
&lt;pre tabindex="0">&lt;code>./main -mode=attackk3 -file=K3.txt
&lt;/code>&lt;/pre>&lt;p>该实现与我在 &lt;a class="link" href="https://www.theamazingking.com" target="_blank" rel="noopener"
>www.theamazingking.com&lt;/a> 上介绍的方法一致。&lt;/p>
&lt;hr>
&lt;h2 id="攻击-k2">攻击 K2
&lt;/h2>&lt;p>在拿到 $K_3$ 的若干候选后，可以继续推算 $K_2$。其核心方程：
$$
X&amp;rsquo; = X_0 \oplus X_1
= F(U_0 \oplus K_2) \oplus F(U_1 \oplus K_2),
$$
其中 $U$ 来自于上一轮运算（包括对 $Y$、$Z$ 等的进一步计算）。同样地：&lt;/p>
&lt;ol>
&lt;li>生成明文对，满足差分条件
$$
P_0 \oplus P_1 = 0x0000000080800000.
$$&lt;/li>
&lt;li>抓取加密后密文差分，通过遍历所有可能 $K_2$ 值，检查是否能满足差分约束 $=0x02000000$。&lt;/li>
&lt;li>命令示例：
&lt;pre tabindex="0">&lt;code>./main -mode=attackk2 -file=K2.txt -k3=cfa38976,cfa309f6,4f238976,4f2309f6
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>最终输出示例如下所示：
&lt;pre tabindex="0">&lt;code>Candidate K2: 8b722e41 (K3: cfa38976)
Candidate K2: 8b72aec1 (K3: cfa38976)
...
Candidate K2: 89722e43 (K3: 4f2309f6)
Candidate K2: 8972aec3 (K3: 4f2309f6)
&lt;/code>&lt;/pre>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="攻击-k1">攻击 K1
&lt;/h2>&lt;p>类似地，攻击 $K_1$ 也可以按前面流程来做。&lt;br>
为了让差分路径能稳定呈现预期结果，我生成了 12 对满足&lt;br>
$$
P_0 \oplus P_1 = 0x0000000002000000
$$&lt;br>
的明文，并验证其加密输出。&lt;/p>
&lt;p>命令示例：&lt;/p>
&lt;pre tabindex="0">&lt;code>./main -mode=attackk1 -file=K1.txt -k3k2=&amp;#34;cfa38976,8b722e41; cfa38976,8b72aec1; ...&amp;#34;
&lt;/code>&lt;/pre>&lt;p>可一次性将前面得到的所有 $K_3, K_2$ 候选组合带入，最终找到对应的 $K_1$ 候选。&lt;/p>
&lt;hr>
&lt;h2 id="攻击-k0-k4-k5">攻击 K0, K4, K5
&lt;/h2>&lt;p>当 $K_3, K_2, K_1$ 全部到手后，就可以反推最后一轮所需的 $K_0, K_4, K_5$。&lt;br>
设最终加密得到的左、右部分为 $L_0, R_0$。对应的明文分块为 $PL, PR$。则有：&lt;/p>
&lt;ul>
&lt;li>$PL \oplus K_4 = LR_0$&lt;/li>
&lt;li>$PR \oplus K_5 = RR_0$&lt;/li>
&lt;li>$RR_0 \oplus LR_0 = R_0$&lt;/li>
&lt;li>$f(R_0 \oplus K_0) \oplus L_0 = LR_0$&lt;/li>
&lt;/ul>
&lt;p>只要枚举所有可能的 $K_0$，即可通过上述方程解出相应的 $K_4$ 与 $K_5$。然后用其他明文-密文对交叉验证正确性。若都满足，则得到正确的最终密钥集。&lt;/p>
&lt;p>命令示例：&lt;/p>
&lt;pre tabindex="0">&lt;code>./main -mode=attackk0k4k5 -k3k2k1=&amp;#34;...&amp;#34; -file=K1.txt -file2=K1_p.txt
&lt;/code>&lt;/pre>&lt;hr>
&lt;h2 id="实现优化与验证">实现、优化与验证
&lt;/h2>&lt;h3 id="1-自动化生成与并行计算">1. 自动化生成与并行计算
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>自动生成随机明文对&lt;/strong>：在代码中事先控制 XOR 差分，即可批量输出满足需求的明文文件（如 &lt;code>K3_p.txt&lt;/code>, &lt;code>K2_p.txt&lt;/code>, &lt;code>K1_p.txt&lt;/code>）。&lt;/li>
&lt;li>&lt;strong>并行化搜索&lt;/strong>：使用 Go 语言的 Goroutines，开启 10 个并行任务，在带有 10 核的 Mac 上最大化利用 CPU 资源。&lt;/li>
&lt;li>&lt;strong>进度条与可视化&lt;/strong>：通过 &lt;code>progressbar&lt;/code> 库实时显示搜索进度，让实验过程更直观。&lt;/li>
&lt;/ul>
&lt;h3 id="2-验证方法">2. 验证方法
&lt;/h3>&lt;ul>
&lt;li>
&lt;p>&lt;strong>本地自定义密钥测试&lt;/strong>&lt;br>
先选定一套已知的 $K_0$–$K_5$（如 &lt;code>0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020&lt;/code>），对前面生成的明文对进行加密，并重复差分分析过程，看是否能在结果中匹配这套密钥。&lt;br>
实验证明能够正确还原这组测试密钥，从而确认代码逻辑的准确性。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>特定明文-密文对测试&lt;/strong>&lt;br>
选定一个明文 &lt;code>1234567890abcdef&lt;/code> 和在 Einstein Zone 生成的密文 &lt;code>f43ae3eeb56e2bbf&lt;/code>，验证最终得到的 256 组候选 $K_0$–$K_5$ 中，每一组都可重现这条加密映射，进一步确认攻击流程无误。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="最终结果">最终结果
&lt;/h2>&lt;p>所有解出的 $K_0$–$K_5$ 存放于 &lt;code>final_result.txt&lt;/code>。去重后包括以下范围：&lt;/p>
&lt;h3 id="possible-values-for-k0">Possible Values for K0
&lt;/h3>&lt;pre tabindex="0">&lt;code>890c2148 890ca1c8 098c2148 098ca1c8 ...
0b8c214a 0b8ca1ca 8b0c214a 8b0ca1ca
&lt;/code>&lt;/pre>&lt;h3 id="possible-values-for-k1">Possible Values for K1
&lt;/h3>&lt;pre tabindex="0">&lt;code>471f077e 471f87fe c79f077e c79f87fc ...
451f077c 451f87fc c59f077c c59f87fc
&lt;/code>&lt;/pre>&lt;h3 id="possible-values-for-k2">Possible Values for K2
&lt;/h3>&lt;pre tabindex="0">&lt;code>8b722e41 8b72aec1 8b722e43 8b72aec3 ...
89722e43 8972aec3
&lt;/code>&lt;/pre>&lt;h3 id="possible-values-for-k3">Possible Values for K3
&lt;/h3>&lt;pre tabindex="0">&lt;code>cfa38976 cfa309f6 4f238976 4f2309f6
&lt;/code>&lt;/pre>&lt;h3 id="possible-values-for-k4">Possible Values for K4
&lt;/h3>&lt;pre tabindex="0">&lt;code>89eb0024 89eb0026 8beb0024 8beb0026
&lt;/code>&lt;/pre>&lt;h3 id="possible-values-for-k5">Possible Values for K5
&lt;/h3>&lt;pre tabindex="0">&lt;code>b85e6bc0 b85e6bc2 ba5e6bc0 ba5e6bc2
&lt;/code>&lt;/pre>&lt;p>借助差分分析，我们最终能获得一批子密钥候选集。与现代分组密码（AES 等）相比，FEAL-4 的轮数较少、结构也更为简洁，因此在教学和研究中非常适合用于演示差分分析思路。&lt;/p>
&lt;hr>
&lt;h2 id="总结">总结
&lt;/h2>&lt;p>本文详细展示了对 FEAL-4 进行差分攻击的完整流程，包括从差分路径设计、生成明文对、中间子密钥 ($K_3, K_2, K_1$) 攻击，再到最后对 $K_0, K_4, K_5$ 的逆向求解。同时也介绍了代码实现过程中的几处优化和验证方法。&lt;br>
FEAL-4 算法因为较低的轮数和简单的结构为差分分析提供了便利。然而对于现代更安全、更复杂的分组算法，同样的分析思路也至关重要。本案例希望对密码学的学习者和研究者有所启发，如有任何问题或改进方案，欢迎在评论区讨论交流！&lt;/p></description></item><item><title>如何快速解决反汇编问题</title><link>https://nansenli.com/zh-cn/post/2024/11/disassembly/</link><pubDate>Sun, 03 Nov 2024 14:21:43 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2024/11/disassembly/</guid><description>&lt;p>对于 DCU 安全编程这门课的反汇编，由于题目有一定套路，使用固定的解题思路，可以快速解题&lt;/p>
&lt;h1 id="前置技能">前置技能
&lt;/h1>&lt;ol>
&lt;li>熟悉各种汇编命令 &lt;a class="link" href="craftdocs://open?blockId=8DC79A21-6886-4C9D-BAF2-8CAB0A186B73&amp;amp;spaceId=c5e6e898-910c-6f27-fbc5-91f4c34a9511" >汇编指令&lt;/a>&lt;/li>
&lt;li>熟悉 % 和 $ 表示寄存器和立即数 &lt;a class="link" href="craftdocs://open?blockId=624020ED-93E1-483B-8FA7-7D21D0B71036&amp;amp;spaceId=c5e6e898-910c-6f27-fbc5-91f4c34a9511" >$ 与 % 寄存器与立即数&lt;/a>&lt;/li>
&lt;li>熟悉直接寻址和间接寻址 &lt;a class="link" href="craftdocs://open?blockId=C4F438DF-5C27-4FAF-82DF-7D815FB93AD1&amp;amp;spaceId=c5e6e898-910c-6f27-fbc5-91f4c34a9511" >直接寻址与间接寻址&lt;/a>&lt;/li>
&lt;li>了解一个case &lt;a class="link" href="craftdocs://open?blockId=F8D3758A-2BBE-46A0-B06A-9CB8EBC9D23E&amp;amp;spaceId=c5e6e898-910c-6f27-fbc5-91f4c34a9511" >C语言代码到汇编的例子&lt;/a>&lt;/li>
&lt;/ol>
&lt;h1 id="解题思路">解题思路
&lt;/h1>&lt;ol>
&lt;li>&lt;a class="link" href="craftdocs://open?blockId=5FC17CD5-B366-4554-B226-D38877BAB7A4&amp;amp;spaceId=c5e6e898-910c-6f27-fbc5-91f4c34a9511" >找到入参个数&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="craftdocs://open?blockId=8B96E2AA-7B13-4371-8DF9-37856DD028BD&amp;amp;spaceId=c5e6e898-910c-6f27-fbc5-91f4c34a9511" >找到局部变量个数&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="craftdocs://open?blockId=F7BE55C0-B922-4724-8405-BD72D9F0C85A&amp;amp;spaceId=c5e6e898-910c-6f27-fbc5-91f4c34a9511" >识别循环体&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="craftdocs://open?blockId=4E432226-5F3A-411D-8479-34D681002111&amp;amp;spaceId=c5e6e898-910c-6f27-fbc5-91f4c34a9511" >解析剩余代码片段&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="craftdocs://open?blockId=E0AD97BA-3EFF-4498-9847-0536A7B7DABD&amp;amp;spaceId=c5e6e898-910c-6f27-fbc5-91f4c34a9511" >识别返回数据&lt;/a>&lt;/li>
&lt;/ol>
&lt;h1 id="找到入参个数">找到入参个数
&lt;/h1>&lt;p>ebp的位置是saved frame ptr，ebp+4的位置是return address。由于题目往往是约定入参全部为int 或者 int * 类型，因此 ebp+8，ebp+c，ebp+10的位置，分别是入参的第一个参数，第二个参数，第三个参数。&lt;/p>
&lt;p>因此，在代码中快速浏览，寻找 0x__(%ebp) 的字样，找到最大的偏移量。&lt;code>(偏移量-4)//4&lt;/code> 的值就是入参的个数。&lt;/p>
&lt;p>例如，下面这段代码&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>push &lt;span style="color:#f92672">%&lt;/span>ebp &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#f92672">%&lt;/span>esp, &lt;span style="color:#f92672">%&lt;/span>ebp &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sub &lt;span style="color:#960050;background-color:#1e0010">$&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>, &lt;span style="color:#f92672">%&lt;/span>esp &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">3&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#ae81ff">0x8&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">6&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#f92672">%&lt;/span>eax, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp) &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">9&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">12&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cmp &lt;span style="color:#ae81ff">0x10&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">15&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>jge &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">18&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#ae81ff">0xc&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">20&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>incl (&lt;span style="color:#f92672">%&lt;/span>eax) &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">23&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>lea &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">25&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>incl (&lt;span style="color:#f92672">%&lt;/span>eax) &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">28&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>jmp &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">12&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">30&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#960050;background-color:#1e0010">$&lt;/span>&lt;span style="color:#ae81ff">0x0&lt;/span>, &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>leave &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">37&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ret &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">38&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我们发现存在 &lt;code>0x10(%ebp)&lt;/code> 因此，参数个数为&lt;code>（16-4）/4&lt;/code> ，也就是是3个。&lt;/p>
&lt;p>因此我们可以写出代码的框架，如下&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">foo&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> a, &lt;span style="color:#66d9ef">int&lt;/span> b, &lt;span style="color:#66d9ef">int&lt;/span> c) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>其中 a, b, c 存在的栈的位置分别是 ebp+8，ebp+c，ebp+10&lt;/p>
&lt;p>注意，参数从后向前入栈，因此越接近ebp，参数在参数列表中越靠前。&lt;/p>
&lt;p>注意，我们这里先假设全部都是int类型，如果后续有遇到不一致的情况，我们再来修改&lt;/p>
&lt;h1 id="找到局部变量个数">找到局部变量个数
&lt;/h1>&lt;p>局部变量的个数在代码第三行，&lt;code>sub $0x4, %esp&lt;/code> 这里减去的数量，就是分配的局部变量的长度。&lt;/p>
&lt;p>在上面的代码中，显然这里分配了4字节，也就是只有一个局部变量。我们假设这个局部变量就是int类型，起名为i。&lt;/p>
&lt;p>我们继续扩展我们的代码&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">foo&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> a, &lt;span style="color:#66d9ef">int&lt;/span> b, &lt;span style="color:#66d9ef">int&lt;/span> c) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> i;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="识别循环体">识别循环体
&lt;/h1>&lt;p>循环体通常是while循环或者for循环。&lt;/p>
&lt;p>对于常见的&lt;code>while&lt;/code>命令，我们寻找下面相关的指令：&lt;/p>
&lt;ul>
&lt;li>判断入口：&lt;/li>
&lt;/ul>
&lt;p>判断入口是一个连续的比较命令（如cmp）和一个跳转命令（如jge或jle）组成。找到判断入口有助于我们识别代码中是否有判断或者循环。&lt;/p>
&lt;ul>
&lt;li>循环：&lt;/li>
&lt;/ul>
&lt;p>假如没有循环标志，那么就不是循环体，而是简单的if判断。循环是一个无条件的jmp命令。循环所跳转的位置就是判断条件的开始。&lt;/p>
&lt;ul>
&lt;li>判断条件：&lt;/li>
&lt;/ul>
&lt;p>判断条件是判断入口以及判断入口和前面几条命令组合成的完整判断条件。判断条件就是while括号里的内容。&lt;/p>
&lt;p>现在我们查看下面的例子：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>push &lt;span style="color:#f92672">%&lt;/span>ebp &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#f92672">%&lt;/span>esp, &lt;span style="color:#f92672">%&lt;/span>ebp &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sub &lt;span style="color:#960050;background-color:#1e0010">$&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>, &lt;span style="color:#f92672">%&lt;/span>esp &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">3&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#ae81ff">0x8&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">6&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#f92672">%&lt;/span>eax, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp) &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">9&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">12&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cmp &lt;span style="color:#ae81ff">0x10&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">15&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>jge &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">18&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#ae81ff">0xc&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">20&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>imul (&lt;span style="color:#f92672">%&lt;/span>eax) &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">23&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>lea &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">25&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>imul (&lt;span style="color:#f92672">%&lt;/span>eax) &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">28&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>jmp &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">12&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">30&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#960050;background-color:#1e0010">$&lt;/span>&lt;span style="color:#ae81ff">0x0&lt;/span>, &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>leave &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">37&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ret &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">38&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>判断入口：&lt;/p>
&lt;p>我们寻找一个连续的判断指令和一个跳转指令，我们可以找到 cmp 和 jge 这两个连续指令，就说明这里是判断入口。&lt;/p>
&lt;p>循环：&lt;/p>
&lt;p>我们看到，最后存在一个jmp命令，jmp命令指向了第foo+12的位置，因此foo+12就是循环的判断条件。&lt;/p>
&lt;p>判断条件：&lt;/p>
&lt;p>判断条件的第一句是 &lt;code>mov -0x4(%ebp), %eax&lt;/code> ，其中 &lt;code>-0x4(%ebp)&lt;/code> 是最后一个局部变量，由于我们只有一个局部变量 i ，因此这句话的意思是，将本地变量i的值赋值给 eax。&lt;/p>
&lt;p>我们看到cmp的命令是：&lt;code>cmp 0x10(%ebp), %eax&lt;/code> ，其中&lt;code>cmp 0x10(%ebp)&lt;/code> 指第三个入参的值。这句话的意思是计算 eax 寄存器中减去第一个入参的数值，也就是c。&lt;/p>
&lt;p>联合起来，我们就知道，这里是计算 i - c的值，并交给 jge 来做跳转。&lt;/p>
&lt;p>由于汇编的条件判断和C语言条件判断是反过来的，汇编是满足判断则跳过循环体，而C语言是满足条件则执行循环体，因此我们的判断条件也是反过来的。&lt;/p>
&lt;p>jge表示大于等于0则跳过循环体，因此C语言的循环条件是小于0则执行循环体，也就是判断 i - c 是否小于 0&lt;/p>
&lt;p>因此我们可以继续完善我们的代码:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">foo&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> a, &lt;span style="color:#66d9ef">int&lt;/span> b, &lt;span style="color:#66d9ef">int&lt;/span> c) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> i;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (i &lt;span style="color:#f92672">-&lt;/span> c &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="解析剩余代码片段">解析剩余代码片段
&lt;/h1>&lt;p>首先标记下已经处理过的代码&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>push &lt;span style="color:#f92672">%&lt;/span>ebp &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">无用&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#f92672">%&lt;/span>esp, &lt;span style="color:#f92672">%&lt;/span>ebp &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">无用&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sub &lt;span style="color:#960050;background-color:#1e0010">$&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>, &lt;span style="color:#f92672">%&lt;/span>esp &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">3&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">分配了几个局部变量&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#ae81ff">0x8&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">6&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#f92672">%&lt;/span>eax, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp) &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">9&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">12&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">判断条件开始&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cmp &lt;span style="color:#ae81ff">0x10&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">15&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">判断内容&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>jge &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">18&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">判断条件结束&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#ae81ff">0xc&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">20&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>incl (&lt;span style="color:#f92672">%&lt;/span>eax) &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">23&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>lea &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">25&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>incl (&lt;span style="color:#f92672">%&lt;/span>eax) &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">28&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>jmp &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">12&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">30&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">循环结束&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#960050;background-color:#1e0010">$&lt;/span>&lt;span style="color:#ae81ff">0x0&lt;/span>, &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>leave &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">37&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ret &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">38&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol>
&lt;li>循环体之前的代码&lt;/li>
&lt;/ol>
&lt;p>我们看一下循环开始之前的部分：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#ae81ff">0x8&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">6&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#f92672">%&lt;/span>eax, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp) &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">9&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我们发现这两句都涉及到了 eax 寄存器&lt;/p>
&lt;p>对于多条涉及eax 寄存器的操作，我们尽量当作一个整体来看&lt;/p>
&lt;p>我们发现，其实这里就是将 &lt;code>0x8(%ebp)&lt;/code> 的数值 赋值给了 &lt;code>-0x4(%ebp)&lt;/code> ，这两个值分别是 a 和 i&lt;/p>
&lt;p>因此这个部分的代码是&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>i &lt;span style="color:#f92672">=&lt;/span> a
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="2">
&lt;li>循环体中间的代码&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#ae81ff">0xc&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">20&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>incl (&lt;span style="color:#f92672">%&lt;/span>eax) &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">23&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>lea &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0x4&lt;/span>(&lt;span style="color:#f92672">%&lt;/span>ebp), &lt;span style="color:#f92672">%&lt;/span>eax &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">25&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>incl (&lt;span style="color:#f92672">%&lt;/span>eax) &lt;span style="color:#f92672">&amp;lt;&lt;/span>foo&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">28&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>对于涉及eax的多条指令，我们不要一条一条看，而是看作一个整体。&lt;/p>
&lt;p>其中，&lt;code>mov 0xc(%ebp), %eax&lt;/code> 和 &lt;code>incl (%eax)&lt;/code> 可以看作 &lt;code>incl (%b)&lt;/code> 注意，由于这里是间接寻址，因此，这意味着，b中存的是地址。因此我们需要修改我们的入参，b这里不是数值，而是地址。这句代码意味着&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#f92672">*&lt;/span>b)&lt;span style="color:#f92672">++&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>注意&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>&lt;code>*b++&lt;/code> 会先对 &lt;code>*b&lt;/code> 解引用，再对 &lt;code>b&lt;/code> 自增&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>因此这里括号不能省略，否则会有问题&lt;/p>
&lt;/blockquote>
&lt;p>而对于 &lt;code>lea -0x4(%ebp), %eax&lt;/code> 和 &lt;code>incl (%eax)&lt;/code> ，可以看作 &lt;code>incl (&amp;amp;i)&lt;/code> ，这里继续是间接寻址，也就是对后者地址上的数值自增。这句代码意味着 &lt;code>*(&amp;amp;i)++&lt;/code> ，可以简化为&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>i&lt;span style="color:#f92672">++&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>之所以这么复杂，是因为汇编指令不能直接对地址进行自增操作，但是可以对一个包含地址的寄存器做自增操作。&lt;/p>
&lt;p>因此，补充了剩余的代码片段，代码应该为&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">foo&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> a, &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#f92672">*&lt;/span>b, &lt;span style="color:#66d9ef">int&lt;/span> c) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> i;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i &lt;span style="color:#f92672">=&lt;/span> a;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (i &lt;span style="color:#f92672">-&lt;/span> c &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#f92672">*&lt;/span>b)&lt;span style="color:#f92672">++&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i&lt;span style="color:#f92672">++&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="识别返回数据">识别返回数据
&lt;/h1>&lt;p>按照C语言的x86的&lt;a class="link" href="craftdocs://open?blockId=742A9A8D-28C1-462B-A1D8-2D6CDB66B3BA&amp;amp;spaceId=c5e6e898-910c-6f27-fbc5-91f4c34a9511" >调用约定&lt;/a>，返回数据放在eax中。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>mov &lt;span style="color:#960050;background-color:#1e0010">$&lt;/span>&lt;span style="color:#ae81ff">0x0&lt;/span>, &lt;span style="color:#f92672">%&lt;/span>eax
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>很显然，这里直接返回了0&lt;/p>
&lt;h1 id="最终代码">最终代码
&lt;/h1>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">foo&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> a, &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#f92672">*&lt;/span>b, &lt;span style="color:#66d9ef">int&lt;/span> c) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> i;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i &lt;span style="color:#f92672">=&lt;/span> a;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (i &lt;span style="color:#f92672">-&lt;/span> c &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#f92672">*&lt;/span>b)&lt;span style="color:#f92672">++&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i&lt;span style="color:#f92672">++&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>如何处理ChatGPT的Latex格式和Obsidian与Craft不一致的问题</title><link>https://nansenli.com/zh-cn/post/2024/10/raycast-craft/</link><pubDate>Sat, 26 Oct 2024 12:59:17 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2024/10/raycast-craft/</guid><description>&lt;p>在之前的文章中，我使用了 Obsidian 的 QuickAdd 来创建一个脚本，自动转换从 ChatGPT 中复制的文本，修复其中的 LaTeX 格式。然而，对于 Craft 这款应用，并没有合适的插件可以使用。&lt;/p>
&lt;p>我们可以通过 Raycast 来实现这个功能的统一操作。&lt;/p>
&lt;hr>
&lt;h1 id="创建-raycast-脚本">创建 Raycast 脚本
&lt;/h1>&lt;p>首先，我们需要创建一个脚本。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/2abd177df831.png"
loading="lazy"
alt="image"
>&lt;/p>
&lt;p>接着选择 Bash 模板。&lt;/p>
&lt;p>然后，我们编辑这个 Bash 脚本，输入如下代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Required parameters:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># @raycast.schemaVersion 1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># @raycast.title Copy From ChatGPT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># @raycast.mode silent&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Optional parameters:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># @raycast.icon 🤖&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># @raycast.packageName ChatGPT Utils&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Documentation:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># @raycast.description Copy From ChatGPT&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># @raycast.author Nansen Li&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># @raycast.authorURL nansenli.com&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 获取剪贴板内容&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>clipboard_content&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>pbpaste&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 检查是否成功获取内容&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[&lt;/span> -z &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$clipboard_content&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;剪贴板为空或无法访问。&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 处理剪贴板内容&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>modified_content&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$clipboard_content&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> sed &lt;span style="color:#e6db74">&amp;#39;s/\\\[/$$/g; s/\\\]/$$/g; s/\\( /$/g; s/ \\\)/$/g&amp;#39;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 将修改后的内容写回剪贴板&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$modified_content&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | pbcopy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>创建完脚本后，我们还需要将脚本所在的目录添加到 Raycast 中。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/60724508c66a.png"
loading="lazy"
alt="image"
>&lt;/p>
&lt;p>在这一步中，选择刚刚创建的脚本目录。此时，我们可以在 &lt;code>Script Commands&lt;/code> 中看到刚刚创建的脚本。&lt;/p>
&lt;hr>
&lt;h1 id="如何使用">如何使用
&lt;/h1>&lt;p>在复制完 ChatGPT 的公式后，打开 Raycast 的面板，找到刚刚的脚本并运行，此时剪贴板中的内容就会被自动修复。接下来，只需将其粘贴到 Obsidian 或 Craft 中即可。&lt;/p></description></item><item><title>华为爱尔兰研究中心Tech Arena 2024竞赛总结</title><link>https://nansenli.com/zh-cn/post/2024/10/huawei2024/</link><pubDate>Thu, 24 Oct 2024 09:58:53 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2024/10/huawei2024/</guid><description>&lt;h2 id="背景介绍">背景介绍
&lt;/h2>&lt;p>我是Nansen，参与了2024年华为爱尔兰研究中心的服务器集群管理优化比赛。在此，我想分享一下这次比赛的经历，并对其中的关键点进行总结。&lt;/p>
&lt;p>我们的算法代码部分可以参考这里: &lt;a class="link" href="https://github.com/linanwx/huawei2024" target="_blank" rel="noopener"
>huawei2024&lt;/a>&lt;/p>
&lt;h2 id="比赛结果">比赛结果
&lt;/h2>&lt;p>我们在算法部分获得了第一名，分数比第二至第四名高出约4%-5%，取得了巨大的优势。然而，在演讲环节我们遇到了很大的挑战。首先，我们意识到英语表达方面有提升空间；其次，我们发现演示PPT还可以更精美突出；最后，在时间安排上也存在一些挑战。不过，尽管如此，我们还是在总分上取得了第三名的成绩。&lt;/p>
&lt;h2 id="比赛过程">比赛过程
&lt;/h2>&lt;p>比赛分为两个阶段。第一阶段有较长的准备时间。当我们确定使用模拟退火算法后，就开始了算法的开发。第一阶段的难点主要在于优化和理解题目需求。在开发过程中，我们遇到了许多bug，但在修复后，分数有了明显提升。&lt;/p>
&lt;p>第二阶段，由于题目在比赛当天才发布，我继续优化第一阶段的算法，成功将评估算法的运行速度提升了1000倍。这显著提高了我们在第二阶段的表现，让我们有足够的实力争夺第一名。&lt;/p>
&lt;p>在决赛阶段，我们的算法表现非常稳定，并在调整后大幅领先对手。然而，由于我们没有足够重视PPT的制作，我们以为只要算法表现好，排名靠前，就能获得高分，但事实证明我们错了。&lt;/p>
&lt;h2 id="经验和教训">经验和教训
&lt;/h2>&lt;h3 id="算法选择">算法选择
&lt;/h3>&lt;p>很幸运，在最初的阶段我就选对了最终的算法，并且在题目发布后不久就构思出了适用于整个比赛的算法框架。然而，我也走了一些弯路，比如尝试了一些不可行的算法（如PPO算法）。在初步尝试无果后，我应该及时停止，而不是继续浪费精力。由于时间有限，我们应追求最短时间内的最优效果，而不是追求一个不切实际、理想中的方案。同时，也要认清自己的能力边界，专注于在短时间内能够实现的目标。&lt;/p>
&lt;h3 id="团队分工">团队分工
&lt;/h3>&lt;p>很幸运，这次团队的分工是合理的，我尽力确保每个成员都能发挥自己的价值。改进之处在于，应该更多地与队员沟通，了解他们的意愿和想法。由于我主要负责算法部分，与队友的沟通相对较少，下一次我会在这方面做得更好。&lt;/p>
&lt;h3 id="ppt的制作">PPT的制作
&lt;/h3>&lt;p>我们没有预料到，其他参赛队伍的PPT水平如此之高。我的队友猜测，他们可能有商科背景，这使得他们在制作PPT时具有优势。此外，他们的团队有五名成员，而我们只有三名，这也让我们在人员配备上处于劣势。这些都是客观上的挑战，但如果我们更加重视PPT的制作，或许第一名就是我们的。&lt;/p>
&lt;h3 id="过分投入导致失衡">过分投入导致失衡
&lt;/h3>&lt;p>在决赛阶段，其实我们的算法已经非常出色，分数也超过了此前排名第一的队伍。然而，我仍然花了大量时间继续优化算法，尽管我们的算法分数领先对手很多，但却因此忽略了PPT的准备。事实上，我应该适可而止，并充分理解评分标准的重要性。&lt;/p>
&lt;h2 id="结论">结论
&lt;/h2>&lt;p>通过参加华为Tech Arena 2024竞赛，我获得了宝贵的经验。这次比赛不仅展示了我们的优势，也暴露了我们在展示技能和团队协作方面需要改进的地方。展望未来，我会牢记这些经验教训，并在今后的比赛中不断改进自己。如果您有问题，可以在留言区反馈给我。&lt;/p></description></item><item><title>Leetcode 背题</title><link>https://nansenli.com/zh-cn/post/2024/10/leetcode241020/</link><pubDate>Mon, 21 Oct 2024 19:39:36 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2024/10/leetcode241020/</guid><description>&lt;p>今天开始了漫长的刷题流程。之前只是简单做了一些题目，保持手感，但今天开始，就要为面试正式准备了。&lt;/p>
&lt;p>之前一直在思考一个问题：如何高效地刷 Leetcode。我认为，要高效地刷题，首先就得背题。书读百遍，其义自见，大语言模型经过大量训练，也锻炼出了写代码的能力。同样地，我相信 Leetcode 也一样，通过多次练习，答案自然会浮现，量变产生质变。&lt;/p>
&lt;h2 id="53-maximum-subarrayhttpsleetcodecomproblemsmaximum-subarray">&lt;a class="link" href="https://leetcode.com/problems/maximum-subarray/" target="_blank" rel="noopener"
>53. Maximum Subarray&lt;/a>
&lt;/h2>&lt;p>解题思路可以使用 &lt;strong>Kadane&amp;rsquo;s Algorithm&lt;/strong>，代码如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">maxSubArray&lt;/span>(nums):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max_current &lt;span style="color:#f92672">=&lt;/span> max_global &lt;span style="color:#f92672">=&lt;/span> nums[&lt;span style="color:#ae81ff">0&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> num &lt;span style="color:#f92672">in&lt;/span> nums[&lt;span style="color:#ae81ff">1&lt;/span>:]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max_current &lt;span style="color:#f92672">=&lt;/span> max(num, max_current &lt;span style="color:#f92672">+&lt;/span> num)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max_global &lt;span style="color:#f92672">=&lt;/span> max(max_global, max_current)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> max_global
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这是一个典型的动态规划题目，上面的算法其实隐藏了动态规划的本质。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Solution&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> maxSubArray(vector&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#66d9ef">int&lt;/span>&lt;span style="color:#f92672">&amp;gt;&amp;amp;&lt;/span> nums) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vector&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#66d9ef">int&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> dp(nums.size());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dp[&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> nums[&lt;span style="color:#ae81ff">0&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (&lt;span style="color:#66d9ef">int&lt;/span> i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>; i &lt;span style="color:#f92672">&amp;lt;&lt;/span> nums.size(); i&lt;span style="color:#f92672">++&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dp[i] &lt;span style="color:#f92672">=&lt;/span> max(dp[i &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>] &lt;span style="color:#f92672">+&lt;/span> nums[i], nums[i]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#a6e22e">max_element&lt;/span>(dp.begin(), dp.end());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这段代码更好地体现了动态规划的思想。&lt;/p>
&lt;p>理解 &lt;code>dp[i] = max(nums[i] + dp[i-1], nums[i])&lt;/code> 的公式可以从动态规划的角度来解析。这个公式的核心在于“在每一个位置上，我们要做出一个最优选择”。以下是详细解释：&lt;/p>
&lt;h3 id="1-dpi-的含义是什么">1. &lt;code>dp[i]&lt;/code> 的含义是什么？
&lt;/h3>&lt;ul>
&lt;li>&lt;code>dp[i]&lt;/code> 表示以位置 &lt;code>i&lt;/code> 结尾的 &lt;strong>最大子数组和&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;h3 id="2-为什么比较-numsi--dpi-1-和-numsi">2. 为什么比较 &lt;code>nums[i] + dp[i-1]&lt;/code> 和 &lt;code>nums[i]&lt;/code>？
&lt;/h3>&lt;ul>
&lt;li>核心问题是：&lt;strong>当前的最大子数组和是否应该包括之前的部分（&lt;code>dp[i-1]&lt;/code>）？&lt;/strong>
&lt;ul>
&lt;li>&lt;strong>&lt;code>nums[i] + dp[i-1]&lt;/code>&lt;/strong>：如果 &lt;code>dp[i-1]&lt;/code> 是正数，加上当前的 &lt;code>nums[i]&lt;/code> 会让子数组和变大，那么我们选择累加。&lt;/li>
&lt;li>&lt;strong>&lt;code>nums[i]&lt;/code>&lt;/strong>：如果 &lt;code>dp[i-1]&lt;/code> 是负数，我们选择从当前位置重新开始一个新的子数组，因为负数只会拖累当前和。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="3-为什么不用比较后续的数">3. 为什么不用比较后续的数？
&lt;/h3>&lt;ul>
&lt;li>我们在比较时，是假设数组在位置 &lt;code>i&lt;/code> 停止。也就是说，我们考虑 &lt;code>[0:i]&lt;/code> 这个范围内的最大值，对于位置 &lt;code>i&lt;/code> 来说，要么加上之前的子数组，要么放弃之前的子数组，只采用当前的数。&lt;/li>
&lt;li>然后，我们遍历整个数组，找出在每个位置停下来的最大值，最终返回最大的那个值。&lt;/li>
&lt;/ul>
&lt;h2 id="57-insert-intervalhttpsleetcodecomproblemsinsert-interval">&lt;a class="link" href="https://leetcode.com/problems/insert-interval/" target="_blank" rel="noopener"
>57. Insert Interval&lt;/a>
&lt;/h2>&lt;p>这是一个经典的区间合并问题，需要将新插入的区间合并到已有的区间中。&lt;/p>
&lt;p>可以使用分解法来解决：&lt;/p>
&lt;ul>
&lt;li>第一步，将不重叠且在 &lt;code>newInterval&lt;/code> 之前的区间添加到结果中。&lt;/li>
&lt;li>第二步，合并所有可能重叠的区间到 &lt;code>newInterval&lt;/code> 中，注意这里的判断条件。&lt;/li>
&lt;li>第三步，将剩余的区间添加到结果中。&lt;/li>
&lt;/ul>
&lt;p>注意，合并区间的判断条件是前一个区间的开始小于等于后一个区间的结束，即 &lt;code>intervals[i][0] &amp;lt;= newInterval[1]&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Solution&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">insert&lt;/span>(self, intervals: List[List[int]], newInterval: List[int]) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> List[List[int]]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ret_list &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> i &lt;span style="color:#f92672">&amp;lt;&lt;/span> len(intervals) &lt;span style="color:#f92672">and&lt;/span> intervals[i][&lt;span style="color:#ae81ff">1&lt;/span>] &lt;span style="color:#f92672">&amp;lt;&lt;/span> newInterval[&lt;span style="color:#ae81ff">0&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ret_list&lt;span style="color:#f92672">.&lt;/span>append(intervals[i])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> i &lt;span style="color:#f92672">&amp;lt;&lt;/span> len(intervals) &lt;span style="color:#f92672">and&lt;/span> intervals[i][&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">&amp;lt;=&lt;/span> newInterval[&lt;span style="color:#ae81ff">1&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> newInterval[&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> min(intervals[i][&lt;span style="color:#ae81ff">0&lt;/span>], newInterval[&lt;span style="color:#ae81ff">0&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> newInterval[&lt;span style="color:#ae81ff">1&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> max(intervals[i][&lt;span style="color:#ae81ff">1&lt;/span>], newInterval[&lt;span style="color:#ae81ff">1&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ret_list&lt;span style="color:#f92672">.&lt;/span>append(newInterval)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> i &lt;span style="color:#f92672">&amp;lt;&lt;/span> len(intervals):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ret_list&lt;span style="color:#f92672">.&lt;/span>append(intervals[i])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> ret_list
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>只要注意细节，这个题目其实并不难。&lt;/p>
&lt;h2 id="300-longest-increasing-subsequencehttpsleetcodecomproblemslongest-increasing-subsequence">&lt;a class="link" href="https://leetcode.com/problems/longest-increasing-subsequence/" target="_blank" rel="noopener"
>300. Longest Increasing Subsequence&lt;/a>
&lt;/h2>&lt;p>显然是一个动态规划问题。&lt;/p>
&lt;p>&lt;code>dp[i]&lt;/code> 表示以某个数字结尾的最长递增子序列的长度。&lt;/p>
&lt;p>每次加入一个元素，我们更新当前的 &lt;code>dp&lt;/code> 数组，如果当前数字比之前的小，它们的结果就加 1。&lt;/p>
&lt;p>注意，第一个 &lt;code>dp[i]&lt;/code> 是从下标 1 开始的。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Solution&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">lengthOfLIS&lt;/span>(self, nums: List[int]) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> int:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dp &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#ae81ff">1&lt;/span>] &lt;span style="color:#f92672">*&lt;/span> len(nums)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> range(&lt;span style="color:#ae81ff">1&lt;/span>, len(nums)):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> j &lt;span style="color:#f92672">in&lt;/span> range(i):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> nums[j] &lt;span style="color:#f92672">&amp;lt;&lt;/span> nums[i]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dp[i] &lt;span style="color:#f92672">=&lt;/span> max(dp[i], dp[j] &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> max(dp)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>复杂度分析&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>时间复杂度：$O(n^2)$，因为有两个嵌套的循环。&lt;/li>
&lt;li>空间复杂度：$O(n)$，因为需要一个长度为 &lt;code>n&lt;/code> 的 &lt;code>dp&lt;/code> 数组。&lt;/li>
&lt;/ul>
&lt;h2 id="674-longest-continuous-increasing-subsequencehttpsleetcodecomproblemslongest-continuous-increasing-subsequence">&lt;a class="link" href="https://leetcode.com/problems/longest-continuous-increasing-subsequence/" target="_blank" rel="noopener"
>674. Longest Continuous Increasing Subsequence&lt;/a>
&lt;/h2>&lt;p>这是一个简单题目，但仍然值得理解。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Solution&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">findLengthOfLCIS&lt;/span>(self, nums: List[int]) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> int:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dp &lt;span style="color:#f92672">=&lt;/span> [&lt;span style="color:#ae81ff">1&lt;/span>] &lt;span style="color:#f92672">*&lt;/span> len(nums)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> range(&lt;span style="color:#ae81ff">1&lt;/span>, len(nums)):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> nums[i] &lt;span style="color:#f92672">&amp;gt;&lt;/span> nums[i&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dp[i] &lt;span style="color:#f92672">=&lt;/span> max(dp[i], dp[i&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>] &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> max(dp)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="392-is-subsequencehttpsleetcodecomproblemsis-subsequence">&lt;a class="link" href="https://leetcode.com/problems/is-subsequence/" target="_blank" rel="noopener"
>392. Is Subsequence&lt;/a>
&lt;/h2>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Solution&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">isSubsequence&lt;/span>(self, s: str, t: str) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> bool:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> j &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> i &lt;span style="color:#f92672">&amp;lt;&lt;/span> len(s) &lt;span style="color:#f92672">and&lt;/span> j &lt;span style="color:#f92672">&amp;lt;&lt;/span> len(t):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> s[i] &lt;span style="color:#f92672">==&lt;/span> t[j]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> i &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> j &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> i &lt;span style="color:#f92672">==&lt;/span> len(s)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个题目可以用双指针法，以 &lt;code>t&lt;/code> 为基准，如果 &lt;code>s&lt;/code> 中有相同字符就向前推进，如果 &lt;code>s&lt;/code> 到结尾了，说明匹配完成。&lt;/p>
&lt;h2 id="115-distinct-subsequenceshttpsleetcodecomproblemsdistinct-subsequences">&lt;a class="link" href="https://leetcode.com/problems/distinct-subsequences/" target="_blank" rel="noopener"
>115. Distinct Subsequences&lt;/a>
&lt;/h2>&lt;p>这个问题要求我们找到字符串 &lt;code>s&lt;/code> 中有多少不同的子序列等于字符串 &lt;code>t&lt;/code>。&lt;/p>
&lt;p>首先，我们需要定义 &lt;code>dp&lt;/code>，其中 &lt;code>dp[i][j]&lt;/code> 表示从 &lt;code>s&lt;/code> 的前 &lt;code>i&lt;/code> 个字符中形成 &lt;code>t&lt;/code> 的前 &lt;code>j&lt;/code> 个字符的不同子序列数。&lt;/p>
&lt;ul>
&lt;li>&lt;code>dp[i][0]&lt;/code> 表示当 &lt;code>t&lt;/code> 为空串时，结果为 1。&lt;/li>
&lt;li>&lt;code>dp[0][j]&lt;/code> 表示从空串中形成 &lt;code>t&lt;/code>，结果为 0。&lt;/li>
&lt;/ul>
&lt;p>对于 &lt;code>dp[i][j]&lt;/code>，其结果取决于位置 &lt;code>i&lt;/code> 和 &lt;code>j&lt;/code> 的字符：&lt;/p>
&lt;ul>
&lt;li>如果两者相等，结果等于不匹配 &lt;code>s[i-1]&lt;/code> 的情况（&lt;code>dp[i-1][j]&lt;/code>）加上匹配的情况（&lt;code>dp[i-1][j-1]&lt;/code>）。&lt;/li>
&lt;li>如果不相等，则结果等于 &lt;code>dp[i-1][j]&lt;/code>。&lt;/li>
&lt;/ul>
&lt;p>注意，这里 &lt;code>i, j&lt;/code> 指的是前 &lt;code>i&lt;/code> 个和前 &lt;code>j&lt;/code> 个字符。&lt;/p>
&lt;p>此外，&lt;code>dp[0][0]&lt;/code> 初始化为 1，因为空字符串是任何字符串的子序列。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Solution&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">numDistinct&lt;/span>(self, s: str, t: str) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> int:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dp &lt;span style="color:#f92672">=&lt;/span> [[&lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#66d9ef">for&lt;/span> _ &lt;span style="color:#f92672">in&lt;/span> range(len(t) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)] &lt;span style="color:#66d9ef">for&lt;/span> _ &lt;span style="color:#f92672">in&lt;/span> range(len(s) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> range(len(s) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dp[i][&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> j &lt;span style="color:#f92672">in&lt;/span> range(&lt;span style="color:#ae81ff">1&lt;/span>, len(t) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> range(&lt;span style="color:#ae81ff">1&lt;/span>, len(s) &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> s[i&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>] &lt;span style="color:#f92672">==&lt;/span> t[j&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dp[i][j] &lt;span style="color:#f92672">=&lt;/span> dp[i&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>][j] &lt;span style="color:#f92672">+&lt;/span> dp[i&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>][j&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dp[i][j] &lt;span style="color:#f92672">=&lt;/span> dp[i&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>][j]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> dp[&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>][&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Off-by-One 溢出攻击分析</title><link>https://nansenli.com/zh-cn/post/2024/10/oboattact-md/</link><pubDate>Tue, 15 Oct 2024 14:18:44 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2024/10/oboattact-md/</guid><description>&lt;h1 id="off-by-one-溢出攻击分析">Off-by-One 溢出攻击分析
&lt;/h1>&lt;h2 id="背景">背景
&lt;/h2>&lt;p>上周，我参加了一门安全课程，其中包含了一个 off-by-one 溢出漏洞的示例。以下是原始代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/* 简单的 off-by-one 溢出示例 */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;stdio.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;stdlib.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;string.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">foo&lt;/span>(&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>input) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">char&lt;/span> buf[&lt;span style="color:#ae81ff">1024&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">strncpy&lt;/span>(buf, input, &lt;span style="color:#66d9ef">sizeof&lt;/span>(buf));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> buf[&lt;span style="color:#66d9ef">sizeof&lt;/span>(buf)] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;\0&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">bar&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;I&amp;#39;ve been hacked&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> argc, &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">**&lt;/span>argv) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (argc &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Usage: %s input_string&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, argv[&lt;span style="color:#ae81ff">0&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">exit&lt;/span>(EXIT_FAILURE);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">foo&lt;/span>(argv[&lt;span style="color:#ae81ff">1&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>利用此漏洞的攻击命令是：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>perl -e &lt;span style="color:#e6db74">&amp;#39;system &amp;#34;./obo&amp;#34;, &amp;#34;\x38\x84\x04\x08&amp;#34;x256&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>运行此命令的结果是屏幕上打印出多行 &lt;code>I've been hacked&lt;/code>。&lt;/p>
&lt;h2 id="分析">分析
&lt;/h2>&lt;p>当程序进入 &lt;code>foo&lt;/code> 函数时，内存布局如下（使用 GDB 观察到）：&lt;/p>
&lt;p>从上到下，布局包含返回地址、保存的帧指针（saved frame pointer）和缓冲区 (&lt;code>buf&lt;/code>)。&lt;/p>
&lt;p>当执行 &lt;code>buf[sizeof(buf)] = '\0';&lt;/code> 时，保存的帧指针 (&lt;code>ebp&lt;/code>) 的最低有效字节被设置为 &lt;code>0&lt;/code>。为了确保 &lt;code>ebp&lt;/code> 在被部分覆盖后仍然指向 &lt;code>buf&lt;/code> 区域内，需要至少 1024 字节的缓冲区。具体来说，需要覆盖 &lt;code>ebp&lt;/code>，使其保持在合理范围内（最高到 &lt;code>0xff&lt;/code>），这就是为什么缓冲区设置为 &lt;code>0xff * 4&lt;/code> 字节的原因。&lt;/p>
&lt;h2 id="理解-foo-返回时的汇编命令">理解 &lt;code>foo&lt;/code> 返回时的汇编命令
&lt;/h2>&lt;p>当 &lt;code>foo&lt;/code> 函数返回时，通常会执行以下关键的汇编指令：&lt;/p>
&lt;h3 id="1-leave-指令">1. &lt;code>leave&lt;/code> 指令
&lt;/h3>&lt;p>&lt;code>leave&lt;/code> 指令等价于：&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-assembly" data-lang="assembly">mov esp, ebp
pop ebp
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>&lt;code>mov esp, ebp&lt;/code>：将栈指针 (&lt;code>esp&lt;/code>) 设置为帧指针 (&lt;code>ebp&lt;/code>) 的值，恢复栈指针到当前栈帧的顶部，从而释放当前函数占用的空间。&lt;/li>
&lt;li>&lt;code>pop ebp&lt;/code>：将栈顶的值弹出并赋给帧指针 (&lt;code>ebp&lt;/code>)，从而恢复调用者的帧指针。实际上，相当于将返回地址写入 &lt;code>ebp&lt;/code>，这意味着将栈中的值（通常是调用者的帧地址）赋给 &lt;code>ebp&lt;/code>，从而恢复调用者的栈帧。&lt;/li>
&lt;/ul>
&lt;p>&lt;code>leave&lt;/code> 的作用是将 &lt;code>esp&lt;/code> 恢复到函数调用前的位置，并弹出保存的 &lt;code>ebp&lt;/code>。如果 &lt;code>ebp&lt;/code> 被覆盖为指向某个特殊地址（例如缓冲区内的地址），在函数返回时可能导致错误的栈指针位置。&lt;/p>
&lt;h3 id="2-ret-指令">2. &lt;code>ret&lt;/code> 指令
&lt;/h3>&lt;p>&lt;code>ret&lt;/code> 指令从栈顶弹出一个地址并跳转到该地址：&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-assembly" data-lang="assembly">pop eip
&lt;/code>&lt;/pre>&lt;p>如果返回地址被覆盖为 &lt;code>bar&lt;/code> 函数的地址，执行流将跳转到 &lt;code>bar&lt;/code>，允许攻击者执行任意代码。本质上，&lt;code>ret&lt;/code> 会将一个地址弹出到指令指针 (&lt;code>eip&lt;/code>) 并跳转到该地址继续执行。&lt;/p>
&lt;h2 id="攻击步骤">攻击步骤
&lt;/h2>&lt;ul>
&lt;li>当执行命令 &lt;code>perl -e 'system &amp;quot;./obo&amp;quot;, &amp;quot;\x38\x84\x04\x08&amp;quot;x256'&lt;/code> 时，程序会将这些重复的字节作为输入传递给 &lt;code>./obo&lt;/code>。&lt;/li>
&lt;li>在 &lt;code>foo&lt;/code> 函数返回时，执行了 &lt;code>leave&lt;/code> 和 &lt;code>ret&lt;/code> 指令，由于返回地址被覆盖，程序跳转到 &lt;code>bar&lt;/code> 函数，打印出成功的消息多次。&lt;/li>
&lt;/ul>
&lt;h2 id="进一步分析确定有效的覆盖位置">进一步分析：确定有效的覆盖位置
&lt;/h2>&lt;p>为了成功执行攻击，关键在于精确确定需要覆盖哪些字节，以有效地操控控制流。在这个例子中，溢出发生在执行 &lt;code>buf[sizeof(buf)] = '\0'&lt;/code> 时，导致保存的帧指针 (&lt;code>ebp&lt;/code>) 的最低有效字节被设置为 &lt;code>0&lt;/code>。因此，需要调整 &lt;code>ebp&lt;/code> 的值，确保其指向缓冲区区域，从而使执行流程按预期进行，最终跳转到 &lt;code>bar&lt;/code> 函数。&lt;/p>
&lt;p>基于进一步的分析和测试，得出以下见解：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>要准确确定覆盖位置，&lt;code>ebp&lt;/code> 的值至关重要。然而，获得这个值是有挑战的，因为：&lt;/p>
&lt;ol>
&lt;li>GDB 调试会影响地址布局。&lt;/li>
&lt;li>输入参数的长度会影响地址布局。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;p>在 GDB 调试下，&lt;code>foo&lt;/code> 函数内的布局如下：&lt;/p>
&lt;/li>
&lt;li>
&lt;p>执行 &lt;code>buf[sizeof(buf)] = '\0';&lt;/code> 后，&lt;code>ebp&lt;/code> 被修改，使得返回地址实际上取的是 &lt;code>ebp + 1&lt;/code> 处的值，即地址 &lt;code>0xbfffed00 + 1&lt;/code>，也就是 &lt;code>0xbfffed04&lt;/code>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>对应的偏移位置在 &lt;code>buf&lt;/code> 的第 255 个位置，这意味着可以通过只在该特定位置填入返回地址来构造攻击。在 GDB 中使用以下命令进行验证：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>r &lt;span style="color:#66d9ef">$(&lt;/span>perl -e &lt;span style="color:#e6db74">&amp;#39;print &amp;#34;\x01\x01\x01\x01&amp;#34;x254 . &amp;#34;\x38\x84\x04\x08&amp;#34;x1 . &amp;#34;\x01\x01\x01\x01&amp;#34;x1&amp;#39;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>经过验证，这在 GDB 调试下是有效的，但需要注意以下细节：&lt;/p>
&lt;ol>
&lt;li>输入参数的长度必须始终为 256 字节，否则 &lt;code>ebp&lt;/code> 的值会发生变化，因为输入参数会占用栈空间，从而影响帧的起始位置，进而影响 &lt;code>ebp&lt;/code> 的值。&lt;/li>
&lt;li>填充必须使用非零值，如 &lt;code>0x01&lt;/code>，因为如果遇到 &lt;code>0&lt;/code> 值，&lt;code>strncpy&lt;/code> 会提前终止。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;p>直接执行程序时（即不使用 GDB），内存布局不同，导致偏移位置也不同。通过实验，发现偏移位置在第 235 个位置。对应的攻击命令是：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>./obo &lt;span style="color:#66d9ef">$(&lt;/span>perl -e &lt;span style="color:#e6db74">&amp;#39;print &amp;#34;\x01\x01\x01\x01&amp;#34;x234 . &amp;#34;\x38\x84\x04\x08&amp;#34;x1 . &amp;#34;\x01\x01\x01\x01&amp;#34;x21&amp;#39;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;p>这样就实现了找到准确覆盖位置并成功执行攻击的效果。&lt;/p></description></item><item><title>将ChatGPT公式格式转换为Obsidian兼容格式</title><link>https://nansenli.com/zh-cn/post/2024/10/obsidian-latex-fix/</link><pubDate>Mon, 14 Oct 2024 15:24:27 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2024/10/obsidian-latex-fix/</guid><description>&lt;p>ChatGPT生成的公式使用了以下格式：&lt;/p>
&lt;pre tabindex="0">&lt;code>\[
公式内容
\]
&lt;/code>&lt;/pre>&lt;p>而Obsidian中的公式渲染使用的是以下格式：&lt;/p>
&lt;pre tabindex="0">&lt;code>$$
公式内容
$$
&lt;/code>&lt;/pre>&lt;p>当我们将ChatGPT的公式复制到Obsidian中时，这种差异会导致无法正确渲染。&lt;/p>
&lt;h2 id="解决方案">解决方案
&lt;/h2>&lt;p>我们可以创建一个Obsidian脚本，在粘贴操作时自动替换公式的格式。&lt;/p>
&lt;h3 id="1-创建脚本">1. 创建脚本
&lt;/h3>&lt;p>可以使用Obsidian中的插件来解决这个问题。&lt;/p>
&lt;p>在你的库中，在&lt;code>template&lt;/code>目录下创建一个文件&lt;code>fixlatex.js&lt;/code>，并输入以下内容：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">module&lt;/span>.&lt;span style="color:#a6e22e">exports&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">async&lt;/span> (&lt;span style="color:#a6e22e">params&lt;/span>) =&amp;gt; {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> { &lt;span style="color:#a6e22e">quickAddApi&lt;/span> } &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">params&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 获取剪贴板内容
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#a6e22e">clipboardContent&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">await&lt;/span> &lt;span style="color:#a6e22e">quickAddApi&lt;/span>.&lt;span style="color:#a6e22e">utility&lt;/span>.&lt;span style="color:#a6e22e">getClipboard&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 检查是否成功获取内容
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>&lt;span style="color:#a6e22e">clipboardContent&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#a6e22e">Notice&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;剪贴板为空或无法访问。&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#a6e22e">modifiedContent&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">clipboardContent&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .&lt;span style="color:#a6e22e">replace&lt;/span>(&lt;span style="color:#e6db74">/\\\[|\\\]/g&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;$$$$&amp;#39;&lt;/span>) &lt;span style="color:#75715e">// 转换 \[ \] 为 $$ $$
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> .&lt;span style="color:#a6e22e">replace&lt;/span>(&lt;span style="color:#e6db74">/\\\(\s*|\s*\\\)/g&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;$$&amp;#39;&lt;/span>); &lt;span style="color:#75715e">// 转换 \( \) 为 $
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 将修改后的内容写回剪贴板
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">await&lt;/span> &lt;span style="color:#a6e22e">navigator&lt;/span>.&lt;span style="color:#a6e22e">clipboard&lt;/span>.&lt;span style="color:#a6e22e">writeText&lt;/span>(&lt;span style="color:#a6e22e">modifiedContent&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">new&lt;/span> &lt;span style="color:#a6e22e">Notice&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;剪贴板内容已处理并修改！&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-在quickadd中设置脚本">2. 在QuickAdd中设置脚本
&lt;/h3>&lt;p>安装QuickAdd插件，并创建一个&lt;code>Macro&lt;/code>，按如下图中的配置进行设置并保存。Macro的第一步是执行我们刚刚创建的用户脚本&lt;code>fixlatex.js&lt;/code>，第二步是等待100毫秒，第三步是执行粘贴操作。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/ec99ec1deab8.png"
loading="lazy"
>&lt;/p>
&lt;h3 id="3-在commander中设置侧边快捷键">3. 在Commander中设置侧边快捷键
&lt;/h3>&lt;p>安装Commander插件，并将刚刚创建的QuickAdd操作设置为侧边栏的快捷键。你也可以跳过这一步，直接使用Obsidian命令执行这个操作。&lt;/p>
&lt;h3 id="4-验证效果">4. 验证效果
&lt;/h3>&lt;p>现在，在ChatGPT的网页中（目前在APP中点击复制按钮似乎有点问题），点击复制按钮后，在Obsidian中点击侧边栏快捷键，或者手动执行QuickAdd命令，就可以将ChatGPT中的内容复制到Obsidian中，并自动转换Latex格式。&lt;/p></description></item><item><title>关于我</title><link>https://nansenli.com/zh-cn/post/2024/10/aboutme/</link><pubDate>Fri, 11 Oct 2024 20:55:02 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/2024/10/aboutme/</guid><description>&lt;h1 id="关于我">关于我
&lt;/h1>&lt;p>本文书写于 2024年10月11日，都柏林，爱尔兰。&lt;/p>
&lt;hr>
&lt;h2 id="自我介绍">自我介绍
&lt;/h2>&lt;p>大家好，我叫李楠森，曾用名李楠。我来自中国，是一名热爱科技的工程师。我拥有机械电子的本科学位和两个计算机科学的硕士学位。我曾在中国一家大型互联网公司担任过 Go 语言工程师，目前正在都柏林一所大学攻读硕士学位。之后计划在爱尔兰发展。&lt;/p>
&lt;p>我对前端和后端开发、系统架构、算法、游戏开发，以及生成式 AI 有着浓厚的兴趣。我一直致力于提高自己的编程能力，也喜欢尝试新技术，保持对行业发展的敏感度。&lt;/p>
&lt;p>除了软件开发设计外，我也有其他各种各样的背景。我曾经参加过嵌入式系统设计、机械软件设计，曾经焊接过电路板，设计过电子元器件，包括放大器等检测电路和模拟摄像头或数字摄像头等功能元件，了解控制系统和控制过程，例如PID。绘制工程图，例如汽车汽缸等模型，使用AutoCAD建模等。参与嵌入式防火墙设计。我还拥有带领不同队伍参加编程或者电子设计的竞赛经历。&lt;/p>
&lt;p>如果你对技术或者生活方式有相同的兴趣，欢迎联系我，我们可以一起讨论和交流。&lt;/p></description></item><item><title>K8S经典安装流程</title><link>https://nansenli.com/zh-cn/post/jianshu/k8s/k8s%E7%BB%8F%E5%85%B8%E5%AE%89%E8%A3%85%E6%B5%81%E7%A8%8B/</link><pubDate>Sun, 20 Jun 2021 12:21:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/k8s/k8s%E7%BB%8F%E5%85%B8%E5%AE%89%E8%A3%85%E6%B5%81%E7%A8%8B/</guid><description>&lt;h1 id="在所有机器上运行">在所有机器上运行
&lt;/h1>&lt;p>机器均为debian
首先安装docker&lt;/p>
&lt;pre tabindex="0">&lt;code> sudo apt-get update
sudo apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo \
&amp;#34;deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \
$(lsb_release -cs) stable&amp;#34; | sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
sudo mkdir /etc/docker
cat &amp;lt;&amp;lt;EOF | sudo tee /etc/docker/daemon.json
{
&amp;#34;exec-opts&amp;#34;: [&amp;#34;native.cgroupdriver=systemd&amp;#34;],
&amp;#34;log-driver&amp;#34;: &amp;#34;json-file&amp;#34;,
&amp;#34;log-opts&amp;#34;: {
&amp;#34;max-size&amp;#34;: &amp;#34;100m&amp;#34;
},
&amp;#34;storage-driver&amp;#34;: &amp;#34;overlay2&amp;#34;
}
EOF
sudo usermod -aG docker ${USER}
sudo systemctl restart docker
sudo su ${USER}
&lt;/code>&lt;/pre>&lt;p>此时运行 docker ps -a 无异常，docker安装成功&lt;/p>
&lt;p>接下来安装k8s组件&lt;/p>
&lt;pre tabindex="0">&lt;code>cat &amp;lt;&amp;lt;EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF
cat &amp;lt;&amp;lt;EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sudo sysctl --system
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
echo &amp;#34;deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main&amp;#34; | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
&lt;/code>&lt;/pre>&lt;h1 id="在主节点上运行">在主节点上运行
&lt;/h1>&lt;p>接下来初始化控制平面&lt;/p>
&lt;pre tabindex="0">&lt;code>sudo kubeadm init --apiserver-advertise-address masterip --control-plane-endpoint masterdns --pod-network-cidr 10.244.0.0/16
&lt;/code>&lt;/pre>&lt;p>将输出内容的最后部分保存备用，包括如何配置.kube以及节点的加入命令，并按照提示配置.kube文件&lt;/p>
&lt;p>然后初始化网络插件&lt;/p>
&lt;pre tabindex="0">&lt;code>wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
kubectl apply -f kube-flannel.yml
&lt;/code>&lt;/pre>&lt;p>检查pods运行状况是否正常&lt;/p>
&lt;pre tabindex="0">&lt;code>kubectl get pods -A
&lt;/code>&lt;/pre>&lt;p>在另外一个节点安装k8s组件，然后运行加入节点join命令，该命令在kubeadm init结束时会打印出来&lt;/p>
&lt;p>之后检查pods运行状态是否正常&lt;/p>
&lt;p>下载dashboad&lt;/p>
&lt;pre tabindex="0">&lt;code>wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.2.0/aio/deploy/recommended.yaml -o dashboard.yaml
&lt;/code>&lt;/pre>&lt;p>编辑dashboard.yaml
在第一处8843下面添加nodePort: 31001，并在上方修改type:NodePort，找到namespace=处在下面添加一行 - &amp;ndash;token-ttl=43200
具体可以参见
&lt;a class="link" href="https://www.huaweicloud.com/articles/dc1dcb0c48cc785a9193c9ce709c8b35.html" target="_blank" rel="noopener"
>https://www.huaweicloud.com/articles/dc1dcb0c48cc785a9193c9ce709c8b35.html&lt;/a>&lt;/p>
&lt;p>创建管理员角色&lt;/p>
&lt;pre tabindex="0">&lt;code>kubectl create -f https://raw.githubusercontent.com/rootsongjc/kubernetes-handbook/master/manifests/dashboard-1.7.1/admin-role.yaml
&lt;/code>&lt;/pre>&lt;p>应用pod&lt;/p>
&lt;pre tabindex="0">&lt;code>kubectl apply -f dashboard.yaml
&lt;/code>&lt;/pre>&lt;p>检查端口&lt;/p>
&lt;pre tabindex="0">&lt;code>sudo lsof -i:31001
&lt;/code>&lt;/pre>&lt;p>获得token&lt;/p>
&lt;pre tabindex="0">&lt;code>kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-token | awk &amp;#39;{print $1}&amp;#39;)
&lt;/code>&lt;/pre>&lt;p>访问 https://masterip:31001 并在chrome输入 thisisunsafe，然后输入token&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/6e04c88e778d.png"
loading="lazy"
alt="dashboard"
>&lt;/p></description></item><item><title>Docker搭建kafka测试环境</title><link>https://nansenli.com/zh-cn/post/jianshu/docker/docker%E6%90%AD%E5%BB%BAkafka%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83/</link><pubDate>Tue, 23 Jun 2020 07:31:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/docker/docker%E6%90%AD%E5%BB%BAkafka%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83/</guid><description>&lt;p>亲测可用&lt;/p>
&lt;p>新建文件docker-compose.yml&lt;/p>
&lt;pre tabindex="0">&lt;code>version: &amp;#39;2&amp;#39;
services:
zoo:
image: wurstmeister/zookeeper
restart: unless-stopped
hostname: zoo
ports:
- &amp;#34;2181:2181&amp;#34;
container_name: zookeeper
kafka:
image: wurstmeister/kafka
ports:
- &amp;#34;9092:9092&amp;#34;
environment:
KAFKA_ADVERTISED_HOST_NAME: 9.134.5.240 ## 修改:宿主机IP
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://9.134.5.240:19092 ## 修改:宿主机IP
KAFKA_ZOOKEEPER_CONNECT: &amp;#34;zoo:2181&amp;#34;
KAFKA_ADVERTISED_PORT: 9092
KAFKA_BROKER_ID: 1
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
depends_on:
- zoo
container_name: kafka
kafka-manager:
image: sheepkiller/kafka-manager ## 镜像：开源的web管理kafka集群的界面
environment:
ZK_HOSTS: zoo:2181 ## 修改:宿主机IP
ports:
- &amp;#34;9000:9000&amp;#34; ## 暴露端口
&lt;/code>&lt;/pre>&lt;p>运行docker-compose up -d即可&lt;/p></description></item><item><title>socketio学习</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/socket-io%E5%AD%A6%E4%B9%A0/</link><pubDate>Tue, 11 Dec 2018 10:50:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/socket-io%E5%AD%A6%E4%B9%A0/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>很急，暂时不写了&lt;/p>
&lt;h2 id="核心点">核心点
&lt;/h2>&lt;p>不管是服务器还是客户端都有 emit 和 on 这两个函数，可以说 socket.io 的核心就是这两个函数了，通过 emit 和 on 可以轻松地实现服务器与客户端之间的双向通信。&lt;/p>
&lt;p>emit ：用来发射一个事件或者说触发一个事件，第一个参数为事件名，第二个参数为要发送的数据，第三个参数为回调函数（一般省略，如需对方接受到信息后立即得到确认时，则需要用到回调函数）。
on ：用来监听一个 emit 发射的事件，第一个参数为要监听的事件名，第二个参数为一个匿名函数用来接收对方发来的数据，该匿名函数的第一个参数为接收的数据，若有第二个参数，则为要返回的函数。
socket.io 提供了三种默认的事件（客户端和服务器都有）：connect 、message 、disconnect 。当与对方建立连接后自动触发 connect 事件，当收到对方发来的数据后触发 message 事件（通常为 socket.send() 触发），当对方关闭连接后触发 disconnect 事件。&lt;/p>
&lt;p>在服务器端区分以下三种情况：&lt;/p>
&lt;p>socket.emit() ：向建立该连接的客户端广播
socket.broadcast.emit() ：向除去建立该连接的客户端的所有客户端广播
io.sockets.emit() ：向所有客户端广播，等同于上面两个的和&lt;/p></description></item><item><title>Go类型官方参考 中英文对照</title><link>https://nansenli.com/zh-cn/post/jianshu/go/go%E7%B1%BB%E5%9E%8B%E5%AE%98%E6%96%B9%E5%8F%82%E8%80%83-%E4%B8%AD%E8%8B%B1%E6%96%87%E5%AF%B9%E7%85%A7/</link><pubDate>Wed, 14 Nov 2018 08:14:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/go/go%E7%B1%BB%E5%9E%8B%E5%AE%98%E6%96%B9%E5%8F%82%E8%80%83-%E4%B8%AD%E8%8B%B1%E6%96%87%E5%AF%B9%E7%85%A7/</guid><description>&lt;h2 id="官方参考">官方参考
&lt;/h2>&lt;p>&lt;a class="link" href="https://golang.org/ref/spec#Types" target="_blank" rel="noopener"
>https://golang.org/ref/spec#Types&lt;/a>&lt;/p>
&lt;p>其中类型有：&lt;/p>
&lt;p>Method sets 方法集合
Boolean types 布尔类型
Numeric types 数字类型
String types 字符串类型
Array types 数组类型
Slice types 切片类型
Struct types 结构体类型
Pointer types 指针类型
Function types 函数类型
Interface types 接口类型
Map types 字典类型
Channel types 通道类型&lt;/p>
&lt;h2 id="method-sets-方法集合">Method sets 方法集合
&lt;/h2>&lt;p>A type may have a &lt;em>method set&lt;/em> associated with it. The method set of an &lt;a class="link" href="https://golang.org/ref/spec#Interface_types" target="_blank" rel="noopener"
>interface type&lt;/a> is its interface. The method set of any other type &lt;code>T&lt;/code> consists of all &lt;a class="link" href="https://golang.org/ref/spec#Method_declarations" target="_blank" rel="noopener"
>methods&lt;/a> declared with receiver type &lt;code>T&lt;/code>. The method set of the corresponding &lt;a class="link" href="https://golang.org/ref/spec#Pointer_types" target="_blank" rel="noopener"
>pointer type&lt;/a> &lt;code>*T&lt;/code> is the set of all methods declared with receiver &lt;code>*T&lt;/code> or &lt;code>T&lt;/code> (that is, it also contains the method set of &lt;code>T&lt;/code>). Further rules apply to structs containing embedded fields, as described in the section on &lt;a class="link" href="https://golang.org/ref/spec#Struct_types" target="_blank" rel="noopener"
>struct types&lt;/a>. Any other type has an empty method set. In a method set, each method must have a &lt;a class="link" href="https://golang.org/ref/spec#Uniqueness_of_identifiers" target="_blank" rel="noopener"
>unique&lt;/a> non-&lt;a class="link" href="https://golang.org/ref/spec#Blank_identifier" target="_blank" rel="noopener"
>blank&lt;/a>&lt;a class="link" href="https://golang.org/ref/spec#MethodName" target="_blank" rel="noopener"
>method name&lt;/a>.&lt;/p>
&lt;p>The method set of a type determines the interfaces that the type &lt;a class="link" href="https://golang.org/ref/spec#Interface_types" target="_blank" rel="noopener"
>implements&lt;/a> and the methods that can be &lt;a class="link" href="https://golang.org/ref/spec#Calls" target="_blank" rel="noopener"
>called&lt;/a>using a receiver of that type.&lt;/p>
&lt;p>接口类型Interface types的接口就是方法集合。若一组方法的接受参数都为参数T，把这组方法叫T类型的方法集合。T指针的对应方法集合，其接受参数为T或者*T。这意味着T指针的方法集合包含T类型的方法集合。对结构体来说，该规则同样适用。任何类型都有一个空的方法集合。在方法集合中，每个方法都有一个非空的方法名称。&lt;/p>
&lt;p>类型的方法集合确定了该类型实现的接口，以及可以可以被该类型的接收器调用的方法。&lt;/p>
&lt;h2 id="boolean-types-布尔类型">Boolean types 布尔类型
&lt;/h2>&lt;p>A &lt;em>boolean type&lt;/em> represents the set of Boolean truth values denoted by the predeclared constants &lt;code>true&lt;/code> and &lt;code>false&lt;/code>. The predeclared boolean type is &lt;code>bool&lt;/code>; it is a &lt;a class="link" href="https://golang.org/ref/spec#Type_definitions" target="_blank" rel="noopener"
>defined type&lt;/a>.&lt;/p>
&lt;p>布尔类型定义了布尔变量，可被赋值为常量true和false。该类型是已定义类型。例如可以使用char类型来定义。&lt;/p>
&lt;h2 id="numeric-types-数字类型">Numeric types 数字类型
&lt;/h2>&lt;p>A numeric type represents sets of integer or floating-point values. The predeclared architecture-independent numeric types are:&lt;/p>
&lt;pre tabindex="0">&lt;code>uint8 the set of all unsigned 8-bit integers (0 to 255)
uint16 the set of all unsigned 16-bit integers (0 to 65535)
uint32 the set of all unsigned 32-bit integers (0 to 4294967295)
uint64 the set of all unsigned 64-bit integers (0 to 18446744073709551615)
int8 the set of all signed 8-bit integers (-128 to 127)
int16 the set of all signed 16-bit integers (-32768 to 32767)
int32 the set of all signed 32-bit integers (-2147483648 to 2147483647)
int64 the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807)
float32 the set of all IEEE-754 32-bit floating-point numbers
float64 the set of all IEEE-754 64-bit floating-point numbers
complex64 the set of all complex numbers with float32 real and imaginary parts
complex128 the set of all complex numbers with float64 real and imaginary parts
byte alias for uint8
rune alias for int32
&lt;/code>&lt;/pre>&lt;p>The value of an &lt;em>n&lt;/em>-bit integer is &lt;em>n&lt;/em> bits wide and represented using &lt;a class="link" href="https://en.wikipedia.org/wiki/Two%27s_complement" target="_blank" rel="noopener"
>two&amp;rsquo;s complement arithmetic&lt;/a>.&lt;/p>
&lt;pre tabindex="0">&lt;code>uint either 32 or 64 bits
int same size as uint
uintptr an unsigned integer large enough to store the uninterpreted bits of a pointer value
&lt;/code>&lt;/pre>&lt;p>There is also a set of predeclared numeric types with implementation-specific sizes:&lt;/p>
&lt;p>To avoid portability issues all numeric types are &lt;a class="link" href="https://golang.org/ref/spec#Type_definitions" target="_blank" rel="noopener"
>defined types&lt;/a> and thus distinct except &lt;code>byte&lt;/code>, which is an &lt;a class="link" href="https://golang.org/ref/spec#Alias_declarations" target="_blank" rel="noopener"
>alias&lt;/a> for &lt;code>uint8&lt;/code>, and &lt;code>rune&lt;/code>, which is an alias for &lt;code>int32&lt;/code>. Conversions are required when different numeric types are mixed in an expression or assignment. For instance, &lt;code>int32&lt;/code> and &lt;code>int&lt;/code> are not the same type even though they may have the same size on a particular architecture.&lt;/p>
&lt;p>数字类型包括整型和浮点。内置声明包括uint8 uint16 uint32 uint64以及int8 int16 int32 int64 float32 float64 complex64 complex128 byte rune。&lt;/p>
&lt;p>N位整数是N位宽度，使用二进制补码计算。&lt;/p>
&lt;p>与平台无关的声明包块uint int uintptr。&lt;/p>
&lt;p>除了byte和rune外，其他数字类型为已定义类型，而byte是uint8的别名，rune是int32的别名。在数字类型之间的转换需要进行显示转换。&lt;/p>
&lt;h2 id="string-types-字符串类型">String types 字符串类型
&lt;/h2>&lt;p>A &lt;em>string type&lt;/em> represents the set of string values. A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string. The predeclared string type is &lt;code>string&lt;/code>; it is a &lt;a class="link" href="https://golang.org/ref/spec#Type_definitions" target="_blank" rel="noopener"
>defined type&lt;/a>.&lt;/p>
&lt;p>The length of a string &lt;code>s&lt;/code> (its size in bytes) can be discovered using the built-in function &lt;a class="link" href="https://golang.org/ref/spec#Length_and_capacity" target="_blank" rel="noopener"
>&lt;code>len&lt;/code>&lt;/a>. The length is a compile-time constant if the string is a constant. A string&amp;rsquo;s bytes can be accessed by integer &lt;a class="link" href="https://golang.org/ref/spec#Index_expressions" target="_blank" rel="noopener"
>indices&lt;/a> 0 through &lt;code>len(s)-1&lt;/code>. It is illegal to take the address of such an element; if &lt;code>s[i]&lt;/code> is the &lt;code>i&lt;/code>&amp;lsquo;th byte of a string, &lt;code>&amp;amp;s[i]&lt;/code> is invalid.&lt;/p>
&lt;p>字符串类型可以表示所有字符串的值。一个字符串的值是一系列空或不空的字节序列。他的值不能被修改。字符串被创建后就不能修改内容。我们使用string来声明一个string类型。string类型是已定义类型。&lt;/p>
&lt;p>一个字符串的长度（其包含多少个字节）可以使用内建函数len来计算。在编译器可以计算一个常量string的长度。string类型的变量可以访问其位置0到位置len-1的值。对这些位置的元素，获取其地址是非法的。使用s[i]表示s字符串第i个位置的元素，而&amp;amp;s[i]是非法的。&lt;/p>
&lt;h2 id="array-types-数组类型">Array types 数组类型
&lt;/h2>&lt;p>An array is a numbered sequence of elements of a single type, called the element type. The number of elements is called the length and is never negative.&lt;/p>
&lt;pre tabindex="0">&lt;code>ArrayType = &amp;#34;[&amp;#34; ArrayLength &amp;#34;]&amp;#34; ElementType .
ArrayLength = Expression .
ElementType = Type .
&lt;/code>&lt;/pre>&lt;p>The length is part of the array&amp;rsquo;s type; it must evaluate to a non-negative &lt;a class="link" href="https://golang.org/ref/spec#Constants" target="_blank" rel="noopener"
>constant&lt;/a> &lt;a class="link" href="https://golang.org/ref/spec#Representability" target="_blank" rel="noopener"
>representable&lt;/a> by a value of type &lt;code>int&lt;/code>. The length of array &lt;code>a&lt;/code> can be discovered using the built-in function &lt;a class="link" href="https://golang.org/ref/spec#Length_and_capacity" target="_blank" rel="noopener"
>&lt;code>len&lt;/code>&lt;/a>. The elements can be addressed by integer &lt;a class="link" href="https://golang.org/ref/spec#Index_expressions" target="_blank" rel="noopener"
>indices&lt;/a> 0 through &lt;code>len(a)-1&lt;/code>. Array types are always one-dimensional but may be composed to form multi-dimensional types.&lt;/p>
&lt;pre tabindex="0">&lt;code>[32]byte
[2*N] struct { x, y int32 }
[1000]*float64
[3][5]int
[2][2][2]float64 // same as [2]([2]([2]float64))
&lt;/code>&lt;/pre>&lt;p>数组类型是多个同类型的元素构成的可数序列。元素的数量是数组的长度。长度不能为负数。&lt;/p>
&lt;p>数组的长度也是数组的一部分。他的值非负，可以用int表示。数组的长度使用len求出。数组的所有元素可以从位置0到len-1进行访问。数组类型永远是一维的，进行组合可以表示多位类型。&lt;/p>
&lt;h2 id="slice-types-切片">Slice types 切片
&lt;/h2>&lt;p>A slice is a descriptor for a contiguous segment of an &lt;em>underlying array&lt;/em> and provides access to a numbered sequence of elements from that array. A slice type denotes the set of all slices of arrays of its element type. The value of an uninitialized slice is &lt;code>nil&lt;/code>.&lt;/p>
&lt;pre tabindex="0">&lt;code>SliceType = &amp;#34;[&amp;#34; &amp;#34;]&amp;#34; ElementType .
&lt;/code>&lt;/pre>&lt;p>Like arrays, slices are indexable and have a length. The length of a slice &lt;code>s&lt;/code> can be discovered by the built-in function &lt;a class="link" href="https://golang.org/ref/spec#Length_and_capacity" target="_blank" rel="noopener"
>&lt;code>len&lt;/code>&lt;/a>; unlike with arrays it may change during execution. The elements can be addressed by integer &lt;a class="link" href="https://golang.org/ref/spec#Index_expressions" target="_blank" rel="noopener"
>indices&lt;/a> 0 through &lt;code>len(s)-1&lt;/code>. The slice index of a given element may be less than the index of the same element in the underlying array.&lt;/p>
&lt;p>A slice, once initialized, is always associated with an underlying array that holds its elements. A slice therefore shares storage with its array and with other slices of the same array; by contrast, distinct arrays always represent distinct storage.&lt;/p>
&lt;p>The array underlying a slice may extend past the end of the slice. The &lt;em>capacity&lt;/em> is a measure of that extent: it is the sum of the length of the slice and the length of the array beyond the slice; a slice of length up to that capacity can be created by &lt;a class="link" href="https://golang.org/ref/spec#Slice_expressions" target="_blank" rel="noopener"
>&lt;em>slicing&lt;/em>&lt;/a> a new one from the original slice. The capacity of a slice &lt;code>a&lt;/code> can be discovered using the built-in function &lt;a class="link" href="https://golang.org/ref/spec#Length_and_capacity" target="_blank" rel="noopener"
>&lt;code>cap(a)&lt;/code>&lt;/a>.&lt;/p>
&lt;p>A new, initialized slice value for a given element type &lt;code>T&lt;/code> is made using the built-in function &lt;a class="link" href="https://golang.org/ref/spec#Making_slices_maps_and_channels" target="_blank" rel="noopener"
>&lt;code>make&lt;/code>&lt;/a>, which takes a slice type and parameters specifying the length and optionally the capacity. A slice created with &lt;code>make&lt;/code> always allocates a new, hidden array to which the returned slice value refers. That is, executing&lt;/p>
&lt;pre tabindex="0">&lt;code>make([]T, length, capacity)
&lt;/code>&lt;/pre>&lt;p>produces the same slice as allocating an array and &lt;a class="link" href="https://golang.org/ref/spec#Slice_expressions" target="_blank" rel="noopener"
>slicing&lt;/a> it, so these two expressions are equivalent:&lt;/p>
&lt;pre tabindex="0">&lt;code>make([]int, 50, 100)
new([100]int)[0:50]
&lt;/code>&lt;/pre>&lt;p>Like arrays, slices are always one-dimensional but may be composed to construct higher-dimensional objects. With arrays of arrays, the inner arrays are, by construction, always the same length; however with slices of slices (or arrays of slices), the inner lengths may vary dynamically. Moreover, the inner slices must be initialized individually.&lt;/p>
&lt;p>切片表示底层数组的连续段，提供对该段位置的元素访问。切片的类型表示其元素的类型。未初始化的切片的值为nil。&lt;/p>
&lt;p>与数组相同，切片的长度可以使用len来求出。长度通常小于原来的数组。&lt;/p>
&lt;p>切片自初始化之后与低层数组共享存储空间，多个切片可以共享同一个存储空间。数组通常代表了不同的存储空间。&lt;/p>
&lt;p>切片的低层数组长度可以超过切片的末端，使用cap可以计算切片的容量，容量是数组超出切片的值加上切片本身的值。可以通过对切片重新切分得到新的超过原长度的切片。&lt;/p>
&lt;p>使用内建函数make创建指定类型的切片。make接受切片类型和切片的长度，容量作为可选参数。使用make创建出来的切片总是被分配一个切片所引用的隐藏数组，这意味着通过make创建长度50容量100的切片make([]int, 50, 100)与通过new创建一个长度100的数组并对其切片50的长度new([100]int)[0:50]两者的结果相同。&lt;/p>
&lt;p>与数组相同，切片的长度是一维的，但是可以表示更高的维度。对于数组来说，内部的数组由于构造的长度相同，是不能改变的。复合切片的内部长度会变成动态的，复合切片的内部也需要单独初始化。&lt;/p>
&lt;h2 id="struct-types-结构体">Struct types 结构体
&lt;/h2>&lt;p>A struct is a sequence of named elements, called fields, each of which has a name and a type. Field names may be specified explicitly (IdentifierList) or implicitly (EmbeddedField). Within a struct, non-&lt;a class="link" href="https://golang.org/ref/spec#Blank_identifier" target="_blank" rel="noopener"
>blank&lt;/a> field names must be &lt;a class="link" href="https://golang.org/ref/spec#Uniqueness_of_identifiers" target="_blank" rel="noopener"
>unique&lt;/a>.&lt;/p>
&lt;pre tabindex="0">&lt;code>StructType = &amp;#34;struct&amp;#34; &amp;#34;{&amp;#34; { FieldDecl &amp;#34;;&amp;#34; } &amp;#34;}&amp;#34; .
FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] .
EmbeddedField = [ &amp;#34;*&amp;#34; ] TypeName .
Tag = string_lit .
&lt;/code>&lt;/pre>&lt;pre tabindex="0">&lt;code>// An empty struct.
struct {}
// A struct with 6 fields.
struct {
x, y int
u float32
_ float32 // padding
A *[]int
F func()
}
&lt;/code>&lt;/pre>&lt;p>A field declared with a type but no explicit field name is called an embedded field. An embedded field must be specified as a type name T or as a pointer to a non-interface type name *T, and T itself may not be a pointer type. The unqualified type name acts as the field name.&lt;/p>
&lt;pre tabindex="0">&lt;code>// A struct with four embedded fields of types T1, *T2, P.T3 and *P.T4
struct {
T1 // field name is T1
*T2 // field name is T2
P.T3 // field name is T3
*P.T4 // field name is T4
x, y int // field names are x and y
}
&lt;/code>&lt;/pre>&lt;p>The following declaration is illegal because field names must be unique in a struct type:&lt;/p>
&lt;pre tabindex="0">&lt;code>struct {
T // conflicts with embedded field *T and *P.T
*T // conflicts with embedded field T and *P.T
*P.T // conflicts with embedded field T and *T
}
&lt;/code>&lt;/pre>&lt;p>A field or &lt;a class="link" href="https://golang.org/ref/spec#Method_declarations" target="_blank" rel="noopener"
>method&lt;/a> &lt;code>f&lt;/code> of an embedded field in a struct &lt;code>x&lt;/code> is called &lt;em>promoted&lt;/em> if &lt;code>x.f&lt;/code> is a legal &lt;a class="link" href="https://golang.org/ref/spec#Selectors" target="_blank" rel="noopener"
>selector&lt;/a> that denotes that field or method &lt;code>f&lt;/code>.&lt;/p>
&lt;p>Promoted fields act like ordinary fields of a struct except that they cannot be used as field names in &lt;a class="link" href="https://golang.org/ref/spec#Composite_literals" target="_blank" rel="noopener"
>composite literals&lt;/a> of the struct.&lt;/p>
&lt;p>Given a struct type &lt;code>S&lt;/code> and a &lt;a class="link" href="https://golang.org/ref/spec#Type_definitions" target="_blank" rel="noopener"
>defined type&lt;/a> &lt;code>T&lt;/code>, promoted methods are included in the method set of the struct as follows:&lt;/p>
&lt;ul>
&lt;li>If &lt;code>S&lt;/code> contains an embedded field &lt;code>T&lt;/code>, the &lt;a class="link" href="https://golang.org/ref/spec#Method_sets" target="_blank" rel="noopener"
>method sets&lt;/a> of &lt;code>S&lt;/code> and &lt;code>*S&lt;/code> both include promoted methods with receiver &lt;code>T&lt;/code>. The method set of &lt;code>*S&lt;/code> also includes promoted methods with receiver &lt;code>*T&lt;/code>.&lt;/li>
&lt;li>If &lt;code>S&lt;/code> contains an embedded field &lt;code>*T&lt;/code>, the method sets of &lt;code>S&lt;/code> and &lt;code>*S&lt;/code> both include promoted methods with receiver &lt;code>T&lt;/code> or&lt;code>*T&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>A field declaration may be followed by an optional string literal &lt;em>tag&lt;/em>, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag. The tags are made visible through a &lt;a class="link" href="https://golang.org/pkg/reflect/#StructTag" target="_blank" rel="noopener"
>reflection interface&lt;/a> and take part in &lt;a class="link" href="https://golang.org/ref/spec#Type_identity" target="_blank" rel="noopener"
>type identity&lt;/a> for structs but are otherwise ignored.&lt;/p>
&lt;pre tabindex="0">&lt;code>struct {
x, y float64 &amp;#34;&amp;#34; // an empty tag string is like an absent tag
name string &amp;#34;any string is permitted as a tag&amp;#34;
_ [4]byte &amp;#34;ceci n&amp;#39;est pas un champ de structure&amp;#34;
}
// A struct corresponding to a TimeStamp protocol buffer.
// The tag strings define the protocol buffer field numbers;
// they follow the convention outlined by the reflect package.
struct {
microsec uint64 `protobuf:&amp;#34;1&amp;#34;`
serverIP6 uint64 `protobuf:&amp;#34;2&amp;#34;`
}
&lt;/code>&lt;/pre>&lt;p>结构体是一系列字段的集合，每个字段有自己的名称和类型。类型可以隐式指定。在结构中非空字段名称必须是唯一的。&lt;/p>
&lt;p>结构体可以为空。也可以包含int float等类型。如果使用_表示padding占位。&lt;/p>
&lt;p>如果结构体的字段只有类型而没有名字，我们表示这个字段是嵌入字段。嵌入字段的类型必须是类型T，或者是一个非接口类型的指针。&lt;/p>
&lt;p>结构体x中的嵌入字段的字段或方法叫做结构体x的提升。对结构体x，调用其不存在的成员或者方法，会使用嵌入字段的字段或方法来代替。&lt;/p>
&lt;p>被提升的字段表现的就像原始字段，除了他们不能用在特殊场合。&lt;/p>
&lt;p>给定结构体类型和定义的类型T，提升方法包含在结构体的方法集中。&lt;/p>
&lt;p>如果S包含嵌入字段T，使用T为接受着的话，提升方法被包含在S和&lt;em>S的方法集合。使用&lt;/em>T为接受者的话，提升方法被包含在*S的方法集合。&lt;/p>
&lt;p>结构体的字段后面，可以跟上一个字符串。这个字符串可以通过反射的方式获取。可以用于特殊的场合。例如json转换，protobuf转换。&lt;/p>
&lt;h2 id="pointer-types-指针类型">Pointer types 指针类型
&lt;/h2>&lt;p>A pointer type denotes the set of all pointers to variables of a given type, called the base type of the pointer. The value of an uninitialized pointer is nil.&lt;/p>
&lt;pre tabindex="0">&lt;code>PointerType = &amp;#34;*&amp;#34; BaseType .
BaseType = Type .
*Point
*[4]int
&lt;/code>&lt;/pre>&lt;p>一个指针类型表示指向给定类型所有变量的指针的类型，是指针的基础类型。指针在未被初始化的时候使用nil作为初值。&lt;/p>
&lt;h2 id="function-types-函数类型">Function types 函数类型
&lt;/h2>&lt;p>A function type denotes the set of all functions with the same parameter and result types. The value of an uninitialized variable of function type is nil.&lt;/p>
&lt;pre tabindex="0">&lt;code>FunctionType = &amp;#34;func&amp;#34; Signature .
Signature = Parameters [ Result ] .
Result = Parameters | Type .
Parameters = &amp;#34;(&amp;#34; [ ParameterList [ &amp;#34;,&amp;#34; ] ] &amp;#34;)&amp;#34; .
ParameterList = ParameterDecl { &amp;#34;,&amp;#34; ParameterDecl } .
ParameterDecl = [ IdentifierList ] [ &amp;#34;...&amp;#34; ] Type .
&lt;/code>&lt;/pre>&lt;p>Within a list of parameters or results, the names (IdentifierList) must either all be present or all be absent. If present, each name stands for one item (parameter or result) of the specified type and all non-blank names in the signature must be unique. If absent, each type stands for one item of that type. Parameter and result lists are always parenthesized except that if there is exactly one unnamed result it may be written as an unparenthesized type.&lt;/p>
&lt;p>The final incoming parameter in a function signature may have a type prefixed with &amp;hellip;. A function with such a parameter is called variadic and may be invoked with zero or more arguments for that parameter.&lt;/p>
&lt;pre tabindex="0">&lt;code>func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefix string, values ...int)
func(a, b int, z float64, opt ...interface{}) (success bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)
&lt;/code>&lt;/pre>&lt;p>一个函数类型表示所有拥有相同参数和返回值的函数的类型。未被初始化的函数类型变量的值是nil。&lt;/p>
&lt;p>在参数列表或者返回列表中，名称必须都被显示的声明出来，或者都没有声明，只写类型。如果写了名称，每个名称都有类型和非空的名字，而名字必须唯一。如果没有声明名字，那就只写类型。参数和返回列表需要使用括号包裹。返回列表在只有一个类型且没有名字时可以不加括号。&lt;/p>
&lt;p>最后传入的参数的前面可以使用可变参数，前缀可以使用三个点。调用时，需要传入0个或以上的参数。&lt;/p>
&lt;h2 id="interface-types-接口类型">Interface types 接口类型
&lt;/h2>&lt;p>An interface type specifies a method set called its interface. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.&lt;/p>
&lt;pre tabindex="0">&lt;code>InterfaceType = &amp;#34;interface&amp;#34; &amp;#34;{&amp;#34; { MethodSpec &amp;#34;;&amp;#34; } &amp;#34;}&amp;#34; .
MethodSpec = MethodName Signature | InterfaceTypeName .
MethodName = identifier .
InterfaceTypeName = TypeName .
&lt;/code>&lt;/pre>&lt;p>As with all method sets, in an interface type, each method must have a unique non-blank name.&lt;/p>
&lt;pre tabindex="0">&lt;code>// A simple File interface
interface {
Read(b Buffer) bool
Write(b Buffer) bool
Close()
}
&lt;/code>&lt;/pre>&lt;p>More than one type may implement an interface. For instance, if two types S1 and S2 have the method set&lt;/p>
&lt;pre tabindex="0">&lt;code>func (p T) Read(b Buffer) bool { return … }
func (p T) Write(b Buffer) bool { return … }
func (p T) Close() { … }
&lt;/code>&lt;/pre>&lt;p>(where T stands for either S1 or S2) then the File interface is implemented by both S1 and S2, regardless of what other methods S1 and S2 may have or share.&lt;/p>
&lt;p>A type implements any interface comprising any subset of its methods and may therefore implement several distinct interfaces. For instance, all types implement the empty interface:&lt;/p>
&lt;p>interface{}
Similarly, consider this interface specification, which appears within a type declaration to define an interface called Locker:&lt;/p>
&lt;pre tabindex="0">&lt;code>type Locker interface {
Lock()
Unlock()
}
&lt;/code>&lt;/pre>&lt;p>If S1 and S2 also implement&lt;/p>
&lt;pre tabindex="0">&lt;code>func (p T) Lock() { … }
func (p T) Unlock() { … }
&lt;/code>&lt;/pre>&lt;p>they implement the Locker interface as well as the File interface.&lt;/p>
&lt;p>An interface T may use a (possibly qualified) interface type name E in place of a method specification. This is called embedding interface E in T; it adds all (exported and non-exported) methods of E to the interface T.&lt;/p>
&lt;pre tabindex="0">&lt;code>type ReadWriter interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type File interface {
ReadWriter // same as adding the methods of ReadWriter
Locker // same as adding the methods of Locker
Close()
}
type LockedFile interface {
Locker
File // illegal: Lock, Unlock not unique
Lock() // illegal: Lock not unique
}
&lt;/code>&lt;/pre>&lt;p>An interface type T may not embed itself or any interface type that embeds T, recursively.&lt;/p>
&lt;pre tabindex="0">&lt;code>// illegal: Bad cannot embed itself
type Bad interface {
Bad
}
// illegal: Bad1 cannot embed itself using Bad2
type Bad1 interface {
Bad2
}
type Bad2 interface {
Bad1
}
&lt;/code>&lt;/pre>&lt;p>接口类型表示一个方法集合，该方法集合叫做该类型的接口。接口类型可以存储一个变量，以及该变量的方法集合的任何超集。传入的类型与对应的接口叫做该类型的实现。未被初始化的接口类型变量的值是nil。&lt;/p>
&lt;p>和方法集合一样，接口类型的内部，每个方法必须油唯一非空名字。&lt;/p>
&lt;p>如果S1、S2两种类型的方法集和都包含了同样的接口实现，不管方法集合其他的区别是什么，S1和S2都被认为是接口的实现。&lt;/p>
&lt;p>如果一个类型实现了一个接口，这个接口的子集又是其他接口的实现，那么这个类型也会实现那些接口。举个例子，所有的类型都实现了空接口。&lt;/p>
&lt;p>接口中也可以内嵌其他接口，但是其内嵌的接口的方法的名字不能冲突。&lt;/p>
&lt;h2 id="map-types-字典类型">Map types 字典类型
&lt;/h2>&lt;p>A map is an unordered group of elements of one type, called the element type, indexed by a set of unique keys of another type, called the key type. The value of an uninitialized map is nil.&lt;/p>
&lt;p>MapType = &amp;ldquo;map&amp;rdquo; &amp;ldquo;[&amp;rdquo; KeyType &amp;ldquo;]&amp;rdquo; ElementType .
KeyType = Type .
The comparison operators == and != must be fully defined for operands of the key type; thus the key type must not be a function, map, or slice. If the key type is an interface type, these comparison operators must be defined for the dynamic key values; failure will cause a run-time panic.&lt;/p>
&lt;p>map[string]int
map[*T]struct{ x, y float64 }
map[string]interface{}
The number of map elements is called its length. For a map m, it can be discovered using the built-in function len and may change during execution. Elements may be added during execution using assignments and retrieved with index expressions; they may be removed with the delete built-in function.&lt;/p>
&lt;p>A new, empty map value is made using the built-in function make, which takes the map type and an optional capacity hint as arguments:&lt;/p>
&lt;p>make(map[string]int)
make(map[string]int, 100)
The initial capacity does not bound its size: maps grow to accommodate the number of items stored in them, with the exception of nil maps. A nil map is equivalent to an empty map except that no elements may be added.&lt;/p>
&lt;p>字典类型的内部是无序元素排序的类型，内部的类型叫元素的类型，使用唯一键值进行索引，键值也有另外一种类型，叫键类型。未被初始化的字典类型的值是nil。&lt;/p>
&lt;p>键值类型必须完全定义了==运算和！=运算。因此函数、字典、切片都是不允许作为键值的。如果键值是一个接口，比较操作必须被定义为可以接受一个动态的键值。对键值的相关操作失败时会引发程序运行时惊慌。&lt;/p>
&lt;p>字典元素的数量称为字典的长度。可以通过len函数检查，其值在运行期间可能被更改。元素可能通过指派操作添加，可以通过下标取出，可以通过delete函数移除。&lt;/p>
&lt;p>一个空的map的值可以通过内建函数make来创建，make的第一个参数是map的类型，第二个参数是容量。&lt;/p>
&lt;p>字典的容量不限制大小，字典的容量会增长以容纳其中项目的数量。但nil字典不是这样的，nil字典基本上和空字典是一样的，除了不能添加任何元素。&lt;/p>
&lt;h2 id="channel-types-通道类型">Channel types 通道类型
&lt;/h2>&lt;p>A channel provides a mechanism for concurrently executing functions to communicate by sending and receiving values of a specified element type. The value of an uninitialized channel is nil.&lt;/p>
&lt;p>ChannelType = ( &amp;ldquo;chan&amp;rdquo; | &amp;ldquo;chan&amp;rdquo; &amp;ldquo;&amp;lt;-&amp;rdquo; | &amp;ldquo;&amp;lt;-&amp;rdquo; &amp;ldquo;chan&amp;rdquo; ) ElementType .
The optional &amp;lt;- operator specifies the channel direction, send or receive. If no direction is given, the channel is bidirectional. A channel may be constrained only to send or only to receive by conversion or assignment.&lt;/p>
&lt;p>chan T // can be used to send and receive values of type T
chan&amp;lt;- float64 // can only be used to send float64s
&amp;lt;-chan int // can only be used to receive ints
The &amp;lt;- operator associates with the leftmost chan possible:&lt;/p>
&lt;p>chan&amp;lt;- chan int // same as chan&amp;lt;- (chan int)
chan&amp;lt;- &amp;lt;-chan int // same as chan&amp;lt;- (&amp;lt;-chan int)
&amp;lt;-chan &amp;lt;-chan int // same as &amp;lt;-chan (&amp;lt;-chan int)
chan (&amp;lt;-chan int)
A new, initialized channel value can be made using the built-in function make, which takes the channel type and an optional capacity as arguments:&lt;/p>
&lt;p>make(chan int, 100)
The capacity, in number of elements, sets the size of the buffer in the channel. If the capacity is zero or absent, the channel is unbuffered and communication succeeds only when both a sender and receiver are ready. Otherwise, the channel is buffered and communication succeeds without blocking if the buffer is not full (sends) or not empty (receives). A nil channel is never ready for communication.&lt;/p>
&lt;p>A channel may be closed with the built-in function close. The multi-valued assignment form of the receive operator reports whether a received value was sent before the channel was closed.&lt;/p>
&lt;p>A single channel may be used in send statements, receive operations, and calls to the built-in functions cap and len by any number of goroutines without further synchronization. Channels act as first-in-first-out queues. For example, if one goroutine sends values on a channel and a second goroutine receives them, the values are received in the order sent.&lt;/p>
&lt;p>通道提供了并行执行程序之间的通信方式，该方式使用发送和接受特定类型的值来实现。未被初始化的通道的值是nil。&lt;/p>
&lt;p>标记&amp;lt;-用来指示通道的方向，例如是接受还是发送。如果不指定方向，则通道从是双向的。&lt;/p>
&lt;p>通道的&amp;lt;-标记，转换或指派通道的方向，成为发送或接收。&lt;/p>
&lt;p>通道所传递的内容也可以是通道。&lt;/p>
&lt;p>使用make也可以创建初始化的通道，make的第二个参数可以设置通道的容量。&lt;/p>
&lt;p>通道的容量是通道中元素的缓存数量。如果容量是0或者不设置，通道是没有缓存的，只有接收者和发送者都准备好时才进行通信。否则，通道在缓存没有满时，是不阻塞的。如果通道的值是0，则永远无法通信。&lt;/p>
&lt;p>通道使用系统调用close来关闭。使用多值接受的第二个参数来判断通道是否关闭。&lt;/p>
&lt;p>单向通道用于发送表达式，接收操作。cap与len也可以调用，并且无须担心多个同步的协程同时运行。通道是先入先出的，如果第一个协成放入了一个值，第二个协成取出的一定是先放入的值。&lt;/p></description></item><item><title>合法单线双拨——提高寝室路由带宽利用率</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/%E5%90%88%E6%B3%95%E5%8D%95%E7%BA%BF%E5%8F%8C%E6%8B%A8%E6%8F%90%E9%AB%98%E5%AF%9D%E5%AE%A4%E8%B7%AF%E7%94%B1%E5%B8%A6%E5%AE%BD%E5%88%A9%E7%94%A8%E7%8E%87/</link><pubDate>Sat, 10 Nov 2018 18:08:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/%E5%90%88%E6%B3%95%E5%8D%95%E7%BA%BF%E5%8F%8C%E6%8B%A8%E6%8F%90%E9%AB%98%E5%AF%9D%E5%AE%A4%E8%B7%AF%E7%94%B1%E5%B8%A6%E5%AE%BD%E5%88%A9%E7%94%A8%E7%8E%87/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>本人寝室有两个移动宽带，使用光纤入户方式，其中一个是20M，另一个是30M，使用pppoe方式拨号上网。希望能够将两个带宽进行合并。&lt;/p>
&lt;h2 id="方案一">方案一
&lt;/h2>&lt;p>在京东上搜索多wan口路由，价格比较贵。pass。&lt;/p>
&lt;h2 id="方案二">方案二
&lt;/h2>&lt;p>将手上的小米路由器mini进行改造。刷入OpenWrt。&lt;/p>
&lt;h2 id="刷入openwrt">刷入OpenWrt
&lt;/h2>&lt;p>不是本篇得重点。但是有一点，OpenWrt的固件可以在官方网站查询到并下载，其源也齐全并新潮，不需要刷入旧的Pandorabox等源不可靠得固件。路由需要刷入开发版固件和ssh固件，然后通过命令行刷入OpenWrt固件。不需要刷入breed的u-boot，这种行为太危险了，还会丢失SN。&lt;/p>
&lt;h2 id="第一次拨号设置国内源">第一次拨号，设置国内源
&lt;/h2>&lt;p>首先开启路由器，插上网线，访问192.168.1.1，在页面中设置root密码。&lt;/p>
&lt;p>在system software页面中，点击configuration，将downloads.openwrt.org进行替换，换成openwrt.proxy.ustclug.org，并提交。在network - interface页面编辑 wan配置，更改协议并配置pppoe上网。&lt;/p>
&lt;p>使用ssh连接路由器，在命令行输入opkg update。注意有没有成功。
然后输入opkg install kmod-macvlan mwan3 luci-app-mwan3。没成功多试几次。&lt;/p>
&lt;h2 id="设置虚拟网卡">设置虚拟网卡
&lt;/h2>&lt;p>在interface中查看wan的interface绑定的vlan，是eth0.2。在终端输入 ip link add link eth0.2 name veth0 type macvlan，并输入ifconfig veth0 up。&lt;/p>
&lt;h2 id="绑定网卡到接口">绑定网卡到接口
&lt;/h2>&lt;p>方便起见，修改WAN6作为我们的第二个接口，设置第二个账号得pppoe的上网账号，然后修改其physical setting为veth0。&lt;/p>
&lt;p>如果此时两个账户无法同时拨号，说明不支持单线双拨。&lt;/p>
&lt;h2 id="设置跃点数">设置跃点数
&lt;/h2>&lt;p>在wan和wan6两个页面中设置use gateway metric，注意要不一样。例如一个10一个11.&lt;/p>
&lt;h2 id="设置负载均衡">设置负载均衡
&lt;/h2>&lt;p>在load balance依次设置。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/30ee28d04636.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/d66fe1e72238.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/9a3584c9d52a.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/ec59a0a7332f.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/f9733e8da055.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/baabfb48f8bc.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;h2 id="重启服务">重启服务
&lt;/h2>&lt;p>点击重启服务&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/daf014ef974b.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;h2 id="测试">测试
&lt;/h2>&lt;p>&lt;img src="https://nansenli.com/img/rescued/d2dbb6461264.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;blockquote>
&lt;p>参考 &lt;a class="link" href="https://acris.me/2017/06/25/Load-balancing-multiple-PPPoE-on-LEDE/" target="_blank" rel="noopener"
>https://acris.me/2017/06/25/Load-balancing-multiple-PPPoE-on-LEDE/&lt;/a>&lt;/p>
&lt;/blockquote></description></item><item><title>2018-09-20 Go学习拾贝</title><link>https://nansenli.com/zh-cn/post/jianshu/go/2018-09-20-go%E5%AD%A6%E4%B9%A0%E6%8B%BE%E8%B4%9D/</link><pubDate>Wed, 07 Nov 2018 06:54:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/go/2018-09-20-go%E5%AD%A6%E4%B9%A0%E6%8B%BE%E8%B4%9D/</guid><description>&lt;h2 id="go的指针与值在作为接受者和参数时的表现不同">Go的指针与值在作为接受者和参数时的表现不同
&lt;/h2>&lt;p>方法与指针重定向
比较前两个程序，你大概会注意到带指针参数的函数必须接受一个指针：&lt;/p>
&lt;p>var v Vertex
ScaleFunc(v, 5) // 编译错误！
ScaleFunc(&amp;amp;v, 5) // OK
而以指针为接收者的方法被调用时，接收者既能为值又能为指针：&lt;/p>
&lt;p>var v Vertex
v.Scale(5) // OK
p := &amp;amp;v
p.Scale(10) // OK
对于语句 v.Scale(5)，即便 v 是个值而非指针，带指针接收者的方法也能被直接调用。 也就是说，由于 Scale 方法有一个指针接收者，为方便起见，Go 会将语句 v.Scale(5) 解释为 (&amp;amp;v).Scale(5)。&lt;/p>
&lt;p>另外，Go会将指针的.运算自动解释为(*).&lt;/p></description></item><item><title>openssl aes 加密算法 api使用</title><link>https://nansenli.com/zh-cn/post/jianshu/c++/openssl-aes-%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95-api%E4%BD%BF%E7%94%A8/</link><pubDate>Fri, 02 Nov 2018 06:49:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/c++/openssl-aes-%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95-api%E4%BD%BF%E7%94%A8/</guid><description>&lt;h2 id="示例-使用-aes-128-cbc加密算法">示例 使用 AES-128-CBC加密算法
&lt;/h2>&lt;p>使用了pkcs5_encode padding 方式 pkcs7_encode也是兼容的&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;stdio.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;stdlib.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;string.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;openssl/aes.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;openssl/rand.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// a simple hex-print routine. could be modified to print 16 bytes-per-line
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">hex_print&lt;/span>(&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span>&lt;span style="color:#f92672">*&lt;/span> pv, size_t len)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span> p &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span>&lt;span style="color:#f92672">*&lt;/span>)pv;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (NULL &lt;span style="color:#f92672">==&lt;/span> pv)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> printf(&lt;span style="color:#e6db74">&amp;#34;NULL&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> size_t i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (; i&lt;span style="color:#f92672">&amp;lt;&lt;/span>len;&lt;span style="color:#f92672">++&lt;/span>i)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> printf(&lt;span style="color:#e6db74">&amp;#34;%02X &amp;#34;&lt;/span>, &lt;span style="color:#f92672">*&lt;/span>p&lt;span style="color:#f92672">++&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> printf(&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">pkcs7_encode&lt;/span>(&lt;span style="color:#66d9ef">uint8_t&lt;/span> &lt;span style="color:#f92672">*&lt;/span>in, &lt;span style="color:#66d9ef">uint8_t&lt;/span> &lt;span style="color:#f92672">*&lt;/span>out, &lt;span style="color:#66d9ef">int&lt;/span> inlen, &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#f92672">*&lt;/span>outlen, &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> enc)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// uint8_t padchr[16] = {0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span>(inlen &lt;span style="color:#f92672">&amp;lt;=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>) &lt;span style="color:#66d9ef">return&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 添加padding
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> (enc &lt;span style="color:#f92672">==&lt;/span> AES_ENCRYPT) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">*&lt;/span>outlen &lt;span style="color:#f92672">=&lt;/span> inlen &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span> &lt;span style="color:#f92672">-&lt;/span> inlen &lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (&lt;span style="color:#66d9ef">int&lt;/span> i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; i &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#f92672">*&lt;/span>outlen; i&lt;span style="color:#f92672">++&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (i &lt;span style="color:#f92672">&amp;lt;&lt;/span> inlen)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> out[i] &lt;span style="color:#f92672">=&lt;/span> in[i];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> out[i] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span> &lt;span style="color:#f92672">-&lt;/span> inlen &lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#ae81ff">16&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 删除padding
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> (enc &lt;span style="color:#f92672">==&lt;/span> AES_DECRYPT) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">*&lt;/span>outlen &lt;span style="color:#f92672">=&lt;/span> inlen &lt;span style="color:#f92672">-&lt;/span> (in[inlen&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (&lt;span style="color:#66d9ef">int&lt;/span> i&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>; i&lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#f92672">*&lt;/span>outlen; i&lt;span style="color:#f92672">++&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> out[i] &lt;span style="color:#f92672">=&lt;/span> in[i];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> argc, &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">**&lt;/span>argv)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> keylength;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> printf(&lt;span style="color:#e6db74">&amp;#34;Give a key length [only 128 or 192 or 256!]:&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> scanf(&lt;span style="color:#e6db74">&amp;#34;%d&amp;#34;&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>keylength);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/* generate a key with a given length */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> aes_key[&lt;span style="color:#ae81ff">16&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> {&lt;span style="color:#ae81ff">0x00&lt;/span>,&lt;span style="color:#ae81ff">0x01&lt;/span>,&lt;span style="color:#ae81ff">0x02&lt;/span>,&lt;span style="color:#ae81ff">0x03&lt;/span>,&lt;span style="color:#ae81ff">0x04&lt;/span>,&lt;span style="color:#ae81ff">0x05&lt;/span>,&lt;span style="color:#ae81ff">0x06&lt;/span>,&lt;span style="color:#ae81ff">0x07&lt;/span>,&lt;span style="color:#ae81ff">0x08&lt;/span>,&lt;span style="color:#ae81ff">0x09&lt;/span>,&lt;span style="color:#ae81ff">0x0a&lt;/span>,&lt;span style="color:#ae81ff">0x0b&lt;/span>,&lt;span style="color:#ae81ff">0x0c&lt;/span>,&lt;span style="color:#ae81ff">0x0d&lt;/span>,&lt;span style="color:#ae81ff">0x0e&lt;/span>,&lt;span style="color:#ae81ff">0x0f&lt;/span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> size_t inputslength &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> printf(&lt;span style="color:#e6db74">&amp;#34;Give an input&amp;#39;s length:&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> scanf(&lt;span style="color:#e6db74">&amp;#34;%lu&amp;#34;&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>inputslength);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/* generate input with a given length */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span> aes_input &lt;span style="color:#f92672">=&lt;/span> malloc(inputslength);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span> aes_out &lt;span style="color:#f92672">=&lt;/span> malloc(inputslength);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> memset(aes_input, &lt;span style="color:#e6db74">&amp;#39;X&amp;#39;&lt;/span>, inputslength);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/* init vector */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> iv_enc[AES_BLOCK_SIZE] &lt;span style="color:#f92672">=&lt;/span> {&lt;span style="color:#ae81ff">0&lt;/span>}, iv_dec[AES_BLOCK_SIZE]&lt;span style="color:#f92672">=&lt;/span>{&lt;span style="color:#ae81ff">0&lt;/span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> size_t encslength &lt;span style="color:#f92672">=&lt;/span> ((inputslength &lt;span style="color:#f92672">+&lt;/span> AES_BLOCK_SIZE) &lt;span style="color:#f92672">/&lt;/span> AES_BLOCK_SIZE) &lt;span style="color:#f92672">*&lt;/span> AES_BLOCK_SIZE;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span> enc_in &lt;span style="color:#f92672">=&lt;/span> malloc(encslength);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pkcs7_encode(aes_input, enc_in, inputslength, &lt;span style="color:#f92672">&amp;amp;&lt;/span>encslength, AES_ENCRYPT);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span> enc_out &lt;span style="color:#f92672">=&lt;/span> malloc(encslength);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AES_KEY enc_key;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AES_set_encrypt_key(aes_key, keylength, &lt;span style="color:#f92672">&amp;amp;&lt;/span>enc_key);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AES_cbc_encrypt(enc_in, enc_out, encslength, &lt;span style="color:#f92672">&amp;amp;&lt;/span>enc_key, iv_enc, AES_ENCRYPT);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> printf(&lt;span style="color:#e6db74">&amp;#34;original:&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> hex_print(aes_input, inputslength);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> printf(&lt;span style="color:#e6db74">&amp;#34;padding:&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> hex_print(enc_in, encslength);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> printf(&lt;span style="color:#e6db74">&amp;#34;encrypt:&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> hex_print(enc_out, encslength);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span> dec_out &lt;span style="color:#f92672">=&lt;/span> malloc(encslength);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AES_KEY dec_key;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AES_set_decrypt_key(aes_key, keylength, &lt;span style="color:#f92672">&amp;amp;&lt;/span>dec_key);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AES_cbc_encrypt(enc_out, dec_out, encslength, &lt;span style="color:#f92672">&amp;amp;&lt;/span>dec_key, iv_dec, AES_DECRYPT);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pkcs7_encode(dec_out, aes_out, encslength, &lt;span style="color:#f92672">&amp;amp;&lt;/span>inputslength, AES_DECRYPT);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> printf(&lt;span style="color:#e6db74">&amp;#34;decrypt:&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> hex_print(dec_out, encslength);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> printf(&lt;span style="color:#e6db74">&amp;#34;depadding:&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> hex_print(aes_out, inputslength);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int8_t&lt;/span> buff[&lt;span style="color:#ae81ff">100&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">long&lt;/span> bufflen;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>关于 Windows 10 使用 SSD 每隔一段时间卡顿问题的探究与解决</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E5%85%B3%E4%BA%8E-windows-10-%E4%BD%BF%E7%94%A8-ssd-%E6%AF%8F%E9%9A%94%E4%B8%80%E6%AE%B5%E6%97%B6%E9%97%B4%E5%8D%A1%E9%A1%BF%E9%97%AE%E9%A2%98%E7%9A%84%E6%8E%A2%E7%A9%B6%E4%B8%8E%E8%A7%A3%E5%86%B3/</link><pubDate>Wed, 24 Oct 2018 11:47:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E5%85%B3%E4%BA%8E-windows-10-%E4%BD%BF%E7%94%A8-ssd-%E6%AF%8F%E9%9A%94%E4%B8%80%E6%AE%B5%E6%97%B6%E9%97%B4%E5%8D%A1%E9%A1%BF%E9%97%AE%E9%A2%98%E7%9A%84%E6%8E%A2%E7%A9%B6%E4%B8%8E%E8%A7%A3%E5%86%B3/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>本来是使用 Intel 的 i5-6500 CPU，在更换为 AMD 的 Ryzen 2700 后，频繁出现卡机现象。&lt;/p>
&lt;h2 id="现象">现象
&lt;/h2>&lt;p>在卡机时，观测到任务管理器的硬盘占用率为100%，同时写入与写出量为0。硬盘位光威 悍将 512 GB杂牌。在事件管理器中有大量编号为129的事件。&lt;/p>
&lt;h2 id="怀疑硬件问题">怀疑硬件问题
&lt;/h2>&lt;p>在重复更换了SATA接口，重复拔插了电源，尝试无效，仍然卡机&lt;/p>
&lt;h2 id="网上搜索相关问题">网上搜索相关问题
&lt;/h2>&lt;p>在网上搜索到，大部分都指向了问题的根源，是WIN10的AHCI驱动问题，以及SSD固件的问题。&lt;/p>
&lt;h2 id="解决途径一-更换ahci驱动-无效">解决途径一 更换AHCI驱动 无效
&lt;/h2>&lt;p>为了更换AHCI驱动，尝试在AMD官网，在华硕官网下载了芯片组驱动，并手动在设备管理器中更换了驱动，使得显示为AMD SATA驱动。后又反复试过各种主板驱动，最终，测试无效，不能解决问题&lt;/p>
&lt;h2 id="解决途径二-更换硬盘为raid格式-无效">解决途径二 更换硬盘为raid格式 无效
&lt;/h2>&lt;p>为了更换raid格式，重新安装Windows，但是在安装过程中，Windows无法识别ssd。根据amd官方英文手册，以及官方raid驱动，手动载入raid驱动，仍然在Windows安装过程中无法看到ssd。重复多次无法看到。可能是因为只有一块硬盘，使用volume模式的raid不被支持吧。放弃该方式&lt;/p>
&lt;h2 id="解决途径三-更改注册表关闭msi-无效">解决途径三 更改注册表，关闭MSI 无效
&lt;/h2>&lt;p>&lt;a class="link" href="https://www.pconline.com.cn/win10/739/7395324.html" target="_blank" rel="noopener"
>https://www.pconline.com.cn/win10/739/7395324.html&lt;/a>&lt;/p>
&lt;blockquote>
&lt;p>大意就是在某些AHCI模式下使用StorAHCI.sys驱动时，可能导致个别SSD无法在“信息信号中断”（MSI）模式下完成正确的输入/输出操作，最终表现就是系统停滞，同时任务管理器中磁盘占用率显示为100%。当然解决的方法也简单，直接在regedit里关闭它（MSI Mode）就成。&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>　　4. 点击Contana，输入“regedit”启动注册表编辑器，导航到“HKEY_LOCAL_MACHINE\System\CurrentControlSet\Enum\PCI\xxxxx \Device Parameters\Interrupt Management\MessageSignaledInterruptProperties”（”xxxxx”是你刚刚记录的“设备实例路径”）；&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>　　5. 最后更改右窗格中的“MSISupported”键值，由1改为0；&lt;/p>
&lt;/blockquote>
&lt;p>经过测试无效&lt;/p>
&lt;h2 id="解决途径四--更改电源设置-无效">解决途径四 更改电源设置 无效
&lt;/h2>&lt;p>PCIE-Express中的设置置为关闭，测试无效&lt;/p>
&lt;h2 id="解决途径五-更改lpm-有用">解决途径五 更改LPM 有用
&lt;/h2>&lt;p>&lt;a class="link" href="http://www.sohu.com/a/204645459_493865" target="_blank" rel="noopener"
>http://www.sohu.com/a/204645459_493865&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="https://answers.microsoft.com/en-us/windows/forum/windows_10-hardware-winpc/event-id-129-storahci-resetting-raidport0/7b30c512-6597-438b-80cb-22fb2f85d62e" target="_blank" rel="noopener"
>https://answers.microsoft.com/en-us/windows/forum/windows_10-hardware-winpc/event-id-129-storahci-resetting-raidport0/7b30c512-6597-438b-80cb-22fb2f85d62e&lt;/a>&lt;/p>
&lt;p>其内容如下：&lt;/p>
&lt;p>我们现在用的大多数SSD都支持一些省电的功能，DIPM/HIPM/Device Sleep。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/8ef2136fa9e4.png"
loading="lazy"
alt="image"
>&lt;/p>
&lt;p>由主机 （Host）发出改变形态请求的我们叫做HIPM(Host-Initiated LPM, HIPM)，而直接由设备(Device)发出形态请求的叫(Device-Initiated LPM,DIPM)。请求什么，请求变慢，请求节能，请求Device Sleep。&lt;/p>
&lt;p>而HIPM，DIPM都离不开电脑LPM技术的支持。LPM，Link Power Management (LPM)，链接电源管理。LPM是SATA接口物理层上的一个功能，它能够将主机（Host）和存储设备（Device）的链接分成3种形态：Active形态，Partial形态和Slumber形态，目的是降低物理层总线传输速度，这样达到省电效果。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/beb05312d902.png"
loading="lazy"
alt="image"
>&lt;/p>
&lt;p>LPM一般是默认打开的。&lt;/p>
&lt;p>那么打开这个LPM对于SSD好么？&lt;/p>
&lt;p>1. 主机发送HIPM形态改变指令，而SSD设备标准不同，不能很好的支持，出现兼容问题导致卡顿，或者掉盘；&lt;/p>
&lt;p>2. 主机发送HIPM形态改变指令太频繁，导致SSD设备不能及时反应，出现卡顿。&lt;/p>
&lt;p>3. LPM开启后造成SATA总线传输延迟增加，导致高速SSD的传输受到阻塞而卡顿；&lt;/p>
&lt;p>4. SSD内部的DIPM和主机请求的HIPM发生冲突导致卡顿。&lt;/p>
&lt;p>很多时候，SSD导致电脑出现卡顿，突然掉盘，都是LPM引起的。&lt;/p>
&lt;p>LPM是能够让SATA设备到达节能的效果。但是，也同样对SSD性能产生了影响。&lt;/p>
&lt;p>那么为了节能，你愿意付出损失SSD性能的代价吗？&lt;/p>
&lt;p>HDD的功耗大概在10-25W。也许LPM曾经仅仅是为HDD而来的。&lt;/p>
&lt;p>SSD启动功耗：0.1W&lt;del>1W（2W），最高功耗：5&lt;/del>10W，平均功耗：4~5W左右。&lt;/p>
&lt;p>我们的电源缺SSD这几W么？我们在乎这点电费么？好意思在乎么！&lt;/p>
&lt;p>既然SSD用不着LPM，这样损害SSD性能的节能功能，那么我们可不可以给它关掉呢？&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/e3137f899356.png"
loading="lazy"
alt="image"
>&lt;/p>
&lt;p>保存为“.reg”格式。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/bd0944771088.png"
loading="lazy"
alt="image"
>&lt;/p>
&lt;p>点击运行。自动写入注册表，更改LPM电源设置。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/cfc7398516b6.png"
loading="lazy"
alt="image"
>&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/cb2106c96417.png"
loading="lazy"
alt="image"
>&lt;/p>
&lt;p>如果你的SSD出现严重卡顿，掉盘等现象，你可以试试这个方法。&lt;/p>
&lt;p>SSD卡顿、掉盘的主要原因还是，SSD太差劲。确实，在SSD价格不断上涨的今天，要想花较少的钱买到好的SSD是非常难的。&lt;/p>
&lt;p>&lt;strong>经过测试，有效&lt;/strong>&lt;/p>
&lt;h2 id="解决途径六-更改bios设置开启sata热拔插功能-推荐">解决途径六 更改BIOS设置，开启sata热拔插功能 推荐
&lt;/h2>&lt;p>&lt;a class="link" href="https://www.aiweibk.com/5795.html" target="_blank" rel="noopener"
>https://www.aiweibk.com/5795.html&lt;/a>&lt;/p>
&lt;blockquote>
&lt;p>在主板BIOS设置中开启Hot Plug热插拔属性之后还会带来一个副作用，那就是固态硬盘的LPM节能将会随热插拔的开启而关闭，从而避免热插拔时数据丢失或重新连接时失去响应。&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>如果你的固态硬盘与主板搭配存在兼容性问题而有间歇性的卡顿现象，不妨在主板BIOS设置当中开启Hot Plug选项来禁用LPM节能，或许就能解决故障。&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>经测试，有效&lt;/strong>&lt;/p>
&lt;h2 id="总结">总结
&lt;/h2>&lt;p>为什么之前用Intel就没问题用了AMD就有问题，我猜测是因为原来的Intel主板比较差，不支持SATA LPM，然后win10默认是开启了LPM，但是SSD因为杂牌，所以支持不好，虽然固件支持LPM，但是会出现严重问题。由于主板不支持LPM，就算Windows开启了LPM，问题也显现不出来。&lt;/p>
&lt;p>现在换了新的主板，支持LPM，在默认情况下，与SSD出现兼容问题。需要手动关闭LPM功能。&lt;/p></description></item><item><title>2018-09-15 网易互娱面试</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/2018-09-15-%E7%BD%91%E6%98%93%E4%BA%92%E5%A8%B1%E9%9D%A2%E8%AF%95/</link><pubDate>Sat, 15 Sep 2018 15:59:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/2018-09-15-%E7%BD%91%E6%98%93%E4%BA%92%E5%A8%B1%E9%9D%A2%E8%AF%95/</guid><description>&lt;p>于成都一酒店面试
有想到的再更新&lt;/p>
&lt;h2 id="笔试">笔试
&lt;/h2>&lt;p>使用开放地址法实现hashmap，key是int，value是string，时间30分钟&lt;/p>
&lt;h2 id="一面">一面
&lt;/h2>&lt;ul>
&lt;li>volatile 作用 原理&lt;/li>
&lt;li>dynamic_cast static_cast 区别&lt;/li>
&lt;li>dynamic_cast父子类转换合法问题&lt;/li>
&lt;li>虚析构函数 虚表&lt;/li>
&lt;li>RTTI type_info&lt;/li>
&lt;li>结构体 sizeof&lt;/li>
&lt;li>红黑树特点；大约12个数字，求该序列一个可能的红黑树结果，画出来&lt;/li>
&lt;li>服务器建立listen的api调用过程，非阻塞epoll如何实现&lt;/li>
&lt;li>epoll原理，边缘触发水平触发业务代码的逻辑区别在哪&lt;/li>
&lt;li>tcp粘包&lt;/li>
&lt;li>扔两个鸡蛋，100层楼，求鸡蛋硬度，动态规划求解方法&lt;/li>
&lt;li>10亿个玩家，每个人都有个分数，分数范围是0~10w，求一个人的排名是多少&lt;/li>
&lt;li>redis跳跃表&lt;/li>
&lt;li>内存栅栏在汇编中是如何实现的&lt;/li>
&lt;li>void func()函数对应的汇编程序刚开始都要做两件事，是什么&lt;/li>
&lt;li>如果程序出现了内存泄漏，如何判断内存是如何泄露&lt;/li>
&lt;li>如何在main函数之前打印一些东西。main函数之前还会执行哪些程序。&lt;/li>
&lt;/ul>
&lt;h2 id="二面">二面
&lt;/h2>&lt;ul>
&lt;li>python is == 区别 id函数&lt;/li>
&lt;li>python xrange range 区别&lt;/li>
&lt;li>Python iterator&lt;/li>
&lt;li>redis ziplist&lt;/li>
&lt;li>redis ttl过期实现&lt;/li>
&lt;li>stl deque实现（我回答list，不对。遂问你如果要实现一个deque，你怎么实现）&lt;/li>
&lt;li>shell中运行一个程序，到程序输出hello world，中间发生了什么，细节&lt;/li>
&lt;li>Linux程序典型内存布局画出来&lt;/li>
&lt;li>不同线程之间可以访问对方的栈空间吗&lt;/li>
&lt;li>一个长为x，宽为y的矩阵，四个角有洞，一个台球从某点出发，不考虑摩擦力无限运动下去，求他会进入到哪个洞里面&lt;/li>
&lt;li>lambda表达式[]可以填什么，你觉得编译器是如何实现lambda表达式的&lt;/li>
&lt;li>什么是右值（这里我把右值和右值引用搞混了）&lt;/li>
&lt;li>malloc实现过程，malloc一定会调用brk调整堆边界吗。介绍buddy&lt;/li>
&lt;li>https 客户端和服务器是如何实现协议选择的，现在常用的协议是什么&lt;/li>
&lt;li>https 客户端鉴定服务器的过程&lt;/li>
&lt;/ul></description></item><item><title>Python 装饰器漫谈</title><link>https://nansenli.com/zh-cn/post/jianshu/python/python-%E8%A3%85%E9%A5%B0%E5%99%A8%E6%BC%AB%E8%B0%88/</link><pubDate>Tue, 04 Sep 2018 08:54:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/python/python-%E8%A3%85%E9%A5%B0%E5%99%A8%E6%BC%AB%E8%B0%88/</guid><description>&lt;p>参考资料 &lt;a class="link" href="http://python.jobbole.com/81683/" target="_blank" rel="noopener"
>http://python.jobbole.com/81683/&lt;/a>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Python的命名空间
任何一个变量在创建时都会创建在自己的命名空间内，如果自己的命名空间内不含有这个名字，就会创建一个新的，而不是覆盖上层的命名空间，在访问时，如果自己的命名空间内没有，则访问上层的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Python的闭包
任何函数都是对象。可以创建一个待调用的对象。如果创建一个待调用的函数，其内部过程使用了一个局部变量，此时设计到了闭包：嵌套定义在非全局作用域里面的函数能够记住它在被定义的时候它所处的封闭命名空间。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>通俗的说：子函数可以使用父函数中的局部变量，这种行为就叫做闭包。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>闭包细节之C++与Python
一般情况下，大部分语言的父函数的局部变量，会随着父函数退出而结束生命周期。此时lambda函数，或者函数对象的调用中，如果引用了父函数的局部变量，就会出现问题。如果定义lambda或函数对象在定义的过程中，能够记住当前的命名空间，就是闭包。可是C++中，局部变量的生命周期规定是和父函数一样，怎么办？使用mutable修饰变量，该变量的生命周期就会变得和函数对象和lambda表达式一样长，否则捕捉这些变量时可能就会有问题。而Python会将闭包中的对象都捕捉到，不让其释放。直到函数对象回收为止。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>装饰器
有的时候我们需要对函数做一些改装，而这些改装的工作几乎类似。例如，我们需要对函数的运行时间进行计算，此时，我们需要在所有函数开始和结尾加上一个时间戳。然后在函数结束时进行打印。或者，我们需要对参数和返回值进行校验。当然我们可以在所有函数开始和结束的时候调用校验函数。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>装饰器可以完成这样的功能。例如我们有一个函数&lt;/p>
&lt;pre tabindex="0">&lt;code>def func():
time.sleep(1)
&lt;/code>&lt;/pre>&lt;p>使用另一个返回带有闭包的函数对象的函数来完成这样的功能&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">addTimePrint&lt;/span>(func):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">retFuncObj&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>args, &lt;span style="color:#f92672">**&lt;/span>kwargs):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> starttime &lt;span style="color:#f92672">=&lt;/span> time()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> func(&lt;span style="color:#f92672">*&lt;/span>args, &lt;span style="color:#f92672">**&lt;/span>kwargs)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(time() &lt;span style="color:#f92672">-&lt;/span> starttime)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> retFuncObj
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后我们需要把新的func函数替换掉。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>func &lt;span style="color:#f92672">=&lt;/span> addTimePrint(func)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Python提供了一个语法糖，在定义了addTimePrint之后，使用@addTimePrint就可以完成函数的定义和装饰&lt;/p>
&lt;pre tabindex="0">&lt;code>@addTimePrint
def func():
time.sleep(1)
&lt;/code>&lt;/pre></description></item><item><title>Redis 集群学习</title><link>https://nansenli.com/zh-cn/post/jianshu/%E6%95%B0%E6%8D%AE%E5%BA%93/redis-%E9%9B%86%E7%BE%A4%E5%AD%A6%E4%B9%A0/</link><pubDate>Sun, 02 Sep 2018 14:25:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E6%95%B0%E6%8D%AE%E5%BA%93/redis-%E9%9B%86%E7%BE%A4%E5%AD%A6%E4%B9%A0/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>回顾一下Redis集群的相关知识点&lt;/p>
&lt;ul>
&lt;li>
&lt;p>配置&lt;/p>
&lt;ul>
&lt;li>一个Redis数据库系统有一个主数据库，多个从数据库&lt;/li>
&lt;li>主数据库不需要做任何配置，从数据库在配置中加入&amp;ndash;slaveof ip port即可&lt;/li>
&lt;li>从数据库默认只读，就算改成可写也没有实际用处&lt;/li>
&lt;li>除了在配置文件中添加，也可以在运行时使用slaveof命令，来修改主数据库&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>持久化回顾&lt;/p>
&lt;ul>
&lt;li>RDB 该方式将Redis进程fork一份，子进程将内存写入文件，然后替换之前的RDB快照&lt;/li>
&lt;li>AOF 该方式Redis将每条命令都写入文件中。当AOF文件太大的时候，每追加一定数量的命令时，需要重写一次AOF文件，因为其中有些命令时冗余的。写入文件由于操作系统有缓存，可以进行配置。不配置的情况下30s写入磁盘一次。可以配置为每条命令都同步缓存，也可以配置为每秒同步一次。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="原理">原理
&lt;/h2>&lt;ul>
&lt;li>初始化复制
&lt;ul>
&lt;li>
&lt;ol>
&lt;li>当从数据库启动后，向主数据库发送SYNC命令&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;ol start="2">
&lt;li>主数据库进行一次快照保存，同时在保存快照时将命令缓存下来，然后将快照与保存快照期间的命令一起发送给从数据库&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;ol start="3">
&lt;li>如果断线之后，Redis2.6以及以前的版本会重新进行初始化复制。Redis2.8之后的版本可以近传输断线期间的命令。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;ol start="4">
&lt;li>从数据库将接受到的临时快照替换为配置中设定的RDB快照文件。之后的操作和RDB持久化恢复的过程一致。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>复制同步
&lt;ul>
&lt;li>之后，主数据库会将导致数据库变化的命令发送给从数据库&lt;/li>
&lt;li>Redis复制策略使用的是乐观复制，存在不一致时间窗。主数据库可以配置当有多少数据库连接时可写，以及允许数据库最长失去链接时间。可以降低一定程度的数据不一致问题。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="读写分离提高性能">读写分离提高性能
&lt;/h2>&lt;p>一些带有耗时的读操作可以通过复制功能建立多个数据库从节点来加快速度&lt;/p>
&lt;h2 id="slave数据库持久化">slave数据库持久化
&lt;/h2>&lt;p>使用从数据库时可以关闭主数据库的持久化。但是主数据库崩溃的时候，主从数据库需要进行身份转换才可以恢复主数据库的内容。从数据库使用slaveof no one，主数据库使用slaveof命令设置为从数据库。&lt;/p>
&lt;p>这种情况下，如果主数据库崩溃后，不能立刻重新启动。因为如果此时从新启动，主数据库因为没有持久化，就会将从数据库中的内容清空。&lt;/p>
&lt;h2 id="无硬盘复制">无硬盘复制
&lt;/h2>&lt;p>主从复制可以通过RDB持久化方式来实现初始化同步。
当主数据库禁用RDB快照，但是当执行了初始化复制，仍然会生成RDB快照。此时主数据库重启动时将会使用RDB快照进行恢复。因为同步时间点不确定，甚至可能很久都没有进行初始化同步。这可能导致恢复的时间点可能是任意的。&lt;/p>
&lt;p>另外，初始化同步时需要创建RDB快照，如果磁盘速度太慢可能会影响同步的效率。&lt;/p>
&lt;p>从Redis2.8.18开始，Redis引入了无硬盘复制，开启该选项的时候，初始化复制将不再创建RDB快照，而是直接通过网络将RDB快照内容发送走。&lt;/p>
&lt;h2 id="增量复制">增量复制
&lt;/h2>&lt;p>主数据库和从数据库如果断开链接后，下一次重新链接如果使用初始化复制，开销会比较高。&lt;/p>
&lt;ul>
&lt;li>数据库在每次运行时，会生成唯一ID。&lt;/li>
&lt;li>复制同步阶段，主数据库会将每一个命令放入回放日志中。并记下当前命令的偏移量。&lt;/li>
&lt;li>从数据库接受命令时，也需要记录下命令偏移量。&lt;/li>
&lt;li>当主从连接准备就绪之后，从数据库发送psync来告诉主数据库可以使用增量复制。&lt;/li>
&lt;li>主数据库判断从数据库发送的id和自己是否相同。不相同的情况下，主数据库可能已经重启过了。&lt;/li>
&lt;li>然后判断从数据库的命令偏移量是否和在backlog回放日志中，如果在，将回放日志中的命令发给从数据库。&lt;/li>
&lt;li>backlog的大小可以配置，默认时1MB。越大可以使断线重连后的容忍程度越长。&lt;/li>
&lt;/ul>
&lt;h2 id="哨兵">哨兵
&lt;/h2>&lt;p>哨兵系统的出现，目的是为了解决之前的主从数据库的维护的不方便。以前存在两个问题，主数据库挂了无法感知。主数据库挂了之后，主从转换问题。&lt;/p>
&lt;p>哨兵进程启动时，会读取配置文件。配置文件中有master-name ip port 。当主节点故障时，哨兵将自动转换其中一个从节点，变为主节点。当之前的主节点恢复后，会自动转变为从节点。&lt;/p>
&lt;p>哨兵启动之后，会定时执行下面3个操作：&lt;/p>
&lt;ul>
&lt;li>10秒向master、slave发送INFO&lt;/li>
&lt;li>2秒向master、slave的&lt;code>__sentinel__:hello&lt;/code>发送自己的信息&lt;/li>
&lt;li>1秒向主数据库、从数据库、和其他哨兵发送PING&lt;/li>
&lt;/ul>
&lt;p>INFO信息使得哨兵可以获取当前数据库的相关信息。向master发送INFO可以获取其从数据库。
向频道发送自己的信息，所有哨兵可以收到监控同一个数据库的其他哨兵的信息。
哨兵之间建立连接目的是为了发送ping信息。
哨兵发送ping命令时没有进行回复，就会认为其主观下线。如果发现一个主数据库主观下线，就会询问其他哨兵是否认为其主观下线。如果达到一定数量的时候，超过哨兵启动参数中设置的quorum，哨兵认为其客观下线。&lt;/p>
&lt;p>RAFT选举：&lt;/p>
&lt;ul>
&lt;li>发现主数据库客观下线的哨兵A向其他哨兵发送命令，请求选自己成为领头哨兵。&lt;/li>
&lt;li>如果被请求的哨兵还没有选过其他人，则会同意&lt;/li>
&lt;li>如果A发现同意的数量超过了哨兵数量的一半和quorum参数，则A成为领头哨兵。&lt;/li>
&lt;li>否则，随机等待一个时间重新发起请求，进行下一轮选举&lt;/li>
&lt;/ul>
&lt;p>领头哨兵会进行如下操作：&lt;/p>
&lt;ul>
&lt;li>查看从数据库的优先级，优先级越高的选他&lt;/li>
&lt;li>查看从数据库的复制命令偏移量，偏移量越大的选他&lt;/li>
&lt;li>选择ID小的从数据库&lt;/li>
&lt;/ul>
&lt;p>被选中的数据库会被领头哨兵发送slaveof no one成为主数据库，其他从数据库会收到slaveof命令更改其主数据库。最后，更改内部记录，将已经停止的数据库更新为主数据库的从数据库，使当其恢复服务时以从数据库的身份恢复。&lt;/p>
&lt;h2 id="部署">部署
&lt;/h2>&lt;p>哨兵一般部署在每个节点上，如果redis集群很大，所有哨兵与一个从数据库之间的连接数量太多会影响redis对所有哨兵的回复。所以部署的数量不能太多。&lt;/p>
&lt;h2 id="集群">集群
&lt;/h2>&lt;p>水平扩容这块，在腾讯实习期间解除了codis。codis分为redis proxy和从节点。主要原理就是对key进行hash之后，放入到具体的redis slave中。脚本在执行的过程中，被分配到具体的slave中执行，可能会遇到找不到其他key的情况。&lt;/p>
&lt;p>除了codis，还有redis cluster。&lt;/p>
&lt;p>在启动redis实例时，配置中cluster-enables改为yes，即可打开集群配置。&lt;/p>
&lt;p>使用脚本可以方便的创建集群。增加新节点可以使用cluster meet命令添加ip和端口。该节点添加新节点后，会使用gossip协议将该节点的信息广播到所有节点。&lt;/p>
&lt;p>插槽的分配。新的节点加入集群后有两种选择，要么使用cluster replicate命令复制每个主数据库来以从数据库的形式运行，要么向集群申请分配插槽slot来以主数据库的形式运行。&lt;/p>
&lt;p>key使用crc16做hash运算，如果存在{*}，则使用该部分做hash运算，然后分给16384个槽中的一个。集群在创建的开始时就有一个槽分配的映射表。最终数据会存在对应的redis节点中。&lt;/p></description></item><item><title>2018-08-30 Go学习拾贝</title><link>https://nansenli.com/zh-cn/post/jianshu/go/2018-08-30-go%E5%AD%A6%E4%B9%A0%E6%8B%BE%E8%B4%9D/</link><pubDate>Sat, 01 Sep 2018 13:40:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/go/2018-08-30-go%E5%AD%A6%E4%B9%A0%E6%8B%BE%E8%B4%9D/</guid><description>&lt;ul>
&lt;li>在函数中，简洁赋值语句 := 可在类型明确的地方代替 var 声明。但是常量必须使用const来声明&lt;/li>
&lt;li>函数外的每个语句都必须以关键字开始（var, func 等等），因此 := 结构不能在函数外使用。&lt;/li>
&lt;li>为什么Go使用后置类型生命 &lt;a class="link" href="https://blog.go-zh.org/gos-declaration-syntax" target="_blank" rel="noopener"
>https://blog.go-zh.org/gos-declaration-syntax&lt;/a>&lt;/li>
&lt;li>Go的类型有如下：&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名
// 表示一个 Unicode 码点
float32 float64
complex64 complex128
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>当你需要一个整数值时应使用 int 类型，除非你有特殊的理由使用固定大小或无符号的整数类型。&lt;/li>
&lt;li>Go不存在隐式转换&lt;/li>
&lt;/ul></description></item><item><title>2018-08-29 腾讯天美电话面试</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/2018-08-29-%E8%85%BE%E8%AE%AF%E5%A4%A9%E7%BE%8E%E7%94%B5%E8%AF%9D%E9%9D%A2%E8%AF%95/</link><pubDate>Wed, 29 Aug 2018 04:32:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/2018-08-29-%E8%85%BE%E8%AE%AF%E5%A4%A9%E7%BE%8E%E7%94%B5%E8%AF%9D%E9%9D%A2%E8%AF%95/</guid><description>&lt;ul>
&lt;li>三次握手四次挥手&lt;/li>
&lt;li>nagle算法&lt;/li>
&lt;li>tcp断开链接一方会进入到哪几个状态&lt;/li>
&lt;li>多进程多线程如何选择，考虑业务&lt;/li>
&lt;li>mysql master slave 集群 了解多少&lt;/li>
&lt;li>多进程下gdb调试流程&lt;/li>
&lt;li>网络框架libevent等介绍&lt;/li>
&lt;li>select 和epoll区别。java为什么connect用select不用epoll&lt;/li>
&lt;li>进程间通信方式&lt;/li>
&lt;li>锁的了解。无锁数据结构实现&lt;/li>
&lt;li>共享内存互斥如何设计&lt;/li>
&lt;/ul></description></item><item><title>解决腾讯云服务器跨运营商搭建k8s集群网卡ip不正确的问题</title><link>https://nansenli.com/zh-cn/post/jianshu/k8s/%E8%A7%A3%E5%86%B3%E8%85%BE%E8%AE%AF%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%B7%A8%E8%BF%90%E8%90%A5%E5%95%86%E6%90%AD%E5%BB%BAk8s%E9%9B%86%E7%BE%A4%E7%BD%91%E5%8D%A1ip%E4%B8%8D%E6%AD%A3%E7%A1%AE%E7%9A%84%E9%97%AE%E9%A2%98/</link><pubDate>Sun, 22 Jul 2018 13:40:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/k8s/%E8%A7%A3%E5%86%B3%E8%85%BE%E8%AE%AF%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%B7%A8%E8%BF%90%E8%90%A5%E5%95%86%E6%90%AD%E5%BB%BAk8s%E9%9B%86%E7%BE%A4%E7%BD%91%E5%8D%A1ip%E4%B8%8D%E6%AD%A3%E7%A1%AE%E7%9A%84%E9%97%AE%E9%A2%98/</guid><description>&lt;p>K8S要求所有节点具有完全互联的网络，因此所有节点应该具有平等的ip，虽然每个节点都有公网ip，但是由于腾讯云的机器在网卡上看不到ip，因此在加入集群时会使用网卡的ip，其他节点无法通过该ip找到这个机器。&lt;/p>
&lt;p>想要在网卡上看到ip，需要开通腾讯云eip直通功能。需要注意的是，官方提供的脚本在debian 10上存在问题，无法正确的自启动，需要做如下更改：&lt;/p>
&lt;p>将/etc/rc.loacl最终行添加 /etc/rc.d/rc.local&lt;/p>
&lt;p>虽然腾讯云的debian 10还是会开机启动/etc/rc.local，但是不会启动/etc/rc.d/rc.local，而提供的脚本是写入到后者的。&lt;/p>
&lt;p>参考文档 &lt;a class="link" href="https://cloud.tencent.com/document/product/1199/41709" target="_blank" rel="noopener"
>https://cloud.tencent.com/document/product/1199/41709&lt;/a>&lt;/p></description></item><item><title>SVN学习笔记</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/svn%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</link><pubDate>Sun, 01 Jul 2018 15:01:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/svn%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>为了工作内容学习SVN。SVN是一个C/S类型的版本管理，对服务器依赖很大，大部分操作是和SVN服务器有关的。&lt;/p>
&lt;h2 id="最常见的操作流程">最常见的操作流程
&lt;/h2>&lt;p>这里假设服务器项目的地址是svn://192.168.1.1/shop&lt;/p>
&lt;ul>
&lt;li>通过checkout检出项目&lt;/li>
&lt;li>update 更新服务器的项目到本地&lt;/li>
&lt;li>commit 提交本地的更改&lt;/li>
&lt;/ul>
&lt;h2 id="如何部署一个svn服务器">如何部署一个svn服务器
&lt;/h2>&lt;p>因为这个svn是一个c/s类型的软件，得部署服务器才能够使用。所以得先弄个服务器出来供我们测试。
为了尽可能简单，这里在Windows下面安装一个visualsvn的软件来当作我们的服务器。注意一般服务器是运行在Linux的，这里只是为了上手方便才使用Windows的服务器软件。在官网很容易就能下载到visualsvn server的软件。我下了个3.9的版本。用就用最新稳定版。中间有一步选标准版，其他的按照默认来就可以了。其中有一个端口号的选择，注意一下不要和本机的端口冲突了。默认端口是443，你可以改成8443。&lt;/p>
&lt;h2 id="在服务器端创建一个测试项目">在服务器端创建一个测试项目
&lt;/h2>&lt;p>因为svn项目就是仓库，就是一个文件夹。我们需要建立一个文件夹。打开D:\Repositories文件夹，在该目录下面运行命令svnadmin create test1，这样就建立了一个项目。为了其他用户能够访问该目录，需要使用svnserve命令启动服务器，这个稍后再说。这里还需要配置权限设置。打开test1目录下面的conf目录svnserve.conf，将其中的anon-access 的注释去掉，将值改为write。此时允许匿名访问，并可写&lt;/p>
&lt;p>然后在D:\Repositories目录下运行命令：
&lt;code>svnserve -d -r ./test1&lt;/code>
这一步是打开svn服务器的监听状态。&lt;/p>
&lt;h2 id="svn客户端常用操作">svn客户端常用操作
&lt;/h2>&lt;p>下载TortoiseSVN进行安装。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>checkout 检出
然后，在需要同步的目录下面单机右键，选择tortoise中的rebo-bowser，并输入svn://127.0.0.1确认。在文件夹图标上右键选择checkout并确认，此时检出成功。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>commit
添加一个txt文件，随便写点东西。然后右键commit。即可commit到服务器端。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>update
A开发者将提交的数据提交后，服务器发生了变化，B开发者需要同步。右键update即可。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>ignore
不需要提交文件时，在文件上面右键然后选择add to ignore list中&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="svn版本">SVN版本
&lt;/h2>&lt;p>svn版本从1开始递增。svn可以会退版本。svn可以进行分支和合并操作，但是和git中的分支操作区别很大。似乎是重新创建了一个项目这样的。最后合并也是只是把冲突合并了。和git基于diff进行分支不同。&lt;/p></description></item><item><title>使用Let's encrypt和nginx配置https访问</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/%E4%BD%BF%E7%94%A8lets-encrypt%E5%92%8Cnginx%E9%85%8D%E7%BD%AEhttps%E8%AE%BF%E9%97%AE/</link><pubDate>Mon, 04 Jun 2018 09:12:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/%E4%BD%BF%E7%94%A8lets-encrypt%E5%92%8Cnginx%E9%85%8D%E7%BD%AEhttps%E8%AE%BF%E9%97%AE/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>为了加密传输http的数据，我们需要用到https证书。免费的证书有lets‘s encrypt。&lt;/p>
&lt;h2 id="使用nginx配置转发">使用nginx配置转发
&lt;/h2>&lt;p>我们需要在服务器上配置nginx，使得lets&amp;rsquo;s encrypt能够验证我们的域名。
下面是我的服务器nginx的配置文件&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nginx" data-lang="nginx">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">server&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">listen&lt;/span> &lt;span style="color:#ae81ff">80&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">server_name&lt;/span> &lt;span style="color:#e6db74">www.unrealblue.cc&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">location&lt;/span> &lt;span style="color:#e6db74">/&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">return&lt;/span> &lt;span style="color:#ae81ff">301&lt;/span> &lt;span style="color:#e6db74">https://&lt;/span>$host$request_uri;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">location&lt;/span> ~ &lt;span style="color:#e6db74">/.well-known/acme-challenge&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">root&lt;/span> &lt;span style="color:#e6db74">/var/www/html&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">allow&lt;/span> &lt;span style="color:#e6db74">all&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>说明：第一个location意思是转发所有的80请求到https连接。第二个请求的意思是，如果访问.well-known，则使用我们提供的/var/www/html目录。&lt;/p>
&lt;p>将文件保存为default.conf。&lt;/p>
&lt;p>这里我使用了nginx docker来部署，所以没有安装nginx。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-docker" data-lang="docker">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>&lt;span style="color:#e6db74"> nginx&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">LABEL&lt;/span> MAINTAINER linanwx@gmail.com&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">RUN&lt;/span> apt update&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">RUN&lt;/span> apt install -y certbot&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">RUN&lt;/span> mkdir -p /var/www/html&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">COPY&lt;/span> ./default.conf /etc/nginx/conf.d/&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">EXPOSE&lt;/span>&lt;span style="color:#e6db74"> 80:80&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">EXPOSE&lt;/span>&lt;span style="color:#e6db74"> 443:443&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">VOLUME&lt;/span> [&lt;span style="color:#e6db74">&amp;#34;/etc/letsencrypt/&amp;#34;&lt;/span>]&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>将第一个代码段存为default.conf，第二个代码段存为dockerfile，然后运行&lt;code>docker build -t nginx-server .&lt;/code>&lt;/p>
&lt;p>运行&lt;code>docker run -it --rm -d -p80:80 -p443:443 --net=server-net --name nginx-server -v $PWD/letsencrypt:/etc/letsencrypt nginx-server&lt;/code>启动容器并在后台运行。&lt;/p>
&lt;h2 id="生成证书">生成证书
&lt;/h2>&lt;p>运行&lt;code>docker exec -it nginx-server /bin/bash&lt;/code>进入容器内部。
运行下面的命令生成证书&lt;/p>
&lt;pre tabindex="0">&lt;code>openssl dhparam -out /etc/letsencrypt/live/dhparams.pem 2048
certbot certonly --agree-tos -a webroot --webroot-path=/var/www/html -d www.unrealblue.cc -m linanwx@gmail.com
&lt;/code>&lt;/pre>&lt;p>certbot程序会访问你的域名，来确认你的域名所有权是你，这就用到了上一步操作的nginx的配置。其中还有一些参数，查看help可以知道含义。另外，除非你确认要部署到服务器上，否则在测试阶段使用&amp;ndash;test-cert参数来进行测试。因为证书生成是有数量限制的。
另外一点就是生成dhparam。他是迪菲-赫尔曼秘钥交换协议参数，生成时间较长。之后我们会在nginx中使用这个。&lt;/p>
&lt;p>不出意外你会获得证书，他们存在/etc/letsencrypt/目录下。并且，由于在容器启动的时候使用了挂卷，证书会出现在宿主机中。&lt;/p>
&lt;h2 id="配置nginx-https">配置nginx https
&lt;/h2>&lt;p>新建一个文件，命名为http.conf&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nginx" data-lang="nginx">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># https://www.jianshu.com/p/f7f39cb24423
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">server&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">listen&lt;/span> &lt;span style="color:#ae81ff">443&lt;/span> &lt;span style="color:#e6db74">ssl&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">server_name&lt;/span> &lt;span style="color:#e6db74">www.unrealblue.cc&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ssl_certificate&lt;/span> &lt;span style="color:#e6db74">/etc/letsencrypt/live/www.unrealblue.cc/fullchain.pem&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ssl_trusted_certificate&lt;/span> &lt;span style="color:#e6db74">/etc/letsencrypt/live/www.unrealblue.cc/fullchain.pem&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ssl_certificate_key&lt;/span> &lt;span style="color:#e6db74">/etc/letsencrypt/live/www.unrealblue.cc/privkey.pem&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ssl_dhparam&lt;/span> &lt;span style="color:#e6db74">/etc/letsencrypt/live/dhparams.pem&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ssl_ciphers&lt;/span> &lt;span style="color:#e6db74">&amp;#39;ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ssl_prefer_server_ciphers&lt;/span> &lt;span style="color:#66d9ef">on&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ssl_protocols&lt;/span> &lt;span style="color:#e6db74">TLSv1&lt;/span> &lt;span style="color:#e6db74">TLSv1.1&lt;/span> &lt;span style="color:#e6db74">TLSv1.2&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ssl_session_cache&lt;/span> &lt;span style="color:#e6db74">shared:SSL:50m&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ssl_session_timeout&lt;/span> &lt;span style="color:#e6db74">1d&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ssl_stapling&lt;/span> &lt;span style="color:#66d9ef">on&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ssl_stapling_verify&lt;/span> &lt;span style="color:#66d9ef">on&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">add_header&lt;/span> &lt;span style="color:#e6db74">Strict-Transport-Security&lt;/span> &lt;span style="color:#e6db74">max-age=60&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">location&lt;/span> &lt;span style="color:#e6db74">/&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">root&lt;/span> &lt;span style="color:#e6db74">/usr/share/nginx/html&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">index&lt;/span> &lt;span style="color:#e6db74">index.html&lt;/span> &lt;span style="color:#e6db74">index.htm&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># location = /gfwlist {
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#75715e"># proxy_pass http://server-handy:8081;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#75715e"># }
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">error_page&lt;/span> &lt;span style="color:#ae81ff">500&lt;/span> &lt;span style="color:#ae81ff">502&lt;/span> &lt;span style="color:#ae81ff">503&lt;/span> &lt;span style="color:#ae81ff">504&lt;/span> &lt;span style="color:#e6db74">/50x.html&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">location&lt;/span> = &lt;span style="color:#e6db74">/50x.html&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">root&lt;/span> &lt;span style="color:#e6db74">/usr/share/nginx/html&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>将其保存。然后修改dockerfile如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>&lt;span style="color:#e6db74"> nginx&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">LABEL&lt;/span> MAINTAINER linanwx@gmail.com&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">RUN&lt;/span> apt update&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">RUN&lt;/span> apt install -y certbot&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">RUN&lt;/span> mkdir -p /var/www/html&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">COPY&lt;/span> ./default.conf /etc/nginx/conf.d/&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">COPY&lt;/span> ./https.conf /etc/nginx/conf.d/&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">EXPOSE&lt;/span>&lt;span style="color:#e6db74"> 80:80&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">EXPOSE&lt;/span>&lt;span style="color:#e6db74"> 443:443&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">VOLUME&lt;/span> [&lt;span style="color:#e6db74">&amp;#34;/etc/letsencrypt/&amp;#34;&lt;/span>]&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这里将这个http.conf也一同复制进去了。&lt;/p>
&lt;p>重新启动容器，访问https://127.0.0.1，可以看到访问成功了。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/27b7400a5c27.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>在本地环境测试的时候会提示证书不正确。
点继续。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/52f75e932b3a.png"
loading="lazy"
alt="image.png"
>&lt;/p></description></item><item><title>socat命令学习</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/socat%E5%91%BD%E4%BB%A4%E5%AD%A6%E4%B9%A0/</link><pubDate>Wed, 23 May 2018 09:04:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/socat%E5%91%BD%E4%BB%A4%E5%AD%A6%E4%B9%A0/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>socat是强大易用的工具，可以实现任意socket的转换。&lt;/p>
&lt;h2 id="使用">使用
&lt;/h2>&lt;p>&lt;code>socat [options] &amp;lt;address&amp;gt; &amp;lt;address&amp;gt;&lt;/code>&lt;/p>
&lt;p>options可以查看help。&lt;/p>
&lt;p>address有一下几种形式：&lt;/p>
&lt;ul>
&lt;li>&lt;code>-&lt;/code> &lt;code>STDIN&lt;/code> &lt;code>STDOUT&lt;/code> ：表示标准输入输出，可以就用一个横杠代替&lt;/li>
&lt;li>&lt;code>/var/log/syslog&lt;/code> : 也可以是任意路径，如果是相对路径要使用./，打开一个文件作为数据流。&lt;/li>
&lt;li>&lt;code>TCP:127.0.0.1:1080&lt;/code> : 建立一个TCP连接作为数据流，TCP也可以替换为UDP&lt;/li>
&lt;li>&lt;code>TCP-LISTEN:12345&lt;/code> : 建立TCP监听端口，TCP也可以替换为UDP&lt;/li>
&lt;li>&lt;code>EXEC:/bin/bash&lt;/code> : 执行一个程序作为数据流。&lt;/li>
&lt;/ul>
&lt;h2 id="场景一">场景一
&lt;/h2>&lt;p>本地有个文件，我想显示在终端中&lt;/p>
&lt;p>&lt;code>socat - /etc/sysctl.conf&lt;/code>&lt;/p>
&lt;h2 id="场景二">场景二
&lt;/h2>&lt;p>有个TCP连接会连上来，我想看看会获得什么数据。
&lt;code>socat TCP-LISTEN:12345 -&lt;/code>&lt;/p>
&lt;h2 id="场景三">场景三
&lt;/h2>&lt;p>我是黑客，我想在目标机上弄一个shell代理
&lt;code>socat TCP-LISTEN:12345 EXEC:/bin/bash&lt;/code>&lt;/p>
&lt;h2 id="场景四">场景四
&lt;/h2>&lt;p>本地有一个UNIX DOMAIN域套接字，我想把他转成TCP SOCKET供局域网内的机器使用，怎么办？&lt;/p>
&lt;p>&lt;code>socat TCP-LISTEN:12345,reuseaddr,fork UNIX-CONNECT:/data/deCOREIDPS/unix.domain&lt;/code>&lt;/p>
&lt;p>当有多个tcp连上来那就fork一个去连域套接字&lt;/p>
&lt;h2 id="场景五">场景五
&lt;/h2>&lt;p>将本地的80端口转到远程去&lt;/p>
&lt;p>&lt;code>socat TCP-LISTEN:80,fork TCP:www.baidu.com:80&lt;/code>&lt;/p>
&lt;blockquote>
&lt;p>参考 &lt;a class="link" href="http://brieflyx.me/2015/linux-tools/socat-introduction/" target="_blank" rel="noopener"
>http://brieflyx.me/2015/linux-tools/socat-introduction/&lt;/a>&lt;/p>
&lt;/blockquote></description></item><item><title>GoogleProtocolBuffer了解</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/google-protocol-buffer-%E4%BA%86%E8%A7%A3/</link><pubDate>Fri, 18 May 2018 11:10:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/google-protocol-buffer-%E4%BA%86%E8%A7%A3/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>睡前学习一下Google protocol buffer&lt;/p>
&lt;h2 id="安装protobuf编译器">安装protobuf编译器
&lt;/h2>&lt;p>启动最新版的ubuntu docker镜像，运行apt install protobuf-compiler&lt;/p>
&lt;h2 id="书写-proto-文件">书写 .proto 文件
&lt;/h2>&lt;p>nano lm.helloworld.proto&lt;/p>
&lt;p>然后输入&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-protobuf" data-lang="protobuf">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">package&lt;/span> lm; &lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">message&lt;/span> &lt;span style="color:#a6e22e">helloworld&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>{ &lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> &lt;span style="color:#66d9ef">required&lt;/span> &lt;span style="color:#66d9ef">int32&lt;/span> id &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>; &lt;span style="color:#75715e">// ID
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">required&lt;/span> &lt;span style="color:#66d9ef">string&lt;/span> str &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>; &lt;span style="color:#75715e">// str
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">optional&lt;/span> &lt;span style="color:#66d9ef">int32&lt;/span> opt &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">3&lt;/span>; &lt;span style="color:#75715e">//optional field
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在上例中，package 名字叫做 lm，定义了一个消息 helloworld，该消息有三个成员，类型为 int32 的 id，另一个为类型为 string 的成员 str。opt 是一个可选的成员，即消息中可以不包含该成员。&lt;/p>
&lt;h2 id="编译proto文件">编译proto文件
&lt;/h2></description></item><item><title>epoll代码示例——handy库自带epoll.cc分析</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/epoll%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8Bhandy%E5%BA%93%E8%87%AA%E5%B8%A6epoll-cc%E5%88%86%E6%9E%90/</link><pubDate>Wed, 16 May 2018 07:43:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/epoll%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8Bhandy%E5%BA%93%E8%87%AA%E5%B8%A6epoll-cc%E5%88%86%E6%9E%90/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>C++网络库有很多。handy是一个C++11风格的网络库，对深入学习C++有很大帮助。&lt;/p>
&lt;h2 id="代码分析">代码分析
&lt;/h2>&lt;p>下面是来自handy/raw_examples下的epoll.cc文件。是水平触发的一个示例。该http服务器无论接收到什么样的请求，都返回一个静态资源123456。编译：c++ -o epoll epoll.cc，运行： sudo ./epoll。源代码中sendRes的if (con.writeEnabled)这句似乎有些问题，导致发送超大资源时出现问题。我已经做了修改，使之能够正确发送超大文件。&lt;/p>
&lt;pre tabindex="0">&lt;code>/*
* 编译：c++ -o epoll epoll.cc
* 运行： ./epoll
* 测试：curl -v localhost
*/
/*
运行效果
使用sudo 运行epoll程序。该程序在本机0.0.0.0的80端口监听，作为一个http服务器运行
每当有连接访问时，返回静态资源httpRes
LT是默认模式
*/
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;sys/epoll.h&amp;gt;
#include &amp;lt;netinet/in.h&amp;gt;
#include &amp;lt;arpa/inet.h&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;errno.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;signal.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
using namespace std;
bool output_log = true;
// 一个宏，用来打印错误并退出
#define exit_if(r, ...) if(r) {printf(__VA_ARGS__); printf(&amp;#34;%s:%d error no: %d error msg %s\n&amp;#34;, __FILE__, __LINE__, errno, strerror(errno)); exit(1);}
// 这个函数用于将指定的fd设置为非阻塞状态
void setNonBlock(int fd) {
// 首先我们取出原来文件描述符的flags
int flags = fcntl(fd, F_GETFL, 0);
exit_if(flags&amp;lt;0, &amp;#34;fcntl failed&amp;#34;);
// 然后加上O_NONBLOCK，再设置回去
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
exit_if(r&amp;lt;0, &amp;#34;fcntl failed&amp;#34;);
}
// 对epoll_ctl二次包装，将events 和 fd放入ev中。
// 并且设置events 设置为可读可写时触发
void updateEvents(int efd, int fd, int events, int op) {
struct epoll_event ev = {0};
ev.events = events;
ev.data.fd = fd;
printf(&amp;#34;%s fd %d events read %d write %d\n&amp;#34;,
op==EPOLL_CTL_MOD?&amp;#34;mod&amp;#34;:&amp;#34;add&amp;#34;, fd, ev.events &amp;amp; EPOLLIN, ev.events &amp;amp; EPOLLOUT);
int r = epoll_ctl(efd, op, fd, &amp;amp;ev);
exit_if(r, &amp;#34;epoll_ctl failed&amp;#34;);
}
// 尝试在fd上做accept操作。如果成功，将其加入到epoll fd的监听列表中。epoll的events设置为当数据有写入的时候触发。
void handleAccept(int efd, int fd) {
struct sockaddr_in raddr;
socklen_t rsz = sizeof(raddr);
int cfd = accept(fd,(struct sockaddr *)&amp;amp;raddr,&amp;amp;rsz);
exit_if(cfd&amp;lt;0, &amp;#34;accept failed&amp;#34;);
sockaddr_in peer, local;
socklen_t alen = sizeof(peer);
int r = getpeername(cfd, (sockaddr*)&amp;amp;peer, &amp;amp;alen);
exit_if(r&amp;lt;0, &amp;#34;getpeername failed&amp;#34;);
printf(&amp;#34;accept a connection from %s\n&amp;#34;, inet_ntoa(raddr.sin_addr));
setNonBlock(cfd);
updateEvents(efd, cfd, EPOLLIN, EPOLL_CTL_ADD);
}
// 表示一个连接。成员有连接已读取的数据、已写入的数据
// string用来保存二进制内容没有问题吗，如果遇到\0会如何？
// 没有问题https://www.zhihu.com/question/33104941
struct Con {
string readed;
size_t written;
bool writeEnabled;
Con(): written(0), writeEnabled(false) {}
};
// 用来映射fd与con的数据结构
map&amp;lt;int, Con&amp;gt; cons;
string httpRes;
// 发送资源
void sendRes(int efd, int fd) {
// 首先取得连接信息
Con&amp;amp; con = cons[fd];
// 没有接受到数据就要求写入
// 说明其可能上一次继续发送的数据发送完了
// 其对应的文件描述符在cons中已经删除
// 然后触发了epoll信号
// 此时关闭其上一次发送的标志
// 然后关闭其缓冲区发送触发epoll的标志
// 只保留其有数据可读时触发
// 为什么不在写入完数据时就将这步做了呢？
// if (!con.readed.length()) {
// if (con.writeEnabled) {
// updateEvents(efd, fd, EPOLLIN, EPOLL_CTL_MOD);
// con.writeEnabled = false;
// }
// return;
// }
// 计算还需要写入的数据长度
size_t left = httpRes.length() - con.written;
int wd = 0;
// 连续写入数据，直到内核缓冲区无法写入数据为止
while((wd=::write(fd, httpRes.data()+con.written, left))&amp;gt;0) {
con.written += wd;
left -= wd;
if(output_log) printf(&amp;#34;write %d bytes left: %lu\n&amp;#34;, wd, left);
};
// 如果没有数据可以写入，则删除这个连接。但是不断开连接，即将连接信息置空
if (left == 0) {
// close(fd); // 测试中使用了keepalive，因此不关闭连接。连接会在read事件中关闭
if (con.writeEnabled) {
updateEvents(efd, fd, EPOLLIN, EPOLL_CTL_MOD);
con.writeEnabled = false;
}
cons.erase(fd);
return;
}
// 如果内核缓冲区满了，没办法写入了
if (wd &amp;lt; 0 &amp;amp;&amp;amp; (errno == EAGAIN || errno == EWOULDBLOCK)) {
// 将其标记上可继续写
if (!con.writeEnabled) {
// 等待其可继续写，或可读
// 避免重复进行系统调用，使用con.writeEnabled标记位
printf(&amp;#34;update it to EPOLLIN|EPOLLOUT\n&amp;#34;);
updateEvents(efd, fd, EPOLLIN|EPOLLOUT, EPOLL_CTL_MOD);
con.writeEnabled = true;
}
return;
}
// 如果是其他情况，比如在没有写完数据时直接返回0，或者是返回了其他错误
// 则说明出错了
if (wd&amp;lt;=0) {
printf(&amp;#34;write error for %d: %d %s\n&amp;#34;, fd, errno, strerror(errno));
close(fd);
cons.erase(fd);
}
}
// 当loop once处理读取数据时，调用该函数
void handleRead(int efd, int fd) {
char buf[4096];
int n = 0;
// 每次读取4k字节，循环读出当前内核中已存在的数据（有可能分包导致信息不完整）
while ((n=::read(fd, buf, sizeof buf)) &amp;gt; 0) {
if(output_log) printf(&amp;#34;read %d bytes\n&amp;#34;, n);
// 这里通过一个map来获取之前fd对应的连接信息。
// 当fd对应的下标不存在的时候，则会调用con的默认构造函数Con(): written(0), writeEnabled(false) {}
string&amp;amp; readed = cons[fd].readed;
// 调用string类的append方法将数据加入到连接信息中
// 注意为了保证二进制安全需要传入参数n
readed.append(buf, n);
std::cout &amp;lt;&amp;lt; &amp;#34;now info is&amp;#34; &amp;lt;&amp;lt; std::endl &amp;lt;&amp;lt; &amp;#34;---&amp;#34; &amp;lt;&amp;lt; readed &amp;lt;&amp;lt; endl &amp;lt;&amp;lt; &amp;#34;---&amp;#34; &amp;lt;&amp;lt; std::endl;
// 判断一个http请求发送完毕。
// 不判断http请求的内容，一律发送静态资源
if (readed.length()&amp;gt;4) {
if (readed.substr(readed.length()-2, 2) == &amp;#34;\n\n&amp;#34; || readed.substr(readed.length()-4, 4) == &amp;#34;\r\n\r\n&amp;#34;) {
//当读取到一个完整的http请求，测试发送响应
// TCP连接建立起来之后，客户端就开始传输首部，然后以\r\n\r\n来标志首部的结束和实体的开始（当然是请求里包含实体才会有实体的开始），
// 接下来就是实体的传输，当实体传输完之后，客户端就开始接收数据，服务器就知道，这次请求就已经结束了，
// 那么实体就是\r\n\r\n到停止接收的那么一段数据。对应的，客户端接收响应的时候也是这样。
// 没有实体，则\r\n\r\n就是http的结束
// 开始写入数据。注意有可能使缓冲区写满，若写满了则在之后继续写入
sendRes(efd, fd);
}
}
}
// read无法读取的话，就会返回-1。此时errno（errno是属于线程的，是线程安全的）是EAGAIN代表没读完。EWOULDBLOCK和EAGAIN是一样的。
// 那就返回，然后等待下次再读取
if (n&amp;lt;0 &amp;amp;&amp;amp; (errno == EAGAIN || errno == EWOULDBLOCK)){
printf(&amp;#34;nothing to read from %d, return. \n&amp;#34;, fd);
return;
}
//实际应用中，n&amp;lt;0应当检查各类错误，如EINTR
if (n &amp;lt; 0) {
printf(&amp;#34;read %d error: %d %s\n&amp;#34;, fd, errno, strerror(errno));
}
// 执行到这里，n是0，表示对端关闭连接。这时我们也关闭连接
printf(&amp;#34;%d close the connection\n&amp;#34;, fd);
close(fd);
cons.erase(fd);
}
// 当loop once缓冲区可写的时候，简单的写入我们准备好的静态资源
void handleWrite(int efd, int fd) {
sendRes(efd, fd);
}
// 对一个epoll句柄进行循环中的一次操作
// 其中l是LISTEN的fd
void loop_once(int efd, int lfd, int waitms) {
// 最多让内核拷贝20个事件出来
const int kMaxEvents = 20;
struct epoll_event activeEvs[100];
int n = epoll_wait(efd, activeEvs, kMaxEvents, waitms);
// n是返回了多少个事件
if(output_log) printf(&amp;#34;epoll_wait return %d\n&amp;#34;, n);
for (int i = 0; i &amp;lt; n; i ++) {
int fd = activeEvs[i].data.fd;
int events = activeEvs[i].events;
// EPOLLIN 事件或者是 EPOLLERR事件。EPOLLERR也代表管道写入结束。
// 参见： http://man7.org/linux/man-pages/man2/epoll_ctl.2.html
if (events &amp;amp; (EPOLLIN | EPOLLERR)) {
// EPOLLIN事件则只有当对端有数据写入时才会触发，所以触发一次后需要不断读取所有数据直到读完EAGAIN为止。否则剩下的数据只有在下次对端有写入时才能一起取出来了。
// 当对方关闭连接，则是EPOLLERR事件
if (fd == lfd) {
printf(&amp;#34;this is accept\n&amp;#34;);
handleAccept(efd, fd);
} else {
printf(&amp;#34;this can read\n&amp;#34;);
handleRead(efd, fd);
}
} else if (events &amp;amp; EPOLLOUT) {
// 这里处理文件描述符如果可以写入的事件
// EPOLLOUT事件只有在连接时触发一次，表示可写
// 之后表示缓冲区的数据已经送出，可以继续写入
// 详见https://www.zhihu.com/question/22840801
if(output_log) printf(&amp;#34;handling epollout\n&amp;#34;);
handleWrite(efd, fd);
} else {
exit_if(1, &amp;#34;unknown event&amp;#34;);
}
}
}
int main(int argc, const char* argv[]) {
if (argc &amp;gt; 1) { output_log = false; }
/*
小知识
signal（参数1，参数2）；
参数1：我们要进行处理的信号。系统的信号我们可以再终端键入 kill -l查看(共64个)。其实这些信号时系统定义的宏。
参数2：我们处理的方式（是系统默认还是忽略还是捕获）。SIG_IGN: 如果func参数被设置为SIG_IGN，该信号将被忽略。
*/
::signal(SIGPIPE, SIG_IGN);
// 设置http返回的内容
httpRes = &amp;#34;HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 19048576\r\n\r\n123456&amp;#34;;
// 将剩下的内容填充成0。最后content的长度是大约1024*1024
for(int i=0;i&amp;lt;19048570;i++) {
httpRes+=&amp;#39;\0&amp;#39;;
}
// 设置端口为80端口
short port = 80;
// 创建一个epoll句柄
int epollfd = epoll_create(1);
exit_if(epollfd &amp;lt; 0, &amp;#34;epoll_create failed&amp;#34;);
// 创建一个socket套接字
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
exit_if(listenfd &amp;lt; 0, &amp;#34;socket failed&amp;#34;);
struct sockaddr_in addr;
memset(&amp;amp;addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
// 先绑定socket到端口上
int r = ::bind(listenfd,(struct sockaddr *)&amp;amp;addr, sizeof(struct sockaddr));
// 这一步如果没有超级用户的权限，就会报错。linux对于非root权限用户不能使用1024以下的端口
exit_if(r, &amp;#34;bind to 0.0.0.0:%d failed %d %s&amp;#34;, port, errno, strerror(errno));
/*
#include&amp;lt;sys/socket.h&amp;gt;
int listen(int sockfd, int backlog)
返回：0──成功， -1──失败
参数sockfd
被listen函数作用的套接字，sockfd之前由socket函数返回。在被socket函数返回的套接字fd之时，它是一个主动连接的套接字，
也就是此时系统假设用户会对这个套接字调用connect函数，期待它主动与其它进程连接，然后在服务器编程中，用户希望这个套接字可以接受外来的连接请求，
也就是被动等待用户来连接。由于系统默认时认为一个套接字是主动连接的，所以需要通过某种方式来告诉系统，用户进程通过系统调用listen来完成这件事。
参数backlog
这个参数涉及到一些网络的细节。在进程正理一个一个连接请求的时候，可能还存在其它的连接请求。
因为TCP连接是一个过程，所以可能存在一种半连接的状态，有时由于同时尝试连接的用户过多，使得服务器进程无法快速地完成连接请求。
如果这个情况出现了，服务器进程希望内核如何处理呢？
内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接，这样的一个队列内核不可能让其任意大，
所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。
毫无疑问，服务器进程不能随便指定一个数值，内核有一个许可的范围。这个范围是实现相关的。很难有某种统一，一般这个值会小30以内。
内核用于跟踪这些完成连接但是用户代码还没有通过accept调用的队列长度这里设置为了20。在队列长度小于20时，内核会立即完成连接的建立。
但是如果队列长度大于20，在用户代码调用accept之前该连接都不会建立，对方则处于阻塞状态。
*/
r = listen(listenfd, 20);
exit_if(r, &amp;#34;listen failed %d %s&amp;#34;, errno, strerror(errno));
printf(&amp;#34;fd %d listening at %d\n&amp;#34;, listenfd, port);
// 接下来设置文件描述符为阻塞。
// 为什么要设置为非阻塞？https://www.zhihu.com/question/23614342
setNonBlock(listenfd);
// 将其设置为可读取时触发，添加到epoll文件描述符池中
updateEvents(epollfd, listenfd, EPOLLIN, EPOLL_CTL_ADD);
for (;;) { //实际应用应当注册信号处理函数，退出时清理资源
loop_once(epollfd, listenfd, 10000);
}
return 0;
}
&lt;/code>&lt;/pre>&lt;h2 id="运行效果">运行效果
&lt;/h2>&lt;p>&lt;img src="https://nansenli.com/img/rescued/7d2d193858a6.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;pre tabindex="0">&lt;code>sudo ./epoll
fd 4 listening at 80
add fd 4 events read 1 write 0
epoll_wait return 1
this is accept
accept a connection from 127.0.0.1
add fd 5 events read 1 write 0
epoll_wait return 1
this can read
read 412 bytes
now info is
---GET / HTTP/1.1
Host: 127.0.0.1
Connection: keep-alive
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,und;q=0.8,zh-TW;q=0.7,en;q=0.6,pl;q=0.5
---
write 4081834 bytes left: 14966851
update it to EPOLLIN|EPOLLOUT
mod fd 5 events read 1 write 4
nothing to read from 5, return.
epoll_wait return 1
handling epollout
write 2226422 bytes left: 12740429
epoll_wait return 1
handling epollout
write 2095456 bytes left: 10644973
epoll_wait return 1
handling epollout
write 1964490 bytes left: 8680483
epoll_wait return 1
handling epollout
write 1506109 bytes left: 7174374
epoll_wait return 1
handling epollout
write 1833524 bytes left: 5340850
epoll_wait return 1
handling epollout
write 1637075 bytes left: 3703775
write 130966 bytes left: 3572809
epoll_wait return 1
handling epollout
write 1571592 bytes left: 2001217
epoll_wait return 1
handling epollout
write 1440626 bytes left: 560591
epoll_wait return 1
handling epollout
write 560591 bytes left: 0
mod fd 5 events read 1 write 0
epoll_wait return 1
this can read
read 375 bytes
now info is
---GET /favicon.ico HTTP/1.1
Host: 127.0.0.1
Connection: keep-alive
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Referer: http://127.0.0.1/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,und;q=0.8,zh-TW;q=0.7,en;q=0.6,pl;q=0.5
---
write 10477280 bytes left: 8571405
update it to EPOLLIN|EPOLLOUT
mod fd 5 events read 1 write 4
nothing to read from 5, return.
epoll_wait return 1
handling epollout
write 1440626 bytes left: 7130779
epoll_wait return 1
handling epollout
write 1768041 bytes left: 5362738
epoll_wait return 1
handling epollout
write 1571592 bytes left: 3791146
epoll_wait return 1
handling epollout
write 1637075 bytes left: 2154071
epoll_wait return 1
handling epollout
write 1702558 bytes left: 451513
epoll_wait return 1
handling epollout
write 451513 bytes left: 0
mod fd 5 events read 1 write 0
epoll_wait return 0
epoll_wait return 0
epoll_wait return 0
&lt;/code>&lt;/pre>&lt;p>这里我发送的资源改大了，改成如下的数值：&lt;/p>
&lt;pre tabindex="0">&lt;code> httpRes = &amp;#34;HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 19048576\r\n\r\n123456&amp;#34;;
// 将剩下的内容填充成0。最后content的长度是大约1024*1024
for(int i=0;i&amp;lt;19048570;i++) {
httpRes+=&amp;#39;\0&amp;#39;;
}
&lt;/code>&lt;/pre>&lt;p>可以看到分了多次传输。最终终端页面上显示123456，后面全都是\0，不会显示。
可以看到浏览器请求了两次，一次请求根目录，一次请求页面的图标favicon.ico&lt;/p></description></item><item><title>2018-05-11 随笔</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/2018-05-11-%E9%9A%8F%E7%AC%94/</link><pubDate>Fri, 11 May 2018 09:50:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/2018-05-11-%E9%9A%8F%E7%AC%94/</guid><description>&lt;p>开始准备写ss助手的服务器端。有很多问题。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>语言
如果将来要从事C++后台开发，最好还是使用C++比较好。但是也想使用一下其他的方案，比如Python。Python试了一下webpy非常的轻量，估计不会再使用之前的flask框架了。另外一个就是Go。Go的语法也是个问题，还没有学习。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>框架
暂时看到了C++的handy。不是特别想用muduo库。还是不想使用boost，虽然effective c++上面说了要学习boost。handy第一印象感觉很好。还在学习中。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>目标是先用handy写一个静态的服务器。之后再扩展。&lt;/p></description></item><item><title>C++完美单例模式</title><link>https://nansenli.com/zh-cn/post/jianshu/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/c++%E5%AE%8C%E7%BE%8E%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/</link><pubDate>Mon, 07 May 2018 06:15:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/c++%E5%AE%8C%E7%BE%8E%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/</guid><description>&lt;h2 id="原始的单例模式">原始的单例模式
&lt;/h2>&lt;p>单例模式要做如下事情：&lt;/p>
&lt;ul>
&lt;li>不能通过构造函数构造，否则就能够实例化多个。构造函数需要私有声明&lt;/li>
&lt;li>保证只能产生一个实例&lt;/li>
&lt;/ul>
&lt;p>下面是一个简单的实现：&lt;/p>
&lt;pre tabindex="0">&lt;code>class Singleton
{
private:
static Singleton *local_instance;
Singleton(){};
public:
static Singleton *getInstance()
{
if (local_instance == nullptr)
{
local_instance = new Singleton();
}
return local_instance;
}
};
Singleton * Singleton::local_instance = nullptr;
int main()
{
Singleton * s = Singleton::getInstance();
return 0;
}
&lt;/code>&lt;/pre>&lt;h2 id="使用局部静态对象来解决存在的两个问题">使用局部静态对象来解决存在的两个问题
&lt;/h2>&lt;p>刚刚的代码中有两个问题，一个是多线程的情况下可能会出现new两次的情况。另外一个是程序退出后没有运行析构函数。
下面采用了静态对象来解决。&lt;/p>
&lt;pre tabindex="0">&lt;code>class Singleton
{
private:
static Singleton *local_instance;
Singleton(){
cout &amp;lt;&amp;lt; &amp;#34;构造&amp;#34; &amp;lt;&amp;lt; endl;
};
~Singleton(){
cout &amp;lt;&amp;lt; &amp;#34;析构&amp;#34; &amp;lt;&amp;lt; endl;
}
public:
static Singleton *getInstance()
{
static Singleton locla_s;
return &amp;amp;locla_s;
}
};
int main()
{
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第一次前&amp;#34; &amp;lt;&amp;lt; endl;
Singleton * s = Singleton::getInstance();
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第一次后&amp;#34; &amp;lt;&amp;lt; endl;
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第二次前&amp;#34; &amp;lt;&amp;lt; endl;
Singleton * s2 = Singleton::getInstance();
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第二次后&amp;#34; &amp;lt;&amp;lt; endl;
return 0;
}
&lt;/code>&lt;/pre>&lt;p>&lt;img src="https://nansenli.com/img/rescued/b616396efe3d.png"
loading="lazy"
alt="输出结果"
>&lt;/p>
&lt;p>该代码可能在c++11之前的版本导致多次构造函数的调用，所以只能在较新的编译器上使用。&lt;/p>
&lt;h2 id="如果是c11之前的版本静态对象线程会不安全">如果是c++11之前的版本，静态对象线程会不安全
&lt;/h2>&lt;p>下面这个版本使用了mutex以及静态成员来析构单例。该方案的劣处在于锁导致速度慢，效率低。但是至少是正确的，也能在c++11之前的版本使用，代码的示例如下：&lt;/p>
&lt;pre tabindex="0">&lt;code>class Singleton
{
private:
static Singleton *local_instance;
static pthread_mutex_t mutex;
Singleton(){
cout &amp;lt;&amp;lt; &amp;#34;构造&amp;#34; &amp;lt;&amp;lt; endl;
};
~Singleton(){
cout &amp;lt;&amp;lt; &amp;#34;析构&amp;#34; &amp;lt;&amp;lt; endl;
}
class rememberFree{
public:
rememberFree(){
cout &amp;lt;&amp;lt; &amp;#34;成员构造&amp;#34; &amp;lt;&amp;lt; endl;
}
~rememberFree(){
if(Singleton::local_instance != nullptr){
delete Singleton::local_instance;
}
}
};
static rememberFree remember;
public:
static Singleton *getInstance()
{
pthread_mutex_lock(&amp;amp;mutex);
if (local_instance == nullptr)
{
local_instance = new Singleton();
}
pthread_mutex_unlock(&amp;amp;mutex);
return local_instance;
}
};
Singleton * Singleton::local_instance = nullptr;
pthread_mutex_t Singleton::mutex = PTHREAD_MUTEX_INITIALIZER;
Singleton::rememberFree Singleton::remember;
&lt;/code>&lt;/pre>&lt;h2 id="使用双锁检查导致未初始化的内存访问">使用双锁检查导致未初始化的内存访问
&lt;/h2>&lt;p>使用如下的代码来实现已经初始化的对象的直接返回。可以使上述代码性能会大大加快。但是相同的代码在Java下面有很明显的问题，由于CPU乱序执行，可能导致访问到未经初始化的对象的引用。
C++是否有同样的问题呢？看下文: &lt;a class="link" href="http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf" target="_blank" rel="noopener"
>http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf&lt;/a>
结论是一样的，c++也存在相同的问题，可能导致未定义行为导致段错误。双锁检查代码的例子如下：&lt;/p>
&lt;pre tabindex="0">&lt;code>static Singleton *getInstance()
{
if(local_instance == nullptr){
pthread_mutex_lock(&amp;amp;mutex);
if (local_instance == nullptr)
{
local_instance = new Singleton();
}
pthread_mutex_unlock(&amp;amp;mutex);
}
return local_instance;
}
&lt;/code>&lt;/pre>&lt;p>假如线程A进入锁内并分配对象的空间，但是由于指令可能乱序，实际上导致local_instance被先指向一块未被分配的内存，然后再在这块内存上进程初始化。但是在指向后，未初始化前，另一线程B可能通过getInstance获取到这个指针。&lt;/p>
&lt;h2 id="尝试使用局部变量并不能保证指令执行顺序">尝试使用局部变量并不能保证指令执行顺序
&lt;/h2>&lt;p>尝试使用临时变量强制指定指令运行顺序时，仍然会被编译器认为是无用的变量，然后被优化掉。下述代码是一个想法很好但是无法实现目的代码：&lt;/p>
&lt;pre tabindex="0">&lt;code> if(local_instance == nullptr){
static mutex mtx;
lock_guard&amp;lt;mutex&amp;gt; lock(mtx);
if (local_instance == nullptr)
{
auto tmp = new Singleton()
local_instance = tmp; }
}
return local_instance;
&lt;/code>&lt;/pre>&lt;h2 id="不优雅的使用volatile来解决指令乱序在双检查锁中出现的问题">不优雅的使用volatile来解决指令乱序在双检查锁中出现的问题
&lt;/h2>&lt;p>尝试使用volatile声明内部的指针，代码如下：&lt;/p>
&lt;pre tabindex="0">&lt;code>class Singleton
{
private:
static Singleton * volatile local_instance;
Singleton(){
cout &amp;lt;&amp;lt; &amp;#34;构造&amp;#34; &amp;lt;&amp;lt; endl;
};
~Singleton(){
cout &amp;lt;&amp;lt; &amp;#34;析构&amp;#34; &amp;lt;&amp;lt; endl;
}
class rememberFree{
public:
rememberFree(){
cout &amp;lt;&amp;lt; &amp;#34;成员构造&amp;#34; &amp;lt;&amp;lt; endl;
}
~rememberFree(){
if(Singleton::local_instance != nullptr){
delete Singleton::local_instance;
}
}
};
static rememberFree remember;
public:
static Singleton *getInstance()
{
if(local_instance == nullptr){
static mutex mtx;
lock_guard&amp;lt;mutex&amp;gt; lock(mtx);
if (local_instance == nullptr)
{
auto tmp = new Singleton();
local_instance = tmp;
}
}
return local_instance;
}
};
Singleton * volatile Singleton::local_instance = nullptr;
Singleton::rememberFree Singleton::remember;
int main()
{
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第一次前&amp;#34; &amp;lt;&amp;lt; endl;
Singleton * s = Singleton::getInstance();
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第一次后&amp;#34; &amp;lt;&amp;lt; endl;
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第二次前&amp;#34; &amp;lt;&amp;lt; endl;
Singleton * s2 = Singleton::getInstance();
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第二次后&amp;#34; &amp;lt;&amp;lt; endl;
return 0;
}
&lt;/code>&lt;/pre>&lt;p>在这份代码中，虽然temp是volatile，但是*temp不是，其成员也不是。所以仍然可能被优化。尝试将其*temp也声明为volatile，你会发现的的代码充满了volatile。但是至少是正确的：&lt;/p>
&lt;pre tabindex="0">&lt;code>class Singleton
{
private:
static volatile Singleton * volatile local_instance;
Singleton(){
cout &amp;lt;&amp;lt; &amp;#34;构造&amp;#34; &amp;lt;&amp;lt; endl;
};
~Singleton(){
cout &amp;lt;&amp;lt; &amp;#34;析构&amp;#34; &amp;lt;&amp;lt; endl;
}
class rememberFree{
public:
rememberFree(){
cout &amp;lt;&amp;lt; &amp;#34;成员构造&amp;#34; &amp;lt;&amp;lt; endl;
}
~rememberFree(){
if(Singleton::local_instance != nullptr){
delete Singleton::local_instance;
}
}
};
static rememberFree remember;
public:
static volatile Singleton *getInstance()
{
if(local_instance == nullptr){
static mutex mtx;
lock_guard&amp;lt;mutex&amp;gt; lock(mtx);
if (local_instance == nullptr)
{
auto tmp = new Singleton();
local_instance = tmp;
}
}
return local_instance;
}
};
volatile Singleton * volatile Singleton::local_instance = nullptr;
Singleton::rememberFree Singleton::remember;
int main()
{
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第一次前&amp;#34; &amp;lt;&amp;lt; endl;
volatile Singleton * s = Singleton::getInstance();
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第一次后&amp;#34; &amp;lt;&amp;lt; endl;
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第二次前&amp;#34; &amp;lt;&amp;lt; endl;
volatile Singleton * s2 = Singleton::getInstance();
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第二次后&amp;#34; &amp;lt;&amp;lt; endl;
return 0;
}
&lt;/code>&lt;/pre>&lt;h2 id="大杀器内存栅栏">大杀器——内存栅栏
&lt;/h2>&lt;p>在新的标准中，atomic类实现了内存栅栏，使得多个核心访问内存时可控。这利用了c++11的内存访问顺序可控。下面是代码实现：&lt;/p>
&lt;pre tabindex="0">&lt;code>class Singleton
{
private:
// static volatile Singleton * volatile local_instance;
static atomic&amp;lt;Singleton*&amp;gt; instance;
Singleton(){
cout &amp;lt;&amp;lt; &amp;#34;构造&amp;#34; &amp;lt;&amp;lt; endl;
};
~Singleton(){
cout &amp;lt;&amp;lt; &amp;#34;析构&amp;#34; &amp;lt;&amp;lt; endl;
}
class rememberFree{
public:
rememberFree(){
cout &amp;lt;&amp;lt; &amp;#34;成员构造&amp;#34; &amp;lt;&amp;lt; endl;
}
~rememberFree(){
Singleton* local_instance = instance.load(std::memory_order_relaxed);
if(local_instance != nullptr){
delete local_instance;
}
}
};
static rememberFree remember;
public:
static Singleton *getInstance()
{
Singleton* tmp = instance.load(std::memory_order_relaxed);
atomic_thread_fence(memory_order_acquire);
if(tmp == nullptr){
static mutex mtx;
lock_guard&amp;lt;mutex&amp;gt; lock(mtx);
tmp = instance.load(memory_order_relaxed);
if (tmp == nullptr)
{
tmp = new Singleton();
atomic_thread_fence(memory_order_release);
instance.store(tmp, memory_order_relaxed);
}
}
return tmp;
}
};
atomic&amp;lt;Singleton*&amp;gt; Singleton::instance;
Singleton::rememberFree Singleton::remember;
int main()
{
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第一次前&amp;#34; &amp;lt;&amp;lt; endl;
Singleton * s = Singleton::getInstance();
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第一次后&amp;#34; &amp;lt;&amp;lt; endl;
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第二次前&amp;#34; &amp;lt;&amp;lt; endl;
Singleton * s2 = Singleton::getInstance();
cout &amp;lt;&amp;lt; &amp;#34;单例模式访问第二次后&amp;#34; &amp;lt;&amp;lt; endl;
return 0;
}
&lt;/code>&lt;/pre>&lt;p>上述代码可能难以阅读，instance的两次加载可以被乱序执行。但是在此期间内的改动被其他CPU核心观察不到。在muduo一书上，内存栅栏也被评价为大杀器。&lt;/p>
&lt;h2 id="使用原子操作的内存顺序">使用原子操作的内存顺序
&lt;/h2>&lt;p>这里有六个内存序列选项可应用于对原子类型的操作：memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel, 以及memory_order_seq_cst。除非你为特定的操作指定一个序列选项，要不内存序列选项对于所有原子类型默认都是memory_order_seq_cst。虽然有六个选项，但是它们仅代表三种内存模型：排序一致序列(sequentially consistent)，获取-释放序列(memory_order_consume, memory_order_acquire, memory_order_release和memory_order_acq_rel)，和自由序列(memory_order_relaxed)。&lt;/p>
&lt;p>这里可以采用的模型有：默认的memory_order_seq_cst即顺序一致与memory_order_acquire、memory_order_release即获取释放序列。后者性能可能更好。&lt;/p>
&lt;p>待完善&lt;/p>
&lt;h2 id="使用pthread_once-或者call_once">使用pthread_once 或者call_once
&lt;/h2>&lt;p>前者来自pthread库。后者来自std::atomic。&lt;/p>
&lt;p>待完善&lt;/p></description></item><item><title>GDB从零学习</title><link>https://nansenli.com/zh-cn/post/jianshu/c++/gdb-%E4%BB%8E%E9%9B%B6%E5%AD%A6%E4%B9%A0/</link><pubDate>Wed, 25 Apr 2018 16:45:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/c++/gdb-%E4%BB%8E%E9%9B%B6%E5%AD%A6%E4%B9%A0/</guid><description>&lt;p>从例子入手。&lt;/p>
&lt;pre tabindex="0">&lt;code>#include &amp;lt;stdio.h&amp;gt;
void my_itoa(int n,char s[])
{
int i,j,sign;
if((sign=n)&amp;lt;0) //记录符号
n=-n; //使n成为正数
i=0;
do{
s[i++]=n%10+&amp;#39;0&amp;#39;; //取下一个数字
}while((n/=10)&amp;gt;0); //循环相除
if(sign&amp;lt;0)
s[i++]=&amp;#39;-&amp;#39;;
s[i]=&amp;#39;\0&amp;#39;;
for(j=i-1;j&amp;gt;=0;j--) //生成的数字是逆序的，所以要逆序输出
printf(&amp;#34;%c&amp;#34;,s[j]);
}
void main()
{
int n;
char str[100];
my_itoa(-123,str);
printf(&amp;#34;\n&amp;#34;);
printf(&amp;#34;%d\n&amp;#34;,my_atoi(&amp;#34;123&amp;#34;));
system(&amp;#34;pause&amp;#34;);
}
&lt;/code>&lt;/pre>&lt;p>将上述文件保存到test.cpp文件，然后编译运行。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/3132a6a25dab.gif"
loading="lazy"
alt="Peek 2018-04-16 17-25.gif"
>&lt;/p>
&lt;p>然后我们来调试一下，看看具体应该怎么调试。按照下面的方式来调试代码。&lt;/p>
&lt;pre tabindex="0">&lt;code>linan@linan-PC:~$ gdb test
GNU gdb (Debian 7.12-6) 7.12.0.20161007-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later &amp;lt;http://gnu.org/licenses/gpl.html&amp;gt;
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type &amp;#34;show copying&amp;#34;
and &amp;#34;show warranty&amp;#34; for details.
This GDB was configured as &amp;#34;x86_64-linux-gnu&amp;#34;.
Type &amp;#34;show configuration&amp;#34; for configuration details.
For bug reporting instructions, please see:
&amp;lt;http://www.gnu.org/software/gdb/bugs/&amp;gt;.
Find the GDB manual and other documentation resources online at:
&amp;lt;http://www.gnu.org/software/gdb/documentation/&amp;gt;.
For help, type &amp;#34;help&amp;#34;.
Type &amp;#34;apropos word&amp;#34; to search for commands related to &amp;#34;word&amp;#34;...
Reading symbols from test...done.
(gdb) l
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i&amp;lt;n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
11 }
(gdb)
12
13 main()
14 {
15 int i;
16 long result = 0;
17 for(i=1; i&amp;lt;=100; i++)
18 {
19 result += i;
20 }
21
(gdb)
22 printf(&amp;#34;result[1-100] = %d \n&amp;#34;, result );
23 printf(&amp;#34;result[1-250] = %d \n&amp;#34;, func(250) );
24 }
(gdb)
Line number 25 out of range; test.cpp has 24 lines.
(gdb) break 15
Breakpoint 1 at 0x706: file test.cpp, line 15.
(gdb) break func
Breakpoint 2 at 0x6d7: file test.cpp, line 5.
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000000706 in main() at test.cpp:15
2 breakpoint keep y 0x00000000000006d7 in func(int) at test.cpp:5
(gdb) run
Starting program: /home/linan/test
Breakpoint 1, main () at test.cpp:16
16 long result = 0;
(gdb) next
17 for(i=1; i&amp;lt;=100; i++)
(gdb) next
19 result += i;
(gdb) next
17 for(i=1; i&amp;lt;=100; i++)
(gdb) continue
Continuing.
result[1-100] = 5050
Breakpoint 2, func (n=250) at test.cpp:5
5 int sum=0,i;
(gdb) p sum
$1 = 32767
(gdb) n
6 for(i=0; i&amp;lt;n; i++)
(gdb) p sum
$2 = 0
(gdb) p i
$3 = -134225552
(gdb) print i
$4 = -134225552
(gdb) n
8 sum+=i;
(gdb) print i
$5 = 0
(gdb) bt
#0 func (n=250) at test.cpp:8
#1 0x000055555555474c in main () at test.cpp:23
(gdb) help bt
Print backtrace of all stack frames, or innermost COUNT frames.
With a negative argument, print outermost -COUNT frames.
Use of the &amp;#39;full&amp;#39; qualifier also prints the values of the local variables.
Use of the &amp;#39;no-filters&amp;#39; qualifier prohibits frame filters from executing
on this backtrace.
(gdb) bt
#0 func (n=250) at test.cpp:8
#1 0x000055555555474c in main () at test.cpp:23
(gdb) finish
Run till exit from #0 func (n=250) at test.cpp:8
0x000055555555474c in main () at test.cpp:23
23 printf(&amp;#34;result[1-250] = %d \n&amp;#34;, func(250) );
Value returned is $6 = 31125
(gdb) c
Continuing.
result[1-250] = 31125
[Inferior 1 (process 11480) exited normally]
(gdb)
&lt;/code>&lt;/pre>&lt;p>&lt;img src="https://nansenli.com/img/rescued/32228d4f4a18.gif"
loading="lazy"
alt="Peek 2018-04-16 17-39.gif"
>&lt;/p>
&lt;h2 id="使用gdb之前">使用GDB之前
&lt;/h2>&lt;p>首先确保你的程序在编译时使用了-g参数。这样才能将相关信息插入到可执行程序中。这些相关信息对程序是没有帮助的，但是对调试有帮助。&lt;/p>
&lt;h2 id="使用gdb启动调试">使用GDB启动调试
&lt;/h2>&lt;p>启动GDB的方法有以下几种：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>gdb &lt;!-- raw HTML omitted -->
program也就是你的执行文件，一般在当然目录下。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>gdb &lt;!-- raw HTML omitted --> core
用gdb同时调试一个运行程序和core文件，core是程序非法执行后core dump后产生的文件。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>gdb &lt;!-- raw HTML omitted --> &lt;!-- raw HTML omitted -->
如果你的程序是一个服务程序，那么你可以指定这个服务程序运行时的进程ID。gdb会自动 &lt;strong>attach&lt;/strong> 上去，并调试他。program应该在PATH环境变量中搜索得到。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="使用gdb的新手注意事项">使用GDB的新手注意事项
&lt;/h2>&lt;p>GDB调试的时候需要知道源码的文件在哪里，在上面的例子中，源码和运行程序在一起。但是实际中不可能这样。在GDB命令加入参数-d或者在运行时使用direcotry 命令来添加源码目录。&lt;/p>
&lt;p>其他注意点：&lt;/p>
&lt;pre tabindex="0">&lt;code> -symbols &amp;lt;file&amp;gt;
-s &amp;lt;file&amp;gt;
从指定文件中读取符号表。
-se file
从指定文件中读取符号表信息，并把他用在可执行文件中。
-core &amp;lt;file&amp;gt;
-c &amp;lt;file&amp;gt;
调试时core dump的core文件。
&lt;/code>&lt;/pre></description></item><item><title>effective C++ 笔记</title><link>https://nansenli.com/zh-cn/post/jianshu/c++/effective-c++-%E7%AC%94%E8%AE%B0/</link><pubDate>Mon, 23 Apr 2018 10:40:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/c++/effective-c++-%E7%AC%94%E8%AE%B0/</guid><description>&lt;h2 id="第一章-从-c-到-c">第一章 从 C 到 C++
&lt;/h2>&lt;ul>
&lt;li>
&lt;p>条款1 C++是语言联邦
这条意思是C++支持面向过程、面向对象、泛型编程、函数编程、元编程五中编程方式，但是会令人困惑。所以他是一门综合语言。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款2 const enum inline 代替 define
define不是语言的一部分，而是预处理的部分。const enum inline可以被编译器发现错误&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款3 使用const来限制出错的可能
例如返回const引用对象来防止用户修改。使用const成员函数来防止对类的破坏。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款4 使用对象前确保已经初始化
主要是使用参数初始化列表初始化成员或者父类。另外如果类使用了全局的对象，则无法知道什么时候初始化的这个对象。所以需要使用类内部的static变量。（也是只有一份）&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="第二章-有关对象的构造与析构">第二章 有关对象的构造与析构
&lt;/h2>&lt;ul>
&lt;li>条款5 了解对象的默认成员函数
编译器会根据需要的时候，默认生成的成员函数包括默认构造函数，拷贝构造函数，析构函数，拷贝赋值函数。&lt;/li>
&lt;/ul>
&lt;p>其中，默认拷贝赋值函数会可能无法拷贝引用成员。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>条款6 使用=delete来拒绝默认生成的成员函数
这个可以防止类被拷贝，组织拷贝&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款7 为多态基类声明virtual析构函数
这个是基本功了。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款8 析构函数不要抛出异常
这个可能导致内存释放不完全，或者导致抛出多个异常&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款9 构造函数和析构函数中中不调用虚函数
这条也比较明显，派生类的构造函数如果进入到基类构造函数中，调用了虚函数，此时派生类的虚函数还没有准备好。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款10 operator = 应该返回对象的引用
这样就可以连续赋值了&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款11 在operator = 处理自我赋值
很难保证你的自我赋值代码没有问题，而且自我赋值是浪费时间的行为。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款12 复制对象时记得复制每一个部分
这个很显然了。你要实现赋值函数，那么就必须拷贝所有内容。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="第三章-有关对象的持有资源">第三章 有关对象的持有资源
&lt;/h2>&lt;ul>
&lt;li>
&lt;p>条款13 对象是持有资源的基本单位
要能够获取资源，还要能够正确释放资源。关键在于使用shared_ptr来管理内存。可以自动释放持有的资源&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款14 持有资源的对象复制
要么禁止复制，要么使用shared_ptr成员，要么进行深度拷贝，要么使拷贝过程变为资源的转移。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款15 持有资源的对象需要提供一个原始对象的方法
这个世界并不完美，许多APIs需要传递原始资源的指针才可以。使用shared_ptr会干扰这个对象的使用方式&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款16 new数组对象的时候需要delete
为了避免这种错误，可以使用vector容器来进行操作，避免对new数组的需求&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款17 new对象作为参数传入式可能导致内存泄漏
假如new对象和参数传入shared_ptr中间抛出了异常，那么就会导致内存泄漏&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="第四章-设计与声明">第四章 设计与声明
&lt;/h2>&lt;ul>
&lt;li>
&lt;p>条款18 让接口容易被正确使用
接口有的时候难以表述清楚，比如传入三个int值，你得清楚的记得这三个值每一个到底是什么。如果传入的参数反了怎么办？这里书上提到了传入特定对象以及使用explicit来限定参数。其实也可以构建一个日期对象，然后指定成员，我感觉这种还好一点。另外，接口除了参数要设置的合理，返回值也要设置的合理（比如返回智能指针而不是原始指针强迫对方）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款19 设计class是个大工程
对象的创建、销毁，对象初始化与对象赋值，对象通过值拷贝传递会异常吗，新type的合法值是什么。类型转换函数写了吗。operator呢。哪些函数应该被delete？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款20 传递常量引用代替传值
传值会带来对象的构造函数的调用，消耗时间，传递引用就是传递指针&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款21 返回临时对象时无法返回对临时对象的引用
如果返回一个由函数创建的对象时，无法返回对该对象的引用。编译器会自己做出正确的优化步骤。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款22 成员变量应该声明为private
如果声明为public，那么删除这个变量会导致所有程序重写。如果声明为private，那么删除这个变量会导致继承类程序重写。所以要声明为private&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款23 使用非成员函数替换成员函数
如果一个方法只是调用了其他几个成员函数，没有必要将其设置为成员函数，把他放在外面，然后用相同的命名空间包裹就可以了&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款24 如果成员函数的参数需要支持类型转换，则将其转为非成员函数
这是因为成员函数在某些情况下可能无法发挥作用。比如operator *只能支持该类型作为第一个参数。如果内置类型作为第一个参数的时候就会失效。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款25 swap函数与异常
swap是个有用的函数。为了实现高效率的swap，你需要设计一个swap成员函数来与另外一个对象进行交换。然后再命名空间设计一个非成员函数雨来调用上面的swap函数。最后特化std::swap来让编译器选择你自己的swap&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="第五章-有关函数的实现">第五章 有关函数的实现
&lt;/h2>&lt;ul>
&lt;li>
&lt;p>条款26 延迟定义出现的顺序
C++对于不同行的顺序不能随意更改其顺序，这意味着只有用到什么再去定义什么更能符合过程，如果在刚开始就将所有的定义，但是程序一开始就返回，这就会带来不必要的构造与析构过程。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款27 避免类型转换
四种c++类型转换的使用场景虽然可以使用，但是如果使用了都会带来更大的问题。旧式的类型转换就更不能用了。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款28 避免返回handles指向对象内部成分
尽量避免对象内部的数据的指针被返回到对象外部，这会带来很大的问题。vector []运算符就是这种情况。这种情况是特例&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款29 异常安全
C++异常是最难以处理的一件事情了。但是还是要保证异常。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款30 了解inline
inline是让编译器尽量避免栈的展开，而不是一定。八二法则告诉我们，程序只会花时间在20%的代码上，不一定见了函数就要都用inline。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款31 减少依赖——定义与声明相分离
两种处理方式——实施类与接口类。实施类就是使用另一个类来代理实现该类的功能。接口类就是使用面向对象的方法来将该类设计为接口。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="第六章-面向对象设计">第六章 面向对象设计
&lt;/h2>&lt;ul>
&lt;li>
&lt;p>条款32 public继承意味着is-a关系
这意味着所有基类能够办到的事情派生类也能办到。但是派生类能够办到的基类不一定能办到&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款33 继承时会发生名称遮掩
如果在派生类中声明了父类函数的另一个重载版本，会将父类的原始版本也一并覆盖，使得无法正确的链接到父类函数。可以使用using来表示使用父类的函数空间。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款34 区分接口继承和实现继承
纯虚函数是只继承接口，虚函数继承接口和默认实现，非虚函数是继承接口和强制实现&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款35 不是只有虚函数一种实现
可以使用其他设计模式来代替虚函数的实现，例如仿函数&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款36 不要重新定义非虚函数
省的到时候你都不知道具体是哪个被调用了。没错说的就是析构函数。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款37 不要重新定义缺省参数
这是因为缺省参数是静态确定的而不是动态确定的。你定义的那个不一定有用&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款38 区分复合与继承
public继承是is-a关系。复合是has-a关系。例如set的实现，如果继承list就会有一些问题了。set应该复合list。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款39 private继承意味着根据其实现
private的使用频率比复合使用的频率要更低。有两种情况需要这样实现，第一种情况是派生类需要访问基类的protected成员。第二种情况是需要重新定义virtual函数（这种情况难道不应该设计一种中间类吗）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款40 小心的使用多重继承
多重继承使用的很少。虚继承内最好不要包含任何成员。因为虚继承的成员初始化是在派生类内完成的。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="第七章-泛型编程">第七章 泛型编程
&lt;/h2>&lt;ul>
&lt;li>
&lt;p>条款41 了解隐式接口与编译器多态
就是静态多态。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款42 typename的双重含义
typename除了会在模板的template一行中使用，还会模板的处理中出现。当我们使用成员变量或者是成员类型时，编译器不知道用的是哪一个。默认使用的成员变量。加上typename来提醒编译器用的是成员类型&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款43 继承一个模板类，对模板类内的符号要显示声明
这是因为模板类可能特化，如果特化，则可能有的符号不存在。所以需要显示的指示模板类内的符号，例如使用this指针或者是作用域&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款44 template导致代码膨胀
template参数在传入一个非类型参数的时候，会生成多份代码。这些代码有一些方式可以避免重复生成，但是速度不如原来的快。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款45 运用成员函数模板接受所有兼容类型
类可以是模板的，成员函数也可以是模板的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款46 需要类型转换时请为模板定义非成员函数
不仅要定义成非成员函数，还要定义成为friend函数。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款47 使用traits
traits是使用了模板编程与特化的性质实现的一个triats类，通过triats类可以获得想要的泛化类内的数据类型。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款48 template元编程
元编程指的是在编译期间就知道结果的编程方法。比如在编译期间处理输入。类似于C++的hello world的程序就是阶乘。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="第八章-new-与-delete">第八章 new 与 delete
&lt;/h2>&lt;ul>
&lt;li>
&lt;p>条款49
了解new-handler的行为
就是不断尝试分配内存。我很好奇如果new-handler一直分配失败会不会出现卡死的现象。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款50
了解自定义new和delete的场合
貌似现在的new和delete性能都很高了。不用替换&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款51
自定义new和delete要保持兼容性&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款52
placement new 和placement delete
placement delete会在placement new出现错误的时候自动运行&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="第九章-现在的c11">第九章 现在的c++11
&lt;/h2>&lt;ul>
&lt;li>
&lt;p>条款53
不要忽视warring&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款54
熟悉tr1的标准程序库&lt;/p>
&lt;/li>
&lt;li>
&lt;p>条款55
熟悉boost&lt;/p>
&lt;/li>
&lt;/ul></description></item><item><title>腾讯校招随笔</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E8%85%BE%E8%AE%AF%E6%A0%A1%E6%8B%9B%E9%9A%8F%E7%AC%94/</link><pubDate>Wed, 11 Apr 2018 08:39:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E8%85%BE%E8%AE%AF%E6%A0%A1%E6%8B%9B%E9%9A%8F%E7%AC%94/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>提前批有两次电话一面，这里略过了。
首发于：https://www.jianshu.com/p/d5aa63a27172&lt;/p>
&lt;h2 id="一面">一面
&lt;/h2>&lt;p>成都4月11日&lt;/p>
&lt;ul>
&lt;li>介绍自己的项目、难点在哪里、怎么解决的&lt;/li>
&lt;li>介绍C++11特性&lt;/li>
&lt;li>介绍fork函数&lt;/li>
&lt;li>介绍time_wait状态&lt;/li>
&lt;li>为什么tcp连接握手需要三次&lt;/li>
&lt;li>介绍迭代器失效。push_back会导致迭代器失效吗。&lt;/li>
&lt;li>红黑树的特征，介绍&lt;/li>
&lt;li>哈希冲突的解决方法。&lt;/li>
&lt;li>进程和线程的区别&lt;/li>
&lt;li>你都使用什么线程模型&lt;/li>
&lt;li>介绍协程&lt;/li>
&lt;li>介绍快排算法&lt;/li>
&lt;li>什么是稳定性排序，快排是稳定性的吗，为什么&lt;/li>
&lt;li>快排算法最差情况推导公式&lt;/li>
&lt;li>析构函数虚函数为什么&lt;/li>
&lt;li>构造函数为什么不能是虚函数&lt;/li>
&lt;li>打印在纸上的题目，考察：new [] 对象。static 成员。子类构造函数具体调用了啥。拷贝构造函数与赋值函数的区别。等号在拷贝构造函数出现的时机。什么时候需要赋值函数。深拷贝与浅拷贝。虚函数的调用时机。&lt;/li>
&lt;/ul>
&lt;p>下面是其中一道题目，请写出运行结果&lt;/p>
&lt;pre tabindex="0">&lt;code>#include &amp;lt;iostream&amp;gt;
using namespace std;
struct A{
A(){
local_var++;
}
virtual void func(){
cout &amp;lt;&amp;lt; &amp;#34;A&amp;#34; &amp;lt;&amp;lt; endl;
}
static int local_var;
};
int A::local_var = 0;
struct B : A{
B(){
local_var+=2;
}
virtual void func(){
cout &amp;lt;&amp;lt; &amp;#34;B&amp;#34; &amp;lt;&amp;lt; endl;
}
};
int main(){
A* a1 = new B[3];
cout &amp;lt;&amp;lt; a1[0].local_var &amp;lt;&amp;lt; endl;
a1-&amp;gt;func();
A a2 = a1[0];
cout &amp;lt;&amp;lt; a2.local_var &amp;lt;&amp;lt; endl;
a2.func();
A a3;
a3 = a1[0];
cout &amp;lt;&amp;lt; a3.local_var &amp;lt;&amp;lt; endl;
a3.func();
}
&lt;/code>&lt;/pre>&lt;p>以下代码会出现什么问题。&lt;/p>
&lt;pre tabindex="0">&lt;code>#include &amp;lt;iostream&amp;gt;
using namespace std;
struct A{
virtual void func1(){
cout &amp;lt;&amp;lt; a &amp;lt;&amp;lt; endl;
}
void func2(){
cout &amp;lt;&amp;lt; a &amp;lt;&amp;lt; endl;
}
void func3(){
cout &amp;lt;&amp;lt; &amp;#34;a&amp;#34; &amp;lt;&amp;lt; endl;
}
int a;
};
int main(){
A * a = (A*)malloc(sizeof(A));
a-&amp;gt;func1();
a-&amp;gt;func2();
a-&amp;gt;func3();
}
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>笔试过程中（之前牛客笔试。面试官居然打印了我的笔试所有答案和结果，包括选择题和算法题，提交了多少次，失败了多少次）一道题的思路。考察虚指针的初始化时机。&lt;/li>
&lt;li>随机出一道纸上的题目。给定前序遍历ABC后序遍历CBA，求中序遍历是什么，画出来两种情况。&lt;/li>
&lt;li>笔试过程中（之前牛客笔试），第一道算法题的思路&lt;/li>
&lt;li>有100个弹珠，双方轮流拿，每个人只能拿1~5个，无法拿的人输，必胜解法。&lt;/li>
&lt;li>最近学什么（服务器编程）。之前学什么（TensorFlow），怎么学习的。你对Tensorflow强大的神经网络是怎么看待的。&lt;/li>
&lt;li>家庭情况了解。&lt;/li>
&lt;li>离开后在大厅手写代码，求char a数组中有的元素和char b数组中没有的元素，放到char c数组。这里我忘记把char字符转成unsigned char类型。&lt;/li>
&lt;/ul>
&lt;h2 id="二面">二面
&lt;/h2>&lt;p>成都4月13日&lt;/p>
&lt;ul>
&lt;li>自我介绍&lt;/li>
&lt;li>系统调用是什么。你用过哪些系统调用。什么系统调用会耗时长。&lt;/li>
&lt;li>gdb调试用过吗。什么是条件断点。&lt;/li>
&lt;li>函数指针和指针函数的区别。写个例子出来。&lt;/li>
&lt;li>手写fork调用示例。你在哪些场合使用过fork调用。&lt;/li>
&lt;li>介绍udp的connect函数。&lt;/li>
&lt;li>索引是什么。多加索引一定会好吗？（索引这个承认了看面经，但是后一个问题自己想出来了）&lt;/li>
&lt;li>上一次面试中，你有哪些问题你回去查了。&lt;/li>
&lt;li>上一次面试中，你的代码有问题，你知道吗。&lt;/li>
&lt;li>你回答的问题是从哪里学习的。你这两天在干什么？&lt;/li>
&lt;li>给你提一些建议，看下gdb调试，看下数据库知识，看下网络编程，多实践。&lt;/li>
&lt;/ul>
&lt;h2 id="后续">后续
&lt;/h2>&lt;p>4月20日收到腾讯的电话offer确认。&lt;/p></description></item><item><title>TCP UDP IP最大报文长度</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/tcp-udp-ip%E6%9C%80%E5%A4%A7%E6%8A%A5%E6%96%87%E9%95%BF%E5%BA%A6/</link><pubDate>Mon, 26 Mar 2018 16:41:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/tcp-udp-ip%E6%9C%80%E5%A4%A7%E6%8A%A5%E6%96%87%E9%95%BF%E5%BA%A6/</guid><description>&lt;h2 id="udp">UDP
&lt;/h2>&lt;p>对于UDP协议来说，整个包的最大长度为65535，其中包头长度是65535-20=65515。&lt;/p>
&lt;h2 id="tcp">TCP
&lt;/h2>&lt;p>对于TCP协议来说，整个包的最大长度是由最大传输大小（MSS，Maxitum Segment Size）决定，MSS就是TCP数据包每次能够传输的最大数据分段。为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值，这个值TCP协议在实现的时候往往用MTU值代替（需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes）所以往往MSS为1460。通讯双方会根据双方提供的MSS值得最小值确定为这次连接的最大MSS值。&lt;/p>
&lt;h2 id="ip">IP
&lt;/h2>&lt;p>对于IP协议来说，IP包的大小由MTU决定。&lt;/p></description></item><item><title>TCP 报文格式 HTTP报文格式</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/tcp-%E6%8A%A5%E6%96%87%E6%A0%BC%E5%BC%8F-http%E6%8A%A5%E6%96%87%E6%A0%BC%E5%BC%8F/</link><pubDate>Mon, 26 Mar 2018 16:37:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/tcp-%E6%8A%A5%E6%96%87%E6%A0%BC%E5%BC%8F-http%E6%8A%A5%E6%96%87%E6%A0%BC%E5%BC%8F/</guid><description>&lt;h2 id="tcp">TCP
&lt;/h2>&lt;pre tabindex="0">&lt;code>源端口 目标端口
序号
确认号
位标识 窗口
校验和 紧急指针
选项
数据
&lt;/code>&lt;/pre>&lt;h2 id="http">HTTP
&lt;/h2>&lt;pre tabindex="0">&lt;code>请求 URL 协议版本\r\n
字段：值\r\n
\r\n
正文
&lt;/code>&lt;/pre>&lt;pre tabindex="0">&lt;code>协议版本 状态码 状态码描述\r\n
字段：值\r\n
\r\n
正文
&lt;/code>&lt;/pre></description></item><item><title>TCP UDP 通过socket建立连接的过程</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/tcp-udp-%E9%80%9A%E8%BF%87socket%E5%BB%BA%E7%AB%8B%E8%BF%9E%E6%8E%A5%E7%9A%84%E8%BF%87%E7%A8%8B/</link><pubDate>Mon, 26 Mar 2018 14:46:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/tcp-udp-%E9%80%9A%E8%BF%87socket%E5%BB%BA%E7%AB%8B%E8%BF%9E%E6%8E%A5%E7%9A%84%E8%BF%87%E7%A8%8B/</guid><description>&lt;h2 id="tcp">TCP
&lt;/h2>&lt;p>| 阶段 | 服务器 | 客户端 |
| &amp;mdash;&amp;mdash;&amp;ndash; | &amp;mdash;&amp;ndash;: | :&amp;mdash;-: |
| | socket | socket |
| | bind | |
| | listen | |
| | accept | connect |
| | write | write |
| | read | read |
| | close | close |&lt;/p>
&lt;h2 id="udp">UDP
&lt;/h2>&lt;p>| 阶段 | 服务器 | 客户端 |
| &amp;mdash;&amp;mdash;&amp;ndash; | &amp;mdash;&amp;ndash;: | :&amp;mdash;-: |
| | socket | socket |
| | bind | |
| | sendto | recvfrom |
| | recvfrom | sendto |
| | close | close |&lt;/p></description></item><item><title>HTTPS 双向加密过程分析</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/https-%E5%8F%8C%E5%90%91%E5%8A%A0%E5%AF%86%E8%BF%87%E7%A8%8B%E5%88%86%E6%9E%90/</link><pubDate>Mon, 26 Mar 2018 14:42:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/https-%E5%8F%8C%E5%90%91%E5%8A%A0%E5%AF%86%E8%BF%87%E7%A8%8B%E5%88%86%E6%9E%90/</guid><description>&lt;p>HTTPS认证过程：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>① 浏览器发送一个连接请求给安全服务器。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>② 服务器将自己的证书，以及同证书相关的信息发送给客户浏览器。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>③ 客户浏览器检查服务器送过来的证书是否是由自己信赖的 CA 中心所签发的。如果是，就继续执行协议；如果不是，客户浏览器就给客户一个警告消息：警告客户这个证书不是可以信赖的，询问客户是否需要继续。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>④ 接着客户浏览器比较证书里的消息，例如域名和公钥，与服务器刚刚发送的相关消息是否一致，如果是一致的，客户浏览器认可这个服务器的合法身份。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>⑤ 服务器要求客户发送客户自己的证书。收到后，服务器验证客户的证书，如果没有通过验证，拒绝连接；如果通过验证，服务器获得用户的公钥。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>⑥ 客户浏览器告诉服务器自己所能够支持的通讯对称密码方案。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>⑦ 服务器从客户发送过来的密码方案中，选择一种加密程度最高的密码方案，用客户的公钥加过密后通知浏览器。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>⑧ 浏览器针对这个密码方案，选择一个通话密钥，接着用服务器的公钥加过密后发送给服务器。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>⑨ 服务器接收到浏览器送过来的消息，用自己的私钥解密，获得通话密钥。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>⑩ 服务器、浏览器接下来的通讯都是用对称密码方案，对称密钥是加过密的。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>上面所述的是双向认证 SSL 协议的具体通讯过程，这种情况要求服务器和用户双方都有证书。单向认证 SSL 协议不需要客户拥有 CA 证书，具体的过程相对于上面的步骤，只需将服务器端验证客户证书的过程去掉，以及在协商对称密码方案，对称通话密钥时，服务器发送给客户的是没有加过密的 （这并不影响 SSL 过程的安全性）密码方案。这样，双方具体的通讯内容，就是加过密的数据，如果有第三方攻击，获得的只是加密的数据，第三方要获得有用的信息，就需要对加密 的数据进行解密，这时候的安全就依赖于密码方案的安全。而幸运的是，目前所用的密码方案，只要通讯密钥长度足够的长，就足够的安全。这也是我们强调要求使用128 位加密通讯的原因。&lt;/p></description></item><item><title>腾讯内推随笔</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E8%85%BE%E8%AE%AF%E5%86%85%E6%8E%A8%E9%9A%8F%E7%AC%94/</link><pubDate>Mon, 26 Mar 2018 12:23:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E8%85%BE%E8%AE%AF%E5%86%85%E6%8E%A8%E9%9A%8F%E7%AC%94/</guid><description>&lt;p>今天下午在刷牛客。忽然想看看腾讯的内推进展如何，微信公众号结果显示，目前的岗位可能并不适合你。&lt;/p>
&lt;p>并没有什么特殊的感想。&lt;/p>
&lt;p>还需要努力，争取一个好的结果吧。无论如何，还是有机会的。&lt;/p>
&lt;p>下午刷题，网易的8道题目有3道不会做，感觉确实有很多要学习的点。&lt;/p>
&lt;p>买了一堆书，不知道什么时候才能看完。&lt;/p>
&lt;p>总之还是要乐观一点。&lt;/p>
&lt;p>面试官说我挺适合的，虽然那是可能还是挺开心的，但是我不会因为我被抛弃了而伤心，也不会因为我被选择了而骄傲。&lt;/p></description></item><item><title>腾讯模拟考试之找零问题</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E8%85%BE%E8%AE%AF%E6%A8%A1%E6%8B%9F%E8%80%83%E8%AF%95%E4%B9%8B%E6%89%BE%E9%9B%B6%E9%97%AE%E9%A2%98/</link><pubDate>Mon, 26 Mar 2018 03:57:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E8%85%BE%E8%AE%AF%E6%A8%A1%E6%8B%9F%E8%80%83%E8%AF%95%E4%B9%8B%E6%89%BE%E9%9B%B6%E9%97%AE%E9%A2%98/</guid><description>&lt;blockquote>
&lt;p>小Q非常富有，拥有非常多的硬币，小Q的拥有的硬币是有规律的，对于所有的非负整数K,小Q恰好&amp;gt; 各有两个数值为2^k，的硬币，所以小Q拥有的硬币是1，1，2，2，4，4……，小Q卖东西需要支付元钱，请问小Q想知道有多少种组合方案。
输入：一个n (1&amp;lt;=n&amp;lt;=10^18),代表要付的钱
输出：表示小Q可以拼凑的方案数目&lt;/p>
&lt;/blockquote>
&lt;p>参考答案&lt;/p>
&lt;pre tabindex="0">&lt;code>#coding=utf-8
import math
n = int(input())
def getMaxCoin(n):
tmp = int(math.log(n,2))
return 2**tmp
buff = {}
def dp(n, coin):
if (n, coin) in buff:
return buff[(n,coin)]
if n == 0: # 找0块钱，必然可以
return 1
if coin == 1: # 只用1元找
if n == 1 or n == 2:
return 1
return 0 # 当前面值无解
if n &amp;gt; coin * 4 -2:
return 0
ret = 0
# 只用了一枚
if n &amp;gt;= coin:
ret += dp(n-coin, coin//2)
if n &amp;gt;= coin*2:
# 用了两枚
ret += dp(n-coin*2, coin//2)
# 没有用
ret += dp(n, coin//2)
buff[(n,coin)] = ret
return ret
print(dp(n,getMaxCoin(n)))
&lt;/code>&lt;/pre>&lt;p>思路：
对于当前的面额，最大可能用到的硬币面额可以通过getMaxCoin函数计算得到。
对于问题面额n和可以使用的最大硬币面额，求dp(n, coin)的值。
coin会不断减小，对于coin为1时，面额为1或2只有1种情况。对于面额为0时，不需要硬币就可以有解。对于面额大于coin*4-2时，超过了能找的上限，无解。对于其他情况，分三种情况，coin用了一枚，coin用了两枚，coin用了零枚。分别计算对应的dp值即可。使用buff缓存已经计算出来的面值。&lt;/p></description></item><item><title>我的第一个程序</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E6%88%91%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E7%A8%8B%E5%BA%8F/</link><pubDate>Wed, 21 Mar 2018 08:06:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E6%88%91%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E7%A8%8B%E5%BA%8F/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>留白&lt;/p>
&lt;h2 id="尝试">尝试
&lt;/h2>&lt;p>在大一的时候，我记得当时参加了一个学院的比赛，这是我第一次接触到有关编程的概念。&lt;/p>
&lt;p>这个比赛是通过该实验室提供的带有编程功能的积木与玩具，搭建出一个作品。有点类似于米兔机器人一样的概念，只不过当时时间比较早而已。&lt;/p>
&lt;p>我搭建了一个智能的小车，它能够按照地面上的黑色胶带的轨迹来行动，然后做出相应的动作。他拥有以下三个功能：&lt;/p>
&lt;ul>
&lt;li>按照地面上的单线胶带行动。该功能是通过两个红外传感器实现的。&lt;/li>
&lt;li>遇到障碍可以按照事先约定好的动作绕过障碍。通过一个红外传感器实现。&lt;/li>
&lt;li>可以用爪子抓取地面上的乒乓球。通过一个电机带动行动。&lt;/li>
&lt;/ul>
&lt;p>我记得当时的编程界面是通过一个图形界面操作的，这个程序中可以拖动不同的矩形，代表程序体。然后用连线来表示状态的转移。编写好代码，需要通过红外线装置传输到可编程积木中。红外线的传输装置有点像立起来的熨斗，积木很大一块，可以安装锂电池，上面有一些齿轮状的物体，可以将其他的轮胎或者是滑动装置安装上去。这样整个装置就可以按照你的期望运动了。&lt;/p>
&lt;p>我记得当时评比的时候，除了我自己的作品外，没有一个队伍的作品能够动起来（就是没有写程序）。&lt;/p>
&lt;p>其他队伍在介绍自己的机器人的时候，只是介绍了功能，但是很遗憾，动不了。&lt;/p>
&lt;p>轮到我介绍的时候，确实惊艳了全场，评委老师很惊奇的看到我的机器人能动。&lt;/p>
&lt;p>实际上，这个内置的程序是掉电消失的，并且，由于积木很老了，里面的锂电池根本就撑不了多久。我在基地写好程序，拿到比赛现场，为了防止程序丢失，我找了充电器。因为在运行的时候，不带充电线的锂电池会瞬间掉电，然后程序消失。所以能动起来也很幸运。&lt;/p>
&lt;p>我本来就没期待什么，我感觉只是按照自己的想法实现了这个小作品。后来宣布第一名的时候，居然不是我。我没有觉得很失落，毕竟也是很好玩的。只是上去领奖的那个人不是我，这让在场的所有人都感到十分的诧异。&lt;/p>
&lt;p>然后颁奖人员说弄错了，最后我还是获得了第一名。&lt;/p>
&lt;h2 id="失败">失败
&lt;/h2>&lt;p>学校要举行一个电子设计竞赛，面向全校人员开放。&lt;/p>
&lt;p>我看了比赛的题目，然后觉得可以尝试一下。我在百度上找到了类似的题目。内容是需要一系列的电子原件，焊接在一起，然后烧录所需要的程序，程序已经提供了。&lt;/p>
&lt;p>对于当时的我，那堆代码就是一堆乱码。&lt;/p>
&lt;p>我去了杭州电子市场，我在无数家商铺中穿梭，商店主人一个个看我感觉就像是异人一样。&lt;/p>
&lt;p>我笨拙着说出我想要的电子原件。商店主人好心的送了我一些LED小灯泡。&lt;/p>
&lt;p>她还说电烙铁要用扁头的，尖头的初学者用不好。&lt;/p>
&lt;p>他们很好奇我到底要做什么。&lt;/p>
&lt;p>原件买回来了，我坐在地板上，按照那篇博客给的电路图，在椅子上将东西焊好了。&lt;/p>
&lt;p>噢，对了，那个时候我也没有学过电路图，所以我只是按照想法焊好的。&lt;/p>
&lt;p>我觉得那时候的我挺单纯的，没有一点基础还想把东西焊出来。但是我却没有资格嘲笑他。&lt;/p>
&lt;p>然后开始烧录程序，我把单芯片放到了插座上，然后又把插座接上电源，开始用软件把那篇博客提供的程序尝试下载到芯片中。&lt;/p>
&lt;p>我其实想对他说，嘿，你确定这个网站提供的固件程序和你的芯片型号一致吗？芯片好烫，其实你插反了你知道吗，芯片估计已经被你烧毁了。&lt;/p>
&lt;p>然而我什么都不知道，我傻傻的拿着一个不知道什么原件焊出来的玩意和一个其实已经烧毁的芯片去参加评比，可想而知，我被评审专家鄙视了。&lt;/p>
&lt;p>然而我不知道的是，几乎没有像我这样的人去报名参加比赛。学校有一个电子设计竞赛实验班，在这个班上可以学习到所有你想学习的有关电子设计的知识。&lt;/p>
&lt;p>虽然比赛是面向全校的，但是报名的也只有这个实验班的会报名。&lt;/p>
&lt;h2 id="我的第一个程序">我的第一个程序
&lt;/h2>&lt;p>后来我才明白有电子设计班的存在。大二我报名了，我记得有一个考试和面试，通过才能进入。&lt;/p>
&lt;p>我记得当时他们问了几个问题，我都答不上来。后来问我C语言成绩是多少，我说97，然后就说可以，你通过了。&lt;/p>
&lt;p>第一个星期主要讲焊接的基本知识。虽然似乎故事才刚刚开始，然而我的这篇文章要讲的故事也快要结束了。&lt;/p>
&lt;p>第二个星期主要讲LED数码管的知识，我拿到了发的51开发板。晚上，我在寝室里学习让数码管按照自己的想法亮起来。&lt;/p>
&lt;p>我学习了怎么显示数字，是常亮的，我明白了原来是先要位选然后段选，我突然明白了很多，这就是最最原始的时间分片法吧。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/e8695ded8be9.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>我突然有一个想法，我想让数码管实现跑马灯的效果，就是外面的一圈不断的转圈圈。&lt;/p>
&lt;p>我写也写，写呀写。&lt;/p>
&lt;p>我写出来了。&lt;/p>
&lt;p>烧进去，运行，居然和我想象的一样，外面的一圈转呀转，真的很好看。我很开心。&lt;/p>
&lt;h2 id="后记">后记
&lt;/h2>&lt;p>也许那算不上我写的第一个程序，毕竟在那之前，C语言课也要求设计程序，那我应该已经写过程序了。&lt;/p>
&lt;p>然而对我而言，那个跑马灯，才是对我真正意义上的第一个程序，完全由我创造。&lt;/p>
&lt;p>就算有一天我死了，如果我写的程序能够运行下去，我可以以另一种方式来延续自己的生命。&lt;/p></description></item><item><title>Linux多线程服务器编程基础 C++ 11 并发编程学习</title><link>https://nansenli.com/zh-cn/post/jianshu/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/linux%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80-c++-11-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0/</link><pubDate>Sun, 18 Mar 2018 08:29:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/linux%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80-c++-11-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>C++中在以往版本中不支持线程。需要使用pthread之类的使用线程。如果能够使用C++自身的线程则可以使程序变得统一简洁。&lt;/p>
&lt;h2 id="头文件">头文件
&lt;/h2>&lt;ul>
&lt;li>&lt;!-- raw HTML omitted --> 该头文件包含有std::thread类与std::this_thread类。以及管理线程的函数。是实现线程的主要文件。&lt;/li>
&lt;li>&lt;!-- raw HTML omitted --> 该头文件包含有std::atomic和std::atomic_flag类，是实现原子操作的的主要文件。&lt;/li>
&lt;li>&lt;!-- raw HTML omitted --> 包含互斥相关的类与函数。&lt;/li>
&lt;li>&lt;!-- raw HTML omitted --> 包含有future类及相关的函数。&lt;/li>
&lt;li>&amp;lt;condition_variable&amp;gt; 包含有条件变量的类。&lt;/li>
&lt;/ul>
&lt;p>以上就是c++11 的线程部分了。虽然有关pthread与c++ thread有很多争议，但是对于跨平台的c++ thread来说，似乎更为标准一些。&lt;/p>
&lt;p>推荐此书来学习c++11的线程。
&lt;a class="link" href="https://www.gitbook.com/book/chenxiaowei/cpp_concurrency_in_action/details" target="_blank" rel="noopener"
>https://www.gitbook.com/book/chenxiaowei/cpp_concurrency_in_action/details&lt;/a>&lt;/p>
&lt;h2 id="hello-world-线程">hello world 线程
&lt;/h2>&lt;p>简单认识thread类&lt;/p>
&lt;pre tabindex="0">&lt;code>#include &amp;lt;thread&amp;gt;
using namespace std;
// 下面这个函数是一个我们想要运行的线程任务
void hello()
{
printf(&amp;#34;%s&amp;#34;, &amp;#34;hello\n&amp;#34;);
}
// 使用thread类，传入一个函数作为我们的初始任务。不仅如此，还能传入类等其他的参数
int main()
{
thread t(hello);
// 使用join来等待结束
t.join();
}
&lt;/code>&lt;/pre>&lt;p>除了join来等待结束，可以使用detach来不等待线程结束。&lt;/p>
&lt;pre tabindex="0">&lt;code>struct func
{
int&amp;amp; i;
func(int&amp;amp; i_) : i(i_) {}
void operator() ()
{
for (unsigned j=0 ; j&amp;lt;1000000 ; ++j)
{
do_something(i); // 1. 潜在访问隐患：悬空引用
}
}
};
void oops()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread my_thread(my_func);
my_thread.detach(); // 2. 不等待线程结束
} // 3. 新线程可能还在运行
&lt;/code>&lt;/pre>&lt;p>这个例子中，已经决定不等待线程结束(使用了detach()②)，所以当oops()函数执行完成时③，新线程中的函数可能还在运行。如果线程还在运行，它就会去调用do_something(i)函数①，这时就会访问已经销毁的变量。如同一个单线程程序——允许在函数完成后继续持有局部变量的指针或引用；当然，这从来就不是一个好主意——这种情况发生时，错误并不明显，会使多线程更容易出错。&lt;/p>
&lt;h2 id="如何等待线程结束">如何等待线程结束？
&lt;/h2>&lt;p>如果需要等待线程，相关的std::thread实例需要使用join()。清单2.1中，将my_thread.detach()替换为my_thread.join()，就可以确保局部变量在线程完成后，才被销毁。在这种情况下，因为原始线程在其生命周期中并没有做什么事，使得用一个独立的线程去执行函数变得收益甚微，但在实际编程中，原始线程要么有自己的工作要做；要么会启动多个子线程来做一些有用的工作，并等待这些线程结束。&lt;/p>
&lt;p>join()是简单粗暴的等待线程完成或不等待。当你需要对等待中的线程有更灵活的控制时，比如，看一下某个线程是否结束，或者只等待一段时间(超过时间就判定为超时)。想要做到这些，你需要使用其他机制来完成，比如条件变量和期待(futures)。调用join()的行为，还清理了线程相关的存储部分，这样std::thread对象将不再与已经完成的线程有任何关联。这意味着，只能对一个线程使用一次join();一旦已经使用过join()，std::thread对象就不能再次加入了，当对其使用joinable()时，将返回false。&lt;/p>
&lt;h2 id="向线程传递参数">向线程传递参数
&lt;/h2>&lt;pre tabindex="0">&lt;code>void f(int i, std::string const&amp;amp; s);
std::thread t(f, 3, &amp;#34;hello&amp;#34;);
&lt;/code>&lt;/pre>&lt;p>代码创建了一个调用f(3, &amp;ldquo;hello&amp;rdquo;)的线程。注意，函数f需要一个std::string对象作为第二个参数，但这里使用的是字符串的字面值，也就是char const *类型。之后，在线程的上下文中完成字面值向std::string对象的转化。&lt;/p>
&lt;p>需要注意的是，构造函数无视函数期待的参数类型，并盲目的拷贝已提供的变量。&lt;/p>
&lt;p>例如一：&lt;/p>
&lt;pre tabindex="0">&lt;code>void f(int i,std::string const&amp;amp; s);
void oops(int some_param)
{
char buffer[1024]; // 1
sprintf(buffer, &amp;#34;%i&amp;#34;,some_param);
std::thread t(f,3,buffer); // 2
t.detach();
}
&lt;/code>&lt;/pre>&lt;p>这种情况下，buffer②是一个指针变量，指向本地变量，然后本地变量通过buffer传递到新线程中②。并且，函数有很有可能会在字面值转化成std::string对象之前崩溃(oops)，从而导致一些未定义的行为。并且想要依赖隐式转换将字面值转换为函数期待的std::string对象，但因std::thread的构造函数会复制提供的变量，就只复制了没有转换成期望类型的字符串字面值。
解决方案就是在传递到std::thread构造函数之前就将字面值转化为std::string对象：&lt;/p>
&lt;pre tabindex="0">&lt;code>void f(int i,std::string const&amp;amp; s);
void not_oops(int some_param)
{
char buffer[1024];
sprintf(buffer,&amp;#34;%i&amp;#34;,some_param);
std::thread t(f,3,std::string(buffer)); // 使用std::string，避免悬垂指针
t.detach();
}
&lt;/code>&lt;/pre>&lt;p>例如二：
还可能遇到相反的情况：期望传递一个引用，但整个对象被复制了。当线程更新一个引用传递的数据结构时，这种情况就可能发生，比如：&lt;/p>
&lt;pre tabindex="0">&lt;code>void update_data_for_widget(widget_id w,widget_data&amp;amp; data); // 1
void oops_again(widget_id w)
{
widget_data data;
std::thread t(update_data_for_widget,w,data); // 2
display_status();
t.join();
process_widget_data(data); // 3
}
&lt;/code>&lt;/pre>&lt;h2 id="使用互斥">使用互斥
&lt;/h2>&lt;p>C++中通过实例化std::mutex创建互斥量，通过调用成员函数lock()进行上锁，unlock()进行解锁。不过，不推荐实践中直接去调用成员函数，因为调用成员函数就意味着，必须记住在每个函数出口都要去调用unlock()，也包括异常的情况。C++标准库为互斥量提供了一个RAII语法的模板类std::lock_guard，其会在构造的时候提供已锁的互斥量，并在析构的时候进行解锁，从而保证了一个已锁的互斥量总是会被正确的解锁。下面的程序清单中，展示了如何在多线程程序中，使用std::mutex构造的std::lock_guard实例，对一个列表进行访问保护。std::mutex和std::lock_guard都在&lt;!-- raw HTML omitted -->头文件中声明。&lt;/p>
&lt;pre tabindex="0">&lt;code>#include &amp;lt;list&amp;gt;
#include &amp;lt;mutex&amp;gt;
#include &amp;lt;algorithm&amp;gt;
std::list&amp;lt;int&amp;gt; some_list; // 1
std::mutex some_mutex; // 2
void add_to_list(int new_value)
{
std::lock_guard&amp;lt;std::mutex&amp;gt; guard(some_mutex); // 3
some_list.push_back(new_value);
}
bool list_contains(int value_to_find)
{
std::lock_guard&amp;lt;std::mutex&amp;gt; guard(some_mutex); // 4
return std::find(some_list.begin(),some_list.end(),value_to_find) != some_list.end();
}
&lt;/code>&lt;/pre>&lt;p>当其中一个成员函数返回的是保护数据的指针或引用时，会破坏对数据的保护。具有访问能力的指针或引用可以访问(并可能修改)被保护的数据，而不会被互斥锁限制。互斥量保护的数据需要对接口的设计相当谨慎，要确保互斥量能锁住任何对保护数据的访问，并且不留后门。&lt;/p>
&lt;h2 id="使用锁">使用锁
&lt;/h2>&lt;p>试想有一个玩具，这个玩具由两部分组成，必须拿到这两个部分，才能够玩。例如，一个玩具鼓，需要一个鼓锤和一个鼓才能玩。现在有两个小孩，他们都很喜欢玩这个玩具。当其中一个孩子拿到了鼓和鼓锤时，那就可以尽情的玩耍了。当另一孩子想要玩，他就得等待另一孩子玩完才行。再试想，鼓和鼓锤被放在不同的玩具箱里，并且两个孩子在同一时间里都想要去敲鼓。之后，他们就去玩具箱里面找这个鼓。其中一个找到了鼓，并且另外一个找到了鼓锤。现在问题就来了，除非其中一个孩子决定让另一个先玩，他可以把自己的那部分给另外一个孩子；但当他们都紧握着自己所有的部分而不给予，那么这个鼓谁都没法玩。&lt;/p>
&lt;p>很幸运，C++标准库有办法解决这个问题，std::lock——可以一次性锁住多个(两个以上)的互斥量，并且没有副作用(死锁风险)。&lt;/p>
&lt;p>如何避免死锁？&lt;/p>
&lt;ul>
&lt;li>
&lt;p>避免嵌套锁
第一个建议往往是最简单的：一个线程已获得一个锁时，再别去获取第二个。如果能坚持这个建议，因为每个线程只持有一个锁，锁上就不会产生死锁。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>使用固定顺序获取锁
当硬性条件要求你获取两个以上(包括两个)的锁，并且不能使用std::lock单独操作来获取它们;那么最好在每个线程上，用固定的顺序获取它们获取它们(锁)。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="同步等待">同步等待
&lt;/h2>&lt;p>假设你在旅游，而且正在一辆在夜间运行的火车上。在夜间，如何在正确的站点下车呢？一种方法是整晚都要醒着，然后注意到了哪一站。这样，你就不会错过你要到达的站点，但是这样会让你感到很疲倦。另外，你可以看一下时间表，估计一下火车到达目的地的时间，然后在一个稍早的时间点上设置闹铃，然后你就可以安心的睡会了。这个方法听起来也很不错，也没有错过你要下车的站点，但是当火车晚点的时候，你就要被过早的叫醒了。当然，闹钟的电池也可能会没电了，并导致你睡过站。理想的方式是，无论是早或晚，只要当火车到站的时候，有人或其他东西能把你唤醒，就好了。&lt;/p>
&lt;p>C++标准库对条件变量有两套实现：std::condition_variable和std::condition_variable_any。这两个实现都包含在&amp;lt;condition_variable&amp;gt;头文件的声明中。两者都需要与一个互斥量一起才能工作(互斥量是为了同步)；前者仅限于与std::mutex一起工作，而后者可以和任何满足最低标准的互斥量一起工作，从而加上了_any的后缀。因为std::condition_variable_any更加通用，这就可能从体积、性能，以及系统资源的使用方面产生额外的开销，所以std::condition_variable一般作为首选的类型，当对灵活性有硬性要求时，我们才会去考虑std::condition_variable_any。&lt;/p></description></item><item><title>牛客网面经题目摘抄</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E7%89%9B%E5%AE%A2%E7%BD%91%E9%9D%A2%E7%BB%8F%E9%A2%98%E7%9B%AE%E6%91%98%E6%8A%84/</link><pubDate>Sat, 17 Mar 2018 06:42:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E7%89%9B%E5%AE%A2%E7%BD%91%E9%9D%A2%E7%BB%8F%E9%A2%98%E7%9B%AE%E6%91%98%E6%8A%84/</guid><description>&lt;h2 id="来源-阿里菜鸟java实习电话面试经凉凉">来源 阿里菜鸟java实习电话面试经，凉凉~！~
&lt;/h2>&lt;p>&lt;a class="link" href="https://www.nowcoder.com/discuss/67251?type=2&amp;amp;order=0&amp;amp;pos=7&amp;amp;page=1" target="_blank" rel="noopener"
>https://www.nowcoder.com/discuss/67251?type=2&amp;order=0&amp;pos=7&amp;page=1&lt;/a>&lt;/p>
&lt;p>去除了java部分的题目&lt;/p>
&lt;ul>
&lt;li>
&lt;p>用户请求在服务器是一个线程一个请求吗
这个问题搜了下有两种模型，一种是阻塞的一种是非阻塞的。现在一般使用基于IO多路复用的技术，Reactor模式，只需要一个线程或者少量线程，就可以处理大量请求。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>服务器抗压策略
这个问题就更大了。百度了一下有两点，一个是缓存，一个是分布式。网友回答了消息队列，可以减轻大量并发量突发的情况。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>很多很多个数，找到前十个
可以使用快排的变体&lt;/p>
&lt;/li>
&lt;li>
&lt;p>堆排序时间复杂度如何计算？
n×log n，就是n次，每次从低向顶出堆要log n次&lt;/p>
&lt;/li>
&lt;li>
&lt;p>HTTP和TCP
这个不知道问的具体是什么，可能是联系？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>TCP关闭连接四次挥手，为啥
两次fin+ack，中间插入未发送完的数据。在带有延迟ack的&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="来源-网易内推cc研发offer之路">来源 网易内推（C++/C研发）offer之路
&lt;/h2>&lt;p>&lt;a class="link" href="https://www.nowcoder.com/discuss/3038?type=2&amp;amp;order=0&amp;amp;pos=15&amp;amp;page=4" target="_blank" rel="noopener"
>https://www.nowcoder.com/discuss/3038?type=2&amp;order=0&amp;pos=15&amp;page=4&lt;/a>&lt;/p>
&lt;ul>
&lt;li>C++多态性都有哪些？
多态是一种接口多种实现。静态多态和动态多态。静态多态是通过泛型编程实现的，动态多态是通过虚函数实现的。&lt;/li>
&lt;li>动态绑定是怎么实现的？
静态绑定：编译时绑定，通过对象调用
动态绑定：运行时绑定，通过地址实现
动态绑定是通过虚表实现的。&lt;/li>
&lt;li>类型转换有哪些？
C风格的转换，强制类型转换，隐式类型转换。C++四种类型转换：static_cast，const_cast，dynamic_cost，reinterpret_cost。分别用于内置静态类型转换（int float）、去掉类型的const属性，用于动态类型转换（必须为父子类），重新解释类型而不做实质转换（指针强转）。&lt;/li>
&lt;li>内存对齐的原则
结构体基本数据类型成员要从成员大小的整数倍开始对齐，结构体作为成员要从最宽的数据类型的整数倍对齐。结构体的大小为内部最大的成员的整数倍对齐。&lt;/li>
&lt;li>模板是如何实现的？
编译器在遇到模板声明时记住这个模板名称。在调用时根据模板传入的参数类型产生出具体的类型的代码，然后进行编译。&lt;/li>
&lt;li>指针和const的用法？
const类型的指针，指向const类型的指针，指向const类型的const类型的指针。&lt;/li>
&lt;li>虚函数、纯虚函数、虚函数与析构函数？为什么析构函数要定义成虚函数？
&lt;a class="link" href="http://liaoxl.github.io/blog/20131205/virtual-destructor/" target="_blank" rel="noopener"
>http://liaoxl.github.io/blog/20131205/virtual-destructor/&lt;/a>
根据这篇文章所述，意思是如果定义为虚函数并且子类也实现了的话，那么子类的析构函数和父类的析构函数都会调用。&lt;/li>
&lt;li>内联函数&lt;/li>
&lt;li>const 和 typedef&lt;/li>
&lt;li>排序算法有哪些？快速排序怎么实现的？最好时间复杂度，平均时间复杂度。
插入、冒泡、堆排序、归并排序、桶排序、希尔排序、快速排序。&lt;/li>
&lt;li>extern “C”有什么用
主要是为了调用C编写的程序。&lt;/li>
&lt;/ul>
&lt;h2 id="2017暑期实习c面经阿里网易游戏腾讯等">2017暑期实习C++面经(阿里，网易游戏，腾讯等)
&lt;/h2>&lt;p>来源：https://www.nowcoder.com/discuss/23512?type=2&amp;amp;order=0&amp;amp;pos=20&amp;amp;page=2&lt;/p>
&lt;ul>
&lt;li>map由什么实现的？你知道红黑树和AVL树有什么区别吗？为什么像map，set都用红黑树来实现？你有听过skip-list吗？&lt;/li>
&lt;li>用过vector.reserve()吗，vector的数组是怎么增长的？&lt;/li>
&lt;li>map-reduce了解吗？&lt;/li>
&lt;li>linux熟悉吗？目录权限，如644代表什么，目录如果有可执行权限代表什么？&lt;/li>
&lt;li>平时有使用vi吗？一般的linux命令熟悉吗？(如awk，grep等) 有用过gdb的调试吗？&lt;/li>
&lt;li>TCP/UDP的区别，如何实现拥塞控制，如何实现可靠传输？UDP的应用？
可靠/不可靠 字节流/报文 连接/非连接 开销大/开销小 有拥塞控制/无拥塞控制 全双工/单工
拥塞控制目的是为了防止发送的数据包超出了网络的承载能力，涉及到所有的主机和路由器。拥塞控制认为丢包是因为数据包超出了网络的承载能力，所以在发送数据包的，需要慢开始，不断地达到网络的承载能力，将拥塞窗口以指数的规律增长到慢开始门限，超出门限后，如果继续增大数据包的发送量，认为网络将会出现拥塞，所以进入拥塞避免状态，该阶段以线性的增长规模扩大拥塞窗口，直到拥塞发生。如果某个数据包在超时计时器已到仍然没有收到对方的ACK，则认为网络中出现了拥塞，此时将慢开始门限设置为当前窗口的一半，并执行慢开始算法。如果收到了对方的三个重复的确认报文，则认为对方尚未收到丢失的报文，则尽早重传未被确认的报文，而不是等到超时计时器到时时才重传，这就是快速重传。如果出现了快速重传，则不执行慢开始算法，而是将拥塞窗口设置为慢开始门限，然后执行拥塞避免，这就是快恢复。&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>作者：Simon_
链接：&lt;a class="link" href="https://www.nowcoder.com/discuss/23512?type=2&amp;amp;order=0&amp;amp;pos=20&amp;amp;page=2" target="_blank" rel="noopener"
>https://www.nowcoder.com/discuss/23512?type=2&amp;amp;order=0&amp;amp;pos=20&amp;amp;page=2&lt;/a>
来源：牛客网&lt;/p>
&lt;p>总结：&lt;/p>
&lt;p>我觉得阿里更注重项目和分布式，多线程方面的知识。网易游戏的话更注重算法和C++吧。腾讯的话就什么都问，从C++的内存结构，RTTI，到网络编程，到智力题的算法实现。同学们可以根据自己要投的公司再好好准备准备。与君共勉。&lt;/p>
&lt;p>准备实习之前刷过的书籍有《C++ primer》, 《深度探索C++对象模型》，《Effective C++》，《STL源码解析》，《剑指offer》还有就是一些网络和操作系统的书。刷了leetcode前120的题 目。&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>给未来找C++研发实习的学弟学妹的一些建议：&lt;/strong>&lt;/p>
&lt;blockquote>
&lt;ol>
&lt;li>最好在找实习之前，先把C++ primer刷几遍，不懂的可以做个笔记，下次再看。当你看《对象模型》的时候，你会发现Primer中看不懂的知识都能在《对象模型》中迎刃而解。（关于Primer的学习方法我是参照https://www.zhihu.com/question/32087709中的第一个回答来做的）&lt;/li>
&lt;li>《STL源码解析》最好也看看。面试官很喜欢问vector，map等的底层实现或者动态增长，如果不看书，关靠看博客和面经的话可能理解起来比较困难。&lt;/li>
&lt;li>剑指offer是大部分公司一面的算法题，我觉得最好把每个题目都看懂吧。leetcode的话前100题确实很经典，二分查找，hash，动态规划，二叉树等的知识也可以通过刷题来巩固。&lt;/li>
&lt;li>网络的话肯定是TCP/IP详解  卷1，有时间的话可以再看看Unix网络编程。时间更充裕的话最好刷一刷陈硕的《linux多线程服务器编程》，对多线程的知识会有一个了解。&lt;/li>
&lt;li>最后一个建议就是越早准备越好，投递简历、面试期间只适合整理牛客网上的面经以及自己的面经，补缺补漏。毕竟面试题都是随机的，想要在面试中表现好一些，最好还是平时多积累。&lt;/li>
&lt;/ol>
&lt;/blockquote>
&lt;h2 id="cc后台研发-腾讯实习春招一面-凉凉">C/C++后台研发 腾讯实习春招一面 凉凉~
&lt;/h2>&lt;p>&lt;a class="link" href="https://www.nowcoder.com/discuss/68508?type=0&amp;amp;order=0&amp;amp;pos=8&amp;amp;page=0" target="_blank" rel="noopener"
>https://www.nowcoder.com/discuss/68508?type=0&amp;order=0&amp;pos=8&amp;page=0&lt;/a>&lt;/p>
&lt;ul>
&lt;li>vector底层的实现. 这里我说的比较多,所以他问到了insert具体做里那些事情. resize函数调用的是什么样. 扩容的情况.&lt;/li>
&lt;li>进程间通信机制
管道（pipe），有名管道（FIFO）
信号（signal）
消息队列
共享内存
信号量
套接字（socket)&lt;/li>
&lt;/ul>
&lt;h2 id="阿里巴巴菜鸟网络二面">阿里巴巴菜鸟网络二面
&lt;/h2>&lt;p>&lt;a class="link" href="http://blog.csdn.net/caishenfans/article/details/44496581" target="_blank" rel="noopener"
>http://blog.csdn.net/caishenfans/article/details/44496581&lt;/a>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>fill和memset的区别
fill 是stl算法，会调用拷贝构造函数，如果是基础类型应该会直接按位复制。memset是c算法，就是一个字节一个字节拷贝到原始指针指向的位置。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>C++内存管理方法
RAII全称为Resource Acquisition Is Initialization
利用栈展开过程栈上对象的析构函数将被 自动调用的保证，从而正确地释放先前获取的资源。RAII只有在栈展开正常执行的前提下才能正常工作。函数调用和正常的C++异常处理流程(异常处于 try-catch块)都存在栈展开。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>智能指针的实现
当有另外一个智能指针对当前智能指针进行拷贝时，引用计数器加1。
当两个智能指针进行赋值操作时，左边的指针指向的对象引用计数减1，右边的加1。
析构时，同样引用计数减1。
当引用计数为0时，对象被回收。
重新定义了一组operator ：-&amp;gt;指针，返回原始指针的指针。解引用*T，返回原始指针的解引用。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="阿里实习电话面试经验一个多小时">阿里实习电话面试经验，一个多小时
&lt;/h2>&lt;p>&lt;a class="link" href="https://www.nowcoder.com/discuss/3836" target="_blank" rel="noopener"
>https://www.nowcoder.com/discuss/3836&lt;/a>&lt;/p>
&lt;ul>
&lt;li>什么是二叉平衡树，如何插入节点，删除节点，说出关键步骤。&lt;/li>
&lt;/ul>
&lt;h2 id="学生党如何拿到阿里技术offer阿里实习生面试经验成功">学生党如何拿到阿里技术offer：《阿里实习生面试经验（成功）》
&lt;/h2>&lt;p>&lt;a class="link" href="https://yq.aliyun.com/articles/6395" target="_blank" rel="noopener"
>https://yq.aliyun.com/articles/6395&lt;/a>&lt;/p>
&lt;h2 id="阿里c研发实习二面和三面面经已跪">阿里C++研发实习二面和三面面经(已跪)
&lt;/h2>&lt;p>&lt;a class="link" href="https://www.nowcoder.com/discuss/27801?type=2&amp;amp;order=3&amp;amp;pos=55&amp;amp;page=1" target="_blank" rel="noopener"
>https://www.nowcoder.com/discuss/27801?type=2&amp;order=3&amp;pos=55&amp;page=1&lt;/a>&lt;/p>
&lt;h2 id="实习面试总结">实习面试总结
&lt;/h2>&lt;p>&lt;a class="link" href="https://www.nowcoder.com/discuss/25302?type=2&amp;amp;order=3&amp;amp;pos=190&amp;amp;page=1" target="_blank" rel="noopener"
>https://www.nowcoder.com/discuss/25302?type=2&amp;order=3&amp;pos=190&amp;page=1&lt;/a>&lt;/p>
&lt;h2 id="知乎牛客总结">知乎牛客总结
&lt;/h2>&lt;p>&lt;a class="link" href="http://link.zhihu.com/?target=https%3A//www.nowcoder.com/discuss/27801%3Ftype%3D2%26order%3D3%26pos%3D55%26page%3D1" target="_blank" rel="noopener"
>阿里C++研发实习二面和三面面经(已跪)&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="http://link.zhihu.com/?target=https%3A//www.nowcoder.com/discuss/27739%3Ftype%3D2%26order%3D3%26pos%3D57%26page%3D2" target="_blank" rel="noopener"
>【阿里、腾讯、头条】等12家企业【C++方向】30场面试经验&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="http://link.zhihu.com/?target=https%3A//www.nowcoder.com/discuss/27616%3Ftype%3D2%26order%3D3%26pos%3D61%26page%3D1" target="_blank" rel="noopener"
>阿里校招实习一面面经&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="http://link.zhihu.com/?target=https%3A//www.nowcoder.com/discuss/27349%3Ftype%3D2%26order%3D3%26pos%3D71%26page%3D1" target="_blank" rel="noopener"
>阿里巴巴秋招再见&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="http://link.zhihu.com/?target=https%3A//www.nowcoder.com/discuss/26107%3Ftype%3D2%26order%3D3%26pos%3D125%26page%3D1" target="_blank" rel="noopener"
>百度三面（offer）&amp;amp;&amp;amp;阿里四面（进行中）&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="http://link.zhihu.com/?target=https%3A//www.nowcoder.com/discuss/25923%3Ftype%3D2%26order%3D3%26pos%3D143%26page%3D1" target="_blank" rel="noopener"
>双非大三春招实习面经&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="http://link.zhihu.com/?target=https%3A//www.nowcoder.com/discuss/25302%3Ftype%3D2%26order%3D3%26pos%3D190%26page%3D1" target="_blank" rel="noopener"
>实习面试总结&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="http://link.zhihu.com/?target=https%3A//www.nowcoder.com/discuss/24900%3Ftype%3D2%26order%3D3%26pos%3D230%26page%3D1" target="_blank" rel="noopener"
>阿里，摩根，野村，腾讯，微软，图鸭科技面经分享&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="http://link.zhihu.com/?target=http%3A//www.nowcoder.com/discuss/8343%3Ftype%3D0%26order%3D3%26pos%3D3950%26page%3D1%26from%3Dmnks201" target="_blank" rel="noopener"
>阿里四面面经 + 已成功拿到offer&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="http://link.zhihu.com/?target=https%3A//www.nowcoder.com/discuss/18460%3Ftype%3D2%26order%3D3%26pos%3D57%26page%3D2%26from%3Dmnks201" target="_blank" rel="noopener"
>我的2016笔试面试经验(送给下一届的学弟学妹们)&lt;/a>&lt;/p>
&lt;h2 id="阿里巴巴2017实习面试分享技术三面hr面">阿里巴巴2017实习面试分享（技术三面+HR面）
&lt;/h2>&lt;p>&lt;a class="link" href="https://www.nowcoder.com/discuss/25446" target="_blank" rel="noopener"
>https://www.nowcoder.com/discuss/25446&lt;/a>&lt;/p></description></item><item><title>c++ new 与 delete</title><link>https://nansenli.com/zh-cn/post/jianshu/c++/c++-new-%E4%B8%8E-delete/</link><pubDate>Wed, 14 Mar 2018 03:52:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/c++/c++-new-%E4%B8%8E-delete/</guid><description>&lt;p>来源 ：&lt;/p>
&lt;p>&lt;a class="link" href="https://kelvinh.github.io/blog/2014/04/19/research-on-operator-new-and-delete/" target="_blank" rel="noopener"
>https://kelvinh.github.io/blog/2014/04/19/research-on-operator-new-and-delete/&lt;/a>&lt;/p>
&lt;h2 id="new-与-delete代码意味着什么">new 与 delete代码意味着什么
&lt;/h2>&lt;pre tabindex="0">&lt;code>Class *pc = new Class;
// ...
delete pc;
&lt;/code>&lt;/pre>&lt;p>上面代码的第一行即为 new operator ，而第三行即为 delete operator ，代码很简单，但对编译器来说，它需要做额外的工作，将上述代码翻译为近似于下面的代码：&lt;/p>
&lt;pre tabindex="0">&lt;code>void *p = operator new(sizeof(Class));
// 对p指向的内存调用Class的构造函数，此处无法用直观的代码展现
Class *pc = static_cast&amp;lt;Class*&amp;gt;(p);
// ...
pc-&amp;gt;~Class();
operator delete(pc);
&lt;/code>&lt;/pre>&lt;h2 id="operator-new-操作符到底做了什么delete-操作符又做了什么">operator new 操作符到底做了什么，delete 操作符又做了什么？
&lt;/h2>&lt;pre tabindex="0">&lt;code>void * operator new(std::size_t size) throw(std::bad_alloc) {
if (size == 0)
size = 1;
void* p;
while ((p = ::malloc(size)) == 0) {
std::new_handler nh = std::get_new_handler();
if (nh)
nh();
else
throw std::bad_alloc();
}
return p;
}
void operator delete(void* ptr) {
if (ptr)
::free(ptr);
}
&lt;/code>&lt;/pre>&lt;p>实际上是malloc的包装，但是至少分配一个字节，并且在内存失败时会尝试获取用户设置的new_handler，如果用户事先设置了new_handler，那么会调用new_handler，否则会执行异常抛出bad_alloc。&lt;/p>
&lt;h2 id="placement-new是什么">placement new是什么
&lt;/h2>&lt;pre tabindex="0">&lt;code>inline _LIBCPP_INLINE_VISIBILITY void* operator new (std::size_t, void* __p) _NOEXCEPT {return __p;}
&lt;/code>&lt;/pre>&lt;p>这个是operator new的一个重载。直接返回指针的值。感觉挺没有用的。那怎么使用呢？&lt;/p>
&lt;pre tabindex="0">&lt;code>void *buf = // 在这里为buf分配内存
Class *pc = new (buf) Class();
&lt;/code>&lt;/pre>&lt;p>上面是将buf的内存分给pc指针。其实就相当于：&lt;/p>
&lt;pre tabindex="0">&lt;code>((* Class)buf)-&amp;gt;Class();
Class *pc = buf;
&lt;/code>&lt;/pre></description></item><item><title>select poll epoll 简明介绍</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/select-poll-epoll-%E7%AE%80%E6%98%8E%E4%BB%8B%E7%BB%8D/</link><pubDate>Mon, 12 Mar 2018 13:19:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/select-poll-epoll-%E7%AE%80%E6%98%8E%E4%BB%8B%E7%BB%8D/</guid><description>&lt;p>来源 &lt;a class="link" href="https://cloud.tencent.com/developer/article/1005481" target="_blank" rel="noopener"
>https://cloud.tencent.com/developer/article/1005481&lt;/a>&lt;/p>
&lt;h1 id="函数简介">函数简介
&lt;/h1>&lt;h2 id="select">select
&lt;/h2>&lt;p>函数原型如下：&lt;/p>
&lt;pre tabindex="0">&lt;code>int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
&lt;/code>&lt;/pre>&lt;p>fd_set是包含有文件描述符的集合，是一个位图，总共有n位。这意味这每次调用就会拷贝一次文件描述符集合到内核。&lt;/p>
&lt;h2 id="poll">poll
&lt;/h2>&lt;p>函数原型如下：&lt;/p>
&lt;pre tabindex="0">&lt;code>int poll (struct pollfd *fds, unsigned int nfds, int timeout);
&lt;/code>&lt;/pre>&lt;p>不同与select使用三个位图来表示三个fdset的方式，poll使用一个 pollfd的数组实现。传入他的长度，以及超时。&lt;/p>
&lt;h2 id="epoll">epoll
&lt;/h2>&lt;p>epoll是在2.6内核中提出的，是之前的select和poll的增强版本。&lt;/p>
&lt;pre tabindex="0">&lt;code>int epoll_create(int size)；//创建一个epoll的句柄，size用来告诉内核这个监听的数目一共有多大
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)；
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
&lt;/code>&lt;/pre>&lt;p>epoll_create是用于创建epoll的句柄。size是建议内核预先分配的文件描述符数目，不会限制实际运行的数目。
epoll_ctl会控制epoll的句柄，我们可以向其中添加、删除、修改文件描述符。ctl与epoll_wait相分离的机制使得只用复制一次事件集合。
epoll_wait可以用于获得从内核得到的事件的集合。&lt;/p>
&lt;h1 id="socket-事件">socket 事件
&lt;/h1>&lt;p>在Linux 2.6内核事件中，设置了wakeup callback机制。当socket在等待事件发生时，由内核的socket睡眠队列管理。当socket事件发生时，内核会顺序遍历socket睡眠队列上的每个process，通知该进程事件发生。通知时，会依次调用该事件的回调函数。&lt;/p>
&lt;h2 id="最初的select">最初的select
&lt;/h2>&lt;p>select最初只是一个朴素的实现，来尝试解决多个文件描述符的检查问题。&lt;/p>
&lt;h3 id="select做了什么">select做了什么？
&lt;/h3>&lt;p>select调用后，做了以下事情：&lt;/p>
&lt;ul>
&lt;li>将参数传进来的文件描述符集合拷贝到内核空间&lt;/li>
&lt;li>依次遍历文件描述符，查看是否有事件可读，如果可读就返回&lt;/li>
&lt;li>没有可读的文件描述符，开始睡眠，等待内核socket事件发生&lt;/li>
&lt;li>被唤醒，再次检查到底是哪个文件描述符发生了操作&lt;/li>
&lt;/ul>
&lt;h3 id="select的问题">select的问题？
&lt;/h3>&lt;ul>
&lt;li>每次调用socket都会拷贝一次到内核空间，性能低下&lt;/li>
&lt;li>任意一个socket被唤醒都需要遍历所有的socket，浪费时间&lt;/li>
&lt;/ul>
&lt;h3 id="select的改进">select的改进？
&lt;/h3>&lt;ul>
&lt;li>被监控的fds集合限制为1024，1024太小了，我们希望能够有个比较大的可监控fds集合&lt;/li>
&lt;li>fds集合需要从用户空间拷贝到内核空间的问题，我们希望不需要拷贝&lt;/li>
&lt;li>被监控的fds中某些有数据可读的时候，我们希望通知更加精细一点，就是我们希望能够从通知中得到有可读事件的fds列表，而不是需要遍历整个fds来收集。&lt;/li>
&lt;/ul>
&lt;h2 id="鸡肋的poll">鸡肋的poll
&lt;/h2>&lt;p>poll只解决了第一个问题。fds的大小是1024限制的问题。仅仅是传入的参数的接口变了而已。&lt;/p>
&lt;h2 id="成熟的epoll">成熟的epoll
&lt;/h2>&lt;p>epoll解决了第二个和第三个问题。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>对于第二个问题&lt;/p>
&lt;ul>
&lt;li>拆分函数调用，再细分函数调用
对于IO多路复用，我们会发现，每次调用select或poll都在重复地准备(集中处理)整个需要监控的fds集合。然而对于频繁调用的select或poll而言，fds集合的变化频率要低得多，我们没必要每次都重新准备(集中处理)整个fds集合。
于是，epoll引入了epoll_ctl系统调用，将高频调用的epoll_wait和低频的epoll_ctl隔离开。同时，epoll_ctl通过(EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL)三个操作来分散对需要监控的fds集合的修改，做到了有变化才变更，将select或poll高频、大块内存拷贝(集中处理)变成epoll_ctl的低频、小块内存的拷贝(分散处理)，避免了大量的内存拷贝。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>使用红黑树
另外，epoll通过epoll_ctl来对监控的fds集合来进行增、删、改，那么必须涉及到fd的快速查找问题，于是，一个低时间复杂度的增、删、改、查的数据结构来组织被监控的fds集合是必不可少的了。在linux 2.6.8之前的内核，epoll使用hash来组织fds集合，于是在创建epoll fd的时候，epoll需要初始化hash的大小。于是epoll_create(int size)有一个参数size，以便内核根据size的大小来分配hash的大小。在linux 2.6.8以后的内核中，epoll使用红黑树来组织监控的fds集合，于是epoll_create(int size)的参数size实际上已经没有意义了。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>对于第三个问题&lt;/p>
&lt;ul>
&lt;li>使用回调机制
通过上面的socket的睡眠队列唤醒逻辑我们知道，socket唤醒睡眠在其睡眠队列的wait_entry(process)的时候会调用wait_entry的回调函数callback，并且，我们可以在callback中做任何事情。为了做到只遍历就绪的fd，我们需要有个地方来组织那些已经就绪的fd。为此，epoll引入了一个中间层，一个双向链表(ready_list)，一个单独的睡眠队列(single_epoll_wait_list)，并且，与select或poll不同的是，epoll的process不需要同时插入到多路复用的socket集合的所有睡眠队列中，相反process只是插入到中间层的epoll的单独睡眠队列中，process睡眠在epoll的单独队列上，等待事件的发生。同时，引入一个中间的wait_entry_sk，它与某个socket sk密切相关，wait_entry_sk睡眠在sk的睡眠队列上，其callback函数逻辑是将当前sk排入到epoll的ready_list中，并唤醒epoll的single_epoll_wait_list。而single_epoll_wait_list上睡眠的process的回调函数就明朗了：遍历ready_list上的所有sk，挨个调用sk的poll函数收集事件，然后唤醒process从epoll_wait返回。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="最后边缘触发与水平触发">最后，边缘触发与水平触发
&lt;/h2>&lt;p>说到Epoll就不能不说说Epoll事件的两种模式了，下面是两个模式的基本概念&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Edge Triggered (ET) 边沿触发
.socket的接收缓冲区状态变化时触发读事件，即空的接收缓冲区刚接收到数据时触发读事件
.socket的发送缓冲区状态变化时触发写事件，即满的缓冲区刚空出空间时触发读事件
仅在缓冲区状态变化时触发事件，比如数据缓冲去从无到有的时候(不可读-可读)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Level Triggered (LT) 水平触发
.socket接收缓冲区不为空，有数据可读，则读事件一直触发
.socket发送缓冲区不满可以继续写入数据，则写事件一直触发
符合思维习惯，epoll_wait返回的事件就是socket的状态&lt;/p>
&lt;/li>
&lt;/ul></description></item><item><title>菜鸟B2B面试一面经历记录、c++后台开发</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E8%8F%9C%E9%B8%9Fb2b%E9%9D%A2%E8%AF%95%E4%B8%80%E9%9D%A2%E7%BB%8F%E5%8E%86%E8%AE%B0%E5%BD%95c++%E5%90%8E%E5%8F%B0%E5%BC%80%E5%8F%91/</link><pubDate>Fri, 09 Mar 2018 02:30:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E8%8F%9C%E9%B8%9Fb2b%E9%9D%A2%E8%AF%95%E4%B8%80%E9%9D%A2%E7%BB%8F%E5%8E%86%E8%AE%B0%E5%BD%95c++%E5%90%8E%E5%8F%B0%E5%BC%80%E5%8F%91/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>我是研二学生，而且导师不放实习，但是为了增加面试经验所以面了春招实习。
我的简历写的很朴素很简单，所以面试官的题目难度比较低。一面，面试时间是2018年3月8日晚7点。由于前一晚我在做阿里测试，所以面试官说延后一天面试，这也给了我时间来准备面试。特别是又把基础知识复习了一遍，很有帮助。&lt;/p>
&lt;h2 id="自我介绍">自我介绍
&lt;/h2>&lt;ul>
&lt;li>略&lt;/li>
&lt;/ul>
&lt;h2 id="项目的经历和对你的影响">项目的经历和对你的影响
&lt;/h2>&lt;ul>
&lt;li>略&lt;/li>
&lt;/ul>
&lt;h2 id="四层网络结构有哪些">四层网络结构有哪些
&lt;/h2>&lt;ul>
&lt;li>我回答了包含有光纤、交换机、双绞线的物理层，ip的网络层，tcp icmp udp的传输层，http dns的应用层。&lt;/li>
&lt;li>标准答案：实际上TCP/IP标准并不定义与ISO数据链路层和物理层相对应的功能。相反，它定义像地址解析协议（Address Resolution Protocol,ARP）这样的协议，提供TCP/IP协议的数据结构和实际物理硬件之间的接口。所以第一个应该是链路层，包含有ARP协议，以及设备的驱动。&lt;/li>
&lt;/ul>
&lt;h2 id="你对其中的哪一层比较熟悉">你对其中的哪一层比较熟悉？
&lt;/h2>&lt;ul>
&lt;li>我回答了传输层的tcp协议。&lt;/li>
&lt;/ul>
&lt;h2 id="tcp与udp的区别">TCP与udp的区别？
&lt;/h2>&lt;ul>
&lt;li>略&lt;/li>
&lt;/ul>
&lt;h2 id="三次握手与四次挥手">三次握手与四次挥手？
&lt;/h2>&lt;ul>
&lt;li>略&lt;/li>
&lt;/ul>
&lt;h2 id="你为什么对这个过程比较熟悉">你为什么对这个过程比较熟悉？
&lt;/h2>&lt;ul>
&lt;li>我回答了是主要看书看的。当然项目里也用到了，自己用tcpdump和wireshark抓包看过过程。&lt;/li>
&lt;/ul>
&lt;h2 id="io多路复用是怎么样的">I/O多路复用是怎么样的？
&lt;/h2>&lt;ul>
&lt;li>
&lt;p>我回答了因为多线程比较占开销之类的，对方听了一会说，不是，他想问I/O多路复用的实现原理。我回答说忘记了，但是我记得select poll epoll的实现过程中使用了高效的数据结构来检查到底是哪一个文件描述符上出现了io操作。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>标准答案是&lt;/p>
&lt;blockquote>
&lt;p>epoll的高效就在于，当我们调用epoll_ctl往里塞入百万个句柄时，epoll_wait仍然可以飞快的返回，并有效的将发生事件的句柄给我们用户。这是由于我们在调用epoll_create时，内核除了帮我们在epoll文件系统里建了个file结点，在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外，还会再建立一个list链表，用于存储准备就绪的事件，当epoll_wait调用时，仅仅观察这个list链表里有没有数据即可。有数据就返回，没有数据就sleep，等到timeout时间到后即使链表没数据也返回。所以，epoll_wait非常高效。
传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合，不过由于网络延时，任一时间只有部分的socket是“活跃”的，但是select/poll每次调用都会线性扫描全部的集合，导致效率呈现线性下降。但是epoll不存在这个问题，它只会对“活跃”的socket进行操作&amp;mdash;这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么，只有“活跃”的socket才会主动的去调用 callback函数，其他idle状态socket则不会。&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;/ul>
&lt;h2 id="介绍c里的多态">介绍C++里的多态
&lt;/h2>&lt;ul>
&lt;li>略&lt;/li>
&lt;/ul>
&lt;h2 id="什么是析构函数作用呢">什么是析构函数，作用呢
&lt;/h2>&lt;ul>
&lt;li>略&lt;/li>
&lt;/ul>
&lt;h2 id="虚析构函数的作用">虚析构函数的作用？
&lt;/h2>&lt;ul>
&lt;li>略&lt;/li>
&lt;/ul>
&lt;h2 id="父类的析构函数是否要设置成为虚函数">父类的析构函数是否要设置成为虚函数？
&lt;/h2>&lt;ul>
&lt;li>肯定要&lt;/li>
&lt;/ul>
&lt;h2 id="浅拷贝和深拷贝的区别">浅拷贝和深拷贝的区别？
&lt;/h2>&lt;ul>
&lt;li>略&lt;/li>
&lt;/ul>
&lt;h2 id="如果一个函数传入一个对象那么这是浅拷贝还是深拷贝">如果一个函数传入一个对象，那么这是浅拷贝还是深拷贝？
&lt;/h2>&lt;ul>
&lt;li>我回答了这要看拷贝构造函数。但是具体是如何的我不确定。&lt;/li>
&lt;li>标准答案应该是，如果使用的是引用或指针不会发生拷贝，如果传入的类没有实现拷贝构造函数则是浅拷贝，如果实现了拷贝构造函数则看实现的拷贝构造函数是浅拷贝还是深拷贝。&lt;/li>
&lt;/ul>
&lt;h2 id="c语言里的指针是什么">C语言里的指针是什么。
&lt;/h2>&lt;ul>
&lt;li>我回答是首先它是一个变量，里面存的是所指向的内部数据类型或对象的地址。取内容可以获得这个数据或对象。&lt;/li>
&lt;li>实际上指针不一定是变量，也有常量指针，不过一般都说指针是指针变量的简称。所以上述表达没有什么问题。&lt;/li>
&lt;/ul>
&lt;h2 id="形参实参的区别">形参实参的区别。
&lt;/h2>&lt;ul>
&lt;li>说实话我也搞不清楚，我说不知道但是把指针、引用、普通传参都说了一遍。&lt;/li>
&lt;li>后来查到形参指的是外部调用的时候传入的参数，实参是函数在处理内部所使用的参数。&lt;/li>
&lt;/ul>
&lt;h2 id="介绍模拟退火算法">介绍模拟退火算法
&lt;/h2>&lt;ul>
&lt;li>由于简历上写了&lt;/li>
&lt;/ul>
&lt;h2 id="熵是什么增大代表什么">熵是什么，增大代表什么
&lt;/h2>&lt;ul>
&lt;li>略&lt;/li>
&lt;/ul>
&lt;h2 id="部门主要以java开发如果让你做java开发你会抵触吗">部门主要以Java开发，如果让你做Java开发你会抵触吗？
&lt;/h2>&lt;ul>
&lt;li>略&lt;/li>
&lt;/ul>
&lt;h2 id="对于公司有什么想了解的">对于公司有什么想了解的。
&lt;/h2>&lt;ul>
&lt;li>略&lt;/li>
&lt;/ul>
&lt;h2 id="后记">后记
&lt;/h2>&lt;p>总时长27分钟，语速比较快，有点紧张和激动。大概就是这些，有想起来的再补充。&lt;/p></description></item><item><title>HTTP长连接与短连接</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/http%E9%95%BF%E8%BF%9E%E6%8E%A5%E4%B8%8E%E7%9F%AD%E8%BF%9E%E6%8E%A5/</link><pubDate>Wed, 07 Mar 2018 03:15:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/http%E9%95%BF%E8%BF%9E%E6%8E%A5%E4%B8%8E%E7%9F%AD%E8%BF%9E%E6%8E%A5/</guid><description>&lt;h2 id="http协议与tcpip协议的关系">HTTP协议与TCP/IP协议的关系
&lt;/h2>&lt;p>HTTP的长连接和短连接本质上是TCP长连接和短连接。HTTP属于应用层协议，在传输层使用TCP协议，在网络层使用IP协议。IP协议主要解决网络路由和寻址问题，TCP协议主要解决如何在IP层之上可靠的传递数据包，使在网络上的另一端收到发端发出的所有包，并且顺序与发出顺序一致。TCP有可靠，面向连接的特点。&lt;/p>
&lt;h2 id="如何理解http协议是无状态的">如何理解HTTP协议是无状态的
&lt;/h2>&lt;p>HTTP协议是无状态的，指的是协议对于事务处理没有记忆能力，服务器不知道客户端是什么状态。也就是说，打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系。HTTP是一个无状态的面向连接的协议，无状态不代表HTTP不能保持TCP连接，更不能代表HTTP使用的是UDP协议（无连接）。&lt;/p>
&lt;h2 id="什么是长连接短连接">什么是长连接、短连接
&lt;/h2>&lt;p>在HTTP/1.0中，默认使用的是短连接。也就是说，浏览器和服务器每进行一次HTTP操作，就建立一次连接，但任务结束就中断连接。如果客户端浏览器访问的某个HTML或其他类型的 Web页中包含有其他的Web资源，如JavaScript文件、图像文件、CSS文件等；当浏览器每遇到这样一个Web资源，就会建立一个HTTP会话。但从 HTTP/1.1起，默认使用长连接，用以保持连接特性。使用长连接的HTTP协议，会在响应头有加入这行代码：&lt;code>Connection:keep-alive&lt;/code>。在使用长连接的情况下，当一个网页打开完成后，客户端和服务器之间用于传输HTTP数据的 TCP连接不会关闭，如果客户端再次访问这个服务器上的网页，会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接，它有一个保持时间，可以在不同的服务器软件（如Apache）中设定这个时间。实现长连接要客户端和服务端都支持长连接。HTTP协议的长连接和短连接，实质上是TCP协议的长连接和短连接。&lt;/p>
&lt;h2 id="tcp连接">TCP连接
&lt;/h2>&lt;p>当网络通信时采用TCP协议时，在真正的读写操作之前，server与client之间必须建立一个连接，当读写操作完成后，双方不再需要这个连接 时它们可以释放这个连接，连接的建立是需要三次握手的，而释放则需要4次握手，所以说每个连接的建立都是需要资源消耗和时间消耗的。&lt;/p>
&lt;h2 id="tcp短连接">TCP短连接
&lt;/h2>&lt;p>接下来我们再模拟一下长连接的情况，client向server发起连接，server接受client连接，双方建立连接。Client与server完成一次读写之后，它们之间的连接并不会主动关闭，后续的读写操作会继续使用这个连接。&lt;/p>
&lt;h2 id="tcp长连接">TCP长连接
&lt;/h2>&lt;p>接下来我们再模拟一下长连接的情况，client向server发起连接，server接受client连接，双方建立连接。Client与server完成一次读写之后，它们之间的连接并不会主动关闭，后续的读写操作会继续使用这个连接。&lt;/p>
&lt;p>首先说一下TCP/IP详解上讲到的TCP保活功能，保活功能主要为服务器应用提供，服务器应用希望知道客户主机是否崩溃，从而可以代表客户使用资源。如果客户已经消失，使得服务器上保留一个半开放的连接，而服务器又在等待来自客户端的数据，则服务器将应远等待客户端的数据，保活功能就是试图在服务 器端检测到这种半开放的连接。&lt;/p>
&lt;p>如果一个给定的连接在两小时内没有任何的动作，则服务器就向客户发一个探测报文段，客户主机必须处于以下4个状态之一：&lt;/p>
&lt;ol>
&lt;li>客户主机依然正常运行，并从服务器可达。客户的TCP响应正常，而服务器也知道对方是正常的，服务器在两小时后将保活定时器复位。&lt;/li>
&lt;li>客户主机已经崩溃，并且关闭或者正在重新启动。在任何一种情况下，客户的TCP都没有响应。服务端将不能收到对探测的响应，并在75秒后超时。服务器总共发送10个这样的探测 ，每个间隔75秒。如果服务器没有收到一个响应，它就认为客户主机已经关闭并终止连接。&lt;/li>
&lt;li>客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应，这个响应是一个复位，使得服务器终止这个连接。&lt;/li>
&lt;li>客户机正常运行，但是服务器不可达，这种情况与2类似，TCP能发现的就是没有收到探查的响应。&lt;/li>
&lt;/ol>
&lt;h2 id="如何选择长连接还是短连接">如何选择长连接还是短连接
&lt;/h2>&lt;p>长连接多用于操作频繁，点对点的通讯，而且连接数不能太多情况，。每个TCP连接都需要三步握手，这需要时间，如果每个操作都是先连接，再操作的话那么处理速度会降低很多，所以每个操作完后都不断开，次处理时直接发送数据包就OK了，不用建立TCP连接。例如：数据库的连接用长连接， 如果用短连接频繁的通信会造成socket错误，而且频繁的socket 创建也是对资源的浪费。&lt;/p>
&lt;p>而像WEB网站的http服务一般都用短链接，因为长连接对于服务端来说会耗费一定的资源，而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源，如果用长连接，而且同时有成千上万的用户，如果每个用户都占用一个连接的话，那可想而知吧。所以并发量大，但每个用户无需频繁操作情况下需用短连好。&lt;/p></description></item><item><title>MySQL学习笔记</title><link>https://nansenli.com/zh-cn/post/jianshu/%E6%95%B0%E6%8D%AE%E5%BA%93/mysql%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</link><pubDate>Mon, 05 Mar 2018 07:04:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E6%95%B0%E6%8D%AE%E5%BA%93/mysql%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>这里使用docker进行学习。配置了docker-cn的镜像。&lt;/p>
&lt;h2 id="mysql集群建立">Mysql集群建立
&lt;/h2>&lt;p>使用下面的命令拉取mysql镜像&lt;/p>
&lt;pre>&lt;code>docker pull mysql
&lt;/code>&lt;/pre>
&lt;p>使用下面的命令建立一个master和一个slave&lt;/p>
&lt;pre tabindex="0">&lt;code>docker run --name mysql-master -P -d -e MYSQL_ROOT_PASSWORD=ln mysql
docker run --name mysql-slave1 -P -d -e MYSQL_ROOT_PASSWORD=ln mysql
&lt;/code>&lt;/pre>&lt;p>其中-d是配置成为守护进程。-P暴露所有端口。设置root密码为ln。&lt;/p>
&lt;p>使用下面命令分别进入两个docker中&lt;/p>
&lt;pre>&lt;code>docker exec -it mysql-master bash
docker exec -it mysql-slave1 bash
&lt;/code>&lt;/pre>
&lt;p>查看网卡ip。这里首先要安装net-tools，安装前要update，update前要换源&lt;/p>
&lt;pre tabindex="0">&lt;code>sed -i &amp;#39;s/deb.debian.org/mirrors.ustc.edu.cn/g&amp;#39; /etc/apt/sources.list
apt updaet
apt install net-tools
&lt;/code>&lt;/pre>&lt;p>输入ifconfig查看ip。
其中master是172.17.0.2，slave1是172.17.0.3&lt;/p>
&lt;p>设置登录权限
在master中的mysql中按照如下步骤操作，首先登录，然后设置名称为root用户都可以无密码访问。&lt;/p>
&lt;pre tabindex="0">&lt;code>mysql -uroot -p
mysql&amp;gt; grant all privileges on *.* to root@&amp;#39;%&amp;#39; identified by &amp;#39;&amp;#39;;
Query OK, 0 rows affected (0.02 sec)
mysql&amp;gt; flush privileges;
Query OK, 0 rows affected (0.02 sec)
&lt;/code>&lt;/pre>&lt;p>在slave1中使用master的数据库，密码为空&lt;/p>
&lt;pre>&lt;code>mysql -uroot -p -h 172.17.0.2
&lt;/code>&lt;/pre>
&lt;p>在master中创建一个database&lt;/p>
&lt;pre>&lt;code>create database test_docker;
&lt;/code>&lt;/pre>
&lt;p>在slave1中查看该数据库是否存在。&lt;/p>
&lt;pre>&lt;code>show databases;
&lt;/code>&lt;/pre>
&lt;h2 id="琐事">琐事
&lt;/h2>&lt;p>sql对大小写不敏感，SELECT与select是一样的。
sql语句后面最好加上分号。
use用于选择数据库。set用于设置值。
添加用户的方式使用create来添加。
然后授权用户使用数据库与表。&lt;/p>
&lt;pre tabindex="0">&lt;code>mysql -u root -p
Enter password:**
mysql&amp;gt; use mysql;
mysql&amp;gt; create user &amp;#39;linanwx&amp;#39;@&amp;#39;%&amp;#39; identified by &amp;#39;ln&amp;#39;;
mysql&amp;gt; grant all on *.* to &amp;#39;linanwx&amp;#39;@&amp;#39;%&amp;#39;;
&lt;/code>&lt;/pre>&lt;p>SHOW DATABASES用于展示当前数据库中的数据。
SHOW TABLES则用于展示当前的表。&lt;/p></description></item><item><title>牛客数据库笔记</title><link>https://nansenli.com/zh-cn/post/jianshu/%E6%95%B0%E6%8D%AE%E5%BA%93/%E7%89%9B%E5%AE%A2%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AC%94%E8%AE%B0/</link><pubDate>Mon, 05 Mar 2018 07:04:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E6%95%B0%E6%8D%AE%E5%BA%93/%E7%89%9B%E5%AE%A2%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AC%94%E8%AE%B0/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>对SQL不熟悉，但是又不想花时间学习。那就以题代学吧。&lt;/p>
&lt;h2 id="事务">事务
&lt;/h2>&lt;p>事务(Transaction)是并发控制的基本单位。所谓事务,它是一个操作序列，这些操作要么都执行，要么都不执行，它是一个不可分割的工作单位。例如，银行转帐工作：从一个帐号扣款并使另一个帐号增款，这两个操作要么都执行，要么都不执行。所以，应该把他们看成一个事务。事务是数据库维护数据一致性的单位，在每个事务结束时，都能保持数据一致性。&lt;/p>
&lt;p>说白了事务就是原子操作，不可分割。&lt;/p>
&lt;h2 id="视图">视图
&lt;/h2>&lt;p>视图是一个虚拟的表，实际上不存在。只是为了方便给某些人看的时候，从数据库中摘出来的部分表。视图由select查询所定义—— 当创建一个视图时，实际上是在数据库里执行了一个select语句&lt;/p>
&lt;h2 id="instead-of-触发器">instead of 触发器
&lt;/h2>&lt;ul>
&lt;li>触发器定义
触发器实际上是一种特殊的存储的过程。触发器的主要功能是监视用户对数据的修改。如果针对一个数据表建了触发器，则 当数据表被修改（插入、更新或者删除）时，SQL Server便使触发器自动执行，以便对数据的修改采用一些适当的措施，从而保证数据的完整性和安全性。&lt;/li>
&lt;/ul>
&lt;p>INSTEAD OF触发器指定执行触发器而不是执行触发 的SQL 语句，从而替代触发语句的操作。&lt;/p>
&lt;h2 id="存储过程">存储过程
&lt;/h2>&lt;p>存储过程是数据库中，一组为了完成特定功能的SQL 语句集，存储在数据库中，经过第一次编译后再次调用不需要再次编译，用户通过指定存储过程的名字并给出参数（如果该存储过程带有参数）来执行它。存储过程是数据库中的一个重要对象。&lt;/p>
&lt;h2 id="sql-关系代数">sql 关系代数
&lt;/h2>&lt;p>&lt;strong>五个基本操作：&lt;/strong>&lt;/p>
&lt;p>    并(∪)、差(-)、笛卡尔积(×)、投影(σ)、选择(π)&lt;/p>
&lt;p>&lt;strong>四个组合操作：&lt;/strong>&lt;/p>
&lt;pre>&lt;code>交(∩)、联接(等值联接)、自然联接、除法(÷)
&lt;/code>&lt;/pre>
&lt;p>并是在两个表中都有的记录。差是在第一个表中有在第二个表中没有。笛卡尔积是两个表记录一对一的所有组合。投影是纵向选择，选择是横向选择。交和并类型，连接是通过共同属性联接。除法是一种复杂的运算。&lt;/p>
&lt;h2 id="关系模式">关系模式
&lt;/h2>&lt;p>关系模式是指关系的描述，现实世界随着时间在不断地变化，因而在不同的时刻，关系模式的关系也会有所变化。但是，现实世界的许多己有事实限定了关系模式所有可能的关系必须满足一定的完整性约束条件。这些约束或者通过对属性&lt;a class="link" href="https://baike.baidu.com/item/%E5%8F%96%E5%80%BC%E8%8C%83%E5%9B%B4" target="_blank" rel="noopener"
>取值范围&lt;/a>的限定，例如职工年龄小于65岁（65岁以后必须退休），或者通过&lt;a class="link" href="https://baike.baidu.com/item/%E5%B1%9E%E6%80%A7%E5%80%BC" target="_blank" rel="noopener"
>属性值&lt;/a>间的相互关连（主要体现于值的相等与否）反映出来。关系模式应当刻画出这些完整性约束条件。&lt;/p>
&lt;h2 id="非关系数据库">非关系数据库
&lt;/h2>&lt;p>NoSQL(NoSQL = Not Only SQL )，意即&amp;quot;不仅仅是SQL&amp;quot;。用于超大规模数据的存储。&lt;/p></description></item><item><title>C智能指针学习</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/c++-%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88%E5%AD%A6%E4%B9%A0/</link><pubDate>Sat, 03 Feb 2018 09:30:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/c++-%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88%E5%AD%A6%E4%B9%A0/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>智能指针似乎越来越广泛的使用了。有必要好好记住。&lt;/p>
&lt;h2 id="shared_ptr">shared_ptr
&lt;/h2>&lt;p>shared_ptr允许多个指针指向同一个对象，unique_ptr则独占所指的对象。&lt;/p>
&lt;p>例子&lt;/p>
&lt;pre tabindex="0">&lt;code>#include &amp;lt;memory&amp;gt;
#include &amp;lt;thread&amp;gt;
class A
{
char * str;
public:
A(){
str = new char[20];
printf(&amp;#34;构造\n&amp;#34;);
}
~A()
{
printf(&amp;#34;析构\n&amp;#34;);
delete [] str;
}
};
std::shared_ptr&amp;lt;A&amp;gt; xixi;
void test()
{
std::shared_ptr&amp;lt;A&amp;gt; shared_a = std::make_shared&amp;lt;A&amp;gt;();
xixi = shared_a;
printf(&amp;#34;%d\n&amp;#34;, xixi.use_count());
}
int main()
{
test();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
&lt;/code>&lt;/pre>&lt;p>上面这个例子中，xixi是全局智能指针，会在程序结束时才会释放自己所指向的对象，如果去掉了xixi=shared_a，则在main函数延迟之前就会看到析构。&lt;/p>
&lt;h2 id="unique_ptr">unique_ptr
&lt;/h2>&lt;p>unique_ptr独占对象，只能在不同的unique_ptr中移动（std::move）。没有make_unique语句。只能在构造函数中传入对象的指针来构造unique_ptr。&lt;/p>
&lt;p>unique_ptr使用场景
1、为动态申请的资源提供异常安全保证
我们先来看看下面这一段代码：&lt;/p>
&lt;pre tabindex="0">&lt;code>void Func()
{
int *p = new int(5);
// ...（可能会抛出异常）
delete p;
}
&lt;/code>&lt;/pre>&lt;p>这是我们传统的写法：当我们动态申请内存后，有可能我们接下来的代码由于抛出异常或者提前退出（if语句）而没有执行delete操作。&lt;/p>
&lt;p>解决的方法是使用unique_ptr来管理动态内存，只要unique_ptr指针创建成功，其析构函数都会被调用。确保动态资源被释放。&lt;/p>
&lt;pre tabindex="0">&lt;code>void Func()
{
unique_ptr&amp;lt;int&amp;gt; p(new int(5));
// ...（可能会抛出异常）
}
&lt;/code>&lt;/pre>&lt;h2 id="week_ptr">week_ptr
&lt;/h2>&lt;p>week_ptr也是一种强大的指针，名字起的比较弱。week_ptr不控制对象的生命周期，用于在不持有对象时使用。week_ptr可以在对象存在时提升为shared_ptr。&lt;/p></description></item><item><title>libcurl交叉编译arm版本动态库</title><link>https://nansenli.com/zh-cn/post/jianshu/%E5%B5%8C%E5%85%A5%E5%BC%8F/libcurl%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91arm%E7%89%88%E6%9C%AC%E5%8A%A8%E6%80%81%E5%BA%93/</link><pubDate>Tue, 30 Jan 2018 10:53:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E5%B5%8C%E5%85%A5%E5%BC%8F/libcurl%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91arm%E7%89%88%E6%9C%AC%E5%8A%A8%E6%80%81%E5%BA%93/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>最近由于项目需要要进行交叉编译libcurl以完成https通信。这里记录一下过程。&lt;/p>
&lt;p>首先下载最新版本的源码文件，并解压，如下：&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/e2f1c84ddaa0.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>我用的deepin自带交叉编译工具链，&lt;code>sudo apt install gcc-5-arm-linux-gnueabihf&lt;/code>安装就可以了。&lt;/p>
&lt;p>arm-linux内容&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/69a2e0029a46.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>在openssl目录下运行以下命令：&lt;/p>
&lt;pre tabindex="0">&lt;code>source ../arm-linux
./Configure --prefix=$(pwd)/../output shared no-asm linux-armv4
make -j4
make install
&lt;/code>&lt;/pre>&lt;p>首先导入环境变量。然后进行编译，输出路径在output下面。&lt;/p>
&lt;p>然后在libcurl 下运行以下命令&lt;/p>
&lt;pre tabindex="0">&lt;code>source ../arm-linux
./configure --with-ssl=$(pwd)/../output --prefix=$PWD/../output --build=x86_64-pc-linux-gnu --host=arm-linux-gnueabihf CC=arm-linux-gnueabihf-gcc-5 --without-zlib
&lt;/code>&lt;/pre>&lt;p>curl配置结果一览&lt;/p>
&lt;pre tabindex="0">&lt;code> curl version: 7.58.0
Host setup: x86_64-pc-linux-gnu
Install prefix: /media/linan/72605383-b1ba-4c79-90fd-443514396fbc/curl_workdir/curl-7.58.0/output
Compiler: arm-linux-gnueabihf-gcc-5
SSL support: enabled (OpenSSL)
SSH support: no (--with-libssh2)
zlib support: no (--with-zlib)
brotli support: no (--with-brotli)
GSS-API support: no (--with-gssapi)
TLS-SRP support: enabled
resolver: POSIX threaded
IPv6 support: enabled
Unix sockets support: enabled
IDN support: no (--with-{libidn2,winidn})
Build libcurl: Shared=yes, Static=yes
Built-in manual: enabled
--libcurl option: enabled (--disable-libcurl-option)
Verbose errors: enabled (--disable-verbose)
SSPI support: no (--enable-sspi)
ca cert bundle: no
ca cert path: no
ca fallback: no
LDAP support: no (--enable-ldap / --with-ldap-lib / --with-lber-lib)
LDAPS support: no (--enable-ldaps)
RTSP support: enabled
RTMP support: no (--with-librtmp)
metalink support: no (--with-libmetalink)
PSL support: no (libpsl not found)
HTTP2 support: disabled (--with-nghttp2)
Protocols: DICT FILE FTP FTPS GOPHER HTTP HTTPS IMAP IMAPS POP3 POP3S RTSP SMB SMBS SMTP SMTPS TELNET TFTP
&lt;/code>&lt;/pre>&lt;p>然后在运行make 以及make install，在curl目录下就会出现output目录。&lt;/p>
&lt;p>最终，我们需要的是openssl以及curl的动态库，分别是：libssl.so libcrypto.so libcurl.so&lt;/p></description></item><item><title>算法题目——射击游戏</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE%E5%B0%84%E5%87%BB%E6%B8%B8%E6%88%8F/</link><pubDate>Tue, 23 Jan 2018 01:39:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE%E5%B0%84%E5%87%BB%E6%B8%B8%E6%88%8F/</guid><description>&lt;p>链接：&lt;a class="link" href="https://www.nowcoder.com/questionTerminal/d3f26db0325444078717cc802e0056d8" target="_blank" rel="noopener"
>https://www.nowcoder.com/questionTerminal/d3f26db0325444078717cc802e0056d8&lt;/a>
来源：牛客网&lt;/p>
&lt;p>小易正在玩一款新出的射击游戏,这个射击游戏在一个二维平面进行,小易在坐标原点(0,0),平面上有n只怪物,每个怪物有所在的坐标(x[i], y[i])。小易进行一次射击会把x轴和y轴上(包含坐标原点)的怪物一次性消灭。
小易是这个游戏的VIP玩家,他拥有两项特权操作:
1、让平面内的所有怪物同时向任意同一方向移动任意同一距离
2、让平面内的所有怪物同时对于小易(0,0)旋转任意同一角度
小易要进行一次射击。小易在进行射击前,可以使用这两项特权操作任意次。&lt;/p>
&lt;p>小易想知道在他射击的时候最多可以同时消灭多少只怪物,请你帮帮小易。&lt;/p>
&lt;p>如样例所示:&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/ee548e34f160.webp"
loading="lazy"
alt="image"
>&lt;/p>
&lt;p>所有点对于坐标原点(0,0)顺时针或者逆时针旋转45°,可以让所有点都在坐标轴上,所以5个怪物都可以消灭。&lt;/p>
&lt;h5 id="输入描述">&lt;strong>输入描述:&lt;/strong>
&lt;/h5>&lt;p>输入包括三行。
第一行中有一个正整数n(1 ≤ n ≤ 50),表示平面内的怪物数量。
第二行包括n个整数x[i](-1,000,000 ≤ x[i] ≤ 1,000,000),表示每只怪物所在坐标的横坐标,以空格分割。
第二行包括n个整数y[i](-1,000,000 ≤ y[i] ≤ 1,000,000),表示每只怪物所在坐标的纵坐标,以空格分割。&lt;/p>
&lt;h5 id="输出描述">&lt;strong>输出描述:&lt;/strong>
&lt;/h5>&lt;p>输出一个整数表示小易最多能消灭多少只怪物。&lt;/p>
&lt;p>示例1&lt;/p>
&lt;h2 id="输入">输入
&lt;/h2>&lt;p>5
0 -1 1 1 -1
0 -1 -1 1 1&lt;/p>
&lt;h2 id="输出">输出
&lt;/h2>&lt;p>5&lt;/p>
&lt;h2 id="分析">分析
&lt;/h2>&lt;p>题目等价于，找一个十字架能够尽可能多的覆盖所有节点。
考虑到一根线至少能够覆盖到两个点，再加一根垂直于这条线至少能够覆盖3个点，在此基础上进行遍历。对任意三个点，我们选择其中两个点做一条直线(三种情况)，对于第三个点，我们做一条垂线到这条直线上。这样的十字架已经经过了三个点。对于剩下的点，我们判断是否在这个十字架上。要判断是否在十字架上，首先判断是否和第一条直线在同一条直线上。否则，判断这个点和第三个点所构成的直线是否和第二条直线垂直。
当点数小于等于3个点，则可以把所有点都覆盖到。&lt;/p>
&lt;pre tabindex="0">&lt;code>#include &amp;lt;vector&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;algorithm&amp;gt;
using namespace std;
struct point{
int x = 0;
int y = 0;
};
bool is_sameline(point p1, point p2, point p3){
return ((p1.x - p2.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p2.y)) == 0;
}
bool is_vertical(point p1, point p2){
return (p1.x * p2.x + p1.y * p2.y) == 0;
}
bool is_vertical(point p1, point p2, point p3, point p4){
point v1, v2;
v1.x = p1.x - p2.x;
v1.y = p1.y - p2.y;
v2.x = p3.x - p4.x;
v2.y = p3.y - p4.y;
return is_vertical(v1, v2);
}
int main()
{
int n, ret = 0;
cin &amp;gt;&amp;gt; n;
point inputs[n];
for (int i = 0; i &amp;lt; n; i++)
cin &amp;gt;&amp;gt; inputs[i].x;
for (int i = 0; i &amp;lt; n; i++)
cin &amp;gt;&amp;gt; inputs[i].y;
if (n &amp;lt; 4)
{
cout &amp;lt;&amp;lt; n &amp;lt;&amp;lt; endl;
return 0;
};
vector&amp;lt;int&amp;gt; select = {1, 1, 1};
for (int i = 0; i &amp;lt; n - 3; i++)
select.push_back(0);
do
{
vector&amp;lt;point&amp;gt; shizi;
for (int i = 0; i &amp;lt; n; i++)
{
if (select[i])
{
shizi.push_back(inputs[i]);
}
}
vector&amp;lt;vector&amp;lt;point&amp;gt;&amp;gt; status;
status.push_back({shizi[0], shizi[1], shizi[2]});
status.push_back({shizi[0], shizi[2], shizi[1]});
status.push_back({shizi[1], shizi[2], shizi[0]});
for (auto points : status)
{
int count = 0;
for (int i = 0; i &amp;lt; n; i++)
{
if (!select[i])
{
if (is_sameline(points[0], points[1], inputs[i]))
count++;
if (is_vertical(points[0], points[1], points[2], inputs[i]))
count++;
}
}
ret = max(ret, count);
}
} while (prev_permutation(select.begin(), select.end()));
cout &amp;lt;&amp;lt; ret + 3 &amp;lt;&amp;lt; endl;
return 0;
}
&lt;/code>&lt;/pre></description></item><item><title>算法题目——最长公共子括号序列</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E6%8B%AC%E5%8F%B7%E5%BA%8F%E5%88%97/</link><pubDate>Sun, 21 Jan 2018 14:05:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E6%8B%AC%E5%8F%B7%E5%BA%8F%E5%88%97/</guid><description>&lt;p>链接：&lt;a class="link" href="https://www.nowcoder.com/questionTerminal/504ad6420b314e5bb614e1684ad46d4d" target="_blank" rel="noopener"
>https://www.nowcoder.com/questionTerminal/504ad6420b314e5bb614e1684ad46d4d&lt;/a>
来源：牛客网&lt;/p>
&lt;p>一个合法的括号匹配序列被定义为:
1. 空串&amp;quot;&amp;ldquo;是合法的括号序列
2. 如果&amp;quot;X&amp;quot;和&amp;quot;Y&amp;quot;是合法的序列,那么&amp;quot;XY&amp;quot;也是一个合法的括号序列
3. 如果&amp;quot;X&amp;quot;是一个合法的序列,那么&amp;rdquo;(X)&amp;ldquo;也是一个合法的括号序列
4. 每个合法的括号序列都可以由上面的规则生成
例如&amp;rdquo;&amp;quot;, &amp;ldquo;()&amp;rdquo;, &amp;ldquo;()()()&amp;rdquo;, &amp;ldquo;(()())&amp;rdquo;, &amp;ldquo;(((()))&amp;ldquo;都是合法的。
从一个字符串S中移除零个或者多个字符得到的序列称为S的子序列。
例如&amp;quot;abcde&amp;quot;的子序列有&amp;quot;abe&amp;rdquo;,&amp;rdquo;&amp;quot;,&amp;ldquo;abcde&amp;quot;等。
定义LCS(S,T)为字符串S和字符串T最长公共子序列的长度,即一个最长的序列W既是S的子序列也是T的子序列的长度。
小易给出一个合法的括号匹配序列s,小易希望你能找出具有以下特征的括号序列t:
1、t跟s不同,但是长度相同
2、t也是一个合法的括号匹配序列
3、LCS(s, t)是满足上述两个条件的t中最大的
因为这样的t可能存在多个,小易需要你计算出满足条件的t有多少个。&lt;/p>
&lt;p>如样例所示: s = &amp;ldquo;(())()&amp;quot;,跟字符串s长度相同的合法括号匹配序列有:
&amp;ldquo;()(())&amp;rdquo;, &amp;ldquo;((()))&amp;rdquo;, &amp;ldquo;()()()&amp;rdquo;, &amp;ldquo;(()())&amp;quot;,其中LCS( &amp;ldquo;(())()&amp;rdquo;, &amp;ldquo;()(())&amp;rdquo; )为4,其他三个都为5,所以输出3.&lt;/p>
&lt;h5 id="输入描述">&lt;strong>输入描述:&lt;/strong>
&lt;/h5>&lt;!-- raw HTML omitted -->
&lt;h5 id="输出描述">&lt;strong>输出描述:&lt;/strong>
&lt;/h5>&lt;p>输出一个正整数,满足条件的t的个数。&lt;/p>
&lt;p>示例1&lt;/p>
&lt;h2 id="输入">输入
&lt;/h2>&lt;p>(())()&lt;/p>
&lt;h2 id="输出">输出
&lt;/h2>&lt;p>3&lt;/p>
&lt;h2 id="分析">分析
&lt;/h2>&lt;p>题目要求对所有的相同长度的括号序列进行遍历，并计算每一个括号序列与题目中给定的字符串的最长公共子序列的长度。然后，计算长度最大的值出现了多少次。&lt;/p>
&lt;p>如果真的按照题目的描述去做，显然是不可能做出来的。光是生成长达50个字符串的括号序列可能花的时间就超了。&lt;/p>
&lt;p>既然要搜索最长的出现了多少次，不如假设出现了最长的子串是原串长度-1，然后求所有由这个子串所构成的括号序列。如果不存在，则求子串长是原串长度-2的串所能构成的括号序列。依次递减这个子串的长度，直到能求出来为止。&lt;/p>
&lt;p>其实，一定存在子串长度为原串长度-1的串通过添加新的括号所构成的另一个和原串不同的括号序列。这是因为，任何一个括号序列都可以只移动一个括号构成另外一个和原来不同的括号序列(思考为什么)。如果可以这样，那么新串和原串的最长公共括号子序列就是n-1个。因为你只移动了一个括号并插入到其他地方。那么除了这个括号外，其他的顺序都没有变。那么，每次将其中一个括号拿掉，然后插入到新的地方。我们统计一下总共能构成多少个合法的括号序列，将这个数字减去原来的那个串，也就是减去1，就是题目的答案。&lt;/p>
&lt;pre tabindex="0">&lt;code>#include &amp;lt;vector&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;set&amp;gt;
using namespace std;
bool isKuohao(const string &amp;amp; str)
{
int count=0;
for(auto c:str)
{
if(c == &amp;#39;(&amp;#39;)
count ++;
if(c == &amp;#39;)&amp;#39;)
count --;
if(count&amp;lt;0)
return false;
}
return count == 0;
}
int main()
{
int ret=0;
string input;
cin &amp;gt;&amp;gt; input;
set&amp;lt;string&amp;gt; haveChose;
set&amp;lt;string&amp;gt; haveBuild;
/* 选出其中任意一个括号 */
for (int i = 0; i &amp;lt; input.size(); i++)
{
char q[] = {input[i], &amp;#39;\0&amp;#39;};
/* 删除该括号 */
string delete_char(input);
delete_char.erase(delete_char.begin() + i);
if (haveChose.count(delete_char))
continue;
else
haveChose.insert(delete_char);
/* 将原来的括号插入新的数组中 */
for (int j = 0; j &amp;lt; delete_char.size() + 1; j++)
{
string add_delete_char(delete_char);
add_delete_char.insert(j, q);
if (haveBuild.count(add_delete_char))
continue;
else
haveBuild.insert(add_delete_char);
if(isKuohao(add_delete_char)){
ret ++;
}
}
}
cout &amp;lt;&amp;lt; ret-1 &amp;lt;&amp;lt; endl;
}
&lt;/code>&lt;/pre></description></item><item><title>算法题目——游历魔法王国</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE%E6%B8%B8%E5%8E%86%E9%AD%94%E6%B3%95%E7%8E%8B%E5%9B%BD/</link><pubDate>Fri, 19 Jan 2018 10:05:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E7%AE%97%E6%B3%95%E9%A2%98%E7%9B%AE%E6%B8%B8%E5%8E%86%E9%AD%94%E6%B3%95%E7%8E%8B%E5%9B%BD/</guid><description>&lt;p>链接：&lt;a class="link" href="https://www.nowcoder.com/questionTerminal/f58859adc39f4edc9cd8e40ba4160339" target="_blank" rel="noopener"
>https://www.nowcoder.com/questionTerminal/f58859adc39f4edc9cd8e40ba4160339&lt;/a>
来源：牛客网&lt;/p>
&lt;p>魔法王国一共有n个城市,编号为0~n-1号,n个城市之间的道路连接起来恰好构成一棵树。
小易现在在0号城市,每次行动小易会从当前所在的城市走到与其相邻的一个城市,小易最多能行动L次。
如果小易到达过某个城市就视为小易游历过这个城市了,小易现在要制定好的旅游计划使他能游历最多的城市,请你帮他计算一下他最多能游历过多少个城市(注意0号城市已经游历了,游历过的城市不重复计算)。&lt;/p>
&lt;h5 id="输入描述">&lt;strong>输入描述:&lt;/strong>
&lt;/h5>&lt;p>输入包括两行,第一行包括两个正整数&lt;code>n(2 ≤ n ≤ 50)&lt;/code>和&lt;code>L(1 ≤ L ≤ 100)&lt;/code>,表示城市个数和小易能行动的次数。
第二行包括&lt;code>n-1&lt;/code>个整数&lt;code>parent[i](0 ≤ parent[i] ≤ i)&lt;/code>, 对于每个合法的&lt;code>i(0 ≤ i ≤ n - 2)&lt;/code>,在&lt;code>(i+1)&lt;/code>号城市和&lt;code>parent[i]&lt;/code>间有一条道路连接。&lt;/p>
&lt;h5 id="输出描述">&lt;strong>输出描述:&lt;/strong>
&lt;/h5>&lt;p>输出一个整数,表示小易最多能游历的城市数量。&lt;/p>
&lt;h2 id="分析">分析
&lt;/h2>&lt;p>题目经过抽象之后，意思是在一个树中进行遍历，经过指定步数，可以获取最长经过节点树量的路径。如果把这个树按照根节点进行悬挂，可能更好理解一些。虽然有些答案是从低向上生长，但是我还是重建了树，采用悬挂树来做的。&lt;/p>
&lt;p>从这个根节点开始遍历，先判断左树深度大还是右树深度大，先遍历树深度大的那个节点。直到步数用完为止。&lt;/p>
&lt;p>树的深度通过后序遍历很容易求出来，结果发现这样的答案只能通过60%。&lt;/p>
&lt;pre tabindex="0">&lt;code>45 73
0 0 0 1 0 0 3 5 6 8 7 9 1 10 1 2 15 6 8 11 14 17 8 14 3 21 23 3 21 15 12 5 21 31 11 13 7 17 20 26 28 16 36 26
&lt;/code>&lt;/pre>&lt;p>错在这个用例上了。这个正确答案是41，通过简单的贪心算法只能得到39个城市。&lt;/p>
&lt;p>后来看了解析也是看不太懂。总之之后看到正确答案中是求出来深度后直接获得最终答案。&lt;/p>
&lt;p>假设我们已经求出了每一个节点的最大深度，用deep[i]来表示，树的最下面一层的深度是1。&lt;/p>
&lt;p>显然，根节点到任意一个节点的最长路径=deep[0]-1。&lt;/p>
&lt;p>以这条路径为基础，我们可以额外访问一些节点。但是每次访问完这些节点的时候，我们必须回来这个路径。这一来一回，每次访问一个节点都必须额外走两步，访问两个节点就必须走4步。&lt;/p>
&lt;p>看图就容易明白一些：&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/95a9d67e20f8.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>参考代码&lt;/p>
&lt;pre tabindex="0">&lt;code>
#include &amp;lt;vector&amp;gt;
#include &amp;lt;iostream&amp;gt;
using namespace std;
vector&amp;lt;vector&amp;lt;int&amp;gt; &amp;gt; tree;
vector&amp;lt;int&amp;gt; deep;
void calc_deep(int i)
{
int max_deep = 0;
for(auto j:tree[i])
{
calc_deep(j);
max_deep = max(deep[j], max_deep);
}
deep[i] = max_deep + 1;
}
int main()
{
int n, L;
cin &amp;gt;&amp;gt; n &amp;gt;&amp;gt; L;
/* 建立树 */
tree.resize(n);
deep.resize(n);
for(int i=0;i&amp;lt;n-1;i++)
{
int num;
cin &amp;gt;&amp;gt; num;
tree[num].push_back(i+1);
}
/* 计算深度 */
calc_deep(0);
// int validpath = min(deep[0] -1,L);
// cout &amp;lt;&amp;lt; min(n, 1 + validpath + (L - validpath)/2) &amp;lt;&amp;lt; endl;
int long_path = deep[0] - 1;
if(long_path &amp;gt; L) cout &amp;lt;&amp;lt; L + 1;
else cout &amp;lt;&amp;lt; 1 + long_path + (L - long_path)/2;
}
&lt;/code>&lt;/pre></description></item><item><title>bash 语言 学习笔记</title><link>https://nansenli.com/zh-cn/post/jianshu/linux%E7%BC%96%E7%A8%8B/bash-%E8%AF%AD%E8%A8%80-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</link><pubDate>Thu, 18 Jan 2018 09:23:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/linux%E7%BC%96%E7%A8%8B/bash-%E8%AF%AD%E8%A8%80-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>大部分人都用过终端命令行。有的时候需要繁琐的输入同样的命令，我们可以编写自己的脚本来完成这个工作。&lt;/p>
&lt;h2 id="指定解释器">指定解释器
&lt;/h2>&lt;p>&lt;code>#!/bin/bash&lt;/code>
这句是指定bash作为我们脚本的解释器。如果我们写的语法是Python，那么应该换成Python。&lt;/p>
&lt;h2 id="定义变量">定义变量
&lt;/h2>&lt;p>变量名=变量值
例如 name=&amp;ldquo;hahaha&amp;rdquo;
对于普通的字符，加不加双引号，单引号都一样。name=hahaha也行。&lt;/p>
&lt;h2 id="使用变量">使用变量
&lt;/h2>&lt;p>在变量名前加上$表示使用变量。也就是说求这个符号的的值。&lt;/p>
&lt;h2 id="字符串">字符串
&lt;/h2>&lt;p>引号有如下特点：&lt;/p>
&lt;ul>
&lt;li>空格作为词的分割符&lt;/li>
&lt;li>单引号中的词&lt;/li>
&lt;li>字符模式匹配&lt;/li>
&lt;li>路径名扩展&lt;/li>
&lt;li>进程替换 (重定向）&lt;/li>
&lt;/ul>
&lt;p>其中单引号不会对内容进行二次处理，而双引号会对内容做处理，例如双引号会对其中的内容进行求值。你可以看看echo &amp;ldquo;$name&amp;rdquo; 和 echo &amp;lsquo;$name&amp;rsquo;就明白了。&lt;/p>
&lt;p>拼接字符串就是把两个字符串放在一起。
${#string}用于获取字符串长度，${string:1:4}用于切片&lt;/p>
&lt;h2 id="获取输入内容">获取输入内容
&lt;/h2>&lt;p>read inputstr
read -p &amp;ldquo;please input : &amp;quot; inputstr
上面两句是从标准输入中获取输入内容。&lt;/p>
&lt;h3 id="获取脚本执行结果">获取脚本执行结果
&lt;/h3>&lt;p>例如获取Python脚本的执行输出
into=$(&amp;lsquo;python3&amp;rsquo; &amp;lsquo;pyscript.py&amp;rsquo; &amp;lsquo;arg1&amp;rsquo; &amp;lsquo;arg2&amp;rsquo;)
arg1 和arg2 是参数&lt;/p></description></item><item><title>动态规划算法学习与思考</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%AE%97%E6%B3%95%E5%AD%A6%E4%B9%A0%E4%B8%8E%E6%80%9D%E8%80%83/</link><pubDate>Thu, 18 Jan 2018 08:52:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%AE%97%E6%B3%95%E5%AD%A6%E4%B9%A0%E4%B8%8E%E6%80%9D%E8%80%83/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>动态规划是笔试中经常出现的一类题目。掌握他很关键。&lt;/p>
&lt;h2 id="网易题目">网易题目
&lt;/h2>&lt;p>小Q和牛博士合唱一首歌曲,这首歌曲由n个音调组成,每个音调由一个正整数表示。
对于每个音调要么由小Q演唱要么由牛博士演唱,对于一系列音调演唱的难度等于所有相邻音调变化幅度之和, 例如一个音调序列是8, 8, 13, 12, 那么它的难度等于|8 - 8| + |13 - 8| + |12 - 13| = 6(其中||表示绝对值)。
现在要对把这n个音调分配给小Q或牛博士,让他们演唱的难度之和最小,请你算算最小的难度和是多少。
如样例所示: 小Q选择演唱{5, 6}难度为1, 牛博士选择演唱{1, 2, 1}难度为2,难度之和为3,这一个是最小难度和的方案了。&lt;/p>
&lt;h2 id="贪心解法错误">贪心解法（错误）
&lt;/h2>&lt;p>将所有数字进行排序，取最大的两个数字差作为间隔，取其中一下半作为小Q的音符，取另一上半作为牛博士的音符，然后计算结果。最终能过通过60%的用例。
如果时间不够，或者不会的情况可以使用这种解法。&lt;/p>
&lt;pre tabindex="0">&lt;code>#include &amp;lt;vector&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;cmath&amp;gt;
using namespace std;
int main(){
int n;
cin &amp;gt;&amp;gt; n;
vector&amp;lt;int&amp;gt; nums;
for(int i=0;i&amp;lt;n;i++)
{
int num;
cin &amp;gt;&amp;gt; num;
nums.push_back(num);
}
vector&amp;lt;int&amp;gt; nums_sort(nums.begin(), nums.end());
sort(nums_sort.begin(), nums_sort.end());
int max_gap = 0;
auto max_gap_it = nums_sort.begin();
for(auto it=nums_sort.begin(); it!=nums_sort.end()-1; it++){
if(*(it+1) - *it &amp;gt; max_gap){
max_gap = *(it+1) - *it;
max_gap_it = it;
}
}
int max_gap_num = *max_gap_it;
int ret = 0;
int last_1 = -1;
int last_2 = -1;
for(auto it = nums.begin(); it!=nums.end(); it++){
if(*it &amp;lt;= max_gap_num){
if(last_1 != -1){
ret += abs(*it - last_1);
}
last_1 = *it;
}
else{
if(last_2 != -1){
ret += abs(*it - last_2);
}
last_2 = *it;
}
}
cout &amp;lt;&amp;lt; ret;
}
&lt;/code>&lt;/pre>&lt;h2 id="动态规划小思">动态规划小思
&lt;/h2>&lt;p>由于我对动态规划一点也不了解，先从找零开始。&lt;/p>
&lt;ul>
&lt;li>找零问题&lt;/li>
&lt;/ul>
&lt;p>动态规划的核心在于问题的分解和子问题递归组合变成原问题。&lt;/p>
&lt;p>举个例子，假设我们有面值为1元、3元和5元的硬币若干枚，如何用最少的硬币凑够11元？
如果你是个人类的话，你会说，我大眼一瞪，两个五元和一个一元，总共3个硬币。我们换个例子，假设题目是237元呢？你可能会说，我先用5块钱换出200元，然后找到剩下37元的最优组合。这种方法实际上是默认了将237元分成了两份，并假设两份的组合是最优组合。如果真是这样的话，那么就没什么问题了。可问题就出在分的200和37元，没有方法能证明这是有效的分割，所以动态规划就是建立在有效分割上的。&lt;/p>
&lt;p>分治算法与动态规划也是建立在分割上的。只是动态规划涉及到状态转移，而分治算法没有状态转移。分治算法可以由上向下分解，而动态规划通常由下向上构建。&lt;/p>
&lt;p>动态规划会涉及到两个维度，第一个维度通常和问题的规模相关，第二个维度需要从题目中提取出来。在本题目中，第二个维度是允许使用的硬币，例如，允许使用1元、允许使用1元和3元、允许使用1元3元5元。我们把他记为下标1,2,3，我们用j来代表这个变量，如果用i来表示当前题目的规模，也就是237，那么我们要求的是c[i][j] 为 c[237][3]的值。对于这个问题，我们可以分两种假设，第一种假设，这个最优解中5元的数量&amp;gt;0，第二种假设，这个最优值中5元的数量=0.对于第一种假设c[237][3]=c[237][2]，因为没有使用5元，那么就不需要5元了，这个最优解的解应该和只是用1元和3元的情况一致。对于第二种情况，由于这个解中必然有一个5元（因为你假设他大于0），那么，除了这枚5元钱，剩下的钱数是232元钱。由于原来c[237][3]是135元找237元的最优值，又因为其中必然有一枚5元，在最优值状态下拿掉其中必然存在的一枚硬币，剩下的钱所组成的钱数必然也是最优状态，(假设子问题不是最优的，即237元找135元的硬币数中构成232元的解有更优解，则237找135元也有更有解，和c[237][3]为最优解矛盾)，那么c[232][3]的值就是c[237][3]-1的值。&lt;/p>
&lt;p>这样我们可以推导出
c[i][j] = c[i][j-1] 假设1
c[i][j] = c[i-j下标代表的钱数][j]+1 假设2
如果写成min的形式，就是熟知的状态转移方程了。&lt;/p>
&lt;p>最终求的是c[11][5]，使用表格从c[0][0]状态开始向右下角推，最终可以推出答案。&lt;/p>
&lt;ul>
&lt;li>双人合唱难度问题
&lt;a class="link" href="https://www.nowcoder.com/questionTerminal/fddf64d5757e41ec93f3ef0c0a10b891" target="_blank" rel="noopener"
>https://www.nowcoder.com/questionTerminal/fddf64d5757e41ec93f3ef0c0a10b891&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>我们要记住&lt;/p>
&lt;blockquote>
&lt;p>题目的最优解不一定是状态矩阵的右下角的值&lt;/p>
&lt;/blockquote>
&lt;p>我们必须时刻记住，状态矩阵的右下角方格不一定就是直接的最优解。如果这样想的话，往往会陷入困境。为此，我们只能这样设想，矩阵i，j一定和题目中的两个维度有关，但是dp[i][j]不一定代表了最优解的值。&lt;/p>
&lt;p>例如本题目，题目要求求出最优难度系数。假如dp[i_max][j_max]就是最优难度系数，那i和j是什么的下标呢？？是无解的。反过来想，最优难度系数到底是什么呢？我们想想看，因为存在状态转移，最优状态必然是从多个状态中获取到的最小值。i代表什么呢？i如果代表当前唱的音符，那么j代表什么呢？j就只能代表另外一个人唱的音符了。那么最优状态下，一共会有多少种情况，就从这些情况来推算最优的值是哪个。在最优状态下，必然有一个人在唱最后一个音符，那么另一个人可能一个都没有唱，可能在唱第一个音符，可能在唱第二个音符。。。可能在唱倒数第二个音符。然后我们计算所有情况的最小值，就知道了问题的最优解。&lt;/p>
&lt;p>还有一个问题，i和j是不是要分人？如果i代表小Q唱的，j代表牛博士唱的，那么这个矩阵就是对称的，我们多计算了一半的内容。其实我们不关心到底是谁唱的，因为都一样。我们只能这样想，其中一个i是当前人唱的，j是上一个人唱到哪里了。&lt;/p>
&lt;p>c[i][j]可能由哪些状态转移过来呢？
例如某人唱到了第6个音符，另一个人最后一次唱到了第3个音符，此时如果当前的总的难度是最小的，那么有以下结论：
第6个音符、第5、第4个音符都是第一个人唱的。因为第二个人才唱到了第三个音符，他不可能去唱后面的音符。
所以c[6][3] = c[5][3] + 56难度差 = c[4][3] + 45难度差。
但是c[4][3]是怎么转移来的呢？如果此时第一个人唱到了第4个音符，另一个人唱到了第3个音符，这说明第一个人唱第4个音符中断了第二个人唱的音符。由于不知道上一次第一个人是从哪里跳到第4个音符的，所以我们分别假设是从可能的所有音符跳过来的，如果是从第二个音符跳过来的，那么就是c[3][2]+ 34差。如果是从第一个音符跳过来的，那么就是c[3][1]+14差。如果是从没有跳过来的，那么就是c[3][0]+04差。由于第一个人唱的时候，第二个人上一次唱的总是比第一个人唱的要小，所以我们不需要考虑j&amp;gt;=i的时候。&lt;/p>
&lt;p>基于此，有以下代码&lt;/p>
&lt;pre tabindex="0">&lt;code>#include &amp;lt;vector&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;array&amp;gt;
using namespace std;
vector&amp;lt;int&amp;gt; nums;
array&amp;lt;array&amp;lt;int, 2100&amp;gt;, 2100&amp;gt; dp;
/* 计算两个音符之间的难度。注意第一个音符的下标在输入数据里面是0，有个差1的关系。如果起始音符是0，说明第一次唱，不增加难度 */
int diffcult(int s, int e)
{
if(s == 0) return 0;
// printf(&amp;#34;diffcult %d %d to %d %d is %d \n&amp;#34;, s,nums[s-1], e,nums[e-1], abs(nums[e-1] - nums[s-1]));
return abs(nums[e-1] - nums[s-1]);
}
int main()
{
int n;
cin &amp;gt;&amp;gt; n;
for (int i = 0; i &amp;lt; n; i++)
{
int num;
cin &amp;gt;&amp;gt; num;
nums.push_back(num);
}
/* 从0开始向后考虑，直到考虑完所有音符，第一个人最差的情况是没有唱，此时j也没有唱，直接continue了 */
for (int i = 0; i &amp;lt;= n; i++)
{
/* 这里也是从0开始考虑，因为第一个人最差一个都不唱，就是处于0 */
for (int j = 0; j &amp;lt;= n; j++)
{
/* 不考虑后面的情况。当然也可以直接写到上面的范围限制里面 */
if (j &amp;gt;= i)
continue;
/* 如果是一般情况下，比如第一个人在唱6音符时，第二个人在唱3音符，显然是从dp[5][3]转移来的 */
if (j + 1 &amp;lt; i)
{
dp[i][j] = dp[i - 1][j] + diffcult(i - 1, i);
}
/* 否则，如果第二个人刚唱完就被第一个人抢唱了，那么第一个人唱的音符就可能是前面跳过来的 */
if (j + 1 == i)
{
/* k表示i是从第几个跳过来的 */
int min_cost = -1;
for (int k = 0; k &amp;lt; j; k++)
{
int cost = dp[j][k] + diffcult(k, j + 1);
/* 只用记录最小跳唱难度 */
if(min_cost == -1 || min_cost &amp;gt; cost){
min_cost = cost;
}
}
if(min_cost == -1) min_cost = 0;
dp[i][j] = min_cost;
}
// for(int a=0;a&amp;lt;=n;a++){
// for(int b=0;b&amp;lt;=n;b++){
// if(a==i &amp;amp;&amp;amp; b == j){
// printf(&amp;#34;\t【%d】&amp;#34;, dp[a][b]);
// }
// else printf(&amp;#34;\t%d&amp;#34;, dp[a][b]);
// }
// // printf(&amp;#34;\n&amp;#34;);
// }
// printf(&amp;#34;\n&amp;#34;);
}
}
int min_cost = -1;
for(int j=0;j&amp;lt;n;j++){
if(min_cost == -1 || min_cost &amp;gt; dp[n][j]){
min_cost = dp[n][j];
}
}
printf(&amp;#34;%d&amp;#34;, min_cost);
}
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>最长回文子串问题
&lt;a class="link" href="https://leetcode.com/problems/longest-palindromic-substring/description/" target="_blank" rel="noopener"
>https://leetcode.com/problems/longest-palindromic-substring/description/&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>其实已经明白了为什么动态规划会以这种方式进行呈现了。二维动态规划问题的最关键的点在于：&lt;/p>
&lt;blockquote>
&lt;p>线性增长问题的平面展开&lt;/p>
&lt;/blockquote>
&lt;p>例如找零问题，展开第二个维度就是以当前硬币种类找零。而合唱问题，则是以唱到当前位置时且上一个唱到某个位置时的难度最小值。最长回文子串，则变得是起始位置。&lt;/p>
&lt;p>最长回文子串的i表示从第i个字符开始，第j个字符结束时的最长子串问题。当i是起始字符，j是终止字符，则是问题的答案。&lt;/p>
&lt;p>对于最小的子问题来说，就是单个字符了，他的长度就是1，对于长字符串来说，如果他两边的字符不一样，那么等于去掉右边或者左边的字符中小的那一个。如果两边的字符一样，那么就等于去掉两边的子串的值加2。&lt;/p>
&lt;pre tabindex="0">&lt;code>
#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
using namespace std;
class Solution {
public:
int longestPalindromeSubseq(string s) {
vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; dp;
dp.resize(s.size() + 1);
for (int i = 0; i &amp;lt; s.size() + 1; i++)
{
dp[i].resize(s.size() + 1);
}
int len = s.size();
for(int t=0;t&amp;lt;len;t++){
for(int j=t; j&amp;lt;len;j++){
int i = j-t;
if(i==j){
dp[i][j] = 1;
}
else if(s[i] == s[j]){
dp[i][j] = dp[i+1][j-1] + 2;
}
else{
if(dp[i+1][j]&amp;gt; dp[i][j-1]){
dp[i][j] = dp[i+1][j];
}
else{
dp[i][j] = dp[i][j-1];
}
}
}
}
return dp[0][len-1];
}
};
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>最大子串问题&lt;/li>
&lt;/ul>
&lt;p>这个题目也很经典，按照上一个题目的思路，我们依次求出所有串的长度，就得到了最大的子串。&lt;/p>
&lt;p>然而事情不是这么简单，这个题目可以用一维来做。（注意其中有负数）。&lt;/p>
&lt;p>我们的记得，最后一个方格内存的不一定就是最优解。往往我们会认为dp[i]就是从开始到这个位置中，能够找到的最大的子串的值。但是这个最大的子串与i又没有什么关系，似乎i往左一位和往右移一位没有什么区别。如果我们的下标与题目中的最优解产生了“松弛”的关系，我们一定要警惕起来。我们需要紧紧把下标和最优解绑在一起。最优解必然和i产生直接关系。那么i是最优的什么。你会想到，i就是必须以第i个数字为结尾的串的最大值。最大子串就是遍历dp矩阵，找到那个最大的值。&lt;/p>
&lt;p>状态方程也容易写出来了，每当我们多考虑一个数字，以这个数字为结尾的子串的最大值有两种情况，第一种就是以上一个数字为结尾的最大子串加上这个数字，第二种情况就是不以上一个数字 为结尾。显然第二种情况就是以这个数字为开头的情况，并且只有这一个数字。&lt;/p>
&lt;p>input表示输入矩阵，dp表示状态矩阵
我们有 dp[i] = max (dp[i-1] + input[i] , input[i])&lt;/p></description></item><item><title>红黑树实现原理</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/</link><pubDate>Mon, 15 Jan 2018 10:00:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E7%BA%A2%E9%BB%91%E6%A0%91%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>二叉排序树存在劣化情况
二叉平衡树为了维持查找性能在插入和删除的时候开销太大
红黑树就出现了。红黑树不像二叉平衡树在任意时刻两个子树高度差相差最多1
参考自 &lt;a class="link" href="http://www.sohu.com/a/201923614_466939" target="_blank" rel="noopener"
>http://www.sohu.com/a/201923614_466939&lt;/a>&lt;/p>
&lt;h2 id="性质">性质
&lt;/h2>&lt;ul>
&lt;li>节点由红色和黑色构成，根节点为黑色&lt;/li>
&lt;li>叶子节点由黑色的NIL节点构成&lt;/li>
&lt;li>红色节点的子节点一定是黑色节点，并且一定包含两个叶子节点。这意味着不存在连续的两个红色节点。&lt;/li>
&lt;li>从任意一个节点出发，到任意一个NIL节点，都经历了相同数目的黑色节点&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/5195eeed6692.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>这意味着从根节点出发，到任意一个叶子节点，最长路径和最短路径差不超过最长路径的一半。这种情况下，最长路径和最短路径的黑色数目一样，最长路径最多多黑色个红色节点。&lt;/p>
&lt;h2 id="变色与旋转">变色与旋转
&lt;/h2>&lt;p>当插入一个节点（或删除一个节点），红黑树的性质被破坏。使用变色和旋转来调整。&lt;/p>
&lt;h3 id="变色">变色
&lt;/h3>&lt;p>如果两个节点连续为红色，需要变色&lt;/p>
&lt;h3 id="旋转">旋转
&lt;/h3>&lt;p>如果有一条路径上的黑色节点太多，需要旋转&lt;/p>
&lt;h2 id="变色与旋转的条件">变色与旋转的条件
&lt;/h2>&lt;p>这个之后在总结&lt;/p></description></item><item><title>Linux 终端后台任务、多窗口怎么使用</title><link>https://nansenli.com/zh-cn/post/jianshu/linux%E7%BC%96%E7%A8%8B/linux-%E7%BB%88%E7%AB%AF%E5%90%8E%E5%8F%B0%E4%BB%BB%E5%8A%A1%E5%A4%9A%E7%AA%97%E5%8F%A3%E6%80%8E%E4%B9%88%E4%BD%BF%E7%94%A8/</link><pubDate>Thu, 11 Jan 2018 15:01:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/linux%E7%BC%96%E7%A8%8B/linux-%E7%BB%88%E7%AB%AF%E5%90%8E%E5%8F%B0%E4%BB%BB%E5%8A%A1%E5%A4%9A%E7%AA%97%E5%8F%A3%E6%80%8E%E4%B9%88%E4%BD%BF%E7%94%A8/</guid><description>&lt;h2 id="byobu">byobu
&lt;/h2>&lt;p>byobu是代替screen以及tmux的一款工具。使用apt install byobu就可以安装好。运行byobu就可以查看他的命令很简单：&lt;/p>
&lt;ul>
&lt;li>f2 创建新窗口&lt;/li>
&lt;li>f3 返回左侧的窗口&lt;/li>
&lt;li>f4 返回右侧的窗口&lt;/li>
&lt;li>f9 离开byobu，打开的窗口所运行的命令不会因此中断。&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>强烈推荐不想使用screen和tmux的人试一下！包你满意。&lt;/p>
&lt;/blockquote></description></item><item><title>C++基本问题——指针与引用的区别</title><link>https://nansenli.com/zh-cn/post/jianshu/c++/c++%E5%9F%BA%E6%9C%AC%E9%97%AE%E9%A2%98%E6%8C%87%E9%92%88%E4%B8%8E%E5%BC%95%E7%94%A8%E7%9A%84%E5%8C%BA%E5%88%AB/</link><pubDate>Wed, 10 Jan 2018 08:44:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/c++/c++%E5%9F%BA%E6%9C%AC%E9%97%AE%E9%A2%98%E6%8C%87%E9%92%88%E4%B8%8E%E5%BC%95%E7%94%A8%E7%9A%84%E5%8C%BA%E5%88%AB/</guid><description>&lt;h3 id="指针与引用的区别">指针与引用的区别
&lt;/h3>&lt;ul>
&lt;li>指针可以是空指针，但是引用不能是空引用。这就导致下面一个区别。&lt;/li>
&lt;li>指针可能是非法的，引用总是合法的。如果指针是空指针，那么就不能合法的使用它。这要求在每次使用指针时都需要判断它的合法性。相反引用则不用判断。&lt;/li>
&lt;li>指针是可以修改的，引用不可以。指针是变量，他存储的地址所指向的对象可以被修改为其他地址，不再指向原对象。但是引用在初始化时被限定指向对象，可以修改对象的内容，但是不允许引用再引用其他对象。&lt;/li>
&lt;li>基于上述情况，指针和引用的应用场合不一样。指针可以应用在指向对象存在不同时刻指向不同对象或者需要指向不存在任何对象的场合。如果总是指向一个对象且指向该对象之后不再改变，则应该使用引用。&lt;/li>
&lt;/ul></description></item><item><title>关于CSDN移动端被电信劫持红包广告的现象与感想</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E5%85%B3%E4%BA%8Ecsdn%E7%A7%BB%E5%8A%A8%E7%AB%AF%E8%A2%AB%E7%94%B5%E4%BF%A1%E5%8A%AB%E6%8C%81%E7%BA%A2%E5%8C%85%E5%B9%BF%E5%91%8A%E7%9A%84%E7%8E%B0%E8%B1%A1%E4%B8%8E%E6%84%9F%E6%83%B3/</link><pubDate>Sun, 31 Dec 2017 06:24:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E5%85%B3%E4%BA%8Ecsdn%E7%A7%BB%E5%8A%A8%E7%AB%AF%E8%A2%AB%E7%94%B5%E4%BF%A1%E5%8A%AB%E6%8C%81%E7%BA%A2%E5%8C%85%E5%B9%BF%E5%91%8A%E7%9A%84%E7%8E%B0%E8%B1%A1%E4%B8%8E%E6%84%9F%E6%83%B3/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>最近疑似被电信劫持，遂将分析记录下来&lt;/p>
&lt;h2 id="现象">现象
&lt;/h2>&lt;p>我的三星S8使用的是四川电信卡，在访问如下网站时会被跳转到全屏广告，也就是支付宝红包界界面，体验非常差，下面这个就是一个会被劫持的地址。
&lt;a class="link" href="http://m.blog.csdn.net/skyroben/article/details/70195575" target="_blank" rel="noopener"
>http://m.blog.csdn.net/skyroben/article/details/70195575&lt;/a>&lt;/p>
&lt;h2 id="分析">分析
&lt;/h2>&lt;ul>
&lt;li>手机使用ss代理（4G）再访问上述网站，没有广告，没有劫持&lt;/li>
&lt;li>手机直连，电信wifi，访问有劫持&lt;/li>
&lt;li>使用热点，用电脑点击上面的网址，不会出现劫持&lt;/li>
&lt;li>使用热点，使用插件更改电脑端user agent为Android客户端，有一定概率出现劫持？？&lt;/li>
&lt;li>不使用热点，电脑有线（电信），更改电脑端user agent为Android客户端，有一定概率出现劫持？？&lt;/li>
&lt;li>使用手机端chrome（s8），连上电信wifi，在网址前加上view-source:查看网页源码，并将结果分别保存，在电脑端查看两文件进行差分对比，并没有不同。&lt;/li>
&lt;li>使用手机端chrome（s8），连上移动wifi，有劫持！（脚本缓存？）&lt;/li>
&lt;li>使用ios chrome，连电信wifi，没有劫持&lt;/li>
&lt;/ul>
&lt;h2 id="综上">综上
&lt;/h2>&lt;p>由于劫持后网页源码没有变化，可能是劫持了脚本。由于使用移动wifi访问也会出现劫持，所有不能排除csdn自己加入的广告。使用ss代理就可以破解劫持，说明劫持是区域性的。电脑和ios访问不会被劫持，说明劫持只针对Android浏览器。&lt;/p>
&lt;h2 id="结论">结论
&lt;/h2>&lt;p>一般用户最好不要使用电信网络和Android手机，并且建议所有网站使用https进行加密&lt;/p></description></item><item><title>c++如何实现 STL 中的vector</title><link>https://nansenli.com/zh-cn/post/jianshu/c++/c++%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0-stl-%E4%B8%AD%E7%9A%84vector/</link><pubDate>Sat, 30 Dec 2017 08:49:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/c++/c++%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0-stl-%E4%B8%AD%E7%9A%84vector/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>面试中可能会考你，怎么去实现一个vector呢？这需要了解vector的底层实现。
在这之前，需要学习动态内存管理，特别是allocator，在c++ primer一书中有讲解。&lt;/p>
&lt;h2 id="要实现的基础内容">要实现的基础内容
&lt;/h2>&lt;p>在cplusplus网站上，查看常见用法如下：
成员函数
(构造器)
Construct vector (public member function )
(析构函数)
Vector destructor (public member function )&lt;/p>
&lt;p>迭代器:
begin
Return iterator to beginning (public member function )
end
Return iterator to end (public member function )&lt;/p>
&lt;p>容量:
size
Return size (public member function )&lt;/p>
&lt;p>元素访问:
operator[]
Access element (public member function )
at
Access element (public member function )&lt;/p>
&lt;p>修改器:
push_back
Add element at the end (public member function )
pop_back
Delete last element (public member function )&lt;/p>
&lt;h2 id="基础的vector的主要结构">基础的vector的主要结构
&lt;/h2>&lt;pre tabindex="0">&lt;code>#include &amp;lt;cstddef&amp;gt;
#include &amp;lt;stdexcept&amp;gt;
#include &amp;lt;memory&amp;gt;
#include &amp;lt;iterator&amp;gt;
template &amp;lt;typename T&amp;gt;
class vector
{
public:
using value_type = T;
using iterator = value_type *;
using size_type = std::size_t;
public:
vector() = default;
~vector();
iterator begin() const;
iterator end() const;
size_type size() const;
value_type &amp;amp;operator[](size_type i) const;
value_type &amp;amp;at(size_type i) const;
void push_back(const value_type &amp;amp;new_elem);
void pop_back();
private:
iterator startptr = nullptr;
iterator endptr = nullptr;
iterator capptr = nullptr;
std::allocator&amp;lt;value_type&amp;gt; alloc;
private:
void check_cap();
void free();
};
&lt;/code>&lt;/pre>&lt;p>在我们这个类中，简单的实现了迭代器，push_back，pop_back，以及[]，at函数，size函数。为了实现内存管理还需要实现构造、析构函数，以及检查容量函数。&lt;/p>
&lt;h2 id="基础功能的内部实现">基础功能的内部实现
&lt;/h2>&lt;pre tabindex="0">&lt;code>template &amp;lt;typename T&amp;gt;
typename vector&amp;lt;T&amp;gt;::iterator vector&amp;lt;T&amp;gt;::begin() const
{
return startptr;
}
template &amp;lt;typename T&amp;gt;
typename vector&amp;lt;T&amp;gt;::iterator vector&amp;lt;T&amp;gt;::end() const
{
return endptr;
}
template &amp;lt;typename T&amp;gt;
typename vector&amp;lt;T&amp;gt;::size_type vector&amp;lt;T&amp;gt;::size() const
{
return endptr - startptr;
}
template &amp;lt;typename T&amp;gt;
typename vector&amp;lt;T&amp;gt;::value_type &amp;amp;vector&amp;lt;T&amp;gt;::operator[](size_type i) const
{
return *(startptr + i);
}
template &amp;lt;typename T&amp;gt;
typename vector&amp;lt;T&amp;gt;::value_type &amp;amp;vector&amp;lt;T&amp;gt;::at(size_type i) const
{
if (startptr + i &amp;gt;= endptr)
{
throw std::runtime_error(&amp;#34;out of range!&amp;#34;);
}
return *(startptr + i);
}
template &amp;lt;typename T&amp;gt;
vector&amp;lt;T&amp;gt;::~vector()
{
free();
}
&lt;/code>&lt;/pre>&lt;p>上面是简单函数的实现。仅仅是取出内部的数据而已。&lt;/p>
&lt;pre tabindex="0">&lt;code>template &amp;lt;typename T&amp;gt;
void vector&amp;lt;T&amp;gt;::free()
{
if (startptr)
{
for (auto p = startptr; p != endptr; p++)
{
alloc.destroy(p);
}
alloc.deallocate(startptr, endptr - startptr);
}
}
template &amp;lt;typename T&amp;gt;
void vector&amp;lt;T&amp;gt;::check_cap()
{
if (endptr == capptr)
{
int newsize = size() ? size() &amp;lt;&amp;lt; 1 : 1;
auto newstartptr = alloc.allocate(newsize);
auto newendptr = uninitialized_copy(std::make_move_iterator(startptr), std::make_move_iterator(endptr), newstartptr);
free();
startptr = newstartptr;
endptr = newendptr;
capptr = newstartptr + newsize;
}
}
template &amp;lt;typename T&amp;gt;
void vector&amp;lt;T&amp;gt;::push_back(const value_type &amp;amp;new_elem)
{
check_cap();
alloc.construct(endptr, new_elem);
endptr++;
}
template &amp;lt;typename T&amp;gt;
void vector&amp;lt;T&amp;gt;::pop_back()
{
if(endptr-startptr&amp;gt;0){
alloc.destroy(endptr);
endptr--;
}
}
&lt;/code>&lt;/pre>&lt;p>这部分是涉及到内存的部分，使用了allocator来管理内存。构造器将分配空间和构造函数分开，分为分配空间、回收空间、析构过程、构造过程，检查容量这部分涉及到这四个情况，首先分配新空间，然后在新位置上构造新元素，然后析构旧元素，释放旧空间。析构旧元素、释放旧空间使用free函数来完成。这里使用uninitialized_copy函数和make_move_iterator，移动迭代器以及无初始化拷贝函数来实现在新的位置上构造新的元素，以求加速新元素构造的速度。&lt;/p>
&lt;h2 id="高级函数与实现">高级函数与实现
&lt;/h2>&lt;h3 id="erase">erase
&lt;/h3>&lt;p>接下来完成一些额外的函数的实现，我们先从erase开始吧
erase删除从指定位置到指定位置的所有内容。函数原型为如下：&lt;/p>
&lt;pre tabindex="0">&lt;code> public:
iterator erase(const_iterator position);
iterator erase(const_iterator first, const_iterator last);
&lt;/code>&lt;/pre>&lt;p>第一个函数也可以看做是第二个函数的调用而已。我们实现第二个函数如下：&lt;/p>
&lt;pre tabindex="0">&lt;code>template &amp;lt;typename T&amp;gt;
typename vector&amp;lt;T&amp;gt;::iterator vector&amp;lt;T&amp;gt;::erase(const_iterator first, const_iterator last)
{
if(last &amp;gt;= endptr || first &amp;lt; startptr) throw std::runtime_error(&amp;#34;out of range!&amp;#34;);
iterator newendptr = std::copy(last, static_cast&amp;lt;const_iterator&amp;gt;(endptr), first);
while(newendptr &amp;lt; endptr){
alloc.destroy(--endptr);
}
return endptr;
}
&lt;/code>&lt;/pre>&lt;p>首先进行合法性检测。然后，使用std::copy将后面的内容部分复制到被删除的部分。注意copy被覆盖的部分会自动调用赋值函数，赋值函数内应该会调用析构函数。然后，如果还有需要析构的内容（这种情况发生在所移动的内容没有删除的内容长时才会发生），析构这些内容。之后返回end指针。&lt;/p>
&lt;p>另外一个重载函数实现比较简单：&lt;/p>
&lt;pre tabindex="0">&lt;code>template&amp;lt;typename T&amp;gt;
typename vector&amp;lt;T&amp;gt;::iterator vector&amp;lt;T&amp;gt;::erase(const_iterator position)
{
return erase(position, position+1);
}
&lt;/code>&lt;/pre></description></item><item><title>c++11 特性 简单介绍</title><link>https://nansenli.com/zh-cn/post/jianshu/c++/c++11-%E7%89%B9%E6%80%A7-%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8D/</link><pubDate>Thu, 28 Dec 2017 07:31:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/c++/c++11-%E7%89%B9%E6%80%A7-%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8D/</guid><description>&lt;h2 id="根据c-primer第五版总结可以用来快速回顾一下这些性质">根据c++ primer第五版总结，可以用来快速回顾一下这些性质
&lt;/h2>&lt;ul>
&lt;li>
&lt;p>long long 类型
定义为至少有32位那么长。一般没用。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>列表初始化
列表是一组由花括号和逗号组成的元素集合，例如{1,2,3}。
我们可以使用花括号来进行初始化变量。
int a{0};
等号运算符在声明时不是赋值，而是初始化，所以也可以用下面的形式初始化：
int a = {0};
这样的效果和int a = 0;是一样的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>nullptr 常量
这是由于在c++11之前，为指针赋值空指针用的是0来直接赋值的。现在使用nullptr更好。nullptr实质上是一个只可以有右值并只能类型转换为指针且值为0地址的一个对象，由此可以解决一些问题，相关资料可以查看如何实现nullptr。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>constexpr 变量
const expressions 常量表达式的意思。和const修饰的区别是，const表示这个变量名在之后使用的时候得是常量，而constexpr不仅如此，还要求初始化该变量时使用的表达式必须也为常量才可以。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>类型别名
using uint = unsigned int
typedef uint unsigned int
这两句是等价的&lt;/p>
&lt;/li>
&lt;li>
&lt;p>auto 类型说明符
声明并定义变量时，求助编译器根据定义类型自动推断声明的变量类型。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>decltype 类型指示符
声明变量时，可以使用decltype(expressions)求出表达式的类型，作为被声明变量的类型&lt;/p>
&lt;/li>
&lt;li>
&lt;p>类内初始化&lt;/p>
&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code> class A {
public:
int a = 7;
};
&lt;/code>&lt;/pre>&lt;p>这等同于之前版本中的：&lt;/p>
&lt;pre tabindex="0">&lt;code> class A {
public:
int a;
A() : a(7) {}
};
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>
&lt;p>使用auto或decltype推断类型
auto len = line.size(); 自动推断string::size_type更好&lt;/p>
&lt;/li>
&lt;li>
&lt;p>范围for语句
打印字符串中的字符
for(auto c:str) cout &amp;laquo; c &amp;laquo; endl;
类似于Python中的for in语句&lt;/p>
&lt;/li>
&lt;li>
&lt;p>vector嵌套
vector&amp;lt;vector&lt;!-- raw HTML omitted -->这里在旧的编译器上需要有个空格不能让两个尖尖连在一起&amp;gt;&lt;/p>
&lt;/li>
&lt;li>
&lt;p>列表初始化vector对象
vector&lt;!-- raw HTML omitted --> strs = {&amp;ldquo;a&amp;rdquo;, &amp;ldquo;bb&amp;rdquo;, &amp;ldquo;ccc&amp;rdquo;};&lt;/p>
&lt;/li>
&lt;li>
&lt;p>cbegin 和 cend
返回类型是const_iterator类型的迭代器&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在二维数组中使用auto&lt;/p>
&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>int mat[3][4];
int main()
{
for (int(*p)[4] = mat; p != mat + 3; p++)
{
/* ... */
}
for (auto p = mat; p != mat + 3; p++)
{
/* ... */
}
}
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>
&lt;p>除法取整
c++11规定除法一律向0取整&lt;/p>
&lt;/li>
&lt;li>
&lt;p>列表赋值vector对象
vector&lt;!-- raw HTML omitted --> strs;
strs = {&amp;ldquo;a&amp;rdquo;, &amp;ldquo;bb&amp;rdquo;, &amp;ldquo;ccc&amp;rdquo;};&lt;/p>
&lt;/li>
&lt;li>
&lt;p>139 使用sizeof获取类的成员的大小
通常情况下只有通过对象才能访问类的成员。但是sizeof可以直接判断成员大小。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>168 范围for语句
在范围for语句中不能增加vector对象的元素，这是因为可能导致范围for语句的end元素变得无效了。这和Python中范围循环可能导致的问题是一样的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>197 initializer_list
initializer_list是一种模板类型，对象元素永远是常量，类型必须相同。花括号的列表就是这种对象。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>203 列表初始化返回值
return 一个 initializer_list 列表可以对返回类型为vector的对象进行临时量初始化。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>206 定义尾置返回类型
声明一个返回数组指针的函数
int * func(int i)这样是不太对的，因为返回了一个指针而不是数组指针
int (* func(int i))[10] 这样的话才是返回大小为10的一个数组指针
也可以这样写：auto func(int i) -&amp;gt; int(*)[10];&lt;/p>
&lt;/li>
&lt;li>
&lt;p>206 使用decltype简化返回类型
可以使用decltype来简化返回类型，但是这种需求较少见。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>214 constexpr 函数
constexpr 函数是返回类型为字面值的函数，且只能有一条return语句。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>237 default默认构造函数
可以对构造函数赋值等号表示使用编译器提供的默认的构造函数。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>246 类内的初始化
新标准可以在类内就初始化赋值成员&lt;/p>
&lt;/li>
&lt;li>
&lt;p>261 委托构造函数
构造函数初始化时，使用初始值列表来初始化成员。除此之外，还可以使用其他构造函数帮助构造。
例如构造函数是func(int a,int b)委托构造函数func():func(0,0){}这样就可以当做默认是0,0来构造了。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>268 constexpr 构造函数
用于生成constexpr对象&lt;/p>
&lt;/li>
&lt;li>
&lt;p>284 使用string类型来表示文件路径
在stream类型中，原来只能使用c字符串，现在可以使用string类型的字符串了&lt;/p>
&lt;/li>
&lt;li>
&lt;p>293 array 与 forward_list
非常的快，与手写list速度相当，比数组安全&lt;/p>
&lt;/li>
&lt;li>
&lt;p>300 使用列表初始化容器
list&lt;!-- raw HTML omitted --> authors = {&amp;ldquo;milton&amp;rdquo;, &amp;ldquo;shakespeare&amp;rdquo;, &amp;ldquo;austen&amp;rdquo;};
vector&amp;lt;const char*&amp;gt; articles = {&amp;ldquo;a&amp;rdquo;, &amp;ldquo;b&amp;rdquo;, &amp;ldquo;cc&amp;rdquo;};&lt;/p>
&lt;/li>
&lt;li>
&lt;p>303 容器的非成员swap
使用swap可以快速交换两个内容的容器&lt;/p>
&lt;/li>
&lt;li>
&lt;p>308 使用insert的返回之值
新标准的insert在插入成功之后返回一个迭代器，该迭代器的位置是指向插入的新元素。使用这个特性可以反复在该位置插入多个元素。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>308 使用emplace函数
使用emplace函数可以实现构造而不是复制。
使用这个方法可以传入构造函数所所需要的参数而不用构造出对象在2复制一份进取。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>shrink_to_fit
对容器调用该函数，会释放多余的vector或者是string所占用的空间。一般没什么用&lt;/p>
&lt;/li>
&lt;li>
&lt;p>string 类型的数值转换函数
to_string 表示一组重载函数，可以将int、double类型的数据转换为string类型的数据。
Stoi 可以将string转换为int，类似的还有其他的函数，包括不同基的、还有浮点数的&lt;/p>
&lt;/li>
&lt;li>
&lt;p>346 lambda 表达式
简单的lambda表达式
auto f = []{return 42；};
这里将一个lambda表达式赋值给f。之后可以调用f()获得42
如何向lambda表达式传递参数？
[](int a, int b){return a&amp;gt;b;};
但是有的时候不想传递参数，仍然使得lambda表达式使用外部的变量怎么办？
[&amp;amp;out](int a, int b){return a&amp;lt;out &amp;amp;&amp;amp; out &amp;lt; b;};
但是我不想让上面这个表达式返回int，而是double怎么办？
[&amp;amp;out](int a, int b)-&amp;gt;double{return a&amp;lt;out &amp;amp;&amp;amp; out &amp;lt; b;}&lt;/p>
&lt;/li>
&lt;li>
&lt;p>354 bind
bind函数可以将一个函数的调用改成简单的调用&lt;/p>
&lt;/li>
&lt;li>
&lt;p>377 关联容器的列表初始化
map&amp;lt;string, string&amp;gt; authors={ {&amp;ldquo;joncy&amp;rdquo;, &amp;ldquo;james&amp;rdquo;} };
set&lt;!-- raw HTML omitted --> exclude = {&amp;ldquo;the&amp;rdquo;, &amp;ldquo;but&amp;rdquo; };&lt;/p>
&lt;/li>
&lt;li>
&lt;p>380 列表初始化pair的返回类型
return 一个 {}表示的列表到pair类型可以自动转换。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>384 pair的列表初始化
当向map插入pair时，最简单的方式是插入花括号表示的一对键值。插入的内容会初始化为pair插入。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>394 无序容器
map和set前面加上unordered就是无序容器。底层使用hash实现。需要实现存储元素的hash模板。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>400 智能指针
shared_ptr允许多个指针指向同一个对象。unique_ptr则独占所指的对象。
智能指针是模板，在声明时需要显示提供类型，类似vector的使用。
shared_ptr&lt;!-- raw HTML omitted --> p1;&lt;/p>
&lt;/li>
&lt;/ul></description></item><item><title>epoll TCP服务器与客户端简明例子</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/epoll-tcp%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%B8%8E%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%AE%80%E6%98%8E%E4%BE%8B%E5%AD%90/</link><pubDate>Mon, 25 Dec 2017 02:52:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/epoll-tcp%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%B8%8E%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%AE%80%E6%98%8E%E4%BE%8B%E5%AD%90/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>epoll是Linux专有API，提供高效的文件描述符管理，常用于批量监听文件描述符的可读情况，是高并发服务器的有效手段之一。下面是一个简单的epoll服务器与客户端例子。&lt;/p>
&lt;h2 id="服务器端">服务器端
&lt;/h2>&lt;pre tabindex="0">&lt;code>#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;netinet/in.h&amp;gt;
#include &amp;lt;arpa/inet.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;sys/epoll.h&amp;gt;
using namespace std;
const int MAX_EPOLL_EVENTS = 1000;
const int MAX_MSG_LEN = 1024;
void setFdNonblock(int fd)
{
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
}
void err_exit(const char *s){
printf(&amp;#34;error: %s\n&amp;#34;,s);
exit(0);
}
int create_socket(const char *ip, const int port_number)
{
struct sockaddr_in server_addr = {0};
/* 设置ipv4模式 */
server_addr.sin_family = AF_INET; /* ipv4 */
/* 设置端口号 */
server_addr.sin_port = htons(port_number);
/* 设置主机地址 */
if(inet_pton(server_addr.sin_family, ip, &amp;amp;server_addr.sin_addr) == -1){
err_exit(&amp;#34;inet_pton&amp;#34;);
}
/* 建立socket */
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
if(sockfd == -1){
err_exit(&amp;#34;socket&amp;#34;);
}
/* 设置复用模式 */
int reuse = 1;
if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &amp;amp;reuse, sizeof(reuse)) == -1)
{
err_exit(&amp;#34;setsockopt&amp;#34;);
}
/* 绑定端口 */
if(bind(sockfd, (sockaddr *)&amp;amp;server_addr, sizeof(server_addr)) == -1){
err_exit(&amp;#34;bind&amp;#34;);
}
/* 设置被动打开 */
if(listen(sockfd, 5) == -1){
err_exit(&amp;#34;listen&amp;#34;);
}
return sockfd;
}
int main(int argc, const char *argv[])
{
/* 帮助 */
if(argc &amp;lt; 3){
printf(&amp;#34;usage:%s ip port\n&amp;#34;, argv[0]);
exit(0);
}
/* 获取服务器参数 */
const char * ip = argv[1];
const int port = atoi(argv[2]);
/* 创建套接字 */
int sockfd = create_socket(ip, port);
printf(&amp;#34;success create sockfd %d\n&amp;#34;, sockfd);
setFdNonblock(sockfd);
/* 创建epoll */
int epollfd = epoll_create1(0);
if(epollfd == -1) err_exit(&amp;#34;epoll_create1&amp;#34;);
/* 添加sockfd到epollfd兴趣列表 */
struct epoll_event ev;
ev.data.fd = sockfd;
ev.events = EPOLLIN ;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &amp;amp;ev) == -1){
err_exit(&amp;#34;epoll_ctl1&amp;#34;);
}
/* 创建一个列表用于存放wait所返回的events */
struct epoll_event events[MAX_EPOLL_EVENTS] = {0};
/* 开始等待所有在epoll上挂上去的事件 */
while(1){
/* 等待事件 */
printf(&amp;#34;begin wait\n&amp;#34;);
int number = epoll_wait(epollfd, events, MAX_EPOLL_EVENTS, -1);
printf(&amp;#34;end wait\n&amp;#34;);
sleep(1);
if(number &amp;gt; 0){
/* 遍历所有事件 */
for (int i = 0; i &amp;lt; number; i++)
{
int eventfd = events[i].data.fd;
/* 如果触发事件的fd是sockfd，则说明有人连接上来了，我们需要accept他 */
if(eventfd == sockfd){
printf(&amp;#34;accept new client...\n&amp;#34;);
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int connfd = accept(sockfd, (struct sockaddr *)&amp;amp;client_addr, &amp;amp;client_addr_len);
setFdNonblock(connfd);
/* accept之后，需要将文件描述符加入到监听列表中 */
struct epoll_event ev;
ev.data.fd = connfd;
ev.events = EPOLLIN;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &amp;amp;ev) == -1){
err_exit(&amp;#34;epoll_ctl2&amp;#34;);
}
printf(&amp;#34;accept new client end.\n&amp;#34;);
}
/* 如果触发的fd不是sockfd，那就是新加的connfd */
else{
/* 读出内容，直到遇到回车。然后显示该内容。 */
printf(&amp;#34;read start...\n&amp;#34;);
while(1){
char buff = -1;
int ret = read(eventfd, &amp;amp;buff, 1);
if(ret &amp;gt; 0){
printf(&amp;#34;%c&amp;#34;, buff);
}
if(buff == &amp;#39;\n&amp;#39;){
break;
}
else if (ret == 0){
printf(&amp;#34;client close.\n&amp;#34;);
close(eventfd);
epoll_ctl(epollfd, EPOLL_CTL_DEL, eventfd, NULL);
break;
}
else if (ret &amp;lt; 0){
printf(&amp;#34;read error.\n&amp;#34;);
break;
}
}
printf(&amp;#34;read end.\n&amp;#34;);
}
}
}
}
}
&lt;/code>&lt;/pre>&lt;h2 id="客户端">客户端
&lt;/h2>&lt;pre tabindex="0">&lt;code>#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;arpa/inet.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;iostream&amp;gt;
using namespace std;
void err_exit(const char *s){
printf(&amp;#34;error: %s\n&amp;#34;,s);
exit(0);
}
int create_socket(const char *ip, const int port_number)
{
struct sockaddr_in server_addr = {0};
/* 设置ipv4模式 */
server_addr.sin_family = AF_INET; /* ipv4 */
/* 设置端口号 */
server_addr.sin_port = htons(port_number);
/* 设置主机地址 */
if(inet_pton(PF_INET, ip, &amp;amp;server_addr.sin_addr) == -1){
err_exit(&amp;#34;inet_pton&amp;#34;);
}
/* 建立socket */
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
if(sockfd == -1){
err_exit(&amp;#34;socket&amp;#34;);
}
if(connect(sockfd, (struct sockaddr*)&amp;amp;server_addr, sizeof(server_addr)) == -1){
err_exit(&amp;#34;connect&amp;#34;);
}
return sockfd;
}
int main(int argc, const char *argv[]){
if(argc &amp;lt; 3){
printf(&amp;#34;usage:%s ip port\n&amp;#34;, argv[0]);
exit(0);
}
/* 获取服务器参数 */
const char * ip = argv[1];
const int port = atoi(argv[2]);
//创建套接字
int sock = create_socket(ip, port);
//向服务器（特定的IP和端口）发起请求
while(1){
string buff;
getline(cin, buff);
if(buff == &amp;#34;exit&amp;#34;) break;
write(sock, buff.c_str(), buff.size());
char end = &amp;#39;\n&amp;#39;;
write(sock, &amp;amp;end, 1);
}
close(sock);
return 0;
}
&lt;/code>&lt;/pre>&lt;h2 id="编译">编译
&lt;/h2>&lt;p>将上述文本存为socket_server.cpp与socket_client.cpp，编译链接该程序。&lt;/p>
&lt;pre tabindex="0">&lt;code>g++ -Wall socket_server.cpp -o server &amp;amp;&amp;amp; g++ -Wall socket_client.cpp -o client
&lt;/code>&lt;/pre>&lt;h2 id="运行">运行
&lt;/h2>&lt;pre tabindex="0">&lt;code>./server localhost 1234
&lt;/code>&lt;/pre>&lt;pre tabindex="0">&lt;code>./client localhost 1234
&lt;/code>&lt;/pre>&lt;p>在client端输入文字并回车，会出现在服务器端。按下Ctrl C关闭客户端或者输入exit关闭客户端。&lt;/p>
&lt;h2 id="运行流程">运行流程
&lt;/h2>&lt;p>服务器首先创建被动打开socket文件描述符，然后将该文件描述符加入到epoll兴趣列表。接下来进入循环。每当兴趣列表wait结束时，说明对应文件描述符可以进行操作。当有客户端连上被动打开socket文件描述符时，说明有客户端连上，被动打开文件描述符可以被accept。accept后所创建的新的文件描述符是与客户端通信的文件描述符，该文件描述符继续加入兴趣列表。当客户端发送数据时，该文件描述符也会产生可读信号，会导致wait结束，此时进入处理模式，读取并显示客户端所发送的数据。&lt;/p></description></item><item><title>SOCKET 学习笔记</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/socket-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</link><pubDate>Mon, 25 Dec 2017 02:34:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/socket-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>面经中提到的epoll，涉及到了socket编程。为了深入了解epoll原理，需要首先了解socket编程。socket是进程间通信IPC，就算在网络中也是如此，所以说网络中通信的主体是进程，而不是计算机。socket学习内容一个是如何建立服务器和客户端，一个是如何使用socket API。&lt;/p>
&lt;h4 id="fdsocketdomain-type-protocol">fd=socket(domain, type, protocol);
&lt;/h4>&lt;p>socket调用可以用来创建一个socket，例如
domain可以用来指定ipv4，type可以用来指定tcp，protocol一般是0。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>domain
domain是通信范围与通信地址的类型。有下面几个经典类型：
UNIX IPV4 IPV6，分别对应的参数是AF_UNIX AF_INET AF_INET6。
domain 的参数都是以AF开头的代表地址簇。PF开头的代表协议簇。本来设计是地址簇和协议簇是多对多的，但是后来实现过程中，一个协议簇和地址簇是一一对应的。所以基本上domain就是指定了协议簇，地址簇也被指定了。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>type
socket表示是流还是数据包，其实就是TCP还是UDP。如果是TCP就是SOCK_STREAM，如果是UDP就是SOCK_DGRAM&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h4 id="bindint-sockfd-const-struct-sockaddr-addr-socklen_t-addrlen">bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
&lt;/h4>&lt;p>该调用用于将socket绑定到一个地址上。之后可以发送TCP报文，在一些场合下也可以通过write发送UDP报文，但是只能在该socket上读取对等socket数据。&lt;/p>
&lt;ul>
&lt;li>sockaddr
该结构体有一个整形表示地址类型，然后后面跟着一个char数组。后面可以看到具体传进来的是根据使用场合的其他数据结构，但是是通用的&lt;/li>
&lt;/ul>
&lt;h4 id="listenint-sockfd-int-backlog">listen(int sockfd, int backlog)
&lt;/h4>&lt;p>将一个socket描述符标记为被动。可以被主动socket连接。backlog是用于限制等待连接的数量。&lt;/p>
&lt;h4 id="acceptint-sockfd-struct-sockaddr-addr-socklen_t--addrlen">accept(int sockfd, struct sockaddr *addr, socklen_t * addrlen);
&lt;/h4>&lt;p>accept调用会阻塞并等待在文件描述符sockfd上的接入请求。一旦请求成功，会创建一个新的socket，这个新的socket与对方进行连接。&lt;/p>
&lt;ul>
&lt;li>addr
返回对方的地址&lt;/li>
&lt;li>addrlen
传入addr的长度，用于告知能够写入输入的最大长度。&lt;/li>
&lt;/ul>
&lt;h4 id="connectint-sockfd-const-struct-sockaddr--addr-socklen_t-addrlen">connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);
&lt;/h4>&lt;p>将sockfd连接到addr所述的地址上。&lt;/p>
&lt;h4 id="closeint-fd">close(int fd)
&lt;/h4>&lt;p>用于关闭连接&lt;/p>
&lt;h4 id="read-write">read write
&lt;/h4>&lt;p>用于对sockfd进行读入或读出&lt;/p>
&lt;h4 id="recvfromint-sockfd-void-buffer-size_t-length-int-flags-struct-sockaddr-src_addr-socklen_t-addrlen">recvfrom(int sockfd, void *buffer, size_t length, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
&lt;/h4>&lt;h4 id="sendtoint-sockfd-const-void-buffer-size_t-length-int-flags-const-struct-sockaddr-dest_addr-socklen_t-addrlen">sendto(int sockfd, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
&lt;/h4>&lt;p>用于发送和接受udp报文。服务器端不能使用listen函数和accept函数，客户端不能使用connect函数。&lt;/p>
&lt;h4 id="unix-domain">unix domain
&lt;/h4>&lt;p>使用上面的API就可以实现本机上通过文件的通信。
unix domain所使用的sockaddr是sockaddr_run，如下表示：&lt;/p>
&lt;pre tabindex="0">&lt;code>struct sockaddr_un{
sa_family_t sun_family;
char sun_path[108];
}
&lt;/code>&lt;/pre>&lt;h4 id="网络字节序">网络字节序
&lt;/h4>&lt;p>网络字节顺序是按照大端来的，x86是小端结构。转换使用的是如下的函数进行的&lt;/p>
&lt;ul>
&lt;li>htons&lt;/li>
&lt;li>htonl&lt;/li>
&lt;li>ntohs&lt;/li>
&lt;li>ntohl
h是host，n是net，s是16位，l是32位。s和l是short和long，虽然现在已经不再使用这样的标准了。&lt;/li>
&lt;/ul>
&lt;h4 id="internet-socket-地址结构">Internet socket 地址结构
&lt;/h4>&lt;p>网络下使用的socket地址是sockaddr_in，定义如下&lt;/p>
&lt;pre tabindex="0">&lt;code>struct sockaddr_in{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char __pad[X];
}
&lt;/code>&lt;/pre>&lt;p>可以看出不一样的地方是后面的char数组变成了一个端口和地址。sin是socket Internet的简写，简写的和sun一样很差。&lt;/p>
&lt;h4 id="internet-socket-地址转换">Internet socket 地址转换
&lt;/h4>&lt;p>字符串式的地址格式和二进制地址格式转换API:&lt;/p>
&lt;h4 id="inet_ptonint-domain-const-char-src-void-addrptr">inet_pton(int domain, const char *src, void *addrptr);
&lt;/h4>&lt;p>该函数用于将src中包含的字符串转换为&lt;strong>网络字节&lt;/strong>的二进制地址，存入addrptr。p是presentation的意思，就是人类方便的地址。&lt;/p>
&lt;h4 id="const-char--inet_ntopint-domain-const-void-addrptr-char-dst_str-size_t-len">const char * inet_ntop(int domain, const void *addrptr, char *dst_str, size_t len);
&lt;/h4>&lt;p>该函数执行网络字节的二进制地址转换为人类可读的地址，写入到dst_str中，缓冲区的大小有len传入。&lt;/p>
&lt;h4 id="getaddrinfoconst-char-host-const-char-service-const-struct-addrinfo-hints-struct-result">getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct **result);
&lt;/h4>&lt;p>该函数给定一个主机名和服务器名，返回socket地址和端口号。
getaddrinfo以host、service、hints参数作为输入，其中host参数包括一个主机名或一个以IPV4字符串。service是服务名或者是端口号。该函数的调用之后需要使用freeaddrinfo来释放空间。&lt;/p>
&lt;h4 id="getnameinfoconst-struct-sockaddr-addr-socklen_t-addrlen-char-host-size_t-hostlen-char-service-size_t-servlen-int-flags">getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, size_t hostlen, char *service, size_t servlen, int flags);
&lt;/h4>&lt;p>给定一个socket地址结构，返回一个主机和服务器名的字符串。&lt;/p>
&lt;h4 id="setsockoptint-sockfd-int-level-int-optname-const-void-optval-socklen_t-optlen">setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
&lt;/h4>&lt;p>sockfd是代表指向套接字的文件描述符。参数level制定了套接字选项所适用的协议。例如TCP或者是IP，这表示选项作用的套接字API层。一般来说，该选项会设置为SOL_SOCKET，表示作用于套接字API层。参数optname表示了我们期待设置的选项，optvalue是用来设置刚刚的选项的值，可以是整数或者是结构体的指针，指向了一个缓冲区，而参数optlen是刚刚那个指针所指向区域的大小。&lt;/p>
&lt;p>例如，要设置sockfd为reuseaddr属性时，可以如下调用：&lt;/p>
&lt;pre tabindex="0">&lt;code> int reuse = 1;
if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &amp;amp;reuse, sizeof(reuse)) == -1)
{
err_exit(&amp;#34;setsockopt&amp;#34;);
}
&lt;/code>&lt;/pre>&lt;h4 id="getsockoptint-sockfd-int-level-int-optname-void-optval-socklen_t-optlen">getsockopt(int sockfd, int level, int optname, void *optval, socklen_t optlen);
&lt;/h4>&lt;p>用法和上面一样，只是获取而已。&lt;/p></description></item><item><title>其他 I/O 模型总结</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/%E5%85%B6%E4%BB%96-i-o-%E6%A8%A1%E5%9E%8B%E6%80%BB%E7%BB%93/</link><pubDate>Thu, 21 Dec 2017 06:18:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/%E5%85%B6%E4%BB%96-i-o-%E6%A8%A1%E5%9E%8B%E6%80%BB%E7%BB%93/</guid><description>&lt;ul>
&lt;li>I/O 多路复用&lt;/li>
&lt;li>信号驱动I/O&lt;/li>
&lt;li>Linux专有epoll&lt;/li>
&lt;/ul>
&lt;h2 id="io多路复用">I/O多路复用
&lt;/h2>&lt;p>通过select系统调用或者poll系统调用，来对文件描述符进行检查。&lt;/p>
&lt;ul>
&lt;li>select函数
&lt;code>int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);&lt;/code>
参数分别的意思是：需要测试的fd范围0～fd-1，满足三种情况要求的文件描述符号，超时时间。&lt;/li>
&lt;li>poll函数
&lt;code>int poll(struct pollfd fds[], nfds_t nfds, int timeout); &lt;/code>
参数分别是：元素包含文件描述符、需要检查的状态、返回的状态的一个数组，数组长度以及超时时间。&lt;/li>
&lt;/ul>
&lt;p>二者的区别在于poll里面传进去的数组大小不受fd_set定义的大小限制。poll的events和revents更丰富一些。select用的更为广泛一些。新版中select的性能经过优化与poll差不多。&lt;/p>
&lt;ul>
&lt;li>epoll函数
epoll函数包括三个API：epoll_create()、epoll_ctl()、epoll_wait()三个调用。
epoll是三个调用中性能最好，效果最多的。但是只有Linux才可以使用。&lt;/li>
&lt;/ul>
&lt;p>主要是因为服务器上面会连接多个socket链接，如果为每一个套接字建立一个线程的话，会比较占用资源，所以需要使用epoll来优化性能。&lt;/p></description></item><item><title>C++ 面向对象程序设计 总结</title><link>https://nansenli.com/zh-cn/post/jianshu/c++/c++-%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1-%E6%80%BB%E7%BB%93/</link><pubDate>Thu, 14 Dec 2017 05:53:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/c++/c++-%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1-%E6%80%BB%E7%BB%93/</guid><description>&lt;h3 id="前言">前言
&lt;/h3>&lt;p>C++与C最大的不同是面向对象。虽然我了解一些有关面向对象的概念，写过一些函数，可惜实际中很少用到。本文是C++ Primer第15章的快速总结。&lt;/p>
&lt;h3 id="概述">概述
&lt;/h3>&lt;p>面向对象程序设计又称为OOP，其核心思想是：数据抽象、继承和动态绑定。数据抽象指的是，声明与实现分离，继承指的是子类获得父类的所有成员，动态绑定是调用类的函数时根据类的不同进行区别调用。&lt;/p>
&lt;h3 id="声明基类与派生类">声明基类与派生类
&lt;/h3>&lt;p>下面的代码进行简单的示例，声明了一个基类animal与派生类cat。&lt;/p>
&lt;pre tabindex="0">&lt;code>#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
class animal
{
public:
virtual void eat(){};
};
class cat : public animal
{
public:
void eat() { std::cout &amp;lt;&amp;lt; &amp;#34;eat fish&amp;#34; &amp;lt;&amp;lt; std::endl; }
};
int main()
{
cat c;
c.eat();
}
&lt;/code>&lt;/pre>&lt;h3 id="类型转换与继承">类型转换与继承
&lt;/h3>&lt;p>派生类的指针和引用可以转为基类的指针或引用，基类的指针或引用不能转化为派生类的指针或引用。这意味着，一个基类的指针，指向的对象，有可能是基类，也有可能是派生类，但肯定不是基类的父类。&lt;/p>
&lt;p>派生类对象可以赋值给基类，但只会拷贝基类的部分。&lt;/p>
&lt;h3 id="虚函数">虚函数
&lt;/h3>&lt;p>虚函数在运行时进行解析。我们继续上面的例子，假如我们写了一个新的函数，是doeat，内部调用了animal的eat方法。eat对animal是eat meat or grass，但是最后显示的结果是eat fish。这就证明了，在运行时，doeat函数调用animal的eat方法时，eat方法找到了引用对象的真实对象cat的eat方法并进行了调用。&lt;/p>
&lt;pre tabindex="0">&lt;code>class animal
{
public:
virtual void eat() { std::cout &amp;lt;&amp;lt; &amp;#34;eat meat or grass&amp;#34; &amp;lt;&amp;lt; std::endl; };
};
class cat : public animal
{
public:
void eat() { std::cout &amp;lt;&amp;lt; &amp;#34;eat fish&amp;#34; &amp;lt;&amp;lt; std::endl; }
};
void doeat(animal &amp;amp;a) { a.eat(); }
int main()
{
cat c;
doeat(c);
}
&lt;/code>&lt;/pre>&lt;p>另外，为了养成良好的习惯，在派生类中最好使用override来显式的声明覆盖的虚函数。&lt;/p>
&lt;h3 id="纯虚函数">纯虚函数
&lt;/h3>&lt;p>在定义虚函数后赋值为0则声明了纯虚函数，纯虚函数相当于接口，派生类必须实现该接口。&lt;/p>
&lt;h2 id="抽象基类">抽象基类
&lt;/h2>&lt;p>含有纯虚函数的类是抽象基类。抽象基类不能创建。&lt;/p>
&lt;h3 id="示例-文本行查询程序">示例 文本行查询程序
&lt;/h3>&lt;p>给定一段文本，要求查询其中的内容。支持单词查询、逻辑查询。&lt;/p></description></item><item><title>C++ 虚函数 总结</title><link>https://nansenli.com/zh-cn/post/jianshu/c++/c++-%E8%99%9A%E5%87%BD%E6%95%B0-%E6%80%BB%E7%BB%93/</link><pubDate>Thu, 14 Dec 2017 05:53:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/c++/c++-%E8%99%9A%E5%87%BD%E6%95%B0-%E6%80%BB%E7%BB%93/</guid><description>&lt;p>虚函数是 C++ 实现多态的方式。&lt;/p>
&lt;ul>
&lt;li>什么是多态？
多态性常被视为自封装和继承之后，面向对象的编程的第三个支柱。
如果举一个简单的例子，比如有动物（Animal）之类别（Class），而且由动物继承出类别鸡（Chicken）和类别狗（Dog），并对同一源自类别动物（父类别）之一消息有不同的响应，如类别动物有“叫()”之动作，而类别鸡会“啼叫()”，类别狗则会“吠叫()”，则称之为多态。
根据维基百科上所解释，多态可定义为，“一种将不同的特殊行为和单个泛化记号相关联的能力”，是指计算机程序运行时，相同的消息可能会送给多个不同的类别之对象，而系统可依据对象所属类别，引发对应类别的方法，而有不同的行为。简单来说，所谓多态意指相同的消息给予不同的对象会引发不同的动作称之。&lt;/li>
&lt;/ul>
&lt;p>虚函数是动态多态，在运行时决定，只有在程序运行时才决定调用基类的还是子类的，系统会根据基类指针所指向的对象来决定要调用的函数。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>如何声明虚函数？
在声明前加上virtual关键字来声明虚函数。在父类中声明，在子类中做具体实现。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>什么是纯虚函数？
纯虚函数是在声明虚函数时赋值为0的函数，使用这种声明方式后，继承类必须对该虚函数做实现。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>虚函数是如何实现的？
通过虚表来实现的。每个对象里有虚表指针，指向虚表。虚表实质上是一个数组而不是什么链表。虚函数的地址按照声明顺序依次存放在虚表中。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>虚函数表是怎么实现的？
为了完成虚函数的功能，编译器（注，这就是为什么C++会被叫做面向编译器编程的原因）会对每一个内部成员声明了虚函数的类创建一个表，称为vtable。在vtable中，以声明顺序放置特定类型的虚函数地址。每一个带有虚函数类的内部都会放置一枚指针，称为vpointer，或者叫vptr，指向这个vtable。每当发生对类的虚函数的调用时，编译器转为调用这个vptr指向的vtable中的函数，而不是静态的调用某个具体的函数。&lt;/p>
&lt;/li>
&lt;/ul></description></item><item><title>Linux popen执行bash命令并获取返回字符串</title><link>https://nansenli.com/zh-cn/post/jianshu/linux%E7%BC%96%E7%A8%8B/linux-popen%E6%89%A7%E8%A1%8Cbash%E5%91%BD%E4%BB%A4%E5%B9%B6%E8%8E%B7%E5%8F%96%E8%BF%94%E5%9B%9E%E5%AD%97%E7%AC%A6%E4%B8%B2/</link><pubDate>Wed, 08 Nov 2017 07:22:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/linux%E7%BC%96%E7%A8%8B/linux-popen%E6%89%A7%E8%A1%8Cbash%E5%91%BD%E4%BB%A4%E5%B9%B6%E8%8E%B7%E5%8F%96%E8%BF%94%E5%9B%9E%E5%AD%97%E7%AC%A6%E4%B8%B2/</guid><description>&lt;p>system命令返回值是个整型，很难判断具体情况是否正确。可以通过popen来执行bash命令，并获取返回值。注意popen获取的字符串返回值是标准输出，如需获取错误输出内容需要将错误输出重定向至标准输出中。&lt;/p>
&lt;pre tabindex="0">&lt;code>void system_cmd(const char * command, char * result)
{
FILE *fpRead;
fpRead = popen(command, &amp;#34;r&amp;#34;);
char buf[1024] = {0};
memset(buf,&amp;#39;\0&amp;#39;,sizeof(buf));
while(fgets(buf,1024-1,fpRead)!=NULL)
{
if (buf[strlen(buf) - 1] == &amp;#39;\n&amp;#39;) {
buf[strlen(buf) - 1] = &amp;#39;\0&amp;#39;; //去除换行符
}
strcpy(result, buf);
}
if(fpRead!=NULL)
pclose(fpRead);
}
&lt;/code>&lt;/pre>&lt;p>调用示例：&lt;/p>
&lt;pre tabindex="0">&lt;code> char pid[10]={0};
system_cmd(&amp;#34;pgrep bash&amp;#34;, pid);
&lt;/code>&lt;/pre></description></item><item><title>Deepin 安装 GPU版 Tensorflow 要点</title><link>https://nansenli.com/zh-cn/post/jianshu/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/deepin-%E5%AE%89%E8%A3%85-gpu%E7%89%88-tensorflow-%E8%A6%81%E7%82%B9/</link><pubDate>Wed, 13 Sep 2017 10:44:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/deepin-%E5%AE%89%E8%A3%85-gpu%E7%89%88-tensorflow-%E8%A6%81%E7%82%B9/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>虽然有很多教程都给出了如何在Ubuntu上安装Tensorflow，但是却没有一篇文章讲如何在Deepin系统安装Tensorflow，这里将安装过程的几个要点讲解一下&lt;/p>
&lt;h2 id="流程">流程
&lt;/h2>&lt;ul>
&lt;li>首先下载Nvidia的Linux驱动。在官网可以下载。然后运行，如果不能运行，则需要修改驱动运行权限，使用chmod u+x来添加运行权限。关于deepin安装显卡驱动，可以参考这个文章 &lt;a class="link" href="https://wiki.deepin.org/index.php?title=%E6%98%BE%E5%8D%A1" target="_blank" rel="noopener"
>https://wiki.deepin.org/index.php?title=%E6%98%BE%E5%8D%A1&lt;/a>&lt;/li>
&lt;li>安装Docker-ce。这里一定要遵循官方的安装方案，只此一个方法可以正常安装最新版docker。https://wiki.deepin.org/index.php?title=Docker&lt;/li>
&lt;li>下载Nvidia-docker。在Github的Nvidia-Docker页面找到Ubuntu的安装包和安装步骤，安装即可&lt;/li>
&lt;li>接下来运行 &lt;code>docker run -it -p 8888:8888 tensorflow/tensorflow:latest-gpu &lt;/code> 此时可以进入官方的tensorflow带GPU版本的镜像啦，打开localhost:8888可以看到jupyter的页面&lt;/li>
&lt;/ul>
&lt;h2 id="坑">坑
&lt;/h2>&lt;p>在安装Nvidia显卡驱动的时候，根据安装软件的提示，需要关闭Linux的桌面系统X server，此时可以通过tty1~6来进行终端操作。但是如果Deepin已经安装了自带的Nvidia驱动，要停止lightdm服务时，在Deepin在关闭X server的同时，显示器也关闭了，无法显示tty1 ~6的屏幕，由于这个问题，导致Nvidia驱动无法安装。即使手动卸载安装的Deepin官方Nvidia驱动，经过了繁琐的卸载指令，在切换到tty1 ~6后，在关闭图形界面的时候，仍然会导致tty屏幕被关闭，黑屏一片，无法继续后面的驱动卸载和重新安装官方Nvidia驱动。&lt;/p>
&lt;h2 id="解决方案">解决方案
&lt;/h2>&lt;p>为了避免Nvidia驱动无法安装，又能简洁科学的操作，所以必须在第一次安装Deepin系统的时候，在未安装显卡驱动的情况下对电脑进行操作进行安装Nvidia显卡官方linux驱动，首先按下快捷键“Ctrl+Alt+F2”，进入tty2，然后输入&lt;code>sudo systemctl stop lightdm&lt;/code> 停止lightdm服务，此时电脑关闭X server的时候不会导致显示器的关闭，之后运行&lt;code>chmod u+x NVIDIA-Linux-x86_64-352.55.run&lt;/code> #赋予可执行权限 以及&lt;code>sudo ./NVIDIA-Linux-x86_64-352.55.run&lt;/code> #安装驱动文件 然后重启就可以正常的使用官方闭源驱动啦。&lt;/p>
&lt;h2 id="后续">后续
&lt;/h2>&lt;p>使用GPU来运行Tensorflow程序是非常快的。一般的程序可以提速10倍以上，有些程序可以提速50到100倍的速度，所以使用GPU来进行Tensorflow编程是非常有必要的。
另外，不知道是Deepin的问题还是Nvidia-Dcoker的问题还是驱动的问题，在电脑待机之后会导致Tensorflow镜像出错，所以在训练的时候不要让机器进行待机动作。&lt;/p></description></item><item><title>TensorFlow 一个简单的入门用例</title><link>https://nansenli.com/zh-cn/post/jianshu/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/tensorflow-%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E5%85%A5%E9%97%A8%E7%94%A8%E4%BE%8B/</link><pubDate>Wed, 09 Aug 2017 07:08:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/tensorflow-%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E5%85%A5%E9%97%A8%E7%94%A8%E4%BE%8B/</guid><description>&lt;h2 id="案例">案例
&lt;/h2>&lt;p>假如我有一系列的数据x-y，x-y之间是呈线性关系的，如果我们需要用一条直线拟合这条直线，我们该如何做？&lt;/p>
&lt;p>下面的程序中，train_x是一系列从-1到1之间的数字，train_y是x的两倍加10，然后随机加了一个0~1之间的数字
接下来我们建立模型，X和Y是一个待初始化的张量占位符，在优化过程中，模型中的XY会一直改变为train_x和train_y中的数据，然后优化器会进行优化，使得w斜率和b截距按照使误差减小的方向变化，这样，w和b经过迭代最终就使模型满足数据了&lt;/p>
&lt;p>模型建立好了以后，我们开始运行模型。首先打开一个会话，然后一定要记得初始化全部变量。接下来我们对全体数据进行10次遍历，在每一次迭代过程中，将一个坐标输入，并计算误差，然后利用梯度下降更正w和b。最后，我们输出解出来的w和b。&lt;/p>
&lt;pre tabindex="0">&lt;code>import tensorflow as tf
import numpy as np
train_x = np.linspace(-1, 1, 101)
train_y = 2 * train_x + np.random.rand(train_x.shape[0]) + 10
X = tf.placeholder(&amp;#34;float&amp;#34;)
Y = tf.placeholder(&amp;#34;float&amp;#34;)
w = tf.Variable(0.0, name = &amp;#34;w&amp;#34;)
b = tf.Variable(0.0, name = &amp;#34;b&amp;#34;)
loss = tf.square(Y - tf.multiply(X,w) - b)
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(loss)
with tf.Session() as session:
session.run(tf.global_variables_initializer())
for i in range(10):
for x,y in zip(train_x, train_y):
session.run(train_op, feed_dict={X:x, Y:y})
print(&amp;#34;w: &amp;#34;, session.run(w))
print(&amp;#34;b: &amp;#34;, session.run(b))
&lt;/code>&lt;/pre>&lt;p>运行效果：
&lt;img src="http://otwwkzjm5.bkt.clouddn.com/17-8-9/99539186.jpg"
loading="lazy"
alt="效果"
>&lt;/p></description></item><item><title>TensorFlow Hello World 入门</title><link>https://nansenli.com/zh-cn/post/jianshu/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/tensorflow-hello-world-%E5%85%A5%E9%97%A8/</link><pubDate>Tue, 08 Aug 2017 09:58:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/tensorflow-hello-world-%E5%85%A5%E9%97%A8/</guid><description>&lt;h2 id="快速安装教程">快速安装教程
&lt;/h2>&lt;ul>
&lt;li>一、安装docker&lt;/li>
&lt;/ul>
&lt;p>&lt;a class="link" href="https://www.docker-cn.com/community-edition#/download" target="_blank" rel="noopener"
>https://www.docker-cn.com/community-edition#/download&lt;/a>&lt;/p>
&lt;p>然后配置官方中国镜像。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/5832fec39b1c.jpg"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;ul>
&lt;li>二、tensorflow环境搭建&lt;/li>
&lt;/ul>
&lt;p>&lt;code>$ docker run -it -p 8888:8888 tensorflow/tensorflow&lt;/code>
运行上述命令会自动下载tensorflow镜像，前提是仓库镜像设置成中国的镜像，否则下载很慢。运行命令后，命令行会出现一个网址，会提示你打开网页，打开这个网址以后会显示tensorflow的jupyter编辑环境，之后我们将在网页中输入所有代码。&lt;/p>
&lt;ul>
&lt;li>挂载docker的文件目录
如果我们需要打开本地文件，需要挂载本地的文件夹到容器目录中。关闭容器，重新打开容器，使用&lt;code>-v 主机目录:容器目录&lt;/code>来进行挂载。
&lt;code>docker run -v /Users/hahaha/tensorflow/:/notebooks -it -p 8888:8888 tensorflow/tensorflow&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>其中/Users/hahaha/tensorflow/是我的mac的一个文件夹，根目录下的notebooks是tensorflow中的jupyter默认编辑目录。&lt;/p>
&lt;h2 id="运行hello-world-代码">运行hello world 代码
&lt;/h2>&lt;p>&lt;img src="https://nansenli.com/img/rescued/5f59450b005f.png"
loading="lazy"
alt="image.png"
>
新建一个Python2 Jupyter文件，在文件中输入如下的代码，然后点击播放按钮，此时在下方应该会出现一段Hello, TensorFlow!字符串。说明程序运行成功了。&lt;/p>
&lt;h2 id="程序解释">程序解释
&lt;/h2>&lt;p>从这段简单的代码可以了解到TensorFlow的使用非常方便，通过Python标准库的形式导入，不需要启动额外的服务。第一次接触TensorFlow可能比较疑惑，输出一段helloworld Python本身可以实现，为什么要使用tf.constant()和tf.Session()呢？其实TensorFlow通过Graph和Session来定义运行的模型和训练，这在复杂的模型和分布式训练上有非常大好处。
首先，在TensorFlow中，有Graph和Operation这两个概念。Operation代表需要计算的内容。一个Graph中有很多Operation。通过Session来执行Graph中的Operation。&lt;/p>
&lt;h2 id="基本使用">基本使用
&lt;/h2>&lt;p>使用 TensorFlow, 你必须明白 TensorFlow:&lt;/p>
&lt;ul>
&lt;li>使用 &lt;code>图 (graph)&lt;/code> 来表示计算任务.&lt;/li>
&lt;li>在被称之为 &lt;code>会话 (Session)&lt;/code> 的 &lt;code>上下文 (context)&lt;/code> 中执行图.&lt;/li>
&lt;li>使用 &lt;code>tensor&lt;/code> 表示数据.&lt;/li>
&lt;li>通过 &lt;code>变量 (Variable)&lt;/code> 维护状态.
使用 &lt;code>feed&lt;/code> 和 &lt;code>fetch&lt;/code> 可以为&lt;code>任意的操作(arbitrary operation) &lt;/code>赋值或者从其中获取数据.&lt;/li>
&lt;/ul>
&lt;h2 id="综述">综述
&lt;/h2>&lt;p>TensorFlow 是一个编程系统, 使用图来表示计算任务. 图中的节点被称之为 &lt;strong>op&lt;/strong> (operation 的缩写)。 一个 op 使用 0 个或多个 Tensor ，执行计算产生0个或多个 Tensor。 每个 Tensor 是一个类型化的多维数组。 例如，你可以将一小组图像集表示为一个四维浮点数数组，这四个维度分别是 [batch, height, width, channels]。&lt;/p>
&lt;p>一个 TensorFlow 图 &lt;strong>描述&lt;/strong> 了计算的过程。 为了进行计算，图必须在&lt;code>会话&lt;/code> 里被启动。 &lt;code>会话&lt;/code> 将图的 op 分发到诸如 CPU 或 GPU 之类的设备上，同时提供执行 op 的方法。这些方法执行后， 将产生的 tensor 返回。 在 Python 语言中, 返回的类型是 tensor 是&lt;a class="link" href="http://www.numpy.org/" target="_blank" rel="noopener"
>numpy&lt;/a>中的&lt;code>ndarray&lt;/code>对象。在 C 和 C++ 语言中, 返回的 tensor 是 tensorflow::Tensor
 实例。&lt;/p></description></item><item><title>使用Tensorflow完成Kaggle任务——泰坦尼克号Titanic: Machine Learning from Disaster</title><link>https://nansenli.com/zh-cn/post/jianshu/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/%E4%BD%BF%E7%94%A8tensorflow%E5%AE%8C%E6%88%90kaggle%E4%BB%BB%E5%8A%A1%E6%B3%B0%E5%9D%A6%E5%B0%BC%E5%85%8B%E5%8F%B7titanic--machine-learning-from-disaster/</link><pubDate>Fri, 04 Aug 2017 10:08:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/%E4%BD%BF%E7%94%A8tensorflow%E5%AE%8C%E6%88%90kaggle%E4%BB%BB%E5%8A%A1%E6%B3%B0%E5%9D%A6%E5%B0%BC%E5%85%8B%E5%8F%B7titanic--machine-learning-from-disaster/</guid><description>&lt;h2 id="引入必要库">引入必要库
&lt;/h2>&lt;pre tabindex="0">&lt;code>import csv
import tensorflow as tf
import numpy as np
import random
import sys
import pandas as pd
from pandas import DataFrame
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
&lt;/code>&lt;/pre>&lt;h2 id="读取源文件并打印">读取源文件并打印
&lt;/h2>&lt;p>在这部分，我们接触了基本的csv操作，并显示结果。
我们读入kaggle上下载的train.csv文件，并展示内容&lt;/p>
&lt;pre tabindex="0">&lt;code>trainFilePath = &amp;#39;./train.csv&amp;#39;
trainSize = 0
def testCSV(filePath):
with open(filePath, &amp;#39;rb&amp;#39;) as trainFile:
global trainSize
csvReader = csv.reader(trainFile)
dataList = [data for data in csvReader]
df = DataFrame(dataList[1:], columns=dataList[0])
trainSize = len(df)
print(df)
print(&amp;#34;trainSize&amp;#34;, trainSize)
testCSV(trainFilePath)
&lt;/code>&lt;/pre>&lt;h2 id="读取源文件并提取数据建立神经网络">读取源文件并提取数据，建立神经网络
&lt;/h2>&lt;p>在这部分，我们读取源文件中的性别，阶级，船费以及SibSp，用于拟合最终的生存概率
然后我们建立一个总共5层，中间3层的神经网络，神经元的个数分别是4-10-20-10-2。
然后运行读取函数。&lt;/p>
&lt;pre tabindex="0">&lt;code>def readTrainDataCSV(filePath):
global trainData, targetData, classifier
with open(filePath, &amp;#39;rb&amp;#39;) as trainFile:
csvReader = csv.reader(trainFile)
dataList = [data for data in csvReader]
dataSize = len(dataList) - 1
trainData = np.ndarray((dataSize, 4), dtype=np.float32)
targetData = np.ndarray((dataSize, 1), dtype=np.int32)
trainDataFrame = DataFrame(dataList[1:], columns=dataList[0])
trainDataFrame_fliter = trainDataFrame.loc[:,[&amp;#39;Pclass&amp;#39;,&amp;#39;Sex&amp;#39;,&amp;#39;SibSp&amp;#39;,&amp;#39;Fare&amp;#39;,&amp;#39;Survived&amp;#39;]]
for i in range(dataSize):
thisData = np.array(trainDataFrame_fliter.iloc[i])
Pclass,Sex,SibSp,Fare,Survived = thisData
Pclass = float(Pclass)
Sex = 0 if Sex == &amp;#39;female&amp;#39; else 1
SibSp = float(SibSp)
Fare = float(Fare)
Survived = int(Survived)
print(Pclass,Sex,SibSp,Fare,Survived)
trainData[i,:] = [Pclass,Sex,SibSp,Fare]
targetData[i,:] = [Survived]
print(thisData)
print(trainData)
print(targetData)
feature_columns = [tf.contrib.layers.real_valued_column(&amp;#34;&amp;#34;, dimension=4)]
classifier = tf.contrib.learn.DNNClassifier(feature_columns=feature_columns,
hidden_units=[10, 20, 10],
n_classes=2)
# model_dir=&amp;#34;/tmp/titanic_model&amp;#34;)
readTrainDataCSV(trainFilePath)
&lt;/code>&lt;/pre>&lt;h2 id="创建输入数据">创建输入数据
&lt;/h2>&lt;p>我们将训练数据和标签包装成一个二元组，并返回&lt;/p>
&lt;pre tabindex="0">&lt;code>def get_train_inputs():
x = tf.constant(trainData)
y = tf.constant(targetData)
print(x)
print(y)
return x, y
get_train_inputs()
&lt;/code>&lt;/pre>&lt;h2 id="训练数据">训练数据
&lt;/h2>&lt;p>我们开始训练神经网络&lt;/p>
&lt;pre tabindex="0">&lt;code>def train():
classifier.fit(input_fn=get_train_inputs, steps=2000)
train()
&lt;/code>&lt;/pre>&lt;h2 id="检查准确度">检查准确度
&lt;/h2>&lt;p>我们使用整个数据集来查看准确度。注意，我们应该使用验证集来完成这件事。但是由于我们只是用来演示，所以就算了&lt;/p>
&lt;pre tabindex="0">&lt;code>accuracy_score = classifier.evaluate(input_fn=get_train_inputs,
steps=1)[&amp;#34;accuracy&amp;#34;]
print(&amp;#34;accuracy:&amp;#34;,accuracy_score)
&lt;/code>&lt;/pre>&lt;h2 id="读入测试集并输出结果">读入测试集，并输出结果
&lt;/h2>&lt;p>在这一部分，我们将读入kaggle中的数据，并输出到文件中，最终提交官网&lt;/p>
&lt;pre tabindex="0">&lt;code>testFilePath = &amp;#39;./test.csv&amp;#39;
def readTestDataCSV(filePath):
global testData, PassengerIdStart
with open(filePath, &amp;#39;rb&amp;#39;) as testFile:
csvReader = csv.reader(testFile)
dataList = [data for data in csvReader]
dataSize = len(dataList)-1
trainDataFrame = DataFrame(dataList[1:], columns=dataList[0])
trainDataFrame_fliter = trainDataFrame.loc[:,[&amp;#39;Pclass&amp;#39;,&amp;#39;Sex&amp;#39;,&amp;#39;SibSp&amp;#39;,&amp;#39;Fare&amp;#39;]]
testData = np.ndarray((dataSize, 4), dtype=np.float32)
PassengerIdStart = trainDataFrame[&amp;#39;PassengerId&amp;#39;][0]
PassengerIdStart = int(PassengerIdStart)
print(&amp;#39;PassengerId&amp;#39;,PassengerIdStart)
for i in range(dataSize):
thisData = np.array(trainDataFrame_fliter.iloc[i])
Pclass,Sex,SibSp,Fare = thisData
Pclass = float(Pclass)
Sex = 0 if Sex == &amp;#39;female&amp;#39; else 1
SibSp = float(SibSp)
Fare = 0 if Fare==&amp;#39;&amp;#39; else float(Fare)
print(Pclass,Sex,SibSp,Fare)
testData[i,:] = [Pclass,Sex,SibSp,Fare]
print(thisData)
print(testData)
def testData_samples():
return testData
readTestDataCSV(testFilePath)
predictions = list(classifier.predict(input_fn=testData_samples))
print(predictions)
with open(&amp;#39;predictions.csv&amp;#39;, &amp;#39;wb&amp;#39;) as csvfile:
writer = csv.writer(csvfile, dialect=&amp;#39;excel&amp;#39;)
writer.writerow([&amp;#39;PassengerId&amp;#39;,&amp;#39;Survived&amp;#39;])
PassengerId = PassengerIdStart
for i in predictions:
writer.writerow([PassengerId, i])
PassengerId += 1
&lt;/code>&lt;/pre>&lt;p>最终在只使用了4个特征值的情况下，准确率有75%。接下来的目标是将其他数据进行利用。&lt;/p></description></item><item><title>基于Flask的个人博客搭建</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/%E5%9F%BA%E4%BA%8Eflask%E7%9A%84%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/</link><pubDate>Sun, 30 Jul 2017 17:38:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%BD%91%E7%BB%9C/%E5%9F%BA%E4%BA%8Eflask%E7%9A%84%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>源码可直接运行，支持markdown语法，支持邮箱注册，任何问题均可参考&lt;a class="link" href="https://book.douban.com/subject/26274202/" target="_blank" rel="noopener"
>《Flask Web开发：基于Python的Web应用开发实战》&lt;/a>。&lt;/p>
&lt;p>博客网址：http://www.unrealblue.cc
项目地址：https://github.com/linanwx/unrealblue-blog&lt;/p>
&lt;h2 id="效果预览">效果预览
&lt;/h2>&lt;p>&lt;img src="https://nansenli.com/img/rescued/a0027251f6b8.jpg"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/ee8b6a7c6286.jpg"
loading="lazy"
alt="markdown语法"
>&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/2482752767fe.jpg"
loading="lazy"
alt="登录"
>&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/a827700f36d3.jpg"
loading="lazy"
alt="首页"
>&lt;/p>
&lt;h2 id="部署过程">部署过程
&lt;/h2>&lt;p>首先为了验证程序功能，需要在本机上部署，之后使用nginx反向代理工具，可以将端口号暴露出去，这样其他人就可以在公网访问你的博客了，之后在服务器进行同样的步骤。具体过程如下。&lt;/p>
&lt;ol>
&lt;li>安装virtualenv python虚拟环境，&lt;code>pip install virtualenv&lt;/code> 或者 &lt;code>pip3 install virtualenv&lt;/code> 。然后使用virtualenv在一个合适的目录，例如在该项目下面建立venv环境&lt;code>virtualenv venv&lt;/code>，&lt;/li>
&lt;li>激活virtualenv环境，运行venv目录下的activate，&lt;code>. venv/bin/activate&lt;/code>， 注意点的位置，之后你会看到命令行前面多了(venv)这个标记。&lt;/li>
&lt;li>在虚拟环境下安装所有requirements.txt中的模块, &lt;code>pip3 install -r requirements.txt&lt;/code>。如果安装太慢，你可能需要配置pip的国内源。更改pip源的方法参见pip官方页面。&lt;/li>
&lt;li>导入坏境变量，在项目目录下创建文件 &lt;code>env&lt;/code> ，输入如下字段：&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;code>MAIL_USERNAME=email@example.com&lt;/code> (服务器发验证码用的邮箱，开启了smtp服务的邮箱账号，程序里默认使用qq邮箱，修改config文件可成其它类型邮箱)&lt;/li>
&lt;li>&lt;code>MAIL_PASSWORD=password&lt;/code> (上面邮箱的密码，注意qq邮箱使用特殊16位专用密码)&lt;/li>
&lt;li>&lt;code>FLASK_ADMIN=admin@example.com&lt;/code> (服务器运行后，使用该邮箱创建账号就是管理者)&lt;/li>
&lt;li>&lt;code>MAIL_SERVER=smtp.qq.com&lt;/code>(邮箱服务器地址)&lt;/li>
&lt;li>&lt;code>FLASKY_MAIL_SENDER=example@foxmail.com&lt;/code>(发送人)&lt;/li>
&lt;/ul>
&lt;ol start="5">
&lt;li>安装数据库迁移。输入以下命令&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;code>python manager.py db init&lt;/code> (使用init命令创建迁移仓库，此时会多出 migrations 文件夹)&lt;/li>
&lt;li>&lt;code>python manager.py db migrate -m &amp;quot;initial migration&amp;quot;&lt;/code>(migrate命令用来自动创建迁移脚本)&lt;/li>
&lt;li>&lt;code>python manager.py db upgrade&lt;/code>(更新数据库，第一次使用该命令会新建一个数据库data-dev.sqlite)&lt;/li>
&lt;/ul>
&lt;ol start="6">
&lt;li>部署程序， &lt;code>python manager.py deploy&lt;/code>&lt;/li>
&lt;li>在本地运行程序, &lt;code>python manager.py runserver&lt;/code>打开 http://127.0.0.1:5000 端口查看，按Ctrl+C退出程序。&lt;/li>
&lt;li>如果在服务器运行，要保留数据，则可以复制数据库 migrations 文件夹以及数据库 data-dev.sqlite 到服务器，然后使用 &lt;code>./venv/bin/gunicorn -w 4 -b 127.0.0.1:8080 manager:app&lt;/code> 此时应该可以查看8080端口显示了网页，且该端口是暴露外网的。在本地浏览器输入服务器地址，此时就可以看到博客了。&lt;/li>
&lt;/ol></description></item><item><title>深度学习零基础学习笔记一（优达学城）</title><link>https://nansenli.com/zh-cn/post/jianshu/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E9%9B%B6%E5%9F%BA%E7%A1%80%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B8%80%E4%BC%98%E8%BE%BE%E5%AD%A6%E5%9F%8E/</link><pubDate>Wed, 19 Jul 2017 07:42:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E9%9B%B6%E5%9F%BA%E7%A1%80%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E4%B8%80%E4%BC%98%E8%BE%BE%E5%AD%A6%E5%9F%8E/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>突发奇想想学机器学习，这里是学习过程的笔记&lt;/p>
&lt;h2 id="准备">准备
&lt;/h2>&lt;p>我做了这些准备工作&lt;/p>
&lt;ul>
&lt;li>MacBook 一台，搭建好Python环境，安装numpy和matplotlib&lt;/li>
&lt;li>优达学城注册免费的《深度学习》课程（Google合作）&lt;/li>
&lt;li>廖雪峰Python入门教程学习&lt;/li>
&lt;li>花费两天时间大致浏览《机器学习实战》&lt;/li>
&lt;/ul>
&lt;p>学习这些知识应该足以进行接下来的优达学城的学习&lt;/p>
&lt;h2 id="课程一-从机器学习到深度学习">课程一 从机器学习到深度学习
&lt;/h2>&lt;p>&lt;img src="https://nansenli.com/img/rescued/71c43f961173.png"
loading="lazy"
alt="前言"
>&lt;/p>
&lt;p>小节1-8，主要介绍了深度学习的发展现状等等知识。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/ff643a3a1ec4.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>小节9-12介绍了softmax模型。&lt;/p>
&lt;p>粗略浏览机器学习实战后，在机器学习实战这本书中，大致介绍了机器学习的几种算法。从表面上来看，机器学习是一些分类和聚类算法。在这些算法中，介绍了一种算法，叫做逻辑回归分类。&lt;/p>
&lt;p>在小节9-12中，主要介绍了分类器模型——逻辑回归，分类函数使用的是softmax函数。&lt;/p>
&lt;ul>
&lt;li>什么是softmax函数&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/cb4b58f90872.jpg"
loading="lazy"
alt="softmax"
>&lt;/p>
&lt;p>这张图片可以表明什么是softmax函数了。对原来数列中的每个数z求exp(z)，新数的大小所占的比例就是新数的softmax概率。&lt;/p>
&lt;ul>
&lt;li>性质&lt;/li>
&lt;/ul>
&lt;p>如果输入同比例扩大，则分类器的结果越两极化，越自信，如果输入同比例缩小，分类器结果趋于平均，不自信。&lt;/p>
&lt;ul>
&lt;li>算法&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> numpy &lt;span style="color:#66d9ef">as&lt;/span> np
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">softmax&lt;/span>(x):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;Compute softmax values for each sets of scores in x.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expList &lt;span style="color:#f92672">=&lt;/span> [np&lt;span style="color:#f92672">.&lt;/span>exp(i) &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> x]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expSum &lt;span style="color:#f92672">=&lt;/span> sum(expList)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x &lt;span style="color:#f92672">=&lt;/span> [i&lt;span style="color:#f92672">/&lt;/span>expSum &lt;span style="color:#66d9ef">for&lt;/span> i &lt;span style="color:#f92672">in&lt;/span> expList]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>array(x)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="https://nansenli.com/img/rescued/8ba75ea46168.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>13-14节主要讲One-Hot编码。在softmax函数给出一组概率数列之后，如何确定分类呢？例如概率最高的为1，其他的为0，这样的一个数列，属于One-Hot编码。这种编码是已经确定了分类。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/d0050afa0519.png"
loading="lazy"
alt="交叉熵"
>&lt;/p>
&lt;p>15-16节讲了交叉熵。softmax可以计算概率数列，OneHot是已经确定的分类，那如何计算概率数列到某个分类的距离呢？使用交叉熵来度量这个距离。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/7a800c91612a.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/a1e862b4bd40.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>17-20 节讲解了如何使用这个分类器。其中，18节讲了为什么需要采用一种特殊的初始数据。&lt;/p>
&lt;pre tabindex="0">&lt;code>sum = 1000000000
for i in range(1000000):
sum += 0.000001
sum -= 1000000000
print(sum)
&lt;/code>&lt;/pre>&lt;p>这段代码运行结果不是1。如果把sum换成一个很小的数字，例如1，而不是1000000000，我们发现结果误差变小了。基于这个原因，我们希望初始数据总是均值为0，并且各个方向的方差为一致的。例如一个灰度图片的像素值0-255，我们需要把它减去128，然后除以128，这样每一个数字都是-1到1之间的数字，这样的初始数据更适合用来训练。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/34f543ea3c1e.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>这样，我们就可以进行训练了。回顾一下视频内容，xi是训练数据矩阵，w是随机权重矩阵，为了性能，随机值取自正态分布中轴为0，方差很小的分布函数，然后计算概率数列，和目标的距离。然后求出到所有目标的平均距离。我们的目的是让距离变小，所以我们沿着梯度下降的方向优化权重矩阵，同时优化截距b。不断重复这一个过程，直到局部最优为止。&lt;/p>
&lt;ul>
&lt;li>安装docker&lt;/li>
&lt;/ul>
&lt;p>&lt;a class="link" href="https://www.docker-cn.com/community-edition#/download" target="_blank" rel="noopener"
>https://www.docker-cn.com/community-edition#/download&lt;/a>&lt;/p>
&lt;p>配置官方中国镜像。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/1a9fbd212a79.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;ul>
&lt;li>安装jupyter notebook&lt;/li>
&lt;/ul>
&lt;p>&lt;code>$ pip3 install jupyter&lt;/code>
&lt;code>$ jupyter notebook&lt;/code>
此时可以使用命令jupyter notebook打开一个jupyter编辑器&lt;/p>
&lt;ul>
&lt;li>tensorflow环境搭建&lt;/li>
&lt;/ul>
&lt;p>&lt;code>$ docker run -it -p 8888:8888 tensorflow/tensorflow&lt;/code>
运行上述命令会自动下载tensorflow镜像，前提是仓库镜像设置成中国的镜像，否则下载很慢。运行命令后，会提示你打开网页，打开这个网址以后会显示tensorflow的jupyter编辑环境，前提是jupyter notebook安装正确&lt;/p>
&lt;ul>
&lt;li>挂载docker的文件目录
我们需要把官方的作业导进去。关闭容器，重新打开容器，使用&lt;code>-v 主机目录:容器目录&lt;/code>来进行挂载。
&lt;code>docker run -v /Users/hahaha/tensorflow/:/notebooks -it -p 8888:8888 tensorflow/tensorflow&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>其中/Users/hahaha/tensorflow/是我的mac的一个文件夹，notebooks是tensorflow中的jupyter默认编辑目录。&lt;/p>
&lt;p>在主机目录的挂载目录下面粘贴第一个作业文件，1_notmnist.ipynb。这个文件可以在这里找到： &lt;a class="link" href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/udacity/1_notmnist.ipynb" target="_blank" rel="noopener"
>1_notmnist.ipynb&lt;/a>&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/7faf184ae282.png"
loading="lazy"
alt="作业一内容"
>&lt;/p>
&lt;h2 id="作业代码段一">作业代码段一
&lt;/h2>&lt;p>首先运行一下第一段代码的import，应该是没有任何出错的，此时什么也不会发生，如果出现了红色的输出错误，那就说明这些from import没有导入成功。&lt;/p>
&lt;pre tabindex="0">&lt;code># These are all the modules we&amp;#39;ll be using later. Make sure you can import them
# before proceeding further.
from __future__ import print_function
# print函数
import matplotlib.pyplot as plt
# 绘图工具
import numpy as np
# 矩阵计算
import os
# 文件路径
import sys
# 文件输出
import tarfile
# 解压缩
from IPython.display import display, Image
# 显示图片
from scipy import ndimage
# 图像处理
from sklearn.linear_model import LogisticRegression
# 逻辑回归模块线性模型
from six.moves.urllib.request import urlretrieve
# url处理
from six.moves import cPickle as pickle
# 数据处理
# Config the matplotlib backend as plotting inline in IPython
%matplotlib inline
# matplotlib是最著名的Python图表绘制扩展库，
# 它支持输出多种格式的图形图像，并且可以使用多种GUI界面库交互式地显示图表。
# 使用%matplotlib命令可以将matplotlib的图表直接嵌入到Notebook之中，
# 或者使用指定的界面库显示图表，它有一个参数指定matplotlib图表的显示方式。
# inline表示将图表嵌入到Notebook中。
&lt;/code>&lt;/pre>&lt;h2 id="作业代码段二">作业代码段二
&lt;/h2>&lt;p>接下来是第二段代码，会进行下载用于训练和测试的字母集合，大概是300mb大小。下载成功后，可以看到挂载目录下面的这两个文件。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/66a1b044606a.png"
loading="lazy"
alt="作业"
>&lt;/p>
&lt;pre tabindex="0">&lt;code>url = &amp;#39;https://commondatastorage.googleapis.com/books1000/&amp;#39;
last_percent_reported = None
data_root = &amp;#39;.&amp;#39; # Change me to store data elsewhere
def download_progress_hook(count, blockSize, totalSize):
&amp;#34;&amp;#34;&amp;#34;A hook to report the progress of a download. This is mostly intended for users with
slow internet connections. Reports every 5% change in download progress.
&amp;#34;&amp;#34;&amp;#34;
# 钩子函数用来实时显示下载进度
global last_percent_reported
percent = int(count * blockSize * 100 / totalSize)
if last_percent_reported != percent:
if percent % 5 == 0:
sys.stdout.write(&amp;#34;%s%%&amp;#34; % percent)
sys.stdout.flush()
else:
sys.stdout.write(&amp;#34;.&amp;#34;)
sys.stdout.flush()
last_percent_reported = percent
def maybe_download(filename, expected_bytes, force=False):
&amp;#34;&amp;#34;&amp;#34;Download a file if not present, and make sure it&amp;#39;s the right size.&amp;#34;&amp;#34;&amp;#34;
dest_filename = os.path.join(data_root, filename)
# data_root是当前目录，在这个目录上加上文件名，设置为要保存的文件位置
if force or not os.path.exists(dest_filename):
# force是强制下载，忽略已经下载的文件
print(&amp;#39;Attempting to download:&amp;#39;, filename)
filename, _ = urlretrieve(url + filename, dest_filename, reporthook=download_progress_hook)
# 使用urlretrieve来下载文件，挂上钩子
print(&amp;#39;\nDownload Complete!&amp;#39;)
statinfo = os.stat(dest_filename)
# 获取下载到的文件的信息
if statinfo.st_size == expected_bytes:
# 正确大小
print(&amp;#39;Found and verified&amp;#39;, dest_filename)
else:
# 错误大小，提示用户使用浏览器下载
raise Exception(
&amp;#39;Failed to verify &amp;#39; + dest_filename + &amp;#39;. Can you get to it with a browser?&amp;#39;)
return dest_filename
train_filename = maybe_download(&amp;#39;notMNIST_large.tar.gz&amp;#39;, 247336696)
test_filename = maybe_download(&amp;#39;notMNIST_small.tar.gz&amp;#39;, 8458043)
&lt;/code>&lt;/pre>&lt;h2 id="作业代码段三">作业代码段三
&lt;/h2>&lt;p>解压缩用例&lt;/p>
&lt;pre tabindex="0">&lt;code>num_classes = 10
# 数字总共有多少个
np.random.seed(133)
# 初始化随机种子
def maybe_extract(filename, force=False):
# 假设已经解压缩了
root = os.path.splitext(os.path.splitext(filename)[0])[0] # remove .tar.gz
# splitext(filename)[0]用于去除一个后缀，用两次就是去除两次后缀，也就是去除.tar.gz这个后缀
if os.path.isdir(root) and not force:
# You may override by setting force=True.
# 已经解压缩了就不再解压缩了
print(&amp;#39;%s already present - Skipping extraction of %s.&amp;#39; % (root, filename))
else:
print(&amp;#39;Extracting data for %s. This may take a while. Please wait.&amp;#39; % root)
tar = tarfile.open(filename)
sys.stdout.flush()
tar.extractall(data_root)
tar.close()
# 解压缩到当前目录下面
data_folders = [
os.path.join(root, d) for d in sorted(os.listdir(root))
if os.path.isdir(os.path.join(root, d))]
if len(data_folders) != num_classes:
raise Exception(
&amp;#39;Expected %d folders, one per class. Found %d instead.&amp;#39; % (
num_classes, len(data_folders)))
print(data_folders)
# 检查解压缩文件目录的数量与期待是否一致，并且打印解压缩出来文件的目录
return data_folders
train_folders = maybe_extract(train_filename)
test_folders = maybe_extract(test_filename)
&lt;/code>&lt;/pre>&lt;h2 id="问题一">问题一
&lt;/h2>&lt;p>写出代码显示解压缩的文件内容信息&lt;/p>
&lt;ul>
&lt;li>参考答案&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>import random
import matplotlib.image as mpimg
def plot_samples(data_folders, sample_size, title=None):
fig = plt.figure()
# 建立空图像
if title: fig.suptitle(title, fontsize=16, fontweight=&amp;#39;bold&amp;#39;)
# 加入标题
for folder in data_folders:
# 遍历每个字母
image_files = os.listdir(folder)
image_sample = random.sample(image_files, sample_size)
# 从该字母中随机选取一定数量的图片
for image in image_sample:
image_file = os.path.join(folder, image)
ax = fig.add_subplot(len(data_folders), sample_size, sample_size * data_folders.index(folder) +
image_sample.index(image) + 1)
# 创建一个子图
image = mpimg.imread(image_file)
# 加载子图图片
ax.imshow(image)
# 显示子图图片
ax.set_axis_off()
# 关闭子图坐标线
fig.set_size_inches(18.5, 10.5)
# 设置图片显示的大小
plt.show()
plot_samples(train_folders, 20, &amp;#39;Train&amp;#39;)
plot_samples(test_folders, 20, &amp;#39;Test&amp;#39;)
&lt;/code>&lt;/pre>&lt;p>运行效果如下&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/94887f5b6eab.png"
loading="lazy"
alt="训练.png"
>&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/3930a2cfe6b0.png"
loading="lazy"
alt="测试.png"
>&lt;/p>
&lt;p>可以看出，部分训练数据是有问题的&lt;/p>
&lt;h2 id="作业代码段四">作业代码段四
&lt;/h2>&lt;p>这之后需要进行数据的归一化处理，就是让图像的每一个像素由0&lt;del>255变换到-1.0&lt;/del>1.0，并且持久化到文件中&lt;/p>
&lt;pre tabindex="0">&lt;code>image_size = 28 # Pixel width and height.
pixel_depth = 255.0 # Number of levels per pixel.
# 图片长宽和图片像素深度
def load_letter(folder, min_num_images):
&amp;#34;&amp;#34;&amp;#34;Load the data for a single letter label.&amp;#34;&amp;#34;&amp;#34;
# 处理一个属于一个字母文件夹下面的文件
image_files = os.listdir(folder)
# 列出该文件夹目录下面的所有文件
dataset = np.ndarray(shape=(len(image_files), image_size, image_size),
dtype=np.float32)
# 创建一个长度为文件个数，宽度和高度为28的
print(folder)
# 打印目录
num_images = 0
# 初始化num_images
for image in image_files:
# 对每一个文件处理
image_file = os.path.join(folder, image)
# 获取完整文件路径
try:
image_data = (ndimage.imread(image_file).astype(float) -
pixel_depth / 2) / pixel_depth
# 读入图像，并且归一化处理
if image_data.shape != (image_size, image_size):
# 检查图像的宽高
raise Exception(&amp;#39;Unexpected image shape: %s&amp;#39; % str(image_data.shape))
dataset[num_images, :, :] = image_data
# 读入到数据集合中
num_images = num_images + 1
# 图片序号加一
except IOError as e:
# 如果无法读取文件的话，则忽略该文件
print(&amp;#39;Could not read:&amp;#39;, image_file, &amp;#39;:&amp;#39;, e, &amp;#39;- it\&amp;#39;s ok, skipping.&amp;#39;)
dataset = dataset[0:num_images, :, :]
# 如果读进来的文件数量少于最小需要文件数量
if num_images &amp;lt; min_num_images:
raise Exception(&amp;#39;Many fewer images than expected: %d &amp;lt; %d&amp;#39; %
(num_images, min_num_images))
# 显示缺少的文件数量
print(&amp;#39;Full dataset tensor:&amp;#39;, dataset.shape)
# 显示文件数量，图片长宽
print(&amp;#39;Mean:&amp;#39;, np.mean(dataset))
# 平均值
print(&amp;#39;Standard deviation:&amp;#39;, np.std(dataset))
# 标准差
return dataset
def maybe_pickle(data_folders, min_num_images_per_class, force=False):
dataset_names = []
for folder in data_folders:
# 对每一个字母文件夹处理
set_filename = folder + &amp;#39;.pickle&amp;#39;
# 设置输出的文件
dataset_names.append(set_filename)
# 设置处理过的文件夹
if os.path.exists(set_filename) and not force:
# You may override by setting force=True.
# 检查是否存在已处理过的文件
print(&amp;#39;%s already present - Skipping pickling.&amp;#39; % set_filename)
else:
print(&amp;#39;Pickling %s.&amp;#39; % set_filename)
dataset = load_letter(folder, min_num_images_per_class)
# 归一化处理这个文件夹下面的所有图片
try:
with open(set_filename, &amp;#39;wb&amp;#39;) as f:
pickle.dump(dataset, f, pickle.HIGHEST_PROTOCOL)
# 持久化数据，将数据保存在硬盘上，而不是一直放在内存中
except Exception as e:
print(&amp;#39;Unable to save data to&amp;#39;, set_filename, &amp;#39;:&amp;#39;, e)
return dataset_names
train_datasets = maybe_pickle(train_folders, 45000)
test_datasets = maybe_pickle(test_folders, 1800)
&lt;/code>&lt;/pre>&lt;h2 id="问题2">问题2
&lt;/h2>&lt;p>显示处理过的图片&lt;/p>
&lt;ul>
&lt;li>参考答案&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>def plot_samples_2(data_folders, sample_size, title=None):
fig = plt.figure()
# 建立空图像
if title: fig.suptitle(title, fontsize=16, fontweight=&amp;#39;bold&amp;#39;)
# 加入标题
for folder in data_folders:
# 遍历每个字母
with open(folder, &amp;#39;rb&amp;#39;) as pk_f:
data = pickle.load(pk_f)
for index, image in enumerate(data):
if index &amp;lt; sample_size :
# 从该字母中随机选取一定数量的图片
ax = fig.add_subplot(len(data_folders), sample_size, sample_size * data_folders.index(folder) +
index + 1)
# 加载子图图片
ax.imshow(image)
# 显示子图图片
ax.set_axis_off()
# 关闭子图坐标线
fig.set_size_inches(18.5, 10.5)
# 设置图片显示的大小
plt.show()
plot_samples_2(train_datasets, 20, &amp;#39;Train&amp;#39;)
plot_samples_2(test_datasets, 20, &amp;#39;Test&amp;#39;)
&lt;/code>&lt;/pre>&lt;p>&lt;img src="https://nansenli.com/img/rescued/c09ea5ab69e9.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/d4363496653b.png"
loading="lazy"
alt="image.png"
>&lt;/p>
&lt;h2 id="问题3">问题3
&lt;/h2>&lt;p>检查每个字母下面的文件数目是否相似。&lt;/p>
&lt;ul>
&lt;li>参考答案&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>file_path = &amp;#39;notMNIST_large/{0}.pickle&amp;#39;
for ele in &amp;#39;ABCDEFJHIJ&amp;#39;:
with open(file_path.format(ele), &amp;#39;rb&amp;#39;) as pk_f:
# 遍历每一个目录
dat = pickle.load(pk_f)
# 加载这个目录下面的持久化文件
print(&amp;#39;number of pictures in {}.pickle = &amp;#39;.format(ele), dat.shape[0])
# 打印相关信息
&lt;/code>&lt;/pre>&lt;p>结果表明数目基本一致。
&lt;img src="https://nansenli.com/img/rescued/31bcb36f8c25.png"
loading="lazy"
alt="问题3效果"
>&lt;/p>
&lt;h2 id="代码段数据分割">代码段——数据分割
&lt;/h2>&lt;p>数据不可能一次性就全部加载到内存中，这里对这些数据进行分割，接下来的这份代码对数据进行了分割&lt;/p>
&lt;pre tabindex="0">&lt;code>def make_arrays(nb_rows, img_size):
if nb_rows:
dataset = np.ndarray((nb_rows, img_size, img_size), dtype=np.float32)
# 创建一个空集合，数据类型是长rows宽img_size高img_size的矩阵，数据类型是浮点32位
labels = np.ndarray(nb_rows, dtype=np.int32)
# 创建一个标签，数据类型是32位整型，长度是rows
else:
dataset, labels = None, None
return dataset, labels
# 返回创建的数据类型
def merge_datasets(pickle_files, train_size, valid_size=0):
num_classes = len(pickle_files)
# 需要处理的类别数量
valid_dataset, valid_labels = make_arrays(valid_size, image_size)
# 建立有效数据集合，长度为有效长度
train_dataset, train_labels = make_arrays(train_size, image_size)
# 建立训练数据集合，长度为训练长度
vsize_per_class = valid_size // num_classes
tsize_per_class = train_size // num_classes
# 计算给定训练长度和有效长度下每个类别的平均长度
start_v, start_t = 0, 0
# 初始化下标，start_v是有效数据的开始，start_t是训练数据的开始
end_v, end_t = vsize_per_class, tsize_per_class
# 初始化下标，end_v是有效数据的结束，end_t是训练数据的结束
end_l = vsize_per_class + tsize_per_class
# 初始化下标，end_l是字母集合的结束，等于每个类别有效数据的长度+训练数据的长度
for label, pickle_file in enumerate(pickle_files):
# 遍历每一个pickle_file
try:
with open(pickle_file, &amp;#39;rb&amp;#39;) as f:
# 打开这个持久化文件
letter_set = pickle.load(f)
# 加载数据集
# let&amp;#39;s shuffle the letters to have random validation and training set
np.random.shuffle(letter_set)
# 打乱数据集的顺序
if valid_dataset is not None:
# 如果不是测试集的话，更新测试集，否则 valid_dataset 不更新
valid_letter = letter_set[:vsize_per_class, :, :]
# numpy切片 http://brieflyx.me/2015/python-module/numpy-array-split/
# 从打乱的数据中选择 每类有效数据 数量的数据进行处理，放到 valid_letter 中
valid_dataset[start_v:end_v, :, :] = valid_letter
# 把这份数据放到valid_dataset中
valid_labels[start_v:end_v] = label
# 标记label 应该是 0~9中的一种
start_v += vsize_per_class
end_v += vsize_per_class
# 更新下标
# 循环结束时， valid_dataset 应该总长度为 valid_size 的一份数据， valid_labels是对应位置的标签
train_letter = letter_set[vsize_per_class:end_l, :, :]
# 除去valid部分的随机其他元素，长度为 end_l - vsize_per_class = tsize_per_class
train_dataset[start_t:end_t, :, :] = train_letter
# 循环结束时，train_dataset应该是总长为 train_size 的 一份数据
#
train_labels[start_t:end_t] = label
start_t += tsize_per_class
end_t += tsize_per_class
# 更新下标
except Exception as e:
print(&amp;#39;Unable to process data from&amp;#39;, pickle_file, &amp;#39;:&amp;#39;, e)
raise
return valid_dataset, valid_labels, train_dataset, train_labels
train_size = 200000
valid_size = 10000
test_size = 10000
valid_dataset, valid_labels, train_dataset, train_labels = merge_datasets(
train_datasets, train_size, valid_size)
_, _, test_dataset, test_labels = merge_datasets(test_datasets, test_size)
print(&amp;#39;Training:&amp;#39;, train_dataset.shape, train_labels.shape)
print(&amp;#39;Validation:&amp;#39;, valid_dataset.shape, valid_labels.shape)
print(&amp;#39;Testing:&amp;#39;, test_dataset.shape, test_labels.shape)
&lt;/code>&lt;/pre>&lt;h2 id="代码段打散数据">代码段——打散数据
&lt;/h2>&lt;p>permutation函数介绍：http://www.jianshu.com/p/f0eb10acaa2d&lt;/p>
&lt;pre tabindex="0">&lt;code>def randomize(dataset, labels):
# labels.shape[0] 是 labels 的长度
permutation = np.random.permutation(labels.shape[0])
# 随机取出这么多数字的打乱
print(labels.shape[0])
shuffled_dataset = dataset[permutation,:,:]
# 打乱数据
shuffled_labels = labels[permutation]
# 打乱标签
return shuffled_dataset, shuffled_labels
train_dataset, train_labels = randomize(train_dataset, train_labels)
test_dataset, test_labels = randomize(test_dataset, test_labels)
valid_dataset, valid_labels = randomize(valid_dataset, valid_labels)
&lt;/code>&lt;/pre>&lt;h2 id="问题4">问题4
&lt;/h2>&lt;p>检验打散后的数据是否正确&lt;/p>
&lt;ul>
&lt;li>参考答案&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>import random
def plot_sample_3(dataset, labels, title):
fig = plt.figure()
plt.suptitle(title, fontsize=16, fontweight=&amp;#39;bold&amp;#39;)
# 设置标题样式
items = random.sample(range(len(labels)), 200)
# 打散 labels 长的顺序序列
for i, item in enumerate(items):
# 随机取一个
plt.subplot(10, 20, i + 1)
# 画子图
plt.axis(&amp;#39;off&amp;#39;)
# 关闭坐标轴
plt.title(chr(ord(&amp;#39;A&amp;#39;) + labels[item]))
# 加标题
plt.imshow(dataset[item])
# 显示对应位置的子图
fig.set_size_inches(18.5, 10.5)
plt.show()
# 显示图片
plot_sample_3(train_dataset, train_labels, &amp;#39;train dataset suffled&amp;#39;)
plot_sample_3(valid_dataset, valid_labels, &amp;#39;valid dataset suffled&amp;#39;)
plot_sample_3(test_dataset, test_labels, &amp;#39;test dataset suffled&amp;#39;)
&lt;/code>&lt;/pre>&lt;p>&lt;img src="https://nansenli.com/img/rescued/2041fe44668c.png"
loading="lazy"
alt="问题4"
>&lt;/p>
&lt;p>省略类似的两图&lt;/p>
&lt;h2 id="代码段保存数据">代码段——保存数据
&lt;/h2>&lt;pre tabindex="0">&lt;code>pickle_file = os.path.join(data_root, &amp;#39;notMNIST.pickle&amp;#39;)
# 输出文件路径
try:
f = open(pickle_file, &amp;#39;wb&amp;#39;)
# 打开这个文件
save = {
&amp;#39;train_dataset&amp;#39;: train_dataset,
&amp;#39;train_labels&amp;#39;: train_labels,
&amp;#39;valid_dataset&amp;#39;: valid_dataset,
&amp;#39;valid_labels&amp;#39;: valid_labels,
&amp;#39;test_dataset&amp;#39;: test_dataset,
&amp;#39;test_labels&amp;#39;: test_labels,
}
# 写入一个字典 string-ndarray
pickle.dump(save, f, pickle.HIGHEST_PROTOCOL)
f.close()
except Exception as e:
print(&amp;#39;Unable to save data to&amp;#39;, pickle_file, &amp;#39;:&amp;#39;, e)
raise
&lt;/code>&lt;/pre>&lt;h2 id="代码段显示保存数据的大小">代码段——显示保存数据的大小
&lt;/h2>&lt;pre tabindex="0">&lt;code>statinfo = os.stat(pickle_file)
print(&amp;#39;Compressed pickle size:&amp;#39;, statinfo.st_size)
&lt;/code>&lt;/pre>&lt;h2 id="问题5">问题5
&lt;/h2>&lt;p>题目的Google翻译&lt;/p>
&lt;p>通过构建，此数据集可能包含大量重叠样本，包括验证和测试集中也包含的训练数据！ 训练和测试之间的重叠可能会使结果偏斜，如果您希望在没有重叠的环境中使用您的模型，但如果您希望在使用训练样本时再次看到训练样本，那么实际上是可以的。 测量培训，验证和测试样本之间的重叠程度。
可选问题：
数据集之间的重复数据怎么样？ （几乎相同的图像）
创建一个消毒验证和测试集，并比较您在随后的作业中的准确性。&lt;/p>
&lt;p>大概意思是训练数据不能和测试用的数据重合，否则导致准确度不准&lt;/p>
&lt;p>参考代码：&lt;/p>
&lt;ul>
&lt;li>仅仅查看重复的图片数量&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>import hashlib
pickle_file = os.path.join(&amp;#39;.&amp;#39;, &amp;#39;notMNIST.pickle&amp;#39;)
try:
with open(pickle_file, &amp;#39;rb&amp;#39;) as f:
data = pickle.load(f)
except Exception as e:
print(&amp;#39;Unable to open data from&amp;#39;, pickle_file, &amp;#39;:&amp;#39;, e)
raise
# 自从保存数据后，如果kernel挂了，就可以从本地直接读取，不用重新运行之前的代码
# 如果报错的话，可以在网上搜索报错的异常
def calcOverlap(sourceSet, targetSet, description):
sourceSetMd5 = np.array([hashlib.md5(img).hexdigest() for img in sourceSet])
# 建立一个md5表格
targetSetMd5 = np.array([hashlib.md5(img).hexdigest() for img in targetSet])
# 建立一个md5表格
overlap = np.intersect1d(sourceSetMd5, targetSetMd5, assume_unique=False)
# 去重
print(description)
print(&amp;#34;overlap&amp;#34;,overlap.shape[0], &amp;#34;from&amp;#34;,sourceSetMd5.shape[0],&amp;#34;to&amp;#34;, targetSetMd5.shape[0])
print(&amp;#34;rate&amp;#34;,overlap.shape[0]*100.0/sourceSetMd5.shape[0],&amp;#34;% and&amp;#34;, overlap.shape[0]*100.0/targetSetMd5.shape[0],&amp;#34;%&amp;#34;)
# 打印重叠数量
calcOverlap(data[&amp;#39;train_dataset&amp;#39;], data[&amp;#39;valid_dataset&amp;#39;], &amp;#34;train_dataset &amp;amp; valid_dataset&amp;#34;)
calcOverlap(data[&amp;#39;train_dataset&amp;#39;], data[&amp;#39;test_dataset&amp;#39;], &amp;#34;train_dataset &amp;amp; test_dataset&amp;#34;)
calcOverlap(data[&amp;#39;test_dataset&amp;#39;], data[&amp;#39;valid_dataset&amp;#39;], &amp;#34;test_dataset &amp;amp; valid_dataset&amp;#34;)```
![运行效果](/img/rescued/fc3c3db78e52.png)
- 去除重复图片资源
待更新
## 问题6
使用逻辑回归训练模型并且进行测试
- 参考代码
&lt;/code>&lt;/pre>&lt;p>import random
def disp_sample_dataset(dataset, labels,trueLabels, title=None):&lt;/p>
&lt;h1 id="展示训练的结果">展示训练的结果
&lt;/h1>&lt;pre>&lt;code>fig = plt.figure()
if title: fig.suptitle(title, fontsize=16, fontweight='bold')
&lt;/code>&lt;/pre>
&lt;h1 id="设置标题样式">设置标题样式
&lt;/h1>&lt;pre>&lt;code>items = random.sample(range(len(labels)), 200)
&lt;/code>&lt;/pre>
&lt;h1 id="随机选择一系列图片">随机选择一系列图片
&lt;/h1>&lt;pre>&lt;code>for i, item in enumerate(items):
plt.subplot(10, 20, i + 1)
&lt;/code>&lt;/pre>
&lt;h1 id="设置一个子图">设置一个子图
&lt;/h1>&lt;pre>&lt;code> plt.axis('off')
&lt;/code>&lt;/pre>
&lt;h1 id="关闭坐标线">关闭坐标线
&lt;/h1>&lt;pre>&lt;code> lab = str(chr(ord('A') + labels[item]))
trueLab = str(chr(ord('A') + trueLabels[item]))
if lab == trueLab:
plt.title( lab )
else:
plt.title(lab + &amp;quot; but &amp;quot; + trueLab)
&lt;/code>&lt;/pre>
&lt;h1 id="加上标题">加上标题
&lt;/h1>&lt;pre>&lt;code> plt.imshow(dataset[item])
&lt;/code>&lt;/pre>
&lt;h1 id="显示这个图片">显示这个图片
&lt;/h1>&lt;pre>&lt;code>fig.set_size_inches(18.5, 10.5)
plt.show()
&lt;/code>&lt;/pre>
&lt;p>def train_and_predict(train_dataset, train_labels, test_dataset, test_labels ,sample_size):
regr = LogisticRegression()&lt;/p>
&lt;h1 id="生成训练器">生成训练器
&lt;/h1>&lt;pre>&lt;code>X_train = train_dataset[:sample_size].reshape(sample_size, 784)
&lt;/code>&lt;/pre>
&lt;h1 id="根据sample_size选择要训练的数据量">根据sample_size选择要训练的数据量
&lt;/h1>&lt;h1 id="把二维向量压缩到一维向量">把二维向量压缩到一维向量
&lt;/h1>&lt;pre>&lt;code>y_train = train_labels[:sample_size]
&lt;/code>&lt;/pre>
&lt;h1 id="取出训练数据">取出训练数据
&lt;/h1>&lt;pre>&lt;code>regr.fit(X_train, y_train)
&lt;/code>&lt;/pre>
&lt;h1 id="训练数据">训练数据
&lt;/h1>&lt;pre>&lt;code>X_test = test_dataset.reshape(test_dataset.shape[0], 28 * 28)
&lt;/code>&lt;/pre>
&lt;h1 id="将测试数据压缩到一维向量">将测试数据压缩到一维向量
&lt;/h1>&lt;pre>&lt;code>y_test = test_labels
&lt;/code>&lt;/pre>
&lt;h1 id="测试数据所对应的真实标签">测试数据所对应的真实标签
&lt;/h1>&lt;pre>&lt;code>pred_labels = regr.predict(X_test)
&lt;/code>&lt;/pre>
&lt;h1 id="生成预测数据">生成预测数据
&lt;/h1>&lt;pre>&lt;code>print('Accuracy:', regr.score(X_test, y_test), 'when sample_size=', sample_size)
disp_sample_dataset(test_dataset, pred_labels, test_labels, 'sample_size=' + str(sample_size))
&lt;/code>&lt;/pre>
&lt;p>train_and_predict(data[&amp;rsquo;train_dataset&amp;rsquo;],data[&amp;rsquo;train_labels&amp;rsquo;],data[&amp;rsquo;test_dataset&amp;rsquo;],data[&amp;rsquo;test_labels&amp;rsquo;], 1000)&lt;/p>
&lt;pre tabindex="0">&lt;code>
![image.png](/img/rescued/abdb5d761390.png)
## 模型性能
小节22~27提到了模型性能的相关知识。我们通常希望模型的性能能够达到100%，显然是不可能的。并且，为了使训练集的准确性提高，模型可能会发生过拟合。这时要遵循两点。
- 不要将训练数据一次性使用，而是分块使用，每次训练一部分
- 当模型参数使30个以上的用例由错误变成正确，则这个参数的改变是有效果的。
![模型性能](/img/rescued/a8e154206ab8.png)
## 随机梯度下降
小节29~31讲解了什么是随机梯度下降
在训练过程中，为了让模型朝着最优的方向走，需要计算该点的导数。1.导数的计算量比较大，我们需要随机选择一部分样本来计算导数，来代替真实的导数。这就是随机梯度下降。2.为了减缓随机选择的随机性，我们使用动量的惯性来减少随机性。3.为了让后期模型能够稳定，我们减少学习的步长。
课程一结束
&amp;gt; 作业代码参考
&amp;gt; http://www.hankcs.com/ml/notmnist.html
&lt;/code>&lt;/pre></description></item><item><title>Cocos Creator 从零学习 一</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/cocos-creator-%E4%BB%8E%E9%9B%B6%E5%AD%A6%E4%B9%A0-%E4%B8%80/</link><pubDate>Tue, 27 Jun 2017 09:31:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/cocos-creator-%E4%BB%8E%E9%9B%B6%E5%AD%A6%E4%B9%A0-%E4%B8%80/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>尽管知乎上对Cocos2dx成见很大，但是徘徊于纠结用什么游戏引擎不如都试试。况且这个软件是国人开发的，学习起来应该不算困难。&lt;/p>
&lt;p>虽然没有学过JavaScript，但是似乎也可以边查边写吧。JavaScript的教程： &lt;a class="link" href="http://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000" target="_blank" rel="noopener"
>http://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000&lt;/a>&lt;/p>
&lt;p>Cocos creator目前版本为1.5.1，官网有一份很详细的手册，网址为 &lt;a class="link" href="http://www.cocos.com/docs/creator/" target="_blank" rel="noopener"
>http://www.cocos.com/docs/creator/&lt;/a> 。这篇文章是该快速上手这一章的精简版，并且对游戏性进行了提升。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/62b797900564.png"
loading="lazy"
alt="知乎网友表情.png"
>&lt;/p>
&lt;h2 id="安装">安装
&lt;/h2>&lt;p>windows下面的安装是没有任何问题的。mac下的安装，需要注意一下。
在官网下载安装包之后，拖动到applications中之后，打开会提示该应用是未被信任的开发者。此时，打开系统偏好设置中的安全性与隐私中的通用，会看到打开的按钮，通过此方式打开之后，不再进行提醒了。&lt;/p>
&lt;h2 id="快速开始">快速开始
&lt;/h2>&lt;p>&lt;img src="https://nansenli.com/img/rescued/9e8dbbc03ce7.png"
loading="lazy"
alt="dashboard"
>&lt;/p>
&lt;p>从dashboard开始新建项目，或者打开项目。根据教程中关于快速上手的部分，我们从这个地址可以下载到一个初始工程项目 &lt;a class="link" href="https://github.com/cocos-creator/tutorial-first-game/releases/download/v1.2/start_project.zip" target="_blank" rel="noopener"
>下载初始项目&lt;/a> 。
或者点击这里下载 &lt;a class="link" href="https://github.com/cocos-creator/tutorial-first-game/releases/download/v1.2/start_project.zip" target="_blank" rel="noopener"
>https://github.com/cocos-creator/tutorial-first-game/releases/download/v1.2/start_project.zip&lt;/a>&lt;/p>
&lt;p>这个快速上手最终做出的游戏效果是这样的： &lt;a class="link" href="http://fbdemos.avosapps.com/star-catcher/" target="_blank" rel="noopener"
>http://fbdemos.avosapps.com/star-catcher/&lt;/a> 。
这个游戏有点类似于接金币这样的游戏，但是更加困难，游戏的学习曲线非常的大，由于这个小怪物的水平加速度难以驾驭以及上下蹦跳的关系，几乎很难已去接到五角星，而且也没有什么剧情。我们最好把他改的好玩一点吧。&lt;/p>
&lt;p>打开这个项目，我们先学习一下界面。左下角是资源面板，在初始项目中，已经建立了一个工程目录，这里有最基本的资源，已经目录组织。assets的意思是资产，在这个总目录下面，有三个子目录，包含有字体，音频，与图片资源。&lt;/p>
&lt;h2 id="创建场景">创建场景
&lt;/h2>&lt;p>场景是至关重要的，因为场景包含了游戏脚本，而场景在游戏开始的时候会自动加载。在资源面板中的assets下点击加号创建一个场景，重命名为game，双击打开。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/a862f7b07015.png"
loading="lazy"
alt="屏幕快照 2017-06-20 上午11.01.53.png"
>&lt;/p>
&lt;p>左上角的层级管理器显示的是当前的场景中节点之间的关系。
在这个game场景中，只有一个根节点canvas（画布），现在这个节点上是空的，意味着什么都不会加载。&lt;/p>
&lt;p>点击根结点在右侧数据面板中，我们可以设置根节点的分辨率。分辨率的高度设置为自适应，总为960x640，和iPhone第一代长宽比一致，推测这个分辨率应该是dp而不是pixel吧。&lt;/p>
&lt;h2 id="场景图像">场景图像
&lt;/h2>&lt;p>将资源background拖拽到canvas上，使得background成为canvas的一个子节点，注意不要将background变成另一个根节点。
使用cmd+s来保存刚刚的变动。
选中场景中的background图片，然后，在左上角找到变换工具的第四个按钮，分别是平移，旋转，缩放，矩形变换。选中矩形变换，将background变换为一个能覆盖场景的大小，如下图所示。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/6b7b8f0da17f.png"
loading="lazy"
alt="场景图像"
>&lt;/p>
&lt;p>上述步骤也可以直接对background的属性进行设置，设置位置为0，0，宽高为1600和800就行。&lt;/p>
&lt;p>用同样的方法添加地面。
用同样的方法插入小怪物，并且重命名为player。图片的默认锚点为中心位置，这里将锚点anchor的y设置为0。&lt;/p>
&lt;h2 id="创建脚步">创建脚步
&lt;/h2>&lt;p>接下来出现了惊人的一幕，教程居然说不会编程也没关系，&lt;strong>你可以交给你的程序员小伙伴来解决&lt;/strong>，果然是我现在只差一个程序员了。&lt;/p>
&lt;p>在下图中资源的player的位置建立一个js脚本文件，并打开。
&lt;img src="https://nansenli.com/img/rescued/f7c5d8668523.png"
loading="lazy"
alt="脚本"
>&lt;/p>
&lt;p>在properties中插入以下内容，这部分是小怪物的物理属性&lt;/p>
&lt;pre tabindex="0">&lt;code> jumpHeight:0,
jumpDuration:0,
maxMoveSpeed:0,
accel:0,
&lt;/code>&lt;/pre>&lt;p>选中小怪物图片，在属性框中找到添加组件，把player脚本添加到player图片上，并且设置好相关参数。&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/83e4e58a11e8.png"
loading="lazy"
alt="参数设置"
>&lt;/p>
&lt;h2 id="完善脚本">完善脚本
&lt;/h2>&lt;p>我们在脚本中继续加入其他功能。首先是跳跃的动作。&lt;/p>
&lt;p>在properties下面加入这一段&lt;/p>
&lt;pre tabindex="0">&lt;code> setJumpAction: function(){
var jumpUp = cc.moveBy(this.jumpDuration, cc.p(0,this.jumpHeight)).easing(cc.easeQuadraticActionOut());
var jumpDown = cc.moveBy(this.jumpDuration, cc.p(0,-this.jumpHeight)).easing(cc.easeQuadraticActionIn());
return cc.repeatForever(cc.sequence(jumpUp, jumpDown));
},
&lt;/code>&lt;/pre>&lt;p>moveBy是官方的API，在官方API中查询一下该API的使用，第一个参数是移动的距离，第二个参数是移动的位置。很显然是向上移动了一段距离。后面的easing是生成缓动运动，生成的对象是easeQuadraticActionIn和Out，这两个是二次曲线。原来的教程中是三次曲线，这个和真实世界中的物理运动差的太多了。&lt;/p>
&lt;p>在onload方法中加入下面这段代码，启动动画。&lt;/p>
&lt;pre tabindex="0">&lt;code> onLoad: function () {
this.jumpAction = this.setJumpAction();
this.node.runAction(this.jumpAction);
},
&lt;/code>&lt;/pre>&lt;p>现在点击播放按钮刻可以查看初始的效果。&lt;/p>
&lt;h2 id="控制角色">控制角色
&lt;/h2>&lt;p>为主角加上移动的控制。
在setJumpAction下面继续添加方法。&lt;/p>
&lt;pre tabindex="0">&lt;code> setInputControl: function(){
var self = this;
cc.eventManager.addListener({
event: cc.EventListener.KEYBOARD,
onKeyPressed: function(keyCode, event){
switch(keyCode){
case cc.KEY.a:
self.accLeft = true;
self.accRight = false;
break;
case cc.KEY.d:
self.accRight = true;
self.accLeft = false;
}
},
onKeyReleased: function(keyCode, event){
switch(keyCode){
case cc.KEY.a:
self.accLeft = false;
break;
case cc.KEY.d:
self.accRight = false;
break;
}
}
}, self.node)
},
&lt;/code>&lt;/pre>&lt;p>更新onload和update代码&lt;/p>
&lt;pre tabindex="0">&lt;code> onLoad: function () {
this.jumpAction = this.setJumpAction();
this.node.runAction(this.jumpAction);
this.accLeft = false;
this.accRight = false;
this.xSpeed = 0;
this.setInputControl();
},
update: function(dt){
if(this.accLeft){
this.xSpeed -= this.accel * dt;
}
else if(this.accRight){
this.xSpeed += this.accel * dt;
}
if(this.xSpeed &amp;gt;0){
this.xSpeed -= this.accel/2 * dt;
}
else if( this.xSpeed &amp;lt;0){
this.xSpeed += this.accel/2 * dt;
}
if(Math.abs(this.xSpeed) &amp;gt; this.maxMoveSpeed){
this.xSpeed = this.maxMoveSpeed * this.xSpeed / Math.abs(this.xSpeed);
}
this.node.x += this.xSpeed * dt;
},
&lt;/code>&lt;/pre>&lt;p>第一个代码添加了个监听器，按键变动的时候改变accLeft和accRight的值。启动时打开监听，然后，在每一帧需要更新的时候，计算怪物的位置。&lt;/p>
&lt;h2 id="增加五角星">增加五角星
&lt;/h2>&lt;p>添加Game脚本到assets/scripts文件夹下，双击打开脚本，加入以下代码&lt;/p>
&lt;pre tabindex="0">&lt;code>cc.Class({
extends: cc.Component,
properties: {
// 这个属性引用了星星预制资源
starPrefab: {
default: null,
type: cc.Prefab
},
// 星星产生后消失时间的随机范围
maxStarDuration: 0,
minStarDuration: 0,
// 地面节点，用于确定星星生成的高度
ground: {
default: null,
type: cc.Node
},
// player 节点，用于获取主角弹跳的高度，和控制主角行动开关
player: {
default: null,
type: cc.Node
},
scoreDisplay: {
default: null,
type: cc.Label
}
},
onLoad: function () {
// 获取地平面的 y 轴坐标
this.groundY = this.ground.y + this.ground.height/2;
// 生成一个新的星星
this.spawnNewStar();
this.score = 0;
},
gainScore: function () {
this.score += 1;
// 更新 scoreDisplay Label 的文字
this.scoreDisplay.string = &amp;#39;Score: &amp;#39; + this.score.toString();
},
spawnNewStar: function() {
// 使用给定的模板在场景中生成一个新节点
var newStar = cc.instantiate(this.starPrefab);
// 将新增的节点添加到 Canvas 节点下面
this.node.addChild(newStar);
// 为星星设置一个随机位置
newStar.setPosition(this.getNewStarPosition());
newStar.getComponent(&amp;#39;star&amp;#39;).game = this;
},
getNewStarPosition: function () {
var randX = 0;
// 根据地平面位置和主角跳跃高度，随机得到一个星星的 y 坐标
var randY = this.groundY + cc.random0To1() * this.player.getComponent(&amp;#39;Player&amp;#39;).jumpHeight + 50;
// 根据屏幕宽度，随机得到一个星星 x 坐标
var maxX = this.node.width/2;
randX = cc.randomMinus1To1() * maxX;
// 返回星星坐标
return cc.p(randX, randY);
},
// called every frame, uncomment this function to activate update callback
// update: function (dt) {
// },
});
&lt;/code>&lt;/pre>&lt;p>从 资源管理器 中拖拽 assets/textures/star 资源到场景中，添加名叫 Star 的JavaScript脚本到assets/scripts/中，并关联脚本。
打开star脚本，添加如下代码&lt;/p>
&lt;pre tabindex="0">&lt;code>cc.Class({
extends: cc.Component,
properties: {
// foo: {
// default: null, // The default value will be used only when the component attaching
// to a node for the first time
// url: cc.Texture2D, // optional, default is typeof default
// serializable: true, // optional, default is true
// visible: true, // optional, default is true
// displayName: &amp;#39;Foo&amp;#39;, // optional
// readonly: false, // optional, default is false
// },
// ...
pickRadius:0,
},
// use this for initialization
onLoad: function () {
},
getPlayerDistance: function () {
// 根据 player 节点位置判断距离
var playerPos = this.game.player.getPosition();
// 根据两点位置计算两点之间距离
var dist = cc.pDistance(this.node.position, playerPos);
return dist;
},
onPicked: function() {
// 当星星被收集时，调用 Game 脚本中的接口，生成一个新的星星
this.game.spawnNewStar();
this.game.gainScore();
// 然后销毁当前星星节
this.node.destroy();
},
update: function (dt) {
// 每帧判断和主角之间的距离是否小于收集距离
if (this.getPlayerDistance() &amp;lt; this.pickRadius) {
// 调用收集行为
this.onPicked();
return;
}
},
// called every frame, uncomment this function to activate update callback
// update: function (dt) {
// },
});
&lt;/code>&lt;/pre>&lt;p>现在在star的属性中设置pickRadius为60。
从层级管理器中将star节点拖拽到资源管理器中的assets文件夹下，就生成了名叫star的 Prefab 资源。
从场景中删除star节点。
选中Canvas节点后，拖拽脚本到 属性检查器，从资源管理器中拖拽star Prefab 资源到Game组件的Star Prefab属性中，从层级编辑器中拖拽ground和Player 节点到组件中相同名字的属性上。&lt;/p>
&lt;p>接下来再加入计分机制，和上面的过程十分的类似，这里就略去不详细写了，此时，测试一下游戏的效果，已经可以看到游戏大致效果。&lt;/p>
&lt;p>如果你有任何问题，可以查看官方的上手的教程，也可以点击这里可以下载到此为止的工程：
链接: &lt;a class="link" href="https://pan.baidu.com/s/1dFw3VSP" target="_blank" rel="noopener"
>https://pan.baidu.com/s/1dFw3VSP&lt;/a> 密码: 7vgu&lt;/p>
&lt;p>官方在接下来继续进行了游戏的完善，不过我不打算继续完善下去了，接下来将关注对触摸屏的支持&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/9e062259e40a.png"
loading="lazy"
alt="效果"
>&lt;/p></description></item><item><title>每周一道Leetcode—— 501. Find Mode in Binary Search Tree</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E6%AF%8F%E5%91%A8%E4%B8%80%E9%81%93leetcode-501--find-mode-in-binary-search-tree/</link><pubDate>Mon, 26 Jun 2017 10:54:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E6%AF%8F%E5%91%A8%E4%B8%80%E9%81%93leetcode-501--find-mode-in-binary-search-tree/</guid><description>&lt;h2 id="题目">题目
&lt;/h2>&lt;p>给定一个BST二分查找树，求众数。
要求，除去递归的空间占用，其他代码空间复杂度为O(1)。&lt;/p>
&lt;h2 id="分析">分析
&lt;/h2>&lt;p>很显然，二分查找树的中序遍历就是一个有序数组。
有序数组查找最长的连续重复的数字，就是众数了。需要注意的是，题目要求返回重复的数字。另外，空间复杂度必须为O(1)。要写出这样的代码并不困难。&lt;/p>
&lt;p>运行16ms，击败62%&lt;/p>
&lt;pre tabindex="0">&lt;code>/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
private:
vector&amp;lt;int&amp;gt; mode;
int last;
int lastCount;
int modeCount;
public:
vector&amp;lt;int&amp;gt; findMode(TreeNode* root) {
last = 0;
lastCount = 0;
modeCount = 0;
mode.clear();
runMode(root);
return mode;
}
void runMode(TreeNode* root){
if(root != NULL){
runMode(root -&amp;gt; left);
int nowValue = root-&amp;gt;val;
int nowCount = nowValue != last? 1 : lastCount + 1;
if(modeCount == nowCount){
mode.push_back(nowValue);
}
if(modeCount &amp;lt; nowCount){
modeCount = nowCount;
mode.clear();
mode.push_back(nowValue);
}
last = nowValue;
lastCount = nowCount;
runMode(root -&amp;gt; right);
}
}
};
&lt;/code>&lt;/pre></description></item><item><title>每周一道leetcode—— 3. Longest Substring Without Repeating Characters</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E6%AF%8F%E5%91%A8%E4%B8%80%E9%81%93leetcode-3--longest-substring-without-repeating-characters/</link><pubDate>Sat, 24 Jun 2017 08:21:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E6%AF%8F%E5%91%A8%E4%B8%80%E9%81%93leetcode-3--longest-substring-without-repeating-characters/</guid><description>&lt;h2 id="题目">题目
&lt;/h2>&lt;p>求一个字符串的一个满足要求的最长子字符串，该子字符串不得含有重复的字符，返回该子字符串的长度。&lt;/p>
&lt;h2 id="思路">思路
&lt;/h2>&lt;p>很容易想到一个时间复杂度为&lt;code>O(n^2)&lt;/code>的算法。遍历每一个字符，从该字符开始看看以这个字符为起点的最长字符串是多长，最后输出所有字符的最长子字符串长度。&lt;/p>
&lt;p>运行时间600ms，击败0%提交&lt;/p>
&lt;pre tabindex="0">&lt;code>class Solution {
public:
int lengthOfLongestSubstring(string s) {
int ret = 0;
for(int i=0; i&amp;lt;s.size(); i++){
unordered_set&amp;lt;char&amp;gt; buff;
for(int j=i; j&amp;lt;s.size(); j++){
if(buff.count(s[j]) == 0) buff.insert(s[j]);
else break;
}
if(buff.size() &amp;gt; ret) ret = buff.size();
}
return ret;
}
};
&lt;/code>&lt;/pre>&lt;h2 id="改进">改进
&lt;/h2>&lt;p>可以看到上面那个算法非常搞笑，是世界上速度最慢的算法。在查找字符串中，我们需要设置一个起点，这个起点是到目前为止，遍历的位置往前最长没有重复出现字符的位置。然后通过与遍历的位置相减，就可以得到最长的子字符串长度了。&lt;/p>
&lt;p>运行时间15ms，击败96%提交&lt;/p>
&lt;pre tabindex="0">&lt;code>class Solution {
public:
int lengthOfLongestSubstring(string s) {
vector&amp;lt;int&amp;gt; lastPos(260, -1);
int length = s.size();
int ret = 0;
int start = 0;
for(int i=0; i&amp;lt;length; i++)
{
char c = s[i];
if(lastPos[c]+1 &amp;gt; start) start = lastPos[c] + 1;
if(i-start+1 &amp;gt; ret) ret = i-start+1;
lastPos[c] = i;
}
return ret;
}
};
&lt;/code>&lt;/pre></description></item><item><title>每周一道leetcode—— 260. Single Number III</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E6%AF%8F%E5%91%A8%E4%B8%80%E9%81%93leetcode-260--single-number-iii/</link><pubDate>Thu, 22 Jun 2017 03:49:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E6%AF%8F%E5%91%A8%E4%B8%80%E9%81%93leetcode-260--single-number-iii/</guid><description>&lt;h2 id="题目">题目
&lt;/h2>&lt;p>给定一个数组，在这个数组中，包含了一些整形数字，除了有两个数字重复了一次，其他数字都重复了两次。找出这两个数字。空间复杂度尽可能做到O(1)。&lt;/p>
&lt;h2 id="分析">分析
&lt;/h2>&lt;p>尽管解决方案中要求空间复杂度为O(1)，这个方法确实有点复杂和难想，而且很难写出常数小的算法。
&lt;a class="link" href="https://www.lijinma.com/blog/2014/05/29/amazing-xor/" target="_blank" rel="noopener"
>感受异或的神奇&lt;/a>
上面这个链接中给出了这道题的标准做法。在这个方法中，首先将所有字母进行异或操作，然后，结果就是两个只出现了一次的数字的异或。这个数字的每一位1意味着这两个数字的哪几位是不同的。任取一位，在这一位上，将数组分为两类，一类是这一位上是1，另一类是这一位上是0，这样对这两个数组分别异或结果就是这两个数字了。&lt;/p>
&lt;p>这利用了异或的性质。&lt;/p>
&lt;blockquote>
&lt;p>a ^ b ^ c = a ^ c ^ b
a ^ a = 0
0 ^ a = a
a ^ b = c =&amp;gt; a ^ c = b&lt;/p>
&lt;/blockquote>
&lt;p>运行13ms，击败68%提交。&lt;/p>
&lt;pre tabindex="0">&lt;code>class Solution {
public:
vector&amp;lt;int&amp;gt; singleNumber(vector&amp;lt;int&amp;gt;&amp;amp; nums) {
int axorb = 0, last = 0;
vector&amp;lt;int&amp;gt; ret(2, 0);
for(auto it = nums.begin(); it!=nums.end() ; it++)
{
axorb ^= *it;
}
last = axorb &amp;amp; (~(axorb - 1));
for(auto it = nums.begin(); it!=nums.end() ; it++)
{
if ((last &amp;amp; *it) != 0)
ret[0] ^= *it;
}
ret[1] = axorb ^ ret[0];
return ret;
}
};
&lt;/code>&lt;/pre>&lt;h2 id="其他方法">其他方法
&lt;/h2>&lt;p>如果不要求空间复杂度为常数，其实也可以用哈希表来做。这样算法的扩展性很好，而且也不晦涩。&lt;/p>
&lt;p>运行16ms，击败%34提交&lt;/p>
&lt;pre tabindex="0">&lt;code>class Solution {
public:
vector&amp;lt;int&amp;gt; singleNumber(vector&amp;lt;int&amp;gt;&amp;amp; nums) {
unordered_set&amp;lt;int&amp;gt; buff(nums.size());
for(auto i = nums.begin(); i!=nums.end() ; i++)
{
auto it = buff.find(*i);
if(it == buff.end()){
buff.insert(*i);
}
else{
buff.erase(it);
}
}
vector&amp;lt;int&amp;gt; ret;
for(const int &amp;amp; i : buff){
ret.push_back(i);
}
return ret;
}
};
&lt;/code>&lt;/pre></description></item><item><title>每周一道leetcode—— 43. Multiply Strings</title><link>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E6%AF%8F%E5%91%A8%E4%B8%80%E9%81%93leetcode-43--multiply-strings-/</link><pubDate>Mon, 19 Jun 2017 06:39:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E7%AE%97%E6%B3%95/%E6%AF%8F%E5%91%A8%E4%B8%80%E9%81%93leetcode-43--multiply-strings-/</guid><description>&lt;h2 id="题目">题目：
&lt;/h2>&lt;p>给定两个字符串十进制数字，给出字符串为他们的乘积。要求如下：&lt;/p>
&lt;ol>
&lt;li>禁止使用内置大数算法。&lt;/li>
&lt;li>字符串长度110&lt;/li>
&lt;li>输入无前置0&lt;/li>
&lt;li>字符串仅含有数字&lt;/li>
&lt;/ol>
&lt;h2 id="思考">思考
&lt;/h2>&lt;p>在一开始没有看到禁止使用内置函数，我直接使用python的&lt;code>str&lt;/code>和&lt;code>int&lt;/code>这两个函数，结果直接前10%了。。。后来我看了下题目原来是不能用内置函数的，不知道这个python语言的特性算不算内置大数算法。这是一个大数乘法问题，大数乘法有很多现成的算法。&lt;/p>
&lt;p>最开始提交的python版本，击败88%&lt;/p>
&lt;pre tabindex="0">&lt;code>class Solution(object):
def multiply(self, num1, num2):
return str(int(num1)*int(num2))
&lt;/code>&lt;/pre>&lt;p>最原始的算法就是模拟手算了吧，首先你得实现&lt;strong>字符串的加法计算&lt;/strong>，&lt;strong>多位乘法乘一位乘法&lt;/strong>，这样应该可以计算多位乘以多位了。例如计算&lt;code>12345*67890&lt;/code>，就是计算&lt;code>12345*6 + 12345*7 + 12345 *8 + 12345 * 9 + 12345 * 0&lt;/code>， 然后每一个计算结果后面补0，再相加就会得到结果。不过这样的速度会比较慢。&lt;/p>
&lt;p>况且，CPU本身就可以计算一些不会溢出的加法，所以，我们得好好利用这一点。首先我们得改进一下上面的算法，计算&lt;code>12345*67890&lt;/code>这个数字的结果，我们可以这样算：&lt;code>12345*67890 = (12300 + 45)*(67800 + 90)&lt;/code> ，显然可以拆解成为4个乘法计算，而且对于低位为0的，可以直接去掉，然后计算结果再补回来就行了，如果这四个乘法分别计算的时候不会溢出，那就没有问题了。否则，我们可以继续分解。&lt;/p>
&lt;p>使用karatsuba乘法可以继续改进上面一点。
我们注意到加法中有&lt;code>12300*90+45*67800&lt;/code>，我们可以利用已经计算过的结果，也就是&lt;code>12300*67800&lt;/code>和&lt;code>45*90&lt;/code>，然后只需要计算&lt;code>(12300+45)*(67800+90) - 12300*67800 - 45*90&lt;/code> 就可以得到 &lt;code>12300*90 + 45*67800&lt;/code>。这样，乘法的计算次数就减少了一次。&lt;/p>
&lt;p>karatsuba乘法写起来会复杂一点，先不实现。首先提交一版n^2的算法也就是普通版，看看效果怎么样，然后再改进。（结果表明，手算版的速度已经很快了）&lt;/p>
&lt;h2 id="第一版">第一版
&lt;/h2>&lt;p>首先我们得写一个字符串相加的算法。我们首先观察一下输入的数据类型和输出的数据类型。是string类型的，那么按照一位一位加起来也是可以的。可以直接用两个整形数组保存一下每一位数，然后相加算出来第三个数组，这时第三个数组会有一些数字超出了10，我们按照从低位开始向高位进一位。最后将这个数组转成字符串就可以了。&lt;/p>
&lt;p>虽然本地测试还可以，但是提交后速度很慢，排名很后，只击败10%。&lt;/p>
&lt;h2 id="改进">改进
&lt;/h2>&lt;p>我觉得理论上这个算法应该是不慢的,但是实际过程是很慢，可能是由于多余的整形和字符串互相转换有关。上面的算法里面，在计算乘法的时候将字符串转成了数字但是之后又转换回字符串，可能是这里产生了多余的时间。所以，应该直接在这里将结果加在最终结果上面。&lt;/p>
&lt;p>提交后运行时间为9ms，击败50%。&lt;/p>
&lt;h2 id="改进2">改进2
&lt;/h2>&lt;p>算法中相同的字符串重复转化，可能会消耗时间。
对字符串转化进行缓存，第一次转换成功就进行缓存，以后如果需要直接取出不需要进行额外的计算。&lt;/p>
&lt;p>最终代码，可直接编译运行。运行时间6ms，击败76%提交。看来字符串转化反而成了性能瓶颈。&lt;/p>
&lt;pre tabindex="0">&lt;code>#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;
const int BINARY = 10;
class Solution
{
private:
vector&amp;lt;int&amp;gt; result;
string _num1, _num2;
long num1buff[120];
long num2buff[120];
public:
string multiply(string num1, string num2)
{
result.clear();
result.resize(num1.length() + num2.length() + 1);
memset(num1buff, -1 , sizeof(long)*120);
memset(num2buff, -1 , sizeof(long)*120);
_num1 = num1;
_num2 = num2;
for(auto &amp;amp;c : _num1){
c-=&amp;#39;0&amp;#39;;
}
for(auto &amp;amp;c : _num2){
c-=&amp;#39;0&amp;#39;;
}
//这个过程是递归的过程,我们先看下什么时候终止吧.
//终止的时候,是两个数相乘不会溢出的时候
//我们假设int是二进制30位的,相乘要30位,原来两个数字必然是15位的, 也就是32768.
//也就是说两个四位数相乘通常都不会溢出了吧.
//使用递归来计算乘积
addMultiply(0,num1.length(), 0, num2.length());
string ret;
int i = result.size() -1;
for(; i&amp;gt;0; i--)
{
if(result[i] != 0) break;
}
for(; i&amp;gt;=0; i--)
{
ret.push_back(result[i] + &amp;#39;0&amp;#39;);
}
return ret;
}
void addMultiply(int a1, int a2, int b1, int b2 )
{;
//判断是否可以直接计算
if (a1 == a2 || b1 == b2)
return;
if (a2 - a1 &amp;lt; 10 &amp;amp;&amp;amp; b2 - b1 &amp;lt; 10)
{
long int_num1 = getLong1(a1, a2);
long int_num2 = getLong2(b1, b2);
long output = int_num1 * int_num2;
int pos = _num1.length() + _num2.length() - a2 - b2;
while (output != 0 || result[pos] &amp;gt;= BINARY)
{
long a = output % BINARY;
result[pos] += a;
result[pos + 1] += result[pos] / BINARY;
result[pos] %= BINARY;
output /= BINARY;
pos++;
}
return;
}
//否则,拆开较长的那个数字
if(a2 - a1 &amp;gt;= 10){
addMultiply(a1, (a2 + a1)/2, b1, b2);
addMultiply((a2 + a1)/2, a2, b1, b2);
}
else {
addMultiply(a1, a2, (b1+b2)/2, b2);
addMultiply(a1, a2, b1, (b1+b2)/2);
}
}
long getLong1(int a, int b){
long ret = 0;
if(num1buff[a] != -1) return num1buff[a];
for(int i=a; i!=b;i++){
ret *= BINARY;
ret += _num1[i] ;
}
num1buff[a] = ret;
return ret;
}
long getLong2(int a, int b){
long ret = 0;
if(num2buff[a] != -1) return num2buff[a];
for(int i=a; i!=b;i++){
ret *= BINARY;
ret += _num2[i] ;
}
num2buff[a] = ret;
return ret;
}
};
int main(void)
{
Solution s;
for(int i=0;i&amp;lt;10000;i++){
cout &amp;lt;&amp;lt; s.multiply(&amp;#34;12345678901&amp;#34;, &amp;#34;100&amp;#34;) &amp;lt;&amp;lt; endl;
cout &amp;lt;&amp;lt; s.multiply(&amp;#34;100&amp;#34;, &amp;#34;100&amp;#34;) &amp;lt;&amp;lt; endl;
}
return 0;
}
&lt;/code>&lt;/pre>&lt;p>最终排名击败70%&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/6f6d2f53b3ee.png"
loading="lazy"
alt="排名"
>&lt;/p></description></item><item><title>如何在windows10的微软拼音中设置小鹤双拼</title><link>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E5%A6%82%E4%BD%95%E5%9C%A8windows10%E7%9A%84%E5%BE%AE%E8%BD%AF%E6%8B%BC%E9%9F%B3%E4%B8%AD%E8%AE%BE%E7%BD%AE%E5%B0%8F%E9%B9%A4%E5%8F%8C%E6%8B%BC/</link><pubDate>Mon, 05 Jun 2017 06:16:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E9%9A%8F%E7%AC%94/%E5%A6%82%E4%BD%95%E5%9C%A8windows10%E7%9A%84%E5%BE%AE%E8%BD%AF%E6%8B%BC%E9%9F%B3%E4%B8%AD%E8%AE%BE%E7%BD%AE%E5%B0%8F%E9%B9%A4%E5%8F%8C%E6%8B%BC/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>win10最新版本系统自带的拼音已经可以支持小鹤双拼了，妈妈再也不用担心我装全家桶啦！&lt;/p>
&lt;p>时隔4年，更新一下进阶版设置，妈妈再也不用担心我一个一个配置按键啦：&lt;/p>
&lt;ol>
&lt;li>win + R，输入 regedit，打开注册表&lt;/li>
&lt;li>找到 &lt;code>计算机\HKEY_CURRENT_USER\Software\Microsoft\InputMethod\Settings\CHS&lt;/code> 项&lt;/li>
&lt;li>新建一个名为 UserDefinedDoublePinyinScheme0 的字符串值，值为 &lt;code>小鹤双拼*2*^*iuvdjhcwfg^xmlnpbksqszxkrltvyovt&lt;/code>&lt;/li>
&lt;li>打开微软拼音输入法设置，把 &lt;code>小鹤双拼&lt;/code> 设置为双拼的默认方案即可。&lt;/li>
&lt;/ol>
&lt;p>如果担心修改注册表引起系统错乱，这里还有手工配置的方案：&lt;/p>
&lt;h2 id="第一步-打开设置">第一步 打开设置
&lt;/h2>&lt;p>系统的拼音输入法虽然没有小鹤双拼，但是我们可以自定义。&lt;/p>
&lt;p>首先在设置中找到设置双拼的位置，如果你找不到，可以看动图：
&lt;img src="https://nansenli.com/img/rescued/98a28b5a7a49.gif"
loading="lazy"
alt="找到设置"
>&lt;/p>
&lt;h2 id="第二步-编辑自定义双拼">第二步 编辑自定义双拼
&lt;/h2>&lt;p>双拼设置如下图所示&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/068b77b04598.png"
loading="lazy"
alt="小鹤方案"
>&lt;/p>
&lt;p>除了模板那里不用设置以外，其他的按照图中的设置就可以了，一个是零声母模式，另外一个是自定义按键，自定义按键按照图中的预览设置就可以了。需要注意的是，小鹤双拼中没有er这个音节，设置成e或者r键都可以。另外，ue，ve这两个音节都设置为T。有一个小技巧，设置按键的时候，可以直接按下对应的按键，而不用找到这个键来设置。&lt;/p>
&lt;h2 id="第三步-保存方案">第三步 保存方案
&lt;/h2>&lt;p>如果添加按钮变为可用，说明方案设置正确。添加为方案后，把该方案设置为默认方案，就可以使用小鹤双拼啦。&lt;/p>
&lt;h2 id="测试">测试
&lt;/h2>&lt;p>进行打字测试，查看设置的对不对&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/582a066041c2.gif"
loading="lazy"
alt="测试"
>&lt;/p>
&lt;h2 id="第四步-点击喜欢">第四步 点击喜欢
&lt;/h2>&lt;p>点击喜欢，提高页面排名，帮助更多人用上win10小鹤双拼&lt;/p></description></item><item><title>在RT-Thread中使用stm32的hal库驱动串口UART</title><link>https://nansenli.com/zh-cn/post/jianshu/%E5%B5%8C%E5%85%A5%E5%BC%8F/%E5%9C%A8rt-thread%E4%B8%AD%E4%BD%BF%E7%94%A8stm32%E7%9A%84hal%E5%BA%93%E9%A9%B1%E5%8A%A8%E4%B8%B2%E5%8F%A3uart/</link><pubDate>Thu, 06 Apr 2017 10:22:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E5%B5%8C%E5%85%A5%E5%BC%8F/%E5%9C%A8rt-thread%E4%B8%AD%E4%BD%BF%E7%94%A8stm32%E7%9A%84hal%E5%BA%93%E9%A9%B1%E5%8A%A8%E4%B8%B2%E5%8F%A3uart/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>之前一直在Linux下面尝试进行部署了RT-Thread的stm32的开发，这种方式确实是可行的，但是也会带来种种不便之处。使用VSCode + Scons + openocd 进行编辑、编译链接、烧录确实是可以进行开发，但是若要进行调试则没有这种环境的解决方案，只得在eclipse中进行调试。而将RT-Thread改成eclipse的方案虽然可行，但是这样还不如直接在windows下面采用keil 5 来进行开发。&lt;/p>
&lt;p>由于stm32f103板载flash过小，栈也十分的小，于是换成stm32f411。板子来自于微雪科技的XNUCLEO，和STM官方NUCLEO板子是兼容的。&lt;/p>
&lt;h2 id="安装keil-523">安装Keil 5.23
&lt;/h2>&lt;p>由于我首先安装了Keil 5.11，然后附带的破解器破解了，之后卸载再安装了Keil 5.23，直接显示已经破解成功。破解器仍然是多年前就流行的注册码生成器。之所以使用Keil 5.23版本，是因为RT-Thread工程只能使用这个版本进行打开。&lt;/p>
&lt;h2 id="使用pack-installer安装库">使用pack installer安装库
&lt;/h2>&lt;p>只有安装了Keil::STM32F4xx_DFP才能打开RT-Thread的bps分支下的nucleo工程。&lt;/p>
&lt;h2 id="安装stm32cubemx">安装STM32CubeMX
&lt;/h2>&lt;p>通过STM32CubeMX，我们可以方便的通过鼠标点击生成初始化代码。但是调用函数还是得我们自己写，不如NPX的PE方便。&lt;/p>
&lt;h2 id="安装st-link驱动">安装ST-Link驱动
&lt;/h2>&lt;p>微雪科技售卖100块钱的板子还附带了ST-Link调试器，那我们就是用ST-Link来进行调试吧。&lt;/p>
&lt;h2 id="下载nucleo工程">下载nucleo工程
&lt;/h2>&lt;p>打开&lt;code>rt-thread-master\bsp\stm32f411-nucleo&lt;/code>下的Keil 5工程，尝试编译，通过后，简单设置工程的debug，使用ST-Link来进行调试，然后将程序下载到板子里面。虽然程序下进去了，但是没有任何效果，因为主函数是空的。&lt;/p>
&lt;p>我们在main函数中加入如下代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C" data-lang="C">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/* user app entry */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">MX_GPIO_Init&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span>(&lt;span style="color:#ae81ff">1&lt;/span>){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">rt_thread_delay&lt;/span>(&lt;span style="color:#ae81ff">5&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">HAL_GPIO_WritePin&lt;/span> (GPIOC, GPIO_PIN_5, GPIO_PIN_SET);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">rt_thread_delay&lt;/span>(&lt;span style="color:#ae81ff">5&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">HAL_GPIO_WritePin&lt;/span> (GPIOC, GPIO_PIN_5, GPIO_PIN_RESET);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>并加上LED的初始化函数&lt;/p>
&lt;pre tabindex="0">&lt;code>static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, GPIO_PIN_RESET);
/*Configure GPIO pin : PC5 */
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &amp;amp;GPIO_InitStruct);
}
&lt;/code>&lt;/pre>&lt;p>这样我们就可以看到板载的LED4灯亮起来了，并且在闪烁。&lt;/p>
&lt;p>这些初始化代码是从哪来的呢？主函数中的代码，是从网上找的，很简单的操作GPIO的函数，而MX_GPIO_Init这段代码，则来自于STM32CubeMX自动生成的代码。下面会讲如何通过STM32CubeMX生成初始化UART代码。&lt;/p>
&lt;h2 id="使用stm32cubemx生成uart初始化代码">使用STM32CubeMX生成UART初始化代码
&lt;/h2>&lt;ul>
&lt;li>首先我们建立一个工程，并设置好芯片型号，下面是演示&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/905af84ac774.gif"
loading="lazy"
alt="2017-04-06_16-41-06.gif"
>&lt;/p>
&lt;ul>
&lt;li>然后设置串口引脚。
由于RT-Thread已经初始化了串口1，我以我们不能使用这个。我们可以使用UART6。&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/4985758a4b21.gif"
loading="lazy"
alt="2017-04-06_16-57-29.gif"
>&lt;/p>
&lt;ul>
&lt;li>打开工程，将代码偷走，蓝色部分就是我们要拿走的代码&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/f06cc889e552.gif"
loading="lazy"
alt="2017-04-06_17-01-11.gif"
>&lt;/p>
&lt;ul>
&lt;li>移植代码
在void HAL_UART_MspInit(UART_HandleTypeDef *huart)中，添加USART6的代码：&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef GPIO_InitStruct;
if (huart-&amp;gt;Instance == USART2)
{
/*##-1- Enable peripherals and GPIO Clocks #################################*/
/* Enable GPIO TX/RX clock */
USARTx_TX_GPIO_CLK_ENABLE();
USARTx_RX_GPIO_CLK_ENABLE();
/* Enable USARTx clock */
USARTx_CLK_ENABLE();
/*##-2- Configure peripheral GPIO ##########################################*/
/* UART TX GPIO pin configuration */
GPIO_InitStruct.Pin = USARTx_TX_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
GPIO_InitStruct.Alternate = USARTx_TX_AF;
HAL_GPIO_Init(USARTx_TX_GPIO_PORT, &amp;amp;GPIO_InitStruct);
/* UART RX GPIO pin configuration */
GPIO_InitStruct.Pin = USARTx_RX_PIN;
GPIO_InitStruct.Alternate = USARTx_RX_AF;
HAL_GPIO_Init(USARTx_RX_GPIO_PORT, &amp;amp;GPIO_InitStruct);
HAL_NVIC_SetPriority(USART2_IRQn, 0, 1);
HAL_NVIC_EnableIRQ(USART2_IRQn);
}
if (huart-&amp;gt;Instance == USART6)
{
/* USER CODE BEGIN USART6_MspInit 0 */
/* USER CODE END USART6_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_USART6_CLK_ENABLE();
/**USART6 GPIO Configuration
PC6 ------&amp;gt; USART6_TX
PC7 ------&amp;gt; USART6_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF8_USART6;
HAL_GPIO_Init(GPIOC, &amp;amp;GPIO_InitStruct);
/* Peripheral interrupt init */
HAL_NVIC_SetPriority(USART6_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART6_IRQn);
/* USER CODE BEGIN USART6_MspInit 1 */
/* USER CODE END USART6_MspInit 1 */
}
}
&lt;/code>&lt;/pre>&lt;p>在main.c文件中，添加&lt;code>MX_USART6_UART_Init();&lt;/code>函数内容，并在主函数中，调用&lt;code>MX_USART6_UART_Init();&lt;/code>进行初始化。&lt;/p>
&lt;p>之后，在main()函数中进行测试：&lt;/p>
&lt;pre tabindex="0">&lt;code> uint8_t TxData[10]= &amp;#34;01234abcde&amp;#34;;
HAL_UART_Transmit(&amp;amp;huart6,TxData,10,0xf);
&lt;/code>&lt;/pre>&lt;p>我们将微雪的板子上的JP4跳帽接在右侧，这样，USB TO UART就能使用PC6、PC7作为串口接口了。然后，接上USB接口到PC机，我们可以在XSHELL之类的串口终端查看PC接受到的字符串。&lt;/p></description></item><item><title>在RT-Thread中使用SPI和UART设备</title><link>https://nansenli.com/zh-cn/post/jianshu/%E5%B5%8C%E5%85%A5%E5%BC%8F/%E5%9C%A8rt-thread%E4%B8%AD%E4%BD%BF%E7%94%A8spi%E5%92%8Cuart%E8%AE%BE%E5%A4%87/</link><pubDate>Tue, 07 Mar 2017 09:41:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E5%B5%8C%E5%85%A5%E5%BC%8F/%E5%9C%A8rt-thread%E4%B8%AD%E4%BD%BF%E7%94%A8spi%E5%92%8Cuart%E8%AE%BE%E5%A4%87/</guid><description>&lt;p>RT-Thread采用了类似于linux的函数调用方法，这里说明一下如何简单的使用设备。&lt;/p>
&lt;ul>
&lt;li>硬件环境 ：STM32F103RBT6&lt;/li>
&lt;/ul>
&lt;h2 id="用户程序位置">用户程序位置
&lt;/h2>&lt;p>用户程序在bsp文件夹下面的applications中的applications.c中书写和调用，如果程序比较多，再新建一个c文件分离出去。&lt;/p>
&lt;h2 id="spi-驱动文件">SPI 驱动文件
&lt;/h2>&lt;p>stm32f10x下面的drivers并没有spi驱动，我们需要将stm32f107对应的spi驱动移植过来。将rt_stm32f10x_spi.c，rt_stm32f10x_spi.h，以及platform.c文件复制过去，然后在rt_init_thread_entry函数中添加rt_platform_init();的调用，在对应文件中加入头文件。然后修改SConscript添加对platform.c，rt_stm32f10x_spi.c的编译，并在config中添加RT_USING_SPI和RT_USING_SPI1的宏定义&lt;/p>
&lt;p>在platform.c中spi初始化加入如下代码，配置频率&lt;/p>
&lt;pre tabindex="0">&lt;code> /* config spi */
{
struct rt_spi_configuration cfg;
cfg.data_width = 8;
cfg.mode = RT_SPI_MODE_3 | RT_SPI_MSB; /* SPI Compatible Modes 3 and SPI_FirstBit_MSB in lis302dl datasheet */
//APB2=168M/2=84M, SPI1 = 84/2,4,8,16,32 = 42M, 21M, 10.5M, 5.25M, 2.625M ...
cfg.max_hz = 2625000; /* SPI_BaudRatePrescaler_16=84000000/16=5.25MHz. The max_hz of lis302dl is 10MHz in datasheet */
rt_spi_configure(&amp;amp;spi_device, &amp;amp;cfg);
} /* config spi */
&lt;/code>&lt;/pre>&lt;h2 id="硬件配置">硬件配置
&lt;/h2>&lt;p>硬件管脚被配置在platform.c文件中, 我们不修改默认的配置,也就是 PA4567这几个管脚为默认的spi输入输出管脚&lt;/p>
&lt;pre tabindex="0">&lt;code>/*
* SPI1_MOSI: PA7
* SPI1_MISO: PA6
* SPI1_SCK : PA5
*
* CS0: PA4 SD card. 这里可以改成自己的spi设备的cs管脚
*/
&lt;/code>&lt;/pre>&lt;h2 id="spi调用">SPI调用
&lt;/h2>&lt;p>SPI调用不用使用open函数，只需执行&lt;/p>
&lt;pre tabindex="0">&lt;code> rt_spi_transfer(spi_device, &amp;amp;ReadAddr, &amp;amp;pBuffer, 1);
&lt;/code>&lt;/pre>&lt;p>或者rt_spi_send_then_recv也可以实现不同的效果。&lt;/p>
&lt;h2 id="uart调用">UART调用
&lt;/h2>&lt;p>UART调用需要使用open函数，这里给出一个简单的例子使用串口设备。&lt;/p>
&lt;pre tabindex="0">&lt;code>void device_thread_entry(void* parameter)
{
rt_device_t device;
device = rt_device_find(&amp;#34;uart2&amp;#34;);
rt_device_open(device, RT_DEVICE_OFLAG_RDWR|RT_DEVICE_FLAG_INT_RX);
char test[10] = &amp;#34;abc&amp;#34;;
while(1){
rt_size_t reclen = rt_device_read(device, 0, test, 10);
if(reclen &amp;gt; 0) rt_device_write(device, 0, test, reclen);
rt_thread_delay(500);
}
}
&lt;/code>&lt;/pre>&lt;p>可以开启这个线程，我们在uart2中输入10个以内的字符，在5秒后就会回显。如果超过10个字符，应该会在下一次5秒显示。
在官方markdown文档中，使用了消息机制来处理UART通信，也可以参考。&lt;/p></description></item><item><title>Ubuntu环境下在STM32上部署RT-thread系统</title><link>https://nansenli.com/zh-cn/post/jianshu/%E5%B5%8C%E5%85%A5%E5%BC%8F/ubuntu%E7%8E%AF%E5%A2%83%E4%B8%8B%E5%9C%A8stm32%E4%B8%8A%E9%83%A8%E7%BD%B2rt-thread%E7%B3%BB%E7%BB%9F/</link><pubDate>Thu, 02 Mar 2017 07:59:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E5%B5%8C%E5%85%A5%E5%BC%8F/ubuntu%E7%8E%AF%E5%A2%83%E4%B8%8B%E5%9C%A8stm32%E4%B8%8A%E9%83%A8%E7%BD%B2rt-thread%E7%B3%BB%E7%BB%9F/</guid><description>&lt;p>RT-Thread是一个中国开源嵌入式系统，目前版本为2.2，已经支持许多微控制器，例如STM32系列，甚至可以在蓝牙BLE芯片中运行。尽管微控制器中资源紧张，但是部署一个易用的微操作系统对项目开发和代码管理是很有益的。&lt;/p>
&lt;h2 id="准备工具">准备工具
&lt;/h2>&lt;ul>
&lt;li>STM32开发版一个，支持USB下载功能&lt;/li>
&lt;li>Ubuntu系统&lt;/li>
&lt;li>USB转串口线&lt;/li>
&lt;/ul>
&lt;p>这里我使用了古老的&lt;code>ALIENTEK STM32 MINI V2.0&lt;/code> 开发板，芯片型号为&lt;code>STM32F103RBT6&lt;/code>，Ubuntu系统版本为&lt;code>16.04&lt;/code>&lt;/p>
&lt;h2 id="1下载交叉编译工具链">1.下载交叉编译工具链
&lt;/h2>&lt;ul>
&lt;li>下载&lt;code>gcc-arm-none-eabi&lt;/code>交叉编译工具链，对于16.04系统，直接使用&lt;code>apt install gcc-arm-none-eabi&lt;/code> 进行安装&lt;/li>
&lt;/ul>
&lt;h2 id="2下载rt-thread编译工具">2.下载RT-Thread编译工具
&lt;/h2>&lt;ul>
&lt;li>该系统使用scons编译，对于16.04系统，使用 &lt;code>apt install scons&lt;/code> 进行安装&lt;/li>
&lt;/ul>
&lt;h2 id="3下载系统源码">3.下载系统源码
&lt;/h2>&lt;ul>
&lt;li>下载RT-Thread系统，地址为：&lt;code>https://github.com/RT-Thread&lt;/code>&lt;/li>
&lt;li>解压目录&lt;/li>
&lt;/ul>
&lt;h2 id="4修改编译工程">4.修改编译工程
&lt;/h2>&lt;ul>
&lt;li>进入目录&lt;code>bsp/stm32f10x&lt;/code>&lt;/li>
&lt;li>编辑文件&lt;code>rtconfig.py&lt;/code>&lt;/li>
&lt;li>修改 CROSS_TOOL 为 gcc&lt;/li>
&lt;/ul>
&lt;h2 id="5针对板子进行配置">5.针对板子进行配置
&lt;/h2>&lt;ul>
&lt;li>&lt;code>stm32f10x.h&lt;/code> 中对晶振进行配置 &lt;code>HSE_VALUE&lt;/code>，例如800000&lt;/li>
&lt;li>&lt;code>board.h&lt;/code> 中对RAM进行配置 &lt;code>STM32_SRAM_SIZE&lt;/code>，例如20&lt;/li>
&lt;li>&lt;code>led.c&lt;/code> 中对 led进行配置&lt;/li>
&lt;/ul>
&lt;h2 id="6编译源码">6.编译源码
&lt;/h2>&lt;ul>
&lt;li>在&lt;code>bsp/stm32f10x&lt;/code>目录下运行 &lt;code>scons&lt;/code>，输入rtthread.bin文件&lt;/li>
&lt;/ul>
&lt;h2 id="7烧录代码">7.烧录代码
&lt;/h2>&lt;ul>
&lt;li>你可以使用Jlink进行下载，须在Jlink官网下载Linux执行文件，由于我是debian系统，可以直接打开JLink_Linux_V614a_x86_64.deb文件进行安装。然后在系统文件下lib/目录下执行&lt;code>JlinkExe&lt;/code>文件，即可以通过Jlink进行下载烧录程序。&lt;/li>
&lt;li>这里我采用了更为方便的串口烧录。首先将开发板的B0接高电平，B1接低电平，然后使用STM32flash-5.0这个软件进行烧录。
&lt;ul>
&lt;li>下载地址&lt;code>https://sourceforge.net/projects/stm32flash/&lt;/code>&lt;/li>
&lt;li>解压目录，在该目录下运行make，得到stm32flash&lt;/li>
&lt;li>将&lt;code>rtthread.bin&lt;/code>复制到该目录下，接入USB转串口线，然后输入&lt;code>sudo ./stm32flash -w rtthread.bin -v -g 0x0 /dev/ttyUSB0&lt;/code>，其中&lt;code>ttyUSB0&lt;/code>是串口设备&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="8检查是否成功">8.检查是否成功
&lt;/h2>&lt;ul>
&lt;li>若能看到LED灯闪烁，则成功&lt;/li>
&lt;/ul></description></item><item><title>Cubieboard2 添加SPI 驱动支持</title><link>https://nansenli.com/zh-cn/post/jianshu/%E5%B5%8C%E5%85%A5%E5%BC%8F/cubieboard2-%E6%B7%BB%E5%8A%A0spi-%E9%A9%B1%E5%8A%A8%E6%94%AF%E6%8C%81/</link><pubDate>Tue, 28 Feb 2017 12:14:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/jianshu/%E5%B5%8C%E5%85%A5%E5%BC%8F/cubieboard2-%E6%B7%BB%E5%8A%A0spi-%E9%A9%B1%E5%8A%A8%E6%94%AF%E6%8C%81/</guid><description>&lt;p>##0.前言&lt;/p>
&lt;p>由于cubieboard2官方稳定版内核linux-3.4未添加原生spi驱动支持，我们需要为修改内核源码，编译并放入nand中，替换掉原来的uImage镜像，并放入新内核的模块文件。除此之外，还需要修改系统文件，使其完整支持全双工通信。&lt;/p>
&lt;p>##1.刷入debian系统到nand。（windows下）&lt;/p>
&lt;p>首先下载为cubieboard2定制的debian系统，地址为：http://dl.cubieboard.org/software/a20-cubieboard/debian/nand/debian-nand.img.gz&lt;/p>
&lt;p>下载Phoenixsuit，通过Phoenixsuit打开下载的镜像。在cubieboard2未接电源的状态下按住FEL按钮的同时通过OTG线接入电脑，在设备管理器将这个未识别的设备安装驱动，指定驱动安装目录为Phoenixsuit的安装目录，确认安装。&lt;/p>
&lt;p>然后格式化并刷入镜像。&lt;/p>
&lt;p>##2.登陆系统&lt;/p>
&lt;p>将cubieboard2接入网线，该debian系统默认设为静态ip地址192.168.1.124，你可以通过ssh进入该系统。你也可以通过TTL线从UART进行登陆，修改为DHCP方式，然后从路由器查看IP地址，再登陆。路由器账户名密码为root/cubieboard。&lt;/p>
&lt;p>##3.修改内核（ubuntu下）&lt;/p>
&lt;p>目前linux内核不支持cubieboard2的spi驱动。首先下载内核源码linux-3.4分支：https://github.com/linux-sunxi/linux-sunxi 。然后为内核增加spi支持（不建议），详情请查看 &lt;a class="link" href="http://blog.csdn.net/u010352603/article/details/51657265#" target="_blank" rel="noopener"
>http://blog.csdn.net/u010352603/article/details/51657265#&lt;/a> 。这里直接下载已经修改好的spi支持，地址 &lt;a class="link" href="https://github.com/linanwx/cubieboard2-spi-support" target="_blank" rel="noopener"
>https://github.com/linanwx/cubieboard2-spi-support&lt;/a> 。下载后，将内核目录drivers/spi删除，将下载的spi文件夹放在删除的位置。&lt;/p>
&lt;p>##4.编译内核（ubuntu下）&lt;/p>
&lt;p>准备好arm-linux-gnueabihf-gcc编译器，版本不高于5.0&lt;/p>
&lt;p>在内核目录中，使用终端，输入&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>make ARCH&lt;span style="color:#f92672">=&lt;/span>arm CROSS_COMPILE&lt;span style="color:#f92672">=&lt;/span>arm-linux-gnueabihf- sun7i_defconfig
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>输入&lt;/p>
&lt;pre tabindex="0">&lt;code>gedit .config
&lt;/code>&lt;/pre>&lt;p>找到以下选项，并将其改为&lt;/p>
&lt;pre tabindex="0">&lt;code>CONFIG_SPI=y
CONFIG_SUNXI_NAND_PARTITION=y
CONFIG_SUNXI_NAND=y
&lt;/code>&lt;/pre>&lt;p>输入以下命令开始编译内核&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>make -j4ARCH&lt;span style="color:#f92672">=&lt;/span>arm CROSS_COMPILE&lt;span style="color:#f92672">=&lt;/span>arm-linux-gnueabihf- uImage modules
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>由于开启了CONFIG_SPI支持和CONFIG_SUNXI_NAND支持，需要进行一系列的设置。当遇到spi support，CONFIG_SPI_SUN7I，CONFIG_SUN7I_SPI_NDMA时，输入y，其他情况输入回车。&lt;/p>
&lt;p>检查路径arch/arm/boot下是否有uImage文件&lt;/p>
&lt;p>输入以下命令生成内核模块&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>make ARCH&lt;span style="color:#f92672">=&lt;/span>arm CROSS_COMPILE&lt;span style="color:#f92672">=&lt;/span>arm-linux-gnueabihf- INSTALL_MOD_PATH&lt;span style="color:#f92672">=&lt;/span>output modules_install
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>检查路径&lt;code>output/lib/modules/3.4.104&lt;/code>目录是否产生&lt;/p>
&lt;p>##5.替换内核（cubieboard2下）&lt;/p>
&lt;p>在cubieboard2 中，打开终端，输入&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>mkdir /media/nanda
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mount /dev/nanda /media/nanda
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>将uImage拷贝至cubieboard2 debian系统的&lt;code>/media/nanda&lt;/code>目录覆盖原来内核&lt;/p>
&lt;p>使用tar命令打包&lt;code>output/lib/modules/3.4.104&lt;/code> 目录，将该文件传入cubieboard2 的 &lt;code>/lib/modules&lt;/code> 目录，然后解压&lt;/p>
&lt;p>##6.检查编译成功&lt;/p>
&lt;p>重启，如果系统正常进入，则成功&lt;/p>
&lt;p>##7.修改系统文件打开SPI，并使其支持全双工SPI（cubieboard2下）
重新挂载&lt;code>/dev/nanda&lt;/code>到&lt;code>/media/nanda/&lt;/code> 目录并进入&lt;/p>
&lt;p>输入如下命令&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>bin2fex script.bin script.fex
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>nano script.fex
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>找到[spi0_para]，将其下面的used改为1，并额外增加如下代码&lt;/p>
&lt;pre tabindex="0">&lt;code>[spi_devices]
spi_dev_num =1
[spi_board0]
modalias =&amp;#34;spidev&amp;#34;
max_speed_hz =100000
bus_num =0
chip_select =0
mode =0
full_duplex =1
manual_cs =0
&lt;/code>&lt;/pre>&lt;p>输入如下命令&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>fex2bin script.fex script.bin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>nano /usr/include/linux/spi/spidev.h
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在&lt;code>__u16 delay_usecs;&lt;/code>上面一行添加&lt;/p>
&lt;pre tabindex="0">&lt;code>__u16 interbyte_usecs;
&lt;/code>&lt;/pre>&lt;p>##8.测试spi
重启cubieboard2。检查路径&lt;code>/dev&lt;/code>下是否存在spidev设备。短接46，48pin脚，管脚图&lt;code>http://docs.cubieboard.org/products/a10_cubieboard/expansion_ports&lt;/code>。
新建文本输入如下代码&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * SPI testing utility (using spidev driver)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * Copyright (c) 2007 MontaVista Software, Inc.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * Copyright (c) 2007 Anton Vorontsov &amp;lt;avorontsov@ru.mvista.com&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * This program is free software; you can redistribute it and/or modify
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * it under the terms of the GNU General Public License as published by
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * the Free Software Foundation; either version 2 of the License.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * Cross-compile with cross-gcc -I/path/to/cross-kernel/include
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;stdint.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;unistd.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;stdio.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;stdlib.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;getopt.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;fcntl.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;sys/ioctl.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;linux/types.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;linux/spi/spidev.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">pabort&lt;/span>(&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>s)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">perror&lt;/span>(s);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">abort&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>device &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;/dev/spidev0.0&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">uint8_t&lt;/span> mode;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">uint8_t&lt;/span> bits &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">8&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">uint32_t&lt;/span> speed &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">500000&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">uint16_t&lt;/span> delay;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">transfer&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> fd)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> ret;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uint8_t&lt;/span> tx[] &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">0x40&lt;/span>, &lt;span style="color:#ae81ff">0x00&lt;/span>, &lt;span style="color:#ae81ff">0x00&lt;/span>, &lt;span style="color:#ae81ff">0x00&lt;/span>, &lt;span style="color:#ae81ff">0x00&lt;/span>, &lt;span style="color:#ae81ff">0x95&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>, &lt;span style="color:#ae81ff">0xFF&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">0xDE&lt;/span>, &lt;span style="color:#ae81ff">0xAD&lt;/span>, &lt;span style="color:#ae81ff">0xBE&lt;/span>, &lt;span style="color:#ae81ff">0xEF&lt;/span>, &lt;span style="color:#ae81ff">0xBA&lt;/span>, &lt;span style="color:#ae81ff">0xAD&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">0xF0&lt;/span>, &lt;span style="color:#ae81ff">0x0D&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uint8_t&lt;/span> rx[&lt;span style="color:#a6e22e">ARRAY_SIZE&lt;/span>(tx)] &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">0&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">struct&lt;/span> spi_ioc_transfer tr &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .tx_buf &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span>)tx,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .rx_buf &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span>)rx,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .len &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">ARRAY_SIZE&lt;/span>(tx),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .delay_usecs &lt;span style="color:#f92672">=&lt;/span> delay,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .speed_hz &lt;span style="color:#f92672">=&lt;/span> speed,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .bits_per_word &lt;span style="color:#f92672">=&lt;/span> bits,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ret &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">ioctl&lt;/span>(fd, &lt;span style="color:#a6e22e">SPI_IOC_MESSAGE&lt;/span>(&lt;span style="color:#ae81ff">1&lt;/span>), &lt;span style="color:#f92672">&amp;amp;&lt;/span>tr);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (ret &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">pabort&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;can&amp;#39;t send spi message&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (ret &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; ret &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#a6e22e">ARRAY_SIZE&lt;/span>(tx); ret&lt;span style="color:#f92672">++&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>(ret &lt;span style="color:#f92672">%&lt;/span> &lt;span style="color:#ae81ff">6&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">puts&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;%.2X &amp;#34;&lt;/span>, rx[ret]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">puts&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">print_usage&lt;/span>(&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>prog)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Usage: %s [-DsbdlHOLC3]&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, prog);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">puts&lt;/span>(&lt;span style="color:#e6db74">&amp;#34; -D --device device to use (default /dev/spidev0.0)&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34; -s --speed max speed (Hz)&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34; -d --delay delay (usec)&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34; -b --bpw bits per word &lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34; -l --loop loopback&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34; -H --cpha clock phase&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34; -O --cpol clock polarity&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34; -L --lsb least significant bit first&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34; -C --cs-high chip select active high&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34; -3 --3wire SI/SO signals shared&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">exit&lt;/span>(&lt;span style="color:#ae81ff">1&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">parse_opts&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> argc, &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>argv[])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">struct&lt;/span> option lopts[] &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {&lt;span style="color:#e6db74">&amp;#34;device&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;D&amp;#39;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {&lt;span style="color:#e6db74">&amp;#34;speed&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;s&amp;#39;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {&lt;span style="color:#e6db74">&amp;#34;delay&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;d&amp;#39;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {&lt;span style="color:#e6db74">&amp;#34;bpw&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;b&amp;#39;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {&lt;span style="color:#e6db74">&amp;#34;loop&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;l&amp;#39;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {&lt;span style="color:#e6db74">&amp;#34;cpha&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;H&amp;#39;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {&lt;span style="color:#e6db74">&amp;#34;cpol&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;O&amp;#39;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {&lt;span style="color:#e6db74">&amp;#34;lsb&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;L&amp;#39;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {&lt;span style="color:#e6db74">&amp;#34;cs-high&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;C&amp;#39;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {&lt;span style="color:#e6db74">&amp;#34;3wire&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;3&amp;#39;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {&lt;span style="color:#e6db74">&amp;#34;no-cs&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;N&amp;#39;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {&lt;span style="color:#e6db74">&amp;#34;ready&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;R&amp;#39;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {NULL, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> c;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">getopt_long&lt;/span>(argc, argv, &lt;span style="color:#e6db74">&amp;#34;D:s:d:b:lHOLC3NR&amp;#34;&lt;/span>, lopts, NULL);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (c &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">switch&lt;/span> (c)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#39;D&amp;#39;&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> device &lt;span style="color:#f92672">=&lt;/span> optarg;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#39;s&amp;#39;&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> speed &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">atoi&lt;/span>(optarg);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#39;d&amp;#39;&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> delay &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">atoi&lt;/span>(optarg);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#39;b&amp;#39;&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bits &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">atoi&lt;/span>(optarg);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#39;l&amp;#39;&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode &lt;span style="color:#f92672">|=&lt;/span> SPI_LOOP;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#39;H&amp;#39;&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode &lt;span style="color:#f92672">|=&lt;/span> SPI_CPHA;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#39;O&amp;#39;&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode &lt;span style="color:#f92672">|=&lt;/span> SPI_CPOL;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#39;L&amp;#39;&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode &lt;span style="color:#f92672">|=&lt;/span> SPI_LSB_FIRST;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#39;C&amp;#39;&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode &lt;span style="color:#f92672">|=&lt;/span> SPI_CS_HIGH;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#39;3&amp;#39;&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode &lt;span style="color:#f92672">|=&lt;/span> SPI_3WIRE;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#39;N&amp;#39;&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode &lt;span style="color:#f92672">|=&lt;/span> SPI_NO_CS;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">case&lt;/span> &lt;span style="color:#e6db74">&amp;#39;R&amp;#39;&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode &lt;span style="color:#f92672">|=&lt;/span> SPI_READY;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">default&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">print_usage&lt;/span>(argv[&lt;span style="color:#ae81ff">0&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> argc, &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>argv[])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> ret &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> fd;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">parse_opts&lt;/span>(argc, argv);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fd &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">open&lt;/span>(device, O_RDWR);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (fd &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">pabort&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;can&amp;#39;t open device&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * spi mode
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ret &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">ioctl&lt;/span>(fd, SPI_IOC_WR_MODE, &lt;span style="color:#f92672">&amp;amp;&lt;/span>mode);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (ret &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">pabort&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;can&amp;#39;t set spi mode&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ret &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">ioctl&lt;/span>(fd, SPI_IOC_RD_MODE, &lt;span style="color:#f92672">&amp;amp;&lt;/span>mode);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (ret &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">pabort&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;can&amp;#39;t get spi mode&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * bits per word
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ret &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">ioctl&lt;/span>(fd, SPI_IOC_WR_BITS_PER_WORD, &lt;span style="color:#f92672">&amp;amp;&lt;/span>bits);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (ret &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">pabort&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;can&amp;#39;t set bits per word&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ret &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">ioctl&lt;/span>(fd, SPI_IOC_RD_BITS_PER_WORD, &lt;span style="color:#f92672">&amp;amp;&lt;/span>bits);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (ret &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">pabort&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;can&amp;#39;t get bits per word&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * max speed hz
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ret &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">ioctl&lt;/span>(fd, SPI_IOC_WR_MAX_SPEED_HZ, &lt;span style="color:#f92672">&amp;amp;&lt;/span>speed);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (ret &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">pabort&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;can&amp;#39;t set max speed hz&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ret &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">ioctl&lt;/span>(fd, SPI_IOC_RD_MAX_SPEED_HZ, &lt;span style="color:#f92672">&amp;amp;&lt;/span>speed);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (ret &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">pabort&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;can&amp;#39;t get max speed hz&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;spi mode: %d&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, mode);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;bits per word: %d&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, bits);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">printf&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;max speed: %d Hz (%d KHz)&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, speed, speed &lt;span style="color:#f92672">/&lt;/span> &lt;span style="color:#ae81ff">1000&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">transfer&lt;/span>(fd);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">close&lt;/span>(fd);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> ret;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>编译该文件运行，若输出结果如下则修改成功&lt;/p>
&lt;pre tabindex="0">&lt;code>spi mode: 0
bits per word: 8
max speed: 500000 Hz (500 KHz)
FF FF FF FF FF FF
40 00 00 00 00 95
FF FF FF FF FF FF
FF FF FF FF FF FF
FF FF FF FF FF FF
DE AD BE EF BA AD
F0 0D
&lt;/code>&lt;/pre></description></item><item><title>ARM GIC-400 寄存器</title><link>https://nansenli.com/zh-cn/post/kokeyuan/arm-gic-400-registers/</link><pubDate>Thu, 30 Jun 2016 20:00:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/kokeyuan/arm-gic-400-registers/</guid><description>&lt;h2 id="1简介">1.简介
&lt;/h2>&lt;p>GIC-400是用于控制中断的。在cubieboard2 上 的A20芯片采用了这款控制器。&lt;/p>
&lt;p>cubieboard2 的GIC控制器 的地址是0x01C80000&amp;mdash;0x01C87FFF ，长度等于GIC-400寄存器的长度。&lt;/p>
&lt;p>具体的内容如下所示。&lt;/p>
&lt;p>以0x01C80000为起始地址，下面给出的是偏移：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>偏移地址&lt;/th>
&lt;th>说明&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0x0000-0x0FFF&lt;/td>
&lt;td>Reserved 未使用，无用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x1000-0x1FFF&lt;/td>
&lt;td>Distributor 配置器 有用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x2000-0x3FFF&lt;/td>
&lt;td>CPU interfaces CPU接口 有用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x4000-0x4FFF&lt;/td>
&lt;td>Virtual interface control block, for the processor that is performing the access&lt;!-- raw HTML omitted -->虚拟化相关，无用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x5000-0x5FFF&lt;/td>
&lt;td>Virtual interface control block, for the processor selected by address bits [11:9]&lt;!-- raw HTML omitted -->虚拟化相关，无用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x5000-0x51FF&lt;!-- raw HTML omitted -->0x5200-0x53FF&lt;!-- raw HTML omitted -->&amp;hellip;&lt;!-- raw HTML omitted -->0x5E00-0x5FFF&lt;/td>
&lt;td>Alias for Processor 0&lt;!-- raw HTML omitted -->Alias for Processor 1&lt;!-- raw HTML omitted -->&amp;hellip;&lt;!-- raw HTML omitted -->Alias for Processor 7&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x6000-0x7FFF&lt;/td>
&lt;td>Virtual CPU interfaces&lt;!-- raw HTML omitted -->虚拟化相关，无用&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>常用的是图中第二块和第三块，0x1000~0x3fff这部分的配置。&lt;/p>
&lt;h2 id="2distributor-配置器">2.Distributor 配置器
&lt;/h2>&lt;p>distributor这块寄存器的地址 = GIC控制器 的地址 + 0x1000 ，使用的时候别忘记了偏移。然后从这里开始，有如下的寄存器&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>偏移&lt;/th>
&lt;th>名称&lt;/th>
&lt;th>可读写&lt;/th>
&lt;th>复位后&lt;/th>
&lt;th>作用&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0x000&lt;/td>
&lt;td>GICD_CTLR&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000[c]&lt;/td>
&lt;td>Distributor Control Register&lt;!-- raw HTML omitted -->写入1使能控制器，必备&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x004&lt;/td>
&lt;td>GICD_TYPER&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>Configuration-dependent[d]&lt;/td>
&lt;td>Interrupt Controller Type Register&lt;!-- raw HTML omitted -->其中可以查看中断线的总数&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x008&lt;/td>
&lt;td>GICD_IIDR&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x0200143B&lt;/td>
&lt;td>Distributor Implementer Identification Register, GICD_IIDR&lt;!-- raw HTML omitted -->存了一些版本信息，没用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x080-0x0BC&lt;/td>
&lt;td>GICD_IGROUPRn&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Interrupt Group Registers[e]&lt;!-- raw HTML omitted -->一个位图，控制中断属于A组还是B组&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x100&lt;/td>
&lt;td>GICD_ISENABLERn&lt;/td>
&lt;td>RW[f]&lt;/td>
&lt;td>SGIs and PPIs:0x0000FFFF[g]&lt;/td>
&lt;td>Interrupt Set-Enable Registers&lt;!-- raw HTML omitted -->一个位图，用于使能各个中断，写入1使能。有用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x104-0x13C&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>SPIs: 0x00000000&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x180&lt;/td>
&lt;td>GICD_ICENABLERn&lt;/td>
&lt;td>RW[f]&lt;/td>
&lt;td>0x0000FFFF[g]&lt;/td>
&lt;td>Interrupt Clear-Enable Registers&lt;!-- raw HTML omitted -->和上一个寄存器类似，作用相反，写入1禁止。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x184-0x1BC&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x200-0x23C&lt;/td>
&lt;td>GICD_ISPENDRn&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Interrupt Set-Pending Registers&lt;!-- raw HTML omitted -->pend位图，写入1可以进入pend状态&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x280-0x2BC&lt;/td>
&lt;td>GICD_ICPENDRn&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Interrupt Clear-Pending Registers&lt;!-- raw HTML omitted -->同上，写入1效果相反，阻止pend状态&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x300-0x33C&lt;/td>
&lt;td>GICD_ISACTIVERn&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Interrupt Set-Active Registers&lt;!-- raw HTML omitted -->位图，写入1可以激活中断&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x380-0x3BC&lt;/td>
&lt;td>GICD_ICACTIVERn&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Interrupt Clear-Active Registers&lt;!-- raw HTML omitted -->写入1反激活中断&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x400-0x5FC&lt;/td>
&lt;td>GICD_IPRIORITYRn&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Interrupt Priority Registers&lt;!-- raw HTML omitted -->存着各个中断的优先级，每8位算一个&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x800-0x81C&lt;/td>
&lt;td>GICD_ITARGETSRn&lt;/td>
&lt;td>RO[h]&lt;/td>
&lt;td>-&lt;/td>
&lt;td>Interrupt Processor Targets Registers[i]&lt;!-- raw HTML omitted -->某个中断应该发往哪个处理器进行处理&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x820-0x9FC&lt;/td>
&lt;td>&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xC00&lt;/td>
&lt;td>GICD_ICFGRn&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>SGIs: 0xAAAAAAAA&lt;/td>
&lt;td>Interrupt Configuration Registers, GICD_ICFGRn&lt;!-- raw HTML omitted -->配置中断是低电平触发还是下降沿触发&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xC04&lt;/td>
&lt;td>&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>PPIs: 0x55540000&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xC08-0xC7C&lt;/td>
&lt;td>&lt;/td>
&lt;td>RW[j]&lt;/td>
&lt;td>SPIs: 0x55555555&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xD00&lt;/td>
&lt;td>GICD_PPISR&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Private Peripheral Interrupt Status Register, GICD_PPISR&lt;!-- raw HTML omitted -->一般没用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xD04-0xD3C&lt;/td>
&lt;td>GICD_SPISRn&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Shared Peripheral Interrupt Status Registers, GICD_SPISRn&lt;!-- raw HTML omitted -->没用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xF00&lt;/td>
&lt;td>GICD_SGIR&lt;/td>
&lt;td>WO&lt;/td>
&lt;td>-&lt;/td>
&lt;td>Software Generated Interrupt Register&lt;!-- raw HTML omitted -->控制软中断&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xF10-0xF1C&lt;/td>
&lt;td>GICD_CPENDSGIRn&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>SGI Clear-Pending Registers&lt;!-- raw HTML omitted -->软中断的pend位&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xF20-0xF2C&lt;/td>
&lt;td>GICD_SPENDSGIRn&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>SGI Set-Pending Registers&lt;!-- raw HTML omitted -->同上，不过写入1时停止pend&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xFD0&lt;/td>
&lt;td>GICD_PIDR4&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x00000004&lt;/td>
&lt;td>Peripheral ID 4 Register&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xFD4&lt;/td>
&lt;td>GICD_PIDR5&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Peripheral ID 5 Register&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xFD8&lt;/td>
&lt;td>GICD_PIDR6&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Peripheral ID 6 Register&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xFDC&lt;/td>
&lt;td>GICD_PIDR7&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Peripheral ID 7 Register&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xFE0&lt;/td>
&lt;td>GICD_PIDR0&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x00000090&lt;/td>
&lt;td>Peripheral ID 0 Register&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xFE4&lt;/td>
&lt;td>GICD_PIDR1&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x000000B4&lt;/td>
&lt;td>Peripheral ID 1 Register&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xFE8&lt;/td>
&lt;td>GICD_PIDR2&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x0000002B&lt;/td>
&lt;td>Peripheral ID 2 Register&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xFEC&lt;/td>
&lt;td>GICD_PIDR3&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Peripheral ID 3 Register&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xFF0&lt;/td>
&lt;td>GICD_CIDR0&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x0000000D&lt;/td>
&lt;td>Component ID 0 Register&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xFF4&lt;/td>
&lt;td>GICD_CIDR1&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x000000F0&lt;/td>
&lt;td>Component ID 1 Register&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xFF8&lt;/td>
&lt;td>GICD_CIDR2&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x00000005&lt;/td>
&lt;td>Component ID 2 Register&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0xFFC&lt;/td>
&lt;td>GICD_CIDR3&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x000000B1&lt;/td>
&lt;td>Component ID 3 Register&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="3cpu接口">3.CPU接口
&lt;/h2>&lt;p>开始地址的偏移量为0x2000&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Offset&lt;/th>
&lt;th>Name&lt;/th>
&lt;th>Type&lt;/th>
&lt;th>Reset&lt;/th>
&lt;th>Description[a]&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>0x0000&lt;/td>
&lt;td>GICC_CTLR&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>CPU Interface Control Register&lt;!-- raw HTML omitted -->使能位。写入1使能&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x0004&lt;/td>
&lt;td>GICC_PMR&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Interrupt Priority Mask Register&lt;!-- raw HTML omitted -->限制中断最低优先级，高于此值无法中断，最好写大一点&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x0008&lt;/td>
&lt;td>GICC_BPR&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000002[b]&lt;/td>
&lt;td>Binary Point Register&lt;!-- raw HTML omitted -->The minimum value of the Binary Point Register depends on which security-banked copy is considered:&lt;!-- raw HTML omitted -->0x2 Secure copy&lt;!-- raw HTML omitted -->0x3 Non-secure copy&lt;!-- raw HTML omitted -->优先级分组&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x000C&lt;/td>
&lt;td>GICC_IAR&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x000003FF&lt;/td>
&lt;td>Interrupt Acknowledge Register&lt;!-- raw HTML omitted -->只读，中断id&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x0010&lt;/td>
&lt;td>GICC_EOIR&lt;/td>
&lt;td>WO&lt;/td>
&lt;td>-&lt;/td>
&lt;td>End of Interrupt Register&lt;!-- raw HTML omitted -->写入以告知cpu已经处理完中断&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x0014&lt;/td>
&lt;td>GICC_RPR&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x000000FF&lt;/td>
&lt;td>Running Priority Register&lt;!-- raw HTML omitted -->当前中断优先级&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x0018&lt;/td>
&lt;td>GICC_HPPIR&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x000003FF&lt;/td>
&lt;td>Highest Priority Pending Interrupt Register [c]&lt;!-- raw HTML omitted -->最高优先级中断号及其pend值&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x001C&lt;/td>
&lt;td>GICC_ABPR&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000003&lt;/td>
&lt;td>Aliased Binary Point Register[d]&lt;!-- raw HTML omitted -->The minimum value of the Aliased Binary Point Register is 0x3.&lt;!-- raw HTML omitted -->别名寄存器&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x0020&lt;/td>
&lt;td>GICC_AIAR&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x000003FF&lt;/td>
&lt;td>Aliased Interrupt Acknowledge Register[d]&lt;!-- raw HTML omitted -->别名寄存器&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x0024&lt;/td>
&lt;td>GICC_AEOIR&lt;/td>
&lt;td>WO&lt;/td>
&lt;td>-&lt;/td>
&lt;td>Aliased End of Interrupt Register[d]&lt;!-- raw HTML omitted -->别名寄存器&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x0028&lt;/td>
&lt;td>GICC_AHPPIR&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x000003FF&lt;/td>
&lt;td>Aliased Highest Priority Pending Interrupt Register[c][d]&lt;!-- raw HTML omitted -->别名寄存器&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x00D0&lt;/td>
&lt;td>GICC_APR0&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Active Priority Register&lt;!-- raw HTML omitted -->用于保存和恢复&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x00E0&lt;/td>
&lt;td>GICC_NSAPR0&lt;/td>
&lt;td>RW&lt;/td>
&lt;td>0x00000000&lt;/td>
&lt;td>Non-Secure Active Priority Register[d]&lt;!-- raw HTML omitted -->用于保存和恢复&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x00FC&lt;/td>
&lt;td>GICC_IIDR&lt;/td>
&lt;td>RO&lt;/td>
&lt;td>0x0202143B&lt;/td>
&lt;td>CPU Interface Identification Register, GICC_IIDR&lt;!-- raw HTML omitted -->存着版本信息&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0x1000&lt;/td>
&lt;td>GICC_DIR&lt;/td>
&lt;td>WO&lt;/td>
&lt;td>-&lt;/td>
&lt;td>Deactivate Interrupt Register&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table></description></item><item><title>NRF24L01 + STC15F204EA 无线通信 源代码</title><link>https://nansenli.com/zh-cn/post/kokeyuan/nrf24l01-stc15f204ea-wireless-communication/</link><pubDate>Sun, 01 Sep 2013 00:55:00 +0000</pubDate><guid>https://nansenli.com/zh-cn/post/kokeyuan/nrf24l01-stc15f204ea-wireless-communication/</guid><description>&lt;p>本代码适用于 NRF24L01 STC15F204EA(STC15L204EA) 组合成的小型无线收发模块,也可以适用于一般51单片机于NRF24L01通信&lt;/p>
&lt;p>前几天在网上购买的一个小型的无线收发模块,卖家发的源代码很简单,没有什么实用的地方,于是移植了原子的STM3224l01程序过来,该程序更完备,更实用&lt;/p>
&lt;p>由于几乎没有管脚用来显示收发的内容,所以使用串口通信来完成显示的内容&lt;/p>
&lt;p>适用模块图片:&lt;/p>
&lt;p>&lt;img src="https://nansenli.com/img/rescued/d098995f91a2.png"
loading="lazy"
alt="模块图片"
>&lt;/p>
&lt;p>主要接线图:&lt;/p>
&lt;p>&lt;img src="http://example.com/wiring_diagram.jpg"
loading="lazy"
alt="接线图"
>&lt;/p>
&lt;p>首先为了串口显示,在网上搜集了如下代码,之所以这样复杂,是因为这块芯片没有串口功能&lt;/p>
&lt;p>首先是H文件:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#ifndef _UART_H
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define _UART_H
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define MCU_FREQ 11059200 &lt;/span>&lt;span style="color:#75715e">// 设置晶振频率
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define UART_BUAD 38400
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define ON 1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define OFF 0
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define UART_TX_PIN P31
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define UART_TX_SET(n) UART_TX_PIN = n
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define UART_TX_HIGH() UART_TX_SET(1)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define UART_TX_LOW() UART_TX_SET(0)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define UART_TX_FLIP() UART_TX_PIN = !UART_TX_PIN
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define UART_RX_PIN P30
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define UART_RX_SET(n) UART_RX_PIN = n
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define UART_RX_HIGH() UART_RX_SET(1)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define UART_RX_LOW() UART_RX_SET(0)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define UART_RX_FLIP() UART_RX_PIN = !UART_RX_PIN
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">uartInit&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">uartSendString&lt;/span>(&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>pS);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">uartSendNum&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> num);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#endif
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>文件中要设定好晶振的频率和串号的频率,分别为MCU_FREQ和UART_BUAD,设定好以后才能正确收发&lt;/p>
&lt;p>接下来是C文件:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;stdio.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;uart.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;15f204ea.h&amp;#34; //由宏晶官网提供的头文件&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> u8;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">short&lt;/span> u16;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> u32;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> uchar;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> uint;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> BYTE;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> bit bUartFlag;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/******************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 函数名称：uartInit
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 输入参数：无
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 输出参数：无
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 函数功能：设置好定时器0的工作模式
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">/******************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">uartInit&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * 设置定时器0为16位自动重载定时器
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AUXR &lt;span style="color:#f92672">|=&lt;/span> &lt;span style="color:#ae81ff">0x80&lt;/span>; &lt;span style="color:#75715e">//定时器0为1T模式
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> TMOD &lt;span style="color:#f92672">&amp;amp;=&lt;/span> &lt;span style="color:#ae81ff">0xF0&lt;/span>; &lt;span style="color:#75715e">//设置定时器为模式0(16位自动重装载)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> TL0 &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#ae81ff">0xFFFF&lt;/span> &lt;span style="color:#f92672">-&lt;/span> MCU_FREQ &lt;span style="color:#f92672">/&lt;/span> UART_BUAD) &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0xFF&lt;/span>; &lt;span style="color:#75715e">//设置定时初值
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> TH0 &lt;span style="color:#f92672">=&lt;/span> ((&lt;span style="color:#ae81ff">0xFFFF&lt;/span> &lt;span style="color:#f92672">-&lt;/span> MCU_FREQ &lt;span style="color:#f92672">/&lt;/span> UART_BUAD) &lt;span style="color:#f92672">&amp;gt;&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">8&lt;/span>) &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0xFF&lt;/span>; &lt;span style="color:#75715e">//设置定时初值
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> TR0 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">//定时器0开始计时
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ET0 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">//使能定时器0中断
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> EA &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/******************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 函数名称：uartSendData
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 输入参数：ucData: 发送字节
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 输出参数：无
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 函数功能：使用串口发送一个字节数据
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">/******************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">uartSendData&lt;/span>(u8 ucData)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u8 ucCnt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">UART_TX_LOW&lt;/span>(); &lt;span style="color:#75715e">//串口起始位开始
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> TR0 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>; &lt;span style="color:#75715e">//定时器0开始计时
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ET0 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>; &lt;span style="color:#75715e">//使能定时器0中断
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> bUartFlag &lt;span style="color:#f92672">=&lt;/span> ON;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span>(bUartFlag &lt;span style="color:#f92672">==&lt;/span> ON);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> * 由低位开始，将数据通过串口输出
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> (ucCnt &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; ucCnt &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">8&lt;/span>; ucCnt&lt;span style="color:#f92672">++&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">UART_TX_SET&lt;/span>(ucData &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0x01&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ucData &lt;span style="color:#f92672">&amp;gt;&amp;gt;=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bUartFlag &lt;span style="color:#f92672">=&lt;/span> ON;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span>(bUartFlag &lt;span style="color:#f92672">==&lt;/span> ON);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">UART_TX_HIGH&lt;/span>(); &lt;span style="color:#75715e">// 发送串口停止位
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> bUartFlag &lt;span style="color:#f92672">=&lt;/span> ON;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span>(bUartFlag &lt;span style="color:#f92672">==&lt;/span> ON);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> TR0 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">//定时器0结束计时
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ET0 &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">//禁能定时器0中断
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/******************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 函数名称：uartSendString
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 输入参数：pS: 字符串首地址
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 输出参数：无
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 函数功能：发送字符串通过串口输出
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">/******************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">uartSendString&lt;/span>(&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>pS)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (&lt;span style="color:#f92672">*&lt;/span>pS) &lt;span style="color:#75715e">//检测字符串结束标志
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartSendData&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>pS&lt;span style="color:#f92672">++&lt;/span>); &lt;span style="color:#75715e">//发送当前字符
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartSendData&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;\r&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartSendData&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;\n&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">uartSendNum&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> num){ &lt;span style="color:#75715e">//使用sprintf函数打印整数(也可打印小数)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> temp[&lt;span style="color:#ae81ff">14&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">sprintf&lt;/span>(temp,&lt;span style="color:#e6db74">&amp;#34;%d&amp;#34;&lt;/span>,num);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartSendString&lt;/span>(temp);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/******************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 函数名称：time0ISR
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 输入参数：无
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 输出参数：无
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 函数功能：串口0服务函数
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">/******************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">time0ISR&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>) interrupt &lt;span style="color:#ae81ff">1&lt;/span> using &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> EA &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bUartFlag &lt;span style="color:#f92672">=&lt;/span> OFF;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> EA &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个代码里面调用了15f204ea.h，这是我在宏晶网站下载的头文件,便于大家查找直接列出来了。由于文件太长，这里不再全部列出。&lt;/p>
&lt;p>至此串口显示搞定了&lt;/p>
&lt;p>然后是24l01的程序代码&lt;/p>
&lt;p>先是H文件:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#ifndef __24L01_H
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define __24L01_H
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;15f204ea.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define u8 unsigned char
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define u16 unsigned int
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> uchar;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> uint;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*nRF24L01引脚定义*/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sbit CE &lt;span style="color:#f92672">=&lt;/span> P1&lt;span style="color:#f92672">^&lt;/span>&lt;span style="color:#ae81ff">4&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sbit CSN &lt;span style="color:#f92672">=&lt;/span> P1&lt;span style="color:#f92672">^&lt;/span>&lt;span style="color:#ae81ff">5&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sbit SCK &lt;span style="color:#f92672">=&lt;/span> P1&lt;span style="color:#f92672">^&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sbit MOSI &lt;span style="color:#f92672">=&lt;/span> P1&lt;span style="color:#f92672">^&lt;/span>&lt;span style="color:#ae81ff">3&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sbit MISO &lt;span style="color:#f92672">=&lt;/span> P1&lt;span style="color:#f92672">^&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sbit IRQ &lt;span style="color:#f92672">=&lt;/span> P1&lt;span style="color:#f92672">^&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//NRF24L01寄存器操作命令
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define READ_NRF_REG 0x00 &lt;/span>&lt;span style="color:#75715e">//读配置寄存器,低5位为寄存器地址
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define WRITE_NRF_REG 0x20 &lt;/span>&lt;span style="color:#75715e">//写配置寄存器,低5位为寄存器地址
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RD_RX_PLOAD 0x61 &lt;/span>&lt;span style="color:#75715e">//读RX有效数据,1~32字节
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define WR_TX_PLOAD 0xA0 &lt;/span>&lt;span style="color:#75715e">//写TX有效数据,1~32字节
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define FLUSH_TX 0xE1 &lt;/span>&lt;span style="color:#75715e">//清除TX FIFO寄存器.发射模式下用
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define FLUSH_RX 0xE2 &lt;/span>&lt;span style="color:#75715e">//清除RX FIFO寄存器.接收模式下用
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define REUSE_TX_PL 0xE3 &lt;/span>&lt;span style="color:#75715e">//重新使用上一包数据,CE为高,数据包被不断发送.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define NOP 0xFF &lt;/span>&lt;span style="color:#75715e">//空操作,可以用来读状态寄存器
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//SPI(NRF24L01)寄存器地址
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define CONFIG 0x00 &lt;/span>&lt;span style="color:#75715e">//配置寄存器地址;bit0:1接收模式,0发射模式;bit1:电选择;bit2:CRC模式;bit3:CRC使能;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//bit4:中断MAX_RT(达到最大重发次数中断)使能;bit5:中断TX_DS使能;bit6:中断RX_DR使能
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define EN_AA 0x01 &lt;/span>&lt;span style="color:#75715e">//使能自动应答功能 bit0~5,对应通道0~5
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define EN_RXADDR 0x02 &lt;/span>&lt;span style="color:#75715e">//接收地址允许,bit0~5,对应通道0~5
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define SETUP_AW 0x03 &lt;/span>&lt;span style="color:#75715e">//设置地址宽度(所有数据通道):bit1,0:00,3字节;01,4字节;02,5字节;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define SETUP_RETR 0x04 &lt;/span>&lt;span style="color:#75715e">//建立自动重发;bit3:0,自动重发计数器;bit7:4,自动重发延时 250*x+86us
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RF_CH 0x05 &lt;/span>&lt;span style="color:#75715e">//RF通道,bit6:0,工作通道频率;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RF_SETUP 0x06 &lt;/span>&lt;span style="color:#75715e">//RF寄存器;bit3:传输速率(0:1Mbps,1:2Mbps);bit2:1,发射功率;bit0:低噪声放大器增益
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define STATUS 0x07 &lt;/span>&lt;span style="color:#75715e">//状态寄存器;bit0:TX FIFO满标志;bit3:1,接收数据通道号(最大:6);bit4,达到最多次重发
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//bit5:数据发送完成中断;bit6:接收数据中断;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define MAX_TX 0x10 &lt;/span>&lt;span style="color:#75715e">//达到最大发送次数中断
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define TX_OK 0x20 &lt;/span>&lt;span style="color:#75715e">//TX发送完成中断
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_OK 0x40 &lt;/span>&lt;span style="color:#75715e">//接收到数据中断
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define OBSERVE_TX 0x08 &lt;/span>&lt;span style="color:#75715e">//发送检测寄存器,bit7:4,数据包丢失计数器;bit3:0,重发计数器
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define CD 0x09 &lt;/span>&lt;span style="color:#75715e">//载波检测寄存器,bit0,载波检测;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_ADDR_P0 0x0A &lt;/span>&lt;span style="color:#75715e">//数据通道0接收地址,最大长度5个字节,低字节在前
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_ADDR_P1 0x0B &lt;/span>&lt;span style="color:#75715e">//数据通道1接收地址,最大长度5个字节,低字节在前
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_ADDR_P2 0x0C &lt;/span>&lt;span style="color:#75715e">//数据通道2接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_ADDR_P3 0x0D &lt;/span>&lt;span style="color:#75715e">//数据通道3接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_ADDR_P4 0x0E &lt;/span>&lt;span style="color:#75715e">//数据通道4接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_ADDR_P5 0x0F &lt;/span>&lt;span style="color:#75715e">//数据通道5接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define TX_ADDR 0x10 &lt;/span>&lt;span style="color:#75715e">//发送地址(低字节在前),ShockBurstTM模式下,RX_ADDR_P0与此地址相等
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_PW_P0 0x11 &lt;/span>&lt;span style="color:#75715e">//接收数据通道0有效数据宽度(1~32字节),设置为0则非法
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_PW_P1 0x12 &lt;/span>&lt;span style="color:#75715e">//接收数据通道1有效数据宽度(1~32字节),设置为0则非法
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_PW_P2 0x13 &lt;/span>&lt;span style="color:#75715e">//接收数据通道2有效数据宽度(1~32字节),设置为0则非法
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_PW_P3 0x14 &lt;/span>&lt;span style="color:#75715e">//接收数据通道3有效数据宽度(1~32字节),设置为0则非法
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_PW_P4 0x15 &lt;/span>&lt;span style="color:#75715e">//接收数据通道4有效数据宽度(1~32字节),设置为0则非法
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_PW_P5 0x16 &lt;/span>&lt;span style="color:#75715e">//接收数据通道5有效数据宽度(1~32字节),设置为0则非法
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define FIFO_STATUS 0x17 &lt;/span>&lt;span style="color:#75715e">//FIFO状态寄存器;bit0,RX FIFO寄存器空标志;bit1,RX FIFO满标志;bit2,3,保留
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//bit4,TX FIFO空标志;bit5,TX FIFO满标志;bit6,1,循环发送上一数据包.0,不循环;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//////////////////////////////////////////////////////////////////////////////
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//24L01操作线
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define NRF24L01_CE CE &lt;/span>&lt;span style="color:#75715e">//24L01片选信号
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define NRF24L01_CSN CSN &lt;/span>&lt;span style="color:#75715e">//SPI片选信号
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define NRF24L01_IRQ IRQ &lt;/span>&lt;span style="color:#75715e">//IRQ主机数据输入
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//24L01发送接收数据宽度定义
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define TX_ADR_WIDTH 5 &lt;/span>&lt;span style="color:#75715e">//5字节的地址宽度
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_ADR_WIDTH 5 &lt;/span>&lt;span style="color:#75715e">//5字节的地址宽度
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define TX_PLOAD_WIDTH 32 &lt;/span>&lt;span style="color:#75715e">//20字节的用户数据宽度
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define RX_PLOAD_WIDTH 32 &lt;/span>&lt;span style="color:#75715e">//20字节的用户数据宽度
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Init&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>);&lt;span style="color:#75715e">//初始化
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">RX_Mode&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>);&lt;span style="color:#75715e">//配置为接收模式
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">TX_Mode&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>);&lt;span style="color:#75715e">//配置为发送模式
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>u8 &lt;span style="color:#a6e22e">NRF24L01_Check&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>);&lt;span style="color:#75715e">//检查24L01是否存在
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>u8 &lt;span style="color:#a6e22e">NRF24L01_TxPacket&lt;/span>(u8 &lt;span style="color:#f92672">*&lt;/span>txbuf);&lt;span style="color:#75715e">//发送一个包的数据
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>u8 &lt;span style="color:#a6e22e">NRF24L01_RxPacket&lt;/span>(u8 &lt;span style="color:#f92672">*&lt;/span>rxbuf);&lt;span style="color:#75715e">//接收一个包的数据
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#endif
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后是C文件:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;15f204ea.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;24l01.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;intrins.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">const&lt;/span> u8 TX_ADDRESS[TX_ADR_WIDTH]&lt;span style="color:#f92672">=&lt;/span>{&lt;span style="color:#ae81ff">0x34&lt;/span>,&lt;span style="color:#ae81ff">0x43&lt;/span>,&lt;span style="color:#ae81ff">0x10&lt;/span>,&lt;span style="color:#ae81ff">0x10&lt;/span>,&lt;span style="color:#ae81ff">0x03&lt;/span>}; &lt;span style="color:#75715e">//发送地址
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">const&lt;/span> u8 RX_ADDRESS[RX_ADR_WIDTH]&lt;span style="color:#f92672">=&lt;/span>{&lt;span style="color:#ae81ff">0x34&lt;/span>,&lt;span style="color:#ae81ff">0x43&lt;/span>,&lt;span style="color:#ae81ff">0x10&lt;/span>,&lt;span style="color:#ae81ff">0x10&lt;/span>,&lt;span style="color:#ae81ff">0x03&lt;/span>}; &lt;span style="color:#75715e">//发送地址
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/******************************************************************************************
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*延时函数
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/******************************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">inerDelay_us&lt;/span>(&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> n)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span>(;n&lt;span style="color:#f92672">&amp;gt;&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;n&lt;span style="color:#f92672">--&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">_nop_&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//****************************************************************************************
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">/*NRF24L01初始化
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//***************************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Init&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">inerDelay_us&lt;/span>(&lt;span style="color:#ae81ff">100&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CE&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">// chip enable
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> CSN&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>; &lt;span style="color:#75715e">// Spi disable
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> SCK&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">//
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/****************************************************************************************************
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*函数：uint SPI_RW(uint uchar)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*功能：NRF24L01的SPI写时序
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/****************************************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>uint &lt;span style="color:#a6e22e">SPI_RW&lt;/span>(uint uchar)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> uint bit_ctr;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span>(bit_ctr&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;bit_ctr&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#ae81ff">8&lt;/span>;bit_ctr&lt;span style="color:#f92672">++&lt;/span>) &lt;span style="color:#75715e">// output 8-bit
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MOSI &lt;span style="color:#f92672">=&lt;/span> (uchar &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#ae81ff">0x80&lt;/span>); &lt;span style="color:#75715e">// output &amp;#39;uchar&amp;#39;, MSB to MOSI
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> uchar &lt;span style="color:#f92672">=&lt;/span> (uchar &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>); &lt;span style="color:#75715e">// shift next bit into MSB..
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> SCK &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>; &lt;span style="color:#75715e">// Set SCK high..
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> uchar &lt;span style="color:#f92672">|=&lt;/span> MISO; &lt;span style="color:#75715e">// capture current MISO bit
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> SCK &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">// ..then set SCK low again
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span>(uchar); &lt;span style="color:#75715e">// return read uchar
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/****************************************************************************************************
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*函数：uchar SPI_Read(uchar reg)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*功能：NRF24L01的SPI时序
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/****************************************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>uchar &lt;span style="color:#a6e22e">NRF24L01_Read_Reg&lt;/span>(uchar reg)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> uchar reg_val;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CSN &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">// CSN low, initialize SPI communication...
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">SPI_RW&lt;/span>(reg); &lt;span style="color:#75715e">// Select register to read from..
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> reg_val &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">SPI_RW&lt;/span>(&lt;span style="color:#ae81ff">0&lt;/span>); &lt;span style="color:#75715e">// ..then read registervalue
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> CSN &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>; &lt;span style="color:#75715e">// CSN high, terminate SPI communication
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span>(reg_val); &lt;span style="color:#75715e">// return register value
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/****************************************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*功能：NRF24L01读写寄存器函数
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/****************************************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>uint &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(uchar reg, uchar value)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> uint status;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CSN &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">// CSN low, init SPI transaction
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> status &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">SPI_RW&lt;/span>(reg); &lt;span style="color:#75715e">// select register
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">SPI_RW&lt;/span>(value); &lt;span style="color:#75715e">// ..and write value to it..
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> CSN &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>; &lt;span style="color:#75715e">// CSN high again
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span>(status); &lt;span style="color:#75715e">// return nRF24L01 status uchar
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/****************************************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*函数：uint SPI_Read_Buf(uchar reg, uchar *pBuf, uchar uchars)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*功能: 用于读数据，reg：为寄存器地址，pBuf：为待读出数据地址，uchars：读出数据的个数
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/****************************************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>uint &lt;span style="color:#a6e22e">NRF24L01_Read_Buf&lt;/span>(uchar reg, uchar &lt;span style="color:#f92672">*&lt;/span>pBuf, uchar uchars)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> uint status,uchar_ctr;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CSN &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">// Set CSN low, init SPI tranaction
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> status &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">SPI_RW&lt;/span>(reg); &lt;span style="color:#75715e">// Select register to write to and read status uchar
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span>(uchar_ctr&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;uchar_ctr&lt;span style="color:#f92672">&amp;lt;&lt;/span>uchars;uchar_ctr&lt;span style="color:#f92672">++&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pBuf[uchar_ctr] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">SPI_RW&lt;/span>(&lt;span style="color:#ae81ff">0&lt;/span>); &lt;span style="color:#75715e">//
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CSN &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span>(status); &lt;span style="color:#75715e">// return nRF24L01 status uchar
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*********************************************************************************************************
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*函数：uint SPI_Write_Buf(uchar reg, uchar *pBuf, uchar uchars)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*功能: 用于写数据：为寄存器地址，pBuf：为待写入数据地址，uchars：写入数据的个数
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">/*********************************************************************************************************/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>uint &lt;span style="color:#a6e22e">NRF24L01_Write_Buf&lt;/span>(uchar reg, uchar &lt;span style="color:#f92672">*&lt;/span>pBuf, uchar uchars)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> uint status,uchar_ctr;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CSN &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">//SPI使能
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> status &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">SPI_RW&lt;/span>(reg);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span>(uchar_ctr&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>; uchar_ctr&lt;span style="color:#f92672">&amp;lt;&lt;/span>uchars; uchar_ctr&lt;span style="color:#f92672">++&lt;/span>) &lt;span style="color:#75715e">//
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">SPI_RW&lt;/span>(&lt;span style="color:#f92672">*&lt;/span>pBuf&lt;span style="color:#f92672">++&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CSN &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>; &lt;span style="color:#75715e">//关闭SPI
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">return&lt;/span>(status); &lt;span style="color:#75715e">//
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//检测24L01是否存在
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//返回值:0，成功;1，失败
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>u8 &lt;span style="color:#a6e22e">NRF24L01_Check&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u8 buf[&lt;span style="color:#ae81ff">5&lt;/span>]&lt;span style="color:#f92672">=&lt;/span>{&lt;span style="color:#ae81ff">0&lt;/span>XA5,&lt;span style="color:#ae81ff">0&lt;/span>XA5,&lt;span style="color:#ae81ff">0&lt;/span>XA5,&lt;span style="color:#ae81ff">0&lt;/span>XA5,&lt;span style="color:#ae81ff">0&lt;/span>XA5};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u8 i;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">NRF24L01_Write_Buf&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>TX_ADDR,buf,&lt;span style="color:#ae81ff">5&lt;/span>);&lt;span style="color:#75715e">//写入5个字节的地址.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Read_Buf&lt;/span>(TX_ADDR,buf,&lt;span style="color:#ae81ff">5&lt;/span>); &lt;span style="color:#75715e">//读出写入的地址
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">for&lt;/span>(i&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;i&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#ae81ff">5&lt;/span>;i&lt;span style="color:#f92672">++&lt;/span>) &lt;span style="color:#66d9ef">if&lt;/span>(buf[i]&lt;span style="color:#f92672">!=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>XA5)&lt;span style="color:#66d9ef">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span>(i&lt;span style="color:#f92672">!=&lt;/span>&lt;span style="color:#ae81ff">5&lt;/span>)&lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;&lt;span style="color:#75715e">//检测24L01错误
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">//检测到24L01
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>u8 &lt;span style="color:#a6e22e">NRF24L01_TxPacket&lt;/span>(u8 &lt;span style="color:#f92672">*&lt;/span>txbuf)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u8 sta;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NRF24L01_CE&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">NRF24L01_Write_Buf&lt;/span>(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);&lt;span style="color:#75715e">//写数据到TX BUF 32个字节
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> NRF24L01_CE&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>;&lt;span style="color:#75715e">//启动发送
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">while&lt;/span>(NRF24L01_IRQ&lt;span style="color:#f92672">!=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>);&lt;span style="color:#75715e">//等待发送完成
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> sta&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#a6e22e">NRF24L01_Read_Reg&lt;/span>(STATUS); &lt;span style="color:#75715e">//读取状态寄存器的值
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>STATUS,sta); &lt;span style="color:#75715e">//清除TX_DS或MAX_RT中断标志
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span>(sta&lt;span style="color:#f92672">&amp;amp;&lt;/span>MAX_TX)&lt;span style="color:#75715e">//达到最大重发次数
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(FLUSH_TX,&lt;span style="color:#ae81ff">0xff&lt;/span>);&lt;span style="color:#75715e">//清除TX FIFO寄存器
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">return&lt;/span> MAX_TX;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span>(sta&lt;span style="color:#f92672">&amp;amp;&lt;/span>TX_OK)&lt;span style="color:#75715e">//发送完成
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> TX_OK;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0xff&lt;/span>;&lt;span style="color:#75715e">//其他原因发送失败
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//启动NRF24L01发送一次数据
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//txbuf:待发送数据首地址
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//返回值:0，接收完成；其他，错误代码
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>u8 &lt;span style="color:#a6e22e">NRF24L01_RxPacket&lt;/span>(u8 &lt;span style="color:#f92672">*&lt;/span>rxbuf)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u8 sta;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sta&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#a6e22e">NRF24L01_Read_Reg&lt;/span>(STATUS); &lt;span style="color:#75715e">//读取状态寄存器的值
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>STATUS,sta); &lt;span style="color:#75715e">//清除TX_DS或MAX_RT中断标志
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span>(sta&lt;span style="color:#f92672">&amp;amp;&lt;/span>RX_OK)&lt;span style="color:#75715e">//接收到数据
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">NRF24L01_Read_Buf&lt;/span>(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);&lt;span style="color:#75715e">//读取数据
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(FLUSH_RX,&lt;span style="color:#ae81ff">0xff&lt;/span>);&lt;span style="color:#75715e">//清除RX FIFO寄存器
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;&lt;span style="color:#75715e">//没收到任何数据
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//该函数初始化NRF24L01到RX模式
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//设置RX地址,写RX数据宽度,选择RF频道,波特率和LNA HCURR
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//当CE变高后,即进入RX模式,并可以接收数据了
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">RX_Mode&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NRF24L01_CE&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">NRF24L01_Write_Buf&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>RX_ADDR_P0,(u8&lt;span style="color:#f92672">*&lt;/span>)RX_ADDRESS,RX_ADR_WIDTH);&lt;span style="color:#75715e">//写RX节点地址
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>EN_AA,&lt;span style="color:#ae81ff">0x01&lt;/span>); &lt;span style="color:#75715e">//使能通道0的自动应答
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>EN_RXADDR,&lt;span style="color:#ae81ff">0x01&lt;/span>);&lt;span style="color:#75715e">//使能通道0的接收地址
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>RF_CH,&lt;span style="color:#ae81ff">40&lt;/span>); &lt;span style="color:#75715e">//设置RF通信频率
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>RX_PW_P0,RX_PLOAD_WIDTH);&lt;span style="color:#75715e">//选择通道0的有效数据宽度
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>RF_SETUP,&lt;span style="color:#ae81ff">0x0f&lt;/span>);&lt;span style="color:#75715e">//设置TX发射参数,0db增益,2Mbps,低噪声增益开启
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>CONFIG, &lt;span style="color:#ae81ff">0x0f&lt;/span>);&lt;span style="color:#75715e">//配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> NRF24L01_CE &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>; &lt;span style="color:#75715e">//CE为高,进入接收模式
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//该函数初始化NRF24L01到TX模式
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//设置TX地址,写TX数据宽度,设置RX自动应答的地址,填充TX发送数据,选择RF频道,波特率和LNA HCURR
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//PWR_UP,CRC使能
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//当CE变高后,即进入RX模式,并可以接收数据了
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//CE为高大于10us,则启动发送.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">TX_Mode&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NRF24L01_CE&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">NRF24L01_Write_Buf&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>TX_ADDR,(u8&lt;span style="color:#f92672">*&lt;/span>)TX_ADDRESS,TX_ADR_WIDTH);&lt;span style="color:#75715e">//写TX节点地址
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Buf&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>RX_ADDR_P0,(u8&lt;span style="color:#f92672">*&lt;/span>)RX_ADDRESS,RX_ADR_WIDTH); &lt;span style="color:#75715e">//设置TX节点地址,主要为了使能ACK
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>EN_AA,&lt;span style="color:#ae81ff">0x01&lt;/span>); &lt;span style="color:#75715e">//使能通道0的自动应答
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>EN_RXADDR,&lt;span style="color:#ae81ff">0x01&lt;/span>); &lt;span style="color:#75715e">//使能通道0的接收地址
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>SETUP_RETR,&lt;span style="color:#ae81ff">0x1a&lt;/span>);&lt;span style="color:#75715e">//设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>RF_CH,&lt;span style="color:#ae81ff">40&lt;/span>); &lt;span style="color:#75715e">//设置RF通道为40
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>RF_SETUP,&lt;span style="color:#ae81ff">0x0f&lt;/span>); &lt;span style="color:#75715e">//设置TX发射参数,0db增益,2Mbps,低噪声增益开启
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">NRF24L01_Write_Reg&lt;/span>(WRITE_NRF_REG&lt;span style="color:#f92672">+&lt;/span>CONFIG,&lt;span style="color:#ae81ff">0x0e&lt;/span>); &lt;span style="color:#75715e">//配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式,开启所有中断
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> NRF24L01_CE&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>;&lt;span style="color:#75715e">//CE为高,10us后启动发送
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">inerDelay_us&lt;/span>(&lt;span style="color:#ae81ff">20&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>接下来是主程序调试部分:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;15f204ea.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;24l01.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;uart.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;intrins.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">delay500ms&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>) &lt;span style="color:#75715e">//误差 -0.000000000063us
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> a,b,c;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span>(c&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">212&lt;/span>;c&lt;span style="color:#f92672">&amp;gt;&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;c&lt;span style="color:#f92672">--&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span>(b&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">160&lt;/span>;b&lt;span style="color:#f92672">&amp;gt;&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;b&lt;span style="color:#f92672">--&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span>(a&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">80&lt;/span>;a&lt;span style="color:#f92672">&amp;gt;&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;a&lt;span style="color:#f92672">--&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">_nop_&lt;/span>(); &lt;span style="color:#75715e">//if Keil,require use intrins.h
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">delay100us&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>) &lt;span style="color:#75715e">//误差 -0.083188657407us
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> a,b;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span>(b&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">58&lt;/span>;b&lt;span style="color:#f92672">&amp;gt;&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;b&lt;span style="color:#f92672">--&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span>(a&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">8&lt;/span>;a&lt;span style="color:#f92672">&amp;gt;&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;a&lt;span style="color:#f92672">--&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u8 tmp_buf[&lt;span style="color:#ae81ff">33&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u8 key,mode;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> u16 t&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">delay500ms&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartInit&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartSendString&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;测试&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartSendNum&lt;/span>(&lt;span style="color:#ae81ff">1234&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">NRF24L01_Init&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span>(&lt;span style="color:#a6e22e">NRF24L01_Check&lt;/span>())&lt;span style="color:#75715e">//检测不到24L01
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartSendString&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;初始化失败&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">delay500ms&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartSendString&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;请检查&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">delay500ms&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartSendString&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;启动&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span>(&lt;span style="color:#ae81ff">0&lt;/span>){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">RX_Mode&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartSendString&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;接收模式&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span>(&lt;span style="color:#ae81ff">1&lt;/span>){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span>(&lt;span style="color:#a6e22e">NRF24L01_RxPacket&lt;/span>(tmp_buf)&lt;span style="color:#f92672">==&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tmp_buf[&lt;span style="color:#ae81ff">32&lt;/span>]&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;&lt;span style="color:#75715e">//加入字符串结束符
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">uartSendString&lt;/span>(tmp_buf);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#a6e22e">delay100us&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">TX_Mode&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartSendString&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;发送模式&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39; &amp;#39;&lt;/span>;&lt;span style="color:#75715e">//从空格键开始
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">while&lt;/span>(&lt;span style="color:#ae81ff">1&lt;/span>){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span>(&lt;span style="color:#a6e22e">NRF24L01_TxPacket&lt;/span>(tmp_buf)&lt;span style="color:#f92672">==&lt;/span>TX_OK)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartSendString&lt;/span>(tmp_buf);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key&lt;span style="color:#f92672">=&lt;/span>mode;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span>(t&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;t&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#ae81ff">32&lt;/span>;t&lt;span style="color:#f92672">++&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key&lt;span style="color:#f92672">++&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span>(key&lt;span style="color:#f92672">&amp;gt;&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;~&amp;#39;&lt;/span>))key&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39; &amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tmp_buf[t]&lt;span style="color:#f92672">=&lt;/span>key;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mode&lt;span style="color:#f92672">++&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span>(mode&lt;span style="color:#f92672">&amp;gt;&lt;/span>&lt;span style="color:#e6db74">&amp;#39;~&amp;#39;&lt;/span>)mode&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39; &amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tmp_buf[&lt;span style="color:#ae81ff">32&lt;/span>]&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;&lt;span style="color:#75715e">//加入结束符
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">uartSendString&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;发送失败&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">delay500ms&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>设定晶振为11.0594MHZ,写入程序以后&lt;/p>
&lt;p>首先连接好串口,然后上电,经过半秒以后,程序会发送串口数据给电脑&amp;quot;测试&amp;quot;和数字&amp;quot;1234&amp;quot;,注意串口工作频率是38400,&lt;/p>
&lt;p>如果确认无误以后应该是可以收到串口数据的,否则就是各个环节的频率没有设定对,要么就是数据线的问题&lt;/p>
&lt;p>然后程序会进行检查,成功返回&amp;quot;启动&amp;quot;,失败则显示&amp;quot;初始化失败&amp;quot;,&amp;ldquo;请检查&amp;rdquo;.失败的这种情况是因为nrf24l01烧坏了,或者是管脚接的不正确&lt;/p>
&lt;p>根据if后面括号里是1还是0,会选择进入接收模式或者发送模式.&lt;/p>
&lt;p>如果是接收模式的话,程序会通过串口发送&amp;quot;接收模式&amp;quot;,如果接收到数据则会返回数据内容;&lt;/p>
&lt;p>如果是发送模式,则程序会发送一段代码(一些不断变化的字符)给指定地址,如果发送成功,则返回发送的长度,并返回发送的数据,发送失败的时候,会显示发送失败;&lt;/p>
&lt;p>如果发送失败,可能是因为接收端不存在,此时NRF24L01_TxPacket返回的数据是10(发送重试最大次数),而不是32(发送的数据位数),所以会报错&lt;/p>
&lt;p>也可能是因为其他原因,则NRF24L01返回0xff,说明是其他原因失败,这时问题就不清楚了,需要仔细查找&lt;/p>
&lt;p>将两个芯片一块写入发程序,一块写入收程序,就可以查看效果了^_^&lt;/p>
&lt;p>至此NRF24L01 与 STC15F204EA (STC15L204EA) 收发搞定&lt;/p></description></item></channel></rss>