掌握这些Go语言特性,你的水平将提升N个档次(二)

前言: 你们好,我是asong,这是个人第二篇原创文章。上一文介绍了切片、变量声明、defer三个知识点(回顾上文,关注公众号便可进行阅读),这一文将继续介绍其余Go语言特性,废话很少说,直接上干货。面试

1. 指针和引用

在Go语言中只有一种参数传递的规则,那就是值拷贝,其包含两种含义:sql

  • 函数参数传递时使用的值拷贝
  • 实例赋值给接口变量,接口对实例的引用是值拷贝

咱们在使用过程当中会发现有时明明是值拷贝的地方,结果却修改了变量的内容,有如下两种状况:编程

  • 直接传递的是指针。指针传递一样是值拷贝,但指针和指针副本的值指向的地址是同一个地方,因此能修改实参
  • 参数是复合数据类型,这些符合数据类型内部有指针类型的元素,此时参数的值拷贝并不影响指针的指向。

在Go语言中,复合类型chan、map、slice、interface内部都是经过指针指向具体的数据,这些类型的变量在做为函数参数传递时,实际上至关于指针的副本。咱们能够经过查看源码,看一看他们的底层数据结构:数组

  1. map的底层数据结构:
//src/runtime/map.go1.14
// A header for a Go map.
type hmap struct {
   // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
   // Make sure this stays in sync with the compiler's definition.
   count     int // # live cells == size of map. Must be first (used by len() builtin)
   flags     uint8
   B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
   noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
   hash0     uint32 // hash seed
   buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
   oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
   nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)
   extra *mapextra // optional fields
}
复制代码

经过源码咱们能够分析,其经过buckets指针来间接引用map中的存储结构。 2. slice的底层数据结构:数据结构

//src/reflect/value.go1.14
// sliceHeader is a safe version of SliceHeader used within this package.
type sliceHeader struct {
   Data unsafe.Pointer
   Len  int
   Cap  int
}
复制代码

slice则采用uinptr指针指向底层存放数据的数组。 3. interface的底层数据结构以下:app

//src/reflect/value.go1.14
// nonEmptyInterface is the header for an interface value with methods.
type nonEmptyInterface struct {
   // see ../runtime/iface.go:/Itab
   itab *struct {
      ityp *rtype // static interface type
      typ  *rtype // dynamic concrete type
      hash uint32 // copy of typ.hash
      _    [4]byte
      fun  [100000]unsafe.Pointer // method table
   }
   word unsafe.Pointer
}
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
   typ  *rtype
   word unsafe.Pointer
}
复制代码

咱们能够看到接口内部经过一个指针指向实例值或地址的副本。 4. chan的底层数据结构以下:less

//src/runtime/chan.go1.14
type hchan struct {
   qcount   uint           // total data in the queue
   dataqsiz uint           // size of the circular queue
   buf      unsafe.Pointer // points to an array of dataqsiz elements
   elemsize uint16
   closed   uint32
   elemtype *_type // element type
   sendx    uint   // send index
   recvx    uint   // receive index
   recvq    waitq  // list of recv waiters
   sendq    waitq  // list of send waiters
   // lock protects all fields in hchan, as well as several
   // fields in sudogs blocked on this channel.
   //
   // Do not change another G's status while holding this lock
   // (in particular, do not ready a G), as this can deadlock
   // with stack shrinking.
   lock mutex
}
复制代码

经过源码咱们能够看出,通道元素的存放地址由buf指针肯定,chan内部的数据也是间接经过指针访问的。函数

2. 函数

Go语言支持匿名函数,其函数名和匿名函数字面量的值有3层含义:工具

  • 类型信息,代表其数据类型是函数类型学习

  • 函数名表明函数的执行代码的起始位置

  • 能够经过函数名进行函数调用,函数调用格式为 func_name(param_list)。在底层执行层面包含如下4部份内容。

  • 准备好参数

  • 修改PC值,跳转到函数代码起始位置开始执行

  • 复制值到函数的返回值栈区

  • 经过RET返回到函数调用的下一条指令处继续执行。

