IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> Golang函数内slice进行append时不改变外部值问题 -> 正文阅读

[数据结构与算法]Golang函数内slice进行append时不改变外部值问题

Golang函数内slice进行append时不影响外部值问题

这个标题…在看完这篇文章之后应该就能知道,这么说是不正确的,虽然看起来好像是这样的,哈哈哈哈

问题引入:下面这样一段代码会输出什么?


package main

import "fmt"

func main() {
	arr := make([]int, 3, 4)
	arr[0] = 0
	arr[1] = 1
	arr[2] = 2
	fmt.Printf("main before: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
	ap1(arr)
	fmt.Printf("main ap1 after: len: %d cap:%d data:%+v\n\n", len(arr), cap(arr), arr)
}
func ap1(arr []int) {
	fmt.Printf("ap1 before:  len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
	arr[0] = 11
	arr = append(arr, 111)
	fmt.Printf("ap1 after:  len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
}

输出:

main before: len: 3 cap:4 data:[0 1 2]
ap1 before:  len: 3 cap:4 data:[0 1 2]
ap1 after:  len: 4 cap:4 data:[11 1 2 111]
main ap1 after: len: 3 cap:4 data:[11 1 2]

可以发现,函数内append之后,外部实参切片的值真的没改变,但到底是没改变,还是 “看不到” 呢?
Golang参数传递:值传递那篇文章中有说过,slice是值传递,但是slice本身是一个结构体,包含一个指针,指向底层数组,将slice按值传递给函数,在函数内对其修改,影响将会传递到函数外,因为底层的数组被修改了。

type slice struct {
array unsafe.Pointer
len   int
cap   int
}

可以打印一下地址值看看:

func main() {
	arr := make([]int, 3, 4)
	arr[0] = 0
	arr[1] = 1
	arr[2] = 2
	fmt.Printf("main before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
	ap1(arr)
	fmt.Printf("main ap1 after: len: %d cap:%d data:%+v\n\n", len(arr), cap(arr), arr)
}
func ap1(arr []int) {
	fmt.Printf("ap1 before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
	arr[0] = 11
	arr = append(arr, 111)
	fmt.Printf("ap1 after: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
}

输出:
在这里插入图片描述
既然都是指向的同一个数组,为什么实参slice输出的内容有些被修改了,有些没被修改呢?一切还要从slice的len这个属性说起,就算cap再大,它也只能感知到len范围内的内容,之外的感知不到的…画图说明如下:
在这里插入图片描述
ok,接下来再做几个实验:

func main() {
	arr := make([]int, 3, 4)
	arr[0] = 0
	arr[1] = 1
	arr[2] = 2
	fmt.Printf("main before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
	ap2(arr)
	fmt.Printf("main ap2 after: len: %d cap:%d data:%+v\n\n", len(arr), cap(arr), arr)
	ap3(arr)
	fmt.Printf("main ap3 after: len: %d cap:%d data:%+v\n\n", len(arr), cap(arr), arr)
	ap4(arr)
	fmt.Printf("main ap4 after: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
}
func ap2(arr []int) {
	fmt.Printf("ap2 before:  len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
	arr = append(arr, 222)
	arr[0] = 22
	fmt.Printf("ap2 after:  len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
}
func ap3(arr []int) {
	fmt.Printf("ap3 before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
	arr = append(arr, 333,333)
	arr[0] = 33
	fmt.Printf("ap3 after: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
}
func ap4(arr []int) {
	fmt.Printf("ap4 before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
	arr[0] = 44
	arr = append(arr, 444,444)
	fmt.Printf("ap4 after: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
}

输出:
在这里插入图片描述

为什么ap3中,向切片中append了两个元素之后,再修改其内容就影响不到实参原切片的内容了呢?

这是因为切片底层的扩容机制,当对切片进行append()操作,若是元素数量超过原有切片的容量,将会使得切片扩容,这就是问题所在。扩容后的切片,本质上是产生一个新的底层数组,并把旧数组的值复制进去,然后旧数组被回收掉。如果在函数内对切片添加元素导致扩容,会导致元素内的切片指向一个新的数组,但是函数外的切片仍然指向原来旧的数组,则将会导致影响无法传递到函数外。如果希望函数内对切片扩容作用于函数外,就需要以指针形式传递切片。

源码:src/runtime/slice.go 中的 growslice 函数中的核心部分。

// src/runtime/slice.go
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
			}
		}
	}
	// ...省略部分
}

指针形式传递切片:【注意切片指针类型寻址的时候要(*指针)[index],因为**[]优先级比较高**】

func main() {
	arr := make([]int, 3, 4)
	arr[0] = 0
	arr[1] = 1
	arr[2] = 2
	fmt.Printf("main before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
	ap5(&arr)
	fmt.Printf("main ap5 after: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
}
func ap5(arr *[]int) {
	fmt.Printf("ap5 before: len: %d cap:%d data:%+v, pointer addr:%p, arr[0] addr:%p\n", len(*arr), cap(*arr), *arr, &(arr), &(*arr)[0])
	*arr = append(*arr, 555,555)
	(*arr)[0] = 55
	fmt.Printf("ap5 after: len: %d cap:%d data:%+v, pointer addr:%p, arr[0] addr:%p\n", len(*arr), cap(*arr), *arr, &(*arr), &(*arr)[0])
}

可以看到,在函数内部对slice扩容之后,形参和实参的底层数组都换到了同一个地址
在这里插入图片描述
再做个小实验,前面说slice只能感知到len范围内的内容,之外的感知不到,那要是把之外的换到之内呢?答案是肯定的

func main() {
	arr := make([]int, 3, 4)
	arr[0] = 0
	arr[1] = 1
	arr[2] = 2
	fmt.Printf("main before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
	reverse1(arr)
	fmt.Printf("main reverse1 after: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
	reverse2(arr)
	fmt.Printf("main reverse2 after: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
}
func reverse1(arr []int) {
	fmt.Printf("reverse1 before:  len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
	arr = append(arr, 111)
	for i, j := 0, len(arr) - 1; i < j; i++ {
		j = len(arr) - (i + 1)
		arr[i], arr[j] = arr[j], arr[i]
	}
	fmt.Printf("reverse1 after:  len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
}
func reverse2(arr []int) {
	fmt.Printf("reverse2 before:  len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
	arr = append(arr, 222,22)
	for i, j := 0, len(arr) - 1; i < j; i++ {
		j = len(arr) - (i + 1)
		arr[i], arr[j] = arr[j], arr[i]
	}
	fmt.Printf("reverse2 after:  len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
}


在这里插入图片描述
再来看点切片的其他知识吧,切片可以用上面实验中用的make的方式生成,也可以从数组中切,那切的话又有什么问题呢?

func main() {
	s := make([]int, 2, 10)
	fmt.Printf("s: %v, s[1] addr: %p\n", s, &s[1])
	b := s[1:3:4] //b的内容对应s的坐标范围:[1,3),len=3-1=2,cap=4-1=3
	fmt.Printf("b: %v, b[0] addr: %p\n", b, &b[0])

	c := append(b, 2)
	fmt.Println("after c := append(b,2):")
	fmt.Println("s:", s)
	fmt.Println("b:", b)
	fmt.Printf("c: %v, len: %v, cap:%v, c[0] addr: %p\n", c, len(c), cap(c), &c[0])

	fmt.Println("由于切片d长度为4,超出b的容量,go会给切片d分配新内存,以后对s的操作都与d无关")
	d := append(b, 4, 5)
	fmt.Println("after d := append(b,4,5):")
	fmt.Println("s:", s)
	fmt.Println("b:", b)
	fmt.Println("c:", c)
	fmt.Printf("d: %v, len: %v, cap:%v, d[0] addr:%p\n", d, len(d), cap(d), &d[0])
	s = append(s, 3, 3)
	fmt.Println("after s = append(s,3,3):")
	fmt.Println("s:", s)
	fmt.Println("b:", b)
	fmt.Println("c:", c)
	fmt.Println("d:", d)

	b = append(b, 6)
	fmt.Println("after b = append(b, 6):")
	fmt.Println("s:", s)
	fmt.Println("b:", b)
	fmt.Println("c:", c)
	fmt.Println("d:", d)
	fmt.Println("由于切片b长度变为4,超出b的容量,go会给切片b分配新内存,以后对b的操作都与基于b的切片无关")
	b = append(b, 7)
	fmt.Println("after b = append(b, 7):")
	fmt.Println("s:", s)
	fmt.Printf("b: %v, b[0] addr: %p\n", b, &b[0])
	fmt.Println("c:", c)
	fmt.Println("d:", d)
	b[0] = 8
	fmt.Println("after b[0] = 8:")
	fmt.Println("s:", s)
	fmt.Println("b:", b)
	fmt.Println("c:", c)
	fmt.Println("d:", d)
}

在这里插入图片描述
在这里插入图片描述

ps:关于copy函数:

copy()将切片s的内容复制给切片s1:返回复制的元素数
若元素不够,则只拷贝一部分
s和s1数据空间相互独立,相互不影响

s := []int{1,2}
s1 := make([]int,len(s)*2,cap(s)*3)

copy(s1,s)
fmt.Println(len(s),cap(s),s)
fmt.Println(len(s1),cap(s1),s1)
//执行结果:
2 2 [1 2]
4 6 [1 2 0 0]

参考

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章           查看所有文章
加:2021-12-02 17:01:12  更:2021-12-02 17:03:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 14:31:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码