上篇主要介绍了Go语言里面常见的复合数据类型的声明和初始化。

这篇主要针对数组、切片和映射这些复合数据类型从其他几个方面介绍比较下。

1、遍历

  不管是数组、切片还是映射结构,都是一种集合类型,要从这些集合取出元素就要查找或者遍历。

  对于从其他语言转到Go语言,在遍历这边还是有稍稍不同的。

数组遍历

形式1

package main

import "fmt"

func main() {
arr := [5]int{1, 2, 3, 4, 5} for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
}

  

  这种最“老土”的遍历形式应该是所有的语言都通用的吧。

形式2

package main

import "fmt"

func main() {
arr := [5]int{1, 2, 3, 4, 5} for index, value := range arr {
fmt.Println(index, value)
}
}

  

  range关键字表示遍历,后面在切片和映射的遍历我们也可以看到。

  这个遍历就有点Java里面的增强for的味道了。

  但是还有有点不一样,我前两天刚写Go代码的时候还在这里掉坑里了。

  for关键字后面有两个变量,一个是index即数组角标表示第几个元素,一个是value即每个元素的值。

  坑就坑在,如果只有一个变量也是可以遍历的,比如这样

func main() {
arr := [5]int{1, 2, 3, 4, 5} for v := range arr {
fmt.Println(v)
}
}

  

  这样和Java的增强for循环遍历几乎就一样了,所以我想当然的以为这里的v就是arr对应的每个元素值。

  但其实不是,这里v表示的是数组角标。所以如果按照这样的写法本以为取到的是数组的值,其实是数组的角标值。

  另外,Go语言中有一个特性,对于有些用不上的变量,可以使用"_"代替,比如上面的代码可以写成

func main() {
arr := [5]int{1, 2, 3, 4, 5} for _, value := range arr {
fmt.Println(value)
}
}

  

切片遍历

  切片的遍历和数组没有什么区别。

package main

import "fmt"

func main() {
s := []int{1, 2, 3, 4, 5} for i := 0; i < len(s); i++ {
fmt.Println(s[i])
} for index, v := range s {
fmt.Println(index, v)
}
}

  

  两种遍历方式也都是适用的。

  注意这里len函数表示获取切片的长度,除此以外,切片还有一个数组没有的函数即cap,cap表示切片的容量,后面在扩容部分会在提到。

映射遍历

  相较于Java里面对于Map遍历与其他集合遍历有些差别来说,Go里面对于Map的遍历与其他集合的遍历倒显得比较一致。

package main

import "fmt"

func main()  {
m := make(map[string]string)
m["Jackie"] = "Zheng"
m["Location"] = "Shanghai" for key, value := range m {
fmt.Println(key, value)
}
}

  

  除此以外,我们可以只针对key进行遍历,如下

func main()  {
m := make(map[string]string)
m["Jackie"] = "Zheng"
m["Location"] = "Shanghai" for key := range m {
fmt.Println(key, m[key])
}
}

  

2、切片扩容

  数组和struct结构体都是静态数据,数组是定长的,而切片和映射都是动态数据类型。

  为什么说是动态数据类型?

  上面有顺带提过,切片除了有长度len的概念,还有容量的概念。上篇说到切片声明初始化的一种方式

s := make([]int, 3, 5) // 3所在位置表示切片长度,5所在位置表示容量即最大可能存储的元素个数

  

  我们可以动态向切片添加或者删除元素。

  如果新添加元素后已经超出切片原来的容量,那么就会扩容了。借用Go圣经里面的例子

var x, y []int
for i := 0; i < 10; i++ {
y = append(x, i)
fmt.Printf("%d cap=%d\t %v\n", i, cap(y), y)
x = y
}

  

  使用append添加新元素每次都会校验当前切片的长度如果已经达到最大容量,则会考虑先扩容,从执行结果可以看出每次扩容是原来的两倍,实际的扩容过程是会先创建一个两倍长的底层数组,然后将原切片数据拷贝到这个底层数组,再添加要插入的元素。

  所以,这里append函数之后要赋值给对应的切片,因为扩容后和扩容前的内存地址变了,如果不做赋值,可能会出现使用原来的变量无法访问到新切片的情况。

3、传值还是传引用

  首先来看一个数组的例子

package main

import "fmt"

func main() {
var arr = [5]int{1, 2, 3, 4, 5}
fmt.Println(arr)
fmt.Printf("origin array address: %p \n", &arr)
passArray(arr)
fmt.Println(arr)
} func passArray (arr1 [5]int) {
fmt.Printf("passed array address, arr1: %p \n", &arr1)
fmt.Println(arr1)
arr1[3] = 111
fmt.Println("pass array arr1: ", arr1)
}

  

  执行结果如下

