1、package 的定义和导入

在任何大型软件项目中,代码的组织和管理都是至关重要的。Go 语言通过 包(Package) 的概念来解决这个问题,它不仅是代码组织的基础,也是代码复用的关键。本文将深入探讨 Go 语言中包的定义、规则和使用方法。

1. 什么是包 (Package)?

在 Go 语言中,一个包是位于同一目录下的一个或多个 Go 源文件的集合。它将功能相关的代码组织在一起,形成一个独立的、可复用的模块。

核心作用:

  • 代码组织:将庞大的代码库拆分成逻辑清晰、易于管理的小单元。
  • 代码复用:通过 import 关键字,可以在一个包中轻松使用另一个包提供的功能。
  • 命名空间:避免不同代码块之间的命名冲突。

Go 语言的标准库本身就是由众多功能强大的包组成的,例如我们常用的 fmt(格式化 I/O)、os(操作系统功能)、io(I/O 原语)等。

2. 包的声明与规则

a. 包声明

Go 语言强制规定,每一个源文件的开头都必须使用 package 关键字声明其所属的包

image.png

b. 核心规则

  1. 同目录同包:位于同一个目录下的所有源文件,必须声明为同一个包。不允许在同一目录下出现多个不同的包声明。
  2. 包名与目录名:包的声明名称(如 package course可以不与其所在的目录名(如 user/)相同。但在实际开发中,为了清晰和一致性,通常建议将包名与目录名保持一致。
  3. 入口包 main:一个可执行程序的入口必须是 main 函数,且该函数必须位于 main 包中。

3. 包内访问与可见性(导出)

a. 包内访问

在同一个包内部(即同一目录下的所有文件),所有成员(如变量、常量、结构体、函数等)都是互相可见的,可以直接访问,无需任何特殊处理。这就像它们被定义在同一个文件中一样,不存在“导出”或“私有”的概念。

image.png

PixPin_2025-06-26_16-47-40.gif

b. 包外访问(导出)

当需要从一个包(例如 main)访问另一个包(例如 course)的成员时,就涉及到可见性规则。在 Go 中,这个规则非常简单:

名称首字母大写的标识符(变量、类型、函数等)可以被导出,从而被其他包访问。首字母小写的标识符则是私有的,仅在包内可见。

如果我们要让 main 包能够创建 Course 结构体的实例并访问其 Name 字段,就必须将它们的首字母大写:

image.png

4. 导入和使用包

要使用其他包的功能,需要使用 import 关键字。

a. Import 路径

import 语句后面跟着的是包的路径,而不是包的名称。这个路径通常是相对于项目模块根目录(在 go.mod 文件中定义)的相对路径。

b. 使用方式

导入包之后,需要通过包声明的名称(而不是目录名)来访问其导出的成员。

image.png

c. Import 组

当需要导入多个包时,推荐使用 import 组的形式,这样可以提高代码的可读性,这也是 Go 语言的通用编码规范。

import (  "fmt"  "onego/xh01/user")
)

5. 与其他语言的简单对比

  • Java: 同样使用 package 关键字,但强制要求目录结构与包名完全匹配。
  • Python: 包是通过目录和 __init__.py 文件隐式定义的,包名就是文件名或目录名。
  • PHP/C#: 使用 namespace 关键字来组织代码,概念上与 Go 的 package 类似,都用于解决代码组织和命名冲突问题。

2、高级 import 技巧

除了标准的导入方式,Go 还提供了一些高级的 import 用法来处理特殊场景。

a. 包的别名 (Package Alias)

如果导入的多个包名称存在冲突,或者原始包名过长,可以为其指定一个别名。
场景:当不同路径下的包恰好同名时,别名是解决命名冲突的唯一方法。

image.png

指定别名后,原始的包名在该文件中将不再可用,必须使用别名来访问。

b. 点导入 (Dot Import)

点(.)导入可以将一个包的所有导出成员直接引入到当前包的命名空间中,这样在调用时就不再需要加包名前缀。

image.png

警告应谨慎使用点导入。这种方式虽然能简化代码,但会严重降低代码的可读性,使得我们很难区分一个标识符是属于当前包还是来自被导入的包,同时也增加了命名冲突的风险。

