简短声明方式来单独为一个变量重复声明, := 左侧至少有一个新变量,才允许多变量的重复声明:
|
|
:= 用于声明变量并赋值,Go中变量不能包含.
,所以data.result,err:=work()
会提示变量命名错误
|
|
不小心覆盖了变量
如果你在新的代码块中像下边这样误用了 :=,编译不会报错,但是变量不会按你的预期工作:
|
|
直接使用值为 nil 的 slice、map
允许对值为 nil 的 slice 添加元素,但对值为 nil 的 map 添加元素则会造成运行时 panic
map 容量
在创建 map 类型的变量时可以指定容量,但不能像 slice 一样使用 cap() 来检测分配空间的大小:
Array 类型的值作为函数参数
在 C/C++ 中,数组(名)是指针。将数组作为参数传进函数时,相当于传递了数组内存地址的引用,在函数内部会改变该数组的值。
在 Go 中,数组是值。作为参数传进函数时,传递的是数组的原始值拷贝,此时在函数内部是无法更新该数组的:
|
|
如果想修改参数数组:
直接传递指向这个数组的指针类型:
直接使用 slice:即使函数内部得到的是 slice 的值拷贝,但依旧会更新 slice 的原始数据(底层 array)
range 遍历 slice 和 array 时混淆了返回值
与其他编程语言中的 for-in 、foreach 遍历语句不同,Go 中的 range 在遍历时会生成 2 个值,第一个是元素索引,第二个是元素的值:
|
|
string 类型的值是常量,不可更改
尝试使用索引遍历字符串,来更新字符串中的个别字符,是不允许的。
string 类型的值是只读的二进制 byte slice,如果真要修改字符串中的字符,将 string 转为 []byte 修改后,再转为 string 即可:
|
|
注意: 上边的示例并不是更新字符串的正确姿势,因为一个 UTF8 编码的字符可能会占多个字节,比如汉字就需要 3~4 个字节来存储,此时更新其中的一个字节是错误的。
更新字串的正确姿势:将 string 转为 rune slice(此时 1 个 rune 可能占多个 byte),直接更新 rune 中的字符
|
|
- string 与索引操作符
对字符串用索引访问返回的不是字符,而是一个 byte 值。
这种处理方式和其他语言一样,比如 PHP 中:
|
|
如果需要使用 for range 迭代访问字符串中的字符(unicode code point / rune),标准库中有 “unicode/utf8” 包来做 UTF8 的相关解码编码。另外 utf8string 也有像 func (s *String) At(i int) rune 等很方便的库函数。
- 字符串并不都是 UTF8 文本
string 的值不必是 UTF8 文本,可以包含任意的值。只有字符串是文字字面值时才是 UTF8 文本,字串可以通过转义来包含其他数据。
判断字符串是否是 UTF8 文本,可使用 “unicode/utf8” 包中的 ValidString() 函数:
- 字符串的长度
在 Python 中:12data = u'♥'print(len(data)) # 1
然而在 Go 中:
Go 的内建函数 len() 返回的是字符串的 byte 数量,而不是像 Python 中那样是计算 Unicode 字符数。
如果要得到字符串的字符数,可使用 “unicode/utf8” 包中的 RuneCountInString(str string) (n int)
注意: RuneCountInString 并不总是返回我们看到的字符数,因为有的字符会占用 2 个 rune:
|
|
- range 迭代 string 得到的值
range 得到的索引是字符值(Unicode point / rune)第一个字节的位置,与其他编程语言不同,这个索引并不直接是字符在字符串中的位置。
注意一个字符可能占多个 rune,比如法文单词 café 中的 é。操作特殊字符可使用norm 包。
for range 迭代会尝试将 string 翻译为 UTF8 文本,对任何无效的码点都直接使用 0XFFFD rune(�)UNicode 替代字符来表示。如果 string 中有任何非 UTF8 的数据,应将 string 保存为 byte slice 再进行操作。
- range 迭代 map
如果你希望以特定的顺序(如按 key 排序)来迭代 map,要注意每次迭代都可能产生不一样的结果。
Go 的运行时是有意打乱迭代顺序的,所以你得到的迭代结果可能不一致。但也并不总会打乱,得到连续相同的 5 个迭代结果也是可能的,如:
- 按位取反
很多编程语言使用 ~ 作为一元按位取反(NOT)操作符,Go 重用 ^ XOR 操作符来按位取反:
|
|
同时 ^ 也是按位异或(XOR)操作符。
一个操作符能重用两次,是因为一元的 NOT 操作 NOT 0x02,与二元的 XOR 操作 0x22 XOR 0xff 是一致的。
Go 也有特殊的操作符 AND NOT &^ 操作符,不同位才取1。
|
|
不导出的 struct 字段无法被 encode
以小写字母开头的字段成员是无法被外部直接访问的,所以 struct 在进行 json、xml、gob 等格式的 encode 操作时,这些私有字段会被忽略,导出时得到零值:1234567891011func main() {in := MyData{1, "two"}fmt.Printf("%#v\n", in) // main.MyData{One:1, two:"two"}encoded, _ := json.Marshal(in)fmt.Println(string(encoded)) // {"One":1} // 私有字段 two 被忽略了var out MyDatajson.Unmarshal(encoded, &out)fmt.Printf("%#v\n", out) // main.MyData{One:1, two:""}}程序退出时还有 goroutine 在执行
程序默认不等所有 goroutine 都执行完才退出,这点需要特别注意:
|
|
如下,main() 主程序不等两个 goroutine 执行完就直接退出了:
常用解决办法:使用 “WaitGroup” 变量,它会让主程序等待所有 goroutine 执行完毕再退出。
如果你的 goroutine 要做消息的循环处理等耗时操作,可以向它们发送一条 kill 消息来关闭它们。或直接关闭一个它们都等待接收数据的 channel:
执行结果:
看起来好像 goroutine 都执行完了,然而报错:
fatal error: all goroutines are asleep - deadlock!
为什么会发生死锁?goroutine 在退出前调用了 wg.Done() ,程序应该正常退出的。
原因是 goroutine 得到的 “WaitGroup” 变量是 var wg WaitGroup 的一份拷贝值,即 doIt() 传参只传值。所以哪怕在每个 goroutine 中都调用了 wg.Done(), 主程序中的 wg 变量并不会受到影响。
运行效果:
- 向无缓冲的 channel 发送数据,只要 receiver 准备好了就会立刻返回
只有在数据被 receiver 处理时,sender 才会阻塞。因运行环境而异,在 sender 发送完数据后,receiver 的 goroutine 可能没有足够的时间处理下一个数据。如:
|
|
运行效果:
- 使用了值为 nil 的 channel
在一个值为 nil 的 channel 上发送和接收数据将永久阻塞:
|
|
runtime 死锁错误:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive (nil chan)]
利用这个死锁的特性,可以用在 select 中动态的打开和关闭 case 语句块:
|
|
- 使用了值为 nil 的 channel
在一个值为 nil 的 channel 上发送和接收数据将永久阻塞:
|
|