day02

for

break

  • 跳出循环,包括break后面的语句也不会再执行
// break
for i := 0; i < 10; i++ {if i == 5 {break // 跳出循环,包括break后面的语句也不会再执行}fmt.Println(i)
}
fmt.Println("overbreak")

continue

  • 当符合条件时,本次循环跳过,进入下一个循环,注意本次循环的continue后的语句均不会执行
// continue
for i := 0; i < 10; i++ {if i == 5 {continue // 跳过i==5这次循环,本次循环的continue后的语句不会执行}fmt.Println(i)
}
fmt.Println("overcontinue")

switch

要点

  • 不用写break,默认就有break功能
  • default最多只能有一个,也可以不写
  • switch后面可以不写表达式,可以写在case后面
  • 可以在switch后面声明变量switch m := 3; m {},这种写法的m只作用在switch里面
  • case后面可以写表达式,可以写单个值,也可以写多个值(多个值用逗号分隔,表示符合其中一个即可)
  • fallthrough(不建议使用,就是不要用) 就是穿透,执行满足条件的case语句后,遇到fallthrough,就再执行下面那个case,是为了兼容C语言的case设计
 // switch// 不用写break,默认就有break功能// default最多只能有一个,也可以不写// switch后面可以不写表达式,可以写在case后面// 可以在switch后面声明变量switch m := 3; m {},这种写法的m只作用在switch里面// case后面可以写表达式,可以写单个值,也可以写多个值(多个值用逗号分隔,表示符合其中一个即可)// fallthrough(不建议使用,就是不要用) 就是穿透,执行满足条件的case语句后,遇到fallthrough,就再执行下面那个case,是为了兼容C语言的case设计var n = 3switch n {case 1:fmt.Println("大拇指")case 2:fmt.Println("食指")case 3:fmt.Println("中指")case 4:fmt.Println("无名指")case 5:fmt.Println("小拇指")default:fmt.Println("无效的数字")}// switch后面可以不写表达式,可以写在case后面switch {case n == 1:fmt.Println("大拇指")case n == 2:fmt.Println("食指")case n == 3:fmt.Println("中指")case n == 4:fmt.Println("无名指")case n == 5:fmt.Println("小拇指")default:fmt.Println("无效的数字")}// 可以在switch后面声明变量switch m := 3; m {case 1:fmt.Println("大拇指")case 2:fmt.Println("食指")case 3:fmt.Println("中指")case 4:fmt.Println("无名指")case 5:fmt.Println("小拇指")default:fmt.Println("无效的数字")}// case后面可以多值switch m := 3; m {case 1, 3, 5, 7:fmt.Println("奇数")case 2, 4, 6, 8:fmt.Println("偶数")}// fallthrough(不建议使用,就是不要用) 就是穿透,执行满足条件的case语句后,遇到fallthrough,就再执行下面那个case,是为了兼容C语言的case设计switch m := 2; m { // 这段会打印出b 和 ccase 1:fmt.Println("a")case 2:fmt.Println("b")fallthroughcase 3:fmt.Println("c")case 4:fmt.Println("d")case 5:fmt.Println("e")}

goto

  • 可以将执行顺序引导到标志语句
  • 很少用
 // goto语句,可以将执行顺序引导到标志语句for i := 0; i < 100; i++ {for j := 'A'; j < 'Z'; j++ {if j == 'C' {break // 这样使用break,只能跳出内层循环,如果当j为C的时候,想要完全跳出两个for循环,那么就可以使用goto语句}fmt.Printf("%v-%c\n", i, j)}}// goto语句可以跳出所有层的foe循环for i := 0; i < 100; i++ {for j := 'A'; j < 'Z'; j++ {if j == 'C' {goto nextStatement}fmt.Printf("%v-%c\n", i, j)}}
nextStatement:fmt.Println("我跳出了所有for循环,来到了这里,将从这继续往下执行")

操作符

要点

  • Go语言中,++和–是单独的语句,不是运算符,这句话的意思就是不能用a = b++,不能放在等号右边,只能使用b++

       a := 1a++fmt.Println(a)
    
  • // Go语言是强类型语言,所以==要类型也相同才为等于,左右类型不同,不是返回false,而是报错。比如b := "s" fmt.Println(a == b)这是会报错的

  • Go语言中的逻辑运算符 && || ! 跟js不同,他不会短路,只会返回true或false

  • 位运算符:对整数按照二进制位进行操作,注意:是对整数

    • & 两位是1才为1

    • | 有一位是1 就是1

    • ^ 异或:两个不同就为1

    • << 左移n位,就是二进制右边补n个0;左移n位就是乘以2的n次方, “a<<b”就是把a的各二进制位左移b个位数,高位丢弃,低位补0

    • >> 右移n位,就是二进制左边补n个0,右边的界限不变,溢出忽略掉;右移n位就是除以2的n次方取整数部分, “a>>b”就是把a的各二进制位右移b个位数

      fmt.Println(2 & 5)   // 010 与 101 ,结果为 0
      fmt.Println(2 | 5)   // 010 与 101, 二进制结果为111,转十进制,结果为7
      fmt.Println(2 ^ 5)   // 010 与 101, 二进制结果为111,转十进制为7
      fmt.Println(2 << 3)  // 16,因为2是10,左移3位就是10000,那就是16
      fmt.Println(10 << 4) // 就是10 * 2^4 = 160
      fmt.Println(10 >> 4) // 10的二进制是 1010,往右移四位,就是|1010,这里的|为最后一位分界线,移出去就不要了,舍弃,所以是0
      fmt.Println(10 >> 2) // 10的二进制是 1010,往右移两位,就是10|10,这里的|为最后一位分界线,移出去就不要了,舍弃,所以二进制是10,十进制结果为2
      
      • 特别注意:

        • 左移右移,不要移出范围

          var q = int8(1)      // int8只能存8位
          fmt.Println(q << 10) // 1左移10位,1000000000,超出了8位,取8位,就是0
          // 位运算实际应用在ip 权限 文件操作会讲到
          
  • 赋值运算

    • =
    • +=
    • -=
    • *=
    • /=
    • %=
    • <<=
    • >>=
    • &=
    • |=
    • ^=

数组

要点

  • 数组是存放元素的容器

  • 必须指定存放的元素的类型和容量(长度)

  • 数组的类型包含长度和类型,所以长度不同的数组,是不能比较的

  • 数组声明

    // 数组的声明
    var a1 [3]bool
    var a2 [4]bool
    fmt.Printf("a1:%T a2:%T\n", a1, a2) // [3]bool [4]bool
    
  • 数组初始化1

    // 数组的初始化
    // 如果不初始化,默认值为零值(布尔值:false,整型和浮点型都是0,字符串是"")
    // 1.初始化方式1
    a1 = [3]bool{true, true} // 想这种长度为3,但是初始化了两个元素,第三个就是零值,为false,所以结果为[true true false]
    fmt.Println(a1)          // [true true false]
    
  • 数组初始化2

    // 2.初始化方式2
    // ... 会根据初始值自动推断数组的长度是多少
    a3 := [...]int{1, 2, 3, 4, 5, 4, 3, 2, 1}
    fmt.Println(a3)
    
  • 数组初始化3

    // 3.初始化方式3
    // 指定某个索引的值
    // 如果指定索引的值,那么用...,长度就是指定的最大的索引值+1
    a4 := [5]int{0: 1, 4: 2}
    fmt.Println(a4) // [1 0 0 0 2]
    a5 := [...]int{0: 1, 4: 2}
    fmt.Println(a5) // [1 0 0 0 2]
    
  • 数组遍历

    • for或for…range遍历

      // 数组的遍历
      // 用for或for...range
      cities := [...]string{"北京", "上海", "杭州"}
      for i := 0; i < len(cities); i++ {fmt.Println(cities[i])
      }for index, city := range cities {fmt.Println(index, city)
      }
      
  • 多维数组

    // 多维数组
    var a11 [3][2]int
    a11 = [3][2]int{[2]int{0, 1},[2]int{1, 2},[2]int{2, 3},
    }
    fmt.Println(a11) // [[0 1] [1 2] [2 3]]
    
  • 多维数组的遍历

    // 多维数组的遍历
    for _, v := range a11 {for index, childV := range v {fmt.Println(index, childV) // 打印出六个}
    }
    
  • 重点:数组是值类型,不是引用类型

    // 重点:数组是值类型,不是引用类型
    b1 := [3]int{1, 2, 3}
    b2 := b1
    b2[0] = 100
    fmt.Println(b1, b2) // b1不会变是[1 2 3],因为数组是值类型,不是引用类型; b2是[100 2 3]
    

数组练习题

  • ​ 求数组[1, 3, 5, 7, 8]所有元素的和

    // 1. 求数组[1, 3, 5, 7, 8]所有元素的和
    a1 := [...]int{1, 3, 5, 7, 8}
    sum := 0
    for _, v := range a1 {sum += v
    }
    fmt.Printf("a1数组的和为%d\n", sum) // a1数组的和为24
    
  • 找出数组[1, 3, 5, 7, 8]中和为8的两个元素的下表分别是(0, 3)和(1, 2)

    // 2. 找出数组[1, 3, 5, 7, 8]中和为8的两个元素的下表分别是(0, 3)和(1, 2)
    for i, v := range a1 {for j := i + 1; j < len(a1); j++ {if v+a1[j] == 8 {fmt.Printf("(%d, %d)\n", i, j) // (0, 3) (1, 2)}}
    }
    

切片

要点

  • 切片的有点:是一个拥有相同类型元素的可变长度的度列。它是基于数组类型做的一层封装。灵活,支持自动扩容。

  • 切片内部结构(3部分):地址、长度、容量

  • 切片是引用类型 (注意:数组是值类型)

    a1 := [...]int{1, 3, 5, 7, 9, 11, 13}
    a1[6] = 1300    // 原数组改变,该数组基础上的切片也会改变,因为切片是引用类型
    fmt.Println("s6:", s6) // [7, 9, 11, 1300]
    fmt.Println("s8:", s8) // [1300]
    
  • 切片声明

    // 切片声明
    var s1 []int // 定义一个存放int类型元素的切片
    var s2 []string
    fmt.Println(s1, s2) // [] []
    
  • 切片的初始化

    // 切片的初始化
    s1 = []int{1, 2}
    s2 = []string{"北京", "上海", "杭州"}
    fmt.Println(s1, s2) // [1 2] [北京 上海 杭州]
    
  • 由数组得到切片

    // 由数组得到切片
    a1 := [...]int{1, 3, 5, 7, 9, 11, 13}
    s3 := a1[0:4]   // 基于一个数组切割,左闭右开,左包含右不包含
    fmt.Println(s3) // [1 3 5 7]
    s4 := a1[1:6]
    fmt.Println(s4)         // [3 5 7 9 11]
    s5 := a1[:4]            // 相当于[0:4]
    s6 := a1[3:]            // 相当于[3: len(a1)]  [7, 9, 11, 13]
    s7 := a1[:]             // 相当于[0: len(a1)]
    fmt.Println(s5, s6, s7) // [1, 3, 5, 7] [7, 9, 11, 13] [1, 3, 5, 7, 9, 11, 13]
    
  • 切片容量

    • 切片的容量:底层数组的容量,开始切的位置到原数组的末尾
    // 切片的容量指的是底层数组的容量,开始切的位置到原数组的末尾
    fmt.Printf("len(s5):%d cap(s5):%d\n", len(s5), cap(s5)) // len(s5):4 cap(s5):7
    fmt.Printf("len(s6):%d cap(s6):%d\n", len(s6), cap(s6)) // len(s6):4 cap(s6):4
    
  • 切片再切片

    // 切片再切片
    s8 := s6[3:]
    fmt.Println(s8)                                         // [13]
    fmt.Printf("len(s8):%d cap(s8):%d\n", len(s8), cap(s8)) // len(s8):1 cap(s8):1
    

通过make创建切片

  • make(类型, 长度, 容量), 如果容量不写,则跟长度一样

    s1 := make([]int, 5, 10)                                          // []int类型 5是长度, 10是容量fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) // s1=[0 0 0 0 0] len(s1)=5 cap(s1)=10
    
  • 切片的本质:

    • 切片不保存值,就是一个框,框住了一块连续的内存(数组),真正的数据都是保存在底层数组里的
  • nil与切片

    • 一个nil值的切片并没有底层数组

    • 一个nil值的切片的长度和容量都为0

    • 但是我们不能说一个长度和容量都为0的切片一定是nil

      var m1 []int                                 // 是一个切片,为nil,没有底层数组
      m2 := []int{}                                // 是一个切片,不为nil,为[]
      m3 := make([]int, 0)                         // 是一个切片,不为nil,为[]
      fmt.Println(m1 == nil, m2 == nil, m3 == nil) // true false false
      
  • 判断一个切片是否为空

    • 要用 len(s) == 0 来判断,不能用 s == nil 来判断
  • 切片的赋值

    sArr := [...]int{1, 3, 5}
    s3 := sArr[:]
    s4 := s3        // s3和s4都指向了同一个底层数组,注意,切片不保存值,只是一个框
    fmt.Println(s4) // [1 3 5]
    s3[0] = 1000
    fmt.Println(s3)   // [1000 3 5]
    fmt.Println(s4)   // [1000 3 5]
    fmt.Println(sArr) // [1000 3 5]
    // 上面这段代码告诉我们两点:
    // 切片是引用类型
    // 切片元素值的改变,会同步改变底层数组对应元素的值
    
  • 切片的遍历

    • 索引遍历

    • for range 遍历

      // 1. 索引遍历
      for i := 0; i < len(s3); i++ {fmt.Println(s3[i])
      }// 2. for range循环遍历
      for i, v := range s3 {fmt.Println(i, v)
      }
      

append为切片追加元素

  • 调用append函数,必须用切片变量接收返回值,因为当append追加元素的时候,如果底层数组放不下(超出底层数组的容量).Go就会把底层数组换一个更大的,这样需要有一个新的变量去接收新底层数组上的切片
  • append追加元素,原来的底层数组放不下的时候,Go就会把底层数组换一个更大的,这里就涉及到扩容策略
  • 扩容策略
  • 时机:发生在append()调用时候
  • 策略:
    • 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
    • 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
    • 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
    • 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。
  • 注意:切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int和string类型的处理方式就不一样。
// append() 为切片追加元素func main() {s1 := []string{"北京", "上海", "深圳"}fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) // s1=[北京 上海 深圳] len(s1)=3 cap(s1)=3// 为s1添加一个杭州// s1[3] = "广州" // 会执行报错,因为切片容量是3,赋值第四个元素超出了,所以报错// 调用append函数,必须用切片变量接收返回值,因为当append追加元素的时候,如果底层数组放不下(超出底层数组的容量).Go就会把底层数组换一个更大的,这样需要有一个新的变量去接收新底层数组上的切片// 重点: append追加元素,原来的底层数组放不下的时候,Go就会把底层数组换一个更大的,这里就涉及到扩容策略/*扩容策略:- 时机:发生在append()调用时候- 策略:- 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。- 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),- 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)- 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。- 注意:切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int和string类型的处理方式就不一样。*/s1 = append(s1, "杭州")fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) // s1=[北京 上海 深圳 杭州] len(s1)=4 cap(s1)=6s1 = append(s1, "广州", "成都")fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) // s1=[北京 上海 深圳 杭州 广州 成都] len(s1)=6 cap(s1)=6// 切片追加切片// 把ss的元素追加到s1中// ...表示拓展,拆开所有元素ss := []string{"武汉", "西安", "苏州"}s1 = append(s1, ss...)fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) // s1=[北京 上海 深圳 杭州 广州 成都 武汉 西安 苏州] len(s1)=9 cap(s1)=12}

copy()函数赋值切片

  • copy是值拷贝,不是引用拷贝
  • 且目标切片的长度是多少,拷贝过来就最多是多少个元素,注意是看长度,不是容量
// copy
// copy()函数复制切片func main() {a1 := []int{1, 3, 5}a2 := a1 // 赋值// var a3 []int // 这样声明变量是nil,没有空间,所以执行下面的copy是拷贝不进去的// copy(a3, a1) // 拷贝var a3 = make([]int, 3, 3)copy(a3, a1)            // 拷贝fmt.Println(a1, a2, a3) // [1 3 5] [1 3 5] [1 3 5]a1[0] = 100fmt.Println(a1, a2, a3) // [100 3 5] [100 3 5] [1 3 5]// 上面得出的结论是:// copy是值拷贝,不是引用拷贝// 且目标切片的长度是多少,拷贝过来就最多是多少个元素,注意是看长度,不是容量// 删除元素// 没有专门删除的方法// 把a1中索引为1的3删除oldArr := [...]int{1, 3, 5}aa1 := oldArr[:]fmt.Println("aa1", aa1)aa1 = append(aa1[:1], aa1[2:]...)fmt.Println(aa1) // [1 5]// 以下是重点,易错点fmt.Println(oldArr) // [1 5 5] // 删除操作后,切片变成了[1 5],对应底层数组的元素也变成了1和5,所以底层数组就是[1 5 5]// 记住一点:切片永远不存值,切片改的值永远是底层数组的值// 切片的删除,就相当于把切片的框缩短了,缩短后,再去套底层数组fmt.Println(cap(oldArr)) // 3 删除操作,底层数组的容量是不会变的
}

切片删除元素

见上面的代码

切片练习题

  • 切片排序

    • sort.Ints(切片)

      • 这个Ints是类型,可以换成strings等
// 1. append练习题1
var a = make([]int, 5, 10)
for i := 0; i < 10; i++ {a = append(a, i)
}
fmt.Println(a)      // [0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]
fmt.Println(cap(a)) // 20// 2. append练习题2
a1 := [...]int{1, 3, 5, 7, 9, 11, 13, 15, 17}
s1 := a1[:]// 删除元素3
s1 = append(s1[:1], s1[2:]...)
fmt.Println(s1) // [1 5 7 9 11 13 15 17]
fmt.Println(a1) // [1 5 7 9 11 13 15 17 17]// 数组排序 利用切片和sort.Ints()
var aa1 = [...]int{3, 7, 8, 9, 1}
sort.Ints(aa1[:])
fmt.Println(aa1) // [1 3 7 8 9]

指针

要点

  • Go语言中,指针只能读,不能修改,不能修改指针变量指向的地址

  • 两个要点

    • & 取地址

    • * 根据地址取值

      n := 18
      p := &n
      fmt.Println(p)        // 0xc0000a2058
      fmt.Printf("%T\n", p) // *int 说明p的类型是int类型的指针// 2. * 根据地址取值
      m := *p
      fmt.Println(m)        // 18
      fmt.Printf("%T\n", m) // int
      
  • new和make

    • new函数申请一个内存地址

      var a = new(int)       // 相当于 var a *int,但是区别是使用new会给a申请一块内存地址,而var a *int只是声明,没有内存地址
      fmt.Println(a)         // 0xc0000140e8
      fmt.Println(*a)        // 0
      *a = 100               // 去内存地址对应的值,赋值为100
      fmt.Println(*a)        // 100
      fmt.Printf("%T\n", a)  // *int
      fmt.Printf("%T\n", *a) // int
      
    • make函数申请一个内存地址

      • 与new的区别是,它只作用于slice、map和channel的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型。因为这三种类型就是引用类型,所以就没必要返回他们的指针了。

        var j map[string]string
        j = make(map[string]string, 3)
        fmt.Println(j)
        j["a"] = "a"
        j["b"] = "b"
        j["c"] = "c"
        fmt.Println(j) // map[a:a b:b c:c]
        
  • 面试题(重点

    • make和new的区别是什么?

      • make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
      • 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。
      • new很少用,一般给基本数据类型申请内存
    • new能作用在slice map channel上吗

      • 可以

        var mm = new(map[int]int) // 因为map本身就是引用类型,所以new一下就是变成了&map[],而不是*map[]
        fmt.Println(mm)           // &map[]
        fmt.Println(*mm)          // map
        *mm = map[int]int{1: 2}
        fmt.Println(mm)  // &map[1:2]
        fmt.Println(*mm) // map[1:2]
        
  • 必须要时刻注意的点

    • 对于引用类型的变量,我们在使用的时候,要做两步:

      • 声明它
      • 给它分配内存空间
    • 所以,不要引用变量一声明,就去使用它,会报错的,必须要分配内存空间

    • 举以下两个使用错误的例子:

      // 错误例子1:
      // var a *int // a是nil pointer,没有内存空间
      // *a = 100 // 执行到这一句就报错了:panic: runtime error: invalid memory address or nil pointer dereference
      // fmt.Println(*a)// 错误例子2:
      // var b map[string]int
      // b["哆瑞咪发"] = 100 // 执行到这一句就报错了:panic: assignment to entry in nil map
      // fmt.Println(b)
      

map

要点

  • map声明后,必须要分配空间

  • make声明一个切片,能自动扩容,但最好估算好该map容量,避免在程序运行期间再动态扩容,这样性能更高

  • map声明

    var m1 map[string]int
    // map声明后,必须要分配空间
    m1 = make(map[string]int, 10) // 容量,能自动扩容,最好估算好该map容量,避免在程序运行期间再动态扩容,这样性能更高
    m1["理想"] = 18
    m1["jiwuming"] = 35fmt.Println(m1) // map[jiwuming:35 理想:18]
    
  • map取值

    // 取值
    fmt.Println(m1["理想"]) // 18
    v, ok := m1["娜扎"]
    fmt.Println(v, ok) // 0 false
    if !ok {fmt.Println("查无此key")
    }
    
  • map的遍历

    for key, v := range m1 {fmt.Println(key, v)
    }
    
  • 删除键值对使用delete()

    // 删除键值对使用delete()
    delete(m1, "jiwuming")
    fmt.Println(m1) //map[理想:18]
    // 如果删除一个不存在的,则啥也不会发生,不进行操作
    

元素为map的切片

  • 面试题(特别易出错)

    // 元素类型为map的切片
    var s1 = make([]map[int]string, 1, 10)/*这里需要特别注意:var s1 = make([]map[int]string, 0, 10)s1[0][100] = "A"会报错,panic: runtime error: index out of range [0] with length 0原因:第一句只是创建了一个切片,但是长度为0,而第二句给第一个元素赋值,那么索引就越界了,就报了上面的错误如果将上面的0改为1,会怎么样?也就是:var s1 = make([]map[int]string, 1, 10)s1[0][100] = "A"会报错:panic: assignment to entry in nil map原因:没有对内部的map进行做初始化。第一句创建了切片,长度为1,但是这个切片的第0个,只是声明map[int]string,并没有创建空间,由于引用类型必须声明且创建空间,所以这时候会报错所以,正确的做法是:第一种:s1[0] = map[int]string{0: "yuhua"}第二种:s1[0] = make(map[int]string, 1)s1[0][100] = "A"*/
    // s1[0] = map[int]string{0: "yuhua"}
    s1[0] = make(map[int]string, 1)
    s1[0][100] = "A" // [map[100:A]]
    fmt.Println(s1)
    

值为切片的map

// 值为切片类型的map
var m1 = make(map[string][]int, 1)
m1["hello"] = []int{1, 2, 3}
fmt.Println(m1) // map[hello:[1 2 3]]

map的练习题

  • 练习题一:按照指定顺序遍历map

    • 思路:

      1. 先将map中所有的key取出
      2. 然后对key组成的切片进行排序
      3. 然后再循环这个key,取出对应的value
    • 举例: 创建100组随机的键值对构成map,然后有序遍历

      rand.Seed(time.Now().UnixNano()) // 初始化随机数种子var scoreMap = make(map[string]int, 200)for i := 0; i < 100; i++ {key := fmt.Sprintf("stu%.2d", i)value := rand.Intn(100) // 生成0-99的随机的帧数scoreMap[key] = value
      }// 取出所有的key存入切片中
      var keys = make([]string, 0, 100)
      for k := range scoreMap {keys = append(keys, k)
      }
      sort.Strings(keys)
      for _, keyVal := range keys {fmt.Printf("key:%s value:%d\n", keyVal, scoreMap[keyVal])
      }
      
  • 练习题二:写一个程序,统计一个字符串中每个单词出现的次数。比如: “how do you do”中how=1 do=2 you=1

    // 写一个程序,统计一个字符串中每个单词出现的次数。比如: “how do you do”中how=1 do=2 you=1
    words := "how do you do"
    wordsArr := strings.Split(words, " ")
    wordsMap := make(map[string]int, 1)
    for _, v := range wordsArr {_, ok := wordsMap[v]if !ok {wordsMap[v] = 1} else {wordsMap[v]++}
    }
    result := ""
    for key, v := range wordsMap {result += key + fmt.Sprintf("=%d ", v)
    }
    fmt.Println(result) // how=1 do=2 you=1
    
  • 练习题三:写一个程序,判断字符串是回文字符串

    • 回文字符串:字符串从左往右读和从右往左度是一样的,那么就是回文。比如:上海自来水来自海上

      func isHuiWen(str string) bool {// 把字符串转成rune类型对切片var strSlice = make([]rune, 0, len(str))for _, v := range str {strSlice = append(strSlice, v)}fmt.Println(strSlice) // [19978 28023 33258 26469 27700 26469 33258 28023 19978]for i := 0; i < len(strSlice)/2; i++ {if strSlice[i] != strSlice[len(strSlice)-1-i] {return false}}return true
      }
      fmt.Println(isHuiWen("上海自来水来自海上")) //true
      

函数

要点

  • 函数的定义

    func sum(x int, y int) (ret int) {return x + y
    }
    
  • 命名的返回值,就相当于在函数中声明了一个变量

  • 如果返回值是有名的,那么

    • 只写return,就返回返回值变量对应的值
    • 如果返回值是有名的, 但return后面写了东西,那么就返回return后面的
  • 多个返回值

    // 参数的类型简写
    func ret4(x, y int, m, n string, i, j bool) int {return x + y
    }
    
  • 可变长参数

    • 可变参数是一个切片

    • 可变长参数必须放在函数参数的最后

      // 可变长参数,可变参数是一个切片
      // 可变长参数必须放在函数参数的最后
      func ret5(x string, y ...int) {fmt.Println(x)fmt.Println(y) // 切片[]int
      }
      ret5("yh", 1, 2, 3) // yh [1 2 3]
      
  • Go语言中没有默认参数的概念

  • Go语言中传递的都是值类型

golang从入门到成仙【day02】相关推荐

  1. webgl入门到成仙【入门-04wegbl的实际绘图思路】

    04webgl的实际绘图思路 知识点 webgl的绘图思路 找一台电脑 浏览器里内置的webgl渲染引擎,负责渲染webgl图形,只认GLSL ES语言 找一块手绘板 程序对象,承载GLSL ES语言 ...

  2. Golang 汇编入门知识总结

    作者:ivansli,腾讯 IEG 运营开发工程师 在深入学习 Golang 的 runtime 和标准库实现的时候发现,如果对 Golang 汇编没有一定了解的话,很难深入了解其底层实现机制.在这里 ...

  3. 【Golang 快速入门】高级语法:反射 + 并发

    Golang 快速入门 Golang 进阶 反射 变量内置 Pair 结构 reflect 结构体标签 并发知识 基础知识 早期调度器的处理 GMP 模型 调度器的设计策略 并发编程 goroutin ...

  4. golang快速入门[8.3]-深入理解IEEE754浮点数

    前文 golang快速入门[1]-go语言导论 golang快速入门[2.1]-go语言开发环境配置-windows golang快速入门[2.2]-go语言开发环境配置-macOS golang快速 ...

  5. Golang Web入门(4):如何设计API

    Golang Web入门(4):如何设计API 摘要 在之前的几篇文章中,我们从如何实现最简单的HTTP服务器,到如何对路由进行改进,到如何增加中间件.总的来讲,我们已经把Web服务器相关的内容大概梳 ...

  6. Golang Web入门(3):如何优雅的设计中间件

    Golang Web入门(3):如何优雅的设计中间件 摘要 我们上篇文章已经可以实现一个性能较高,且支持RESTful风格的路由了.但是,在Web应用的开发中,我们还需要一些可以被扩展的功能. 因此, ...

  7. Golang Web入门(2):如何实现一个RESTful风格的路由

    Golang Web入门(2):如何实现一个RESTful风格的路由 摘要 在上一篇文章中,我们聊了聊在Golang中怎么实现一个Http服务器.但是在最后我们可以发现,固然DefaultServeM ...

  8. 【Golang 快速入门】项目实战:即时通信系统

    Golang 快速入门 即时通信系统 - 服务端 版本一:构建基础 Server 版本二:用户上线功能 版本三:用户消息广播机制 版本四:用户业务层封装 版本五:在线用户查询 版本六:修改用户名 版本 ...

  9. C语言—函数_成仙不问道

    函数 函数(function):完成特定任务的独立程序代码单. 函数组成:由函数头和函数体组成. 函数头:类型名 函数名(形式参数) 函数体:{语句:return 返回值} //典型函数模型类型名 函 ...

最新文章

  1. 每天导航超4亿公里,百度地图整合AI功能
  2. mysql三表查询数据重复_解决mybatis三表连接查询数据重复的问题
  3. C# 用户控件之温度计
  4. Redis缓存穿透 缓存击穿 缓存雪崩原因及其解决方案
  5. XAML和VBA 7规范发布
  6. 南安职业中专学校计算机专业,南安职专:国家级重点职业中专学校
  7. 一段比较好的加1操作。能够防止简单的++造成的溢出。
  8. 一道经典面试题的不同解法
  9. MAC OSX 正確地同時安裝 PYTHON 2.7 和 PYTHON3
  10. The Furthest Distance In The World
  11. CentOS 7下编译FreeSWITCH 1.6
  12. 马拦过河卒问题 (递推解法)
  13. C语言学习有感day01
  14. python 网站 批量 投票_python requests 简单实现易班自动登录,批量_文章发布,投票发布,评论,点赞,v2.0...
  15. PDF写出:使用fop输出为pdf格式文件的Demo
  16. 电脑上如何进行屏幕录像?--QVE屏幕录像
  17. 360奇舞团钟恒:选用Vue.js进行组件化开发,我们遇到了哪些坑?
  18. WIN32 opengl缩放、旋转、移动图形
  19. 2038年无数Java应用的崩溃
  20. 汤小丹计算机操作系统慕课版课后题答案第六章:虚拟储存器

热门文章

  1. 用python画爱心写一句话_python中用turtle画爱心表白
  2. [论文翻译]数据集的domian问题:Intramodality Domain Adaptation Using Self Ensembling and Adversarial Training
  3. Qt项目中,用QPainter进行绘制图形时,边角显示不完整问题的梳理
  4. 【线性代数】6-1:特征值介绍(Introduction to Eigenvalues)
  5. ea6500 v1 刷梅林_继续测试:Linksys EA6500 v1 的TT固件
  6. 苹果系统微信实况图照片发送-竞品分析初步思考
  7. Redisson 限流器 RRateLimiter的使用
  8. 吉林大学计算机系高级语言程序设计(C语言)期末题目及解答(上)
  9. linux系统取消时间同步,linux下时间同步的两种方法分享
  10. 商品搜索结果页用RecyclerView列表实现的单排和双排展示及切换