摘抄:https://www.luozhiyun.com/archives/211java
以下:數組
type AnimalCategory struct { kingdom string // 界。 phylum string // 門。 class string // 綱。 order string // 目。 family string // 科。 genus string // 屬。 species string // 種。 } func (ac AnimalCategory) String() string { return fmt.Sprintf("%s%s%s%s%s%s%s", ac.kingdom, ac.phylum, ac.class, ac.order, ac.family, ac.genus, ac.species) }
咱們在Go中通常構建一個結構體由上面代碼塊所示。AnimalCategory結構體中有7個string類型的字段,下邊有個名叫String的方法,這個方法其實就是java類中的toString方法。其實這個結構體就是java中的類,結構體中有屬性,有方法。安全
category := AnimalCategory{species: "cat"} fmt.Printf("The animal category: %s\n", category)
咱們在上面的代碼塊中初始化了一個AnimalCategory類型的值,並把它賦給了變量category,經過調用fmt.Printf方法調用了category實例內的String方法,⽽⽆需 顯式地調⽤它的String⽅法。dom
由於在Go中是沒有繼承一說,因此使用了嵌入字段的方式來實現類型之間的組合,實現了方法的重用。函數
這裏繼續用到上面的結構體AnimalCategoryui
type Animal struct { scientificName string // 學名。 AnimalCategory // 動物基本分類。 }
字段聲明AnimalCategory表明了Animal類型的⼀個嵌⼊字段。Go語⾔規範規定,若是⼀個字段 的聲明中只有字段的類型名⽽沒有字段的名稱,那麼它就是⼀個嵌⼊字段,也能夠被稱爲匿名字段。嵌⼊字段的類型既是類型也是名稱。atom
若是要像java中引用字段裏面的屬性,那麼能夠這麼寫:線程
func (a Animal) String() string { return a.AnimalCategory.String() }
這裏仍是和java是同樣的,可是接下來要講的卻和java有很大區別指針
因爲咱們在AnimalCategory中寫了一個String的方法,若是咱們沒有給Animal寫String的方法,那麼咱們直接打印會獲得什麼結果?code
category := AnimalCategory{species: "cat"} animal := Animal{ scientificName: "American Shorthair", AnimalCategory: category, } fmt.Printf("The animal: %s\n", animal)
在這裏fmt.Printf函數至關於調用animal的String⽅法。在java中只有父類纔會作到方法的覆蓋,可是在Go中,嵌⼊字段的⽅法集合會被⽆條件地合併進被嵌⼊類型的⽅法集合中。
若是爲Animal類型編寫⼀個String⽅法,那麼會將嵌⼊字段AnimalCategory的String⽅法被「屏蔽」了,從而調用Animal的String方法。
只 要名稱相同,⽆論這兩個⽅法的簽名是否⼀致,被嵌⼊類型的⽅法都會「屏蔽」掉嵌⼊字段的同名⽅法。也就是說無論返回值類型或者方法參數如何,只要名稱相同就會屏蔽掉嵌⼊字段的同名⽅法。
上面咱們的例子其實都是值方法,下面咱們舉一個指針方法的例子:
func main() { cat := New("little pig", "American Shorthair", "cat") cat.SetName("monster") // (&cat).SetName("monster") fmt.Printf("The cat: %s\n", cat) cat.SetNameOfCopy("little pig") fmt.Printf("The cat: %s\n", cat) } type Cat struct { name string // 名字。 scientificName string // 學名。 category string // 動物學基本分類。 } //構造一個cat實例 func New(name, scientificName, category string) Cat { return Cat{ name: name, scientificName: scientificName, category: category, } } //傳指針設置cat名字 func (cat *Cat) SetName(name string) { cat.name = name } //傳入值 func (cat Cat) SetNameOfCopy(name string) { cat.name = name } func (cat Cat) String() string { return fmt.Sprintf("%s (category: %s, name: %q)", cat.scientificName, cat.category, cat.name) }
在這個例子中,咱們爲Cat設置了兩個方法,SetName是傳指針的方法,SetNameOfCopy是傳值的方法。
⽅法SetName的接收者類型是Cat。Cat左邊再加個表明的就是Cat類型的指針類型。
咱們經過運行上面的例子能夠得出,值⽅法的接收者是該⽅法所屬的那個類型值的⼀個副本。⽽指針⽅法的接收者,是該⽅法所屬的那個基本類型值的指針值的⼀個副本。咱們在這樣的⽅法內對該副本指向的值進⾏ 修改,卻⼀定會體如今原值上。
type Pet interface { SetName(name string) Name() string Category() string }
當數據類型中的方法實現了接口中的全部方法,那麼該數據類型就是該接口的實現類型,以下:
type Pet interface { Name() string Category() string SetName(name string) } type Dog struct { name string // 名字。 } func (dog *Dog) SetName(name string) { dog.name = name } func (dog Dog) Name() string { return dog.name } func (dog Dog) Category() string { return "dog" }
在這裏Dog類型實現了Pet接口。
接口變量賦值也涉及了值傳遞和指針傳遞的概念。以下:
// 示例1 dog := Dog{"little pig"} fmt.Printf("The dog's name is %q.\n", dog.Name()) var pet Pet = dog dog.SetName("monster") fmt.Printf("The dog's name is %q.\n", dog.Name()) fmt.Printf("This pet is a %s, the name is %q.\n", pet.Category(), pet.Name()) fmt.Println() // 示例2。 dog = Dog{"little pig"} fmt.Printf("The dog's name is %q.\n", dog.Name()) pet = &dog dog.SetName("monster") fmt.Printf("The dog's name is %q.\n", dog.Name()) fmt.Printf("This pet is a %s, the name is %q.\n", pet.Category(), pet.Name())
返回
The dog's name is "little pig". The dog's name is "monster". This pet is a dog, the name is "little pig". The dog's name is "little pig". The dog's name is "monster". This pet is a dog, the name is "monster".
在示例1中,賦給pet變量的其實是dog的一個副本,因此當dog設置了name的時候pet的name並沒發生改變。
在實例2中,賦給pet變量的是一個指針的副本,因此pet和dog同樣發生了編髮。
能夠經過接口間的嵌入實現接口的組合。接⼝類型間的嵌⼊不會涉及⽅法間的「屏蔽」。只要組合的接⼝之間有同名的⽅法就會產⽣衝突,從⽽⽆ 法經過編譯,即便同名⽅法的簽名彼此不一樣也會是如此。
type Animal interface { // ScientificName 用於獲取動物的學名。 ScientificName() string // Category 用於獲取動物的基本分類。 Category() string } type Named interface { // Name 用於獲取名字。 Name() string } type Pet interface { Animal Named }
若是一個變量是不可變的,那麼基於它的索引或切⽚的結果值都是不可尋址的,由於即便拿到了這種值的內存地址也改變不了什麼。
如:
const num = 123 //_ = &num // 常量不可尋址。 //_ = &(123) // 基本類型值的字面量不可尋址。 var str = "abc" _ = str //_ = &(str[0]) // 對字符串變量的索引結果值不可尋址。 //_ = &(str[0:2]) // 對字符串變量的切片結果值不可尋址。 str2 := str[0] _ = &str2 // 但這樣的尋址就是合法的。
在咱們把臨時結果值賦給任何變量或常量以前,即便能拿到它的內存地址也是沒有任何意義的。因此也是不可尋址的。
咱們能夠把各類對值字⾯量施加的表達式的求值結果都看作是 臨時結果。
如:
* ⽤於得到某個元素的索引表達式。
* ⽤於得到某個切⽚(⽚段)的切⽚表達式。
* ⽤於訪問某個字段的選擇表達式。
* ⽤於調⽤某個函數或⽅法的調⽤表達式。
* ⽤於轉換值的類型的類型轉換表達式。
* ⽤於判斷值的類型的類型斷⾔表達式。
* 向通道發送元素值或從通道那⾥接收元素值的接收表達式。
⼀個須要特別注意的例外是,對切⽚字⾯量的索引結果值是可尋址的。由於不論怎樣,每一個切⽚值都會持有⼀個底層數組,⽽ 這個底層數組中的每一個元素值都是有⼀個確切的內存地址的。
//_ = &(123 + 456) // 算術操做的結果值不可尋址。 //_ = &([3]int{1, 2, 3}[0]) // 對數組字面量的索引結果值不可尋址。 //_ = &([3]int{1, 2, 3}[0:2]) // 對數組字面量的切片結果值不可尋址。 _ = &([]int{1, 2, 3}[0]) // 對切片字面量的索引結果值倒是可尋址的。 //_ = &([]int{1, 2, 3}[0:2]) // 對切片字面量的切片結果值不可尋址。 //_ = &(map[int]string{1: "a"}[0]) // 對字典字面量的索引結果值不可尋址。
可是,這樣的函數和⽅法都是不可尋址的。⼀個緣由是函數就是代碼,是不可變的。另⼀個緣由是,拿到指向⼀段代碼的指針是不安全的。
此外,對函數或⽅法的調⽤結果值也是不可尋址的,這是由於它們都屬 於臨時結果。
如:
//_ = &(func(x, y int) int { // return x + y //}) // 字面量表明的函數不可尋址。 //_ = &(fmt.Sprintf) // 標識符表明的函數不可尋址。 //_ = &(fmt.Sprintln("abc")) // 對函數的調用結果值不可尋址。
在Go語言中,協程是由go函數進行觸發的,當程序執⾏到⼀條go語句的時候,Go語⾔ 的運⾏時系統,會先試圖從某個存放空閒的G的隊列中獲取⼀個G(也就是goroutine),它只有在找不到空閒G的狀況下才會 去建立⼀個新的G。
故已存在的goroutine老是會被優先復⽤。
在拿到了⼀個空閒的G以後,Go語⾔運⾏時系統會⽤這個G去包裝當前的那個go函數(或者說該函數中的那些代碼),而後再 把這個G追加到某個存放可運⾏的G的隊列中。
在Go語⾔並不會去保證這些goroutine會以怎樣的順序運⾏。因此哪一個goroutine先執⾏完、哪一個goroutine後執⾏完每每是不可預知的,除⾮咱們使⽤了某種Go語⾔提供的⽅式進⾏了⼈爲 ⼲預。
因此,怎樣讓咱們啓⽤的多個goroutine按照既定的順序運⾏?
下面咱們先看個例子:
func main() { for i := 0; i < 10; i++ { go func() { fmt.Println(i) }() } }
在下面的代碼中,因爲Go語言並不會按順序去執行調度,因此無法知道fmt.Println(i)會在何時被打印,也不知道fmt.Println(i)打印的時候i是多少,也有可能main方法執行完了,可是沒有一條輸出。
因此咱們須要進行以下改造:
func main() { var count uint32 trigger := func(i uint32, fn func()) { for { if n := atomic.LoadUint32(&count); n == i { fn() atomic.AddUint32(&count, 1) break } time.Sleep(time.Nanosecond) } } for i := uint32(0); i < 10; i++ { go func(i uint32) { fn := func() { fmt.Println(i) } trigger(i, fn) }(i) } trigger(10, func() {}) }
咱們在for循環中聲明瞭一個fn函數,fn函數裏面只是簡單的執行打印i的值,而後傳入到trigger中。
trigger函數會不斷地獲取⼀個名叫count的變量的值,並判斷該值是否與參數i的值相同。若是相同,那麼就⽴即調⽤fn代 表的函數,而後把count變量的值加1,最後顯式地退出當前的循環。不然,咱們就先讓當前的goroutine「睡眠」⼀個納秒再進 ⼊下⼀個迭代。
由於會有多個線程操做trigger函數,因此使用的count變量是經過原子操做來進行獲取值和加一操做。
因此過函數實際執行順序會根據count的值依次執行,這裏實現了一種自旋,未知足條件的時候會不斷地進行檢查。
最後防止主協程在其餘協程沒有運行完的時候就關閉,加上一個trigger(10, func() {})
代碼。