第4章：复杂数据类型

复杂数据类型 / 派生数据类型【指针、数组、结构体、管道、函数、切片、接口、map】

指针

什么是指针

Copy
package main

import "fmt"

func main() {
var age int = 18
//*int表示ptr对应的指针类型
//&age表示age变量在内存中的地址
var ptr *int = &age
fmt.Println(ptr)
//&ptr表示ptr指针变量所在内存空间的地址
fmt.Println(&ptr)
//*ptr表示ptr指针指向的数据
fmt.Println(*ptr)
}

总结：

&符号表示取内存地址

* 符号表示根据地址取值

指针的 4 个细节

1、可以通过指针改变指向值

2、指针变量接收的一定是地址值

3、指针变量的地址不可以不匹配

4、基本数据类型都有对应的指针类型

函数

函数的引入

函数的定义：为完成某一功能的程序指令的合集，称为函数。

作用：提高代码的复用，减少代码的冗余，提高代码维护性。

基本语法

Func 函数名 (形参列表) (返回值类型列表) {
执行语句...
Return + 返回值列表
}

Copy
package main

import "fmt"

// 自定义cal函数，功能：两个数相加
func cal(sum1 int, num2 int) int { //返回值类型列表就一个可以省略括号
var sum int = 0
sum += sum1
sum += num2
return sum
}
func main() {
var n int = 10
var m int = 20
s := cal(m, n)
fmt.Println(s)
}

细节

函数名

遵循标识符命名规范

首字母不能为数字

首字母大写可以被本包和其他包调用（类似 public）

首字母小写只能被本包调用（类似 private）

形参列表

个数：参数可以是 0 个、1 个或 n 个

作用：接收外来数据

返回值类型列表

返回值类型列表为 0 个直接省略此项

返回值类型列表就一个可以省略括号

如果有返回值不想被接收，可以用_来忽略

不支持重载

支持可变参数

Copy
package main

import "fmt"

func test(args ...int) {
for j := 0; j < len(args); j++ {
fmt.Println(args[j])
}
}
func main() {
test()
fmt.Println("________________________")
test(3)
fmt.Println("________________________")
test(2, 5, 6, 9)
}

运行结果：

Copy
________________________
3
________________________
2
5
6
9

基本数据类型和数组默认都是值传递，在函数内修改不会影响原来的值。

Copy
package main

import "fmt"

func ex(num int) {
num = 30
}
func main() {
var num int = 10
ex(num)
fmt.Println(num)
}

运行结果：

Copy
10

以值传递方式的数据类型，如果希望函数内的变量修改函数外的变量，可以用 & 传入变量的地址，函数内以指针方式操作变量。

Copy
package main

import "fmt"

func ex(num *int) {
*num = 30
}
func main() {
var num int = 10
ex(&num)
fmt.Println(num)
}

运行结果：

Copy
30

函数也是一种数据类型，可以赋值给一个变量，可通过该变量调用函数，函数也可以作为形参被调用。

Copy
package main

import "fmt"

// 定义一个函数
func re(num int) {
fmt.Println(num)
}

// 定义一个函数，把另一个函数作为形参
func te(num1 int, num2 float32, testFunc func(int)) {
fmt.Println("*************")
}
func main() {
a := re
fmt.Printf("a的类型是：%T,re函数的类型是：%T", a, re) //a的类型是：func(int),re函数的类型是：func(int)
a(10) //等价于test(10)
te(1, 2.5, a) //等价于te(1, 2.5, re)
}

Go 支持自定义数据类型（相当于给现有数据类型起别名）
基本语法：type 自定义数据类型名数据类型
例如：type myInt int 【此时的 myInt 等价于 int】
例如：type mySum func (int, int) int 【此时的 mySum 等价于 func (int, int) int】

支持对函数返回值命名

Copy
package main

func test1(num1 int, num2 int) (int, int) {
result1 := num1 + num2
result2 := num1 - num2
return result1, result2
}