2). 函数的方法设计 咱们在开发时,有时内部会实现两个"同名"的函数或方法,一个首字母大写,用于导出API供外部调用;一个首字母小写,用于实现具体逻辑。通常首字母大写的函数调用首字母小写的函数,同时包装一些功能;首字母小写的函数负责更多的底层细节。 大部分状况下咱们不须要两个同名且只是首字母大小写不一样的函数,只有在函数逻辑很复杂,并且函数在包的内、外部都被调用的状况下,才考虑拆分为两个函数进行实现。一方面减小单个函数的复杂性,另外一方面进行调用隔离。

这种编程方法在database/sql库中体现较明显,有兴趣的能够查看这一部分的源码。 3) 多值返回函数设计 Go语言支持多值返回函数,这里不对多值返回函数基础使用进行介绍,这里只介绍多值返回函数的推荐编程风格方法。 多值返回函数里若是有error或bool类型的返回值,则应该将error或bool做为最后一个返回值。这是一种编程风格,没有对错。Go标准库的写法也遵循这样的规则。当大多数人都使用、遵循这种方法时,若是有人不遵循这种"潜规则",则写出的代码会让别人读起来就会很别扭。因此推荐大家开发时这样进行书写。示例以下:

func testBool() (int ,bool){}
func testError() (int,error){}
复制代码

3. 代码风格

Go做为新世纪开发的一门语言,其做者在代码干净上有了近乎苛刻的要求,有以下几方面的体现: 1) 编译器不能经过未使用的局部变量。 2)"import"未使用的包一样通不过编译。 3)全部的控制结构、函数和方法定义的"i"放到行尾,而不能另起一行。 4)提供go fmt工具格式化代码,使全部的代码风格保持统一。 Go支持使用comma,ok表达式 常见的几个comma,ok 表达式以下。

1. 读取chan值 读取已经关闭的通道,不会阻塞,也不会引发panic,而是一直返回该通道的零值。若判断通道是否已经关闭有两种方法:一种是读取通道的comma,ok 表达式,若是通道已经关闭,则ok的返回值是false,另外一种就是经过range循环迭代。看下面的示例:

import "fmt"
func main()  {
   c := make(chan int)

   go func() {
      c <- 1
      c <- 2
      close(c)
   }()
   for{
      v,ok := <-c
      if ok{
         fmt.Println(v)
      }else {
         break
      }
   }

   /* for v := range c{ fmt.Println(v) } */
}
复制代码
  1. 获取map值 获取map中不存在键的值不会发生异常,而是会返回值类型的零值,若是想肯定map中是否存在key,则可使用获取map值的comma,ok语法。示例以下:
import "fmt"
func main()  {
   m := make(map[string]string)

   v,ok := m["test"]
   //经过ok进行判断
   if !ok{
      fmt.Println("m[test] is nil")
   }else {
      fmt.Println("m[test] =",v)
   }
}
复制代码
  1. 类型断言 类型断言,是Go语言中一个难点。有一点难理解。这一文将不详细介绍用法,后面将会专门写一篇文章进行详细的介绍。 接口断言一般可使用comma,ok语句来肯定接口是否绑定某个实例类型,或者判断接口绑定的实例类型是否实现另外一个接口。能够看src/net/http/request.go中部分代码以下:
858 rc, ok := body.(io.ReadCloser)
1191 if _, ok := r.Body.(*maxBytesReader); !ok {
复制代码

好啦,本文到此结束啦,基本对Go语言基于其余语言的不一样作了一个介绍,由于我也是一个新手,理解的还不是很到位,也在努力学习中,有错误或者有须要更改的地方,请联系我,很是感谢。同时再一次推荐个人公众号:Golang梦工厂,我会不断发表关于Golang方面的知识,面试、我的理解等多个方面,必定对你受益不浅的。公众号搜索:Golang梦工厂,或者直接扫描下方二维码便可。

在这里插入图片描述
相关文章
相关标签/搜索