golang AST语法树解析

1. 源码示例

package mainimport ("context"
)// Foo 结构体
type Foo struct {i int
}// Bar 接口
type Bar interface {Do(ctx context.Context) error
}// main方法
func main() {a := 1
}

2. Golang中的AST

golang官方提供的几个包,可以帮助我们进行AST分析:

  • go/scanner:词法解析,将源代码分割成一个个token

  • go/token:token类型及相关结构体定义

  • go/ast:ast的结构定义

    • ast的各种结构定义入口在go/ast/ast.go
  • go/parser:语法分析,读取token流生成ast

通过上述的四个库,我们就可以实现golang代码的语法树分析

3. 使用main.go解析demo.go的AST树

package mainimport ("go/ast""go/parser""go/token""log""path/filepath"
)func main() {fset := token.NewFileSet()// 这里取绝对路径,方便打印出来的语法树可以转跳到编辑器path, _ := filepath.Abs("./demo.go")f, err := parser.ParseFile(fset, path, nil, parser.AllErrors)if err != nil {log.Println(err)return}// 打印语法树ast.Print(fset, f)
}

3.1. 解析的结果如下

可在http://goast.yuroyoro.net/里贴上源代码后查看

*ast.File {1  .  Package: 1:12  .  Name: *ast.Ident {3  .  .  NamePos: 1:94  .  .  Name: "main"5  .  }6  .  Decls: []ast.Decl (len = 4) {7  .  .  0: *ast.GenDecl {8  .  .  .  TokPos: 3:19  .  .  .  Tok: import10  .  .  .  Lparen: 3:811  .  .  .  Specs: []ast.Spec (len = 1) {12  .  .  .  .  0: *ast.ImportSpec {13  .  .  .  .  .  Path: *ast.BasicLit {14  .  .  .  .  .  .  ValuePos: 4:215  .  .  .  .  .  .  Kind: STRING16  .  .  .  .  .  .  Value: "\"context\""17  .  .  .  .  .  }18  .  .  .  .  .  EndPos: -19  .  .  .  .  }20  .  .  .  }21  .  .  .  Rparen: 5:122  .  .  }23  .  .  1: *ast.GenDecl {24  .  .  .  TokPos: 8:125  .  .  .  Tok: type26  .  .  .  Lparen: -27  .  .  .  Specs: []ast.Spec (len = 1) {28  .  .  .  .  0: *ast.TypeSpec {29  .  .  .  .  .  Name: *ast.Ident {30  .  .  .  .  .  .  NamePos: 8:631  .  .  .  .  .  .  Name: "Foo"32  .  .  .  .  .  .  Obj: *ast.Object {33  .  .  .  .  .  .  .  Kind: type34  .  .  .  .  .  .  .  Name: "Foo"35  .  .  .  .  .  .  .  Decl: *(obj @ 28)36  .  .  .  .  .  .  }37  .  .  .  .  .  }38  .  .  .  .  .  Type: *ast.StructType {39  .  .  .  .  .  .  Struct: 8:1040  .  .  .  .  .  .  Fields: *ast.FieldList {41  .  .  .  .  .  .  .  Opening: 8:1742  .  .  .  .  .  .  .  List: []*ast.Field (len = 1) {43  .  .  .  .  .  .  .  .  0: *ast.Field {44  .  .  .  .  .  .  .  .  .  Names: []*ast.Ident (len = 1) {45  .  .  .  .  .  .  .  .  .  .  0: *ast.Ident {46  .  .  .  .  .  .  .  .  .  .  .  NamePos: 9:247  .  .  .  .  .  .  .  .  .  .  .  Name: "i"48  .  .  .  .  .  .  .  .  .  .  .  Obj: *ast.Object {49  .  .  .  .  .  .  .  .  .  .  .  .  Kind: var50  .  .  .  .  .  .  .  .  .  .  .  .  Name: "i"51  .  .  .  .  .  .  .  .  .  .  .  .  Decl: *(obj @ 43)52  .  .  .  .  .  .  .  .  .  .  .  }53  .  .  .  .  .  .  .  .  .  .  }54  .  .  .  .  .  .  .  .  .  }55  .  .  .  .  .  .  .  .  .  Type: *ast.Ident {56  .  .  .  .  .  .  .  .  .  .  NamePos: 9:457  .  .  .  .  .  .  .  .  .  .  Name: "int"58  .  .  .  .  .  .  .  .  .  }59  .  .  .  .  .  .  .  .  }60  .  .  .  .  .  .  .  }61  .  .  .  .  .  .  .  Closing: 10:162  .  .  .  .  .  .  }63  .  .  .  .  .  .  Incomplete: false64  .  .  .  .  .  }65  .  .  .  .  }66  .  .  .  }67  .  .  .  Rparen: -68  .  .  }69  .  .  2: *ast.GenDecl {70  .  .  .  TokPos: 13:171  .  .  .  Tok: type72  .  .  .  Lparen: -73  .  .  .  Specs: []ast.Spec (len = 1) {74  .  .  .  .  0: *ast.TypeSpec {75  .  .  .  .  .  Name: *ast.Ident {76  .  .  .  .  .  .  NamePos: 13:677  .  .  .  .  .  .  Name: "Bar"78  .  .  .  .  .  .  Obj: *ast.Object {79  .  .  .  .  .  .  .  Kind: type80  .  .  .  .  .  .  .  Name: "Bar"81  .  .  .  .  .  .  .  Decl: *(obj @ 74)82  .  .  .  .  .  .  }83  .  .  .  .  .  }84  .  .  .  .  .  Type: *ast.InterfaceType {85  .  .  .  .  .  .  Interface: 13:1086  .  .  .  .  .  .  Methods: *ast.FieldList {87  .  .  .  .  .  .  .  Opening: 13:2088  .  .  .  .  .  .  .  List: []*ast.Field (len = 1) {89  .  .  .  .  .  .  .  .  0: *ast.Field {90  .  .  .  .  .  .  .  .  .  Names: []*ast.Ident (len = 1) {91  .  .  .  .  .  .  .  .  .  .  0: *ast.Ident {92  .  .  .  .  .  .  .  .  .  .  .  NamePos: 14:293  .  .  .  .  .  .  .  .  .  .  .  Name: "Do"94  .  .  .  .  .  .  .  .  .  .  .  Obj: *ast.Object {95  .  .  .  .  .  .  .  .  .  .  .  .  Kind: func96  .  .  .  .  .  .  .  .  .  .  .  .  Name: "Do"97  .  .  .  .  .  .  .  .  .  .  .  .  Decl: *(obj @ 89)98  .  .  .  .  .  .  .  .  .  .  .  }99  .  .  .  .  .  .  .  .  .  .  }100  .  .  .  .  .  .  .  .  .  }101  .  .  .  .  .  .  .  .  .  Type: *ast.FuncType {102  .  .  .  .  .  .  .  .  .  .  Func: -103  .  .  .  .  .  .  .  .  .  .  Params: *ast.FieldList {104  .  .  .  .  .  .  .  .  .  .  .  Opening: 14:4105  .  .  .  .  .  .  .  .  .  .  .  List: []*ast.Field (len = 1) {106  .  .  .  .  .  .  .  .  .  .  .  .  0: *ast.Field {107  .  .  .  .  .  .  .  .  .  .  .  .  .  Names: []*ast.Ident (len = 1) {108  .  .  .  .  .  .  .  .  .  .  .  .  .  .  0: *ast.Ident {109  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  NamePos: 14:5110  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  Name: "ctx"111  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  Obj: *ast.Object {112  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  Kind: var113  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  Name: "ctx"114  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  Decl: *(obj @ 106)115  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  }116  .  .  .  .  .  .  .  .  .  .  .  .  .  .  }117  .  .  .  .  .  .  .  .  .  .  .  .  .  }118  .  .  .  .  .  .  .  .  .  .  .  .  .  Type: *ast.SelectorExpr {119  .  .  .  .  .  .  .  .  .  .  .  .  .  .  X: *ast.Ident {120  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  NamePos: 14:9121  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  Name: "context"122  .  .  .  .  .  .  .  .  .  .  .  .  .  .  }123  .  .  .  .  .  .  .  .  .  .  .  .  .  .  Sel: *ast.Ident {124  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  NamePos: 14:17125  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  Name: "Context"126  .  .  .  .  .  .  .  .  .  .  .  .  .  .  }127  .  .  .  .  .  .  .  .  .  .  .  .  .  }128  .  .  .  .  .  .  .  .  .  .  .  .  }129  .  .  .  .  .  .  .  .  .  .  .  }130  .  .  .  .  .  .  .  .  .  .  .  Closing: 14:24131  .  .  .  .  .  .  .  .  .  .  }132  .  .  .  .  .  .  .  .  .  .  Results: *ast.FieldList {133  .  .  .  .  .  .  .  .  .  .  .  Opening: -134  .  .  .  .  .  .  .  .  .  .  .  List: []*ast.Field (len = 1) {135  .  .  .  .  .  .  .  .  .  .  .  .  0: *ast.Field {136  .  .  .  .  .  .  .  .  .  .  .  .  .  Type: *ast.Ident {137  .  .  .  .  .  .  .  .  .  .  .  .  .  .  NamePos: 14:26138  .  .  .  .  .  .  .  .  .  .  .  .  .  .  Name: "error"139  .  .  .  .  .  .  .  .  .  .  .  .  .  }140  .  .  .  .  .  .  .  .  .  .  .  .  }141  .  .  .  .  .  .  .  .  .  .  .  }142  .  .  .  .  .  .  .  .  .  .  .  Closing: -143  .  .  .  .  .  .  .  .  .  .  }144  .  .  .  .  .  .  .  .  .  }145  .  .  .  .  .  .  .  .  }146  .  .  .  .  .  .  .  }147  .  .  .  .  .  .  .  Closing: 15:1148  .  .  .  .  .  .  }149  .  .  .  .  .  .  Incomplete: false150  .  .  .  .  .  }151  .  .  .  .  }152  .  .  .  }153  .  .  .  Rparen: -154  .  .  }155  .  .  3: *ast.FuncDecl {156  .  .  .  Name: *ast.Ident {157  .  .  .  .  NamePos: 18:6158  .  .  .  .  Name: "main"159  .  .  .  .  Obj: *ast.Object {160  .  .  .  .  .  Kind: func161  .  .  .  .  .  Name: "main"162  .  .  .  .  .  Decl: *(obj @ 155)163  .  .  .  .  }164  .  .  .  }165  .  .  .  Type: *ast.FuncType {166  .  .  .  .  Func: 18:1167  .  .  .  .  Params: *ast.FieldList {168  .  .  .  .  .  Opening: 18:10169  .  .  .  .  .  Closing: 18:11170  .  .  .  .  }171  .  .  .  }172  .  .  .  Body: *ast.BlockStmt {173  .  .  .  .  Lbrace: 18:13174  .  .  .  .  List: []ast.Stmt (len = 1) {175  .  .  .  .  .  0: *ast.AssignStmt {176  .  .  .  .  .  .  Lhs: []ast.Expr (len = 1) {177  .  .  .  .  .  .  .  0: *ast.Ident {178  .  .  .  .  .  .  .  .  NamePos: 19:2179  .  .  .  .  .  .  .  .  Name: "a"180  .  .  .  .  .  .  .  .  Obj: *ast.Object {181  .  .  .  .  .  .  .  .  .  Kind: var182  .  .  .  .  .  .  .  .  .  Name: "a"183  .  .  .  .  .  .  .  .  .  Decl: *(obj @ 175)184  .  .  .  .  .  .  .  .  }185  .  .  .  .  .  .  .  }186  .  .  .  .  .  .  }187  .  .  .  .  .  .  TokPos: 19:4188  .  .  .  .  .  .  Tok: :=189  .  .  .  .  .  .  Rhs: []ast.Expr (len = 1) {190  .  .  .  .  .  .  .  0: *ast.BasicLit {191  .  .  .  .  .  .  .  .  ValuePos: 19:7192  .  .  .  .  .  .  .  .  Kind: INT193  .  .  .  .  .  .  .  .  Value: "1"194  .  .  .  .  .  .  .  }195  .  .  .  .  .  .  }196  .  .  .  .  .  }197  .  .  .  .  }198  .  .  .  .  Rbrace: 20:1199  .  .  .  }200  .  .  }201  .  }202  .  Scope: *ast.Scope {203  .  .  Objects: map[string]*ast.Object (len = 3) {204  .  .  .  "Foo": *(obj @ 32)205  .  .  .  "Bar": *(obj @ 78)206  .  .  .  "main": *(obj @ 159)207  .  .  }208  .  }209  .  Imports: []*ast.ImportSpec (len = 1) {210  .  .  0: *(obj @ 12)211  .  }212  .  Unresolved: []*ast.Ident (len = 3) {213  .  .  0: *(obj @ 55)214  .  .  1: *(obj @ 119)215  .  .  2: *(obj @ 136)216  .  }217  }