[1 2 3 4 5]
origin array address: 0xc000090000
passed array address, arr1: 0xc000090060
[1 2 3 4 5]
pass array arr1: [1 2 3 111 5]
[1 2 3 4 5]

  

  • 先打印该数组,没有问题
  • 在打印当前数组的地址为:0xc000090000
  • 再调用函数passArray,先打印改数组地址为:0xc000090060,可以看出这里的地址和原始数组的地址不一样,这是因为这里传的是一个数组的副本,并非指向原数组
  • 然后打印arr1数组,和原数组数据一致
  • 再更新角标为3的元素值为111,打印后的结果为:[1 2 3 111 5]。可以发现arr1数组已经更新了
  • 调用完成passArray后,在打印原始数组,发现数据仍为:[1 2 3 4 5]并没有因为arr1的更新而受影响。

  这是因为,在调用函数passArray时,传的是arr数组的一个副本,重新开辟了一个新空间存储这5个数组元素,不同内存空间的数组变动是不会影响另一块存储数组元素的内存空间的。

  这种数组传递是非常笨重的,因为需要重新开辟一块空间把原来的数组copy一份,这里是5个元素,如果是1000或者10000个元素呢?所以,我们可以通过其他的方式规避这种笨重的操作,没错,就是指针,代码如下

package main

import "fmt"

func main() {
var arr = [5]int{1, 2, 3, 4, 5}
fmt.Println(arr)
fmt.Printf("origin array address: %p \n", &arr)
passAddress(&arr)
fmt.Println(arr)
} func passAddress (arr2 *[5]int) {
fmt.Printf("passed array address, arr2: %p \n", arr2)
fmt.Printf("passed array address, arr2: %p \n", &arr2)
fmt.Println(arr2)
arr2[3] = 111
fmt.Println("pass array arr2: ", *arr2)
}

  

  执行结果如下

[1 2 3 4 5]
origin array address: 0xc000084000
passed array address, arr2: 0xc000084000
passed array address, arr2: 0xc00000e010
&[1 2 3 4 5]
pass array arr2: [1 2 3 111 5]
[1 2 3 111 5]

  

  • 先打印该数组,没有问题
  • 在打印当前数组的地址为:0xc000084000
  • 然后调用函数passAddress,注意这里传的是数组的地址,接收的是一个指针类型变量arr2。第一次我们直接打印arr2,得到地址为:0xc000084000。没错,这里的意思是arr2这个指针指向的内存地址就是0xc000084000,即和原始数组指向的是同一块内存区域,也就是指向同一块存储这5个元素的区域。
  • 紧接着,打印arr2的地址,这个&arr2的意思是arr2这个指针的地址,为0xc00000e010,通过上面一点,我们已经知道这个指针指向的地址是0xc000084000
  • 然后我们打印arr2,得到&[1 2 3 4 5]
  • 之后我们再改变第三个角标的值为111,并打印arr2指针指向的数组的值为:[1 2 3 111 5],即arr2中元素已经更新
  • 调用完passAddress后,我们再次打印原始数组,得到的是:[1 2 3 111 5]

  原始数组的值被改变了,这是因为我们传递的是一个引用,通过一个地址指向了原来数组存储的地址。所以在函数passAddress中实际上是对原来的内存空间的数据更新,显然也会反应到原来的数组上。

  如上是数组传值的例子,slice和map也是传值的。虽然我们在传递slice或者map的时候没有显式使用指针,但是他们的内部结构都间接使用了指针,所以slice和map都是引用类型,传递的时候相当于传递的是指针的副本,可以理解为上面数组中传指针的例子。

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

