GO千练——指针、struct、数组、slice 和映射

指针

什么是指针?

Go 拥有指针,一个指针变量指向了一个值的内存地址。

比如:类型 *T 是指向 T 类型值的指针。其零值为 nil。指针声明格式如下:

var var_name *var-type

var p *int

& 操作符

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

var i int = 10
p = &i

* 操作符

  • 操作符表示指针指向的底层值,作用是可以操作变量具体的值,也就是通常所说的“间接引用”或“重定向”。
// 通过指针 p 读取 i
fmt.Println(*p) 

// 通过指针 p 设置 i
*p = 20         

实例

package main

import "fmt"

func main() {
	var i int = 10

	// 指针
	var p *int = &i
	fmt.Println("指针p指向的值:", *p)

	// 指针重新设值
	*p = 20
	fmt.Println("指针p重新指向的值:", *p)

	// panic: runtime error: invalid memory address or nil pointer dereference
	var pointer *string = nil
	fmt.Println("指针p指向的值为nil:", *pointer)
}

结构体

什么是结构体?

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

简单来说,一个结构体(struct)就是一组字段(field)。

比如保存图书馆的书籍记录,每本书有以下属性:
Title :标题
Author : 作者
Subject:学科

定义结构体的语法:

type struct_variable_type struct {
    member definition
    member definition
    ...
    member definition
}

简单实例

package main

import "fmt"

type Book struct {
	Title   string
	Author  string
	Subject string
}

func main() {
	// 初始化
	var b Book = Book{"Go程序设计语言", "Kernighan", "计算机"}
	fmt.Println(b)
	
	// 使用点号来访问,
	fmt.Println("书的名称:", b.Title)
}

结构体指针

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

package main

import "fmt"

type Book struct {
	Title   string
	Author  string
	Subject string
}

func main() {
	// 初始化
	var b Book = Book{"Go程序设计语言", "Kernighan", "计算机"}

	// 结构体指针
	var p *Book = &b

	// 通过 (*p).X 来访问其字段 X
	fmt.Println((*p).Title)

	// 更方便的隐式间接引用
	fmt.Println((*p).Title)
}

结构体文法

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

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

package main

import "fmt"

type Book struct {
	Title   string
	Author  string
	Subject string
}

var (
	b1 = Book{"Go程序设计语言", "Kernighan", "计算机"}
	b2 = Book{Author: "Kernighan", Title: "Go程序设计语言"}
	p  = &Book{Title: "Go程序设计语言"}
)

func main() {
	fmt.Println(b1)
	fmt.Println(b2)
	fmt.Println(p)
}

数组

Go 语言提供了数组类型的数据结构。
数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。

数组表达式:类型 [n]T 表示拥有 n 个 T 类型的值的数组。语法格式:

var variable_name [SIZE] variable_type


var a [10]int
var s [2]string
package main

import "fmt"

func main() {
	var s [2]string
	s[0] = "hello"
	s[1] = "go!"
	
	fmt.Println(s)
}

切片

为什么使用切片?

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用。

Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

所以在实践中,切片比数组更常用。

切片定义

类型 []T 表示一个元素类型为 T 的切片。
第一:可以声明一个未指定大小的数组来定义切片:

var identifier []type

如果是用数组的片段来创建切片,则切片通过两个下标来界定,即一个上界和一个下界(左闭右开),二者以冒号分隔:

a[low : high]
package main

import "fmt"

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

	fmt.Println(slice)
	// 打印输出:[2 3]
}

第二:使用 make() 函数来创建切片,这是创建动态数组的方式。

var slice1 []type = make([]type, len)

// 也可以简写为
slice1 := make([]type, len)

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

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

//如果要指定它的容量,需向 make 传入第三个参数
// len(ss)=0, cap(ss)=5
ss := make([]int, 0, 5)
package main

import "fmt"

func main() {
	s := make([]string, 5)
	fmt.Printf("%s 长度=%d 容量=%d %v\n",
		s, len(s), cap(s), s)

	ss := make([]string, 5, 10)
	fmt.Printf("%s 长度=%d 容量=%d %v\n",
		ss, len(ss), cap(ss), ss)
}

// 打印
[    ] 长度=5 容量=5 [    ]
[    ] 长度=5 容量=10 [    ]

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

package main

import (
	"fmt"
	"strings"
)

func main() {
	// 创建一个井字板(经典游戏)
	b := [][]string{
		[]string{"_", "_"},
		[]string{"_", "_"},
	}

    // 设置
	b[0][0] = "X"
	b[1][1] = "O"

    // 遍历输出
	for i := 0; i < len(b); i++ {
		fmt.Printf("%s\n", strings.Join(b[i], " "))
	}
}

向切片追加元素

为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数。

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

package main

import "fmt"

func main() {
	s := []string{}
	s = append(s, "张三")

	fmt.Printf("长度=%d 容量=%d %v\n", len(s), cap(s), s)
}

切片原理

