21天速成go-第五天

81 阅读8分钟

const 常量的用法

一开始看到 const a = 10 的时候,就愣了一下,这个不需要指定类型的吗? 实际上const 有两种定义方法,一种是直接指定类型,另外一种是根据赋值类型让编译器自动推断(go 1.9开始,你可以在声明常量的时候根据赋值自动推断而无需指定)

当常量的值由多个部分组成时,编译器会根据需要操作的类型来自动推断

    const s = "foo" + "bar" // 推断为 string

尽管类型推断提供了便利,但是定义常量时候最好还是显示指定类型

ioat 的用法

ioat 只在const定义里面有效,在每个新的const组中重置为0,可以出现在const的任意位置,但是一般都会放在开头

image.png

08指针

注意对于这么这么一段代码,直接输出b不是输出的值,而是输出指针地址,使用* 才能取值

func main() {

   a := 43

   fmt.Println(a)
   fmt.Println(&a)

   var b = &a

   fmt.Println(b)
   fmt.Println(*b)

   // the above code makes b a pointer to the memory address where an int is stored
   // b is of type "int pointer"
   // *int -- the * is part of the type -- b is of type *int
}

另外如果对*b修改的话,也是修改的a的值

*b = 42        // b says, "The value at this address, change it to 42"
fmt.Println(a) // 42

比如这样a就变成了42

之前纠结关于命名返回值还要写return的情况

image.png

如果返回没有返回值,就不需要写return了,达到了省略return的效果,但是一旦有了返回值的话,还是要显示指明return

09+10 循环

for 还有一种拆分的写法,这样看起来就类似于while的写法了

func main() {
   i := 0
   for i < 10 {
      fmt.Println(i)
      i++
   }
}

另外for 这里不写条件就变成while 1了,和while用法很像

func main() {
   i := 0
   for {
      fmt.Println(i)
      i++
   }
}

真的是和while 一样还能break

func main() {
   i := 0
   for {
      fmt.Println(i)
      if i >= 10 {
         break
      }
      i++
   }
}

同样的,里面还能写上continue的

func main() {
   i := 0
   for {
      i++
      if i%2 == 0 {
         continue
      }
      fmt.Println(i)
      if i >= 50 {
         break
      }
   }
}

所以go 里面是没有while和do-while循环的,全部只有for循环

func main(){
   i := 0
   while i < 10 {
      fmt.Println(i)
      i ++
   }
}

这样的while循环是会报错的

11 switch

Switch 里面主要是由 defalut 和 fallthrough

default 是当什么都没有匹配到的时候,才会执行,或者某些情况下只有一个default,其实也就是隐藏的什么其他条件都没有,也执行

fallthrough 是强制性执行下一条而不管是否匹配,但是必须是要进去到swtich 当中某个条件才行

另外 Switch 可以使多选的,多个匹配的情况,类似于

image.png

另外Switch还可以判断类型

type contact struct {
   greeting string
   name     string
}

// SwitchOnType works with interfaces
// we'll learn more about interfaces later
func SwitchOnType(x interface{}) {
   switch x.(type) { // this is an assert; asserting, "x is of this type"
   case int:
      fmt.Println("int")
   case string:
      fmt.Println("string")
   case contact:
      fmt.Println("contact")
   default:
      fmt.Println("unknown")

   }
}

func main() {
   SwitchOnType(7)
   SwitchOnType("McLeod")
   var t = contact{"Good to see you,", "Tim"}
   SwitchOnType(t)
   SwitchOnType(t.greeting)
   SwitchOnType(t.name)
   //fmt.Println(t.name.(type))
}

这段代码注意几个地方,第一,contact 不是字符串连接的函数,而是定义的一个结构体,不要搞混淆

第二,SwtichOneType 传参是一个 interface 接口类型,这个不一样

第三,我以为任意的变量 .(type) 就能获取其类型的,实际上不是, .(type) 只能应用于接口类型里面,其他类型是不能用的,所以我的 string类型的.(type) 是无法使用的

而且.(type)只能用于断言里面,不是直接输出的,而且 .(type) 是一种笼统的写法,还可以写成 .(int), .(string)

对于下面的这个代码

func main() {

   var a interface{} = 1
   var b interface{} = "ssss"

   if v, ok := a.(int); ok {
      fmt.Println("its type is ", v)
   } else {
      fmt.Println("its type is not int")
   }

   if v, ok := b.(string); ok {
      fmt.Println("its type is ", v)
   } else {
      fmt.Println("its type is not string")
   }

}

首先要注意的是变量的定义,定义的是interface类型,而且是用的等于号,第一次写的时候变成了

var a interface 1

这个是错误的

另外第二个问题,if 里面同时存在的赋值的时候,是使用的冒号,不是逗号 是 if v, ok := a.(int)冒号;

第三个问题,这里面输出的是

its type is  1
its type is  ssss

也就是说,输出的不是变量的类型,而是变量的值了,总结一下就是,类型断言 x.(type)要求

  1. 必须是一个接口类型
  2. 返回两个内容,第一个是被断言参数的值,第二个是内容是断言的成功还是失败 true or false

这里就涉及到了一个接口类型,interface 以及为什么可以 var a interface {} =1 这种写法 这是这样子的: interface {} 是定义了一个空的接口,本质上还是一个接口,但是这样子他就可以保存任意类型的值,这种空接口,常用在需要接受任意类型值的函数参数,或者当你要储存不同类型的值的时候

