• 一文了解 Go 中的指针和结构体


    耐心和持久胜过激烈和狂热。

    前言

    前面的两篇文章对 Go 语言的基础语法和基本数据类型以及几个复合数据类型进行介绍,本文将对 Go 里面的指针和结构体进行介绍,也为后续文章做铺垫。

    指针

    Go 语言中,指针可以简单理解是一个地址,指针类型是依托于某一个类型而存在的,例如 Go 里面的基本数据类型 intfloat64string 等,它们所对应的指针类型为 *int*float64*string等。

    指针的定义

    • 语法格式:var 指针变量名 *数据类型 = &变量
      & 为取地址符号,通过 & 符号获取某个变量的地址,然后赋值给指针变量

      import (
      	"fmt"
      )
      
      func main() {
      	var num int = 666
      	var numPtr *int = &num
      	fmt.Println(numPtr)  // num 变量的地址值 0xc00001c098
      	fmt.Println(&numPtr) // 指针变量本身的地址值 0xc00000a028
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      npmPtr 指针变量指向变量 num0xc00001c098num 变量的地址,0xc00000a028 为指针变量本身的地址值。

      在这里插入图片描述

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

      import (
      	"fmt"
      )
      
      func main() {
      	numPtr := new(int)
      	fmt.Println(numPtr)  // 0xc000122058
      	fmt.Println(*numPtr) // 0
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      new(T) 函数为每个类型分配一片内存,内存单元保存的是对应类型的零值,函数返回一个指针类型。

    • 错误的类型地址赋值

      func main() {
      	var num float64 = 666
      	var numPtr *int = &num // cannot use &num (value of type *float64) as type *int in variable declaration
      }
      
      • 1
      • 2
      • 3
      • 4

      当指针所依托的类型与变量的类型不一致时,Go 编译器会报错,类型不匹配。

    获取和修改指针所指向变量的值

    • 通过指针获取所指向变量的值
      func main() {
      	var num int = 666
      	var numPtr *int = &num
      
      	// 获取 num 的值
      	fmt.Println(*numPtr) // 666
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      对指针使用 * 操作符可以获取所指向变量的值。
    • 通过指针修改所指向变量的值
      import (
      	"fmt"
      )
      
      func main() {
      	var num int = 666
      	var numPtr *int = &num
      
      	// 修改 num 的值
      	*numPtr = 555
      	fmt.Println(num) // 555
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      同时也可以对指针使用 * 操作符修改所指向变量的值。

    结构体

    通过上一篇文章,我们了解了数组和切片的特点,它们可以存储一组相同类型的数据,而结构体,它可以由 0 个或多个字段组成,每个字段的数据类型可以一样,也可以不一样。结构体可以表示一个对象,例如:人,人包含的一些字段有姓名、性别和年龄等。

    结构体定义

    语法格式:

    type XXX struct {
    	/*
    	结构体字段定义区域
    	*/
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    XXX 为结构体的名字,下面以人为对象,引入结构体

    // Person定义一个人的结构体
    type Person struct {
    	// 姓名
    	Name string
    	// 年龄
    	Age int
    	// 性别
    	Sex string
    	// 身份证号
    	idNumber string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上述代码定义了人的结构体 Person,包含四个字段,字段的命名规则和变量是一样的,前三个字段首字母大写,可以被包外访问,第四个字段首字母小写,表示只能在包内访问。
    除了上述的定义方式以外,结构体里还可以内嵌结构体

    // Person 定义一个人的结构体
    type Person struct {
    	// 姓名
    	Name string
    	// 年龄
    	Age int
    	// 性别
    	Sex string
    	// 身份证号
    	idNumber string
    }
    
    // Phone 手机结构体
    type Phone struct {
    	// 品牌
    	Brand string
    	// 拥有者
    	Owner Person
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    上述代码定义了 Person 结构体和 Phone 结构体,Phone 结构体拥有两个字段,分别为 Brand 品牌和 Owner 拥有者,Owner 属性的类型,指定为我们所自定义的 Person 结构体。以这种方式定义的结构体字段,我们可以称为嵌入字段(Embedded Field)。也可以将这种字段称为匿名字段。

    结构体的创建方式

    • 1、声明一个结构体变量
      // Person 定义一个人的结构体
      type Person struct {
      	// 姓名
      	Name string
      	// 年龄
      	Age int
      	// 性别
      	Sex string
      	// 身份证号
      	idNumber string
      }
      
      func main() {
      	var person Person
      	fmt.Println(person.Name)     // ""
      	fmt.Println(person.Age)      // 0
      	fmt.Println(person.Sex)      // ""
      	fmt.Println(person.idNumber) // ""
      
      	// 修改结构体字段的值
      	person.Name = "chenmingyong"
      	fmt.Println(person.Name) // "chenmingyong"
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 通过声明一个结构体变量,隐式创建一个结构体变量,这个结构体变量各个字段值默认为零值,也就是对应类型的默认值。
      • 结构体属性的值,可以通过 变量.字段名 的方式获取,同时也可以通过此方式对字段值进行修改。
    • 2、使用复合字面值进行显式初始化结构体对象
      import "fmt"
      
      // Person 定义一个人的结构体
      type Person struct {
      	// 姓名
      	Name string
      	// 年龄
      	Age int
      	// 性别
      	Sex string
      	// 身份证号
      	idNumber string
      }
      
      func main() {
      	person := Person{
      		"chenmingyong",
      		18,
      		"男",
      		"xxxxxxxxx",
      	}
      
      	fmt.Println(person) // {chenmingyong 18 男 xxxxxxxxx}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 上述代码根据字段的顺序进行赋值,实际上,Go 语言并不推荐我们用这种方式进行赋值,因为可能会带来一些问题,例如结构体的某个中间的字段被删除或者字段的顺序改变了,那么我们需要维护对应代码,调整赋值的顺序。
      • 上述创建结构体的方式有弊端,因此我们需要换一种方法,如下第三种方法。
    • 3、 使用 field:value 形式的复合字面值进行显式初始化结构体对象
      import "fmt"
      
      // Person 定义一个人的结构体
      type Person struct {
      	// 姓名
      	Name string
      	// 年龄
      	Age int
      	// 性别
      	Sex string
      	// 身份证号
      	idNumber string
      }
      
      func main() {
      	person := Person{
      	    Sex:      "男",
      	    Age:      18,
      	    Name:     "chenmingyong",
      	    idNumber: "xxxxxxxxx",
          }
      
      	fmt.Println(person) // {chenmingyong 18 男 xxxxxxxxx}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      通过以上的方式,我们就不被字段的顺序所约束了。
    • 4、通过 new(T) 函数创建结构体指针
      // Person 定义一个人的结构体
      type Person struct {
      	// 姓名
      	Name string
      	// 年龄
      	Age int
      	// 性别
      	Sex string
      	// 身份证号
      	idNumber string
      }
      
      func main() {
      	person := new(Person)
      	(*person).Name = "chenmignyong"
      	fmt.Println((*person).Name) // chenmignyong
      	// 简化赋值,底层自动转换成 (*person).Age = 18
      	person.Age = 18
      	fmt.Println(person.Age) // 18
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      前面提到过,访问指针所指向变量的值,需要使用 * 操作符,但在结构体这里有点特殊,如果不加 * 的话,底层会将 person.Age = 18 转成 (*person).Age = 18

    小结

    本文对指针和结构体进行了介绍,也指出使用指针和结构体时需要注意的一些地方。因为本文是基于了解的层面去写的文章,一些深入的知识并没有提到,然后也没有提到结构体的方法,是因为打算留到后面和函数一起去介绍。

    如果本文对你有帮助,欢迎点赞收藏加关注,如果本文有错误的地方,欢迎指出!

  • 相关阅读:
    10.SpringBoot实战演练
    Ant design-13
    公路中、边桩坐标计算及放样程序
    redis教程
    【Python 千题 —— 基础篇】进制转换:十进制转十六进制
    IllegalStateException: FragmentManager is already executing transactions
    Ubuntu18.04安装深度学习环境(Anaconda、显卡驱动、CUDA、Tensorflow-GPU等)
    手把手教你搭建MinIO分布式集群
    再来谈谈如何从binlog文件恢复误update的数据,模拟Oracle的闪回功能
    Android studio Gradle download:kotlin-compiler-embeddable-1.7.20
  • 原文地址:https://blog.csdn.net/weixin_44604586/article/details/128040481