切片和数组的引用一样,切片并不存储任何数据,它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素。与它共享底层数组的切片都会观测到这些修改。

package main

import "fmt"

func main() {
	persons := [3]string{"张三", "李四", "王五"}

	fmt.Println(persons)
	fmt.Println("----------")

	// : 代表首尾全部元素
	s1 := persons[:]
	s2 := persons[1:2]

	fmt.Println(s1)
	fmt.Println(s2)
	fmt.Println("----------")

	// 改名验证
	s1[1] = "改名"
	fmt.Println(s1)
	fmt.Println(s2)
	fmt.Println(persons)
}


//打印结果:
[张三 李四 王五]
----------
[张三 李四 王五]
[李四]
----------
[张三 改名 王五]
[改名]
[张三 改名 王五]

切片遍历

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

package main

import "fmt"

func main() {
	persons := [3]string{"张三", "李四", "王五"}

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

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

package main

import "fmt"

func main() {
	persons := [3]string{"张三", "李四", "王五"}

	for _, v := range persons {
		fmt.Printf("%v\n", v)
	}
}

切片默认行为

切片下界的默认值为 0,上界则是该切片的长度。所以在进行切片时,你可以利用它的默认行为来忽略上下界。
以下切片是等价的:

var a [10]int
// 等价
slice1 = a[0:10]
slice2 = a[:10]
slice3 = a[0:]
slice4 = a[:]

长度与容量

切片拥有 长度容量

对于切片 s 来说:
切片的长度就是它目前所包含的元素个数,可以使用 len(s) 来获取。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。可以使用 cap(s) 来获取。

package main

import "fmt"

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

	slice1 := s[:]
	fmt.Printf("长度=%d 容量=%d %v\n", len(slice1), cap(slice1), slice1)

	slice2 := s[2:]
	fmt.Printf("长度=%d 容量=%d %v\n", len(slice2), cap(slice2), slice2)
}

nil 切片

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

package main

import "fmt"

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

映射

Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

定义 Map

可以使用内建函数 make 也可以使用 map 关键字来定义 Map:

/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	persons := make(map[string]Person)
	persons["man"] = Person{"张三", 30}
	persons["woman"] = Person{"小花", 30}

	fmt.Println(persons)
}

映射的文法

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	// 结构体定义
	persons := map[string]Person{
		"man":   Person{"张三", 30},
		"woman": Person{"小花", 30},
	}
	fmt.Println(persons)
}

增删改查映射

插入或修改元素(在映射 persons 中):

persons[key] = elem

// 比如
persons["man"] = Person{"李四", 22}

获取元素:

elem = m[key]

// 比如
man = persons["man"]

还可以通过双赋值检测某个键是否存在。
如果 key 在 persons 中存在,ok 为 true ;否则 ok 为 false。
如果 key 不在映射中,那么 elem 是该映射元素类型的零值,ok 为 false。

elem, ok = m[key]

// 比如
man, ok = persons["man"]
package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	// 结构体定义
	persons := map[string]Person{
		"man":   Person{"张三", 30},
		"woman": Person{"小花", 30},
	}
	fmt.Println(persons)

	// 修改元素
	persons["woman"] = Person{"小红", 18}
	fmt.Println(persons)

	// 获取元素
	man, ok := persons["man"]
	if ok {
		fmt.Println("man 存在:", man)
	}

	// 删除元素
	delete(persons, "man")
	// 再次获取元素
	m, isExist := persons["man"]
	if isExist {
		fmt.Println("man 元素存在:", m)
	} else {
		fmt.Println("man 元素不存在!")
	}
	fmt.Println(persons)
}



// 打印结果
map[man:{张三 30} woman:{小花 30}]
map[man:{张三 30} woman:{小红 18}]
man 存在: {张三 30}
man 元素不存在!
map[woman:{小红 18}]

函数值

在 GO 中函数也是值,它们可以像其它值一样传递。函数值可以用作函数的参数或返回值。

package main

import "fmt"

// f 作为函数值传递
func computer(f func(int, int) int, a int, b int) int {
	return f(a, b)
}

func Add(x int, y int) int {
	return x + y
}

func Sub(x int, y int) int {
	return x - y
}

func main() {
	fmt.Println("传递Add函数:", computer(Add, 10, 20))
	fmt.Println("传递Sub函数:", computer(Add, 10, 20))

}

函数的闭包

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() {
	// 调用adder获得一个闭包函数
	pos := adder()
	for i := 0; i < 10; i++ {
		// 调用闭包
		fmt.Println(pos(i))
	}
}

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

package main

import "fmt"

// 返回一个“返回int的函数”
func fibonacci() func() int {
	// 预设初始值,相当于绑定闭包
	back1, back2 := 0, 1
	return func() int {
		// 关键代码
		temp := back1
        // 核心:交换两值
		back1, back2 = back2, (back1 + back2)
		return temp
	}
}

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

请关注公众号【Java千练】,更多干货文章等你来看!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值