Golang 通关初级(2)

前端之家收集整理的这篇文章主要介绍了Golang 通关初级(2)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

原文

https://tour.golang.org/welcome/1

指针

Go 具有指针。 指针保存了变量的内存地址。

类型 *T 是指向 T 类型值的指针。其零值为 nil 。

var p *int

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

i := 42
p = &i
  • 操作符表示指针指向的底层值。
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21         // 通过指针 p 设置 i

这也就是通常所说的“间接引用”或“重定向”。

与 C 不同,Go 没有指针运算。

package main

import (
    "fmt"
)

func main() {
    a,b := 12,33

    p := &a
    fmt.Printf("p = %v,*p = %v\n",p,*p)

    *p = *p * 2
    fmt.Printf("p = %v,*p)

    p = &b
    fmt.Printf("p = %v,*p)

}

输出

p = 0xc082004258,*p = 12
p = 0xc082004258,*p = 24
p = 0xc082004280,*p = 33

结构体

一个结构体( struct )就是一个字段的集合。

(而 type 声明就是定义类型的。)

package main

import (
    "fmt"
)

type People struct {
    name string
    age  int
}

func main() {
    fmt.Println(People{"Afra", 22})
}

输出:

{Afra 22}

结构体字段
结构体字段使用点号来访问。

package main

import (
    "fmt"
)

type People struct {
    name string
    age  int
}

func main() {
    me := People{"Afra", 22}
    fmt.Printf("name = %v,age = %v\n",me.name,me.age)
}

输出

name = Afra,age = 22

结构体指针
结构体字段可以通过结构体指针来访问。

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

package main

import (
    "fmt"
)

type People struct {
    name string
    age  int
}

func main() {
    me := People{"Afra", 22}
    p := &me
    fmt.Printf("name = %v,p.name,p.age)
}

输出

name = Afra,age = 22

结构体文法
结构体文法通过直接列出字段的值来新分配一个结构体。

使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)

特殊的前缀 & 返回一个指向结构体的指针。

package main

import (
    "fmt"
)

type People struct {
    name string
    age  int
}

var (
    a = People{"Afra", 22}
    b = People{age: 22}
    c = People{name: "Victor"}
    d = People{}
    e = &People{"Victor", 87}
)

func main() {
    fmt.Println(a,b,c,d,e)
}

输出

{Afra 22} { 22} {Victor 0} { 0} &{Victor 87}

数组

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

表达式

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

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

package main

import (
    "fmt"
)

func main() {
    var a [2]string
    a[0] = "Afra55"
    a[1] = "Victor"

    fmt.Println(a[0],a[1])
    fmt.Println(a)

    b := [6]int{1,2,4,5,6,6}
    fmt.Println(b)
}

输出

Afra55 Victor
[Afra55 Victor]
[1 2 4 5 6 6]

切片

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

类型 []T 表示一个元素类型为 T 的切片。

以下表达式为数组 a 的前五个元素创建了一个切片。

a[0:5]
package main

import (
    "fmt"
)

func main() {
    source := [6]int{1, 2, 3, 5, 4}

    var s []int = source[2:6]
    fmt.Println(s)
}

输出

[3 5 4 0]

切片就像数组的引用

切片并不存储任何数据, 它只是描述了底层数组中的一段。

更改切片的元素会修改其底层数组中对应的元素。

与它共享底层数组的切片都会观测到这些修改

package main

import (
    "fmt"
)

func main() {
    source := [6]int{1,3,4}

    var s []int = source[2:6]
    fmt.Println(s)
    source[5] = 7
    fmt.Println(s)
    s[0] = 88
    fmt.Println(source)
}

输出

[3 5 4 0]
[3 5 4 7]
[1 2 88 5 4 7]

切片文法
切片文法类似于没有长度的数组文法。

这是一个数组文法:

[3]bool{true,true,false}

下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片:

[]bool{true,false}
package main

import (
    "fmt"
)

func main() {
    // a 是切片
    a := []int{12, 6, 8, 6}

    fmt.Println(a)

    // b 是个切片
    b := []struct {
        age  int
        name string
    }{
        {1,"xx"},{2,"xxx"},{3,"xxxx"},}

    fmt.Println(b)
}

输出

[12 5 3 6 8 6]
[{1 xx} {2 xxx} {3 xxxx}]

切片的默认行为
在进行切片时,你可以利用它的默认行为来忽略上下界。

切片下界的默认值为 0 ,上界则是该切片的长度。

对于数组:

var a [10]int

来说,以下切片是等价的:

a[0:10]
a[:10]
a[0:]
a[:]
package main

import (
    "fmt"
)

func main() {
    // a 是切片
    a := []int{12,8,6}

    a = a[1:6]
    fmt.Println(a)

    a = a[:3]
    fmt.Println(a)

    a = a[2:]
    fmt.Println(a)
}

输出

[5 3 6 8 6]
[5 3 6]
[6]

切片的长度与容量
切片拥有 长度 和 容量 。

切片的长度就是它所包含的元素个数。

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

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

package main

import (
    "fmt"
)

func main() {
    // a 是切片
    a := []int{12,6}

    // 让切片的长度为 0
    a = a[:0]
    printSlice(a)

    // 扩充切片的长度
    a = a[:3]
    printSlice(a)

    // 丢掉开始的两个元素
    a = a[2:]
    printSlice(a)
}

func printSlice(s []int) {
    fmt.Printf("len = %d,cap = %d,value = %v\n",len(s),cap(s),s)
}

输出

len = 0,cap = 6,value = []
len = 3,value = [12 5 3]
len = 1,cap = 4,value = [3]

由上面可见,不管切片怎么变化,他的长度和容量都和他存储的 第一个元素 有关。

nil 切片
切片的零值是 nil 。

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

package main

import "fmt"

func main() {
    var s []int
    fmt.Println(s,cap(s))
    if s == nil {
        fmt.Println("nil!")
    }
}

输出

[] 0 0
nil!

用 make 创建切片

切片可以用内建函数 make 来创建,这也是你创建动态数组的方式。

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

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

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

b := make([]int, 0, 5) // len(b)=0,cap(b)=5

b = b[:cap(b)] // len(b)=5,cap(b)=5
b = b[1:]      // len(b)=4,cap(b)=4
package main

import (
    "fmt"
)

func main() {
    a := make([]int, 6)
    printSlice("a",a)

    b := make([]int, 5)
    printSlice("b",b)

    c := make([]int, 5)
    printSlice("c",c)

    d := b[:2]
    printSlice("d",d)

    e := d[2:5]
    printSlice("e",e)
}

func printSlice(flag string,s []int) {
    fmt.Printf("%s,len = %d,flag,cap(s),s)
}

输出

a,len = 6,value = [0 0 0 0 0 0]
b,len = 0,cap = 5,value = []
c,len = 3,value = [0 0 0]
d,len = 2,value = [0 0]
e,cap = 3,value = [0 0 0]

切片的切片
切片可包含任何类型,甚至包括其它的切片。

package main

import (
    "fmt"
    "strings"
)

func main() {

    // a是个切片
    a := [][]string{
        []string{"_","_","_"}, // 这是个切片
        []string{"_", // 这是个切片
    }

    printSlice("s",a)

    a[0][0] = "x"
    a[2][2] = "x"
    a[1][2] = "x"
    a[0][2] = "x"

    printSlice("s",a)

    for i := 0; i < len(a); i++ {
        fmt.Println(strings.Join(a[i]," "))
    }
}

func printSlice(flag string,s [][]string) {
    fmt.Printf("%s,s)
}

输出

s,len = 3,value = [[_ _ _] [_ _ _] [_ _ _]]
s,value = [[x _ x] [_ _ x] [_ _ x]]
x _ x
_ _ x
_ _ x

向切片追加元素
为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数。 内建函数文档对此函数有详细的介绍。

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

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

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

(要了解关于切片的更多内容,请阅读文章Go 切片:用法和本质。)

package main

import (
    "fmt"
)

func main() {
    a := []int{1, 4, 5}
    printSlice(a)

    a = append(a, 8)
    printSlice(a)

    a = append(a, 9, 10, 11, 12)
    printSlice(a)

    var b []int
    printSlice(b)

    b = append(b, 0)
    printSlice(b)

    b = append(b, 1)
    printSlice(b)

    b = append(b, 2)
    printSlice(b)

    b = append(b, 5)
    printSlice(b)
}

func printSlice(s []int) {
    fmt.Printf("len = %d,s)
}

输出

len = 5,value = [1 2 3 4 5]
len = 6,cap = 10,value = [1 2 3 4 5 8]
len = 11,cap = 20,value = [1 2 3 4 5 8 8 9 10 11 12]
len = 0,cap = 0,value = []
len = 1,cap = 1,value = [0]
len = 2,cap = 2,value = [0 1]
len = 3,value = [0 1 2]
len = 6,cap = 8,value = [0 1 2 3 4 5]

由上面的输出结果可看,向切片追加元素,新切片的容量以原切片的容量为基数倍增,直到空间能够容乃所有元素。

Range

for 循环的 range 形式可遍历切片或映射。

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

package main

import (
    "fmt"
)

var result = []int{1, 16, 32, 64, 128}

func main() {
    printSlice(result)

    for i,v := range result {
        fmt.Printf("i = %d,value = %d\n",i,v)
    }
}

func printSlice(s []int) {
    fmt.Printf("len = %d,s)
}

输出

len = 8,value = [1 2 4 8 16 32 64 128]
i = 0,value = 1
i = 1,value = 2
i = 2,value = 4
i = 3,value = 8
i = 4,value = 16
i = 5,value = 32
i = 6,value = 64
i = 7,value = 128

可以将下标或值赋予 _ 来忽略它。

若你只需要索引,去掉,value 的部分即可。

package main

import (
    "fmt"
)

func main() {
    result := make([]int, 10)
    printSlice(result)

    fmt.Println("\n只获取索引:")
    for i := range result {
        result[i] = 1 << uint(i) // == 2**i
        fmt.Printf("i = %d,result[i])
    }

    fmt.Println("\n只获取 value:")
    for _,v := range result {
        fmt.Printf("value = %d\n",s)
}

输出

len = 10,value = [0 0 0 0 0 0 0 0 0 0]

只获取索引:
i = 0,value = 128
i = 8,value = 256
i = 9,value = 512获取 value:
value = 1
value = 2
value = 4
value = 8
value = 16
value = 32
value = 64
value = 128
value = 256
value = 512

映射

映射将键映射到值。

映射的零值为 nil 。nil 映射既没有键,也不能添加键。

make 函数会返回给定类型的映射,并将其初始化备用。

package main

import (
    "fmt"
)

type People struct {
    age  int
    name string
}

var m map[string]People

func main() {
    m = make(map[string]People)
    fmt.Println(m)

    m["Afra55"] = People{
        22,"Victor",}
    fmt.Println(m)

    fmt.Println(m["Afra55"])
}

输出

map[]
map[Afra55:{22 Victor}]
{22 Victor}

映射的文法

映射的文法与结构体相似,不过必须有键名。

package main

import (
    "fmt"
)

type People struct {
    age  int
    name string
}

var m = map[string]People{
    "Victor": People{12,"LaBabaLa"},"Afra55": People{221,"FFFFFFF"},}

func main() {
    fmt.Println(m)

    m["Afra55"] = People{
        22,"XXXXXX",}
    fmt.Println(m)

    fmt.Println(m["Afra55"])
}

输出

map[Victor:{12 LaBabaLa} Afra55:{221 FFFFFFF}]
map[Afra55:{22 XXXXXX} Victor:{12 LaBabaLa}]
{22 XXXXXX}

可以在文法的元素中省略顶级类型名。

var m = map[string]People{
    "Victor": {12,"Afra55": {221,}

修改映射
在映射 m 中插入或修改元素:

m[key] = elem

获取元素:

elem = m[key]

删除元素:

delete(m,key)

通过双赋值检测某个键是否存在:

elem,ok = m[key]

若 key 在 m 中, ok 为 true ;否则, ok 为 false 。

若 key 不在映射中,那么 elem 是该映射元素类型的零值。

同样的,当从 映射 中读取某个不存在的键时,结果是 映射 的元素类型的零值。

注 :若 elem 或 ok 还未声明,你可以使用短变量声明:

elem,ok := m[key]
package main

import (
    "fmt"
)

type People struct {
    age  int
    name string
}

var m = map[string]People{
    "Victor": {12,}

func main() {

    // 插入元素
    m["Lazyer"] = People{
        32,"Old man",}
    fmt.Println(m)

    // 修改元素
    m["Afra55"] = People{
        22,}
    fmt.Println(m)

    // 检测元素是否存在
    checkExist("Afra55")

    // 删除元素
    delete(m,"Afra55")

    // 检测元素是否存在
    checkExist("Afra55")
}

func checkExist(key string) {
    elem,ok := m[key]

    if ok {
        fmt.Printf("%s is %d\n",key,elem)
    } else {
        fmt.Printf("%s not exist,the value is %v\n",elem)
    }
}

输出

map[Lazyer:{32 Old man} Victor:{12 LaBabaLa} Afra55:{221 FFFFFFF}]
map[Victor:{12 LaBabaLa} Afra55:{22 XXXXXX} Lazyer:{32 Old man}]
Afra55 is {22 %!d(string=XXXXXX)}
Afra55 not exist,the value is {0 }

函数

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

函数值可以用作函数的参数或返回值。

package main

import (
    "fmt"
    "math"
)

func main() {
    toSqrt := func(x,y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }

    fmt.Println(toSqrt(12, 5))

    fmt.Println(autoSqrt(toSqrt))
    fmt.Println(autoSqrt(math.Pow))

}

func autoSqrt(fn func(x,y float64) float64) float64 {

    return fn(4, 3)
}

输出

13
5
64

函数的闭包

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

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

package main

import (
    "fmt"
)

func main() {
    a := adder()
    fmt.Println(a)

    for i := 0; i < 10; i++ {

        fmt.Println(a(i))
    }

    b := adder()
    fmt.Println(b)

    for i := 0; i < 10; i++ {

        fmt.Println(b(i * -1))
    }
}

func adder() func(int) int {
    sum := 0
    fmt.Println("sum is init: ",sum)
    return func(x int) int { // 这就是闭包
        sum += x
        return sum
    }
}

输出

sum is init:  0
0x401660
0
1
3
6
10
15
21
28
36
45
sum is init:  0
0x401660
0
-1 -3 -6 -10 -15 -21 -28 -36 -45

练习:斐波纳契闭包

让我们用函数做些好玩的事情。

实现一个 fibonacci 函数,它返回一个函数(闭包), 该闭包返回一个斐波纳契数列 (0,1,2,3,5,...)

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
    a,b:=0, 1
    return func() int{
        b,a = b+a,b
        return a
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

输出

1
1
2
3
5
8
13
21
34
55

猜你在找的Go相关文章