[{"content":"距离上一次写《Hexo折腾记》已经过去很多年了。那时候折腾 Hexo + Next，主要是在装 Node、装插件、改主题配置、解决部署抽风。现在重新整理博客，我换成了 Hugo + PaperMod。\n这次不想只停留在“能跑起来”。我的目标是：主题保持可更新，文章按技术、股票、随笔分区管理，首页和导航更像自己的站点，搜索、归档、评论、目录、提示框、代码高亮、备案这些功能都补齐，并且尽量把改动放在 Hugo 推荐的覆盖层里，不直接魔改主题源码。\n这篇就记录一下整个折腾过程。\n01#目录结构 最后站点工程大概是这样：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 hugo/ ├── archetypes/ # 文章模板 ├── assets/ # Hugo Pipes 处理的样式 │ └── css/ │ └── extended/ # PaperMod 自动加载的扩展样式 ├── content/ # 文章和页面 │ ├── tech/ │ ├── stock/ │ ├── essay/ │ ├── topics/ │ ├── about.md │ ├── archives.md │ └── search.md ├── data/ # notice 图标等数据 ├── i18n/ # 中文翻译 ├── layouts/ # 覆盖 PaperMod 的模板 ├── static/ # 原样发布到站点根路径 ├── themes/ │ └── PaperMod/ # git submodule ├── hugo.yaml └── vercel.json 我把 PaperMod 放在 themes/PaperMod，用 git submodule 管理：\n1 2 git submodule add https://github.com/adityatelange/hugo-PaperMod themes/PaperMod git submodule update --init --recursive 这样做的好处是，后续主题更新时只要：\n1 git submodule update --remote --merge 真正的自定义改动尽量放在根目录：\n1 2 3 4 5 layouts/ assets/css/extended/ static/ data/ i18n/ Hugo 的模板查找优先级会先找站点根目录，再找主题目录。所以只要在根目录放同名模板，就能覆盖主题行为，不需要直接改 themes/PaperMod。\n02#基础配置 主配置文件是 hugo.yaml。\n一开始先把站点基本信息、主题和语言配好：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # 站点的根 URL，生产环境填正式域名 baseURL: \u0026#34;https://www.zeyes.org/\u0026#34; # 站点区域/语言标识，配合 i18n、日期格式和搜索引擎语义使用 locale: \u0026#34;zh-Hans-CN\u0026#34; defaultContentLanguage: \u0026#34;zh-cn\u0026#34; # 站点标题和描述，主题模板、RSS、SEO 元信息都会用到 title: \u0026#34;问道凌虚\u0026#34; description: \u0026#34;从问道开始，最终凌虚而上！\u0026#34; # 使用的 Hugo 主题，PaperMod 放在 themes/PaperMod theme: [\u0026#34;PaperMod\u0026#34;] # 站点作者，会用于文章元信息或主题模板 author: \u0026#34;Zeyes\u0026#34; # 是否生成 robots.txt，方便搜索引擎爬虫识别抓取规则 enableRobotsTXT: true # 是否构建 draft: true 的草稿文章；生产环境一般关闭 buildDrafts: false # 是否构建发布日期在未来的文章；生产环境一般关闭 buildFuture: false # 是否构建已经过期的文章；这里保留，避免旧内容被意外过滤 buildExpired: true # 中文、日文、韩文等 CJK 语言需要打开，字数统计和阅读时间更准确 hasCJKLanguage: true hasCJKLanguage: true 对中文站点比较重要，Hugo 统计字数、阅读时间时会更符合中文内容。\n首页除了 HTML 和 RSS，我还额外打开了 JSON 输出：\n1 2 3 4 5 6 outputs: # 首页除了生成 HTML/RSS，再额外生成 JSON，PaperMod 搜索依赖这个 JSON home: - \u0026#34;HTML\u0026#34; - \u0026#34;RSS\u0026#34; - \u0026#34;JSON\u0026#34; 这个 JSON 后面会给 PaperMod 的搜索页用。如果少了它，搜索页面能打开，但没有数据。\n03#内容分区 我不想所有文章都堆在 posts 下面，于是按内容类型分成三个 section：\n1 2 3 4 content/ ├── tech/ ├── stock/ └── essay/ 然后在 hugo.yaml 里告诉 PaperMod，首页、归档、上一篇下一篇等文章列表主要看这些 section：\n1 2 3 params: # 站点主要内容分区；首页、归档、上一篇/下一篇等列表会优先读取这些 section mainSections: [\u0026#34;tech\u0026#34;, \u0026#34;stock\u0026#34;, \u0026#34;essay\u0026#34;] 为了 URL 更统一，我又配置了永久链接：\n1 2 3 4 5 6 7 8 9 10 11 12 # 自定义文章和 section 的 URL 规则，避免不同分区生成的路径风格不一致 permalinks: # 单篇文章 URL page: tech: \u0026#34;/posts/:sections/:slug\u0026#34; stock: \u0026#34;/posts/:sections/:slug\u0026#34; essay: \u0026#34;/posts/:sections/:slug\u0026#34; # 分区列表 URL section: tech: \u0026#34;/posts/:sections/\u0026#34; stock: \u0026#34;/posts/:sections/\u0026#34; essay: \u0026#34;/posts/:sections/\u0026#34; 比如一篇文章放在：\n1 content/tech/2026/06/2026-06-07-Hugo折腾记.md 只要 front matter 里有：\n1 2 # slug 会参与 permalink 生成最终 URL，建议手工写成稳定的英文短横线格式 slug: hugo-papermod-customization 最后 URL 就会是：\n1 /posts/tech/2026/06/hugo-papermod-customization 文章模板也简单改了一下，让新文章默认是草稿：\n1 2 3 4 5 6 7 8 9 10 +++ # 新文章创建时间，hugo new 时自动填入 date = \u0026#39;{{ .Date }}\u0026#39; # 新文章默认作为草稿，发布前再改成 false draft = true # 默认用文件名生成标题，后面可以手工改 title = \u0026#39;{{ replace .File.ContentBaseName \u0026#34;-\u0026#34; \u0026#34; \u0026#34; | title }}\u0026#39; +++ 发布前再把 draft 改成 false，或者像我旧文章迁移时一样保留 status: publish。\n04#首页改成个人资料模式 PaperMod 有一个 profileMode，适合个人博客首页。\n我在 hugo.yaml 里这样配置：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 params: # ---------- 首页个人资料模式 ---------- profileMode: # 开启后首页显示头像、标题、按钮，而不是普通文章列表 enabled: true # 首页主标题 title: \u0026#34;从问道开始，最终凌虚而上！\u0026#34; # 首页副标题；两个空格 + \\n 可以在 Markdown 渲染后换行 subtitle: \u0026#34;你好呀，欢迎来访 \\n这里记录技术、投资与生活里的折腾\u0026#34; # 头像路径，文件放在 static/avatar.jpg，访问路径就是 /avatar.jpg imageUrl: \u0026#34;/avatar.jpg\u0026#34; # 头像尺寸 imageWidth: 120 imageHeight: 120 # 图片 title 属性 imageTitle: \u0026#34;avatar\u0026#34; # 首页入口按钮 buttons: - name: \u0026#34;技术\u0026#34; url: \u0026#34;/topics/tech/\u0026#34; - name: \u0026#34;股票\u0026#34; url: \u0026#34;/topics/stock/\u0026#34; - name: \u0026#34;随笔\u0026#34; url: \u0026#34;/topics/essay/\u0026#34; # ---------- 社交图标 ---------- socialIcons: # PaperMod 内置 github 图标 - name: \u0026#34;github\u0026#34; title: \u0026#34;GitHub\u0026#34; url: \u0026#34;https://github.com/lixize\u0026#34; # 邮箱链接 - name: \u0026#34;email\u0026#34; title: \u0026#34;Email\u0026#34; url: \u0026#34;mailto:lixize8888@163.com\u0026#34; # RSS 订阅入口 - name: \u0026#34;rss\u0026#34; title: \u0026#34;RSS\u0026#34; url: \u0026#34;/index.xml\u0026#34; 头像放在 static/avatar.jpg，最终访问路径就是：\n1 /avatar.jpg PaperMod 的 static/ 规则很好用：放进去什么，构建后就原样出现在站点根路径。\n05#导航菜单和下拉分类 默认 PaperMod 的菜单是平铺的。我想要“分类”下面挂三个子菜单：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 menu: main: # identifier 是菜单项唯一标识；weight 控制排序，数字越小越靠前 - identifier: \u0026#34;home\u0026#34; name: \u0026#34;主页\u0026#34; url: \u0026#34;/\u0026#34; weight: 10 - identifier: \u0026#34;search\u0026#34; name: \u0026#34;搜索\u0026#34; url: \u0026#34;/search/\u0026#34; weight: 20 # 分类父菜单；下面的 tech/stock/essay 通过 parent 挂到这里 - identifier: \u0026#34;categories\u0026#34; name: \u0026#34;分类\u0026#34; url: \u0026#34;/topics/\u0026#34; weight: 30 - identifier: \u0026#34;tech\u0026#34; name: \u0026#34;技术\u0026#34; url: \u0026#34;/topics/tech/\u0026#34; # parent 指向 categories，表示这是“分类”的子菜单 parent: \u0026#34;categories\u0026#34; weight: 31 - identifier: \u0026#34;stock\u0026#34; name: \u0026#34;股票\u0026#34; url: \u0026#34;/topics/stock/\u0026#34; parent: \u0026#34;categories\u0026#34; weight: 32 - identifier: \u0026#34;essay\u0026#34; name: \u0026#34;随笔\u0026#34; url: \u0026#34;/topics/essay/\u0026#34; parent: \u0026#34;categories\u0026#34; weight: 33 - identifier: \u0026#34;tags\u0026#34; name: \u0026#34;标签\u0026#34; url: \u0026#34;/tags/\u0026#34; weight: 40 - identifier: \u0026#34;archives\u0026#34; name: \u0026#34;归档\u0026#34; url: \u0026#34;/archives/\u0026#34; weight: 50 - identifier: \u0026#34;about\u0026#34; name: \u0026#34;关于\u0026#34; url: \u0026#34;/about/\u0026#34; weight: 60 菜单数据有父子关系后，还需要覆盖主题的 header。\n文件：\n1 layouts/partials/header.html 核心逻辑是判断 .HasChildren，如果有子菜单，就输出一层 .submenu：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 {{- range site.Menus.main }} {{- $raw_url := .URL | default \u0026#34;#\u0026#34; }} \u0026lt;li{{- if .HasChildren }} class=\u0026#34;menu-item-has-children\u0026#34;{{- end }}\u0026gt; {{- if .HasChildren }} \u0026lt;a href=\u0026#34;{{ $raw_url | absLangURL }}\u0026#34; class=\u0026#34;menu-trigger\u0026#34; aria-haspopup=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;span\u0026gt;{{ .Name }}\u0026lt;/span\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;ul class=\u0026#34;submenu\u0026#34;\u0026gt; {{- range .Children }} \u0026lt;li\u0026gt; \u0026lt;a href=\u0026#34;{{ .URL | absLangURL }}\u0026#34; title=\u0026#34;{{ .Title | default .Name }}\u0026#34;\u0026gt; \u0026lt;span\u0026gt;{{ .Name }}\u0026lt;/span\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;/li\u0026gt; {{- end }} \u0026lt;/ul\u0026gt; {{- else }} \u0026lt;a href=\u0026#34;{{ $raw_url | absLangURL }}\u0026#34; title=\u0026#34;{{ .Title | default .Name }}\u0026#34;\u0026gt; \u0026lt;span\u0026gt;{{ .Name }}\u0026lt;/span\u0026gt; \u0026lt;/a\u0026gt; {{- end }} \u0026lt;/li\u0026gt; {{- end }} 样式放在：\n1 assets/css/extended/custom.css 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 .menu { overflow: visible; } .menu li { position: relative; } .menu-trigger { display: block; height: var(--header-height); padding: 0; color: var(--primary); font: inherit; font-size: 16px; line-height: var(--header-height); background: transparent; border: 0; cursor: pointer; } .menu-trigger::after { content: \u0026#34;\u0026#34;; display: inline-block; width: 0.42em; height: 0.42em; margin-left: 0.42em; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: translateY(-0.18em) rotate(45deg); } .submenu { position: absolute; top: calc(100% - 0.25rem); left: 50%; z-index: 20; min-width: 7rem; padding: 0.35rem; list-style: none; background: var(--entry); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 12px 30px rgba(0, 0, 0, 0.12); opacity: 0; visibility: hidden; transform: translate(-50%, -0.35rem); transition: opacity 0.16s ease, transform 0.16s ease, visibility 0.16s ease; } .menu-item-has-children:hover .submenu, .menu-item-has-children:focus-within .submenu { opacity: 1; visibility: visible; transform: translate(-50%, 0); } 这里没有写复杂 JS，靠 hover 和 focus-within 就够了。鼠标可用，键盘 Tab 聚焦也能展开。\n06#自定义分类入口页 这里的“分类”不是 Hugo 默认 taxonomy，而是我自己做的专题入口。\n入口页面：\n1 content/topics/_index.md 内容很短，主要把三个分类的数据写在 front matter：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 --- # 页面标题 title: \u0026#34;分类\u0026#34; # 固定页面访问路径 url: \u0026#34;/topics/\u0026#34; # 指定使用 layouts/topic-index.html layout: \u0026#34;topic-index\u0026#34; # 自定义专题数据，模板会遍历这里生成入口卡片 topics: - title: \u0026#34;技术\u0026#34; icon: \u0026#34;💻\u0026#34; url: \u0026#34;/topics/tech/\u0026#34; # 用来统计 content/tech 下的文章数量 section: \u0026#34;tech\u0026#34; description: \u0026#34;技术实践、开发记录与折腾笔记\u0026#34; - title: \u0026#34;股票\u0026#34; icon: \u0026#34;📈\u0026#34; url: \u0026#34;/topics/stock/\u0026#34; section: \u0026#34;stock\u0026#34; description: \u0026#34;投资观察与交易复盘\u0026#34; - title: \u0026#34;随笔\u0026#34; icon: \u0026#34;📝\u0026#34; url: \u0026#34;/topics/essay/\u0026#34; section: \u0026#34;essay\u0026#34; description: \u0026#34;生活、想法与片段记录\u0026#34; --- 对应模板：\n1 layouts/topic-index.html 核心代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 {{- define \u0026#34;main\u0026#34; }} \u0026lt;header class=\u0026#34;page-header\u0026#34;\u0026gt; {{- partial \u0026#34;breadcrumbs.html\u0026#34; . }} \u0026lt;h1\u0026gt;{{ .Title }}\u0026lt;/h1\u0026gt; {{- if .Description }} \u0026lt;div class=\u0026#34;post-description\u0026#34;\u0026gt; {{ .Description | markdownify }} \u0026lt;/div\u0026gt; {{- end }} \u0026lt;/header\u0026gt; \u0026lt;div class=\u0026#34;topic-list\u0026#34;\u0026gt; {{- range .Params.topics }} {{- $count := len (where site.RegularPages \u0026#34;Section\u0026#34; .section) }} \u0026lt;a class=\u0026#34;topic-item\u0026#34; href=\u0026#34;{{ .url | absLangURL }}\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;topic-icon\u0026#34;\u0026gt;{{ .icon }}\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;topic-body\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;topic-title\u0026#34;\u0026gt;{{ .title }}\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;topic-description\u0026#34;\u0026gt;{{ .description }}\u0026lt;/span\u0026gt; \u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;topic-count\u0026#34;\u0026gt;{{ $count }}\u0026lt;/span\u0026gt; \u0026lt;/a\u0026gt; {{- end }} \u0026lt;/div\u0026gt; {{- end }} 这里比较关键的是：\n1 {{- $count := len (where site.RegularPages \u0026#34;Section\u0026#34; .section) }} 它会按 section 统计文章数量，所以分类入口右侧能显示“技术有多少篇、股票有多少篇、随笔有多少篇”。\n样式：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 .topic-list { display: grid; gap: 0.85rem; margin-top: var(--content-gap); } .topic-item { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 0.85rem; align-items: center; padding: 1rem; background: var(--entry); border: 1px solid var(--border); border-radius: var(--radius); transition: border-color 0.2s ease, transform 0.2s ease; } .topic-item:hover, .topic-item:focus { border-color: var(--tertiary); transform: translateY(-1px); } .topic-count { padding: 0.15rem 0.5rem; background: var(--code-bg); border-radius: 999px; } 07#自定义分区列表页 分类入口只是入口。点进“技术”以后，还需要展示 content/tech 下的文章。\n页面文件：\n1 2 3 content/topics/tech/_index.md content/topics/stock/_index.md content/topics/essay/_index.md 以技术页为例：\n1 2 3 4 5 6 7 8 9 10 11 12 13 --- # 页面标题 title: \u0026#34;技术\u0026#34; # 这个页面本身的访问路径 url: \u0026#34;/topics/tech/\u0026#34; # 指定使用 layouts/category-list.html layout: \u0026#34;category-list\u0026#34; # 告诉模板当前列表页要展示 content/tech 这个 section targetSection: \u0026#34;tech\u0026#34; --- 三个页面共用一个模板：\n1 layouts/category-list.html 模板里只关心 targetSection：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 {{- define \u0026#34;main\u0026#34; }} {{- $targetSection := .Params.targetSection | default \u0026#34;\u0026#34; }} {{- $pages := where site.RegularPages \u0026#34;Section\u0026#34; $targetSection }} {{- $pages = where $pages \u0026#34;Params.hiddenInHomeList\u0026#34; \u0026#34;!=\u0026#34; \u0026#34;true\u0026#34; }} {{- $paginator := .Paginate $pages }} \u0026lt;header class=\u0026#34;page-header\u0026#34;\u0026gt; {{- partial \u0026#34;breadcrumbs.html\u0026#34; . }} \u0026lt;h1\u0026gt;{{ .Title }}\u0026lt;/h1\u0026gt; {{- if .Description }} \u0026lt;div class=\u0026#34;post-description\u0026#34;\u0026gt; {{ .Description | markdownify }} \u0026lt;/div\u0026gt; {{- end }} \u0026lt;/header\u0026gt; {{- range $index, $page := $paginator.Pages }} \u0026lt;article class=\u0026#34;post-entry\u0026#34;\u0026gt; \u0026lt;header class=\u0026#34;entry-header\u0026#34;\u0026gt; \u0026lt;h2 class=\u0026#34;entry-hint-parent\u0026#34;\u0026gt; {{- .Title }} \u0026lt;/h2\u0026gt; \u0026lt;/header\u0026gt; {{- if (ne (.Param \u0026#34;hideSummary\u0026#34;) true) }} \u0026lt;div class=\u0026#34;entry-content\u0026#34;\u0026gt; \u0026lt;p\u0026gt;{{ .Summary | plainify | htmlUnescape }}{{ if .Truncated }}...{{ end }}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; {{- end }} {{- if not (.Param \u0026#34;hideMeta\u0026#34;) }} \u0026lt;footer class=\u0026#34;entry-footer\u0026#34;\u0026gt; {{- partial \u0026#34;post_meta.html\u0026#34; . -}} \u0026lt;/footer\u0026gt; {{- end }} \u0026lt;a class=\u0026#34;entry-link\u0026#34; aria-label=\u0026#34;post link to {{ .Title | plainify }}\u0026#34; href=\u0026#34;{{ .Permalink }}\u0026#34;\u0026gt;\u0026lt;/a\u0026gt; \u0026lt;/article\u0026gt; {{- end }} {{- end }} 这个模板基本复用了 PaperMod 的列表卡片结构，只是数据源从当前 section 改成了 front matter 指定的 section。\n以后想加一个 notes 分区，只需要：\n新建 content/notes/ params.mainSections 加上 notes permalinks 加上 notes content/topics/_index.md 加一个入口 新建 content/topics/notes/_index.md 模板不用动。\n08#搜索页 PaperMod 自带搜索模板，但要让它工作，需要几个点配合。\n先建页面：\n1 content/search.md 1 2 3 4 5 6 7 8 9 10 --- # 搜索页标题 title: \u0026#34;搜索\u0026#34; # 使用 PaperMod 的 search 布局 layout: \u0026#34;search\u0026#34; # 页面摘要，避免列表页展示空摘要 summary: \u0026#34;搜索\u0026#34; --- 然后首页输出里必须有 JSON：\n1 2 3 4 5 6 outputs: # PaperMod 搜索需要首页 JSON 作为索引数据 home: - \u0026#34;HTML\u0026#34; - \u0026#34;RSS\u0026#34; - \u0026#34;JSON\u0026#34; 搜索参数：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 params: # Fuse.js 搜索参数，PaperMod 的前端搜索会读取这些配置 fuseOpts: # 是否大小写敏感；中文站点一般关闭 isCaseSensitive: false # 是否按匹配分数排序 shouldSort: true # 匹配位置偏移，保持默认从开头开始 location: 0 # 允许匹配偏移的距离，调大一点更容易搜到正文内容 distance: 1000 # 匹配阈值，越小越严格，越大越宽松 threshold: 0.4 # 最短匹配字符数；设为 0 表示不额外限制 minMatchCharLength: 0 # 最多返回 10 条结果 limit: 10 # 搜索字段：标题、链接、摘要、正文 keys: [\u0026#34;title\u0026#34;, \u0026#34;permalink\u0026#34;, \u0026#34;summary\u0026#34;, \u0026#34;content\u0026#34;] keys 决定搜索哪些字段。中文博客里我比较看重全文搜索，所以把 content 也放进去了。\n09#关于页单独做模板 关于页不太适合直接套普通文章页。我希望它更像一张个人介绍页：头像居中，正文宽一点，标题风格单独调。\n页面：\n1 content/about.md front matter：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 --- # 页面标题 title: \u0026#34;关于我\u0026#34; # 使用自定义关于页模板 layouts/about.html layout: \u0026#34;about\u0026#34; # 固定关于页路径 url: \u0026#34;/about/\u0026#34; # 页面日期 date: 2026-06-07T00:00:00+08:00 # 页面描述，显示在标题下方，也可用于元信息 description: \u0026#34;一个写 Java 后端，也爱折腾博客和新工具的人。\u0026#34; # 关于页不显示目录 ShowToc: false # 关于页不显示面包屑 ShowBreadCrumbs: false # 关于页不显示日期、字数、阅读时间等元信息 hideMeta: true # 关于页不显示上一篇/下一篇 ShowPostNavLinks: false # 关于页标题不加锚点 disableAnchoredHeadings: true --- 模板：\n1 layouts/about.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 {{- define \u0026#34;main\u0026#34; }} \u0026lt;article class=\u0026#34;post-single about-page\u0026#34;\u0026gt; \u0026lt;header class=\u0026#34;post-header about-header\u0026#34;\u0026gt; {{ partial \u0026#34;breadcrumbs.html\u0026#34; . }} \u0026lt;h1 class=\u0026#34;post-title entry-hint-parent\u0026#34;\u0026gt; {{ .Title }} \u0026lt;/h1\u0026gt; {{- if .Description }} \u0026lt;div class=\u0026#34;post-description\u0026#34;\u0026gt; {{ .Description }} \u0026lt;/div\u0026gt; {{- end }} \u0026lt;/header\u0026gt; {{- if .Content }} \u0026lt;div class=\u0026#34;post-content md-content\u0026#34;\u0026gt; {{- if not (.Param \u0026#34;disableAnchoredHeadings\u0026#34;) }} {{- partial \u0026#34;anchored_headings.html\u0026#34; .Content -}} {{- else }}{{ .Content }}{{ end }} \u0026lt;/div\u0026gt; {{- end }} \u0026lt;/article\u0026gt; {{- end }} 样式放在：\n1 assets/css/extended/about.css 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 .about-page { max-width: 760px; } .about-page .about-header { margin-bottom: 18px; text-align: center; } .about-page .post-title { margin-bottom: 0; font-size: 2.35rem; letter-spacing: 0; } .about-page .post-content { color: var(--content); font-size: 1.02rem; line-height: 1.9; } .about-page .post-content \u0026gt; p:first-child { margin: 2px 0 18px; text-align: center; } .about-page .post-content \u0026gt; p:first-child img { width: 136px; height: 136px; margin: 0 auto; border: 4px solid var(--entry); border-radius: 50%; box-shadow: 0 18px 42px rgba(15, 23, 42, 0.16); object-fit: cover; } 这里用了一个“小技巧”：关于页正文第一个段落只放头像图片，然后 CSS 用：\n1 .about-page .post-content \u0026gt; p:first-child img 把第一张图片处理成圆形头像。Markdown 仍然保持简单：\n1 ![Zeyes 的头像](/avatar.jpg) 10#文章目录改成宽屏左侧固定 PaperMod 默认目录在正文里。我更喜欢宽屏时把目录放到文章左侧，滚动时固定，同时高亮当前标题。\n这部分主要参考了周鑫的《在PaperMod中引入侧边目录和阅读进度显示》。我这里没有继续做阅读百分比，只保留并调整了侧边目录、宽度判断、滚动高亮这些逻辑。\n配置先打开：\n1 2 3 4 5 params: # 全局显示文章目录 ShowToc: true # 目录默认展开 TocOpen: true 单篇文章不想显示目录，可以在 front matter 写：\n1 2 # 单篇文章关闭目录 ShowToc: false 覆盖模板：\n1 layouts/partials/toc.html 模板开头先抓取正文里的标题：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 {{- $headers := findRE \u0026#34;\u0026lt;h[1-6].*?\u0026gt;(.|\\n])+?\u0026lt;/h[1-6]\u0026gt;\u0026#34; .Content -}} {{- $has_headers := ge (len $headers) 1 -}} {{- if $has_headers -}} \u0026lt;aside id=\u0026#34;toc-container\u0026#34; class=\u0026#34;toc-container wide\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;toc\u0026#34;\u0026gt; \u0026lt;details {{if (.Param \u0026#34;TocOpen\u0026#34;) }} open{{ end }}\u0026gt; \u0026lt;summary accesskey=\u0026#34;c\u0026#34; title=\u0026#34;(Alt + C)\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;details\u0026#34;\u0026gt;{{- i18n \u0026#34;toc\u0026#34; | default \u0026#34;Table of Contents\u0026#34; }}\u0026lt;/span\u0026gt; \u0026lt;/summary\u0026gt; \u0026lt;div class=\u0026#34;inner\u0026#34;\u0026gt; \u0026lt;!-- 根据标题层级生成 ul/li --\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/details\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/aside\u0026gt; {{- end }} 真正麻烦的是多级标题嵌套。我的处理方式是遍历所有 header，根据当前标题级别和上一个标题级别的差值，决定什么时候开 \u0026lt;ul\u0026gt;、什么时候闭合：\n1 2 3 4 5 6 7 8 9 10 11 12 {{- range $i, $header := $headers -}} {{- $headerLevel := index (findRE \u0026#34;[1-6]\u0026#34; . 1) 0 -}} {{- $headerLevel := len (seq $headerLevel) -}} {{- $id := index (findRE \u0026#34;(id=\\\u0026#34;(.*?)\\\u0026#34;)\u0026#34; $header 9) 0 }} {{- $cleanedID := replace (replace $id \u0026#34;id=\\\u0026#34;\u0026#34; \u0026#34;\u0026#34;) \u0026#34;\\\u0026#34;\u0026#34; \u0026#34;\u0026#34; }} {{- $header := replaceRE \u0026#34;\u0026lt;h[1-6].*?\u0026gt;((.|\\n])+?)\u0026lt;/h[1-6]\u0026gt;\u0026#34; \u0026#34;$1\u0026#34; $header -}} \u0026lt;li\u0026gt; \u0026lt;a href=\u0026#34;#{{- $cleanedID -}}\u0026#34; aria-label=\u0026#34;{{- $header | plainify -}}\u0026#34;\u0026gt; {{- $header | safeHTML -}} \u0026lt;/a\u0026gt; {{- end -}} 然后加一段 JS，在滚动时找当前标题：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 \u0026lt;script\u0026gt; let activeElement; let elements; document.addEventListener(\u0026#39;DOMContentLoaded\u0026#39;, function () { checkTocPosition(); elements = document.querySelectorAll(\u0026#39;h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]\u0026#39;); if (elements.length \u0026gt; 0) { activeElement = elements[0]; const id = encodeURI(activeElement.getAttribute(\u0026#39;id\u0026#39;)).toLowerCase(); document.querySelector(`.inner ul li a[href=\u0026#34;#${id}\u0026#34;]`).classList.add(\u0026#39;active\u0026#39;); } }, false); window.addEventListener(\u0026#39;scroll\u0026#39;, () =\u0026gt; { const scrollPosition = window.pageYOffset || document.documentElement.scrollTop; if (elements \u0026amp;\u0026amp; elements.length \u0026gt; 0) { activeElement = Array.from(elements).find((element) =\u0026gt; { if ((getOffsetTop(element) - scrollPosition) \u0026gt; 0 \u0026amp;\u0026amp; (getOffsetTop(element) - scrollPosition) \u0026lt; window.innerHeight / 2) { return element; } }) || activeElement; elements.forEach(element =\u0026gt; { const id = encodeURI(element.getAttribute(\u0026#39;id\u0026#39;)).toLowerCase(); const tocLink = document.querySelector(`.inner ul li a[href=\u0026#34;#${id}\u0026#34;]`); if (element === activeElement) { tocLink.classList.add(\u0026#39;active\u0026#39;); } else { tocLink.classList.remove(\u0026#39;active\u0026#39;); } }); } }, false); function getOffsetTop(element) { if (!element.getClientRects().length) { return 0; } let rect = element.getBoundingClientRect(); let win = element.ownerDocument.defaultView; return rect.top + win.pageYOffset; } \u0026lt;/script\u0026gt; 宽屏判断也放在 JS 里。根据正文宽度、TOC 宽度和间距判断是否有空间放左侧目录：\n1 2 3 4 5 6 7 8 9 10 11 12 13 const main = parseInt(getComputedStyle(document.body).getPropertyValue(\u0026#39;--article-width\u0026#39;), 10); const toc = parseInt(getComputedStyle(document.body).getPropertyValue(\u0026#39;--toc-width\u0026#39;), 10); const gap = parseInt(getComputedStyle(document.body).getPropertyValue(\u0026#39;--gap\u0026#39;), 10); function checkTocPosition() { const width = document.body.scrollWidth; if (width - main - (toc * 2) - (gap * 4) \u0026gt; 0) { document.getElementById(\u0026#34;toc-container\u0026#34;).classList.add(\u0026#34;wide\u0026#34;); } else { document.getElementById(\u0026#34;toc-container\u0026#34;).classList.remove(\u0026#34;wide\u0026#34;); } } CSS：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 :root { --nav-width: 1380px; --article-width: 650px; --toc-width: 300px; } .toc-container.wide { position: absolute; height: 100%; border-right: 1px solid var(--border); left: calc((var(--toc-width) + var(--gap)) * -1); top: calc(var(--gap) * 2); width: var(--toc-width); } .wide .toc { position: sticky; top: var(--gap); border: unset; background: unset; border-radius: unset; width: 100%; margin: 0 2px 40px 2px; } .toc .inner { margin: 0 0 0 20px; padding: 0 15px 15px 20px; font-size: 16px; max-height: 83vh; overflow-y: auto; } .active { font-size: 110%; font-weight: 600; } 这一步是整个站点里比较“折腾”的地方。目录看起来只是一个侧边栏，但要兼顾标题层级、滚动高亮、宽度不足时回到正文上方，细节还挺多。\n11#Notice短代码 写技术文章经常需要“注意”“提示”“警告”这种块。Markdown 原生没有很好看的提示框，于是我加了一个 notice shortcode。\n文件：\n1 layouts/shortcodes/notice.html 1 2 3 4 5 6 7 8 9 {{- $noticeType := .Get 0 -}} {{- $raw := (markdownify .Inner | chomp) -}} {{- $block := findRE \u0026#34;(?is)^\u0026lt;(?:address|article|aside|blockquote|canvas|dd|div|dl|dt|fieldset|figcaption|figure|footer|form|h(?:1|2|3|4|5|6)|header|hgroup|hr|li|main|nav|noscript|ol|output|p|pre|section|table|tfoot|ul|video)\\\\b\u0026#34; $raw 1 -}} {{- $icon := (replace (index hugo.Data.SVG $noticeType) \u0026#34;icon\u0026#34; \u0026#34;icon notice-icon\u0026#34;) -}} \u0026lt;div class=\u0026#34;notice {{ $noticeType }}\u0026#34; {{ if len .Params | eq 2 }} id=\u0026#34;{{ .Get 1 }}\u0026#34; {{ end }}\u0026gt; \u0026lt;div class=\u0026#34;notice-title\u0026#34;\u0026gt;{{ $icon | safeHTML }}\u0026lt;/div\u0026gt; {{- if or $block (not $raw) }}{{ $raw }}{{ else }}\u0026lt;p\u0026gt;{{ $raw }}\u0026lt;/p\u0026gt;{{ end -}} \u0026lt;/div\u0026gt; 这里有两个点：\n第一，图标不直接写在 shortcode 里，而是放在数据文件：\n1 data/SVG.toml 1 2 3 notice-warning = \u0026#39;\u0026lt;svg xmlns=\u0026#34;http://www.w3.org/2000/svg\u0026#34; class=\u0026#34;icon\u0026#34; viewBox=\u0026#34;0 0 576 512\u0026#34;\u0026gt;\u0026lt;path d=\u0026#34;M570 440c18 32-5 72-42 72H48c-37 0-60-40-42-72L246 24c19-32 65-32 84 0l240 416zm-282-86a46 46 0 100 92 46 46 0 000-92zm-44-165l8 136c0 6 5 11 12 11h48c7 0 12-5 12-11l8-136c0-7-5-13-12-13h-64c-7 0-12 6-12 13z\u0026#34;/\u0026gt;\u0026lt;/svg\u0026gt;\u0026#39; notice-info = \u0026#39;\u0026lt;svg xmlns=\u0026#34;http://www.w3.org/2000/svg\u0026#34; class=\u0026#34;icon\u0026#34; viewBox=\u0026#34;0 0 512 512\u0026#34;\u0026gt;\u0026lt;path d=\u0026#34;M256 8a248 248 0 100 496 248 248 0 000-496zm0 110a42 42 0 110 84 42 42 0 010-84zm56 254c0 7-5 12-12 12h-88c-7 0-12-5-12-12v-24c0-7 5-12 12-12h12v-64h-12c-7 0-12-5-12-12v-24c0-7 5-12 12-12h64c7 0 12 5 12 12v100h12c7 0 12 5 12 12v24z\u0026#34;/\u0026gt;\u0026lt;/svg\u0026gt;\u0026#39; 第二，shortcode 里判断了一下 .Inner 生成的是块级 HTML 还是普通文本。如果只是普通文本，就补一层 \u0026lt;p\u0026gt;；如果里面已经是 \u0026lt;ul\u0026gt;、\u0026lt;pre\u0026gt;、\u0026lt;blockquote\u0026gt; 这类块级元素，就不再强行套 \u0026lt;p\u0026gt;，避免 HTML 结构乱掉。\n样式：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 .notice { display: flex; align-items: center; position: relative; padding: 0.6em; margin-bottom: 1em; border-radius: 4px; } .notice p:last-child { margin-bottom: 0; } .notice .notice-title { margin-right: 0.5em; margin-top: 0.5em; } .notice .notice-title .notice-icon { width: 1.2em; height: 1.2em; } .notice.notice-warning { background: hsla(0, 65%, 65%, 0.15); } .notice.notice-warning .notice-title { color: hsl(0, 65%, 65%); } .notice.notice-tip { background: hsla(140, 65%, 65%, 0.15); } .notice.notice-tip .notice-title { color: hsl(140, 65%, 65%); } 使用时这样写：\n1 2 3 {{\u0026lt; notice notice-tip \u0026gt;}} 这里是一条提示。 {{\u0026lt; /notice \u0026gt;}} 带 id：\n1 2 3 {{\u0026lt; notice notice-warning my-warning \u0026gt;}} 这里是一条警告。 {{\u0026lt; /notice \u0026gt;}} 12#评论接入Giscus 评论系统我选了 Giscus。它基于 GitHub Discussions，不需要自己维护服务端。\n这一步在提交信息里我一开始写成了“discus插件”，实际接入的是 Giscus。名字容易写混，功能上就是把 GitHub Discussions 作为文章评论区。\nPaperMod 会调用：\n1 layouts/partials/comments.html 所以直接覆盖这个 partial。具体嵌入脚本不用手写，去 Giscus 官方配置页生成即可：\n1 https://giscus.app/zh-CN 在页面上填好仓库、Discussion 分类、映射方式、主题和语言以后，它会自动生成一段 \u0026lt;script\u0026gt;，把这段放进 layouts/partials/comments.html 就行。\n我这里生成时选择了 pathname 映射、preferred_color_scheme 主题和 zh-CN 语言。具体仓库和分类以生成页给出的结果为准，不需要写进文章里。\n全局开关：\n1 2 3 params: # 开启 PaperMod 评论区域，随后由 layouts/partials/comments.html 注入 Giscus comments: true pathname 表示按页面路径映射评论。这样文章标题改了，只要 URL 不变，评论就还能对上。\n13#访问统计接入不蒜子 评论之后，我又想给博客加一点访问量统计。个人站点不想为了这点数据专门上复杂分析系统，所以先接了不蒜子。\n不蒜子比较简单：页面里放好指定的 id，再加载它的脚本，脚本会异步把访问量填进去。我这里分成两类数据：\n1 2 站点统计：本站总访问量 site_pv、本站访客数 site_uv 文章统计：当前文章阅读量 page_pv 站点统计放在页脚。PaperMod 会调用：\n1 layouts/partials/extend_footer.html 所以我在这个扩展 partial 里往 .footer 前面插一行统计：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 \u0026lt;script\u0026gt; (() =\u0026gt; { const footer = document.querySelector(\u0026#34;.footer\u0026#34;); if (!footer || footer.querySelector(\u0026#34;.footer-busuanzi\u0026#34;)) return; const stats = document.createElement(\u0026#34;div\u0026#34;); stats.className = \u0026#34;footer-busuanzi\u0026#34;; stats.innerHTML = [ \u0026#39;\u0026lt;span id=\u0026#34;busuanzi_container_site_pv\u0026#34;\u0026gt;本站总访问量 \u0026lt;span id=\u0026#34;busuanzi_value_site_pv\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; 次\u0026lt;/span\u0026gt;\u0026#39;, \u0026#39;\u0026lt;span id=\u0026#34;busuanzi_container_site_uv\u0026#34;\u0026gt;本站访客数 \u0026lt;span id=\u0026#34;busuanzi_value_site_uv\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; 人\u0026lt;/span\u0026gt;\u0026#39; ].join(\u0026#34; · \u0026#34;); footer.prepend(stats); })(); \u0026lt;/script\u0026gt; \u0026lt;script async src=\u0026#34;https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; 这里用 footer.querySelector(\u0026quot;.footer-busuanzi\u0026quot;) 做了一层保护，避免局部刷新或脚本重复执行时插入两遍。\n文章阅读量则单独做了一个 partial：\n1 layouts/partials/busuanzi_page_pv.html 1 2 3 4 5 6 7 8 9 10 {{- if not (.Param \u0026#34;hidePageViews\u0026#34;) -}} {{- $author := \u0026#34;\u0026#34; -}} {{- if not (.Param \u0026#34;hideAuthor\u0026#34;) -}} {{- $author = partial \u0026#34;author.html\u0026#34; . -}} {{- end -}} {{- if or (not .Date.IsZero) (.Param \u0026#34;ShowReadingTime\u0026#34;) (.Param \u0026#34;ShowWordCount\u0026#34;) $author -}} {{- printf \u0026#34;\u0026amp;nbsp;·\u0026amp;nbsp;\u0026#34; | safeHTML -}} {{- end -}} \u0026lt;span id=\u0026#34;busuanzi_container_page_pv\u0026#34;\u0026gt;阅读量 \u0026lt;span id=\u0026#34;busuanzi_value_page_pv\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; 次\u0026lt;/span\u0026gt; {{- end -}} 这段逻辑主要是为了跟日期、阅读时间、字数、作者这些元信息保持同一行，并且只在前面已经有元信息时补分隔符。\n然后覆盖单篇文章模板：\n1 layouts/single.html 在文章头部元信息位置加上阅读量：\n1 2 3 4 5 6 7 \u0026lt;div class=\u0026#34;post-meta\u0026#34;\u0026gt; {{- partial \u0026#34;post_meta_single.html\u0026#34; . -}} {{- partial \u0026#34;busuanzi_page_pv.html\u0026#34; . -}} {{- partial \u0026#34;translation_list.html\u0026#34; . -}} {{- partial \u0026#34;edit_post.html\u0026#34; . -}} {{- partial \u0026#34;post_canonical.html\u0026#34; . -}} \u0026lt;/div\u0026gt; 如果某篇文章不想显示阅读量，可以在 front matter 里写：\n1 hidePageViews: true 页脚多了一行统计后，原来的 footer 高度不太够，于是在 assets/css/extended/custom.css 里顺手调了一下：\n1 2 3 4 5 6 7 :root { --footer-height: 84px; } .footer { padding: 18px var(--gap); } 这类访问统计只能算轻量展示，不适合当严肃分析数据用。它的好处是接入快、侵入小，坏处是依赖第三方脚本，统计口径也比较粗。\n14#显示文章更新时间 接着又补了文章更新时间。需求很简单：文章头部除了发布日期，还能显示“更新于某天”。\n先在 hugo.yaml 里打开更新时间：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 是否读取 Git 提交时间作为页面更新时间；这里关闭，更新时间只来自文章 front matter enableGitInfo: false frontmatter: # Hugo 的 .Lastmod 取值顺序。去掉 :git，避免 Git 提交时间覆盖文章里手写的 lastmod lastmod: - lastmod - modified - date - publishdate - pubdate - published params: # 显示最后更新时间 ShowLastMod: true 这里我特意把 enableGitInfo 关掉，也没有在 frontmatter.lastmod 里放 :git。否则 Hugo 可能会拿 Git 提交时间当页面更新时间，结果就是一次全站重构、批量迁移或者改模板，都可能让文章看起来像“刚刚更新过”。\n单篇文章里手工写：\n1 2 date: 2026-06-06T16:00:00+08:00 lastmod: 2026-06-07T18:00:00+08:00 然后新建单篇文章专用的元信息 partial：\n1 layouts/partials/post_meta_single.html 核心逻辑是先放发布时间，再判断是否显示 lastmod：\n1 2 3 4 5 6 7 8 9 10 {{- if not .Date.IsZero -}} {{- $dateText := (.Date | time.Format $dateFormat) }} {{- $scratch.Add \u0026#34;meta\u0026#34; (slice (printf \u0026#34;\u0026lt;span title=\u0026#39;%s\u0026#39;\u0026gt;%s\u0026lt;/span\u0026gt;\u0026#34; (.Date) $dateText)) }} {{- if and (.Param \u0026#34;ShowLastMod\u0026#34;) (isset .Params \u0026#34;lastmod\u0026#34;) (not .Lastmod.IsZero) -}} {{- $lastmodText := (.Lastmod | time.Format $dateFormat) }} {{- if ne $lastmodText $dateText -}} {{- $scratch.Add \u0026#34;meta\u0026#34; (slice (printf \u0026#34;\u0026lt;span title=\u0026#39;%s\u0026#39;\u0026gt;更新于%s\u0026lt;/span\u0026gt;\u0026#34; (.Lastmod) $lastmodText)) }} {{- end }} {{- end }} {{- end }} 这里有两个小判断：\n第一，用 isset .Params \u0026quot;lastmod\u0026quot; 限制只有手工写了 lastmod 的文章才显示更新时间。这样旧文章即使 .Lastmod 回退到 date，也不会凭空多一个“更新于”。\n第二，dateText 和 lastmodText 一样时不显示更新时间。当前日期格式是：\n1 DateFormat: \u0026#34;2006年01月02日\u0026#34; 所以只要发布日和更新日是同一天，即使具体时分不同，页面上也不会额外显示“更新于”。如果以后想显示当天内的更新时间，就要把 DateFormat 改成包含时分，或者去掉这层日期文本比较。\n最后在 layouts/single.html 里把 PaperMod 原来的：\n1 {{- partial \u0026#34;post_meta.html\u0026#34; . -}} 换成：\n1 {{- partial \u0026#34;post_meta_single.html\u0026#34; . -}} 这样列表页还继续用 PaperMod 默认元信息，只有单篇文章页显示自定义的更新时间和阅读量，改动范围比较收敛。\n15#代码高亮改用Chroma PaperMod 可以用 highlight.js，也可以用 Hugo 内置 Chroma。我最后选了 Chroma，因为它在构建阶段完成高亮，不依赖前端 JS。\n代码块背景颜色和 PaperMod 主题变量的处理，参考了 hcy-asleep 的《Hugo PaperMod 改变主题配色（代码块背景颜色）》。我自己的重点则放在 Chroma 的 class 模式，以及亮色/暗色两套语法高亮 CSS 如何避免互相覆盖。\n先关闭 highlight.js：\n1 2 3 4 params: assets: # 禁用 highlight.js，改用 Hugo 内置 Chroma disableHLJS: true 然后配置 Hugo 的高亮：\n1 2 3 4 5 6 7 8 9 10 11 12 markup: highlight: # false 表示输出 CSS class；true 表示把样式写成内联 style noClasses: false # 开启 Markdown ``` 代码围栏高亮 codeFences: true # 没写语言时尝试自动猜测 guessSyntax: true # 显示行号 lineNos: true # noClasses=false 时，颜色交给外部 CSS，这里留空即可 style: \u0026#34;\u0026#34; 重点是：\n1 2 # Chroma 输出 CSS class，方便用外部 CSS 同时维护亮色/暗色两套样式 noClasses: false 这样 Hugo 会输出 Chroma class，而不是把颜色写成内联样式。随后在 CSS 里控制亮色和暗色主题。\nPaperMod 会自动加载：\n1 assets/css/extended/*.css 所以我放了：\n1 2 3 assets/css/extended/syntax-light.css assets/css/extended/syntax-dark.css assets/css/extended/custom.css 生成 Chroma 样式可以用：\n1 2 hugo gen chromastyles --style=tango \u0026gt; assets/css/extended/syntax-light.css hugo gen chromastyles --style=dracula \u0026gt; assets/css/extended/syntax-dark.css 但这还不够。两套 CSS 如果都是裸 .chroma，后加载的会覆盖先加载的。所以需要给选择器加主题作用域：\n1 2 3 4 5 6 7 :root[data-theme=\u0026#34;light\u0026#34;] .chroma { background-color: #f8f8f8; } :root[data-theme=\u0026#34;dark\u0026#34;] .chroma { background-color: #282a36; } 另外补一下纯文本和 fallback 代码块的颜色：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 :root[data-theme=\u0026#34;light\u0026#34;] .chroma code.language-fallback, :root[data-theme=\u0026#34;light\u0026#34;] .chroma code.language-text, :root[data-theme=\u0026#34;light\u0026#34;] .chroma code.language-bash, :root[data-theme=\u0026#34;light\u0026#34;] .chroma code[data-lang=\u0026#34;fallback\u0026#34;], :root[data-theme=\u0026#34;light\u0026#34;] .chroma code[data-lang=\u0026#34;text\u0026#34;], :root[data-theme=\u0026#34;light\u0026#34;] .chroma code[data-lang=\u0026#34;bash\u0026#34;] { color: #24292f; } :root[data-theme=\u0026#34;dark\u0026#34;] .chroma code.language-fallback, :root[data-theme=\u0026#34;dark\u0026#34;] .chroma code.language-text, :root[data-theme=\u0026#34;dark\u0026#34;] .chroma code[data-lang=\u0026#34;fallback\u0026#34;], :root[data-theme=\u0026#34;dark\u0026#34;] .chroma code[data-lang=\u0026#34;text\u0026#34;] { color: #f8f8f2; } 代码块背景也顺手覆盖一下：\n1 2 3 4 5 6 7 8 9 :root { --hljs-bg: #f5f5f5; --code-block-bg: #f8f8f8; } .dark { --hljs-bg: #2e2e33; --code-block-bg: #2e2e33; } 这里有个坑：PaperMod 的不同版本里，暗色模式可能使用 .dark，也可能使用 data-theme=\u0026quot;dark\u0026quot;。所以调 CSS 时最好打开浏览器开发者工具，看 \u0026lt;html\u0026gt; 或 \u0026lt;body\u0026gt; 上到底挂了什么标识。\n16#字体和全局样式 字体加载放在：\n1 layouts/partials/extend_head.html PaperMod 会在 head 里调用这个扩展 partial。\n我加载了正文和代码字体：\n1 2 3 4 5 6 7 8 9 10 \u0026lt;link rel=\u0026#34;preconnect\u0026#34; href=\u0026#34;https://fonts.googleapis.com\u0026#34;\u0026gt; \u0026lt;link rel=\u0026#34;preconnect\u0026#34; href=\u0026#34;https://fonts.gstatic.com\u0026#34; crossorigin\u0026gt; \u0026lt;link rel=\u0026#34;preload\u0026#34; href=\u0026#34;https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700\u0026amp;family=Noto+Serif+SC:wght@400;600;700\u0026amp;display=swap\u0026#34; as=\u0026#34;style\u0026#34; onload=\u0026#34;this.onload=null;this.rel=\u0026#39;stylesheet\u0026#39;\u0026#34;\u0026gt; \u0026lt;noscript\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700\u0026amp;family=Noto+Serif+SC:wght@400;600;700\u0026amp;display=swap\u0026#34;\u0026gt; \u0026lt;/noscript\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;https://fonts.googleapis.com/css2?family=Source+Code+Pro\u0026amp;display=swap\u0026#34;\u0026gt; 然后在 custom.css 里应用：\n1 2 3 4 5 6 7 8 9 10 11 body, .post-content { font-family: Lora, \u0026#34;Noto Serif SC\u0026#34;, \u0026#34;PingFang SC\u0026#34;, \u0026#34;Microsoft YaHei\u0026#34;, sans-serif; } code, pre, kbd, samp { font-family: \u0026#34;Source Code Pro\u0026#34;, monospace; } 首页标题还加了一个打字机效果：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 .profile .profile_inner h1 { max-width: 100%; width: 14em; overflow: hidden; white-space: nowrap; border-right: 1px solid currentColor; animation: profile-title-typing 3.2s steps(14, end), profile-title-caret 0.75s step-end infinite; } @keyframes profile-title-typing { from { width: 0; } to { width: 14em; } } @keyframes profile-title-caret { from, to { border-color: transparent; } 50% { border-color: currentColor; } } @media (prefers-reduced-motion: reduce) { .profile .profile_inner h1 { animation: none; border-right: 0; } } prefers-reduced-motion 这个判断是为了照顾系统关闭动画的用户。虽然只是个人博客，但这种小地方顺手做一下并不麻烦。\n17#页脚备案信息 备案信息配置在 hugo.yaml：\n1 2 3 4 5 6 7 8 params: footer: # ICP 备案号；有值时会在页脚显示并链接到工信部备案查询 icp: \u0026#34;xxx\u0026#34; # 公安备案文案；没有就留空 mps: \u0026#34;\u0026#34; # 公安备案号里的数字编码；没有就留空，模板里也会尝试从 mps 文案提取 mpsCode: \u0026#34;\u0026#34; PaperMod 页脚本身没有我想要的备案位置，所以我用：\n1 layouts/partials/extend_footer.html 在前端插进去：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 {{- $footer := site.Params.footer -}} {{- if or $footer.icp $footer.mps }} \u0026lt;script\u0026gt; (() =\u0026gt; { const footer = document.querySelector(\u0026#34;.footer\u0026#34;); if (!footer || footer.querySelector(\u0026#34;.footer-beian\u0026#34;)) return; const powered = footer.querySelector(\u0026#34;span:last-of-type\u0026#34;); const beianItems = [ {{- with $footer.icp }} { text: {{ . | jsonify | safeJS }}, href: \u0026#34;https://beian.miit.gov.cn/\u0026#34; }, {{- end }} ]; beianItems.forEach((item) =\u0026gt; { const beian = document.createElement(\u0026#34;span\u0026#34;); beian.className = \u0026#34;footer-beian\u0026#34;; const link = document.createElement(\u0026#34;a\u0026#34;); link.href = item.href; link.target = \u0026#34;_blank\u0026#34;; link.rel = \u0026#34;noopener noreferrer\u0026#34;; link.textContent = item.text; beian.append(link); if (powered) { powered.before(beian, document.createTextNode(\u0026#34; · \u0026#34;)); } else { footer.append(document.createTextNode(\u0026#34; · \u0026#34;), beian); } }); })(); \u0026lt;/script\u0026gt; {{- end }} 这里用 jsonify | safeJS，是为了把配置里的字符串安全地塞进 JS 字符串里，避免手写引号转义。\n样式很简单：\n1 2 3 4 5 6 7 .footer-beian { display: inline; } .footer-beian a { text-decoration: none; } 18#中文文案 PaperMod 有不少默认文案来自 i18n。中文覆盖文件：\n1 i18n/zh-cn.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # 上一页按钮 - id: prev_page translation: \u0026#34;上一页\u0026#34; # 下一页按钮 - id: next_page translation: \u0026#34;下一页\u0026#34; # 阅读时间，.Count 由 Hugo/PaperMod 传入 - id: read_time translation: one: \u0026#34;1 分钟\u0026#34; other: \u0026#34;{{ .Count }} 分钟\u0026#34; # 文章字数 - id: words translation: one: \u0026#34;1 字\u0026#34; other: \u0026#34;{{ .Count }} 字\u0026#34; # 文章目录标题 - id: toc translation: \u0026#34;目录\u0026#34; # 代码复制按钮 - id: code_copy translation: \u0026#34;复制\u0026#34; # 代码复制成功后的提示 - id: code_copied translation: \u0026#34;已复制！\u0026#34; 配置里对应：\n1 2 # 这里要和 i18n/zh-cn.yaml 的文件名对应 defaultContentLanguage: \u0026#34;zh-cn\u0026#34; 文件名和语言代码要对上，否则可能加载不到自己的翻译。\n19#静态资源 static/ 目录下的文件会原样发布到站点根路径。\n我放了这些常用资源：\n1 2 3 4 5 6 7 8 9 static/ ├── avatar.jpg ├── favicon.ico ├── favicon.svg ├── favicon-16x16.png ├── favicon-32x32.png ├── apple-touch-icon-next.png ├── alipay.jpg └── wechatpay.png 配置 favicon：\n1 2 3 4 5 6 7 8 9 10 params: assets: # 浏览器地址栏 favicon favicon: \u0026#34;/favicon.ico\u0026#34; # 16x16 favicon favicon16x16: \u0026#34;/favicon-16x16.png\u0026#34; # 32x32 favicon favicon32x32: \u0026#34;/favicon-32x32.png\u0026#34; # iOS 添加到主屏幕时使用的图标 apple_touch_icon: \u0026#34;/apple-touch-icon-next.png\u0026#34; 不要放到 assets/，因为 assets/ 是给 Hugo Pipes 处理的，不是原样复制目录。\n20#本地开发和构建 本地开发：\n1 hugo server --disableFastRender 预览草稿：\n1 hugo server --disableFastRender -D 构建：\n1 hugo 生产构建：\n1 hugo --gc --minify 清理构建输出：\n1 2 Remove-Item -Recurse -Force public hugo 如果主题目录为空，多半是 submodule 没初始化：\n1 git submodule update --init --recursive 21#部署到Vercel Vercel 构建 Hugo 站点时，最好指定 Hugo 版本。\n文件：\n1 vercel.json 注意 JSON 标准不支持注释，所以下面这段要保持纯 JSON，不要把注释直接写进 vercel.json：\n1 2 3 4 5 6 7 { \u0026#34;build\u0026#34;: { \u0026#34;env\u0026#34;: { \u0026#34;HUGO_VERSION\u0026#34;: \u0026#34;0.162.0\u0026#34; } } } Vercel 项目里构建命令用：\n1 hugo 或者：\n1 hugo --gc --minify 发布目录：\n1 public 如果用的是 git submodule，部署平台还要能拉 submodule。否则本地能跑，线上会因为 themes/PaperMod 不存在而构建失败。\n22#这次踩到的几个点 第一，主题尽量不要直接改。\n一开始直接进 themes/PaperMod 改东西很爽，但以后主题更新会很痛苦。Hugo 的覆盖机制已经足够好用，能放 layouts/ 就放 layouts/，能放 assets/css/extended/ 就放 assets/css/extended/。\n第二，搜索页依赖首页 JSON。\n只创建 content/search.md 不够，还要：\n1 2 3 4 outputs: # PaperMod 搜索索引必须依赖 JSON 输出 home: - \u0026#34;JSON\u0026#34; 第三，分区不是菜单里写一下就完事。\n新增 section 时，要同时检查：\n1 2 3 4 5 6 content/\u0026lt;section\u0026gt;/ params.mainSections permalinks menu.main content/topics/_index.md content/topics/\u0026lt;section\u0026gt;/_index.md 第四，代码高亮别让两套 CSS 互相覆盖。\n亮色和暗色 Chroma 样式必须加作用域：\n1 2 :root[data-theme=\u0026#34;light\u0026#34;] .chroma {} :root[data-theme=\u0026#34;dark\u0026#34;] .chroma {} 第五，shortcode 示例要转义。\n如果文章里要展示 shortcode 写法，最好这样写：\n1 2 3 {{\u0026lt; notice notice-tip \u0026gt;}} 这里是提示内容。 {{\u0026lt; /notice \u0026gt;}} 否则 Hugo 可能会把示例当成真正的 shortcode 执行。\n第六，不蒜子统计依赖固定 id。\n站点访问量、访客数和文章阅读量分别对应：\n1 2 3 busuanzi_value_site_pv busuanzi_value_site_uv busuanzi_value_page_pv 这些 id 写错一个，页面结构还在，但数字不会回来。另外脚本全站只需要引一次，放在 extend_footer.html 里就够了。\n第七，更新时间最好别直接交给 Git。\nGit 提交时间很方便，但对博客文章不一定准确。尤其是迁移旧文、批量改模板、批量格式化 front matter 时，Git 时间会把很多并没有真正更新正文的文章也标成新更新。所以我这里关闭 enableGitInfo，只认文章 front matter 里的 lastmod。\n参考资料 周鑫：《在PaperMod中引入侧边目录和阅读进度显示》 hcy-asleep：《Hugo PaperMod 改变主题配色（代码块背景颜色）》 小结 这次从 Hexo 换到 Hugo，最大的感受是：Hugo 更像一个静态站点编译器，很多能力不是靠插件堆出来，而是靠内容组织、模板覆盖、数据文件和 Hugo Pipes 组合出来。\n现在这套博客基本形成了自己的结构：\n1 2 3 4 5 6 内容：tech / stock / essay 入口：topics 自定义分类页 模板：layouts 覆盖 PaperMod 样式：assets/css/extended 扩展 功能：搜索、归档、评论、访问统计、更新时间、TOC、notice、代码高亮 部署：Vercel + Hugo Extended 后面如果继续折腾，大概会往这几个方向走：\n给旧文章批量补 slug 和 tags 做一个文章发布脚本，把 Obsidian 源文档同步到 Hugo 给图片加自动压缩和尺寸规范 给股票和随笔分区做更适合它们的列表样式 折腾博客这件事说起来挺矛盾：它本来是为了写东西，结果常常会先折腾出一堆写东西的工具。但也正是这些小修小补，最后会把一个通用主题慢慢拧成自己的工作台。\n","permalink":"https://www.zeyes.org/posts/tech/hugo-papermod-customization/","summary":"\u003cp\u003e距离上一次写《Hexo折腾记》已经过去很多年了。那时候折腾 Hexo + Next，主要是在装 Node、装插件、改主题配置、解决部署抽风。现在重新整理博客，我换成了 Hugo + PaperMod。\u003c/p\u003e\n\u003cp\u003e这次不想只停留在“能跑起来”。我的目标是：主题保持可更新，文章按技术、股票、随笔分区管理，首页和导航更像自己的站点，搜索、归档、评论、目录、提示框、代码高亮、备案这些功能都补齐，并且尽量把改动放在 Hugo 推荐的覆盖层里，不直接魔改主题源码。\u003c/p\u003e\n\u003cp\u003e这篇就记录一下整个折腾过程。\u003c/p\u003e","title":"Hugo折腾记"},{"content":" 这是一段 警告 信息。\n这是一段 常规 信息。\n这是一段 笔记 信息。\n这是一段 提示 信息。\n","permalink":"https://www.zeyes.org/posts/test-shortcode/","summary":"\u003cdiv class=\"notice notice-warning\" \u003e\n  \u003cdiv class=\"notice-title\"\u003e\u003csvg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon notice-icon\" viewBox=\"0 0 576 512\"\u003e\u003cpath d=\"M570 440c18 32-5 72-42 72H48c-37 0-60-40-42-72L246 24c19-32 65-32 84 0l240 416zm-282-86a46 46 0 100 92 46 46 0 000-92zm-44-165l8 136c0 6 5 11 12 11h48c7 0 12-5 12-11l8-136c0-7-5-13-12-13h-64c-7 0-12 6-12 13z\"/\u003e\u003c/svg\u003e\u003c/div\u003e\u003cp\u003e这是一段 \u003cstrong\u003e警告\u003c/strong\u003e 信息。\u003c/p\u003e\u003c/div\u003e\n\n\u003cdiv class=\"notice notice-info\" \u003e\n  \u003cdiv class=\"notice-title\"\u003e\u003csvg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon notice-icon\" viewBox=\"0 0 512 512\"\u003e\u003cpath d=\"M256 8a248 248 0 100 496 248 248 0 000-496zm0 110a42 42 0 110 84 42 42 0 010-84zm56 254c0 7-5 12-12 12h-88c-7 0-12-5-12-12v-24c0-7 5-12 12-12h12v-64h-12c-7 0-12-5-12-12v-24c0-7 5-12 12-12h64c7 0 12 5 12 12v100h12c7 0 12 5 12 12v24z\"/\u003e\u003c/svg\u003e\u003c/div\u003e\u003cp\u003e这是一段 \u003cstrong\u003e常规\u003c/strong\u003e 信息。\u003c/p\u003e","title":"测试 Notice 样式"},{"content":"最近给博客调整代码高亮样式时，遇到一个小问题：使用 Hugo 的 Chroma 高亮，并设置 noClasses: true、style: \u0026quot;tango\u0026quot; 后，一些代码块看起来颜色特别浅。尤其是生成出来带有 class=\u0026quot;language-fallback\u0026quot; 的代码块，浅灰背景配浅灰文字，阅读体验不太好。\n折腾了一圈后，发现更适合 PaperMod 的方式是：关闭内联样式，使用 Chroma 生成 CSS 类，然后自己准备亮色和暗色两套高亮样式，让它们跟随 PaperMod 的主题切换。\n问题原因 PaperMod 支持 Hugo 内置的 Chroma 语法高亮。配置里一般会先关闭 Highlight.js：\n1 2 3 params: assets: disableHLJS: true 如果使用下面这种配置：\n1 2 3 4 5 6 7 markup: highlight: noClasses: true codeFences: true guessSyntax: true lineNos: true style: \u0026#34;tango\u0026#34; Hugo 会把 Chroma 的样式直接写到 HTML 里，也就是内联样式。比如 tango 的代码块背景就是浅灰色：\n1 background-color: #f8f8f8; 这本身没有问题，但当某些代码块没有写语言，或者 Chroma 无法判断语言时，生成的 HTML 里会出现：\n1 \u0026lt;code class=\u0026#34;language-fallback\u0026#34; data-lang=\u0026#34;fallback\u0026#34;\u0026gt; 这种 fallback 代码块没有具体语言的 token 颜色，容易吃到主题默认的代码文字颜色。如果背景又是浅灰色，就会显得很淡。\n所以问题不是 tango 没生效，而是 fallback 代码块没有语法 token，只剩下普通文字颜色和代码块背景在互相配合。\n推荐配置 我最后选择把 Chroma 改成 class 模式：\n1 2 3 4 5 6 7 markup: highlight: noClasses: false codeFences: true guessSyntax: true lineNos: true style: \u0026#34;\u0026#34; 这里最关键的是：\nnoClasses: false 表示让 Hugo 输出 Chroma 的 CSS class。 style: \u0026quot;\u0026quot; 基本不用再管，因为颜色交给外部 CSS 控制。 disableHLJS: true 仍然保留，避免 highlight.js 参与。 这样生成出来的代码结构会带 .chroma、.k、.s、.c 等 class，具体颜色由 CSS 文件决定。\nPaperMod如何加载自定义CSS PaperMod 官方 FAQ 里提到，可以把自定义 CSS 放到站点根目录：\n1 2 3 4 5 6 assets/ └── css/ └── extended/ ├── custom.css ├── syntax-light.css └── syntax-dark.css 这个目录下所有 CSS 文件都会被 PaperMod 自动打包进最终样式文件，而且加载顺序在主题核心 CSS 后面，所以很适合覆盖代码高亮样式。\n生成亮色和暗色高亮 先生成两套 Chroma 样式。比如亮色使用 github，暗色使用 dracula：\n1 2 hugo gen chromastyles --style=github \u0026gt; assets/css/extended/syntax-light.css hugo gen chromastyles --style=dracula \u0026gt; assets/css/extended/syntax-dark.css 但这样还不能直接用。因为两个文件里都会生成类似这样的选择器：\n1 2 3 4 5 6 7 .chroma { background-color: #f8f8f8; } .chroma .k { color: #cf222e; } 如果亮色和暗色 CSS 都是裸的 .chroma，那它们会互相覆盖，最后加载的文件会赢。结果就是无论切到亮色还是暗色，都只剩一套高亮。\nPaperMod 切换主题时，会修改 \u0026lt;html\u0026gt; 上的 data-theme 属性：\n1 2 \u0026lt;html data-theme=\u0026#34;light\u0026#34;\u0026gt; \u0026lt;html data-theme=\u0026#34;dark\u0026#34;\u0026gt; 所以正确做法是给两套 Chroma CSS 分别加上作用域。\n亮色 CSS 应该类似这样：\n1 2 3 4 5 6 7 :root[data-theme=\u0026#34;light\u0026#34;] .chroma { background-color: #f8f8f8; } :root[data-theme=\u0026#34;light\u0026#34;] .chroma .k { color: #cf222e; } 暗色 CSS 应该类似这样：\n1 2 3 4 5 6 7 :root[data-theme=\u0026#34;dark\u0026#34;] .chroma { background-color: #282a36; } :root[data-theme=\u0026#34;dark\u0026#34;] .chroma .k { color: #ff79c6; } 这样切换主题时，浏览器会自动命中对应的 CSS。\n用PowerShell自动加作用域 手工给每一行加前缀太麻烦，可以直接用 PowerShell 处理。\n生成亮色主题：\n1 2 3 hugo gen chromastyles --style=github | ForEach-Object { $_ -replace \u0026#39;^(/\\*.*?\\*/\\s*)?(\\.bg|\\.chroma)\u0026#39;, \u0026#39;${1}:root[data-theme=\u0026#34;light\u0026#34;] ${2}\u0026#39; } | Set-Content -Encoding utf8 assets/css/extended/syntax-light.css 生成暗色主题：\n1 2 3 hugo gen chromastyles --style=dracula | ForEach-Object { $_ -replace \u0026#39;^(/\\*.*?\\*/\\s*)?(\\.bg|\\.chroma)\u0026#39;, \u0026#39;${1}:root[data-theme=\u0026#34;dark\u0026#34;] ${2}\u0026#39; } | Set-Content -Encoding utf8 assets/css/extended/syntax-dark.css 这段命令会把 Chroma 生成的选择器从：\n1 2 3 .chroma .k { color: #cf222e; } 变成：\n1 2 3 :root[data-theme=\u0026#34;light\u0026#34;] .chroma .k { color: #cf222e; } 修复fallback代码块 有些代码块本来就不是程序代码，比如示例输入、示例输出、命令输出等。这类内容即使写成 text 也不会有什么 token 高亮。\n为了避免 fallback 或纯文本代码块颜色太浅，可以在 assets/css/extended/custom.css 里补一段：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 :root[data-theme=\u0026#34;light\u0026#34;] .chroma code.language-fallback, :root[data-theme=\u0026#34;light\u0026#34;] .chroma code.language-text, :root[data-theme=\u0026#34;light\u0026#34;] .chroma code.language-bash, :root[data-theme=\u0026#34;light\u0026#34;] .chroma code[data-lang=\u0026#34;fallback\u0026#34;], :root[data-theme=\u0026#34;light\u0026#34;] .chroma code[data-lang=\u0026#34;text\u0026#34;], :root[data-theme=\u0026#34;light\u0026#34;] .chroma code[data-lang=\u0026#34;bash\u0026#34;] { color: #24292f; } :root[data-theme=\u0026#34;dark\u0026#34;] .chroma code.language-fallback, :root[data-theme=\u0026#34;dark\u0026#34;] .chroma code.language-text, :root[data-theme=\u0026#34;dark\u0026#34;] .chroma code[data-lang=\u0026#34;fallback\u0026#34;], :root[data-theme=\u0026#34;dark\u0026#34;] .chroma code[data-lang=\u0026#34;text\u0026#34;] { color: #f8f8f2; } 如果还想让普通代码块背景也跟主题一致，可以补充：\n1 2 3 4 5 6 7 :root[data-theme=\u0026#34;light\u0026#34;] { --code-block-bg: #f8f8f8; } :root[data-theme=\u0026#34;dark\u0026#34;] { --code-block-bg: #2e2e33; } 注意 PaperMod 使用的是 data-theme=\u0026quot;dark\u0026quot;，不是 .dark class。因此不要写成：\n1 2 3 .dark { --code-block-bg: #2e2e33; } 这样不会命中 PaperMod 的主题切换。\n给代码块写清楚语言 虽然 guessSyntax: true 可以自动猜语言，但它并不总是可靠。尤其是算法题里的输入输出示例，很容易被当成 fallback。\n程序代码建议明确写语言：\n1 2 3 4 5 ```c int main(void) { return 0; } ``` 普通文本或运行结果建议明确写 text：\n1 2 3 4 ```text Case 1: 1 + 2 = 3 ``` 这样生成结果更稳定，也方便以后统一调整样式。\n最终结构 最后相关文件大概是这样：\n1 2 3 4 5 6 assets/ └── css/ └── extended/ ├── custom.css ├── syntax-light.css └── syntax-dark.css hugo.yaml 里保留：\n1 2 3 4 5 6 7 8 9 10 11 params: assets: disableHLJS: true markup: highlight: noClasses: false codeFences: true guessSyntax: true lineNos: true style: \u0026#34;\u0026#34; 之后如果想换风格，只要重新生成两份 CSS：\n1 2 hugo gen chromastyles --style=github hugo gen chromastyles --style=dracula 也可以换成其他组合，比如：\n亮色：github、tango、solarized-light 暗色：dracula、monokai、catppuccin-mocha 小结 在 PaperMod 里切换代码高亮明暗主题，比较稳的思路是：\n关闭 Highlight.js，使用 Hugo Chroma。 设置 noClasses: false，让 Hugo 输出 Chroma class。 用 hugo gen chromastyles 生成亮色和暗色两套 CSS。 给两套 CSS 分别加上 :root[data-theme=\u0026quot;light\u0026quot;] 和 :root[data-theme=\u0026quot;dark\u0026quot;] 作用域。 把 CSS 放进 assets/css/extended/，交给 PaperMod 自动打包。 单独修一下 language-fallback 或 text 代码块的文字颜色。 这样做以后，代码高亮就会跟随 PaperMod 的主题按钮一起切换，不会再出现两套 CSS 互相覆盖的问题。\n","permalink":"https://www.zeyes.org/posts/tech/papermod-chroma-light-dark-syntax/","summary":"\u003cp\u003e最近给博客调整代码高亮样式时，遇到一个小问题：使用 Hugo 的 Chroma 高亮，并设置 \u003ccode\u003enoClasses: true\u003c/code\u003e、\u003ccode\u003estyle: \u0026quot;tango\u0026quot;\u003c/code\u003e 后，一些代码块看起来颜色特别浅。尤其是生成出来带有 \u003ccode\u003eclass=\u0026quot;language-fallback\u0026quot;\u003c/code\u003e 的代码块，浅灰背景配浅灰文字，阅读体验不太好。\u003c/p\u003e\n\u003cp\u003e折腾了一圈后，发现更适合 PaperMod 的方式是：关闭内联样式，使用 Chroma 生成 CSS 类，然后自己准备亮色和暗色两套高亮样式，让它们跟随 PaperMod 的主题切换。\u003c/p\u003e","title":"PaperMod使用Chroma切换代码高亮明暗主题"},{"content":"\n你好，我是 Zeyes 95 后，本科，专业软件工程。平时主要写 Java 后端，也会折腾博客、Linux、开源工具和一些 AI 相关的小东西。\n这个博客像我的一个小工作台：技术记录、随笔、投资复盘、读到的东西、踩过的坑，都会慢慢放在这里。\n世界上没有什么事儿是一顿烧烤不能解决的。如果有，那就两顿。\n—— 亚洲气质舞王尼古拉斯·赵四\n经历简述 我有 7 年左右 Java 开发经验，主要做后端。参与过业务中台、订单链路、电商营销、法务系统、大模型应用等系统的设计和开发。\n我关注的东西 把复杂业务拆成清楚、可维护的系统 用工具和自动化减少重复劳动 持续学习新技术，但尽量不被新名词牵着走 交个朋友 Telegram: @zeyes\n邮箱：lixize8888(at)163.com\n","permalink":"https://www.zeyes.org/about/","summary":"\u003cp\u003e\u003cimg alt=\"Zeyes 的头像\" loading=\"lazy\" src=\"/avatar.jpg\"\u003e\u003c/p\u003e\n\u003ch2 id=\"你好我是-zeyes\"\u003e你好，我是 Zeyes\u003c/h2\u003e\n\u003cp\u003e95 后，本科，专业软件工程。平时主要写 Java 后端，也会折腾博客、Linux、开源工具和一些 AI 相关的小东西。\u003c/p\u003e\n\u003cp\u003e这个博客像我的一个小工作台：技术记录、随笔、投资复盘、读到的东西、踩过的坑，都会慢慢放在这里。\u003c/p\u003e","title":"关于我"},{"content":" 每当在屏幕点击宠物的时候，金钱的方法就会额外增加，同时心跳包会带上点击的宠物ID和点击次数发给服务器。 所以，可以使用HttpClient来模拟心跳包来实现金钱的额外增长，实际加的很少，但也好过没有。\n代码地址: https://github.com/lixize/wx-game-dogbian.git\n核心代码 发包代码\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 /** * 模拟心跳 * @param dogId 宠物狗的内部ID * @param count 点击次数（100以内） * @return 心跳结果 */ public HeartPacket doHeart(int dogId, int count) { String param = \u0026#34;?cmd=sync\u0026#34;; param += \u0026#34;\u0026amp;token=\u0026#34; + token; param += \u0026#34;\u0026amp;a=[]\u0026#34;; param += \u0026#34;\u0026amp;e1_p=0\u0026#34;; param += \u0026#34;\u0026amp;now=\u0026#34; + Calendar.getInstance().getTimeInMillis(); param += \u0026#34;\u0026amp;seq=\u0026#34; + (seq++); param += \u0026#34;\u0026amp;c=[[\u0026#34;+dogId+\u0026#34;,\u0026#34;+count+\u0026#34;]]\u0026#34;; param += \u0026#34;\u0026amp;e1=[]\u0026amp;e2_mer=[]\u0026amp;e2_eat=[]\u0026#34;; System.out.println(host + param); Request request = new Request(method, host, path + param, 2000); Map\u0026lt;String, String\u0026gt; headers = new HashMap\u0026lt;\u0026gt;(); Map\u0026lt;String, String\u0026gt; querys = new HashMap\u0026lt;\u0026gt;(); headers.put(\u0026#34;content-type\u0026#34;, ContentType.CONTENT_TYPE_JSON); headers.put(\u0026#34;charset\u0026#34;, \u0026#34;utf-8\u0026#34;); request.setHeaders(headers); request.setQuerys(null); try { Response response = Client.execute(request); System.out.println(response.getStatusCode()); //System.out.println(response.getBody()); ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 忽略Bean中不存在的字段 HeartPacket hp = mapper.readValue(response.getBody(), HeartPacket.class); System.out.println(hp); setChanged(); notifyObservers(hp); return hp; } catch (Exception e) { e.printStackTrace(); } return null; } 定时器代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 public class HeartService { Timer timer; HeartController c; private String token; private List\u0026lt;Integer\u0026gt; dogId; private static int index = 0; private Integer count; public HeartService() { c = new HeartController(); } public void run() { index = 0; timer = new Timer(); if (token != null \u0026amp;\u0026amp; !token.isEmpty()) { System.out.println(c); System.out.println(token); c.setToken(token); } if (count != null) { c.setCount(count); } else { c.setCount(100); } TimerTask task = new HeartTimerTask(c); timer.schedule(task, 0L, 2000L); System.out.println(\u0026#34;ok\u0026#34;); } public void addObserver(Observer o) { if (o != null \u0026amp;\u0026amp; c != null) { c.addObserver(o); } } public void cancel() { timer.cancel(); } public String getToken() { return token; } public void setToken(String token) { this.token = token; } public List\u0026lt;Integer\u0026gt; getDogId() { return dogId; } public void setDogId(List\u0026lt;Integer\u0026gt; dogId) { this.dogId = dogId; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } class HeartTimerTask extends TimerTask { HeartController c; public HeartTimerTask(HeartController c) { this.c = c; } @Override public void run() { if (dogId != null \u0026amp;\u0026amp; dogId.size() \u0026gt; 0) { c.setDogId(dogId.get(index++)); index %= dogId.size(); c.doHeart(); } else { System.out.println(\u0026#34;DogId Null\u0026#34;); } } } } ","permalink":"https://www.zeyes.org/posts/tech/wx-game-dogbian/","summary":"\u003cblockquote\u003e\n\u003cp\u003e每当在屏幕点击宠物的时候，金钱的方法就会额外增加，同时心跳包会带上点击的宠物ID和点击次数发给服务器。\n所以，可以使用HttpClient来模拟心跳包来实现金钱的额外增长，实际加的很少，但也好过没有。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"微信小游戏《萌宠变变变》模拟点击开发"},{"content":" 很多地方，我们需要使用观察者设计模式，而Java内部有提供了这样的一个类。通过继承Observable类和实现Observer接口，就可以省下一些代码，不用我们自己去实现这些代码。\nObservable类 Observable类的方法 public void addObserver(Observer o) 添加一个观察者 public void deleteObserver(Observer o) 删除一个观察者 public void notifyObservers() 通知所有观察者 public void notifyObservers(Object arg) 通知所有观察者，带一个Object参数 public void deleteObservers() 删除所有观察者 protected void setChanged() 设置主题数据改变标志 protected void clearChanged() 清除主题数据改变标志 public boolean hasChanged() public int countObservers() Observer接口 void update(Observable o, Object arg); 主题更新时调用这个方法 一个例子 这个例子实现了对一个书单的监视，当书单改变时，会通知观察者。 具体原理如下：\n实现Observer接口，也就是观察者 继承Observable类，在数据改变时使用setchanged()和notifyObservers()方法可以通知观察者 新建Observer和Observable实例，并使用addObserver()将观察者加入到观察列表中 改变数据，这时候观察者已经可以收到改变的通知了。 BookObserver.java代码 1 2 3 4 5 6 7 8 9 10 11 12 13 import java.util.Observable; import java.util.Observer; public class BookObserver implements Observer { private String name; public BookObserver(String name) { this.name = name; } @Override public void update(Observable o, Object arg) { // 主题更新时调用这个方法 BookList bl = (BookList)o; System.out.println(name+\u0026#34;: \u0026#34;+bl.getBooks().toString()); } } BookList.java代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import java.util.ArrayList; import java.util.List; import java.util.Observable; public class BookList extends Observable { List\u0026lt;String\u0026gt; books; public void addBook(String bookName) { if (books == null) books = new ArrayList\u0026lt;\u0026gt;(); books.add(bookName); setChanged(); // 设置数据发生变化标志 notifyObservers(); // 通知所有观察者 } public void delBook(String bookName) { if (books.remove(bookName)) { setChanged(); notifyObservers(); } } public List\u0026lt;String\u0026gt; getBooks() { return books; } public void setBooks(List\u0026lt;String\u0026gt; books) { this.books = books; } } Main.java代码 1 2 3 4 5 6 7 8 9 10 11 12 public class Main { public static void main(String[] args) { BookList bl = new BookList(); // 新建主题实例 BookObserver bo1 = new BookObserver(\u0026#34;Observer1\u0026#34;); // 新建观察者实例 BookObserver bo2 = new BookObserver(\u0026#34;Observer2\u0026#34;); bl.addObserver(bo1); // 将观察者加入主题 bl.addObserver(bo2); bl.addBook(\u0026#34;Java从入门到放弃\u0026#34;); // 更新主题 bl.addBook(\u0026#34;MySQL从删库到跑路\u0026#34;); bl.delBook(\u0026#34;MySQL从删库到跑路\u0026#34;); } } 输出结果 1 2 3 4 5 6 7 \u0026#34;F:\\Program Files\\Java\\jdk1.8.0_172\\bin\\java.exe\u0026#34; ... Observer2: [Java从入门到放弃] Observer1: [Java从入门到放弃] Observer2: [Java从入门到放弃, MySQL从删库到跑路] Observer1: [Java从入门到放弃, MySQL从删库到跑路] Observer2: [Java从入门到放弃] Observer1: [Java从入门到放弃] 源码解析(JDK 8u172) Observer接口源码 Observer接口只有一个方法。\n1 2 3 4 package java.util; public interface Observer { void update(Observable o, Object arg); } Observable类源码 Observable类内部维护了changed和ob两个变量，其中changed是数据改变标志，而obs就是观察者列表了。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package java.util; public class Observable { private boolean changed = false; private Vector\u0026lt;Observer\u0026gt; obs; public Observable() { obs = new Vector\u0026lt;\u0026gt;(); } public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } public void notifyObservers() { notifyObservers(null); } public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); // 对观察者列表做一个缓存 clearChanged(); } for (int i = arrLocal.length-1; i\u0026gt;=0; i--) ((Observer)arrLocal[i]).update(this, arg); // 根据缓存来通知观察者 // 缓存机制解决了并发线程对obs改变造成的异常问题 // 但实际这样子有时候还是会出问题的，比如多线程的时候，可能某个观察者已经删除了，但是它仍然可能被通知。 // 或者新加进来的没有被通知到，因为用的是缓存。 } public synchronized void deleteObservers() { obs.removeAllElements(); } protected synchronized void setChanged() { changed = true; } protected synchronized void clearChanged() { changed = false; } public synchronized boolean hasChanged() { return changed; } public synchronized int countObservers() { return obs.size(); } } 总结 Observable是一个类，只能通过继承来实现。这也限制了它的使用范围，比如Java是不支持多重继承的。 在必要的时候，可以自己手写代码，而不是用这个工具类。\n","permalink":"https://www.zeyes.org/posts/tech/java-learn-observable/","summary":"\u003cblockquote\u003e\n\u003cp\u003e很多地方，我们需要使用观察者设计模式，而Java内部有提供了这样的一个类。通过继承Observable类和实现Observer接口，就可以省下一些代码，不用我们自己去实现这些代码。\u003c/p\u003e","title":"Java工具类Observable源码解析与使用"},{"content":" 通常我们需要定时执行一些任务，这时候可以使用Timer来完成，准确来说是Timer和TimerTask两个类配合完成。Timer是一个工具类，而TimerTask是一个抽象类，实现了Runnable接口。通过继承TimerTask可以定制我们自己的任务，并将我们自己的TimerTask加入Timer中。 Timer内部会调用TimerTask的run方法来执行业务流程。\nTimer类 Timer类的方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - public void schedule(TimerTask task, Date time) 只执行一次，在等于或者超过某个时间之后执行 - public void schedule(TimerTask task, Date firstTime, long period) 执行多次，在fistTime后首次执行task，之后每隔period毫秒执行task - public void schedule(TimerTask task, long delay) 执行一次，在delay毫秒后执行task - public void schedule(TimerTask task, long delay, long period) 执行多次，在delay毫秒后首次执行task，之后每隔period毫秒执行task - public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 与上面类似，有点不一样。 - public void scheduleAtFixedRate(TimerTask task, long delay, long period) - public void cancel() 终止Timer里面的所有已安排任务 - public int purge() 移除Timer里面的所有已取消的任务，返回取消任务的数量 schedule 与 scheduleAtFixedRate区别 schedule按照上一次实际执行完成的时间点进行计算 scheduleAtFixedRate按照上一次开始的时间点进行计算，需要考虑同步问题 TimerTask类 public void cancel() 取消当前TimerTask里的任务 public long scheduledExecutionTime() 返回此任务的最近实际执行的计划执行时间。 一个例子 MyTimerTask.java代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import java.text.SimpleDateFormat; import java.util.TimerTask; public class MyTimerTask extends TimerTask { private String name; public MyTimerTask() { } public MyTimerTask(String name) { this.name = name; } @Override public void run() { // 具体业务代码可以在run方法内实现，Timer在触发任务的时候会调用这个方法 SimpleDateFormat sf = new SimpleDateFormat(\u0026#34;yyyy-MM-dd HH:mm:ss\u0026#34;); System.out.println(sf.format(System.currentTimeMillis()) + \u0026#34;: \u0026#34; +name); // 简单输出一下 } } Main.java代码 1 2 3 4 5 6 7 8 9 10 11 12 import java.util.Timer; import java.util.TimerTask; public class Main { public static void main(String[] args) { Timer timer = new Timer(); // 新建Timer实例 TimerTask task = new MyTimerTask(\u0026#34;Task Test\u0026#34;); // 新建任务 // public void schedule(TimerTask task, long delay, long period) timer.schedule(task, 2000L, 1000L); // 添加任务 } } 输出结果 1 2 3 4 \u0026#34;F:\\Program Files\\Java\\jdk1.8.0_172\\bin\\java.exe\u0026#34; ... 2018-05-05 01:00:09: Task Test 2018-05-05 01:00:10: Task Test 2018-05-05 01:00:11: Task Test 源码解析(JDK 8u172) TimerTask类源码 TimerTask类其实很简单，里面只有两个函数有具体代码。一个是cancel()方法，一个是scheduledExecutionTime()方法。 TimerTask类中有一个属性是period，这个属性是用来表示重复任务的周期，以毫秒为单位。但是这个属性不一定是正数，也可能是负数。当是正数的时候表示以固定的速率执行，负数是固定延迟执行。这个比较难理解，其实就是说正数的时候不受到任务的执行时间影响，而负数的时候是任务完成了才开始计算这个周期。Timer中的scheduleAtFixedRate是正数模式，schedule方法是负数模式。0的时候表示非重复任务。 另外一个比较重要的是nextExecutionTime这个属性，他是代表了下一次执行的时间，格式是类似System.currentTimeMillis()的返回值。Timer也是根据这个属性来对TimerTask建堆的。 下面是删除了注释的TimerTask类源码，其实也就这么一点。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package java.util; public abstract class TimerTask implements Runnable { final Object lock = new Object(); int state = VIRGIN; static final int VIRGIN = 0; static final int SCHEDULED = 1; static final int EXECUTED = 2; static final int CANCELLED = 3; long nextExecutionTime; long period = 0; protected TimerTask() { } public abstract void run(); public boolean cancel() { synchronized(lock) { boolean result = (state == SCHEDULED); state = CANCELLED; return result; } } public long scheduledExecutionTime() { synchronized(lock) { return (period \u0026lt; 0 ? nextExecutionTime + period : nextExecutionTime - period); } } } Timer类源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 package java.util; import java.util.Date; import java.util.concurrent.atomic.AtomicInteger; public class Timer { // 任务队列，使用最小堆实现 private final TaskQueue queue = new TaskQueue(); // 内部维护的线程 private final TimerThread thread = new TimerThread(queue); private final Object threadReaper = new Object() { protected void finalize() throws Throwable { synchronized(queue) { thread.newTasksMayBeScheduled = false; queue.notify(); // In case queue is empty. } } }; private final static AtomicInteger nextSerialNumber = new AtomicInteger(0); private static int serialNumber() { return nextSerialNumber.getAndIncrement(); } // 构造方法 public Timer() { this(\u0026#34;Timer-\u0026#34; + serialNumber()); } // 构造方法, 守护方式执行 public Timer(boolean isDaemon) { this(\u0026#34;Timer-\u0026#34; + serialNumber(), isDaemon); } public Timer(String name) { thread.setName(name); thread.start(); } public Timer(String name, boolean isDaemon) { thread.setName(name); thread.setDaemon(isDaemon); thread.start(); } public void schedule(TimerTask task, long delay) { if (delay \u0026lt; 0) throw new IllegalArgumentException(\u0026#34;Negative delay.\u0026#34;); sched(task, System.currentTimeMillis()+delay, 0); } public void schedule(TimerTask task, Date time) { sched(task, time.getTime(), 0); } public void schedule(TimerTask task, long delay, long period) { if (delay \u0026lt; 0) throw new IllegalArgumentException(\u0026#34;Negative delay.\u0026#34;); if (period \u0026lt;= 0) throw new IllegalArgumentException(\u0026#34;Non-positive period.\u0026#34;); sched(task, System.currentTimeMillis()+delay, -period); } public void schedule(TimerTask task, Date firstTime, long period) { if (period \u0026lt;= 0) throw new IllegalArgumentException(\u0026#34;Non-positive period.\u0026#34;); sched(task, firstTime.getTime(), -period); } public void scheduleAtFixedRate(TimerTask task, long delay, long period) { if (delay \u0026lt; 0) throw new IllegalArgumentException(\u0026#34;Negative delay.\u0026#34;); if (period \u0026lt;= 0) throw new IllegalArgumentException(\u0026#34;Non-positive period.\u0026#34;); sched(task, System.currentTimeMillis()+delay, period); } public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) { if (period \u0026lt;= 0) throw new IllegalArgumentException(\u0026#34;Non-positive period.\u0026#34;); sched(task, firstTime.getTime(), period); } private void sched(TimerTask task, long time, long period) { if (time \u0026lt; 0) throw new IllegalArgumentException(\u0026#34;Illegal execution time.\u0026#34;); // Constrain value of period sufficiently to prevent numeric // overflow while still being effectively infinitely large. if (Math.abs(period) \u0026gt; (Long.MAX_VALUE \u0026gt;\u0026gt; 1)) period \u0026gt;\u0026gt;= 1; synchronized(queue) { if (!thread.newTasksMayBeScheduled) throw new IllegalStateException(\u0026#34;Timer already cancelled.\u0026#34;); synchronized(task.lock) { if (task.state != TimerTask.VIRGIN) throw new IllegalStateException( \u0026#34;Task already scheduled or cancelled\u0026#34;); task.nextExecutionTime = time; task.period = period; task.state = TimerTask.SCHEDULED; } queue.add(task); if (queue.getMin() == task) queue.notify(); } } // 这个cancel与TimerTask中的不一样，这该是取消所有的任务 public void cancel() { synchronized(queue) { thread.newTasksMayBeScheduled = false; queue.clear(); // 清空队列里面的内容 queue.notify(); // In case queue was already empty. } } // 清除已经cancel的任务 public int purge() { int result = 0; synchronized(queue) { for (int i = queue.size(); i \u0026gt; 0; i--) { if (queue.get(i).state == TimerTask.CANCELLED) { queue.quickRemove(i); // 这个quickRemove不调整堆，所以接下来要重新建堆 result++; } } if (result != 0) queue.heapify(); // 调整堆，其实也是重新建堆 } return result; } } // Timer的任务线程类 class TimerThread extends Thread { boolean newTasksMayBeScheduled = true; private TaskQueue queue; // 任务队列 TimerThread(TaskQueue queue) { this.queue = queue; } public void run() { try { mainLoop(); } finally { // Someone killed this Thread, behave as if Timer cancelled synchronized(queue) { newTasksMayBeScheduled = false; queue.clear(); // Eliminate obsolete references } } } // 线程循环 private void mainLoop() { while (true) { try { TimerTask task; // 当前的任务 boolean taskFired; // 任务激活标志 synchronized(queue) { // 如果队列是空的，就使用wait等待任务 while (queue.isEmpty() \u0026amp;\u0026amp; newTasksMayBeScheduled) queue.wait(); if (queue.isEmpty()) break; // 如果收到notify后仍然是空的，直接退出循环 // 如果队列不为空，往下执行 long currentTime, executionTime; task = queue.getMin(); synchronized(task.lock) { if (task.state == TimerTask.CANCELLED) { queue.removeMin(); // 移除已经cancel的队列 continue; } currentTime = System.currentTimeMillis(); executionTime = task.nextExecutionTime; if (taskFired = (executionTime\u0026lt;=currentTime)) { if (task.period == 0) { // 0是不重复任务 queue.removeMin(); // 这个removeMin是有维护堆的 task.state = TimerTask.EXECUTED; } else { // 重复的任务 queue.rescheduleMin( task.period\u0026lt;0 ? currentTime - task.period /* schedule模式 */ : executionTime + task.period); // AtFixedRate模式 } } } if (!taskFired) // 如果发现有任务，但是最近的任务没有到执行时间，进入线程等待 queue.wait(executionTime - currentTime); } if (taskFired) // Task fired; run it, holding no locks task.run(); } catch(InterruptedException e) { } } } } class TaskQueue { private TimerTask[] queue = new TimerTask[128]; /** * The number of tasks in the priority queue. (The tasks are stored in * queue[1] up to queue[size]). */ private int size = 0; int size() { return size; } void add(TimerTask task) { // Grow backing store if necessary if (size + 1 == queue.length) // 如果发现位置不够用，就扩充数组 queue = Arrays.copyOf(queue, 2*queue.length); queue[++size] = task; // 将任务加入堆的最后 fixUp(size); // 堆向上调整，维护堆 } TimerTask getMin() { return queue[1]; } TimerTask get(int i) { return queue[i]; } void removeMin() { queue[1] = queue[size]; queue[size--] = null; // Drop extra reference to prevent memory leak fixDown(1); // 堆向下调整，维护堆 } void quickRemove(int i) { assert i \u0026lt;= size; queue[i] = queue[size]; queue[size--] = null; // Drop extra ref to prevent memory leak } void rescheduleMin(long newTime) { queue[1].nextExecutionTime = newTime; fixDown(1); } boolean isEmpty() { return size==0; } void clear() { // Null out task references to prevent memory leak for (int i=1; i\u0026lt;=size; i++) queue[i] = null; size = 0; } private void fixUp(int k) { while (k \u0026gt; 1) { int j = k \u0026gt;\u0026gt; 1; // j是父结点，k是子结点 if (queue[j].nextExecutionTime \u0026lt;= queue[k].nextExecutionTime) break; TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; k = j; } } private void fixDown(int k) { int j; while ((j = k \u0026lt;\u0026lt; 1) \u0026lt;= size \u0026amp;\u0026amp; j \u0026gt; 0) { // 这里j和j+1是子结点，k是父结点 if (j \u0026lt; size \u0026amp;\u0026amp; queue[j].nextExecutionTime \u0026gt; queue[j+1].nextExecutionTime) // 找出两个子结点中最小的 j++; // j indexes smallest kid if (queue[k].nextExecutionTime \u0026lt;= queue[j].nextExecutionTime) // 判断是否需要调整堆 break; TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; k = j; } } void heapify() { // 建堆 for (int i = size/2; i \u0026gt;= 1; i--) fixDown(i); } } 总结 对于一些比较简单的任务可以用Timer和TimerTask实现任务队列，但是对于一些耗时的任务这个Timer就不能胜任了。在Timer的内部实现中，始终是单线程的方式执行，任务的耗时实际上是会影响下个任务的开始执行时间的，即便有scheduleAtFixedRate仍然不能很好的解决这个问题。\n","permalink":"https://www.zeyes.org/posts/tech/java-learn-timer/","summary":"\u003cblockquote\u003e\n\u003cp\u003e通常我们需要定时执行一些任务，这时候可以使用Timer来完成，准确来说是Timer和TimerTask两个类配合完成。Timer是一个工具类，而TimerTask是一个抽象类，实现了Runnable接口。通过继承TimerTask可以定制我们自己的任务，并将我们自己的TimerTask加入Timer中。\nTimer内部会调用TimerTask的run方法来执行业务流程。\u003c/p\u003e","title":"Java工具类Timer源码解析与使用"},{"content":"Hexo折腾过程 01#安装git 下载地址：https://git-scm.com/downloads\n02#安装Node.js 下载地址：https://nodejs.org/en/download/\n03#安装Hexo 进入Git Bash，在某个地方创建一个目录，比如E:/hexo\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 cd /e/hexo npm install -g cnpm --registry=https://registry.npm.taobao.org cnpm install hexo-cli -g hexo init cnpm install cnpm install hexo-server --save cnpm install hexo-deployer-git --save cnpm install hexo-generator-index --save cnpm install hexo-generator-archive --save cnpm install hexo-generator-tag --save cnpm install hexo-generator-feed --save cnpm install hexo-generator-sitemap --save cnpm install hexo-generator-baidu-sitemap --save cnpm install hexo-related-popular-posts --save #cnpm install hexo-generator-searchdb --save #cnpm install hexo-algolia --save #cnpm install hexo-asset-image --save #cnpm instal hexo-util --save-dev #cnpm install hexo-leancloud-counter-security --save 04#安装Next主题 1 2 git clone https://github.com/theme-next/hexo-theme-next.git themes/next git clone https://github.com/theme-next/theme-next-algolia-instant-search themes/next/source/lib/algolia-instant-search 05#配置Hexo 配置文件：_config.yml 官方文档：https://hexo.io/zh-cn/docs/ 个人喜好配置：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 # 设置网站信息 title: 凌点时代 subtitle: description: 一个码农。 keywords: author: Zeyes language: zh-CN timezone: Asia/Shanghai url: http://www.zeyes.org root: / permalink: blog/:year/:month/:urlname.html new_post_name: :year-:month-:day-:title.md index_generator: path: \u0026#39;\u0026#39; per_page: 10 order_by: -date # 归档每页文章数量 archive_generator: per_page: 50 yearly: true monthly: true tag_generator: per_page: 10 # 设置主题 theme: next # sitemap sitemap: path: sitemap.xml baidusitemap: path: baidusitemap.xml # 部署 deploy: type: git repo: git@server:/data/repo/blog.git branch: master 06#配置Next (1) 复制配置文件 在hexo的source下新建_data文件夹，将theme/next目录下的_config.yml复制到source/_data文件夹，并重命名为next.yml PS:值得注意的是，hexo也有配置文件，称为站点配置文件，而next里面的配置文件称为主题配置文件\n(2) 配置next.yml 此处根据官方文档，进行个性化配置。 Next官方文档：http://theme-next.iissnan.com/getting-started.html 另外一份文档：https://github.com/iissnan/hexo-theme-next/wiki\n07#配置服务器 配置文档1：https://segmentfault.com/a/1190000005723321 配置文档2：http://www.swiftyper.com/2016/04/17/deploy-hexo-with-git-hook/\n08#部署 1 hexo d 09#常用操作 1 2 3 4 5 6 7 8 # 新建文章 hexo new pages \u0026#34;xxxx\u0026#34; # 清理 hexo clean # 生成 hexo g # 本地查看 hexo s 10# 插件使用 1 2 export HEXO_ALGOLIA_INDEXING_KEY=xxx hexo algolia 11#一些踩过的坑 (1) 经常用着用着就不认hexo命令了 解决办法： 删除node_modules文件夹在npm install一下就可以了\n(2) 主题页面部署后没有正确显示，只显示默认主题 解决办法：\n删除.deploy_git文件夹 hexo clean \u0026amp;\u0026amp; hexo g 在服务器上删除重建blog.git hexo d Ps： 这个问题至今未真正解决，莫名其妙就好了。 ","permalink":"https://www.zeyes.org/posts/tech/install-hexo-and-next/","summary":"\u003ch2 id=\"hexo折腾过程\"\u003eHexo折腾过程\u003c/h2\u003e\n\u003ch3 id=\"01安装git\"\u003e01#安装git\u003c/h3\u003e\n\u003cp\u003e下载地址：\u003ca href=\"https://git-scm.com/downloads\"\u003ehttps://git-scm.com/downloads\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"02安装nodejs\"\u003e02#安装Node.js\u003c/h3\u003e\n\u003cp\u003e下载地址：\u003ca href=\"https://nodejs.org/en/download/\"\u003ehttps://nodejs.org/en/download/\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"03安装hexo\"\u003e03#安装Hexo\u003c/h3\u003e\n\u003cp\u003e进入Git Bash，在某个地方创建一个目录，比如\u003ccode\u003eE:/hexo\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e 1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 4\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 5\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 6\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 7\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 8\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 9\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e10\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e11\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e12\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e13\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e14\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e15\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e16\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e17\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e18\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e19\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ecd\u003c/span\u003e /e/hexo\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enpm install -g cnpm --registry\u003cspan class=\"o\"\u003e=\u003c/span\u003ehttps://registry.npm.taobao.org\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecnpm install hexo-cli -g\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ehexo init\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecnpm install\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecnpm install hexo-server --save\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecnpm install hexo-deployer-git --save\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecnpm install hexo-generator-index --save\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecnpm install hexo-generator-archive --save\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecnpm install hexo-generator-tag --save\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecnpm install hexo-generator-feed --save\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecnpm install hexo-generator-sitemap --save\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecnpm install hexo-generator-baidu-sitemap --save\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecnpm install hexo-related-popular-posts --save\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#cnpm install hexo-generator-searchdb --save\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#cnpm install hexo-algolia --save\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#cnpm install hexo-asset-image --save\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#cnpm instal hexo-util --save-dev\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#cnpm install hexo-leancloud-counter-security --save\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch3 id=\"04安装next主题\"\u003e04#安装Next主题\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit clone https://github.com/theme-next/hexo-theme-next.git themes/next\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit clone https://github.com/theme-next/theme-next-algolia-instant-search themes/next/source/lib/algolia-instant-search\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch3 id=\"05配置hexo\"\u003e05#配置Hexo\u003c/h3\u003e\n\u003cp\u003e配置文件：\u003ccode\u003e_config.yml\u003c/code\u003e\n官方文档：\u003ca href=\"https://hexo.io/zh-cn/docs/\"\u003ehttps://hexo.io/zh-cn/docs/\u003c/a\u003e\n个人喜好配置：\u003c/p\u003e","title":"Hexo折腾记"},{"content":"这个问题其实是烦恼了挺久的，就我本人而言，通常不用括号补全，我觉得手打比较完整，在学JSP以前，用Eclipse虽然他带括号补全，但是也不影响手打，比如打括号时，不会出现三个括号。 而JSP的Editor就比较烦人了。比如打\u0026lt;%%\u0026gt;会出现\u0026lt;%%\u0026raquo;。\n找了许久，解决办法如下Window-\u0026gt;Preferences-\u0026gt;Web-\u0026gt;JSP Files-\u0026gt;Editor-\u0026gt;Typing 然后把Automatically close下面的勾全部取消，然后Apply。\n","permalink":"https://www.zeyes.org/posts/tech/study-eclipse-jsp-auto-close/","summary":"\u003cp\u003e这个问题其实是烦恼了挺久的，就我本人而言，通常不用括号补全，我觉得手打比较完整，在学JSP以前，用Eclipse虽然他带括号补全，但是也不影响手打，比如打括号时，不会出现三个括号。\n而JSP的Editor就比较烦人了。比如打\u0026lt;%%\u0026gt;会出现\u0026lt;%%\u0026raquo;。\u003c/p\u003e","title":"解决用Eclipse EE写JSP代码时，自动括号补全问题"},{"content":" 学校用的是中兴802.1X认证，只有通过认证的客户端才能上内网和外网。外网登录天翼校园网才能上网。而官方的中兴802.1X客户端，只能运行在Window平台上，并且不能共享WIFI。因此下决心开发了个OpenWrt版的中兴802.1X客户端，这个客户端不同的是专门为我校写的，所以还带了登录天翼校园网的功能。\n源码说明 源码地址：https://gitee.com/zeyes/dclient.git\ndclient D客户端主要代码 luci-app-dclient D客户端LUCI设置界面 po2lmo 多语言支持（英文+中文） 认证过程 初始化并读取配置 进行802.1X认证 等待获取IP地址 进行天翼校园网认证 认证成功后维持登录状态 编译准备 操作系统：Ubuntu 14.04 OpenWrt SDK: OpenWrt SDK 15.05\n安装依赖 不同系统，OpenWrt所需的依赖不一样，可在OpenWrt官网找到，我这里给出Ubuntu 14.04的\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 sudo apt-get update sudo apt-get install git sudo apt-get install asciidoc bash bc binutils bzip2 sudo apt-get install fastjar flex git-core g++ sudo apt-get install build-essential util-linux gawk sudo apt-get install libgtk2.0-dev intltool jikespg sudo apt-get install zlib1g-dev genisoimage genisoimage sudo apt-get install libncurses5-dev libssl-dev patch sudo apt-get install perl-modules python2.7-dev rsync ruby sdcc sudo apt-get install unzip wget gettext xsltproc libboost-dev libboost-tools-dev sudo apt-get install libxml-parser-perl libusb-dev bin86 sudo apt-get install bcc ecj sharutils openjdk-8-jdk subversion sudo apt-get install gcc-multilib quilt libglib2.0-dev gperf sudo apt-get install ccache sudo apt-get install libcurl4-openssl-dev libpcap-dev sudo apt-get install libjpeg-dev glibc-doc manpages-posix-dev 安装OpenWrt SDK SDK的安装其实就是解压，将SDK解压的home目录就可以了，建议将SDK文件夹重命名为openwrt。\n更新Feeds 这一步可能容易出错，建议忽略，如果忽略，则编译的时候不会生成依赖ipk，但是依赖ipk可以从如下网址找到：http://archive.openwrt.org/chaos_calmer/15.05.1/ramips/mt7620/packages/\n1 2 3 4 5 6 cd openwrt/ ./scripts/feeds update -a ./scripts/feeds install -a # 检查编译环境是否完整 make defconfig make prereq 将源码放入 OpenWrt 下载源码，并将dclient和luci-app-dclient文件夹放入SDK目录的package文件夹下,如果如果SDK目录是openwrt，那么应该放入openwrt/package文件夹下面。\n编译安装po2lmo 1 2 cd po2lmo sudo make \u0026amp;\u0026amp; make install 编译 在SDK根目录下（注意位置），执行命令\n1 2 make package/dclient/complie V=s make package/luci-app-dclient/compile V=s 如果没有出错，那么可以在SDK的bin目录下找到dclient_xxx.ipk和luci-app-dclient_xxx.ipk，将其复制出来。\n下载IPK依赖 安装的时候，提示什么就下载什么。 依赖ipk可以从如下网址找到：http://archive.openwrt.org/chaos_calmer/15.05.1/ramips/mt7620/packages/\n安装ipk 1 2 3 4 # 安装 opkg install xxx.ipk # 卸载 opkg remove xxx.ipk 一键安装脚本 百度网盘下载：https://pan.baidu.com/s/1ouBo-FWumKIROtYk2fUhyg 密码：emzl 此脚本是为K2 Pandorabox写的，仅供参考。 将ipk文件夹和k2.sh上传到/tmp目录下，直接执行/tmp/k2.sh\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 #!/bin/sh export PATH=\u0026#34;/usr/bin:/usr/sbin:/bin:/sbin\u0026#34; . /lib/functions.sh . /lib/config/uci.sh Install() { local basepath=$(cd `dirname $0`; pwd) local config_path=\u0026#34;/etc/config/dclient\u0026#34; local dir=\u0026#34;${basepath}/ipk/\u0026#34; local libpolarssl=\u0026#34;libpolarssl_*.ipk\u0026#34; local libcurl=\u0026#34;libcurl_*.ipk\u0026#34; local libpthread=\u0026#34;libpthread_*.ipk\u0026#34; local libstdcpp=\u0026#34;libstdcpp_*.ipk\u0026#34; local libjpeg=\u0026#34;libjpeg_*.ipk\u0026#34; local libpcap=\u0026#34;libpcap_*.ipk\u0026#34; local dclient=\u0026#34;dclient_*.ipk\u0026#34; local luci_app_dclient=\u0026#34;luci-app-dclient_*.ipk\u0026#34; local running local ipk_conf local backup_conf if [ ! -d \u0026#34;$dir\u0026#34; ]; then echo \u0026#34;安装文件不存在...\u0026#34; return -1 fi ipk_conf=$(cat \u0026#34;/etc/opkg.conf\u0026#34; | grep \u0026#34;mispel_24kec\u0026#34;) backup_conf=$(cat \u0026#34;/etc/sysupgrade.conf\u0026#34; | grep \u0026#34;/overlay\u0026#34;) if [ -z \u0026#34;$ipk_conf\u0026#34; ]; then echo \u0026#34;正在注入ipk配置...\u0026#34; cat ${dir}arch.txt \u0026gt;\u0026gt; /etc/opkg.conf fi if [ -z \u0026#34;$backup_conf\u0026#34; ]; then echo \u0026#34;正在注入备份配置...\u0026#34; echo \u0026#34;/overlay\u0026#34; \u0026gt;\u0026gt; /etc/sysupgrade.conf fi echo \u0026#34;正在安装依赖文件...\u0026#34; opkg install ${dir}${libpolarssl} opkg install ${dir}${libcurl} opkg install ${dir}${libpthread} opkg install ${dir}${libstdcpp} opkg install ${dir}${libjpeg} opkg install ${dir}${libpcap} if [ -f $config_path ]; then echo \u0026#34;正在备份配置文件...\u0026#34; cp $config_path /tmp/dclient.bak fi running=$( ps | grep \u0026#34;dclient -u\u0026#34; | grep -v \u0026#34;grep\u0026#34; ) if [ -n \u0026#34;$running\u0026#34; ]; then echo \u0026#34;正在停止dclient客户端...\u0026#34; /etc/init.d/dclient stop fi echo \u0026#34;正在安装主程序...\u0026#34; opkg install ${dir}${dclient} echo \u0026#34;正在升级客户端界面...\u0026#34; opkg install ${dir}${luci_app_dclient} if [ -f \u0026#34;/tmp/dclient.bak\u0026#34; ]; then echo \u0026#34;正在恢复备份配置文件...\u0026#34; cp /tmp/dclient.bak $config_path fi echo \u0026#34;正在设置定时任务...\u0026#34; cp -f ${dir}/rm_dclient_log.sh /usr/bin/rm_dclient_log.sh chmod 755 /usr/bin/rm_dclient_log.sh sed -i \u0026#39;/rm_dclient_log.sh/d\u0026#39; /etc/crontabs/root echo \u0026#34;50 5 * * 3,6 sh /usr/bin/rm_dclient_log.sh \u0026amp;\u0026#34; \u0026gt;\u0026gt; /etc/crontabs/root /etc/init.d/cron reload echo \u0026#34;正在启动dclient客户端...\u0026#34; /etc/init.d/dclient start echo \u0026#34;正在设置硬件加速...\u0026#34; if [ -f \u0026#34;/etc/config/hwacc\u0026#34; ]; then rm -f /etc/config/hwacc fi touch /etc/config/hwacc uci add hwacc \u0026#39;hwnat\u0026#39; uci set hwacc.@hwnat[0]=hwnat uci set hwacc.@hwnat[0].enabled=\u0026#39;1\u0026#39; uci set hwacc.@hwnat[0].tcp_offload=\u0026#39;1\u0026#39; uci set hwacc.@hwnat[0].udp_offload=\u0026#39;1\u0026#39; uci set hwacc.@hwnat[0].ipv6_offload=\u0026#39;1\u0026#39; uci set hwacc.@hwnat[0].wifi_offload=\u0026#39;1\u0026#39; uci commit hwacc # 修复城院二期网络dhcp需要发送mac作为clientid才能获取ip地址的问题 echo \u0026#34;正在修改/lib/netifd/proto/dhcp.sh文件\u0026#34; sed -i \u0026#39;s/ || clientid=\u0026#34;-C\u0026#34;//\u0026#39; /lib/netifd/proto/dhcp.sh echo \u0026#34;正在修改版本号...\u0026#34; if [ -f \u0026#34;/etc/openwrt_release\u0026#34; ]; then rm -f /etc/openwrt_release touch /etc/openwrt_release fi echo -e \u0026#34;DISTRIB_ID=\\\u0026#34;DotTimes\\\u0026#34;\\nDISTRIB_RELEASE=\\\u0026#34;17.01\\\u0026#34;\u0026#34; \u0026gt;\u0026gt; /etc/openwrt_release echo -e \u0026#34;DISTRIB_REVISION=\\\u0026#34;${ls_date}\\\u0026#34;\\nDISTRIB_CODENAME=\\\u0026#34;17.01\\\u0026#34;\u0026#34; \u0026gt;\u0026gt; /etc/openwrt_release echo \u0026#34;DISTRIB_TARGET=\\\u0026#34;ralink/mt7620\\\u0026#34;\u0026#34; \u0026gt;\u0026gt; /etc/openwrt_release echo \u0026#34;DISTRIB_DESCRIPTION=\\\u0026#34;DotTimes ${ls_date} (PandoraBox 17.01)\\\u0026#34;\u0026#34; \u0026gt;\u0026gt; /etc/openwrt_release echo \u0026#34;DISTRIB_TAINTS=\\\u0026#34;no-all busybox\\\u0026#34;\u0026#34; \u0026gt;\u0026gt; /etc/openwrt_release echo \u0026#34;${ls_date}\u0026#34; \u0026gt; /etc/openwrt_version return 0 } echo \u0026#34;start....\u0026#34; Install echo \u0026#34;end.\u0026#34; echo \u0026#34;Copyright @2017 Zeyes(lixize8888@qq.com)\u0026#34; 相关固件（已经内置插件）： 斐讯K2固件下载以及配置教程 固件下载链接：https://pan.baidu.com/s/1tQ2Q9NSxOo_qgJNnLi_NpA 密码：ddo7 配置教程：https://mp.weixin.qq.com/s/SbD90w2VxaS0uZAYNk-qxg\n斐讯K2P_A1版中兴802.1X固件（内附教程） 百度网盘：https://pan.baidu.com/s/1qYPuVXa 密码: xti9\n极路由系列 教程1：http://tieba.baidu.com/p/5335066340 教程2：https://mp.weixin.qq.com/s/QkRwB4l-1vSyd8OcyWwzHA\n其他 http://www.right.com.cn/forum/forum.php?mod=viewthread\u0026amp;tid=217137\n相关公众号 凌点时代\n","permalink":"https://www.zeyes.org/posts/tech/dclient-for-openwrt/","summary":"\u003cblockquote\u003e\n\u003cp\u003e学校用的是中兴802.1X认证，只有通过认证的客户端才能上内网和外网。外网登录天翼校园网才能上网。而官方的中兴802.1X客户端，只能运行在Window平台上，并且不能共享WIFI。因此下决心开发了个OpenWrt版的中兴802.1X客户端，这个客户端不同的是专门为我校写的，所以还带了登录天翼校园网的功能。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"D客户端(中兴802.1X) For OpenWrt"},{"content":"今天无缘无故，ubuntu的菜单栏就消失了。找了以下终于找到了解决办法。\n打开命令行窗口，输入以下语句：\n1 2 dconf reset -f /org/compiz/ setsid unity 然后，菜单栏就回来了。\n","permalink":"https://www.zeyes.org/posts/tech/solve-ubuntu-menu-item-disappered/","summary":"\u003cp\u003e今天无缘无故，ubuntu的菜单栏就消失了。找了以下终于找到了解决办法。\u003c/p\u003e\n\u003cp\u003e打开命令行窗口，输入以下语句：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edconf reset -f /org/compiz/\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esetsid unity\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e然后，菜单栏就回来了。\u003c/p\u003e","title":"Ubuntu 16.04 菜单栏消失的解决办法"},{"content":"中文乱码解决方案 “设置”菜单下选“环境设置”：\n“常规设置”，最下面\u0026quot;启动控制台程序的终端”，选择gnome-terminal -t $TITLE -x 原来是xterm -T $TITLE –e\n原地址： Ubuntu安装codeblocks 汉化 中文乱码 颜色主题 中文显示不全\n","permalink":"https://www.zeyes.org/posts/tech/solve-ubuntu-codeblocks-charset/","summary":"\u003ch3 id=\"中文乱码解决方案\"\u003e中文乱码解决方案\u003c/h3\u003e\n\u003cp\u003e“设置”菜单下选“环境设置”：\u003c/p\u003e\n\u003cp\u003e“常规设置”，最下面\u0026quot;启动控制台程序的终端”，选择gnome-terminal -t $TITLE -x\n原来是xterm -T $TITLE –e\u003c/p\u003e","title":"ubuntu中Codeblock运行中文乱码解决方法"},{"content":"最近开机总是遇到一段提示，内容大概是这样：\n下载额外数据文件失败 以下软件包要求安装后下载附加数据，但其数据无法下载或无法处理。 ttf-mscorefonts-installer 稍后系统将自动重试下载，您也可以手工立即重试。执行此命令需要有活动的网络连接。\n开始也挺烦恼，总是解决不掉。\n后台发现/usr/share/package-data-downloads有一个文件ttf-mscorefonts-installer，用gedit打开，有一大串地址。 其实解决办法是，手动把这些地址链接的文件下载下来，然后放到一个文件夹中。\n如果懒得下载，也可以使用已下载好的文件，百度网盘： https://pan.baidu.com/s/1jIcfEMa 密码: rbeh\n手动在命令行执行sudo dpkg-reconfigure ttf-mscorefonts-installer这条语句手动指定文件夹的位置,重新配置下。\n注意：在命令行不支持用鼠标点击，如果移动请使用tab键。\n然后输入以下命令\n1 2 3 4 5 6 cd /usr/share/package-data-downloads/ sudo rm ttf-mscorefonts-installer cd /var/lib/update-notifier/package-data-downloads/ sudo rm ttf-mscorefonts-installer cd /var/lib/update-notifier/user.d/ sudo rm data-downloads-failed 重启后应该就没有烦人的提示了。\n","permalink":"https://www.zeyes.org/posts/tech/solve-ubuntu-download-ttf-mscorefonts-installer-failed/","summary":"\u003cp\u003e最近开机总是遇到一段提示，内容大概是这样：\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e下载额外数据文件失败\n以下软件包要求安装后下载附加数据，但其数据无法下载或无法处理。\nttf-mscorefonts-installer\n稍后系统将自动重试下载，您也可以手工立即重试。执行此命令需要有活动的网络连接。\u003c/p\u003e","title":"解决Ubuntu“下载额外数据文件失败 ttf-mscorefonts-installer”的问题"},{"content":"1# 要下载的资料 MySQL Connector/J 6.0 下载网址：https://cdn.mysql.com/Downloads/Connector-J/mysql-connector-java-6.0.6.zip 查看最新版本下载链接：https://dev.mysql.com/downloads/connector/j/\n2# 解压文件 解压mysql-connector-java-6.0.6.zip，可得如下文件： 3# 放置文件 假如在D盘创建一个文件夹java，然后在java文件夹里面创建jdbc-drivers文件夹，将mysql-connector-java-6.0.6-bin.jar放入此文件夹。 可以得到此文件的路径，记住这个文件的位置，下面还需要用到。 D:\\java\\jdbc-drivers\\mysql-connector-java-6.0.6-bin.jar 其实文件路径是随意的，放在一个可以找得到的文件夹就可以了。\n4# 在Eclipse上配置mysql库文件 打开Eclipse在菜单里找到Window\u0026ndash;\u0026gt;Preferences打开 在设置界面里面找到Java\u0026ndash;\u0026gt;Build Path\u0026ndash;\u0026gt;User Libraries，打开如图所示： 点击New新建，在输入框输入jdbc，然后下面的System library勾上。 5# 导入jar包 接下类点击Add External JARs，将刚才的mysql-connector-java-6.0.6-bin.jar这个文件选中，然后最后OK保存设置就可以了。 6# 在Eclipse项目上使用Mysql库文件 在项目上右键弹出菜单选择Build Path然后选择Add Libraries 然后在弹出的选项中选则，User Libraries，接着勾选jdbc这个库，最后点Finsh完成就可以成功在项目中使用mysql进行连接数据库了。 7# 测试运行 注意：6.0版本的MySQL Connector/J 6.0与之前的版本有很大区别，不兼容。\n可以查看官方文档：https://dev.mysql.com/doc/connector-j/6.0/en/\n","permalink":"https://www.zeyes.org/posts/tech/eclipse-mysql/","summary":"\u003ch3 id=\"1-要下载的资料\"\u003e1# 要下载的资料\u003c/h3\u003e\n\u003cp\u003eMySQL Connector/J 6.0\n下载网址：\u003ca href=\"https://cdn.mysql.com/Downloads/Connector-J/mysql-connector-java-6.0.6.zip\"\u003ehttps://cdn.mysql.com/Downloads/Connector-J/mysql-connector-java-6.0.6.zip\u003c/a\u003e\n查看最新版本下载链接：\u003ca href=\"https://dev.mysql.com/downloads/connector/j/\"\u003ehttps://dev.mysql.com/downloads/connector/j/\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"2-解压文件\"\u003e2# 解压文件\u003c/h3\u003e\n\u003cp\u003e解压mysql-connector-java-6.0.6.zip，可得如下文件：\n\u003cimg loading=\"lazy\" src=\"/assets/tech/2017-06-15/63546416.png\"\u003e\u003c/p\u003e","title":"Eclipse Neon配置Mysql"},{"content":"1# 下载CodeBlocks 16.01和wxWidgets 3.0.3 CodeBlocks 16.01 下载地址：http://www.codeblocks.org/downloads/ wxWidgets 3.03 下载地址：https://www.wxwidgets.org/downloads/\n最新的wxWidgets 3.1.0 但是这里面没有使用这个版本的原因是CodeBlocks 16.01是2016.01发布的，而wxWidgets 3.1.0是2016.02发布的，这个版本的CodeBlocks没有集成3.10的配置。如果非要用也行，要改项目工程文件的配置。\n下载后得到两个文件：\ncodeblocks-16.01mingw-setup.exe wxWidgets-3.0.3.zip 2# 安装CodeBlocks 16.01和解压wxWidgets 3.0.3 将CodeBlocks安装到F:\\Program Files (x86)\\CodeBlocks 将wxWidgets解压到F:\\wxWidgets-3.0.3目录下 PS：其实这个随意，只要安装到找得到的地方就好。\n3# 设置环境变量 在编译wxWidgets之前首先要设置一下MinGw环境变量，CodeBlocks 16.01中有自带gcc 4.9.2 这个版本的编译器。比如我的CodeBlocks装在F:\\Program Files (x86)\\CodeBlocks这个目录下，那么将;F:\\Program Files (x86)\\CodeBlocks\\MinGW\\bin;加入Path路径中，保存。注意：环境变量每个路径以分号分隔的，如果前面路径结尾已经有了分号，那么只需要一个分号。\n在命令行下输入gcc –v有如下信息：\n1 2 3 4 5 6 7 8 9 C:\\Users\\Administrator\u0026gt;gcc -v Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=F:/Program\\ Files\\ (x86)/CodeBlocks/MinGW/bin/../libexec/gcc /mingw32/4.9.2/lto-wrapper.exe Target: mingw32 … Thread model: posix gcc version 4.9.2 (tdm-1) 看最末尾那行gcc version是4.9.2那么就是成功配置了。wxWidgets对编译器的版本也有要求的，如果是新手，建议使用CodeBlocks自带的编译器。\n4# 编译wxWidgets 3.0.3 在命令进入wxWigets的build/msw文件夹，例如我的是F:\\wxWidgets-3.0.3\\build\\msw这个路径\n1 2 F: cd F:\\wxWidgets-3.0.3\\build\\msw 接下来就是make了。\n编译命令如下：\n1 2 mingw32-make -j2 -f makefile.gcc BUILD=release SHARED=0 MONOLITHIC=1 UNICODE=1 mingw32-make -j2 -f makefile.gcc BUILD=debug SHARED=0 MONOLITHIC=1 UNICODE=1 这里使用开两个线程来编译，加入参数-j2，以加快速度，第一次运行会直接提示出错，再运行同样的命令一次就不会出问题了。当然如果不加-j2，也没问题，以正常的速度编译。值得一说的是，可以开两个命令行窗口同时编译，运行上面命令。比如一个窗口编译release版本，一个窗口编译debug版本。速度也能大大加快。\n还有其他的一些参数解释如下： BUILD BUILD控制wxWidgets构建调试版本（BUILD=debug）或者是发布版本（BUILD=release）。绝大多数情况下你只需要 wxWidgets的发布版本就可以了，因为你应该不想要去调试wxWidgets自身，同时你依然可以通过链接wxWidgets的发布版本来构建你自己的程序的调试版本。\nSHARED SHARED控制wxWidgets是构建DLL（SHARED=1）还是静态库（SHARED=0）。利用构建的DLL，主程序构建时间较快，可执行文件更小。但是可执行文件加上wxWidgets DLL的总大小更大，但是不同的可执行文件可以使用同一个DLL。\nMONOLITHIC MONOLITHIC控制是构建一个单一的库（MONOLITHIC=1）还是多个组件库（MONOLITHIC=0）。使用单一构建，项目的设置 和开发会更加简单，如果你同时使用DLL构建的话，你只需要分发一个DLL文件。如果使用非单一构建（multilib），会构建出多个不同的库同时你可 以避免将整个wxWidgets的基本代码链接到主程序，就可以去掉不需要的库。同时你也必须确保你选择了正确的组件库。\nUNICODE UNICODE控制wxWidgets以及你的程序是否使用支持Unicode的宽字符串。\n5# 配置CodeBlocks 编译好了以后还有在CodeBlocks上配置一下。 在Settings--\u0026gt;Global Variable Editor上Current Variable这个选项上New，输入wx。 然后设置下base、include和lib，例如我的wxWidgets安装位置是F:\\wxWidgets-3.0.3 6# 进行测试 下面是新建工程测试。 有一个事情是必须记住的，要记住编译时的参数。 上面的命令一共编译了两个版本，release和debug版，在构建项目的时候，两个都可以勾选。而且，SHARED=0代表使用静态库，Use wxWidgets DLL不勾选，MONOLITHIC=1 表示使用单一的库，wxWidgets is build as a monolithic library就要勾选，同理UNICODE=1表示支持Unicode，这样Enable unicode也要勾选。 7# 正常使用 接下来可以随意使用了。\n","permalink":"https://www.zeyes.org/posts/tech/configured-codeblocks-wxwidgets-303/","summary":"\u003ch3 id=\"1-下载codeblocks-1601和wxwidgets-303\"\u003e1# 下载CodeBlocks 16.01和wxWidgets 3.0.3\u003c/h3\u003e\n\u003cp\u003eCodeBlocks 16.01 下载地址：\u003ca href=\"http://www.codeblocks.org/downloads/\"\u003ehttp://www.codeblocks.org/downloads/\u003c/a\u003e\nwxWidgets 3.03 下载地址：\u003ca href=\"https://www.wxwidgets.org/downloads/\"\u003ehttps://www.wxwidgets.org/downloads/\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e最新的wxWidgets 3.1.0 但是这里面没有使用这个版本的原因是CodeBlocks 16.01是2016.01发布的，而wxWidgets 3.1.0是2016.02发布的，这个版本的CodeBlocks没有集成3.10的配置。如果非要用也行，要改项目工程文件的配置。\u003c/p\u003e","title":"CodeBlocks 16.01+wxWidgets 3.03配置"},{"content":"1# 给root用户设置密码 1 2 sudo passwd root sudo apt-get install numlockx 2# 使用命令行登录root 1 2 su - nautilus 这样弹出来的文件管理器就有root权限了(当然只是临时的)，为了方便改文件\n3# 改文件 在/usr/share/lightdm/lightdm.conf.d/50-ubuntu.conf文件增加两行\n1 2 greeter-show-manual-login=true allow-guest=false 在/usr/share/lightdm/lightdm.conf.d/50-unity-greeter.conf文件增加一行\n1 greeter-setup-script=/usr/bin/numlockx on 4# 重启 重启完以后就可以禁用guest账户和使用root登录了\n5# 可能出现的问题 使用root用户登录可能会出现Error found when loading /root/.profile stdin: is not a tty这样的错误\n解决办法如下： 修改/root/.profile文件，在文件管理器中设置 编辑\u0026ndash;首选项 勾选 显示隐藏和备份文件 就可以查看这个文件，用vi修改也行。把mesg n进行注释，增加一行tty -s \u0026amp;\u0026amp; mesg n保存，注销后重新登录问题解决。\n","permalink":"https://www.zeyes.org/posts/tech/study-ubuntu-login-settings/","summary":"\u003ch3 id=\"1-给root用户设置密码\"\u003e1# 给root用户设置密码\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo passwd root\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt-get install numlockx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch3 id=\"2-使用命令行登录root\"\u003e2# 使用命令行登录root\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esu -\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enautilus\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e这样弹出来的文件管理器就有root权限了(当然只是临时的)，为了方便改文件\u003c/p\u003e","title":"Ubuntu 14.04/16.04 开启root用户登录/自动开启小键盘"},{"content":" 目标： 搭建完成之后，可以在服务器(外网)上直接输入校园网(内网)地址进行访问。 #00 前言 最近对穿透学校的内网有需求，于是决定搭建环境。 网上的教程大多都是客户端访问服务器的内网， 但这个教程是，穿透客户端所在内网，由服务器访问客户端的内网。\n服务器IP：x.x.x.x 客户端IP：192.168.1.1 所在网段 192.168.1.0/24 学校内网网段：10.20.208.0/24\n路由器通过学校内网IP上网，此处省去路由端802.1X的认证过程，假设直接就能通过学校的认证并且获取到一个ip。\n在路由器上是可以直接访问10.20.208.0/24网段的，但是10.20.208.x是一个内网的地址，服务器所在环境是外网无法访问。\n如果是对内网的某一个某个网站进行穿透，建议使用ngrok，这里是对整个网段的穿透。\n#01 OpenVPN Server和Client所用的环境 OpenVpn Sever使用的服务器（CentOS 7.0） OpenVpn Client放在Openwrt(Pandorabox 16.10 stable, Openwrt 17.01)路由器上运行 #02 安装OpenVPN(服务器端) 登录服务器端，输入以下命令进行openvpn的安装\n1 yum install -y openvpn 或者是到openvpn的官方网站下载源码 https://openvpn.net/index.php/open-source/downloads.html\n1 wget https://swupdate.openvpn.org/community/releases/openvpn-2.4.1.tar.gz 然后按照下面的命令进行安装\n1 2 3 4 5 tar -zxf openvpn-2.4.1.tar.gz cd openvpn-2.4.1 ./configure make make install 安装完成openvpn之后，输入以下命令就可看到版本信息\n1 2 3 4 5 6 openvpn --version OpenVPN 2.4.1 x86_64-redhat-linux-gnu [Fedora EPEL patched] [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Apr 3 2017 library versions: OpenSSL 1.0.1e-fips 11 Feb 2013, LZO 2.06 Originally developed by James Yonan Copyright (C) 2002-2017 OpenVPN Technologies, Inc. \u0026lt;sales@openvpn.net\u0026gt; ...... #03 下载easy-rsa 1 2 3 4 5 6 7 wget -c https://github.com/OpenVPN/easy-rsa/archive/master.zip unzip master.zip mv easy-rsa-master easy-rsa cp -rf easy-rsa /etc/openvpn cd /etc/openvpn/easy-rsa cd /etc/openvpn/easy-rsa/easyrsa3 mv vars.example vars 解除注释之后，修改如下内容(填写自己的信息):\n1 2 3 4 5 6 7 vi vars set_var EASYRSA_REQ_COUNTRY \u0026#34;CN\u0026#34; set_var EASYRSA_REQ_PROVINCE \u0026#34;Guangdong\u0026#34; set_var EASYRSA_REQ_CITY \u0026#34;Dongguan\u0026#34; set_var EASYRSA_REQ_ORG \u0026#34;DotTimes Co\u0026#34; set_var EASYRSA_REQ_EMAIL \u0026#34;xxx@xxxx.com\u0026#34; set_var EASYRSA_REQ_OU \u0026#34;Zeyes\u0026#34; 修改完之后，按下ESC，然后输入:wq保存\n#04 配置证书文件 1. 初始化配置 1 ./easyrsa init-pki 如果成功的话，会在当前目录下创建pki/{reqs,private}目录，用于保存证书文件。\n2. 创建根证书 1 ./easyrsa build-ca 创建过程中需要输入根证书的密码以及Common Name。如果创建成功，则会在pki/private/目录下创建ca.key私钥文件以及pki/目录下创建ca.crt证书文件。\n3. 创建服务器证书 1 ./easyrsa build-server-full server nopass 创建过程和根证书创建类似，需要输入证书的密码以及上一个步骤创建根证书的密码。 如果创建成功，则会在pki/private目录创建server.key私钥文件。在pki/issued目录创建server.crt证书文件。\n4. 创建dh证书 1 ./easyrsa gen-dh DH parameters of size 2048 created at /usr/local/easy-rsa-master/easyrsa3/pki/dh.pem\n5. 创建客户端证书 ./easyrsa build-client-full openwrt nopass # openwrt是用户名，也可以输入其他的, nopass表示不需要密码 创建过程要输入ca证书的密码（第2步的密码）\n6. server端的文件，复制到/etc/openvpn/下 1 2 3 4 5 cd /etc/openvpn/ cp /etc/openvpn/easy-rsa/easyrsa3/pki/ca.crt . cp /etc/openvpn/easy-rsa/easyrsa3/pki/issued/server.crt . cp /etc/openvpn/easy-rsa/easyrsa3/pki/private/server.key . cp /etc/openvpn/easy-rsa/easyrsa3/pki/dh.pem ./dh2048.pem 7. client端的文件, 然后下载 (放到一个可以下载的地方，注意权限) 1 2 3 4 5 6 7 cd /data/wwwroot/default/ mkdir client cp /etc/openvpn/easy-rsa/easyrsa3/pki/ca.crt ./client/ cp /etc/openvpn/easy-rsa/easyrsa3/pki/issued/openwrt.crt ./client/ cp /etc/openvpn/easy-rsa/easyrsa3/pki/private/openwrt.key ./client/ chown -R www.www client chmod 644 ./client/* 我这里是放在服务器网站目录下，如果有其他选择，可以选择其他方便进行下载的地方\n#05 配置OpenVPN Server 1 cd /etc/openvpn 1. 将server配置模板复制到/etc/openvpn下 1 cp /usr/share/doc/openvpn-2.4.1/sample/sample-config-files/server.conf . 2. 编辑server.conf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 vi server.conf local xxx.xxx.xxx.xxx # 这里填服务器的外网ip port 1194 proto udp dev tun ca ca.crt cert server.crt key server.key dh dh2048.pem server 10.8.0.0 255.255.255.0 ifconfig-pool-persist ipp.txt client-config-dir ccd route 10.20.208.0 255.255.255.0 # 学校内网网段 route 192.168.1.0 255.255.255.0 # 路由器网段 client-to-client keepalive 10 120 ;tls-auth ta.key 0 cipher AES-256-CBC # 加密方式 comp-lzo ;user nobody ;group nobody persist-key persist-tun status openvpn-status.log verb 3 explicit-exit-notify 1 #06 配置ccd文件夹 1 2 3 4 5 6 7 cd /etc/openvpn mkdir ccd cd ccd vi openwrt # 填你的生成的客户端证书名 iroute 10.20.208.0 255.255.255.0 # 学校内网网段 iroute 192.168.1.0 255.255.255.0 # 路由器网段 ifconfig-push 10.8.0.2 10.8.0.1 #07 iptables 设置（根据实际情况） 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 yum install -y iptables-services systemctl enable iptables systemctl stop firewalld systemctl start iptables iptables -L -n iptables -P INPUT ACCEPT iptables -F iptables -X iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT iptables -A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j ACCEPT iptables -A INPUT -p tcp --dport 21 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j ACCEPT iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp --dport 3306 -j ACCEPT iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o tun+ -j MASQUERADE iptables -t nat -A POSTROUTING -s 10.20.208.0/24 -o tun+ -j MASQUERADE iptables -A FORWARD -d 10.20.208.0/24 -o tun+ -j ACCEPT service iptables save service iptables restart #08 服务器设置 1. 设置允许转发 1 2 3 vi /etc/sysctl.conf net.ipv4.ip_forward = 1 sysctl -p # 立即生效 2. 设置openvpn开机启动 1 2 3 systemctl -f enable openvpn@server.service systemctl start openvpn@server.service systemctl restart openvpn@server.service #09 下载证书 总共有三个文件\nca.crt openwrt.crt openwrt.key #10 客户端设置(Pandorabox, Openwrt 17.01) 1. 安装openvpn以及luci web配置界面还有中文 1 2 3 4 opkg update opkg install openvpn-openssl opkg install luci-app-openvpn opkg install luci-i18n-openvpn-zh-cn 2. 设置允许转发 默认情况下，防火墙是禁止转发的。 所以登录路由器的web设置界面luci, 找到网络-防火墙-一般设置， 可以看到转发是拒绝的，把转发设置成接受，然后点击保存并应用。\n#11 测试OpenVPN(客户端)能否正常运行（此步可以省略） 1. 预先编辑好文件client.conf，然后传到/tmp目录，文件内容如下： 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 client ;dev tap dev tun ;dev-node MyTap ;proto tcp proto udp remote x.x.x.x 1194 # 这里修改为你的服务器ip ;remote my-server-2 1194 ;remote-random resolv-retry infinite nobind ;user nobody ;group nobody persist-key persist-tun ;http-proxy-retry # retry on connection failures ;http-proxy [proxy server] [proxy port #] ;mute-replay-warnings ca ca.crt cert openwrt.crt key openwrt.key remote-cert-tls server ;tls-auth ta.key 1 cipher AES-256-CBC comp-lzo verb 3 ;mute 20 2. 测试运行，如果不成功会有提示 1 openvpn --config client.conf 3. 在服务器端输入以下命令测试 1 2 3 ping 10.8.0.2 -c 4 ping 192.168.1.1 -c 4 ping 10.20.208.12 -c 4 如果能够正常运行，说明客户端和配置都没有问题\n#11 配置OpenVPN Client 1. 使用scp，把证书传到/etc/luci-uploads/文件夹(没有就创建), 传好后目录内容如下： 1 2 3 /etc/luci-uploads/ca.crt /etc/luci-uploads/openwrt.crt /etc/luci-uploads/openwrt.key 2. 登录ssh，编辑openvpn配置文件 1 vi /etc/config/openvpn 把以下内容添加到最后面，并保存\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 config openvpn \u0026#39;openwrt\u0026#39; option enabled \u0026#39;1\u0026#39; option float \u0026#39;1\u0026#39; option client \u0026#39;1\u0026#39; option nobind \u0026#39;1\u0026#39; option comp_lzo \u0026#39;yes\u0026#39; option reneg_sec \u0026#39;0\u0026#39; option dev \u0026#39;tun\u0026#39; option verb \u0026#39;3\u0026#39; option persist_tun \u0026#39;1\u0026#39; option persist_key \u0026#39;1\u0026#39; option remote_cert_tls \u0026#39;server\u0026#39; list remote \u0026#39;x.x.x.x 1194\u0026#39; # 此处要设置成服务器的地址 option ca \u0026#39;/etc/luci-uploads/ca.crt\u0026#39; option cert \u0026#39;/etc/luci-uploads/openwrt.crt\u0026#39; option key \u0026#39;/etc/luci-uploads/openwrt.key\u0026#39; option resolv_retry \u0026#39;infinite\u0026#39; option keepalive \u0026#39;10 120\u0026#39; option log \u0026#39;/tmp/openvpn.log\u0026#39; option log_append \u0026#39;/tmp/openvpn.log\u0026#39; option cipher \u0026#39;AES-256-CBC\u0026#39; #12 启动Openvpn Client 登录openwrt的Web设置界面，找到openvpn，点击启用，然后再点start就可以了。 如果有配置问题，可以输入命令查看日志\n1 cat /tmp/openvpn.log 到此，服务端和客户端都已经搭建完成。如果有需要可以再测试一下连通。\n#13 参考文章 ECS服务器OPENVPN搭建，方便管理所有内网服务器 阿里云CentOS服务器上搭建openvpn 通过OpenWrt路由器和OpenVPN实现两地局域网互联\n","permalink":"https://www.zeyes.org/posts/tech/openvpn-nat/","summary":"\u003cblockquote\u003e\n\u003cp\u003e目标： 搭建完成之后，可以在服务器(外网)上直接输入校园网(内网)地址进行访问。\n\u003cimg loading=\"lazy\" src=\"/assets/tech/2017-04-22/OpenVPN.png\"\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"00-前言\"\u003e#00 前言\u003c/h3\u003e\n\u003cp\u003e最近对穿透学校的内网有需求，于是决定搭建环境。\n网上的教程大多都是客户端访问服务器的内网，\n但这个教程是，穿透客户端所在内网，由服务器访问客户端的内网。\u003c/p\u003e","title":"使用OpenVPN对校园网内网穿透"},{"content":"wxStaticText类 1 #include \u0026lt;wx/stattext.h\u0026gt; 静态文本控件，显示一行或者多行只读文本。 wxStaticText控件支持三种典型的文本对齐。\n样式(Styles) 这个类支持以下样式：\nwxALIGN_LEFT： 文本向左对齐。 wxALIGN_RIGHT： 文本向右对齐。 wxALIGN_CENTRE_HORIZONTAL： 文本水平居中。 wxST_NO_AUTORESIZE： 默认情况下，当调用SetLabel()时，控件将调整到适合放下文本的大小。如果给出这个样式标志，控件就不会改变它的大小（这个样式对于具有wxALIGN_RIGHT或wxALIGN_CENTRE_HORIZONTAL样式的控件特别有用，因为否则在调用SetLabel()之后它们将不再有意义了。 wxST_ELLIPSIZE_START： 如果标签文本宽度超过控件宽度，则用省略号替换标签的开头部分；此参数调用wxControl::Ellipsize。 wxST_ELLIPSIZE_MIDDLE： 如果标签文本宽度超过控件宽度，则用省略号替换标签的中间部分；此参数调用wxControl::Ellipsize。 wxST_ELLIPSIZE_END： 如果标签文本宽度超过控件宽度，则用省略号替换标签的末尾部分；此参数调用wxControl::Ellipsize。 公开成员函数（Public Member Functions) wxStaticText() 默认构造函数\nbool Create (wxWindow *parent, wxWindowID id, const wxString \u0026amp;label, const wxPoint \u0026amp;pos=wxDefaultPosition, const wxSize \u0026amp;size=wxDefaultSize, long style=0, const wxString \u0026amp;name=wxStaticTextNameStr) 构造函数，创建和显示文本控件。\nbool Create (wxWindow *parent, wxWindowID id, const wxString \u0026amp;label, const wxPoint \u0026amp;pos=wxDefaultPosition, const wxSize \u0026amp;size=wxDefaultSize, long style=0, const wxString \u0026amp;name=wxStaticTextNameStr) 生成功能，用于两步骤构建。\nbool IsEllipsized () const 如果此控件的窗口样式包含wxST_ELLIPSIZE_START，wxST_ELLIPSIZE_MIDDLE或wxST_ELLIPSIZE_END样式之一，则返回true。\nvoid Wrap (int width) 这个函数折叠控件标签内容，以使其每条线最多宽度为像素宽（如果可能的话，这些线在字边界处断开，因此如果单词太长，则可能不是这样）。\nExample 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 两步创建模式 wxStaticText* StaticText1 = new wxStaticText(); StaticText1-\u0026gt;Create(this, STATIC_TEXT_1, \u0026#34;这是一个测试\u0026#34;, wxDefaultPosition, wxDefaultSize); // 单步步创建模式 wxStaticText* StaticText2 = new wxStaticText(this, STATIC_TEXT_2, \u0026#34;test\u0026#34;, wxDefaultPosition, wxDefaultSize); StaticText1-\u0026gt;SetLabel(\u0026#34;Abc-test\u0026#34;); // 设置标签 wxString str = StaticText1-\u0026gt;GetLabelText(); // 获取标签内容 StaticText1-\u0026gt;SetForegroundColour(*wxRED); // 设置文本颜色 StaticText1-\u0026gt;SetBackgroundColour(*wxWHITE); // 设置背景颜色 wxSize tsize = StaticText1-\u0026gt;GetSizeFromTextSize(100, 50); // 获取文本最佳大小 StaticText2-\u0026gt;SetLabelText(str); StaticText2-\u0026gt;SetSize(tsize); // 设置文本框大小 ","permalink":"https://www.zeyes.org/posts/tech/wx-static-text-learn/","summary":"\u003ch2 id=\"wxstatictext类\"\u003ewxStaticText类\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#include\u003c/span\u003e \u003cspan class=\"cpf\"\u003e\u0026lt;wx/stattext.h\u0026gt;\u003c/span\u003e\u003cspan class=\"cp\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e静态文本控件，显示一行或者多行只读文本。\nwxStaticText控件支持三种典型的文本对齐。\u003c/p\u003e\n\u003ch3 id=\"样式styles\"\u003e样式(Styles)\u003c/h3\u003e\n\u003cp\u003e这个类支持以下样式：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003ewxALIGN_LEFT\u003c/strong\u003e： 文本向左对齐。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ewxALIGN_RIGHT\u003c/strong\u003e： 文本向右对齐。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ewxALIGN_CENTRE_HORIZONTAL\u003c/strong\u003e： 文本水平居中。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ewxST_NO_AUTORESIZE\u003c/strong\u003e： 默认情况下，当调用SetLabel()时，控件将调整到适合放下文本的大小。如果给出这个样式标志，控件就不会改变它的大小（这个样式对于具有wxALIGN_RIGHT或wxALIGN_CENTRE_HORIZONTAL样式的控件特别有用，因为否则在调用SetLabel()之后它们将不再有意义了。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ewxST_ELLIPSIZE_START\u003c/strong\u003e： 如果标签文本宽度超过控件宽度，则用省略号替换标签的开头部分；此参数调用wxControl::Ellipsize。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ewxST_ELLIPSIZE_MIDDLE\u003c/strong\u003e： 如果标签文本宽度超过控件宽度，则用省略号替换标签的中间部分；此参数调用wxControl::Ellipsize。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ewxST_ELLIPSIZE_END\u003c/strong\u003e： 如果标签文本宽度超过控件宽度，则用省略号替换标签的末尾部分；此参数调用wxControl::Ellipsize。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"公开成员函数public-member-functions\"\u003e公开成员函数（Public Member Functions)\u003c/h3\u003e\n\u003cp\u003ewxStaticText()\n默认构造函数\u003c/p\u003e","title":"wxWidgets学习 - wxStaticText类"},{"content":"CodeBlocks在Ubuntu 14.04下会出现与原生的ibus有冲突，解决的办法很简单，装fcitx就行了。安装完之后记得装拼音输入法，这个很重要。\n在Ubuntu 16.04下就比较简单了。\n传统流程是这样的， 按快捷键Ctrl + Alt + T打开命令行终端：\n1 2 sudo apt-get update sudo apt-get install codeblocks 然后直接在任务栏搜索框上搜索CodeBlocks就能打开使用。\n上面安装的版本一般是13.12算是比较老的版本了，而现在最新的稳定版是16.01。\nCodeBlocks 13.12也不是不能用，当然我也只是用了几分钟，就发现了在有激活项目的时候，也就是在写代码的时候，打开Setting的Editer会卡死。试了几次都是这样，无解。而且，还有一个致命的问题，不能自动缩进。当然这个不能缩进不是bug，只是我们没装全功能。\n于是找了下，找到了解决问题的办法，在这个网址：https://launchpad.net/~damien-moore/+archive/ubuntu/codeblocks-stable （这个网址可以在CodeBlocks官网的Dowload页面找到）\n首先将软件源添加进来，就是运行以下命令\n1 2 sudo add-apt-repository ppa:damien-moore/codeblocks-stable sudo apt-get update 完成之后，不管是不是已经安装了CodeBocks，使用命令\n1 sudo apt-get install codeblocks 都可以获取最新版本的CodeBlcoks。\n获取完成了之后，这个CodeBlocks还是阉割版的。\n还需运行以下 命令来把常用的插件装上\n1 sudo apt-get install codeblocks-contrib 然后，重启CodeBlocks就行了。\n以下是我个人的喜好：\n在CodeBlocks菜单Setting-\u0026gt;Edit-\u0026gt;General settings中可以取消设置Brace completion，这个选项是把括号自动补全取消，括号还是自己打才爽啊。\n另外，使用一款好看的字体也是必须的，我个人喜欢Monaco，确实好看。\nMoncao下载链接：https://github.com/todylu/monaco.ttf/blob/master/monaco.ttf?raw=true\n另外需要注意的是，直接下载双击就能安装。如果没有不能（我就遇到了，直接崩溃，什么字体都装不了），就升级一下，输入命令 sudo apt-get upgrade 升级下软件就能打开了。\n参考博客： codeblocks缩进等设置 ubuntu16.04安装monaco字体\n","permalink":"https://www.zeyes.org/posts/tech/ubuntu-16-04-codeblocks/","summary":"\u003cp\u003eCodeBlocks在Ubuntu 14.04下会出现与原生的ibus有冲突，解决的办法很简单，装fcitx就行了。安装完之后记得装拼音输入法，这个很重要。\u003c/p\u003e\n\u003cp\u003e在Ubuntu 16.04下就比较简单了。\u003c/p\u003e\n","title":"Ubuntu 16.04 安装CodeBlocks"},{"content":"一入编程深似海，真系坑爹。\n高考完了，爽吧。确实爽，有接近三个月的暑假。高中三年，无数次幻想这个暑假，充斥着自由、疯狂与青春。也做过无数计划。但最终，赶不上变化。\n假期一有空就疯狂地写代码，写累了就睡觉，醒来又写，C++果然是学不完的语言。\n很多人说，MFC已经是十几年前的玩意，已经不值得学。然而，我还是去了解了下。相比WIN32 API编程，便捷了不少，但是代码也很乱，而VC6这个落后于时代的IDE自身也有不少毛病，经常各种无理由错误。MFC确实是对API简单包装，很多函数就少了个句柄，因为MFC内部维护。Qt很优雅，也确实庞大。其实一开始是看上Qt的，也是由于生成的体积过大，转MFC，没想到也是巨坑。又要转回Qt了，当然MFC也没白学，至少简单了解了下。\n李桃大师曾言：如果你不知道要去哪里，通常哪里都去不了。确实如此，在假期有段时间烦躁无比，明明知道自己很多不会，很多想学，却还是无从下手。我不知道目标在哪，不知道自己下一步想做什么。通常，这个时候是最苦恼的。因为什么都想学，贪多嚼不烂。学着别人，听了几首轻音乐，看看天空，放松心情，然后找出最喜欢的，学下去。然后烦恼就此解除。\n我也不是代码狂人，天天看代码也确实会腻，每个星期都有不想写代码的7天。当把《汇编语言》看完时，整个人都疯了，汇编简直变态。汇编是非常底层的语言，用汇编写代码软件效率提上了，但写代码的效率不是那么理想。幸好是写小程序，不然彻底抓狂。在今天有C语言给我们用简直幸福，感谢D.M.Ritchie，感谢世界，Hello World！\n假期复习了C Primer Plus，复习了C++ Primer Plus，也看了C陷阱和缺陷。((void()())0)();这句最为印象深刻，一眼看去高大上（咳咳，其实可用于装逼）。然后整个人就叼了起来，偶尔用来吓吓新手。\n虽然假期学了不少，但始终时间不够用，有时晚上直接搞到凌晨。还有非常非常多的东西没学，有些至少连入门都没有做到。路途非常遥远，也将很艰辛，但无所畏惧。有句话说得很好，既然选择了远方，便只顾风雨兼程，既然目标是地平线，留给世界的只能是背影。\n编程方面就这么多。\n大学开学之际，写点东西。至逝去的三个月，至学生时代最长假期。\n——2015.09.05 于家中\n博主简介： 本人擅长 Ai、Fw、Fl、Br、Ae、Pr、Id、Ps 等软件的安装与卸载，精通 CSS、JavaScript、PHP、ASP、C、C++、C#、Java、Ruby、Perl、Lisp、Python、Objective-C、ActionScript、Pascal 等单词的拼写，熟悉 Windows、Linux、OS X、Android、iOS、WP8 等系统的开关机。\n","permalink":"https://www.zeyes.org/posts/essay/before-college/","summary":"\u003cp\u003e一入编程深似海，真系坑爹。\u003c/p\u003e\n\u003cp\u003e高考完了，爽吧。确实爽，有接近三个月的暑假。高中三年，无数次幻想这个暑假，充斥着自由、疯狂与青春。也做过无数计划。但最终，赶不上变化。\u003c/p\u003e\n\u003cp\u003e假期一有空就疯狂地写代码，写累了就睡觉，醒来又写，C++果然是学不完的语言。\u003c/p\u003e\n","title":"献给世界"},{"content":"Let the Balloon Rise Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 87994 Accepted Submission(s): 33308\nProblem Description Contest time again! How excited it is to see balloons floating around. But to tell you a secret, the judges\u0026rsquo; favorite time is guessing the most popular problem. When the contest is over, they will count the balloons of each color and find the result.\nThis year, they decide to leave this lovely job to you.\nInput Input contains multiple test cases. Each test case starts with a number N (0 \u0026lt; N \u0026lt;= 1000) \u0026ndash; the total number of balloons distributed. The next N lines contain one color each. The color of a balloon is a string of up to 15 lower-case letters.\nA test case with N = 0 terminates the input and this test case is not to be processed.\nOutput For each case, print the color of balloon for the most popular problem on a single line. It is guaranteed that there is a unique solution for each test case.\nSample Input 1 2 3 4 5 6 7 8 9 10 11 5 green red blue red red 3 pink orange pink 0 Sample Output ··· red pink ···\nIdea 给出各种颜色（字符串），求颜色相同最多的。 原题地址：http://acm.hdu.edu.cn/showproblem.php?pid=1004\nCode 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; #define MAXN 1005 int main(void) { int n; int i, j, max; int a[MAXN]; char c[MAXN][16]; //freopen(\u0026#34;input.txt\u0026#34;, \u0026#34;r\u0026#34;, stdin); scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); while(n \u0026gt; 0) { memset(a, 0, sizeof(int) * MAXN); for(i = 0; i \u0026lt; n; i++) { scanf(\u0026#34;%s\u0026#34;, c[i]); ++a[i]; for(j = 0; j \u0026lt; i; j++) { if(strcmp(c[i], c[j]) == 0) { ++a[j]; break; } } } max = 0; for(i = 0; i \u0026lt; n; i++) { if(a[max] \u0026lt; a[i]) max = i; } printf(\u0026#34;%s\\n\u0026#34;, c[max]); scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); } return 0; } ","permalink":"https://www.zeyes.org/posts/tech/hdu-1004/","summary":"\u003ch2 id=\"let-the-balloon-rise\"\u003eLet the Balloon Rise\u003c/h2\u003e\n\u003cp\u003eTime Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)\nTotal Submission(s): 87994 Accepted Submission(s): 33308\u003c/p\u003e\n\u003ch3 id=\"problem-description\"\u003eProblem Description\u003c/h3\u003e\n\u003cp\u003eContest time again! How excited it is to see balloons floating around. But to tell you a secret, the judges\u0026rsquo; favorite time is guessing the most popular problem. When the contest is over, they will count the balloons of each color and find the result.\u003c/p\u003e\n\u003cp\u003eThis year, they decide to leave this lovely job to you.\u003c/p\u003e","title":"HDU 1004 Let the Balloon Rise"},{"content":"Max Sum Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 173855 Accepted Submission(s): 40493\nProblem Description Given a sequence a[1],a[2],a[3]\u0026hellip;\u0026hellip;a[n], your job is to calculate the max sum of a sub-sequence. For example, given (6,-1,5,4,-7), the max sum in this sequence is 6 + (-1) + 5 + 4 = 14.\nInput The first line of the input contains an integer T(1\u0026lt;=T\u0026lt;=20) which means the number of test cases. Then T lines follow, each line starts with a number N(1\u0026lt;=N\u0026lt;=100000), then N integers followed(all the integers are between -1000 and 1000).\nOutput For each test case, you should output two lines. The first line is \u0026ldquo;Case #:\u0026rdquo;, # means the number of the test case. The second line contains three integers, the Max Sum in the sequence, the start position of the sub-sequence, the end position of the sub-sequence. If there are more than one result, output the first one. Output a blank line between two cases.\nSample Input 1 2 3 2 5 6 -1 5 4 -7 7 0 6 -1 1 -6 7 -5 Sample Output 1 2 3 4 5 Case 1: 14 1 4 Case 2: 7 1 6 Idea 求最大子数组，要注意的是全是0和负数的情况（被坑了一下） 原题地址：http://acm.hdu.edu.cn/showproblem.php?pid=1003\nCode 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include \u0026lt;stdio.h\u0026gt; #define MAXN 100005 typedef struct{ int s, l, r; }Sum; Sum MaxSub(int a[], int n); int main(void) { int ar[MAXN], c, n, i, j; Sum x = {0, 0, 0}; //freopen(\u0026#34;input.txt\u0026#34;, \u0026#34;r\u0026#34;, stdin); //freopen(\u0026#34;output.txt\u0026#34;, \u0026#34;w\u0026#34;, stdout); scanf(\u0026#34;%d\u0026#34;, \u0026amp;c); for(i = 1; i \u0026lt;= c; i++) { scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); for(j = 1; j \u0026lt;= n; j++) scanf(\u0026#34;%d\u0026#34;, \u0026amp;ar[j]); x = MaxSub(ar, n); printf(\u0026#34;Case %d:\\n%d %d %d\u0026#34;, i,x.s, x.l, x.r); if(i != c) printf(\u0026#34;\\n\\n\u0026#34;); else printf(\u0026#34;\\n\u0026#34;); } } Sum MaxSub(int a[], int n) { int i, s = 0, l = 1, r = 1, b; Sum sum = {a[1], 0, 0}; for(i = 1; i \u0026lt;= n; i++) { if(s \u0026gt;= 0) { s += a[i]; if(a[i] \u0026gt; 0) r = i; } else { s = a[i]; r = l = i; } if(s \u0026gt;= sum.s) { sum.s = s; sum.l = l; sum.r = r; } } return sum; } ","permalink":"https://www.zeyes.org/posts/tech/hdu-1003/","summary":"\u003ch2 id=\"max-sum\"\u003eMax Sum\u003c/h2\u003e\n\u003cp\u003e\u003cem\u003eTime Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)\nTotal Submission(s): 173855 Accepted Submission(s): 40493\u003c/em\u003e\u003c/p\u003e\n\u003ch3 id=\"problem-description\"\u003eProblem Description\u003c/h3\u003e\n\u003cp\u003eGiven a sequence a[1],a[2],a[3]\u0026hellip;\u0026hellip;a[n], your job is to calculate the max sum of a sub-sequence. For example, given (6,-1,5,4,-7), the max sum in this sequence is 6 + (-1) + 5 + 4 = 14.\u003c/p\u003e\n\u003ch3 id=\"input\"\u003eInput\u003c/h3\u003e\n\u003cp\u003eThe first line of the input contains an integer T(1\u0026lt;=T\u0026lt;=20) which means the number of test cases. Then T lines follow, each line starts with a number N(1\u0026lt;=N\u0026lt;=100000), then N integers followed(all the integers are between -1000 and 1000).\u003c/p\u003e","title":"HDU 1003 Max Sum"},{"content":"A + B Problem II Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 257918 Accepted Submission(s): 49865\nProblem Description I have a very simple problem for you. Given two integers A and B, your job is to calculate the Sum of A + B.\nInput The first line of the input contains an integer T(1\u0026lt;=T\u0026lt;=20) which means the number of test cases. Then T lines follow, each line consists of two positive integers, A and B. Notice that the integers are very large, that means you should not process them by using 32-bit integer. You may assume the length of each integer will not exceed 1000.\nOutput For each test case, you should output two lines. The first line is \u0026ldquo;Case #:\u0026rdquo;, # means the number of the test case. The second line is the an equation \u0026ldquo;A + B = Sum\u0026rdquo;, Sum means the result of A + B. Note there are some spaces int the equation. Output a blank line between two test cases.\nSample Input 1 2 3 2 1 2 112233445566778899 998877665544332211 Sample Output 1 2 3 4 5 Case 1: 1 + 2 = 3 Case 2: 112233445566778899 + 998877665544332211 = 1111111111111111110 Idea 原题传送门：http://acm.hdu.edu.cn/showproblem.php?pid=1002 新手必做题，简单的高精度加法，写了两个代码都AC了，第一个快些，第二个好理解些\nCode1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;string.h\u0026gt; #define MAX 1005 int main(void) { int n, i = 1, n1, n2, k, j, p, q, x, m; char a[MAX], b[MAX], c[MAX]; //freopen(\u0026#34;input.txt\u0026#34;, \u0026#34;r\u0026#34;, stdin); //freopen(\u0026#34;output.txt\u0026#34;,\u0026#34;w\u0026#34;, stdout); scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); while(n) { scanf(\u0026#34;%s %s\u0026#34;, a, b); printf(\u0026#34;Case %d:\\n%s + %s = \u0026#34;, i, a, b); n1 = strlen(a) - 1; n2 = strlen(b) - 1; k = x = 0; while( n1 \u0026gt;= 0 || n2 \u0026gt;= 0) { p = n1 \u0026gt;= 0 ? a[n1--] - \u0026#39;0\u0026#39; : 0; q = n2 \u0026gt;= 0 ? b[n2--] - \u0026#39;0\u0026#39; : 0; m = p + q + x; x = m / 10; m = m % 10; c[k++] = m + \u0026#39;0\u0026#39;; } for(j = k - 1; j \u0026gt;=0; j--) printf(\u0026#34;%c\u0026#34;, c[j]); i++; if(n != 1) printf(\u0026#34;\\n\\n\u0026#34;); else printf(\u0026#34;\\n\u0026#34;); n--; } return 0; } Code2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;string.h\u0026gt; #define MAX 1015 void Add(char *a, char *b, char *c); int main(void) { int n; int i; char s1[MAX], s2[MAX], s3[MAX]; //freopen(\u0026#34;input.txt\u0026#34;, \u0026#34;r\u0026#34;, stdin); //freopen(\u0026#34;output.txt\u0026#34;,\u0026#34;w\u0026#34;, stdout); scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); //while(getchar() != \u0026#39;\\n\u0026#39;) continue; for(i = 1; i \u0026lt;= n; i++) { scanf(\u0026#34;%s %s\u0026#34;, s1, s2); Add(s1, s2, s3); printf(\u0026#34;Case %d:\\n%s + %s = %s\u0026#34;, i, s1, s2, s3); if(i == n) printf(\u0026#34;\\n\u0026#34;); else printf(\u0026#34;\\n\\n\u0026#34;); } return 0; } void Add(char *a, char *b, char *c) { int i = strlen(a) - 1, j = strlen(b) - 1, k = 0; int x = 0, m = 0; char t[MAX]; int p, q; while( i \u0026gt;= 0 || j \u0026gt;= 0) { p = i \u0026gt;= 0 ? a[i--] - \u0026#39;0\u0026#39; : 0; q = j \u0026gt;= 0 ? b[j--] - \u0026#39;0\u0026#39; : 0; m = p + q + x; x = m / 10; m = m % 10; t[k++] = m + \u0026#39;0\u0026#39;; } for(i = 0; i \u0026lt; k; i++) c[i] = t[k - i - 1]; c[k] = \u0026#39;\\0\u0026#39;; } ","permalink":"https://www.zeyes.org/posts/tech/hdu-1002/","summary":"\u003ch2 id=\"a--b-problem-ii\"\u003eA + B Problem II\u003c/h2\u003e\n\u003cp\u003e\u003cem\u003eTime Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)\nTotal Submission(s): 257918 Accepted Submission(s): 49865\u003c/em\u003e\u003c/p\u003e\n\u003ch3 id=\"problem-description\"\u003eProblem Description\u003c/h3\u003e\n\u003cp\u003eI have a very simple problem for you. Given two integers A and B, your job is to calculate the Sum of A + B.\u003c/p\u003e\n\u003ch3 id=\"input\"\u003eInput\u003c/h3\u003e\n\u003cp\u003eThe first line of the input contains an integer T(1\u0026lt;=T\u0026lt;=20) which means the number of test cases. Then T lines follow, each line consists of two positive integers, A and B. Notice that the integers are very large, that means you should not process them by using 32-bit integer. You may assume the length of each integer will not exceed 1000.\u003c/p\u003e","title":"HDU 1002 A + B Problem II"},{"content":" 归并排序是稳定排序的一种，之所以说它稳定是因为，两个相等的数排序之后不会调换位置。（当然这个是比较业余的说法，如果想得到准确答案，问度娘。）归并排序的时间复杂度为O(nlgn)，同时归并排序做较少改动就可以求逆序对，只需改动最后一个for循环就可以。\n算法原理 1.二路归并排序是将两个已经有序的数组重新组合到一个有序的数组。 递归部分： 2.因为一个随机的数组分成两个数组（假设A和B）之后一般（A和B）不会是有序的，所以递归求解。 3.直到最后一个元素（一个元素是不用排序的），一个元素看成一个数组是有序的，所以递归返回。 非递归部分： 4.申请两个临时数组，用来存放分开的两组数据。 5.两个临时数组比较，哪边小就从哪边抽出元素放回原数组。（这里指的是升序排序） 6.直到没有。（为了少写代码，设置两个无穷大的数在数组的最后，效果自己模拟下看看）\n代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;limits.h\u0026gt; /*INT_MAX支持*/ #define MAXN 100 #define MAX INT_MAX void MergeSort(int a[], int p, int r); void Merge(int a[], int p, int q, int r); int main(void) { int i, n; int ar[MAXN]; freopen(\u0026#34;input.txt\u0026#34;, \u0026#34;r\u0026#34;, stdin); /*重定向文件到标准输入输出*/ freopen(\u0026#34;output.txt\u0026#34;, \u0026#34;w\u0026#34;, stdout); scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); /*读取数据个数*/ for(i = 1; i \u0026lt;= n; i++) /*读取数据*/ { scanf(\u0026#34;%d\u0026#34;, \u0026amp;ar[i]); } MergeSort(ar, 1, n); /*调用归并排序*/ for(i = 1; i \u0026lt;= n; i++) /*输出数据*/ { printf(\u0026#34;%d \u0026#34;, ar[i]); if(i % 10 == 0) /*每行10个*/ putchar(\u0026#39;\\n\u0026#39;); } return 0; } void MergeSort(int a[], int p, int r) { int q; if(p \u0026lt; r) { q = (p + r) / 2; /*分治策略*/ MergeSort(a, p, q); /*递归 不断将数组分成两部分，直到没法分*/ MergeSort(a, q + 1, r); Merge(a, p, q, r); } } void Merge(int a[], int p, int q, int r) { int i, j, k; int m[q - p + 2]; /*变长数组(VLA)*/ int n[r - q + 1]; /*C99特性：GCC编译需加-std=c99*/ for(i = 0; i \u0026lt; q - p + 1; i++) /*将前a[p...q]复制到临时数组*/ m[i] = a[p + i]; for(j = 0; j \u0026lt; r - q; j++) /*将前a[q+1...r]复制到临时数组*/ n[j] = a[q + 1 + j]; m[i] = n[j] = MAX; /*定义为无穷大的数*/ i = j = 0; for( k = p; k \u0026lt;= r; k++) /*两组已经有序的数据开始排序，合成一组*/ { if(m[i] \u0026gt; n[j]) a[k] = n[j++]; else a[k] = m[i++]; } } 测试数据 输入 1 2 3 4 5 6 50 77 30 48 66 25 86 84 56 27 10 58 64 4 47 2 41 27 88 90 97 73 71 81 91 16 26 37 87 93 21 88 41 58 26 7 12 62 96 78 16 83 41 18 6 6 60 16 87 9 74 输出 1 2 3 4 5 2 4 6 6 7 9 10 12 16 16 16 18 21 25 26 26 27 27 30 37 41 41 41 47 48 56 58 58 60 62 64 66 71 73 74 77 78 81 83 84 86 87 87 88 88 90 91 93 96 97 ","permalink":"https://www.zeyes.org/posts/tech/c-merge-sort-learn/","summary":"\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e归并排序\u003c/strong\u003e是稳定排序的一种，之所以说它稳定是因为，两个相等的数排序之后不会调换位置。（\u003cem\u003e当然这个是比较业余的说法，如果想得到准确答案，问度娘。\u003c/em\u003e）归并排序的时间复杂度为\u003cstrong\u003eO(nlgn)\u003c/strong\u003e，同时归并排序做较少改动就可以求逆序对，只需改动最后一个for循环就可以。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"算法原理\"\u003e算法原理\u003c/h3\u003e\n\u003cp\u003e1.二路归并排序是将两个已经有序的数组重新组合到一个有序的数组。\n递归部分：\n2.因为一个随机的数组分成两个数组（假设A和B）之后一般（A和B）不会是有序的，所以递归求解。\n3.直到最后一个元素（一个元素是不用排序的），一个元素看成一个数组是有序的，所以递归返回。\n非递归部分：\n4.申请两个临时数组，用来存放分开的两组数据。\n5.两个临时数组比较，哪边小就从哪边抽出元素放回原数组。（这里指的是升序排序）\n6.直到没有。（为了少写代码，设置两个无穷大的数在数组的最后，效果自己模拟下看看）\u003c/p\u003e\n","title":"归并排序(Merge Sort)"},{"content":"Brainman Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 8755 Accepted: 4723\nDescription Background Raymond Babbitt drives his brother Charlie mad. Recently Raymond counted 246 toothpicks spilled all over the floor in an instant just by glancing at them. And he can even count Poker cards. Charlie would love to be able to do cool things like that, too. He wants to beat his brother in a similar task.\nProblem Here\u0026rsquo;s what Charlie thinks of. Imagine you get a sequence of N numbers. The goal is to move the numbers around so that at the end the sequence is ordered. The only operation allowed is to swap two adjacent numbers. Let us try an example: Start with: 2 8 0 3 swap (2 8) 8 2 0 3 swap (2 0) 8 0 2 3 swap (2 3) 8 0 3 2 swap (8 0) 0 8 3 2 swap (8 3) 0 3 8 2 swap (8 2) 0 3 2 8 swap (3 2) 0 2 3 8 swap (3 8) 0 2 8 3 swap (8 3) 0 2 3 8\nSo the sequence (2 8 0 3) can be sorted with nine swaps of adjacent numbers. However, it is even possible to sort it with three such swaps: Start with: 2 8 0 3 swap (8 0) 2 0 8 3 swap (2 0) 0 2 8 3 swap (8 3) 0 2 3 8\nThe question is: What is the minimum number of swaps of adjacent numbers to sort a given sequence?Since Charlie does not have Raymond\u0026rsquo;s mental capabilities, he decides to cheat. Here is where you come into play. He asks you to write a computer program for him that answers the question. Rest assured he will pay a very good prize for it.\nInput The first line contains the number of scenarios. For every scenario, you are given a line containing first the length N (1 \u0026lt;= N \u0026lt;= 1000) of the sequence,followed by the N elements of the sequence (each element is an integer in [-1000000, 1000000]). All numbers in this line are separated by single blanks.\nOutput Start the output for every scenario with a line containing \u0026ldquo;Scenario #i:\u0026rdquo;, where i is the number of the scenario starting at 1. Then print a single line containing the minimal number of swaps of adjacent numbers that are necessary to sort the given sequence. Terminate the output for the scenario with a blank line.\nSample Input 1 2 3 4 5 4 4 2 8 0 3 10 0 1 2 3 4 5 6 7 8 9 6 -42 23 6 28 -100 65537 5 0 0 0 0 0 Sample Output 1 2 3 4 5 6 7 8 9 10 11 Scenario #1: 3 Scenario #2: 0 Scenario #3: 5 Scenario #4: 0 Idea 这题求逆序对，可以用改动归并排序来解决。POJ上的题目，原题地址：http://poj.org/problem?id=1804\nCode 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include \u0026lt;stdio.h\u0026gt; #define MAXN 1005 #define MAX 1000005 void MergeInversion(int a[], int p, int r); void Merge(int a[], int p, int q, int r); int ans; int main(void) { int i , j, n, x; int ar[MAXN]; //freopen(\u0026#34;input.txt\u0026#34;, \u0026#34;r\u0026#34;, stdin); scanf(\u0026#34;%d\u0026#34;, \u0026amp;x); for(i = 1; i \u0026lt;= x; i++) { ans = 0; scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); for(j = 1; j \u0026lt;= n; j++) scanf(\u0026#34;%d\u0026#34;, \u0026amp;ar[j]); MergeInversion(ar, 1, n); printf(\u0026#34;Scenario #%d:\\n%d\\n\\n\u0026#34;, i, ans); } } void MergeInversion(int a[], int p, int r) { int q; if(p \u0026lt; r) { q = (p + r) / 2; MergeInversion(a, p, q); MergeInversion(a, q + 1, r); Merge(a, p, q, r); } } void Merge(int a[], int p, int q, int r) { int i, j, k; int m[q - p + 2], n[r - q + 1]; for(i = 0; i \u0026lt; q - p + 1; i++) m[i] = a[p + i]; for(j = 0; j \u0026lt; r - q; j++) n[j] = a[q + j + 1]; m[i] = n[j] = MAX; i = j = 0; for( k = p; k \u0026lt;= r; k++) { if(m[i] \u0026lt;= n[j]) a[k] = m[i++]; else { a[k] = n[j++]; ans += q - (p + i) + 1; } } } ","permalink":"https://www.zeyes.org/posts/tech/poj-1804/","summary":"\u003ch1 id=\"brainman\"\u003eBrainman\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eTime Limit: 1000MS Memory Limit: 30000K\nTotal Submissions: 8755 Accepted: 4723\u003c/em\u003e\u003c/p\u003e\n\u003ch3 id=\"description\"\u003eDescription\u003c/h3\u003e\n\u003ch4 id=\"background\"\u003eBackground\u003c/h4\u003e\n\u003cp\u003eRaymond Babbitt drives his brother Charlie mad. Recently Raymond counted 246 toothpicks spilled all over the floor in an instant just by glancing at them. And he can even count Poker cards. Charlie would love to be able to do cool things like that, too. He wants to beat his brother in a similar task.\u003c/p\u003e\n\u003ch4 id=\"problem\"\u003eProblem\u003c/h4\u003e\n\u003cp\u003eHere\u0026rsquo;s what Charlie thinks of. Imagine you get a sequence of N numbers. The goal is to move the numbers around so that at the end the sequence is ordered. The only operation allowed is to swap two adjacent numbers. Let us try an example:\nStart with: 2 8 0 3\nswap (2 8) 8 2 0 3\nswap (2 0) 8 0 2 3\nswap (2 3) 8 0 3 2\nswap (8 0) 0 8 3 2\nswap (8 3) 0 3 8 2\nswap (8 2) 0 3 2 8\nswap (3 2) 0 2 3 8\nswap (3 8) 0 2 8 3\nswap (8 3) 0 2 3 8\u003c/p\u003e","title":"POJ 1804 Brainman"},{"content":" 选择排序是经典排序的一种，最差的时间复杂度为O(n^2)，它的主要原理是直接从待排序数组里选择一个最小(或最大)的数字,每次都拿一个最小数字出来，顺序放入新数组,直到全部拿完。\n代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include \u0026lt;stdio.h\u0026gt; #define MAX 100 void Select(int a[], int n); int main(void) { int ar[MAX], n; int i; freopen(\u0026#34;input.txt\u0026#34;, \u0026#34;r\u0026#34;, stdin); /*重定向标准输入输出*/ freopen(\u0026#34;output.txt\u0026#34;, \u0026#34;w\u0026#34;, stdout); scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); /*写入数据*/ for(i = 1; i \u0026lt;= n; i++) scanf(\u0026#34;%d\u0026#34;, \u0026amp;ar[i]); Select(ar, n); /*排序*/ for(i = 1; i \u0026lt;= n; i++) /*输出数据*/ { printf(\u0026#34;%d \u0026#34;, ar[i]); if(i % 10 == 0) putchar(\u0026#39;\\n\u0026#39;); } return 0; } void Select(int a[], int n) /*待排序数组a[1...n]*/ { int min, i, j, t; for(i = 1; i \u0026lt;= n; i++) { min = i; /*从a[i...n]选出最小值*/ for(j = i + 1; j \u0026lt;= n; j++) { if(a[min] \u0026gt; a[j]) min = j; } if(min != i) /*交换*/ { t = a[min]; a[min] = a[i]; a[i] = t; } } } 测试数据： 输入：\n1 2 3 4 5 6 50 13 95 29 62 69 34 74 38 29 78 29 67 98 22 27 13 92 26 94 98 28 62 2 27 23 92 87 96 11 93 25 94 6 15 35 63 61 88 80 5 39 47 36 35 26 83 39 77 25 61 输出：\n1 2 3 4 5 2 5 6 11 13 13 15 22 23 25 25 26 26 27 27 28 29 29 29 34 35 35 36 38 39 39 47 61 61 62 62 63 67 69 74 77 78 80 83 87 88 92 92 93 94 94 95 96 98 98 ","permalink":"https://www.zeyes.org/posts/tech/c-selection-sort-learn/","summary":"\u003cstrong\u003e选择排序\u003c/strong\u003e是经典排序的一种，最差的时间复杂度为\u003cstrong\u003eO(n^2\u003c/strong\u003e)，它的主要原理是直接从待排序数组里选择一个最小(或最大)的数字,每次都拿一个最小数字出来，顺序放入新数组,直到全部拿完。","title":"选择排序（Selection Sort）"},{"content":"插入排序，稳定排序的一种,平均时间复杂度为O(n^2),它的代码量很小，对于处理小数据的排序还是可以的。 排序扑克牌可以形象地描述插入排序（贴近生活），算法导论就是用它来引入主题的。\n算法描述如下（摘自百度百科）： ⒈ 从第一个元素开始，该元素可以认为已经被排序 ⒉ 取出下一个元素，在已经排序的元素序列中从后向前扫描 ⒊ 如果该元素（已排序）大于新元素，将该元素移到下一位置 ⒋ 重复步骤3，直到找到已排序的元素小于或者等于新元素的位置 ⒌ 将新元素插入到下一位置中 ⒍ 重复步骤2~5\n具体代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include \u0026lt;stdio.h\u0026gt; #define MAX 100 void Insert(int [], int); int main(void) { int i = 1; int n; int ar[MAX]; //freopen(\u0026#34;input.txt\u0026#34;, \u0026#34;r\u0026#34;, stdin);/*可以将标准输入输出重定向到文件*/ //freopen(\u0026#34;output.txt\u0026#34;,\u0026#34;w\u0026#34;, stdout); scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); /*读取待排序元素的个数*/ for(i = 1; i \u0026lt;= n; i++) { scanf(\u0026#34;%d\u0026#34;, \u0026amp;ar[i]); /*读取元素并写入数组中*/ } Insert(ar, n); /*排序*/ for(i = 1; i \u0026lt;= n; i++) { printf(\u0026#34;%d \u0026#34;, ar[i]); /*显示排序后的数据*/ if(i % 10 == 0) putchar(\u0026#39;\\n\u0026#39;); /*输出每10个元素一行*/ } return 0; } void Insert(int a[], int n) //a数组有效数据是下标1...n { int i, j; for(i = 2; i \u0026lt;= n; i++) { a[0] = a[i]; /*用a[0]作为临时保存a[i]的变量*/ j = i-1; while(a[0] \u0026lt; a[j]) /*从a[i - 1]开始到a[0]逐个检查，并将元素推后*/ a[j + 1] = a[j--]; a[j + 1] = a[0]; /*找到合适位置，将元素插入*/ } } /* void Insert(int a[], int n) //递归版本 { int i; if(n \u0026gt; 1) { Insert(a, n - 1); i = n - 1; a[0] = a[n]; while(a[0] \u0026lt; a[i]) a[i + 1] = a[i--]; a[i + 1] = a[0]; } } */ 测试数据: 输入：\n1 2 3 4 5 6 50 5 17 18 97 72 71 62 84 66 38 25 68 86 57 45 5 70 51 52 97 35 4 55 24 62 63 33 64 84 75 26 32 69 51 67 26 53 87 18 33 33 94 2 84 76 93 19 76 53 81 输出：\n1 2 3 4 5 2 4 5 5 17 18 18 19 24 25 26 26 32 33 33 33 35 38 45 51 51 52 53 53 55 57 62 62 63 64 66 67 68 69 70 71 72 75 76 76 81 84 84 84 86 87 93 94 97 97 ","permalink":"https://www.zeyes.org/posts/tech/c-insert-sort-learn/","summary":"\u003cstrong\u003e插入排序\u003c/strong\u003e，稳定排序的一种,\u003cstrong\u003e平均时间复杂度为O(n^2)\u003c/strong\u003e,它的代码量很小，对于处理小数据的排序还是可以的。\n排序扑克牌可以形象地描述插入排序（贴近生活），算法导论就是用它来引入主题的。","title":"插入排序的C语言实现"},{"content":" Kruskal算法求加权连通图的最小生成树的算法。kruskal算法总共选择n- 1条边，所使用的贪婪准则是：从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中。注意到所选取的边若产生环路则不可能形成一棵生成树。kruskal算法分e 步，其中e 是网络中边的数目。按耗费递增的顺序来考虑这e 条边，每次考虑一条边。当考虑某条边时，若将其加入到已选边的集合中会出现环路，则将其抛弃，否则，将它选入。\n首先，文章不是LZ写的，在网上看到比我写的更好的，直接拿过来了。 编写程序：对于如下一个带权无向图，给出所有边以及权值，用kruskal算法求最小生成树。 样例输入:\n1 2 3 4 5 6 7 8 9 10 11 12 11 A B 7 A D 5 B C 8 B D 9 B E 7 C E 5 D E 15 D F 6 E F 8 E G 9 F G 11 样例输出:\n1 2 3 4 5 6 7 A - D : 5 C - E : 5 D - F : 6 A - B : 7 B - E : 7 E - G : 9 Total:39 代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; #define MAX 100 /* 定义边(x,y)，权为w */ typedef struct { int x, y; int w; }edge; edge e[MAX]; /* rank[x]表示x的秩 */ int rank[MAX]; /* father[x]表示x的父节点 */ int father[MAX]; int sum; /* 比较函数，按权值(相同则按x坐标)非降序排序 */ int cmp(const void *a, const void *b) { if ((*(edge *)a).w == (*(edge *)b).w) { return (*(edge *)a).x - (*(edge *)b).x; } return (*(edge *)a).w - (*(edge *)b).w; } /* 初始化集合 */ void Make_Set(int x) { father[x] = x; rank[x] = 0; } /* 查找x元素所在的集合,回溯时压缩路径 */ int Find_Set(int x) { if (x != father[x]) { father[x] = Find_Set(father[x]); } return father[x]; } /* 合并x,y所在的集合 */ void Union(int x, int y, int w) { if (x == y) return; /* 将秩较小的树连接到秩较大的树后 */ if (rank[x] \u0026gt; rank[y]) { father[y] = x; } else { if (rank[x] == rank[y]) { rank[y]++; } father[x] = y; } sum += w; } /* 主函数 */ int main() { int i, n;. int x, y; char chx, chy; //freopen(\u0026#34;kruskal.in\u0026#34;, \u0026#34;r\u0026#34;,stdin); //freopen(\u0026#34;kruskal.out\u0026#34;, \u0026#34;w\u0026#34;, stdout); /* 读取边的数目 */ scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); getchar(); /* 读取边信息并初始化集合 */ for (i = 0; i \u0026lt; n; i++) { scanf(\u0026#34;%c %c %d\u0026#34;, \u0026amp;chx, \u0026amp;chy, \u0026amp;e[i].w); getchar(); e[i].x = chx - \u0026#39;A\u0026#39;; e[i].y = chy - \u0026#39;A\u0026#39;; Make_Set(i); } /* 将边排序 */ qsort(e, n, sizeof(edge), cmp); sum = 0; for (i = 0; i \u0026lt; n; i++) { x = Find_Set(e[i].x); y = Find_Set(e[i].y); if (x != y) { printf(\u0026#34;%c - %c : %d\\n\u0026#34;, e[i].x + \u0026#39;A\u0026#39;, e[i].y + \u0026#39;A\u0026#39;, e[i].w); Union(x, y, e[i].w); } } printf(\u0026#34;Total:%d\\n\u0026#34;, sum); //system(\u0026#34;pause\u0026#34;); return 0;\t} 文章作者：姜南(Slyar) 文章来源：Slyar Home\n","permalink":"https://www.zeyes.org/posts/tech/study-c-kruskal/","summary":"Kruskal算法求加权连通图的最小生成树的算法。kruskal算法总共选择n- 1条边，所使用的贪婪准则是：从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中。注意到所选取的边若产生环路则不可能形成一棵生成树。kruskal算法分e 步，其中e 是网络中边的数目。按耗费递增的顺序来考虑这e 条边，每次考虑一条边。当考虑某条边时，若将其加入到已选边的集合中会出现环路，则将其抛弃，否则，将它选入。","title":"Kruskal算法的C语言实现（并查集版）"},{"content":"下面有四条声明，const修饰的到底是哪个？是a是常量还是*a是常量？由于只是关键字调换下顺序，是非常容易搞混的。我来详细说下。\n1 2 3 4 const int * a = \u0026amp;b; int const * a = \u0026amp;b; int * const a = \u0026amp;b; const int * const a = \u0026amp;b; 区分它们是非常简单的，只需要下面记住两条规则：\n如果const位于星号的左侧，则const就是用来修饰指针所指向的变量，即指针指向为常量； 如果const位于星号的右侧，const就是修饰指针本身，即指针本身是常量。 于是乎，刚才的四条声明也就容易区分了。\n1 2 3 4 5 const int * a = \u0026amp;b; //const在*的左边 ,用来修饰a所指向的地址的值,*a为常量 int const * a = \u0026amp;b; //这句与上句是相同的，const在*的左边 int * const a = \u0026amp;b; //const在*的右边 那么const修饰的是a本身, a是常量 const int * const a = \u0026amp;b; //a与*a均为常量，都不能被改变 如果还是不明白，请上机测试下，分别给a和*a赋值，常量一般情况是不能被改变的（当然有特殊情况，以后再说）。 我这里再说下const与define、typedef，是否跟我们想象的那样。\n1 2 3 4 5 6 7 typedef int* pint; #define PINT int* int k = 5; const pint m = \u0026amp;k; const PINT n= \u0026amp;k; 我们用一个完整的程序（其实有错误，等下就知道了）来测试。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include \u0026lt;stdio.h\u0026gt; typedef int* pint; #define PINT int* int k = 5; const pint m = \u0026amp;k; const PINT n= \u0026amp;k; int main(void) { int b = 2; *m = 3; *n = 3; m = \u0026amp;b; n = \u0026amp;b; return 0; } 放上面的代码上去测试，我们看到编译出错了,位置是\n1 *n = 3; 很明显*n是不可改变的，我们自己也可以很容易推是我出来。\n1 2 3 4 #define PINT int* int k = 5; const PINT n= \u0026amp;k; //等价于const int * n = \u0026amp;k; //const PINT n= \u0026amp;k; 被预处理为const int * n = \u0026amp;k; 我们接着这把*n = 3;注释掉，再编译一次，发现又出错了，错误的地方是\n1 m = \u0026amp;b; 也就是说明指针m是常量，这倒是出乎意料了，\n1 2 3 4 typedef int* pint; int k = 5; const pint m = \u0026amp;k; const int* m = \u0026amp;k; //这两句竟然不是相等的！为什么呢？ 其实这里很多人把typedef当成了define。 typedef不是define，typedef是别名。 我们仔细看下pint, pint是指针类型，const的指针当然是int *const\n1 2 3 4 typedef int* pint; int k = 5; const pint m = \u0026amp;k; //相当于int* const m = \u0026amp;k; //typedef不是宏代换，ping是个指针类型，const的指针是int *const 好了，其实const关键字与指针、define、typedef混用也不是太麻烦嘛。\n","permalink":"https://www.zeyes.org/posts/tech/study-const-define-typedef/","summary":"\u003cp\u003e下面有四条声明，const修饰的到底是哪个？是a是常量还是*a是常量？由于只是关键字调换下顺序，是非常容易搞混的。我来详细说下。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e4\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"k\"\u003econst\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e区分它们是非常简单的，只需要下面记住两条规则：\u003c/p\u003e","title":"浅谈const关键字与指针、define、typedef混用"},{"content":" 普里姆算法（Prim算法），图论中的一种算法，可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中，不但包括了连通图里的所有顶点，且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克发现；并在1957年由美国计算机科学家罗伯特·普里姆独立发现；1959年，艾兹格·迪科斯彻再次发现了该算法。因此，在某些场合，普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆－亚尔尼克算法。\n以上摘自维基百科。我简略地说下算法思想（或许不大严谨，想要看专业严谨版的朋友自行百度）：\n1.在把生成树看成一个集合（开始集合为空，到各个结点的距离当然未知） 2.结点与集合之间的权值可以看成结点到集合距离 3.将第一个结点加入集合，并初始化集合与其他结点的距离 4.搜索集合与结点最小的权值（距离），并把这点加入集合 5.更新集合与结点之间的距离 6.不断重复4和5步，直到所有的结点都加入了集合 （实际上把一个结点加入集合的时候，可以记录这个结点的父节点，也就是前驱，这么说吧，当找到一个与集合最小的结点的时候，他与集合中哪一结点的距离最小，把他记录来，作为生成树的路径）\n算法实现如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include \u0026lt;stdio.h\u0026gt; #define MAXN 100 #define INF 100001 /*INF表示不存在边的长度，用一个很大的数表示它*/ void prim(int [][MAXN], int [], int); //函数原型 int main(void) { int i, j, t, n; int w[MAXN][MAXN], fa[MAXN]; /*w是邻接矩阵，fa[x]表示是结点x的父结点）*/ //freopen(\u0026#34;prim.in\u0026#34;, \u0026#34;r\u0026#34;, stdin); //打开文件 //freopen(\u0026#34;prim.out\u0026#34;, \u0026#34;w\u0026#34;, stdout); scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); for(i = 1; i \u0026lt;= n; i++) for(j = 1; j \u0026lt;= n; j++ ) { scanf(\u0026#34;%d\u0026#34;, \u0026amp;t); //数据读入 w[i][j] = (t == 0) ? INF : t; } prim(w, fa, n); //调用函数 for(i = 2; i \u0026lt;= n; i++) //打印结果 printf(\u0026#34;%d---\u0026gt;%d\\n\u0026#34;, i, fa[i]); return 0; } void prim(int w[][MAXN], int fa[], int n) { int i, j, m, k; int d[MAXN]; /*d[j]可以理解成结点j到生成树(集合)的距离，它的最终值是w[j][fa[j]]*/ for(j = 1; j \u0026lt;= n; j++) { d[j] = (j == 1 ? 0 : w[1][j]); /*将第一个结点加入集合，并初始化集合与其他结点的距离*/ fa[j] = 1; /*当前集合中有且只有一个结点1，其他结点暂时未加入集合，所以没有父结点，就先姑且初始化成1*/ } for(i = 2; i \u0026lt;=n; i++) { m = INF; for(j = 1; j \u0026lt;= n; j++) if(d[j] \u0026lt;= m \u0026amp;\u0026amp; d[j] != 0) m = d[k = j]; /*选取与集合距离最小的边*/ d[k] = 0; /*0在这里表示与集合没有距离，也就是说赋值0就是将结点k添加到集合中*/ for(j = 1; j \u0026lt;= n; j++) /*对刚加入的结点k进行扫描，更新d[j]的值*/ if(d[j] \u0026gt; w[k][j] \u0026amp;\u0026amp; d[j] != 0) { d[j] = w[k][j]; fa[j] = k; } } } 输入样例：\n1 2 3 4 5 6 7 6 0 7 6 2 0 0 7 0 0 3 4 0 6 0 0 5 0 3 2 3 5 0 5 4 0 4 0 5 0 6 0 0 3 4 6 0 输出样例：\n1 2 3 4 5 2---\u0026gt;4 3---\u0026gt;6 4---\u0026gt;1 5---\u0026gt;2 6---\u0026gt;4 大家可以新建prim.txt把输入样例写进去，改名成prim.in，然后解除注释这两行：\n1 2 freopen(\u0026#34;prim.in\u0026#34;, \u0026#34;r\u0026#34;, stdin); //打开文件 freopen(\u0026#34;prim.out\u0026#34;, \u0026#34;w\u0026#34;, stdout); 输出在prim.out中，用记事本打开可以看到结果。这样方便调试。\n","permalink":"https://www.zeyes.org/posts/tech/study-c-prim/","summary":"普里姆算法（Prim算法），图论中的一种算法，可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中，不但包括了连通图里的所有顶点，且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克发现；并在1957年由美国计算机科学家罗伯特·普里姆独立发现；1959年，艾兹格·迪科斯彻再次发现了该算法。因此，在某些场合，普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆－亚尔尼克算法。","title":"Prim算法的C语言实现"},{"content":" 指针是C/C++语言的特色，而数组名与指针有太多的相似，甚至很多时候，数组名可以作为指针使用。于是乎，很多程序猿就被搞糊涂了，错误地认为“数组名就是指针”。 想必这种误解的根源在于国内某著名的C程序设计教程。如果这篇文章能够纠正许多中国程序员对数组名和指针的误解，笔者就不甚欣慰了。借此文，笔者站在无数对知识如饥似渴的中国程序员之中，深深寄希望于国内的计算机图书编写者们，能以\u0026quot;深入探索\u0026quot;的思维方式和精益求精的认真态度来对待图书编写工作，但愿市面上多些融入作者思考结晶的心血之作！\n魔幻数组名 请看程序（本文程序在WIN32平台下编译）：\n1 2 3 4 5 6 7 8 9 #include \u0026lt;stdio.h\u0026gt; //c语言版本 int main(void) { char str[10]; char *pStr = str; printf(\u0026#34;%d\\n\u0026#34;, sizeof(str)); //输出10 printf(\u0026#34;%d\\n\u0026#34;, sizeof(pStr)); //输出4 return 0; } 1、数组名不是指针 我们先来推翻\u0026quot;数组名就是指针\u0026quot;的说法，用反证法。\n证明　数组名不是指针\n假设：数组名是指针；\n则：pStr和str都是指针；\n因为：在WIN32平台下，指针长度为4；\n所以：上面两个输出都应该为4；\n实际情况是：第一个输出10，第二个输出4；\n所以：假设不成立，数组名不是指针\n2、数组名神似指针 上面我们已经证明了数组名的确不是指针，但是我们再看看这句char *pStr = str;。它将数组名直接赋值给指针，这显得数组名又的确是个指针！\n我们还可以发现数组名显得像指针的例子：\n1 2 3 4 5 6 7 8 9 10 11 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; int main(void) { char str1[10] = \u0026#34;I Love U\u0026#34;; char str2[10]; strcpy(str2, str1); printf(\u0026#34;String array 1: %s\\n\u0026#34;, str1); printf(\u0026#34;String array 2: %s\\n\u0026#34;, str2); return 0; } 标准C库函数strcpy的函数原形中能接纳的两个参数都为char型指针，而我们在调用中传给它的却是两个\n数组名！函数输出：\n1 2 string array 1: I Love U string array 2: I Love U 数组名再一次显得像指针！\n既然数组名不是指针，而为什么到处都把数组名当指针用？于是乎，许多程序员得出这样的结论：数组名(主)是(谓)不是指针的指针(宾)。\n整个一魔鬼。\n揭密数组名 现在到揭露数组名本质的时候了，先给出三个结论：\n(1)数组名的内涵在于其指代实体是一种数据结构，这种数据结构就是数组；\n(2)数组名的外延在于其可以转换为指向其指代实体的指针，而且是一个指针常量；\n(3)指向数组的指针则是另外一种变量类型（在WIN32平台下，长度为4），仅仅意味着数组的存放地址！\n1、数组名指代一种数据结构：数组\n现在可以解释为什么第1个程序printf(\u0026quot;%d\\n\u0026quot;, sizeof(str));的输出为10的问题，根据结论1，数组名str的内涵为一种数据结构，即一个长度为10的char型数组，所以sizeof(str)的结果为这个数据结构占据的内存大小：10字节。\n再看：\n1 2 int intArray[10]; printf(\u0026#34;%d\\n\u0026#34;, sizeof(intArray)); /*输出结果为40（整型数组占据的内存空间大小）*/ 2、数组名可作为指针常量\n根据结论2，数组名可以转换为指向其指代实体的指针，所以程序1中的第5行数组名直接赋值给指针，程序2中strcpy(str2, str1);直接将数组名作为指针形参都可成立。\n下面的程序成立吗？\n1 2 int intArray[10]; intArray++; 读者可以编译之，发现编译出错。原因在于，虽然数组名可以转换为指向其指代实体的指针，但是它只能被看作一个指针常量，不能被修改。\n而指针，不管是指向结构体、数组还是基本数据类型的指针，都不包含原始数据结构的内涵，在WIN32平台下，sizeof操作的结果都是4。 顺便纠正一下许多程序员的另一个误解。许多程序员以为sizeof是一个函数，而实际上，sizeof是一个操作符，不过其使用方式看起来的确太像一个函数了。语句sizeof(int)就可以说明sizeof的确不是一个函数，因为函数接纳形参（一个变量），世界上没有一个C/C++函数接纳一个数据类型（如int）为\u0026quot;形参\u0026quot;。\n3、数据名可能失去其数据结构内涵\n到这里似乎数组名魔幻问题已经宣告圆满解决，但是平静的湖面上却再次掀起波浪。请看下面一段程序：\n1 2 3 4 5 6 7 8 9 10 11 12 #include \u0026lt;stdio.h\u0026gt; void arrayTest(char []); int main(void) { char str1[10] = \u0026#34;I Love U\u0026#34;; arrayTest(str1); return 0; } void arrayTest(char str[]) { printf(\u0026#34;%d\\n\u0026#34;, sizeof(str)); //输出结果为4 } 程序的输出结果为4。不可能吧？\n一个可怕的数字，前面已经提到其为指针的长度!\n结论1指出，数据名内涵为数组这种数据结构，在arrayTest函数体内，str是数组名，那为什么sizeof的结果却是指针的长度？这是因为：\n(1)数组名作为函数形参时，在函数体内，其失去了本身的内涵，仅仅只是一个指针； (2)很遗憾，在失去其内涵的同时，它还失去了其常量特性，可以作自增、自减等操作，可以被修改。 所以，数据名作为函数形参时，其全面沦落为一个普通指针！它的贵族身份被剥夺，成了一个地地道道的只拥有4个字节的平民。\n本文章来自互联网，本人仅作稍加修改。\n","permalink":"https://www.zeyes.org/posts/tech/study-c-array/","summary":"\u003cblockquote\u003e\n\u003cp\u003e指针是C/C++语言的特色，而数组名与指针有太多的相似，甚至很多时候，数组名可以作为指针使用。于是乎，很多程序猿就被搞糊涂了，错误地认为“数组名就是指针”。\n想必这种误解的根源在于国内某著名的C程序设计教程。如果这篇文章能够纠正许多中国程序员对数组名和指针的误解，笔者就不甚欣慰了。借此文，笔者站在无数对知识如饥似渴的中国程序员之中，深深寄希望于国内的计算机图书编写者们，能以\u0026quot;深入探索\u0026quot;的思维方式和精益求精的认真态度来对待图书编写工作，但愿市面上多些融入作者思考结晶的心血之作！\u003c/p\u003e","title":"[转载]数组名与指针"},{"content":" Dijkstra算法可用于计算正权图的单源最短路（Single-Source Shortest Paths，SSSP），即从单个源点出发，到所有节点的最短路。该算法同时适用于有向图和无向图。\n思想：设置顶点集合S，首先将源点加入集合，然后依据源点到其他顶点的路径的长度，选择路径长度最小的边加入集合，根据所加入的顶点更新源点到其他顶点的路径长度，然后再选取长度最小的边的顶点，依次来做，直到所有的顶点路径都加入集合，也就是求解出了到达所有顶点的路径长度。\n算法（文字版）：\n清除所有点的标号\n1 2 3 4 5 6 7 设d[0]=0，其他d[i]=INF（INF是一个很大的数，用以表示不存在的路径） 循环n次 { 在所有未标号的结点中，选出d值最小的结点x 给结点x标记 对于从x出发的所有边(x, y)，更新d[y] = min{d[y], d[x] + w[x][y]} } 完整代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; #define INF 10000 /*INF表示不存在边的长度，用一个很大的数表示它*/ void dijkstra(int w[][21], int [], int n); int main(void) { int n, i, j; int w[21][21], d[21]; //freopen(\u0026#34;dijkstra.in\u0026#34;, \u0026#34;r\u0026#34;, stdin); //打开文件 //freopen(\u0026#34;dijkstra.out\u0026#34;, \u0026#34;w\u0026#34;, stdout); scanf(\u0026#34;%d\u0026#34;, \u0026amp;n); //读入n for(i = 0; i \u0026lt; n; i++) for(j = 0; j \u0026lt; n; j++) { scanf(\u0026#34;%d\u0026#34;, \u0026amp;w[i][j]); //读入数据，不存在的边是0 if(w[i][j] == 0) w[i][j] = INF; //把不存在的边0，改成一个很大的数字 } dijkstra(w, d, n); //调用算法函数 for(i = 0; i \u0026lt; n; i++) //打印结果 printf(\u0026#34;%d \u0026#34;, d[i]); return 0; } void dijkstra(int w[][21], int d[], int n) { int v[21]; int i, y, x, m; memset(v, 0, sizeof(v)); //v作为是否访问的标志，0表示没有访问，1表示已经访问 for(i = 0; i \u0026lt; n; i++) d[i] = (i == 0 ? 0 : INF); for(i = 0; i \u0026lt; n; i++) { m = INF; for(y = 0; y \u0026lt; n; y++) if(!v[y] \u0026amp;\u0026amp; d[y] \u0026lt;= m) m = d[x = y]; v[x] = 1; for(y = 0; y \u0026lt; n; y++) if(d[y] \u0026gt; d[x] + w[x][y]) d[y] = d[x] + w[x][y]; } } 输入样例：\n1 2 3 4 5 6 5 0 4 29 4 0 2 0 0 0 3 0 6 0 0 4 0 0 0 0 6 0 0 4 0 0 输出样例：\n1 0 4 11 4 7 大家可以新建dijkstra.txt把输入样例写进去，改名成dijkstra.in，然后解除注释这两行：\n1 2 freopen(\u0026#34;dijkstra.in\u0026#34;, \u0026#34;r\u0026#34;, stdin); //打开文件 freopen(\u0026#34;dijkstra.out\u0026#34;, \u0026#34;w\u0026#34;, stdout); 输出在dijkstra.out中，用记事本打开可以看到结果。这样方便调试。\n","permalink":"https://www.zeyes.org/posts/tech/study-c-dijkstra/","summary":"\u003cblockquote\u003e\n\u003cp\u003eDijkstra算法可用于计算正权图的单源最短路（Single-Source Shortest Paths，SSSP），即从单个源点出发，到所有节点的最短路。该算法同时适用于有向图和无向图。\u003c/p\u003e","title":"Dijkstra算法的C语言实现"},{"content":"通常情况下，我们一个函数的参数个数是固定的，传多了会报错，少了有时也可能报错。 例如：\n1 int abc(int a, int b, int c); 若是想调用abc()这个函数，必须传给他三个实参，函数才能正常执行。但是，我想调用一个函数，他的参数个数不确定呢？比如我们经常用的printf()，想在屏幕上打印一些东西。很多时候，参数个数都是不一样的，例如：\n1 2 printf(“%d%d”, a, b); printf(“%d%d”,a, b, c); 以上两个调用都能正常返回结果。其实他就是一个可变参数函数。\n可变参数函数是指一个函数拥有不定引数，即是它接受一个可变量目的参数。在C语言中，C标准函式库的stdarg.h标头档定义了提供可变参数函数使用的宏。在C++，应该使用标头档cstdarg。\nstdarg.h数据类型\n类型名称 描述 相容 va_list 用来保存宏va_arg与宏va_end所需信息 C89 stdarg.h宏\n巨集名称 描述 相容 va_start 使va_list指向起始的参数 C89 va_arg 检索参数 C89 va_end 释放va_list C89 va_copy 拷贝va_list的内容 C99 函数printf()的声明是这样的：int printf(char *fmt, \u0026hellip;); 后面“\u0026hellip;”表示表示参数表中参数的数量和类型都是可变的。 我们看例子： 1 2 3 4 5 6 7 8 9 10 11 12 #include \u0026lt;stdarg.h\u0026gt;//必须的头文件 double average(int count, ...) { va_list ap; int j; double tot = 0; va_start(ap, count); //使va_list指向起始的参数 for(j=0; j\u0026lt;count; j++) tot+=va_arg(ap, double); //检索参数，必须按需要指定类型 va_end(ap); //释放va_list return tot/count; } va_list是一个类型，va_start，va_arg是宏，\nva_start原型：va_start(va_list ap, lastarg); //lastarg是函数的最后一个有名参数（如果有多个有名参数）。 va_arg原型：类型 va_arg(va_list ap, 类型); //在va_arg里面指定什么类型，就返回什么类型 va_end原型：void va_end(va_list ap); //释放ap\nva_list类型用于声明一个变量（我这里把他写成ap，其实是随意的，就像声明int一样，变量名称当然是随意的），该变量将依次引用各参数。\n我们要用va_start初始化va_list才能正常使用。在上面例子里，参数中包含一个有名参数count，和不知道多少个无名的形参（如果有多个有名的参数，我们要把最右边也就是最后一个有名参数传给va_start，例如double average(int count1，int count2, \u0026hellip;),我们应该这样va_start(ap, count2);）说是初始化，实际上是把va_list指向第一个无名参数。也就是说，ap被初始化为指向第一个无名参数的指针。\n我们接着如何使用，这些没有名字的参数呢。我们可以用va_arg，我们用va_arg来决定返回对象的类型和指针移动的步长。va_arg(ap, double)，指针是ap（用前必须用va_start初始化）,第一次调用它，返回的是第一个无名参数，第二次调用它，返回的是第二个无名参数，以此类推。返回的参数，在形参上是用“\u0026hellip;”代替的，每次调用va_arg必须指定一个返回的类型，例如上面的double。\n我们在调用完可变参数后,应该使用va_end做一些必要的清理工作，例如va_end(ap);\n（提示：我们看count这个参数是有名字的，既然有名字，我们就可以用它做一些工作，在上面的例子，他就是用来传递参数的个数，不过既然是普通的有名字参数，就可以用来做任何事情，不仅仅是传递参数个数的作用）\n下面的这个例子有名参数就不是传递参数个数了，他是直接当做无名参数的一部分（这么说其实也不好，看个人理解）。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdarg.h\u0026gt; void printargs(int arg1, ...) /* 输出所有int类型的参数，直到-1结束 */ { va_list ap; int i; va_start(ap, arg1); for (i = arg1; i != -1; i = va_arg(ap, int)) printf(\u0026#34;%d \u0026#34;, i); va_end(ap); putchar(\u0026#39;n\u0026#39;); } int main(void) { printargs(5, 2, 14, 84, 97, 15, 24, 48, -1); printargs(84, 51, -1); printargs(-1); printargs(1, -1); return 0; } 这个程序产生输出: ","permalink":"https://www.zeyes.org/posts/tech/study-c-stdarg/","summary":"\u003cp\u003e通常情况下，我们一个函数的参数个数是固定的，传多了会报错，少了有时也可能报错。\n例如：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"nf\"\u003eabc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e若是想调用abc()这个函数，必须传给他三个实参，函数才能正常执行。但是，我想调用一个函数，他的参数个数不确定呢？比如我们经常用的printf()，想在屏幕上打印一些东西。很多时候，参数个数都是不一样的，例如：\u003c/p\u003e","title":"可变参数的使用"},{"content":"scanf()开始读取后,会在遇到的第一个空白字符空格（blank）、制表符（Tab）或者换行符(newline)处停止读取。\n代码如下：\n1 2 3 4 5 6 7 8 9 #include \u0026lt;stdio.h\u0026gt;; int main(void) { char word[40]; printf(\u0026#34;请输入一个英文句子n\u0026#34;); scanf(\u0026#34;%s\u0026#34;, word); printf(\u0026#34;%sn\u0026#34;, word); return 0; } 编译运行，输入一个英文句子，比如Zeyes Studio，接着便输出Zeyes\n","permalink":"https://www.zeyes.org/posts/tech/study-c-scanf/","summary":"\u003cp\u003escanf()开始读取后,会在遇到的第一个空白字符空格（blank）、制表符（Tab）或者换行符(newline)处停止读取。\u003c/p\u003e\n\u003cp\u003e代码如下：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e4\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e5\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e6\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e7\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e8\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e9\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#include\u003c/span\u003e \u003cspan class=\"cpf\"\u003e\u0026lt;stdio.h\u0026gt;;\u003c/span\u003e\u003cspan class=\"cp\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e \u003cspan class=\"n\"\u003eword\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e40\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;请输入一个英文句子n\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nf\"\u003escanf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;%s\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eword\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;%sn\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eword\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e编译运行，输入一个英文句子，比如Zeyes Studio，接着便输出Zeyes\u003c/p\u003e","title":"使用字符串输入"},{"content":"【2014端午纪念版】抽签系统3.25-Final，欢迎使用。 【2014.05.31新版更新说明】 1.修复多个小Bug 2.部分功能进行小幅度优化\n【2014.01.31新版更新说明】 1.全面支持WIN7\n【2014.01.02旧版更新说明】 1.软件更新 完善更新模块，支持一键升级,点击 关于 \u0026ndash;\u0026gt; 关于软件 \u0026ndash;\u0026gt; 检测更 新即可 （3.22旧版也有自动检测，但只能手动更新） 2.安装包更小 将高达70M的语音引擎移动到百度网盘(如不需语音，可不下) 语音引擎链接 http://pan.baidu.com/s/1o6lpMe2（具体安装看旧版说明） 3.Windows7下需要使用兼容模式才可以正常运行 使用方法：右键单击抽签系统 3.23.exe \u0026ndash;\u0026gt; 属性 \u0026ndash;\u0026gt; 兼容性 \u0026ndash;\u0026gt; 选择以兼容模式运行这个程序(Windows XP (Service Pack 3)) 4.报错邮箱：admin@zeyes.org 官方网站(新)：http://www.zeyes.org/\n【2013.10.05旧版更新说明】 1.账号登陆 起始临时账号为admin，密码为admin，区分大小写，该账号不能修改密码 注册账号后不能再使用（除非把自定义的账号全部删除） 删除账号方法：进入修改密码界面 输入原密码后 新密码留空即可删除该账号 建议：安装完成后，立即注册账号，以防抽签名单恶意修改 2.软件更新 在主界面点击关于即可自动检测软件是否需要更新 需等待几秒 检测 完成后即可知道软件是否需要更新3.操作界面美化 全面美化UI，实现仿win8，让界面更美观 4.报错邮箱：admin@zeyes.org 官方网站：http://www.zeyes.org/\n【2013.04.05旧版更新说明】 1.不重复模式 默认为开 选择了则软件没关闭前不会抽到同样的名字（当然，如果名单抽完了还是会重复的，不然就没名单抽了） 2.语音引擎 主要是用来读出抽到的名字 默认选择Girl XiaoKun 如果您想选择其他的 则一定要选择能念中文的引擎 如果您没有安装任何中文语音引擎，则可以双击Girl_XiaoKun.exe安装(若压缩包中没有，下载链接：http://pan.baidu.com/s/1o6lpMe2)，这也是软件压缩包比较大的原因 3.快捷调出 您可以在“菜单”——“快捷键设置”中设置快捷键 默认为F10在PPT下可以调出 4.名单在“菜单”——“抽签名单”中设置 标题在“菜单”——“抽签标题”中设置 5.其他的一些选项都是傻瓜化的，一看便懂就不做介绍了 6.报错邮箱：admin@zeyes.org\n【更新历史】\n2014.05.31 抽签系统3.25 1.修复多个小Bug 2.部分功能进行小幅度优化\n2014.01.31 抽签系统3.24 1.增加对WIN7的支持 2.修复了几个bug\n2014.01.02 抽签系统3.23 1.完善更新模块（独立分离出一块小程序） 2.修复多bug(登陆界面不能按回车登陆，窗口排列不正常等) 3.代码优化（精简代码）\n2013.10.05 抽签系统3.22 1.全面优化美化UI 2.修复多个bug 3.新增更新模块，支持检测更新 4.新增账号登陆模块，防止恶意删改抽签名单 5.新增支持动态更新抽签名单，更改名单无需重启软件 6.优化部分代码\n2013.04.05 抽签系统3.1 1.修复多个bug； 2.增加语音库选择； 3.全面优化操作界面，人性化操作； 4.在名单设置增加修改名单； 5.快捷键设置增加即时显示按下的键； 6.美化部分按钮 7.去除过多的提示和警告 8.增加“不重复抽签”按钮 9.深度优化核心代码\n2012.12.15 抽签系统2.1： 1.新增全屏下按预先设定的键时可以呼出； 2.修复多个BUG； 3.新增托盘图标； 4.新增在主界面右键呼出菜单； 5.新增语音系统； 6.防止软件多开（只能打开一个）；\n2012.11 抽签系统1.2 1.增加批量导入名单；\n2012.11 抽签系统1.1 1.增加名单管理 可以方便地加入名单 删除名单 清空名单；\n2012.10 抽签系统1.0 1.基本抽签功能；\n下载地址：抽签系统3.25 语音引擎下载链接 http://pan.baidu.com/s/1o6lpMe2（建议下载语音引擎）\n","permalink":"https://www.zeyes.org/posts/tech/e-random-number-system/","summary":"“2014端午纪念版”抽签系统3.25最终版，是一款面向学生课堂用的趣味抽签应用。","title":"【2014端午纪念版】抽签系统3.25-Final"}]