array and slice
1.数组是一个固定长度的数据结构,一旦定义了数组的长度,在程序运行过程中就不能改变。而切片则可以动态增长或缩小。
2.在声明时,数组的长度是必须指定的,而切片则可以使用内置的make()函数进行创建,并且在创建时可以指定初始容量和预留容量。
定义
// 没有声明长度的都是切片 否则是数组
var arr1 []int // 切片
var arr2 []int = []int{1, 2, 3} // 切片
var arr3 = []int{1, 2, 3, 4} // 切片
var arr4 = [...]int{1, 2, 3, 4, 5} // 数组
var arr5 = [5]int{1, 2, 3, 4, 5} // 数组
截取切片
// 包头不包尾
slice3[3:5] 下标3-下标5之前
slice3[3:] 下标3-最后
slice3[:3] 下标0-下标3之前
slice3[:] 所有
crud
// 增
var s1 = []int{}
s1 = append(s1, 10, 20, 30)
s1 = append(s1, 40, 50)
fmt.Printf("s1: %v\n", s1)
// 删
var s3 = []int{1, 0, 0, 8, 6}
var index = 3
s4 := append(s3[:index], s3[index+1:]...)
fmt.Printf("s3: %v\n", s3)
fmt.Printf("s4: %v\n", s4)
// ... 为展开切片
// s4是由s3创建的切片,所以使用append函数修改切片时也会影响s3
// s3 [1,0,0,6,6]
// s4 [1,0,0,6]
// 改
s1[0] = 1
// 查
for i, v := range s1 {
fmt.Printf("index:%v, value:%v \r", i, v)
}
for i := 0; i < len(s1); i++ {
fmt.Printf("index:%v, value:%v \r", i, s1[i])
}
// copy(dst, source) copy(目标切片,源切片)
slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, 3)
n := copy(slice2, slice1)
fmt.Println(slice2) // [1 2 3]
fmt.Println(n) // 3
//`slice1` 中有 5 个元素,`slice2` 的长度为 3,调用 `copy()` 后,
// 只有前 3 个元素被复制到了 `slice2` 中。函数返回值 `n` 为 3,表示实际复制的元素数量。
switch
switch 匹配上case后会自动跳出啊 无需break 和java有点区别
map
// 声明map
var m map[string]string
fmt.Printf("m: %v\n", m)
// make构造map
m1 := make(map[string]string)
m1["name"] = "m1"
m1["gender"] = "female"
fmt.Printf("m1: %v\n", m1)
// 声明并赋值
m2 := map[string]string{
"name": "m2",
"gender": "male",
}
fmt.Printf("m2: %v\n", m2)
// key获取value 会返回value以及key是否存在
value, exist := m2["name"]
fmt.Printf("是否存在:%v, value:%v", exist, value)
// 遍历map
for key, value := range m2 {
fmt.Printf("key: %v, value:%v\n", key, value)
}
// 删除map中的key 如果key不存在不会有任何效果以及报错
delete(m2, "name")
函数别名 类型别名
type MyInt int
type MyFunc func(int, int) int
func adder(a int, b int) int {
return a + b
}
func main() {
var age MyInt = 1 // 类型别名
var myfunc MyFunc = adder // 函数别名
fmt.Println(age) // output 1
fmt.Println(myfunc(3, 5)) // output 8
}
高阶函数
在 Go 中,函数可以像其他类型的变量一样传递给函数,这意味着可以将函数作为参数传递给另一个函数。这种能力被称为高阶函数
函数作为参数
func sum(a int, b int) int {
return a + b
}
func mult(a int, b int) int {
return a * b
}
// apply函数接收三个参数 int变量a,b 以及一个函数f
func apply(a int, b int, f func(int, int) int) int {
return f(a, b)
}
func main() {
sr := apply(4, 5, sum)
fmt.Printf("sr: %v\n", sr) // output 9
mr := apply(4, 5, mult)
fmt.Printf("mr: %v\n", mr) // output 20
}
函数作为返回值
func sum(a int, b int) int {
return a + b
}
func mult(a int, b int) int {
return a * b
}
func calculate(operator string) func(int, int) int {
switch operator {
case "+":
return sum
case "*":
return mult
default:
return nil
}
}
func main() {
f := calculate("+") // 调用calculate返回一个函数
total := f(1, 2) // 调用函数
fmt.Printf("total: %v\n", total) // output 3
result := calculate("*")(7, 8) // 调用calculate函数返回一个函数再调返回函数
fmt.Printf("result: %v\n", result) // output 56
}
匿名函数
func main() {
//函数名的函数为匿名函数,匿名函数可以定义在函数中,通常用于创建闭包
sub := func(x int, y int) int {
return x - y
}
diff := sub(4, 2) // output 2
fmt.Printf("diff: %v\n", diff)
// 定义匿名函数时就调用
diff1 := func(x int, y int) int {
return x - y
}(8, 5)
fmt.Printf("diff1: %v\n", diff1) // output 3
}
闭包
在 Go 语言中,闭包(Closure)指的是一个函数和其相关的引用环境组合而成的实体。换句话说,闭包是由一个函数和这个函数的引用环境组合而成的一个封闭的实体。
闭包函数被创建时,会同时创建 sum 变量,并将其保存在闭包函数的引用环境中,在闭包函数被销毁时,sum 变量也会被销毁。
由于 a 和 b 中的闭包函数的生命周期是不同的,所以它们所对应的 sum 变量的生命周期也是不同的,它们是独立的,互不干扰的。
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main(){
closure1 := adder()
closure2 := adder()
fmt.Printf("closure1: %v\n", closure1(1)) // (0+1) output 1
fmt.Printf("closure1: %v\n", closure1(2)) // (1+2) output 3
fmt.Printf("closure1: %v\n", closure1(3)) // (3+3) output 6
fmt.Printf("closure2: %v\n", closure2(5)) // (0+5) output 5
fmt.Printf("closure2: %v\n", closure2(5)) // (5+5) output 10
}
defer
Go语言中的defer语句用于在当前函数返回之前执行某个函数,无论函数是正常返回还是发生了异常。defer语句通常用于释放资源、关闭文件、解锁互斥锁等操作,以确保这些操作在函数返回之前被执行
func test() {
fmt.Println("function start")
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("function end")
// 输出 先定义的后执行(类似栈) 由下到上执行
// function start
// function end
// 3
// 2
// 1
}
init
Go语言中的init函数是一个特殊的函数,它会在程序启动时自动执行。init函数没有参数和返回值,可以定义在任何包中,并且可以定义多个init函数。当程序启动时,会按照包的导入顺序依次执行每个包中的init函数。
init函数的主要作用是执行一些初始化操作,例如初始化全局变量、注册HTTP服务、解析配置文件等。init函数通常用于包的初始化,而不是用于程序的初始化。 init会先于main执行
func init() {
fmt.Println("init1")
}
func init() {
fmt.Println("init2")
}
func main() {
fmt.Println("main")
// output init1 init2 mian
}
指针(pointer)
在Go语言中,每个变量在运行时都有一个内存地址,可以通过&操作符获取一个变量的内存地址,通过*操作符获取指针指向的变量
func main() {
var a int = 11
var memory *int = &a
fmt.Printf("a的内存地址%v, 指针指向的变量:%v \r", memory, *memory) // output a的内存地址0x14000122008, 指针指向的变量:11
*memory = 20
fmt.Printf("a: %v\n", a) // 修改a指针的值后,a = 20
}
方法
type Person struct {
name string
}
// person是接收者
func (person Person) eat(food string) {
fmt.Printf("%v正在吃:%v", person.name, food)
}
func main() {
p := Person{name: "gopher"}
p.eat("屎") // output gopher正在吃:屎
}
继承
在 Go 中,没有传统意义上的继承机制,但是可以通过组合和接口来实现类似继承的功能。
type Animal struct {
name string
age int
}
func (animal Animal) eat() {
fmt.Println(animal.name + "正在吃")
}
type Dog struct {
Animal
}
func main() {
dog := Dog{Animal{name: "狗", age: 1}}
dog.eat()
}
构造函数
在 Go 中并没有像其他语言一样的构造函数的概念,但是可以使用结构体的初始化方法来实现类似的功能。
结构体类型可以定义一个自己的方法,这个方法的名称可以是任意的,但是建议使用 New 开头,用于创建结构体的实例。这个方法返回一个结构体的指针,通常用于初始化结构体的一些字段或者调用一些必要的操作。
注意,使用初始化方法创建结构体实例时,返回的是结构体的指针,因为结构体本身是值类型,在函数调用时会复制整个结构体,而使用指针则只需要复制指针本身,提高了性能。
type Person struct {
name string
age int
}
func NewPerson(name string, age int) *Person {
return &Person{name: name, age: age}
}
func main() {
p := NewPerson("gopher", 18)
fmt.Println(p.name, p.age) // output gohper 18
}
协程(goroutine)
在Go语言中,协程(goroutine)是一种轻量级的线程,由Go语言运行时(runtime)负责调度。协程可以通过 go 关键字来启动
通过 go 关键字启动的协程会在当前的go程中异步运行,不会阻塞当前的go程
在 Go 语言中,main() 函数确实可以看作是一个协程,也就是一个 goroutine。
当程序启动时,Go 编译器会创建一个名为 main.main 的 goroutine,并在其中执行 main() 函数的代码。因此,main() 函数实际上是在一个 goroutine 中执行的。
与其他 goroutine 不同的是,main() 函数所在的 goroutine 是整个程序的入口和起点,它会在程序结束时,最后一个结束的 goroutine 退出之前,等待所有 goroutine 完成其工作。
在 main() 函数中,我们可以通过 go 关键字来创建新的 goroutine,并在其中执行代码。这些新的 goroutine 将在主 goroutine 之外异步执行,从而实现高效的并发编程。
需要注意的是,如果主 goroutine 结束了,那么整个程序也将结束,因此我们需要确保在所有 goroutine 完成其工作之前,主 goroutine 不会退出。一种常见的方式是使用通道来协调 goroutine 之间的通信和同步,以确保所有 goroutine 都已经完成了它们的工作,才能让主 goroutine 退出。
通道(channel)
在 Go 编程语言中,通道(channel)是一种通信机制,允许一个 Goroutine 向另一个 Goroutine 发送消息。它提供了一种方式让 Goroutine 同步它们的执行并相互通信,而无需显式的锁或条件变量。
在 Go 中,通道可以被看作是数据流动的管道或通道。通道有一个类型,该类型指定可以通过它发送和接收的数据类型。可以使用内置的 make() 函数创建通道,并使用 <- 运算符发送和接收数据。
Go 中的通道对于实现生产者-消费者模式、扇入/扇出模式和工作池模式等并发编程模式非常有用。它们还提供了一种安全和高效的方式,让 Goroutine 之间共享数据,而无需显式的同步原语。
ch <- 1 向通道中发送数据 value := <- ch 从通道中接收数据
func main() {
ch := make(chan int) // 创建一个无缓冲的int channel
ch <- 1 // 向channel中发送数据
这段代码会导致死锁(deadlock)错误。在 Go 中,发送和接收操作是阻塞的,也就是说,当一个
Goroutine 尝试向一个没有缓冲区的通道发送数据时,它会一直阻塞,直到另一个 [Goroutine]() 从
通道接收数据。如果没有其他 Goroutine 从通道接收数据,那么发送 Goroutine 将一直阻塞,
从而导致死锁错误。
因此,为了避免死锁错误,你需要确保在向通道发送数据之前,有其他 Goroutine 准备好从通道
接收数据。
}
// 同上,创建一个带有一个缓冲区的channel 第一条数据正常发送 第二条数据发送一直阻塞 没有其他协程取走数据导致死锁
func main() {
ch := make(chan int, 1)
ch <- 1
ch <- 1
fmt.Printf("ch: %v\n", ch)
}