golang定义一个方法_在Go中定义方法

news/2024/7/4 0:50:24

golang定义一个方法

介绍 (Introduction)

Functions allow you to organize logic into repeatable procedures that can use different arguments each time they run. In the course of defining functions, you’ll often find that multiple functions might operate on the same piece of data each time. Go recognizes this pattern and allows you to define special functions, called methods, whose purpose is to operate on instances of some specific type, called a receiver. Adding methods to types allows you to communicate not only what the data is, but also how that data should be used.

函数使您可以将逻辑组织为可重复的过程,这些过程每次运行时都可以使用不同的参数。 在定义函数的过程中,您经常会发现多个函数可能每次都对相同的数据进行操作。 Go可以识别这种模式,并允许您定义称为方法的特殊功能,其目的是对某些特定类型的实例(称为接收器)进行操作 。 向类型添加方法不仅使您可以传达数据是什么,还可以传达如何使用数据。

定义方法 (Defining a Method)

The syntax for defining a method is similar to the syntax for defining a function. The only difference is the addition of an extra parameter after the func keyword for specifying the receiver of the method. The receiver is a declaration of the type that you wish to define the method on. The following example defines a method on a struct type:

定义方法的语法类似于定义函数的语法。 唯一的区别是在func关键字之后添加了一个额外的参数,用于指定方法的接收者。 接收器是您希望在其上定义方法的类型的声明。 以下示例在结构类型上定义了一个方法:

package main

import "fmt"

type Creature struct {
    Name     string
    Greeting string
}

func (c Creature) Greet() {
    fmt.Printf("%s says %s", c.Name, c.Greeting)
}

func main() {
    sammy := Creature{
        Name:     "Sammy",
        Greeting: "Hello!",
    }
    Creature.Greet(sammy)
}

If you run this code, the output will be:

如果运行此代码,输出将是:


   
Output
Sammy says Hello!

We created a struct called Creature with string fields for a Name and a Greeting. This Creature has a single method defined, Greet. Within the receiver declaration, we assigned the instance of Creature to the variable c so that we could refer to the fields of the Creature as we assemble the greeting message in fmt.Printf.

我们创建了一个名为Creature的结构,其中包含NameGreeting string字段。 这个Creature有一个定义的方法Greet 。 在接收者声明中,我们将Creature的实例分配给了变量c以便在我们在fmt.Printf组装问候消息时fmt.Printf Creature的字段。

In other languages, the receiver of method invocations is typically referred to by a keyword (e.g. this or self). Go considers the receiver to be a variable like any other, so you’re free to name it whatever you like. The style preferred by the community for this parameter is a lower-case version of the first character of the receiver type. In this example, we used c because the receiver type was Creature.

在其他语言中,方法调用的接收者通常由关键字(例如thisself )引用。 Go认为接收器是一个和其他变量一样的变量,因此您可以随意命名。 社区对此参数首选的样式是接收器类型的第一个字符的小写版本。 在此示例中,我们使用c因为接收器类型为Creature

Within the body of main, we created an instance of Creature and specified values for its Name and Greeting fields. We invoked the Greet method here by joining the name of the type and the name of the method with a . and supplying the instance of Creature as the first argument.

main ,我们创建了Creature的实例,并为其NameGreeting字段指定了值。 我们在此处通过将类型名称和方法名称与进行连接来调用Greet方法. 并提供Creature实例作为第一个参数。

Go provides another, more convenient, way of calling methods on instances of a struct as shown in this example:

Go提供了另一种更方便的在结构实例上调用方法的方式,如以下示例所示:

package main

import "fmt"

type Creature struct {
    Name     string
    Greeting string
}

func (c Creature) Greet() {
    fmt.Printf("%s says %s", c.Name, c.Greeting)
}

func main() {
    sammy := Creature{
        Name:     "Sammy",
        Greeting: "Hello!",
    }
    sammy.Greet()
}

If you run this, the output will be the same as the previous example:

如果运行此命令,则输出将与前面的示例相同:


   
Output
Sammy says Hello!

This example is identical to the previous one, but this time we have used dot notation to invoke the Greet method using the Creature stored in the sammy variable as the receiver. This is a shorthand notation for the function invocation in the first example. The standard library and the Go community prefers this style so much that you will rarely see the function invocation style shown earlier.

这个示例与上一个示例相同,但是这次我们使用点符号来调用Greet方法,该方法使用存储在sammy变量中的Creature作为接收者。 这是第一个示例中函数调用的简写形式。 标准库和Go社区非常喜欢这种样式,以致您几乎看不到前面显示的函数调用样式。

The next example shows one reason why dot notation is more prevalent:

下一个示例显示了点表示法更加普遍的一个原因:

package main

import "fmt"

type Creature struct {
    Name     string
    Greeting string
}

func (c Creature) Greet() Creature {
    fmt.Printf("%s says %s!\n", c.Name, c.Greeting)
    return c
}

func (c Creature) SayGoodbye(name string) {
    fmt.Println("Farewell", name, "!")
}

func main() {
    sammy := Creature{
        Name:     "Sammy",
        Greeting: "Hello!",
    }
    sammy.Greet().SayGoodbye("gophers")

    Creature.SayGoodbye(Creature.Greet(sammy), "gophers")
}

If you run this code, the output looks like this:

如果运行此代码,则输出如下所示:


   
Output
Sammy says Hello!! Farewell gophers ! Sammy says Hello!! Farewell gophers !

We’ve modified the earlier examples to introduce another method called SayGoodbye and also changed Greet to return a Creature so that we can invoke further methods on that instance. In the body of main we call the methods Greet and SayGoodbye on the sammy variable first using dot notation and then using the functional invocation style.

我们已经修改了前面的示例,以引入另一个名为SayGoodbye方法,还更改了Greet以返回Creature以便我们可以在该实例上调用其他方法。 在体内main我们调用的方法GreetSayGoodbyesammy变量首先使用点符号,然后使用功能的调用风格。

Both styles output the same results, but the example using dot notation is far more readable. The chain of dots also tells us the sequence in which methods will be invoked, where the functional style inverts this sequence. The addition of a parameter to the SayGoodbye call further obscures the order of method calls. The clarity of dot notation is the reason that it is the preferred style for invoking methods in Go, both in the standard library and among the third-party packages you will find throughout the Go ecosystem.

两种样式都输出相同的结果,但是使用点符号的示例更具可读性。 点链还告诉我们将调用方法的顺序,在此功能样式会反转此顺序。 向SayGoodbye调用添加参数进一步模糊了方法调用的顺序。 点符号的清晰性是为什么它是Go中调用方法的首选样式的原因,无论是在标准库中还是在整个Go生态系统中都可以找到的第三方程序包中。

Defining methods on types, as opposed to defining functions that operate on some value, have other special significance to the Go programming language. Methods are the core concept behind interfaces.

与定义以某种值操作的函数相反,在类型上定义方法对Go编程语言具有其他特殊意义。 方法是接口背后的核心概念。

介面 (Interfaces)

When you define a method on any type in Go, that method is added to the type’s method set. The method set is the collection of functions associated with that type as methods and used by the Go compiler to determine whether some type can be assigned to a variable with an interface type. An interface type is a specification of methods used by the compiler to guarantee that a type provides implementations for those methods. Any type that has methods with the same name, same parameters, and same return values as those found in an interface’s definition are said to implement that interface and are allowed to be assigned to variables with that interface’s type. The following is the definition of the fmt.Stringer interface from the standard library:

当您在Go中的任何类型上定义方法时,该方法都会添加到该类型的方法集中 。 方法集是与该类型关联的函数的方法集合,Go编译器使用该方法来确定是否可以将某种类型分配给具有接口类型的变量。 接口类型是编译器用来确保类型为这些方法提供实现的方法规范。 具有与在接口的定义中找到的名称,参数和返回值相同的方法的任何类型都可以实现该接口,并允许将其分配给具有该接口类型的变量。 以下是标准库中fmt.Stringer接口的定义:

type Stringer interface {
  String() string
}

For a type to implement the fmt.Stringer interface, it needs to provide a String() method that returns a string. Implementing this interface will allow your type to be printed exactly as you wish (sometimes called “pretty-printed”) when you pass instances of your type to functions defined in the fmt package. The following example defines a type that implements this interface:

对于要实现fmt.Stringer接口的类型,它需要提供一个返回stringString()方法。 当您将类型的实例传递给fmt包中定义的函数时,实现此接口将使您的类型可以完全按照您的期望进行打印(有时称为“漂亮打印”)。 以下示例定义了实现此接口的类型:

package main

import (
    "fmt"
    "strings"
)

type Ocean struct {
    Creatures []string
}

func (o Ocean) String() string {
    return strings.Join(o.Creatures, ", ")
}

func log(header string, s fmt.Stringer) {
    fmt.Println(header, ":", s)
}

func main() {
    o := Ocean{
        Creatures: []string{
            "sea urchin",
            "lobster",
            "shark",
        },
    }
    log("ocean contains", o)
}

When you run the code, you’ll see this output:

运行代码时,您将看到以下输出:


   
Output
ocean contains : sea urchin, lobster, shark

This example defines a new struct type called Ocean. Ocean is said to implement the fmt.Stringer interface because Ocean defines a method called String, which takes no parameters and returns a string. In main, we defined a new Ocean and passed it to a log function, which takes a string to print out first, followed by anything that implements fmt.Stringer. The Go compiler allows us to pass o here because Ocean implements all of the methods requested by fmt.Stringer. Within log, we use fmt.Println, which calls the String method of Ocean when it encounters a fmt.Stringer as one of its parameters.

本示例定义了一个名为Ocean的新结构类型。 据说Ocean 实现fmt.Stringer接口,因为Ocean定义了一种称为String的方法,该方法不带任何参数并返回一个string 。 在main ,我们定义了一个新的Ocean并将其传递给log函数,该函数首先需要输出一个string ,然后是实现fmt.Stringer的任何东西。 Go编译器允许我们在此处传递o ,因为Ocean实现了fmt.Stringer请求的所有方法。 在log ,我们使用fmt.Println ,它在遇到fmt.Stringer作为其参数之一时调用OceanString方法。

If Ocean did not provide a String() method, Go would produce a compilation error, because the log method requests a fmt.Stringer as its argument. The error looks like this:

如果Ocean不提供String()方法,则Go会产生编译错误,因为log方法请求fmt.Stringer作为其参数。 错误看起来像这样:


   
Output
src/e4/main.go:24:6: cannot use o (type Ocean) as type fmt.Stringer in argument to log: Ocean does not implement fmt.Stringer (missing String method)

Go will also make sure that the String() method provided exactly matches the one requested by the fmt.Stringer interface. If it does not, it will produce an error that looks like this:

Go还将确保提供的String()方法与fmt.Stringer接口请求的方法完全匹配。 如果没有,将产生如下错误:


   
Output
src/e4/main.go:26:6: cannot use o (type Ocean) as type fmt.Stringer in argument to log: Ocean does not implement fmt.Stringer (wrong type for String method) have String() want String() string

In the examples so far, we have defined methods on the value receiver. That is, if we use the functional invocation of methods, the first parameter, referring to the type the method was defined on, will be a value of that type, rather than a pointer. Consequently, any modifications we make to the instance provided to the method will be discarded when the method completes execution, because the value received is a copy of the data. It’s also possible to define methods on the pointer receiver to a type.

在到目前为止的示例中,我们已经在值接收器上定义了方法。 也就是说,如果我们使用方法的功能调用,则第一个参数(指的是定义方法的类型)将是该类型的值,而不是指针 。 因此,当方法完成执行时,我们对提供给该方法的实例所做的任何修改都将被丢弃,因为接收到的值是数据的副本。 也可以在指针接收器上为类型定义方法。

