Go語言基礎之結構體(面向對象編程上)

1 自定義類型和類型別名

1.1 自定義類型

Go語言中能夠基於一些基本的數據類型,使用type關鍵字定義自定義類型的數據 。html

自定義類型定義了一個全新的類型,該新類型具備基本數據類型的特性。自定義類型定義的方法以下:程序員

type TypeName Type

//將 NewType定義爲int 類型
type NewType int

NewType是一個新的類型,其具備int的特性。面試

1.2 類型別名

類型別名是Go1.9版本添加的新功能。利用類型別名的功能,能夠給一些基本的數據類型定義一些讓讀者見名知意的名字,從而提升代碼的可讀性。類型別名定義的方法以下:編程

type TypeAlias = Type

Go語言中的runebyte就是類型別名,它們的定義以下:json

type byte = uint8
type rune = int32

1.3 自定義類型和類型別名的區別

自定義類型:自定義類型定義了一個全新的類型,其繼承了基本類型的全部特性,而且能夠實現新類型的特有的一些方法。數組

類型別名:只存在代碼編寫的過程,代碼編譯之後根本不存在這個類型別名。其做用用來提升代碼的可讀性。編程語言

以下代碼,體現了兩者的區別:函數

//自定義類型
type NewInt int

//類型別名
type MyInt = int

func main(){
    var a NewInt
    var b MyInt
    var c int
    // c = a  //?  可使用強制類型轉換c = int(a)
    c = b  //c和b是同一類型
    
    fmt.Println("type of a:%T\n", a)  //type of a:main.NewInt
    fmt.Println("type of b:%T\n", b)  //type of b:int
}

2 結構體

在Go語言中可使用基本數據類型表示一些事物的屬性,可是若是想表達一個事物的所有或部分屬性,好比說一個學生(學號、姓名、年齡、班級等),這時單一的基本數據類型就不可以知足需求。ui

Go語言中提供了一種自定義數據類型,能夠將多個基本數據類型或引用類型封裝在一塊兒,這種數據類型叫struct結構體。Go語言也是經過struct來實現面向對象的。this

2.1 Golang語言面向對象編程說明

  • Golang也支持面向對象編程(OOP),可是和傳統的面向對象編程有區別,並非純粹的面嚮對象語言。因此說Golang支持面向對象編程特性是比較準確的;
  • Golang沒有類(class),Go語言的結構體(struct)和其餘編程語言的類(class)有同等的地位,能夠理解Golang是基於struct來實現OOP特性的;
  • Golang面向對象編程很是簡潔,去掉了傳統OOP語言的繼承、方法重載、構造函數和析構函數、隱藏的this指針等;
  • Golang仍然有面向對象編程的繼承、封裝和多態的特性,只是實現的方法和其餘OOP語言不同,好比繼承:Golang的繼承是經過匿名字段來實現的;
  • Golang面向對象(OOP)很優雅,OOP自己就是語言類型系統(type system)的一部分,經過接口(interface)關聯,耦合性低,也很是靈活。Golang中面向接口編程是很是重要的特性。

2.2 快速入門

// 建立一個結構體類型的student
type student struct {
	name string
	age int
	gender string
	hobby []string
}

func main() {
	// 建立一個student實例
	var viktor = student{
		name:   "viktor",
		age:    24,
		gender: "男",
		hobby:  []string{"乒乓球", "羽毛球"},
	}

	fmt.Println(viktor)			//  {viktor 24 男 [乒乓球 羽毛球]}
    // 分別取出viktor實例中的每一個字段
	fmt.Println(viktor.name)	// viktor
	fmt.Println(viktor.age)		// 24
	fmt.Println(viktor.gender)	// 男
	fmt.Println(viktor.hobby)	// [乒乓球 羽毛球]
}

2.3 如何聲明結構體

基本語法:

type StructName struct {
    field1 type
    field2 type
}

示例,聲明一個學生的結構體Student

type Student struct {
    Name string
    Age int
    Score float32
}

2.4 字段/屬性

struct中封裝了一些基本數據類型的變量,咱們稱之爲結構體字段或者是該結構體的屬性。

