Golang语法笔记
const & iota
完全掌握iota:https://studygolang.com/articles/22468?fr=sidebar
- 不同 const 定义块互不干扰;
- 所有注释行和空行全部忽略;
- 没有表达式的常量定义复用上一行的表达式;
- 从第一行开始,iota 从 0 逐行加一;
// 定义数量级,也可以定义为整型
type ByteSize float64
const (
_ = iota // ignore first value by assigning to blank identifier
KB ByteSize = 1 << (10 * iota) // 1 << (10*1)
MB // 1 << (10*2)
GB // 1 << (10*3)
TB // 1 << (10*4)
PB // 1 << (10*5)
EB // 1 << (10*6)
ZB // 1 << (10*7)
YB // 1 << (10*8)
)
数组 & 切片 & map
数组是值类型,切片和map都是引用类型;
值类型:赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。
数组
https://www.liwenzhou.com/posts/Go/05_array/
数组的长度必须是常量,并且长度是数组类型的一部分。一旦定义,长度不能变。 [5]int
和[10]int
是不同的类型。
特殊:
- 一般情况下我们可以让编译器根据初始值的个数自行推断数组的长度,使用
[...]
var numArray = [...]int{1, 2}
fmt.Printf("type of numArray:%T\n", numArray) //type of numArray:[2]int
- 使用指定索引值的方式来初始化数组
func main() {
a := [...]int{1: 1, 3: 5} // a[1]=1, a[3]=5
fmt.Println(a) // [0 1 0 5]
fmt.Printf("type of a:%T\n", a) //type of a:[4]int
}
- 数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。
切片
https://www.liwenzhou.com/posts/Go/06_slice/
切片是一个引用类型,它的内部结构包含地址
、长度
和容量
。切片一般用于快速地操作一块数据集合。
引用类型:特指slice、map、channel这三种预定义类型。引用类型拥有更复杂的存储结构:(1)分配内存 (2)初始化一系列属性等;一个引用类型的变量r1存储的是r1的值所在的内存地址。
append 使用需要重点关注:
https://www.liwenzhou.com/posts/Go/06_slice/#autoid-2-4-0 特别是内存地址的变化,那种情况下变化;
用一个例子来说明:
func main() {
type Map map[string][]int
m := make(Map)
s := []int{1, 2}
s = append(s, 3) // 此处s和上一步的s指向的内存地址发生改变,因为“扩容”了
fmt.Printf("%+v\n", s) // 123
m["q1mi"] = s // s和m指向的底层数组地址是一样的
s = append(s[:1], s[2:]...) // 这一步相当于删除切片s的第二个元素;
fmt.Printf("%+v\n", s) // s切片:1,3;底层数组此时是:1 3 3
fmt.Printf("%+v\n", m["q1mi"]) // 133
}
// 稍微变换下,分析结果是多少;
func main() {
type Map map[string][]int
m := make(Map)
s := []int{1, 2}
s = append(s, 3)
fmt.Printf("%+v\n", s) // [1 2 3]
m["q1mi"] = s
s = append(s, 1,1,1,1,1,1) // 底层数组相当于“扩容”,s切片指向的内存地址发生改变了,就和m的不一样了
fmt.Printf("%+v\n", s) // [1 2 3 1 1 1 1 1 1]
fmt.Printf("%+v\n", m["q1mi"]) // [1 2 3]
}
切片的长度和容量:
- 长度len(arr),切片有值或者初始化的部分;
- 容量cap(arr),切片一共能存几个;
// make([]T, size, cap)
slice := make([]int, 3, 5)
// slice : [0,0,0] 但他最多存5个;cap不指明的话默认和len一致;
切片的本质就是对底层数组的封装
//请使用内置的sort包对数组var a = [...]int{3, 7, 8, 9, 1}进行排序
func main() {
var a = [...]int{3, 7, 8, 9, 1} // 此时a是数组
b := a[:] // 此时a,b都是切片,指向的底层数组,
sort.Ints(b) //对b排序,就是对底层数组排序
fmt.Println(a) // 所以打印的a,b 都是{1,3,}
}
map
https://www.liwenzhou.com/posts/Go/08_map/
- Go语言中的map是引用类型,必须初始化才能使用。
函数
https://www.liwenzhou.com/posts/Go/09_function/
可变参数
-
可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加
...
来标识。注意:可变参数通常要作为函数的最后一个参数。
func intSum2(y int, x ...int) int {
fmt.Println(x) //x是一个切片
sum := 0
for _, v := range x {
sum = sum + v
}
return sum
}
return
- 在Go语言的函数中
return
语句在底层并不是原子操作;
https://www.cnblogs.com/aiandbigdata/p/10822123.html
1、第一步给返回值赋值(若是有名返回值直接赋值,匿名返回值 则 先声明再 赋值) ;
2、第二步调用RET返回指令并传入返回值,RET会检查是否存在defer语句,若存 在就先逆序插播 defer语句 ;
3、最后 RET 携带返回值退出函数 。
func f1() int {} // 匿名返回值
func f2() (x int) {} // 有名返回值
defer
Go语言中的defer
语句会将其后面跟随的语句进行延迟处理。在defer
归属的函数即将返回时,将延迟处理的语句按defer
定义的逆序进行执行,也就是说,先defer
的后执行,后defer
的,先执行。
return-defer-闭包的综合应用
package main
import "fmt"
func f1() int { // 匿名返回值
x := 5
defer func() { // step2: 有defer,先执行defer里的,x++ x=6
x++
}()
return x // step1: 先声明一个中间变量tmp再把x赋值给tmp,
} // step3: 返回tmp也就是5;
func f2() (x int) { // 有名返回值
defer func() { // step2: 有defer,先执行defer里的 x++ x=8
x++
}()
return 7 // step1: 有名返回值,直接赋值x=7
} // step3: 最终返回8
func f3() (y int) { // 有名返回值y
x := 5
defer func() { // step2: 有defer,先执行defer里的 x++ x=6
x++
}()
return x // step1: 有名返回值,直接赋值y=x=5
} // step3: 最终返回y是5
func f4() (x int) { // 有名返回值x
defer func(x int) { // step2: defer的匿名函数里的参数也是x,这个相当于匿名函数内的局部变量(上面几个是闭包的概念);而不是f4里的x
x++
}(x)
return 5 // step1: 有名返回值,直接赋值x=5
} // step3: 最终返回x是5
func main() {
fmt.Println(f1()) // 5
fmt.Println(f2()) // 5
fmt.Println(f3()) // 0
fmt.Println(f4()) // 5
}
面试题:defer
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y))
x = 10
defer calc("BB", x, calc("B", x, y))
y = 20
}
// 程序运行到defer,先defer后执行,后defer先执行
// defer calc("AA", x, calc("A", x, y)) 执行到这个以后要先执行 calc("A", x, y)即:calc("A",1,2) 然后把calc("AA", 1, 3)加入defer栈
// defer calc("BB", x, calc("B", x, y)), 先执行calc("B", x, y)即calc("B", 10, 2), 然后把calc("BB", 10, 12)加入defer栈
// 执行后入defer栈中的 calc("BB", 10, 12)
// 再执行defer栈中的 calc("AA", 1, 3)
闭包=函数+引用环境
指针
https://www.liwenzhou.com/posts/Go/07_pointer/
Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间。要分配内存,就引出来今天的new和make。 Go语言中new和make是内建的两个函数,主要用来分配内存。
// 未分配内存空间案例
func main() {
var a *int // 只是声明了变量,但未进行初始化也就是未分配内存;
*a = 100
fmt.Println(*a)
var b map[string]int
b["aaa"] = 100
fmt.Println(b)
}
new & make的异同
- 二者都是用来做内存分配的。
- make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
- 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。
new
func new(Type) *Type
func main() {
a := new(int)
*a = 100
fmt.Println(*a)
}
make
make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。
func make(t Type, size ...IntegerType) Type
func main() {
b := make(map[string]int)
b["沙河娜扎"] = 100
fmt.Println(b)
}
结构体
https://www.liwenzhou.com/posts/Go/10_struct/
结构体是值类型;
类型定义和类型别名的区别
//类型定义,自定义类型
type NewInt int
//类型别名
type MyInt = int
func main() {
var a NewInt
var b MyInt
fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt
fmt.Printf("type of b:%T\n", b) //type of b:int
}
结果显示a的类型是main.NewInt
,表示main包下定义的NewInt
类型。b的类型是int
。MyInt
类型只会在代码中存在,编译完成时并不会有MyInt
类型。
结构体指针
type person struct {
name string
city string
age int8
}
func main() {
p1 := new(person) // p1是一个结构体指针,*person
fmt.Printf("%T", p1) // *main.person
// (*p1).age = 17
// (*p1).name = "XiaoGang"
p1.age = 17
p1.name = "XiaoGang" // 这里和上面(*p1)是一直的,因为语法糖简化了这个过程;
}
面试题:
推测下面的输出结果
func main() {
m := make(map[string]*student)
stus := []student{
{name: "小王子", age: 18},
{name: "娜扎", age: 23},
{name: "大王八", age: 9000},
}
for _, stu := range stus {
fmt.Printf("%v : %p\n", stu, &stu)
m[stu.name] = &stu
}
for k, v := range m {
fmt.Println(k, "=>", v.name)
}
}
“单一变量不同值,取的却是地址” ❌
单一变量,该变量地址不会变;但却每次拿的都是这个地址,这个地址内不会保存多个值,只会存在最后一次使用改地址的值;
构造函数
构造函数主要用来在创建对象时完成对对象属性的一些初始化等操作, 当创建对象时, 对象会自动调用它的构造函数。一般来说, 构造函数有以下三个方面的作用:
- 给创建的对象建立一个标识符;
- 对象数据成员开辟内存空间;
- 完成对象数据成员的初始化。
Go语言的结构体没有构造函数,可以自己实现。 例如,下方的代码就实现了一个person
的构造函数。 因为struct
是值类型,如果结构体比较复杂的话,(结构体是值类型)值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。
type person struct {
name string
city string
age int8
}
// 构造函数
func newPersion(name, city string, age int8) *person {
return &person{
name: name,
city: city,
age: age,
}
}
func main() {
p := newPersion("XG", "beijing", 17)
fmt.Printf("%#v", p) //&main.person{name:"XG", city:"beijing", age:17}
}
方法和接收者
为结构体(可以简单理解为对象),添加方法,那么接受者也就是这个结构体的实例;
https://www.liwenzhou.com/posts/Go/10_struct/#autoid-2-6-0
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
指针类型的接收者:
func (p *Person) SetAge(newAge int8) {}
// 直接对 *p 进行的操作
type person struct {
name string
city string
age int8
}
⚠️:*p 是person结构体,所以在SetAge方法里你可以修改这个*p的name,city,age也就是所有结构体内部的东西,但你绝不可以在方法内新建一个*p2:p=p2,这样做方法外的值并没有被修改可以通过下面这个例子尝试;
package main
import "fmt"
type person struct {
name string
city string
age int8
}
func (p *person) SetAge() {
p2 := &person{age: 18}
p = p2
}
func main() {
p1 := &person{}
fmt.Println(p1.age)
}// 打印结果是0,仔细体会这里是因为什么;
值类型的接受者:
func (p Person) SetAge2(newAge int8) {}
// 将接收者的值复制一份,不影响原来的了;
什么时候应该使用指针类型接收者?
- 需要修改接收者中的值
- 接收者是拷贝代价比较大的大对象
- 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。
面试题:能否通过编译?
type People interface {
Speak(string) string
}
type Student struct{}
func (stu *Student) Speak(think string) (talk string) {
if think == "sb" {
talk = "你是个大帅比"
} else {
talk = "您好"
}
return
}
func main() {
var peo People = Student{}
think := "bitch"
fmt.Println(peo.Speak(think))
}
使用值接收者实现接口之后,不管是dog结构体还是结构体指针*dog类型的变量都可以赋值给该接口变量。因为Go语言中有对指针类型变量求值的语法糖,dog指针
fugui
内部会自动求值*fugui
。
结构体字段的可见性
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。
另外:如果一个Go包中定义的标识符(如:结构体,函数名)首字母是大写的,那就是对外可见的;
结构体与JSON序列化
type Student struct {
ID int
Gender string
Name string
}
type Class struct {
Tittle string
Students []*Student
}
JSON序列化:结构体 -> JSON格式的字符串
//JSON序列化:结构体-->JSON格式的字符串
data, err := json.Marshal(c)
if err != nil {
fmt.Println("json marshal faild")
return
}
fmt.Printf("%s", data)
反序列化:JSON格式的字符串 -> 结构体
//反序列化:JSON格式的字符串-->结构体
str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`
c1 := &Class{}
err = json.Unmarshal([]byte(str), c1)
if err != nil {
fmt.Println("json unmarshal failed!")
return
}
fmt.Printf("%#v", c1)
- Unmarshal函数解析json编码的数据并将结果存入v指向的值。解释如下:
package main
import (
"encoding/json"
"fmt"
)
type Student struct {
ID int `json:"id"`
Name string `json:"name"`
Gender string `json:"gender"`
}
func main() {
str := `{"name":"stu1"}` //需要反序列化的str,相当于要修改的值;
byts := []byte(str) //
stu2 := &Student{ //序列化之后存到这里,相当于原有数据,也可以为空;
ID: 1,
Name: "xiaohong",
Gender: "female",
}
fmt.Println("执行前: ", stu2)
if err := json.Unmarshal(byts, stu2); err != nil { //相当于用str修改stu2的对应字段的值;
fmt.Println(err)
return
}
fmt.Println("执行后: ", stu2)
}
output:
执行前: &{1 xiaohong female}
执行后: &{1 stu1 female}
json.NewDecoder(r.Body).Decode(pInfo):另外Decode和Unmarshal后面调用的是同一个函数,所以他们有类似的功效;
重要:
如下结构体,是在main包中定义的(此处将Name --> name),根据结构体字段的可见性,那么name这个结构体字段只能在main包中访问;如果使用json包来进行序列化或者反序列化操作:
- 序列化结果没有name字段;
- 反序列化结果name字段为"";
所以小写开头的字段,在其他包下不可见;
type Student struct {
ID int
Gender string
name string
}
如果json字段里必须需要全小写,如前后端,异步传递json时候,如果type里面用小写,则其他包不可见,无法解析;如果type用大写,那么前端传递进来的字段也要是大写,不过一般都要用小写;这时候就需要使用:结构体标签tag;
结构体标签Tag
详细教程:https://www.cnblogs.com/FSH1014/p/12709922.html
type Person struct {
Name string `json:"name"` // 指定json序列化/反序列化时使用小写name
Age int64 `json:"email,omitempty"` // 在tag中添加omitempty忽略空值
Weight float64 `json:"-"` // 指定json序列化/反序列化时忽略此字段
ID int64 `json:"id,string"` // 添加string tag
}
- 当 struct 中的字段没有值时,
json.Marshal()
序列化的时候不会忽略这些字段,而是默认输出字段的类型零值(例如int
和float
类型零值是 0,string
类型零值是""
,对象类型零值是 nil)。如果想要在序列序列化时忽略这些没有值的字段时,可以在对应字段添加omitempty
tag。 - 前端在传递来的json数据中可能会使用字符串类型的数字,这个时候可以在结构体tag中添加
string
来告诉json包从字符串中解析相应字段的数据
Package
Package导入
包内大写字母开头的变/常量、函数、结构体等声明,才可以在其他包中使用;
import (
"fmt"
m "github.com/Q1mi/studygo/pkg_test" //自定义包名
_ "包的路径" //匿名导入包
)
如果只希望导入包(导入包时会执行包内的init()函数),而不使用包内部的数据时,可以使用匿名导入包;匿名导入的包与其他方式导入的包一样都会被编译到可执行文件中。
包不可以循环引入
包的init()函数
- 在Go语言程序执行时导入包语句会自动触发包内部
init()
函数的调用。 init()
函数没有参数也没有返回值。init()
函数在程序运行时自动被调用执行,不能在代码中主动调用它。
init()函数执行顺序:
- 全局声明:包括变量,常量,struct等全局的声明;
- 再执行init();
init()函数执行顺序:
Go语言包会从main
包开始检查其导入的所有包,每个包中又可能导入了其他的包。Go编译器由此构建出一个树状的包引用关系,再根据引用顺序决定编译顺序,依次编译这些包的代码。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aI9F8tKO-1607003162571)(/Users/hang/Library/Application%20Support/typora-user-images/image-20201118122822620.png)]
接口interface
https://www.liwenzhou.com/posts/Go/12_interface/#autoid-1-0-0
接口(interface):是一种类型,一种抽象的类型。
在不知道接口是什么的时候可以说三遍这句话;
Go语言提倡面向接口编程。
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
接口实现
一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。
实现接口的值接受者和指针接收者区别:https://www.liwenzhou.com/posts/Go/12_interface/#autoid-1-5-0
常用:
var _ IRouter = &RouterGroup{} // 确保RouterGroup实现了接口IRouter
- 如果接口没有全部实现的话,ide会检测错误标红;
- 编译的时候也会保持,提示那些接口里的方法还没有实现;
空接口:
-
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
-
空接口类型的变量可以存储任意类型的变量。
// 定义一个空接口x
var x interface{}
应用:
使用空接口实现可以接收任意类型的函数参数;
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
使用空接口实现可以保存任意值的字典(常用);
// 空接口作为map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "xxxx"
studentInfo["age"] = 18
studentInfo["married"] = false
接口断言
想要判断空接口中的值这个时候就可以使用类型断言:
x.(T)
其中:
- x:表示类型为
interface{}
的变量 - T:表示断言
x
可能是的类型。
该语法返回两个参数,
- 第一个参数是
x
转化为T
类型后的变量; - 第二个值是一个布尔值,若为
true
则表示断言成功,为false
则表示断言失败。
func main() {
var x interface{}
x = "Hello 沙河"
v, ok := x.(string)
if ok {
fmt.Println(v)
} else {
fmt.Println("类型断言失败")
}
}
反射
反射的学习:https://www.liwenzhou.com/posts/Go/13_reflect/
一种典型场景:空接口和反射
-
空接口相当于一个容器,能接收任意类型;
-
那怎么判断空接口变量存储的是什么类型呢?断言可以,这只是一个比较基础的方法;而且大部分时候断言猜不到;
-
如果想获取存储变量的类型信息和值信息就要使用反射机制;反射就是动态的获取变量类型信息和值信息的机制;
reflect.TypeOf()
package main
import (
"fmt"
"reflect"
)
type Dint int
func reflectType(x interface{}) {
v := reflect.TypeOf(x)
fmt.Printf("type:%v\n", v)
fmt.Printf("name:%v\n", v.Name())
fmt.Printf("kind:%v\n", v.Kind())
fmt.Println()
}
func main() {
var a float32 = 3.14
reflectType(a) // type:float32,name:float32,kind:float32
var b int64 = 100
reflectType(b) // type:int64,name:int64,kind:int64
var dd Dint = 111
reflectType(dd) // main.Dint,name:Dint,kind:int
slice := []int{1, 2, 3, 4}
reflectType(slice) // []int, , slice
}
- reflect.TypeOf()
- 反射中关于类型还划分为两种:类型(Type)
和
种类(Kind) - 种类(Kind):就是指底层的类型;查看详细的kind:https://www.liwenzhou.com/posts/Go/13_reflect/#autoid-3-1-0
- Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的
.Name()
都是返回空。
reflect.ValueOf()
reflect.ValueOf()
返回的是reflect.Value
类型,其中包含了原始值的值信息。reflect.Value
与原始值之间可以互相转换。
fmt.Printf("type c :%T\n", c) // type c :reflect.Value
fmt.Printf("kind: %v\n", c.Kind()) // kind: int
fmt.Printf("type: %T\n", int64(c.Int())) // type: int64
通过反射获取值:
package main
import (
"fmt"
"reflect"
)
func reflectValue(x interface{}) {
v := reflect.ValueOf(x)
k := v.Kind()
switch k {
case reflect.Int64:
// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
case reflect.Float32:
// v.Float()从反射中获取浮点型的原始值,然后通过float32()强d制类型转换
fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
case reflect.Float64:
// v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换
fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
}
}
func main() {
var a float32 = 3.14
var b int64 = 100
reflectValue(a) // type is float32, value is 3.140000
reflectValue(b) // type is int64, value is 100
}
通过反射设置变量的值:
通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中使用专有的Elem()
方法来获取指针对应的值。
package main
import (
"fmt"
"reflect"
)
func reflectSetValue1(x interface{}) {
v := reflect.ValueOf(x)
if v.Kind() == reflect.Int64 {
v.SetInt(200) //修改的是副本,reflect包会引发panic
}
}
func reflectSetValue2(x interface{}) {
v := reflect.ValueOf(x)
// 反射中使用 Elem()方法获取指针对应的值
if v.Elem().Kind() == reflect.Int64 {
v.Elem().SetInt(200)
}
}
func main() {
var a int64 = 100
// reflectSetValue1(a) //panic: reflect: reflect.Value.SetInt using unaddressable value
reflectSetValue2(&a)
fmt.Println(a)
}
isNil()和isValid()
IsNil()
常被用于判断指针是否为空;IsValid()
常被用于判定返回值是否有效
package main
import (
"fmt"
"reflect"
)
func main() {
// *int类型空指针
var a *int
fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil())
// nil值
fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid())
// 实例化一个匿名结构体
b := struct{}{}
// 尝试从结构体中查找"abc"字段
fmt.Println("不存在的结构体成员:", reflect.ValueOf(b).FieldByName("abc").IsValid())
// 尝试从结构体中查找"abc"方法
fmt.Println("不存在的结构体方法:", reflect.ValueOf(b).MethodByName("abc").IsValid())
// map
c := map[string]int{}
// 尝试从map中查找一个不存在的键
fmt.Println("map中不存在的键:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("娜扎")).IsValid())
}
结构体反射
type student struct {
Name string `json:"name"`
Score int `json:"score"`
}
func main() {
stu1 := student{
Name: "小王子",
Score: 90,
}
t := reflect.TypeOf(stu1) //t: reflect.Type
v := reflect.ValueOf(stu1) //v: reflect.Value
//获取结构体内任意字段信息
structField := t.Field(0) //reflect.StructField
value := v.Field(0) //reflect.Value, 直接打印就是值
}
reflect.Type
和reflect.Value
类型实例都有下面的方法:
方法 | 说明 |
---|---|
Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。 |
NumField() int | 返回结构体成员字段数量。 |
FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。 |
FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。 |
FieldByNameFunc(match func(string) bool) (StructField,bool) | 根据传入的匹配函数匹配需要的字段。 |
NumMethod() int | 返回该类型的方法集中方法的数目 |
Method(int) Method | 返回该类型方法集中的第i个方法 |
MethodByName(string)(Method, bool) | 根据方法名返回该类型方法集中的方法 |
reflect.StructField
:
struct -> reflect.TypeOf() -> reflect.TypeOf().Field()
//源代码
type StructField struct {
// Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。
// 参见http://golang.org/ref/spec#Uniqueness_of_identifiers
Name string
PkgPath string
Type Type // 字段的类型
Tag StructTag // 字段的标签
Offset uintptr // 字段在结构体中的字节偏移量
Index []int // 用于Type.FieldByIndex时的索引切片
Anonymous bool // 是否匿名字段
}
package main
import (
"fmt"
"reflect"
)
type student struct {
Name string `json:"name"`
Score int `json:"score"`
}
func (s student) Study() string {
msg := "好好学习,天天向上"
fmt.Println(msg)
return msg
}
func (s student) Sleep() string {
msg := "好好休息,快快长大"
fmt.Println(msg)
return msg
}
func printMethod(x interface{}) {
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
for i := 0; i < t.NumMethod(); i++ {
methodType := v.Method(i).Type()
fmt.Printf("method name:%s\n", t.Method(i).Name)
fmt.Printf("method:%s\n", methodType)
// 通过反射调用方法传递的参数必须是 []reflect.Value 类型
var args = []reflect.Value{}
v.Method(i).Call(args)
}
}
func main() {
stu := student{
Name: "xxx",
Score: 99,
}
printMethod(stu)
}
反射是把双刃剑
使用reflect要适可而止;
https://www.liwenzhou.com/posts/Go/13_reflect/#autoid-4-3-0
并发
goroutine
sync.WaitGroup
:设置等待所有进程结束才结束;
runtime.GOMAXPROCS
:设置当前程序并发时占用的CPU逻辑核心数。
package main
import (
"fmt"
"runtime"
"sync"
)
var wg sync.WaitGroup
func a() {
for i := 1; i < 10; i++ {
fmt.Println("A:", i)
}
wg.Done()
}
func b() {
for i := 1; i < 10; i++ {
fmt.Println("B:", i)
}
wg.Done()
}
func main() {
runtime.GOMAXPROCS(2)
wg.Add(2)
go a()
go b()
wg.Wait()
}
channel
https://www.liwenzhou.com/posts/Go/14_concurrence/#autoid-1-3-3
var 变量 chan 元素类型
- 是一种引用类型;
创建channel的格式如下:
make(chan 元素类型, [缓冲大小])
- channel的缓冲大小是可选的。
单向通道:
func squarer(out chan<- int, in <-chan int)
chan<- int
是一个只写单向通道(只能对其写入int类型值),可以对其执行发送操作但是不能执行接收操作;<-chan int
是一个只读单向通道(只能从其读取int类型值),可以对其执行接收操作但是不能执行发送操作。