Go語言結構體(struct)物件導向程式設計基礎篇

尹正杰發表於2024-07-27

                                              作者:尹正傑

版權宣告:原創作品,謝絕轉載!否則將追究法律責任。

目錄
  • 一.物件導向的引入
    • 1.Golang語言物件導向程式設計
    • 2.結構體的引入
    • 3.結構體定義
    • 4.結構體五種初始化方式
    • 5.結構體的互相轉換
    • 6.匿名結構體
  • 二.結構體方法定義
    • 1.方法概述
    • 2.方法是值複製傳遞方式
    • 3.指標型別的接收者
    • 4.為內建資料型別繫結方法
    • 5.結構體的String()方法
    • 6.函式和方法的區別
    • 7.什麼時候應該使用指標型別接受者
  • 三.跨包建立結構體例項
    • 1 首字母大寫可以支援跨包
      • 1.1 專案測試結構
      • 1.2 建立go.mod檔案
      • 1.3 dongman.go
      • 1.4 main.go
      • 1.5 測試程式碼
    • 2 首字母小寫基於工廠模式實現跨包
      • 2.1 專案測試結構
      • 2.2 建立go.mod檔案
      • 2.3 dongman.go
      • 2.4 main.go
      • 2.5 測試程式碼

一.物件導向的引入

1.Golang語言物件導向程式設計

- 1.Golang也支援物件導向程式設計(OOP),但是和傳統的物件導向程式設計有區別,並不是純粹的面嚮物件語言,所以我們說Golang支援物件導向程式設計特性是比較準確的;

- 2.Golang中沒有類(Class)的概念,Go語言的結構體(struct)和其他程式語言的類有同等的地位,你可以理解Golang是基於struct來實現OOP特性的;

- 3.Golang物件導向程式設計非常簡潔,卻掉了傳統OOP語言的方法過載,建構函式和解構函式,隱藏的this指標等等;

- 4.Golang仍然有物件導向程式設計的繼承,封裝和多型的特性,只是實現的方式和其他OOP語言不一樣,比如繼承Golang沒有類似於extends關鍵字,而是透過匿名欄位實現;

- 5.Golang中透過結構體的內嵌再配合介面比物件導向具有更高的擴充套件性和靈活性。

- 6.Golang中有一些基本的資料型別,如string、整型、浮點型、布林等資料型別, Go語言中可以使用type關鍵字來定義自定義型別。

- 7.Golang自定義型別是定義了一個全新的型別。我們可以基於內建的基本型別定義,也可以透過struct定義。

2.結構體的引入

package main

import "fmt"

func main() {

	var (
		// 描述《仙逆》男主
		dongman string   = "仙逆"
		name    string   = "王林"
		gender  bool     = true
		hobby   []string = []string{"修煉", "李慕婉", "極鏡"}

		// 描述《凡人修仙傳》男主
		dongman2 string   = "凡人修仙傳"
		name2    string   = "韓立"
		gender2  bool     = true
		hobby2   []string = []string{"煉丹", "陣法", "修煉"}
	)

	/*
		如果描述一個物件一直使用變數來處理,則會存在以下缺點:
			- 1.不利於資料的管理和維護;
			- 2.每個動漫的很多熟悉屬於一個物件,用變數管理太分散;
	
		綜上所述,我們就不得不學習結構體(struct):
			- 1.Go語言中的基礎資料型別可以表示一些事物的基本屬性,但是當我們想表達一個事物的全部或部分屬性時,這時候再用單一的基本資料型別明顯就無法滿足需求了。
			- 2.Go語言提供了一種自定義資料型別,可以封裝多個基本資料型別,這種資料型別叫結構體,英文名稱struct。 也就是我們可以透過struct來定義自己的型別了。

	*/
	fmt.Printf("《%s》name: [%s], gender: [%t], hobby: %v\n", dongman, name, gender, hobby)
	fmt.Printf("《%s》name: [%s], gender: [%t], hobby: %v\n", dongman2, name2, gender2, hobby2)

}

3.結構體定義

package main

import "fmt"

