GO基础入门
本文最后更新于794 天前,其中的信息可能已经过时,如有错误请评论留言

Go简介

Go语言,Go 语言起源 2007 年,并于 2009 年正式对外发布,又称为Golang,是由Google三位顶尖大神开发的一种静态类型、编译型语言。它旨在提高多核处理器的编程效率,并且使得编程更加简单和高效。Go语言具有以下特点:

  1. 简洁性:Go语言的语法简单,易于学习,并且容易编写。
  2. 高性能:Go语言是编译型语言,编译后的程序运行速度快。
  3. 并发性:Go语言内置了并发编程的支持,通过goroutine和channel简化了并发编程。
  4. 垃圾回收:Go语言拥有自动垃圾回收机制,减轻了程序员的负担。
  5. 内存安全:Go语言在编译期间进行多种检查,确保内存安全。
  6. 丰富的标准库:Go语言拥有丰富的标准库,提供了许多实用的功能。
  7. 跨平台:Go语言支持跨平台编译,可以在多种操作系统上运行。
  8. 工具链:Go语言拥有强大的工具链,包括gofmt(代码格式化工具)、go test(测试工具)等。

Go语言适用于多种场景,包括但不限于:

  • 网络编程和服务器端应用
  • 分布式系统
  • 微服务架构
  • 云计算
  • DevOps工具和自动化脚本

Go语言的这些特性使其成为现代软件开发中非常受欢迎的选择之一。

注:因为go官网golang.org在国内打不开,可以使用godoc -http=:8080在自己本地的8080端口开一个一样的官方进行浏览

Go的基础结构与基本数据类型

文件名、关键字与标识符

如果Go文件的文件名由多个部分组成,则使用下划线 _ 对它们进行分隔,如 scanner_test.go 。文件名不包含空格或其他特殊字符。

注意:_ 本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。

在编码过程中,你可能会遇到没有名称的变量、类型或方法。虽然这不是必须的,但有时候这样做可以极大地增强代码的灵活性,这些变量被统称为匿名变量。

Go 代码中会使用到的 25 个关键字或保留字

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

Go 语言还有 36 个预定义标识符

其中包含了基本类型的名称和一些基本的内置函数

append bool byte cap close complex complex64 complex128 uint16
copy false float32 float64 imag int int8 int16 uint32
int32 int64 iota len make new nil panic uint64
print println real recover string true uint uint8 uintptr

学习笔记

因为go官网golang.org在国内打不开,可以使用godoc -http=:8080在自己本地的8080端口开一个一样的官方进行浏览

项目结构

根据Go约定,gopath目录下应包含三个文件夹:

  • bin:存放编译后的可执行文件
  • pkg:存放编译后生成的包文件,一般是以.a为后缀的文件
  • src:存放项目的源码

包别名

在导入包时可以为其取别名,例如给”fmt“取别名为std:import std "fmt",此外有一个特别用法,就是别名取为.,这样在调用该包函数时前面无需写包名,被称为省略调用,如下:

import . "fmt"
func main(){
    // 省略fmt包名调用
    Println("Hello World!")
}

当然,省略调用不能和别名同时使用,不然别名就没有意义了

访问权限

在Golang中根据约定函数名首字母小写即为private,函数名首字母大写即为public

go基本类型

  • bool:布尔型,1字节,只有true和false,不能用数字代表
  • int/uint:整型,根据运行平台可能为32或64位
    • int8/uint8:8位整型,1字节
  • byte:字节型(uint8的别名,无符号8位整型)
  • rune:int32的别名
  • float/float64:4/8字节,精确到小数点后7/15位
  • 复数:complex64/complex128,长度:8/16字节
  • 足够保存指针的32位或64位整型:uintptr
  • 其他值类型:array,struct,string
  • 引用类型:slice(切片),map,chan(通道)
  • 接口类型:interface
  • 函数类型:func

变量声明

