Golang语言结构体(struct)面向对象编程基础篇

                                              作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

一.面向对象的引入

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 % 
posted @ 2024-07-27 00:38  尹正杰  阅读(11)  评论(0编辑  收藏  举报