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.pswp.cn/pingmian/76817.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/76817.shtml
英文地址,請注明出處:http://en.pswp.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標準 一、組件化架構改造 …