教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Go语言学习笔记 - 第五章 函数(The Go Programming Language)

Go语言学习笔记 - 第五章 函数(The Go Programming Language)

发布时间:2022-01-01   编辑:jiaochengji.com
教程集为您提供Go语言学习笔记 - 第五章 函数(The Go Programming Language)等资源,欢迎您收藏本站,我们将为您提供最新的Go语言学习笔记 - 第五章 函数(The Go Programming Language)资源
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"/></svg><h2>第五章 函数</h2> <h3>5.1函数声明</h3>

划重点

<ul><li>函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。</li></ul><blockquote> <pre><code>func name(parameter-list) (result-list) { body } </code></pre> </blockquote> <ul><li>形参为局部变量</li><li>没有返回值或者一个无名变量时,返回的括号可以省略。</li><li>返回值也可以像形式参数一样被命名,并被声明成一个局部变量。</li><li>如果形参或返回值有相同的类型,参数类型可以省略</li></ul><blockquote> <pre><code>func f(i, j, k int, s, t string) { /* ... */ } func f(i int, j int, k int, s string, t string) { /* ... */ } </code></pre> </blockquote> <ul><li>函数的类型被称为函数的标识符</li><li>Go语言没有默认参数值</li><li>函数的形参和有名返回值作为函数最外层的局部变量,被存储在相同的词法块中。</li><li>实参通过值的方式传递,因此函数的形参是实参的拷贝</li><li>对形参进行修改不会影响实参。引用类型,如指针,slice(切片)、map、function、channel等类型,实参可能会被修改</li><li>没有函数体的函数声明,这表示该函数不是以Go实现的</li></ul><blockquote> <pre><code>package math func Sin(x float64) float //implemented in assembly language </code></pre> </blockquote> <ul><li/></ul><h3>
5.2递归</h3>

划重点

<ul><li>golang.org/x/… 目录下存储了一些由Go团队设计、维护,对网络编程、国际化文件处理、移动平台、图像处理、加密解密、开发者工具提供支持的扩展包</li><li>大部分编程语言使用固定大小的函数调用栈,常见的大小从64KB到2MB不等,递归深度会导致栈溢出。</li><li>Go语言使用可变栈,栈的大小按需增加(初始时很小)。这使得我们使用递归时不必考虑溢出和安全问题。</li></ul>

常用库及方法

<ul><li><code>golang.org/x/net/html</code></li><li><code>html.Parse</code> <code>html.ElementNode</code> <code>html.Node.FirstChild</code> <code>html.Node.NextSibling</code> <code>html.NewTokenizer</code></li></ul><h3>
5.3多返回值</h3>

划重点

<ul><li>在Go中,一个函数可以返回多个值</li><li>许多标准库中的函数返回2个值,一个是期望得到的返回值,另一个是函数出错时的错误信息</li><li>通过<code>fmt.Errorf(§7.8)</code>输出详细的错误信息</li><li><code>resp.Body</code>需要确保被关闭,释放网络资源。虽然Go的垃圾回收机制会回收不被使用的内存,但是这不包括操作系统层面的资源,比如打开的文件、网络连接。因此我们必须显式的释放这些资源。</li><li>函数调用者必须显式的将返回值分配给变量</li><li>对于可以接受多参数的函数,可将多返回值的函数作为该函数的参数</li><li><code>bare return</code>返回值除了类型还带有变量名,那么该函数的<code>return</code>语句可以省略操作数。</li></ul>

常用库及方法

<h3>
5.4错误</h3>

划重点

<ul><li><code>panic</code>是来自被调函数的信号,表示发生了某个已知的bug。一个良好的程序永远不应该发生panic异常。</li><li>通过最后一个返回值,来传递错误信息。如果错误原因只有一个,可以设置为布尔值,这时可命名为ok</li><li>内置的<code>error</code>是接口类型</li><li><code>error</code>类型可能是<code>nil</code>或者<code>non-nil</code>,<code>error</code>的<code>Error</code>可以打印错误<code>string</code></li><li>Go有别于那些将函数运行失败看作是异常(exception)的语言,这些语言会将错误当做异常处理,并输出堆栈信息,无法帮助定位错误。</li><li>Go使用控制流机制(如if和return)处理异常</li></ul>

