地址: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服务端