原文:https://www.jianshu.com/p/af42cb368cef

----------------------------------------------------

Go语言的指针与C或C++的指针类似,但是Go语言的指针不支持指针运算,这样就消除了在C或C++程序中一些潜在的问题。由于Go语言有自己的垃圾回收器,并且会自动管理内存,所以Go语言也不需要像C或C++一样使用free函数或者delete操作符。

Go语言的指针创建后可以像Java和Python中对象的引用一样使用。

在Go语言中,对于布尔变量或数值类型或字符串类型或数组都是按照值传递的:值在传递给函数或者方法时会被复制一份,然后方法或函数使用的是复制的这份值,也就不会对原值产生什么影响。一般情况下,对于布尔变量或数值类型或字符串类型的按值传递是非常廉价的,Go语言编译器会在传递过程中进行安全优化。

但是在Go语言中,字符串是不可变的,因此在进行修改字符串时(例如使用+=操作),Go语言必须创建一个新的字符串,然后复制原始的字符串并将其添加到新字符串之后,对于大字符串来说,操作的代价可能会比较大。

对于大字符串是这样,对于数组进行值传递也是如此。为了解决可能产生的巨大代价,Go语言使用数组切片来代替数组的使用。传递一个切片的代价跟传递字符串差不多,无论该切片的长度或容量是多大。对切片进行复制修改操作也不会像字符串那样需要创建新的切片,因为切片是可变的,属于引用类型。

func main() {
a := 3
b := 4
c := "abc"
d := [3]int{1,2,3}
fmt.Printf("main方法:a的值为 %v,b的值为 %v,c的值为 %v,d的值为 %v \n",a,b,c,d)
demo(a,b,c,d)
fmt.Printf("main方法:a的值为 %v,b的值为 %v,c的值为 %v,d的值为 %v \n",a,b,c,d)
} func demo(a,b int,c string,d [3]int) {
a = 5
b = 6
c = "efg"
d[0] = 0
fmt.Printf("demo函数:a的值为 %v,b的值为 %v,c的值为 %v,d的值为 %v\n",a,b,c,d)
}
----output----
main方法: a的值为 3,b的值为 4,c的值为 abc,d的值为 [1 2 3]
demo函数: a的值为 5,b的值为 6,c的值为 efg,d的值为 [0 2 3]
main方法: a的值为 3,b的值为 4,c的值为 abc,d的值为 [1 2 3]

Go语言中的引用类型有:映射(map),数组切片(slice),通道(channel),方法与函数。

由于Go语言存在垃圾回收器,因此在一个本地变量不再被使用时(不再被引用或者不在作用于范围)就会被垃圾回收器回收掉,这时本地变量的生命周期由它们的作用域决定。那如果我们想要管理本地变量的生命周期呢?这时就需要使用指针来管理本地变量,只要该变量至少存在一个指针,那么该变量的生命周期就可以独立于作用域。

使用指针能让我们控制变量的生命周期,不受作用域的影响,另外变量在传递过程中成本最小化,且可以轻易的修改变量的内容,而不是对复制的值进行操作。指针是一个变量,这个变量实际上是保存了另一个变量的内存地址,任何被指针保存了内存地址的变量都可以通过指针来修改内容。指针的传递非常廉价。

在使用指针前,我们需要明白两个操作符的含义
①操作符& : 当作二元操作符时,是按位与操作;当作一元操作符时,是返回该变量的内存地址。
②操作符* : 当作二元操作符时,是相乘的操作;当作一元操作符(解引用操作符)时,是返回该指针指向的变量的值,其实就是解除变量的指针引用,返回该变量的值。

指针的创建与使用,可以看下面的代码实例

func main() {
a := 3
p := &a //这里是获取变量a的内存地址,并将其赋值给变量p
fmt.Printf("a的值为 %v, a的指针是 %v ,p指向的变量的值为 %v\n",a,p,*p)
}
-----output-----
a的值为 3, a的指针是 0xc042060080 ,p指向的变量的值为 3

其实*p和变量a的值是相等的,两者可以交换着使用,两者都与同一块内存地址相关联,任意一个变量进行修改操作都会影响到另一个变量的值,但是若变量p被赋值其他变量的指针就不行了。

关于指针的综合运用,我们看以下的代码实例

func main() {
a := 3
b := 4
p1 := &a //获取变量a的内存地址,并将其赋值给变量p1
p2 := &b //获取变量b的内存地址,并将其赋值给变量p2
fmt.Printf("a的值为 %v, a的指针是 %v ,p1指向的变量的值为 %v\n",a,p1,*p1)
fmt.Printf("b的值为 %v, b的指针是 %v ,p2指向的变量的值为 %v\n",b,p2,*p2)
fmt.Println(demo(p1,p2))
fmt.Printf("a的值为 %v, a的指针是 %v ,p1指向的变量的值为 %v\n",a,p1,*p1)
fmt.Printf("b的值为 %v, b的指针是 %v ,p2指向的变量的值为 %v\n",b,p2,*p2)
} func demo(a,b *int)int {
*a = 5
*b = 6
return *a * *b //这里出现连续的两个*,Go编译器会根据上下文自动识别乘法与两个引用
}
-----output-----
a的值为 3, a的指针是 0xc042060080 ,p1指向的变量的值为 3
b的值为 4, b的指针是 0xc042060088 ,p2指向的变量的值为 4
30
a的值为 5, a的指针是 0xc042060080 ,p1指向的变量的值为 5
b的值为 6, b的指针是 0xc042060088 ,p2指向的变量的值为 6

