原文链接

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. Fatal Error: Out of memory php内存溢出处理三种方法

    有时候我们在运行php程序的时候会发现 Fatal Error: Out of memory 这样的提示,这有可能是程序中用到了大量了变量和对象,导致分配的内存不够用. 修改php.ini文件里的me ...

  2. mysql向表中某字段前后追加一段字符串 concat(), trim(), ltrim(), rtrim(), repeat()

    1.mysql向表中某字段后面追加一段字符串:update table_name set field=CONCAT(field, '分隔符', str);//'分隔符',可以为空,也可以省略updat ...

  3. 用Python实现九九乘法表打印

    #!usr/bin/env python # -*- coding:utf-8 -*- # dic={ # 'apple':10, # 'iphon':5000, # 'wwatch Tv':3000 ...

  4. 轮子:读取config.ini文件

    python: 把config.ini文件成map返回 def get_conf(conf_file): conf = {} ll=list(map(lambda x: x.replace('&quo ...

  5. Acwing-283-多边形(区间DP)

    链接: https://www.acwing.com/problem/content/285/ 题意: "多边形游戏"是一款单人益智游戏. 游戏开始时,给定玩家一个具有N个顶点N条 ...

  6. QT:如何重新生成makefile文件

  7. List集合的三个实现类比较

    1. ArrayList 底层数据结构是数组,查询快,增删慢 线程不安全,效率高 2. Vector 底层数据结构是数组,查询快,增删慢 线程安全,效率低 3. LinkedList 底层数据结构是链 ...

  8. BZOJ 3697: 采药人的路径 点分治

    好久不做点分治的题了,正好在联赛之前抓紧复习一下. 先把边权为 $0$ 的置为 $-1$.定义几个状态:$f[dis][0/1],g[dis][0/1]$ 其中 $f$ 代表在当前遍历的子树内的答案. ...

  9. Confluence 6 上传文件

    当你上传一个文件的时候,例如上传一个图片或者文档,上传的文件将会附加到当前页面上. 你可以选择在页面中将文件显示为一个链接,一个图片或者嵌入到页面中(使用宏). 上传一个文件到页面中你需要具有空间的权 ...

  10. confluence 附件docx文件 乱码处理

    服务器安装字体库 Fontconfig是一个用于配置和自定义字体访问的库 yum -y install fontconfig 拷贝需要的字体文件 fonts.zip(或自己电脑中的字体文件c:/Win ...