1、创建一个未公开类型的变量
需要两个理由。
第一,公开或者未公开的标识符,不是一个值。
第二,短变量声明操作符,有能力捕获引用的类型,并创建一个未公开的类型的变量。永远不能显式创建一个未公开的类型的变量,不过短变量声明操作符可以这么做。
2、编译器只允许为命名的用户定义的类型声明方法,
3、引用类型的值在其他方面像原始的数据类型的值一样对待。
4、通道并不提供跨 goroutine 的数据访问保护机制。
5、如果发生了错误,永远不要使用该函数返回的另一个值①。这时必须忽略另一个值,否则程序会产生更多的错误,甚至崩溃。
6、创建一个变量并初始化为其零值,习惯是使用关键字var。这种用法是为了更明确地表示一个变量被设置为零值。如果变量被初始化为某个非零值,就配合结构字面量和短变量声明操作符来创建变量。
7、在main.main 函数执行之前所有代码都运行在同一个goroutine,也就是程序的主系统线程中。因此,如果某个init 函数内部用go关键字启动了新的goroutine的话,新的goroutine只有在进入main.main 函数之后才可能被执行到。
8、数组是切片和映射的基础数据结构
9、select 默认是阻塞的,只有当监听的channel 中有发送或接收可以进行时才会运行,当多个都准备好的时候,select 是随机的选择一个执行的。
10、引用一个 nil 指针,会导致 panic。因此,在进行指针操作之前,一定要先判断指针是否为 nil。
11、如果在[]运算符里指定了一个值,那么创建的就是数组而不是切片。只有不指定值的时候,才会创建切片,
12、想把一个键值对从映射里删除,就使用内置的delete函数。 删除键为Coral的键值对 delete(colors, "Coral")
13、对于指针对象的方法来说,就算指针的值为 nil, 也是可以调用的
type Person struct{}
func sayHi(p *Person) {fmt.Println("hi")}
func (p *Person) sayHi() {fmt.Println("hi")}
var p *Person
p.sayHi()
14、一个为nil
的slice
,除了不能索引外,其他的操作都是可以的
15、对于 nil
的 map
, 我们可以简单把它看成是一个只读的 map,不能进行写操作,否则就会 panic。
16、关闭一个 nil
的 channel 会导致程序 panic
17、函数可以被用作结构体字段, 逻辑上,默认的零值为 nil
type Foo struct {
f func() error
}
18、接口的零值就是把它的动态类型和值都设置为nil,一个接口值是否是 nil 取决于它的动态类型,可以用 c == nil 或者 c != nil 检测,调用一个 nil 接口的任何方法都会导致崩溃
19、因为接口值是可比较的,所以他们可以作为 map 的键,也可以作为 switch 语句的操作数。其他类型要么是可以安全比较的(比如基础类型和指针),要么是完全不可比较的(比如 slice、map、函数),但比较接口值或者其中包含接口值的聚合类型时,我们必须小心崩溃的可能性。仅在能确认接口值包含的动态值可以比较时,才比较接口值
20、log包里的Fatal函数。Fatal函数接受这个错误的值,并将这个错误在终端窗口里输出,随后终止程序。
21、在Go语言中,所有的变量都以值的方式传递。
22、与直接通过值或者指针调用方法不同,如果通过接口类型的值调用方法,规则有很大不同,如代码清单 2-38 所示。使用指针作为接收者声明的方法,只能在接口类型的值是一个指针的时候被调用。使用值作为接收者声明的方法,在接口类型的值为值或者指针时,都可以被调用。
// 方法声明为使用指向defaultMatcher类型值的指针作为接收者
func (m *defaultMatcher) Search(feed *Feed, searchTerm string)
// 通过interface类型的值来调用方法
var dm defaultMatcher
var matcher Matcher = dm
// 将值赋值给接口类型
matcher.Search(feed, "test") // 使用值来调用接口方法
> go build
cannot use dm (type defaultMatcher) as type Matcher in assignment
// 方法声明为使用defaultMatcher类型的值作为接收者
func (m defaultMatcher) Search(feed *Feed, searchTerm string)
// 通过interface类型的值来调用方法
var dm defaultMatcher
var matcher Matcher = &dm
// 将指针赋值给接口类型
matcher.Search(feed, "test") // 使用指针来调用接口方法
> go build
Build Successful
23、变量必须先声明后使用。如果尚未使用 var 关键字对变量进行声明,那么尝试向他赋值将导致 go 报告错误。
24、go 只提供一个相对运算符(==),并且不允许直接将文本和数值进行比较。
25、不为 for 语句设置任何条件将产生无限循环,然后在需要的时候通过循环体内使用 break 语句来跳出循环。
26、无类型常量可以存储非常大的值,并且所有数值型字面量都是无类型常量。无类型常量在被用作函数参数的时候,必须转换为有类型变量。
27、因为闭包保留的是周围变量的引用 而不是副本值,所以修改被闭包捕获的变量可能会导致调用匿名函数的结果发生变化
var k kelvin = 294.0
sensor := func() kelvin {
return k
}
fmt.Println(sensor()) // 294
k++
fmt.Println(sensor()) // 295
28、结构也是值,它在被赋值或者被传递至函数的时候都会产生相应的副本。
29、结构可以通过嵌入满足接口的类型来满足接口。
30 、指针是指向另一变量的地址的变量。
31、地址操作符无法取得字符串字面量、数字字面量和布尔值字面量的地址,诸如 &42 和 &“anther level of indirection”这样的语句将导致 go 编译器报错。
32、地址操作符(&)提供值的内存地址,而它的反向操作解引用则提供内存地址指向的值。
33、将星号放在类型前面表示要声明指针类型,而将星号放在变量前面则表示解引用变量指向的值。(声明指针变量、解引用指针)
34、指针的作用就是指向。
35、注意 go 不提供像 C 语言中的 address++ 这样的指针运算操作。
36、在 go 语言中,nil 是一个零值。通道、指针、切片、映射、接口和函数的零值为 nil。
尝试引用一个 nil 指针将导致程序崩溃。
var nowhere = *int
fmt.Println(nowhere) // 打印<nil>
fmt.Println(*nowhere) // panic , nil 指针解引用
// 如何避免崩溃,可以应用前,使用 if 判断一下
if nowhere != nil {
fmt.Println(*nowhere)
}
当变量被声明为函数类型时,它的默认值为 nil。
var fn func(a, b int) int
fmt.Println(fn == nil) //打印 true
切片在声明之后没有使用复合字面量或者内置的 make 函数进行初始化,那么它的默认值 nil。但 关键字 range 和 len、append等内置函数都可以正常处理值为 nil 的切片。
虽然不包含任何元素的空切片和值为 nil 的切片并不相等,但它们通常可以替换使用。如下:跳过了创建空切片的步骤,而直接将 nil 传递给了一个接受切片作为参数的函数
func main() {
soup := mirepoix(nil)
fmt.Println(soup)
}
func mirepoix(ingredients []string) []string {
return append(ingredients, "onion", "carrot", "celery")
}
在编写接受切片作为参数的函数时,请确保该函数在处理 nil 切片和空切片的时候具有同样的行为。
nil 映射 ,同切片类似。我们还可以对值为nil 的映射执行读取操作,但尝试对其进行写入将会引发 panic。所以,如果函数只需要对映射执行读取操作,那么向函数传入 nil 来代替空映射是可行的。
接口变量只有在类型和值都为 nil 时才等于 nil。
var v interface{}
fmt.Println("%T %v %v\n", v, v, v == nil) // 打印”<nil><nil>true“
var p *int
v = p
fmt.Println("%T %v %v\n", v, v, v == nil) // 打印” *int<nil>false“
// 格式化变量 %#v 能够查看变量的类型和值
fmt.Println("%#v\n", v, v, v == nil) // 打印” (*int)(nil)“
为了避免在比较接口变量和 nil 时得到出乎意料的结果,最好的做法是明确地使用 nil 标识符,而不是指向一个包含 nil 的变量。
nil 并不是表示 “无” 的唯一方法。
type number struct {
value int
valid bool
}
func newNumber(v int) number {
return number{value: v, valid: true}
}
func (n number) String() string {
if !n.valid {
return ”not set“
}
return fmt.Println("%d", n.value)
}
func main() {
n := newNumber(42)
fmt.Println(n) // "42"
e := number{}
fmt.Println(e) // "not set"
}
37、读写 nil 管道均会阻塞,且是永久阻塞。关闭的管道仍然可以读取数据,向关闭的管道写数据会触发 panic。
38、协程读取管道时,阻塞的条件:管道无缓存区,管道缓存区中无数据,管道的值为nil;协程写入管道时,阻塞的条件:管道无缓冲区,管道的缓冲区已满,管道的值为 nil。
39、nil映射不能用于存储键值对,否则,会产生一个语言运行时错误,即向值为 nil 的 map 添加元素会触发 panic 。操作前应先判断 map 值是否为 nil。
40、在Go语言里,通过键来索引映射时,即便这个键不存在也总会返回一个值。在这种情况下,返回的是该值对应的类型的零值。
41、在 map 为 nil 或指定的键不存在 delete () 删除键值对时,不会报错,相当于空操作。
42、map 操作不是原子的,多协程同时操作 map 时有可能产生读写冲突,读写冲突会触发 panic 从而导致程序退出。可以使用额外的锁,或者标准库 sync包中的 sunc.map
43、未初始化的 map 值为 nil , 在向值为 nil 的 map 添加元素时会触发 panic,尽管操作值为 nil 的 map 没有意义,但查询、删除操作不会报错。
44、 结构体 Tag 约定,Tag 本身是一个字符串,他有一个约定的格式,那就是字符串由 key:"value"组成。key 和 value 之间使用冒号分割,冒号前后不能有空格,多个 key:"value" 之间用空格隔开。
key: 必须是非空字符串,字符串不能包含控制字符、空格、引号、冒号;
value: 以双引号标记的字符串
45、iota 代表了 const 声明块的行索引(下标从 0 开始)。
const 声明还有一个特点,即如果为常量声明了一个表达式,但后续的常量没有表达式,则继承上面的表达式
const (
bit0, mask0 = 1 << iota, 1 << iota - 1 // const 声明第 0 行,即 iota == 0 ;bit0 == 1, mask0 == 0
bit1, mask1 // const 声明 第 1 行,即 iota == 1, 表达式继承上面的语句; bit1 == 2 mask1 == 1
_, _ // 表达式声明第 2 行,即 iota == 2
bit3, mask3 // const 声明第 3 行,即 iota == 3; bit3 == 8 mask3 == 7
)
46、字符串可以为空,长度为0,但不会是 nil;字符串不可以修改(修改字符串其实是创建新的字符串)。
47、字符串可以使用双引号赋值,也可以使用反单引号赋值(注不是单引号)。
48、字符串拼接时( + 号拼接)会触发内存分配和内存拷贝。无论字符串转换成 []byte ,还是 []byte 转换成 string, 都会发生一次内存拷贝,会有一定的性能开销。因此在一些高频场景中会成为性能瓶颈,如数据库访问、 http 请求处理等。
49、字符串不支持取地址操作。当你试图获取一个字符串变量的地址时,实际上得到的是指向字符串数据的第一个字节的指针。然而,这个指针是只读的,不能用来修改字符串的内容。这是因为字符串是不可变的,一旦创建就不能改变其内容。
50、string 和 []byte 都可以标识字符串,但因数据结构不同,其衍生出的方法也不同。
string 擅长的场景:1)需要字符串比较的场景 2)不需要 nil 字符串的场景
[]byte 擅长的场景: 1)修改字符串,尤其时粒度为一个字节的场景。2)函数返回值,需要 nil 表示含义的场景。3)需要切片操作的场景。
51、当一个或多个 goroutine 因为某些永远无法发生的事情而被阻塞时,我们称这种情况为 死锁。
52、被阻塞的 goroutine 将什么也不做。