天天看點

Golang資料類型之結構體-下篇

目錄

  • 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 ~