12.Go 函数

1、函数的声明

语法:func 函数名(参数列表)(返回参数列表){函数体}

  • 函数名遵循标识符的命名规则,首字母的大小写决定该函数在其他包的可见性:大写时其他包可见,小写时只有相同的包可以访问;
  • 函数的参数和返回值需要使用“()”,如果只有一个返回值,而且使用的是非命名的参数,则返回参数的“()”可以省略。
  • 函数体使用“{}”,并且“{”必须位于函数返回值同行的行尾。
func main() {sum := add(1, 2)fmt.Println(sum)
}func add(a, b int) int {return a + b
}

声明一个在外部定义的函数只需给出函数名与函数签名即可,不需要写出完整的函数体,例如:

func hello(str,num int)  //外部实现

函数同样可以通过声明的方式作为一个函数类型被使用,例如:type addNum func (int, int)int

  • 此处不需要函数体“{}”,因为函数在Go语言中属于一等值(frst-class value)
  • 函数也可以赋值给变量,如add := addNum,由于变量指向了addNum函数的签名,所以,不能再给它赋一个具有不同签名的函数值。

函数的特点:

  • 函数可以没有输入参数,也可以没有返回值(默认为0)
  • 多个相邻的相同类型的参数可以使用简写模式
  • 支持有名的返回值,参数名相当于函数体内最外层的局部变量,命名返回值变量会被初始化为类型零值,最后的return可以不带参数名直接返回。
  • 不支持默认值参数。
  • 不支持函数重载。
  • 不支持函数嵌套,严格来说是不支持命名函数的嵌套,但支持嵌套匿名函数。
func add(a int,b int) int {return a+b
}// 简写为func add(a,b int) int {return a+b
}
2、函数的调用

调用前的函数局部变量都会被保存起来不会丢失,被调用的函数运行结束后,恢复到调用函数的下一行继续执行代码,之前的局部变量也能继续访问。函数内的局部变量只能在函数体中使用,函数调用结束后,这些局部变量都会被释放并且失效。

语法:返回值变量列表 = 函数名(参数列表)

func main() {var num1 int = 200var num2 int = 100var maxNum intmaxNum = max(num1, num2)fmt.Printf("最大值是:%d\n", maxNum)
}/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {var result intif num1 > num2 {result = num1} else {result = num2}return result
}
3、函数的参数

函数可以有一个或多个参数,每个参数后面都带有类型,通过“,”符号分隔。如果参数列表中若干个相邻参数的类型相同,则可以在参数列表中省略前面变量的类型声明:

func add(a,b int) (ret int, err error) {函数体
}

如果返回值列表中多个返回值的类型相同,也可以使用同样的方式合并。如果函数只有一个返回值,可以写成如下形式:

func add(a,b int) int {
}

函数如果使用参数,该变量可称为函数的形参。形参就像定义在函数体内的局部变量。

调用函数,可以通过两种方式来传递参数:值传递、引用传递。

a. 值传递

值传递是指在调用函数时将实际参数复制传递到函数中,这样在函数中如果对参数进行修改,将不会影响实际参数。默认情况下,Go语言使用的是值传递,即在调用过程中不会影响实际参数。

func main() {/* 定义局部变量 */var a int = 100var b int = 200fmt.Printf("交换前 a 的值为:%d\n", a)fmt.Printf("交换前 b 的值为:%d\n", b)/* 通过调用函数来交换值 */swapNum(a, b)fmt.Printf("交换后 a 的值为:%d\n", a)fmt.Printf("交换后 b 的值为:%d\n", b)
}/* 定义相互交换值的函数 */
func swapNum(x, y int) int {var temp inttemp = xx = yy = tempreturn temp
}

b. 引用传递

引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响实际参数。引用传递指针参数传递到函数内,例如,交换函数swap()使用引用传递:

func main() {/* 定义局部变量 */var a int = 100var b int = 200fmt.Printf("交换前 a 的值为:%d\n", a)fmt.Printf("交换前 b 的值为:%d\n", b)/** &a 指向 a指针,a 变量的地址* &b 指向 b指针,b 变量的地址*/swapRef(&a, &b)fmt.Printf("交换后 a 的值为:%d\n", a)fmt.Printf("交换后 b 的值为:%d\n", b)
}func swapRef(x, y *int) {var temp inttemp = *x*x = *y*y = temp
}