// DongMan 定義動漫結構體,將動漫中各個屬性放入一個結構體中管理
type DongMan struct {

	// 變數名稱大寫外界可以訪問這個屬性
	Name string

	Age int

	Gender bool

	Hobby []string
}

func main() {
	// 建立動漫結構體的例項,物件,變數

	// 描述《仙逆》男主
	var xianni DongMan
	xianni.Name = "王林"
	xianni.Gender = true
	xianni.Hobby = []string{"修煉", "李慕婉", "極鏡"}
	xianni.Age =400

	// 描述《凡人修仙傳》男主
	var xiuxian DongMan
	xiuxian.Name = "韓立"
	xiuxian.Gender = true
	xiuxian.Hobby = []string{"煉丹", "陣法", "修煉"}
	xiuxian.Age = 217
	
	fmt.Printf("《仙逆》: %v\n", xianni)
	fmt.Printf("《凡人修仙傳》: %v\n", xiuxian)
}

4.結構體五種初始化方式

package main

import "fmt"

// DongMan 定義動漫結構體,將動漫中各個屬性放入一個結構體中管理
type DongMan struct {

	// 變數名稱大寫外界可以訪問這個屬性
	Name string

	Age int

	Gender bool

	Hobby []string
}

func main() {
	// 例項化方式一: 先定義再賦值
	var xianni DongMan
	xianni.Name = "王林"
	xianni.Gender = true
	xianni.Age = 400
	xianni.Hobby = []string{"修煉", "李慕婉", "極鏡"}

	fmt.Println(xianni)

	// 例項化方式二: 按照欄位定義順序初始化賦值,缺點: 必須按照順序填寫且各欄位不可省略,有侷限性。
	var xiuxian DongMan = DongMan{
		"韓立", 217, true, []string{"煉丹", "陣法", "修煉"},
	}
	
	// 例項化方式三: 按照欄位名稱初始化賦值,即使用鍵值對初始化,跟順序無關。
	var yongSheng DongMan = DongMan{
		Hobby:  []string{"修煉", "耍酷"},
		Gender: true,
		Name:   "方寒",
		Age:    20,
	}

	// 例項化方式四: 透過指標變數賦值, 透過new方法返回初始化變數,得到指標變數
	var yiNianYongHeng *DongMan = new(DongMan)
	// "yiNianYongHeng"是指標,其實指向的就是地址,應該給這個地址指向的物件欄位賦值,其中"*"的作用就是根據地址取值
	(*yiNianYongHeng).Name = "白小純"
	(*yiNianYongHeng).Age = 50
	// 為了符合程式設計師的程式設計習慣,go提供了簡化的賦值方式,go編譯器底層對"yiNianYongHeng.Gender"轉換為"(*yiNianYongHeng).Gender",這種簡寫的方式我們稱之為"語法糖"
	yiNianYongHeng.Gender = true 
	yiNianYongHeng.Hobby = []string{"煉丹", "修煉", "整蠱"}

	// 例項化方式五: 取物件地址值返回指標物件,取結構體的地址例項化。用&對結構體進行取地址操作相當於對該結構體型別進行了一次new例項化操作
	var tunShiXingKong *DongMan = &DongMan{}

	(*tunShiXingKong).Name = "羅峰"
	(*tunShiXingKong).Age = 25
	tunShiXingKong.Gender = true
	tunShiXingKong.Hobby = []string{"修煉", "宇宙級精神念師"}

	fmt.Printf("《仙逆》: %v\n", xianni)
	fmt.Printf("《凡人修仙傳》: %v\n", xiuxian)
	fmt.Printf("《永生》: %v\n", yongSheng)
	fmt.Printf("《一念永恆》: %v\n", *yiNianYongHeng)
	fmt.Printf("《吞噬星空》: %v\n", *tunShiXingKong)

}

5.結構體的互相轉換

package main

import "fmt"

type Cat struct {
	Name string
	Age  uint8
}

type Dog struct {
	Name string
	Age  uint8
}

type BaGeQuan Dog

