深入理解Golang一等公民(函数)、切片和Map | 青训营笔记

120 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第2篇笔记

函数 一等公民

 func div(a, b int) (q, r int) {
 ​
     return a / b, a % b
 }
  • 函数返回多个值时可以起名字
  • 仅用于非常简单的函数
  • 对于调用者而言没有区别

函数式编程语言

函数的参数,返回值,函数体内都可以有函数

 func apply(op func(int, int) int, a, b int) int {
     p := reflect.ValueOf(op).Pointer()
     opName := runtime.FuncForPC(p).Name() //拿到函数名
     fmt.Printf("Calling function %s with args "+"(%d, %d)\n", opName, a, b)
     return op(a, b)
 }
 ​
 func pow(a, b int) int {
     return int(math.Pow(float64(a), float64(b)))
 }
 func main() {
 ​
     fmt.Println(apply(pow, 3, 4))
     //Calling function main.pow with args (3, 4) 81
             
 }

可变参数列表

 func sum(values ...int) int {
     sum := 0
     for i := range values {
         sum += values[i]
     }
     return sum
 }
 func main() {
     fmt.Println(sum(1,2,3,4,5)) //15
 }

指针(重要)

  • 指针不能运算

如果用var x int 声明一个变量x,那么&x(取x变量的内存地址)表达式将产生一个指向该整数变量的指针

指针对应的数据类型是*int

若指针名字为p,那么可以说“p指针指向变量x”,或者“p指针保存了x变量的内存地址

 x := 1
 p := &x   //p, of type *int ,points to x
 fmt.Prinln(*p)  //"1"
 *p = 2   //equivalent to x = 2
 fmt.Println(x)  // "2"

参数传递

C++中

  • 值传递 : 拷贝
  • 引用传递: 不拷贝

Go中

Go语言中只有值传递(拷贝)一种方式

方法一

 func swap(a, b *int) {
     *b, *a = *a, *b
 }
 ​
 func main() {
     a, b := 3,4
     swap(&a, &b)
     fmt.Println(a, b) //4 3
 }

方法二

 func swap(a, b int) (int, int) {
     return b, a
 }
 ​
 func main() {
     a, b := 3,4
     swap(a, b)
     fmt.Println(a, b) //4 3
 }

数组Array

  • 数量写在类型前
  • range 的三种用法
  • 数组的遍历
 package main
 ​
 import "fmt"
 ​
 func main() {
     var arr1 [5]int
     arr2 := [3]int{1, 3, 5}          //需要告诉初值
     arr3 := [...]int{2, 4, 6, 8, 10} //
 ​
     var grid [4][5]int
 ​
     fmt.Println(arr1, arr2, arr3)
     fmt.Println(grid)
 ​
     for i, v := range arr3 {
         fmt.Println(i, v)
     }
     //for _, v := range arr3 {
     //fmt.Println(v)
     //}
 ​
 }

为什么要用range?

  • 意义明确美观,C++没有类似能力
  • Java Python :只能for each value,不能同时获取i,v
  • 除了数组,其他地方也会用到
  • 在Go语言中一般不直接使用数组,而是使用切片

数组是值类型

  • [10]int 和 [20]int 是不同类型
  • 调用func f(arr [10]int) 会 拷贝 数组
 func printArray(arr *[5]int) {
     arr[0] = 100
     for i, v := range arr {
         fmt.Println(i, v)
     }
 }
 ​
 func main() {
     var arr1 [5]int
     arr3 := [...]int{2,4,6,8,10}
     fmt.Println("printArray(&arr1)")
     printArray(&arr1)
     fmt.Println("printArray(&arr3)")
     printArray(&arr3)
     fmt.Println("arr1 and arr3")
     fmt.Println(arr1, arr3)
 }

切片(Slice) 重要!

 arr := [...]int {0,1,2,3,4,5,6,7}
 s := arr[2,6]   //2 3 4 5
 s [0] = 10   //arr的值变为[0 1 10 3 4 5 6 7]
  • Slice 本身没有数据,是对底层array的一个view

Slice 可以 Reslice

Slice的扩展

  • Slice可以向后扩展,不可以向前扩展
  • s[i] 不可以超越len(s), 向后扩展不可以超越底层数组cap(s)
 package main
 import "fmt"
 func main() {
     arr := [...]int {1,2,3,4,5,6,7}
     s1 := arr[2:6]
     s2 := s1[3:5]
     fmt.Println("s1= ", s1) //s1 = [2 3 4 5]         
     fmt.Println("s2= ", s2)  //s2 = [5 6] 
 }

Slice的扩展.jpg

Slice实现.jpg

Slice添加元素

  • 添加元素时如果超越cap, 系统会重新分配更大的底层数组,原来的数组(可能会被垃圾回收掉)
  • 由于值传递的关系,必须接受append的返回值
  • s = append(s, val)
  • s := make([]int, 16, 32) // len, cap
 package main
 import "fmt"
 func printSlice(s []int) {
     fmt.Printf("len=%d,cap=%d",len(s),cap(s))
 }
 func main(){
     s1 := []int{2,4,6,8}
     printSlice(s1)
     
     s2 := make([]int, 16)
     printSlice(s2)
     
     s3 := make([]int, 10, 32)
     printSlice(s3)
     
     //len=4, cap=4
     //len=16, cap=16
     //len=10, cap=32
 ​
 }

Slice复制元素

copy(destination,source)

Slice删除元素

 s2 := []int{2,4,6,8,0,0,0,0,0,0,0}
 fmt.Println("Deleting elements from slice") //delete 8
 s2 = append(s2[:3],s2[4:]...)
 ​
 fmt.Println("Popping from front")
 front := s2[0]
 s2 = s2[1:]
 ​
 fmt.Println("Popping from back")
 tail := s2[len(s2) -1]
 s2 = s2[:len(s2) - 1]