地址:https://studygolang.com/articles/20062?fr=sidebar#google_vignette
# Golang研學:在函數、方法、接口中用好指針類型
在大部分面向對象語言如C++、C#、Java,在函數傳參數時除了基礎值類型,對象是通過引用方式傳遞的。
**然而,在Go語言中,除了map、slice和chan,所有類型(包括struct)都是值傳遞的。**
那么,如何在**函數外**使用**函數內處理后**的變量呢?只能通過返回新變量嗎?
**不,可以使用指針**
大部分面向對象語言都很少有用到指針的場景了,但是在Go語言中有大量的指針應用場景,要成為一名合格的Gopher,必須了解。
## [](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2FDawnGrp%2FDawnCode%2Ftree%2Fmaster%2Fcodes%2FgoPointer%23%25E6%25A6%2582%25E5%25BF%25B5)概念
每一個變量都會分配一塊內存,數據保存在內存中,內存有一個地址,就像門牌號,通過這個地址就可以找到里面存儲的數據。
指針就是保存這個內存地址的變量。
**在Go語言中,用`&`取得變量的地址**
~~~
//為了說明類型,我采用了顯性的變量定義方法,實際開發中更多的是用“:=”自動獲取類型變量類型
var mystr string = "Hello!"
var mystrP *string = &mystr
fmt.Println(mystrP)
~~~
將以上代碼敲入main函數中,`go run`,打印出的內容就是`mystr`的內存地址。`mystrP`就是`mystr`的指針變量。
**用`*`取得指針變量指向的內存地址的值**
在之前的代碼的后面增加一句代碼:
~~~
fmt.Println(*mystrPointer)
~~~
`go run`運行后,可以看到打印出`mystr`的值“Hello!”
**符號`*`也用做定義指針類型的關鍵字。**
例如:
~~~
var p *int
~~~
## [](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2FDawnGrp%2FDawnCode%2Ftree%2Fmaster%2Fcodes%2FgoPointer%23%25E6%258C%2587%25E9%2592%2588%25E5%25BA%2594%25E7%2594%25A8%25E5%259C%25BA%25E6%2599%25AF)指針應用場景
在其他OOP語言中,大多數情況是不需要花太多時間操作指針的,如Java、C#,對象的引用操作都已經交給了虛擬機和框架。而Go經常會用到指針。原因主要有3個:
1. Go語言中除了map、slice、chan外,其他類型在函數參數中都是值傳遞
2. Go語言不是面向對象的語言,很多時候實現結構體方法時需要用指針類型實現引用結構體對象
3. 指針也是一個類型,在實現接口`interface`時,結構體類型和其指針類型對接口的實現是不同的
接下來就分別介紹一下,期間會穿插一些簡單的代碼片段,您可以創建一個Go文件輸入代碼,運行體驗一下。
### [](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2FDawnGrp%2FDawnCode%2Ftree%2Fmaster%2Fcodes%2FgoPointer%23%25E5%2587%25BD%25E6%2595%25B0%25E4%25B8%25AD%25E4%25BC%25A0%25E9%2580%2592%25E6%258C%2587%25E9%2592%2588%25E5%258F%2582%25E6%2595%25B0)函數中傳遞指針參數
Go語言都是值傳遞,例如:
~~~
package main
import "fmt"
func main() {
i := 0
f(i)
fmt.Println(i)
}
func f(count int) {
fmt.Println(count)
count++
}
~~~
結果:
~~~
0
0
~~~
`i`在執行前后沒有變化
如果希望被函數調用后,`i`的值產生變化,`f`函數的參數就應該改為`*int`類型。如:
~~~
func main() {
i := 0
f(&i)
fmt.Println(i)
}
func f(count *int) {
fmt.Println(*count)
(*count)++
}
~~~
1. f定義參數用`*int`替代`int`,申明參數是一個int類型的指針類型
2. 調用函數時,不能直接傳遞int的變量`i`,而要傳遞用`&`取得`i`的地址
3. f函數內,參數`count`現在是指針了,不能直接打印,需要用`*`取出這個指針指向的地址里保存的值
4. `count`的取值+1.
5. 調用f函數,在主函數`main`里打印`i`。
可以看到結果
~~~
0
1
~~~
`i`的值改變了。
### [](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2FDawnGrp%2FDawnCode%2Ftree%2Fmaster%2Fcodes%2FgoPointer%23struct%25E7%25BB%2593%25E6%259E%2584%25E4%25BD%2593%25E6%258C%2587%25E9%2592%2588%25E7%25B1%25BB%25E5%259E%258B%25E6%2596%25B9%25E6%25B3%2595)Struct結構體指針類型方法
Go語言中給結構體定義方法
~~~
//定義一個結構體類型
type myStruct struct {
Name string
}
//定義這個結構體的改名方法
func (m myStruct) ChangeName(newName string) {
m.Name = newName
}
func main() {
//創建這個結構體變量
mystruct := myStruct{
Name: "zeta",
}
//調用改名函數
mystruct.ChangeName("Chow")
//沒改成功
fmt.Println(mystruct.Name)
}
~~~
這樣的方法不會改掉結構體變量內的字段值。 就算是結構體方法,如果不使用指針,方法內還是傳遞結構體的值。
現在我們改用指針類型定義結構體方法看看。
只修改`ChangeName`函數,用`*myStruct`類型替代`myStruct`
~~~
func (m *myStruct) ChangeName(newName string) {
m.Name = newName
}
~~~
再運行一次,可以看到打印出來的名字改變了。
**當使用指針類型定義方法后,結構體類型的變量調用方法時會自動取得該結構體的指針類型并傳入方法。**
### [](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2FDawnGrp%2FDawnCode%2Ftree%2Fmaster%2Fcodes%2FgoPointer%23%25E6%258C%2587%25E9%2592%2588%25E7%25B1%25BB%25E5%259E%258B%25E7%259A%2584%25E6%258E%25A5%25E5%258F%25A3%25E5%25AE%259E%25E7%258E%25B0)指針類型的接口實現
最近在某問答平臺回答了一個Gopher的問題,大致內容是問為什么不能用結構體指針類型實現接口方法?
看一下代碼
~~~
//定義一個接口
type myInterface interface {
ChangeName(string)
SayMyName()
}
//定義一個結構體類型
type myStruct struct {
Name string
}
//定義這個結構體的改名方法
func (m *myStruct) ChangeName(newName string) {
m.Name = newName
}
func (m myStruct) SayMyName() {
fmt.Println(m.Name)
}
//一個使用接口作為參數的函數
func SetName(s myInterface, name string) {
s.ChangeName(name)
}
func main() {
//創建這個結構體變量
mystruct := myStruct{
Name: "zeta",
}
SetName(mystruct, "Chow")
mystruct.SayMyName()
}
~~~
這段代碼是無法編譯通過的,會提示
~~~
cannot use mystruct (type myStruct) as type myInterface in argument to SetName:
myStruct does not implement myInterface (ChangeName method has pointer receiver)
~~~
`myStruct`類型沒有實現接口方法`ChangeName`,也就是說`func (m *myStruct) ChangeName(newName string)`并不算實現了接口,因為它是`*myStruct`類型實現的,而不是`myStruct`。
改一改
在調用SetName時,用&mystruct 替代 mystruct:
~~~
SetName(&mystruct, "Chow")
~~~
編譯運行,成功。
為什么結構體類型實現的接口該結構體的指針類型也算實現了,而指針類型實現的接口,不算是該結構體實現了接口呢?
\*\* 原因是,結構體類型定義的方法可以被該結構體的指針類型調用;而結構體類型調用該指針類型的方法時是被轉換成指針,不是直接調用。\*\*
所以,`&mystruct`直接實現了接口定義的`ChangeName`和`SayMyName`兩個方法,而`mystruct`只能實現了`SayMyName`,`mystruct`調用`ChangeName`方法其實轉換成指針類型后調用的,不算實現了接口。
* * *
到此Go語言指針類型的應用介紹差不多了。
總結一下:
1. Go語言中指針非常常用,一定要掌握
2. Go語言除了map、slice、chan其他都是值傳遞,引用傳遞一定要用指針類型
3. 結構體類型定義方法要注意使用指針類型
4. 接口實現方法時,用指針類型實現的接口函數只能算是指針類型實現的,用結構體類型實現的方法也作為是指針類型實現。
歡迎大家一起討論、學習Go語言!!
- Golang
- Beego框架
- Gin框架
- gin框架介紹
- 使用Gin web框架的知名開源線上項目
- go-admin-gin
- air 熱啟動
- 完整的form表單參數驗證語法
- Go 語言入門練手項目推薦
- Golang是基于多線程模型
- golang 一些概念
- Golang程序開發注意事項
- fatal error: all goroutines are asleep - deadlock
- defer
- Golang 的內建調試器
- go部署
- golang指針重要性
- 包(golang)
- Golang框架選型比較: goframe, beego, iris和gin
- GoFrame
- golang-admin-項目
- go module的使用方法及原理
- go-admin支持多框架的后臺系統(go-admin.cn)
- docker gocv
- go-fac
- MSYS2
- 企業開發框架系統推薦
- gorm
- go-zero
- 優秀系統
- GinSkeleton(gin web 及gin 知識)
- 一次 request -> response 的生命周期概述
- 路由與路由組以及gin源碼學習
- 中間件以及gin源碼學習
- golang項目部署
- 獨立部署golang
- 代理部署golang
- 容器部署golang
- golang交叉編譯
- goravel
- kardianos+gin 項目作為windows服務運行
- go env
- 適用在Windows、Linux和macOS環境下打包Go應用程序的詳細步驟和命令
- Redis
- Dochub
- Docker部署開發go環境
- Docker部署運行go環境
- dochub說明
- Vue
- i18n
- vue3
- vue3基本知識
- element-plus 表格單選
- vue3后臺模板
- Thinkphp
- Casbin權限控制中間件
- 容器、依賴注入、門面、事件、中間件
- tp6問答
- 偽靜態
- thinkphp-queue
- think-throttle
- thinkphp隊列queue的一些使用說明,queue:work和queue:listen的區別
- ThinkPHP6之模型事件的觸發條件
- thinkphp-swoole
- save、update、insert 的區別
- Socket
- workerman
- 介紹
- 從ThinkPHP6移植到Webman的一些技術和經驗(干貨)
- swoole
- swoole介紹
- hyperf
- hf官網
- Swoft
- swoft官網
- easyswoole
- easyswoole官網地址
- EASYSWOOLE 聊天室DEMO
- socket問答
- MySQL
- 聚簇索引與非聚簇索引
- Mysql使用max獲取最大值細節
- 主從復制
- 隨機生成20萬User表的數據
- MySQL進階-----前綴索引、單例與聯合索引
- PHP
- 面向切面編程AOP
- php是單線程的一定程度上也可以看成是“多線程”
- PHP 線程,進程、并發、并行 的理解
- excel數據畫表格圖片
- php第三方包
- monolog/monolog
- league/glide
- 博客-知識網站
- php 常用bc函數
- PHP知識點的應用場景
- AOP(面向切面編程)
- 注解
- 依賴注入
- 事件機制
- phpspreadsheet導出數據和圖片到excel
- Hyperf
- mineAdmin
- 微服務
- nacos注冊服務
- simps-mqtt連接客戶端simps
- Linux
- 切換php版本
- Vim
- Laravel
- RabbitMQ
- thinkphp+rabbitmq
- 博客
- Webman框架
- 框架注意問題
- 關于內存泄漏
- 移動端自動化
- 懶人精靈
- 工具應用
- render
- gitlab Sourcetree
- ssh-agent失敗 錯誤代碼-1
- 資源網站
- Git
- wkhtmltopdf
- MSYS2 介紹
- powershell curl 使用教程
- NSSM(windows服務工具)
- MinGW64
- 知識擴展
- 對象存儲系統
- minio
- 雪花ID
- 請求body參數類型
- GraphQL
- js 深拷貝
- window 共享 centos文件夾
- 前端get/post 請求 特殊符號 “+”傳參數問題
- 什么是SCM系統?SCM系統與ERP系統有什么區別?
- nginx 日志格式統一為 json
- 特殊符號怎么打
- 收藏網址
- 收藏-golang
- 收藏-vue3
- 收藏-php
- 收藏-node
- 收藏-前端
- 規劃ITEM
- 旅游類
- 人臉識別
- dlib
- Docker&&部署
- Docker-compose
- Docker的網絡模式
- rancher
- DHorse
- Elasticsearch
- es與kibana都docke連接
- 4種數據同步到Elasticsearch方案
- GPT
- 推薦系統
- fastposter海報生成
- elasticsearch+logstash+kibana
- beego文檔系統-MinDoc
- jeecg開源平臺
- Java
- 打包部署
- spring boot
- 依賴
- Maven 相關 命令
- Gradle 相關命令
- mybatis
- mybatis.plus
- spring boot 模板引擎
- SpringBoot+Maven多模塊項目(創建、依賴、打包可執行jar包部署測試)完整流程
- Spring Cloud
- Sentinel
- nacos
- Apollo
- java推薦項目
- gradle
- Maven
- Nexus倉庫管理器
- Python
- Masonite框架
- scrapy
- Python2的pip2
- Python3 安裝 pip3
- 安全攻防
- 運維技術
- 騰訊云安全加固建議
- 免費freessl證書申請
- ruby
- homeland
- Protobuf
- GIT
- FFMPEG
- 命令說明
- 音頻
- ffmpeg合并多個MP4視頻
- NODEJS
- 開發npm包
- MongoDB
- php-docker-mongodb環境搭建
- mongo基本命令
- Docker安裝MongoDB最新版并連接
- 少兒編程官網
- UI推薦
- MQTT
- PHP連接mqtt
- EMQX服務端
- php搭建mqtt服務端