从零开始的go世界(3)

287 阅读5分钟

指针

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

i := 42
p = &i

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

var p *int

* 操作符表示指针指向的底层值。

fmt.Println(*p) // 通过指针 p 读取 i
*p = 21         // 通过指针 p 设置 i
package main
import "fmt"

func main(){
    i, j := 42, 2701
    p := &i         // 指向 i
    fmt.Println(*p) // 通过指针读取 i 的值
    *p = 21         // 通过指针设置 i 的值
    fmt.Println(i)  // 查看 i 的值

    p = &j         // 指向 j
    *p = *p / 37   // 通过指针对 j 进行除法运算
    fmt.Println(j) // 查看 j 的值
}

结构体

一个结构体(struct)就是一组字段(field)

package main
import "fmt"
type Vex struct{
    x int 
    y int 
}

func main(){
    fmt.Println(Vex{1,2})
}
  • 结构体字段

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

package main
import "fmt"
type Vex struct{
    x int 
    y int 
}

func main(){
    vex := Vex
    p := vex.x
    fmt.Println(Vex{1,2})
}
  • 结构体指针

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

语言也允许我们使用隐式间接引用,直接写 p.X 就可以。

import "fmt"
type Vex struct{
    x int 
    y int 
}

func main(){
    vex := Vex
    p := vex.x
    a := &vex.y
    a.x = 1
    fmt.Println(Vex{1,2})
}
  • 结构体文法

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

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

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

package main
import "fmt"

type Vex struct {
	X, Y int
}

func main(){
    v1 = Vex{1,2}
    v2 = Vex{X:1} //Y被隐式赋值为0
    v3 = Vex{} // X,Y为0
    p = &Vex{}
}

数组

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

表达式

var a [10]int
package main
import "fmt"

func main(){
    var b [10]int
    b[0] = 1
    b[1] = 2 
    primes := [6]int{2, 3, 5, 7, 11, 13}
}

切片

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

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

一个半开区间,包括第一个元素,但排除最后一个元素。

a[low : high]
package main

import "fmt"

func main() {
	primes := [6]int{2, 3, 5, 7, 11, 13}

	var s []int = primes[1:4] // 范围1-3
	fmt.Println(s)
}
  • 切片就像数值的引用

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

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

package main

import "fmt"

func main() {
	names := [4]string{
		"John",
		"Paul",
		"George",
		"Ringo",
	}
	fmt.Println(names)

	a := names[0:2]
	b := names[1:3]
	fmt.Println(a, b)

	b[0] = "XXX"
	fmt.Println(a, b)
	fmt.Println(names)
}
  • 切片文法

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

[]bool{true, true, false}
package main

import "fmt"

func main() {
    q := []int{2, 3, 5, 7, 11, 13}
    fmt.Println(q)
}
  • 切片的默认行为

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

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

a[0:10]
a[:10]
a[0:]
a[:]
package main
import "fmt"

func main(){
    s := []int{2,3,4}
    s = s[:2]
    s = s[:]
}
  • nil切片

切片的零值是 nil

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

package main

import "fmt"

func main() {
    var s []int
    if s == nil {
       fmt.Println("nil!")
    }
}
  • 用 make 创建切片

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

golang 分配内存主要有内置函数new和make,make只能为slice, map, channel分配内存,并返回一个初始化的值。

make(map[string]string)
make([]int, 2)
make([]int, 2, 4)
  • 切片的切片

package main

import (
	"fmt"
	"strings"
)

func main() {
	board := [][]string{
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
	}

	board[0][0] = "X"
	board[2][2] = "O"
	board[1][2] = "X"
	board[1][0] = "O"
	board[0][2] = "X"

	for i := 0; i < len(board); i++ {
		fmt.Printf("%s\n", strings.Join(board[i], " "))
	}
}
  • 向切片追加元素

Go 提供了内建的 append 函数。

func append(s []T, vs ...T) []T

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

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

package main
import "fmt"

func main(){
    var s []int
    s = append(s,0)
    s = append(s , 1)
    s = append(s,2,3,4)
}

Range

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

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

package main
import "fmt"
var pow : = {1,2,3,4,5}
func main(){
    for i,v := range pow{
       fmt.Printf("2**%d = %d\n", i, v)
    }
}

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

for i, _ := range pow
for _, value := range pow
// 忽略第二个变量
for i := range pow

映射 (map)

映射将键映射到值。

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

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

package main
import "fmt"
type Vex struct{
    x,y float64
}
var m map[string]Vex
func main(){
    m = make(map[string]Vex)
    m["Bell Labs"] = Vex{
	40.68433, -74.39967,
    }
    fmt.Println(m["Bell Labs"])
}
  • 映射的文法

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

package main
import "fmt"

type Vex struct{
    a, b float64
}
var m = map[string]Vex{
    "Hi":{
        43.1, 23.1
    }
}

func main(){
    
}
  • 修改映射

通过双赋值检测某个键是否存在: 若 key 在 m 中,ok 为 true ;否则,ok 为 false

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

elem, ok = m[key]
package main

import "fmt"

func main() {
	m := make(map[string]int)

	m["Answer"] = 42
	fmt.Println("The value:", m["Answer"])

	m["Answer"] = 48
	fmt.Println("The value:", m["Answer"])

	delete(m, "Answer")
	fmt.Println("The value:", m["Answer"])

	v, ok := m["Answer"]
	fmt.Println("The value:", v, "Present?", ok)
}

函数值

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

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

package main
import "fmt"

func comp(fn func(int,int) int) int{
    return fn(3,4)
}

func main(){
    hx := func(x ,y int) int{
        return x+y
    }
    
    fmt.Println(hx(3,4))
    fmt.Println(comp((hx))
}

函数的闭包

package main
import "fmt"

func add () func(int) int {
    sum := 0 
    return func(x int) int{
        sum += x
        return sum 
    }
}

func main(){
    pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
	}
}