您的位置 首页 golang

网络工程师的Golang之路——Go数据类型(指针)

版权声明:我已加入“维权骑士”( )的版权保护计划,知乎专栏“网路行者”下的所有文章均为我本人(知乎ID:弈心)原创,未经允许不得转载。

如果你喜欢我的文章,请关注我的知乎专栏“网路行者” , 里面有更多像本文一样深度讲解计算机网络技术的优质文章。


指针(pointer)对多数只学过Python的Netdevops读者来说是一个既陌生又熟悉的词汇,说它陌生是因为Python里没有指针的概念,说它熟悉是因为很多读者或多或少地听说过指针是编程领域里一个较难理解和掌握的知识点。和C语言一样,Go语言支持指针,但不同的是Go并不支持指针运算(pointer arithmetic)。

指针重要概念

简单点来说,所谓指针就是一个变量的内存地址,比如说我们有一个int类型,值为100的变量a (var a int = 100),它对应的内存地址为0xc00000a0c0( 内存地址是CPU随机分配的并不固定 ),那么变量a的指针就是0xc00000a0c0,我们用*int来表示整数型指针的数据类型(同理,如果变量a的数据类型是字符串,那么它的指针的数据类型就为*string,以此类推)。

指针变量

既然指针代表的是一个变量的内存地址,那么所谓指针变量就是指保存内存地址的变量, 为了区分指针变量和我们通常理解的变量,我会在后文中将后者称为普通变量。

取地址操作符&

要得到一个指针变量有两种方法,第一种方法是在一个普通变量的前面加上&这个符号( &为取地址操作符,它的作用是用来获取一个普通变量的内存地址 ),然后再将它赋值给指针变量即可,举例如下:

 package main

import "fmt"

func main() {
    a := 1
    b := "Network Engineer"
    ptr_a := &a //&为取地址操作符,对普通变量使用&操作符可以获得该普通变量的内存地址。
    ptr_b := &b //&为取地址操作符,对普通变量使用&操作符可以获得该普通变量的内存地址。
    fmt.Println(ptr_a)   //指针变量的值是它对应的普通变量的内存地址
    fmt.Println(ptr_b)   //指针变量的值是它对应的普通变量的内存地址
    fmt.Printf("%T\n", ptr_a) //指针变量ptr_a对应的普通变量的数据类型为int,那么ptr_a的数据类型就为*int
    fmt.Printf("%T\n", ptr_b) //指针变量ptr_b对应的普通变量的数据类型为string,那么ptr_a的数据类型就为*string
}
  

这里我们首先声明两个普通变量a和b(a的数据类型为int,b的数据类型为string),然后对a和b使用取地址符&来获取它们的内存地址,然后分别赋值给指针变量ptr_a和ptr_b。随后我们将ptr_a和ptr_b的值打印出来,分别为0xc00000c0a8和0xc000046240,最后我们再打印出ptr_a和ptr_b的数据类型,因为是指针变量,因此可以看到它们的数据类型分别为*int和*string,分别代表整数型指针和字符串型指针。

另外一种获取指针变量方法是使用new()函数,会在后文中讲到。

取值操作符*

和取地址运算符&相对应的是取值运算符*,我们可以用它来获取一个指针变量(内存地址)所对应的具体的值(也就是底层普通变量对应的值),通俗点说,就是把*int变回int,把*string变回string。使用方法是将它写在一个指针变量的前面即可,我们将上面的代码稍作修改如下:

 package main

import "fmt"

func main() {
    a := 1
    b := "Network Engineer"
    ptr_a := &a                //&为取地址操作符,对普通变量使用&操作符可以获得该普通变量的内存地址。
    ptr_b := &b                //&为取地址操作符,对普通变量使用&操作符可以获得该普通变量的内存地址。
    fmt.Println(ptr_a)         //指针变量的值是它对应的普通变量的内存地址
    fmt.Println(ptr_b)         //指针变量的值是它对应的普通变量的内存地址
    fmt.Printf("%T\n", ptr_a)  //指针变量ptr_a对应的普通变量的数据类型为int,那么ptr_a的数据类型就为*int
    fmt.Printf("%T\n", ptr_b)  //指针变量ptr_b对应的普通变量的数据类型为string,那么ptr_a的数据类型就为*string
    fmt.Println(*ptr_a)        //对指针变量ptr_a使用取值运算符,得到底层普通变量a的值,即整数1
    fmt.Println(*ptr_b)        //对指针变量ptr_b使用取值运算符,得到底层普通变量b的值,即字符串"Network Engineer"
    fmt.Printf("%T\n", *ptr_a) //数据类型从*int变回了int
    fmt.Printf("%T\n", *ptr_b) //数据类型从*string变回了string
}
  

这里我们对指针变量ptr_a和ptr_b分别使用取值操作符*,得到了这两个指针变量(内存地址)对应的值整数1和字符串”Network Engineer”,然后打印出了*ptr_a和*ptr_b的数据类型,自然也就是整数和字符串。

修改*指针变量对应的值会同时修改底层变量的值

在上面的例子中我们可以看到*ptr_a返回的值(整数1)实际上就是该指针变量对应的底层变量a的值(a :=1),*ptr_b返回的值(字符串”Network Engineer”),实际上就是该指针变量对应的底层变量b的值(b :=”Network Engineer”),也就是说*ptr_a = a = 1, *ptr_b = b = “Network Engineer”,那么相应的, 如果我们修改*ptr_a的值就会同时修改底层变量a的值,修改*ptr_b的值就会同时修改底层变量b的值, 举例如下:

 package main

import "fmt"