// 全局变量
var a int = 1
var b int
var c = 1
d := 1
var (e = 1, f = 2)
// 局部变量
func main(){
    var a,b,c,d = 1,2,3,4
    x,y,z := 1,2,3
}
var a int = 1
b := string(a)
fmt.Println(b) // 输出A
// 如果想要输出字符串的65,需要使用包“strconv”
b := strconv.Itoa(a)
fmt.Println(b) // 输出字符串65
c := strconv.AtoI(b) // 从字符串转回数字

常量

一般规范中定义常量使用全部大写字母来定义,但是由于golang规则中首字母大写表示可以被外部包调用,如果想声明私有常量一般会用_或小写字母c开头,如:

const _MAX = 10
const cNAME = "JOJO"

常量的右侧只能是常量或常量表达式,使用函数也只能是内置的函数,如果不赋值将使用上一行的值

const (
    A = "123"
    B = len(a)
    C
    D
)

因此上述代码中a为“123”,b为3,c和d也都为3

注意:使用上一行的值时要保证每行声明的常量个数要一样

const (
    A, B = 1, 2
    C // 这是不行的,与上一行个数不一致
    C, D // 这个可以,c为1,d为2
)

在Golang中,有iota作为常量的计数器,初始为0,每定义一个常量iota就会增加1,同时每遇到constiota都会变为0,因此枚举应写在一个常量组中

运算符

image-20240602102145861

<<运算符将一个数左移 n 位,相当于将该数以$2^n$;>>运算符将一个数右移 n 位,相当于将该数以$2^n$。

在golang中,++--是作为语句存在,而不像其他语言中作为表达式,因此不能放在等号右侧,只能作为单独的一行

控制语句

分支

golang中的 if 后面没有小括号(),直接用空格分隔if关键字与条件,类似python,但是后面依旧是在同一行接左大括号,左大括号不能换行,否则编译错误。并且可以在写条件的部分声明新变量,此变量为局部变量,如果与外部变量同名则会隐藏外部变量,使用定义的局部变量,定义变量与后面条件用分号;分隔

switch:golang中的switch不需要每个case都写break,找到符合条件的case后会自动break跳出整个switch,如果你想要它继续向下验证可以使用fallthrough显式声明,并且golang中的switch也有不同形式

// 第一种
a := 1
switch a {
    case 0:
        fmt.Println("a=0")
    case 1:
        fmt.Println("a=1") // 只会输出a=1
    default:
        fmt.Println("None")
}
// 第二种
a := 1
switch {
    case a >= 0:
        fmt.Println("a>=0") // 只会输出a>=0
    case a >= 1:
        fmt.Println("a>=1")
    default:
        fmt.Println("None")
}
// 使用 fallthrough
a := 1
switch {
    case a >= 0:
        fmt.Println("a>=0") // 会输出a>=0
        fallthrough
    case a >= 1:
        fmt.Println("a>=1") // 也会输出a>=0
    default:
        fmt.Println("None")
}
// 局部变量a
switch a := 1; {
    case a >= 0:
        fmt.Println("a>=0") // 会输出a>=0
        fallthrough
    case a >= 1:
        fmt.Println("a>=1") // 也会输出a>=0
    default:
        fmt.Println("None")
}

循环

golang中的循环只有for一种,但是这里的for更多样,如下:

// 第一种
for {
    a++
    if a > 3 {
        break
    }
}
// 第二种
for a < 3 {
    a++
}
// 第三种
for a := 0; a < 3; a++ {
    fmt.println(a)
}

break,continue,goto

go中的break和continue除了在其他语言中的用法外还可以配合标签使用

LABLE:
    for {
        for i := 0; i < 3; i++ {
            if i > 1 {
                break LABLE // 直接跳出LABLE处的循环
            }
        }
    }
// go to
for {
    for i := 0; i < 3; i++ {
        if i > 1 {
            goto LABLE // 直接跳到LABLE处开始执行
        }
    }
}
LABLE:
    fmt.Println("goto 到此处")

continue也是同理,带上标签就是跳过标签处的当前这次循环

数组

go中的数组为值类型,在使用时是对数组进行拷贝,并不是像其他语言那样是引用类型指向同一个地址

数组之间可以使用==!=进行比较,但是不能使用<>,并且类型不同也是不能进行比较

