上一篇 Go圣经-学习笔记之复合类型(三)
 下一篇 Go圣经-学习笔记之函数值(二)
 函数声明
 下面给出函数的四种声明方法:
 func add(x int, y int) int { return x+y }  func add(x, y int) (z int) { z = x + y ; return z }  func first(x int, _ int) int { return x } func second(int, int) int { return 0 } 
 存在必有其存在的价值,那后两者存在的应用场景在哪里呢?我表示持续懵逼中......, 我写了这个场景,看可以不?
 type Interface interface {     Add(int, int) int     Sub(int, int) int     Mul(int, int) int }  type Idem struct{}  func (i Idem) Add(x int, y int) int {     return x + y }  func (i Idem) Sub(int, int) int {     return 0 }  func (i Idem) Mul(x int, _ int) int {     return x * 2 } 
 Go语言有一个非常核心的特性:组合,上一篇文章也提及了。如果一个对象是由多个接口、匿名方法集或者对象的自身行为集构成。如果这个对象觉得有些行为是它忽略或者不想要的,这时候就可以采用上述的后两者方法去声明方法,但是这个是方法的声明,不是函数的声明。能想到的就这么多,其他我就不知道了。
 函数递归
 对于递归的运用,我们通过一个url,获取网页数据流,然后通过html标准库解析数据,获取各个标签元素节点。
 说明: 运用html标准库解析完数据流,得到的是一个多叉树的元素节点,包括:文档节点、文本节点、元素标签节点、评论节点、注脚节点和错误节点。由这六个节点构成一颗多叉树
 DEMO实例代码片段
 ********************************************************************** // html标准库相关说明: type Node struct {     Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node      Type      NodeType     DataAtom  atom.Atom     Data      string     Namespace string     Attr      []Attribute } func Parse(r io.Reader) (*Node, error) **********************************************************************   var (     purl = flag.String("url", "https://www.golang.org", "website url") )  func main() {     var (         resp *http.Response         err  error         node *html.Node     )     flag.Parse()     if resp, err = http.Get(*purl); err != nil {         log.Fatal(err.Error())         return     }     node, err = html.Parse(resp.Body)     if err != nil {         log.Fatal(err.Error())         return     }     resp.Body.Close()     // 我们采用多叉树的深度遍历算法,并打印所有节点元素url。     for _, link := range visit(nil, node) {         fmt.Println(link)     } }  func visit(links []string, node *html.Node) []string {     if node.Type == html.ElementNode && node.Data == "a" {         for _, attr := range node.Attr {             links = append(links, attr.Val)         }     }     for child := node.FirstChild; child != nil; child = child.NextSibling {         links = visit(links, child)     }     return links } 
 递归元素的存储是由stack数据结构存储的。C++等主流语言的stack栈是由固定大小的,如果过多递归会导致栈内存溢出。但是Go语言的栈是动态分配的,按需分配,一般不需要考虑stack溢出安全问题,但是还是要多多尽量节约内存
 大家吐槽比较多的函数返回error
 在Go语言标准库使用过程中,几乎每个函数或者方法返回参数的最后一个类型都是error,类似:
 func xx(sss type, ...) (xxx type, ..., err error) 
 比如,我们经常这样做:
 *************************************************** // ioutil标准库: func ReadAll(r io.Reader) (data []byte, err error) ***************************************************  if bts, err := ioutil.ReadAll(r); err !=nil{     err = errors.Wrap(err, "xxxxx")     return } 
 大家觉得如果调用的每个方法或者函数,都要处理error,大家比较崩溃。Go语言官方是这样解释返回error的设计的:
 由于对于某个应该在控制流程中处理的错误而言,将这个错误以异常的形式抛出会混乱对错误的描述,这通常会导致一些糟糕的后果。当某个程序错误被当作异常处理后,这个错误会将堆栈信息返回给用户,这些信息复杂且无用,无法帮助定位错误
 所以Go语言想通过控制流if,return语句处理异常,这使得编程人员能更多的关注错误处理。
 在这里给大家提供用于error追踪的包,很好用。github地址:errors
 // 我一般这样使用 // 业务逻辑层 func AddSaleOrder(so *SaleOrder, o *orm.Ormer) (retCode int, err error) {     if _, err = (*o).Insert(so); err != nil {         err = errors.Wrap(err, "AddSaleOrder")         retCode = consts.DB__INSERT_ERROR         return     }     return }  // 控制层 func (s *SaleOrderController) AddSaleOrder() {     var so *models.SaleOrder     ......     if retCode, err = models.AddSaleOrder(so, nil); err !=nil {         Logger.Error(err.Error()) // 这里的err.Error() 会返回整个的error跟踪调用链         s.Data["json"] = map[string]interface{}{             "err_code": retCode,             "err_msg": errors.Cause(err).Error(), // 这个返回err调用链中产生错误最初始的形态。         }         s.ServeJSON()         return     }     s.Data["json"] = map[string]interface{}{         "err_code": 0,         "err_msg": "",     }     s.ServeJSON()     return } 
 这样做的好处是:我们服务端既可以记录错误产生的整个调用链跟踪,同时,前端也返回了关键错误。屏蔽了不想要用户看到的信息。