Skip to content

1.数组(Array)

在Go语言中,数组(Array)是一种具有固定长度、相同类型的元素序列。数组的长度在声明时就确定,无法改变。数组可以包含任何类型的元素,包括数值类型、字符串、结构体,甚至其他数组。

数组的声明和初始化

在Go中,数组可以通过以下几种方式声明和初始化:

1. 声明固定长度的数组

go
var arr [5]int // 声明一个长度为5的int类型数组,元素默认值为0

2. 声明并初始化数组

go
arr := [5]int{1, 2, 3, 4, 5} // 声明并初始化一个长度为5的数组

3. 自动推断长度的数组

go
arr := [...]int{1, 2, 3, 4, 5}  // 使用 `...` 让Go自动推断数组的长度

访问和修改数组元素

数组元素可以通过索引来访问和修改,索引从 0 开始。

go
package main

import "fmt"

func main() {
    arr := [5]int{10, 20, 30, 40, 50}
    fmt.Println(arr[0]) // 输出:10

    arr[0] = 100        // 修改数组的第一个元素
    fmt.Println(arr[0]) // 输出:100
}

数组的遍历

可以使用 for 循环或 range 循环来遍历数组。

使用 for 循环

go
arr := [5]int{10, 20, 30, 40, 50}
for i := 0; i < len(arr); i++ {
    fmt.Println(arr[i])
}

使用 range 循环

go
arr := [5]int{10, 20, 30, 40, 50}
for index, value := range arr {
    fmt.Println("Index:", index, "Value:", value)
}

range 循环中,第一个返回值是索引,第二个返回值是元素的值。如果不需要索引,可以使用 _ 忽略它:

go
for _, value := range arr {
    fmt.Println("Value:", value)
}

数组的特点

  1. 固定长度:数组的长度在定义时就确定,不能在运行时动态改变。
  2. 值类型:数组是值类型,赋值或传递时会复制整个数组的内容,而不是引用。
  3. 相同类型的元素:数组中的所有元素必须是相同的数据类型。

数组作为函数参数

因为数组是值类型,将数组作为参数传递给函数时,函数接收到的是数组的副本。若想在函数中修改原数组,可以使用数组指针或切片。

数组作为参数

go
package main

import "fmt"

func modifyArray(arr [5]int) {
    arr[0] = 100 // 修改的是副本,不会影响原数组
}

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    modifyArray(arr)
    fmt.Println(arr) // 输出:[1 2 3 4 5]
}

数组指针作为参数

go
package main

import "fmt"

func modifyArray(arr *[5]int) {
    arr[0] = 100 // 修改的是原数组
}

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    modifyArray(&arr)
    fmt.Println(arr) // 输出:[100 2 3 4 5]
}

多维数组

Go语言支持多维数组,可以创建二维数组、三维数组等。常见的是二维数组。

二维数组的定义和初始化

go
var matrix [3][3]int // 定义一个3x3的二维数组

matrix := [3][3]int{
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9},
}

访问多维数组的元素

可以通过多个索引访问多维数组中的元素:

go
fmt.Println(matrix[1][2]) // 输出:6

数组的使用场景

尽管数组在Go语言中功能强大,但由于其固定长度的限制,在实际应用中较少直接使用。更多时候,开发者会使用更灵活的切片(slice)来代替数组,因为切片可以动态扩展。然而,在需要确定大小和高性能的情况下,数组仍然是一个很好的选择。

总结

  • 数组是具有固定长度、相同类型元素的容器。
  • 数组是值类型,赋值或传递时会复制整个数组。
  • 可以使用 forrange 循环遍历数组元素。
  • 在需要动态长度或灵活性时,通常使用切片替代数组。

2.切片(Slice)

在Go语言中,切片(Slice)是一个动态数组,长度可以在运行时动态调整。切片是基于数组构建的,提供了比数组更灵活的功能。切片是Go语言中非常重要的数据结构,广泛用于存储和操作动态数据集合。