var a [2]int // 类型为 长度为2的整型
var a [1]int // 类型为 长度为1的整型,两个数组类型不同
// 前19个元素为0,第20个元素为1
a := [20]int{19: 1}
// 已知数组内容可以用 ... 省略数组元素个数
a := [...]int{1,2,3,4,5}
a := [...]int{19: 1} // 这样也是可以的,和上面的效果一样

指向数组的指针和指针数组

// 指向数组的指针
a := [...]int{3: 1}
var p *[4]int = &a // p的值为 &[0,0,0,1],就是打印a的值前面加上了&,表示取这个数组的地址
// new
p := new([5]int) // 使用new这种形式得到的依旧是指针,p的值为 &[0,0,0,0,0]
// 指针数组
x, y = 1, 2
a := [...]*int{&x, &y} // 是一个指针类型的数组,里面存放了x和y的地址

go中也是依然有多维数组,也是只能省略前面不能省略后面

a := [...][3]{{1,1,1},{2,2,2}}

用golang实现一下经典的冒泡排序

// 冒泡排序
a := [...]int{5, 2, 6, 3, 9}
fmt.Println("排序前:", a)
num := len(a) // 提前计算长度免得每次循环都要重新计算浪费资源
for i := 0; i < num; i++ {
    for j := 0; j < num; j++ {
        if a[i] < a[j] {
            a[i], a[j] = a[j], a[i] // 真的方便,而且不用再定义一个temp
        }
    }
}
fmt.Println("排序后", a)

切片Slice

切片为引用类型,其作为变长数组的替代方案,可以关联底层数组的局部或全部,可以直接创建或从底层数组获取生成

如果多个slice指向相同的底层数组,其中一个的值改变会影响全部

声明切片和数组类似,只不过中括号里是空的

var s []int // 打印出来是 []

slice从数组中取元素

a := [10]int{0,1,2,3,4,5,6,7,8,9}
s := a[5] // 取索引为5的值,打印出来是 5
s := a[5: 10] // 范围左闭右开,即取索引5到9的数据,打印出来是 [5,6,7,8,9]
s := a[5:] // 结果和上面一样
s := a[:5] // 取前五个元素,依然是左闭右开,打印出来是 [0,1,2,3,4]

切片比较正式的创建是使用make创建

make([]T, len, cap) // len表示存储元素的个数,cap表示容量,cap可以省略,省略代表cap和len的值一样

补充:函数传参修改slice时推荐使用返回

package main

import "fmt"

func Keng(s []int) []int {
    s = append(s, 3)
    return s
}
func main() {
    s := make([]int, 0)
    fmt.Println(s)
    s = Keng(s)
    fmt.Println(s)
}
/* 输出结果
[]
[3]
*/

make时slice的长度和容量都为0,为了避免空间浪费可以使用返回的形式对其重新赋值

Reslice

Reslice时索引以被slice的切片为准

索引不可以超过被slice的切片的容量cap0值

索引越界不会导致底层数组的重新分配而是引发错误

func main() {
    a := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'}
    sa := a[2:5]
    fmt.Println(string(sa)) // cde
    fmt.Println(len(sa), cap(sa)) // 3 9
    sb := sa[3:5]
    fmt.Println(string(sb)) // fg
}

image-20240609120142179

Append

可以在glice尾部追加元素

可以将一个slice追加在另一个slice尾部

func main() {
    s1 := make([]int, 3, 6)
    fmt.Printf("%p\n", s1) // 0xc00000e3f0
    s1 = append(s1, 1, 2, 3)
    fmt.Printf("%v %p\n", s1, s1) // [0 0 0 1 2 3] 0xc00000e3f0
    s1 = append(s1, 1, 2, 3)
    fmt.Printf("%v %p\n", s1, s1) // [0 0 0 1 2 3 1 2 3] 0xc000016180
}

如果最终长度未超过追加到slice的容量则返回原始slice

如果超过追加到的slice的容量则将重新分配数组并拷贝原始数据

func main() {
    a := []int{1, 2, 3, 4, 5}
    s1 := a[2:5]
    s2 := a[1:3]
    fmt.Println(s1, s2) // [3 4 5] [2 3]
    s1[0] = 9
    fmt.Println(s1, s2) // [9 4 5] [2 9]
}

