目錄
- 1、結構體指針
- 1.1 聲明
- 1.2 聲明并初始化
- 1.3 通過new函數建立指針對象
- 1.4 傳遞結構體指針
- 1.5 結構體值與結構體指針
- 1.6 傳值還是傳遞指針
- 2、匿名結構體
- 3、結構體方法
- 4、結構體嵌套
- 4.1 匿名嵌套
- 4.2 命名嵌套
- 4.3 指針類型結構體嵌套
- 4.4 結構體嵌套的實際意義
- 5、通過函數建立結構體對象
- 6、結構體的可見性
本文是Golang資料類型之結構體-上篇的續篇内容
和其他基礎資料類型一樣,也可聲明結構體指針變量,此時變量被初始化為
nil
func TestMain4(t *testing.T) {
var person *Person
fmt.Println(person) // <nil>
}
聲明并初始化指針對象
// 先聲明再初始化
//var person *Person
//person = &Person{}
// 簡短聲明
person := new(Person)
//person := &Person{} // *Person
fmt.Printf("%p", person) // 0xc00013a080
聲明并初始化指派
var person *Person = &Person{
Name: "andy",
Age: 66,
Gender: "male",
Weight: 120,
FavoriteColor: []string{"red", "blue"},
}
fmt.Printf("%p", person) // 0xc0000ce080
Go
中常定義
N(n)ew
+結構體名命名的函數用于建立對應的結構體值對象或指針對象
person := new(Person)
fmt.Printf("%p", person) // 0xc00013a080
fmt.Printf("%T", person) // *test.Person
// 定義工廠函數用于建立Author對象
func NewAuthor(id int, name, birthday, addr, tel, desc string) *User {
return &User{id, name, birthday,addr, tel, desc}
}
// 調用
me8 := NewAuthor(1004, "geek", "2021-06-08", "北京市", "15588888888", "備注")
fmt.Printf("%T: %#v\n", me8, me8)
将一個結構體的指針傳遞給函數,能否修改到該結構體
結果是可以修改該執行個體對象
func ChangeColor(car *Car) {
car.Color = "blue"
fmt.Println(car.Color)
}
func main() {
car := Car{
Color: "yellow", // 黃色
Brand: "ford", // 福特
Model: "Mustang", // 野馬
}
ChangeToW(car)
fmt.Println(car.Color) // blue
}
什麼是值? 什麼是指針?
下面三種方式都可以構造
Car struct
的執行個體
c1 := Car{}
c2 := &Car{}
c3 := new(Car)
fmt.Println(c1, c2, c3) // { } &{ } &{ }
c1
、
c2
c3
都是
car struct
的執行個體,
c2
,
c3
是指向執行個體的指針,指針中儲存的是執行個體的位址,是以指針再指向執行個體,
c1
則是直接指向執行個體。這三個變量與
Car struct
執行個體的指向關系如下
變量名 指針 資料對象(執行個體)
-------------------------------
c1 -------------------> { }
c2 -----> ptr(addr) --> { }
c3 -----> ptr(addr) --> { }
通路執行個體和通路執行個體指針是否有差別
fmt.Println("c1, ", c1.Color) // 通路執行個體的屬性
fmt.Println("c2, ", (*c2).Color) // 先通過*求出 指針的值,就是執行個體的記憶體位址, 然後通過執行個體的記憶體位址通路該執行個體對象的屬性
如果我們需要通路指針對象的屬性, 上面的
(*c2).Color
是理論上的正确寫法, 可以看出過于繁瑣, 而我們方法指針,往往也是想通路這個指針的執行個體, 是以編譯幫我們做了優化, 比如通路指針執行個體也可以這樣寫
fmt.Println("c2, ", c2.Color) // 編譯器自動補充上(*c2).Color, 這樣寫法上就簡潔了
簡單總結:盡管一個是資料對象值,一個是指針,它們都是資料對象的執行個體。也就是說,
p1.name
和
p2.name
都能通路對應執行個體的屬性,隻是指針的通路寫法是一種簡寫(正确寫法由編譯器補充)
前面文章Golang函數參數的值傳遞和引用傳遞說的也是這個話題
即什麼時候傳值,什麼時候傳遞指針?
- 傳遞值: 不希望執行個體被外部修改的時候,傳值就相當于
了一份副本給函數copy
- 傳遞指針: 希望外部能修改到這個執行個體本身的時候,就需要傳遞該執行個體的指針,就是把該執行個體的記憶體位址告訴對方,可以通過位址直接找到本體
但是經常看到函數接收的結構體參數都是指針是為什麼
因為複制傳值時,如果函數的參數是一個
struct
對象,将直接複制整個資料結構的副本傳遞給函數,這有兩個問題
- 函數内部無法修改傳遞給函數的原始資料結構,它修改的隻是原始資料結構拷貝後的副本
- 如果傳遞的原始資料結構很大,完整地複制出一個副本開銷并不小
是以為了節省開銷一般都會選擇傳遞指針
在定義變量時将類型指定為結構體的結構,此時叫匿名結構體。匿名結構體常用于初始化一次結構體變量的場景,例如項目配置
package main
import "fmt"
func main() {
var me struct {
ID int
Name string
}
fmt.Printf("%T\n", me) // struct { ID int; Name string }
fmt.Printf("%#v\n", me) // struct { ID int; Name string }{ID:0, Name:""}
fmt.Println(me.ID) // 0
me.Name = "geek"
fmt.Printf("%#v\n", me) // struct { ID int; Name string }{ID:0, Name:"geek"}
me2 := struct {
ID int
Name string
}{1, "geek"}
fmt.Printf("%#v\n", me2) // struct { ID int; Name string }{ID:1, Name:"geek"}
}
可以為結構體定義屬于自己的函數
在聲明函數時,聲明屬于結構體的函數,方法與結構體綁定,隻能通過結構體
person
的執行個體通路,不能在外部直接通路,這就是結構體方法和函數的差別,例如
// p 是person的别名
func (p Person) add() int {
return p.Age * 2
}
調用結構體方法
func TestMain6(t *testing.T) {
m := new(Person)
m.Age = 18
fmt.Println(m.add()) // 36
}
簡單來說,就是将資料結構直接放進去,放進去的時候不進行命名
匿名結構體可以組合不同類型的資料,使得處理資料變得更為靈活。尤其是在一些需要将多個變量、類型資料組合應用的場景,匿名結構體是一個不錯的選擇
// 通路方式 結構體.成員名
type Person2 struct {
Name string
Age int
Gender string
Weight uint
FavoriteColor []string
NewAttr string
Addr Home
NewHome
}
type NewHome struct {
City string
}
func TestPerson2(t *testing.T) {
m := new(Person2)
m.Age = 18
m.City = "beijing"
fmt.Println(m.City) // beijing
}
嵌套過後帶來的好處就是能夠像通路原生屬性一樣通路嵌套的屬性
示例
package main
import (
"encoding/json"
"fmt"
)
//定義手機螢幕
type Screen01 struct {
Size float64 //螢幕尺寸
ResX, ResY int //螢幕分辨率 水準 垂直
}
//定義電池容量
type Battery struct {
Capacity string
}
//傳回json資料
func getJsonData() []byte {
//tempData 接收匿名結構體(匿名結構體使得資料的結構更加靈活)
tempData := struct {
Screen01
Battery
HashTouchId bool // 是否有指紋識别
}{
Screen01: Screen01{Size: 12, ResX: 36, ResY: 36},
Battery: Battery{"6000毫安"},
HashTouchId: true,
}
jsonData, _ := json.Marshal(tempData) //将資料轉換為json
return jsonData
}
結構體命名嵌入是指結構體中的屬性對應的類型也是結構體
給嵌入的結構體一個名字,讓其成為另一個結構體的屬性
适用于複合資料結構<嵌入匿名>
嵌套定義
type Book struct {
Author struct{
Name string
Aage int
}
Title struct{
Main string
Sub string
}
}
聲明和初始化
b := &Book{
Author: struct {
Name string
Aage int
}{
Name: "xxxx",
Aage: 11,
},
Title: struct {
Main string
Sub string
}{
Main: "xxx",
Sub: "yyy",
},
}
//
b := new(Book)
b.Author.Aage = 11
b.Author.Name = "xxx"
嵌入命名,在外面定義
type Author struct {
Name string
Aage int
}
type Title struct {
Main string
Sub string
}
type Book struct {
Author Author
Title Title
}
package main
import "fmt"
type Person struct {
Name string
Age int
}
type TeacherNew struct {
Pn Person
TeacherId int
}
func main() {
t2 := TeacherNew{
Pn: Person{
Name: "geek",
Age: 18,
},
TeacherId: 123,
}
fmt.Printf("[TeacherId: %v][Name: %v][Age: %v]", t2.TeacherId, t2.Pn.Name, t2.Pn.Age)
// [TeacherId: 123][Name: geek][Age: 18]
}
結構體嵌套(命名&匿名)類型也可以為結構體指針
聲明&初始化&操作
type Book2 struct {
Author *Author
Title *Title
}
func (b *Book2) GetName() string {
return b.Author.GetName() + "book"
}
func TestMain8(t *testing.T) {
b1 := Book2{
Author: &Author{
Name: "ssgeek",
},
Title: &Title{},
}
b2 := &Book2{
Author: &Author{},
Title: &Title{},
}
}
使用屬性為指針類型底層共享資料結構,當底層資料發生變化,所有引用都會發生影響
使用屬性為值類型,則在複制時發生拷貝,兩者不互相影響
- 例如大項目對應複雜的配置檔案,将公共的字段抽取出來,放到一個公共
的結構體common
- cmdb、資産系統等類型設計
package main
import "time"
// 雲有雲資源公共字段
type Common struct {
ChargingMod string // 付費模式:預付費和後付費
Region string // 區域
Az string // 可用區
CreateTime time.Time // 購買時間
}
type Ecs struct {
Common
guide string // 4C 16G
}
type Rds struct {
Common
dbType string // 代表資料庫是哪一種
}
除了通過直接指派建立結構體對象,還可以通過函數來建立,也就是把建立結構體對象的過程進行封裝
即“工廠函數”
package main
import "fmt"
type Address struct {
Region string
Street string
No string
}
type User struct {
ID int
Name string
Addr *Address
}
func NewUser(id int, name string, region, street, no string) *User {
return &User{
ID: id,
Name: name,
Addr: &Address{region, street, no},
}
}
func main() {
me := User{
ID: 1,
Name: "geek",
Addr: &Address{"上海市", "南京路", "0001"},
}
me2 := me
me2.Name = "ss"
me2.Addr.Street = "黃河路"
fmt.Printf("%#v\n", me.Addr)
fmt.Printf("%#v\n", me2.Addr)
hh := NewUser(2, "hh", "北京市", "海澱路", "0001")
fmt.Printf("%#v\n", hh)
}
結構體對外是否可見,在
go
中受其首字母是否大寫控制,結論是
結構體首字母大寫則包外可見(公開的),否者僅包内可通路(内部的)
結構體屬性名首字母大寫包外可見(公開的),否者僅包内可通路(内部的)
組合起來的可能情況:
- 結構體名首字母大寫,屬性名大寫:結構體可在包外使用,且通路其大寫的屬性名
- 結構體名首字母大寫,屬性名小寫:結構體可在包外使用,且不能通路其小寫的屬性名
-
結構體名首字母小寫,屬性名大寫:結構體隻能在包内使用,屬性通路在結構體嵌入時由被嵌入結構體(外層)決定,被嵌入結構體名首字母大寫時屬性名包外可見,否者隻能
在包内使用
- 結構體名首字母小寫,屬性名小寫:結構體隻能在包内使用
- 結構體成員變量在同包内小寫也是可以通路到的
總結:
- 跨包通路:全局變量、結構體本身、結構體成員變量、必須要首字母大寫才可以暴露出來被通路到(在go中常見的是會給結構體綁定一個方法,傳回小寫的成員變量讓外面通路到)
- 同包通路:上述變量首字母小寫也可以被通路到
示例:
首先在
tt
包下定義一個
person
結構體,
person
大寫的時候外部的包可以通路到,
person
小寫的時候外部的包不可以通路到
package main
import (
"fmt"
"go-learning/chapter06/tt"
)
func main() {
p1 := tt.Person{
Name: "geek",
Age: 18,
}
fmt.Println(p1)
/*
# command-line-arguments
./last.go:9:8: cannot refer to unexported name tt.person
*/
}
See you ~