c. 匿名导入 (Blank Import)

匿名导入使用下划线 _作为包的别名。这种导入方式的唯一目的,是执行被导入包的 init 函数,以实现其副作用(Side Effect),而并不会实际使用包中的任何成员。

场景:最常见的用途是在程序启动时,通过导入数据库驱动包来自动注册其驱动。

假设 user 包中有一个 init 函数:

image.png

main 包中进行匿名导入:

image.png

即使 main 函数中没有显式调用 user 包的任何代码,其 init 函数也会在 main 函数执行前被自动调用。如果只是普通导入而未使用,编译器会报错,而匿名导入则完美解决了这个问题。

3、使用 Go Modules 管理依赖

Go Modules 是 Go 语言官方的依赖管理系统,用于管理项目中的外部包(第三方库)。它通过 go.modgo.sum 两个文件来精确记录和控制项目的依赖关系,确保构建的可复现性。

a. 自动化的依赖管理

当你在代码中导入一个尚未被项目引用的外部包时,Go 工具链会自动处理后续的一切。

以流行的 Web 框架 Gin 为例:

image.png

在代码中添加 import 语句:

image.png

image.png
image.png

保存文件后,现代 IDE(如 GoLand)或手动执行 go mod tidy 命令,会触发以下操作:

  • 发现新依赖:Go 工具检测到 import 路径,并发现它是一个需要从网络下载的模块。
  • 下载模块:工具会访问该路径(如 GitHub),查找最新的合适版本,并将其下载到本地的模块缓存中。
  • 更新 go.mod:自动在 go.mod 文件中添加一条 require 记录。

b. 理解 go.mod 文件

go.mod 文件是项目的核心依赖清单。在上述操作后,它可能看起来像这样:

image.png

  • module: 定义了当前项目的模块路径。
  • go: 指定了项目所使用的 Go 最低版本。
  • require: 列出了项目的直接依赖。
  • // indirect: 注释标记的依赖项表示它们是间接依赖。即,你的项目直接依赖 gin,而 gin 内部又依赖了这些包。Go Modules 会智能地将它们区分开。

c. 理解 go.sum 文件

在依赖更新的同时,还会生成或更新一个 go.sum 文件。此文件包含项目所有直接和间接依赖项的特定版本的加密哈希值(checksum)。

image.png

作用:确保每次构建时,你使用的都是与首次下载时完全相同的、未经篡改的依赖包代码,为项目提供安全保障。

注意go.modgo.sum 这两个文件都由 Go 工具自动维护,不应手动修改。它们应该与您的源代码一起提交到版本控制系统(如 Git)中。

d. 依赖的存储位置

所有通过 Go Modules 下载的依赖包,并不会放在你的项目目录中,而是存储在一个统一的全局缓存位置,通常是 $GOPATH/pkg/mod。这使得多个项目可以共享同一个下载的依赖包,节省磁盘空间。

通过掌握 Go Modules,您可以高效、安全地管理项目依赖,专注于业务逻辑的开发。

4、配置代理下载源

由于 Go 模块的默认下载源(proxy.golang.org)在国内访问可能较慢,建议配置国内镜像代理来加速下载。通过设置环境变量即可完成配置:

我们进入终端。

启用 Go Modules (在 Go 1.13及以上版本中默认开启)

go env -w GO111MODULE=on

设置国内镜像代理

go env -w GOPROXY=https://goproxy.cn,direct

GOPROXY 的值是一个逗号分隔的 URL 列表,direct 表示在代理不可用时回源到代码仓库原始地址。设置完成后,可以通过 go env 命令检查 GOPROXY 的值是否已更新。

5、常用管理命令

