go_demo
values
Go 有各种值类型,包括字符串、整数、浮点数、布尔值等。这里是一些基本示例
1
2
3
4
5
6
7
8
9
10
11
12
13
| package main
import "fmt"
func main() {
fmt.Println("go" + "lang")
fmt.Println("1+1 =", 1+1)
fmt.Println("7.0/3.0 =", 7.0/3.0)
fmt.Println(true && false)
fmt.Println(true || false)
fmt.Println(!true)
}
|
变量
在 Go 中,变量由编译器明确声明和使用,例如检查函数调用的类型正确性
var可以一次声明一个或多个变量,Go将判断初始化变量的类型,未进行初始化的声明变量为零值
:=近可在函数内部使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| package main
import "fmt"
func main() {
var a = "initial"
fmt.Println(a)
var b, c int = 1, 2
fmt.Println(b, c)
var d = true
fmt.Println(d)
var e int
fmt.Println(e)
f := "apple"
fmt.Println(f)
}
|
常量
Go支持字符、字符串、布尔值、数字值的常量
const
声明一个常量值,用const取代var即可,数字常量没有类型,除非通过显式转换等方式指定类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| package main
import (
"fmt"
"math"
)
const s string = "constant"
func main() {
fmt.Println(s)
const n = 500000000
const d = 3e20 / n
fmt.Println(d)
fmt.Println(int64(d))
fmt.Println(math.Sin(n))
}
|
for
for
是 Go 唯一的循环结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| package main
import "fmt"
func main() {
i := 1
for i <= 3 {
fmt.Println(i)
i++
}
for j := 0; j < 5; j++ {
fmt.Println(j)
}
nums := []int{1, 2, 3}
for i := range nums {
fmt.Println("range", i)
}
for {
fmt.Println("loop")
break
}
for i := 1; i <= 6; i++ {
if i%2 == 1 {
continue
}
fmt.Println(i)
}
}
|
If-else
可以有if
一个不带 else 的语句,语句可以位于条件之前,此语句声明任何变量可以在该分支及后续分支使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| package main
import "fmt"
func main() {
if 7%2 == 0 {
fmt.Println("7 is even")
}
if 8%2 == 0 || 7%2 == 0 {
fmt.Println("either 8 or 7 are even")
}
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num > 0 {
fmt.Println(num, "is positive")
} else {
fmt.Println(num, "is zero")
}
}
|
switch
Switch 语句表达跨多个分支的条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| package main
import (
"fmt"
"time"
)
func main() {
i := 2
fmt.Print("write", i, "as")
switch i {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
}
switch time.Now().Weekday() {
//可以使用逗号分隔同一`case`语句中的多个表达式
case time.Saturday, time.Sunday:
fmt.Println("it's the weekend")
default:
fmt.Println("it's a weekday")
}
t := time.Now()
//`switch`不使用表达式是表达 if/else 逻辑的另一种方式,
switch {
case t.Hour() < 12:
fmt.Println("it's before noon")
default:
fmt.Println("it's after noon")
}
whatAmI := func(i interface{}) {
//类型`switch`比较的是类型而不是值。可以使用它来发现接口值的类型
switch t := i.(type) {
case bool:
fmt.Println("I'm a bool")
case int:
fmt.Println("I'm an int")
case string:
fmt.Println("I'm a string")
default:
fmt.Printf("Don't know type %T\n", t)
}
}
whatAmI(true)
whatAmI("hey")
whatAmI(1)
}
|
数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| package main
import "fmt"
func main() {
var a [5]int
fmt.Println(a)
a[4] = 100
fmt.Println(a[4])
fmt.Println(len(a))
b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)
//可以让编译器计算元素的数量...
b = [...]int{1, 2, 3, 4, 5}
fmt.Println(b)
//[100 0 0 400 500],使用 指定索引:,则其间的元素将被清零
b = [...]int{100, 3: 400, 500}
fmt.Println(b)
var twoD [2][3]int
for i := 0; i < len(twoD); i++ {
for j := 0; j < len(twoD[i]); j++ {
twoD[i][j] = i + j
}
}
fmt.Println(twoD)
twoD = [2][3]int{
{1, 2, 3},
{2, 3, 4},
}
fmt.Println(twoD)
}
|
切片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
| package main
import (
"fmt"
"reflect"
"slices"
)
func main() {
var s []string
//[] true true
fmt.Println(s, s == nil, len(s) == 0)
s = make([]string, 3)
//[ ] false 3 3
fmt.Println(s, s == nil, len(s), cap(s))
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println(s)
fmt.Println(s[2])
fmt.Println(len(s))
////需要接受返回值, append因为我们可能会得到一个新的切片值
s = append(s, "d")
s = append(s, "e", "f")
//[a b c d e f]
fmt.Println(s)
c := make([]string, len(s))
//这里我们创建一个与 长度相同的copy空切片并将其复制到中
copy(c, s)
fmt.Println(c)
l := s[2:5]
//[c d e]
fmt.Println(l)
//可以在一行中声明并初始化切片的变量
t := []string{"a", "b", "c"}
t2 := []string{"a", "b", "c"}
//t==t2,这个用于判断两个切片中的内容是否相同
if slices.Equal(t, t2) {
fmt.Println("t==t2")
}
//如果想比较两个的地址的话,可以使用返回或者比较两个的指针,不能直接用t==t2进行比较
if reflect.ValueOf(t).Pointer() == reflect.ValueOf(t2).Pointer() {
fmt.Println("t and t2 share the same underlying array")
} else {
fmt.Println("t and t2 do not share the same underlying array")
}
if &t[0] == &t2[0] {
fmt.Println("t and t2 share the same underlying array")
} else {
fmt.Println("t and t2 do not share the same underlying array")
}
//切片可以组成多维数据结构。与多维数组不同,内部切片的长度可以变化
twoD := make([][]int, 3)
for i := 0; i < len(twoD); i++ {
inner := i + 1
twoD[i] = make([]int, inner)
for j := 0; j < inner; j++ {
twoD[i][j] = i + j
}
}
//[[0] [1 2] [2 3 4]]
fmt.Println(twoD)
}
|
映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| package main
import (
"fmt"
"maps"
)
func main() {
m := make(map[string]int)
m["k1"] = 7
m["k2"] = 13
//map[k1:7 k2:13]
fmt.Println(m)
v1 := m["k2"]
fmt.Println(v1)
v3 := m["k3"]
//0,如果键不存在, 则返回值类型的零值
fmt.Println(v3)
fmt.Println(len(m))
//内置命令delete从映射中删除键/值对
delete(m, "k2")
//map[k1:7]
fmt.Println(m)
//要从map中删除所有clear键/值对
clear(m)
//map[]
fmt.Println(m)
//从映射中获取值时,可选的第二个返回值指示映射中是否存在该键。
//这可用于区分缺失键和具有零值的键(如0或 )""。
//这里我们不需要值本身,因此我们用空白标识符 _忽略它
_, prs := m["k2"]
//false
fmt.Println(prs)
n := map[string]int{"foo": 1, "bar": 2}
fmt.Println(n)
//map[k:v k:v]请注意,使用 打印时,地图会以表格形式出现fmt.Println
n2 := map[string]int{"foo": 1, "bar": 2}
if maps.Equal(n, n2) {
//true
fmt.Println("true")
}
}
|
函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| package main
import "fmt"
func plus(a int, b int) int {
//Go 需要显式返回,即它不会自动返回最后一个表达式的值
return a + b
}
func plusPlus(a, b, c int) int {
return a + b + c
}
func vals() (int, int) {
//可以多返回值
return 3, 7
}
func main() {
res := plus(1, 2)
fmt.Println(res)
res = plusPlus(1, 2, 3)
fmt.Println(res)
a, b := vals()
fmt.Println(a, b)
_, c := vals()
//7
fmt.Println(c)
}
|
闭包
Go 支持匿名函数,可以形成闭包。当你想以内联方式定义函数而不必为其命名时,匿名函数非常有用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| package main
import "fmt"
func intSeq() func() int {
i := 0
return func() int {
i++
return i
}
}
func main() {
//每次会创建一个新的状态
nextInt := intSeq()
//1
fmt.Println(nextInt())
//2
fmt.Println(nextInt())
//3
fmt.Println(nextInt())
newInt := intSeq()
//1
fmt.Println(newInt())
}
|
递归
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| package main
import "fmt"
func fact(n int) int {
if n == 0 {
return 1
}
return n * fact(n-1)
}
func main() {
fmt.Println(fact(5))
var fib func(n int) int
fib = func(n int) int {
if n < 2 {
return n
}
return fib(n-1) + fib(n-2)
}
fmt.Println(fib(10))
}
|
range函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| package main
import "fmt"
func main() {
nums := []int{1, 2, 3}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println(sum)
for i, num := range nums {
if num == 3 {
//2
fmt.Println(i)
}
}
kvs := map[string]int{"a": 1, "b": 2}
for k, v := range kvs {
fmt.Println(k, v)
}
for k := range kvs {
fmt.Println(k)
}
for i, c := range "go" {
fmt.Println(i, c)
}
}
|
指针
&i
语法给出了 的内存地址i
,即 的指针i
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| package main
import "fmt"
func zeroval(inval int) {
inval = 0
}
func zeroptr(inptr *int) {
*inptr = 0
}
func main() {
i := 1
fmt.Println(i)
zeroval(i)
fmt.Println(i)
zeroptr(&i)
fmt.Println(i)
}
|
strings
在 Go 中,rune
是一个类型,实际上是一个表示 Unicode 码点的整数(本质上是 int32
类型)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| package main
import (
"fmt"
"unicode/utf8"
)
func main() {
const s = "Zbs"
//3
fmt.Println(len(s))
for i := 0; i < len(s); i++ {
//Z b s
fmt.Printf("%c ", s[i])
}
fmt.Println()
//3,用于计算字符串中 Unicode 字符(即 rune) 的数量
fmt.Println(utf8.RuneCountInString(s))
//如果没有%c,就会输出ascii的int值
for idx, value := range s {
//0 90
//1 98
//2 115
fmt.Println(idx, value)
}
for i, w := 0, 0; i < len(s); i += w {
//用于解码字符串中的下一个 Unicode 字符(rune),并返回该字符以及它的字节宽度
runValue, width := utf8.DecodeRuneInString(s[i:])
fmt.Println(width, runValue)
w = width
examineRunne(runValue)
}
}
func examineRunne(r rune) {
if r == 'b' {
fmt.Println("b")
} else if r == 'a' {
fmt.Println("a")
}
}
|
结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| package main
import "fmt"
type person struct {
name string
age int
}
func newPerson(name string, age int) *person {
return &person{name, age}
}
func main() {
fmt.Println(person{"Bob", 20})
fmt.Println(person{name: "Bob", age: 20})
fmt.Println(person{name: "Bob"})
//&{Bob 20}
fmt.Println(&person{name: "Bob", age: 20})
//&{Bob 20}
//将新结构体的创建封装在构造函数中是一种惯用做法
fmt.Println(newPerson("Bob", 20))
s := person{name: "Bob", age: 20}
fmt.Println(s.name)
sp := &s
//20
fmt.Println(sp.age)
sp.age = 30
//30
fmt.Println(s.age)
dog := struct {
name string
isGood bool
}{
"name",
true,
}
fmt.Println(dog)
}
|
方法
Go 自动处理方法调用时值与指针之间的转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| package main
import "fmt"
type rect struct {
width, height int
}
//该area方法的接收器类型为*rect
func (r *rect) area() int {
return r.width * r.height
}
//可以为指针或值接收者类型定义方法。以下是值接收者
func (r rect) perim() int {
return 2*r.width + 2*r.height
}
func main() {
r := rect{width: 10, height: 5}
fmt.Println("area: ", r.area())
fmt.Println("perim:", r.perim())
//Go 自动处理方法调用时值与指针之间的转换
rp := &r
fmt.Println("area: ", rp.area())
fmt.Println("perim:", rp.perim())
}
|
接口
在Go中实现接口,我们只需要实现接口里的所有方法即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| package main
import (
"fmt"
"math"
)
type geometry interface {
area() float64
perim() float64
}
type rectt struct {
width, height float64
}
type circle struct {
radius float64
}
func (r rectt) area() float64 {
return r.width * r.height
}
func (r rectt) perim() float64 {
return 2*r.width + 2*r.height
}
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
//如果变量具有接口类型,那么我们可以调用指定接口中的方法
func measure(g geometry) {
fmt.Println(g)
}
func main() {
r := rectt{width: 10, height: 5}
c := circle{radius: 5}
//circle和结构类型rect都实现了geometry接口,因此我们可以使用这些结构的实例作为的参数measure
measure(r)
measure(c)
}
|
枚举
枚举是一种具有固定数量可能值的类型,每个值都有不同的名称。Go 没有枚举类型作为独特的语言功能,但使用现有的语言习语很容易实现枚举。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| package main
import "fmt"
type ServerState int
const (
//这里通过 iota 创建了四个常量,分别表示服务器的四种状态
//iota 是 Go 中用于生成常量值的一个特殊关键字,它在每一行常量定义中会自动递增
StateIdle ServerState = iota
StateConnected
StateError
StateRetrying
)
var stateName = map[ServerState]string{
StateIdle: "idle",
StateConnected: "connected",
StateError: "error",
StateRetrying: "retrying",
}
func (ss ServerState) String() string {
return stateName[ss]
}
func main() {
//这里虽然ServerState是int类型,但不能传int类型进去,否则编译器会报错,因为它们的类型不同。
ns := transition(StateIdle)
fmt.Println(ns)
ns2 := transition(ns)
fmt.Println(ns2)
}
// transition 模拟服务器的状态转换;它采用现有状态并返回新状态
func transition(s ServerState) ServerState {
switch s {
case StateIdle:
return StateConnected
case StateConnected, StateRetrying:
return StateIdle
case StateError:
return StateError
default:
panic(fmt.Errorf("unknown state: %s", s))
}
}
|
结构体嵌入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| package main
import "fmt"
type base struct {
num int
}
func (b base) describe() string {
return fmt.Sprintf("base with num=%v", b.num)
}
type container struct {
//Acontainer 嵌入a base。嵌入看起来像一个没有名称的字段
base
str string
}
func main() {
//当使用文字创建结构时,我们必须明确初始化嵌入;这里嵌入的类型用作字段名称
co := container{
base: base{
num: 1,
},
str: "some name",
}
//这样就可以直接访问子结构体的子字段 例如co.num
fmt.Printf("co={num: %v, str: %v}\n", co.num, co.str)
fmt.Println("also num:", co.base.num)
//由于container嵌入base, 的方法 base也成为 的方法。这里我们 直接container在 上调用嵌入的方法
fmt.Println("describe:", co.describe())
type describer interface {
describe() string
}
//嵌入带有方法的结构可用于将接口实现赋予其他结构。这里我们看到 acontainer现在实现了 describer接口,因为它嵌入了base。
var d describer = co
fmt.Println("describer:", d.describe())
}
|
泛型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| package main
import "fmt"
// SlicesIndex 使用了 类型约束,其中 S ~[]E 和 E comparable 是函数的类型参数约束
// S 必须是 切片类型,并且该切片的元素类型是 E
// ~ 运算符在这里的作用是允许类型别名,即允许 S 不仅仅是 []E 这样的原始切片类型,也可以是一个切片类型的类型别名
func SlicesIndex[S ~[]E, E comparable](s S, v E) int {
//E comparable 限制了传入的元素类型 E 必须是可以通过 == 和 != 运算符进行比较的类型
for i := range s {
if v == s[i] {
return i
}
}
return -1
}
// List List[T any] 是一个泛型链表类型,T 表示链表中节点的元素类型,any 表示可以是任何类型
type List[T any] struct {
head, tail *element[T]
}
// element[T any] 是链表中的节点类型
type element[T any] struct {
next *element[T]
val T
}
//为链表定义了两个方法
//我们可以像在常规类型上一样在泛型类型上定义方法,但必须保留类型参数。类型是List[T],而不是List
func (lst *List[T]) Push(v T) {
if lst.tail == nil {
lst.head = &element[T]{val: v}
lst.tail = lst.head
} else {
lst.tail.next = &element[T]{val: v}
lst.tail = lst.tail.next
}
}
func (lst *List[T]) AllElements() []T {
var elems []T
for e := lst.head; e != nil; e = e.next {
elems = append(elems, e.val)
}
return elems
}
func main() {
var s = []string{"foo", "bar", "zoo"}
fmt.Println("index of zoo:", SlicesIndex(s, "zoo"))
_ = SlicesIndex[[]string, string](s, "zoo")
lst := List[int]{}
lst.Push(10)
lst.Push(13)
lst.Push(23)
fmt.Println("list:", lst.AllElements())
}
|
range迭代器
迭代器函数将另一个函数作为参数,yield
按照惯例调用该函数(但名称可以任意)。它将调用yield
我们想要迭代的每个元素,并注意yield
的返回值以防可能提前终止。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
| package main
import (
"fmt"
"iter"
"slices"
)
type List[T any] struct {
head, tail *element[T]
}
type element[T any] struct {
next *element[T]
val T
}
func (lst *List[T]) Push(v T) {
if lst.tail == nil {
lst.head = &element[T]{val: v}
lst.tail = lst.head
} else {
lst.tail.next = &element[T]{val: v}
lst.tail = lst.tail.next
}
}
func (lst *List[T]) All() iter.Seq[T] {
return func(yield func(T) bool) {
for e := lst.head; e != nil; e = e.next {
if !yield(e.val) {
return
}
}
}
}
//迭代(Iteration) 不一定依赖于特定的数据结构,甚至不需要是有限的(比如无限序列)
func genFib() iter.Seq[int] {
return func(yield func(int) bool) {
a, b := 1, 1
for {
if !yield(a) {
return
}
a, b = b, a+b
}
}
}
func main() {
lst := List[int]{}
lst.Push(10)
lst.Push(13)
lst.Push(23)
//由于List.All返回一个迭代器,我们可以在常规range循环中使用它
for e := range lst.All() {
fmt.Println(e)
}
//像切片这样的包有许多有用的函数可以与迭代器一起使用
all := slices.Collect(lst.All())
fmt.Println("all:", all)
for n := range genFib() {
if n >= 10 {
break
}
fmt.Println(n)
}
}
|
错误