Go 语言入门很简单 -- 12. Go 方法 #私藏项目实操分享#

简介: Go 语言入门很简单 -- 12. Go 方法 #私藏项目实操分享#

前言

虽然从技术上讲 Go 不是面向对象的编程语言,但类型和方法允许采用面向对象的编程风格。最大的不同是 Go 不支持类型继承,而是有接口的概念。

在本文中,我们将重点介绍 Go 对方法和接口的使用。

image.png

Note:一个常见的问题是“函数和方法之间的区别是什么”。方法是具有定义接收器的函数,在 OOP( Object Oriented Programming language) 术语中,方法是对象实例上的函数。

方法

在上一章 Go 结构体的文章中,我们使用如下的方法来计算一个圆的面积:

image.png

但在调用的时候需要使用 &c 来调用它,如何通过方法这一特殊类型的函数来改善呢?

func (c *Circle) area() float64 {
    return math.Pi * c.r * c.r
}

Go 没有类。但是,您可以在结构体类型上定义方法。方法定义与函数定义很像。事实上,它们只有一点不同:你需要增加一个额外的参数,一个接收器参数,它在函数名称之前的括号中。

func 关键字和函数名中间,可以添加一个方法接收器 “receiver”,接收器就像是一个参数(它有名字和类型)。

image.png

通过以这种方式创建的方法,它允许我们使用 点号 . 运算符,类似这样:

fmt.Println(c.area())

为了调用你定义的方法,键入你要在其上调用方法的值、一个点要调用的方法的名称,跟着一对括号。这里你调用的方法的值被称为方法接收器。

方法调用和方法定义的相似性能帮助你记住语法:当你调用一个方法时,接收器要被列为第一个,并且当你定义一个方法的时候,接收器参数也被列为第一个。

image.png

这样读起来就容易多了,我们不再需要 & 运算符(Go 自动知道为这个方法传递一个指向圆的指针),而且因为这个函数只能用于 Circle类型,我们可以将该函数的名称改为 area

方法定义中的接收器参数的名称并不重要,但它的类型很重要;你定义的方法与此类型的值都关联。Go使用接收器参数来代替其他语言中的 “self” 或者 “this” 。

问:我见到其他语言使用的方法接收器在方法块中是一个特定的以self或者this命名的值。Go也这么做吗?

答:Go使用接收器参数来代替  self和 this 。两者有着巨大的不同,self 和 this 是隐含的,而你是显式地声明一个接收器参数。除此以外,接收器的用处相同,Go  没有必要保留 self 和 this 关键字!(如果你想要,你可以将接收器参数命名为  this,但是不要这么做,约定是使用接收器参数类型名称的第一个字母。)

练习

定义一个长方体的结构体类型,利用方法来计算长方形的周长和面积:

package main
import "fmt"
type rectangle struct {
    width, height int
}
func (r *rectangle) area() int {
    return r.width * r.height
}
func (r *rectangle) perimeter() int {
    return 2 * r.width * r.height
}
func main() {
    r := rectangle{width: 10, height: 6}
    fmt.Println("area: ", r.area())
    fmt.Println("perimeter: ", r.perimeter())
}
// area:  60
// perimeter:  120

嵌入类型

继承是OOPs最重要的支柱,但是Go并没有继承(Is-A),也就是说,没有父类和子类的关系。但是可以通过嵌入来实现类似的功能(Has-A)

一个结构体字段通常表示一种从属关系( has-a relationship)。比如圆有半径,矩形有长和宽。

嵌入类型,或者嵌套类型,这是一种可以把已有的类型声明在新的类型里的一种方式,这种功能对代码复用非常重要。

在其他语言中,利用继承可以做同样的事情,但是在 Go 语言中,没有继承的概念,Go 提倡的代码复用的方式是组合,所以这也是嵌入类型的意义所在,组合而不是继承,所以Go才会更灵活。

假如有一个 Person 结构体:

type Person struct {
    Name string
}
func (p *Person) Talk() {
    fmt.Println("Hello, my name is ", p.Name)
}

同时,我们想要创建一个 Student 结构体,可以创建如下:

type Student struct {
    Person Person
    School string
}

Go 支持通过嵌入类型来支持这种复合关系,也被成为匿名字段:

type Student struct {
    Person
    Model string
}

我们使用 Person 类型而不用给出一个名字。当以这样的方式定义的时候,可以访问类型名:

s := new(Student)
s.Person.Talk()

同时可以在 Student 结构体中直接调用 Person 的方法:

s := new(Student)
s.Talk()

这种关系有点像:人能说话,学生是人,因此学生能说话。来看一下整个例子:

package main
import "fmt"
type Person struct {
    Name string
}
type Student struct {
    Person
    School string
}
func (p *Person) Talk() {
    fmt.Println("Hello, my name is ", p.Name)
}
func main() {
    s := new(Student)
    s.Name = "Michael"
    s.Person.Talk()
    s1 := new(Student)
    s1.Name = "Kobe"
    s1.School = "Lauer Merion High School"
    s1.Talk()
    fmt.Println("I come from ", s1.School)
}
// Hello, my name is  Michael
// Hello, my name is  Kobe
// I come from  Lauer Merion High School

方法解析过程

