Go 中的struct与C中的struct非常相似,并且Go没有class
使用 type <Name> struct{} 定义结构,名称遵循可见性规则
package main
import "fmt"
type persion struct {
Name string
Age int
}
func main() {
a := persion{}
a.Name="david"
a.Age=13
fmt.Println(a)
}
{david 13}
package main
import "fmt"
type persion struct {
Name string
Age int
}
func main() {
a := persion{Name:"david"}
fmt.Println(a)
}
{david 0} //0是int的初始值
package main
import "fmt"
type persion struct {
Name string
Age int
}
func main() {
a := persion{Name:"david",Age:13}
fmt.Println(a)
}
{david 13}
package main
import "fmt"
type persion struct {
Name string
Age int
}
func main() {
a := persion{"david",28}
fmt.Println(a)
}
{david 28}
struct也是一个值类型,传递的时候也是值拷贝
package main
import "fmt"
type persion struct {
Name string
Age int
}
func main() {
a := persion{
Name:"david",
Age:28,
}
fmt.Println(a)
A(a)
fmt.Println(a)
}
func A(per persion){
per.Age = 13
fmt.Println("A",per)
}
{david 28}
A {david 13}
{david 28}
支持指向自身的指针类型成员
如何真正修改Age为13呢?答案是采用指针方式修改内存地址中的值,指针值传递
package main
import "fmt"
type persion struct {
Name string
Age int
}
func main() {
a := persion{
Name:"david",
Age:28,
}
fmt.Println(a)
A(&a)
fmt.Println(a)
}
func A(per *persion){
per.Age = 13
fmt.Println("A",per)
}
{david 28}
A &{david 13}
{david 13}//这里修改了内存中的age为13
package main
import "fmt"
type persion struct {
Name string
Age int
}
func main() {
a := persion{
Name:"david",
Age:28,
}
fmt.Println(a)
A(&a)
B(&a)
fmt.Println(a)
}
func A(per *persion){
per.Age = 13
fmt.Println("A",per)
}
func B(per *persion){
per.Age = 15
fmt.Println("B",per)
}
{david 28}
A &{david 13}
B &{david 15}
{david 15}//如果有一个B函数,修改age为15,最终age等于15
如果有(100个)多个函数需要调用?每次都需要取地址符号,很麻烦,怎么办? 用指针保存,把a变为一个指向结构的指针呢?初始化的时候就把地址取出来
package main
import "fmt"
type persion struct {
Name string
Age int
}
func main() {
a := &persion{ //定义a是一个指针
Name:"david",
Age:28,
}
fmt.Println(a)
A(a) //这里直接调用即可,不用取地址
B(a) //这里直接调用即可,不用取地址
fmt.Println(a)
}
func A(per *persion){
per.Age = 23
fmt.Println("A",per)
}
func B(per *persion){
per.Age = 25
fmt.Println("B",per)
}
&{david 28}//这里可以看到a是一个指向struct的指针
A &{david 23}
B &{david 25}
&{david 25}
如果此时我需要要像class一样对a的属性性操作,怎么做?a.Name = "ok"
package main
import "fmt"
type persion struct {
Name string
Age int
}
func main() {
a := &persion{"david", 28,}
a.Name="ok" //这里直接修改属性的值
fmt.Println(a)
A(a)
fmt.Println(a)
}
func A(per *persion) {
per.Age = 23
fmt.Println("A", per)
}
&{ok 28}
A &{ok 23}
&{ok 23}
支持匿名结构,可用作成员或定义成员变量
package main
import "fmt"
func main() {
a := struct{
Name string
Age int
}{
Name:"david",
Age:19,
}
fmt.Println(a)
}
{david 19}
也可以定义指针类型的匿名结构
package main
import "fmt"
func main() {
a := &struct{ //a是没有名称的匿名结构,定义指针类型结构
Name string
Age int
}{ //需要对结构属性(字面值)赋值
Name:"david",
Age:19,
}
fmt.Println(a)
}
&{david 19}
匿名结构是否可以嵌套其他结构中呢?可以
package main
import "fmt"
type person struct {
Name string
Age int
Contact struct {
Phone,City string
}
}
func main() {
a := person{}
fmt.Println(a)
}
{ 0 { }} //0是int型的Age初始值,{ }是Contact的结构
package main
import "fmt"
type person struct {
Name string
Age int
Contact struct {
Phone,City string
}
}
func main() {
a := person{Name:"david",Age:13}
a.Contact.City="sahgnhai"//匿名结构赋值的方法
a.Contact.Phone="111111111"
fmt.Println(a)
}
{david 13 {111111111 sahgnhai}}
什么是匿名字段?
package main
import "fmt"
type person struct {
string //这就是匿名字段,没有定义属性的名字,只是定义了类型
int
}
func main() {
a := person{"david",19}//注意,赋值顺序是string、int,不能对调,否则报错
fmt.Println(a)
}
{david 19}
匿名结构也可以用于map的值
可以使用字面值对结构进行初始化
允许直接通过指针来读写结构成员
相同类型的成员可进行直接拷贝赋值
支持 == 与 !=比较运算符,但不支持 > 或 <
支持匿名字段,本质上是定义了以某个类型名为名称的字段
结构也是一种类型,相同的类型之间,变量可以进行赋值
package main
import "fmt"
type person struct {
Name string
Age int
}
func main() {
a := person{"david",19}
var b person
b=a //把a赋值给b,打印出来b和a是一样的
fmt.Println(b)
}
{david 19}
struct是一种类型,所以可以比较?
a和b虽然内容包含相同,但是名称不同,就是不同类型,没有可比性,无法比较,所以报错,只有用相同的struct person才可以比较。
package main
import "fmt"
type person1 struct {
Name string
Age int
}
type person2 struct {
Name string
Age int
}
func main() {
a := person1{"david",19}
b := person2{"david",19}
fmt.Println(a == b )
}
invalid operation: a == b (mismatched types person1 and person2)
package main
import "fmt"
type person struct {
Name string
Age int
}
func main() {
a := person{"david",19}
b := person{"david",19}
fmt.Println(a == b )
}
true
package main
import "fmt"
type person struct {
Name string
Age int
}
func main() {
a := person{"david",19}
b := person{"david",20}
fmt.Println(a == b )
}
false
嵌入结构作为匿名字段看起来像继承,但不是继承
可以使用匿名字段指针
package main
import (
"fmt"
)
type human struct {
Sex int
}
type teacher struct {
human
Name string
Age int
}
type student struct {
human
Name string
Age int
}
func main() {
a := teacher{Name:"joe",Age:19}
b := student{Name:"joe",Age:20}
fmt.Println(a,b )
}
{{0} joe 19} {{0} joe 20} //human的int默认值0已经嵌套进去了
package main
import (
"fmt"
)
type human struct {
Sex int
}
type teacher struct {
human
Name string
Age int
}
type student struct {
human
Name string
Age int
}
func main() {
a := teacher{Name:"joe",Age:19,Sex:0}
b := student{Name:"joe",Age:20,Sex:1}
fmt.Println(a,b )
}
hello.go:19:36: unknown field 'Sex' in struct literal of type teacher
hello.go:20:36: unknown field 'Sex' in struct literal of type student
//这里报错了,因为初始化方法错误,应该怎么定义呢?
方法1:human在teacher中被当做匿名字段,所以定义human:{Sex:0}
package main
import (
"fmt"
)
type human struct {
Sex int
}
type teacher struct {
human
Name string
Age int
}
type student struct {
human
Name string
Age int
}
func main() {
a := teacher{Name:"joe",Age:19,human:human{Sex:0}}//human在teacher中被当做匿名字段变量,给变量赋值
b := student{Name:"joe",Age:20,human:human{Sex:1}}
fmt.Println(a,b )
}
{{0} joe 19} {{1} joe 20}
package main
import (
"fmt"
)
type human struct {
Sex int
}
type teacher struct {
human
Name string
Age int
}
type student struct {
human
Name string
Age int
}
func main() {
a := teacher{Name:"joe",Age:19,human:human{Sex:0}}
b := student{Name:"joe",Age:20,human:human{Sex:1}}
a.Name="joe2"
a.Age=13
a.human.Sex=100 //标准方法,防止有多个导入字段重复报错
a.Sex=100 //sex已经被当做teacher的一个属性嵌入,human结构嵌入,把嵌入结构的字段sex都给了外层结构teacher
fmt.Println(a,b )
}
{{100} joe2 13} {{1} joe 20}
如果匿名字段和外层结构有同名字段,应该如何进行操作?
package main
import (
"fmt"
)
type A struct {
B
Name string
}
type B struct {
Name string
}
func main() {
a := A{Name: "A", B: B{Name: "B"}}
fmt.Println(a.Name,a.B.Name) //分别输出a和b的name值A和B
}
A B
如果A中不存在Name字段,那么打印a.Name是什么呢?答案是B
package main
import (
"fmt"
)
type A struct {
B
}
type B struct {
Name string
}
func main() {
a := A{B: B{Name: "B"}}
fmt.Println(a.Name,a.B.Name)//此时的a.Name=B,此时a.Name,a.B.Name写法等价
}
B B
package main
import (
"fmt"
)
type A struct {
B
C
}
type B struct {
Name string
}
type C struct {
Name string
}
func main() {
a := A{B: B{Name: "B"},C: C{Name: "C"}}
fmt.Println(a.Name,a.B.Name,a.C.Name)
}
ambiguous selector a.Name//报错提示有重名的字段,因为a.Name此时到底等于a.B.Name还是等于a.C.Name呢?完全不清楚
Go 中虽没有class,但依旧有method
只能为同一个包中的类型结构定义方法method呢?通过显示说明receiver来实现与某个类型的组合,编译器根据接受者的类型来判断是哪一个类型的方法
package main
import (
"fmt"
)
type A struct {
Name string
}
type B struct {
Name string
}
//定义一个链接到struct A的方法print
func(a A)Print(){ //局部变量a的接受者类型是struct:A,所以定义的方法print是struct A的类型方法
fmt.Println("A")
}
func main(){
a :=A{} //声明一个结构A
a.Print()//然后a调用Print方法,打印结果A
}
A
package main
import (
"fmt"
)
type A struct {
Name string
}
type B struct {
Name string
}
//定义一个链接到struct A的方法print
func(a A)Print(){ //局部变量a的接受者类型是struct:A,所以定义的方法print是struct A的类型方法
a.Name = "A"
fmt.Println("A")
}
func(b B)Print(){ //定义一个连接到B结构的方法Print
b.Name="B"
fmt.Println("B")
}
func main(){
c :=A{} //声明一个结构A
c.Print()//然后a调用Print方法,打印结果A
d :=B{}
d.Print()//注意这里调用方法使用c.Print()和d.Print(),不能使用Print(),否则编译器不清楚是c还是d的Print()方法
}
A
B
Receiver 可以是类型的值或者指针
package main
import (
"fmt"
)
type A struct {
Name string
}
type B struct {
Name string
}
//定义一个链接到struct A的指针类型的方法Print
func(a *A)Print(){ //局部变量a的接受者类型是指针类型struct:A,所以定义的方法print是指针类型struct A
a.Name = "AAA" //引用类型是指针类型拷贝,操作了内存中对象,所以a.Name = "AAA"保存到内存中
fmt.Println("A")
}
func(b B)Print(){
b.Name="BB" //值类型以值传递,只是得到一个拷贝副本,结束方法之后失效,打印空
fmt.Println("B")
}
func main(){
a :=A{} //声明一个结构A
a.Print()//然后a调用Print方法,打印结果A
fmt.Println(a.Name)
b :=B{}
b.Print()
fmt.Println(b.Name)
}
A
AAA
B
不存在方法重载
可以使用值或指针来调用方法,编译器会自动完成转换
package main
import (
"fmt"
)
type TZ int //可以为任何类型绑定方法Print(),非常灵活,可以对int类型做高级的绑定定义等
func (a *TZ)Print(){
fmt.Println("TZ")
}
func main(){
var a TZ
a.Print()
}
TZ //这里打印TZ
从某种意义上来说,方法是函数的语法,因为receiver其实就是
方法所接收的第1个参数(Method Value vs. Method Expression)
package main
import (
"fmt"
)
type TZ int
func (a *TZ)Print(){
fmt.Println("TZ")
}
func main(){
var a TZ
a.Print()//Method Value,已经声明了receiver a,通过类似类的方法调用的形式
(*TZ).Print(&a)//Method Expression,通过类型(*TZ),把变量&a作为receiver(第一个参数)传给对应的Print方法,而不是类型变量调用方法
}
TZ
TZ
如果外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法
类型别名不会拥有底层类型所附带的方法
方法访问权限的问题:
方法可以调用结构中的非公开字段,同一包package中方法访问权限认为公开的,不是私有的,相对其他包package才认为是私有字段:
package main
import (
"fmt"
)
type A struct {
name string
}
func (a *A)Print(){
a.name = "123"
fmt.Println(a.name)
}
func main() {
a:=A{}
a.Print()
fmt.Println(a.name)
}
输出:
123
123
根据为结构增加方法的知识,尝试声明一个底层类型为int的类型,
并实现调用某个方法就递增100。0+100=100
如:a:=0,调用a.Increase()之后,a从0变成100。
package main
import (
"fmt"
)
type TZ int
func (tz *TZ)Increase(num int){
*tz += TZ(num)//这里需要经num强制类型转换为TZ型,否则报错,因为TZ和int是不同的类型
}
func main() {
var a TZ //声明a是TZ类型,a的初始值是0
a.Increase(100)
fmt.Println(a)
}
100
接口是一个或多个方法签名的集合
只要某个类型拥有该接口的所有方法签名,即算实现该接口,无需显示声明实现了哪个接口,这称为 Structural Typing
package main
import (
"fmt"
)
type USB interface{ //定义一个USB接口
Name() string //返回USB名称
Connect() //连接的方法
}
type PhoneConnecter struct{ //定义手机连接器结构,有一个name变量
name string
}
//怎么样用结构让USB实现呢?实现就是为结构添加方法,对应USB中的Name()
func (pc PhoneConnecter)Name() string { //定义一个receiver是PhoneConnecter结构的Name()方法,返回一个string类型的pc.name
return pc.name
}
//怎么样用结构让USB实现呢?实现就是为结构添加方法,对应USB中的Connect()
func (pc PhoneConnecter)Connect(){ //定义一个receiver是PhoneConnecter结构的Connect()方法,打印pc.name
fmt.Println("Connect",pc.name)
}
func main(){
var a USB //定义一个USB接口
a = PhoneConnecter{"PhoneConnecter"}//初始化一个name=PhoneConnecter的手机连接器(具有接口USB属性)
a.Connect()
}
Connect PhoneConnecter
package main
import (
"fmt"
)
type USB interface{ //定义一个USB接口
Name() string //返回USB名称
Connect() //连接的方法
}
type PhoneConnecter struct{ //定义手机连接器结构,有一个name变量
name string
}
//怎么样用结构让USB实现呢?实现就是为结构添加方法,对应USB中的Name()
func (pc PhoneConnecter)Name() string { //定义一个receiver是PhoneConnecter结构的Name()方法,返回一个string类型的pc.name
return pc.name
}
//怎么样用结构让USB实现呢?实现就是为结构添加方法,对应USB中的Connect()
func (pc PhoneConnecter)Connect(){ //定义一个receiver是PhoneConnecter结构的Connect()方法,打印pc.name
fmt.Println("Connect",pc.name)
}
func Disconnect(usb USB){
fmt.Println("Disconnected")
}
func main(){
//var a USB 这里省略定义一个USB接口,因为PhoneConnecter已经具备了USB的Name属性和Connect方法,就已经实现了USB接口,可以直接调用,也可以Disconnect
a := PhoneConnecter{"PhoneConnecter"}//初始化一个name=PhoneConnecter的手机连接器(具有接口USB属性)
a.Connect()
Disconnect(a)
}
Connect PhoneConnecter
Disconnected
接口只有方法声明,没有实现,没有数据字段
接口可以匿名嵌入其它接口,或嵌入到结构中
ok,pattern模式,利用多返回值特性,第一个返回值pc是转换后的结果,第二个返回值是bool型,用于判断是否转换成功?是否是期望的类型?如果是第一个值就返回一个有意义的值,第二值为false,第一个值是0值或者空。
package main
import (
"fmt"
)
type USB interface{ //定义一个USB接口
Name() string //返回USB名称
Connecter //连接的方法
}
type Connecter interface{ //这里的Connecter具备Connect()方法,所以USB中直接嵌入Connecter
Connect()
}
type PhoneConnecter struct{ //定义手机连接器结构,有一个name变量
name string
}
func (pc PhoneConnecter)Name() string { //定义一个receiver是PhoneConnecter结构的Name()方法,返回一个string类型的pc.name
return pc.name
}
func (pc PhoneConnecter)Connect(){ //定义一个receiver是PhoneConnecter结构的Connect()方法,打印pc.name
fmt.Println("Connect",pc.name)
}
//这里只是单纯的disconnected,但是不知道谁断开了连接,不知道断开的是不是一个PhoneConnecter,怎么办呢?
func Disconnect(usb USB){//利用多返回值特性,第一个返回值pc是转换后的结果,第二个返回值是bool型,用于判断是否转换成功,是否是需要的类型,如果是第一个值就返回一个有意义的值,第二值为false,第一个值是0值或者空。
if pc,ok :=usb.(PhoneConnecter);ok { //ok,pattern模式,类型判断USB是否是PhoneConnecter结构?如果成立ok=true,打印pc.name
fmt.Println("Disconnected",pc.name)
return
}
fmt.Println("Unknow device")
}
func main(){
//var a USB //定义一个USB接口
a := PhoneConnecter{"PhoneConnecter"}//初始化一个name=PhoneConnecter的手机连接器(具有接口USB属性)
a.Connect()
Disconnect(a) //这里a类型传给pc,所以pc的类型是PhoneConnecter类型,与usb.(PhoneConnecter)相同,返回true,if判断成立
}
Connect PhoneConnecter
Disconnected PhoneConnecter
通过类型断言的ok pattern可以判断接口中的数据类型
使用type switch则可针对空接口进行比较全面的类型判断
package main
import (
"fmt"
)
type empty interface{
}
type USB interface{ //定义一个USB接口
Name() string //返回USB名称
Connecter //连接的方法
}
type Connecter interface{ //这里的Connecter具备Connect()方法,所以USB中直接嵌入Connecter
Connect()
}
type PhoneConnecter struct{ //定义手机连接器结构,有一个name变量
name string
}
func (pc PhoneConnecter)Name() string { //定义一个receiver是PhoneConnecter结构的Name()方法,返回一个string类型的pc.name
return pc.name
}
func (pc PhoneConnecter)Connect(){ //定义一个receiver是PhoneConnecter结构的Connect()方法,打印pc.name
fmt.Println("Connect",pc.name)
}
func Disconnect(usb interface{}){
switch v := usb.(type){ //这里使用高效简便的type switch方法判断局部变量v的类型
case PhoneConnecter:
fmt.Println("Disconnected",v.name)
default:
fmt.Println("Unknown device")
}
}
func main(){
//var a USB //定义一个USB接口
a := PhoneConnecter{"PhoneConnecter"}//初始化一个name=PhoneConnecter的手机连接器(具有接口USB属性)
a.Connect()
Disconnect(a)
}
Connect PhoneConnecter
Disconnected PhoneConnecter
可以将拥有超集的接口转换为子集的接口
可以把USB转换为Connecter方法,因为USB还包含了name属性
package main
import (
"fmt"
)
type USB interface{ //定义一个USB接口
Name() string //返回USB名称
Connecter //连接的方法
}
type Connecter interface{ //这里的Connecter具备Connect()方法,所以USB中直接嵌入Connecter
Connect()
}
type PhoneConnecter struct{ //定义手机连接器结构,有一个name变量
name string
}
func (pc PhoneConnecter)Name() string { //定义一个receiver是PhoneConnecter结构的Name()方法,返回一个string类型的pc.name
return pc.name
}
func (pc PhoneConnecter)Connect(){ //定义一个receiver是PhoneConnecter结构的Connect()方法,打印pc.name
fmt.Println("Connect",pc.name)
}
func main(){
b := PhoneConnecter{"PhoneConnecter"}//初始化一个name=PhoneConnecter的手机连接器b(天生实现了接口USB接口),因为b具备Name属性和Connect()方法
b.Connect()
fmt.Println(b.name)
var a Connecter
a=Connecter(b)//强制类型转换,将PhoneConnecter的pc转换为Connecter,connect只有Connect()方法,没有name属性
a.Connect()
//fmt.Println(a.name) 这里打印出错,因为Connecter没有name属性
}
Connect PhoneConnecter
PhoneConnecter
Connect PhoneConnecter
将对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针
package main
import (
"fmt"
)
type USB interface{ //定义一个USB接口
Name() string //返回USB名称
Connecter //连接的方法
}
type Connecter interface{ //这里的Connecter具备Connect()方法,所以USB中直接嵌入Connecter
Connect()
}
type PhoneConnecter struct{ //定义手机连接器结构,有一个name变量
name string
}
func (pc PhoneConnecter)Name() string { //定义一个receiver是PhoneConnecter结构的Name()方法,返回一个string类型的pc.name
return pc.name
}
func (pc PhoneConnecter)Connect(){ //定义一个receiver是PhoneConnecter结构的Connect()方法,打印pc.name
fmt.Println("Connect",pc.name)
}
func main(){
b := PhoneConnecter{"PhoneConnecter"}//初始化一个name=PhoneConnecter的手机连接器b(天生实现了接口USB接口),因为b具备Name属性和Connect()方法
var a Connecter
a=Connecter(b)//强制类型转换,将PhoneConnecter的pc转换为Connecter,connect只有Connect()方法,没有name属性
a.Connect()
b.name="pc"
a.Connect() //这里完全忽视了我们的修改,因为拿到的是一个拷贝
}
Connect PhoneConnecter
Connect PhoneConnecter
只有当接口存储的类型和对象都为nil时,接口才等于nil
package main
import "fmt"
func main(){
var a interface{} //a=nil
fmt.Println(a==nil)
var p *int = nil
a = p //a指向的对象是nil,但是本身是一个指向int型的指针,不是nil,所以打印false
fmt.Println(a==nil)
}
true
false
接口调用不会做receiver的自动转换
接口同样支持匿名字段方法
接口也可实现类似OOP中的多态
空接口可以作为任何类型数据的容器
反射可大大提高程序的灵活性,使得 interface{} 有更大的发挥余地
反射使用 TypeOf 和 ValueOf 函数从接口中获取目标 类型信息和字段信息
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) Hello() {
fmt.Println("Hello world")
}
func Info(o interface{}){
t := reflect.TypeOf(o)
fmt.Println("Type:",t.Name())
v:=reflect.ValueOf(o)
fmt.Println("Fields:")
for i :=0; i<t.NumField();i++{
f := t.Field(i)
val := v.Field(i).Interface()
fmt.Printf("%6s: %v = %v\n",f.Name,f.Type,val)
}
}
func main(){
u := User{1,"OK",12}
Info(u)
}
Type: User
Fields:
Id: int = 1
Name: string = OK
Age: int = 12
获取方法信息?
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) Hello() {
fmt.Println("Hello world")
}
func Info(o interface{}){
t := reflect.TypeOf(o)
fmt.Println("Type:",t.Name())
v:=reflect.ValueOf(o)
fmt.Println("Fields:")
for i :=0; i<t.NumField();i++{
f := t.Field(i)
val := v.Field(i).Interface()
fmt.Printf("%6s: %v = %v\n",f.Name,f.Type,val)
}
for i :=0;i < t.NumMethod();i++{//定义获取方法信息
m := t.Method(i)
fmt.Printf("%6s: %v\n",m.Name,m.Type)
}
}
func main(){
u := User{1,"OK",12}
Info(u)
}
Type: User
Fields:
Id: int = 1
Name: string = OK
Age: int = 12
Hello: func(main.User) //获取方法信息
上面传的u是值拷贝方式,如果传递的是一个指针,报错了,该怎么判断类型是否符合预期reflect.struct呢?
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) Hello() {
fmt.Println("Hello world")
}
func Info(o interface{}){
t := reflect.TypeOf(o)
fmt.Println("Type:",t.Name())
if k:=t.Kind();k !=reflect.Struct{//取出类型,判断是否等于reflect.Struct,不是就直接return退出
fmt.Println("XX")
return
}
v:=reflect.ValueOf(o)
fmt.Println("Fields:")
for i :=0; i<t.NumField();i++{
f := t.Field(i)
val := v.Field(i).Interface()
fmt.Printf("%6s: %v = %v\n",f.Name,f.Type,val)
}
for i :=0;i < t.NumMethod();i++{
m := t.Method(i)
fmt.Printf("%6s: %v\n",m.Name,m.Type)
}
}
func main(){
u := User{1,"OK",12}
Info(&u)
}
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) Hello() {
fmt.Println("Hello world")
}
func Info(o interface{}){
t := reflect.TypeOf(o)
fmt.Println("Type:",t.Name())
if k:=t.Kind();k !=reflect.Struct{//取出类型,判断是否等于reflect.Struct,不是就直接return退出
fmt.Println("XX")
return
}
v:=reflect.ValueOf(o)
fmt.Println("Fields:")
for i :=0; i<t.NumField();i++{
f := t.Field(i)
val := v.Field(i).Interface()
fmt.Printf("%6s: %v = %v\n",f.Name,f.Type,val)
}
for i :=0;i < t.NumMethod();i++{
m := t.Method(i)
fmt.Printf("%6s: %v\n",m.Name,m.Type)
}
}
func main(){
u := User{1,"OK",12}
Info(&u)
}
Type:
XX
反射会将匿名字段作为独立字段(匿名字段本质)
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
type Manager struct {
User //反射会将匿名字段作为独立字段处理,这里输入User等于输入User User初始化
title string
}
func main(){
m := Manager{User:User{1,"OK",12},title:"13"}
t:=reflect.TypeOf(m)//使用typeof取出类型
fmt.Printf("%#v\n",t.Field(0))//如何取出匿名字段呢?go语言中使用序号形式取出匿名字段,user是匿名字段,true
fmt.Printf("%#v\n",t.FieldByIndex([]int{0,0}))//如何取出匿名字段呢?go语言中使用序号形式取出匿名字段,id不是匿名字段,打印false
fmt.Printf("%#v\n",t.FieldByIndex([]int{0,1}))//如何取出匿名字段呢?go语言中使用序号形式取出匿名字段,name不是匿名字段,打印false
fmt.Printf("%#v\n",t.Field(1))//如何取出匿名字段呢?go语言中使用序号形式取出匿名字段,title不是匿名字段,打印false
}
reflect.StructField{Name:"User", PkgPath:"", Type:(*reflect.rtype)(0x10b4b40), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:true}
reflect.StructField{Name:"Id", PkgPath:"", Type:(*reflect.rtype)(0x10a54e0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x10a5b60), Tag:"", Offset:0x8, Index:[]int{1}, Anonymous:false}
reflect.StructField{Name:"title", PkgPath:"main", Type:(*reflect.rtype)(0x10a5b60), Tag:"", Offset:0x20, Index:[]int{1}, Anonymous:false}
想要利用反射修改对象内容状态,前提是 interface.data 是 settable,即 pointer-interface
package main
import (
"fmt"
"reflect"
)
func main(){
x:=123
v:=reflect.ValueOf(&x)//要求类型是reflect.Struct
v.Elem().SetInt(999) //通过elem取v的内容,并重新赋值
fmt.Println(x)
}
999
package main
import (
"fmt"
"reflect"
)
type User struct{
Id int
Name string
Age int
}
func Set(o interface{}) {
v := reflect.ValueOf(o)//先取出o的值
if v.Kind()==reflect.Ptr && !v.Elem().CanSet(){//先判断的是不是point interface,判断对象是否可被修改?返回bool值告诉编译器是否可以被修改
fmt.Println("XXX")//如果上述条件不满足,return退出
return
}else {
v = v.Elem()//如果两个条件都满足,重新赋值实际对象
}
if f := v.FieldByName("Name");f.Kind() == reflect.String{ //修改username,先取出字段,判断类型是否是reflect.String,可以用type switch判断
f.SetString("BYEBYE")//修改username
}
}
func main(){
u := User{1,"OK",12}
Set(&u)
fmt.Println(u)
}
{1 BYEBYE 12} //name从OK变为BYEBYE,修改成功了
怎么判断真的找到了name这个字段,并且修改了呢?
package main
import (
"fmt"
"reflect"
)
type User struct{
Id int
Name string
Age int
}
func Set(o interface{}) {
v := reflect.ValueOf(o)//先取出o的值
if v.Kind()==reflect.Ptr && !v.Elem().CanSet(){//先判断的是不是point interface,判断对象是否可被修改?返回bool值告诉编译器是否可以被修改
fmt.Println("XXX")//如果上述条件不满足,return退出
return
}else {
v = v.Elem()//如果两个条件都满足,重新赋值实际对象
}
f := v.FieldByName("Name123")//先取name字段,name123根本找不到
if !f.IsValid(){//判断name字段是否为空,如果为空,返回reflect.value=value{} !null=true
fmt.Println("BAD")
return
}
if f.Kind() == reflect.String{ //修改username,先取出字段,判断类型是否是reflect.String,可以用type switch判断
f.SetString("BYEBYE")//修改username
}
}
func main(){
u := User{1,"OK",12}
Set(&u)
fmt.Println(u)
}
BAD
{1 OK 12}
package main
import (
"fmt"
"reflect"
)
type User struct{
Id int
Name string
Age int
}
func Set(o interface{}) {
v := reflect.ValueOf(o)//先取出o的值
if v.Kind()==reflect.Ptr && !v.Elem().CanSet(){//先判断的是不是point interface,判断对象是否可被修改?返回bool值告诉编译器是否可以被修改
fmt.Println("XXX")//如果上述条件不满足,return退出
return
}else {
v = v.Elem()//如果两个条件都满足,重新赋值实际对象
}
f := v.FieldByName("Name")//先取name字段,name找到
if !f.IsValid(){ //判断name字段是否为空,如果不为空,返回reflect.value=非空 !true=false,这里的return不会被执行
fmt.Println("BAD")
return
}
if f.Kind() == reflect.String{ //修改username,先取出字段,判断类型是否是reflect.String,可以用type switch判断
f.SetString("BYEBYE")//修改username
}
}
func main(){
u := User{1,"OK",12}
Set(&u)
fmt.Println(u)
}
{1 BYEBYE 12}
通过反射可以“动态”调用方法
定义一个结构,通过反射来打印其信息,并调用方法。
package main
import (
"fmt"
)
type User struct{
Id int
Name string
Age int
}
func (u User) Hello(name string) {
fmt.Println("Hello",name,",my name is",u.Name)
}
func main(){
u :=User{1,"OK",12}
u.Hello("joe")
}
Hello joe ,my name is OK
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) HelloWorld(name string) {
fmt.Println("Hello", name, ",my name is", u.Name)
}
func main() {
u := User{1, "OK", 12}
v := reflect.ValueOf(u) //ValueOf 获取字段信息
mv := v.MethodByName("HelloWorld") //与上面的方法HelloWorld对应
args := []reflect.Value{reflect.ValueOf("daixuan")}//这里是被调用的name
mv.Call(args)
}
Hello daixuan ,my name is OK
很多人都是冲着 Go 大肆宣扬的高并发而忍不住跃跃欲试,但其实从源码的解析来看,goroutine 只是由官方实现的超级“线程池”而已。不过话说回来,每个实例 4-5KB 的栈内存占用和由于实现机制而大幅减少的创建和销毁开销,是制造 Go 号称的高并发的根本原因。另外,goroutine 的简单易用,也在语言层面上给予了开发者巨大的便利。
并发不是并行:Concurrency Is Not Parallelism
并发主要由切换时间片来实现“同时”运行,在并行则是直接利用
多核实现多线程的运行,但 Go 可以设置使用核数,以发挥多核计算机
的能力。
package main
import (
"fmt"
"time"
)
func main(){
go Go()//启动gorutine
time.Sleep(2 * time.Second)//main函数sleep的时候,启动goruntine
}
func Go(){
fmt.Println("Go GO GO !!!")
}
Go GO GO !!!
最好使用匿名函数,gorutine,结合闭包,就很便捷了
如果运行某个函数超过2s,不可能无限制的sleep等待,怎么办呢?
使用channel
Goroutine 奉行通过通信来共享内存,而不是共享内存来通信。
Channel 是 goroutine 沟通的桥梁,大都是阻塞同步的
通过 make 创建,close 关闭
Channel 是引用类型
package main
import (
"fmt"
)
func main(){
c:=make(chan bool)//创建一个chan,bool类型
//此时怎么对chan进行读取写入的操作呢?
go func(){
fmt.Println("GO GO GO!!")
c <- true //把对象c存起来,存的值是bool类型的true
}()
//启动goroutine之后,mian执行到这里,因为没有将内容放进去,所以阻塞了,一直在等待,直到输出GO GO GO!!执行完,把true存到chan中,这时候取到值,才能继续执行程序
<-c//取chan值的操作,一直等待,直到能chan值才结束main
}
GO GO GO!!
可以使用 for range 来迭代不断操作 channel
package main
import (
"fmt"
)
func main(){
c:=make(chan bool)//创建一个chan,bool类型
//此时怎么对chan进行读取写入的操作呢?
go func(){
fmt.Println("GO GO GO!!")
c <- true //把对象c存起来,存的值是bool类型的true
close(c) //这里需要关闭chan,然后main执行完成,退出,否则go routine都在等待中,死锁了
}()
//启动goroutine之后,mian执行到这里,因为没有将内容放进去,所以阻塞了,一直在等待,直到输出GO GO GO!!执行完,把true存到chan中,这时候取到值,才能继续执行程序
for v := range c{
fmt.Println(v) //使用for range 迭代chan,等待chan有个值进去,然后打印v的值true,并没有退出,进行下一次for range等待
}
}
GO GO GO!!
true
package main
import (
"fmt"
)
func main(){
c:=make(chan bool)//创建一个chan,bool类型
//此时怎么对chan进行读取写入的操作呢?
go func(){
fmt.Println("GO GO GO!!")
c <- true //把对象c存起来,存的值是bool类型的true
//这里需要关闭chan,然后main执行完成,退出,否则go routine都在等待中,死锁了
}()
//启动goroutine之后,mian执行到这里,因为没有将内容放进去,所以阻塞了,一直在等待,直到输出GO GO GO!!执行完,把true存到chan中,这时候取到值,才能继续执行程序
for v := range c{
fmt.Println(v) //使用for range 迭代chan,等待chan有个值进去,然后打印v的值true,并没有退出,进行下一次for range等待
}
}
GO GO GO!!
fatal error: all goroutines are asleep - deadlock! //这里死锁了,崩溃退出
true
goroutine 1 [chan receive]:
main.main()
/Users/daixuan/qbox/test/test.go:14 +0xcd
可以设置单向或双向通道
make双向通道可以存也可以取,单项通道是只能存或者只能取,只能够写不能读
可以设置缓存大小,在未被填满前不会发生阻塞
那就是一个是同步的 一个是非同步的
怎么说?比如
c1:=make(chan int) 无缓冲
c2:=make(chan int,1) 有缓冲
c1<-1
无缓冲的 不仅仅是 向 c1 通道放 1 而是 一直要有别的线程 <-c1 接手了 这个参数,那么c1<-1才会继续下去,要不然就一直阻塞着
而 c2<-1 则不会阻塞,因为缓冲大小是1 只有当 放第二个值的时候 第一个还没被人拿走,这时候才会阻塞。
打个比喻
无缓冲的 就是一个送信人去你家门口送信 ,你不在家 他不走,你一定要接下信,他才会走。无缓冲保证信能到你手上
有缓冲的 就是一个送信人去你家仍到你家的信箱 转身就走 ,除非你的信箱满了 他必须等信箱空下来。有缓冲的 保证 信能进你家的邮箱
注意:
chan没有缓存的话,注意“取chan操作”应该在前,“放chan操作”放在后
package main
import (
"fmt"
)
func main(){
c:=make(chan bool)//创建一个chan,bool类型
//此时怎么对chan进行读取写入的操作呢?
go func(){
fmt.Println("GO GO GO!!")
c <- true //把对象c存起来,存的值是bool类型的true,放chan操作放在后
}()
<-c //这是取chan操作”放在前,没有东西可以取,只能等待快递送信过来
}
GO GO GO!!
chan如果有缓存的话,注意“取chan操作”应该在后,“放chan操作”放在前
package main
import (
"fmt"
)
func main(){
c:=make(chan bool, 1)//创建一个chan,bool类型 1
go func(){
fmt.Println("GO GO GO!!")
c <- true //把对象c存起来,存的值是bool类型的true
}()
<-c //这是取chan操作”放在前,信箱(缓存)没有东西可以取,只能等送信的来,还是直接结束呢?测试结果是等待信送过来
}
GO GO GO!!//这里输出GO GO GO没有问题
此时把c <- true和c <- 对调一下,没有输出结果
package main
import (
"fmt"
)
func main(){
c:=make(chan bool,1)//创建一个chan,bool类型,没有缓存
//此时怎么对chan进行读取写入的操作呢?
go func(){//main函数已经结束了,不会等待该线程(gotoutine)执行了,不打印GO GO GO
fmt.Println("GO GO GO!!")
<-c
}()
c <- true //快递来了(先存),有信箱(缓存),所以快递直接放到信箱里面走人,main结束,不打印GO GO GO
}
这里输出为空
如果把bool 1的缓存1删除,又输出GO GO GO了
package main
import (
"fmt"
)
func main(){
c:=make(chan bool)//创建一个chan,bool类型,没有缓存
//此时怎么对chan进行读取写入的操作呢?
go func(){
fmt.Println("GO GO GO!!")//线程会先打印出 Go Go GO
<-c //直到有人出来取快递了,才能结束快递员的等待
}()
c <- true //快递来了(先存),没有信箱(缓存),同时没有人取,所以快递一直在等等待
}
GO GO GO!!
什么原因呢?
有缓存的时候直接读出bool值,有缓存是异步,无缓存是同步阻塞的。
举个新手及其容易犯的错误,并发执行的时候gorutine并没有按照顺序执行,而是随机执行,以第9个goruntine执行完作为判断标准,并不合理,因为9执行完成的时候,只有5,1也执行完成,其他的goroutine并没有执行完成。
package main
import (
"fmt"
"runtime"
)
func main(){
runtime.GOMAXPROCS(runtime.NumCPU())
c := make(chan bool)
for i := 0; i<10; i++{
go Go(c,i)
}
<-c
}
func Go(c chan bool,index int){
a := 1
for i :=0;i< 10000000;i++{
a +=i
}
fmt.Println(index,a)
if index == 9{
c <- true
}
}
5 49999995000001
1 49999995000001
9 49999995000001
解决办法:
1、取10次chan值,缓存10次c := make(chan bool,10),这里缓存应该大于等于10,防止突然10次请求一起来,如果缓存是8,就不够用,有2个需要等待,影响性能,但是结果ok(测试一致)
package main
import (
"fmt"
"runtime"
)
func main(){
runtime.GOMAXPROCS(runtime.NumCPU())
c := make(chan bool,10)
for i := 0; i<10; i++{
go Go(c,i)
}
for i :=0;i<10;i++ {//取10次chan值
<-c
}
}
func Go(c chan bool,index int){
a := 1
for i :=0;i< 10000000;i++{
a +=i
}
fmt.Println(index,a)
c <- true
}
3 49999995000001
9 49999995000001
0 49999995000001
1 49999995000001
4 49999995000001
6 49999995000001
7 49999995000001
8 49999995000001
2 49999995000001
5 49999995000001
通过sync设置waitGroup(10)任务数,每完成一个任务,标记wg.Done(),总任务数减一,最终完成所有任务。
package main
import (
"fmt"
"runtime"
"sync"
)
func main(){
runtime.GOMAXPROCS(runtime.NumCPU())
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i<10; i++{
go Go(&wg,i)
}
wg.Wait()
}
func Go(wg *sync.WaitGroup ,index int){
a := 1
for i :=0;i< 10000000;i++{
a +=i
}
fmt.Println(index,a)
wg.Done()//标记一次任务完成
}
5 49999995000001
3 49999995000001
1 49999995000001
6 49999995000001
2 49999995000001
9 49999995000001
4 49999995000001
0 49999995000001
8 49999995000001
7 49999995000001
可处理一个或多个 channel 的发送与接收
package main
import (
"fmt"
)
func main(){
c1,c2 := make(chan int),make(chan string)
o :=make(chan bool,2)//信号通道,通知里面的东西是否都被处理完了,缓存为2
go func(){
for { //这里使用for的无限循环,selct,不断的信息接收和发送操作
select {
case v,ok := <- c1: //
if !ok { //如果chan被关闭,ok!=true,退出slect
o<- true//当c1或者c2其中任何一个关闭,传true进去,读到<-o,main函数退出
break
}
fmt.Println("c1",v) //如果没有被关闭,打印传进来的值
case v,ok := <-c2:
if !ok{
o<-true
break
}
fmt.Println("c2",v)
}
}
}()
c1 <-1
c2 <-"hi"
c1 <-3
c2 <-"hello"
close(c1)//关闭c1和c2
close(c2)
for i :=0;i<2;i++{ //通信chan的缓存为2,取两次o的值
<-o
}
}
c1 1
c2 hi
c1 3
c2 hello
同时有多个可用的 channel时按随机顺序处理
package main
import (
"fmt"
)
func main(){
c:=make(chan int)
go func() {
for v := range c {
fmt.Println(v)
}
}()
for {
select {
case c <- 0:
case c <- 1:
}
}
}
1 //0或者1随机打印
0
0
0
1
1
1
1
1
0
0
1
0
0
1
1
0
可用空的 select 来阻塞 main 函数
可设置超时
package main
import (
"fmt"
"time"
)
func main(){
c := make(chan bool)
select {
case v := <-c:
fmt.Println(v)
case <- time.After(3 * time.Second):
fmt.Println("Timeout")
}
}
3s后打印
Timeout
创建一个 goroutine,与主线程按顺序相互发送信息若干次并打印
package main
import (
"fmt"
)
var c chan string //创建一个全局变量的chan
//定义一个接受goroutine,先接收,再发送
func Pingpong() {//定义一个goroutine,//先接受c,再发送From Pingpong:Hi....
i := 0
for { //3、执行到这里,无限循环,等待chan有内容传递进去为止,
fmt.Println(<-c)//6、这里从chan取到内容:From main:Hello,打印出来
c<-fmt.Sprintf("From Pingpong: Hi,#%d",i)//7、把From Pingpong:传到chan中去
i++//8、i的值加1
}
}
func main(){
c=make(chan string)//1、初始化chan
go Pingpong() //2、启动goroutine:Pingpong,进入for无限循环,然后等待接受
//这里相反,先发送再接受
for i := 0;i<10;i++{
c<-fmt.Sprintf("From main:Hello, #%d",i)//4、这里先向全局chan中放字符串From main:Hello
fmt.Println(<-c)//5、然后等待接受,等待gorutine从取出chan数据后再放回去 9、这里取到chan中的值From Pingpong:然后打印出来
}
}
From main:Hello, #0
From Pingpong: Hi,#0
From main:Hello, #1
From Pingpong: Hi,#1
From main:Hello, #2
From Pingpong: Hi,#2
From main:Hello, #3
From Pingpong: Hi,#3
From main:Hello, #4
From Pingpong: Hi,#4
From main:Hello, #5
From Pingpong: Hi,#5
From main:Hello, #6
From Pingpong: Hi,#6
From main:Hello, #7
From Pingpong: Hi,#7
From main:Hello, #8
From Pingpong: Hi,#8
From main:Hello, #9
From Pingpong: Hi,#9
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。