数组的定义
var 数组名 [ 数组大小]数据类型
var a [5]int
数组必须定义长度,且一个数组不能动态改变长度。不要担心这个限制,切片(slice)可以弥补这个不足
四种初始化数组的方式
var numArr01 [3]int =[3]int{1, 2, 3}
var numArr02 = [3]int{5, 6, 7}
//这里的 [...]是规定的写法
var numArr03 = [...]int{8, 9 ,10}
var numArr04 = [...]int{1: 800, 0: 900, 2:999}
直接赋值: var 数组名 [大小] [类型]=[大小] [类型]{{初值。。。}{初值。。。。}}
arr := [ ] [ ] int {{123}{356}}
数组是值类型
在 Go 中数组是值类型而不是引用类型。这意味着当数组变量被赋值时,将会获得原数组(:也就是等号右面的数组)的拷贝。新数组中元素的改变不会影响原数组中元素的值。
值类型就是现金,要用直接用;引用类型是存折,要用还得先去银行取现。
值类型与引用类型的区别在于值类型的变量直接包含其数据,而引用类型的变量则存储对象引用。
对于值类型,每个变量都有自己的数据副本,对一个变量的操作不可能影响另一个变量。
对于引用类型,两个变量可能引用同一个对象,因此对一个变量的操作可能影响另一个变量所引用的对象。
值类型 : 基本数据类型int、float、bool、string以及数组和struct。
引用类型:指针、slice、map、chan等都是引用类型。
值类型:变量直接存储,内存通常在栈中分配。
引用类型:变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配。通过GC回收。
通常在函数中转入指针效率比较高,因为方法中的参数是需要进行拷贝的,拷贝指针的效率比较高,要是一个大的接口体的话拷贝的效率就比较低。
同样的,如果将数组作为参数传递给函数,仍然是值传递,在函数中对(作为参数传入的)数组的修改不会造成原数组的改变。
数组的长度
内置函数 len 用于获取数组的长度:len(a)
数组的遍历
1.普通方式遍历
func main() {
a := [...]float64{67.7, 89.8, 21, 78}
for i := 0; i < len(a); i++ {
fmt.Printf("%d th element of a is %.2f\n", i, a[i])
}
}
2.for-range 结构遍历
func main() {
a := [...]float64{67.7, 89.8, 21, 78}
for i, v := range a {
fmt.Printf("%d the element of a is %.2f\n", i, v)
}
}
range 返回数组的索引和索引对应的值
多维数组
func printarray(a [3][2]string) {
for _, v1 := range a {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
}
func main() {
a := [3][2]string{
{"lion", "tiger"},
{"cat", "dog"},
{"pigeon", "peacock"}, //this comma is necessary. The compiler will complain if you omit this comma
}
printarray(a)
var b [3][2]string
b[0][0] = "apple"
b[0][1] = "samsung"
b[1][0] = "microsoft"
b[1][1] = "google"
b[2][0] = "AT&T"
b[2][1] = "T-Mobile"
fmt.Printf("\n")
printarray(b)
}
上利用速记声明创建了一个二维数组 a
,逗号是必须的,这是因为词法分析器会根据一些简单的规则自动插入分号。
在第 23 行声明了另一个二维数组 b
,并通过索引的方式给数组 b
中的每一个元素赋值。这是初始化二维数组的另一种方式。声明的函数 printarray
通过两个嵌套的 range for 打印二维数组的内容。
遍历一次的话就是遍历出里面的数组,要是想遍历出所有的元素要进行嵌套循环
输出为:
lion tiger
cat dog
pigeon peacock
apple samsung
microsoft google
AT&T T-Mobile
基本语法
for index ,value := range arry01{
}
index 返回的是数组的下标,value是该下标对应的值。
-
二维数组的遍历
-
for ---- range
for i , v := range arr{----------- v 是一个一维数组
for j , v1 := range v{
..............................................
}
}
-
双重for循环:
for i:=0;i<len(arr);i++{
for j:=0;j<len (arr[ i ]);j++{
................................................
}
}
-
切片
数组的长度是固定的,没办法动态增加数组的长度。而切片却没有这个限制,切片更灵活,实际上在 Go 中,切片比数组更为常见。切片并不存储任何元素而只是对现有数组的引用。切片[ ]中没数字,数组中[ ]内有数字。
方式一
func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:4] //creates a slice from a[1] to a[3]
fmt.Println(b)
}
通过 a[start:end]
这样的语法创建了一个从 a[start]
到 a[end -1]
的切片。在上面的程序中,第 9 行 a[1:4]
创建了一个从 a[1]
到 a[3]
的切片。因此 b
的值为:[77 78 79]
。
方式二
func main() {
c := []int{6, 7, 8} //creates and array and returns a slice reference
fmt.Println(c)
}
在上面的程序中,第 9 行 c := []int{6, 7, 8}
创建了一个长度为 3 的 int 数组,并返回一个切片给 c。
修改切片
切片本身不包含任何数据。它仅仅是底层数组的一个上层表示。对切片进行的任何修改都将反映在底层数组中。
func main() {
darr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59}
dslice := darr[2:5]
fmt.Println("array before",darr)
for i := range dslice {
dslice[i]++
}
fmt.Println("array after",darr)
}
上面程序的第 9 行,我们创建了一个从 darr[2]
到 darr[5]
的切片 dslice
。for
循环将这些元素值加 1
。执行完 for
语句之后打印原数组的值,我们可以看到原数组的值被改变了。程序输出如下:
array before [57 89 90 82 100 78 67 69 59]
array after [57 89 91 83 101 78 67 69 59]
当若干个切片共享同一个底层数组时,对每一个切片的修改都会反映在底层数组中
func main() {
numa := [3]int{78, 79 ,80}
nums1 := numa[:] //creates a slice which contains all elements of the array
nums2 := numa[:]
fmt.Println("array before change 1",numa)
nums1[0] = 100
fmt.Println("array after modification to slice nums1", numa)
nums2[1] = 101
fmt.Println("array after modification to slice nums2", numa)
}
可以看到,在第 9 行, numa[:]
中缺少了开始和结束的索引值,这种情况下开始和结束的索引值默认为 0
和len(numa)
。这里 nums1
和 nums2
共享了同一个数组。程序的输出为:
array before change 1 [78 79 80]
array after modification to slice nums1 [100 79 80]
array after modification to slice nums2 [100 101 80]
从输出结果可以看出,当多个切片共享同一个数组时,对每一个切片的修改都将会反映到这个数组中。
切片的长度和容量
切片的长度是指切片中元素的个数。切片的容量是指从切片的起始元素开始到其底层数组中的最后一个元素的个数。
func main() {
fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
fruitslice := fruitarray[1:3]
fmt.Printf("length of slice %d capacity %d", len(fruitslice), cap(fruitslice)) //length of is 2 and capacity is 6
}
译者注:使用内置函数 cap 返回切片的容量。
func main() {
fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
fruitslice := fruitarray[1:3]
fmt.Printf("length of slice %d capacity %d", len(fruitslice), cap(fruitslice)) //length of is 2 and capacity is 6
}
在上面的程序中,创建了一个以 fruitarray
为底层数组,索引从 1
到 3
的切片 fruitslice
。因此 fruitslice
长度为2
。
fruitarray
的长度是 7。fruiteslice
是从 fruitarray
的索引 1
开始的。因此 fruiteslice
的容量是从 fruitarray
的第 1
个元素开始算起的数组中的元素个数,这个值是 6
。因此 fruitslice
的容量是 6
。程序的输出为:length of slice 2 capacity 6。
切片的长度可以动态的改变(最大为其容量)。任何超出最大容量的操作都会发生运行时错误。
用make创建切片
内置函数 func make([]T, len, cap) []T 可以用来创建切片,该函数接受长度和容量作为参数,返回切片。容量是可选的,默认与长度相同。使用 make 函数将会创建一个数组并返回它的切片。
func main() {
i := make([]int, 5, 5)
fmt.Println(i)
}
用 make
创建的切片的元素值默认为 0 值。上面的程序输出为:[0 0 0 0 0]
。
追加元素到切片
我们已经知道数组是固定长度的,它们的长度不能动态增加。而切片是动态的,可以使用内置函数 append
添加元素到切片。append
的函数原型为:append(s []T, x ...T) []T
。
x …T 表示 append
函数可以接受的参数个数是可变的。这种函数叫做变参函数。
你可能会问一个问题:如果切片是建立在数组之上的,而数组本身不能改变长度,那么切片是如何动态改变长度的呢?实际发生的情况是,当新元素通过调用 append
函数追加到切片末尾时,如果超出了容量,append
内部会创建一个新的数组。并将原有数组的元素被拷贝给这个新的数组,最后返回建立在这个新数组上的切片。这个新切片的容量是旧切片的二倍(译者注:当超出切片的容量时,append
将会在其内部创建新的数组,该数组的大小是原切片容量的 2 倍。最后 append
返回这个数组的全切片,即从 0 到 length - 1 的切片)。下面的程序使事情变得明朗:
func main() {
cars := []string{"Ferrari", "Honda", "Ford"}
fmt.Println("cars:", cars, "has old length", len(cars), "and capacity", cap(cars)) //capacity of cars is 3
cars = append(cars, "Toyota")
fmt.Println("cars:", cars, "has new length", len(cars), "and capacity", cap(cars)) //capacity of cars is doubled to 6
}
在上面的程序中,cars
的容量开始时为 3。在第 10 行我们追加了一个新的元素给 cars
,并将 append(cars, "Toyota")
的返回值重新复制给 cars
。现在 cars
的容量翻倍,变为 6。上面的程序输出为:
cars: [Ferrari Honda Ford] has old length 3 and capacity 3
cars: [Ferrari Honda Ford Toyota] has new length 4 and capacity 6
切片的 0 值为 nil。一个 nil 切片的长度和容量都为 0。可以利用 append 函数给一个 nil 切片追加值。
func main() {
var names []string //zero value of a slice is nil
if names == nil {
fmt.Println("slice is nil going to append")
names = append(names, "John", "Sebastian", "Vinay")
fmt.Println("names contents:",names)
}
}
在上面的程序中 names
为 nil
,并且我们把 3 个字符串追加给 names
。程序的输出为:
slice is nil going to append
names contents: [John Sebastian Vinay]
可以使用 ... 操作符将一个切片追加到另一个切片末尾:
func main() {
veggies := []string{"potatoes","tomatoes","brinjal"}
fruits := []string{"oranges","apples"}
food := append(veggies, fruits...)
fmt.Println("food:",food)
}
上面的程序中,在第10行将 fruits
追加到 veggies
并赋值给 food
。...
操作符用来展开切片。程序的输出为:food: [potatoes tomatoes brinjal oranges apples]
。
切片作为函数参数
可以认为切片在内部表示为如下的结构体:
type slice struct {
Length int
Capacity int
ZerothElement *byte
}
可以看到切片包含长度、容量、以及一个指向首元素的指针。当将一个切片作为参数传递给一个函数时,虽然是值传递,但是指针始终指向同一个数组。因此将切片作为参数传给函数时,函数对该切片的修改在函数外部也可以看到。让我们写一个程序来验证这一点。
func subtactOne(numbers []int) {
for i := range numbers {
numbers[i] -= 2
}
}
func main() {
nos := []int{8, 7, 6}
fmt.Println("slice before function call", nos)
subtactOne(nos) //function modifies the slice
fmt.Println("slice after function call", nos) //modifications are visible outside
}
在上面的程序中,第 17 行将切片中的每个元素的值减2
。在函数调用之后打印切片的的内容,发现切片内容发生了改变。你可以回想一下,这不同于一个数组,对函数内部的数组所做的更改在函数外不可见。上面的程序输出如下:
array before function call [8 7 6]
array after function call [6 5 4]
多维切片
同数组一样,切片也可以有多个维度。
func main() {
pls := [][]string {
{"C", "C++"},
{"JavaScript"},
{"Go", "Rust"},
}
for _, v1 := range pls {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
}
上面程序的输出如下:
C C++
JavaScript
Go Rust
内存优化
切片保留对底层数组的引用。只要切片存在于内存中,数组就不能被垃圾回收。这在内存管理方便可能是值得关注的。假设我们有一个非常大的数组,而我们只需要处理它的一小部分,为此我们创建这个数组的一个切片,并处理这个切片。这里要注意的事情是,数组仍然存在于内存中,因为切片正在引用它。
解决该问题的一个方法是使用 copy 函数 func copy(dst, src []T) int
来创建该切片的一个拷贝。这样我们就可以使用这个新的切片,原来的数组可以被垃圾回收。
func countries() []string {
countries := []string{"USA", "Singapore", "Germany", "India", "Australia"}
neededCountries := countries[:len(countries)-2]
countriesCpy := make([]string, len(neededCountries))
copy(countriesCpy, neededCountries) //copies neededCountries to countriesCpy
return countriesCpy
}
func main() {
countriesNeeded := countries()
fmt.Println(countriesNeeded)
}
在上面程序中,第 9 行 neededCountries := countries[:len(countries)-2]
创建一个底层数组为 countries
并排除最后两个元素的切片。第 11 行将 neededCountries
拷贝到 countriesCpy
并在下一行返回 countriesCpy
。现在数组countries
可以被垃圾回收,因为 neededCountries
不再被引用。
系列文章来源于网络和本人总结,只作学习备忘使用。
本文参考:https://blog.csdn.net/u011304970/article/details/74938457