4、函数的返回值

Go语言既支持安全指针,也支持多返回值,因此在使用函数进行逻辑编写时更为方便。

Go语言支持多返回值,多返回值能方便地获得函数执行后的多个返回参数。Go语言经常使用多返回值中的最后一个返回参数返回函数执行中可能发生的错误:

conn, err := connectToNetwork()connectToNetwork返回两个参数,conn表示连接对象,err表示返回的错误信息。

如果返回值有多个,则用括号将多个返回值类型括起来,用逗号分隔每个返回值的类型。使用return语句返回时,值列表的顺序需要与函数声明的返回值类型一致。

func multiValues() (int, int, string) {return 1, 22, "tom"
}func main() {sex, age, name := multiValues()fmt.Printf("我叫%s,今年%d岁,我是%d", name, age, sex) // 我叫tom,今年22岁,我是1
}
  • 以上属于纯类型的返回值,这种方式对于代码可读性不是很友好,特别是在同类型的返回值出现时,无法区分每个返回参数的意义。

Go语言支持对返回值进行命名,这样返回值就和参数一样拥有参数变量名和类型。命名的返回值变量的默认值为类型的默认值,即数值为0,字符串为空字符串,布尔值为false,指针为nil。

func multiValues() (sex int , age int, name string) {return 1, 22, "tom"
}func main() {sex, age, name := multiValues()fmt.Printf("我叫%s,今年%d岁,我是%d", name, age, sex)
}
  • 同类型的返回值可以只写一个: (sex int , age int, name string)->(sex , age int, name string)

注意:如果只有一个返回值且不声明返回值变量,那么可以省略返回值(包括返回值的括号都可以不写)。如果没有返回值,那么就直接省略最后的返回信息,什么都不用写;如果有返回值,那么必须在函数的最后添加return语句。

5、函数类型和匿名函数

Go语言支持匿名函数,即在需要使用函数时再定义函数,匿名函数没有函数名,只有函数体,函数可以作为一种类型被赋值给函数类型的变量。匿名函数也往往以变量的方式被传递。

a. 函数类型

函数类型和map()、slice()一样,实际函数类型变量和函数名都可以当作指针变量,该指针指向函数代码的开始位置。通常说函数类型变量是一种引用类型,未初始化的函数类型的变量的默认值是nil。

func main() {sum := do(add, 1, 3)fmt.Println("1 + 3 = ", sum)sub := do(sub, 1, 3)fmt.Println("1 - 3 = ", sub)}type Op func(int, int) int // 定义一个函数类型,输入的是两个int类型,返回值是一个int类型func do(f Op, a, b int) int {return f(a, b) // 函数类型变量可以直接用来进行函数调用
}func add(a, b int) int {return a + b
}func sub(a, b int) int {return a - b
}

Go语言中有名函数的函数名可以看作函数类型的常量,可以直接使用函数名调用函数,也可以直接赋值给函数类型变量,后续通过该变量来调用该函数:

func main() {fmt.Println(add(1, 2)) //直接调用f := add               // 直接赋给函数类型变量fmt.Println(f(3, 5))}func add(a, b int) int {return a + b
}
b. 匿名函数

匿名函数是指不需要定义函数名的一种函数实现方式,由一个不带函数名的函数声明和函数体组成。

① 在定义时调用匿名函数。匿名函数可以在声明后调用

func main() {func(name string) {fmt.Println("hello", name)}("Lebron")
}

② 将匿名函数赋值给变量。匿名函数可以被赋值。匿名函数本身就是一种值,可以方便地保存在各种容器中,实现回调函数和操作的封装。

func main() {// 将匿名函数保存在f()中f := func(name string) {fmt.Println("hello", name)}f("James")
}

③ 匿名函数用作回调函数。例如:实现对切片的遍历操作。

