Go 语言指针

简介: go语言指针基础Go 空指针Go 语言指针数组Go 语言指向指针的指针Go 语言指针作为函数参数

go语言指针基


获取变量内存地址


变量是一种使用方便的占位符,用于引用计算机内存地址(16进制)。

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。

package main
import "fmt"
func main() {
   var a int = 10  
   fmt.Printf("变量的地址: %x\n", &a  )
}
//输出结果:
变量的地址: 20818a220


什么是指针:


2387773-20220329133103413-1329069587.png


一个指针变量指向了一个值的内存地址。

类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:

var var_name *var-type


ar-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明

var ip *int        /* 指向整型*/

var fp *float32    /* 指向浮点型 */


如何使用指针


指针使用流程:


  • 定义指针变量。
  • 为指针变量赋值(赋予的是内存地址)。
  • 访问指针变量中指向地址的值。

在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。

package main
import "fmt"
//指针声明和赋值
func main() {
 var (
  //实际变量
  a int = 33
  //指针变量
  ip *int
 )
 //将a的地址赋值给指针变量ip
 ip = &a
 fmt.Println("a的值是", a)
 fmt.Println("a的地址是:", &a)
 fmt.Println("指针变量ip的值是:", ip)
 fmt.Println("指针ip 指向的值:", *ip)
}
//输出结果:
a的值是 33
a的地址是: 0xc000014098
指针变量ip的值是: 0xc000014098
指针ip 指向的值: 33


Go 空指针


当一个指针被定义后没有分配到任何变量时,它的值为 nil。

nil 指针也称为空指针。

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。

一个指针变量通常缩写为 ptr。

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 是空指针 */
package main
import "fmt"
func main() {
 var ptr *int
 if ptr == nil {
  fmt.Println("空指针")
 } else {
  fmt.Println("非空")
 }
}
//输出结果:
空指针


Go 语言指针数组


示例:

package main
import "fmt"
func main() {
 a := []int{10, 100, 200}
 for i := 0; i < len(a); i++ {
  fmt.Printf("a[%d] = %d\n", i, a[i])
 }
}
//输出结果:
a[0] = 10
a[1] = 100
a[2] = 200

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

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

var ptr [MAX]*int;



ptr 为整型指针数组。因此每个元素都指向了一个值。以下实例的三个整数将存储在指针数组中:


package main
import "fmt"
func main() {
 var ptr [3]*int // 声明一个长度为3的指针数组
 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(ptr); j++ {         
  fmt.Printf("a[%d] = %d\n", j, *ptr[j])
 }
}
//输出结果:
第0个元素的指针地址是:824633762152
第1个元素的指针地址是:824633762160
第2个元素的指针地址是:824633762168
a[0] = 10
a[1] = 100
a[2] = 200

 

Go 语言指向指针的指针


如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。

当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址:


2387753-20220329114423685-1669394882.png


指向指针的指针变量声明格式如下:

var ptr **int;



以上指向指针的指针变量为整型。

访问指向指针的指针变量值需要使用两个 * 号,如下所示:

指向指针的指针变量声明格式如下:
var ptr **int;
  
以上指向指针的指针变量为整型。
访问指向指针的指针变量值需要使用两个 * 号,如下所示:
package main
import "fmt"
//指向指针的指针
func main() {
  var (
    a    int   //变量
    ptr  *int  //指针
    pptr **int //指向指针的指针
  )
  // 变量赋值
  a = 1000
  //ptr赋值 a 的地址
  ptr = &a
  //pptr 赋值ptr的地址
  pptr = &ptr
  fmt.Println("a的值", a)
  fmt.Println("a的地址是", &a)
  //ptr=&a 指针变量ptr 存放的是变量a的内存地址
  fmt.Println("指针ptr的值是(ptr):", ptr)
  // *ptr= *&a  获取在变量a内存地址存储的值,即变量a的值
  fmt.Println("指针ptr指向的值(*ptr):", *ptr)
  // pptr = &ptr 获取指针变量 ptr 的内存地址,即指针变量在内存中的位置
  fmt.Println("指针pptr的值是(pptr):", pptr)
  // *pptr = *&ptr 获取存放在指针变量ptr内存地址的存储值(存储的是变量a的内存地址)
  fmt.Println("指针pptr指向的值(*pptr):", *pptr)
  //等效于*ptr,获取变量a的值
  fmt.Println("指针pptr指向 指针ptr指向的值(**pptr):", **pptr)
}
//输出结果:
a的值 1000
a的地址是 0xc000014098
指针ptr的值是(ptr): 0xc000014098
指针ptr指向的值(*ptr): 1000
指针pptr的值是(pptr): 0xc000006028
指针pptr指向的值(*pptr): 0xc000014098
指针pptr指向 指针ptr指向的值(**pptr): 1000
  
package main
import "fmt"
func main(){
    var a int = 5
    //把ptr指针 指向ss所在地址
    var ptr *int = &a
    //开辟一个新的指针,指向ptr指针指向的地方
    var pts *int = ptr 
    //二级指针,指向一个地址,这个地址存储的是一级指针的地址
    var pto **int = &ptr
    //三级指针,指向一个地址,这个地址存储的是二级指针的地址,二级指针同上
    var pt3 ***int = &pto
    fmt.Println("a的地址:",&a,
                "\n 值", a, "\n\n",
                "ptr指针所在地址:",&ptr,
                "\n ptr指向的地址:",ptr,
                "\n ptr指针指向地址对应的值",*ptr,"\n\n", 
                "pts指针所在地址:",&pts,
                "\n pts指向的地址:", pts,
                "\n pts指针指向地址对应的值:",*pts,"\n\n", 
                "pto指针所在地址:",&pto,
                "\n pto指向的指针(ptr)的存储地址:",pto, 
                "\n pto指向的指针(ptr)所指向的地址: " ,*pto, 
                "\n pto最终指向的地址对应的值(a)",**pto,"\n\n",
                "pt3指针所在的地址:",&pt3,
                "\n pt3指向的指针(pto)的地址:",pt3,//等于&*pt3,
                "\n pt3指向的指针(pto)所指向的指针的(ptr)地址", *pt3, //等于&**pt3,
                "\n pt3指向的指针(pto)所指向的指针(ptr)所指向的地址(a):",**pt3, //等于&***pt3,
                "\n pt3最终指向的地址对应的值(a)", ***pt3)
}
//输出结果:
a的地址: 0xc00009a008 
 值 5 
 ptr指针所在地址: 0xc000092010 
 ptr指向的地址: 0xc00009a008 
 ptr指针指向地址对应的值 5 
 pts指针所在地址: 0xc000092018 
 pts指向的地址: 0xc00009a008 
 pts指针指向地址对应的值: 5 
 pto指针所在地址: 0xc000092020 
 pto指向的指针(ptr)的存储地址: 0xc000092010 
 pto指向的指针(ptr)所指向的地址:  0xc00009a008 
 pto最终指向的地址对应的值(a) 5 
 pt3指针所在的地址: 0xc000092028 
 pt3指向的指针(pto)的地址: 0xc000092020 
 pt3指向的指针(pto)所指向的指针的(ptr)地址 0xc000092010 
 pt3指向的指针(pto)所指向的指针(ptr)所指向的地址(a): 0xc00009a008 
 pt3最终指向的地址对应的值(a) 5
  
Go 语言指针作为函数参数
Go 语言允许向函数传递指针,只需要在函数定义的参数上设置为指针类型即可。
指针属于引用类型。
以下实例演示了如何向函数传递指针,并在函数调用后修改函数内的值,:
package main
import "fmt"
func main() {
  var (
    a int = 10
    b int = 20
  )
  fmt.Printf("交换前a=%d,b=%d\n", a, b)
  //调用函数,将指针作为参数传递
  swap(&a, &b)
  fmt.Printf("交换后a=%d,b=%d\n", a, b)
}
//定义两个形参,类型是指针(&a,&b作为参数传递进入,因此,*x和&a是与同一个内存地址关联。*y与&b与同一个内存地址关联)
func swap(x, y *int) {
  *x, *y = *y, *x
}
//输出结果:
交换前a=10,b=20
交换后a=20,b=10
  
package main
import "fmt"
//实际值的传递
func main() {
  var (
    a int = 10
    b int = 20
  )
  fmt.Printf("交换前a=%d,b=%d\n", a, b)
  //将变量a,b作为参数传递,并使用x,y接收函数的返回值
  x, y := swap(a, b)
  fmt.Printf("交换后a=%d,b=%d\n", x, y)
}
func swap(x, y int) (a, b int) {
  //值传递,两数交换.
  x, y = y, x
  fmt.Printf("swap里x=%d,y=%d\n", x, y)
  //需要将交换后的结果,作为返回值传递
  return x, y
}
//输出结果:
交换前a=10,b=20
swap里x=20,y=10
交换后a=20,b=10


Go 语言指针作为函数参数


Go 语言允许向函数传递指针,只需要在函数定义的参数上设置为指针类型即可。

指针属于引用类型。

以下实例演示了如何向函数传递指针,并在函数调用后修改函数内的值,:

package main
import "fmt"
func main() {
 var (
  a int = 10
  b int = 20
 )
 fmt.Printf("交换前a=%d,b=%d\n", a, b)
  //调用函数,将指针作为参数传递
 swap(&a, &b)
 fmt.Printf("交换后a=%d,b=%d\n", a, b)
}
//定义两个形参,类型是指针(&a,&b作为参数传递进入,因此,*x和&a是与同一个内存地址关联。*y与&b与同一个内存地址关联)
func swap(x, y *int) {
 *x, *y = *y, *x
}
//输出结果:
交换前a=10,b=20
交换后a=20,b=10
package main
import "fmt"
//实际值的传递
func main() {
 var (
  a int = 10
  b int = 20
 )
 fmt.Printf("交换前a=%d,b=%d\n", a, b)
  //将变量a,b作为参数传递,并使用x,y接收函数的返回值
 x, y := swap(a, b)
 fmt.Printf("交换后a=%d,b=%d\n", x, y)
}
func swap(x, y int) (a, b int) {
 //值传递,两数交换.
 x, y = y, x
 fmt.Printf("swap里x=%d,y=%d\n", x, y)
  //需要将交换后的结果,作为返回值传递
 return x, y
}
//输出结果:
交换前a=10,b=20
swap里x=20,y=10
交换后a=20,b=10
相关文章
|
5天前
|
JSON 测试技术 Go
零值在go语言和初始化数据
【7月更文挑战第10天】本文介绍在Go语言中如何初始化数据,未初始化的变量会有对应的零值:bool为`false`,int为`0`,byte和string为空,pointer、function、interface及channel为`nil`,slice和map也为`nil`。。本文档作为指南,帮助理解Go的数据结构和正确使用它们。
53 22
零值在go语言和初始化数据
|
5天前
|
JSON Java Go
Go 语言性能优化技巧
在Go语言中优化性能涉及数字字符串转换(如用`strconv.Itoa()`代替`fmt.Sprintf()`)、避免不必要的字符串到字节切片转换、预分配切片容量、使用`strings.Builder`拼接、有效利用并发(`goroutine`和`sync.WaitGroup`)、减少内存分配、对象重用(`sync.Pool`)、无锁编程、I/O缓冲、正则预编译和选择高效的序列化方法。这些策略能显著提升代码执行效率和系统资源利用率。
42 13
|
1天前
|
Cloud Native Java Go
为什么要学习Go语言?
GO logo的核心理念,即简单胜于复杂。使用现代斜体无衬线字体与三条简单的运动线相结合,形成一个类似于快速运动的两个轮子的标记,传达速度和效率。字母的圆形暗示了GO地鼠的眼睛,创造了一个熟悉的形状,让标记和吉祥物很好地搭配在一起。
12 4
|
5天前
|
设计模式 Go
Go语言设计模式:使用Option模式简化类的初始化
在Go语言中,面对构造函数参数过多导致的复杂性问题,可以采用Option模式。Option模式通过函数选项提供灵活的配置,增强了构造函数的可读性和可扩展性。以`Foo`为例,通过定义如`WithName`、`WithAge`、`WithDB`等设置器函数,调用者可以选择性地传递所需参数,避免了记忆参数顺序和类型。这种模式提升了代码的维护性和灵活性,特别是在处理多配置场景时。
41 8
|
5天前
|
存储 Go
go语言中fmt格式化包和内置函数汇总
【7月更文挑战第10天】本文介绍fmt包和`Errorf`用于创建格式化的错误消息。`fmt`包还涉及一些接口,如`Formatter`、`GoStringer`、`ScanState`、`Scanner`和`Stringer`,支持自定义格式化和输入/输出处理。
17 1
|
5天前
|
Go
go语言中格式化输出的占位符
【7月更文挑战第10天】`fmt` 包在 Go 语言中用于格式化输出,包括不同类型的占位符:%v(默认格式)、%+v(带字段名的结构体)、%#v(Go语法表示)、%T(类型表示)、%%(百分号)。布尔值用%t,整数有%b、%c、%d、%o、%q、%x、%X和%U。浮点数和复数用%b、%e、%E、%f、%g、%G。字符串和字节切片用%s、%q、%x、%X。指针用%p。占位符可配合+、-、#、空格和0进行调整。宽度和精度控制输出格式,例如 %.4g 控制小数精度。Go 没有 `%u`,但无符号整数默认打印为正数。运算符包括逻辑、比较、加减、乘除、移位、按位和按位异或等。
17 1
|
7天前
|
存储 Go 索引
在go语言中自定义泛型的变长参数
【7月更文挑战第8天】在Go语言中,由于官方1.18以前的版本不支持泛型,可以通过空接口和反射模拟泛型。泛型适用于通用数据结构和函数,虽牺牲了一些性能,但提高了代码复用和类型安全性。
41 1
|
3天前
|
安全 Go
Go语言map并发安全,互斥锁和读写锁谁更优?
Go并发编程中,`sync.Mutex`提供独占访问,适合读写操作均衡或写操作频繁的场景;`sync.RWMutex`允许多个读取者并行,适用于读多写少的情况。明智选择锁可提升程序性能和稳定性。示例展示了如何在操作map时使用这两种锁。
6 0
|
3天前
|
安全 Go 开发者
Go语言map并发安全使用的正确姿势
在Go并发编程中,由于普通map不是线程安全的,多goroutine访问可能导致数据竞态。为保证安全,可使用`sync.Mutex`封装map或使用从Go 1.9开始提供的`sync.Map`。前者通过加锁手动同步,后者内置并发控制,适用于多goroutine共享。选择哪种取决于具体场景和性能需求。
6 0
|
3天前
|
存储 安全 Java
Go语言中的map为什么默认不是并发安全的?
Go语言的map默认不保证并发安全,以优化性能和简洁性。官方建议在需要时使用`sync.Mutex`保证安全。从Go 1.6起,并发读写map会导致程序崩溃,鼓励开发者显式处理并发问题。这样做的哲学是让代码更清晰,并避免不必要的性能开销。
4 0