利用github pages建立Serverless个人博客

news2024/12/23 23:45:03

利用github pages建立Serverless个人博客

概述

使用github pages,可以在github上部署静态网站。利用这个功能,可以很方便地实现个人博客的发布托管。

比如我的个人博客:Buttering’s Blog

对应代码仓库:buttering/EasyBlog: 自动将本地博客部署到github中展示

相关技术

1. 博客处理

采用go语言对md格式的博客进行处理,主要目的是处理文档中的图片,将其保存在图床,并将文档中的图片链接替换为图床链接。

2. 图床

采用github仓库直接作为图床。图片和博客存放在同一个仓库中。

3. Hexo博客框架

Hexo是一个快速、简洁且高效的博客框架,能很方便地生成静态的博客网站。采用Fluid主题。

4. CI/CD流程

采用github actions,自动实现博客上传后网站的生成和部署。

5. 更多功能

使用hexo-renderer-markdown-it-plus插件,实现对latex的支持。

使用giscus评论插件,利用github discussions实现博客的评论功能。

实现

0. 项目框架

项目所有文章和图片都位于同一个github仓库中:

image-20240505100150836

  • .github:存放github action的配置文件
  • asset:存放处理后的博客和图片,pictures起到了图床的作用
  • publish:Hexo部署在此子目录
  • resource:存放githubPub公钥
  • tools:一些自己写的工具函数
  • UploadBlogs:实现博客处理和自动push的脚本

1. 博客预处理

对md格式的博客进行预处理,使用正则表达式提取其中的图片路径:

  • 对于本地图片,直接将其拷贝到asset/pictures目录中;
  • 对于网络图片,将其下载在asset/pictures目录中。

在pictures目录中,对文章名和图片进行了哈希处理,如图:

image-20240505100811570

博客则直接按照原文件名放在blogs目录下。

博客中图片的链接会被替换为形如 https://raw.githubusercontent.com/buttering/EasyBlogs/master/asset/pictures/bdf03a9b9bdacaaf60d1f899c0222865/74d51bfa109b4065ccb3ba37b6922bb3.png的url。

raw.githubusercontent.com 是github用来存储用户上传文件的服务地址。

package main

