Sakurairo

加载中...

首页文章专题归档关于友链

主题

返回文章列表
GO

Go语言基础学习笔记(更多类型)

接下来就是最麻烦的结构体和切片了,还有我们最爱的指针(cpp永远的痛) 3.指针 Go和cpp一样,都提供了指针操作的能力。 官方定义:指针保存了值的内存地址。 指针的其实就是一个标志,标志着存储了这个值的内存地址。JS的浅拷贝和深拷贝也是...

2024-05-086 分钟 224 阅读

发布于 2024-05-08 05:55

·

作者:八云澈

接下来就是最麻烦的结构体和切片了,还有我们最爱的指针(cpp永远的痛)

3.指针

Go和cpp一样,都提供了指针操作的能力。

官方定义:指针保存了值的内存地址。

指针的其实就是一个标志,标志着存储了这个值的内存地址。JS的浅拷贝和深拷贝也是这个问题,但是Go提供了直接对内存地址操作的方式也可以近似的理解为深拷贝。

类型 *T 是指向 T 类型值的指针,其零值为 nil。 (重点,后面会用到很多次)

axapta
1·var p *int

& 操作符会生成一个指向其操作数的指针。

makefile
1i := 422p = &i

* 操作符表示指针指向的底层值。与 C 不同,Go 没有指针运算。

示例:

Go
1package main2 3import "fmt"4 5func main() {6	i, j := 42, 27017 8	p := &i         // 指向 i9	fmt.Println(*p) // 通过指针读取 i 的值10	*p = 21         // 通过指针设置 i 的值11	fmt.Println(i)  // 查看 i 的值12 13	p = &j         // 指向 j14	*p = *p / 37   // 通过指针对 j 进行除法运算15	fmt.Println(j) // 查看 j 的值

4.struct 结构体

一个结构体(struct)就是一组字段 (field)。struct关键字是用来定义一个结构类型。 可以理解为TypeScript中的对象type定义方式。

示例:

Go
1 2package main3 4import "fmt"5 6type Vertex struct {7	X int8	Y int9}10 11func main() {12	fmt.Println(Vertex{1, 2})13}

结构体字段可以使用点号 .来访问。(这不就和TS/JS一样嘛)

现在我们定义一个指针指向Vertex。

如果我们有一个指向结构体的指针 p 那么可以通过 (*p).X 来访问其字段 X。 不过这么写太啰嗦了,所以语言也允许我们使用隐式解引用,直接写 p.X 就可以

Go
1 2v:=Vertex{1.3}3 4p:=&v5 6p.X=15

5.数组

类型 [n]T 表示一个数组,它拥有 n 个类型为 T 的值。

定义方式:

CSS
1var a [10] int 

会将变量 a 声明为拥有 10 个整数的数组。

数组的长度是其类型的一部分,因此数组不能改变大小。 这看起来是个限制,不过没关系,Go 拥有更加方便的使用数组的方式.。

示例:

Go
1package main2 3import "fmt"4 5func main() {6	var a [2]string7	a[0] = "Hello"8	a[1] = "World"9	fmt.Println(a[0], a[1])10	fmt.Println(a)11 12	primes := [6]int{2, 3, 5, 7, 11, 13}13	fmt.Println(primes)14}

6.切片

每个数组的大小都是固定的。而切片则为数组元素提供了动态大小的、灵活的视角。 在实践中,切片比数组更常用。

切片其实就是切割后的数组。使用类型 []T 表示一个元素类型为 T 的切片。

切片通过两个下标来界定,一个下界和一个上界,二者以冒号分隔:

CSS
1a[low : high]

切片是左闭右开区间,以下表达式创建了一个切片,它包含 a 中下标从 1 到 3 的元素:

apache
1a[1:4]

示例:

Go
1package main2 3import "fmt"4 5func main() {6	primes := [6]int{2, 3, 5, 7, 11, 13}7 8	var s []int = primes[1:4]9	fmt.Println(s)10}

ps:切片就像数组的引用 切片并不存储任何数据,它只是描述了底层数组中的一段。如果你修改了切片中的元素会导致底层数组的改变,所有基于这个数组的切片都会观测到这个修改。

除了从数组切割而来,还有其他办法创建一个切片,就是使用切片字面量。

Go
1s := []int{1, 2, 3, 4, 5} // 直接创建长度为5、容量也为5的int切片

在进行切片的时候,你可以利用它的默认行为来忽略上下界。切片下界的默认值为 0,上界则是该切片的长度。

对于数组

CSS
1var a [10]int

来说,以下切片表达式和它是等价的:

CSS
1a[0:10]2a[:10]3a[0:]4a[:]

切片拥有 长度 和 容量,切片的长度就是它所包含的元素个数。

切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。

切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。

你可以通过重新切片来扩展一个切片,给它提供足够的容量。

注意:切片在一定程度上算数组的条件引用,所以他的零值是nil。

nil 切片的长度和容量为 0 且没有底层数组。

如果你需要创建动态数组,可以使用make函数进行创建

make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:

Go
1a := make([]int, 5)  // len(a)=5

要指定它的容量,需向 make 传入第三个参数:

stylus
1b := make([]int, 0, 5) // len(b)=0, cap(b)=52 3b = b[:cap(b)] // len(b)=5, cap(b)=54b = b[1:]      // len(b)=4, cap(b)=4

切片可以包含任何类型,当然也可以包含切片。

示例:

Go
1package main2 3import (4	"fmt"5	"strings"6)7 8func main() {9	// 创建一个井字棋(经典游戏)10	board := [][]string{11		[]string{"_", "_", "_"},12		[]string{"_", "_", "_"},13		[]string{"_", "_", "_"},14	}15 16	// 两个玩家轮流打上 X 和 O17	board[0][0] = "X"18	board[2][2] = "O"19	board[1][2] = "X"20	board[1][0] = "O"21	board[0][2] = "X"22 23	for i := 0; i < len(board); i++ {24		fmt.Printf("%s\n", strings.Join(board[i], " "))25	}26}

如果需要向切片中添加元素,我们需要使用内置的append函数。

append函数参数:第一个参数为需要进行添加的切片,第二个以及后面的参数为添加的元素。

append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。

append 的结果是一个包含原切片所有元素加上新添加元素的切片。

当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。 返回的切片会指向这个新分配的数组。

示例:

Go
1 2package main3 4import "fmt"5 6func main() {7	var s []int8	printSlice(s)9 10	// 可在空切片上追加11	s = append(s, 0)12	printSlice(s)13 14	// 这个切片会按需增长15	s = append(s, 1)16	printSlice(s)17 18	// 可以一次性添加多个元素19	s = append(s, 2, 3, 4)20	printSlice(s)21}22 23func printSlice(s []int) {24	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)25}

注意:当切片容量不足以容纳新元素时,append()函数会自动扩容。扩容策略如下:

  1. 首次扩容:新容量为原容量的两倍加上新添加元素的数量。
  2. 后续扩容:若原容量已达到或超过1000,新容量为原容量的1.25倍加上新添加元素的数量;否则,新容量仍为原容量的两倍加上新添加元素的数量。

扩容可能导致性能开销和数据迁移,因此在预知切片大小的情况下,建议使用make()函数指定合适的初始容量。

常见错误和问题:、 (1)修改切片元素会影响所有引用同一底层数组的切片。理解这一点有助于避免数据竞争和意外修改。 (2)访问切片时,索引超出切片长度会导致panic。确保索引在有效范围内: (3)append()函数可能改变原切片的地址,因此应始终使用其返回值:

Go
1s := []int{1, 2, 3} s = append(s, 4) // 必须使用append的返回值,否则可能丢失新添加的元素

切片和映射的遍历方式:

当使用 for 循环遍历切片时,每次迭代都会返回两个值。 第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。

Go
1package main2 3import "fmt"4 5var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}6 7func main() {8	for i, v := range pow {9		fmt.Printf("2**%d = %d\n", i, v)10	}11}

可以将下标或值赋予 _ 来忽略它。如果只需要索引或者值只需要使用下划线就可以忽略

Go
1package main2 3import "fmt"4 5func main() {6	pow := make([]int, 10)7	for i := range pow {8		pow[i] = 1 << uint(i) // == 2**i9	}10	for _, value := range pow {11		fmt.Printf("%d\n", value)12	}13}

7.map映射

map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。 类似于JS中的对象,map 映射将键映射到值。映射的零值为 nil 。nil 映射既没有键,也不能添加键。make 函数会返回给定类型的映射,并将其初始化备用。 创建映射的方式推荐使用make函数进行创建: m := make(map[string]int) 其中map是映射关键字,string代表了键的类型,int代表了值的类型。

示例:

Go
1 2package main3 4import "fmt"5 6type Vertex struct {7	Lat, Long float648}9 10var m map[string]Vertex11 12func main() {13	m = make(map[string]Vertex)14	m["Bell Labs"] = Vertex{15		40.68433, -74.39967,16	}17	fmt.Println(m["Bell Labs"])18}

映射的字面量和结构体类似,只不过必须有键名。

Go
1 2package main3 4import "fmt"5 6type Vertex struct {7	Lat, Long float648}9 10var m = map[string]Vertex{11	"Bell Labs": Vertex{12		40.68433, -74.39967,13	},14	"Google": Vertex{15		37.42202, -122.08408,16	},17}18 19func main() {20	fmt.Println(m)21}

映射的修改,使用m[key] = elem对key的元素进行修改,获取元素对应的值使用elem = m[key],删除元素使用delete(m, key)传入映射以及需要删除的key。

如果我们需要检查某个键是否存在,可以使用elem, ok = m[key]的双赋值方式进行检测。 若 key 在 m 中,ok 为 true ;否则,ok 为 false。 若 key 不在映射中,则 elem 是该映射元素类型的零值。

8.函数闭包

函数也是值。它们可以像其他值一样传递。

函数值可以用作函数的参数或返回值。 Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。 该函数可以访问并赋予其引用的变量值,换句话说,该函数被“绑定”到了这些变量。

例如,函数 adder 返回一个闭包。每个闭包都被绑定在其各自的 sum 变量上。

八云澈

Writer · Recorder

八云澈(Bayunche)

开发者 / 写作者 / 普通生活记录者 · 中国 · 深圳

喜欢把工作里的技术问题、读书时的触动、生活中的琐碎观察慢慢写下来。既想把复杂问题讲清楚,也想留住那些很快会被忘掉的日常。

Java 后端Go 工程化读书札记生活记录日常随笔

Continue Reading

相关文章

更多文章

上一篇

Go语言基础学习笔记(流程控制语句)

2024-05-06

下一篇

Go语言基础学习笔记 (方法与结构)

2024-05-29

评论区

读完这篇文章后,如果你也有类似经验或不同看法,欢迎继续交流。

💬 评论