Go语言基础语法(下)

83 阅读7分钟

五、集合

array

数组

  1. 数组存放的是固定长度、相同类型的数据,连续存放
  2. 声明数组:arr := [5]string{"a", "b", "c", "d", "e"}
  3. [5]string和[6]string,不是同一类型,即长度也是数组类型的一部分
  4. 下标从 0 开始,访问元素:arr[0]
  5. 定义数组时长度可以忽略,go会自动推导出长度:arr:=[...]string{"a", "b"}
  6. 但是以上省略长度的写法,只适用于所有元素全部初始化的方式 如下的:arr := [5]string{1:"a", 3:"c"}表示,初始化下标1的值为"a",下标3的值为"c",数组长度为5,则其他位置会补“零值” 但是如果省略长度的话,整个数组的长度只有4
 arr := [...]string{1: "a", 3: "c"}
 fmt.Println(arr) // [ , a,  , c] 没有初始化的默认是空值
 ​
 /*
         循环遍历数组
 */
 for i := 0; i < len(arr); i++ {
     fmt.Print(arr[i], " ")
 }
 ​
 // for range 循环遍历数组
 for i, v := range arr {
     fmt.Printf("数组索引:%d, 值是:%s\n", i, v)
 }
 // 如果返回值用不到,可以使用下划线_代替,直接丢弃
 for _, v := range arr {
     fmt.Println(v)
 }

slice

切片

  1. 切片和数组类型,可以理解为动态数组,是基于数组实现的,对数组分割,得到切片

  2. slice := arr[st:ed],st:ed是下标,是左闭右开的

  3. 切片是具备三个字段的数据结构:data | len | cap

    data:指向数组的指针 len:长度 cap:容量

  4. 小技巧,st和ed都是可以省略的,

    arr[:3] = arr[0:3] arr[1:] = arr[1:5] arr[:] = arr[0:5]

  5. 切片修改,可以修改切片中的元素,发现被引用的数组也被修改了。进一步证明,切片的底层是数组

  6. 切片声明:有三种方式

    6.1 切割数组 slice := arr[2:5] 6.2 make函数声明 slice := make([]string, len, cap) len是切片长度,cap:切片容量 声明时,切片容量cap可以省略,切片容量不能小于切片长度 6.3 通过字面量方式初始化(和声明数组很类似) slice := []int{1, 2, 3} 此时长度 = 容量

  7. append函数,对切片追加元素 append 会自动处理切片容量不足需要扩容的问题。

    7.1 追加一个元素 slice2 := append(slice1, "F") 7.2 追加多个元素 slice2 := append(slice1, "E", "F") 7.3 追加另一个切片 slice2 := append(slice1, slice3....) 小技巧:在创建新切片的时候,最好要让新切片的长度和容量一样, 这样在追加操作的时候就会生成新的底层数组,从而和原有数组分离, 就不会因为共用底层数组导致修改内容的时候影响多个切片。

  8. for range 循环遍历切片

  9. 因为切片效率高,一般都用切片

 arr := [...]int{1, 2, 3, 4, 5}
 sli := arr[1:4]
 fmt.Println(sli) // [2, 3, 4]
 ​
 // 循环遍历切片
 for _, v := range sli {
     fmt.Println(v)
 }

map

  1. map是无序的K-V键值对集合,所有的key必须为同一类型,所有的value也必须是同一类型。map[K]V

  2. key的类型必须要支持 == 运算符,这样才能判断元素是否存在,并且保证key的唯一

  3. 声明map

    3.1 make函数 mp := make(map[string]int) 声明了 key类型为string,value类型为int的map 3.2 通过字面量初始化 mp := map[string]int{"赛涵", 20}

  4. 添加键值对

    mp["赛涵"] = 21 key是唯一的,如果key已经存在,则更新value的值

  5. map的获取

    a := mp["赛涵"] // 通过key来获取value的值

    map可以获取不存在的K-V键值对,如果key不存在,则返回value类型的“零值” map的[] 返回两个值,1.对应的value,2.bool值,标志key是否存在

  6. 删除键值对

    使用delete函数:delete(mp, Key) 第一个参数是map,第二个参数是要删除的K-V对的Key

  7. 遍历map

    使用 for range 遍历,有两个返回值:Key和Value 注意:

    • map的遍历是无序的,每次遍历的顺序可能会不同
    • for range map 时可以只要一个返回值,默认是key
  8. map的大小

    map只有长度,没有容量,可以一直添加元素

 // 声明
 mp := make(map[string]int)
 ​
 mp["赛涵"] = 20
 a := mp["赛涵"]
 fmt.Println(a)
 ​
 // 取出两个值:value、是否存在
 age, ok := mp["saihan"]
 if ok {
     fmt.Println(age)
 } else {
     fmt.Println("不存在")
 }
 ​
 // 如果key已存在:赋值,key不存在:添加
 mp["saihan"] = 18
 mp["Changersh"] = 20
 ​
 // for range 遍历
 for k, v := range mp {
     fmt.Println(k, v)
 }

六、函数与方法

函数