指针接收器 (Pointer Receivers)

The syntax for defining methods on the pointer receiver is nearly identical to defining methods on the value receiver. The difference is prefixing the name of the type in the receiver declaration with an asterisk (*). The following example defines a method on the pointer receiver to a type:

在指针接收器上定义方法的语法与在值接收器上定义方法的语法几乎相同。 区别在于,接收方声明中的类型名称以星号( * )开头。 以下示例将指针接收器上的方法定义为类型:

package main

import "fmt"

type Boat struct {
    Name string

    occupants []string
}

func (b *Boat) AddOccupant(name string) *Boat {
    b.occupants = append(b.occupants, name)
    return b
}

func (b Boat) Manifest() {
    fmt.Println("The", b.Name, "has the following occupants:")
    for _, n := range b.occupants {
        fmt.Println("\t", n)
    }
}

func main() {
    b := &Boat{
        Name: "S.S. DigitalOcean",
    }

    b.AddOccupant("Sammy the Shark")
    b.AddOccupant("Larry the Lobster")

    b.Manifest()
}

You’ll see the following output when you run this example:

运行此示例时,将看到以下输出:


   
Output
The S.S. DigitalOcean has the following occupants: Sammy the Shark Larry the Lobster

This example defined a Boat type with a Name and occupants. We want to force code in other packages to only add occupants with the AddOccupant method, so we’ve made the occupants field unexported by lowercasing the first letter of the field name. We also want to make sure that calling AddOccupant will cause the instance of Boat to be modified, which is why we defined AddOccupant on the pointer receiver. Pointers act as a reference to a specific instance of a type rather than a copy of that type. Knowing that AddOccupant will be invoked using a pointer to Boat guarantees that any modifications will persist.

本示例定义了一个具有NameoccupantsBoat类型。 我们要强制其他包中的代码仅使用AddOccupant方法添加乘员,因此我们通过将字段名的首字母小写来使occupants字段未导出。 我们还想确保调用AddOccupant会导致Boat实例被修改,这就是为什么我们在指针接收器上定义了AddOccupant原因。 指针充当对类型的特定实例的引用,而不是该类型的副本。 知道将使用指向Boat的指针来调用AddOccupant可以确保所有修改都将持久。

Within main, we define a new variable, b, which will hold a pointer to a Boat (*Boat). We invoke the AddOccupant method twice on this instance to add two passengers. The Manifest method is defined on the Boat value, because in its definition, the receiver is specified as (b Boat). In main, we are still able to call Manifest because Go is able to automatically dereference the pointer to obtain the Boat value. b.Manifest() here is equivalent to (*b).Manifest().

main ,我们定义了一个新变量b ,它将保存一个指向Boat ( *Boat )的指针。 我们在此实例上两次调用AddOccupant方法以添加两个乘客。 Manifest方法是在Boat值上定义的,因为在其定义中,接收方被指定为(b Boat) 。 在main ,我们仍然可以调用Manifest因为Go能够自动取消引用指针以获得Boat值。 b.Manifest()相当于(*b).Manifest()

Whether a method is defined on a pointer receiver or on a value receiver has important implications when trying to assign values to variables that are interface types.

在尝试将值分配给接口类型的变量时,在指针接收器上还是在值接收器上定义方法具有重要意义。

指针接收器和接口 (Pointer Receivers and Interfaces)

When you assign a value to a variable with an interface type, the Go compiler will examine the method set of the type being assigned to ensure that it has the methods the interface expects. The method sets for the pointer receiver and the value receiver are different because methods that receive a pointer can modify their receiver where those that receive a value cannot.

当您为具有接口类型的变量分配值时,Go编译器将检查要分配的类型的方法集,以确保其具有接口期望的方法。 指针接收者和值接收者的方法设置不同,因为接收指针的方法可以修改接收者的方法,而接收值的方法则不能。