slice指向数组,未超出容量时修改其中一个的值,其他的值也会跟着改变

func main() {
    a := []int{1, 2, 3, 4, 5}
    s1 := a[2:5]
    s2 := a[1:3]
    fmt.Println(s1, s2) // [3 4 5] [2 3]
    s2 = append(s2, 1, 2, 3, 4, 5)
    s1[0] = 9
    fmt.Println(s1, s2) // [9 4 5] [2 3 1 2 3 4 5]
}

超过容量后会重新分配地址,修改其中一个的值,将不会影响到新分配的slice

copy

func main() {
    s1 := []int{1, 2, 3, 4, 5}
    s2 := []int{7, 8, 9}
    copy(s1, s2)
    fmt.Println(s1) // [7 8 9 4 5]
}
func main() {
    s1 := []int{1, 2, 3, 4, 5}
    s2 := []int{7, 8, 9}
    copy(s2, s1)
    fmt.Println(s2) // [1 2 3]
}

也可以只拷贝一部分

func main() {
    s1 := []int{1, 2, 3, 4, 5}
    s2 := []int{7, 8, 9}
    copy(s2, s1[:2])
    fmt.Println(s2) // [1 2 9]
}

也可以这样

func main() {
    s1 := []int{1, 2, 3, 4, 5}
    s2 := []int{7, 8, 9}
    copy(s1[2:4], s2[:2])
    fmt.Println(s1) // [1 2 7 8 5]
}

map

类似其它语言中的哈希表或者字典,以key-value形式存储数据

Key必须是支持==或!=比较运算的类型,不可以是函数、map或slice

Map查找比线性搜索快很多,但比使用索引访问数据的类型慢100倍

Map使用make0创建,支持:=这种简写方式

make([keyType]valueType,cap),cap表示容量,可省略

超出容量时会自动扩容,但尽量提供一个合理的初始值

使用len0)获取元素个数

键值对不存在时自动添加,使用delete0)删除某键值对

使用 for range 对map和slice进行迭代操作

var m map[int]string = make(map[int]string) // 声明map
func main() {
    m := make(map[int]string)
    m[1] = "go"
    a := m[1]
    fmt.Println(a) // go
    delete(m, 1)
    a = m[1]
    fmt.Println(a) //  (输出的是空)
}

map的值为一个map

func main() {
    var m map[int]map[int]string
    m = make(map[int]map[int]string)
    a, ok := m[2][1]
    fmt.Println(a, ok) //  false (表示map没有声明)
    if !ok {
        m[2] = make(map[int]string)
    }
    m[2][1] = "OKK"
    a, ok = m[2][1]
    fmt.Println(a, ok) // OKK true (表示map声明了)
}

迭代

// 迭代slice
for i,v := range slice {
    // i是索引,v是slice的拷贝
}
// 迭代map
for i,v := range map {
    // i是键,v是map的拷贝
}

v只是一个拷贝,对v操作不会影响slice(map)本身

func main() {
    sm := make([]map[int]string, 5)
    for _, v := range sm {
        v = make(map[int]string, 1)
        v[1] = "OK"
        fmt.Println(v)
    }
    fmt.Println(sm)
}
/* 输出结果
map[1:OK]
map[1:OK]
map[1:OK]
map[1:OK]
map[1:OK]
[map[] map[] map[] map[] map[]]
*/

上面并不会影响切片map本身,如果想操作其本身,可以使用下面代码

func main() {
    sm := make([]map[int]string, 5)
    for i := range sm {
        sm[i] = make(map[int]string, 1)
        sm[i][1] = "OK"
        fmt.Println(sm[i])
    }
    fmt.Println(sm)
}
/* 输出结果
map[1:OK]
map[1:OK]
map[1:OK]
map[1:OK]
map[1:OK]
[map[1:OK] map[1:OK] map[1:OK] map[1:OK] map[1:OK]]
*/

对map实现间接排序