func main() {// 使用匿名函数打印切片内容visit([]int{1, 2, 3, 4}, func(v int) {fmt.Printf("%d\t", v) // 1       2       3       4})
}// 遍历切片的每个元素,通过给定函数进行元素访问
func visit(list []int, f func(int)) {for _, v := range list {f(v)}
}

④ 使用匿名函数实现操作封装。例如:将匿名函数作为map的键值,通过命令行参数动态调用匿名函数。

var skillParam = flag.String("skill", "", "skill to perform") // 6func main() {flag.Parse()var skill = map[string]func(){"fire": func() {fmt.Println("chicken fire")},"run": func() {fmt.Println("soldier run")},"fly": func() {fmt.Println("angel fly")},}if f, ok := skill[*skillParam]; ok {f()} else {fmt.Println("skill not found")}}
  • 第1行,定义命令行参数skill,从命令行输入--skill,可以将=后的字符串传入skillParam指针变量。
  • 第4行,解析命令行参数,解析完成后,skillParam指针变量将指向命令行传入的值。
  • 第5行,定义一个从字符串映射到func()的map,然后填充这个map。
  • 第6~14行,初始化map的键值对,值为匿名函数。
  • 第16行,skillParam是一个*string类型的指针变量,使用*skillParam获取命令行传过来的值,并在map中查找对应命令行参数指定的字符串的函数。
  • 如果在map定义中存在这个参数就调用,否则打印“skill not found”。

⑤ 匿名函数可以看作函数字面量,所有直接使用函数类型变量的地方都可以由匿名函数代替。匿名函数可以直接赋值给函数变量,可以当作实参,也可以作为返回值,还可以直接被调用:

// 匿名函数被直接复制函数变量
var sum1 = func(a, b int) int {return a + b
}// 匿名参数作为参数
func doinput(f func(int, int) int, a, b int) int {return f(a, b)
}// 匿名函数作为返回值
func wrap(op string) func(int, int) int {switch op {case "add":return func(a, b int) int {return a + b}case "sub":return func(a, b int) int {return a + b}default:return nil}
}func main() {// 匿名函数直接被调用defer func() {if err := recover(); err != nil {fmt.Println(err)}}()fmt.Println(sum1(1, 2)) // 3// 匿名参数作为实参result := doinput(func(x, y int) int {return x + y}, 1, 2)fmt.Println(result) // 3opFunc := wrap("add")re := opFunc(2, 3) // 5fmt.Printf("%d\n", re)
}
6、函数类型实现接口

函数和其他类型一样,其他类型能够实现接口,函数也可以实现接口。

a. 结构体实现接口

结构体实现Invoker接口。

// 调用器接口
type Invoker interface {// 需要实现一个Call方法Call(interface{})
}// 结构体类型
type Struct struct{}// 实现Invoker的Call
func (s *Struct) Call(p interface{}) {fmt.Println("from struct", p)
}func main() {// 声明接口变量var invoker Invoker// 实例化结构体s := new(Struct)// 将实例化的结构体复制到接口invoker = s// 使用接口调用实例化结构体的方法Struct.Callinvoker.Call("hello")
}
  • s类型为*Struct,已经实现了Invoker接口类型,因此赋值给invoker时是成功的。
  • 通过接口的Call()方法,传入hello,此时将调用Struct结构体的Call()方法。
b. 函数体实现接口

函数的声明不能直接实现接口,需要将函数定义为类型后,使用类型实现结构体,当类型方法被调用时,还需要调用函数本体

// 调用器接口
type Invoker interface {// 实现Call方法Call(interface{})
}// 函数定义为类型
type FuncCaller func(interface{})// 实现Invoker的Call
func (f FuncCaller) Call(p interface{}) {// 调用f()函数本体f(p)
}func main() {// 声明接口变量var invoker Invoker// 将匿名函数转为FuncCaller类型,再赋值给接口invoker = FuncCaller(func(v interface{}) {fmt.Println("from function", v)})// 使用接口调用FuncCaller.Call,内部会调用函数本体invoker.Call("hello")
}
  • 将func(v interface{}){}匿名函数转换为FuncCaller类型,此时FuncCaller类型实现了Invoker的Call()方法,赋值给invoker接口是成功的。