切片的基本特性

  1. 长度和容量:切片的长度可以动态变化,但容量一般固定,是切片从底层数组中分配的最大长度。可以通过 len(slice) 获取切片长度,通过 cap(slice) 获取切片容量。
  2. 引用类型:切片是一个引用类型,指向底层数组的一个连续部分,对切片的操作会影响底层数组。
  3. 零值为空切片:切片的零值是 nil,一个 nil 切片的长度和容量都是 0。

定义和初始化切片

1. 使用字面量初始化切片

go
slice := []int{1, 2, 3, 4, 5} // 创建一个含有 5 个元素的切片
fmt.Println(slice)             // 输出:[1 2 3 4 5]

2. 使用 make 函数创建切片

make 函数可以创建指定长度和容量的切片。

go
slice := make([]int, 5, 10) // 创建一个长度为5、容量为10的切片
fmt.Println(slice)          // 输出:[0 0 0 0 0]
fmt.Println(len(slice))     // 输出:5
fmt.Println(cap(slice))     // 输出:10

在这里,make([]int, 5, 10) 创建了一个长度为5、容量为10的切片,初始值为元素类型的零值(例如 int 类型为 0)。

3. 从数组或已有切片中截取

可以通过“切片表达式”从数组或已有切片中创建新的切片,格式为:slice[start:end],其中 start 是起始索引,end 是结束索引(不包括)。

go
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]            // 从数组中创建切片,包含索引 1 到 3 的元素
fmt.Println(slice)           // 输出:[2 3 4]
fmt.Println(len(slice))      // 输出:3
fmt.Println(cap(slice))      // 输出:4 (切片的容量从起始索引到数组的末尾)

切片的操作

1. 添加元素

可以使用 append 函数向切片添加元素。append 会自动扩展切片的容量(如果需要),并返回一个新的切片。

go
slice := []int{1, 2, 3}
slice = append(slice, 4, 5) // 添加元素
fmt.Println(slice)          // 输出:[1 2 3 4 5]

如果 append 导致切片容量不够,Go 会自动分配一个新的底层数组,新的切片将引用这个新的数组,而不是原始数组。

2. 复制切片

使用 copy 函数可以将一个切片的内容复制到另一个切片中。copy 函数会返回复制的元素个数。

go
src := []int{1, 2, 3}
dst := make([]int, len(src)) // 创建一个目标切片,长度和 src 相同
copy(dst, src)               // 将 src 复制到 dst
fmt.Println(dst)             // 输出:[1 2 3]

3. 切片的截取

可以通过切片表达式对切片进行截取,获得一个新的子切片:

go
slice := []int{1, 2, 3, 4, 5}
subslice := slice[1:4] // 截取索引 1 到 3 的元素
fmt.Println(subslice)  // 输出:[2 3 4]

切片截取后的子切片与原切片共享同一个底层数组,因此对截取后的子切片的修改会影响到原切片。

4. 修改切片中的元素

切片是引用类型,因此可以通过切片直接修改底层数组中的元素:

go
slice := []int{1, 2, 3, 4, 5}
slice[2] = 10              // 修改第3个元素
fmt.Println(slice)         // 输出:[1 2 10 4 5]

切片的容量变化

在使用 append 向切片添加元素时,如果切片容量不足,Go会重新分配更大的数组并将现有元素复制到新数组中。通常情况下,新数组的容量会比旧容量翻倍。

go
package main

import "fmt"

func main() {
    slice := []int{}
    for i := 1; i <= 10; i++ {
        slice = append(slice, i)
        fmt.Printf("len=%d cap=%d slice=%v\n", len(slice), cap(slice), slice)
    }
}

输出可能类似如下:

go
len=1 cap=1 slice=[1]
len=2 cap=2 slice=[1 2]
len=3 cap=4 slice=[1 2 3]
len=4 cap=4 slice=[1 2 3 4]
len=5 cap=8 slice=[1 2 3 4 5]
...

