前一篇说到了go的变量、常量和基本数据类型,接下来继续说一下go的其他内置类型:数组、切片(slice)和集合(map)
一,数组
go和java中肯定都有数组了,只不过在定定义上和定义变量类似,把类型放到了后边(还是觉得很难受有没有?)
首先对比一下定义:
//java: int[] arr; //没有初始化 arr = new int[1]{}; //初始化长度为1的数组 int[] arr1 = new int[]{1,2,3}; //初始化三个值 //go: var arr [1]int //同样 arr = [1]int{} //初始化长度为1的数组 arr1 := [3]int{1,2,3} //同样初始化三个值 arr2 := [...]int{1,2,3} //同样初始化三个值
我们看到不光数组类型放到了变量后边,类型名称都放到了"[]"后边,和java完全是颠倒的(真的好别扭);
注意,在初始化arr1的时候,go的[]中指定了3(也可以不写,但是不写就是切片了,之后会说到),但是在java中的[]中是不能指定的,否则会报错。
再有,在go初始化arr2的时候,[]中写额是“...”,三个点表示自动计算长度,用户用户保持长度的正确性,但是这种只能是在静态初始化的时候(指定数组内容),如下也会报错的:var arr2 [...]string //报错:use of [...] array outside of array literal数组在使用上也都一样,都是使用“[]”来对元素进行操作:
var arr [3]string = [3]string{"1","2","3"} //固定长度 arr1 := [3]string{"1","2","3"} //还是这样写简单 arr[0] = "3" arr1[len(arr1)-1] = "1" fmt.Println("arr=",arr) fmt.Println("arr1=",arr1)
数组长度也属于类型的一部分,所以我们看第一行,定义并初始化的时候,长度必须一致才可以,否则会报错,因为不同长度的数组是两个类型。
再有数组的长度是不可变的。数组之间的赋值是值的赋值,当数组作为参数的时候,其实是数组的拷贝,不是指针,如果要使用指针,那就要使用切片(slice)了,这个后面就会说到。
说到数组,当然不能只是一维,多维数组如何定义呢?跟java没两样,只是要记得go的定义风格就好了,请看:
arr_1 := [1][2]int{{1,2}}
多维数组操作上就不多说了。
二,切片(slice)
切片也是go原生的类型,也是一种数组,叫动态数组,其实就相当于java中的ArrayList,可变数组。
在说数组初始化的时候说到,数组的定义不分是包含长度的(中括号[]中的值),在定义的时候如果不写这个长度值,就相当于定义个一个切片(定义了长度就是数组),如:
//go var slice []int slice = []int{1,2,3} slice1 := [3]int{1,2,3}可以看到除了[]中没有指定值外,和数组没有什么区别,但是在使用上就灵活很多,比如:
//go slice := []int{1,2,3} slice2 := slice[0:2] //从一个slice初始化,0:2表示从开始的位置截止到第三个。 slice2 = slice[:2] //如果是从0开始,可以简写为[:2] slice2 = slice[:] //拷贝slice我们看到可以从一个slice截取一部分作为初始化和赋值,当然也可以使用这种方式来截取数组。
另外切片和数组的不同是,切片其实是一种引用类型,底层都是一个数组的引用,所以在改变引用值得时候,切片的值也会改变的:
arr := [3]int{1,2,3} slice = arr[:] //copy arr arr[0] = 10 fmt.Println("arr=",arr) //[10,2,3] fmt.Println("slice=",slice) //[10,2,3]在使用切片的时候,还经常会用到几个内置函数:
- cap 得到slice的最大容量 ,这个我们在使用List的时候也经常会给集合一个初始化容量,只不过我们没有直接的方法获得这个初始化容量的值
- len 获取slice的长度
- append 添加一个或者多个元素,返回相同类型的切片
- copy 函数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数。(go中,函数是可以返回多个值的,哈哈,这个功能是不是感觉挺不错,如果java中也可以就好了)
arr := [10]int{1,2,3,4,5,6,7,8,9,10} slice3 := arr[:4:6]如上slice3的初始化的[]中多了一个参数,这个参数就相当于我们在java中使用集合给定一个初始容量一样,给这个切片定义一个初始容量。
接下来就对比一下go的slice和java中的ArrayList:
- 本质上都是数组,ArrayList使用数组来存储,slice操作的也是数组,并且都可以自动扩容;
- 因为使用的是数组,所以他们也都是有序的,可重复的。
- slice是引用类型,所以作为参数传递的时候,效果和java中的List传参是一样的。
三,Map
map,也叫字典或集合,和java中的map是一样的,是一个键值对的集合,但在go的定义方式上和java就有一些区别了,先看一下java的初始化:
//java Map<Object,Object> map = new HashMap<>(10);
在java中所有对象的父类都是Object,所以这个集合的key和value可以是任意对象,但通常很少这么用,我们都会根据key和value的类型进行分别指定,这样在处理的过程中也方便使用,不用类型转换;
再有java中的Map是接口,初始化的时候需要指定实现类,在go中,没有类,但是有结构体(struct),也可定义接口,也可实现集成,这个后面再说,先来看下go中map的定义:
//go var map1 map[int]string //声明,没有初始化的时候是nil,此时是不能进行key操作的 map1 = map[int]string{} //初始化之后才能进行操作,初始化一个空的集合 map1 = map[int]string{0:"zero",1:"1"} //也可以在此添加初始数据 //map1 = make(map[int]string) //还可以使用make函数来初始化 map1[1] = "one" map1[2] = "two" fmt.Println("map1",map1) //map1 map[0:zero 1:one 2:two] delete(map1, 2) //删除key fmt.Println("map1",map1) //map1 map[0:zero 1:one]map的定义需要使用map关键字,[]中的是key的类型,后面跟着的是value的类型。
map在声明之后,初始化之前是不能进行键值对操作的,但是可以使用len、range等函数(后面会说到)进行操作,并不会报错,而如果是java,这个时候肯定会报错的,因为还是null。
我们看到map添加数据就像使用数组一样,只不过[]中不是index,而是key,不用显式的get、add,很方便,不过在移除的时候需要用到delete方法。
同样,我们对go原生的map和java中的HashMap(真的很像)进行对比:- 都不能通过index进行操作,只能通过key进行操作
- go中的map是无序的,类似java中的HashMap,也是无需的
- go的map不是基本类型,是线程不安全的,同样类似于HashMap。(java中有对应的线程安全实现HashTable)
- map是引用类型,所以在作为参数使用的时的效果依然是引用传参的效果,和java中的map传参效果相同。
四,遍历
接下来说一下内置的range函数,说了上边的数组、切片还有map后,当然就会想到怎么遍历,rang就是干这个用的,直接上例子:
//遍历数组 arr:= []int{1,2,3,4,5} fmt.Println(arr) for v:=range arr{ fmt.Println(v) } //遍历slice slice := arr[:3] fmt.Println(slice) for v:= range slice{ fmt.Println(v) } //遍历map var map1 map[int]string = make(map[int]string) //还可以使用make函数来初始化 map1[1] = "one" map1[2] = "two" fmt.Println("map1",map1) for k,v := range map1{ fmt.Println(k,"=",v) }
又看到了多个返回值,用起来真的好爽啊,哈哈。
今天就说这么多吧,今天提到了slice和map是两个引用类型,还提到make函数,这些就留在下次说吧。