go语言的指针类型
一、指针与引用的相关概念
什么是指针?
指针,全称为指针变量,是用来存储内存地址的一种变量。程序中,一般通过指针来访问其指向的内存地址中的内容(数据)。
什么是引用?
引用,是C++中提出来的一种新的使用变量的方式,即,给实际变量起个别名,通过这个别名来引用实际的变量。标准C中不支持变量的引用。
指针与引用的区别?
1.指针是实实在在的变量,有自己的内存存储空间,它可以指向任何有效的变量。
2.引用是一种形式、方法,定义的引用变量,实际上是原实际变量的另一个名称(别名),引用变量本身没有自己的实际存储空间,操作引用变量,就是在操作实际变量。
go语言的指针与c语言指针,以及java,python等引用类型的语言的区别与联系:
1.Java,Python,Javascript 等引用类型的语言
2.Golang 的指针是单独的类型,而不是 C 语言中的 int 类型,而且也不能对指针做整数运算。
3.从这一点看,Golang 的指针基本就是一种引用。
对引用类型语言来说,参数传递时,什么时候传值,什么时候传引用?
1.在大部分引用型语言里,参数为基本类型时,传进去的大都是值,也就是另外复制了一份参数到当前的函数调用栈。
2.参数为高级类型时,传进去的基本都是引用。这个主要是因为虚拟机的内存管理导致的。
传值和传引用的区别?
1.内存管理中的内存区域一般包括 heap 和 stack, stack 主要用来存储当前调用栈用到的简单类型数据:string,boolean,int,float 等。
2.这些类型的内存占用小,容易回收,基本上它们的值和指针占用的空间差不多,因此可以直接复制,GC也比较容易做针对性的优化。
3.复杂的高级类型占用的内存往往相对较大,存储在 heap 中,GC 回收频率相对较低,代价也较大,因此传引用/指针可以避免进行成本较高的复制操作,并且节省内存,提高程序运行效率。
而在 Golang 中,具体到高级类型 struct,slice,map,也各有不同。实际上,只有 struct 的使用有点复杂,slice,map,chan 都可以直接使用,不用考虑是值还是指针。
二、go语言指针介绍
go的原生数据类型分类:
1.基本类型:string, bool, int, float等 使用值传递
2.高级类型:sturuct, array/slice, map, chan, func。 使用引用传递
go的指针定义:
a.一个指针的值是另一个变量的地址。
b.一个指针对应变量在内存中的存储位置。
c.并不是每一个值都会有一个内存地址。
d.灭一个变量必然有对应的内存地址。
e.用过指针我们可以直接读或者更新对应变量的值,而不需要知道该变量的名字。
go语言中,什么时候使用指针?(指针使用的场景):
1.需要改变参数的值
2.避免复制操作
3.节省内存
go语言的值传递和“引用传递”
1.go语言中的传递方式只有值传递
2.所谓的“引用传递”是指将要传递的较大的值,复制他的内存地址然后将内存地址通过值传递传给对象。对象拿到内存地址的值就可以直接访问了。这个过程看起来很像“引用传递”
指针(pointer)在go 语言中拆分为两个核心概念:
1.类型指针,允许对这个指针类型的数据进行修改。传递数据使用指针,而无需拷贝数据。类型指针不能进行偏移量和运算。
2.切片,由指向起始元素的原始指针、元素数量和容量组成。
go语言指针的优势:
1.Go 语言的指针类型变量拥有指针的高效访问
2.不会发生指针偏移,从而避免非法修改关键性数据问题。
3.垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。
4.切片比原始指针具备更强大的特性,更为安全。
5.切片发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。
指针类型和指针地址:
1.每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置
2.Go 语言中使用 & 作符放在变量前面对变量进行“取地址”操作。
ptr := &v // v的类型为T
v 代表被取地址的变量
变量prt 接收 被取地址的 v
ptr 的类型就为*T
,称做 T 的指针类型。*
代表指针。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "fmt" ) func main() { //声明整型 cat 变量。 var cat int = 1 //声明字符串 str 变量。 var str string = "banana" //使用 fmt.Printf 的动词%p输出 cat 和 str 变量取地址后的指针值,指针值带有0x的十六进制前缀。 fmt.Printf("%p %p", &cat, &str) } //运行结果 //0xc042052088 0xc0420461b0
变量、指针、地址,三者的关系?
1.每个变量都拥有地址
2.指针的值就是地址
如何使用指针获取指针指向的值?
在对普通变量使用&
操作符取地址获得这个变量的指针后,可以对指针使用*
操作,也就是指针取值
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import ( "fmt" ) func main() { // 准备一个字符串类型 var house = "Malibu Point 10880, 90265" // 对字符串取地址, 将指针保存到 ptr 中,ptr类型为*string ptr := &house // 打印ptr的类型 类型为 *string。 fmt.Printf("ptr type: %T\n", ptr) // 打印ptr的指针地址 每次运行都会发生变化。 fmt.Printf("address: %p\n", ptr) // 对 ptr 指针变量进行取值操作 value 变量类型为 string。 value := *ptr // 取值后的类型 fmt.Printf("value type: %T\n", value) // 指针取值后就是指向变量的值 fmt.Printf("value: %s\n", value) } //运行结果 //ptr type: *string //address: 0xc0420401b0 //value type: string //value: Malibu Point 10880, 90265
取地址操作符&
和取值操作符*
是一对互补操作符
&
取出地址,*
根据地址取出地址指向的值。
变量、指针地址、指针变量、取地址、取值的相互关系和特性:
1.对变量进行取地址(&)操作,可以获得这个变量的指针变量。
2.指针变量的值是指针地址。
3.对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。
指针的零值和相等测试
1.任何类型的指针的零值都是nil。
2.如果 p != nil 为真,那么p就指向某个有效变量 (p为指针)
3.指针只有指向同一个变量或者全部为nil时才相等。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
var x,y int var z *int var w *string fmt.Println(&x ==&x, &x ==&y,&x ==nil , z == nil , w ==nil) //true false false true true
如何使用指针修改值?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import "fmt" // 定义一个交换函数,参数为 a、b,类型都为 *int,都是指针类型。 func swap(a, b *int) { // 将 a 指针取值,把值(int类型)赋给 t 变量,t 此时也是 int 类型。 t := *a // 取 b 指针值,赋给 a 变量指向的变量。注意,此时*a的意思不是取 a 指针的值,而是“a指向的变量”。 *a = *b // 将 t 的值赋给 b 指向的变量。 *b = t } func main() { // 准备 x、y 两个变量,赋值 1 和 2,类型为 int。 x, y := 1, 2 // 取出 x 和 y 的地址作为参数传给 swap() 函数进行调用。 swap(&x, &y) // 交换完毕时,输出 x 和 y 的值。 fmt.Println(x, y) } //运行结果 //2 1
1.*
操作符作为右值时,意义是取指针的值
2.作为左值时,也就是放在赋值操作符的左边时,表示 a 指向的变量。
归纳:
1.*
操作符的根本意义就是操作指针指向的变量。
2.当操作在右值时,就是取指向变量的值;
3.当操作在左值时,就是将值设置给指向的变量。
每次我们对一个变量取地址,或者是复制指针,我们都是为原变量创建新的别名。例如a就是变量p的别名。
指针特别有价值的地方在于我们可以不用名字而访问一个变量的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import "fmt" func swap(a, b *int) { b, a = a, b } func main() { x, y := 1, 2 swap(&x, &y) fmt.Println(x, y) } //运行结果 //1 2
1.上面代码中的 swap() 函数交换的是 a 和 b 的地址
2.在交换完毕后,a 和 b 的变量值确实被交换。
3.但和 a、b 关联的两个变量并没有实际关联。
4.这就像写有两座房子的卡片放在桌上一字摊开,交换两座房子的卡片后并不会对两座房子有任何影响。
创建指针的另一种方法--new()函数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package main import "fmt" func main() { //申请一个指针str,类型为*string str := new(string) //指针str指向字符串“ninja” *str = "ninja" //打印str指向的地址空间 fmt.Println(*str) //打印指针str的内存地址 fmt.Printf("%p\n",str) fmt.Printf("%d\n",str) //打印指针str的类型 fmt.Printf("%T\n",str) } //运行结果 //ninja //0xc00000e1e0 //824633778656 //*string