编译器使用方法解析过程来寻找方法。当一个方法在一个类型上被调用时,它首先尝试在同类型中找到。如果在同一类型中没有找到,它就会查看所有的嵌入类型;如果没有一个嵌入类型有这个方法,它就会尝试在嵌入类型的嵌入类型中寻找。

代码组织结构

既然我们现在学习了变量、常量、结构体、方法和函数,那么一个包中该怎么同时组织这些结构呢?

方法其实可以在包中的任何文件任意位置上定义,但我的建议是按如下所示组织代码:

package models
// list of packages to import
import (
    "fmt"
)
// list of constants
const (
    ConstExample = "const before vars"
)
// list of variables
var (
    ExportedVar    = 42
    nonExportedVar = "so say we all"
)
// Main type(s) for the file,
// try to keep the lowest amount of structs per file when possible.
type User struct {
    FirstName, LastName string
    Location            *UserLocation
}
type UserLocation struct {
    City    string
    Country string
}
// List of functions
func NewUser(firstName, lastName string) *User {
    return &User{FirstName: firstName,
        LastName: lastName,
        Location: &UserLocation{
            City:    "Santa Monica",
            Country: "USA",
        },
    }
}
// List of methods
func (u *User) Greeting() string {
    return fmt.Sprintf("Dear %s %s", u.FirstName, u.LastName)
}

总结

又到了总结的时候,方法学完了,来看看有哪些重点吧:

  • 函数和方法的声明几乎相似。在它们的声明中只有一个区别,那就是方法的声明包含接收器,而函数则没有。
  • 方法总是可以与任何类型相关联。我们可以通过接收器指定该类型的接收器。
  • 我们不能声明一个没有主体类型的方法。但可以在任何地方声明一个方法的类型。
  • 当我们想修改和获取一个类型的属性时,我们会向该类型声明一个方法。
  • 有两种类型的接收器:
  • 指针:当我们想修改类型的属性时,使用指针。
  • 值:当我们想获得该类型的属性时,使用值。值接收器方法所做的修改并不反映在方法之外。
  • 我们可以在基本数据类型、复合类型,甚至是函数上声明方法。
  • 一个方法可以在一个类型的实例上使用点运算符被调用。
  • 如果一个方法在嵌入式类型中是可用的,那么Golang会使用 方法解析过程来找到一个方法。如果一个方法被嵌入到两个类型中,可能会在同一级别出现模糊不清的情况。

   

相关文章
|
4天前
|
算法 安全 Go
Go语言中的加密和解密是如何实现的?
Go语言通过标准库中的`crypto`包提供丰富的加密和解密功能,包括对称加密(如AES)、非对称加密(如RSA、ECDSA)及散列函数(如SHA256)。`encoding/base64`包则用于Base64编码与解码。开发者可根据需求选择合适的算法和密钥,使用这些包进行加密操作。示例代码展示了如何使用`crypto/aes`包实现对称加密。加密和解密操作涉及敏感数据处理,需格外注意安全性。
29 14
|
4天前
|
Go 数据库
Go语言中的包(package)是如何组织的?
在Go语言中,包是代码组织和管理的基本单元,用于集合相关函数、类型和变量,便于复用和维护。包通过目录结构、文件命名、初始化函数(`init`)及导出规则来管理命名空间和依赖关系。合理的包组织能提高代码的可读性、可维护性和可复用性,减少耦合度。例如,`stringutils`包提供字符串处理函数,主程序导入使用这些函数,使代码结构清晰易懂。
38 11
|
4天前
|
存储 安全 Go
Go语言中的map数据结构是如何实现的?
Go 语言中的 `map` 是基于哈希表实现的键值对数据结构,支持快速查找、插入和删除操作。其原理涉及哈希函数、桶(Bucket)、动态扩容和哈希冲突处理等关键机制,平均时间复杂度为 O(1)。为了确保线程安全,Go 提供了 `sync.Map` 类型,通过分段锁实现并发访问的安全性。示例代码展示了如何使用自定义结构体和切片模拟 `map` 功能,以及如何使用 `sync.Map` 进行线程安全的操作。
|
9天前
|
监控 安全 算法
深度剖析核心科技:Go 语言赋能局域网管理监控软件进阶之旅
在局域网管理监控中,跳表作为一种高效的数据结构,能显著提升流量索引和查询效率。基于Go语言的跳表实现,通过随机化索引层生成、插入和搜索功能,在高并发场景下展现卓越性能。跳表将查询时间复杂度优化至O(log n),助力实时监控异常流量,保障网络安全与稳定。示例代码展示了其在实际应用中的精妙之处。
32 9
|
18天前
|
算法 安全 Go
Go 语言中实现 RSA 加解密、签名验证算法
随着互联网的发展,安全需求日益增长。非对称加密算法RSA成为密码学中的重要代表。本文介绍如何使用Go语言和[forgoer/openssl](https://github.com/forgoer/openssl)库简化RSA加解密操作,包括秘钥生成、加解密及签名验证。该库还支持AES、DES等常用算法,安装简便,代码示例清晰易懂。
52 12
|
24天前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
69 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
1月前
|
存储 Go 索引
go语言中数组和切片
go语言中数组和切片
44 7
|
1月前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
1月前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
111 71
|
1月前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
113 67