// 给函数的返回值命名可以写成
func test2(num1 int, num2 int) (sum int, sub int) {
sum = num1 + num2
sub = num1 - num2
return
}

包的引入

使用包的原因

可以把函数分类，放在不同原文件中

解决同名问题，不同包里的函数可以同名，通过包来区分

细节

建议声明的包与所在文件夹同名

Main 包是程序入口包，一般 main 函数会在这个包

打包语法：package 包名

引入包的语法：import "包的路径"，多个包一次性导入加 {}

不同包函数调用时要先定位到所在包

函数名、变量名首字母大写才可以被其它包访问

同目录下不能有重复函数

包名和文件夹名可以不一样

一个目录下同级文件归属于同一个包

可以给包取别名，一旦取别名，原包名不能使用

Init 函数

初始化函数，可以用来一些初始化操作。

匿名函数

如果某个函数希望只使用一次，可以考虑匿名函数

匿名函数使用方式

在定义匿名函数时直接调用，这种方式只能调用一次

Copy
package main

import "fmt"

func main() {
//定义匿名函数:定义的同时调用
sum := func(num1 int, num2 int) int {
return num1 + num2
}(10, 20)
fmt.Println(sum)
}

将匿名函数赋值给一个变量（即函数变量），通过此变量来调用

Copy
package main

import "fmt"

func main() {
//定义匿名函数并赋值给变量
sub := func(num1 int, num2 int) int {
return num1 - num2
}
result := sub(20, 10)
fmt.Println(result)
}

闭包

什么是闭包

闭包就是一个函数和与其相关的引用环境组合的一个整体。

Copy
package main

import "fmt"

// 定义fucSum函数，参数为空
// fucSum函数返回一个函数，该函数参数为int类型，返回值也为int类型
func fucSum() func(int) int {
var sum int = 0
return func(p int) int {
sum += p
return sum
}
}

// 闭包就比如这里的fucSum函数返回的函数加上变量sum
func main() {
f := fucSum()
fmt.Println(f(1)) //1
fmt.Println(f(2)) //3
fmt.Println(f(3)) //6
fmt.Println(f(4)) //10
}

> 匿名函数引用的那个变量会保存在内存中，可以一直使用，对内存消耗大，所以不可滥用

闭包的本质

闭包 = 匿名函数 + 引用的变量 / 参数

Defer 关键字

为了函数执行完毕后及时释放资源

Copy
package main

import "fmt"

func add(m int, n int) int {
//在golang中，遇到defer关键字，不会立即执行defer后的语句，而是将defer后面的语句压入一个栈中，然后继续执行函数后面的语句
//栈的特点：先进后出
//在函数执行完毕以后，从栈中取出语句开始执行，按照先进后出的规则执行语句
defer fmt.Println(m)
defer fmt.Println(n)
fmt.Println(m + n)
return m + n
}
func main() {
fmt.Println("结果为：", add(10, 20))
}

运行结果：

Copy
30
20
10
结果为： 30

defer 应用场景

defer 有延迟执行机制（函数执行完毕再执行 defer 压入栈的语句），所以需要关闭资源时，直接随手 defer 省心省事。

系统函数

字符串相关函数

统计字符串长度

Copy
len(str)

字符串遍历

Copy
r := []rune(str)

字符串转整数

Copy
n, err := strconv.Atoi("66")

整数转字符串

Copy
str := strconv.Itoa(88)

判断字符串中是否包含另一字符串

Copy
strings.Contains("javaandgolang", "go")

统计某字符串在另一字符串中出现次数

Copy
strings.Count("javaandgolang", "a")

不区分大小判断两个字符串是否一致

Copy
fmt.Println(strings.EqualFold("go", "Go"))

返回字符串在另一字符串中第一次出现的索引值

Copy
strings.Index("javaandgolang", "a")

如果没有返回 - 1

字符串的替换

Copy
strings.Replace("goandjavagogo", "go", "golang", n)

N 表示指定替换几个，如果 n 为 - 1 表示全部替换

按指定字符串把另一个字符串拆分分割成字符串数组