func main() {
    m := map[int]string{1: "a", 2: "b", 3: "c", 4: "d", 5: "e"}
    s := make([]int, len(m))
    i := 0
    for k, _ := range m {
        s[i] = k
        i++
    }
    sort.Ints(s) // 对整型进行排序
    fmt.Println(s)
}

迭代map取出的key顺序是随机的,将取出的key放入slice中,再对其排序可以实现间接对map的key进行排序

将map的key和value对调

func main() {
    m := map[int]string{1: "a", 2: "b", 3: "c", 4: "d", 5: "e"}
    fmt.Println(m) // map[1:a 2:b 3:c 4:d 5:e]
    m2 := make(map[string]int)
    for k, v := range m {
        m2[v] = k
    }
    fmt.Println(m2) // map[a:1 b:2 c:3 d:4 e:5]
}

函数function

Go 函数 不支持 嵌套、重载和默认参数

但支持以下特性:

  • 无需声明原型:不定长度变参、多返回值、命名返回值参数
  • 匿名函数、闭包

定义函数使用关键字 func,且左大括号不能另起一行

函数也可以作为一种类型使用

// 不定长度变参 相当于拷贝一份并放入slice中 不定长度变参只能放在参数最后面
func A(b string, a ...int){
    fmt.Println(a)
}
// 多返回值 命名返回值参数
func A() (a,b,c int){
    a,b,c = 1,2,3 // a,b,c已经声明过了,这里可以直接赋值
    return // 上面返回值处已经声明了要返回的a,b,c,这里可以省略
}
// 匿名函数
a := func(){
    fmt.Println("Func")
} // 可以使用a()来调用这个函数

匿名函数不能作为顶级函数,也就是不能作为最外层函数

闭包

func main() {
    f := closure(10)
    fmt.Println(f(1)) // 
    fmt.Println(f(2)) // 
}

func closure(x int) func(int) int {
    fmt.Printf("%p\n", &x) // 
    return func(y int) int {
        fmt.Printf("%p\n", &x) // 
        return x + y
    }
}
/* 输出结果
0xc000096068
0xc000096068
11
0xc000096068
12
*/

defer

defer的执行方式类似其它语言中的析构函数,在函数体执行结束后

按照调用顺序的相反顺序逐个执行

即使函数发生严重错误也会执行

支持匿名函数的调用

常用于资源清理、文件关闭、解锁以及记录时间等操作

通过与匿名函数配合可在return之后修改函数计算结果

如果函数体内某个变量作为defer时匿名函数的参数,则在定义defer

时即已经获得了拷贝,否则则是引用某个变量的地址

Go 没有异常机制,但有 panic/recover 模式来处理错误

Panic 可以在任何地方引发,但recover只有在defer调用的函数中有效

for i := 0; i < 3; i++ {
    defer fmt.Println(i)
}

上述代码打印结果为2 1 0,defer先进后出,为栈结构

defer与闭包一起使用

for i := 0; i < 3; i++ {
    defer func() {
        fmt.Println(i)
    }() // 自执行函数,如果把前面的匿名函数赋给变量a,就和a()一样了
}

上述代码执行结果为3 3 3,内层函数没有i,向外层寻找找到了i,拿到了i的引用也就是i的地址,循环结束时i为3,所以闭包调用i的都为3

panic与recover

func main() {
    A()
    B()
    C()
}
func A() {
    fmt.Println("func A")
}
func B() {
    panic("panic in B")
}
func C() {
    fmt.Println("func C")
}
/* 输出结果
func A
panic: panic in B
报错
*/

调用panic之后程序不会再执行后面的内容,这个时候就可以使用recover再回复回来,注:recover只有在defer调用的函数中有效

func main() {
    A()
    B()
    C()
}
func A() {
    fmt.Println("func A") // func A
}
func B() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("recover in B") // recover in B
            fmt.Println(err) // panic in B
        }
    }()
    panic("panic in B")
}
func C() {
    fmt.Println("func C") // func C
}

调用recover之后将不会调用panic,打印err时会输出panic in B

疑问代码

以下代码输出结果不一致

