Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > go make函数和append函数

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

作者:QX_hao

本文给大家介绍Go中make函数和append函数的作用详解,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

Go 中的 make 函数和 append 函数

make 函数的作用

append 函数的作用

1. make 函数的基本概念

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

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

2. make 函数的语法格式

make(T, args...)

其中:

3. 用于不同数据类型的详细用法

3.1 使用 make 创建切片(slice)

语法:

make([]T, length, capacity)

参数说明:

示例:

package main
import "fmt"
func main() {
    // 创建长度为 3,容量为 5 的整型切片
    slice1 := make([]int, 3, 5)
    fmt.Printf("slice1: %v, len: %d, cap: %d\n", 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: %d\n", slice2, len(slice2), cap(slice2))
    // 输出: slice2: [  ], len: 3, cap: 3
    // 不指定容量,容量默认等于长度
    slice3 := make([]int, 3)
    fmt.Printf("slice3: %v, len: %d, cap: %d\n", slice3, len(slice3), cap(slice3))
    // 输出: slice3: [0 0 0], len: 3, cap: 3
}

注意事项:

3.2 使用 make 创建映射(map)

语法:

make(map[K]V, initialCapacity)

参数说明:

示例:

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

注意事项:

3.3 使用 make 创建通道(channel)

语法:

make(chan 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: %d\n", len(ch2), cap(ch2))
    // 输出: ch2 len: 3, cap: 3
    // 从无缓冲通道接收数据
    value := <-ch1
    fmt.Printf("Received from ch1: %d\n", value)
    // 输出: Received from ch1: 42
    // 从有缓冲通道接收数据
    fmt.Printf("Received from ch2: %s\n", <-ch2)
    fmt.Printf("Received from ch2: %s\n", <-ch2)
    fmt.Printf("Received from ch2: %s\n", <-ch2)
}

注意事项:

4. make 与 new 的区别

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

示例对比:

package main
import "fmt"
func main() {
    // 使用 make 创建切片
    slice1 := make([]int, 3)
    fmt.Printf("make slice: %v, type: %T\n", slice1, slice1)
    // 输出: make slice: [0 0 0], type: []int
    // 使用 new 创建切片
    slice2 := new([]int)
    fmt.Printf("new slice: %v, type: %T\n", slice2, slice2)
    // 输出: new slice: &[], type: *[]int
    // 使用 make 创建映射
    map1 := make(map[string]int)
    map1["key"] = 1
    fmt.Printf("make map: %v, type: %T\n", 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: %T\n", map2, map2)
    // 输出: new map: &map[], type: *map[string]int
}

6. append 函数

6.1 append 函数的基本概念

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

6.2 append 函数的语法格式

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

参数说明:

6.3 append 函数的使用方法

6.3.1 添加单个元素
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, 容量: %d\n", slice, len(slice), cap(slice))
    // 输出: 切片内容: [1 2 3], 长度: 3, 容量: 5
}
6.3.2 添加多个元素
package main
import "fmt"
func main() {
    slice := []int{1, 2, 3}
    // 添加多个元素
    slice = append(slice, 4, 5, 6)
    fmt.Printf("切片内容: %v, 长度: %d, 容量: %d\n", slice, len(slice), cap(slice))
    // 输出: 切片内容: [1 2 3 4 5 6], 长度: 6, 容量: 6
}
6.3.3 合并两个切片
package main
import "fmt"
func main() {
    slice1 := []int{1, 2, 3}
    slice2 := []int{4, 5, 6}
    // 合并两个切片(注意使用 ... 展开操作符)
    slice1 = append(slice1, slice2...)
    fmt.Printf("合并后的切片: %v\n", slice1)
    // 输出: 合并后的切片: [1 2 3 4 5 6]
}
6.3.4 在指定位置插入元素
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("插入后的切片: %v\n", 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: %v\n", slice2)
    // 输出: 插入后的切片2: [1 2 3 4 5]
}

6.4 append 函数的扩容机制

package main
import "fmt"
func main() {
    // 演示 append 的扩容过程
    slice := make([]int, 0, 2)
    fmt.Printf("初始状态 - 长度: %d, 容量: %d\n", len(slice), cap(slice))
    for i := 1; i <= 10; i++ {
        slice = append(slice, i)
        fmt.Printf("添加 %d - 长度: %d, 容量: %d\n", 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
    */
}

6.5 append 函数的注意事项

6.5.1 返回值必须接收
package main
import "fmt"
func main() {
    slice := []int{1, 2, 3}
    // 错误:append 的返回值必须接收
    // append(slice, 4) // 这样不会修改原切片
    // 正确:接收返回值
    slice = append(slice, 4)
    fmt.Printf("正确使用: %v\n", slice)
    // 输出: 正确使用: [1 2 3 4]
}
6.5.2 切片共享问题
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: %v\n", slice1, slice2, arr)
    // 修改 slice2
    slice2[0] = 200
    fmt.Printf("修改 slice2 后 - slice1: %v, slice2: %v, arr: %v\n", slice1, slice2, arr)
    // 对 slice1 进行 append(可能触发扩容)
    slice1 = append(slice1, 6)
    slice1[0] = 300
    fmt.Printf("append slice1 后 - slice1: %v, slice2: %v, arr: %v\n", slice1, slice2, arr)
}
6.5.3 性能优化建议
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, 容量: %d\n", 
        len(efficientSlice), cap(efficientSlice))
    fmt.Printf("批量添加 - 长度: %d, 容量: %d\n", 
        len(batchSlice), cap(batchSlice))
}

7. make 和 append 的配合使用

7.1 最佳实践示例

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, 容量: %d\n", len(dataSlice), cap(dataSlice))
    // 4. 如果需要释放内存,可以重新分配
    if cap(dataSlice) > len(dataSlice)*2 {
        // 创建新的切片,只保留需要的容量
        optimizedSlice := make([]int, len(dataSlice))
        copy(optimizedSlice, dataSlice)
        dataSlice = optimizedSlice
    }
    fmt.Printf("优化后 - 长度: %d, 容量: %d\n", len(dataSlice), cap(dataSlice))
}

7.2 实际应用场景

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("输入: %v\n", input)
    fmt.Printf("输出: %v\n", output)
    // 输出: 输入: [hello world test go programming]
    // 输出: 输出: [HELLO WORLD GO PROGRAMMING]
}

8. 常见使用场景和最佳实践

8.1 切片的最佳实践

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: %d\n", 
        len(efficientSlice), cap(efficientSlice))
    fmt.Printf("Inefficient slice len: %d, cap: %d\n", 
        len(inefficientSlice), cap(inefficientSlice))
}

8.2 映射的最佳实践

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: %d\n", len(efficientMap))
    fmt.Printf("Inefficient map size: %d\n", len(inefficientMap))
}

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

您可能感兴趣的文章:
阅读全文