常用库及方法

<ul><li><code>strings.Contains</code></li><li><code>strconv.FormatBool</code></li><li><code>time.Date</code> <code>time.Time</code></li><li><code>cache.Lookup</code></li><li><code>error.Error</code></li></ul><h4>
5.4.1错误处理策略</h4>

划重点

<ul><li>常用的5中处理错误的方式 <ul><li>传播错误,调用者直接返回被调用函数的返回值</li><li>偶发性或者不可预知的错误,使用重新尝试失败的操作</li><li>输出错误信息并结束程序,<code>os.Exit()</code>,注意该策略只应在<code>main</code>中执行</li><li>输出错误信息,不中断程序,使用<code>log</code>包或<code>fmt.Fprintf(os.Stderr...</code></li><li>直接忽略掉错误</li></ul></li><li><code>fmt.Errorf</code>函数使用<code>fmt.Sprintf</code>格式化错误信息并返回</li><li><code>log</code>包中的所有函数会为没有换行符的字符串增加换行符</li><li/></ul>

常用库及方法

<ul><li><code>fmt.Errorf</code></li><li><code>os.Exit()</code> <code>os.RemoveAll</code></li><li><code>log.Fatalf</code> <code>log.SetPrefix()</code> <code>log.SetFlags()</code></li><li><code>ioutil.TempDir</code></li></ul><h4>
5.4.2文件结尾错误(EOF)</h4>

划重点

<ul><li><code>io</code>包中任何由文件结束引起的读取失败都返回同一个错误——<code>io.EOF</code></li><li/></ul><h3>
5.5函数值</h3>

划重点

<ul><li>函数可以看做是一种值,第一类值(first-class values)</li><li>函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回</li><li>函数类型的零值是nil。调用值为nil的函数值会引起panic错误</li><li>函数值可以与nil比较</li><li>函数值之间是不可比较的,也不能用函数值作为map的key</li><li/></ul>

常用库及方法

<ul><li><code>strings.Map</code></li><li/></ul><h3>
5.6 匿名函数</h3>

划重点

<ul><li>匿名函数存在变量引用,这是函数值属于引用类型和函数值不可比较的原因。Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包。</li><li>当匿名函数需要被递归调用时,必须首先声明一个变量,再将匿名函数赋值给这个变量,必须分两步,无法直接绑定,无法递归调用。</li></ul><blockquote> <pre><code>visitAll := func(items []string) { // ... visitAll(m[item]) // compile error: undefined: visitAll // ... } </code></pre> </blockquote> <ul><li><code>append</code>的参数“<code>f(item)...</code>”,会将<code>f</code>返回的一组元素一个个添加到
<code>worklist</code>中。<code>worklist = append(worklist, f(item)...)</code></li><li/></ul>

常用库及方法

<ul><li><code>sort.Strings</code></li><li><code>http.StatusOK</code></li><li><code>resp, err := http.Get(url)</code> <code>resp.Request.URL.Parse</code></li><li/></ul><h4>
5.6.1 警告:捕获迭代变量</h4>

划重点

<ul><li>这是,Go词法作用域的一个陷阱。</li><li>下面的例子是错误的,函数值中记录的是循环变量的内存地址,而不是循环变量某一时刻的值,for循环已完成,dir中存储的值等于最后一次迭代的值。</li></ul><blockquote> <pre><code>var rmdirs []func() for _, dir := range tempDirs() { os.MkdirAll(dir, 0755) rmdirs = append(rmdirs, func() { os.RemoveAll(dir) // NOTE: incorrect! }) } </code></pre> </blockquote>

解决这个问题可以:

<blockquote> <pre><code>for _, dir := range tempDirs() { dir := dir // declares inner dir, initialized to outer dir // ... } </code></pre> </blockquote> <ul><li><mark>如果你使用go语句或者defer语句会经常遇到此类问题</mark></li></ul><h3>
5.7可变参数</h3>

划重点

<ul><li>参数数量可变的函数称为为可变参数函数,典型的是<code>fmt.Printf</code></li><li>在声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号“…”,这表示该函数会接收任意数量的该类型参数。</li><li>调用者隐式的创建一个数组,并将原始参数复制到数组中,再把数组的一个切片作为参数传给被调函数</li><li><mark>如果原始参数已经是切片类型,只需在最后一个参数后加上省略符<code>...</code></mark>,下面的调用是一致的:</li></ul><blockquote> <pre><code>fmt.Println(sum(1, 2, 3, 4)) // "10" //---- values := []int{1, 2, 3, 4} fmt.Println(sum(values...)) // "10" </code></pre> </blockquote> <ul><li>可变参数函数和以切片作为参数的函数是不同的,虽然看起来类似</li></ul><blockquote> <pre><code>func f(...int) {} func g([]int) {} fmt.Printf("%T\n", f) // "func(...int)" fmt.Printf("%T\n", g) // "func([]int)" </code></pre> </blockquote> <h3>
5.8Deferred函数</h3>

划重点

<ul><li><code>resp.Body.close</code>关闭网络连接</li><li><code>defer</code>用于延迟执行操作,具有以下特性: <ul><li><code>defer</code>后面的函数会被延迟执行</li><li><code>defer</code>后的函数会在包含该defer的函数执行完毕执行</li><li>无论<code>return</code>或<code>panic</code>,<code>defer</code>都会执行</li><li>一个函数中可以有多个<code>defer</code>,执行顺序和声明顺序相反</li></ul></li><li><code>defer</code>语句经常被用于处理成对的操作,如<mark>打开、关闭、连接、断开连接、加锁、释放锁</mark></li><li>调试复杂程序时,<code>defer</code>机制也常被用于记录何时进入和退出函数</li><li>一个有意思的用法<mark>defer trace(“bigSlowOperation”)()</mark>:</li></ul><blockquote> <pre><code>func bigSlowOperation() { defer trace("bigSlowOperation")() // don't forget the extra >parentheses // ...lots of work… time.Sleep(10 * time.Second) // simulate slow operation by >sleeping } func trace(msg string) func() { start := time.Now() log.Printf("enter %s", msg) return func() { log.Printf("exit %s (%s)", msg,time.Since(start)) } } </code></pre> </blockquote> <ul><li>因为<code>defer</code>在<code>return</code>后执行,所以可以通过匿名函数拿到包括返回值在内的所有变量的值。</li><li>被延迟执行的匿名函数甚至可以修改函数返回给调用者的返回值</li><li>在循环体中的<code>defer</code>语句需要注意,他们不会在循环体中针对某一次循环执行,而是在整个函数执行完毕后,才会执行。比较有效的做法是把其中的逻辑独立出来形成一个函数。</li><li>在关闭文件时,没有对<code>f.close</code>采用<code>defer</code>机制,因为这会产生一些微妙的错误。许多文件系统,尤其是NFS,写入文件时发生的错误会被延迟到文件关闭时反馈。如果没有检查文件关闭时的反馈信息,可能会导致数据丢失,而我们还误以为写入操作成功。
如果要使用<code>defer f.close()</code>,可以使用上面的通过<code>defer 匿名函数</code>的方式,在defer里面改写返回的<code>error</code>信息 <blockquote> <pre><code>defer func() { // Close file, but prefer error from Copy, if any. if closeErr := f.Close(); err == nil { err = closeErr } }() </code></pre> </blockquote> </li><li/></ul>

常用库及方法

<ul><li><code>resp.Header.Get("Content-Type")</code> <code>resp.Body.close</code></li><li><code>strings.HasPrefix</code></li><li><code>sync.Mutex</code> <code>sync.Mutex.Lock</code> <code>sync.Mutex.Unlock</code></li><li><code>resp.Request.URL.Path</code></li><li><code>path.Base</code></li><li><code>os.Create</code> <code>f.Close()</code></li><li><code>io.Copy</code></li></ul><h3>
5.9Panic异常</h3>

划重点

<ul><li><code>panic</code>异常是指只能在运行时检查,如数组访问越界、空指针引用等</li><li>当<code>panic</code>异常发生时,程序会中断运行,并立即执行在该goroutine中被延迟的函数(defer 机制),程序崩溃并输出日志信息</li><li>日志信息包括panic value和函数调用的堆栈跟踪信息。panic value通常是某种错误信息。</li><li>panic异常除了来自运行时,也可以直接调用内置的panic函数,panic函数接受任何值作为参数</li><li>Go的panic机制类似于其他语言的异常,但panic的适用场景有一些不同。golang建议尽量使用Go提供的错误机制,而不是panic,尽量避免程序的崩溃。</li><li>为了方便诊断问题,runtime包允许程序员输出堆栈信息</li><li>在Go的panic机制中,延迟函数的调用在释放堆栈信息之前</li><li/></ul>

常用库及方法

<ul><li><code>panic()</code></li><li><code>regexp.Compile</code> <code>regexp.MustCompile</code></li><li><code>template.Must</code></li><li><code>runtime.Stack</code> <code>os.Stdout.Write</code></li><li/></ul><h3>
5.10Recover捕获异常</h3>

划重点

<ul><li>对panic正常不应该做处理,但有时可以从异常panic中恢复以帮助在程序崩溃前做一些操作。比如,web服务器可以在崩溃前关闭连接。</li><li>如果要恢复<code>panic</code>,比如在deferred函数中调用了内置函数<code>recover</code>,并且定义该<code>defer</code>语句的函数发生了<code>panic</code>异常,<code>recover</code>会使程序从<code>panic</code>中恢复,并返回<code>panic value</code>。导致<code>panic</code>异常的函数不会继续运行,但能正常返回。在未发生<code>panic</code>时调用<code>recover</code>,<code>recover</code>会返回<code>nil</code></li><li>对<code>panic</code>异常的恢复处理要慎重,<code>panic</code>后的恢复无法保证包级变量不出问题。。比如,对数据结构的一次重要更新没有被完整完成、文件或者网络连接没有被关闭、获得的锁没有被释放。此外,如果写日志时产生的panic被不加区分的恢复,可能会导致漏洞被忽略。</li><li>不该试图去恢复其他包引起的panic;</li><li>公有的API应该将函数的运行失败作为error返回,而不是panic</li><li>不应该恢复一个由他人开发的函数引起的panic,比如说调用者传入的回调函数,因为你无法确保这样做是安全的</li><li>有时我们很难完全遵循规范,举个例子,net/http包中提供了一个web服务器,不能因为某个用户的处理Handler函数引发的panic异常,杀掉整个进程;web服务器遇到处理函数导致的panic时会调用recover,输出堆栈信息,继续运行。</li><li>有些情况下,我们无法恢复。某些致命错误会导致Go在运行时终止程序,如内存不足。</li><li/></ul>

常用库及方法

<ul><li><code>recover()</code></li></ul> 到此这篇关于“Go语言学习笔记 - 第五章 函数(The Go Programming Language)”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
想系统学习GO语言(Golang
go 语言学习历程
Go 开发关键技术指南 | 为什么你要选择 Go?(内含超全知识大图)
Golang学习笔记(五):Go语言与C语言的区别
【Go语言入门系列】(八)Go语言是不是面向对象语言?
golang是面向对象吗_Go是面向对象的吗?
go run main.go 参数_Go语言入门:Hello world
Golang笔记:语法,并发思想,web开发,Go微服务相关
Go语言学习笔记 - 第五章 函数(The Go Programming Language)
Go语言发展历史、核心、特性及学习路线

[关闭]
~ ~