复杂数据类型 / 派生数据类型【指针、数组、结构体、管道、函数、切片、接口、map】#
指针#
什么是指针#
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 + 返回值列表
}
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 个直接省略此项
-
返回值类型列表就一个可以省略括号
-
如果有返回值不想被接收,可以用_来忽略
-
-
不支持重载
-
支持可变参数
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)
}
运行结果:
________________________
3
________________________
2
5
6
9
- 基本数据类型和数组默认都是值传递,在函数内修改不会影响原来的值。
package main
import "fmt"
func ex(num int) {
num = 30
}
func main() {
var num int = 10
ex(num)
fmt.Println(num)
}
运行结果:
10
- 以值传递方式的数据类型,如果希望函数内的变量修改函数外的变量,可以用 & 传入变量的地址,函数内以指针方式操作变量。
package main
import "fmt"
func ex(num *int) {
*num = 30
}
func main() {
var num int = 10
ex(&num)
fmt.Println(num)
}
运行结果:
30
- 函数也是一种数据类型,可以赋值给一个变量,可通过该变量调用函数,函数也可以作为形参被调用。
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】 - 支持对函数返回值命名
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 函数#
初始化函数,可以用来一些初始化操作。
匿名函数#
如果某个函数希望只使用一次,可以考虑匿名函数
匿名函数使用方式#
- 在定义匿名函数时直接调用,这种方式只能调用一次
package main
import "fmt"
func main() {
//定义匿名函数:定义的同时调用
sum := func(num1 int, num2 int) int {
return num1 + num2
}(10, 20)
fmt.Println(sum)
}
- 将匿名函数赋值给一个变量(即函数变量),通过此变量来调用
package main
import "fmt"
func main() {
//定义匿名函数并赋值给变量
sub := func(num1 int, num2 int) int {
return num1 - num2
}
result := sub(20, 10)
fmt.Println(result)
}
闭包#
什么是闭包#
闭包就是一个函数和与其相关的引用环境组合的一个整体。
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 关键字#
为了函数执行完毕后及时释放资源
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))
}
运行结果:
30
20
10
结果为: 30
defer 应用场景#
defer 有延迟执行机制(函数执行完毕再执行 defer 压入栈的语句),所以需要关闭资源时,直接随手 defer 省心省事。
系统函数#
字符串相关函数#
统计字符串长度
len(str)
字符串遍历
r := []rune(str)
字符串转整数
n, err := strconv.Atoi("66")
整数转字符串
str := strconv.Itoa(88)
判断字符串中是否包含另一字符串
strings.Contains("javaandgolang", "go")
统计某字符串在另一字符串中出现次数
strings.Count("javaandgolang", "a")
不区分大小判断两个字符串是否一致
fmt.Println(strings.EqualFold("go", "Go"))
返回字符串在另一字符串中第一次出现的索引值
strings.Index("javaandgolang", "a")
如果没有返回 - 1
字符串的替换
strings.Replace("goandjavagogo", "go", "golang", n)
N 表示指定替换几个,如果 n 为 - 1 表示全部替换
按指定字符串把另一个字符串拆分分割成字符串数组
strings.Split("go-java-python", "-")
字符串中字母大小写替换
strings.ToLower("GO")
strings.ToUpper("go")
去掉字符串左右两边空格
strings.TrimSpace(" go and java ")
去掉字符串左右两边指定字符
strings.Trim("~go~", "~")
去掉字符串左边指定字符
strings.TrimLeft("~go", "~")
去掉字符串右边指定字符
strings.TrimRight("go~", "~")
判断字符串是否以指定字符串开头
strings.HasPrefix("https://libilibi.eu.org", "https")
判断字符串是否以指定字符串结束
strings.HasSuffix("https://libilibi.eu.org", "org")
日期和时间相关函数#
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 函数:分配内存,主要分配值类型
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 变量名 [索引值] 数据类型
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
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)
}
}
数组的初始化#
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)
}
运行结果:
[1 2 3]
[2.5 3.6 5.5]
[4 5 6]
[wang jia ming]
数组注意事项#
- 数组长度是数组类型的一部分
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)
}
运行结果:
数组类型为:[3]int
数组类型为:[2]float32
数组类型为:[4]uint8
数组类型为:[3]string
- Go 中数组属于值类型,默认情况是值传递,因此会进行值拷贝,所以想在其他函数修改数组,需要使用引用传递(即指针方式)
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 变量名 [索引值][索引值] 数据类型
package main
import "fmt"
func main() {
var a [2][3]int
fmt.Println(a) //[[0 0 0] [0 0 0]] 表示这个数组有2个长度为3一维数组组成
}
二维数组的初始化#
package main
import "fmt"
func main() {
var arr = [2][3]string{{"a", "b", "c"}, {"a", "b", "c"}}
fmt.Println(arr)
}
二维数组的遍历#
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)
}
}
}
运行结果:
a
b
c
d
e
f
g
h
i
______________________________________________________
a
b
c
d
e
f
g
h
i
切片#
切片是什么#
切片是一个结构体,len 表示底层数组长度,cap 是预设总的分配的内存空间长度(即底层数组和预分配内存的长度总和),预分配内存的长度是可变的,一旦超出预设,以预设总内存长度为单位继续加长
切片的声明和初始化#
var 变量名 [] 数据类型
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]
}
切片的细节#
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 此时内存地址也改变,所以不共享内存地址了
}
切片的遍历#
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])
}
}
运行结果:
0xc0000a6058 0xc0000d2020 7 7
0xc0000a6058 0xc0000d2028 8 8
0xc0000a6058 0xc0000d2030 9 9
遍历过程 value 内存地址不变
map#
map 的声明和初始化#
var 变量名 map [键的数据类型] 值的数据类型
package main
import "fmt"
func main() {
var m map[string]int
fmt.Println(m)
}
map 的细节#
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\"]已被删除")
}
}
运行结果:
6
0
m["a"]已被删除
map 的遍历#
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)
}
}
运行结果:
a 1
b 2
c 3
结构体#
结构体的定义和运用#
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 北大}
}
接口#
接口的定义和运用#
接口是一组行为规范的集合
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 {},接受一切行为规范 (即包容一切),可以随意赋值
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
}