从零开始学GO ---- 结构体和方法

本文从零开始介绍Go语言中的结构体(struct)和方法,包括struct的初始化、匿名字段、结构字段、方法调用以及组合。详细阐述了如何定义和使用结构体,如何为结构体定义方法,以及在组合中如何处理内嵌字段和方法集。通过实例解析了值类型和指针类型接收者的方法调用差异,并讨论了何时应使用指针类型接收者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

从零开始学GO ---- 结构体和方法

struct

Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。

struct结构中的类型可以是任意类型,struct的存储空间是连续的,其字段按照声明时候的顺序存放(注意字段之间有对齐要求

struct有两种类型:

  • struct类型字面量

    struct{
        FeildName FeildType
        FeildName FeildType
        FeildName FeildType
    }
    
  • 使用type声明的自定义struct类型

    type TypeName struct{
        FeildName FeildType
        FeildName FeildType
        FeildName FeildType
    }
    

例如定义一个人的信息,包括名字、年龄、城市:

type person struct {
	name string
	city string
	age  int8
}

只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。

struct初始化

定义一个Person结构:

type Person struct{
    name string
    age int
} 
  • 按照字段顺序初始化

    	a:=Person("Tom",19)
    
    	b:=Person{"James",20}
    

    但一旦结构体增加字段,将不得不修改初始化语句

  • 指定字段名进行初始化

    	a := Person{
    		name: "Tom",
    		age:  19}
    

    哪怕结构增加字段,也不需要修改初始化语句

  • 使用new内置函数,字段默认初始化威其类型的零值,返回值是指向结构的指针

    p:=new(Person)
    //name为"",age是0
    
  • 一次初始化一个字段

    	p:=Person{}
    	p.name="Tom"
    	p.age=19
    

    这个通常用于实例化某个具体字段,不常用来进行初始化

  • 使用构造函数进行初始化

    Go语言的结构体没有构造函数,需要自己实现, 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以构造函数返回的是结构体指针类型会更高效

    func newPerson(name string, age int8) *person {
    	return &person{
    		name: name,
    		age:  age,
    	}
    }
    p := newPerson("Tom",19)
    
匿名字段

定义struct的过程中,如果字段只给出字段类型,没有给出字段名,则这样的字段为匿名字段。匿名字段并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。

type Person struct {
	string            //匿名字段
	int               //匿名字段
}

func main() {
	p1 := Person{
		"Tom",
		18,
	}
}
结构字段

结构字段可以是任意类型,基本类型、接口类型、指针类型、函数类型都可以作为struct的字段。内嵌自身的指针是实现链表、树等复杂结构的基础。

type node struct{           //简单的双向链表的结点
    //指向自身类型的指针
    next,prev *Element
    value int
}

方法

Go语言的方法类型是一种对类型行为的封装,方法就是一种作用于特定类型变量的函数,这种特定类型变量叫做接收者(Receiver)

为命名类型定义方法的格式:

//类型方法接收者是值类型
func (t TypeName) MethodName(ParamList)(ReturnList){
    //method body
}
//类型方法接收者是指针
func (t *TypeName) MethodName(ParamList)(ReturnList){
    //method body
}

示例:写一个求 slice 的和的函数

type SliceInt []int

func (s SliceInt) Sum() int {
	sum := 0
	for _, i := range s {
		sum += i
	}
	return sum
}

func main() {
	var s SliceInt = []int{1, 2, 3, 4}
	fmt.Println(s.Sum())       //10
}
  • 可以为命名类型增加方法,非命名类型不可以

    例如不能为[]int类型增加方法,因为[]int是非命名类型。但是可以为[]int命名,从而增加方法

  • 使用type定义的自定义类型是一个新类型,新类型不能调用原有类型的方法,但是底层类型支持的运算可以被继承

    type Map map[string]string
    func (m Map) Print(){
        //底层类型支持的range运算,新类型可用
        for _,key:=range m{
            fmt.Println(key)
        }
    }
    

指针类型的接收者:

指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。

// 使用指针接收者
func (p *Person) SetAge(newAge int8) {
	p.age = newAge
}            //通过指针接收者可以改变原结构体的内容

值类型的接收者:

当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。

// 使用值接收者
func (p Person) SetAge2(newAge int8) {
	p.age = newAge
}        //不能修改原结构体的内容

什么时候应该使用指针类型接收者

  • 需要修改接收者中的值
  • 接收者是拷贝代价比较大的大对象
方法调用
  • 一般调用 :TypeInstanceName.MethodName(ParamList)

    type T struct {
    	a int
    }
    
    func (t T) Get() int {
    	return t.a
    }
    func (t *T) Set(i int) {
    	t.a = i
    }
    
    func main() {
    	var t = &T{}
    	t.Set(2)
    	fmt.Println(t.Get())  //2
    }
    
  • 方法值(method value)

    变量x的静态类型是TMT的一个方法,x.T被称为方法值。x.T是一个函数类型变量,可以赋值给其他变量

    f:=x.M
    f(args..)
    //<====>等价于
    x.M(args..)
    

    方法值是一个带有闭包的函数变量,接受值被隐式绑定到方法值的闭包环境中,后续调用不需要再显示传递接收者。

    type T struct {
    	a int
    }
    
    func (t T) Get() int {
    	return t.a
    }
    func (t *T) Set(i int) {
    	t.a = i
    }
    func (t *T) Print() {
    	fmt.Println(t.a)
    }
    
    func main() {
    	var t = &T{}
    	//方法值
    	f := t.Set
    	f(2)
    	t.Print()    //2
    	f(3)
    	t.Print()    //3
    }
    
  • 方法表达式

    方法表达式提供一种语法,将类型方法调用显式转换为函数调用,接收者需要显式传进去

    	t := T{a: 1}
    	//方法表达式调用
    	(T).Get(t)
    	//<===>等价
    	f1 := T.Get
    	f1(t)
    
    	f2 := (*T).Print
    	(*T).Set(&t, 1)
    	f2(&t)     //1
    	//<====>
    	f3 := (*T).Set
    	f3(&t, 3)
    	f2(&t)  //3
    

组合

内嵌字段的初始化和访问
package main

import "fmt"

type X struct {
	a int
}
type Y struct {
	X
	b int
}
type Z struct {
	Y
	c int
}

func main() {
	x := X{a: 1}
	y := Y{X: x, b: 2}
	z := Z{Y: y, c: 3}
	//由于字段唯一,因此Z.a等价于Z.Y.X.a
	fmt.Println(z.a, z.Y.a, z.Y.X.a)   // 1 1 1
	z.a = 2
	fmt.Println(z.a, z.Y.a, z.Y.X.a)  //2 2 2
}
//如果字段不唯一,应该精确到具体层次进行初始化和访问
内嵌字段的方法调用

从外向内查,找到可调用的即停止。

package main

import "fmt"

type X struct {
	a int
}
type Y struct {
	X
	b int
}
type Z struct {
	Y
	c int
}

func (x X) Print() {
	fmt.Printf("In X,a=%d\n", x.a)
}

func (x X) XPrint() {
	fmt.Printf("In X,a=%d\n", x.a)
}
func (y Y) Print() {
	fmt.Printf("In Y,b=%d\n", y.b)
}
func (z Z) Print() {
	fmt.Printf("In Z,c=%d\n", z.c)
    z.Y.Print()   //调用的是Y的Print()
	z.Y.X.Print()
}

func main() {
	x := X{a: 1}
	y := Y{X: x, b: 2}
	z := Z{Y: y, c: 3}
    z.Print()  //调用了Z的Print()
	//In Z,c=3
	//In Y,b=2
	//In X,a=1
}
组合的方法集

组合构造的方法集有如下规则:

  1. 若类型S包含匿名字段T,则S的方法集包含T的方法集
  2. 若类型S包含匿名字段*T,则S的方法集包含T和*T方法集
  3. 不管类型S中嵌入的匿名字段是T还是*T, *S方法集总是包含T和*T方法集
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值