在容量不足时,Go会自动扩展容量。

多维切片

Go支持多维切片,常用于表示矩阵、二维表格等结构。创建多维切片时,通常需要通过嵌套切片来实现。

go
package main

import "fmt"

func main() {
    matrix := make([][]int, 3) // 创建一个三行的二维切片
    for i := range matrix {
        matrix[i] = make([]int, 3) // 每行有三个元素
    }

    matrix[1][1] = 5
    fmt.Println(matrix) // 输出:[[0 0 0] [0 5 0] [0 0 0]]
}

切片的零值和空切片

  • 切片的零值是 nil,表示未初始化的切片,nil 切片的长度和容量都是 0。
  • 空切片是 []T{},它有一个已分配的底层数组,但长度和容量也是 0。

可以使用 lencap 检查切片是否为空,也可以直接判断 slice == nil 来判断是否是零值切片。

注意事项

  1. 共享底层数组:多个切片可能共享同一个底层数组,对其中一个切片的修改可能会影响到另一个切片。
  2. 切片的扩容:切片在扩容时会生成一个新的底层数组,可能导致旧数组不再被使用,原切片失去对旧数据的引用。
  3. append 返回值:由于扩容可能导致底层数组重新分配内存,因此 append 操作后的切片应该使用返回值来更新原切片引用。

总结

  • 切片是基于数组的动态数据结构,长度可变。
  • appendcopy、切片截取等操作让切片灵活使用。
  • 切片是引用类型,多个切片可以共享同一底层数组。
  • 切片的扩容机制自动处理内存分配,是Go语言中灵活高效的数据结构之一。

3.映射 (Map)

在Go语言中,map 是一种用于存储键值对的数据结构,类似于其他编程语言中的字典或哈希表。map 提供了通过键快速查找和访问值的能力,是一个非常高效的查找表结构。

map 的基本特性

  1. 键值对存储map 中的每个元素都是一个键值对,通过键来唯一标识值。
  2. 键的唯一性map 中的每个键是唯一的,重复的键会覆盖之前的值。
  3. 动态大小map 是动态的,可以根据需要自动调整大小,元素数量不固定。
  4. 无序性map 中的元素没有顺序,遍历时的顺序也不是固定的。

定义和初始化 map

1. 使用字面量创建 map

可以在声明时直接初始化 map

go
m := map[string]int{"apple": 5, "banana": 3, "orange": 8}
fmt.Println(m) // 输出:map[apple:5 banana:3 orange:8]

2. 使用 make 函数创建 map

make 函数用于创建 map,不带初始值。

go
m := make(map[string]int) // 创建一个空的 map
m["apple"] = 5
m["banana"] = 3
fmt.Println(m) // 输出:map[apple:5 banana:3]

map 的基本操作

1. 添加或更新元素

可以直接使用键来添加或更新元素。如果键已经存在,则会更新其对应的值。

go
m := map[string]int{}
m["apple"] = 10          // 添加元素
m["banana"] = 15         // 添加元素
m["apple"] = 20          // 更新元素
fmt.Println(m["apple"])   // 输出:20

2. 获取元素

通过键访问 map 中的值。如果键不存在,则返回该类型的零值。

go
value := m["apple"]
fmt.Println(value) // 如果存在 "apple",输出其值;否则输出 0

3. 检查键是否存在

可以使用带有两个返回值的语法来检查键是否存在。

go
value, exists := m["apple"]
if exists {
    fmt.Println("Key exists with value:", value)
} else {
    fmt.Println("Key does not exist")
}
  • existstrue 表示键存在,为 false 表示键不存在。

4. 删除元素

使用 delete 函数删除指定键的元素。

go
delete(m, "banana")
fmt.Println(m) // 如果 "banana" 存在,将其删除

5. 遍历 map