func main() {
	var (
		b BaGeQuan = BaGeQuan{"雨花劍傳人神醫", 19}
		c Cat      = Cat{"虹貓", 18}
		d Dog      = Dog{"逗逗", 17}
	)
	fmt.Printf("轉換前: b = %v\n", b)
	fmt.Printf("轉換前: c = %v\n", c)
	fmt.Printf("轉換前: d = %v\n", d)

	// 結構體是使用者單獨定義的型別,和其他型別進行轉換時需要有完全相同的欄位(名字,個數和型別)
	c = Cat(d)

	// 結構體進行type重新定義(相當於取別名),Golang認為是新的資料型別,但是互相間可以強轉。
	d = Dog(b)

	fmt.Println("----- 分割線 -----")
	fmt.Printf("轉換後: b = %v\n", b)
	fmt.Printf("轉換後: c = %v\n", c)
	fmt.Printf("轉換後: d = %v\n", d)
}

6.匿名結構體

package main

import (
	"fmt"
)

func main() {

	// 在定義一些臨時資料結構等場景下還可以使用匿名結構體。
	var user struct {
		Name string
		Age  int
	}

	// 使用匿名結構體進行賦值
	user.Name = "Jason Yin"
	user.Age = 18

	fmt.Printf("%#v\n", user)
}

二.結構體方法定義

1.方法概述

Go語言中的方法(Method)是一種作用於特定型別變數的函式,這種特定型別變數叫做接收者(Receiver)。接收者的概念就類似於其他語言中的this或者self。

方法的定義格式如下:
    func (接收者變數 接收者型別) 方法名(引數列表) (返回引數) {
        函式體
    }

  相關引數說明:
    接收者變數:
      接收者中的引數變數名在命名時,官方建議使用接收者型別名稱首字母的小寫,而不是self、this之類的命名。
      例如,Person型別的接收者變數應該命名為p。

    接收者型別:
      接收者型別和引數類似,可以是指標型別和非指標型別。

    方法名、引數列表、返回引數:
      具體格式與函式定義相同。


方法使用注意事項:
	- 1.結構體型別是值型別,在方法呼叫中,遵守值型別的傳遞機制,是值複製傳遞方式;
	- 2.如程式設計師希望在方法中,修改結構體變數的值,可以透過結構體指標的方式來傳遞;
	- 3.Golang中的方法作用在指定的資料型別上的,和指定的資料型別繫結,因此自定義型別,都可以有方法,而不僅僅是struct,比如int,float32等都可以有方法;
	- 4.方法的訪問控制的規則,和函式一樣,方法名首字母小寫,只能在本包訪問,方法首字母大寫,可以在本包和其他包訪問;
	- 5.如果一個型別實現了String()這個方法,那麼"fmt.Println"預設會呼叫這個變數的"String()"方法進行輸出;


函式和方法的區別如下:
		- 1.繫結指定型別
		方法需要繫結指定資料型別,而函式不需要繫結資料型別。
		
		- 2.呼叫方式不一樣
			方法的呼叫方式為: "物件.方法名稱(實參列表)",函式的呼叫方式為: "函式名(實參列表)"。
			
		- 3.傳遞引數方式不一樣
			對於方法來說,接受者為值型別,可以傳入指標型別,接受值為指標型別,可以傳入值型別。
			對於函式來說,引數型別對應是什麼就要傳入對應的型別。

2.方法是值複製傳遞方式

package main

import "fmt"

// DongMan 定義動漫結構體
type DongMan struct {
	Name   string
	Leader string
	Age    uint16
}

// 給DongMan結構體繫結SayHi方法,方法名稱可以隨意起
func (d DongMan) SayHi() {
	d.Age = 500
	fmt.Printf("大家好,我是《%s》的男主[%s],[%d]歲達到元嬰期境界~\n", d.Name, d.Leader, d.Age)
}

func main() {
	var xianNi DongMan

	// 結構體物件傳入SayHi方法中是值傳遞,和函式引數傳遞效果一致
	xianNi.Name = "仙逆"
	xianNi.Leader = "王林"
	xianNi.Age = 407

	// 結構體DongMan和SayHi方法繫結,呼叫SayHi方法必須靠指定型別DongMan,如果其他型別呼叫SayHi一定會報錯。
	xianNi.SayHi()

	fmt.Printf("xianNi.Age =  %d\n", xianNi.Age)
}