func main() {
    a := 1
    b := "Network Engineer"
    ptr_a := &a                //&为取地址操作符,对普通变量使用&操作符可以获得该普通变量的内存地址。
    ptr_b := &b                //&为取地址操作符,对普通变量使用&操作符可以获得该普通变量的内存地址。
    fmt.Println(ptr_a)         //指针变量的值是它对应的普通变量的内存地址
    fmt.Println(ptr_b)         //指针变量的值是它对应的普通变量的内存地址
    fmt.Printf("%T\n", ptr_a)  //指针变量ptr_a对应的普通变量的数据类型为int,那么ptr_a的数据类型就为*int
    fmt.Printf("%T\n", ptr_b)  //指针变量ptr_b对应的普通变量的数据类型为string,那么ptr_a的数据类型就为*string
    fmt.Println(*ptr_a)        //对指针变量ptr_a使用取值运算符,得到底层普通变量a的值,即整数1
    fmt.Println(*ptr_b)        //对指针变量ptr_b使用取值运算符,得到底层普通变量b的值,即字符串"Network Engineer"
    fmt.Printf("%T\n", *ptr_a) //数据类型从*int变回了int
    fmt.Printf("%T\n", *ptr_b) //数据类型从*string变回了string
    *ptr_a = 2                 //将指针变量ptr_a对应的值从1改为2
    *ptr_b = "Golang"          //将指针变量ptr_b对应的值从"Network Engineer"改为"Golang"
    fmt.Println(a)             //底层变量a的值受指针函数ptr_a对应值变化的影响,也从1变为了2
    fmt.Println(b)             //底层变量b的值受指针函数ptr_b对应值变化的影响,也从"Network Engineer"变为了"Golang"
}
  

这里我们将指针变量*ptr_a对应的值从1改为2,可以发现底层变量a的值也从1变为了2。将指针变量 *ptr_b对应的值从”Network Engineer”改为”Golang”,底层变量b的值也从”Network Engineer”变为了”Golang”。

用new()函数创建指针变量

前面提到了,除了通过使用取地址操作符&外,我们还可以用new()函数来创建指针变量。new()函数要求我们提供一个数据类型作为参数,然后new()函数会返回该数据类型的零值所对应的内存地址,在得到内存地址后,我们再将它赋值给一个变量,那么该变量自然就成为了指针变量。举例如下:

 package main

import "fmt"

func main() {
    pointer := new(int)         //将数据类型int作为参数传入new(),new()返回int类型的零值(即0)所对应的内存地址,然后将其赋值给pointer。
    fmt.Println(pointer)        //int零值(即0)所对应的内存地址
    fmt.Printf("%T\n", pointer) //因为pointer是指针函数,它的数据类型自然是*int
    fmt.Println(*pointer)       //对指针变量pointer使用取之操作符,得到int类型的零值,即整数0
}
  

这里我们将int这个数据类型作为参数传入new()函数中,new()随即返回int数据类型的零值:也就是整数0所对应的内存地址,然后我们将它赋值给pointer这个变量,因为该变量保存的是一个内存地址,自然该变量也就是指针变量。

然后我再相继将指针变量pointer的值、数据类型打印出来做验证,并在最后对point使用取值运算符*获取它(内存地址)对应的值,即整数0。


为什么使用指针

读到这里的你也许会问,为什么要使用指针?使用指针有什么好处?

回答这个问题之前,首先我们来看下不使用指针的话会有什么问题。

首先我们来看一段不使用指针,包含一个自定义函数的代码(自定义函数会在后面讲到,这里了解即可):

 package main

import "fmt"

func addFive(x int) int {
    x = x + 5
    return x
}

func main() {
    number := 5
    addFive(number)
    fmt.Println(number)
}
  

这里我们在main()主函数之外自定义了一个函数addFive(),该函数很简单,它只需要我们传入一个数据类型为整数的参数,然后返回该参数加5后的值(返回的值的数据类型也为int)。然后我们在主函数main()里声明了一个变量number,该变量的值为整数5。随后我们将整数变量number作为参数传入addFive()中, 但最后的结果却没有得到我们想要的整数10 (5+5) ,而还是number本身的值,即整数5 。这是因为在Go中,任何作为参数传递进函数中的变量并不是该变量本身,而是该变量的副本,调用函数时只会影响该变量副本的值,不会对变量本身产生任何影响。 也就是说这里addFive(number)中的number是number := 5这个number的副本,而非该变量本身,调用addFive(number)后,副本number变为了10,但是我们通过fmt.Println(number)打印出来的不是副本number,而是number变量本身,所以得到的值还是5,而不是我们想要的10。

而使用指针作为函数参数传入的话则不会有这个影响 ,我们将上面的代码稍作修改,如下:

 package main

import "fmt"

func addFive(x *int) {
       *x = *x + 5
}

func main() {
    number := 5
    addFive(&number)
    fmt.Println(number)
}
  

  • 代码第5排,我们通过addFive(x *int)来指定使用指针变量作为addFive()函数的参数。
  • 代码第6排,因为参数x是一个指针变量,我们需要对其使用取值操作符*才能得到具体的值,否则如果你写成x = x +5的话,等于是让一个内存地址和5相加,显然是错误的。
  • 代码第11排,因为addFive()需要的是一个指针变量的参数,因此我们将变量number对应的指针变量&number作为参数传入addFive()函数中。
  • 最后得到我们想要的结果,即整数10。

除此之外你还能看到使用指针作为函数参数的时候,我们无需指定函数返回值的数据类型,也无需使用return语句,让代码看起来更简洁。

文章来源:智云一二三科技

文章标题:网络工程师的Golang之路——Go数据类型(指针)

文章地址:https://www.zhihuclub.com/97962.shtml

关于作者: 智云科技

热门文章

网站地图