Copy
strings.Split("go-java-python", "-")

字符串中字母大小写替换

Copy
strings.ToLower("GO")
strings.ToUpper("go")

去掉字符串左右两边空格

Copy
strings.TrimSpace(" go and java ")

去掉字符串左右两边指定字符

Copy
strings.Trim("~go~", "~")

去掉字符串左边指定字符

Copy
strings.TrimLeft("~go", "~")

去掉字符串右边指定字符

Copy
strings.TrimRight("go~", "~")

判断字符串是否以指定字符串开头

Copy
strings.HasPrefix("https://libilibi.eu.org", "https")

判断字符串是否以指定字符串结束

Copy
strings.HasSuffix("https://libilibi.eu.org", "org")

日期和时间相关函数

Copy
package main

import (
"fmt"
"time"
)

func main() {
//获取当前本地时间Now(),返回值是一个结构体,类型是time.Time
now := time.Now()
fmt.Println(now) //2023-06-27 23:11:41.8027164 +0800 CST m=+0.001038201
fmt.Printf("%T \n", now) //time.Time
//调用结构体中的方法：
fmt.Printf("年：%v \n", now.Year()) //年：2023
fmt.Printf("月：%v \n", now.Month()) //月：June
fmt.Printf("月：%v \n", int(now.Month())) //月：6
fmt.Printf("日：%v \n", now.Day()) //日：27
fmt.Printf("时：%v \n", now.Hour()) //时：23
fmt.Printf("分：%v \n", now.Minute()) //分：20
fmt.Printf("秒：%v \n", now.Second()) //秒：56
fmt.Println("_____________________________________________")
//Printf 是直接输出字符串
fmt.Printf("当前年月日：%d-%d-%d 时分秒：%d:%d:%d \n", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
//Sprintf可以得到字符串，以便调用
dateStr := fmt.Sprintf("当前年月日：%d-%d-%d 时分秒：%d:%d:%d \n", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
fmt.Println(dateStr) //当前年月日：2023-6-27 时分秒：23:25:37
fmt.Println("_____________________________________________")
dateStr2 := now.Format("2006/01/02 15/04/05") //各个位置数字必须固定，可以省略但不可更改
fmt.Println(dateStr2) //2023/06/27 23/38/24
}

内置函数

内置函数在 builtin 包下，不用导包直接使用

常用内置函数：

Len 函数：按字节统计字符串长度

New 函数：分配内存，主要分配值类型

Copy
package main

import "fmt"

func main() {
//new分配内存，new函数的实参是一个类型而不是数值，new函数返回值是对应类型的指针
num := new(int)
fmt.Printf("num的类型：%T,num的值是：%v,num的地址是：%v,num指针指向的值是：%v", num, num, &num, *num) //num的类型：*int,num的值是：0xc0000a6058,num的地址是：0xc0000ca018,num指针指向的值是：0
}

Make 函数：分配内存，主要分配引用类型

数组

数组的声明

var 变量名 [索引值] 数据类型

Copy
package main

import (
"fmt"
)

func main() {
//声明一个变量名为scores,索引值为5（即表示能存5个变量）的int类型的数组
var scores [5]int
scores[0] = 89
scores[1] = 79
scores[2] = 92
scores[3] = 95
scores[4] = 88
var sum int
//求和
for i := 0; i < len(scores); i++ {
sum += scores[i]
}
//求平均
avg := sum / len(scores)
fmt.Println(sum) //443
fmt.Println(avg) //88
}

复习 for rang

Copy
package main

import "fmt"

func main() {
var scores [5]int
//手动输入数组内每个变量的值
for i := 0; i < len(scores); i++ {
fmt.Printf("请输入第%d个值------", i+1)
fmt.Scanln(&scores[i])
}
for m, n := range scores {
fmt.Printf("第%d个值为：%d\n", m+1, n)
}
}

数组的初始化

Copy
package main

import "fmt"

func main() {
//第一种
var a [3]int = [3]int{1, 2, 3}
fmt.Println(a)
//第一种
var b = [3]float32{2.5, 3.6, 5.5}
fmt.Println(b)
//第三种
var c = [...]byte{4, 5, 6}
fmt.Println(c)
//第四种
var d = [...]string{1: "jia", 2: "ming", 0: "wang"}
fmt.Println(d)
}

运行结果：

Copy
[1 2 3]
[2.5 3.6 5.5]
[4 5 6]
[wang jia ming]

数组注意事项

数组长度是数组类型的一部分

Copy
package main

import "fmt"

func main() {
//第一种
var a [3]int = [3]int{1, 2, 3}
fmt.Printf("数组类型为：%T\n", a)
//第一种
var b = [2]float32{2.5, 3.6}
fmt.Printf("数组类型为：%T\n", b)
//第三种
var c = [...]byte{4, 5, 6, 7}
fmt.Printf("数组类型为：%T\n", c)
//第四种
var d = [...]string{1: "jia", 2: "ming", 0: "wang"}
fmt.Printf("数组类型为：%T\n", d)
}

运行结果：

Copy
数组类型为：[3]int
数组类型为：[2]float32
数组类型为：[4]uint8
数组类型为：[3]string

Go 中数组属于值类型，默认情况是值传递，因此会进行值拷贝，所以想在其他函数修改数组，需要使用引用传递（即指针方式）

Copy
package main

import "fmt"

func main() {
var arr = [3]int{7, 8, 9}
test1(arr)
fmt.Println(arr) //[7,8,9] test函数赋值arr[0]后数组没有变化
test2(&arr) //传入数组地址
fmt.Println(arr) //[5,8,9] 此时数组有变化了
}
func test1(arr [3]int) { //取出数组的值进行赋值
arr[0] = 5
}
func test2(arr *[3]int) { //取出数组的值进行赋值
arr[0] = 5
}

二维数组的声明

var 变量名 [索引值][索引值] 数据类型

Copy
package main

import "fmt"

func main() {
var a [2][3]int
fmt.Println(a) //[[0 0 0] [0 0 0]] 表示这个数组有2个长度为3一维数组组成
}

二维数组的初始化

Copy
package main

import "fmt"

func main() {
var arr = [2][3]string{{"a", "b", "c"}, {"a", "b", "c"}}
fmt.Println(arr)
}

二维数组的遍历

Copy
package main

import "fmt"

func main() {
var arr = [3][3]string{{"a", "b", "c"}, {"d", "e", "f"}, {"g", "h", "i"}}
//双层for循环
for i := 0; i < len(arr); i++ {
for j := 0; j < 3; j++ {
fmt.Println(arr[i][j])
}
}
println("______________________________________________________")
//双层for rang 如果不需要索引可以用下划线忽略
for _, m := range arr {
for _, n := range m {
fmt.Println(n)
}
}
}

运行结果：

Copy
a
b
c
d
e
f
g
h
i
______________________________________________________
a
b
c
d
e
f
g
h
i

切片

切片是什么

切片是一个结构体，len 表示底层数组长度，cap 是预设总的分配的内存空间长度（即底层数组和预分配内存的长度总和），预分配内存的长度是可变的，一旦超出预设，以预设总内存长度为单位继续加长

切片的声明和初始化

var 变量名 [] 数据类型

Copy
package main

import "fmt"

func main() {
var arr []int
arr = make([]int, 3, 5)
arr[0], arr[1], arr[2] = 7, 8, 9
//打印底层数组长度和预设总的分配的内存空间长度
fmt.Printf("%d %d\n", len(arr), cap(arr)) //3 5
//打印切片
fmt.Println(arr) //[7 8 9]
}

切片的细节

Copy
package main

import "fmt"

func main() {
var arr = make([]int, 3, 4)
arr[0], arr[1], arr[2] = 7, 8, 9
//打印底层数组长度和预设总的分配的内存空间长度
fmt.Printf("%d %d\n", len(arr), cap(arr)) //3 4
//打印切片
fmt.Println(arr) //[7 8 9]
//把切片arr赋值给切片brr
brr := arr
fmt.Printf("%p %p\n", arr, brr) //0xc00000c330 0xc00000c330 发现这两个切片内存地址一样，所以是共享一个内存地址
//在arr切片基础上追加上一个10放到预分配内存然后赋值给brr切片
brr = append(arr, 10)
fmt.Printf("%d %d %d\n", len(brr), cap(brr), brr) //4 4 [7 8 9 10] 此时底层数组发生改变,预分配内存已经填满
//在brr切片原基础上追加上一个11,预分配内存不够，需要增加，之前内存总长4，再开辟添加长度为4的内存
brr = append(brr, 11)
fmt.Printf("%d %d %d\n", len(brr), cap(brr), brr) //5 8 [7 8 9 10 11] 此时底层数组发生改变,分配内存总长度也改变
fmt.Printf("%p %p\n", arr, brr) //0xc000012180 0xc00000e3c0 此时内存地址也改变，所以不共享内存地址了
}

切片的遍历

Copy
package main

import "fmt"

func main() {
var arr = make([]int, 3, 4)
arr[0], arr[1], arr[2] = 7, 8, 9
for i, value := range arr {
fmt.Printf("%p %p %d %d\n", &value, &arr[i], value, arr[i])
}
}

运行结果：

Copy
0xc0000a6058 0xc0000d2020 7 7
0xc0000a6058 0xc0000d2028 8 8
0xc0000a6058 0xc0000d2030 9 9

遍历过程 value 内存地址不变

map

map 的声明和初始化

var 变量名 map [键的数据类型] 值的数据类型

Copy
package main

import "fmt"

func main() {
var m map[string]int
fmt.Println(m)
}

map 的细节

Copy
package main

import "fmt"

func main() {
var m map[string]int
m = map[string]int{"a": 7, "b": 8, "c": 9}
m["a"] = 6
fmt.Println(m["a"])
//删除m中a映射的值,虽然删除了但是返回默认值0
delete(m, "a")
fmt.Println(m["a"])
//判断m中a映射的值到底删除了没
if v, exists := m["a"]; exists { //初始化v和exists，等价m["a"]，判断exists是否存在
fmt.Println(v)
} else {
fmt.Println("m[\"a\"]已被删除")
}
}

运行结果：

Copy
6
0
m["a"]已被删除

map 的遍历

Copy
package main

import (
"fmt"
)

func main() {
var m = map[string]int{"a": 1, "b": 2, "c": 3}
for key, value := range m {
fmt.Println(key, value)
}
}

运行结果：

Copy
a 1
b 2
c 3

结构体

结构体的定义和运用

Copy
package main

import "fmt"

type Teacher struct {
Name string
Age int
School string
}
type User struct {
Name string
Age int
School string
}

func main() {
t := Teacher{"张三", 18, "清华"}
fmt.Println(t) //{张三 18 清华}
u := *new(User)
u.Name = "李四"
u.Age = 19
u.School = "北大"
fmt.Println(u) //{李四 19 北大}
}

接口

接口的定义和运用

接口是一组行为规范的集合

Copy
package main

import "fmt"
//Math接口可以定义一些规则
type Math interface {
Formula(int, int) int
}

func test(math Math) {
m := math.Formula(6, 3)
fmt.Println(m)
}
func main() {
//Add和Sub都是Math接口的具体实现
m := Add{}
n := Sub{}
test(m) //9
test(n) //3
}

type Add struct {
}
//给结构体安装方法
func (Add) Formula(a int, b int) int {
return a + b
}

type Sub struct {
}
//给结构体安装方法
func (Sub) Formula(a int, b int) int {
return a - b
}

空接口

空接口可以直接写成 interface {}，接受一切行为规范 (即包容一切)，可以随意赋值

Copy
package main

import "fmt"

func main() {
var inter interface{}
var str = "golang"
inter = str
fmt.Println(inter) //golang
var a = 0
inter = a
fmt.Println(inter) //0
var f = true
inter = f
fmt.Println(inter) //true
}