Go Modules 提供了一系列命令来管理依赖。以下是一些最常用的命令,建议在项目根目录(go.mod 文件所在位置)下执行。

  • go mod tidy:自动整理依赖 这是最常用且最重要的命令之一。它会分析当前项目所有源码,执行两大核心操作:

    1. 添加缺失的依赖:扫描代码中的 import 语句,如果发现有包被导入但尚未记录在 go.mod 文件中,tidy 会自动查找、下载并将它们添加进去。
    2. 移除未使用的依赖:检查 go.mod 文件中记录的所有依赖,如果发现某个依赖在项目中已不再被任何代码使用,tidy 会将其移除,保持依赖清单的整洁。
    # 自动下载 gorm 等新依赖,并清理不再使用的旧依赖
    go mod tidy 
    

    实际上,go mod tidy 的功能涵盖了 go get 的部分场景,许多开发者倾向于在添加或删除代码中的 import 后,直接运行此命令来同步所有依赖。

  • go get:获取或更新特定依赖 此命令主要用于显式地管理单个依赖。

    • 下载新依赖

      go get github.com/go-redis/redis/v8
      
    • 更新到特定版本:使用 @ 符号可以指定版本号(或分支、commit hash)。

      # 更新(或降级)gin到v1.8.0版本
      go get github.com/gin-gonic/gin@v1.8.0
      
    • 更新到最新版本

      go get -u github.com/gin-gonic/gin
      
  • go list:列出依赖信息

    • 列出所有依赖

      go list -m all
      
    • 查找模块可用版本

      go list -m -versions github.com/gin-gonic/gin
      
  • go mod graph:查看依赖关系图 此命令会打印出项目的模块依赖图,每一行表示一个模块和它的一个依赖,方便分析复杂的依赖关系。

    go mod graph
    
  • go mod download:仅下载依赖 此命令会将 go.mod 文件中指定的依赖下载到本地缓存,但不进行安装或构建。这在 CI/CD 环境中预热缓存时非常有用。

  • go install:编译并安装命令 这个命令与 go get 不同,它的主要目的是编译和安装一个可执行的二进制文件到你的 $GOBIN 目录(通常是 $GOPATH/bin),而不是为了管理当前项目的依赖。

    # 安装一个名为 'golangci-lint' 的代码检查工具
    go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
    

使用 replace 指令处理特殊依赖

replace 指令是 go.mod 文件中一个强大的特性,它允许你在不修改源代码 import 路径的情况下,将一个依赖模块的源码路径替换为另一个路径。

核心场景

  1. 本地开发与调试:你正在开发的项目A依赖于另一个项目B。如果你发现了B的一个bug并想在本地修复它,你可以使用 replace 指令,让项目A使用你本地存放的、已修改但未发布的B项目代码,而不是远程仓库的版本。
  2. 使用Fork仓库:当一个官方依赖不再维护或有紧急bug未修复时,你可以Fork其仓库进行修改,并使用 replace 指令将项目依赖指向你的Fork仓库。

使用方法

可以直接在 go.mod 文件中手动添加 replace 语句,或使用 go mod edit 命令。

  • 替换为本地路径: 假设你的项目 my-app 和你正在调试的依赖 gin 存放在同一目录下:

    /workspace
    ├── /my-app
    └── /gin  (这是 github.com/gin-gonic/gin 的本地克隆)
    

    my-app/go.mod 中添加:

    replace github.com/gin-gonic/gin => ../gin
    

    当构建 my-app 时,Go 工具会使用本地的 ../gin 目录下的代码,而不是从 github.com/gin-gonic/gin 下载。

  • 替换为其他仓库

    replace example.com/original/lib v1.2.3 => example.com/my-fork/lib v1.2.3-fixed
    
  • 使用命令修改

    go mod edit -replace=github.com/gin-gonic/gin=../gin

replace 指令仅在主模块(你的项目)的 go.mod 文件中生效,它不会在被依赖的模块中传递。这确保了替换行为只影响你当前的项目,不会对其他依赖此模块的项目造成意外影响。

6、规范

良好的代码规范是高效团队协作和软件长期维护的基石。它并非强制性的语法规则,而是一套提升代码可读性、一致性和可维护性的最佳实践。遵循统一的规范,可以使代码风格在团队内部保持一致,极大地降低沟通成本和后续的迭代维护难度。

本文将介绍 Go 语言社区广泛遵循的一些核心编码规范。

1. 命名规范 (Naming Conventions)

命名是代码的“门面”,清晰的命名规范至关重要。

a. 包命名 (Package Naming)

  • 简短且有意义:包名应使用简短、清晰、有意义的单个词。例如,使用 httpuser 而不是 http_utilscommon_helpers
  • 全小写:包名应始终使用小写字母,不使用下划线 (snake_case) 或混合大写 (camelCase)。
  • 与目录名一致:尽量保持包名与其所在的目录名一致。
  • 避免与标准库冲突:不要使用 Go 标准库中已有的包名,如 ioos

