Go的基础部分三(更多的数据类型)

Go的基础部分三(更多的数据类型:struct、array、slice、map、函数作为值、闭包)

一、指针

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() {
	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(结构)

一个struct是一个字段的集合。

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	fmt.Println(Vertex{1, 2})
}

三、struct字段

struct的字段通过点读取。

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X)
}

四、指向Struct

能够通过一个struct的指针读取struct的字段。

当我们有一个struct指针p的时候,为了读取一个struct的字段X,我们能够写(*p).X。可是,这个符号是繁琐的,因此该语言允许我们替换仅仅写p.X,不用带明确的引用。

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 1e9
	fmt.Println(v)
}

五、Struct的字面量

一个struct的字面量表示一个新的已经分配的struct值,通过列出字段的值。

你能仅仅列出一个子字段,通过使用Name:语法。(并且字段名称的顺序是无关紧要的)

特殊的前缀&,返回一个指针指向这个struct的值。

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

var (
	v1 = Vertex{1, 2}  // 有一个类型Vertex
	v2 = Vertex{X: 1}  // Y 是0 是隐含的
	v3 = Vertex{}      // X 是0, Y是0
	p  = &Vertex{1, 2} // 有一个类型 *Vertex
)

func main() {
	fmt.Println(v1, p, v2, v3)
}

六、数组

类型[n]T是一个有n个值的类型为T的数组。

表达式:

var a [10]int

声明了一个变量a,作为一个数组,带有10个整数。

数组的长度是类型的一部分,因此数组不能改变大小。这似乎是限制,但是不用担心。Go提供了一个方便的方法作用与数组。

package main

import "fmt"

func main() {
	var a [2]string
	a[0] = "hello"
	a[1] = "world"
	fmt.Println(a[0], a[1])
	fmt.Println(a)

	primes := [6]int{2, 3, 5, 7, 11, 13}
	fmt.Println(primes)
}

七、Slices(切片)

一个数组有一个固定大小。另一方面,切片是数组元素的动态大小、灵活的视图。实践中,切片比数组更常见。

类型[]T是一个带有T类型元素的切片。

通过指定两个索引形成切片,一个低的和高的边界,通过冒号分割。

a[low:high]

这将选择一个半开范围——包含第一个元素,不包含最后一个元素。

下面的表达式创建一个切片,它包含元素1到3:

a[1:4]
package main

import "fmt"

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

	var s []int = primes[1:4]
	fmt.Println(s)
}

八、切片就像数组的引用

一个切片不能存储任何的数据,它仅仅描述了内部数组的一部分。

改变切片的元素,修改相应内部数组的元素。

其它共享相同底层数组的切片将看到改变。

package main

import "fmt"

func main() {
	names := [4]string{
		"aa",
		"bb",
		"cc",
		"dd",
	}
	fmt.Println(names)

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

	b[1] = "xxx"
	fmt.Println(a, b)
	fmt.Println(names)
}

九、切片的字面量

切片的字面量就像数组的字面量,不带长度。

这是数组的字面量:

[3]bool{true, false, false}

这个创建和上面相同的数组,然后构建一个切片并引用它:

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

import "fmt"

func main() {
	a := [3]int{2, 3, 5}
	q := []int{2, 3, 5, 7, 11, 13}
	fmt.Printf("%T, %v\n", q, q)
	fmt.Printf("%T, %v\n", a, a)

	r := []bool{true, false, true, true, false, false}
	fmt.Println(r)

	s := []struct {
		i int
		b bool
	}{
		{2, true},
		{3, false},
		{5, true},
		{7, true},
		{11, false},
		{13, false},
	}
	fmt.Println(s)
}

十、切片的默认值

当切片的时候,你能忽略高或低边界,去使用默认值代替。

对于低边界默认是0,切片的长度是高的边界。

对于数组:

var a [10]int

下面的切片表达式是等价的:

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

import "fmt"

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

	s = s[1:4]
	fmt.Println(s)

	s = s[:2]
	fmt.Println(s)

	s = s[1:]
	fmt.Println(s)
}

十一、切片的长度和容量

切片既有长度,也有容量。

切片的长度是它包含元素的数量。

切片的容量是内部数组元素的数量,从切片的第一个元素统计。

一个切片的长度和容量能够获取通过使用表达式len(s)cap(s)

你能扩展切片的长度,通过重新进行切片,只要它有充足的容量。

试着去改变下面程序中其中一个切片的操作,去扩展超过它的容量,并看看会发生什么。

package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s)
	// 切片这个切片给一个0长度
	s = s[:0]
	printSlice(s)

	// 扩展它的长度
	s = s[:6]
	printSlice(s)

	// 删除它前两个值
	s = s[2:6]
	printSlice(s)
	// s = s[:6]
	// printSlice(s)

}

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

十二、零切片

切片的零值是nil

一个nil切片有一个长度和容量为0,并且没有底层数组。

package main

import "fmt"

