从零开始的Go语言异世界笔记

初始化顺序

IMG-20251215123440047.png

基本数据类型&标识符

  1. 不允许将数组定义为常量
  2. 常量标识符无法取址
  3. _为匿名标识符,不能被使用,只能被赋值
  4. 关于iota定义常量的知识点
    1. 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
)
  1. 双引号"abc"是字符串string;单引号'a'是rune类型,仅能为一个字符
  2. 可以通过b, c = c, b来交换变量,前提是类型相同
  3. 预定义的函数,它并不是一个关键字,因此我们可以将new等预定义函数的名字重新定义为别的类型

运算符

  1. 除法运算符/的行为则依赖于操作数是否全为整数,比如5.0/4.0的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。

选择&分支&循环

  1. Go1.22后循环体中的循环变量的生命周期仅存在当前循环的一趟中
  2. for-range语句对字符串遍历会按照单个字符遍历,而不是for语句的按照字节遍历

数组&切片

  1. go语言的数组元素可变但是长度是不可变的,长度和容量也是相同的,其中元素可被索引,是顺序表存储结构
  2. 定义数组的时候必须明确大小,后续无法更改
  3. var b [3]int的类型是[3]int,意为长度和容量为3的数组类型
  4. arr := [2][3]int{}这个二维数组中第一维的类型就是[3]int
  5. 数组的地址就是数组中首元素的地址
  6. a2 := a1数组的拷贝就是值拷贝,创建完整的副本
  7. &a[0]取的是数组中第一个元素的地址;(&a)[0]是取这个数组的索引值,等价于a[0]

  1. 切片是顺序表,线性结构,采用和数组一样的访问方式
  2. var s1 []ints2 := []int{}不一样,前者没有建立底层的数据结构但是后者有,前者的指针是指向0地址的
  3. s3 := make(type, len, cap)容量cap必须>长度len
  4. 切片扩容实际上是新建一个相同的容量更大的数组
  5. 用切片赋值给新切片只复制Header副本,实际上共用同个底层数组;方法传参切片也只是复制Header
  6. 切片索引右边界缺省值长度最大值容量

字符串数组

  1. 字符串数组比较特殊,内部每个元素不是直接存储字符串内容,而是存储了内容的元数据和指向内存中另一部分的字符串的指针,元数据表示有多长,真正存储的内容在别的内存空间中,所以字符串数组不管每个元素的大小差别有多大,间隔的内存地址还是一样长的。以下是字符串本身的源码,需要注意的是字符串本身就是一个常量,也是不可变的!
type string struct {
    Data uintptr // 指向实际字符串数据的指针
    Len  int     // 字符串的长度
}

指针

  1. 任何类型的指针的零值都是nil。如果p指向某个有效变量,那么p != nil测试为真。指针之间也是可以进行相等测试的,只有当它们指向同一个变量或全部是nil时才相等。
var x, y int
fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
  1. 指针指向方法的局部变量是有效的
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)