GO语言基础语法(下)

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[:]中,起始值和结束值都省略了,它们的默认值分别是0len(numa)。切片nums1nums2共享同一个数组。程序的输出是

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,因为它是可变参数。

因此,变参只能出现在最后。

变参函数替代切片作为参数的优点

  1. 在函数调用时不需要创建切片。例如,find(89, []int{89, 90, 95})后面一个参数实际上创建了一个切片。
  2. 对于变参,可以不传入参数,但切片参数不能省略。例如,find(87, []int{})不能省略后一个参数,虽然该切片是空的。
  3. 变参函数比使用切片参数的函数可读性更好。

切片传递给变参函数

不能直接把切片作为变参参数值,传入变参函数。

但我们可以使用一种语法糖,**可以将一个切片传递给变参函数。必须在切片后面加上省略号,**这样切片就可以传递给函数。

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[]
    }
}

在上面的程序中,personSalarynil,因此使用make函数初始化。输出:map is nil. Going to make one.

添加元素

向map(映射)中添加新项的语法与数组相同。下面的程序将一些新项目添加到personSalarymap中。

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映射一个不存在的元素,那么将会返回int0值。

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]okok值为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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值