func main() {
	var s []int
	fmt.Println(s, len(s), cap(s))

	if s == nil {
		fmt.Println("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, 5)
	printSlice("a", a)

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

	c := b[0:2]
	printSlice("c", c)

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

}

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

十四、切片的切片

切片能够包含任何类型,包含其他的切片。

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

这个内置的append函数,添加元素到切片的最后。如果它有一个充足的容量,则重新划分以容纳新的元素。如果没有充足的容量,一个新的内部数组将被分配。添加返回更新的切片。因此有必要将 append 的结果存储在保存切片本身的变量中。

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

append的第一个参数是类型为T的切片,剩余的是要添加到这个切片的T类型的值。

append的结果是是一个切片,包含了初始切片的元素和所提供的值的所有的元素。

如果s后面的数组太小,为了存放所有给定的值,一个更大的数组将被分配。返回的切片将指向新分配的数组。

package main

import "fmt"

func main() {
	var s []int
	printSlice(s)

	// append 使用于 nil 切片
	s = append(s, 0)
	printSlice(s)

	// 切片根据需要增长
	s = append(s, 1)
	printSlice(s)

	// 一次能够添加多个元素
	s = append(s, 2, 3, 4, 5)
	printSlice(s)

}

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

更多关于切片,看Go的切片:用法和内部结构

十六、范围Range

range在一个切片或map之上产生一个for循环迭代。

当在一个切片上获取范围的时候,对于每次迭代将返回两个值。第一个值是索引,第二个值是对基于索引元素的拷贝。

package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
	for i, v := range pow {
		fmt.Printf("2**%d ==%d\n", i, v)
	}
}

可以声明_来跳过索引或值:

for i, _ := range pow
for _, v := range pow

如果你仅仅只要索引,你可以忽略第二个值:

for i := range pow
package main

import "fmt"

func main() {
	pow := make([]int, 10)
	for i := range pow {
		pow[i] = 1 << uint(i) // == 2**i
	}
	for _, v := range pow {
		fmt.Printf("%d\n", v)
	}
}

十七、练习:切片

实现一个Pic。它应该返回一个长度为dy的切片,它的每一个元素是一个dx无符号的整数。当你运行程序的时候,它将展示你的图片,将整数解释为灰度值。

图片的选择取决于你,有趣的函数包含(x+y)/2x*y 、和x^y

你需要使用一个循环分配每一个[]uint8[][]uint8中。

使用uint8(intValue)在两种类型进行转化。

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
	res := make([][]uint8, dy)
	for i := range res {
		a := make([]uint8, dx)
		for j := range a {
			a[j] = uint8((i + j) / 2)
		}
		res[i] = a
	}
	return res
}

func main() {
	pic.Show(Pic)
}

运行程序

$ go mod init go-function/examples
$ go mod tidy
$ go run exercise-slices.go 

十八、Maps

一个map映射键到值。

map的零值是nil。一个nil的map没有key,也不能添加key。

make函数返回一个给定类型的map,被初始化并准备使用。

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m map[string]Vertex

func main() {
	if m == nil {
		fmt.Println(m)
		fmt.Println("m is nil")
	}
	m = make(map[string]Vertex)
	m["aa"] = Vertex{23.1, -12.1}
	fmt.Println(m["aa"])
}

十九、Map的字面量

Map的字面量,像结构字面量,但是key是需要的。

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"aa": Vertex{
		12.1, -9.1,
	},
	"bb": Vertex{
		51.1, -20.1,
	},
}

func main() {
	fmt.Println(m)
}

如果顶级类型只是一个类型名称,您可以从字面量的元素中省略它。

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"aa": {51.2, -161.1},
	"bb": {-91.3, 9.91},
}

func main() {
	fmt.Println(m)
}

二十、更改map

插入或更新map中的一个元素:

m[key] = elem

取出一个元素

elem = m[key]

删除一个元素中

delete(m, key)

通过二值分配测试一个键是否存在:

elem, ok = m[key]

如果key在m中,oktrue。如果没有,okfalse

如果key没有在map中,那么elem是零值,具体取决于map元素的类型。

注意,如果elemok还没有声明,你应该使用一个简短的声明格式:

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, "Persent?", ok)
}

二十一、练习:map

实现WorldCount。它应该返回在一个字符串s中每个单词的数量的map。这个wc.Test函数对提供的函数运行一个测试序列,并打印成功或失败。

可以在strings.Fields中获取帮助。

package main

import (
	"strings"

	"golang.org/x/tour/wc"
)

func WorldCount(s string) map[string]int {
	sp := strings.Split(s, " ")
	res := make(map[string]int)
	for _, v := range sp {
		c := res[v]
		c += 1
		res[v] = c
	}
	return res
}

func main() {
	wc.Test(WorldCount)
}

二十二、函数值

函数也是值。它们能被传递就像其它值一样。

函数值能作为函数的参数使用,并返回值。

package main

import (
	"fmt"
	"math"
)

func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 { return math.Sqrt(x*x + y*y) }
	fmt.Println(hypot(5, 12))

	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}

二十三、函数的闭包

Go函数可能是闭包。闭包是一个函数值,引用来它方法体外面的变量。函数能够被读取并分配给应用变量。在这种意义上函数是绑定到变量上的。

例如,adder函数 返回一个闭包。每个闭包,绑定了它自己的sum变量。

package main

import "fmt"

func adder() 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),
		)
	}
}

二十四、斐波那契闭包

让我们使用一些带有函数的函数。

实现一个fibonacci函数,它返回一个函数(一个闭包),它能一次返回斐波那契数值(0, 1, 1, 2, 3, 5, …)

package main

import "fmt"

func fibonacci() func() int {
	first, second := 0, 0
	return func() int {
		if first == 0 {
			first = 1
			second = 1
			return 0
		} else {
			current := first
			firstc := second
			second = first + second
			first = firstc
			return current
		}
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值