Go语言入门——数组、切片和映射(下)的更多相关文章

  1. Go语言入门——数组、切片和映射

    按照以往开一些专题的风格,第一篇一般都是“从HelloWorld开始” 但是对于Go,思来想去,感觉真的从“HelloWorld”说起,压根撑不住一篇的篇幅,因为Go的HelloWorld太简单了. ...

  2. go语言学习-数组-切片-map

    数组 go语言中数组的特点: 数组的长度是固定的,并且长度也是数组类型的一部分 是值类型,在赋值或者作为参数传递时,会复制整个数组,而不是指针 定义数组的语法: var arr1 = [5]int{1 ...

  3. go语言 类型:数组切片

    初看起来,数组切片就像一个指向数组的指针,实际上它拥有自己的数据结构,而不仅仅是个指针.数组切片的数据结构可以抽象为以下3个变量: 1.一个指向原生数组的指针: 2.数组切片中的元素个数: 3.数组切 ...

  4. (三)Lua脚本语言入门(数组)

    又要找工作了,变的忧虑了,唯有学习才让内心变得踏实,今天玩了一下午的王者荣耀,正事都忘了...... 如果认为所谓的毅力是每分每秒的“艰苦忍耐”式的奋斗,那这是一种很不足的心理状态.毅力是一种习惯,毅 ...

  5. Numpy入门 - 数组切片操作

    本节主要演示数组的切片操作,数组的切片操作有两种形式:更改原数组的切片操作和不更改原数组的切片操作. 一.更改原数组的切片操作 import numpy as np arr = np.array([1 ...

  6. (四)Lua脚本语言入门(数组遍历)

    这篇文章就当成铺垫型的文章,写着写着发现有好多想写的,,关于C#与Java,当然作为铺垫肯定与Lua的下部分介绍有关..... 对于"泛型",先看C#中"泛型" ...

  7. Go语言入门之切片的概念

    切片是对数组的抽象,对切片的改变会改变原数组的值 package main import "fmt" func test6(){ arr:=[...],,,,,,,,,,} s1: ...

  8. C语言入门-数组

    今天十月一日,上午看阅兵激情澎湃,但是下午还是要继续写C语言,前面的这块很简单 int number[100]; scanf("%d" , &number[i]); 一.定 ...

  9. 第四章 go语言 数组、切片和映射

    文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/ 数组是由同构的元素组成.结构体是由异构的元素组成.数据和结构体都是有固定内存大小的数 ...

随机推荐

  1. 2018 Multi-University Training Contest 10 CSGO(HDU - 6435)(最远曼哈顿距离)

    有 n 种主武器,m 种副武器.每种武器有一个基础分数k种属性值 X[i] . 选出一种主武器 mw 和一种副武器 sw,使得两种武器的分数和 + 每个属性的差值尽量大.(参考下面的式子) 多维的最远 ...

  2. Linux操作系统启动流程

    一般来说,所有的操作系统的启动流程基本就是: 总的来说,linux系统启动流程可以简单总结为以下几步:1)开机BIOS自检,加载硬盘.2)读取MBR,进行MBR引导.3)grub引导菜单(Boot L ...

  3. linux学习-systemd-journald.service 简介

    过去只有 rsyslogd 的年代中,由于 rsyslogd 必须要开机完成并且执行了 rsyslogd 这个 daemon 之 后,登录文件才会开始记录.所以,核心还得要自己产生一个 klogd 的 ...

  4. UVa 1629 DP Cake slicing

    题意: 一块n×m的蛋糕上有若干个樱桃,要求切割若干次以后,每块蛋糕上有且仅有1个樱桃.求最小的切割长度. 分析: d(u, d, l, r)表示切割矩形(u, d, l, r)所需要的最小切割长度. ...

  5. HDU 5396 区间DP 数学 Expression

    题意:有n个数字,n-1个运算符,每个运算符的顺序可以任意,因此一共有 (n - 1)! 种运算顺序,得到 (n - 1)! 个运算结果,然后求这些运算结果之和 MOD 1e9+7. 分析: 类比最优 ...

  6. Tinkoff Internship Warmup Round 2018 and Codeforces Round #475 (Div. 2)

    A. Splits time limit per test 1 second memory limit per test 256 megabytes input standard input outp ...

  7. PHP变量的生命周期

    变量不仅有其特定的作用范围,还有其存活的周期--生命周期.变量的生命周期指的是变量可被使用的一个时间段,在这个时间段内变量是有效的,一旦超出这个时间段变量就会失效,我们就不能够再访问到该变量的值了. ...

  8. Invalid regular expression flags 错误

    找到写正则表达式的地方,检查是不是写了一个非法的正则表达式. Invalid regular expression flags

  9. iOS--app自定义相册--给图片重写exif数据-定义相册时间戳

    1.Exif简介 可交换图像文件格式常被简称为Exif(Exchangeable image file format),是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据. Exif可 ...

  10. 【bzoj3261】最大异或和 可持久化Trie树

    题目描述 给定一个非负整数序列 {a},初始长度为 N.       有M个操作,有以下两种操作类型:1.A x:添加操作,表示在序列末尾添加一个数 x,序列的长度 N+1.2.Q l r x:询问操 ...