b. 文件命名 (File Naming)

文件名应清晰地描述其内容,通常使用小写的蛇形命名法 (snake_case)。

  • 例如: user_service.go, db_connection.go

c. 变量命名 (Variable Naming)

Go 语言推荐使用驼峰命名法 (camelCase)。

  • 风格userNameorderCount。避免使用下划线,如 user_name
  • 简洁性:Go 崇尚简洁,倾向于使用短小的变量名,尤其是在作用域较小的代码块中(如 i 用于循环,r 用于 reader)。但这不应以牺牲清晰度为代价。
  • 专有名词:对于常见的专有名词(如 API, URL, ID),建议保持其大写形式,如 apiClient, customerID, requestURL,而不是 apiUrlCustomerId
  • 布尔类型:布尔型变量建议使用 is, has, can, allow 等前缀,以明确其含义。例如:isReady, hasPermission

d. 结构体命名 (Struct Naming)

结构体命名同样遵循驼峰命名法。首字母的大小写决定了其可见性(是否被导出)。

// 可导出的结构体
type UserProfile struct {// ...
}// 仅包内可见的结构体
type sessionCache struct {// ...
}

e. 接口命名 (Interface Naming)

  • er 后缀:Go 语言中最地道的接口命名方式是为其添加 er 后缀。例如:Reader, Writer, Formatter
  • 其他场景:如果 er 后缀不适用,则根据接口的功能进行命名。在一些其他语言背景的团队中,也可能见到以 I 开头的命名方式(如 IUserService),但这并非 Go 的原生习惯。

f. 常量命名 (Constant Naming)

常量命名与变量类似,使用驼峰命名法。如果需要导出,则首字母大写。对于一组相关的常量,可以使用 iota 进行枚举。

const ApiVersion = "v1.2.0" // 单个常量const (StatusActive = iota // 值为 0StatusInactive      // 值为 1StatusPending       // 值为 2
)

在某些情况下,特别是当常量模仿其他语言的枚举时,也可能见到全大写带下划线的命名方式(API_VERSION),但这在 Go 中不如驼峰法常见。

2. 注释规范 (Commenting)

清晰的注释是理解代码逻辑的关键。Go 支持 //(单行注释)和 /* ... */(块注释)。

a. 包注释 (Package Comment)

每个包都应该有一个包级别的注释,位于 package 声明的正上方,用以说明该包的功能。

// package user 封装了用户相关的操作,
// 包括用户信息的增删改查以及权限校验。
//
// Author: bobby
// Date: 2025-06-26
package user

b. 函数与方法注释 (Function & Method Comments)

所有导出的函数和方法都应该有注释,用以说明其功能、参数和返回值。注释内容应以函数名开头。

// GetCourseInfo 用于根据课程ID获取详细的课程信息。
// 它接收一个课程对象作为参数,并返回课程的名称。
//
// c: 包含课程ID的课程对象
// returns: 课程的名称
func GetCourseInfo(c Course) string {// ...
}

c. 类型注释 (Type Comments)

所有导出的类型(结构体、接口等)都应有注释,说明其用途。

// Course 代表一个课程实体,包含了课程的基本信息。
type Course struct {ID   intName string // 课程名称
}

d. 代码逻辑注释

在复杂的代码逻辑块上方或行尾添加注释,解释“为什么”这么做,而不是“做了什么”。

// 在事务开始前预先检查库存,避免无效的数据库操作
if stock < required {return ErrInsufficientStock
}
3. 导入规范 (Import)

import 语句的管理直接影响代码的整洁度。

  • 分组:Go 推荐将 import 的包分为三组,组与组之间用一个空行隔开。

    1. 第一组:Go 标准库中的包。
    2. 第二组:第三方库的包。
    3. 第三组:项目内部或公司内部的包。
  • 排序:在每个分组内部,按照包路径的字母顺序进行排序。

一个规范的 import 示例如下:

import ("encoding/json""fmt""os""github.com/gin-gonic/gin""github.com/go-redis/redis/v8""my-project/internal/auth""my-project/internal/models"
)

