GO语言基础语法(下)
切片
切片的创建
元素类型为T的切片用[]T
表示。
package main
import (
"fmt"
)
func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:4] //创建从[1]到[3]的切片
fmt.Println(b)
}
语法a[start:end]
从数组a
中创建一个切片,从索引start
开始到索引end-1
结束。上面的程序a[1:4]
从索引1到索引3,创建数组a
的切片b
。因此切片b
有值[77 78 79]。
让我们看看另一种创建切片的方法。
package main
import (
"fmt"
)
func main() {
c := []int{6, 7, 8} //创建数组并返回一个该数组的切片引用
fmt.Println(c)
}
在上面的程序中,c := []int{6, 7, 8}
创建一个有3个整数的数组,并返回一个存储在c
中的切片引用。
切片的修改
切片不拥有自己的任何数据。它只是底层数组的一种表示。对切片所做的任何修改都将影响底层数组。
package main
import (
"fmt"
)
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)
}
代码行dslice := darr[2:5]
,从数组的索引2、3、4
创建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]
当许多切片共享相同的底层数组时,每个切片所做的更改,都将反映在数组中。
package main
import (
"fmt"
)
func main() {
numa := [3]int{78, 79 ,80}
nums1 := numa[:] //创建一个包含数组所有元素的slice
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 ums2", numa)
}
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]
切片的长度和容量
-
切片长度是切片中元素的数量。切片容量是所引用数组中,从切片起始索引开始到数组结尾的元素数。(切片容量用cap(切片名)获得)
package main import ( "fmt" ) 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)) //切片长度为2,容量为6 }
一个切片可以被重新切片,但不能超出它的容量,否则就会抛出运行时错误。
package main import ( "fmt" ) 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\n", len(fruitslice), cap(fruitslice)) //长度为2,容量为6 fruitslice = fruitslice[:cap(fruitslice)] //重新切片 fmt.Println("After re-slicing length is",len(fruitslice), "and capacity is",cap(fruitslice)) }
切片被重新切片,新切片的结束索引是源切片的容量,以上程序输出:
length of slice 2 capacity 6 After re-slicing length is 6 and capacity is 6
make创建切片
- make创建切片:可以使用函数
func make([]T, len, cap) []T
来创建切片,参数是类型、长度和容量。cap
参数是可选的,默认值为长度。make函数将创建一个数组并返回一个切片引用。
package main
import (
"fmt"
)
func main() {
i := make([]int, 5, 5)
fmt.Println(i)
}
当使用make创建切片时,元素值值默认为零。以上程序将输出[0 0 0 0 0]。
附加切片元素
我们已经知道数组的长度是固定的,不能增加。切片长度是动态的,可以使用append
函数将新元素添加到片中。append
函数的定义是func append(s []T, x...T) []T
。
函数定义中的x...T
,表示可变参数,参数类型为T
,传入的参数数量可变,这种函数称为可变参数函数。
当新元素被添加到切片中时,就会创建一个新的数组。将现有数组的元素复制到这个新数组中,并返回这个新数组的一个新切片引用。
package main
import (
"fmt"
)
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,给这个切片增加一个新元素,将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
- 切片类型的零值为
nil
。值为nil
的切片,其长度和容量为0。可以使用append
函数将值追加到nil
切片。
package main
import (
"fmt"
)
func main() {
var names []string //切片的零值是nil
if names == nil {
fmt.Println("slice is nil going to append")
names = append(names, "John", "Sebastian", "Vinay")
fmt.Println("names contents:",names)
}
}
在上面的程序中,名称是nil
,我们在名称后面附加了3个字符串。程序的输出是
slice is nil going to append
names contents: [John Sebastian Vinay]
- 也可以使用
...
符号将一个切片附加到另一个切片。
package main
import (
"fmt"
)
func main() {
veggies := []string{"potatoes","tomatoes","brinjal"}
fruits := []string{"oranges","apples"}
food := append(veggies, fruits...)
fmt.Println("food:",food)
}
输出: food: [potatoes tomatoes brinjal oranges apples]
切片函数参数
Go语言内部,切片的结构类型表示为:
type slice struct {
Length int
Capacity int
ZerothElement *byte
}
切片包含长度、容量和指向数组第0个元素的指针。当一个切片被传递给函数时,即使它是通过值传递的,指针变量也会指向相同的底层数组。
因此,当切片作为参数传递给函数时,在函数内部所做的更改,在函数外部也是可见的。
多维切片
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")
}
}
切片的内存优化
切片是对底层数组的引用。只要切片存在,就不能释放数组的内存,即不能对此数组进行垃圾收集。某些情况下,这会导致内存浪费。
解决这个问题的方法是使用复制函数func copy(dst, src []T) int
来复制那个切片,我们可以使用新的切片,原来的数组就可以被垃圾收集。
package main
import (
"fmt"
)
func countries() []string {
countries := []string{"China", "USA", "Singapore", "Germany", "India", "Australia"} //len : 6 cap : 6
neededCountries := countries[:len(countries)-2] //len : 4 cap : 6
countriesCpy := make([]string, len(neededCountries))
copy(countriesCpy, neededCountries) // 复制 neededCountries 到 countriesCpy
return countriesCpy //len : 4 cap : 4
}
func main() {
countriesNeeded := countries()
fmt.Println(countriesNeeded)
}
复制切片countriesCpy
后,数组countries
就可以被释放了。
变参函数
函数的参数个数通常是固定的,但变参函数的参数个数是可变的。如果函数定义的最后一个参数用省略号(…)作前缀,则表示该参数是变参,可以传入多个参数值。
只有函数的最后一个参数可以作为变参。
变参函数语法
func hello(a int, b ...int) {
}
在上面的函数中,参数b
是可变的,它的前缀是省略号,可以接受任意数量的参数值。这个函数的调用示例:
hello(1, 2) //将一个参数值“2”传递给b
hello(5, 6, 7, 8, 9) //将参数值“6、7、8、9”传递给b
对于变参,也可以不传入参数值。
hello(1)
变参只能放在最后。假设我们把变参放在前面,如下所示:
func hello(b ...int, a int) {
}
在上面的函数中,不可能将参数值传递给参数a
,因为传递的任何参数都将被分配给第一个参数b
,因为它是可变参数。
因此,变参只能出现在最后。
变参函数替代切片作为参数的优点
- 在函数调用时不需要创建切片。例如,
find(89, []int{89, 90, 95})
后面一个参数实际上创建了一个切片。 - 对于变参,可以不传入参数,但切片参数不能省略。例如,
find(87, []int{})
不能省略后一个参数,虽然该切片是空的。 - 变参函数比使用切片参数的函数可读性更好。
切片传递给变参函数
不能直接把切片作为变参参数值,传入变参函数。
但我们可以使用一种语法糖,**可以将一个切片传递给变参函数。必须在切片后面加上省略号…
,**这样切片就可以传递给函数。
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() {
nums := []int{89, 90, 95}
find(89, nums...)
}
变参函数修改传入的参数
package main
import (
"fmt"
)
func change(s ...string) {
s[0] = "Go"
s = append(s, "playground") //返回一个新的数组引用,s不再指向welcome
fmt.Println(s)
}
func main() {
welcome := []string{"hello", "world"}
change(welcome...)
fmt.Println(welcome)
}
// 输出:[GO world playground]
// [Go world]
Map
Map的创建
可以通过make
函数来创建map,函数定义:make(map[type of key]type of value)
,需要传入键和值的类型。(make可以理解为为申请为map分配空间)
personSalary := make(map[string]int)
上面的代码行创建了一个名为personSalary
的map,它具有string
键和int
值。
map的零值为nil
。如果试图将数据项添加到nil
值的map,将会报错。因此,必须先使用make
函数初始化map。
package main
import (
"fmt"
)
func main() {
var personSalary map[string]int
if personSalary == nil {
fmt.Println("map is nil. Going to make one.")
personSalary = make(map[string]int) //此时输出personSalary,结果为 map[]
}
}
在上面的程序中,personSalary
为nil
,因此使用make
函数初始化。输出:map is nil. Going to make one.
添加元素
向map(映射)中添加新项的语法与数组相同。下面的程序将一些新项目添加到personSalary
map中。
package main
import (
"fmt"
)
func main() {
personSalary := make(map[string]int)
personSalary["steve"] = 12000
personSalary["jamie"] = 15000
personSalary["mike"] = 9000
fmt.Println("personSalary map contents:", personSalary)
}
输出: personSalary map contents: map[steve:12000 jamie:15000 mike:9000]
也可以在声明过程中初始化映射。
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int {
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("personSalary map contents:", personSalary)
}
上面的程序声明了personSalary
,并在声明过程中添加了两个元素。稍后,添加了一个带有键mike
的元素。
程序输出
personSalary map contents: map[steve:12000 jamie:15000 mike:9000]
所有可以比较的类型,如布尔型、整数型、浮点型、复杂型、字符串型……也可以作为键。
访问元素
map[key]
是访问map元素的语法。
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
employee := "jamie"
fmt.Println("Salary of", employee, "is", personSalary[employee])
}
上面的程序检索并打印员工jamie的工资。该程序输出Salary of jamie is 15000
。
元素不存在,返回0值
如果一个元素不存在,映射将返回该元素类型的零值。例如, 访问personSalary
映射一个不存在的元素,那么将会返回int
的0
值。
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
employee := "jamie"
fmt.Println("Salary of", employee, "is", personSalary[employee])
fmt.Println("Salary of joe is", personSalary["joe"])
}
复制
以上程序的输出为
Salary of jamie is 15000
Salary of joe is 0
上面的程序将joe
的工资返回为0
, 说明键joe
不存在。程序也没有报任何运行时错误。
判断元素是否存在
如果我们想知道一个键是否存在于映射中,该怎么办?
value, ok := map[key]
上面的语法用于查明某个键是否在映射中存在。如果ok
为真,则该键存在,其值存于value
中,否则该键不存在。
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
newEmp := "joe"
value, ok := personSalary[newEmp]
if ok == true {
fmt.Println("Salary of", newEmp, "is", value)
} else {
fmt.Println(newEmp,"not found")
}
}
在上述程序中,value, ok := personSalary[newEmp]ok
中ok
值为false,因为joe
不存在。
程序会输出:
joe not found
遍历map中的元素
for
循环的range
形式可用于遍历映射中的所有元素。
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("All items of a map")
for key, value := range personSalary {
fmt.Printf("personSalary[%s] = %d\n", key, value)
}
}
以上程序输出,
All items of a map
personSalary[mike] = 9000
personSalary[steve] = 12000
personSalary[jamie] = 15000
注意,在使用for range
时,不能保证每次执行程序时,从映射中检索值的顺序都是相同的。
删除元素
delete(map, key)
是从map中删除键的语法。delete
函数不返回任何值。
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("map before deletion", personSalary)
delete(personSalary, "steve")
fmt.Println("map after deletion", personSalary)
}
上面的程序删除键“steve”并输出
map before deletion map[steve:12000 jamie:15000 mike:9000]
map after deletion map[mike:9000 jamie:15000]
长度
map(映射)的长度可以使用len
函数获取
Map为映射类型
与切片类似,映射也是引用类型。
当一个映射被赋值给新的变量时,它们都指向相同的内部数据结构。
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("Original person salary", personSalary)
newPersonSalary := personSalary
newPersonSalary["mike"] = 18000
fmt.Println("Person salary changed", personSalary)
}
程序输出,
Original person salary map[steve:12000 jamie:15000 mike:9000]
Person salary changed map[steve:12000 jamie:15000 mike:18000]
类似的情况还有将映射作为参数传递给函数。当在函数内部对映射进行更改时,外部同样被影响。
Map相等比较
不能使用==
操作符比较2个map。
==
只能用于检查映射是否为nil
。