zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Go 编程 | 连载 14 - 指针 Pointer

Go编程 指针 14 连载 Pointer
2023-06-13 09:13:28 时间

一、指针

指针的概念以及定义

一个变量交换的例子

交换两个变量的值在排序过程中是一个高频操作,交换变量值最直接的方式就是通过一个临时变量来实现交换,在 Go 中可以这么来实现:

func main() {

   a := 10
   b := 20
   switchVal(a, b)
   
   fmt.Println(a, b)

}

// 定义一个函数:交换两个变量的值
func switchVal(a int, b int) {

   temp := a
   a = b
   b = temp
}

执行上述代码,输出结果如下:

10 20

根据结果来看并没有成功的实现交换两个变量的值,为什么?因为整型是值类型,作为函数参数时会将拷贝非副本作为实际的参数。

那么如何修改呢?

func main() {

   a := 10
   b := 20
   switchVal(&a, &b)

   fmt.Println(a, b)

}

// 交换两个变量的值
func switchVal(a *int, b *int) {

   temp := *a
   *a = *b
   *b = temp
}

执行上述代码,输出结果如下:

20 10

根据输出结果可以确定,a 和 b 两个变量的变量值交换成功。

指针

上述代码中的 & 在之前的文章有提到过,表示获取变量的内存地址,那么 switchVal 函数中的 *int 又表示什么?

*int 表示指针类型,指针是一种数据类型,指针中存储的是内存地址,而这个内存地址指向一块具体的内存,这块具体的内存中保存着个中类型的数据。

指针类型包含两个部分,第一个是 * 符号,表示是指针类型,第二个组成部分是基本数据类型标识符,表示指针所执行的内存地址中存储的数据类型。

指针变量与普通变量不同的是,普通变量存储的直接就是变量值,而指针存储的是变量的内存地址。

func main() {

   var yankee int = 200

   // 定义指针
   var zulu *int = &yankee

   fmt.Printf("%T, %v, %v\n", yankee, yankee, &yankee)
   fmt.Printf("%T, %v, %v\n", zulu, zulu, *zulu)

}

执行上述代码,输出结果如下:

int, 200, 0xc0000b2008
*int, 0xc0000b2008, 200

在普通变量前添加 & 就可以获得该变量的内存地址,在指针变量前添加 * 就可以获取该指针变量中的内存地址所指向的值。如果要修改指针变量内存地址指向的值只需要 *指针变量=newVal 即可。

当然定义指针变量时也可以省略 var 关键字,直接使用 := 来定义。

func main() {

   xray := 1.0
   whiskey := "Go"
   victor := []string{"Stark", "Thor", "Steve"}
   uniform := [...]int{1, 3, 5, 7, 9}
   tango := map[string]string{
      "code":   "Tango 6",
      "number": "6",
   }

   tangoCode := tango["code"]

   fmt.Printf("%T, %v\n", &xray, &xray)
   fmt.Printf("%T, %v\n", &whiskey, &whiskey)
   fmt.Printf("%T, %v\n", &victor[0], &victor[0])
   fmt.Printf("%T, %v\n", &uniform[0], &uniform[0])
   fmt.Printf("%T, %v\n", &tangoCode, &tangoCode)
}

执行上述代码,输出结果如下:

*float64, 0xc000132008
*string, 0xc000116210
*string, 0xc000118180
*int, 0xc00012a060
*string, 0xc000116220

Go 的指针 和 C 语言的指针的区别

C 和 C++ 都提供了指针而且功能强大,可以进行指针的转换、偏移以及运算等,其他静态类型语言如 Java 和动态类型语言如 Python 则是将指针屏蔽,不提供指针的概念以及运算等。

Go 中的指针相比 C 和 C++ 中的指针做了很多的限制,有更高的安全性,不涉及到指针的运算、转换和偏移等功能。

make 函数和 new 函数

先来定义一个指针变量,给指针变量的内存地址赋值,并输出其中保存在内存地址中的值

func main() {

   var p *int
   *p = 10

   fmt.Println(*p)
}

执行上述代码,输出结果如下:

这里为什么会报错呢?这里错误信息提示无效的内存地址或者空的指针指向,也就是说没有指定的内存地址来保存 10。

要初始化一个指针变量可以使用 new 函数

func main() {

   var p *int = new(int)
   fmt.Println(*p)

   *p = 10
   fmt.Println(*p)
}

执行上述代码,输出结果如下:

0
10

new 函数在这里会现申请一个内存空间,然后将内存空间保存的值设置为 int 的默认值既 0,而且 new 函数返回的是一个内存地址。

func main() {

   var p2 *int
   fmt.Println(p2) // 输出结果为:<nil>
}

其他类型如 Map、Slice 如果只定义不初始化,也会报错。

func main() {

   var info map[string]string
   info["name"] = "Stark"
   fmt.Println(info)

   var namesSlic []string
   namesSlic[0] = "Thor"
   fmt.Println(namesSlic)
}

也可以使用 make 函数来进行初始化,创建内存保存数据,返回一个具体的实例。

func main() {

   var info map[string]string = make(map[string]string)
   fmt.Println(info)
   info["name"] = "Stark"
   fmt.Println(info)

   var namesSlic []string = make([]string, 3)
   fmt.Println(namesSlic)
   namesSlic[0] = "Thor"
   fmt.Println(namesSlic)

}

Go 编程 | 连载 11 - 复杂数据类型 Slice 中使用 new 函数创建一个 Slice 实例,因为 new 函数返回的是内存地址,要获取内存地址中保存的实例则需要使用 * 来获取。

make 函数和 new 函数的区别或者使用场景如下:

  • make 和 new 都是用来分配内存的內建函数,且在堆上分配内存,make 即分配内存,也初始化内存;new只是将内存清零,并没有初始化内存。
  • make 返回的还是引用类型(实例)本身;而 new 返回的是指向类型的指针(内存地址)。
  • make 只能用来分配及初始化类型为 slice,map,channel;new 可以分配任意类型的数据。