借问变量何处存,牧童笑称用指针,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang类型指针(Pointer)的使用EP05
指针是指什么?指针是存储另一个变量的内存地址的变量。变量是一种使用方便的占位符,用于引用计算机内存地址,一个指针变量可以指向任何一个值的内存地址它指向那个值的内存地址。类比的话,指针就是书籍中的目录,本身也占据书页,既可以通过目录获得章节内容,又可以指向具体章节的页数(地址)。
指针声明
声明指针,*T是指针变量的类型,它指向T类型的值:
var var_name *var-type
var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。
例如:
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */
之前我们曾经使用&关键字来获取变量在内存中的地址,事实上,该对象就是指针:
package main
import "fmt"
func main() {
var a int = 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */
ip = &a /* 指针变量的存储地址 */
fmt.Printf("a 变量的地址是: %x\n", &a)
/* 指针变量的存储地址 */
fmt.Printf("ip 变量的存储地址: %x\n", ip)
/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip)
}
由此可见,指针变量的类型为 *Type,该指针指向一个 Type 类型的变量。指针变量最大的特点就是存储的某个实际变量的内存地址,通过记录某个变量的地址,从而间接的操作该变量。
& 关键字可以从一个变量中取到其内存地址。
* 关键字如果在赋值操作值的左边,指该指针指向的变量;* 关键字如果在赋值操作符的右边,指从一个指针变量中取得变量值,又称指针的解引用。
指针也分不同的类型:
package main
import "fmt"
func main() {
mystr := "字符串"
myint := 1
mybool := false
myfloat := 3.2
fmt.Printf("type of &mystr is :%T\n", &mystr)
fmt.Printf("type of &myint is :%T\n", &myint)
fmt.Printf("type of &mybool is :%T\n", &mybool)
fmt.Printf("type of &myfloat is :%T\n", &myfloat)
}
程序返回:
type of &mystr is :*string
type of &myint is :*int
type of &mybool is :*bool
type of &myfloat is :*float64
但其实,指针的类型,其实就是它所指的变量的基本类型,二者类型是一致的。
空指针
Go lang空指针是当一个指针被定义后没有分配到任何变量时,它的值为 nil。 nil 指针也称为空指针。 nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。 一个指针变量通常缩写为 ptr:
if(ptr != nil) /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */
具体例子:
package main
import "fmt"
func main() {
x := "字符串"
var ptr *string
fmt.Println("ptr is ", ptr)
ptr = &x
fmt.Println("ptr is ", ptr)
}
程序返回:
ptr is <nil>
ptr is 0xc00003c250
但也需要注意的是,指针的空值和变量的空值一样,都需要用恒等或者非恒等来判断,而并非像Python一样使用is关键字去比对内存的具体地址。
指针操作
获取一个指针意味着访问指针指向的变量的值。语法是:*a
package main
import (
"fmt"
)
func main() {
b := 255
a := &b
fmt.Println("address of b is", a)
fmt.Println("value of b is", *a)
}
程序返回:
address of b is 0xc000014088
value of b is 255
一般情况下,我们可以通过二次赋值来改变变量的值,现在通过指针也可以:
package main
import (
"fmt"
)
func main() {
b := 255
a := &b
fmt.Println("address of b is", a)
fmt.Println("value of b is", *a)
*a++
fmt.Println("new value of b is", b)
}
程序返回:
address of b is 0xc0000aa058
value of b is 255
new value of b is 256
这里值需要通过对指针进行递增操作,就可以修改变量b的值。
与此同时,在传参过程中,也可以使用指针:
package main
import (
"fmt"
)
func change(val *int) {
*val = 55
}
func main() {
a := 58
fmt.Println("value of a before function call is", a)
b := &a
change(b)
fmt.Println("value of a after function call is", a)
}
程序返回:
value of a before function call is 58
value of a after function call is 55
众所周知,int是值类型数据,如果通过变量进行传递到方法作用域中,方法内作用域内操作的实际上是另外一个对象,比如:
package main
import (
"fmt"
)
func change(val int) {
val = 55
}
func main() {
b := 58
change(b)
fmt.Println("value of a after function call is", b)
}
返回:
value of a after function call is 58
但如果传参过程中使用指针,将a变量的指针对象传递到方法内,方法内修改的其实是内存地址变量,如此就可以将值类型对象的值对应更改,节省了额外的内存申请空间。
假设我们想对方法内的数组进行一些修改,并且对调用者可以看到方法内的数组所做的更改。一种方法是将一个指向数组的指针传递给方法:
package main
import (
"fmt"
)
func modify(arr *[3]int) {
(*arr)[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(&a)
fmt.Println(a)
}
程序返回:
[90 90 91]
虽然可以用指针传递给一个数组作为方法的实参并对其进行修改,但这并不意味着我们一定得这么干,因为还可以使用切片:
package main
import (
"fmt"
)
func modify(sls []int) {
sls[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(a[:])
fmt.Println(a)
}
程序返回:
[90 90 91]
因为切片与指针一样是引用类型,如果我们想通过一个函数改变一个数组的值,可以将该数组的切片当作参数传给函数,也可以将这个数组的指针当作参数传给函数,显而易见,使用切片更加方便。
此外,指针也可以拥有指针,也就是说,指针也可以指向别的指针所在的内存地址:
package main
import "fmt"
func main() {
var a int
var ptr *int
var pptr **int
a = 3000
/* 指针 ptr 地址 */
ptr = &a
/* 指向指针 ptr 地址 */
pptr = &ptr
/* 获取 pptr 的值 */
fmt.Printf("变量 a = %d\n", a)
fmt.Printf("指针变量 *ptr = %d\n", *ptr)
fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)
}
程序返回:
变量 a = 3000
指针变量 *ptr = 3000
指向指针的指针变量 **pptr = 3000
这里指针pptr指向指针ptr,指针ptr执行变量a
当我们改变指针的指针值:
package main
import "fmt"
func main() {
var a int
var ptr *int
var pptr **int
a = 3000
/* 指针 ptr 地址 */
ptr = &a
/* 指向指针 ptr 地址 */
pptr = &ptr
/* 获取 pptr 的值 */
fmt.Printf("变量 a = %d\n", a)
fmt.Printf("指针变量 *ptr = %d\n", *ptr)
fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)
**pptr = 200
fmt.Printf("变量 a = %d\n", a)
fmt.Printf("指针变量 *ptr = %d\n", *ptr)
fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)
}
程序返回:
变量 a = 3000
指针变量 *ptr = 3000
指向指针的指针变量 **pptr = 3000
变量 a = 200
指针变量 *ptr = 200
指向指针的指针变量 **pptr = 200
可以看到发生了连锁反应,起始指向和最终指向都发生了变化,可谓是牵一发而动全身,事实上,指针操作要比重复赋值更加快捷。
结语
简而言之,很多编译型语言都在事实上存在指针,c/c++是真实的指针,而Java中其实是指针的引用,可以理解为不能操作指针的值,不允许指针运算的指针。现实问题是,go lang这种“次时代”的新潮流编程语言,为什么不像Java那样,仅仅实现“引用”,而一定非得给出“指针”的实质概念呢?
在Go lang官网文档中,可以窥得些许端倪:
In a function call, the function value and arguments are evaluated in the usual order. After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution.
文档地址:https://go.dev/ref/spec#Calls
一望而知,go lang的设计者们在go lang语法设计上存在“完美主义强迫症”,方法传参是绝对的传值,Go lang中方法传参只有值传递一种方式,不存在引用传递,这样一来,必须有明确的指针类型,才可以保证在传值的前提下能对对象进行修改。
其实 Python也在此处做出了妥协,可变数据类型进行引用传递,但go lang作为钢铁直男,宁愿增加更复杂的指针逻辑,也要彻底贯彻值传递逻辑,为的就是在适当的地方使用指针, 对程序运行速度和内存消耗有所增益。
借问变量何处存,牧童笑称用指针,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang类型指针(Pointer)的使用EP05的更多相关文章
- 用闭包解决 js 循环中函数变量暂存问题
需求:有一个数组,根据数组的值渲染对应的数字div,单击对应的div 在控制台打印对应的数字.如点击1,控制台打印1. 问题: 不管点击哪个值 打出来都是4 代码如下 <!DOCTYPE htm ...
- Python 零基础 快速入门 趣味教程 (咪博士 海龟绘图 turtle) 2. 变量
大家在中学就已经学过变量的概念了.例如:我们令 x = 100,则可以推出 x*2 = 200 试试下面这段 Python 代码 import turtle turtle.shape("tu ...
- PHP入门培训教程 PHP变量的使用
很多朋友在编写PHP程序的时候有时候对变量总有着不能确定的问题,而且也有很多问题就是因为变量的处理不当所造成的.这里兄弟连PHP培训 小编,就PHP变量系统说一下. PHP的变量分为全局变量与局部 ...
- Python学习入门基础教程(learning Python)--2.2 Python下的变量基础
变量的基本概念,变量可以这样去理解,变量是一个值,这个值存储在计算机的内存里.以网购为例,您在选购傻商品的时候,是在不同页面里选不同的商品,选好一件点击“放入购物车”,选完了再点击去结帐,这些商品的价 ...
- Python学习入门基础教程(learning Python)--2.2.1 Python下的变量解析
前文提及过变量代表内存里的某个数据,这个说法有根据么? 这里我们介绍一个python内建(built-in)函数id.我们先看看id函数的帮助文档吧.在python查某个函数的帮助文档很简单,只用he ...
- golang中值类型/指针类型的变量区别总结
转自:https://segmentfault.com/a/1190000012329213 值类型的变量和指针类型的变量 先声明一个结构体: type T struct { Name string ...
- C#图解教程读书笔记(第3章 类型、存储及变量)
1.C#的中的数值不具有bool特性. 2.dynamic在使用动态语言编写的程序集时使用,这个不太明白,看到后面需要补充!! 动态化的静态类型 3.对于引用类型,引用是存放在栈中,而数据是存放在堆里 ...
- PHP入门培训教程 PHP变量及常量
一.PHP5.4的基本语法格式 1.PHP的分割符 $php=true; //分号结束语句 if($php){ echo "真"; //分号结束语句 } //大括号结束语 ...
- 成员变量NSString类型指针的属性为什么用copy(属性)
创建一个分类Person设置属性@property(nonatomic,strong) NSString * name; 在- (void)viewDidLoad 中打印测试 name的属性是stro ...
- 你有对象类,我有结构体,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang结构体(struct)的使用EP06
再续前文,在面向对象层面,Python做到了超神:万物皆为对象,而Ruby,则干脆就是神:飞花摘叶皆可对象.二者都提供对象类操作以及继承的方式为面向对象张目,但Go lang显然有一些特立独行,因为它 ...
随机推荐
- 快速启动Stable Diffusion WebUI
快速启动Stable Diffusion WebUI详情 产品文档 输入文档关键字查找 机器学习PAI 产品概述 快速入门 操作指南 准备工作 开通PAI并创建默认工作空间 开通并授权依 ...
- vim vimtutor
=============================================================================== = 歡 迎 閱 ...
- 算法解析:LeetCode——机器人碰撞和最低票价
摘要:本文由葡萄城技术团队原创.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 机器人碰撞 问题: 现有 n 个机器人,编号从 1 开始,每个机器人包含在路 ...
- 基本操作:vscode快捷键
1.复制,剪切 补选中具体内容的话,光标放在这一行的任何位置,输入Ctrl+C,就表示已经复制这一行了:直接Ctrl+V可以粘贴: 截切也一样:光标放在这一行的任何位置,输入Ctrl+X,就表示已 ...
- 初探富文本之React实时预览
初探富文本之React实时预览 在前文中我们探讨了很多关于富文本引擎和协同的能力,在本文中我们更偏向具体的应用组件实现.在一些场景中比如组件库的文档编写时,我们希望能够有实时预览的能力,也就是用户可以 ...
- AI图形算法之一:液位计识别
AI人工智能的主要应用之一就是图形化处理和识别,之前写了两篇,分别是: AI图形算法的应用之一:通过图片模板对比发现油田漏油 AI图形算法的应用之一:仪表识别 经过几个晚上的辛苦,液位计识别也测试成功 ...
- 文心一言 VS 讯飞星火 VS chatgpt (120)-- 算法导论10.3 5题
五.用go语言,设 L 是一个长度为 n 的双向链表,存储于长度为 m 的数组key.prev 和next 中.假设这些数组由维护双链自由表 F的两个过程 ALLOCATE-OBJECT 和 FREE ...
- MVVM前后端分离:web接口规范
大前端前提下,开发采用前后端分离的方式,前端和后端主要通过接口进行分离, 后端开发接口,前端使用接口,前后端接口开发告一段落以后,接口联调差不多就进入开发尾声,准备送测了. 那么,对接口的约束和规范就 ...
- JUC并发编程学习笔记(三)生产者和消费者问题
生产者和消费者问题 synchronized版-> wait/notify juc版->Lock 面试:单例模式.排序算法.生产者和消费者.死锁 生产者和消费者问题 Synchronize ...
- 记录一次金仓V8R3数据库坏块处理过程、PostgreSQL数据库适用
因数广政务云华为业务存储固件升级,导致数据库产生坏块,业务SQL查询报错如下: ERROR: missing chunk number 0 for toast value 38166585 in SY ...