原文链接

http://www.limerence2017.com/2019/05/08/golang05/#more

golang 的引用类型和内置类型变量

golang 中变量类型分为引用类型和值类型(也叫作内置类型)

1.值类型:变量直接存储值,内存通常在栈中分配。

值类型:基本数据类型int、float、bool、string以及数组和struct

2.引用类型:变量存储的是一个地址,这个地址存储最终的值。内存通常在 堆上分配。通过GC回收。

引用类型:指针、slice、map、chan等都是引用类型。这类型变量需要通过make构造

golang中函数传参只有一种方式

golang中函数传递参数,只有值传递一种,也就是实参内容按照值copy方式传递给形参。
当函数的形参变量类型为指针,slice,map,chan等类型时,虽然实参和形参地址不同,但是内部指向了同一个地址,所以可以达到修改指定空间数据的目的。
不要着急,接下来我会写一写小demo帮助大家理解。

数组

先把这段代码写一遍看看结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//数组声明方法
var bytearray [8]byte //长度为8的数组
fmt.Println(bytearray)
var pointarray [4]*float64 //指针数组
fmt.Println(pointarray)
var mularray [3][5]int
fmt.Println(mularray)
fmt.Printf(" pointarray len is %v\n", len(pointarray))
//数组遍历
for i := 0; i < len(pointarray); i++ {
fmt.Println("Element", i, "of array is", pointarray[i])
} //采用range遍历
for i, v := range pointarray {
fmt.Println("Array element [", i, "]=", v)
}

