Go中make函数和append函数的作用详解

Written by

in

文章目录
  • 目录
    • Go 中的 make 函数和 append 函数
      • make 函数的作用
      • append 函数的作用
      • 1. make 函数的基本概念
      • 2. make 函数的语法格式
      • 3. 用于不同数据类型的详细用法
        • 3.1 使用 make 创建切片(slice)
        • 3.2 使用 make 创建映射(map)
        • 3.3 使用 make 创建通道(channel)
      • 4. make 与 new 的区别
        • 6. append 函数
          • 6.1 append 函数的基本概念
          • 6.2 append 函数的语法格式
          • 6.3 append 函数的使用方法
          • 6.4 append 函数的扩容机制
          • 6.5 append 函数的注意事项
        • 7. make 和 append 的配合使用
          • 7.1 最佳实践示例
          • 7.2 实际应用场景
        • 8. 常见使用场景和最佳实践
          • 8.1 切片的最佳实践
          • 8.2 映射的最佳实践

      • make 函数专门用于创建和初始化 slice、map 和 channel 这三种内建引用类型
      • 对于 slice,可以指定长度和容量;对于 map,可以指定初始容量;对于 channel,可以指定缓冲区大小
      • make 返回的是已初始化的类型引用,可以直接使用
      • 合理使用预分配容量可以显著提高程序性能,特别是在处理大量数据时
      • new 函数不同,make 会执行完整的初始化过程,而不仅仅是内存分配

      • append 函数用于向切片(slice)添加元素
      • 可以添加单个元素或多个元素
      • 可以合并两个切片
      • 自动处理切片的扩容机制
      • 是操作切片最常用的函数之一

      make 是 Go 语言中的一个内置函数,主要用于创建并初始化以下三种内建的引用类型:

      • slice(切片)
      • map(映射)
      • channel(通道)

      new 函数不同,make 不仅分配内存,还会进行初始化操作,返回的是类型的引用(而不是指针)。

      make(T, args...)

      其中:

      • T:要创建的类型(slice、map 或 channel)
      • args...:根据类型不同而变化的参数

      语法:

      make([]T, length, capacity)

      参数说明:

      • T:切片元素的类型
      • length:切片的长度(当前包含的元素个数)
      • capacity:切片的容量(可选参数,默认等于 length)

      示例:

      package main
      import "fmt"
      func main() {
          // 创建长度为 3,容量为 5 的整型切片
          slice1 := make([]int, 3, 5)
          fmt.Printf("slice1: %v, len: %d, cap: %dn", slice1, len(slice1), cap(slice1))
          // 输出: slice1: [0 0 0], len: 3, cap: 5
          // 创建长度和容量都为 3 的字符串切片
          slice2 := make([]string, 3)
          fmt.Printf("slice2: %v, len: %d, cap: %dn", slice2, len(slice2), cap(slice2))
          // 输出: slice2: [  ], len: 3, cap: 3
          // 不指定容量,容量默认等于长度
          slice3 := make([]int, 3)
          fmt.Printf("slice3: %v, len: %d, cap: %dn", slice3, len(slice3), cap(slice3))
          // 输出: slice3: [0 0 0], len: 3, cap: 3
      }

      注意事项:

      • 使用 make 创建的切片会被自动初始化为元素类型的零值
      • 容量必须大于等于长度

      语法:

      make(map[K]V, initialCapacity)

      参数说明:

      • K:键的类型
      • V:值的类型
      • initialCapacity:初始容量(可选参数)

      示例:

      package main
      import "fmt"
      func main() {
          // 创建字符串到整型的映射,不指定初始容量
          map1 := make(map[string]int)
          map1["apple"] = 5
          map1["banana"] = 3
          fmt.Printf("map1: %vn", map1)
          // 输出: map1: map[apple:5 banana:3]
          // 创建字符串到字符串的映射,指定初始容量为 10
          map2 := make(map[string]string, 10)
          map2["name"] = "Alice"
          map2["city"] = "Beijing"
          fmt.Printf("map2: %vn", map2)
          // 输出: map2: map[city:Beijing name:Alice]
          fmt.Printf("map1 len: %dn", len(map1))
          fmt.Printf("map2 len: %dn", len(map2))
      }

      注意事项:

      • 初始容量只是提示性的,映射会根据需要自动扩容
      • 使用 make 创建的映射是空的,可以立即进行键值对操作

      语法:

      make(chan T, bufferSize)
      

      参数说明:

      • T:通道传输的数据类型
      • bufferSize:缓冲区大小(可选参数)

      示例:

      package main
      import (
          "fmt"
          "time"
      )
      func main() {
          // 创建无缓冲的整型通道
          ch1 := make(chan int)
          // 创建缓冲大小为 3 的字符串通道
          ch2 := make(chan string, 3)
          // 使用无缓冲通道的示例
          go func() {
              time.Sleep(1 * time.Second)
              ch1 <- 42  // 发送数据
          }()
          // 使用有缓冲通道的示例
          ch2 <- "hello"
          ch2 <- "world"
          ch2 <- "golang"
          fmt.Printf("ch2 len: %d, cap: %dn", len(ch2), cap(ch2))
          // 输出: ch2 len: 3, cap: 3
          // 从无缓冲通道接收数据
          value := <-ch1
          fmt.Printf("Received from ch1: %dn", value)
          // 输出: Received from ch1: 42
          // 从有缓冲通道接收数据
          fmt.Printf("Received from ch2: %sn", <-ch2)
          fmt.Printf("Received from ch2: %sn", <-ch2)
          fmt.Printf("Received from ch2: %sn", <-ch2)
      }

      注意事项:

      • 无缓冲通道(bufferSize=0 或省略)是同步的,发送和接收操作会阻塞直到另一端准备好
      • 有缓冲通道是异步的,只有在缓冲区满时发送才会阻塞,缓冲区空时接收才会阻塞

      特性 make new
      适用类型 slice、map、channel 任意类型
      返回值 类型的引用(T) 类型的指针(*T)
      初始化 会进行初始化 只分配零值内存
      零值 返回已初始化的值 返回指向零值的指针

      示例对比:

      package main
      import "fmt"
      func main() {
          // 使用 make 创建切片
          slice1 := make([]int, 3)
          fmt.Printf("make slice: %v, type: %Tn", slice1, slice1)
          // 输出: make slice: [0 0 0], type: []int
          // 使用 new 创建切片
          slice2 := new([]int)
          fmt.Printf("new slice: %v, type: %Tn", slice2, slice2)
          // 输出: new slice: &[], type: *[]int
          // 使用 make 创建映射
          map1 := make(map[string]int)
          map1["key"] = 1
          fmt.Printf("make map: %v, type: %Tn", map1, map1)
          // 输出: make map: map[key:1], type: map[string]int
          // 使用 new 创建映射
          map2 := new(map[string]int)
          // (*map2)["key"] = 1  // 这会导致 panic,因为映射未初始化
          fmt.Printf("new map: %v, type: %Tn", map2, map2)
          // 输出: new map: &map[], type: *map[string]int
      }

      append 是 Go 语言中的一个内置函数,专门用于向切片(slice)添加元素。它是操作切片最常用的函数之一。

      append(slice []T, elements ...T) []T
      

      参数说明:

      • slice:目标切片
      • elements:要添加的元素(可以是单个元素或多个元素)
      • 返回值:新的切片(可能指向新的底层数组)

      package main
      import "fmt"
      func main() {
          // 创建空切片
          slice := make([]int, 0, 5)
          // 添加单个元素
          slice = append(slice, 1)
          slice = append(slice, 2)
          slice = append(slice, 3)
          fmt.Printf("切片内容: %v, 长度: %d, 容量: %dn", slice, len(slice), cap(slice))
          // 输出: 切片内容: [1 2 3], 长度: 3, 容量: 5
      }
      package main
      import "fmt"
      func main() {
          slice := []int{1, 2, 3}
          // 添加多个元素
          slice = append(slice, 4, 5, 6)
          fmt.Printf("切片内容: %v, 长度: %d, 容量: %dn", slice, len(slice), cap(slice))
          // 输出: 切片内容: [1 2 3 4 5 6], 长度: 6, 容量: 6
      }
      package main
      import "fmt"
      func main() {
          slice1 := []int{1, 2, 3}
          slice2 := []int{4, 5, 6}
          // 合并两个切片(注意使用 ... 展开操作符)
          slice1 = append(slice1, slice2...)
          fmt.Printf("合并后的切片: %vn", slice1)
          // 输出: 合并后的切片: [1 2 3 4 5 6]
      }
      package main
      import "fmt"
      func main() {
          slice := []int{1, 2, 4, 5}
          // 在索引2的位置插入元素3
          index := 2
          element := 3
          // 方法:使用 append 和切片的组合
          slice = append(slice[:index], append([]int{element}, slice[index:]...)...)
          fmt.Printf("插入后的切片: %vn", slice)
          // 输出: 插入后的切片: [1 2 3 4 5]
          // 更简洁的插入方法
          slice2 := []int{1, 2, 4, 5}
          slice2 = append(slice2, 0) // 先扩展切片
          copy(slice2[index+1:], slice2[index:]) // 移动元素
          slice2[index] = element // 插入新元素
          fmt.Printf("插入后的切片2: %vn", slice2)
          // 输出: 插入后的切片2: [1 2 3 4 5]
      }

      package main
      import "fmt"
      func main() {
          // 演示 append 的扩容过程
          slice := make([]int, 0, 2)
          fmt.Printf("初始状态 - 长度: %d, 容量: %dn", len(slice), cap(slice))
          for i := 1; i <= 10; i++ {
              slice = append(slice, i)
              fmt.Printf("添加 %d - 长度: %d, 容量: %dn", i, len(slice), cap(slice))
          }
          /* 输出示例:
          初始状态 - 长度: 0, 容量: 2
          添加 1 - 长度: 1, 容量: 2
          添加 2 - 长度: 2, 容量: 2
          添加 3 - 长度: 3, 容量: 4  (第一次扩容)
          添加 4 - 长度: 4, 容量: 4
          添加 5 - 长度: 5, 容量: 8  (第二次扩容)
          添加 6 - 长度: 6, 容量: 8
          添加 7 - 长度: 7, 容量: 8
          添加 8 - 长度: 8, 容量: 8
          添加 9 - 长度: 9, 容量: 16 (第三次扩容)
          添加 10 - 长度: 10, 容量: 16
          */
      }

      package main
      import "fmt"
      func main() {
          slice := []int{1, 2, 3}
          // 错误:append 的返回值必须接收
          // append(slice, 4) // 这样不会修改原切片
          // 正确:接收返回值
          slice = append(slice, 4)
          fmt.Printf("正确使用: %vn", slice)
          // 输出: 正确使用: [1 2 3 4]
      }
      package main
      import "fmt"
      func main() {
          // 创建底层数组
          arr := [5]int{1, 2, 3, 4, 5}
          // 创建两个共享底层数组的切片
          slice1 := arr[1:4] // [2, 3, 4]
          slice2 := slice1[0:2] // [2, 3]
          fmt.Printf("修改前 - slice1: %v, slice2: %v, arr: %vn", slice1, slice2, arr)
          // 修改 slice2
          slice2[0] = 200
          fmt.Printf("修改 slice2 后 - slice1: %v, slice2: %v, arr: %vn", slice1, slice2, arr)
          // 对 slice1 进行 append(可能触发扩容)
          slice1 = append(slice1, 6)
          slice1[0] = 300
          fmt.Printf("append slice1 后 - slice1: %v, slice2: %v, arr: %vn", slice1, slice2, arr)
      }
      package main
      import "fmt"
      func main() {
          // 性能优化:预分配容量
          // 方法1:使用 make 预分配容量
          efficientSlice := make([]int, 0, 1000)
          for i := 0; i < 1000; i++ {
              efficientSlice = append(efficientSlice, i)
          }
          // 方法2:一次性添加多个元素
          var batchSlice []int
          for i := 0; i < 1000; i += 100 {
              batch := make([]int, 100)
              for j := 0; j < 100; j++ {
                  batch[j] = i + j
              }
              batchSlice = append(batchSlice, batch...)
          }
          fmt.Printf("预分配容量 - 长度: %d, 容量: %dn", 
              len(efficientSlice), cap(efficientSlice))
          fmt.Printf("批量添加 - 长度: %d, 容量: %dn", 
              len(batchSlice), cap(batchSlice))
      }

      package main
      import "fmt"
      func main() {
          // 场景:处理大量数据时,先预分配容量再使用 append
          // 1. 使用 make 预分配容量
          expectedSize := 10000
          dataSlice := make([]int, 0, expectedSize)
          // 2. 使用 append 添加数据
          for i := 0; i < expectedSize; i++ {
              dataSlice = append(dataSlice, i*2)
          }
          // 3. 处理完成后可以截断不需要的部分
          if len(dataSlice) > 5000 {
              dataSlice = dataSlice[:5000]
          }
          fmt.Printf("最终结果 - 长度: %d, 容量: %dn", len(dataSlice), cap(dataSlice))
          // 4. 如果需要释放内存,可以重新分配
          if cap(dataSlice) > len(dataSlice)*2 {
              // 创建新的切片,只保留需要的容量
              optimizedSlice := make([]int, len(dataSlice))
              copy(optimizedSlice, dataSlice)
              dataSlice = optimizedSlice
          }
          fmt.Printf("优化后 - 长度: %d, 容量: %dn", len(dataSlice), cap(dataSlice))
      }

      package main
      import (
          "fmt"
          "strings"
      )
      func processStrings(words []string) []string {
          // 预分配容量
          result := make([]string, 0, len(words))
          for _, word := range words {
              // 过滤和处理字符串
              if len(word) > 0 && !strings.Contains(word, "test") {
                  processed := strings.ToUpper(word)
                  result = append(result, processed)
              }
          }
          return result
      }
      func main() {
          input := []string{"hello", "world", "test", "go", "programming"}
          output := processStrings(input)
          fmt.Printf("输入: %vn", input)
          fmt.Printf("输出: %vn", output)
          // 输出: 输入: [hello world test go programming]
          // 输出: 输出: [HELLO WORLD GO PROGRAMMING]
      }

      package main
      import "fmt"
      func main() {
          // 当你知道大概需要多少元素时,预分配容量可以提高性能
          expectedSize := 1000
          // 好的做法:预分配容量
          efficientSlice := make([]int, 0, expectedSize)
          for i := 0; i < expectedSize; i++ {
              efficientSlice = append(efficientSlice, i)
          }
          // 不好的做法:让切片频繁扩容
          var inefficientSlice []int
          for i := 0; i < expectedSize; i++ {
              inefficientSlice = append(inefficientSlice, i)
          }
          fmt.Printf("Efficient slice len: %d, cap: %dn", 
              len(efficientSlice), cap(efficientSlice))
          fmt.Printf("Inefficient slice len: %d, cap: %dn", 
              len(inefficientSlice), cap(inefficientSlice))
      }

      package main
      import "fmt"
      func main() {
          // 当你知道大概需要存储多少键值对时,预分配容量可以减少重新哈希的次数
          expectedItems := 1000
          // 好的做法:预分配容量
          efficientMap := make(map[int]string, expectedItems)
          for i := 0; i < expectedItems; i++ {
              efficientMap[i] = fmt.Sprintf("value%d", i)
          }
          // 不好的做法:让映射频繁扩容
          inefficientMap := make(map[int]string)
          for i := 0; i < expectedItems; i++ {
              inefficientMap[i] = fmt.Sprintf("value%d", i)
          }
          fmt.Printf("Efficient map size: %dn", len(efficientMap))
          fmt.Printf("Inefficient map size: %dn", len(inefficientMap))
      }

      到此这篇关于Go中make函数和append函数的作用详解的文章就介绍到这了,更多相关go make函数和append函数内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

      您可能感兴趣的文章:

      • Go语言之重要数组类型切片(slice)make,append函数解读
      • Go slice切片make生成append追加copy复制示例
      • Go语言中append函数用法分析
      • Golang中的new()和make()函数本质区别
      • Go语言中内建函数make的使用
      • Go中的new()和make()函数区别及底层原理详解
      • 深入讲解Go语言中函数new与make的使用和区别

      站内搜索