在“Go”语言中,星号(*)的作用是什么?

96
我一直在研究并尝试理解Go官网上的示例,但我一直遇到像这样的特殊星号字符的示例:
s := "hello"
if s[1] != 'e' {
    os.Exit(1)
}
s = "good bye"
var p *string = &s
*p = "ciao"

另外,我刚刚注意到,&s是什么意思?这是引用赋值吗(可能是在谈论PHP)?


在像Go和C这样的低级语言中,您会看到星号和取地址符。 - jcao219
3
C和Go可以使用特殊变量,称为指针,其中包含对象的内存地址。指针可以传递,并且是一种向其他代码传达对对象访问权限的紧凑方式。在Java中,使用new运算符分配给对象的所有变量都是引用变量。引用是指针变量类似的概念,但指针更加明确。地址运算符允许在需要时获取对象的地址。指针变量可以重新分配不同的地址。引用变量只能初始化一次地址。 - RogerV
7个回答

159

*跟在一个类型(*string)后面表示该类型的指针。

*跟在赋值语句中的变量(*v = ...)后面表示间接赋值。也就是说,更改变量所指向的值。

*跟在变量或表达式(*v)后面表示指针解引用。也就是说,获取变量所指向的值。

&跟在变量或表达式(&v)后面表示引用。也就是说,创建一个指向变量或字段值的指针。


5
值得澄清的是,对于 *v = ... 的情况,指针并不会改变指向,它仍指向内存中的同一位置,但该位置存储的值会被改变。 - Charles L.
参见 * 附加到结构体 https://dev59.com/2n7aa4cB1Zd3GeqPlhbj - Michael Freidgeim

78

我猜它的意思与C语言中相同

p是一个指向字符串的指针

语句var p *string = &s会将s对象的地址赋值给p

下一行*p = "ciao"会更改s的内容

请参阅Language Design FAQ中的此链接

有趣的是,没有指针算术运算

为什么没有指针算术运算? 安全性。 没有指针算术运算, 可以创建一种永远无法推导出 非法地址的语言。 编译器和硬件技术已经发展到了 使用数组索引的循环和使用指针 算术的循环一样有效。 此外,缺少指针 算术可以简化垃圾收集器的实现。


4
通过将变量类型声明为“*string”,可以将“p”声明为指针,而“&s”指向变量s的内存位置。那么,为什么不能直接说p =“ciao”(而不是*p)?那样会创建一个名为“p”的新变量吗? - rich97
12
当你说*p = "ciao"时,你的意思是“取p所指向的内存并将其设置为“ciao””。如果你说p = "ciao",你试图表达的是“使p指向地址“ciao””,这没有意义,所以它不能编译。p = &s表示“让p指向s的地址”。也许这有帮助? - Evan Shaw
为什么在 p *string = s 的情况下,解释器不能自动将 p 指向 s 的地址,而是需要明确指定 &s - James Lin
1
它还建议阅读这篇简短的文章Go语言中的指针。星号和取地址符的小故事,以帮助您理解Go语言中的指针。 - joseluisq

45

Go 语言 地址、指针和类型:

s := "hello"      // type string
t := "bye"        // type string
u := 44           // type int
v := [2]int{1, 2} // type array
q := &s           // type pointer

所有这些Go变量都有一个内存地址。它们之间的区别在于字符串类型保存字符串值,整数类型保存整数值,指针类型保存地址。因此,即使保存地址的变量也有自己的内存地址。 指针: 在变量名前添加&可以得到它的地址。或者想象一下,“这是我的地址,所以你知道在哪里找到我。”
// make p type pointer (to string only) and assign value to address of s
var p *string = &s // type *string
// or
q := &s // shorthand, same deal

在指针变量前加上*将解引用指针以获取其所持有的地址。或者可以这样理解,"将操作传递到我的值所代表的地址上"。
*p = "ciao"   // change s, not p, the value of p remains the address of s

// j := *s    // error, s is not a pointer type, no address to redirect action to
// p = "ciao" // error, can't change to type string

p = &t        // change p, now points to address of t
//p = &u      // error, can't change to type *int