遵循这些基本的编码规范,可以显著提升代码质量,为个人和团队带来长远的益处。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#语言入门-task4 :C#语言的高级应用

C# 作为一门现代化、面向对象的编程语言&#xff0c;在企业级应用、游戏开发、移动应用、云计算等领域有着广泛的应用。以下是 C# 语言的一些高级应用场景和技术方向&#xff1a; 一、高级语言特性与编程范式 1. 异步编程&#xff08;Async/Await&#xff09; 应用场景&…

FastAPI vs Flask vs Django:Python Web框架全面对比

Python 作为最受欢迎的编程语言之一&#xff0c;其 Web 开发生态极为丰富。FastAPI、Flask 和 Django 是当前主流的三大 Python Web 框架&#xff0c;各有千秋。本文将从架构设计、开发效率、性能表现、生态支持、适用场景等方面&#xff0c;全面对比这三大框架&#xff0c;帮助…

如何从零开始掌握Pandas的DataFrame使用

视频演示 如何通过实例学习Pandas DataFrame的创建与数据访问 &#x1f9e9; 理解 Pandas DataFrame&#xff1a;数据分析的核心结构 Pandas 是 Python 中用于数据分析与处理的主力库&#xff0c;而 DataFrame 是 Pandas 最常用的二维表格数据结构。我们可以将其想象成一个 Ex…

LaTeX下载与实践入门指南

LaTeX下载与实践入门指南 简单来说&#xff0c;LaTeX 是一种以代码驱动的排版系统。和 Word 那种所见即所得&#xff08;WYSIWYG&#xff09;的编辑方式不同&#xff0c;LaTeX 更像是你写代码、它帮你生成精美排版。你写的不是文字排版&#xff0c;而是一种“结构化内容&#…

Java--数组

目录 1.1 介绍&#xff1a;数据可以存放多个同一类型的数据。 1.2 排序&#xff1a; 冒泡排序法&#xff1a; 1.3 查找 1. 顺序查找 2. 二分查找 二维数组&#xff1a; 杨辉三角&#xff1a; 1.1 介绍&#xff1a;数据可以存放多个同一类型的数据。 数组的引用&#xf…

地址簇与数据序列

深入理解IP地址与端口号&#xff1a;网络通信的基础 IP地址&#xff1a;互联网的门牌号 IP地址&#xff08;Internet Protocol Address&#xff09;是分配给网络中每台设备的唯一标识符&#xff0c;就像现实世界中的门牌号一样。在计算机上&#xff0c;一个网卡对应一个IP地址…

中学数集相等概念凸显无穷集不可~其真子集——初数一直将不是N的真子集误为⊂N

中学数集相等概念凸显无穷集不可&#xff5e;其真子集——初数一直将不是N的真子集误为⊂N 黄小宁 [摘要]证明了初等数学应有几何起码常识&#xff1a;当且仅当平移的距离0时才能使平移前、后的点集&#xff08;元点不少于两个&#xff09;重合。从而表明初中的直线公理使中学…

常规层叠设计需要了解的板材知识

常规层叠设计需要了解的板材知识: 层叠设计的第一个关键要点就是要了解板材的基本知识。 观点: PCB是由铜箔(“皮”)、树脂(“筋”)、玻璃纤维布及其他功能性补强添加物(“骨”)组成。层叠设计时&#xff0c;要对“筋骨皮”的材料特性参数有一定了解。 先来看看“皮”,在对常…

Zabbix 监控VMware Vcenter

本次实验测试如何在Zabbix中添加Vcenter监控对象实现对VMware虚拟化平台的监控。 一、测试环境 1、Zabbix服务器配置&#xff1a; Zabbix 版本: Zabbix 7.0.11 LTS 操作系统: Ubuntu 24.04 数据库: MySQL 8 Web 服务器: Apache IP&#xff1a;192.168.1.242 2、监控目标…

链表最终章——双向链表及其应用

———————————本文旨在交流探讨计算机知识&#xff0c;欢迎交流指正———————————— 上一章&#xff0c;我们介绍了链表的循环扩展&#xff0c;但是&#xff0c;单向链表毕竟是单向查询的&#xff0c;就算是经过循环来查找&#xff0c;终究是效率偏低&#x…

