Golang基础学习: array和slice对比和使用

前言

在golang中,常见的序列型数据类型有array和slice这两种,但array因为其固定长度的限制,在实际使用中用得不多,slice则更为常用。下面简单介绍和对比一下这两种相似却又有很多差异的数据类型。

Array:

概念:

在golang中,数组由相同类型的元素组成的具有固定长度的一种序列型复合数据类型。

声明和使用:
package main

import "fmt"

/*
数组: array在go里是固定长度、每个元素类型必须相同的,因此使用较少,slice使用反而较多
*/

func main() {
	var a [3]int   // 3个整数的数组
	fmt.Println(a) // [0 0 0]
	fmt.Println(a[0])
	for i, v := range a {
		fmt.Printf("%d %d\n", i, v) // 打印索引和元素
	}

	for _, v2 := range a {
		fmt.Printf("%d\n", v2) // 打印元素
	}

	// 默认情况下,数组创建后元素都是零值,可以使用数组字面量来初始化一个数组的元素
	var q [3]int = [3]int{1, 2, 3}
	fmt.Println(q) // [1 2 3]

	var q2 [3]int = [3]int{1, 2}
	fmt.Println(q2) // [1 2 0]

	var q3 = [3]int{1, 2, 3}
	fmt.Println(q3) // 缩写用这个方式比较简洁

	/*
		数组长度是数组类型的一部分,所以q4和q5是不同的类型.
		数组的长度必须是常量表达式,因此数组长度在声明时必须确定
	*/
	q4 := [...]int{1, 2, 3, 4}
	q5 := [...]int{1, 2, 3, 4, 5}
	fmt.Printf("Type: %T\n", q4) // Type: [4]int
	fmt.Printf("Type: %T\n", q5) // Type: [5]int
	//q4 = q5  // 编译错误,赋值失败,不同类型不能赋值
}
总结

在golang中array的长度是固定的,声明时必须以数值形式明确指定或者以’…'形式间接指定元素长度,且不同的长度的array类型不同不能转换,因此使用场景有限。

Slice

概念
在golang中,数组由相同类型的元素组成的可变长度的一种序列型复合数据类型。在理解上来说,slice和array唯一的区别是长度可变
声明和使用
package main

import (
	"bytes"
	"fmt"
)

/*
	slice数据类型在声明时如果不指定len/cap,则slice的cap/len值默认以声明时的数组的长度为准。
	len: 长度。slice包含的元素的个数,可变
	cap: 容量。slice当前可接纳的元素的个数,根据了len值动态变化
*/