上边提供了数组的声明方式 var 数组名 [数组长度] 元素类型,
同时给出了两种数组遍历方式:
1 len(数组名) 可以获取数组大小,然后遍历
2 采用range遍历,第一个返回值是索引,第二个返回值是对应的内容
int 类型数组初始值为0,指针类型数组初始值为nil
结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
[0 0 0 0 0 0 0 0]
[<nil> <nil> <nil> <nil>]
[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
pointarray len is 4
Element 0 of array is <nil>
Element 1 of array is <nil>
Element 2 of array is <nil>
Element 3 of array is <nil>
Array element [ 0 ]= <nil>
Array element [ 1 ]= <nil>
Array element [ 2 ]= <nil>
Array element [ 3 ]= <nil>

前文说过数组是值类型变量,我们写个函数,在函数内部修改形参数组的变量内容,看是否会对实参影响

1
2
3
4
5
6
7
8
9
func modify(array [5]int) {
array[0] = 200
fmt.Println("In modify(), array values:", array)
}
func main(){
array := [5]int{1, 2, 3, 4, 5}
modify(array)
fmt.Println("In main(), array values:", array)
}

结果如下

1
2
In modify(), array values: [200 2 3 4 5]
In main(), array values: [1 2 3 4 5]

说明实参没有被函数修改。那么既然golang传递变量的方式都是值传递,是不是就没办法通过函数修改外部变量了呢?
肯定不是的,可以通过引用类型变量修改,比如指针,slice,map,chan等都可以在函数体内修改,从而影响外部实参的内容。
下面通过slice说明这一点

slice切片

先看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
array := [5]int{1, 2, 3, 4, 5}
//根据数组生成切片
//切片
var mySlice []int = array[:3]
fmt.Println("Elements of array")
for _, v := range array {
fmt.Print(v, " ")
}
fmt.Println("\nElements of mySlice: ")
for _, v := range mySlice {
fmt.Print(v, " ")
} //直接创建元素个数为5的数组切片
mkslice := make([]int, 5)
fmt.Println("\n", mkslice)
//创建初始元素个数为5的切片,元素都为0,且预留10个元素存储空间
mkslice2 := make([]int, 5, 10)
fmt.Println("\n", mkslice2)
mkslice3 := []int{1, 2, 3, 4, 5}
fmt.Println("\n", mkslice3) //元素遍历
for i := 0; i < len(mkslice3); i++ {
fmt.Println("mkslice3[", i, "] =", mkslice3[i])
} //range 遍历
for i, v := range mkslice3 {
fmt.Println("mkslice3[", i, "] =", v)
}

生成切片有三种方式

1 通过数组或者切片截取生成新的切片
2 通过make生成 如mkslice := make([]int, 5)
3 直接初始化 如mkslice3 := []int{1, 2, 3, 4, 5}
切片遍历和数组遍历类似,上面结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Elements of array
1 2 3 4 5
Elements of mySlice:
1 2 3 [0 0 0 0 0] [0 0 0 0 0] [1 2 3 4 5]
mkslice3[ 0 ] = 1
mkslice3[ 1 ] = 2
mkslice3[ 2 ] = 3
mkslice3[ 3 ] = 4
mkslice3[ 4 ] = 5
mkslice3[ 0 ] = 1
mkslice3[ 1 ] = 2
mkslice3[ 2 ] = 3
mkslice3[ 3 ] = 4
mkslice3[ 4 ] = 5

获取切片大小和容量

1
2
3
4
//获取size和capacity
mkslice4 := make([]int, 5, 10)
fmt.Println("len(mkslice4):", len(mkslice4))
fmt.Println("cap(mkslice4):", cap(mkslice4))

获取大小采用len,获取实际开辟的容量用cap

切片添加和删除

1
2
3
4
5
6
//末尾添加三个元素
mkslice4 = append(mkslice4, 1, 2, 3)
fmt.Println("mkslice4 is : ", mkslice4) mkslice4 = append(mkslice4, mkslice3...)
fmt.Println("mkslice4 is : ", mkslice4)

采用append 方式可以添加切片数据,但是要注意将append赋值给要存储结果的slice
append有两种用法,第一种是多个参数,第一个参数是slice,后边是要加的多个元素。
第二种是第一个参数为slice,第二个参数为slice展开,slice…表示把slice中元素一个个展开加入。
切片的删除较为麻烦,比如说删除第n个元素,就是截取n-1之前的序列和n之后的序列进行拼接。

1
2
3
4
5
6
7
8
9
10
11
   mkslice4 := make([]int, 0)

//末尾添加三个元素
mkslice4 = append(mkslice4, 1, 2, 3)
fmt.Println("mkslice4 is : ", mkslice4)
mkslice3 := []int{1, 2, 3, 4, 5}
mkslice4 = append(mkslice4, mkslice3...) fmt.Println("mkslice4 is : ", mkslice4)
mkslice4 = append(mkslice4[:4-1], mkslice4[4:]...)
fmt.Println("mkslice4 is : ", mkslice4)

切片的copy

copy函数提供了切片的深层复制,而赋值操作(=)紧紧是浅拷贝。
看看赋值操作,我们修改slice内部元素数据,其他slice是否会受到影响

1
2
3
4
5
6
7
8
9
10
   oldslice := []int{1, 2, 3, 4, 5}
newslice := oldslice[:3]
newslice2 := oldslice
fmt.Println("newslice is :", newslice)
fmt.Println("newslice2 is :", newslice2)
fmt.Printf("newslice addr is : %p \n", &newslice)
fmt.Printf("newslice2 addr is: %p \n", &newslice2)
oldslice[0] = 1024
fmt.Println("newslice is :", newslice)
fmt.Println("newslice2 is :", newslice2)

输出一下

1
2
3
4
5
6
newslice is : [1 2 3]
newslice2 is : [1 2 3 4 5]
newslice addr is : 0xc00005a400
newslice2 addr is: 0xc00005a420
newslice is : [1024 2 3]
newslice2 is : [1024 2 3 4 5]

可以看到oldslice修改后,newslice和newslice2都受到影响了,即便他们地址不同。
为什么呢?这要追溯到slice内部实现

1
2
3
4
5
type Slice struct {
ptr unsafe.Pointer // Array pointer
len int // slice length
cap int // slice capacity
}

Slice 内部其实存放了一个指针ptr,这个ptr指向的地址就是存放数据连续空间的首地址,len表示空间当前长度,cap表示空间实际开辟了多大。
如下图

那如何深copy元素到另一个slice呢?就是copy函数了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1)
fmt.Println("after copy.....")
fmt.Println("slice1: ", slice1)
fmt.Println("slice2: ", slice2)
slice2[0] = 1024
slice2[1] = 999
slice2[2] = 1099
fmt.Println("after change element slice2...")
fmt.Println("slice1: ", slice1)
fmt.Println("slice2: ", slice2)
copy(slice1, slice2)
fmt.Println("after copy.....")
fmt.Println("slice1: ", slice1)
fmt.Println("slice2: ", slice2)

结果如下

1
2
3
4
5
6
7
8
9
after copy.....
slice1: [1 2 3 4 5]
slice2: [1 2 3]
after change element slice2..
slice1: [1 2 3 4 5]
slice2: [1024 999 1099]
after copy.....
slice1: [1024 999 1099 4 5]
slice2: [1024 999 1099]

可以看到copy(destslice,srcslice),当destslice 大小< srcslice时,只拷贝destslice大小的数据。
也就是说copy的大小取决于destslice和srcslice最小值
另外copy后,修改slice2元素,slice1也不会受到影响,是深copy。
感谢关注我的公众号

