go語言的結構體


go語言的結構體

簡介

go語言中沒有類的概念,也不支持類的繼承等面向對象的概念, 但在結構體的內嵌配合接口比面向對象更具更高的擴展性和靈活性。

Go語言中的基礎數據類型可以表示一些事物的基本屬性,但是當我們想表達一個事物的全部或部分屬性時,這時候再用單一的基本數據類型明顯就無法滿足需求了,Go語言提供了一種自定義數據類型,可以封裝多個基本數據類型,這種數據類型叫結構體struct,通過結構體來實現面向對象。

定義

使用typestruct關鍵字來定義結構體,格式如下:

type 類型名 struct {
    字段名 字段類型
    字段名 字段類型
    …
}

其中:

  • 類型名:表示自定義結構體的名稱,在同一個包內不能重復。
  • 字段名:表示結構體字段名,結構體中的名字必須唯一。
  • 字段類型:表示結構體字段的具體類型。

舉個例子,定義一個坐標結構體:

type Point struct {
    X int
    Y int
}

同種類型的字段也可以放一行,定義一個顏色的三原色結構體:

type Color struct {
    R, G, B byte
}

結構體的實例化

結構體的定義只是一種內存布局的描述,此時並不會分配內存,只有實例化的時候才會真正分配內存。

在定義結構體並實例化才能使用結構體字段,有多重實例方式。

基本實例化

結構體本身是一種類型,可以像聲明變量一樣,以 var 的方式聲明結構體即可完成實例化。

var 結構體實例 結構體類型

舉個例子:

package main

import (
	"fmt"
)

type Person struct {
	name string
	city string
	age  int
}

func main() {
	var p1 Person
	p1.name = "江子牙"
	p1.city = "江西"
	p1.age = 23

	// {江子牙 江西 23}
	fmt.Println(p1)
	
	// 類型:main.Person       值:main.Person{name:"江子牙", city:"江西", age:23}
	fmt.Printf("類型:%T\t值:%#v", p1, p1)
}

我們可以通過.來對結構體的字段進行賦值和取值操作。

創建指針類型的結構體

我們還可以通過使用new關鍵字對結構體進行實例化,得到的是結構體的地址。 格式如下:

package main

import (
	"fmt"
)

type Person struct {
	name string
	city string
	age  int
}

func main() {
	p1 := new(Person)

	// &{  0} 得到的是一個結構體指針,字段的值對應該字段類型的零值
	fmt.Println(p1)
	// 指針:0xc00006e2d0      類型:*main.Person      值:&main.Person{name:"", city:"", age:0}
	fmt.Printf("指針:%p\t類型:%T\t值:%#v", p1, p1, p1)
}

盡管得到的是一個結構體指針,但是go語言中同樣可以通過.來進行對字段進行操作:

package main

import (
	"fmt"
)

type Person struct {
	name string
	city string
	age  int
}

func main() {
	p1 := new(Person)

	p1.name = "江子牙"
	p1.city = "江西"
	p1.age = 23

	// &{江子牙 江西 23}
	fmt.Println(p1)
	// 指針:0xc00006e2d0      類型:*main.Person      值:&main.Person{name:"江子牙", city:"江西", age:23}
	fmt.Printf("指針:%p\t類型:%T\t值:%#v", p1, p1, p1)
}

取結構體的地址實例化

使用&對結構體進行取地址操作相當於對結構體進行了一次new操作。

package main

import (
	"fmt"
)

type Person struct {
	name string
	city string
	age  int
}

func main() {
	p2 := &Person{}
	p2.name = "江子牙"
	p2.city = "江西"
	p2.age = 23

	// &{江子牙 江西 23}
	fmt.Println(p2)
	// 指針:0xc00006e2d0      類型:*main.Person      值:&main.Person{name:"江子牙", city:"江西", age:23}
	fmt.Printf("指針:%p\t類型:%T\t值:%#v", p2, p2, p2)
}

結構體的初始化

鍵值對初始化

類似於Python的關鍵字傳參

package main

import (
	"fmt"
)

type Person struct {
	name string
	age int
	isLogin bool
} 

func main() {

	p1 := Person{
		name:"江子牙",
		age:23,
		isLogin:true,
	}
	
	// {江子牙 23 true}
	fmt.Println(p1)
	
}

值的列表初始化

類似於Python的位置參數

package main

import (
	"fmt"
)

type Person struct {
	name string
	age int
	isLogin bool
}

func main() {

	p2 := Person{
		"江子牙", 23, true,
	}
	// {江子牙 23 true}
	fmt.Println(p2)
}

