golang从入门到成仙【day02】

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 = 3
	switch 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 和 c
	case 1:
		fmt.Println("a")
	case 2:
		fmt.Println("b")
		fallthrough
	case 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 := 1
    	a++
    	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)=6
   s1 = 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] = 100
	fmt.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"] = 35
    
    fmt.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语言中传递的都是值类型

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值