Go:方法

方法声明

type point struct { X, Y float64 }// 普通函数
func Distance(p, q Point) float64 {return math.Hypot(q.x - p.x, q.y -  p.Y)
}// Point类型的方法
func (p Point) Distance(q Point) float64 {return math.Hypot(q.x - p.x, q.y -  p.Y)
}

方法声明与普通函数声明类似,只是在函数名前多一个参数(接收者 ),将方法绑定到对应类型上 。以几何包为例 ,定义Point结构体 ,分别展示计算两点距离的普通函数DistancePoint类型的方法Distance ,方法的接收者p类似面向对象语言中向对象发送消息 ,Go 语言中接收者名字可自行选择 ,常用类型名首字母 。

p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(q))   // 函数调用
fmt.Println(p.Distance(q)) // 方法调用
  • 调用方式:调用方法时,接收者在方法名前面 ,如p.Distance(q) ,与声明保持一致 ,p.Distance这种表达式称作选择子 。
type Path []Pointfunc(path Path) Distance() float64 {sum := 0.0for i := range path {if i > 0 {sum += path[i-1].Distance(path[i])}}return sum
}
  • 命名冲突:不同类型可使用相同方法名 ,如PointPath类型都有Distance方法 ,编译器根据方法名和接收者类型决定调用哪个 。在同一类型命名空间内 ,方法名不能与字段名冲突 。

类型与方法绑定

Go 语言可将方法绑定到多种类型上 ,不仅限于结构体类型 ,像Path这种命名的 slice 类型也能定义方法 。同一个包下 ,只要类型不是指针类型和接口类型 ,都可声明方法 。不同类型的同名方法彼此无关 ,如Path.Distance内部可能使用Point.Distance计算相邻点距离 。

指针接收者的方法

func (p *point) ScaleBy(factor float64) {p.x *= factorp.Y *= factor
}

当函数需更新变量,或实参过大想避免复制整个实参时,需用指针传递变量地址 。如(*Point).ScaleBy方法 ,用于按指定因子缩放Point结构体的坐标 ,其接收者为*Point指针类型 。

type P *int
func (P) f() { /*...*/ } // 编译错误:非法的接收者类型
  • 声明规则:方法名是(*Point).ScaleBy ,括号必需 ,否则表达式会被错误解析 。习惯上若类型的一个方法使用指针接收者,其他方法也尽量用指针接收者 。命名类型(如Point )与其指针(*Point )是不同类型 ,不允许本身是指针的类型进行方法声明 。
r := &Point{1, 2}
r.ScaleBy(2)
fmt.Println(*r) // "{2, 4}"
// or
p := Point{1, 2}
pptr := &p
pptr.ScaleBy(2)
fmt.Println(p)
// or
p := Point{1, 2}
(&p).ScaleBy(2)
fmt.Println(p)
  • 调用规则:可通过*Point类型变量调用(*Point).ScaleBy方法 ,如r := &Point{1, 2}; r.ScaleBy(2) 。若变量是Point类型,但方法要求*Point接收者 ,编译器会对变量进行&p的隐式转换 ,只有变量(包括结构体字段、数组或 slice 元素 )允许这种转换 ,不能对不能取地址的Point临时变量调用*Point方法 。

合法的方法调用表达式需符合以下三种形式:

  • 实参接收者和形参接收者是同一类型 ,如Point{1, 2}.Distance(q) (都是Point类型 ),pptr.ScaleBy(2) (都是*Point类型 )。
  • 实参接收者是T类型变量,形参接收者是*T类型 ,编译器会隐式获取变量地址 ,如p.ScaleBy(2)pPoint类型 )。
  • 实参接收者是*T类型,形参接收者是T类型 ,编译器会隐式解引用接收者获取实际取值 ,如pptr.Distance(q)

复制问题

若类型T方法接收者是T本身,调用方法时实参会被复制 ;若接收者是指针类型 ,应避免复制T实例 ,防止破坏内部数据 ,如bytes.Buffer实例复制会有问题 。

nil是一个合法的接收者

nil在自定义类型方法中的使用

type IntList struct {Value intTail *IntList
}func (list *IntList) Sum() int {if list == nil {return 0}return list.Value + List.Tail.Sum()
}

以整型数链表IntList为例 ,*IntList类型中nil代表空链表 。Sum方法用于返回链表元素总和 ,当接收者listnil时 ,直接返回 0 ,否则返回当前节点值与后续链表总和 。定义允许nil作为接收者的类型时 ,应在文档注释中明确标明 。

nil在标准库类型方法中的使用