func main() {
    var fs = [4]func(){}
    for i := 0; i < 4; i++ {
        defer fmt.Println("defer i = ", i)
        defer func() {
            fmt.Println("defer_closure i = ", i)
        }()
        fs[i] = func() {
            fmt.Println("closure i = ", i)
        }
    }
    for _, f := range fs {
        f()
    }
}
/* 输出结果
closure i =  0
closure i =  1
closure i =  2
closure i =  3
defer_closure i =  3
defer i =  3
defer_closure i =  2
defer i =  2
defer_closure i =  1
defer i =  1
defer_closure i =  0
defer i =  0
*/

上述代码原来的输出结果如下

closure i =  4
closure i =  4
closure i =  4
closure i =  4
defer_closure i =  4
defer i =  3
defer_closure i =  4
defer i =  2
defer_closure i =  4
defer i =  1
defer_closure i =  4
defer i =  0

这是为什么,go发生了什么重大的改变吗?

结构struct

Go 中的struct与C中的struct非常相似,并且Go没有class

使用 typestruct{} 定义结构,名称遵循可见性规则

支持指向自身的指针类型成员

支持匿名结构,可用作成员或定义成员变量

匿名结构也可以用于map的值

可以使用字面值对结构进行初始化

允许直接通过指针来读写结构成员

相同类型的成员可进行直接拷贝赋值

支持 ==与!=比较运算符,但不支持>或<

支持匿名字段,本质上是定义了以某个类型名为名称的字段

嵌入结构作为匿名字段看起来像继承,但不是继承

可以使用匿名字段指针

package main

import "fmt"

type person struct {
    Name string
    Age  int
}

func main() {
    a := person{
        Name: "jojo",
    }
    a.Age = 19
    fmt.Println(a)
    A(a)
    fmt.Println(a)
}
func A(per person) {
    per.Age = 12
    fmt.Println("func A", per)
}
/* 输出结果
{jojo 19}
func A {jojo 12}
{jojo 19}
*/

在函数A中只是一个值拷贝,可以使用指针来修改原值,变成引用

package main

import "fmt"

type person struct {
    Name string
    Age  int
}

func main() {
    a := person{"jojo", 19} // 也可以直接这样赋值
    fmt.Println(a)
    A(&a)
    fmt.Println(a)
}
func A(per *person) {
    per.Age = 12
    fmt.Println("func A", per)
}
/* 输出结果
{jojo 19}
func A &{jojo 12}
{jojo 12}
*/

在go中哪怕取了地址,也可以像之前一样修改值,如下

a := &person{
    Name: "jojo"
}
a.Name = "Dio" // 完全可以并且是正确的,这是go方便的地方

匿名结构

a := struct {
    Name string
    Age int
}{
    Name: "jojo",
    Age: 19,
}

go中的组合,对标继承的存在

type human struct{
    Sex int
}
type teacher struct{
    human
    Name string
    Age int
}
type student struct {
    human
    Name string
    Age int
}
func main(){ 
    a := teacher{Name: "joe",Age: 19,human: human{Sex: 0}}
    b := student{Name: "joe",Age: 20,human: human{Sex: 1}}
    a.Name = "joe2"
    a.Age = 13
    a.human.Sex = 10 // 可以这样
    a.Sex = 100 // 也可以直接这样
    fmt.Println(a,b)
}

方法method

Go 中虽没有class,但依旧有method

通过显示说明receiver来实现与某个类型的组合

只能为同一个包中的类型定义方法

Receiver可以是类型的值或者指针

不存在方法重载

可以使用值或指针来调用方法,编译器会自动完成转换

从某种意义上来说,方法是函数的语法糖,因为receiver其实就是方法所接收的第1个参数(Method Valuevs,Method Expression)

如果外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法

类型别名不会拥有底层类型所附带的方法

方法可以调用结构中的非公开字段

package main

import "fmt"

type A struct {
    Name string
}
type B struct {
    Name string
}

func main() {
    a := A{}
    a.Print() // A
    fmt.Println(a.Name) // AA
    b := B{}
    b.Print() // B
    fmt.Println(b.Name) // 输出是空,因为Name没有值
}
func (a *A) Print() {
    a.Name = "AA"
    fmt.Println("A")
}
func (b B) Print() {
    b.Name = "BB"
    fmt.Println("B")
}