字段是結構體的一個組成部分,通常是基本數據類型、數組,也能夠是引用類型,甚至是struct(嵌套結構體)等。

注意事項和細節說明:

  • 字段聲明語法同變量;

  • 在建立一個結構體變量後,若是沒有給字段賦值,都對應一個零值(默認值):布爾類型是false,數值是0,字符串是"",數組的默認值和它的元素類型相關,好比 score [3]int則爲[0, 0, 0],指針、slice和map的零值都是nil,即尚未分配空間。

type Person struct {
    Name string 
    Age int
    Scores [5]float64
    ptr *int
    slice []int
    map1 map[string]string
}

func main() {
    //定義結構體變量
    var p1 Person
    fmt.Println(p1)
    
    if p1.ptr == nil {
        fmt.Println("ok1")
    }
    
    if p1.slice == nil {
        fmt.Println("ok2")
    }
    
    if p1.map1 == nil {
        fmt.Println("ok3")
    }
    
    p1.slice = make([]int, 10)
    p1.slice[0] = 100
    
    p1.map1 = make(map[string]string)
    p1.map1["key1"] = "tom"
    fmt.Println(p1)
}
  • 不一樣結構體變量的字段是獨立,互不影響,一個結構體變量字段的更改,不影響另一個,結構體是值類型

2.5 結構體的實例化

能夠這樣理解,聲明一個結構體相似創造一個模具,若是想要真正的描述一個事物,那麼就得使用這個模具來製造一個事物,這個製造事物的過程稱爲 建立結構體變量或者結構體實例化。結構體的實例化有四種方式:

  • 方式1:直接聲明
type Person struct {
    Name string 
    Age int
}

func main() {
    //定義結構體變量
    var p1 Person
    fmt.Println(p1)
}
  • 方式2:{}
//使用值列表初始化
p2 := Person{"tom", 20}
fmt.Println(p2)
  • 方式3:使用new關鍵字

struct是值類型,那麼就可使用new關鍵字定義一個結構體指針:

var p3 *Person = new(Person)
(*p3).Name = "smith"
p3.Name = "john"

(*p3).Age = 20
p3.Age = 30
fmt.Println(p3)
  • 方式4:使用&,定義一個結構體指針
//使用鍵值對初始化
var person *Person = &Person{
    Name : "tom", 
    Age : 19,
}

//也能夠經過字段訪問的形式進行賦值
(*p3).Name = "scott"
p3.Name = "scott~"

(*p3).Age = 20
p3.Age = 30
fmt.Println(p3)

說明:

  • 第三種和第四種方式返回的是結構體指針;
  • 在結構體初始化是,必須初始化結構體的全部字段;初始值的填充順序必須與字段在結構體中的聲明順序一致;值列表初始化方式和鍵值初始化方式不能混用;
  • 結構體指針訪問字段的標準方式是:(*結構體指針).字段名
  • 因爲Go語言中的指針不支持偏移和運算,語句go編譯器底層對person.Name作了轉化(*person).Name

2.6 結構體使用注意事項和細節

  • 結構體的全部字段在內存中是連續的
type Point struct {
    x, y int
}

type Rect struct {
    leftUp, rightDown Point
}

type Rect2 struct {
    leftUp, rightDown *Point
}

func main() {
    r1 := Rect(Point{1,2}, Point{3,4})
    //r1有四個int,在內存中是連續分佈
    fmt.Printf("r1.leftUp.x 地址=%p r1.leftUp.y 地址=%p r1.rightDown.x 地址=%p r1.rightDown.y 地址=%p", &r1.leftUp.x, &r1.leftUp.y, &r1.rightDown.x, &r1.rightDown.y)
    
    //r2有兩個*Point類型,這兩個*Point類型的自己地址也是連續的,可是其指向的地址不必定是連續的
    r2 := Rect(&Point{10,20}, Point{30,40})
    
    fmt.Printf("r2.leftUp 自己地址=%p r2.rightDown 自己地址=%p", &r2.leftUp, &r2.rightDown)
    
    fmt.Printf("r2.leftUp 指向地址=%p r2.rightDown 指向地址=%p", r2.leftUp, r2.rightDown)
}
  • 結構體是用戶單獨定義的類型,和其餘類型進行轉換時須要有徹底相同的字段(名字、個數和類型)
  • struct的每一個字段上,能夠寫上一個tag,該tag能夠經過反射機制獲取,常見的使用場景:序列化和反序列化。