可以使用 range 关键字遍历 map 中的所有键值对。遍历的顺序是随机的,因为 map 本身是无序的。

go
m := map[string]int{"apple": 5, "banana": 3, "orange": 8}
for key, value := range m {
    fmt.Printf("Key: %s, Value: %d\n", key, value)
}

map 的使用注意事项

  1. 键的类型限制map 的键必须是可比较的(支持 ==!= 操作符),常用的键类型包括 stringintfloatbool 等。不能使用切片、map 或函数作为键,因为它们是不可比较的。
  2. 零值为 nilmap 的零值是 nil。一个 nil map 没有初始化,无法存储键值对,但可以读取,返回零值。使用 make 或字面量初始化 map
  3. 并发访问map 在并发情况下是不安全的,多个 goroutine 同时读写 map 会引发竞态条件。可以使用 sync.RWMutexsync.Map 来保证并发安全。

使用 struct 作为 map 的值

map 的值可以是任意类型,包括自定义的结构体。以下是一个使用结构体作为值的示例:

go
package main

import "fmt"

type Product struct {
    Name  string
    Price float64
}

func main() {
    products := map[string]Product{
        "p1": {"Apple", 1.99},
        "p2": {"Banana", 0.99},
    }

    fmt.Println(products["p1"].Name) // 输出:Apple
    fmt.Println(products["p1"].Price) // 输出:1.99
}

使用 struct 作为 map 的键

尽管 struct 类型可以作为 map 的键,但要注意键的==所有字段必须是可比较的==(例如,不包含切片、mapfunc 等类型),否则会引发编译错误。

go
package main

import "fmt"

type Point struct {
    X int
    Y int
}

func main() {
    m := map[Point]string{
        {1, 2}: "Point A",
        {3, 4}: "Point B",
    }

    fmt.Println(m[Point{1, 2}]) // 输出:Point A
}

使用 sync.Map 实现并发安全的 map

Go标准库提供了 sync.Map 类型来支持并发安全的 map 操作。sync.Map 有特殊的API,适合需要并发访问的场景。

go
package main

import (
    "fmt"
    "sync"
)

func main() {
    var m sync.Map

    // Store key-value pairs
    m.Store("apple", 5)
    m.Store("banana", 3)

    // Load value by key
    if value, ok := m.Load("apple"); ok {
        fmt.Println("apple:", value) // 输出:apple: 5
    }

    // Delete a key
    m.Delete("banana")

    // Iterate over all key-value pairs
    m.Range(func(key, value interface{}) bool {
        fmt.Println("Key:", key, "Value:", value)
        return true
    })
}

常见应用场景

  1. 快速查找:使用键值对查找比线性搜索更高效,适合频繁查找的场景。
  2. 计数:可以用 map 来统计元素的出现次数,例如字符统计、单词统计等。
  3. 分组:可以根据某个属性分组,例如根据年龄分组、根据城市分组等。
  4. 缓存:可以将常用数据存储在 map 中作为缓存,以减少重复计算。

总结

  • 定义和操作map 是键值对数据结构,支持快速查找、添加、更新和删除操作。
  • 并发安全map 在并发情况下是不安全的,推荐使用 sync.Map 进行并发读写。
  • 典型应用map 常用于快速查找、统计计数、分组和缓存等场景。
  • 无序性map 中元素的顺序是随机的,遍历时不保证顺序。

4.结构体(Struct)

在Go语言中,结构体(Struct)是一种聚合数据类型,可以将多个不同类型的字段组合在一起。结构体通常用于表示数据的复杂对象,是Go语言实现面向对象编程的基础。通过结构体可以实现类型的封装和方法绑定,便于代码的组织和管理。

定义结构体

结构体的定义使用 typestruct 关键字:

go
type StructName struct {
    Field1 Type1
    Field2 Type2
    // 可以添加更多字段
}
  • StructName 是结构体的名称,推荐使用大写开头的驼峰命名。
  • Field1Field2 是结构体的字段名,Type1Type2 是字段的类型。