func main() {
	// 声明方式1
	var b []int                                                                 // 声明一个空slice,默认len和cap的值都为0。声明长度为0的array: var b [0]int,这两者声明方式非常相似
	fmt.Printf("Slice a , length: %d cap: %d content: %v\n", len(b), cap(b), b) // Slice a , length: 0 cap: 0 content: []

	// 声明方式2
	a := []int{1, 2, 3, 4, 5}
	fmt.Printf("Slice a , length: %d cap: %d content: %v\n", len(a), cap(a), a) // Slice a , length: 5 cap: 5 content: [1 2 3 4 5]
	a = append(a, 6)                                                            // 追加元素
	fmt.Printf("Slice a , length: %d cap: %d content: %v\n", len(a), cap(a), a) // Slice a , length: 6 cap: 10 content: [1 2 3 4 5 6]

	// 声明方式3
	c := make([]int, 5, 10)                                                     // make(type, len[, cap])
	fmt.Printf("Slice a , length: %d cap: %d content: %v\n", len(c), cap(c), c) // Slice a , length: 5 cap: 10 content: [0 0 0 0 0]

	// copy方法
	d := []int{1, 2}
	e := []int{3, 4, 5, 6}
	copy(e, d)        // copy(to,from) ,第一个参数是目标slice,第二个参数是源slice
	fmt.Println(d, e) // [1 2] [1 2 5 6]

	f := []int{3, 4, 5, 6}
	copy(d, f)        // copy(to,from) ,第一个参数是目标slice,第二个参数是源slice
	fmt.Println(d, f) // [3 4] [3 4 5 6]

	// slice的比较
	g := []string{"a", "b"}
	h := []string{"a", "b", "c"}
	fmt.Println(g != nil) // true
	//fmt.Println(g == h) // 这里会编译错误,slice没有==比较方法

	// 只能自定义方法来比较两个slice,例如下方自定义的equal方法:
	fmt.Println(equal(g, h))

	// 检查slice是否为空
	fmt.Println(len(g) != 0)
	fmt.Println(g != nil) // 不推荐用这个方法,因为即使g != nil,g也有可能是空值

	
	/*
		------------------------------------Slice增加/删除/反转元素/转换格式等常用操作------------------------------------

	*/

	// 尾部操作
	j := []int{1, 2, 3, 4, 5}
	fmt.Println(append(j, 6)) // [1 2 3 4 5 6] 尾部插入
	top := j[len(j)-1]
	fmt.Println(top) // 5 获取尾部(栈顶)元素
	j = j[:len(j)-1] // 弹出尾部元素
	fmt.Println(j)   // [1 2 3 4]

	// slice中间移除/插入操作,没有自带函数,需要自己写方法
	j = sliceRemove(j, 2)
	fmt.Println(j) // [1 2 4]
	j = sliceInsert(j, 2, 3)
	fmt.Println(j) // [1 2 3 4]

	// 反转元素
	j = sliceReverse(j)
	fmt.Println(j) // [4 3 2 1]

	// 转换为字符串格式
	fmt.Println(intsToString([]int{1, 2, 3})) // [1 & 2 & 3]
}

func sliceRemove(slice []int, i int) []int {
	// 移除slice的指定索引位置的元素
	copy(slice[i:], slice[i+1:])
	return slice[:len(slice)-1]
}

func sliceInsert(slice []int, i int, n int) []int {
	// 将元素插入到slice的指定索引位置
	sub := []int{n}
	for _, e := range slice[i:] {
		sub = append(sub, e)
	}
	copy(slice[i:], sub)
	slice = append(slice, sub[len(sub)-1])
	return slice
}

func sliceReverse(slice []int) []int {
	// 反转slice
	for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
		slice[i], slice[j] = slice[j], slice[i]
	}
	return slice
}

func equal(x, y []string) bool {
	if len(x) != len(y) {
		return false
	}
	flag := true
	for i, e := range x {
		if e != y[i] {
			flag = false
			break
		}
	}
	return flag
}

func intsToString(values []int) string {
	var buf bytes.Buffer
	buf.WriteByte('[')
	for i, v := range values {
		if i > 0 {
			buf.WriteString(" & ")
		}
		//fmt.Println(v)
		fmt.Fprintf(&buf, "%d", v) //输出重定向
	}
	buf.WriteByte(']')
	return buf.String()
}


Slice说明

1.声明一个空slice,默认len和cap的值都为0。声明长度为0的array的方式: var b [0]int,这两者声明方式非常相似
2.slice在新增元素的时候,如果len超过cap,会动态扩容cap,根据网上查到的扩容函数的源码:

func growslice(et *_type, old slice, cap int) slice {

    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        if old.len < 1024 {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = cap
            }
        }
    }
...
}

可概括扩容策略如下:
当cap小于len时启动扩容,若cap < 1024,则申请新的内存cap翻倍,若大于2014,则cap提升1/4。(这与python的list扩容策略很相似)

3.使用make方式声明可以指定cap以直接申请一块连续的内存空间,避免频繁地改变cap容量而带来额外的开销。

4.copy函数复制slice时,假设源slice长度为x,目标slice长度为y,则覆盖策略为:
如果 x<y,则用源slice的所有元素逐个覆盖目标slice的前x个元素;
如果 x>y,则用源slice前y个元素逐个覆盖目标slice的所有元素。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值