// make r type pointer (to a pointer which is a pointer to a string) and assign value to address of p
var r **string = &p // shorthand: r := &p

w := (  r == &p) // (  r evaluates to address of p) w = true
w =  ( *r == p ) // ( *r evaluates to value of p [address of t]) w = true
w =  (**r == t ) // (**r evaluates to value of t) w = true

// make n type pointer (to string) and assign value to address of t (deref'd p)
n := &*p
o := *&t // meaningless flip-flop, same as: o := t

// point y to array v
y := &v
z := (*y)[0] // dereference y, get first value of element, assign to z (z == 1)

在这里玩耍:http://play.golang.org/p/u3sPpYLfz7


20

这就是我的看法。不同的措辞可能会帮助某些人更好地理解它(您可以复制粘贴该代码并检查输出):

package main

import (
    "fmt"
)

func main() {
    // declare a variable of type "int" with the default value "0"
    var y int

    // print the value of y "0"
    fmt.Println(y)

    // print the address of y, something like "0xc42008c0a0"
    fmt.Println(&y)

    // declare a variable of type "int pointer"
    // x may only hold addresses to variables of type "int"
    var x *int

    // y may not simply be assigned to x, like "x = y", because that 
    // would raise an error, since x is of type "int pointer", 
    // but y is of type "int"

    // assign address of y "0xc42008c0a0" as value to x
    x = &y

    // print the value of x "0xc42008c0a0" which is also the address of y
    fmt.Println(x)

    // print the address of x, something like "0xc420030028" 
    // (x and y have different addresses, obviously)
    fmt.Println(&x)

    // x is of type "int pointer" and holds an address to a variable of 
    // type "int" that holds the value "0", something like x -> y -> 0;
    // print the value of y "0" via x (dereference)
    fmt.Println(*x)

    // change the value of y via x
    *x = 1; /* same as */ y = 1

    // print value of y "1"
    fmt.Println(y); /* same as */ fmt.Println(*x)
}

6
< p >在C和Go中,*字符用于定义指针。变量不是一个实际的值,而是一个指向值位置的地址。&运算符用于获取对象的地址。


只是一点提醒 - 内存地址本身就是一个真实的值。这就是为什么在 C 中(但根据常见问题解答不适用于 Go),您可以对指针执行算术运算。 - alternative
@monadic:作为实数与进行算术运算有什么关系? - newacct

2
我不懂Go语言,但根据语法看来它与C语言相似——都有指针。指针类似于引用,但更底层且更强大。它包含所讨论项的内存地址。&a获取变量的内存地址,*a取消引用,获取内存地址处的值。

此外,在声明中的*表示它是一个指针。

因此,就像在PHP中一样,p&s指向同一块内存,因此更改p的值也会更改s的值。


OP链接的页面似乎加强了这一点。“在前面加上&符号给我们提供了一个唯一值实例的地址。”这个例子似乎符合这个解释。 - Matchu
然而,PHP的比较并不是非常准确的。虽然&可能代表一个略微相似的概念,但它们的实际用法有足够的不同(鉴于PHP不容易获取内存地址),因此说它们是相同的概念可能会具有误导性。 - Matchu
我知道这并不准确,PHP网站本身就说引用不是指针,但引用是我最接近真正理解指针的东西。 - rich97
它允许您以不同的方式执行与PHP引用相同的操作。我绝不是在说PHP引用= Go指针。 - alternative

0
在Golang中,*用作指针。指针保存值的内存地址。
类型*T是指向T值的指针。它的零值为nil。
var p *int

& 运算符会生成指向其操作数的指针。

i := 42
p = &i

* 运算符表示指针的基础值。

fmt.Println(*p) // read i through the pointer p
*p = 21         // set i through the pointer p

这被称为“解引用”或“间接引用”。

与C不同,Go没有指针算术运算。

package main

import "fmt"

func main() {
    i, j := 42, 2701

    p := &i         // point to i
    fmt.Println(*p) // read i through the pointer
    *p = 21         // set i through the pointer
    fmt.Println(i)  // see the new value of i

    p = &j         // point to j
    *p = *p / 37   // divide j through the pointer
    fmt.Println(j) // see the new value of j
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接