从零开始的Go语言异世界笔记
初始化顺序

基本数据类型&标识符
- 不允许将数组定义为常量
- 常量标识符无法取址
_为匿名标识符,不能被使用,只能被赋值- 关于iota定义常量的知识点
2 * iota可以设置常量为偶数索引
const (
a = iota // == 0
b // == 1
c // == 2
d = 567 // iota被打断
e // == 567
f = iota // == 6 还是从最开始计数
g // == 7
)
const (
a = 123
b // == 123
c // == 123
d = 567
e // == 567
f = iota // == 5 仍旧从开头计数
// 如果想从0开始,那就减去5:iota - 5
g // == 6
)
- 双引号
"abc"是字符串string;单引号'a'是rune类型,仅能为一个字符 - 可以通过
b, c = c, b来交换变量,前提是类型相同 - 预定义的函数,它并不是一个关键字,因此我们可以将new等预定义函数的名字重新定义为别的类型
运算符
- 除法运算符
/的行为则依赖于操作数是否全为整数,比如5.0/4.0的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。
选择&分支&循环
- Go1.22后循环体中的循环变量的生命周期仅存在当前循环的一趟中
- for-range语句对字符串遍历会按照单个字符遍历,而不是for语句的按照字节遍历
数组&切片
- go语言的数组元素可变但是长度是不可变的,长度和容量也是相同的,其中元素可被索引,是顺序表存储结构
- 定义数组的时候必须明确大小,后续无法更改
var b [3]int的类型是[3]int,意为长度和容量为3的数组类型arr := [2][3]int{}这个二维数组中第一维的类型就是[3]int- 数组的地址就是数组中首元素的地址
a2 := a1数组的拷贝就是值拷贝,创建完整的副本&a[0]取的是数组中第一个元素的地址;(&a)[0]是取这个数组的索引值,等价于a[0]
- 切片是顺序表,线性结构,采用和数组一样的访问方式
var s1 []int和s2 := []int{}不一样,前者没有建立底层的数据结构但是后者有,前者的指针是指向0地址的s3 := make(type, len, cap)容量cap必须>长度len- 切片扩容实际上是新建一个相同的容量更大的数组
- 用切片赋值给新切片只复制Header副本,实际上共用同个底层数组;方法传参切片也只是复制Header
- 切片索引右边界缺省值是长度,最大值是容量
字符串数组
- 字符串数组比较特殊,内部每个元素不是直接存储字符串内容,而是存储了内容的元数据和指向内存中另一部分的字符串的指针,元数据表示有多长,真正存储的内容在别的内存空间中,所以字符串数组不管每个元素的大小差别有多大,间隔的内存地址还是一样长的。以下是字符串本身的源码,需要注意的是字符串本身就是一个常量,也是不可变的!
type string struct {
Data uintptr // 指向实际字符串数据的指针
Len int // 字符串的长度
}
指针
- 任何类型的指针的零值都是nil。如果p指向某个有效变量,那么
p != nil测试为真。指针之间也是可以进行相等测试的,只有当它们指向同一个变量或全部是nil时才相等。
var x, y int
fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
- 指针指向方法的局部变量是有效的
var p = f()
func f() *int {
v := 1
return &v
}
这个方法每次返回的p是不同的,指向的是不同地址,虽然其值是相同
3. 在方法中更改指针指向变量的值
func incr(p *int) int {
*p++ // 非常重要:只是增加p指向的变量的值,并不改变p指针!!!
return *p
}
v := 1
incr(&v) // side effect: v is now 2
fmt.Println(incr(&v)) // "3" (and v is 3)