1、定義一個結構體
type User struct { userid int username string password string}
2、初始化一個結構體
有兩種情況,一是得到結構體的對象,一是得到結構的對象指標,分別有三種方式:
//第1種方式,先聲明對象,再初始化 var player1 Player player1.userid = 1 player1.username = "lina1" player1.password = "123456" //第2種方式,聲明同時初始化 player2 := Player{2, "lina2", "123456"} //第3種方式,通過 field:value 形式初始化,該方式可以靈活初始化欄位的順序 player3 := Player{username: "lina3", password: "123456", userid: 3} //上面三種初始化方式都是生產對象的,相應如果想初始化得到對象指標的三種方法如下: //第1種方式,使用 new 關鍵字 player4 := new(Player) player4.userid = 4 player4.username = "lina4" player4.password = "123456" //第2種方式,聲明同時初始化 player5 := &Player{5, "lina2", "123456"} //第3種方式,通過 field:value 形式初始化,該方式可以靈活初始化欄位的順序 player6 := &Player{username: "lina3", password: "123456", userid: 6}
3、對象與對象指標的區別(更確切的說應該是實值型別和指標類型)
與C/C++類似,GO語言也存在對象與對象的指標,但不同的是,GO語言中沒有 -> 操作符來調用指標所屬的成員,而與一般對象一樣,都是使用 . 來調用。
對於一個函數(或方法),如果函數的參數(或接收者)是對象指標時,表示此對象是可被修改的;相反的,如果是對象時,表示是不可修改的(但如果該對象本身就是參考型別,如 map\func\chan 等,則本質上是可以修改的)。所以一般的做法是,方法的接收者習慣性使用對象指標,而不是對象,一方面可以在想修改對象時進行修改,另一方面也減少參數傳遞的拷貝成本。
另外,有一點尤為特殊,如果是作為函數的參數,則函數定義時,是使用對象還是對象指標,是有本質區別的,在使用對象作為參數的函數中,不能傳入對象指標,同樣的,在使用對象指標作為參數的函數中,也不能傳入對象,否則編譯器會報錯。但如果是方法,則接收者定義為對象還是對象指標,都可以接收對象和對象指標的調用。下面我們來定義相關的函數和方法如下:
//傳入 Player 對象參數func print_obj(player Player) { //player.username = "new" //修改並不會影響傳入的對象本身 log.Println("userid:", player.userid)}//傳入 Player 對象指標參數func print_ptr(player *Player) { player.username = "new" log.Println("userid:", player.userid)}//接收者為 Player 對象的方法,方法接收者的變數,按照 GO 語言的習慣一般不用 this/self ,而是使用接收者類型的第一個小寫字母,可以看標準庫中的代碼風格。func (p Player) m_print_obj() { //p.username = "new" //修改並不會影響傳入的對象本身 log.Println("self userid:", p.userid) } //接收者為 Player 對象指標的方法func (p *Player) m_print_ptr() { p.username = "new" log.Println("self userid:", p.userid) }
然後測試一下函數跟方法的調用:
print_obj(player2) //print_ptr(player2) //無法調用,編譯出錯 player2.m_print_obj() player2.m_print_ptr() //print_obj(player6) //無法調用,編譯出錯 print_ptr(player6) player6.m_print_obj() player6.m_print_ptr()
既然對於對象與對象指標的區別,方法的處理很特殊,那麼將一個對象傳入到接收者為對象指標的方法中,及將一個對象指標傳入到一個接收者為對象的方法中,能不能修改傳入對象的值呢?答案是,由方法的定義決定,而不是方法的調用者類型決定。
4、匿名欄位
結構體裡的欄位可以只有類型名,而沒有欄位名,這種欄位稱為匿名欄位。匿名欄位可以是一個結構體、切片等複合類型,也可以是 int 這樣的簡單類型。但建議不要把簡單類型作為匿名欄位。
type Pet struct { id int petname string}type Player struct { id int Pet int}func main() { var player1 Player player1.petname = "pet1" //可以直接存取匿名欄位中的成員,就像訪問自己的成員一樣 player1.int = 3 //一般不推薦將簡單類型作為匿名欄位,如果有多個匿名的int,這裡就沒法處理了 player1.id = 1 //如果外層跟內層欄位名重複的話,優先取外層欄位 player1.Pet.id = 10 //如果外層跟內層欄位名重複的話,可以通過這種形式來訪問內層欄位}
一個命名為S的結構體類型將不能再包含S類型的成員:因為一個彙總的值不能包含它自身。(該限制同樣適應於數組。)但是S類型的結構體可以包含*S
指標類型的成員,這可以讓我們建立遞迴的資料結構,比如鏈表和樹結構等。
如果結構體沒有任何成員的話就是空結構體,寫作struct{}。它的大小為0,也不包含任何資訊,但是有時候依然是有價值的。有些Go語言程式員用map帶類比set資料結構時,用它來代替map中布爾類型的value,只是強調key的重要性,但是因為節約的空間有限,而且文法比較複雜,所有我們通常避免避免這樣的用法。
seen := make(map[string]struct{}) // set of strings// ...if _, ok := seen[s]; !ok { seen[s] = struct{}{} // ...first time seeing s...}
結構體可以作為函數的參數和傳回值,如果結構體較大,一般使用指標參數,而且如果要在函數修改結構體,則必須使用指標形式。go語言中所有的函數參數都是值拷貝。
如果結構體的全部成員都是可比較的,則該結構體也可比較,則可作為Map的key類型。
得意於匿名嵌入的特性,我們可以直接存取葉子屬性而不需要給出完整的路徑:
var w Wheelw.X = 8 // equivalent to w.Circle.Point.X = 8w.Y = 8 // equivalent to w.Circle.Point.Y = 8w.Radius = 5 // equivalent to w.Circle.Radius = 5w.Spokes = 20
不幸的是,結構體字面值並沒有簡短表示匿名成員的文法, 因此下面的語句都不能編譯通過:
w = Wheel{8, 8, 5, 20} // compile error: unknown fieldsw = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20} // compile error: unknown fields
到目前為止,我們看到匿名成員特性只是對訪問嵌套成員的點運算子提供了簡短的文法糖。稍後,我們將會看到匿名成員並不要求是結構體類型;其實任何命令的類型都可以作為結構體的匿名成員。但是為什麼要嵌入一個沒有任何子成員類型的匿名成員類型呢?
答案是匿名型別的方法集。簡短的點運算子文法可以用於選擇匿名成員嵌套的成員,也可以用於訪問它們的方法。實際上,外層的結構體不僅僅是獲得了匿名成員類型的所有成員,而且也獲得了該類型匯出的全部的方法。這個機制可以用於將一個有簡單行為的對象組合成有複雜行為的對象。組合是Go語言中物件導向編程的核心
結體體定義時,可以為每一個欄位添加一個 Tag,比如使用內建Json庫時,就可能用到這個Tag。具體看中繼資料和反射。