使用此方式初始化時,值得注意的是:

  • 必須初始化結構體的所有字段
  • 初始化順序必須和結構體聲明時保持一致
  • 兩種方式的初始化不能混用

匿名結構體的初始化

顧名思義,匿名結構體就是沒有名字的結構體,無需用type關鍵字聲明就可以直接使用。

package main

import (
	"fmt"
)

func main() {
	s1 := struct {
		name string
		age  int
	}{
		"江子牙",
		23,
	}
    
	// 江子牙
	fmt.Println(s1.name)
	// 23
	fmt.Println(s1.age)
	// {江子牙 23}
	fmt.Println(s1)
}

構造函數

結構體沒有構造函數,我們可以自己實現。因為結構體是值類型,如果結構體較復雜的話,值拷貝的性能開銷會比較大,所以我們的構造函數返回結構體指針類型。

package main

import "fmt"

type Cat struct {
	Name  string
	Color string
}

func NewCat(name, color string) *Cat {
	return &Cat{
		Name:  name,
		Color: color,
	}
}

func main() {
	
    // 調用構造函數實例化一個小豹子
	cat := NewCat("小豹子", "豹紋")
	// &{小豹子 豹紋}
	fmt.Println(cat)
	// 小豹子
	fmt.Println(cat.Name)
	// 豹紋
	fmt.Println(cat.Color)
}

方法Method

go語言中的方法method是一種作用於特定類型變量的函數,叫做receiver,可以理解為其他語言的thisself

格式如下:

func (接收器變量 接收器類型) 方法名 (參數列表) (返回值列表) {
    函數體
}
  • 接收器變量:官方建議,該接收器變量最好和接收器類型的第一個字母保持一致。如 Cat c 、Bag b 、Dog d。
  • 接收者類型:可以是指針類型和非指針類型。
  • 方法名、參數列表、返回值:與函數定義相同。
package main

import "fmt"

// 定義一個包的結構體
type Bag struct {
	items []interface{}
}

// 為結構體定義一個Insert方法
func (b *Bag) Insert(thing interface{}) {
	b.items = append(b.items, thing)
}

func main() {

	// 實例化一個包
	bag := new(Bag)
	// 放入一個東西
	bag.Insert("口紅")

	// [口紅]
	fmt.Println(bag.items)
	// &{[口紅]}
	fmt.Println(bag)


	// 放入一個手機
	bag.Insert("手機")
	// [口紅 手機]
	fmt.Println(bag.items)
	// &{[口紅 手機]}
	fmt.Println(bag)

}

接收者

指針類型的接收者

指針類型的接收器是一個結構體的指針,更接近於面向對象的this or self。

由於指針的特性,調用方法時,修改接收器指針的任意成員變量,在方法調用結束后,已經修改過得都是有效的。

上個例子的接收器*Bag就是一個指針接收器,所以才能append之后保持修改。

值類型的接收者

當方法作用與非指針接收器時,go語言內部會在代碼運行的時候將接收器的數據復制一份,在非指針接收器的方法可以獲取,也可以修改,只是修改無效而已。

package main

import "fmt"

type Bag struct {
	name string
}

func (b *Bag) SetName(name string) {
	b.name = name
}

func (b Bag) SetName1(name string) {
	fmt.Println(name, "修改不生效")
	b.name = name
}

func main() {

	// 實例化一個包
	bag := new(Bag)

	// 設置
	bag.SetName("指針類型的接收者")
	// 生效了:指針類型的接收者
	fmt.Println(bag.name)
	// 再設置
	bag.SetName1("值類型的接收者")
	// 不生效:指針類型的接收者
	fmt.Println(bag.name)

}

總結

  • 需要修改接收者中的值

  • 接收者是拷貝代價比較大的大對象

  • 保證一致性,如果有某個方法使用了指針接收者,那么其他的方法也應該使用指針接收者。

為任意類型添加方法

go語言可以對任何一種類型添加方法。給一種類型添加方法就像給結構體添加方法一樣。

package main

import "fmt"

// 定義一個MyInt類型
type MyInt int

// 為 MyInt 添加是否為0的方法
func (m MyInt) IsZero() bool {
	return m == 0
}

// 為 MyInt 添加add()方法
func (m MyInt) Add(other int) int {
	return int(m) + other
}

func main() {
	var b MyInt
	// 定義之后為零值
	fmt.Println(b.IsZero())
	
	b = 100
	// 賦值之后不為0
	fmt.Println(b.IsZero())
	// 兩數相加:100+150
	fmt.Println(b.Add(150))
}

結構體的匿名字段

結構體允許字段沒有字段名,只有類型。這種字段就叫做匿名字段。

package main

import "fmt"

// 定義一個匿名字段的結構體
type Person struct {
	string
	int
}