4. AST树结构

// 该结构体位于标准包 go/ast/ast.go 中,有兴趣可以转跳到源码阅读更详尽的注释
type File struct {Doc        *CommentGroup   // 如果文件有文档,则文档会被存储在这个结构体中,否则为 nilPackage    token.Pos       // "package"关键字,主要是所在的位置信息Name       *Ident          // package的名字Decls      []Decl          // 文件级别的声明。它包含文件中所有变量、函数、类型声明。如果文件中没有声明,则 decls 值为 nilScope      *Scope          // 包级作用域。它代表包级作用域,包含所有在包内声明的变量和函数。它对当前文件有效Imports    []*ImportSpec   // imports in this fileUnresolved []*Ident        // unresolved identifiers in this file。未使用的标识符Comments   []*CommentGroup // 文件中的所有注释。它包含文件中所有注释的列表
}

img

img

4.1. Doc

如果文件有文档,则文档会被存储在这个结构体中,否则为 nil

todo:目前没找到什么样的源代码解析成AST树后有Doc的

4.2. Package

*ast.File {1  .  Package: 1:12  .  Name: *ast.Ident {3  .  .  NamePos: 1:94  .  .  Name: "main"5  .  }

Package: 1:1, package关键字所在的位置

4.3. Name

img

type为ast.ident,表示它是一个变量值,可以看到内容为"main"

4.4. Decls

文件级别的声明。它包含文件中所有变量、函数、类型声明。如果文件中没有声明,则 decls 值为 nil

img

4.5. Decls总共有三种类型

img

4.5.1. BadDecl

语法出错的声明

4.5.2. GenDecl

常规的声明,包含以下部分

  • import
  • constant
  • type
  • variable
4.5.2.1. import

img

4.5.2.2. constant
4.5.2.3. type

img

4.5.2.4. variable

4.5.3. FunDecl

方法的声明

4.6. Scope

包级作用域。它代表包级作用域,包含所有在包内声明的变量和函数。它对当前文件有效

img

4.6.1. 示例如下

img

4.7. Imports

回顾以下File结构体定义,其中ImportsImportSpec类型数组

// 该结构体位于标准包 go/ast/ast.go 中,有兴趣可以转跳到源码阅读更详尽的注释
type File struct {Doc        *CommentGroup   // 如果文件有文档,则文档会被存储在这个结构体中,否则为 nilPackage    token.Pos       // "package"关键字,主要是所在的位置信息Name       *Ident          // package的名字Decls      []Decl          // 文件级别的声明。它包含文件中所有变量、函数、类型声明。如果文件中没有声明,则 decls 值为 nilScope      *Scope          // 包级作用域。它代表包级作用域,包含所有在包内声明的变量和函数。它对当前文件有效Imports    []*ImportSpec   // imports in this fileUnresolved []*Ident        // unresolved identifiers in this file。未使用的标识符Comments   []*CommentGroup // 文件中的所有注释。它包含文件中所有注释的列表
}

ImportSpec结构体定义如下,一条import就是一个ImportSpec

// An ImportSpec node represents a single package import.ImportSpec struct {Doc     *CommentGroup // associated documentation; or nilName    *Ident        // local package name (including "."); or nilPath    *BasicLit     // import pathComment *CommentGroup // line comments; or nilEndPos  token.Pos     // end of spec (overrides Path.Pos if nonzero)}

img

4.8. Unresolved

unresolved identifiers in this file。未使用的标识符

4.9. Comments

文件中的所有注释。它包含文件中所有注释的列表。实际上这块有问题,并没有注释解析出来

5. AST数节点类型

6. 参考资料

  1. Golang AST语法树使用教程及示例
  2. GoAst Viewer
  3. https://github.com/DrmagicE/ast-example
  4. [golang深入源代码系列之一:AST的遍历](

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

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

相关文章

[杂谈] 关于 Mac 电脑使用 Logitech 鼠标导致 Vscode 侧键无法进行代码前进、回退的问题

我个人使用的是一台 14 寸的 Mac_Apple_M1,外接键盘显示器罗技的 MX Master 3 for Mac 的鼠标。 之前一直使用的 GoLand 开发,查看代码时进行代码跳转就很方便,滚轮键 进入函数方法,鼠标侧键进行前进、后退。看代码完全可以右手单…

【大模型入门】LLM-AI大模型介绍

大语言模型 (LLM) 背景 🍹大语言模型 (Large Language Model) 是一种人工智能模型, 它们通常包含数千亿甚至更多的参数,并在大规模数据集上进行训练。大语言模型可以处理多种自然语言任务,如文本分类、问答、翻译、对话等等。 自然语言模型…

MQ基础1

对应B站视频: MQ入门-01.MQ课程介绍_哔哩哔哩_bilibili 微服务一旦拆分,必然涉及到服务之间的相互调用,目前我们服务之间调用采用的都是基于OpenFeign的调用。这种调用中,调用者发起请求后需要等待服务提供者执行业务返回结果后…

求立方体面积体积以及判断(c++)

代码&#xff1a; #include<iostream> using namespace std;class Cube { public:void setL(int l){m_L l;}int getL(){return m_L;}void setW(int w){m_W w;}int getW(){return m_W;}void setH(int h){m_H h;}int getH(){return m_H;}int calculateS(){return 2 * (…

netdata 监控软件安装与学习

netdata官网 netdata操作文档 前言&#xff1a; netdata是一款监控软件&#xff0c;可以监控多台主机也可以监控单台主机&#xff0c;监控单台主机时&#xff0c;开箱即用&#xff0c;web ui很棒。 环境&#xff1a; [root192 ~]# cat /etc/system-release CentOS Linux rel…

GD32F407VET6新建固件库工程并下载运行

零、所需文件及环境&#xff1a; 1、固件库的压缩包 GD32F4xx_Firmware_Library_V3.2.0.7z 官网 2、GD32F407的keil支持包 官网 兆易创新GigaDevice-资料下载兆易创新GD32 MCU 2、 keilkilll.bat 用来删除编译过程文件 可以不要 &#xff08;原子、野火资料里都有&…

算法金 | 来了,pandas 2.0

大侠幸会&#xff0c;在下全网同名「算法金」 0 基础转 AI 上岸&#xff0c;多个算法赛 Top 「日更万日&#xff0c;让更多人享受智能乐趣」 今日 210/10000 Pandas 是一个强大的数据分析库&#xff0c;广泛应用于科学研究、金融分析、商业智能等领域。它提供了高效的数据结构…

[WUSTCTF2020]level4题解 入土为安的第三天

二叉树 Practice my Data Structure code..... Typing....Struct.....char....*left....*right............emmmmm...OK! Traversal! Traversal type 1:2f0t02T{hcsiI_SwA__r7Ee} Traversal type 2:20f0Th{2tsIS_icArE}e7__w Traversal type 3: //type3(&x[22]); No w…

C++基础篇(2)

目录 前言 1.缺省参数 2.函数重载 2.1函数重载的基本规则 ​编辑2.2注意事项 2.3 重载解析&#xff08;Overload Resolution&#xff09;--补充内容 3.引用 3.1引用的概念和定义 3.2引用的特性 3.3引用的使用 3.4const引用 4.指针和引用的关系 结束语 前言 上节小编…

感应灯光画纯电路开源版本

前言 之前那版灯光画用的从垃圾佬淘的电路板拼出来的&#xff0c;功能不全&#xff0c;显示效果不太好而且无法固定到相框上&#xff0c;这次改版用的嘉立创smt&#xff0c;贴了5片板子&#xff08;19元&#xff09;&#xff0c;功能上的改进是加了无极触摸调光、添加了黄白两…

debian固定ip

debian固定ip 前言 安装好的Debian系统后&#xff0c;为了确保每次登陆的ip不变&#xff0c;需要固定 方法 命令如下 ip addr | grep inet因为有有线网和无线网 2 种连接方式&#xff0c;因此需要区别。 其中 enp 的是有线&#xff0c;wlp 的是无线 查看网关 IP 命令如下 …

互联网末法时代的一些思考

这篇文章也是临时起意&#xff0c;很长一段时间没写个人思考类的文章&#xff0c;主要原因也是时间完全不够用。随着年龄的增长&#xff0c;看待问题的视角也逐渐发生变化&#xff0c;例如从关注现象到关注动机&#xff0c;从关注结果到关注起因&#xff0c;2021年的时代我曾经…

java面向对象进阶篇--static

一、前言 java进阶篇已经开始了&#xff0c;先从面向对象开始&#xff0c;由于时间原因今天就只更新了static部分&#xff0c;内容上特别详细&#xff0c;一些特别的注意事项也在反复的提醒大家。 温馨提示一下&#xff0c;往后的java篇会越来越难&#xff0c;希望大家能够坚…

P2p网络性能测度及监测系统模型

P2p网络性能测度及监测系统模型 网络IP性能参数 IP包传输时延时延变化误差率丢失率虚假率吞吐量可用性连接性测度单向延迟测度单向分组丢失测度往返延迟测度 OSI中的位置-> 网络层 用途 面相业务的网络分布式计算网络游戏IP软件电话流媒体分发多媒体通信 业务质量 通过…

python编程:从入门到实践(第三版) 笔记

文章目录 资源网站:https://www.ituring.com.cn/book/3038配置VSCode推荐资源网站推荐资源网址 资源网站:https://www.ituring.com.cn/book/3038 配置VSCode 推荐资源网站 推荐资源网址 英文版主页&#xff1a; https://ehmatthes.github.io/pcc_3e 中文版主页&#xff1a; h…

上市公司企业共同机构所有权数据、机构交叉持股数据(2005-2023)

数据来源&#xff1a;基础数据来源于上市公司企业年报 时间跨度&#xff1a;2005-2023年 数据范围&#xff1a;企业层面 数据指标&#xff1a; 参考《中国工业经济》杜勇&#xff08;2021&#xff09;老师的做法&#xff0c;从 3 个维度构造指标反映上市公司共同机构所有权&…

Vue和Element UI 路由跳转

在Vue.js中&#xff0c;使用Vue Router可以方便地实现页面之间的路由跳转。Element UI是一个基于Vue 2.0的桌面端组件库&#xff0c;它本身并不直接提供路由跳转的功能&#xff0c;但你可以在使用Element UI的Vue项目中结合Vue Router来实现这一功能。 以下是一个基于Vue和Ele…

Proxyman for Mac v5.6.1 抓包调试工具

Mac分享吧 文章目录 效果一、下载软件二、功能三、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功 四、运行测试1、打开软件 安装完成&#xff01;&#xff01;&#xff01; 效果 一…

【华为OD笔试】2024D卷命题规律解读【分析300+场OD笔试考点总结】

可上 欧弟OJ系统 练习华子OD、大厂真题 绿色聊天软件戳 od1441了解算法冲刺训练&#xff08;备注【CSDN】否则不通过&#xff09; 文章目录 相关推荐阅读华为OD笔试2024D卷命题规律解读华为OD算法/大厂面试高频题算法练习冲刺训练 相关推荐阅读 【华为OD笔试】2024D卷机考套题…

C# Opencv实现本地以图搜图

地址&#xff1a;冯腾飞/本地以图搜图