类型别名也可以绑定方法

type TZ int
func main(){
    var a TZ
    a.Print() // TZ
}
func (a *TZ)Print(){
    fmt.Println("TZ")
}

还可以这样

type TZ int
func main(){
    var a TZ
    a.Print() // Method Valuevs
    (*TZ).Print(&a) // Method Expression
}
func (a *TZ)Print(){
    fmt.Println("TZ")
}
/* 输出结果
TZ
TZ
*/

接囗interface

接口是一个或多个方法签名的集合

只要某个类型拥有该接口的所有方法签名,即算实现该接口,无需显示

声明实现了哪个接口,这称为 Structural Typing

接口只有方法声明,没有实现,没有数据字段

接口可以匿名嵌入其它接口,或嵌入到结构中

将对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个

复制品的指针,既无法修改复制品的状态,也无法获取指针

只有当接口存储的类型和对象都为nil时,接口才等于nil

接口调用不会做receiver的自动转换

接口同样支持匿名字段方法

接口也可实现类似OOP中的多态

空接口可以作为任何类型数据的容器

package main

import "fmt"

type USB interface {
    Name() string
    Connecter
}
type Connecter interface {
    Connect()
}

type PhoneConnecter struct {
    name string
}

func (pc PhoneConnecter) Name() string {
    return pc.name
}
func (pc PhoneConnecter) Connect() {
    fmt.Println("Connected:", pc.name)
}
func main() {
    a := PhoneConnecter{"PhoneConnecter"}
    a.Connect()
    Disconnect(a)
}

func Disconnect(usb USB) {
    if pc, ok := usb.(PhoneConnecter); ok {
        fmt.Println("Disconnect:", pc.name)
        return
    }
    fmt.Println("Unknown decive")
}
/* 输出结果
Connected: PhoneConnecter
Disconnect: PhoneConnecter
*/

像Object一样,go中也可以看作所有结构都“继承”了空的接口

package main

import "fmt"

type empty interface{}
type USB interface {
    Name() string
    Connecter
}
type Connecter interface {
    Connect()
}

type PhoneConnecter struct {
    name string
}

func (pc PhoneConnecter) Name() string {
    return pc.name
}
func (pc PhoneConnecter) Connect() {
    fmt.Println("Connected:", pc.name)
}
func main() {
    a := PhoneConnecter{"PhoneConnecter"}
    a.Connect()
    Disconnect(a)
}

func Disconnect(usb interface{}) {
    switch v := usb.(type) {
    case PhoneConnecter:
        fmt.Println("Disconnect:", v.name)
    default:
        fmt.Println("Unknown decive")
    }
}
/* 输出结果
Connected: PhoneConnecter
Disconnect: PhoneConnecter
*/

反射reflection

反射可大大提高程序的灵活性,使得interface{} 有更大的发挥余地

反射使用 TypeOf和 ValueOf函数从接口中获取目标对象信息

反射会将匿名字段作为独立字段(匿名字段本质)

想要利用反射修改对象状态,前提是interface.data是settable即 pointer-interface

通过反射可以“动态”调用方法

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id   int
    Name string
    Age  int
}

func (u User) Hello() {
    fmt.Println("hello world")
}

func main() {
    u := User{1, "jack", 20}
    Info(u)
}

func Info(o interface{}) {
    t := reflect.TypeOf(o)
    fmt.Println("type:", t.Name())
    if k := t.Kind(); k != reflect.Struct {
        fmt.Println("不合法的类型:", t.Kind())
        return
    }
    v := reflect.ValueOf(o)
    fmt.Println("Fields:")
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        val := v.Field(i).Interface()
        fmt.Printf("%6s: %v = %v\n", f.Name, f.Type, val)
    }
    for i := 0; i < t.NumMethod(); i++ {
        m := t.Method(i)
        fmt.Printf("%6s: %v\n", m.Name, m.Type)
    }
}
/* 输出结果
type: User
Fields:
    Id: int = 1
  Name: string = jack
   Age: int = 20
 Hello: func(main.User)
*/