// Values 映射字符串到字符串列表
type Values map[string][]string// Get 返回第一个具有给定 key 的值
// 如不存在,则返回空字符串
func (v Values) Get(key string) string {if vs := v[key]; 1en(vs) > 0{ return vs[0]
} return ""// Add 添加一个键值到对应 key 列表中
func (v Values) Add(key, value string){v[key] = append(v[key], value)
}

net/url包中的Values类型为例 ,它本质是映射字符串到字符串列表的map类型 ,提供GetAdd等方法 。Get方法返回指定键的第一个值 ,若键不存在或接收者为nil ,返回空字符串 ;Add方法向对应键列表添加值 。当Values类型接收者为nil时 ,Get方法可正常工作 ,但Add方法会宕机 ,因为尝试更新一个空map 。方法对接收者引用本身的改变(如设置为nil或指向不同map )不会影响调用者 。

通过结构体内嵌组成类型

type Point struct{ X, Y float64 }
type ColordPoint struct {PointColor color.RGBA
}var cp ColoredPoint
cp.X = 1
fmt.Println(cp.Point.X)
cp.Point.Y = 2
fmt.Println(cp.Y)red := color.RGBA{255, 0, 0, 255}
bule := color.RGBA{0, 0, 255, 255}
var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(q.Point)) // "5"
q.ScaleBy(2)
q.ScaleBy(2)
fmt.Println(p.Distance(q.Point)) // "10"

ColoredPoint类型为例 ,它嵌套了Point结构体 ,并包含Color字段 。通过嵌套 ,ColoredPoint可直接使用Point的字段 ,如cp.X等同于cp.Point.X ,也能调用Point类型的方法 ,如p.Distance(q.Point) ,实现代码相当于自动生成了包装方法来调用Point声明的方法 。但要注意ColoredPoint不是Point ,不能直接传递ColoredPoint实例给要求Point参数的方法 。

type coloredPoint struct {*PointColor color.RGBA
}
p := ColoredPoint{&Point{1, 1}, red}
q := ColoredPoint{&Point{5, 4}, blue}
fmt.Println(p.Distance(*q.Point)) // "5"
q.Point = p.Point
p.ScaleBy(2)
fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}"

ColoredPoint嵌套*Point指针类型时 ,字段和方法间接地来自所指向的对象 ,如pq可共享一个Point 。结构体类型可拥有多个嵌套字段 ,编译器处理选择子(如p.ScaleBy )时 ,优先查找直接声明的方法 ,再依次从嵌套字段的方法中查找 。

var (mu sync.Mutexmapping = make(map[string]string)
)func Lookup(key string) string {mu.Lock()v := mapping[key]mu.Unlock()return v
}
var cache = struct {sync.Mutexmapping map[string]string
} {mapping: make(map[string]string),
}func Lookup(key string) string {cache.Lock()v := cache.mapping[key]cache.Unlock()return v
}

结构体嵌套在缓存实现中的应用,最初使用包级别的互斥锁mumapping变量保护map数据 ,后来将相关变量封装到cache结构体中 ,该结构体嵌套了sync.Mutex ,这样cache变量可直接使用MutexLockUnlock方法进行加锁和解锁操作 ,使代码结构更清晰 。

方法变量与表达式

p := Point{1, 2}
q := Point{4, 6}
distanceFromP := p.Distance  // 方法变量
fmt.Println(ditanceFromP(q))
var origin Point
fmt.Println(distanceFromP(origin))scaleP := p.ScaleBy // 方法变量
scaleP(2)  // p -> (2, 4)
scaleP(3)  //      (6, 12)
scaleP(10) //      (60, 120)
  • 可将选择子(如p.Distance )赋值给一个变量 ,形成方法变量 ,它是一个函数 ,绑定了方法(Point.Distance )和接收者p 。调用时只需提供实参 ,无需再提供接收者 。
type Rocket struct { /*...*/ }
func (r *Rocket) Launch { /*...*/ }r := new(Rocket) Launch { /*...*/ }
// time.AfterFunc(10 * time.Second, func() { r. Launch() })
time.AfterFunc(10 * time.Second, r.Launch)
  • time.AfterFunc等场景中 ,方法变量很有用 。如启动火箭的例子 ,使用方法变量可使代码更简洁 ,直接传递r.Launchtime.AfterFunc ,在延迟后调用该方法 。

方法表达式

  • 方法表达式写成T.f(*T).f形式 ,其中T是类型 ,它把方法的接收者替换成函数的第一个形参 ,可像普通函数一样调用 。同样以Point结构体的DistanceScaleBy方法为例 ,展示方法表达式的赋值与调用 。