3.指標型別的接收者

package main

import "fmt"

type DongMan struct {
	Name   string
	Leader string
	Age    uint16
}

func (d DongMan) SayHi() {
	fmt.Printf("in SayHi ... d的地址: %p, d儲存的資料:%p\n", &d, &d)

	fmt.Printf("大家好,我是《%s》的男主[%s],[%d]歲達到元嬰期境界~\n", d.Name, d.Leader, d.Age)
}

func (d *DongMan) SetAge(age uint16) {
	(*d).Age = age // 可簡寫為"d.Age = age"

	fmt.Printf("in SetAge ... d的地址: %p, d儲存的資料:%p\n", &d, d)
}

func main() {
	var xianNi DongMan

	xianNi.Name = "仙逆"
	xianNi.Leader = "王林"
	xianNi.Age = 407

	fmt.Printf("in main ... xianNi的地址: %p\n", &xianNi)
	(&xianNi).SetAge(500) // 可簡寫為“xianNi.SetAge(500)”

	xianNi.SayHi()

	fmt.Printf("xianNi.Age =  %d\n", xianNi.Age)
}

4.為內建資料型別繫結方法

package main

import "fmt"

// 定義一個Integer型別,讓其作為int的別名,之後就可以為其新增方法
type Integer int

func (i Integer) SayHi() {
	fmt.Printf(" i = %d\n", i)
}

func (i *Integer) SetInt(number int) {
	*i = Integer(number)
}

func main() {

	var x Integer = 100

	x.SayHi()
	x.SetInt(200)
	x.SayHi()
}

5.結構體的String()方法

package main

import "fmt"

type DongMan struct {
	Name   string
	Leader string
	Age    int
	Hobby  []string
}

// 建議大家定義String()作為輸出結構體資訊的方法,在會"fmt.Println"時自動呼叫喲~
func (d DongMan) String() string {

	return fmt.Sprintf("[%s]的男主是[%s],在[%d]歲時修煉到元嬰", d.Name, d.Leader, d.Age)
}

func main() {

	var xiuxian DongMan
	xiuxian.Name = "《凡人修仙傳》"
	xiuxian.Leader = "韓立"
	xiuxian.Hobby = []string{"煉丹", "陣法", "修煉"}
	xiuxian.Age = 217

	fmt.Println(xiuxian)

}

6.函式和方法的區別

package main

import "fmt"

type DongMan struct {
	Name string
}

// SetName 方法
func (d *DongMan) SetName(name string) {
	d.Name = name
}

// getName 方法
func (d DongMan) getName() {
	fmt.Printf("in method ... d.Name = %s\n", d.Name)
}

// getName 函式
func getName(d DongMan) {
	fmt.Printf("in function ... d.Name = %s\n", d.Name)
}

func main() {
	var yongSheng DongMan

	// 呼叫方法
	yongSheng.SetName("永生") // 但是不使用指標方式傳遞依舊可行,
	// (&yongSheng).SetName("一念永恆") // 正常應該使用指標方式傳遞

	// yongSheng.getName()
	(&yongSheng).getName()    // 雖然用指標型別呼叫,但是傳遞還是按照值傳遞的形式

	// 呼叫函式
	getName(yongSheng)
	// getName(&yongSheng) // 錯誤,函式不支援指標變數傳遞


	/*
	綜上所述,我們總結函式和方法的區別如下:
		- 1.繫結指定型別
		方法需要繫結指定資料型別,而函式不需要繫結資料型別。
		
		- 2.呼叫方式不一樣
			方法的呼叫方式為: "物件.方法名稱(實參列表)",函式的呼叫方式為: "函式名(實參列表)"。
			
		- 3.傳遞引數方式不一樣
			對於方法來說,接受者為值型別,可以傳入指標型別,接受值為指標型別,可以傳入值型別。
			對於函式來說,引數型別對應是什麼就要傳入對應的型別。
	*/
}

7.什麼時候應該使用指標型別接受者

- 1.需要修改接收者中的值;

- 2.接收者是複製代價比較大的大物件

- 3.保證一致性,如果有某個方法使用了指標接收者,那麼其他的方法也應該使用指標接收者。

