GO语言中的数组与切片

当涉及到 Go 语言中的数据结构时,数组和切片无疑是两个不可或缺的角色。它们为我们提供了处理数据的强大工具,分别适用于不同的场景和需求。下文将深入探讨Go语言中数组和切片的特性、用法以及它们之间的区别。从固定长度的数组到动态可调整长度的切片,我们将揭开它们的面纱,帮助你更好地理解如何在Go中有效地利用这两种数据结构。

数组

定义

数组是一个长度固定、相同类型的元素组成的连续序列;数组类型不仅是逻辑上的连续序列,而且在实际内存分配时也占据着一整块内存。

声明语法

  • 声明数组

    go 复制代码
    var name [size]type
    • name 为数组的名字。
    • size 为数组元素的个数。
    • type 为元素的类型。
  • 初始化数组的三种方式

    go 复制代码
    var arr [3]string
    var numbers = [5]int{1,2,3,4,5}
    var skill = [...]string{"JS","TS","Go"}
    • 数组中的元素个数不能超过中括号中的数字。

    • 不显示指明数组元素的个数则可以使用 ... 来代替数组长度,Go 编译器会根据大括号中的元素个数自动推导数组的长度。

      go 复制代码
      var arr = [...]int{1,2,3,4,5} // Go 编译器自动计算长度, len = 5
    • 声明一个数组类型变量的同时,也可以显式地对它进行初始化。如果不进行显式初始化,那么数组中的元素值就是它类型的零值。

      go 复制代码
      var arr1 [6]int // [0 0 0 0 0 0]  // 不进行显示初始化
      var arr2 = [6]int { 11, 12, 13, 14, 15, 16,} // 显示初始化
    • 如果两个数组类型的元素类型数组长度都是一样时,那么这两个数组类型是等价的,如果有一个属性不同,它们就是两个不同的数组类型。

    • 数组间的比较,长度和类型也是一个关键的条件。

    • 数组是值传递,有较大的内存开销(指针可以解决内存开销的问题)。

  • 访问数组元素

    数组长度可以通过内置的 len 函数获取,数组的下标值是从 0 开始的。如果下标值超出数组长度范畴或者是负数,那么 Go 编译器会报错,防止访问溢出。

    go 复制代码
    var arr = [6]int{1, 2, 3, 4, 5, 6}
    fmt.Println(arr[0], arr[5]) // 11 16
    fmt.Println(arr[-1])        // 报错:下标值不能为负数
    fmt.Println(arr[7])         // 报错:超出了 arr 的长度范围

切片

切片是对数组的一个连续"片段"的引用,切片是引用类型 ;切片的出现,解决了数组的不足:固定的元素个数不可变和传值机制下导致的开销较大的问题 ;切片中可以通过内置函数 append,动态地向切片中添加元素。

一个切片在运行时,由指针、长度和容量3部分构成;切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。

go 复制代码
type slice struct {
    array unsafe.Pointer
    len int
    cap int
}

array:是指向底层数组的指针。

len:是切片的长度,即切片中当前元素的个数。使用 len()获取长度。

cap:是底层数组的长度,也是切片的最大容量,cap 值永远大于等于 len 值。使用 cap() 获取容量。

声明语法

go 复制代码
var name []type
  • name 表示切片的变量名;
  • type 表示切片对应元素的类型;
go 复制代码
package main
​
import "fmt"
​
func main() {
    var sliceStr []string                                          // 声明一个字符串切片
    var sliceNum []int                                             // 声明整型切片
    var emptySliceNum = []int{}                                    // 声明一个int类型的切片,默认值为int类型的零值 0
    fmt.Printf("slice string length: %d\n", len(sliceStr))         // slice string length: 0
    fmt.Printf("slice number length: %d\n", len(sliceNum))         // slice number length: 0
    fmt.Printf("empty slice num length: %d\n", len(emptySliceNum)) // empty slice num length: 0
​
}

切片的创建方法

  • 通过 make 函数来创建切片,并指定底层数组的长度。

    make 函数生成切片会发生内存分配操作;如果有显示声明开始和结束位置的切片,则只是将新的切片结构指向已经分配好的内存区域。

    go 复制代码
    slice := make([]byte, 6, 10) // 6 -> 切片的初始长度, 10 -> cap 值,也就是底层数组的长度

    如果没有在 make 中指定 cap 参数,那么底层数组长度 cap 就等于 len

    go 复制代码
    sl := make([]byte, 6) // cap = len = 6

    slice 容量小于 1024 时,当在切片中增加数据且容量不足时,会在当前容量的基础上进行翻倍扩容;当大于等于 1024 时,会在当前容量的基础上增加当前容量的四分之一。

  • 基于数组创建切片

    Go语言的切片原则:左含右不含,传入的数字是元素的索引

    go 复制代码
    package main
    
    import "fmt"
    
    func main() {
    	arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    	sl := arr[3:7] // 3 为起始下标,7 为结束下标
    	fmt.Printf("slice: %d", sl) // slice: [4 5 6 7]
    
    }
    • 取出的元素数量为"结束索引减去开始位置"。
    • 取出的元素不包含结束索引的值;切片最后一个元素可以使用 slice[len(slice)] 获取。
    • 如果缺省开始位置,则表示从下标0到结束下标。
    • 如果缺省结束位置,则表示从开始索引到整个切片的结尾。
    • 如果两者同时缺省,则新生成的切片与原切片等效。
    • 如果两者同时为0,则等效于一个空切片,一般用于切片复位。

    根据索引位置去切片 slice 元素时,取值范围是 0 ~ len(slice)-1;如果越界,则会报错;在生成切片时,结束位置可以填写len(slice),不会报错。

数组或切片中的常用方法

  • 迭代

    go 复制代码
    package main
    
    import "fmt"
    
    func main() {
    	var arr = []int{1, 2, 3}
        fmt.Printf("arr 的长度为: %d\n", len(arr)) // arr 的长度为: 3
        
    	// 如果不需要使用某个值,则可以使用下划线(_)来忽略
    	for k, v := range arr {
    		fmt.Printf("第%d个值为: %d\n", k, v)
    	}
    }
    // 第0个值为: 1
    // 第1个值为: 2
    // 第2个值为: 3
  • 删除元素

    go 复制代码
    package main
    
    import "fmt"
    
    func main() {
    	var skill = []string{"Go", "JavaScript", "Node.js", "TypeScript", "React", "React Native", "Vue"}
    
    	// 指定索引(下标)删除
    	index := 2
    
    	// [:index] -> 从切片索引 0 开始,在 index 结束,不包含 index 的元素;[index+1:] -> 从index + 1 开始至切片的最后一个元素
    	skill = append(skill[:index], skill[index+1:]...)
    	fmt.Printf("类型为: %T, 长度为:%d,结果为:%s", skill, len(skill), skill) // 类型为: []string, 长度为:6,结果为:[Go JavaScript TypeScript React React Native Vue]
    }
  • 将数组转换为字符串

    go 复制代码
    package main
    
    import (
    	"fmt"
    	"strings"
    )
    
    func main() {
    	var skill = []string{"Go", "JavaScript", "Node.js", "TypeScript", "React", "React Native", "Vue"}
    	fmt.Printf("result: %s\n", strings.Join(skill, ",")) // result: Go,JavaScript,Node.js,TypeScript,React,React Native,Vue
    	fmt.Printf("result: %s", sliceToString(skill)) // result: Go,JavaScript,Node.js,TypeScript,React,React Native,Vue
    
    }
    
    func sliceToString(arr []string) string {
    	var result string
    	for k, v := range arr {
    		value := v
    		if k < len(arr)-1 {
    			value += ","
    		}
    		result += value
    	}
    	return result
    }
  • 检查某个值是否在切片中

    go 复制代码
    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var skill = []string{"Go", "JavaScript", "Node.js", "TypeScript", "React", "React Native", "Vue"}
    	fmt.Printf("是否存在 Next.js:%t", exist("Next.js", skill)) // 是否存在 Next.js:false
    
    }
    
    func exist(target string, array []string) bool {
    	if len(array) <= 0 {
    		return false
    	}
    	for _, element := range array {
    		if element == target {
    			return true
    		}
    	}
    	return false
    }
  • 查找一个元素在数组中的位置

    通过 reflect 包的 ValueOf 函数获取数组的值,然后使用 for 循环遍历数组对值进行比较,如果相等,则返回值的索引。

    go 复制代码
    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main() {
    	var skill = []string{"Go", "JavaScript", "Node.js", "TypeScript", "React", "React Native", "Vue"}
    	fmt.Printf("查找 React 的索引:%d", findPosition(skill, "react")) // 查找 react 的索引:-1
    	fmt.Printf("查找 React 的索引:%d", findPosition(skill, "React")) // 查找 React 的索引:4
    
    }
    
    func findPosition(arr any, target any) int {
    	array := reflect.ValueOf(arr)
    
    	for i := 0; i < array.Len(); i++ {
    		v := array.Index(i)
    		if v.Interface() == target {
    			return i
    		}
    	}
    
    	return -1
    }
  • 查找最大值和最小值的元素

    冒泡排序从小到大排序后,第一个是最小值,最后一个是最大值。

    go 复制代码
    package main
    
    import "fmt"
    
    func main() {
    	numbers := []int8{-1, 40, 50, -23, 100, 88, -32, 56, 2, 6, 39}
    	bubbleSort(numbers)
    	fmt.Println("排序后:", numbers)                                          // 排序后: [-32 -23 -1 2 6 39 40 50 56 88 100]
    	fmt.Printf("最大值: %d, 最小值: %d\n", numbers[len(numbers)-1], numbers[0]) // 最大值: 100, 最小值: -32
    }
    
    func bubbleSort(numbers []int8) {
    	n := len(numbers)
    	for i := 0; i < n-1; i++ {
    		for j := 0; j < n-i-1; j++ {
    			if numbers[j] > numbers[j+1] {
    				numbers[j], numbers[j+1] = numbers[j+1], numbers[j]
    			}
    		}
    	}
    }
  • 删除重复的元素

    go 复制代码
    package main
    
    import "fmt"
    
    func main() {
    	numbers := []int{-1, 6, 50, 2, 100, 9, -32, 56, 2, 6, 39}
    	numbers = removeDuplicates(numbers)
    	fmt.Printf("去重后: %d\n", numbers) // 去重后: [-1 6 50 2 100 9 -32 56 39]
    }
    
    func removeDuplicates(numbers []int) []int {
    	result := []int{}
    
    	for _, num := range numbers {
    		if !contains(result, num) {
    			result = append(result, num)
    		}
    	}
    
    	return result
    }
    
    func contains(numbers []int, target int) bool {
    	for _, num := range numbers {
    		if num == target {
    			return true
    		}
    	}
    	return false
    }
  • 排序

    go 复制代码
    package main
    
    import (
    	"fmt"
    	"sort"
    )
    
    func main() {
    	numbers := []int{-1, 6, 50, 2, 100, 9, -32, 56, 2, 6, 39}
    	sort.Sort(sort.IntSlice(numbers))    // 升序
    	fmt.Printf("numbers: %d\n", numbers) // numbers: [-32 -1 2 2 6 6 9 39 50 56 100]
    
    	sort.Sort(sort.Reverse(sort.IntSlice(numbers))) // 降序
    	fmt.Printf("numbers: %d\n", numbers)            // numbers: [100 56 50 39 9 6 6 2 2 -1 -32]
    }