type Point struct{ X, Y float64 }func (p Point) Add(q Point) Point { return Point{p.X + q.X, p.Y + q.Y} }
func (p Point) Sub(q Point) Point { return Point{p.X - q.X, p.Y - q.Y} }type Path []Pointfunc (path Path) TranslateBy(offset Point, add bool) {var op func(p, q Point) Pointif add {op = Point.Add} else {op = Point.Sub}for i := range path {// 调用 path[i].Add(offset) 或者是 path[i].Sub(offset)path[i] = op(path[i], offset)}
}

当需要用一个值代表同一类型的多个方法 ,并处理不同接收者时 ,方法变量很有帮助 。如Path.TranslateBy函数 ,根据add参数决定使用Point.AddPoint.Sub方法 ,对路径上的每个点进行相应计算 。

示例:位向量

在数据分析领域,对于元素为小的非负整型且元素众多,操作多为求并集和交集的集合,使用map[T]bool实现集合扩展性虽好但性能欠佳,位向量是更优数据结构 。

// IntSet 是一个包含非负整数的集合
// 零值代表空的集合
type IntSet struct {words []uint64
}
// Has 方法的返回值表示是否存在非负数x
func(s *IntSet) Has(x int) bool{word, bit := x/64, uint(x%64) return word < len(s.words) && s.words[word]&(1<<bit)!=0
}
// Add  添加非负数 x 到集合中
func(s *IntSet) Add(x int){word, bit := x/64, uint(x%64) for word >= 1en(s.words){s.word s = append(s.words, 0)}s.words[word] |= 1<<bit
}// Unionwith 将会对 s 和 t 做并集并将结果存在 s 中
func(s *ntSet) Unionwith(t *IntSet){for i, tword := range t.words { if i < len(s.words){s.words[1] |= tword} else {  s.words = append(s.words, tword)}
}// String方法以字符串"{1 2 3}"的形式返回集中
func (s *IntSet) String() string {var buf bytes.Bufferbuf.WriteByte('{')for i, word := range s.words {if word == 0 {continue}for j := 0; j < 64; j++ {if word&(1<<uint(j)) != 0 {if buf.Len() > len("{") {buf.WriteByte(' ')}fmt.Fprintf(&buf, "%d", 64*i+j)}}}buf.WriteByte('}')return buf.String()
}

IntSet类型的实现与方法

  • 结构定义IntSet结构体包含words []uint64字段 ,用无符号整型值的 slice 表示集合 ,每一位代表集合中的一个元素 。
  • 方法功能:
    • Has方法:判断集合中是否存在非负数x ,通过计算x所在的字索引和位索引 ,检查对应位是否为 1 。
    • Add方法:向集合中添加非负数x ,确定x所在字索引 ,若字不存在则扩展words ,然后将对应位置为 1 。
    • UnionWith方法:对两个IntSet求并集 ,遍历另一个集合的字 ,与当前集合对应字按位或操作 ,不存在的字添加到当前集合 。
    • String方法:以字符串形式输出集合元素 ,使用bytes.Buffer ,遍历words ,对每个字的每一位检查 ,是 1 则将对应元素添加到字符串 。

IntSet类型方法声明为指针类型接收者 ,使用值调用方法时需注意 ,编译器会隐式插入&操作符获取指针以调用String方法 ,若无String方法 ,fmt.Println会直接输出结构体 。

封装

  • 概念:封装(数据隐藏 )是面向对象编程重要方面,指变量或方法不能通过对象访问 。
  • 实现方式:Go 语言通过标识符首字母大小写控制命名可见性 ,首字母大写可从包中导出 ,首字母小写则不导出 ,要封装对象需使用结构体 。以IntSet类型为例 ,若定义为结构体且字段words首字母小写 ,则该字段在包外不可见 ;若重新定义IntSet为 slice 类型 ,表达式*s可在其他包内使用 ,但会暴露内部表示 。Go 语言中封装单元是包而非类型 ,结构体字段在同包内代码可见

优点

  • 减少变量检查:使用方不能直接修改对象变量 ,减少检查变量值的代码 。
type Buffer struct {buf       []byteinitial   [64]byte/*... */
}// Grow方法按需扩展缓冲区的大小
// 保证n个字节的空间
func (b *Buffer) Grow(n int) {if b.buf == nil {b.buf = b.initial[:0] // 最初使用预分配的空间}if len(b.buf)+n > cap(b.buf) {buf := make([]byte, b.Len(), 2*cap(b.buf)+n)copy(buf, b.buf)b.buf = buf}
}
  • 隐藏实现细节:防止使用方依赖的属性改变 ,方便设计者灵活改变 API 实现且不破坏兼容性 。以bytes.Buffer为例 ,其内部字段未导出 ,外部使用者无需关心实现细节 ,仅感知性能提升 。
type Counter struct { n int }func (c *Counter) N() int { return c.n }
func (c *Counter) Increment() { c.n++ }
func (c *Counter) Reset() { c.n = 0 }
  • 保护对象内部资源:防止使用者随意改变对象内变量 ,包作者可通过包内函数维护对象内部资源 。如Counter类型 ,使用者只能通过特定方法递增或重置计数器 ,不能随意设置计数值 。

导出字段与封装的权衡

Go 语言允许导出字段 ,但需慎重考虑 API 兼容性、维护复杂度等因素 。同时封装并非总是必需 ,如time.Duration类型暴露int64整型用于运算 。

参考资料:《Go程序设计语言》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/76817.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端基础之《Vue(4)—响应式原理》

一、什么是响应式 1、响应式英文reactive 当你get/set一个变量时&#xff0c;你有办法可以“捕获到”这种行为。 2、一个普通对象和一个响应式对象对比 &#xff08;1&#xff09;普通对象 <script>// 这种普通对象不具备响应式var obj1 {a: 1,b: 2} </script>…

【技术派部署篇】Windows本地部署技术派

一、技术派简介 技术派是一个采用 Spring Boot、MyBatis-Plus、MySQL、Redis、ElasticSearch、MongoDB、Docker、RabbitMQ 等技术栈的社区系统&#xff0c;其 1.0 版已正式上线。该项目的技术栈按阶段集成引入&#xff0c;开发者可根据自身需求选择不同版本进行学习。 二、环…

DeepSeek和ChatGPT的全面对比

DeepSeek和ChatGPT作为当前领先的大语言模型&#xff0c;代表了AI发展的不同技术路径和应用理念。以下从技术架构到用户体验的全面对比分析&#xff0c;将揭示两者在AI竞赛中的独特定位。 一、模型架构与原理 1. DeepSeek 架构特点&#xff1a;采用混合专家系统&#xff08;…

Python星球日记 - 第20天:数据分析入门

🌟引言: 欢迎来到Python星球🪐的第20天!今天我们将踏入数据分析的世界,学习如何使用pandas处理数据并提取有价值的信息。无论你是想分析商业销售数据、股票市场趋势还是科学实验结果,pandas都是你必不可少的工具! 上一篇:Python星球日记 - 第19天:Web开发基础 名人…

算力云平台部署—SadTalker的AI数字人视频

选择算力 部署选择 选择镜像 机器管理 控制台 通过平台工具进入服务器 认识管理系统 打开命令行 进入目录 stable-diffusion-webui# cd 增加执行权限 chmod x ./webui.sh 运行命令 bash ./webui.sh sudo apt install -y python3 python3-venv git 安装软件 Creating the …

Linux目录结构:核心目录功能与用途解析

引言 Linux的目录结构就像一棵精心设计的大树&#x1f333;&#xff0c;每个分支都有其特定的用途和规范&#xff01;与Windows不同&#xff0c;Linux采用单一的目录层次结构&#xff0c;所有设备、分区和网络资源都挂载在这个统一的目录树下。本文将带你深入探索Linux目录结构…

【学习笔记】两个类之间的数据交互方式

在面向对象编程中&#xff0c;两个类之间的数据交互可以通过以下几种方式实现&#xff0c;具体选择取决于需求和设计模式&#xff1a; 1. 通过方法调用 一个类通过调用另一个类的公共方法来获取或传递数据。这是最常见的方式&#xff0c;符合封装原则。 class ClassA:def __…

神经网络学习--误差反向传播法

最近在学习神经网络&#xff0c;主要是依据书本《深度学习入门&#xff08;基于Python的理论与实现&#xff09;》&#xff0c;现对第5章“误差反向传播法”中的示例程序进行注释修改如下&#xff0c;以备后续查阅。 编程软件用的是Eric7&#xff0c;界面如下&#xff1a; 神经…

前端常用组件库全览与推荐

&#x1f4cc; 一、组件库生态全景图 &#x1f680; 二、React 生态组件库推荐 名称简介官网Ant Design阿里出品&#xff0c;企业级 UI 系统&#xff0c;设计规范完整&#xff0c;适合后台系统https://ant.designMaterial UIGoogle Material Design 实现&#xff0c;样式响应式…

群晖如何通过外网访问

1、进入群晖控制面板-》连接性-》外部访问-》DDNS 2、新增&#xff0c;添加DDNS 选择服务供应商&#xff0c;我这里以DNSPod.cn为例。 3、这一步开始&#xff0c;需要前往DNSPod.cn进行注册域名&#xff08;也可以使用你已有的域名&#xff0c;转入即可&#xff09;&#xff0…

3.2.2.1 Spring Boot配置静态资源映射

在Spring Boot中配置静态资源映射&#xff0c;可以通过默认路径或自定义配置实现。默认情况下&#xff0c;Spring Boot会在classpath:/static/等目录下查找静态资源。若需自定义映射&#xff0c;可通过实现WebMvcConfigurer接口的addResourceHandlers方法或在全局配置文件中设置…

【概念】什么是UI(User interface)什么是UX(User experience)?

1. 软件生命周期管理 (Software Life Cycle Management) 解释&#xff1a; 中文&#xff1a; 软件生命周期管理是指从软件规划、设计、开发、测试、部署到后续维护甚至退役的整个过程。English: Software Life Cycle Management refers to the systematic process of plannin…

第十六届蓝桥杯大赛软件赛省赛 C/C++ 大学B组

由于官方没有公布题目的数据, 所以代码仅供参考 1. 移动距离 题目链接&#xff1a;P12130 [蓝桥杯 2025 省 B] 移动距离 - 洛谷 【问题描述】 小明初始在二维平面的原点&#xff0c;他想前往坐标 (233, 666)。在移动过程中&#xff0c;他 只能采用以下两种移动方式&#xf…

​​IPerf工具使用笔记(基于MobaXterm串口终端)​

​​一、问题现象​​ ​​终端输入无响应​​ 启动iperf服务器后&#xff0c;终端被阻塞&#xff0c;无法输入其他命令&#xff08;如图中重复输出日志覆盖输入区域&#xff09;。​​直接原因​​&#xff1a;iperf_server线程未正确处理退出标志&#xff0c;导致select或acc…

【从C到C++的算法竞赛迁移指南】第五篇:现代语法糖精粹 —— 写出优雅的竞赛代码

系列导航&#xff1a; [第一篇] 基础语法与竞赛优势[第二篇] 动态数组与字符串革命[第三篇] 映射与集合的终极形态[第四篇] STL算法与迭代器[▶ 本篇] 现代语法糖精粹[第六篇] 竞赛实战技巧 一、范围for循环&#xff1a;告别索引的束缚 1.1 C风格遍历的四大痛点 // 痛点示例&…

mongodb在window10中创建副本集的方法

创建Mongodb的副本集最好是新建一个文件夹&#xff0c;如D:/data&#xff0c;不要在mongodb安装文件夹里面创建副本集&#xff0c;虽然这样也可以&#xff0c;但是容易造成误操作或路径混乱&#xff1b;在新建文件夹里与现有 MongoDB 数据隔离&#xff0c;避免误操作影响原有数…

使用Python进行AI图像生成:从GAN到风格迁移的完整指南

AI图像生成是一个非常有趣且前沿的领域&#xff0c;结合了深度学习和计算机视觉技术。以下是一些使用Python和相关库进行AI图像生成的创意和实现思路&#xff1a; 1. 使用GAN&#xff08;生成对抗网络&#xff09; 基本概念&#xff1a;GAN由两个神经网络组成&#xff1a;生成…

P10413 [蓝桥杯 2023 国 A] 圆上的连线

题意&#xff1a; 给定一个圆&#xff0c;圆上有 n2023 个点从 1 到 n 依次编号。 问有多少种不同的连线方式&#xff0c;使得完全没有连线相交。当两个方案连线的数量不同或任何一个点连接的点在另一个方案中编号不同时&#xff0c;两个方案视为不同。 答案可能很大&#x…

鸿蒙5.0 非桌面页面,设备来电后挂断,自动返回桌面

1.背景 其实在Android上面打开一个应用,然后设备来电后挂断应该是返回到前面打开的这个应用的,但是在鸿蒙里面现象是直接返回桌面,设计如此 2.分析 这个分析需要前置知识,鸿蒙的任务栈页面栈,具体参考如下链接: zh-cn/application-dev/application-models/page-missio…

智能Todo协作系统开发日志(二):架构优化与安全增强

&#x1f4c5; 2025年4月14日 | 作者&#xff1a;Aphelios380 &#x1f31f; 今日优化目标 在原Todo单机版基础上进行三大核心升级&#xff1a; 组件化架构改造 - 提升代码可维护性 本地数据加密存储 - 增强隐私安全性 无障碍访问支持 - 践行W3C标准 一、组件化架构改造 …