func main() {
	p1 := &Person{
		"江子牙",
		23,
	}
	// &{江子牙 23}
	fmt.Println(p1)
}

嵌套結構體

一個結構體中可以嵌套包含另一個結構體或結構體指針。

比如:

package main

import "fmt"

// 定義一個學生的結構體
type Student struct {
	name string
	age int
	*Class  // 嵌套一個匿名結構體的指針
	School // 嵌套一個匿名結構體
}

// 定義一個班級結構體
type Class struct {
	className string
}

// 定義一個學校結構體
type School struct {
	schoolName string
}

func main() {
	s1 := &Student{
		"江子牙",
		23,
		&Class{
			"高三一班",
		},
		School{"明珠中學"},
	}
	// &{江子牙 23 0xc0000421c0 {明珠中學}}
	fmt.Println(s1)
	// 高三一班
	fmt.Println(s1.className)
	// 明珠中學
	fmt.Println(s1.schoolName)
}

學生結構體嵌套了班級和學校兩個結構體,獲取班級學校和班級名字的時候,會一直往下找。可以通過.的方式。

結構體的繼承

go語言沒有面向對象,更沒有繼承。只是可以實現同樣的效果。

package main

import "fmt"

// 定義一個兒子的結構體
type Child struct {
	name string
	*Father // 通過匿名結構體實現繼承
}

// 定義一個父親結構體
type Father struct {
	money int64
}

// 為父親添加一個生小孩的方法
func (f *Father) Make(name string) {
	fmt.Printf("%s生小孩", name)
}

func main() {
	ch := &Child{
		name:"小明",
		Father:&Father{50000},
	}

	// 小明繼承的財產為:50000
	fmt.Printf("%s繼承的財產為:%d\n",ch.name, ch.money )

	// 小明生小孩
	ch.Make("小明")
}

小明結構體中並沒有money字段和Make方法。這兩個都是他父親結構體中才有的,這樣一來便實現了繼承屬性和方法。

結構體字段的可見性

結構體中字段大寫開頭表示可公開訪問,小寫表示私有(僅在定義當前結構體的包中可訪問)。

結構體json序列化

package main

import (
	"encoding/json"
	"fmt"
)

// 學生結構體
type Student struct {
	ID      int
	Gender  string
	Name    string
	IsLogin bool
}

func main() {
	var stu1 = Student{
		ID:      1,
		Gender:  "男",
		Name:    "豪傑",
		IsLogin: false,
	}

	// 序列化:把內存里的數據轉換為字符串,以便用於網絡傳輸和數據交換
	if v, err := json.Marshal(stu1); err != nil {
		fmt.Println("JSON格式化出錯")
		fmt.Println(err)
	} else {
		// 字節類型的切片
		fmt.Println(v)
		// 轉為字符串
		fmt.Println(string(v))
	}

	// 反序列化:把json字符串轉換為當前編程語言可用的對象
	str := `{"ID":1,"Gender":"男","Name":"豪傑","IsLogin":false}`
	stu2 := new(Student)
	_ = json.Unmarshal([]byte(str), stu2)
	fmt.Println(stu2)
	fmt.Printf("stu2 類型:%T\tstu2值:%#v", stu2, stu2)
}

結構體的標簽

Tag是結構體的元信息,可以在運行的時候通過反射的機制讀取出來。 Tag在結構體字段的后方定義,由一對反引號包裹起來,具體的格式如下:

`key1:"value1" key2:"value2"`

結構體標簽由一個或多個鍵值對組成。鍵與值使用冒號分隔,值用雙引號括起來。鍵值對之間使用一個空格分隔。

注意事項: 為結構體編寫Tag時,必須嚴格遵守鍵值對的規則。結構體標簽的解析代碼的容錯能力很差,一旦格式寫錯,編譯和運行時都不會提示任何錯誤,通過反射也無法正確取值。例如不要在key和value之間添加空格。

因為序列化和反序列化大多是與前端進行數據交互。不能給他返回一些大寫的鍵。所以可以通過tag來解決。

package main

import (
	"encoding/json"
	"fmt"
)

//Student 學生
type Student struct {
	ID     int    `json:"id"` //通過指定tag實現json序列化該字段時的key
	Gender string //json序列化是默認使用字段名作為key
	name   string //私有不能被json包訪問
}

func main() {
	s1 := Student{
		ID:     1,
		Gender: "男",
		name:   "沙河娜扎",
	}
	data, err := json.Marshal(s1)
	if err != nil {
		fmt.Println("json marshal failed!")
	}

	//json str:{"id":1,"Gender":"男"}
	fmt.Printf("json str:%s\n", data)
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM