go语言指针

一、认识指针

指针是一个变量,其值是另一个变量的内存地址,即存储器位置的直接地址。类似变量或常量一样,必须要先声明一个指针,然后才能使用它来存储任何变量地址。

1.1、C/C++ 中的指针

指针是 C/C++ 语言拥有极高性能的根本所在,在操作大块数据和做偏移时既方便又便捷。因此,操作系统依然使用C语言及指针的特性进行编写。

C/C++ 中指针饱受诟病的根本原因是指针的运算和内存释放,C/C++ 语言中的裸指针可以自由偏移,甚至可以在某些情况下偏移进入操作系统的核心区域,我们的计算机操作系统经常需要更新、修复漏洞的本质,就是为解决指针越界访问所导致的"缓冲区溢出"的问题。

1.2、go语言指针

Go语言对指针的支持介于Java语言和 C/C++ 语言之间, 它既没有像Java那样取消了代码对指针的直接操作的能力, 也避免了 C/C++ 中由于对指针的滥用而造成的安全和可靠性问题.

指针(pointer)在Go语言中可以被拆分为两个核心概念:

  • 类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
  • 切片,由指向起始元素的原始指针、元素数量和容量组成。

受益于这样的约束和拆分,Go语言的指针类型变量既拥有指针高效访问的特点,又不会发生指针偏移,从而避免了非法修改关键性数据的问题。同时,垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。

切片比原始指针具备更强大的特性,而且更为安全。切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。

二、go语言指针特性

2.1、指针地址和变量空间

一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。当一个指针被定义后没有分配到任何变量时,它的默认值为 nil。指针变量通常缩写为 ptr。
Go语言保留了指针, 但是与C语言指针有所不同. 主要体现在:

  • 默认值: nil
  • 操作符 & 取变量地址, * 通过指针访问目标对象
  • 不支持指针运算, 不支持 -> 运算符, 直接用 . 访问目标成员
package main

import "fmt"

func main(){
    var s string = "hello"
    var p *string = &s

    fmt.Println(p)
}

当我们运行到 var s string = "hello" 时, 在内存中就会生成一个空间, 这个空间我们给它起了个名字叫 s, 同时, 它也有一个地址, 例如: 0xc000010230. 当我们想要使用这个空间时, 我们可以用地址去访问,也可以用我们给它起的名字 s 去访问.

继续运行到 p *string = &s 时, 我们定义了一个指针变量 p , 这个 p 就存储了变量 s 的地址。所以, 指针就是地址, 指针变量就是存储地址的变量

改变s 和*p:

package main

import "fmt"

func main(){
    var s string = "hello"
    var p *string = &s

    fmt.Println(p)

    s = "hello world"

    fmt.Println("s: ", s)
    fmt.Println("*p: ", *p)

    *p = "hello world!"

    fmt.Println("s: ", s)
    fmt.Println("*p: ", *p)
}

// 结果为
0xc000096210
s:  hello world
*p:  hello world
s:  hello world!
*p:  hello world!

可以发现, s 与 *p 的结果一样的。其中, *p 称为 解引用 或者 间接引用*p = "hello world!" 是通过借助 s 变量的地址, 来操作 s 对应的空间,不管是 s 还是 *p , 操作的都是同一个空间.

2.2、从指针获取指针指向的值

当使用&操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用*操作符,也就是指针取值:

package main

import "fmt"

func main() {
    // 准备一个字符串类型
    var school = "heyang middle school"
    // 对字符串取地址, ptr类型为*string
    ptr := &school
    // 打印ptr的类型
    fmt.Printf("ptr type: %T\n", ptr)
    // 打印ptr的指针地址
    fmt.Printf("pointer address: %p\n", ptr)
    // 对指针进行取值操作
    pointer_value := *ptr
    // 取值后的类型
    fmt.Printf("pointer_value type: %T\n", pointer_value)
    // 指针取值后就是指向变量的值
    fmt.Printf("pointer_value: %s\n", pointer_value)
}

// 结果为
ptr type: *string
pointer address: 0xc000096210
pointer_value type: string
pointer_value: heyang middle school

取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。

变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

  • 对变量进行取地址操作使用&操作符,可以获得这个变量的指针变量。
  • 指针变量的值是指针地址。
  • 对指针变量进行取值操作使用*操作符,可以获得指针变量指向的原变量的值。

2.3、使用指针修改值

如下:

package main

import "fmt"

// 交换函数
func swap(a, b *int) {
    // 取a指针的值, 赋给临时变量t
    t := *a
    // 取b指针的值, 赋给a指针指向的变量
    *a = *b
    // 将a指针的值赋给b指针指向的变量
    *b = t
}
func main() {
// 准备两个变量, 赋值1和2
    x, y := 1, 2
    // 交换变量值
    swap(&x, &y)
    // 输出变量值
    fmt.Println(x, y)
}

// 结果为
2 1

*操作符作为右值时,意义是取指针的值,作为左值时,也就是放在赋值操作符的左边时,表示 a 指针指向的变量。其实归纳起来,*操作符的根本意义就是操作指针指向的变量。当操作在右值时,就是取指向变量的值,当操作在左值时,就是将值设置给指向的变量。

