浅析Go的方法 | 豆包MarsCode AI刷题

71 阅读4分钟

方法介绍

Golang 方法总是绑定对象实例,并隐式将实例作为第一实参。一个方法就是一个包含了参数的函数,参数可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。方法特征如下:

• 只能为当前包内命名类型定义方法。

• 参数 receiver 可任意命名。如方法中未曾使用 ,可省略参数名。

• 参数 receiver 类型可以是 T 或 *T。基本类型 T 不能是接口或指针。

• 不支持方法重载,receiver 只是参数签名的组成部分。

• 可用实例 value 或 pointer 调用全部方法,编译器自动转换。

懂的人都知道这里的门道有多深

方法定义格式:

func (recevier type) methodName(参数列表)(返回值列表){}  //参数和返回值可以省略

1.方法案例

package main

import "fmt"

//定义一个Users结构体
type User struct {
	name string
	age uint8
}

// 定义User的method01方法,无参数无返回
func (u User) m01(){}
// 定义User的m02方法,有参数无返回
func (u User) me02(age int){}
// 定义User的m03方法,有参数单返回值
func (u User) m03(age int) string{
	return "age:"+string(age)
}
// 定义User的m04方法,有参数多返回值
func (u User) m04(age int) (string,error){
	return "age:"+string(age),nil
}
// 定义User的m05方法,多参数多返回值
func (u User) m05(name string,age int) (string,error){
	return "name:name"+name+",age:"+string(age),nil
}
func (u User) m06(){
	fmt.Println("name:",u.name,",age:",u.age)
}

func M01(){
	//值类型调用方法,注意:当接受者是指针时,即使用值类型调用那么方法内部也是对指针的操作
	u1:=User{"张三",10}
	u1.m06() // name: 张三 ,age: 10

	//指针调用方法
	u2:=&User{"李四",20}
	u2.m06() // name: 李四 ,age: 20
}
// 验证值调用方法与指针调用方法的区别
func (u User) ValueTest(){
	fmt.Printf("Value: %p\n", &u)
}
func (u *User) PointerTest() {
	fmt.Printf("Pointer: %p\n", u)
}

func M02(){
	u1:=User{"张三",10}
	u2:=&u1

	u1.ValueTest()
	u1.PointerTest()

	u2.ValueTest()
	u2.PointerTest()
}
func main() {
	M01()
	M02()
}

image.png

函数与方法异同

image.png 和 Go 函数一样,Go 的方法也是以 func 关键字修饰的,并且和函数一样,也包含方法名(对应函数名)、参数列表、返回值列表与方法体(对应函数体)。

而且,方法中的这几个部分和函数声明中对应的部分,在形式与语义方面都是一致的,比如:方法名字首字母大小写决定该方法是否是导出方法;方法参数列表支持变长参数;方法的返回值列表也支持具名返回值等。

不过,它们也有不同的地方。从上面这张图我们可以看到,和由五个部分组成的函数声明不同,Go 方法的声明有六个组成部分,多的一个就是图中的 receiver 部分。在 receiver 部分声明的参数,Go 称之为 receiver 参数,这个 receiver 参数也是方法与类型之间的纽带,也是方法与函数的最大不同。

Go 中的方法必须是归属于一个类型的,而 receiver 参数的类型就是这个方法归属的类型,或者说这个方法就是这个类型的一个方法。以图中的 ListenAndServeTLS 为例,这里的 receiver 参数 srv 的类型为 *Server,那么我们可以说,这个方法就是 *Server 类型的方法。

函数与方法的区别在于:

  • 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然。
  • 对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以。 懂的人都知道这里的门道有多深

2.匿名字段

Golang匿名字段可以像字段成员那样访问匿名字段方法,编译器负责查找。

package main
import "fmt"

type DogUser struct{
	name string
	age int
}
type DogManager struct{
	DogUser //匿名字段,只有字段类型,没有字段名称
}
func (u *DogUser) ToString() string {
	return fmt.Sprintf("User: %p, %v", u, u)
}
func callMethod03(){
	d:=DogManager{DogUser{"Tom",2}}
	fmt.Printf("Manager: %p\n", &d) // Manager: 0xc000004078

	fmt.Println(d.ToString()) // User: 0xc000004078, &{Tom 2}
	fmt.Println(d.name) // Tom
    fmt.Println(d.age) // 2
}
func main(){
    callMethod03()
}

image.png

案例一:计算矩形面积

type Rectangle struct {
    width  float64
    height float64
}

func (r *Rectangle) Area() float64 {
    return r.width * r.height
}

func main() {
    rect := &Rectangle{width: 5, height: 10}
    fmt.Println("矩形面积为:", rect.Area()) // 输出: 矩形面积为: 50
}

案例二:反转字符串

type StringUtil struct {
    str string
}

func (s *StringUtil) Reverse() string {
    runes := []rune(s.str)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

func main() {
    strUtil := &StringUtil{str: "hello, world"}
    fmt.Println("反转后的字符串为:", strUtil.Reverse()) // 输出: 反转后的字符串为: dlrow,olleh
}

那么恭喜你,完成了本次的go方法学习和练习