根据上面代码,我们可以看到使用指针后,本来是值传递的整数类型在函数或方法中修改会影响到原变量的值。

空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。
nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
查看以下实例:

package main

import "fmt"
func main() {
var ptr *int
fmt.Printf("ptr 的值为 : %x\n", ptr )
}

以上实例输出结果为:

ptr 的值为 : 0

空指针判断:

if(ptr != nil)     /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */

多重间接引用
以上面代码为例, 在a := 3, p1 := &a中,p1是指向a的内存地址,这种叫做间接引用,若还有一个p2是指向p1的内存地址,p1指向a的内存地址,这种就叫做多重间接引用,不管哪种引用,若其中一个变量进行修改内容操作,均会影响到其他所有变量的内容。

func main() {
a := 3
p1 := &a //p1是指向变量a内存地址的指针
p2 := &p1 //p2是指向变量p1内存地址的指针
fmt.Printf("a:%v, p1:%v, *p1:%v, p2:%v, **p2:%v\n",a,p1,*p1,p2,**p2)
a = 4
fmt.Printf("a:%v, p1:%v, *p1:%v, p2:%v, **p2:%v\n",a,p1,*p1,p2,**p2)
}
-----output-----
a:3, p1:0xc0420080b8, *p1:3, p2:0xc042004028, **p2:3
a:4, p1:0xc0420080b8, *p1:4, p2:0xc042004028, **p2:4

new函数与&操作符
Go语言中提供两种创建变量的方式,同时可以获得指向它们的指针:new函数与&操作符。这两种的使用方式如下面代码所示

type Person struct {
name string
sex string
age int
}
func main() {
person1 := Person{"zhangsan","man",25} //创建一个person1对象
person2 := new(Person)//使用new创建一个person2对象,同时获得person的指针
person2.name,person2.sex,person2.age = "wangwu","man",25
person3 := &Person{"lisi","man",25}//使用&创建一个person3对象,同时获得person的指针
fmt.Printf("person1:%v, person2:%v, person3:%v\n",person1,person2,person3)
}
-----output-----
person1:{zhangsan man 25}, person2:&{wangwu man 25}, person3:&{lisi man 25}

从输出结果来看,new函数与&操作符两种方式区别不大,&操作符创建起来更加的简洁,并且随时可以指定属性初始值。Go语言打印指向person的指针时,会打印person属性的具体内容,并且在前缀上加上&表示该变量是一个指针。

接下来我们来传递一个结构体指针,并修改结构体的属性,看看Go语言是如何操作的。

type Person struct {
name string
sex string
age int
}
func main() {
person1 := Person{"zhangsan","man",25} //创建一个person1对象
fmt.Printf("person1:%v\n",person1)
demo(&person1)
fmt.Printf("person1:%v\n",person1)
} func demo(person *Person) {
(*person).age = 18 //显示的解引用
person.name = "GoLang" //隐式的解引用
}

main函数中创建一个pserson1对象,设置初始化属性后将它的指针传递到demo函数中,大家可以看到demo函数中有两种解引用(就是将一个指针转化为原对象)的方式,第一种: (*person).age是显示的解引用,第二种是使用 "." 操作符自动的将指针解引用,使用上这两种没什么区别,但第二种更简单。

作者:小杰的快乐时光
链接:https://www.jianshu.com/p/af42cb368cef
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

最新文章

  1. log4net在Asp.net Mvc中的应用配置与介绍
  2. Jquery easy UI 上中下三栏布局 分类: ASP.NET 2015-02-06 09:19 368人阅读 评论(0) 收藏
  3. CreateJSのeasel.js(一)
  4. Oracle创建表时涉及的参数解析
  5. POJ 1251 Jungle Roads
  6. easyui 1.3.3 中combotree post传参问题
  7. Action<T> 和 Func<T> 委托
  8. QT:窗口最小化时显示一个小浮标
  9. java学习笔记 --- 多态
  10. foreman容器化部署
  11. MTK平台-抓取蓝牙log
  12. ES6 4个方法
  13. 剑指Offer 4. 重建二叉树 (二叉树)
  14. js获取带#号链接后的参数
  15. Ruby基础教程
  16. 第14章 Linux账号管理与ACL权限设置
  17. css display属性详解
  18. jar包错误
  19. IE7不兼容slideDown()
  20. 【STSRM12】夏令营

热门文章

  1. 【转贴】内存重要参数详解 RAS CAS
  2. django fields lookup methods(lookup类型)
  3. 我的第一个python web开发框架(2)——第一个Hello World
  4. Microsoft SQL Server 2008 R2 Express and Management Studio Express
  5. 怎样在页面关闭时发起HTTP请求
  6. Api文档生成工具与Api文档的传播(pdf)
  7. 一个jenkins的bug
  8. js重点——作用域——作用域分类(三)
  9. docker启动 elasticsearch 修改 xmx xms 堆内存大小修改
  10. Linux学习(五)-Linux组管理和权限管理