The following example demonstrates defining two methods: one on a type’s pointer receiver and on its value receiver. However, only the pointer receiver will be able to satisfy the interface also defined in this example:

下面的示例演示了定义两种方法:一种在类型的指针接收器上,在其值接收器上。 但是,只有指针接收器才能满足在此示例中也定义的接口:

package main

import "fmt"

type Submersible interface {
    Dive()
}

type Shark struct {
    Name string

    isUnderwater bool
}

func (s Shark) String() string {
    if s.isUnderwater {
        return fmt.Sprintf("%s is underwater", s.Name)
    }
    return fmt.Sprintf("%s is on the surface", s.Name)
}

func (s *Shark) Dive() {
    s.isUnderwater = true
}

func submerge(s Submersible) {
    s.Dive()
}

func main() {
    s := &Shark{
        Name: "Sammy",
    }

    fmt.Println(s)

    submerge(s)

    fmt.Println(s)
}

When you run the code, you’ll see this output:

运行代码时,您将看到以下输出:


   
Output
Sammy is on the surface Sammy is underwater

This example defined an interface called Submersible that expects types having a Dive() method. We then defined a Shark type with a Name field and an isUnderwater method to keep track of the state of the Shark. We defined a Dive() method on the pointer receiver to Shark which modified isUnderwater to true. We also defined the String() method of the value receiver so that it could cleanly print the state of the Shark using fmt.Println by using the fmt.Stringer interface accepted by fmt.Println that we looked at earlier. We also used a function submerge that takes a Submersible parameter.

此示例定义了一个名为Submersible的接口,该接口期望具有Dive()方法的类型。 然后,我们使用Name字段和isUnderwater方法定义了Shark类型,以跟踪Shark的状态。 我们在指向Shark的指针接收器上定义了Dive()方法,该方法将isUnderwater修改为true 。 我们还定义String()值接收机的方法,以便它可以清晰地打印的状态Shark使用fmt.Println使用fmt.Stringer被接受的接口fmt.Println是我们在前面。 我们还使用了带有Submersible参数的功能submerge

Using the Submersible interface rather than a *Shark allows the submerge function to depend only on the behavior provided by a type. This makes the submerge function more reusable because you wouldn’t have to write new submerge functions for a Submarine, a Whale, or any other future aquatic inhabitants we haven’t thought of yet. As long as they define a Dive() method, they can be used with the submerge function.

使用Submersible接口而不是*Shark允许submerge函数仅取决于类型提供的行为。 这使得submerge功能更加可重用,因为您不必为SubmarineWhale或我们尚未想到的任何其他未来水生​​动物编写新的submerge功能。 只要它们定义了Dive()方法,它们就可以与submerge函数一起使用。

Within main we defined a variable s that is a pointer to a Shark and immediately printed s with fmt.Println. This shows the first part of the output, Sammy is on the surface. We passed s to submerge and then called fmt.Println again with s as its argument to see the second part of the output printed, Sammy is underwater.

main我们定义了一个变量s ,它是一个指向Shark的指针,并使用fmt.Println立即打印出s 。 这显示了输出的第一部分, Sammy is on the surface 。 我们将s传递给submerge ,然后再次使用s作为参数调用fmt.Println ,以查看输出的第二部分, Sammy is underwater

If we changed s to be a Shark rather than a *Shark, the Go compiler would produce the error:

如果将s更改为Shark而不是*Shark ,则Go编译器将产生错误:


   
Output
cannot use s (type Shark) as type Submersible in argument to submerge: Shark does not implement Submersible (Dive method has pointer receiver)

The Go compiler helpfully tells us that Shark does have a Dive method, it’s just defined on the pointer receiver. When you see this message in your own code, the fix is to pass a pointer to the interface type by using the & operator before the variable where the value type is assigned.

