一、复合数据结构
Go的复合类型包含数组、切片、map、结构体。
1. 数组 Array
数组是固定长度、元素类型相同 的连续内存集合。数组长度属于类型的一部分,[3]int 和 [5]int 是两种完全不同的类型。
数组属于值类型,赋值或者传给函数时,会完整复制一份全部元素,数据量大的时候性能开销高,所以业务代码很少直接使用数组,数组更多是切片的底层载体。
go
package main
import "fmt"
func main() {
// 声明,默认零值初始化
var arr [3]int
arr[0] = 10
arr[1] = 20
arr[2] = 30
fmt.Println(arr)
// 简写初始化
arr2 := [3]string{"Go", "Rust", "C++"}
fmt.Println(arr2)
// 自动推导长度
arr3 := [...]int{1, 2, 3, 4}
fmt.Printf("arr3类型:%T\n", arr3)
}
- 长度一旦定义,不能扩容;
- 遍历可以用
for或者for range; - 值拷贝,函数接收数组参数会复制整个数组,大数组不推荐。
2. 切片 Slice
切片是Go开发中最常用 的数据结构,它并不是数组,而是对底层数组的引用视图。
切片结构包含三部分:指针(指向底层数组)、len(当前有效元素长度)、cap(底层数组容量) 。切片是引用类型。
go
package main
import "fmt"
func main() {
// 方式1:直接字面量创建切片
s := []int{10, 20, 30}
fmt.Println("len:", len(s), "cap:", cap(s))
// 方式2:make创建,make(类型, len, cap)
s2 := make([]int, 2, 5)
fmt.Println(s2)
// append追加元素,超过cap自动扩容
s = append(s, 40, 50)
fmt.Println(s)
// 切片截取 s[low:high],左闭右开,新切片和原切片共享底层数组
s3 := s[1:3]
s3[0] = 999
fmt.Println("原切片", s)
}
- 切片截取后共享底层数组,修改新切片会影响原切片;
- append扩容时,如果容量不足,会开辟新数组,不再共享底层;
- nil切片:
var s []int,本身没有底层数组,可以直接append;空切片make([]int,0)有底层数组。
3. map
map是键值对(key-value)集合,适合根据key快速查找数据。map是引用类型 。
map的key必须是可比较的类型(int、string等,不能是切片、map、函数);key唯一,重复赋值会覆盖旧值;map内部元素无序,遍历顺序每次可能不一样
go
package main
import "fmt"
func main() {
// 声明map,nil,不能直接写入key
var m map[string]int
// 必须make初始化
m = make(map[string]int)
m["age"] = 22
m["score"] = 95
fmt.Println(m["age"])
// 获取值,同时判断key是否存在
val, ok := m["name"]
if ok {
fmt.Println("name:", val)
} else {
fmt.Println("key不存在")
}
// 删除key
delete(m, "age")
}
- 只
var m map[xxx]不make,直接写键会panic; - 遍历
for range map每次顺序不同; - map的value如果是结构体,不能直接修改结构体内部字段,需要取出或者用指针。
4. 结构体 struct
结构体可以把不同类型的字段 组合在一起,用来描述现实中的实体,比如用户、订单。结构体是自定义类型,可以绑定方法,是Go实现面向对象思想的基础。
go
package main
import "fmt"
// 定义结构体
type User struct {
Name string
Age int
}
func main() {
// 初始化方式1:按顺序赋值
u1 := User{"lisi", 20}
// 初始化方式2:指定字段名,推荐,顺序无关
u2 := User{Name: "wangwu", Age: 21}
fmt.Println(u1.Name, u1.Age)
fmt.Println(u2)
// 修改字段
u2.Age = 22
fmt.Println(u2)
}
- 字段首字母大写,包外可以访问;小写只能当前包访问;
- 结构体可以嵌套结构体;
- 结构体本身是值类型,赋值会完整拷贝;结构体指针则不会拷贝。
二、函数
参数、变量、返回值。函数支持多返回值,这是Go最标志性的特性,通常用第二个返回值返回error。
基础函数定义
语法:func 函数名(参数列表) (返回值列表) {函数体}
go
package main
import "fmt"
// 普通函数,两个入参,一个返回值
func add(a int, b int) int {
return a + b
}
// 多返回值,第二个返回error
func div(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为0")
}
return a / b, nil
}
func main() {
sum := add(3, 5)
fmt.Println(sum)
res, err := div(10, 2)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(res)
}
}
1. 可变参数
...类型代表可变参数,可以传入任意多个同类型参数,可变参数本质就是切片。可变参数必须放在参数列表最后。
go
func sum(nums ...int) int {
total := 0
for _, v := range nums {
total += v
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3))
fmt.Println(sum(10, 20))
}
2. 匿名函数与闭包
匿名函数:没有函数名,可以直接赋值给变量或者立即执行。
闭包:匿名函数捕获函数外部的变量,闭包保存的是变量引用,不是变量快照。
go
package main
import "fmt"
func main() {
// 匿名函数赋值给变量
f := func(x int) int {
return x * 2
}
fmt.Println(f(6))
// 闭包示例
a := 10
closure := func() {
a++
fmt.Println(a)
}
closure()
closure()
}
3. 方法(绑定到结构体的函数)
方法和普通函数的区别:方法带有接收者,接收者写在函数名前面。分值接收者 和指针接收者。
- 值接收者:传入结构体副本,方法内部修改不会影响原结构体,开销大,适合小结构体、只读场景。
- 指针接收者:传入结构体指针,直接操作原结构体,不会拷贝,开发优先使用指针接收者。
go
package main
import "fmt"
type User struct {
Name string
}
// 值接收者
func (u User) Hello() {
fmt.Println("hello,", u.Name)
}
// 指针接收者,可以修改原结构体
func (u *User) Rename(newName string) {
u.Name = newName
}
func main() {
u := User{Name: "zhangsan"}
u.Hello()
u.Rename("newName")
fmt.Println(u.Name)
}
4. 函数参数传递规则
- 值类型:基础类型int、bool、string、数组、结构体。传参是拷贝副本,函数内部修改不影响外部原始变量。
- 引用类型:切片、map、channel。传参拷贝的是引用头,底层数据共享,函数内修改底层数据会影响外部。