/* Info(&u) 触发if报错的输出结果
type: 
不合法的类型: ptr
*/

反射操作基本类型

func main() {
    x := 123
    v := reflect.ValueOf(&x)
    v.Elem().SetInt(999)
    fmt.Println(x) // 999
}

反射修改对象属性

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id   int
    Name string
    Age  int
}

func main() {
    u := User{1, "jack", 20}
    Set(&u)
    fmt.Println(u)
}

func Set(o interface{}) {
    v := reflect.ValueOf(o)
    if v.Kind() == reflect.Ptr && !v.Elem().CanSet() {
        fmt.Println("类型不对")
        return
    } else {
        v = v.Elem()
    }
    f := v.FieldByName("Name")
    if !f.IsValid() {
        fmt.Println("没找到这个字段")
        return
    }
    if f.Kind() == reflect.String {
        f.SetString("OK")
    }
}
/* 输出结果
{1 OK 20}
*/

通过反射调用方法

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id   int
    Name string
    Age  int
}

func (u User) Hello(Name string) {
    fmt.Println("Hello:", Name, ",我是:", u.Name)
}

func main() {
    u := User{1, "jack", 20}
    v := reflect.ValueOf(u)
    mv := v.MethodByName("Hello")
    args := []reflect.Value{reflect.ValueOf("joe")}
    mv.Call(args)
}
/* 输出结果
Hello: joe ,我是: jack
*/

并发concurrency

Channel

Channel是 goroutine沟通的桥梁,大都是阻塞同步的

通过 make 创建,close 关闭

Channel是引用类型

可以使用 for range 来迭代不断操作 channel

可以设置单向或双向通道

可以设置缓存大小,在未被填满前不会发生阻塞

func main() {
    c := make(chan bool)
    go func() {
        fmt.Println("GO GO GO!") // GO GO GO!
        c <- true
    }()
    <-c
}
func main() {
    c := make(chan bool)
    go func() {
        fmt.Println("GO GO GO!")
        c <- true
        close(c)
    }()
    for v := range c {
        fmt.Println(v)
    }
}
/* 输出结果
GO GO GO!
true
*/

多CPU核数

package main

import (
    "fmt"
    "runtime"
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    c := make(chan bool, 10)
    for i := 0; i < 10; i++ {
        go Go(c, i)
    }
    for i := 0; i < 10; i++ {
        <-c
    }
}
func Go(c chan bool, index int) {
    a := 1
    for i := 0; i < 10000000; i++ {
        a += 1
    }
    fmt.Println(index, a)
    c <- true
}

另一种方法,同步任务组

package main

import (
    "fmt"
    "runtime"
    "sync"
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    wg := sync.WaitGroup{}
    wg.Add(10)
    for i := 0; i < 10; i++ {
        go Go(&wg, i)
    }
    wg.Wait()
}
func Go(wg *sync.WaitGroup, index int) {
    a := 1
    for i := 0; i < 10000000; i++ {
        a += 1
    }
    fmt.Println(index, a)
    wg.Done()
}

Select

可处理一个或多个channel 的发送与接收

同时有多个可用的 channel时按随机顺序处理

可用空的 select 来阻塞 main 函数

可设置超时

package main

import (
    "fmt"
)

func main() {
    c1, c2 := make(chan int), make(chan string)
    o := make(chan bool, 2)
    go func() {
        for {
            select {
            case v, ok := <-c1:
                if !ok {
                    o <- true
                    break
                }
                fmt.Println("c1", v)
            case v, ok := <-c2:
                if !ok {
                    o <- true
                    break
                }
                fmt.Println("c2", v)
            }
        }
    }()
    c1 <- 1
    c2 <- "hi"
    c1 <- 3
    c2 <- "hello"
    close(c1)
    close(c2)
    for i := 0; i < 2; i++ {
        <-o
    }
}
/* 输出结果
c1 1
c2 hi
c1 3
c2 hello
*/

时间time的坑

time.format

image-20240615205114208

山水有相逢,我们江湖再见!じゃな~
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