三.跨包建立結構體例項

1 首字母大寫可以支援跨包

1.1 專案測試結構

如上圖所示,本案例僅需要讓main包倒入dongman包進行測試即可。

1.2 建立go.mod檔案

yinzhengjie@bogon 10-struct-package % go mod init yinzhengjie-dongman
go: creating new go.mod: module yinzhengjie-dongman
yinzhengjie@bogon 10-struct-package % 
yinzhengjie@bogon 10-struct-package % ls
go.mod
yinzhengjie@bogon 10-struct-package % 
yinzhengjie@bogon 10-struct-package % cat go.mod 
module yinzhengjie-dongman

go 1.22.4
yinzhengjie@bogon 10-struct-package % 

1.3 dongman.go

package dongman

import "fmt"

type DongMan struct {
	Name   string
	Leader string
	Age    int
	Hobby  []string
}

func (d DongMan) String() string {

	return fmt.Sprintf("[%s]的男主是[%s],愛好是: %v,在[%d]歲時修煉到元嬰喲~", d.Name, d.Leader, d.Hobby, d.Age)
}

1.4 main.go

package main

import (
	"fmt"
	"yinzhengjie-dongman/dongman"
)

func main() {

	var xiuxian dongman.DongMan
	xiuxian.Name = "《凡人修仙傳》"
	xiuxian.Leader = "韓立"
	xiuxian.Hobby = []string{"煉丹", "陣法", "修煉"}
	xiuxian.Age = 217

	fmt.Println(xiuxian)

}

1.5 測試程式碼

yinzhengjie@bogon 10-struct-package % go run main/main.go
[《凡人修仙傳》]的男主是[韓立],愛好是: [煉丹 陣法 修煉],在[217]歲時修煉到元嬰喲~
yinzhengjie@bogon 10-struct-package % 

2 首字母小寫基於工廠模式實現跨包

2.1 專案測試結構

如上圖所示,本案例僅需要讓main包倒入dongman包進行測試即可。

2.2 建立go.mod檔案

yinzhengjie@bogon 11-struct-package-2 % source /etc/profile 
yinzhengjie@bogon 11-struct-package-2 % 
yinzhengjie@bogon 11-struct-package-2 % ls       
yinzhengjie@bogon 11-struct-package-2 % 
yinzhengjie@bogon 11-struct-package-2 % go mod init yinzhengjie-dongman
go: creating new go.mod: module yinzhengjie-dongman
yinzhengjie@bogon 11-struct-package-2 % 
yinzhengjie@bogon 11-struct-package-2 % ls
go.mod
yinzhengjie@bogon 11-struct-package-2 % 
yinzhengjie@bogon 11-struct-package-2 % cat go.mod 
module yinzhengjie-dongman

go 1.22.4
yinzhengjie@bogon 11-struct-package-2 % 

2.3 dongman.go

package dongman

import "fmt"

type dongMan struct {
	Name   string
	Leader string
	Age    int
	Hobby  []string
}

func (d dongMan) String() string {

	return fmt.Sprintf("[%s]的男主是[%s],愛好是: %v,在[%d]歲時修煉到元嬰喲~", d.Name, d.Leader, d.Hobby, d.Age)
}


// 工廠模式,呼叫該方法就會返回"dongMan"的指標型別物件喲~有點類似其他程式語言的"構造方法"
func NewDongMan(name,leader string,age int,hobby []string) *dongMan {
	return &dongMan{
		Name: name,
		Leader: leader,
		Age: age,
		Hobby: hobby,
	}
}

2.4 main.go

package main

import (
	"fmt"
	"yinzhengjie-dongman/dongman"
)

func main() {

	xianNi := dongman.NewDongMan("《仙逆》", "王林", 400, []string{"極鏡", "陣法", "修煉"})

	fmt.Println(xianNi)

}

2.5 測試程式碼

yinzhengjie@bogon 11-struct-package-2 % go run main/main.go
[《仙逆》]的男主是[王林],愛好是: [極鏡 陣法 修煉],在[400]歲時修煉到元嬰喲~
yinzhengjie@bogon 11-struct-package-2 % 

相關文章