Golang數(shù)據(jù)類型之結(jié)構(gòu)體-下篇
目錄
1、結(jié)構(gòu)體指針
1.1 聲明
1.2 聲明并初始化
1.3 通過 new 函數(shù)創(chuàng)建指針對象
1.4 傳遞結(jié)構(gòu)體指針
1.5 結(jié)構(gòu)體值與結(jié)構(gòu)體指針
1.6 傳值還是傳遞指針
2、匿名結(jié)構(gòu)體
3、結(jié)構(gòu)體方法
4、結(jié)構(gòu)體嵌套
4.1 匿名嵌套
4.2 命名嵌套
4.3 指針類型結(jié)構(gòu)體嵌套
4.4 結(jié)構(gòu)體嵌套的實際意義
5、通過函數(shù)創(chuàng)建結(jié)構(gòu)體對象
6、結(jié)構(gòu)體的可見性

本文是 Golang數(shù)據(jù)類型之結(jié)構(gòu)體-上篇 的續(xù)篇內(nèi)容
1、結(jié)構(gòu)體指針
1.1 聲明
和其他基礎(chǔ)數(shù)據(jù)類型一樣,也可聲明結(jié)構(gòu)體指針變量,此時變量被初始化為nil
func TestMain4(t *testing.T) {
var person *Person
fmt.Println(person) // <nil>
}
1.2 聲明并初始化
聲明并初始化指針對象
// 先聲明再初始化
//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
1.3 通過 new 函數(shù)創(chuàng)建指針對象
Go中常定義N(n)ew+結(jié)構(gòu)體名命名的函數(shù)用于創(chuàng)建對應(yīng)的結(jié)構(gòu)體值對象或指針對象
person := new(Person)
fmt.Printf("%p", person) // 0xc00013a080
fmt.Printf("%T", person) // *test.Person
// 定義工廠函數(shù)用于創(chuàng)建Author對象
func NewAuthor(id int, name, birthday, addr, tel, desc string) *User {
return &User{id, name, birthday,addr, tel, desc}
}
// 調(diào)用
me8 := NewAuthor(1004, "geek", "2021-06-08", "北京市", "15588888888", "備注")
fmt.Printf("%T: %#v\n", me8, me8)
1.4 傳遞結(jié)構(gòu)體指針
將一個結(jié)構(gòu)體的指針傳遞給函數(shù),能否修改到該結(jié)構(gòu)體
結(jié)果是可以修改該實例對象
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
}
1.5 結(jié)構(gòu)體值與結(jié)構(gòu)體指針
什么是值?什么是指針?
下面三種方式都可以構(gòu)造Car struct的實例
c1 := Car{}
c2 := &Car{}
c3 := new(Car)
fmt.Println(c1, c2, c3) // { } &{ } &{ }
c1、c2、c3都是car struct的實例,c2, c3是指向?qū)嵗闹羔槪羔樦斜4娴氖菍嵗牡刂罚灾羔樤僦赶驅(qū)嵗?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(150, 84, 181);">c1則是直接指向?qū)嵗_@三個變量與Car struct實例的指向關(guān)系如下
變量名 指針 數(shù)據(jù)對象(實例)
-------------------------------
c1 -------------------> { }
c2 -----> ptr(addr) --> { }
c3 -----> ptr(addr) --> { }
訪問實例和訪問實例指針是否有區(qū)別
fmt.Println("c1, ", c1.Color) // 訪問實例的屬性
fmt.Println("c2, ", (*c2).Color) // 先通過*求出 指針的值,就是實例的內(nèi)存地址, 然后通過實例的內(nèi)存地址訪問該實例對象的屬性
如果我們需要訪問指針對象的屬性, 上面的(*c2).Color是理論上的正確寫法, 可以看出過于繁瑣, 而我們方法指針,往往也是想訪問這個指針的實例, 所以編譯幫我們做了優(yōu)化, 比如訪問指針實例也可以這樣寫
fmt.Println("c2, ", c2.Color) // 編譯器自動補充上(*c2).Color, 這樣寫法上就簡潔了
簡單總結(jié):盡管一個是數(shù)據(jù)對象值,一個是指針,它們都是數(shù)據(jù)對象的實例。也就是說,p1.name和p2.name都能訪問對應(yīng)實例的屬性,只是指針的訪問寫法是一種簡寫(正確寫法由編譯器補充)
1.6 傳值還是傳遞指針
前面文章 Golang函數(shù)參數(shù)的值傳遞和引用傳遞 說的也是這個話題
即什么時候傳值,什么時候傳遞指針?
傳遞值: 不希望實例被外部修改的時候,傳值就相當(dāng)于 copy了一份副本給函數(shù)傳遞指針: 希望外部能修改到這個實例本身的時候,就需要傳遞該實例的指針,就是把該實例的內(nèi)存地址告訴對方,可以通過地址直接找到本體
但是經(jīng)常看到函數(shù)接收的結(jié)構(gòu)體參數(shù)都是指針是為什么
因為復(fù)制傳值時,如果函數(shù)的參數(shù)是一個struct對象,將直接復(fù)制整個數(shù)據(jù)結(jié)構(gòu)的副本傳遞給函數(shù),這有兩個問題
函數(shù)內(nèi)部無法修改傳遞給函數(shù)的原始數(shù)據(jù)結(jié)構(gòu),它修改的只是原始數(shù)據(jù)結(jié)構(gòu)拷貝后的副本 如果傳遞的原始數(shù)據(jù)結(jié)構(gòu)很大,完整地復(fù)制出一個副本開銷并不小
所以為了節(jié)省開銷一般都會選擇傳遞指針
2、匿名結(jié)構(gòu)體
在定義變量時將類型指定為結(jié)構(gòu)體的結(jié)構(gòu),此時叫匿名結(jié)構(gòu)體。匿名結(jié)構(gòu)體常用于初始化一次結(jié)構(gòu)體變量的場景,例如項目配置
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"}
}
3、結(jié)構(gòu)體方法
可以為結(jié)構(gòu)體定義屬于自己的函數(shù)
在聲明函數(shù)時,聲明屬于結(jié)構(gòu)體的函數(shù),方法與結(jié)構(gòu)體綁定,只能通過結(jié)構(gòu)體person的實例訪問,不能在外部直接訪問,這就是結(jié)構(gòu)體方法和函數(shù)的區(qū)別,例如
// p 是person的別名
func (p Person) add() int {
return p.Age * 2
}
調(diào)用結(jié)構(gòu)體方法
func TestMain6(t *testing.T) {
m := new(Person)
m.Age = 18
fmt.Println(m.add()) // 36
}
4、結(jié)構(gòu)體嵌套
4.1 匿名嵌套
簡單來說,就是將數(shù)據(jù)結(jié)構(gòu)直接放進去,放進去的時候不進行命名
在定義變量時將類型指定為結(jié)構(gòu)體的結(jié)構(gòu),此時叫匿名結(jié)構(gòu)體。匿名結(jié)構(gòu)體常用于初始化一次結(jié)構(gòu)體變量的場景,例如項目配置
匿名結(jié)構(gòu)體可以組合不同類型的數(shù)據(jù),使得處理數(shù)據(jù)變得更為靈活。尤其是在一些需要將多個變量、類型數(shù)據(jù)組合應(yīng)用的場景,匿名結(jié)構(gòu)體是一個不錯的選擇
// 訪問方式 結(jié)構(gòu)體.成員名
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數(shù)據(jù)
func getJsonData() []byte {
//tempData 接收匿名結(jié)構(gòu)體(匿名結(jié)構(gòu)體使得數(shù)據(jù)的結(jié)構(gòu)更加靈活)
tempData := struct {
Screen01
Battery
HashTouchId bool // 是否有指紋識別
}{
Screen01: Screen01{Size: 12, ResX: 36, ResY: 36},
Battery: Battery{"6000毫安"},
HashTouchId: true,
}
jsonData, _ := json.Marshal(tempData) //將數(shù)據(jù)轉(zhuǎn)換為json
return jsonData
}
4.2 命名嵌套
結(jié)構(gòu)體命名嵌入是指結(jié)構(gòu)體中的屬性對應(yīng)的類型也是結(jié)構(gòu)體
給嵌入的結(jié)構(gòu)體一個名字,讓其成為另一個結(jié)構(gòu)體的屬性
適用于復(fù)合數(shù)據(jù)結(jié)構(gòu)<嵌入匿名>
嵌套定義
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]
}
4.3 指針類型結(jié)構(gòu)體嵌套
結(jié)構(gòu)體嵌套(命名&匿名)類型也可以為結(jié)構(gòu)體指針
聲明&初始化&操作
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{},
}
}
使用屬性為指針類型底層共享數(shù)據(jù)結(jié)構(gòu),當(dāng)?shù)讓訑?shù)據(jù)發(fā)生變化,所有引用都會發(fā)生影響 使用屬性為值類型,則在復(fù)制時發(fā)生拷貝,兩者不相互影響
4.4 結(jié)構(gòu)體嵌套的實際意義
例如大項目對應(yīng)復(fù)雜的配置文件,將公共的字段抽取出來,放到一個公共 common的結(jié)構(gòu)體cmdb、資產(chǎn)系統(tǒng)等類型設(shè)計
示例
package main
import "time"
// 云有云資源公共字段
type Common struct {
ChargingMod string // 付費模式:預(yù)付費和后付費
Region string // 區(qū)域
Az string // 可用區(qū)
CreateTime time.Time // 購買時間
}
type Ecs struct {
Common
guide string // 4C 16G
}
type Rds struct {
Common
dbType string // 代表數(shù)據(jù)庫是哪一種
}
5、通過函數(shù)創(chuàng)建結(jié)構(gòu)體對象
除了通過直接賦值創(chuàng)建結(jié)構(gòu)體對象,還可以通過函數(shù)來創(chuàng)建,也就是把創(chuàng)建結(jié)構(gòu)體對象的過程進行封裝
即“工廠函數(shù)”
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)
}
6、結(jié)構(gòu)體的可見性
結(jié)構(gòu)體對外是否可見,在go中受其首字母是否大寫控制,結(jié)論是
結(jié)構(gòu)體首字母大寫則包外可見(公開的),否者僅包內(nèi)可訪問(內(nèi)部的) 結(jié)構(gòu)體屬性名首字母大寫包外可見(公開的),否者僅包內(nèi)可訪問(內(nèi)部的)
組合起來的可能情況:
結(jié)構(gòu)體名首字母大寫,屬性名大寫:結(jié)構(gòu)體可在包外使用,且訪問其大寫的屬性名 結(jié)構(gòu)體名首字母大寫,屬性名小寫:結(jié)構(gòu)體可在包外使用,且不能訪問其小寫的屬性名 結(jié)構(gòu)體名首字母小寫,屬性名大寫:結(jié)構(gòu)體只能在包內(nèi)使用,屬性訪問在結(jié)構(gòu)體嵌入時由被嵌入結(jié)構(gòu)體(外層)決定,被嵌入結(jié)構(gòu)體名首字母大寫時屬性名包外可見,否者只能 在包內(nèi)使用 結(jié)構(gòu)體名首字母小寫,屬性名小寫:結(jié)構(gòu)體只能在包內(nèi)使用 結(jié)構(gòu)體成員變量在同包內(nèi)小寫也是可以訪問到的
總結(jié):
跨包訪問:全局變量、結(jié)構(gòu)體本身、結(jié)構(gòu)體成員變量、必須要首字母大寫才可以暴露出來被訪問到(在 go 中常見的是會給結(jié)構(gòu)體綁定一個方法,返回小寫的成員變量讓外面訪問到) 同包訪問:上述變量首字母小寫也可以被訪問到
示例:
首先在tt包下定義一個person結(jié)構(gòu)體,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 ~