总结

Go语言中的数组和切片是两种不同的数据类型,各自具有独特的特性和应用场景。数组是固定长度的数据结构,要求所有元素为相同类型 ,同时具备值语义。这意味着在传递和赋值时,会发生元素的复制。由于数组长度在创建时就已经确定,因此在编译时可以获得性能优势。数组通常用于需要高效访问固定大小数据的场景

相较之下,切片提供了动态长度的抽象,基于数组实现。切片是引用类型 ,具有引用语义,这意味着多个切片可以共享同一底层数组 。通过使用make函数,我们能够动态调整切片的长度和容量。append函数允许在切片尾部追加元素,系统会在需要时重新分配底层数组。切片表达式允许我们根据现有切片创建新的切片。

在实际编程中,数组适用于固定大小且对性能要求较高的情境,而切片则为动态数据集提供了更灵活的管理方式,允许动态伸缩。合理地使用数组与切片将有助于提高代码的效率和可维护性。

相关推荐
颜淡慕潇12 分钟前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes
向前看-7 小时前
验证码机制
前端·后端
超爱吃士力架9 小时前
邀请逻辑
java·linux·后端
AskHarries11 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion12 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp12 小时前
Spring-AOP
java·后端·spring·spring-aop
TodoCoder13 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
凌虚14 小时前
Kubernetes APF(API 优先级和公平调度)简介
后端·程序员·kubernetes
机器之心14 小时前
图学习新突破:一个统一框架连接空域和频域
人工智能·后端
.生产的驴15 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven