您的位置 首页 golang

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

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

1. 指针和引用

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

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

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

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

在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中的存储结构。

  1. 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指针指向底层存放数据的数组。

  1. interface的底层数据结构如下:
//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}

我们可以看到接口内部通过一个指针指向实例值或地址的副本。

  1. chan的底层数据结构如下:
//src/runtime/chan.go1.14type 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库中体现较明显,有兴趣的可以查看这一部分的源码。

  1. 多值返回函数设计
    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梦工厂,或者直接扫描下方二维码即可。

在这里插入图片描述

文章来源:智云一二三科技

文章标题:掌握这些Go语言特性,你的水平将提高N个档次(二)

文章地址:https://www.zhihuclub.com/798.shtml

关于作者: 智云科技

热门文章

网站地图