一.数组
1.1 数组的声明
1.1.1 常规声明
var a [3]Type
例如:
package main
import (
"fmt"
)
func main() {
var a [3]int
fmt.Println(a)
}
/* outputs:
[0 0 0]
*/
数组默认为了[0 0 0]
1.1.2 短变量形式的初始化
刚才那样的初始化可以用该语句代替:a := [3]int{0, 0, 0}
当然你可以填写需要的值
若后面的常量并没有包含全部的Size
,那么还是会由0代替:a := [3]int{12}
则生成的是[12 0 0]
。
若初始化数组的常量是简单的,那么可以用...
让编译器去推导size
:a := [3]int{0, 0, 0}
,此时a还是一个长度为3的数组。
1.2 数组是一种特殊的类型
目前所用用到的数组都是采用的一个实例化的变量,而并非引用,如同这样:
package main
import "fmt"
func main() {
a := [...]int{1, 2, 3, 4}
b := a
b[0] = 2
fmt.Println("a is ", a)
fmt.Println("b is ", b)
}
/* outputs:
a is [1 2 3 4]
b is [2 2 3 4]
*/
1.3 数组的遍历
1.3.1 常规方法
一般所有数组都通用len
获取数组长度的方法,通过遍历下标去遍历数组:
package main
import "fmt"
func main() {
a := [...]int{4, 3, 2, 1}
for i := 0; i < len(a); i++ {
fmt.Printf("No.%d %d\n", i, a[i])
}
}
1.3.2 range方法
通过这种类似于迭代器的方式去遍历数组,也可以得到和上例相同的结果:
package main
import "fmt"
func main() {
a := [...]int{4, 3, 2, 1}
for index, value := range a{
fmt.Printf("No.%d %d\n", index, value)
}
}
如果你只需要index
和value
中的一个,那么用空标识符_
代替不需要的那个元素:
例如,我只需要值:for _, value := range a {
或者 for index := range a {
,且这个index
和value
均为拷贝
1.4 二维数组
二维数组类似于其他语言,那么看这个example
即可:
package main
import (
"fmt"
)
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)
}
二.切片
这里的切片又不是Python
或者·matlab
那样的切片,Go
语言的切片是对原数组的部分的引用
!!!
引用!!!
2.1 创建切片
2.1.1 已知数组创建切片
切片是左闭右开,实际引用的范围应该是[start, end - 1]
package main
import (
"fmt"
)
func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:4]
fmt.Println(b)
}
/* outputs:
[77 78 79]
*/
2.1.2 隐形创建一个数组并返回其切片
当我们省略掉初始化数组的长度时:
veggies := []string{"potatoes","tomatoes","brinjal"}
编译器会给我们创建一个数组,并返回其切片
2.2 修改切片
因为是引用,所以对切片的修改会作用到原数组:
package main
import (
"fmt"
)
func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:4]
fmt.Println("A_before is ", a)
b[1]++
fmt.Println("A_after is ", a)
}
/* outputs
A_before is [76 77 78 79 80]
A_after is [76 77 79 79 80]
*/
2.3 切片的长度与容量
package main
import (
"fmt"
)
func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:3]
fmt.Printf("length of slice %d capacity %d\n", len(b), cap(b))
}
/* outputs
length of slice 2 capacity 4
*/
然后我们把var b []int = a[1:3]
改为var b []int = a[1:4]
,此时的输出则是:length of slice 3 capacity 4
。
继续我们把var b []int = a[1:4]
改为var b []int = a[2:4]
,此时的输出则是:length of slice 2 capacity 3
我们可以看到:
- 切片的长度是由切片引用的长度决定的
- 切片的容量是原数组的
len
减去切片起始的下标,代表切片的最大长度,这样就比较好理解了。
2.4 修改切片长度
通过再次对切片赋值,我们可以实现切片的长度修改,但是永远不能访问到第一次创建切片之外的元素
package main
import (
"fmt"
)
func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:3]
fmt.Println("B is ", b)
b = b[0:1]
fmt.Println("B is ", b)
b = b[0:4]
fmt.Println("B is ", b)
}
/* outputs
B is [77 78]
B is [77]
B is [77 78 79 80]
*/
例如这个例子,我们可以访问到77(包括77)后面的所有元素,但是并不能访问到76
2.5 使用make创建切片
func make([]T, len, cap) []T
可以用来创建切片,该函数接受长度和容量作为参数,返回切片。容量是可选的,默认与长度相同。使用 make 函数将会创建一个数组并返回它的切片。
package main
import (
"fmt"
)
func main() {
i := make([]int, 5, 5)
fmt.Println(i)
}
2.6 追加元素到切片
2.6.1 追加一个元素
使用append
追加元素到切片,其用法为:append(s []T, x ...T) []T
先看这段代码:
package main
import (
"fmt"
)
func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int= a[:]
b[0] = 1
fmt.Println("A is ", a)
fmt.Println("B is ", b, "cap is", cap(b))
b = append(b, 81)
b[0] = 0
fmt.Println("B is ", b, "cap is", cap(b))
fmt.Println("A is ", a)
}
/* outputs
A is [1 77 78 79 80]
B is [1 77 78 79 80] cap is 5
B is [0 77 78 79 80 81] cap is 10
A is [1 77 78 79 80]
*/
好奇怪,为什么此时的B又不是A的引用了?
因为:
当append
超出切片的容量时,append
将会在其内部创建新的数组,该数组的大小是原切片容量的 2 倍。最后 append
返回这个数组的全切片,即从 0 到 length - 1 的切片)真是好奇怪的特性,对其他语言的使用者非常不友好
2.6.2 追加一个切片
如果我们要合并两个切片,我们可以使用...
且鉴于go的特性,我们就不创建数组了:
package main
import (
"fmt"
)
func main() {
veggies := []string{"potatoes","tomatoes","brinjal"}
fruits := []string{"oranges","apples"}
food := append(veggies, fruits...)
fmt.Println("food:",food)
}
/* outputs
food: [potatoes tomatoes brinjal oranges apples]
*/
2.7 切片作为参数
切片的结构可以看做:
type slice struct {
Length int
Capacity int
ZerothElement *byte
}
可以看到切片包含长度、容量、以及一个指向首元素的指针。所以我们可以通过这种方式传递实参
package main
import (
"fmt"
)
func add (vals []int) {
for index := range vals {
vals[index]++
}
}
func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int= a[:]
fmt.Println("A is ", a)
add(b)
fmt.Println("A is ", a)
}
/* outputs
A is [76 77 78 79 80]
A is [77 78 79 80 81]
*/
2.8 多维切片
还是用一个例子表示:
package main
import (
"fmt"
)
func main() {
pls := [][]string {
{"C", "C++"},
{"JavaScript"},
{"Go", "Rust"},
}
for _, v1 := range pls {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
}
/* outputs
C C++
JavaScript
Go Rust
*/
2.9 内存优化
因为数组只是引用,不管你怎么引用,数组一直都在内存里。如果我们只需要这个数组的一部分,那么我们可以使用copy(dst, src []T) int
来拷贝一个数组,然后触发回收机制,把原来那个长数组回收。
package main
import (
"fmt"
)
func add (vals []int) {
for index := range vals {
vals[index]++
}
}
func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int= a[1:3]
c := make([]int, len(b))
copy(c, b)
c[1] = 2
fmt.Println("C is ", c)
fmt.Println("A is ", a)
}
/* outputs
C is [77 2]
A is [76 77 78 79 80]
*/
理论上,如果不输出,此时的a
所使用的内存已经会被回收。
三.可变参数
变参函数是指可以接收可变数量的参数的函数。
如果一个函数的最后一个参数由 ...T
表示,则表示该函数可以接收任意数量的类型为 T
的参数。
例如:
package main
import (
"fmt"
)
func find(num int, nums ...int) {
fmt.Printf("type of nums is %T\n", nums)
found := false
for i, v := range nums {
if v == num {
fmt.Println(num, "found at index", i, "in", nums)
found = true
}
}
if !found {
fmt.Println(num, "not found in ", nums)
}
fmt.Printf("\n")
}
func main() {
find(89, 89, 90, 95)
find(45, 56, 67, 45, 90, 109)
find(78, 38, 56, 98)
find(87)
}
/* outputs
type of nums is []int
89 found at index 0 in [89 90 95]
type of nums is []int
45 found at index 2 in [56 67 45 90 109]
type of nums is []int
78 not found in [38 56 98]
type of nums is []int
87 not found in []
*/