函数:

  1. 函数声明

    func funcName(params) result{}

    1.1 params 是定义形参的变量名、类型,有0~多个参数 1.2 result是函数的返回值,用于定义返回值的类型,如果没有返回值,则可以省略 1.3 如果参数类型都是一样的,可以只写最后一个的参数类型

  2. 多值返回

    go支持多个返回值

  3. 命名返回参数

    可以给函数的返回值命名和类型,这个名字可以和参数一样在函数体内使用 都进行命名之后,就可以忽略return之后的返回值了,直接只写一个return即可

  4. 可变参数

    func name(params...int)(){} 在参数类型前面加三个点...即可

  5. 包级函数

    同一个包中的函数,哪怕是私有的,也可被调用,不同包中的函数如果要被调用,必须是公有的

    5.1 函数名称首字母大写,代表公有函数,不同的包可以调用 5.2 函数名称首字母小写,代表私有函数,只有在同一个包中才可调用 5.3 任何一个函数都会属于某个包

  6. 匿名函数和闭包

    1. 匿名函数就是没有名字的函数 fun main(){ sum := func(a, b int) int{ return a + b } } 上面这个就是匿名函数,sum是数据类型为函数类型的变量,并不是函数名 通过sum,我们可以对匿名函数进行调用 sum(1, 2)
    2. 闭包 通过匿名函数,我们可以进行函数嵌套
 func main() {
     // 调用函数
     fmt.Println(sum(5, 10))
 ​
     fmt.Println(sum1(5, -6))
 ​
     fmt.Println(sum2(3, 4, 5, 5, 7, 8, 2, 5))
 ​
     // 函数嵌套
     cl := closure()
     fmt.Println(cl()) // 1
     fmt.Println(cl()) // 2
     fmt.Println(cl()) // 3
 }
 ​
 func sum(a int, b int) int {
     // 计算两数之和
     return a + b
 }
 ​
 // 测试多个返回值
 func sum1(a, b int) (int, error) {
     // 要求 a b都要是非负数,否则有异常
     if a < 0 || b < 0 {
         return 0, errors.New("a或者b不能为负数")
     }
 ​
     return a + b, nil
 }
 ​
 // 可变参数 + 返回值命名
 func sum2(params ...int) (sum int, err error) {
     for a := range params {
         if a < 0 {
             sum = 0
             err = errors.New("不能为负值")
             return
         }
         sum += a
     }
     // 返回值命名后,函数内可以使用
     // 返回值只写 return即可
     return
 }
 ​
 /*
     函数closure返回了一个匿名函数,该匿名函数也被称为闭包。
     闭包函数内部定义了变量i并初始化为0。然后,闭包函数返回另一个匿名函数,该函数会递增变量i的值,并将其作为结果返回。
 ​
     闭包函数通过捕获周围作用域内的变量(在本例中是变量i),使得在每次调用闭包函数时,可以访问和修改该变量的值,并保留状态。
     这种特性使得闭包函数在某些情况下非常有用,例如计数器或迭代器等场景
 */
 func closure() func() int {
     i := 0
     return func() int {
         i++
         return i
     }
 }
 ​

方法

  1. 方法和函数类似,但是方法必须要有一个 接收者。这个接收者是一个类型,这样这个方法和该类型联系起来,成为该类型的方法。

  2. 定义

    type Age int // type 定义新类型,约等于int,类似重命名

    func (age Age) String(){}

    String就是 Age 类型的方法,类型Age是方法的接收者

  3. 值类型接收者和指针类型接收者

    3.1 值类型接收者 就是上面的形式 3.2 指针类型接收者 func (age *Age) Modify(){ *age =Age(30) }

  4. 所以感觉方法就相当于我们新建的类型的方法,相当于我们新建了一个“类”?

  5. 在调用方法的时候,传递的接收者本质上都是副本,只不过一个是值的副本,一个是指向这个值的指针的副本

  6. 其实像上面的指针类型接收者,调用的时候应该用指针调用,我们下面是值调用,也可以使用,是因为go语言自动帮我们转化了

  7. Go 语言编译器帮我们自动做的事情:

    7.1 如果使用一个值类型变量调用指针类型接收者的方法,Go 语言编译器会自动帮我们取指针调用,以满足指针接收者的要求。 7.2 同样的原理,如果使用一个指针类型变量调用值类型接收者的方法,Go 语言编译器会自动帮我们解引用调用,以满足值类型接收者的要求。

总结: 在 Go 语言中,虽然存在函数和方法两个概念,但是它们基本相同,不同的是所属的对象。 函数属于一个包,方法属于一个类型,所以方法也可以简单地理解为和一个类型关联的函数。 小提示:不管方法是否有参数,通过方法表达式调用,第一个参数必须是接收者,然后才是方法自身的参数。

 func main() {
     age := Age(25) // 强转为 Age类型
     age.String() // 25
 ​
     (&age).Modify() // 30
     age.String()
 ​
     // 将方法赋值给变量
     Aa := Age.String
     // 传入一个接收者进行调用
     Aa(age)
 }
 ​
 type Age int
 ​
 func (age Age) String() {
     fmt.Println(age)
 }
 ​
 // 指针类型接收者
 func (age *Age) Modify() {
     *age = Age(30)
 }