Go语法基础(2)
在之前笔记基础上,我们将继续深入探讨Go语言的数组、切片、接口和方法。
这些是Go语言中非常重要的概念,有助于我们编写更灵活和高效的代码。
1. 数组
数组是一个固定长度的序列,包含相同类型的元素。下面是一个数组的示例:
package main
import "fmt"
func main() {
var arr [5]int // 声明一个长度为5的整数数组
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
arr[4] = 5
//另一种方式
str := [5]string{"zhangsan","lisi","wangwu","zhaoliu","huangqi"}
num := [...]int{1,2,3,4,5,6,7,8,9,10}
fmt.Println("数组元素:", arr)
fmt.Println("数组元素:",str)
fmt.Println("数组元素:",num)
// 遍历数组
for i, v := range arr {
fmt.Printf("索引 %d: 值 %d\n", i, v)
}
}
代码解析
var arr [5]int:声明一个长度为5的整数数组。[...]暂时不知道数组的长度,由编译器自动推导长度for i, v := range arr:使用range遍历数组,i是索引,v是值。
多维数组
Go中的多维数组和C语言的多维数组相似,下面是一个多维数组的示例:
package main
import "fmt"
func main() {
num := [2][4]int{
{1, 2, 3, 4},
{5, 6, 7, 8},
}
//打印
fmt.Println(num)
//遍历
fmt.Printf("%T %T\n", num, num[0])
for i := 0; i < len(num); i++ {
for j := 0; j < len(num[0]); j++ {
fmt.Println("num", num[i][j])
}
}
}
代码解析
num的类型是[2][4]int,num[0]的类型是[4]int。len(num)的结果就是二维数组的行数,len(num[0])的结果是二维数组的列数。
除此之外还由三维数组等高维数组。
2. 切片
切片是动态大小的数组,具有更灵活的操作。以下是一个切片的示例:
package main
import "fmt"
func main() {
// 创建一个切片
slice := []int{1, 2, 3, 4, 5}
fmt.Println("切片元素:", slice)
// 添加元素
slice = append(slice, 6)
fmt.Println("添加元素后的切片:", slice)
// 切片切割
subSlice := slice[1:4]
fmt.Println("切片的子切片:", subSlice)
}
代码解析
slice := []int{1, 2, 3, 4, 5}:创建一个整数切片。slice = append(slice, 6):使用append函数添加元素。
因为切片的底层指向数组,自然会有二维切片……以下是二维切片的示例:
package main
import "fmt"
func main(){
var num [][]int //此时切片中没有元素
fmt.Println(num)
fmt.Println("len:",len(num),"cap",cap(num))
num = append(num, []int{1,2,3})
fmt.Println(num)
fmt.Println("len:",len(num),"cap",cap(num))
num = append(num, []int{4,5,6})
fmt.Println(num)
fmt.Println("len:",len(num),"cap",cap(num))
num = append(num, []int{7,8,9})
fmt.Println(num)
fmt.Println("len:",len(num),"cap",cap(num))
num = append(num, []int{10,11,12})
fmt.Println(num)
fmt.Println("len:",len(num),"cap",cap(num))
}
切片扩容注意事项:
- 二维切片常见的扩容场景:扩展外层切片、扩展内层切片、 动态创建新内层切片、 模拟矩阵动态扩容……
-
-
容量增长策略:
- 切片扩容时,Go 会按照倍增策略分配新的底层数组(初始容量不足时分配更大的空间)。
- 扩容时会拷贝旧数据到新的底层数组,因此大量扩容操作可能影响性能。
- 当容量增大到一个非常大的值时(通常是几千甚至更大的数值),Go 会将扩容的倍数减小为较小的增量,而不再是 2 倍。这个增长策略的目的是避免为非常大的切片分配过多内存。
-
避免多层引用问题:
- 如果二维切片的内层切片是共享的(例如使用
make初始化后复制引用),在扩容或修改某行时,其他行可能也会被影响。
- 如果二维切片的内层切片是共享的(例如使用
-
善用
make提前分配:- 频繁的扩容会影响性能,尤其是在数据量很大的情况下。
- 如果可以预估切片的大小,使用
make初始化容量可以减少扩容带来的性能开销。 - 示例:
make([][]int, 0, 10)为外层切片预分配容量。
-
- 因为切片的底层是数组,因此在
append的时候如果没超出其cap,底层数组的数据也会发生变化,而超过cap的时候,即扩容的时候,旧数组的数据可能就没有改变或者旧数组被垃圾回收。
最重要的是:切片可以和结构体、map以及自定义类型相结合,可以自己尝试写一个简单的用户管理系统。
思考题:
num1 := [][4]int
num2 := [4][]int
思考num1和num2的数据类型?
答案:num1是一个切片,其元素是长度为4的数组;num2是一个数组,其元素是切片。
3. 方法
方法是与特定类型关联的函数。与函数不同,方法有一个接收者。下面是一个定义方法的示例:
package main
import "fmt"
// 定义一个结构体
type Circle struct {
Radius float64
}
// 为Circle定义一个方法
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func main() {
circle := Circle{Radius: 7}
fmt.Println("圆的面积:", circle.Area())
}
代码解析
func (c Circle) Area() float64:定义一个接收者为Circle的Area方法。
注: 方法是可以重名的,只要接收者不同。如果接收者相同的话,就不能重名了。
package main
import (
"fmt"
)
// 定义一个结构体
type Circle struct {
Radius float64
}
type Rectangle struct {
length float64
width float64
}
// 为Circle定义一个方法
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
// 为Square定义一个方法
func (s Rectangle) Area() float64 {
return s.length * s.width
}
func main() {
circle := Circle{Radius: 7}
rectangle:= Rectangle{length: 7.5, width: 4}
fmt.Println("矩形的面积:", rectangle.Area())
fmt.Println("圆的面积:", circle.Area())
}
除此之外,还有方法的重写,这个就是面向对象的三个核心思维中的继承。
4. 接口
接口是Go语言中一种重要的抽象机制,允许你定义方法的集合,方法多了 - > 接口。下面是一个接口的示例:
package main
import "fmt"
// 定义一个接口
type Shape interface {
Area() float64
}
// 定义一个结构体
type Rectangle struct {
Width, Height float64
}
// 实现接口方法
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func main() {
var s Shape = Rectangle{Width: 10, Height: 5}
fmt.Println("矩形的面积:", s.Area())
}
代码解析
type Shape interface:定义一个接口Shape,包含一个方法Area。func (r Rectangle) Area() float64:为Rectangle结构体实现Area方法。
注: 一个结构体实现了接口的全部方法就代表实现了这个接口(这个结构体就是这个接口类型),否则就不算实现这个接口。
示例:
package main
import "fmt"
type anmial interface{
eat()
sleep()
}
type Cat struct{
name string
age int
}
type Dog struct{
name string
age int
}
func (c Cat) eat(){
fmt.Println("cat",c.name,"eat cat treat stick")
}
func (c Cat) sleep(){
fmt.Println("cat",c.name,"sleeping")
}
func (d Dog) eat (){
fmt.Println("dog",d.name,"eat dog food")
}
func (d Dog) sleep(){
fmt.Println("dog",d.name,"sleeping")
}
func test(a anmial){
a.eat()
a.sleep()
}
func main (){
cat := Cat{"duobao",1}
dog := Dog{"wangcai",2}
//func test(a anmial)
//结构体实现了接口的所有方法,这个结构体就是这个接口类型
test(cat)
test(dog)
//定义高级类型,cat就升级了
//cat -> anmial 向上转型
var anm anmial //模糊的 --> 具体化,将具体实现的类赋值给他,才有意义
anm = cat
fmt.Println(anm)
//接口无法实现类的属性
//fmt.Println(anm.name)
}
空接口
不包含任何方法:因此所有的结构体都默认实现了空接口。因此空接口可以存储任何的类型。
interface{} == any
package main
import "fmt"
type A interface{}
type Dog struct {
name string
age int
}
type Cat struct {
name string
age int
}
func test(a A) {
fmt.Println(a)
}
//any is an alias for interface{} and is equivalent to interface{} in all ways.
//type any = interface{}
func main() {
var a1 A = Cat{name: "duoabo",age:2}
var a2 A = Dog{name: "wangcai",age:1}
var a3 A = 1
var a4 A = "welcome"
fmt.Println(a1)
fmt.Println(a2)
fmt.Println(a3)
fmt.Println(a4)
// map
map1 := make(map[string]interface{})
map1["name"] = "xiaoli"
map1["age"] = 18
fmt.Println(map1)
// slice
s1 := make([]any, 0, 10)
s1 = append(s1, 1, "12312", false, a1, a2)
fmt.Println(s1)
var arr [4]interface{}
fmt.Println(arr)
}
结论
通过本篇笔记,我们涵盖了Go语言的数组、切片、方法和接口。这些特性使得Go语言在处理数据和构建灵活的程序时非常强大。掌握这些基础知识将有助于我们在Go语言的学习之路上更进一步。