1、多值赋值和短变量声明
(1)相同类型的交量可以在末尾带上类型,例:var x , y int = 1 , 2。 (2)如果不带类型编译器,则可以直接进行类型推断,例:var x, y = 1 ,”tata ”。 (3)不同类型的交量声明和隐式初始化可以使用如下语法: var { x int y string } (4)多位赋值语句中每个变量后面不能都带 类型,例:var x int, y string = 1 ,”ta ta "。 (5)右边是一个返回多值的表达式, 可以是返回多值的函数调用,也可以是 range map slice 等函数 作, 还可以是类型断言。 (6)赋值的左边操作数和右边的单一返回值的表达式的个数一样,逐个从左向右依次对左边的操作数赋值。 (7)赋值的左边操作数和右边的单 返回值的表达式的个数一样,逐个从左 向右依次对边的操作数赋值。 多值赋值语义含义: (1))对左侧操作数中的表达式、索引值进行计算和确定,首先确定左侧的操作数的地址;然后对右侧的赋值表达式进行计算,如果发现右侧的表达式计算引用了左侧的变量,则创建临时变量进行值拷贝,最后完成计算。 (2)从左到右的顺序依次赋值。
2、短变量的声明和赋值(a , b := va , vb)
(1)使用“ := ”操作符 变量的定义和初始化同时完成。 (2)变量名后不要跟任何类型名, 编译器完全靠右边的值进行推导。 (3)支持多值短变量声明赋值。 (4)只能用在函数和类型方法的 部。 (5),在多值短变量声明和赋值时, 至少有一个变量是新创建的局部变量,其他的变量可以复用以前的变量,不是新创建的变量执行的仅仅是赋值。
3、赋值操作符“=”和“:=”的区别:
(1)“=”不会声明并创建新变 ,而是在当前赋值语句所在的作用域由内向外逐个去搜寻变量,如果没有搜索到相同变量名,则报编译错误。 (2)“:=”必须出现在函数或类型方法内部。 (3)“:=”至少要创建一个局部变量并初始化。
4、大多情况下 fo 循环块里的代码是在同一个 oroutine 里运行的,为了避免空间的浪费和 GC 的压力,复用了 range 是代临时变量。语言使用者明白这个规约,在for 循环下调用并发时要复制造代变量后再使用,不要直接引用 for 迭代变量。
5、defer陷阱(带 defer 的函数返回整体上有三个步骤)
(1)执行 return 的值拷贝,将 return 语句返回的值复制到函数返回值战区(如果只有一个return ,不带任何变量或值,则此步骤不做任何动作)。 (2)执行 defer 吾句,多个 defer按照 FILO 顺序执行。 (3)执行调整RET指令。
6、切片困惑
(1)数组:数组是有固定个相同类型元素的数据结构,底层采用连续的内存空间存放,数组一旦声明后大小就不可改变了。 (2)数组的一切传递都是值拷贝,体现在 以下几个方面: i. 数组间的直接赋值。 ii. 数组作为函数参数。 iii. 数组内嵌到 struct中。
7、make([]int,O va a []int 建的切片是有区别的 前者的切片指针有分配,后者的内部指
针为0。
8、多个切片引用同一个底层数组引发的混乱:切片可以由数组 建,一个底层数组可以创建多个切片,这些切片共享底层数组,使用append 扩展切片过程中可能修改底层数组的元素,间接地影响其他切片的值,也可能发生数组复制重建 共用底层数组的切片,由于其行为不明朗 不推荐使用。
9、多个切片共享一个底层数组,其中一个切片的 append 操作可能引发如下两种情况。
(1)append 追加的元素没有超过底层数组的容量,此种 append 操作会直接操作共享的底层数组,如果其他切片有引用数组被覆盖的元素,则会导致其他切片的值 隐式地发生变化。 (2)append 追加的元素加上原来的元素如果超出底层数组的容 ,则此种 append 操作会 重新申请新数组,并将原来数组值复制到新数组。 由于有这种 义性,所以在使用切片的过程中应该尽量避免多个切面共享底层数组, 可以 使用 co py 进行显式的复制。
10、Go 只有一种参数传递规则,那就是值拷贝,这种规则包括两种含义:
(1)函数参数传递时使用的是值拷贝。 (2)实例赋值给接口变量,接口对实例的引用是值拷贝。 (3)有时在明明是值拷贝的地方,结果却修改了变量的内容,有以下两种情况:i. 直接传递的是指针。指针传递同样是值拷贝,但指针和指针副本的值指向的地址是同一个地方,所以能修改实参值。ii. 参数是复合数据类型,这些复合数据类型内部有指针类型的元素,此时参数的值拷贝并不影响指针的指向。
11、Go 复合类型中 chan map slice interface 内部都是通过指针指向具体的数据,这些类型,的变量在作为函数参数传递时,实际上相当于指针的副本 。
12、comma,ok 表达式小结
(1)获取 map值:获取 map 中不存在键的值不会发生异常,而是会返回值类型的零值,如果想确定 map 中是否存在 key ,则可以使用获取 map 值的 comma,ok 语法。例如:v , ok := m[ ” some ” ] (2)读取 chan 值:读取己经关闭的通道,不会阻塞,也不会引起 panic ,而是一直返回该通道的值。怎么判断通道已经关闭?有两种方法, 种是读取通道的 comma,ok 表达式,如果通道己经关闭,则 ok 的返回值是 fas le 另一种就是通过 range 循环迭代。示例如下:v , ok : = <- c。 (3)类型断言:re , ok : = body.(io.ReadCloser)
13、包中的函数或方法设计
(1)很多包的开发者会在内部实现两个“同名”的函数或方法,一个首字母大写 用于导出 API供外部调用 一个首字母 写,用于实现具体逻辑。一般首字母大写的函数调用首宇母 写的函数,同时包装一些功能 首字母小写的函数负责更多的底层细节。 (2)大部分情况下我 不需要两个同名且只是首字母大小写不同的函数,只有在函数逻辑很复杂,而且函数在包的 内、外部都被调用 的情况下,才考虑拆分为两个函数进行实现。一方面减少单个函数 复杂性,另一方面进行调用隔离。 (3)多值返回函数里如果有 error或者 bool 类型的返回值,则应该将 error或者 bool 作为最后一个返回值。