对方在使用的时候,可以通过类型判断,检查接口变量中值得类型,再把必要时把他转化回原始类型

接口可以包含方法,但是这个空接口就是不包含任何方法,可以认为是一个非常通用的容器,可以持有任意类型

空接口可以储存任意类型的值,通常用于变长参数列表来实现多种行为的场景

func collectValues(...interface{}) { // 这个函数可以接受任意数量的任意类型参数 }

提问:对于空接口类型,我可以储存其他的值吗,或者储存多个值 var a interface{} = 1 比如我同事储存1 和2 ,或者储存1 和 "string "

可以,但是你如果要储存多个同类型的值的话,那么就是数组,你要储存多个不同类型的值得话,那么就是要储存一个结构体来达到你的目的

比如如下代码


//type Data struct {
// var name string
// var id int
//}

type Data struct {
   name string
   id   int
}

func main() {

   var a interface{} = 1
   var b interface{} = "ssss"
   var c interface{} = []int{1, 2, 3, 4}
   var d interface{} = Data{
      name: "a",
      id:   1,
   }
   //var d interface{} = Data{
   // "a",
   // 1,
   //}

   if v, ok := a.(int); ok {
      fmt.Println("its type is ", v)
   } else {
      fmt.Println("its type is not int")
   }

   if v, ok := b.(string); ok {
      fmt.Println("its type is ", v)
   } else {
      fmt.Println("its type is not string")
   }

   if v, ok := c.([]int); ok {
      fmt.Println("its type is ", v)
   } else {
      fmt.Println("its type is not []int]")
   }

   if v, ok := d.(Data); ok {
      fmt.Println("its type is ", v)
   } else {
      fmt.Println("its type is not Data")
   }

}

注意几个点,第一,结构体定义的里面不需要写var 比如注释掉的结构体定义的部分,就是因为写了var Name string 导致出错的,直接 name string 即可

第二。Data 在定义的时候 带不带参数名的设置都可以,可以是 name: "a" 也可以是直接的a

另外注意他这里的 .(type) 这里面的括号是必要的,请不要遗漏掉

另外除了用接口之外,还有一种方法是用反射达到目的,用反射来查询type, 比如

import (
   "fmt"
   "reflect"
)


t := reflect.TypeOf(d)
fmt.Printf("d type is %v, return type is %T\n", t, reflect.TypeOf(t))

a = reflect.TypeOf(a)
fmt.Printf("a type is %v, return type is %T\n", a, reflect.TypeOf(a))


返回的内容是
d type is main.Data, return type is *reflect.rtype
a type is int, return type is *reflect.rtype

这里返回的就是类型的名字了,实际上返回的也不是类型的名字,返回的a 是一个 *reflect.rtype

另外空接口可以接受任意数量和任意类型的参数的一个实际例子见下面这个

package main

import (
	"fmt"
)

// Animal 接口定义了动物的行为
type Animal interface {
	MakeSound()
}

// Dog 结构体
type Dog struct {
	Name string
}

// 实现 Animal 接口的 MakeSound 方法
func (d *Dog) MakeSound() {
	fmt.Println("Woof!")
}

// Cat 结构体
type Cat struct {
	Name string
}

// 实现 Animal 接口的 MakeSound 方法
func (c *Cat) MakeSound() {
	fmt.Println("Meow!")
}

// MakeAnimalsSound 函数接受任何实现了 Animal 接口的动物
func MakeAnimalsSound(animals ...Animal) {
	for _, animal := range animals {
		animal.MakeSound()
	}
}

func main() {
	// 创建 Dog 和 Cat 的实例
	myDog := &Dog{Name: "Fido"}
	myCat := &Cat{Name: "Whiskers"}

	// 调用 MakeAnimalsSound 函数,传入 Dog 和 Cat 实例
	// 尽管它们是不同类型的值,但都实现了 Animal 接口
	MakeAnimalsSound(myDog, myCat)
}

12 if-else

if 里面赋值还有判断可以不是同一个进行,例如

func main() {

   b := true

   if food := "Chocolate"; b {
      fmt.Println(food)
   }

}

可以是这样分开的,另外注意在 if 里面定义的类型,只在if范围内生效,在后面如果输出 fmt.Println(food) 的话,是无效的,这个变量是没有的,准确的说,是在整个 if else范围内生效,比如

func main() {

   b := false

   if food := "Chocolate"; b {
      fmt.Println(food)
   } else {
      fmt.Println(food)
   }
   //fmt.Println(food)

}

13 - 13_exercise-solutions

最后一个文件夹里面的 go test -bench=".*" 的用法

import (
   "fmt"
   "testing"
)

func BenchmarkHello(b *testing.B) {
   counter := 0
   for i := 0; i < b.N; i++ {
      if i%3 == 0 {
         counter += i
      } else if i%5 == 0 {
         counter += i
      }
   }
   fmt.Println(counter)
}

注意需要引入testing 包,命令用法如下

go test -bench=".*" [package]

-bench=".*" 表示运行所有以Benchmark开头的函数,是一个正则表达式 package 如果不写的话,就是档期啊目录下所有包都运行

至于结果 21.4 ns/op 8 B/op 1 allocs/op

  • 平均每次调用的时间为 21.4 ns
  • 每次调用分配了 8 B 的内存。
  • 每次调用有 1 次内存分配。