- 指针变量
- 结构体
- 数组
- 切片
- make 函数
- range 遍历
- map 映射
- 函数
值类型:改变变量副本值的时候,不会改变变量本身的值(数组、基本数据类型、结构体)
引用类型:改变变量副本值的时候,会改变变量本身的值(切片、map)
指针变量
Go 拥有指针。指针保存了值的内存地址。指针也是一个变量,但它是一种特殊的变量,它存储的数据不是一个普通的值,而是另一个变量的地址。
类型 *T 是指向 T 类型值的指针,其零值为 nil。
& 字符放在变量前面 对变量进行取地址操作。 Go 语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如 *int 、 *float 、*bool、*string。
* 操作符表示指针变量对应的内存地址的值。
示例代码:
package main
import "fmt"
func main() {
i, j := 42, 2701
p := &i //指针p 指向变量 i 的内存地址 0xc000010070
fmt.Println(*p) //通过指针读取 i 的值,为42
fmt.Println(p) //通过指针读取 i 的内存地址 0xc000010070
*p = 21 //通过指针设置 i 的值
fmt.Println(i) //查看 i 的值,发现 i 变为 21了
p = &j // p := &i 的表示声明并初始化变量 p ,p = &j表示对已经声明过的变量p 进行赋值
*p = *p / 37 //通过指针对 j 进行除法运算
fmt.Println(j) //查看 j 的值,发现 j 变为 73了
}
输出结果

结构体
一个结构体(struct)就是一组字段(field)。当我们想表达一个事物的全部或部分属性时,单一的基础数据类型就无法做到了。结构体数据类型作为一种自定义数据类型可以封装多个基本数据类型,类似 Java 语言中的类。
注意:结构体变量名首字母可以大写也可以小写,大写表示该结构体是公有的,在其他包中可以使用。小写表示该结构体是私有的,只有这个包里可以使用。
写法:
type 结构体变量名 struct {
字段变量名 数据结构类型
字段变量名 数据结构类型
}
结构体字段可通过点号 . 来访问
示例代码:
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
//或者使用var来实例化Vertex结构体 var v Vertex
fmt.Println(v.X)
v.X = 4
fmt.Println(v.X)
//打印结构体信息,建议使用 %#v 来打印,可以获取更多结构体的信息
fmt.Printf("值:%#v 类型:%T", v, v)
}
输出结果:

结构体指针:结构体的字段可以通过结构体指针来访问,如果有一个指向结构体的指针 p ,那么可以通过显式解引用 (*p).X 来访问结构体的 X 字段。不过为了简化写法,通常使用隐式解引用 p.X 就可以了。
示例代码:
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{
X:1,
Y:2,
}
p := &v
p.X = 1e9
//写法一:隐式解引用
fmt.Println(p.X)
//写法二:显式解引用
fmt.Println((*p).X)
fmt.Println(v)
}
输出结果:

结构体字面量:
使用 字段名:要赋的值 可以将仅列出的字段进行赋值
特殊的前缀 & 返回一个指向结构体的指针。
示例代码:
package main
import "fmt"
type Vertex struct {
X, 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)
}
输出结果:

结构体的字段类型可以是:基本数据类型、切片、Map 以及 结构体。如果结构体的字段类型是指针、切片、Map,那么在初始化结构体对象时,一定要用make方法来分配空间才能继续使用。
示例代码:
package main
import "fmt"
type Person struct {
Name string
Age int
Sex string
hobby []string
mapInfo map[string]string
}
func main() {
//声明结构体对象
var p Person
p.Name = "梦塔"
p.Age = 100
p.Sex = "男"
p.hobby = make([]string, 4, 6)
p.hobby[0] = "写代码"
p.hobby[1] = "健身"
p.hobby[2] = "打羽毛球"
p.hobby[3] = "看书"
p.mapInfo = make(map[string]string)
p.mapInfo["地址"] = "北京"
p.mapInfo["电话"] = "12345678901"
fmt.Printf("%#v", p)
}
输出结果:

结构体的嵌套
示例代码:
package main
import "fmt"
type User struct {
UserName string
Password string
Address Address
}
type Address struct {
Name string
Phone string
City string
}
func main() {
var user User
user.UserName = "梦塔"
user.Password = "123456"
//对于嵌套结构体的字段也可以直接访问,user.Name user.City
user.Address.Name = "梦塔"
user.Address.Phone = "12345566"
user.Address.City = "北京"
fmt.Printf("%#v", user)
}
输出结果:

在 go 中,结构体的嵌套就是 Java 中的继承
示例代码:
package main
import "fmt"
//父结构体
type Animal struct {
Name string
}
//父结构体的方法
func (a Animal) run() {
fmt.Printf("%v 在打球\n", a.Name)
}
//子结构体 去继承 父结构体
type Dog struct {
Age int
Animal //结构体嵌套即继承
}
//子结构体方法
func (d Dog) wang() {
fmt.Printf("%v 在敲代码\n", d.Name) //子结构体Dog并没有Name字段,去找它的父结构体Animal
}
func main() {
var d = Dog{
Age: 10,
Animal: Animal{
Name: "梦塔",
},
}
fmt.Printf("%#v\n", d)
d.wang()
d.run()
}
输出结果:

结构体对象与 JSON 相互转换,序列化与反序列化
结构体对象转成 JSON,使用 json 包下的 Marshal( ) 方法
var s1 = Student{
ID: 12,
Gender: "男",
Name: "李四"
}
jsonByte, _ := json.MarShal(s1) //返回的 jsonByte 是 byte类型的切片
jsonStr := string(jsonByte) //需要将切片强转成string类型的字符串
此时的 jsonStr 就是JSON格式的字符串。
JSON 转成结构体对象,使用 json 包下的 UnMarshal( ) 方法
package main
import (
"encoding/json"
"fmt"
)
// 结构体
type Student struct {
ID int
Gender string
Name string
}
func main() {
//JSON字符串
var str = `{"ID":12, "Gender":"男", "Name":"梦塔"}`
//声明 结构体对象
var s1 Student
//使用 Unmarshal方法转换
//传入参数需要将 JSON 字符串转成 byte类型的数组
err := json.Unmarshal([]byte(str), &s1)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%#v", s1)
}
结构体标签:结构体对象转成 JSON 字符串的时候可以把结构体的字段转成程序员想要的样式,代码如下:
package main
import (
"encoding/json"
"fmt"
)
// 结构体
type Student struct {
ID int `json:"id"`
Gender string `json:"gender"`
Name string `json:"name"`
}
func main() {
var s1 = Student{
ID: 12,
Gender: "男",
Name: "梦塔",
}
jsonByte, _ := json.Marshal(s1)
jsonStr := string(jsonByte)
fmt.Printf("%v", jsonStr)
}
输出结果:

数组
数组语法如下:
var [n]T
表示创建一个数组,它拥有 n 个类型为 T 的值
表达式举例:
var a [10]int
数组 a 声明拥有 10 个整数的数组。数组的长度是其类型的一部分,也就是说变量 a 的数据类型是 [10]int,因此数组不能改变大小。 这看起来是个限制,不过没关系,Go 拥有更加方便的使用数组的方式。
切片
每个数组的大小都是固定的,切片则为数组元素提供了动态大小。在实践中,切片比数组更常用。切片语法与数组语法很类似,不同的是切片通过两个下标来界定,一个下界一个上界,二者以冒号分隔,语法如下:
a[low : high]
它会选出一个左闭右开的区间元素,举个例子 a[1 : 4] 会创建一个切片,切片 a 的下标索引是 1、 2、 3。
切片就像数组的引用,切片并不存储任何数据,它只是描述了底层数组中的一段。主要特点有以下两个:
- 更改切片的元素会修改其底层数组中对应的元素。
- 和它共享底层数组的切片都会观测到这些修改。
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" //更改切片的元素会修改其底层数据 names 中对应的元素
fmt.Println(a, b) //切片 a 与 切片 b 共享底层数组 names ,因此 a 也可以观测到这些修改
fmt.Println(names)
}
输出结果:

数组在声明的时候必须指定数组的大小 n ,而切片字面量是指采用同样方式创建一个数组,但是不用指定数组大小。
示例代码:
package main
import "fmt"
func main() {
q := []int{2, 3, 5, 7, 11, 13}
fmt.Println(q)
r := []bool{true, false, false, true}
fmt.Println(r)
s := []struct{
i int
b bool
}{
{2, true}
{3, false}
{5, true}
}
fmt.Println(s)
}
输出结果:

在进行切片时,利用切片的默认行为来忽略上下界,切片的下界为默认值 0 ,上界为该切片的长度。
示例代码:
package main
import "fmt"
func main(){
s := []int{2, 3, 5, 7, 11, 13}
s = s[1:4] //切片 s 变为 {3, 5, 7}
fmt.Println(s)
s = s[:2] //切片 s 变为 {3, 5}
fmt.Println(s)
s = s[1:] //切片 s 变为 {5}
fmt.Println(s)
}
输出结果:

