GO基础 10 结构与方法

10 结构与方法

组成结构体类型的那些数据称为字段,每个字段都有一个类型和一个名字;在一个结构体中,字段名字必须是唯一的。

10.1 结构体定义

type identifier struct {
    field1 type1
    field2 type2
}

结构体的字段可以是任何类型,可以是结构体本身,函数,接口等。

初始化方法

type person struct {
	name   string
	age    int
	sex    string
	height float32
	weight int
}

type myStruct struct {
	i int
}

func main() {
	var tom person
	tom.sex = "male"
	tom.name = "tom"
	tom.age = 10
	fmt.Println(tom)
	fmt.Printf("%p\n", &tom)
	//lilly的类型 是 指向person实例的指针
	lilly := new(person)
	lilly.sex = "female"
	fmt.Println(lilly)         //&{ 0 female 0 0}
	fmt.Printf("%p\n", &lilly) //0xc000006030
	fmt.Printf("%v\n", *lilly) //{ 0 female 0 0}

	var v myStruct
	//var p *myStruct
	v.i = 10
	fmt.Println(v.i)
	//fmt.Println(p.i)

	ms := &myStruct{99}
	fmt.Println(getStructI(ms))
	fmt.Println(*ms)

}
func getStructI(strcut *myStruct) (num int) {
	strcut.i = 1000
	num = strcut.i
	return
}
{tom 10 male 0 0}
0xc000062040    
&{ 0 female 0 0}
0xc000006030    
{ 0 female 0 0} 
10              
1000            
{1000}

定义指向结构体指针的方法

//混合字面量语法
&struct1{a, b, c}
等价于
new()

type Interval struct {
    start int
    end int
}
intr := Interval{0,3}//start = 0 end = 3
intr := Interval{end:3,start:10}
intr := Interval{end:11}//start为默认值0

结构体和指向它的指针的内存布局

type Point struct{x,y int}

使用new初始化:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zObM2FZt-1651567442824)(D:\markdown文件\go\photo\Snipaste_2022-05-02_16-53-58.png)]

使用结构体字面量初始化

pp := &Point{10,20}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BW0WRx7C-1651567442825)(D:\markdown文件\go\photo\Snipaste_2022-05-02_16-55-12.png)]

例子

type People struct {
	firstName string
	lastName  string
}

func main() {
	var tom People
	tom.firstName = "Tom"
	tom.lastName = "James"
	toUpper(&tom)
	fmt.Printf("toUpper firstName: %s\n", tom.firstName)
	fmt.Printf("toUpper lastName: %s\n", tom.lastName)

	l := new(People)
	//可以直接通过指针l 对结构体字段赋值 也可以用*指针变量 拿到指针指向的变量  对结构体字段进行赋值
	l.lastName = "Ben"
	l.firstName = "Lilly"
	(*l).lastName = "Black"
	toUpper(l)
	fmt.Printf("toUpper firstName: %s\n", l.firstName)
	fmt.Printf("toUpper lastName: %s\n", l.lastName)

	x := &People{"James", "Harden"}
	toUpper(x)
	fmt.Printf("toUpper firstName: %s\n", x.firstName)
	fmt.Printf("toUpper lastName: %s\n", x.lastName)
}
func toUpper(p *People) {
	p.firstName = strings.ToUpper(p.firstName)
	p.lastName = strings.ToUpper(p.lastName)
}
toUpper firstName: TOM
toUpper lastName: JAMES 
toUpper firstName: LILLY
toUpper lastName: BLACK 
toUpper firstName: JAMES
toUpper lastName: HARDEN

结构体的内存布局

Go中,结构体和它所包含的数据在内存中是以连续块的形式存在的。

type Rect1 struct {Min, Max Point }
type Rect2 struct {Min, Max *Point }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pzJAL1UV-1651567442825)(D:\markdown文件\go\photo\Snipaste_2022-05-02_17-23-41.png)]

递归结构体

结构体类型可以通过引用自身来定义。

type Node struct{
    //data存放数据 su指针指向后继节点
    data float64
    su *Node
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GGKahRVj-1651567442825)(D:\markdown文件\go\photo\Snipaste_2022-05-02_17-28-42.png)]

链表第一个元素为head指向下一个元素,最后一个元素交tail,它没有后继节点,是链表的末尾。

结构体转换

当给结构体定义一个alias(别名)类型时,此结构体类型和它的alias类型都有相同的底层类型,它们之间可以相互转换。

type Boy struct {
	name string
	age  int
}
type Man Boy
func main(){
    boy := Boy{"tom", 10}
	man := Man{"Ben", 34}
	//var c Boy = man//Cannot use 'man' (type Man) as the type Boy
	fmt.Println(boy)
	fmt.Println(man)
	//fmt.Println(c)
	var c = Boy(man)
	var d = Man(boy)
	fmt.Println(c)
	fmt.Println(d)
}

10.2 使用工厂方法创建结构体实例

工厂方法的名字以new或New开头。

type File struct{
    fd int
    name string
}
func NewFile(fd int, name string) *File{
    if fd < 0 {
        return nil
    }
    return &File{fd,name}
}

例子

type File struct {
	fd   int
	name string
}

func main() {
	file1 := NewFile(10, "test")
	file2 := NewFile(342, "test1")
	fmt.Println(file1)
	fmt.Println(file2)
}

//结构体对应的工厂方法
func NewFile(fd int, name string) *File {
	if fd < 0 {
		return nil
	}
	return &File{fd, name}
}
&{10 test}
&{342 test1}

如何强制使用工厂方法

定义结构体为私有,定义工厂方法为公有,则可以在外部调用工厂方法创建结构体

type matrix struct{
    
}
func NewMatrix(params) *matrix {
    m :=new(matrix)//初始化m
    return m
}

在其他包中使用工厂方法

package main
import "matrix"

x := matrix.NewMatrix(...)

map,struct,new(),make()

type Bar struct {
	name    string
	address string
}
type Foo map[string]string

func main() {
	y := new(Bar)
	y.name = "王朝酒吧"
	y.address = "互动司法是的会"
	//x := make(Bar)//Cannot make Bar
	z := make(Foo)
	z["aa"] = "AA"
	z["bb"] = "BB"
	z["cc"] = "CC"
	q := new(Foo)
	(*q)["aa"] = "djfsd"//assignment to entry in nil map
	(*q)["bb"] = "32432432"
	(*q)["cc"] = "dgafdsaf"
}

10.3 使用自定义包中的结构体

自定义包中的结构体

package pack1

type expStruct struct {
	Name string
	Age  int
	Sex  string
}

func MakeExpStruct(age int, name, sex string) *expStruct {
	m := &expStruct{name, age, sex}
	return m
}

其他的包调用pack1包中的工厂方法

import (
	"fmt"
	"hello/hello1"
)

func main() {
	qqq := pack1.MakeExpStruct(10, "jack", "male")
	fmt.Println(qqq)

}
&{jack 10 male}

10.4 带标签的结构体

结构体的字段除了名字和类型外,还可以有一个可选的标签(tag):它是附属于字段的字符串,可以是文档或其他重要的标记。标签的内容不可以在一般的编程中使用,只有包reflect(反射)能获取到。

type Boy struct {
	name string "年龄"
	age  int
}
func main() {
	boy := Boy{"tom", 10}
	fmt.Println(boy)
	boyType := reflect.TypeOf(boy)
	ixField := boyType.Field(0)
	fmt.Printf("%v\n", ixField.Tag)//年龄
}

10.5 匿名字段和内嵌结构体

结构体可以包含一个或多个匿名(或内嵌)字段,即这些字段没有显式的名字,自由字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即结构体可以包含内嵌结构体。

type inners struct {
	in1 int
	in2 int
}
type outerS struct {
	b      int
	c      float32
	int    //匿名
	inners //匿名
}

func main() {
	//使用结构体字面量
	i := inners{1, 1}
	o := outerS{10, 34.3432, 90, i}
	fmt.Println(o)

	outer := new(outerS)
	outer.in1 = 1
	outer.in2 = 2
	outer.b = 10
	outer.c = 11
	outer.int = 99
	fmt.Printf("%v\n", *outer)
}
{10 34.3432 90 {1 1}}
{10 11 99 {1 2}}

内嵌结构体

结构体也可以作为 一个结构体的字段,也可以作为匿名字段来使用。如上文,外层结构体通过outer.in1直接进入内层结构体的字段,内嵌结构体也可以来自其他包。

命名冲突

当两个字段用于相同的名字

1.外层名字会覆盖内层名字(但两者的内存空间都保留),这提供了一种重载字段或方法的方式

2.如果相同的名字在同一级别出现了两次,如果这个名字被程序使用了,会引发一个错误(不使用不会报错)

type A struct{a int}
type B struct{a,b int}
type C struct {
	A
	B
}
func main(){
    //var c C
    //不知道是 c.A.a 还是 c.B.a
	//c.a//Ambiguous reference 'a'
}

10.6 方法

10.6.1 方法是什么

Go方法是作用在接收者(receiver)上的一个函数,接收者是某种类型(不能是接口,指针)

一个结构体加上它的方法 等价于面向对象中的一个类。区别是:Go中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,可以存放在不同的源文件中,但它们必须在同一个包中。

定义方法的一般格式:

func (recv receiver_type) methodName(parameter_list)(return_value_list) {...}

在方法名之前,func关键字之后的括号中指定receiver。

如果recv是receiver的实例,Methods1是它的方法名,那么方法调用为recv.Method1()。

如果方法不需要使用recv的值,可以用_代替它。

func (_ receiver_type) methodName(parameter_list) (return_value_list) { ... }

recv就像是面向对象中的this。

例子:

package main

import "fmt"

type TwoInts struct {
	a int
	b int
}
type IntVector []int

func main() {
	tow1 := new(TwoInts)
	tow1.a = 12
	tow1.b = 10
	fmt.Println(tow1.AddThem())
	fmt.Println(tow1.AddToParam(20))
	fmt.Println(IntVector{1, 2, 3}.Sum())
}

func (this *TwoInts) AddThem() int {
	return this.a + this.b
}
func (this *TwoInts) AddToParam(param int) int {
	return this.a + this.b + param
}
func (v IntVector) Sum() (s int) {
	for _, x := range v {
		s += x
	}
	return
}
22
42
6 

类型和作用在它上面定义的方法必须在同一个包里定义,所以不能在int、float或类似的这些类型上定义方法。

有两个间接的方法:

1.可以先定义该类型(比如int、float)的别名类型,然后再为别名类型定义方法。

2.将此类型作为匿名类型嵌入在一个新的结构体中,此方法只在这个别名类型上有效。

package main

import (
	"fmt"
	"time"
)

type myTime struct {
	time.Time //匿名属性
}
type aliasInt int

func main() {
	m := myTime{time.Now()}
	fmt.Println(m.first4Chars())
	var a aliasInt = 10
	fmt.Println(a.add())
}

//截取time类型前4位
func (t myTime) first4Chars() string {
	return t.Time.String()[0:4]
}
func (i aliasInt) add() aliasInt {
	i++
	return i
}

10.6.2 函数和方法的区别

函数将变量作为参数:

Function1(recv)

方法在变量上被调用

recv.Method1()

在接收者是指针时,方法可以改变接收者的值(或状态),函数也可以(当参数作为指针传递)

接收者必须有一个显式的名字,这个名字必须在方法中被使用。

receiver_type叫接收者基本类型,这个类型必须在和方法同样的包中被声明

10.6.3 指针或值作为接收者

想要方法改变接收者的数据,就在接收者的指针类型上定义该方法。否则,就在普通的值类型上定义方法。

在调用方法时,值类型和指针类型既可以调用值接收者的方法,也可以调用指针接收者的方法。值类型调用指针接收者方法,会自动加引用。指针类型调用值类型接收者方法时,会自动解引用。

type BB struct {
	thing int
}
func main(){
    var b1 BB //b1是值
	b1.change()

	b2 := new(BB) //b2是指针
	b2.change()
	fmt.Println(b2.write())
}
func (b *BB) change() {
	b.thing = 1
	fmt.Println("调用了change 方法")
}
func (b BB) write() string {
	return fmt.Sprint(b)
}
调用了change 方法
{1}              
调用了change 方法
{1}
10.6.4 方法和未导出字段

自定义结构体 属性为私有的 提供了外部可以的get和set

package pack1

type Animal struct {
	name      string
	varieties string
}

func (a *Animal) GetName() string {
	return a.name
}
func (a *Animal) GetVarieties() string {
	return a.varieties
}
func (a *Animal) SetName(name string) {
	a.name = name
}
func (a *Animal) SetVarieties(varieties string) {
	a.varieties = varieties
}

外部类中

dog := new(pack1.Animal)
dog.SetName("xiaohei")
dog.SetVarieties("dog")
fmt.Printf("name: %s, varieties: %s\n", dog.GetName(), dog.GetVarieties())
//name: xiaohei, varieties: dog
10.6.5 内嵌类型的方法和继承

当一个匿名类型被内嵌在结构体中,匿名类型的可见方法也同样被内嵌,在效果上等同于外层类型继承了这些方法。

package main

import (
	"fmt"
	"math"
)

type Point struct {
	x, y float64
}
//内嵌类型的方法 由于 与外层类型定义的方法重名 被覆写
func (p *Point) Abs() float64 {
	return math.Sqrt(p.x*p.x + p.y*p.y)
}

type NamedPoint struct {
	Point
	name string
}

func (n *NamedPoint) Abs() float64 {
	return n.x*100 + n.y*100
}

func main() {
	n := &NamedPoint{Point{1.1, 2.1}, "xxxx"}
	fmt.Println(n.Abs())
}

内嵌将一个已存在类型的字段和方法注入到另一个类型里,匿名字段上的可见方法成为了外层类型的方法。当外层定义与内嵌类型同名的方法时会覆写内嵌类型对应的方法

结构体内嵌和自己在同一个包中的结构体时,可以彼此访问对方所有的字段和方法。

10.6.6 如何在类型中嵌入功能

两种方法来实现在类型中嵌入功能:

1.聚合:包含一个所需功能类型的具名字段

2.内嵌:内嵌所需功能类型

例子:在Customer类型中,通过Log类型来包含日志功能。

方法一:

package main

import "fmt"

type Log struct {
	msg string
}
type Customer struct {
	Name string
	log  *Log
}

func main() {
	c := &Customer{"Tom", &Log{"Wuhu,qifei"}}
	//c.log.Add("dasima")
	c.Log().Add("dasima")
	//fmt.Println(c.log)
	fmt.Println(c.Log())
}
func (l *Log) Add(s string) {
	l.msg += "\n" + s
}
func (l *Log) String() string {
	return l.msg
}
func (c *Customer) Log() *Log {
	return c.log
}
Wuhu,qifei
dasima

方法二

package main

import "fmt"

type log struct {
	msg string
}
type Customer1 struct {
	name string
	log
}

func main() {
	c := &Customer1{"jack", log{"wuhu"}}
	c.add("qqq")
	fmt.Println(c.string())
}
func (l *log) add(str string) {
	l.msg += "\n" + str
}
func (c *Customer1) string() string {
	return c.name + "\n" + c.msg
}
jack
wuhu
qqq
10.6.7 多重继承

例子:有一个类型CameraPhone,通过它可以Call(),也可以TakeAPicture(),但第一个方法属于类型Phone,第二个方法属于类型Camera

package main

import "fmt"

type Camera struct {
}

func (c *Camera) TakeAPicture() string {
	return "take a photo"
}

type Phone struct {
}

func (p *Phone) Call() string {
	return "take a call"
}

type CaremaPhone struct {
	Camera
	Phone
}

func main() {
	c := &CaremaPhone{}
	fmt.Println(c.Call())
	fmt.Println(c.TakeAPicture())
}
take a call
take a photo

例题;

定义一个结构体类型 Base,它包含一个字段 id,方法 Id() 返回 id,方法 SetId() 修改 id。结构体类型 Person 包含 Base,及 FirstName 和 LastName 字段。结构体类型 Employee 包含一个 Person 和 salary 字段。

创建一个 employee 实例,然后显示它的 id。

package main

import "fmt"

type Base struct {
	id int
}

func (b *Base) GetId() int {
	return b.id
}
func (b *Base) SetId(id int) {
	b.id = id
}

type Person1 struct {
	Base
	FirstName string
	LastName  string
}
type Employee struct {
	Person1
	salary float64
}

func main() {
	e := &Employee{Person1{Base{1001}, "James", "Harden"}, 1110.34}
	fmt.Println(e.GetId())
}

例题2 : 说出执行结果

package main

import (
	"fmt"
)

type Base1 struct{}

func (Base1) Magic() {
	fmt.Println("base magic")
}

func (self Base1) MoreMagic() {
	self.Magic()
	self.Magic()
}

type Voodoo struct {
	Base1
}

func (Voodoo) Magic() {
	fmt.Println("voodoo magic")
}

func main() {
	v := new(Voodoo)
	v.Magic()
	v.MoreMagic()
}
voodoo magic
base magic
base magic

10.7 类型的String()方法和格式化描述符

如果类型定义了 String() 方法,它会被用在 fmt.Printf() 中生成默认的输出:等同于使用格式化描述符 %v 产生的输出。还有 fmt.Print() 和 fmt.Println() 也会自动使用 String() 方法。

package main

import (
    "fmt"
    "strconv"
)

type TwoInts struct {
    a int
    b int
}

func main() {
    two1 := new(TwoInts)
    two1.a = 12
    two1.b = 10
    fmt.Printf("two1 is: %v\n", two1)
    fmt.Println("two1 is:", two1)
    fmt.Printf("two1 is: %T\n", two1)
    fmt.Printf("two1 is: %#v\n", two1)
}

func (tn *TwoInts) String() string {
    return "(" + strconv.Itoa(tn.a) + "/" + strconv.Itoa(tn.b) + ")"
}
two1 is: (12/10)
two1 is: (12/10)
two1 is: *main.TwoInts
two1 is: &main.TwoInts{a:12, b:10}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值