7、defer
a. 用途

在进行I/O操作时,如果遇到错误,需要提前返回,而返回之前应该关闭相应的资源,否则容易造成资源泄露等问题,这时就需要使用defer语句来解决这些问题。

在defer后指定的函数会在函数退出前调用。如果多次调用defer,那么defer采用后进先出的模式:

func main() {for i := 0; i < 5; i++ {defer fmt.Printf("%d\t", i) // 4       3       2       1       0}
}

defer的使用可以总结为以下几点:

(1)defer后面必须是函数或方法的调用,不能是语句,否则编译器会提示expression in defer mustbe function call错误。

(2)defer函数的实参在注册时通过值复制传递。

(3)defer语句必须先注册后才能执行,如果defer位于return之后,则defer因为没有注册,不会执行。

(4)在主动调用os.Exit(int)退出进程时,defer将不再被执行。

(5)defer的好处是可以在一定程度上避免资源泄露,特别是在有很多return语句,有多个资源需要关闭的场景中,很容易漏掉资源的关闭操作。

(6)使用defer改写后,在打开资源无报错后直接调用defer关闭资源,养成这样的编程习惯后,就很难忘记资源的释放。

(7)defer语句的位置不当有可能导致panic,一般defer语句放在错误检查语句之后。

(8)defer也有明显的副作用:defer会推迟资源的释放,defer尽量不要放到循环语句中,而应将大函数内部的defer语句单独拆分成一个小函数。

(9)defer中最好不要对有名的返回值参数进行操作,否则也会出现错误。

b. 执行顺序

多个defer语句的执行顺序为“逆序”,defer、return、返回值三者的执行逻辑如下:

  • defer最先执行一些收尾工作;
  • 然后return执行,return负责将结果写入返回值中;
  • 最后函数携带当前返回值退出。

也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行

func main() {fmt.Println("defer begin...")//将defer 放入延迟调用栈defer fmt.Println(0)defer fmt.Println(1)//最后一个放入 , 位于栈顶, 最先调用defer fmt.Println(2)fmt.Println("defer end!!!")
}
  • 代码的延迟顺序与最终的执行顺序是反向的。
  • 延迟调用在defer所在的函数结束时进行,函数结束可以是正常返回时,也可以是发生宕机时。

8、闭包

闭包 = 函数 + 引用环境

闭包是由函数及其相关引用环境组合而成的实体,一般通过在匿名函数中引用外部函数的局部变量或包全局变量构成。

闭包对闭包外的环境引入是直接引用,编译器检测到闭包,会将闭包引用的外部变量分配到堆上。

如果函数返回的闭包引用了该函数的局部变量(参数或函数内部变量):

  • 多次调用该函数,返回的多个闭包所引用的外部变量是多个副本,原因是每次调用函数都会为局部变量分配内存。
  • 用一个闭包函数多次,如果该闭包修改了其引用的外部变量,则每一次调用该闭包对该外部变量都有影响,因为闭包函数共享外部引用。

闭包最初的目的是减少全局变量,在函数调用的过程中隐式地传递共享变量,有其有用的一面;但是这种隐秘的共享变量的方式带来的坏处是不够直接,不够清晰,除非是非常有价值的地方,否则一般不建议使用闭包。

对象是附有行为的数据,而闭包是附有数据的行为,类在定义时已经显式地集中定义了行为,但是闭包中的数据没有显式地集中声明的地方,这种数据和行为耦合的模型不是一种推荐的编程模型,闭包仅仅是锦上添花的东西,不是不可缺少的。

闭包对它作用域上部的变量可以进行修改,修改引用的变量会对变量进行实际修改:

func main() {// 准备一个字符串str := "Apple"// 创建一个匿名函数foo := func() {// 匿名函数中访问strstr = "Orange"}// 调用匿名函数foo()fmt.Println(str) // Orange
}
  • 在匿名函数中并没有定义str,str的定义在匿名函数之前,此时,str就被引用到了匿名函数中,形成了闭包。
  • 执行闭包,此时str发生修改,变为Orange。

注意:如果函数返回的闭包引用的是全局变量,则多次调用该函数返回的多个闭包引用的都是同一个全局变量。同理,调用一个闭包多次引用的也是同一个全局变量。此时如果闭包中修改了全局变量值的逻辑,则每次闭包调用都会影响全局变量的值。使用闭包是为了减少全局变量,所以,闭包引用全局变量不是好的编程方式。

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

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

相关文章

GIT提交、回滚等基本操作记录

1、add文件时warning: LF will be replaced by CRLF in .idea/workspace.xml. 原因&#xff1a;windows中的换行符为 CRLF&#xff0c; 而在Linux下的换行符为LF&#xff0c;所以在执行add . 时会出现以下提示 解决&#xff1a;git config core.autocrlf false 2、GIT命令&…

pip 国内镜像源

pip 国内镜像源 部分可用的pip国内镜像源有下面这些&#xff1a; 阿里云 http://mirrors.aliyun.com/pypi/simple/ 中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/ 豆瓣 http://pypi.douban.com/simple Python官方 https://pypi.python.org/simple/ v2ex http://pypi…

【微信】公众号授权绑定登录流程详解

在做微信公众号开发时&#xff0c;经常需要对公众号上面的菜单做授权登录&#xff0c;如果是首次登录还需要做微信openId和系统账号的绑定操作。 这里做如下假设&#xff1a; 系统前端地址&#xff1a;http://www.test.com系统接口地址&#xff1a;http://api.test.com需要打…

Activity启动过程

首语 Activity作为Android四大组件中使用最频繁的组件&#xff0c;也是和用户交互最多的组件&#xff0c;可见它在Android技术体系的核心地位&#xff0c;了解Activity的启动过程可以帮助我们更好的了解Android系统和使用Activity。 文章目录 首语Activity启动过程根Activity…

加强->servlet->tomcat

0什么是servlet jsp也是servlet 细细体会 Servlet 是 JavaEE 的规范之一&#xff0c;通俗的来说就是 Java 接口&#xff0c;将来我们可以定义 Java 类来实现这个接口&#xff0c;并由 Web 服务器运行 Servlet &#xff0c;所以 TomCat 又被称作 Servlet 容器。 Servlet 提供了…

打造完备数据生态,「开放互信、合作共赢」: 拓数派亮相2023龙蜥操作系统大会

拓数派始终持「开放互信&#xff0c;合作共赢」的理念&#xff0c;通过积极建立合作伙伴生态网络、构建生态工具、打造活跃的技术和用户社区等方式&#xff0c;构筑更加完善的数据生态体系&#xff0c;为用户带来更加便捷的使用体验。2023年12月17-18日&#xff0c;由开放原子开…

如何实现无公网ip环境访问vscode远程开发【内网穿透】

文章目录 前言1、安装 OpenSSH2、vscode 配置 ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu 安装 cpolar 内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定 TCP 端口地址5.1 保留一个固定 TCP 端口地址5.2 配置固定 TCP 端口地址5.3 测试固定公网地址远…

使用Vue3开发学生管理系统模板5 学生家长信息的实现

字段设计 IDname&#xff1a;姓名&#xff0c;字符串&#xff0c;最长36个字符gender&#xff1a;性别&#xff0c;字符串&#xff0c;最长6个字符串age&#xff1a;年龄&#xff0c;数字类型phone&#xff1a;电话&#xff0c;字符串&#xff0c;最长20个字符student_id&…

无需手动搜索!轻松创建IntelliJ IDEA快捷方式的Linux教程

轻松创建IntelliJ IDEA快捷方式的Linux教程 一、IntelliJ IDEA简介二、在Linux系统中创建快捷方式的好处三、命令行创建IntelliJ IDEA快捷方式四、图形界面创建IntelliJ IDEA快捷方式五、常见问题总结 一、IntelliJ IDEA简介 IntelliJ IDEA是一个由JetBrains搞的IDE&#xff0…

搭建FTP服务器详细介绍

一.FTP简介 &#xff11;.&#xff11;什么是FTP &#xff11;.&#xff12;FTP服务器介绍 &#xff11;.&#xff13;FTP服务器优缺点 二.FTP服务器的搭建与配置 2.1 开启防火墙 2.2创建组 2.3创建用户 2.4安装FTP服务器 2.5配置FTP服务器 &#xff12;.&#xff…

麒麟KYLINOS _ 传书 _ 如何传输文件?

原文链接&#xff1a;麒麟KYLINOS | 传书 | 如何传输文件&#xff1f; hello&#xff0c;大家好啊&#xff01;今天我要给大家介绍的是在麒麟KYLINOS操作系统上使用自带的文件传输软件——传书。在日常工作和生活中&#xff0c;我们经常需要在不同设备之间传输文件和信息。传书…

论文阅读《Restormer: Efficient Transformer for High-Resolution Image Restoration》

论文地址:https://openaccess.thecvf.com/content/CVPR2022/html/Zamir_Restormer_Efficient_Transformer_for_High-Resolution_Image_Restoration_CVPR_2022_paper.html 源码地址:https://github.com/swz30/Restormer 概述 图像恢复任务旨在从受到各种扰动(噪声、模糊、雨滴…

账号租号平台PHP源码,支持单独租用或合租使用

源码简介 租号平台源码&#xff0c;采用常见的租号模式。 平台的主要功能如下&#xff1a; 支持单独租用或采用合租模式&#xff1b; 采用易支付通用接口进行支付&#xff1b; 添加邀请返利功能&#xff0c;以便站长更好地推广&#xff1b; 提供用户提现功能&#xff1b;…

超强整理,Web自动化测试-验证码/cookie机制(详全)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、后台登录案例 …

Springboot整合Elastic-job

一 概述 Elastic-Job 最开始只有一个 elastic-job-core 的项目&#xff0c;定位轻量级、无中心化&#xff0c;最核心的服务就是支持弹性扩容和数据分片&#xff01;从 2.X 版本以后&#xff0c;主要分为 Elastic-Job-Lite 和 Elastic-Job-Cloud 两个子项目。esjbo官网地址 Ela…

【五】【C语言\动态规划】删除并获得点数、粉刷房子、买卖股票的最佳时机含冷冻期,三道题目深度解析

动态规划 动态规划就像是解决问题的一种策略&#xff0c;它可以帮助我们更高效地找到问题的解决方案。这个策略的核心思想就是将问题分解为一系列的小问题&#xff0c;并将每个小问题的解保存起来。这样&#xff0c;当我们需要解决原始问题的时候&#xff0c;我们就可以直接利…

Docker部署Plik临时文件上传系统并实现远程访问设备上传下载文件

文章目录 1. Docker部署Plik2. 本地访问Plik3. Linux安装Cpolar4. 配置Plik公网地址5. 远程访问Plik6. 固定Plik公网地址7. 固定地址访问Plik 本文介绍如何使用Linux docker方式快速安装Plik并且结合Cpolar内网穿透工具实现远程访问&#xff0c;实现随时随地在任意设备上传或者…

[Angular] 笔记 11:可观察对象(Observable)

chatgpt: 在 Angular 中&#xff0c;Observables 是用于处理异步数据流的重要工具。它们被广泛用于处理从异步操作中获取的数据&#xff0c;比如通过 HTTP 请求获取数据、定时器、用户输入等。Observables 提供了一种机制来订阅这些数据流&#xff0c;并可以在数据到达时执行相…

代码随想录算法训练营day1|704.二分查找、27.移除元素

第一章 数组 part01 今日任务 数组理论基础&#xff0c;704. 二分查找&#xff0c;27. 移除元素 详细布置 数组理论基础 文章链接&#xff1a;代码随想录 题目建议&#xff1a; 了解一下数组基础&#xff0c;以及数组的内存空间地址&#xff0c;数组也没那么简单。 704. 二…

Jupyter Notebook 开启远程登录

Jupyter Notebook可以说是非常好用的小工具&#xff0c;但是不经过配置只能够在本机访问 安装jupyter notebook conda install jupyter notebook 生成默认配置文件 jupyter notebook --generate-config 将会在用户主目录下生成.jupyter文件夹&#xff0c;其中jupyter_noteb…