切片拥有长度和容量这两个概念
切片的长度就是它所包含的元素个数
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片 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[:4]
printSlice(s)
// 舍弃前两个值
s = s[2:]
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([]int,i,j)
或者
make([]int,i)
make 方法传入三个参数时,第一个参数代表数组,第二个参数代表切片的长度,第三个参数代表切片的容量。
append 方法可以向切片追加新的元素,语法如下:
func append(s []T, vs ...T) []T
append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值 vs 都会追加到该切片的末尾,然后结果返回一个元素类型为 T 的新的切片。当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
示例代码:
package main
import "fmt"
func main() {
var s []int
printSlice(s)
s = append(s, 0)
printSlice(s)
s = appand(s, 1)
printSlice(s)
s = appand(s, 2, 3, 4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
输出结果:

range 遍历
for 循环 与 range 相配合可以遍历切片或映射,当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值是当前元素的下标,第二个值是该下标对应元素值的副本。
示例代码:
package main
import "fmt"
func main() {
for i, v := range pow {
fmt.Printf("下标%d = %d\n", i, v)
}
}
可以将下标或值赋予下划线 _ 来忽略它
示例代码:
for i, _ := range pow
for _, value := range pow
或者只需要索引,忽略第二个变量即可
for i := range pow
切片练习题:
package main
import "golang.org/x/tour/pic"
func Pic(dx dy int) [][]uint8 {
image := make([][]uint8, dy)
for y := range image {
image[y] = make([]uint8, dx)
for x := range image[y] {
value := uint(x * y)
image[x][y] = value
}
}
}
func main() {
pic.Show(Pic)
}
输出结果:

map 映射
map 与 Java 一样是键值对的形式,键类型和值类型可以随意指定。声明 map 数据结构后,可以通过 make 函数返回给定类型的映射,并将其初始化备用。
package main
import "fmt"
type TestStruct struct {
Lat, Long float64
}
var m map[string]TestStruct
func main() {
m = make(map[string]TestStruct)
m["test"] = TestStruct{
40.11231, 50.235425,
}
fmt.Println(m["test"])
}
输出结果:

不使用 make ,直接创建一个 map 数据结构
package main
import "fmt"
func main() {
var m = map[string]string{
"AI":"你是谁?",
"用户":"我是学Go的开发者",
}
fmt.Println(m)
}
对 map 类型数据的修改操作
添加 m[key] = value
获取 value = m[key]
删除 delete(m, key)
检测某个键是否存在 value, ok := m[key],如果存在: value 会返回该键对应的值,ok会为true;如果不存在:value 会返回 value 定义的元素类型的零值,ok会为false。
练习:
实现 WordCount。它应当返回一个映射,其中包含字符串 s 中每个“单词”的个数。 函数 wc.Test 会为此函数执行一系列测试用例,并输出成功还是失败。
package main
import (
"golang.org/x/tour/wc"
"strings"
)
func WordCount(s string) map[String]int {
//初始化map
m := make(map[string]int)
//字符串转成切片slice
strSlice = strings.Fields(s)
//遍历字符切片
for _, word := range strSlice {
m[word]++
}
}
func main() {
wc.Test(WordCount)
}
函数值
Go 中也可以将函数值用作函数的参数或返回值。
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))
}
解析:
compute 函数的参数 fn 是一个函数类型,接受两个 float64 参数并返回一个 float64。
功能是调用传入的函数 fn ,将 3 和 4 作为参数传递给它。
函数闭包
闭包可以让一个变量常驻内存的同时还可以不污染全局,结合了全局变量和局部变量的优点。
闭包的写法:函数里面嵌套一个函数,最后返回里面的函数
package main
import "fmt"
//闭包的第一种写法
func adder1() func() int {
var i = 10
return func() int {
return i + 1
}
}
//闭包的第二种写法
func adder2() func(int) int {
var i = 10 //变量常驻内存,不污染全局
return func(y int) int {
i += y
return i
}
}
func main() {
var fn1 = adder1() //返回闭包函数
fmt.Println(fn1()) // 11
fmt.Println(fn1()) // 11
fmt.Println(fn1()) // 11
var fn2 = adder2() //返回闭包函数
fmt.Println(fn2(10)) //20
fmt.Println(fn2(10)) //30
fmt.Println(fn2(10)) //40
}
输出结果:

通过函数闭包实现斐波那契数列
package main
import "fmt"
// fibonacci 是返回一个「返回一个 int 的函数」的函数
func fibonacci() func() int {
a, b := 0, 1
return func() int {
result := a
a, b = b, a+b
return result
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
输出结果:

下载:
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/
go install golang.org/x/tools/cmd/goimports@latest

No responses yet