type Monster struct {
    Name string `json:"name"`
    Age int `json:"age"`
    Skill string `json:"skill"`
}

func main() {
    //建立一個Monster實例
    monster := Monster{"蜘蛛精", 200, "吐絲"}
    
    //將monster序列化
    jsonStr, err := json.Marshal(monster)
    if err != nil {
        fmt.Println("json 處理錯誤", err)
    }
    fmt.Println("jsonStr", string(jsonStr))
}

2.7 面試題

下面代碼的執行結果?

type student struct {
	name string
	age  int
}

func main() {
	m := make(map[string]*student)
	stus := []student{
		{name: "李四", age: 18},
		{name: "張三", age: 23},
		{name: "李明", age: 9000},
	}

	for _, stu := range stus {
		m[stu.name] = &stu
	}
	for k, v := range m {
		fmt.Println(k, "=>", v.name)
	}
}

3 方法

方法是什麼?在聲明瞭一個結構體後,好比說Person結構體,那麼這我的都有哪些功能,或者說都有什麼能力,這些功能或者能力就是一個結構體的方法。

Golang中的方法是做用在指定的數據類型上(即,和指定的數據類型綁定),所以自定義類型,均可以有方法,而不只僅是struct。以下示例:

//MyInt 將int定義爲自定義MyInt類型
type MyInt int

//SayHello 爲MyInt添加一個SayHello的方法
func (m MyInt) SayHello() {
	fmt.Println("Hello, 我是一個int。")
}
func main() {
	var m1 MyInt
	m1.SayHello() //Hello, 我是一個int。
	m1 = 100
	fmt.Printf("%#v  %T\n", m1, m1) //100  main.MyInt
}

3.1 方法的聲明和調用

方法的聲明語法:

//聲明一個自定義類型struct
type A struct {
    Num int
}

//聲明A類型的方法
func (a A) test() {		//其中的(a A)表示test方法和A類型綁定
    fmt.Println(a.Num)
}

舉例說明:

type Person struct {
    Name string
}

func (p Person) test() {
    fmt.Println("test() name=", p.Name)
}

func main() {
    var p Person	//實例化
    p.Name = "tom"
    p.test()	//調用方法
}
  • test方法和Person類型綁定;

  • test方法只能經過Person類型的變量來調用,不能直接調用,也不能使用其它類型變量來調用;

  • func (p Person) test() {...},其中p表示哪一個Person變量調用,這個p就是它的副本,表明接收者。這點和函數傳參很是類似,而且p能夠有程序員任意指定;

3.2 方法的傳參機制

在3.1中 提到,方法要和指定自定義類型的變量綁定,那個這個綁定方法的變量被稱爲接收者,而方法的傳參機制被這個接收者的類型不一樣分爲值類型的接收者和指針類型的接收者,下面分別來看這這兩方式的傳參機制:

3.2.1 值類型的接收者

指針類型的接收者由一個結構體的指針組成,因爲指針特性,調用方法時修改接收者的任意成員變量,在方法接收後,修改都是有效的。例如爲Person結構體添加一個SetAge方法,來修改實例中的年齡:

//Person 結構體
type Person struct {
	name string
	age  int8
}

// SetAge 設置p的年齡
// 使用指針接收者
func (p *Person) SetAge(newAge int8) {
	p.age = newAge
}

func main() {
	p1 := NewPerson("小王子", 25)
	fmt.Println(p1.age) // 25
	p1.SetAge(30)
	fmt.Println(p1.age) // 30
}

3.2.2 指針類型的接收者

當方法和值類型接收者綁定是,Go語言會在代碼運行時將接收者的值複製一份 。在值類型接收者的方法中能夠獲取接收者的成員值,但修改操做只是針對副本,沒法修改接收者變量自己。

