Go语法基础(2) | 豆包MarsCode AI刷题

57 阅读7分钟

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]intnum[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))
}

切片扩容注意事项:

  • 二维切片常见的扩容场景:扩展外层切片、扩展内层切片、 动态创建新内层切片、 模拟矩阵动态扩容……
    1. 容量增长策略:

      • 切片扩容时,Go 会按照倍增策略分配新的底层数组(初始容量不足时分配更大的空间)。
      • 扩容时会拷贝旧数据到新的底层数组,因此大量扩容操作可能影响性能。
      • 当容量增大到一个非常大的值时(通常是几千甚至更大的数值),Go 会将扩容的倍数减小为较小的增量,而不再是 2 倍。这个增长策略的目的是避免为非常大的切片分配过多内存。
    2. 避免多层引用问题:

      • 如果二维切片的内层切片是共享的(例如使用 make 初始化后复制引用),在扩容或修改某行时,其他行可能也会被影响。
    3. 善用 make 提前分配:

      • 频繁的扩容会影响性能,尤其是在数据量很大的情况下。
      • 如果可以预估切片的大小,使用 make 初始化容量可以减少扩容带来的性能开销。
      • 示例:make([][]int, 0, 10) 为外层切片预分配容量。
  • 因为切片的底层是数组,因此在append的时候如果没超出其cap,底层数组的数据也会发生变化,而超过cap的时候,即扩容的时候,旧数组的数据可能就没有改变或者旧数组被垃圾回收。

最重要的是:切片可以和结构体、map以及自定义类型相结合,可以自己尝试写一个简单的用户管理系统。

思考题:

num1 := [][4]int
num2 := [4][]int

思考num1num2的数据类型?

答案: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:定义一个接收者为CircleArea方法。

注: 方法是可以重名的,只要接收者不同。如果接收者相同的话,就不能重名了。

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语言的学习之路上更进一步。