import (
	"EasyBlogs/tools"
	"fmt"
	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing/object"
	"github.com/go-git/go-git/v5/plumbing/transport/ssh"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

type markdownPicture struct {
	isUrl             bool
	sourcePicturePath string
	start             int // md中源图片路径的起始偏移量
	end               int
	hashName          string // 均将包含后缀的文件名进行hash,且后拼接上原有后缀名
	targetUrl         string // 修改后在github仓库中的url
}

type Blog struct {
	name          string
	hashName      string
	pictures      []markdownPicture
	directoryPath string // 源文件文件夹路径
	legal         bool   // 成功通过解析
}

var (
	BLOG_PATH      string
	PICTURE_PATH   string
	REPOSITORY_URL string
)

func getBlogList(path string) (blogsList []Blog) {
	blogsList = make([]Blog, 0, 10)

	fileList, err := os.ReadDir(path)
	if err != nil {
		panic(err)
	}

	for _, file := range fileList {
		if !file.IsDir() && filepath.Ext(file.Name()) == ".md" {
			fileName := file.Name()

			blogsList = append(blogsList, Blog{fileName, tools.Hash(fileName), nil, path, false})
		}
	}
	return
}

func extractPicture(blog *Blog) {
	isUrl := func(path string) bool {
		return strings.HasPrefix(path, `http://`) || strings.HasPrefix(path, `https://`)
	}

	content, err := os.ReadFile(filepath.Join(blog.directoryPath, blog.name))
	if err != nil {
		println(err)
		return
	}

	re, _ := regexp.Compile(`!\[.*?]\((.*?)\)`)
	matches := re.FindAllSubmatchIndex(content, -1)

	for _, match := range matches {
		start := match[2]
		end := match[3]

		picturePath := string(content[start:end])
		var pictureName string
		if isUrl(picturePath) {
			u, err := url.Parse(picturePath)
			if err != nil {
				println("解析图片url:", picturePath, " 失败")
				continue
			}
			pictureName = path.Base(u.Path)
		} else if filepath.IsAbs(picturePath) {
			pictureName = filepath.Base(picturePath)
		} else { // 相对路径的本地文件
			picturePath = filepath.Join(blog.directoryPath, picturePath)
			pictureName = filepath.Base(picturePath)
		}
		hashName := tools.Hash(pictureName) + path.Ext(pictureName)

		blog.pictures = append(
			blog.pictures,
			markdownPicture{
				isUrl(picturePath),
				picturePath,
				start,
				end,
				hashName,
				REPOSITORY_URL + "/" + blog.hashName + "/" + hashName,
			},
		)
	}

	blog.legal = true
}

func copyBlog(blog *Blog) {
	fmt.Println("拷贝博客:“" + blog.name + "”")

	blogTargetPath := filepath.Join(BLOG_PATH, blog.name)
	pictureTargetPath := filepath.Join(PICTURE_PATH, blog.hashName)
	if _, err := os.Stat(blogTargetPath); !os.IsNotExist(err) {
		println("文章“" + blog.name + "”已经存在")
		blog.legal = false
		return
	}

	if err := os.Mkdir(pictureTargetPath, 0777); err != nil {
		println("为博客“" + blog.name + "”创建对应picture文件夹失败")
		blog.legal = false
		return
	}

	content, _ := os.ReadFile(filepath.Join(blog.directoryPath, blog.name))

	offset := 0
	for _, picture := range blog.pictures {
		start := picture.start + offset
		end := picture.end + offset
		content = append(content[:start], append([]byte(picture.targetUrl), content[end:]...)...)
		offset += len(picture.targetUrl) - (end - start)
	}

	err := os.WriteFile(blogTargetPath, content, 0644)
	if err != nil {
		println("复制文件“" + blog.name + "”错误")
		blog.legal = false
	}

}

func copyPicture(blog Blog) {
	pictureTargetPath := filepath.Join(PICTURE_PATH, blog.hashName)

	for _, picture := range blog.pictures {
		fmt.Println("导入图片:“" + picture.sourcePicturePath + "”")

		var sourceFile interface{}
		if picture.isUrl {
			for i := 0; i < 5; i++ {
				response, err := http.Get(picture.sourcePicturePath)
				if err == nil && response.StatusCode == http.StatusOK {
					sourceFile = response.Body
					break
				}
				time.Sleep(50 * time.Millisecond)
			}
			if sourceFile == nil {
				println("下载图片“" + picture.sourcePicturePath + "”失败")
				continue
			}

		} else {
			file, err := os.Open(picture.sourcePicturePath)
			if err != nil {
				println("打开图片“" + picture.sourcePicturePath + "”失败")
				continue
			}
			sourceFile = file
		}

		destinationFile, _ := os.Create(filepath.Join(pictureTargetPath, picture.hashName))
		_, err := io.Copy(destinationFile, sourceFile.(io.Reader))
		if err != nil {
			println("复制图片“" + picture.sourcePicturePath + "”失败")
		}
	}
}

func gitOperate(blogList []Blog) {
	if len(blogList) == 0 {
		return
	}
	repositoryPath, _ := filepath.Abs(".")
	r, err := git.PlainOpen(repositoryPath)
	if err != nil {
		println("打开仓库失败")
		return
	}
	w, err := r.Worktree()
	if err != nil {
		println("打开仓库失败")
		println(err.Error())
		return
	}

	_, err = w.Add("./asset")
	if err != nil {
		println("向仓库添加文件失败")
		println(err.Error())
		return
	}
	status, _ := w.Status()
	println("git 状态:")
	println(status.String())

	nameList := tools.Map(blogList, func(blog Blog) string {
		return blog.name
	})
	var summary string
	if len(nameList) == 1 {
		summary = fmt.Sprintf("提交文件 [%s]", blogList[0].name)
	} else {
		summary = fmt.Sprintf(
			"提交 %d 个博客\n"+
				"\n"+
				"文件列表: [%s]",
			len(blogList),
			strings.Join(nameList, ", "),
		)
	}
	commit, err := w.Commit(summary, &git.CommitOptions{
		Author: &object.Signature{
			Name: "Wang",
			When: time.Now(),
		},
	})

	obj, _ := r.CommitObject(commit)
	fmt.Println("提交文件:")
	fmt.Println(obj.String())

	// user必须是"git"。。。困扰了半天,最后查issue发现的。真够郁闷的。
	privateKey, err := ssh.NewPublicKeysFromFile("git", "./resource/githubPublicKey", "")

	if err != nil {
		println(err.Error())
	}

	for i := 0; i < 3; i++ {
		err = r.Push(&git.PushOptions{
			RemoteName: "origin",
			RemoteURL:  `git@github.com:buttering/EasyBlogs.git`,
			Auth:       privateKey,
			Progress:   os.Stdout,
		})
		if err == nil {
			break
		}
		println("第 %d 次上传失败")
	}
	if err != nil {
		println("重试次数已达上限,上传失败")
		return
	}

	fmt.Println("提交成功!")
}

func init() {
	path, _ := filepath.Abs(".")
	BLOG_PATH = filepath.Join(path, "asset", "blogs")
	PICTURE_PATH = filepath.Join(path, "asset", "pictures")
	REPOSITORY_URL = `https://raw.githubusercontent.com/buttering/EasyBlogs/master/asset/pictures`
}

func main() {
	filePath := "E:/desktop/blog"
	//yamlPath := "./asset/blogs-list.yaml"
	blogList := getBlogList(filePath)
	for i := range blogList {
		extractPicture(&blogList[i])
		copyBlog(&blogList[i])
		copyPicture(blogList[i])
	}
	if len(blogList) == 0 {
		return
	}

	// 改用github page进行博客部署,不需要额外记录博客信息
	//yamlOperate(yamlPath, blogList)
	//dbOperate(blogList)
	gitOperate(blogList)

}

2. 引入Hexo框架

Hexo框架的使用方式很简单,只需使用Hexo CLI建立基本框架,而后将博客和相关资源移入指定文件夹,然后运行Hexo服务器即可。这里为实现Serverless,后两部交由github action实现。

安装Hexo并初始化站点

保证电脑中安装了Node.js。Hexo的部署目录为publish。

npm install -g hexo-cli
nexo init publish
cd publish
npm install

执行后,Hexo会自动在publish目录建立一个空白站点,目录如下:

.
├── _config.yml
├── package.json
├── scaffolds
├── source
|   └── _posts
└── themes
  • _config.yml:站点的配置文件
  • _posts:Hexo会自动将其中的文档转化为静态资源
  • themes:主题文件夹
安装fluid主题

初始的Hexo站点已经很完善了,但是为了更加个性化,安装了fluid主题:配置指南 | Hexo Fluid 用户手册 (fluid-dev.com)

# 仍是在publish目录
npm install --save hexo-theme-fluid

然后在博客目录下创建 _config.fluid.yml,将主题的 _config.yml(位于publish\themes\fluid_config.yml)内容复制进去。这个_config.fluid.yml是主题的覆盖配置,之后要自定义主题,只需修改其中的内容即可。

修改publish下的_config.yml如下:

theme: fluid  # 指定主题
language: zh-CN  # 指定语言,会影响主题显示的语言,按需修改

最后创建主题的关于页

hexo new page about
测试博客

自此,一个本地的博客网站即搭建成功,可以尝试将文档移入_post文件夹,而后运行hexo ghexo s ,就可以在浏览器看到博客。

最后记得使用hexo clean 清空生成的文件,避免影响到后面的部署。

3. github actions配置

在预处理函数的最后一步,实现了git的自动提交和推送。在成功向github推送后,就轮到github action实现博客网站的自动部署了。

配置github仓库

首先将本地的git仓库上传至github,且设置为公开。

进入github 项目的设置页面开启page功能。设置SourceGitHub Actions

image-20240505163116047

编写actions脚本文件

回到本地,在项目根目录创建 .github/workflows/xxx.yml 文件。

这里需要一些github actions的知识,简单地说 Actions 就是在设定的时机触发创建一个虚拟云环境,然后执行一连串动作,从而实现自动部署的功能。

可以直接复制以下内容:

name: Deploy Github

# 在master分支发生push事件时触发。
on:
  push:
    branches:
      - master
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: 检出仓库
        uses: actions/checkout@v4

      - name: Install pandoc
        run: |
          cd /tmp
          wget -c https://github.com/jgm/pandoc/releases/download/2.14.0.3/pandoc-2.14.0.3-1-amd64.deb
          sudo dpkg -i pandoc-2.14.0.3-1-amd64.deb

      - name: 安装Node.js 18
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: Cache NPM dependencies
        uses: actions/cache@v4
        with:
          path: node_modules
          key: ${{ runner.OS }}-npm-cache
          restore-keys: |
            ${{ runner.OS }}-npm-cache

      - name: 安装相关依赖
        working-directory: publish
        run: |
          npm install --silent --no-fund
          npm un hexo-renderer-marked --save
          npm i hexo-renderer-markdown-it-plus --save
          
      - name: 复制博客文件
        run: |
          cd publish
          rm -rf source/_posts
          mkdir source/_posts
          cp -rf ../asset/blogs/* ./source/_posts/

      - name: 构建网站
        working-directory: publish
        run: npm run build

      - name: Upload Pages artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./publish/public
          
  deploy:
    needs: build
    permissions:
      pages: write
      id-token: write
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

在完成了环境的初始化后,会自动将位于 asset/blog 中的博客文件复制到 publish/source/_post 目录内。而后的构建步骤,就会利用 _post 目录中的博客文件生成网站。

修改配置

编辑 _config.yml,将 url: 更改为 <你的 GitHub 用户名>.github.io/<repository 的名字>

提交并自动部署

在提交前,记得编辑.gitignore文件:

/resource/githubPublicKey
/publish/node_modules/
/publish/public/

在之后的每次提交,都会触发actions流程,生成站点。部署完成后,前往 https://<你的 GitHub 用户名>.github.io/<repository 的名字> 查看站点。

4. 开启latex功能

hexo默认的渲染器不支持复杂的数学公式。

Latex的渲染分为前端渲染和后端渲染两种方法,这里选择后端渲染,需要更换渲染器(已经在actions脚本中完成了)。

npm un hexo-renderer-marked --save
npm i hexo-renderer-markdown-it-plus --save

修改 _config.fluid.yml

post:
  math:
    enable: true
    specific: false
    engine: katex

使用hexo-renderer-markdown-it-plus插件进行latex渲染时,如果使用mathjax,一些不完全符合md语法的标题会渲染失败(如一级标题前没有空行),因此要将引擎改为katex。

可以参考配置指南 | Hexo Fluid 用户手册 (fluid-dev.com) 和 CHENXCHEN/hexo-renderer-markdown-it-plus

5. 实现评论功能

使用 Giscus插件,且基于 GitHub Discussion实现评论功能。

开启 GitHub Discussion

进入github仓库->Settings->General->Features,勾选 Discussions

image-20240505170039793

安装 Github Apps

点击链接GitHub Apps - giscus ,为本仓库安装应用。

image-20240505170017470

可以到 giscus仓库 检查自己的仓库是否满足giscus要求的条件。

image-20240505170207236

配置插件

由于使用了fluid主题,因此只需修改配置文件即可开启评论。

修改 _config.fluid.yml

  comments:
    enable: true
    # 指定的插件,需要同时设置对应插件的必要参数
    # The specified plugin needs to set the necessary parameters at the same time
    # Options: utterances | disqus | gitalk | valine | waline | changyan | livere | remark42 | twikoo | cusdis | giscus | discuss
    type: giscus
    
# Giscus
# 基于 GitHub Discussions,类似于 Utterances
# Based on GitHub Discussions, similar to Utterances
# See: https://giscus.app/
giscus:
  repo: buttering/EasyBlog
  repo-id: 
  category: Announcements
  category-id: 
  theme-light: light
  theme-dark: dark
  mapping: title
  reactions-enabled: 1
  emit-metadata: 0
  input-position: bottom
  lang: zh-CN

在配置基于 Github Discussions 的博客评论系统时(例如 giscus),往往需要获取 repo 的 repo-id, category-id 等属性,因此这里介绍一种获取这些信息的方法

首先打开以下 Github Docs 地址:Github Docs Explorer,然后授权 Github 账户,并输入以下内容

{
  repository(owner: "userName", name: "repoName") {
    id
    discussionCategories (first: 5) {
      nodes {
        name
        id
      }
    }
  }
}
  • userName 换成具体的 Github 用户名;
  • repoName 换成保存评论数据的 Repo 名称。

点击运行按钮,即可得到形如 "id": "R_kgDOKjFfn1"DIC_kwdOJPFfnc4CU9... 就是我们需要的 repoIdcategoryId

因为网站中文章的url是直接使用文章名进行定位的,如 https://buttering.github.io/EasyBlog/2022/10/15/使用plotly离线模式报错UnicodeEncodeError:gbk codec can't encode character in position的解决方法/,如果 mapping: 的值设为 pathname 或者 url,其在 github discussions中出现的标题,中文会被base64字符代替,严重影响观感,设为 title 可以解决这个问题。

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

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

相关文章

从一到无穷大 #26 Velox:Meta用cpp实现的大一统模块化执行引擎

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 引言业务案例PrestoSparkXStreamDistributed messaging systemData IngestionData Pr…

【软件测试理论001】认识软件测试、测试分类、测试原则

目录 1 认识软件测试 1.1 什么是软件 1.2 什么是软件测试 1.3 软件测试的发展历程 1.4 为什么要进行软件测试 1.5 软件测试职业规划 2 软件测试的分类 2.1 按开发阶段划分 单元测试&#xff08;Unit Testing&#xff09; 集成测试&#xff08;Integration Testing&am…

【ChatGPT with Date】使用 ChatGPT 时显示消息时间的插件

文章目录 1. 介绍2. 使用方法2.1 安装 Tampermonkey2.2 安装脚本2.3 使用 3. 配置3.1 时间格式3.2 时间位置3.3 高级配置(1) 生命周期钩子函数(2) 示例 4. 反馈5. 未来计划6. 开源协议7. 供给开发者自定义修改脚本的文档7.1 项目组织架构7.2 定义新的 Component(1) 定义一个新的…

Vue2——前端笔记

Vue 一、Vue核心1.1、vue简介1.2、初始vue1.3、模板语法1.4、数据绑定1.5、el与data的两种写法1.6、MVVM模型1.7、Vue中的数据代理1.7.1、Object.defineProperty() 理解1.7.2、Vue中的数据代理 1.8、事件处理1.8.1、事件的基本用法1.8.2、事件修饰符1.8.3、键盘事件 1.9、计算属…

电商中文场景多模态测试prompt

魔搭社区汇聚各领域最先进的机器学习模型&#xff0c;提供模型探索体验、推理、训练、部署和应用的一站式服务。https://www.modelscope.cn/datasets 多模态大模型Yi-VL-plus体验 效果很棒 - 知乎最近测了一下零一万物的多模态大模型Yi-VL-plus的效果&#xff0c;发现多模态理解…

CNN实现卫星图像分类(tensorflow)

使用的数据集卫星图像有两类&#xff0c;airplane和lake&#xff0c;每个类别样本量各700张&#xff0c;大小为256*256&#xff0c;RGB三通道彩色卫星影像。搭建深度卷积神经网络&#xff0c;实现卫星影像二分类。 数据链接百度网盘地址&#xff0c;提取码: cq47 1、查看tenso…

【一刷《剑指Offer》】面试题 14:调整数组顺序使奇数位于偶数前面

力扣对应题目链接&#xff1a;LCR 139. 训练计划 I - 力扣&#xff08;LeetCode&#xff09; 牛客对应题目链接&#xff1a;调整数组顺序使奇数位于偶数前面(二)_牛客题霸_牛客网 (nowcoder.com) 核心考点&#xff1a;数组操作&#xff0c;排序思想的扩展使用。 一、《剑指Off…

LAME及 iOS 编译

文章目录 关于 LAME编译 for iOS 关于 LAME 官网&#xff1a;https://lame.sourceforge.io LAME是根据LGPL许可的高质量MPEG音频层III&#xff08;MP3&#xff09;编码器。 LAME的开发始于1998年年中左右。Mike Cheng 最开始将它作为针对8hz-MP3编码器源的补丁。在其他人提出…

docker-本地私有仓库、harbor私有仓库部署与管理

一、本地私有仓库&#xff1a; 1、本地私有仓库简介&#xff1a; docker本地仓库&#xff0c;存放镜像&#xff0c;本地的机器上传和下载&#xff0c;pull/push。 使用私有仓库有许多优点&#xff1a; 节省网络带宽&#xff0c;针对于每个镜像不用每个人都去中央仓库上面去下…

实现 Trie (前缀树) - LeetCode 热题 54

大家好&#xff01;我是曾续缘&#x1f49c; 今天是《LeetCode 热题 100》系列 发车第 54 天 图论第 4 题 ❤️点赞 &#x1f44d; 收藏 ⭐再看&#xff0c;养成习惯 实现 Trie (前缀树) Trie&#xff08;发音类似 "try"&#xff09;或者说 前缀树 是一种树形数据结构…

C#知识|上位机项目主窗体设计思路及流程(实例)

哈喽,你好啊,我是雷工! 昨天练习了登录窗体的设计实现,今天练习上位机项目主窗体的设计实现。 01 主窗体效果展示 02 实现步骤 2.1、添加主窗体 添加窗体,名称:FrmMain.cs 2.2、窗体属性设置 将FrmMain窗体属性FormBorderStyle设置为None,无边框; 将FrmMain窗体属性…

神经网络中的算法优化(皮毛讲解)

抛砖引玉 在深度学习中&#xff0c;优化算法是训练神经网络时至关重要的一部分。 优化算法的目标是最小化&#xff08;或最大化&#xff09;一个损失函数&#xff0c;通常通过调整神经网络的参数来实现。 这个过程可以通过梯度下降法来完成&#xff0c;其中梯度指的是损失函数…

Windows查找JDK的安装路径

如果很久之前安装了JDK&#xff0c;或者在别人的电脑上&#xff0c;想要快速指导JDK 的安装路径&#xff0c;可以通过啥方式指导JDK的安装路径是在哪里呢&#xff1f; 一、确认是否安装了JDK 首先我们打开命令行&#xff0c;如果输入 java -version 如果显示这种&#xff0c;…

IBM收购HashiCorp:开源工具的未来与“好软件的坟墓”

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

VBA 创建透视表,录制宏,自动化报表

目录 一. 数据准备二. 需求三. 准备好报表模板四. 执行统计操作&#xff0c;录制宏4.1 根据数据源创建透视表4.2 填充数据到报表4.3 结束宏录制 五. 执行录制好的宏&#xff0c;自动化报表 一. 数据准备 ⏹数据源1 姓名学科成绩丁志敏语文91李平平语文81王刚语文64张伊语文50…

场景文本检测识别学习 day08(无监督的Loss Function、代理任务、特征金字塔)

无监督的Loss Function&#xff08;无监督的目标函数&#xff09; 根据有无标签&#xff0c;可以将模型的学习方法分为&#xff1a;无监督、有监督两种。而自监督是无监督的一种无监督的目标函数可以分为以下几种&#xff1a; 生成式网络的做法&#xff0c;衡量模型的输出和固…

【C++STL详解(六)】--------list的模拟实现

目录 前言 一、接口总览 一、节点类的模拟实现 二、迭代器类的模拟实现 迭代器的目的 list迭代器为何要写成类&#xff1f; 迭代器类模板参数说明 模拟实现 1.构造函数 2.*运算符重载 3.->运算符重载 4.前置 5.后置 6.前置-- 7.后置-- 8.! 9. 三、list类的…

【知识加油站】——机电产品数字孪生机理模型构建

明确一种多领域、多层次、参数化、一致性的机电一体化装备数字孪生机理模型构建准则&#xff01; 关键词英文简称&#xff1a; 数字孪生&#xff1a;DT物联网&#xff1a;IoT网络物理系统&#xff1a;CPS高级架构&#xff1a;HLA统一建模语言&#xff1a;UML数控机床&#xf…

2-qt之信号与槽-简单实例讲解

前言、因实践课程讲解需求&#xff0c;简单介绍下qt的信号与槽。 一、了解信号与槽 怎样使用信号与槽&#xff1f; 概览 还记得 X-Window 上老旧的回调函数系统吗&#xff1f;通常它不是类型安全的并且很复杂。&#xff08;使用&#xff09;它&#xff08;会&#xff09;有很多…

精析React与Vue架构异同及React核心技术——涵盖JSX、组件、Props、State、生命周期与16.8版后Hooks深化解析

React&#xff0c;Facebook开源的JavaScript库&#xff0c;用于构建高性能用户界面。通过组件化开发&#xff0c;它使UI的构建、维护变得简单高效。利用虚拟DOM实现快速渲染更新&#xff0c;适用于单页应用、移动应用&#xff08;React Native&#xff09;。React极大推动了现代…