在前面熟悉了常见的基础语法东西之后,我们来学点稍微复杂的东西,这也是很关键的知识,对于提高性能是必不可少的,对于基础知识想要熟悉的,可以先看如下:
Go语言零基础入门,从安装到运行代码
https://blog.csdn.net/weixin_41896770/article/details/127473110
Go语言零基础入门,捕获错误、slice切片、for循环、test
https://blog.csdn.net/weixin_41896770/article/details/127510535
闭包的概念,简单来说就是可访问局部变量,使得局部变量看起来就是全局变量,这种情况就是说不需要定义全局变量,但是我可以访问函数内部的变量(普通情况是访问不了局部变量的)
- package main
-
- import "fmt"
-
- // 斐波拉契数列,返回的是一个函数
- func fib() func() int {
- a, b := 0, 1
- return func() int {
- a, b = b, a+b
- fmt.Println(&a)
- return a
- }
- }
-
- func main() {
- f := fib()
- for i := 0; i < 6; i++ {
- fmt.Println(f())
- }
- }
- /*
- 0xc0000b8000
- 1
- 0xc0000b8000
- 1
- 0xc0000b8000
- 2
- 0xc0000b8000
- 3
- 0xc0000b8000
- 5
- 0xc0000b8000
- 8
- */
当然使用闭包的时候需要注意的是,变量是一个引用,所以我特意打印a变量的地址,可以看出地址没有变化,如果不引用这个a的话,需要做个赋值的操作即可
- aa := a
- fmt.Println(&aa)
- /*
- 0xc0000a0010
- 1
- 0xc0000a0018
- 1
- 0xc0000a0030
- 2
- 0xc0000a0038
- 3
- 0xc0000a0040
- 5
- 0xc0000a0048
- 8
- */
这样的话,这个地址就随着每次的循环而变化。
对于新手来说,有的会比较害怕指针,尤其是初学C/C++的时候,会感觉挺复杂的,其实很简单,不要怕,只要理解了指针、内存地址、取值这些概念就好办了。
比如变量,大家很熟悉,那变量的值需要在内存中存放是吧,那必须要给个地址,既然是变量,意思就是说里面的值是可以变化的,也就是说同一个地址可以随着不同条件而变化,通俗来说就是公交车上的人,经常上上下下,里面的乘客经常在变化。
获取变量的内存地址就是:&变量,有了地址,想要获取值,那就是:*地址
- var a int = 10
- var b string = "hello"
- var i *int // 定义一个整型的指针
- var ii *int
- var s *string// 定义一个字符串的指针
- i = &a //将变量a的内存地址赋值给i,这个就是形象的说指针i指向a,相当于我要去这个地方找到你
- fmt.Printf("%p, %p, %d \n", i, ii, *i)//0xc000122000, 0x0, 10
- //知道地址了,取这个内存地址的值:*指针,如果**ii会报错,这个就是没有赋值,是空指针或野指针
- /*
- panic: runtime error: invalid memory address or nil pointer dereference
- */
- fmt.Printf("类型:%T \n", s)//类型:*string
- s = &b
- fmt.Printf("%p, %s", s, *s)//0xc00010c210, hello
另外比较常见的一种就是指针作为参数的传递,交换值的例子:
- package main
-
- import "fmt"
-
- //等价于func swap(a, b *int)类型一样的,可以简化成只需在最后声明类型即可
- func swap(a *int, b *int) {
- *b, *a = *a, *b
- }
-
- func main() {
- x, y := 1, 2
- fmt.Println(x, y)
- swap(&x, &y)
- fmt.Println(x, y)
- }
- /*
- 1 2
- 2 1
- */
如果不是指针做参数的话,就是常见的值传递,这样就是一个拷贝,就是会复制一份,那对复制的这份无论进行什么操作,都不会影响原来的值是吧,如下:
- func swap(a int, b int) {
- b, a = a, b
- }
- func main() {
- x, y := 1, 2
- fmt.Println(x, y)
- swap(x, y)
- fmt.Println(x, y)
- }
- 结果是不会变化的
- /*
- 1 2
- 1 2
- */
当然了,单纯对于交换数据来说,值类型换种写法也是可以搞定了,只不过这里是介绍指针的特性:
- func swap(a, b int) (int, int) {
- return b, a
- }
-
- func main() {
- x, y := swap(1, 2)
- fmt.Println(x, y)//2 1
- }
以前处理并发,一般使用线程池,在Go语言中的机制是goroutine,是一种轻量级的线程实现,这种机制是系统会自动的把任务分配给CPU,有Go的运行时管理。
了解这个goroutine机制,需要熟悉通道,先来看个示例:
- package main
-
- import "fmt"
- // 将自然数发送到通道ch里面
- func Generate(ch chan<- int) {
- for i := 1; ; i++ {
- ch <- i
- }
- }
-
- func main() {
- ch := make(chan int)// 创建新通道
- go Generate(ch) // 这里就是goroutine机制:go 函数
- for i := 0; i < 5; i++ {
- a := <-ch //将通道里的值拿出来
- fmt.Println(a)
- }
- }
- /*
- 1
- 2
- 3
- 4
- 5
- */
了解了通道,接下来了解并发,看例子:
- package main
-
- import (
- "fmt"
- "time"
- )
-
- func f() {
- var n int
- for {
- n++
- fmt.Println(n)
- time.Sleep(time.Second)
- }
- }
-
- func main() {
- go f()
- var s string
- fmt.Scanln(&s)
- }
这个例子就是说,一直累加并输出的同时,你可以进行输入,两个同时进行。
了解了上述内容,将这些结合起来,实现素数筛选的例子:
- package main
-
- import "fmt"
-
- // 发送自然数(从2开始)到通道ch
- func Generate(ch chan<- int) {
- for i := 2; ; i++ {
- ch <- i
- }
- }
-
- // 筛选素数
- // 将通道in的值除以得到的素数,不被整除的输出到通道out
- func Filter(in <-chan int, out chan<- int, prime int) {
- for {
- i := <-in // 通道in的值赋给i
- if i%prime != 0 {
- out <- i // 发送i到通道out
- }
- }
- }
-
- func main() {
- ch := make(chan int) // 创建新的通道
- go Generate(ch) // 启动goroutine机制
- for i := 0; i < 10; i++ {
- prime := <-ch
- fmt.Println(prime)
- ch1 := make(chan int) // 再创建一个通道
- go Filter(ch, ch1, prime)
- ch = ch1 //将通道ch1筛选出来的素数赋给通道ch
- }
- }
-
- /*
- 2
- 3
- 5
- 7
- 11
- 13
- 17
- 19
- 23
- 29
- */