Go(03) slice切片的使用的更多相关文章

  1. golang:slice切片

    一直对slice切片这个概念理解的不是太透彻,之前学习python的就没搞清楚,不过平时就用python写个工具啥的,也没把这个当回事去花时间解决. 最近使用go开发又遇到这个问题,于是打算彻底把这个 ...

  2. golang slice切片的原理以及内置函数cap, len

    golang中slice(切片)是常用的类型, slice是对数组进行封装 package main import ( "fmt" "strconv") fun ...

  3. python定制类(1):__getitem__和slice切片

    python定制类(1):__getitem__和slice切片 1.__getitem__的简单用法: 当一个类中定义了__getitem__方法,那么它的实例对象便拥有了通过下标来索引的能力. c ...

  4. golang基础---Slice切片

    切片Slice在go语言中是单独的类型(指向底层的数组),不同于python(对可迭代对象操作的工具),注意区分数组和slice的区别 定义一个空slice,格式var s []int,这种既没有长度 ...

  5. Go语言核心之美 3.2-slice切片

    Slice(切片)是长度可变的元素序列(与之相应,上一节中的数组是不可变的),每一个元素都有同样的类型.slice类型写作[]T.T是元素类型.slice和数组写法非常像,差别在于slice没有指定长 ...

  6. Go - Slice 切片

    概述 切片是一种动态数组,比数组操作灵活,长度不是固定的,可以进行追加和删除. len() 和 cap() 返回结果可相同和不同. 声明切片 //demo_7.go package main impo ...

  7. 3.9 Go Slice切片

    3.9 Go Slice切片 Go语言切片(Slice) 切片是可动态变化的序列,是对数组的引用,引用类型,遵循引用传递的机制 slice类型写作[ ]T,T是slice元素类型,var s1 []i ...

  8. [日常] Go语言圣经-Slice切片习题

    1.Slice(切片)代表变长的序列,序列中每个元素都有相同的类型,一个slice类型一般写作[]T,其中T代表slice中元素的类型:slice的语法和数组很像,只是没有固定长度而已,slice的底 ...

  9. golang 数组以及slice切片

    老虞学GoLang笔记-数组和切片   数组 Arrays 数组是内置(build-in)类型,是一组同类型数据的集合,它是值类型,通过从0开始的下标索引访问元素值.在初始化后长度是固定的,无法修改其 ...

随机推荐

  1. Codeforces Round #581 (Div. 2) B. Mislove Has Lost an Array (贪心)

    B. Mislove Has Lost an Array time limit per test1 second memory limit per test256 megabytes inputsta ...

  2. Flight HDU - 3499 (分层最短路)

    Recently, Shua Shua had a big quarrel with his GF. He is so upset that he decides to take a trip to ...

  3. 面试题:Dubbo中zookeeper做注册中心,如果注册中心集群全都挂掉,发布者和订阅者之间还能通信么?

    1.[提供者]在[启动]时,向注册中心zk [注册]自己提供的服务. 2.[消费者]在[启动]时,向注册中心zk [订阅]自己所需的服务.   可以的,消费者在启动时,消费者会从zk拉取注册的生产者的 ...

  4. C# MVC的默认页

    MVC的默认页,其实是默认路由设置启动哪一个Controller的哪一个Action,在根目录的Global.asax.cs里面设置.是MVC项目里面的路由.將下面的controller和action ...

  5. 32.把数组排成最小的数(python)

    题目描述 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个.例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323. # -*- ...

  6. plsql 连不上64位oracle客户端

    1)安装Oracle 11g 64位 2)安装32位的Oracle客户端( instantclient-basic-win32-11.2.0.1.0) 下载instantclient-basic-wi ...

  7. web文件夹上传下载方案

    第一点:Java代码实现文件上传 FormFile file = manform.getFile(); String newfileName = null; String newpathname =  ...

  8. POJ 2456 编程技巧之------二分查找思想的巧妙应用

    Aggressive cows Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 18599   Accepted: 8841 ...

  9. 3105: [cqoi2013]新Nim游戏

    貌似一道经典题 在第一个回合中,第一个游戏者可以直接拿走若干个整堆的火柴.可以一堆都不拿,但不可以全部拿走.第二回合也一样,第二个游戏者也有这样一次机会.从第三个回合(又轮到第一个游戏者)开始,规则和 ...

  10. windows如何正确下载补丁包

    今天公司让给windows安装补丁,打开链接,我蒙蔽了,这么多包要下载哪个腻?下面来跟杨老师一起学习一下如何确定windows版本,下载正确的补丁包. 首先先看一下下载补丁的页面,懵~~ 登录你需要安 ...