golang——array和slice

1.array和slice差别:

  1. 长度可变
  2. 函数传参
  3. 计算数据长度的方式

array

  1. 长度不可变,初始化必须指定长度
  2. 值类型的,将一个数组赋值给另一个数组时,传递的是一份深拷贝,会占用额外的内存,函数内对数组元素值的修改,不会修改原数组内容
  3. 数组需要遍历计算长度,时间复杂度是O(n)

slice

  1. 长度不固定,通过append追加元素,cap不够容纳元素时会进行扩容
  2. 引用类型的,将一个切片赋值给另一个切片,传递的是浅拷贝,函数传参操作不会拷贝整个切片,只会赋值len和cap,底层共用是同一个数组,不会占用额外的内存,函数内对数组元素值的修改,会修改数组内容
  3. 通过len直接获取,时间复杂度是O(n)

2.slice底层实现

占用24个字节

type slice struct{
    array unsafe.Pointer//一个指向数组的指针   //占用8个字节
    len int									//占用8个字节
    cap int									//占用8个字节 
}

初始化方式:

1.直接声明
var slice1 []int

2.使用字面量
slice2 := []int{1,2,3,4}

3.使用make
slice3 := make([]int,3,5)

4.从切片/数组截取
slice4 := arr[1:3]
追加
slice1 := append(slice1,1)
获取长度
len(slice1)
获取容量
cap(slice1)

扩容机制

扩容时机:发生在slice append时,当slice的cap不足以容纳新元素,就会进行扩容

append() 底层逻辑

  1. 计算追加后slice的总长度n
  2. 如果总长度n大于原cap,则调用growslice计算容量大小,并申请内存(cap最小为n,具体扩容规则见growslice)
  3. 对扩容后的slice进行切片,长度为n,获取slice s,用以存储所有的数据
  4. 根据不同的数据类型,调用对应的复制方法,将原slice及追加的slice的数据复制到新的slice

growslice 计算容量大小的逻辑

  1. 如果新申请容量比2倍原有容量大,扩容大小为新申请容量
  2. 如果原slice长度小于1024,则扩容为原来2倍
  3. 否则在原slice长度大于等于1024,每次扩容为原来1.25倍
package main

import "fmt"

func main() {
	s1 := []int{}
	fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
	s1 = append(s1, 1)
	fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
	s1 = append(s1, 2)
	fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
	s1 = append(s1, 3)
	fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
	s1 = append(s1, 4)
	fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
	s1 = append(s1, 5)
	fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
	s1 = append(s1, 6)
	fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
	s1 = append(s1, 7)
	fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
	s1 = append(s1, 8)
	fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
	s1 = append(s1, 9)
	fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))

}
/*
len=0,cap=0
len=1,cap=1
len=2,cap=2 
len=3,cap=4 
len=4,cap=4 
len=5,cap=8 
len=6,cap=8 
len=7,cap=8 
len=8,cap=8 
len=9,cap=16
*/
  1. 注意:用range拿到的切片元素是值引用的,因此修改无效,只能通过arr[i]的形式修改切片的值
     

3.slice深拷贝

深拷贝和浅拷贝(+)

深拷贝:

拷贝的是数据本身

在内存中开辟一个新的内存地址,复制值过去,新对象和旧对象不共享内存

修改时互不影响

浅拷贝:

拷贝的是数据地址

只复制指向对象的指针,此时新对象和老对象指向的内存地址一样

引用类型变量默认赋值操作为浅拷贝方式

修改时一个变另一个也变

slice浅拷贝实现方式:

        slice1:=slice2

slice深拷贝实现方式:

  1. copy(slice1,slice2)
  2. 遍历append赋值

值传递和引用传递

值传递

将实参的值传给形参,形参是实参的一份拷贝,实参和形参的内存地址不同,函数内对形参值内容的修改,是否会影响实参的值内容,取决于参数是否是引用类型

引用传递

将实参的地址传给形参,函数内对于形参值内容的修改,将会影响实参的值内容

Go语言没有引用传递,C++有

即使参数类型是指针(p *Pointer)或其他引用类型,函数内指针地址也会改变,但是指针指向的地址不变,所以可以通过指针修改原数据内容

4.slice为什么不是线程安全的(+)

线程安全:

        多线程同时对一个对象进行并发读写,调用这个对象的行为都可以获得正确的结果

若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全

go实现线程安全常用的几种方式:

1.互斥锁

2.读写锁

3.原子操作

4.sync.once

5.sync.atomic

6.channel

slice底层没有使用加锁等方式,不支持并发读写

在并发读写时不会报错,但是会数据丢失

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值