Go编译器有助于告诉我们Shark确实具有Dive方法,它只是在指针接收器上定义的。 当您在自己的代码中看到此消息时,解决方法是在分配值类型的变量之前使用&运算符将指针传递给接口类型。

结论 (Conclusion)

Declaring methods in Go is ultimately no different than defining functions that receive different types of variables. The same rules of working with pointers apply. Go provides some conveniences for this extremely common function definition and collects these into sets of methods that can be reasoned about by interface types. Using methods effectively will allow you to work with interfaces in your code to improve testability and leaves better organization behind for future readers of your code.

在Go中声明方法最终与定义接收不同类型变量的函数没有什么不同。 适用于指针的相同规则。 Go为这种极其常见的函数定义提供了一些便利,并将它们收集到可以由接口类型推理的方法集中。 有效地使用方法将使您能够使用代码中的接口来提高可测试性,并为以后的代码读者留下更好的组织。

If you’d like to learn more about the Go programming language in general, check out our How To Code in Go series.

如果您想全面了解Go编程语言,请查看我们的如何在Go中编写代码系列 。

翻译自: https://www.digitalocean.com/community/tutorials/defining-methods-in-go

golang定义一个方法


http://www.niftyadmin.cn/n/3649527.html

相关文章

vue中的突变方法在哪_了解GraphQL中的突变

vue中的突变方法在哪This is a continuation of a series on GraphQL Actions. The previous tutorial explored GraphQL Queries. This time, we will take a closer look at Mutations. There’s a lot of similarities in the way GraphQL Queries and Mutations work, howe…

Android:ViewPager制作幻灯片

最近在项目中用到图片轮播&#xff0c;试了Gallery&#xff0c;ViewFlipper&#xff0c;ViewPager,感觉Gallery最符合需求&#xff0c;但是Gallery的系统边框很难看&#xff0c;项目中要求用自己的背景图片。接下来用viewpager来做幻灯片效果。 <?xml version"1.0&quo…

建站分享:WordPress自定义网站背景图片

▣ 博主主站地址&#xff1a;微笑涛声 【www.cztcms.cn】 ▣ 博主其他平台&#xff1a; CSDN 简书 开源中国 思否 华为云博客 我的博客从上线到现在&#xff0c;网站背景一直是纯色的。突然想把背景改为图片。好在WordPress自定义背景图片比价简单。 1、找到正在使用的主题的…

建站分享:Gitee+PicGo 搭建Markdown图床

图床是干什么的&#xff1f; 图床就是一个便于在博文中插入在线图片连接的个人图片仓库。设置图床之后&#xff0c;在自己博客中插入的图片链接就可以随时随地在线预览了&#xff0c;并且不会因为任何意外原因无法查看&#xff0c;除非自己亲自删除。 ▣ 博主主站地址&#xff…

Android 基于google Zxing实现二维码、条形码扫描,仿微信二维码扫描效果

转载请注明出处&#xff1a;http://blog.csdn.net/xiaanming/article/details/10163203 了解二维码这个东西还是从微信中&#xff0c;当时微信推出二维码扫描功能&#xff0c;自己感觉挺新颖的&#xff0c;从一张图片中扫一下竟然能直接加好友&#xff0c;不可思议啊&#xff…

容器和Kubernetes入门:DigitalOcean工作坊套件

Getting Started with Containers and Kubernetes Workshop Kit Materials容器和Kubernetes Workshop Kit材料入门 This meetup kit is designed to help a technical audience become familiar with core Kubernetes concepts and practices. 该见面工具包旨在帮助技术读者熟悉…

体验一次Mac Win10主题——Win10美化之路

MAC Win10主题——Win10美化之路 偶遇致美化 一次偶然的机会&#xff0c;在网上看到了致美化 这个网站&#xff0c;好奇怎样给Windows 10换主题。Windows系统的界面风格虽然比较高效。不过UI界面是真的不好看。在网站找了半天&#xff0c;找到一个免费的Mac Win10主题。看了介绍…