从前端开发转到后端设计时选择了 Go 语言这一适合服务器编程的语言,适应了 Javascript 编程时自动猜测数据类型的特性后,面对需要提前声明数据类型的静态语言感觉十分不适应。再后来,发现指针这个奇妙的东西,更神奇的是,这个指针竟然也有不同的数据类型,而且它会在之后的服务器编程中大量运用。紧急恶补了一些知识以后,觉得不能藏着掖着,还是得写一篇文章以启示后人。(更主要的原因是发现自己快三个月没有更新了

可以让你舒适阅读本文章的前置条件

  • 了解变量、数据类型等基本编程知识的新手

  • 从脚本解释型语言(Javascript、Python 等)转向静态编译型语言(C++、Go 等)的前端开发者。

变量都放在哪?

一个程序往往由许多的变量组成,如果这些变量没有经过序列化保存到硬盘中,那么它们应该都会放在计算机的内存中。计算机会用一个 0x 开头,16 进制的一串值(例如 0x0026c38)来记录这些变量存取的位置。这个值称为变量的地址

变量名 地址
username Nick 0xc42000a080
ID 12 0xc42001a1e0
balance 3.14 0xc42007356f

因此,应用获取数据主要有两种方式:

  • 通过访问变量获取数据
  • 通过访问内存地址获取数据

而通过变量访问数据,本质上还是在访问内存地址,具体实现可理解为程序单独维护着一张变量名与地址对应的 Hash 表,访问变量时,程序从 Hash 表中找到对应的值,然后取出地址对应的数据。

如果你需要知道某个变量对应的内存地址是什么,可以在变量名称前使用 & 符号,它会返回值对应的一串地址,这个操作被称为引用

fmt.Print(*username)

// 0xc42000a080

而如果你知道一个变量的地址,需要访问地址里面的内容,可以在地址前使用 * 符号,它会返回地址对应的值,这个操作称为解引用

fmt.Print(&0xc42001a1e0)

// Nick

指针——存放地址的变量

对,就这么简单,指针就是一种用于存放地址的变量。这些变量存储的值是一串关于某个值的地址。但是在声明指针的过程中一定要注明这是什么数据类型的指针,否则由于不同数据类型长度不同,会导致数据的存取过程出错。

声明指针的类型很简单,只需要在数据类型前面加上 * 即可。

var a *int

此时,变量 a 是一个指针,储存的应是一个 int 类型数据的地址。

在 C++、C 中,指针甚至可以进行加减运算,其代表的是将指针的内存地址左移或右移一位。

指针支持指向的数据类型很多:int,string,struct都可以,甚至…你可以指向另一个指针*int,无限套娃。以下程序代码来自菜鸟教程:

package main
import "fmt"

func main() {

   var a int
   var ptr *int
   var pptr **int

   a = 3000

   /* 指针 ptr 地址 */
   ptr = &a

   /* 指向指针 ptr 地址 */
   pptr = &ptr

   /* 获取 pptr 的值 */
   fmt.Printf("变量 a = %d\n", a )
   fmt.Printf("指针变量 *ptr = %d\n", *ptr )
   fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)
}

指针与函数

在 Go 语言中,结构体的指针可以作为一个形参传入函数,这将会直接在结构体上修改函数值(注意区别!)

因此,指针函数相当于针对某一个结构体的方法,通过它就可以用来修改传入的数据。以下代码来自《The Way To Go》:

package main

import "fmt"

/*定义以下结构体*/
type TwoInts struct {
	a int
	b int
}

/*可先不关注此处*/
func main() {
	two1 := new(TwoInts)
	two1.a = 12
	two1.b = 10

	fmt.Printf("The sum is: %d\n", two1.AddThem())
	fmt.Printf("Add them to the param: %d\n", two1.AddToParam(20))

	two2 := TwoInts{3, 4}
	fmt.Printf("The sum is: %d\n", two2.AddThem())
}

/*请关注此处,这里展示了结构体的两个方法,TwoInts 的指针作为了 Receiver 被传入函数中,表示这个函数会修改传入的 TwoInts 类型的值。*/
func (tn *TwoInts) AddThem() int {
	return tn.a + tn.b
}

func (tn *TwoInts) AddToParam(param int) int {
	return tn.a + tn.b + param
}

一个简单的小结

与 C、C++ 相比,Go 语言提供的指针只有简单的一个功能:引用,并不能进行各种复杂的指针运算。但是新手在接触指针时就不会把整个程序弄得一团糟,在程序中过度的引用指针并不会引起多大的性能提升,还会将损失代码的一部分可读性,因此,使用指针时应先衡量使用场景。