结构体实例的创建方式

1.使用字面量创建(常用)

可以通过字面量的方式创建结构体实例:

go
person := Person{
    Name: "Alice",
    Age:  30,
}

字段顺序不影响赋值,但字段名必须明确写出。

2.使用 new 关键字创建

使用 new 可以创建结构体的指针:

go
personPtr := new(Person)
personPtr.Name = "Bob"
personPtr.Age = 25

new(Person) 返回 *Person 类型,personPtr 是指向 Person 结构体的指针。

3.直接定义不初始化的实例

可以声明一个结构体变量,所有字段会被初始化为其零值。

go
var person Person // person 的 Name 为 "",Age 为 0

匿名字段

在Go语言的结构体中,可以定义匿名字段,即不指定字段名称,而直接使用类型作为字段名。匿名字段的类型必须是唯一的。

go
type User struct {
    string // 匿名字段
    int    // 匿名字段
}

func main() {
    user := User{"John Doe", 30}
    fmt.Println("Name:", user.string)
    fmt.Println("Age:", user.int)
}

在这个例子中,User 结构体包含两个匿名字段,分别是 stringint 类型,访问时通过字段类型访问。

嵌套结构体

结构体可以嵌套另一个结构体,通过嵌套可以实现复杂的数据结构。Go语言中的结构体嵌套类似于面向对象语言中的继承。

go
type Address struct {
    City    string
    ZipCode string
}

type Person struct {
    Name    string
    Age     int
    Address // 匿名嵌套 Address 结构体
}

func main() {
    person := Person{
        Name: "Alice",
        Age:  30,
        Address: Address{
            City:    "New York",
            ZipCode: "10001",
        },
    }

    // 直接访问嵌套结构体的字段
    fmt.Println("City:", person.City)       // 输出:City: New York
    fmt.Println("ZipCode:", person.ZipCode) // 输出:ZipCode: 10001
}

在这个示例中,Person 结构体嵌套了 Address 结构体,创建实例时可以直接初始化 Address 的字段,并通过 person.City 的方式访问嵌套结构体的字段。

结构体方法

Go语言中可以为结构体定义方法,方法的第一个参数是接收者,表示该方法所属的结构体类型。结构体方法可以是值接收者或指针接收者

go
type Rectangle struct {
    Width, Height float64
}

// 为 Rectangle 定义一个方法,计算面积
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 为 Rectangle 定义一个方法,更新宽度和高度
func (r *Rectangle) SetSize(width, height float64) {
    r.Width = width
    r.Height = height
}

func main() {
    rect := Rectangle{Width: 5, Height: 3}

    fmt.Println("Area:", rect.Area()) // 输出:Area: 15

    rect.SetSize(10, 6)
    fmt.Println("Updated Area:", rect.Area()) // 输出:Updated Area: 60
}

在这个示例中,Area 方法使用值接收者,因此它不能修改原始结构体的字段;SetSize 方法使用指针接收者,可以修改结构体的字段。

结构体的内存布局和零值

  • 结构体的每个字段在内存中是连续存储的,字段的顺序决定了内存布局。
  • 未初始化的结构体字段会被赋予默认零值(如int0string为空字符串,boolfalse等)。

结构体标签