2.4、空指针

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

package main

import "fmt"

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

// 结果为
ptr 的值为 : <nil>
ptr 的值为 : (*int)(nil)

空指针判断:

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

野指针:被一片无效的地址空间初始化.

func main() {
   var p *int = 0xc00000a0c8
   fmt.Printf("p 的值为 : %v\n", p  )
   fmt.Printf("p 的值为 : %#v\n", p  )
}

// 结果为
cannot use 824633761992 (type int) as type *int in assignment

2.5、new()创建指针

表达式 new(T) 将创建一个 T 类型的匿名变量, 所做的是为 T 类型的新值分配并清零一块内存空间, 然后将这块内存空间的地址作为结果返回, 而这个结果就是指向这个新的 T 类型值的指针值, 返回的指针类型为 *T.
new() 创建的内存空间位于heap上, 空间的默认值为数据类型的默认值. 如: p := new(int) 则 *p 为 0,这时 p 就不再是空指针或者野指针.

func main(){
    p := new(int)
    fmt.Println(p)
    fmt.Println(*p)
}

// 结果为
0xc000016070
0

2.6、向函数传递指针参数

传地址(引用): 将地址值作为函数参数传递.

传值(数据): 将实参的值拷贝一份给形参.

无论是传地址还是传值, 都是实参将自己的值拷贝一份给形参.只不过这个值有可能是地址, 有可能是数据.所以, 函数传参永远都是值传递.

func modify1(x int) {
	x = 100
}

func modify2(x *int) {
	*x = 100
}

func main() {
	a := 10
	modify1(a)       //修改失败
	fmt.Println(a)
	modify2(&a)      //修改成功
	fmt.Println(a)
}

// 结果为
10
100

2.7、指针数组

在我们了解指针数组前,先看个实例,定义了长度为 3 的整型数组:

package main

import "fmt"

const MAX int = 3

func main() {

   a := []int{10,100,200}
   fmt.Println(a)
   var i int

   for i = 0; i < MAX; i++ {
      fmt.Printf("a[%d] = %d\n", i, a[i] )
   }
}
// 结果为:
[10 100 200]
a[0] = 10
a[1] = 100
a[2] = 200

有一种情况,我们可能需要保存数组,这样我们就需要使用到指针。

以下声明了整型指针数组:

const MAX int = 3

func main() {
   a := []int{10,100,200}
   var i int
   var ptr [MAX]*int

   for  i = 0; i < MAX; i++ {
      ptr[i] = &a[i] /* 整数地址赋值给指针数组 */
   }

   for  i = 0; i < MAX; i++ {
      fmt.Printf("a[%d] = %d\n", i,*ptr[i] )
   }
}
// 结果为:
a[0] = 10
a[1] = 100
a[2] = 200

三、参考文档

1、https://blog.csdn.net/qq_39941141/article/details/125741462
2、http://c.biancheng.net/view/21.html
3、https://www.cnblogs.com/cheyunhua/p/16652003.html
4、https://blog.csdn.net/weixin_44211968/article/details/121343717

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言中,指针变量和指针地址的概念与其他编程语言类似。指针变量是存储内存地址的变量,而指针地址是指针变量所指向的内存地址。 以下是关于Go语言指针变量和指针地址的介绍和演示: 1. 声明指针变量和指针地址: ```go var var_name *var-type ``` 其中,`var_name`是指针变量名,`*var-type`是指针类型,`*`号用于指定变量是作为一个指针。 2. 指针变量的使用: ```go package main import "fmt" func main() { var a int = 10 var ptr *int // 声明指针变量 ptr = &a // 将变量a的地址赋值给指针变量ptr fmt.Printf("a的值:%d\n", a) // 输出:10 fmt.Printf("a的地址:%d\n", &a) // 输出:变量a的地址 fmt.Printf("ptr的值:%d\n", *ptr) // 输出:指针变量ptr所指向的值 fmt.Printf("ptr的地址:%d\n", &ptr) // 输出:指针变量ptr的地址 } ``` 运行结果为: ``` a的值:10 a的地址:变量a的地址 ptr的值:10 ptr的地址:指针变量ptr的地址 ``` 3. 指针数组和指向指针指针: ```go package main import "fmt" func main() { var ptr [3]*int // 声明指针数组 a := [...]int{10, 100, 200} // 实际数组 for i := 0; i < len(a); i++ { ptr[i] = &a[i] // 地址赋值给指针数组 fmt.Printf("第%d个元素的指针地址:%d\n", i, &a[i]) } for j := 0; j < len(a); j++ { fmt.Printf("a[%d] = %d\n", j, *ptr[j]) // 使用指针变量值指向值进行遍历 } } ``` 运行结果为: ``` 第0个元素的指针地址:变量a[0]的地址 第1个元素的指针地址:变量a[1]的地址 第2个元素的指针地址:变量a[2]的地址 a[0] = 10 a[1] = 100 a[2] = 200 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值