// SetAge2 設置p的年齡
// 使用值接收者
func (p Person) SetAge2(newAge int8) {
	p.age = newAge
}

func main() {
	p1 := NewPerson("張三", 25)
	p1.Dream()
	fmt.Println(p1.age) // 25
	p1.SetAge2(30) // (*p1).SetAge2(30)
	fmt.Println(p1.age) // 25
}

3.2.3 使用指針類型傳參的時機

  • 須要修改接收者中的值;
  • 傳參時拷貝表明比較大的大實例;
  • 保證形參實例和實參實例的一致性,若是有某個方法使用了指針接收者,那麼其餘的方法也應該使用指針接收者。

3.3 方法和函數區別

  • 聲明方式不同

    • 函數的聲明方式:func 函數名(形參列表) (返回值列表) {...}
    • 方法的聲明方式:func (變量 自定義類型) 函數名(形參列表) (返回值列表) {...}
  • 調用方式不同

    • 函數的調用方式:函數名(實參列表)
    • 方法的調用方式:變量.方法名(實參列表)
  • 對於普通函數,接收者爲值類型時,不能講指針類型的數據直接傳遞,反之亦然;

type Person struct {
    Name string
}

func test01(p Person) {
    fmt.Println(p.Name)
}

func test02(p *Person) {
    fmt.Println(p.Name)
}

func main() {
    var p = Person{"tom"}
    test01(p)
    test02(&p)
}
  • 對於方法(如struct的方法),接收者爲值類型時,能夠直接用指針類型的變量調用方法,反之亦然;
func (p Person) test03() {
    p.Name = "jack"
    fmt.Println(p.Name)		//jack
}

func (p *Person) test03() {
    p.Name = "jerry"
    fmt.Println(p.Name)		//jerry
}

func main() {
    p := Person{"viktor"}
    p.test03()
    fmt.Println(p.Name)		// viktor
    
    (&p).test03()	//從形式上傳入地址,可是本質扔然是值拷貝
    fmt.Println(p.Name)		// viktor
    
    (&p).test04()
    fmt.Println(p.Name)		// jerry
    p.test04()	//等價於(&p).test04(),從形式上是傳入值類型,可是本事仍然是地址拷貝
}

對於方法來講,無論調用形式如何,真正決定是之拷貝仍是地址拷貝,看這個方法是和哪一種類型綁定,也就是接收者的類型是值類型仍是指針類型。

4 工廠模式

Golang的結構體沒有構造函數,一般可使用工廠模式來解決這個問題。

4.1 爲什麼須要工廠模式

首選,在Golang語言中公有和私有變量這一說法。若是說一個包中的變量的首字母爲小寫,在其餘包若是引入這個包,就不能訪問這個變量;若是這個變量的變量名爲大寫字母,那麼能夠直接訪問。

一樣對於自定義的struct類型也是,而工廠模式,就是爲了解決變量的首字母爲小寫的結構體可以被其它包引用的問題。

4.2 工廠模式的使用

//student.go屬於model包
package model

//定義一個結構體
type student struct {
    Name string
    Score float64
}

func NewStudent(n string, s float64) *stuent {
    return &student{
        Name : n,
        Score : s,
    }
}

//mian.go中聲明一個student實例,並初始化
func main() {
    var stu = model.NewStudent("viktor", 86.6)
    
    fmt.Println(*stu)
    fmt.Println("name=", stu.Name, "score=", stu.Score)
}

另外,若是結構體中的某個字段的首字母也爲小寫該如何訪問?

//student.go屬於model包
package model

//定義一個結構體
type student struct {
    Name string
    score float64
}

func NewStudent(n string, s float64) *stuent {
    return &student{
        Name : n,
        Score : s,
    }
}

func (s *student) GetScore() float64 {
    return s.score
}

//mian.go中聲明一個student實例,並初始化
func main() {
    var stu = model.NewStudent("viktor", 86.8)
    
    fmt.Println(*stu)
    fmt.Println("name=", stu.Name, "score=", stu.GetScore())
}

原文出處:https://www.cnblogs.com/dabric/p/12361979.html

相關文章
相關標籤/搜索