智能体的5个核心要素

文章目录 如何看待智能体智能体的发展阶段国内大模型厂家推出的智能体智能体的应用领域智能体架构智能体的核心要素1. ​​认知中枢&#xff08;大模型&#xff09;​​&#x1f9e0; 2. ​​记忆系统&#xff08;Memory&#xff09;​​&#x1f6e0;️ 3. ​​规划与决策&…

QUdpScoket 组播实现及其中的踩坑点记录

QUdpScoket 组播实现及其中的踩坑点记录 QUdpSocket要想组播需要打开MulticastTtlOption配置项&#xff0c;否则无法生效&#xff0c;亲身踩坑经历 m_socketnew QUdpSocket(this);m_socket->setSocketOption(QAbstractSocket::MulticastTtlOption,1);确定一个组播地址&…

250627-结合Guacamole与FRP访问CentOS-Stream-9及Windows10

A. FRP的配置 A.1 FRP在CentOS中的配置 frps.toml [common] bind_port 7000 bind_addr 0.0.0.0dashboard_port 7500 dashboard_user admin dashboard_pwd admin启动&#xff1a;./frps -c frps.toml frpc.toml [common] server_addr 123.456.789.98 server_port 700…

环保法规下的十六层线路板创新:猎板 PCB 如何实现无铅化与可持续制造

在全球环保法规趋严的背景下&#xff0c;十六层线路板作为高端电子设备的核心组件&#xff0c;正面临无铅化与可持续制造的双重挑战。猎板 PCB 凭借材料革新与工艺升级&#xff0c;构建了从焊料到基材、从生产到回收的全链路绿色体系&#xff0c;为行业树立了合规标杆。 一、无…

OpenLayers 拖动旋转和缩放

前言 在 OpenLayers 框架中已经封装了很多便利的交互控件&#xff0c;可以做到开箱即用&#xff0c;非常方便。像拖动缩放、绘制、选择等交互控件可以供开发者直接使用。本篇给大家介绍拖动旋转交互控件 1. 旋转控件简介 此控件通过按住shift键结合鼠标左键或右键进行使用。在…

element ui Cascader 级联选择器 处理未全选时去除父节点值,选中父节点时去除子节点值

目前我这边的需求时&#xff1a;当用户的选择&#xff0c;只保留最顶层的选中节点 如果选中了父节点&#xff0c;则移除其所有子孙节点以及它的祖先节点&#xff08;因为选中父节点代表选中整个分支&#xff0c;所以不需要再显示子节点&#xff1b;同时&#xff0c;如果存在祖…

uniapp实现远程图片下载到手机相册功能

在 UniApp 中实现点击下载图片到相册的功能&#xff0c;需要以下几个步骤&#xff1a; 1. 下载图片到本地&#xff08;uni.downloadFile&#xff09; 2. 将图片保存到相册&#xff08;uni.saveImageToPhotosAlbum&#xff09; 完整代码示例&#xff1a; <template>&l…

【世纪龙科技】吉利博瑞汽车车身诊断与校正仿真教学软件

在汽车产业蓬勃发展的当下&#xff0c;汽车车身诊断与校正技术人才的需求与日俱增。然而&#xff0c;职业院校在汽车车身教学实践中&#xff0c;却面临着学生实训机会稀缺、教学互动匮乏、过程评价缺失、学生技能提升缓慢等诸多难题。江苏世纪龙科技凭借其卓越的技术实力与行业…

极速二刷leetcode hot100

简单题 1.移动0 283. 移动零 - 力扣&#xff08;LeetCode&#xff09; 刚开始没看到非零子串的顺序不变&#xff1a; // if(nums.size() 1){// return;// }// //所有 0 移动到数组的末尾//同时保持非零元素的相对顺序。// int n nums.size();// int notZero n-1;////…

技术博客:如何用针孔相机模型理解图像

引言 大家好&#xff01;今天我们来聊聊一个非常有趣的话题——针孔相机模型。这个模型可以帮助我们理解相机是如何捕捉图像的。我们会用一些简单的数学公式来解释这个过程&#xff0c;不用担心&#xff0c;我会尽量让这些内容简单易懂。 什么是针孔相机模型&#xff1f; 针…