您好,登錄后才能下訂單哦!
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"http://匿名結構賦值的方法
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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。