变量赋值&结构访问

1. 取地址与解引用

&是取地址符号,放到一个变量前使用就会返回相应变量的内存地址。如&a
*是指针运算符,一个指针变量指向了一个值的内存地址。放到一个变量前可以对指针解引用,获取指针指向的实际变量。

package main

import "fmt"

func main() {
    var a int = 20 /* 声明实际变量 */
    var ip *int    /* 声明指针变量 */

    ip = &a /* 指针变量的存储地址 */

    fmt.Printf("a 变量的地址是: %x\n", &a)

    /* 指针变量的存储地址 */
    fmt.Printf("ip 变量储存的指针地址: %x\n", ip)

    /* 使用指针访问值 */
    fmt.Printf("*ip 变量的值: %d\n", *ip)
}

输出如下

a 变量的地址是: c0000120d0
ip 变量储存的指针地址: c0000120d0
*ip 变量的值: 20

2. 变量赋值

go中直接对结构体进行复制,会进行值拷贝。

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    p1 := Person{Name: "张三", Age: 25}
    p2 := p1 // 这里是值拷贝

    // 修改 p2 不会影响 p1
    p2.Name = "李四"
    p2.Age = 30

    fmt.Println(p1) // 输出: {张三 25}
    fmt.Println(p2) // 输出: {李四 30}
}

结构体的指针属性也会拷贝,但指针指向的地址是相同的

package main

import "fmt"

type Student struct {
    Name   string
    Scores *[]int
}

func main() {
    scores := []int{90, 85, 95}
    s1 := Student{Name: "小明", Scores: &scores}
    s2 := s1 // 结构体是值拷贝,但Scores指针指向同一个切片

    (*s2.Scores)[0] = 100 // 通过s2修改切片的内容

    fmt.Println(*s1.Scores) // 输出: [100 85 95]
    fmt.Println(*s2.Scores) // 输出: [100 85 95]
    // 打印s1和s2的地址
    fmt.Printf("s1的地址: %p\n", &s1)
    fmt.Printf("s2的地址: %p\n", &s2)
    // 两个结构体中Scores指针本身的地址不一样,相对s1和s2的地址偏移量是固定的
    fmt.Printf("s1.Scores的指针地址: %p\n", &(s1.Scores))
    fmt.Printf("s2.Scores的指针地址: %p\n", &(s2.Scores))
    // 两个指针实际存储(指向)的地址是一样的
    fmt.Printf("s1.Scores的指针指向地址: %p\n", s1.Scores)
    fmt.Printf("s2.Scores的指针指向地址: %p\n", s2.Scores)
    // 修改指针指向地址内容,会同时生效
    (*(s1.Scores))[0] = 1
    fmt.Printf("s1.Scores数组元素: %v\n", *s1.Scores) // 输出: [1 85 95]
    fmt.Printf("s2.Scores数组元素: %v\n", *s2.Scores) // 输出: [1 85 95]

    // 将s1.Scores指针指向新开辟的内存,两个指针实际存储的地址不一样
    s1.Scores = &[]int{5, 6, 7}
    fmt.Printf("s1.Scores的指针地址: %p\n", &(s1.Scores))
    fmt.Printf("s2.Scores的指针地址: %p\n", &(s2.Scores))
    fmt.Printf("s1.Scores的指针指向地址: %p\n", s1.Scores)
    fmt.Printf("s2.Scores的指针指向地址: %p\n", s2.Scores)
    fmt.Printf("s1.Scores数组元素: %v\n", *s1.Scores)  // 输出: [5 6 7]
    fmt.Printf("s2.Scores数组元素: %v\n", *s2.Scores) //输出: [1 85 95]
}

输出如下

100 85 95]
[100 85 95]
s1的地址: 0xc000010030
s2的地址: 0xc000010048
s1.Scores的指针地址: 0xc000010040
s2.Scores的指针地址: 0xc000010058
s1.Scores的指针指向地址: 0xc000010018
s2.Scores的指针指向地址: 0xc000010018
s1.Scores数组元素: [1 85 95]
s2.Scores数组元素: [1 85 95]
s1.Scores的指针地址: 0xc000010040
s2.Scores的指针地址: 0xc000010058
s1.Scores的指针指向地址: 0xc0000100c0
s2.Scores的指针指向地址: 0xc000010018
s1.Scores数组元素: [5 6 7]
s2.Scores数组元素: [1 85 95]

如果我们不希望重新分配内存空间、进行值拷贝,使用两个变量引用同一块内存空间,值变化可以联动,则需要考虑使用结构体指针复制,以便进行引用传递

package main

import "fmt"

type Student struct {
    Name   string
    Scores *[]int
}

func main() {
    var p1 = Student{Name: "张三"}
    var p2 *Student = &p1 // p2是指向p1的指针
    p2.Name = "李四"        // 自动解引用,等同于(*p2).Name = "李四"

    fmt.Println(p1.Name) // 输出: {李四 25}
    fmt.Println(p2.Name) // 输出: {李四 25}
}

3. 结构体元素访问

在Go语言中,结构体指针和结构体对象都可以使用点号(.)语法访问结构体成员,这是Go语言的一个语法糖设计,目的是让代码更简洁易读。

  1. 简化语法:如果没有这个特性,使用结构体指针时我们需要先解引用再访问成员,像这样:(*p).field。这种写法相对繁琐且容易出错。
  2. 一致性体验:允许指针和值类型使用相同的访问语法,减少了开发者需要记住的特殊情况,使代码更一致。
  3. 自动解引用:当你使用 p.field 语法(其中 p 是指针)时,Go 编译器会自动将其解释为 (*p).field
package main

import "fmt"

type Person struct {
    Name string
}

func main() {
    // 结构体对象
    p1 := Person{Name: "张三"}
    fmt.Println(p1.Name) // 直接用点号访问

    // 结构体指针
    p2 := &p1
    fmt.Println(p2.Name) // 也可以用点号访问,编译器会自动转换为 (*p2).Name
    fmt.Println((*p2).Name)
}

输出如下:

张三
张三
张三