结构体标签是在Go语言中,用于为结构体字段附加元数据的一种机制。它们通常用于指定在JSON编码/解码、XML处理、数据库交互等操作时所用的名称和属性。标签以反引号(`)包围,并且可以包含多个键值对。

结构体标签的基本语法

结构体标签的基本语法如下:

go
type StructName struct {
    FieldName FieldType `key1:"value1" key2:"value2"`
}

主要用途

  1. JSON 数据处理

    • 在将结构体转换为JSON格式或将JSON数据解码为结构体时,结构体标签可以用于指定JSON中的字段名称。

    • 例如:

      go
      type Person struct {
          Name string `json:"name"`
          Age  int    `json:"age"`
      }

      在这个例子中,结构体字段 Name 在JSON中对应的名称是 name,而 Age 对应 age

  2. XML 数据处理

    • 类似于JSON,标签也可以用于XML编码和解码。
    • 例如:
      go
      type Person struct {
          Name string `xml:"name"`
          Age  int    `xml:"age"`
      }
  3. 数据库操作

    • 在使用ORM(对象关系映射)库与数据库交互时,标签可以指明与数据库表字段的对应关系。
    • 例如:
      go
      type User struct {
          ID   int    `db:"id"`
          Name string `db:"name"`
      }
  4. 自定义标签

    • 除了常见的应用,开发者还可以自行定义标签,用于特定的逻辑处理。

示例

以下是一个完整的示例,演示了如何使用结构体标签:

go
package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name    string `json:"name"`    // JSON 映射
    Age     int    `json:"age"`
    Country string `json:"country,omitempty"` // 如果值为空,则不显示
}

func main() {
    p := Person{Name: "Alice", Age: 30, Country: ""}
    jsonData, _ := json.Marshal(p) // 将结构体转换为 JSON
    fmt.Println(string(jsonData))   // 输出:{"name":"Alice","age":30}
}

CopyInsert

在这个示例中,Country 字段使用了 omitempty 标签,如果它的值为空(例如空字符串),在转换为JSON时将不会包含该字段。

5.接口(Interface)

在Go语言中,接口(Interface)是一种定义对象行为的抽象类型。接口定义了一组方法,任何实现了这些方法的类型都可以被视为实现了该接口。接口在Go语言中用于实现多态性和解耦,是面向对象编程的核心特性之一。

接口的定义

接口使用 typeinterface 关键字定义,接口内包含方法的签名。没有实现这些方法的具体逻辑,这些逻辑由实现接口的类型去完成。

go
type InterfaceName interface {
    Method1(param1 Type1) ReturnType1
    Method2(param2 Type2) ReturnType2
}

接口的实现

在Go语言中,一个类型无需显式声明实现了某个接口,只要该类型实现了接口中所有的方法,则被视为实现了该接口。这种机制称为==隐式实现==,它使得接口的使用更加灵活。

示例:基本接口实现(隐式

go
package main

import "fmt"

// 定义一个接口
type Speaker interface {
    Speak() string
}

// Dog 类型实现了 Speaker 接口
type Dog struct{}

func (d Dog) Speak() string {
    return "Woof!"
}

// Cat 类型实现了 Speaker 接口
type Cat struct{}

func (c Cat) Speak() string {
    return "Meow!"
}

func main() {
    var s Speaker

    s = Dog{}              // Dog 实现了 Speaker 接口
    fmt.Println(s.Speak()) // 输出:Woof!

    s = Cat{}              // Cat 也实现了 Speaker 接口
    fmt.Println(s.Speak()) // 输出:Meow!
}

在这个示例中,DogCat 类型都实现了 Speaker 接口,因此可以将它们赋值给接口变量 s。通过接口变量 s,可以调用实现的 Speak() 方法,实现了多态性。

接口的动态类型与类型断言

接口变量实际上包含了两个部分:一个是动态类型(存储具体的实现类型),另一个是动态值(存储实际的值)。可以使用类型断言获取接口变量的实际类型。

类型断言的语法

go
value, ok := interfaceValue.(ConcreteType)
  • interfaceValue 是接口变量。
  • ConcreteType 是具体类型。
  • value 是转换后的值,ok 表示转换是否成功。

示例:类型断言

go
package main

import "fmt"

func main() {
    var i interface{} = "hello"

    // 类型断言为 string 类型
    str, ok := i.(string)
    if ok {
        fmt.Println("String:", str) // 输出:String: hello
    } else {
        fmt.Println("不是字符串类型")
    }

    // 类型断言为 int 类型
    num, ok := i.(int)
    if ok {
        fmt.Println("Integer:", num)
    } else {
        fmt.Println("不是整型类型")
    }
}

在这个例子中,首先将接口 i 的值断言为 string 类型,断言成功。然后尝试将 i 断言为 int 类型,断言失败,返回 false

空接口(interface{}

空接口是没有任何方法的接口,所有类型都实现了空接口,因此空接口可以表示任何类型。空接口通常用于存储任意类型的值,类似于动态类型。

示例:使用空接口存储任意类型

go
package main

import "fmt"

func describe(i interface{}) {
    fmt.Printf("Type: %T, Value: %v\n", i, i)
}

func main() {
    describe(42)
    describe("hello")
    describe(true)
}

在这个例子中,函数 describe 接受一个 interface{} 类型的参数,可以传入任何类型的值。

接口组合

接口可以通过组合实现更复杂的接口。Go语言允许将多个接口嵌入到一个接口中,从而实现接口的组合。

go
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type ReadWriter interface {
    Reader
    Writer
}

在这里,ReadWriter 接口组合了 ReaderWriter 接口,任何实现了 ReadWrite 方法的类型都实现了 ReadWriter 接口。

接口的实际应用场景

1. 多态

接口可以实现不同类型的多态行为。例如,可以定义一个 Shape 接口表示形状,不同的形状(如圆形和矩形)实现各自的 Area 方法,程序可以通过 Shape 接口统一处理这些不同的形状。

go
package main

import (
    "fmt"
    "math"
)

// 定义 Shape 接口
type Shape interface {
    Area() float64
}

// 定义 Circle 结构体并实现 Shape 接口
type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

// 定义 Rectangle 结构体并实现 Shape 接口
type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 计算形状面积的函数
func printArea(s Shape) {
    fmt.Printf("Area: %.2f\n", s.Area())
}

func main() {
    c := Circle{Radius: 5}
    r := Rectangle{Width: 4, Height: 3}

    printArea(c) // 输出:Area: 78.54
    printArea(r) // 输出:Area: 12.00
}

2. 解耦

接口可以让代码更具扩展性和复用性。例如,通过定义 Database 接口,程序可以针对接口编程,而不依赖具体的数据库实现。

go
package main

import "fmt"

// Database 接口
type Database interface {
    Query(query string) string
}

// MySQL 实现 Database 接口
type MySQL struct{}

func (m MySQL) Query(query string) string {
    return "MySQL result for query: " + query
}

// PostgreSQL 实现 Database 接口
type PostgreSQL struct{}

func (p PostgreSQL) Query(query string) string {
    return "PostgreSQL result for query: " + query
}

// 执行查询的函数
func executeQuery(db Database, query string) {
    fmt.Println(db.Query(query))
}

func main() {
    mysql := MySQL{}
    postgres := PostgreSQL{}

    executeQuery(mysql, "SELECT * FROM users")
    executeQuery(postgres, "SELECT * FROM orders")
}

在这个示例中,MySQLPostgreSQL 都实现了 Database 接口,executeQuery 函数可以对任何实现了 Database 接口的类型进行查询操作。

总结

  • 接口的定义:接口定义了一组方法,任何实现这些方法的类型都可以作为该接口的实现。
  • 隐式实现:类型不需要显式声明实现某个接口,只要实现了接口中的方法即可。
  • 空接口:空接口 interface{} 可以表示任何类型,常用于存储任意类型的值。
  • 接口组合:接口可以嵌套其他接口,实现接口组合。
  • 应用场景:接口用于实现多态性、解耦、提高代码的扩展性和复用性。

接口在Go语言中极为重要,它们提供了灵活的抽象机制,使得程序更具通用性、扩展性和模块化

6. 链表(Linked List)

7.队列(Queue)

8. 栈(Stack)

9. 堆(Heap)

10. 字符串(String)

评论区

欢迎留言、补充或勘误。

xiaoba.blog