Go语言之路————指针、结构体、方法

Go语言之路------------指针、结构体、方法

前言

  • 我是一名多年Java开发人员,因为工作需要现在要学习go语言,Go语言之路是一个系列,记录着我从0开始接触Go,到后面能正常完成工作上的业务开发的过程,如果你也是个小白或者转Go语言的,希望我这篇文章对你有所帮助。
  • 有关go其他基础的内容的文章大家可以查看我的主页,接下来主要就是把这个系列更完,更完之后我会在每篇文章中挂上连接,方便大家跳转和复习。

go中的指针,通常在结构体中用的特别多,而方法又是结构体一部分,所以我把这三个知识点放在一起来说,这样大家可以连贯起来方便理解和吸收。

指针

指针你只需要记住两个操作符,一个是取地址符&,另一个是解引用符 *。对一个变量进行取地址,会返回对应类型的指针,下面我简单举个例子:

我们先看取地址符号:&

go 复制代码
package main

import "fmt"

func main() {
	a := 1
	fmt.Printf("%T\n", a)
	b := &a
	fmt.Println(b)
	fmt.Printf("%T\n", b)
}

console打印:
int
*int
0xc00008c0a8

我们对变量a用&符号取地址得到变量b,打印出来b的值就是a的地址,打印出b的类型,就是一个指针,这时候再回顾一下上面这句话:对一个变量进行取地址,会返回对应类型的指针。指针b存储的是变量a的地址

我们再看看解引用符:*

解引用符第一个用处,就跟它的命名一样,解除引用,就是解除指针的引用而获得具体的值,下面我们看个例子:

go 复制代码
import "fmt"

func main() {
	a := 123
	b := &a
	result := *b
	fmt.Println(result)
	fmt.Printf("%T", result)
}

console打印:
123
int

通过这个代码可以看到,我们通过&取得了指向a地址的指针b,但是通过解引符号*,用*b就可以解除指针引用直接获得这个地址对应的值,也就是a的值,打印result的数据类型也是int类型。

解引用符第一个用处:声明一个变量的类型为指针类型。

这里我们指定一个变量a它的类型为int类型的指针

go 复制代码
var a *int

打印一下a看看呢

go 复制代码
<nil>

因为我们没有给a赋值或者初始化,所以打印出来的为nil,要么使用取地址符将其他变量的地址赋值给该指针,要不就使用内置函数:new

说到:new 。go中的new和Java中的new有区别的是,go中的new是专门为指针服务的,它的用处就是新建或者说初始化一个指针

看看代码

go 复制代码
func main() {
	var a *int
	fmt.Println(a)
	a = new(int)
	fmt.Println(a)
}

我们用new去初始化了a,看看输出呢:

go 复制代码
<nil>
0xc00000a120

为啥输出来是一个地址呢,因为该函数会为该指针分配内存,并且指针指向对应类型的零值

用上面的知识点,接引符*来验证下是不是零值呢:

go 复制代码
func main() {
	var a *int
	fmt.Println(a)
	a = new(int)
	fmt.Println(a)
	fmt.Println(*a)
}
console打印:
<nil>
0xc00000a120
0

这么一看还真是对的,我们点进new函数,看看源码怎么写的:

go 复制代码
func new(Type) *Type

通过代码分析,我们定义的a为int,这里new中传入的是int,那么返回的就是 int,正好和a类型一致,是不是就是初始化了啊,是不是很简单啊。

而上面的示例代码,我们一般使用短赋值,简单一点:

go 复制代码
func main() {
	a:=new(int)
	fmt.Println(a)
}

ps:在go中指针是不能运算的,而且这里我们还要区分一下new和make,前者是为指针服务器的,后者是为具体数据类型的值服务的,不要搞混了。

结构体

go中的结构体,你可以理解为Java中的实体类,但是他们又有细微的差别,但是不是很多,下面我就一一道来。

既然是结构体,那么定义它的关键词就是:struct。我们先通过一个例子简单看下。

定义一个UserInfo的结构体,里面分别有name、age、phone三个字段:

声明

go 复制代码
type UserInfo struct {
	name  string
	age   int
	phone int
}

这是一个简单的声明,跟函数一样,如果遇到相同的数据类型,也可以写一起,所以上面的age和phone可以这样写:

go 复制代码
type UserInfo struct {
	name       string
	age, phone int
}

初始化

注意,上面只是声明,在Java中,是以new关键词创建一个类,比如这里:new UserInfo(),但是在go中没有那么复杂,直接调用传参,看下面例子:

go 复制代码
type UserInfo struct {
	name       string
	age, phone int
}

func main() {
	//这里需要注意点,为了方便阅读,或者灵活传参,这里尽量用这种格式字段名称:字段值,
	//也可以省略字段名称,但是就要传所有参数并且可读性很差,不推荐。
	var user = UserInfo{
		name:  "John",
		age:   42,
		phone: 1000,
	}
	fmt.Println(user)
}

console打印:
{John 42 1000}

注意:这里的结构体命名和里面字段的命名,都遵循首字母大小写的规则,可能有同学忘了,这里提一下,go中首字母大写的方法就是public,小写的就是private,切记。

使用

我们访问和结构体和修改结构体中的值也很简单,直接用.就行:

获取值:

go 复制代码
func main() {
	var user = UserInfo{
		name:  "John",
		age:   42,
		phone: 1000,
	}
	fmt.Println(user.name)
	fmt.Println(user.age)
}

打印:
John
42

Process finished with the exit code 0

赋值或修改值:

go 复制代码
func main() {
	var user = UserInfo{
		name:  "John",
		age:   42,
		phone: 1000,
	}
	user.name = "一颗知足的心"
	user.age = 18
	fmt.Println(user.name)
	fmt.Println(user.age)
}

打印:
一颗知足的心
18

Process finished with the exit code 0

如果实例化过程比较复杂,可以编写一个函数来实例化结构体,就像下面这样,你也可以把它理解为一个构造函数,但是go中函数不能重载,所以你想像Java那样通过参数不同用多个一样的函数名是不行的。

go 复制代码
func main() {
	user := NewUser("一颗知足的心", 18, 9527)
	fmt.Println(user)
}

func NewUser(name string, age int, phone int) *UserInfo {
	return &UserInfo{name: name, age: age, phone: phone}
}

组合引用

和Java一样,直接在内部字段声明就行,请看下面例子:

go 复制代码
type Person struct {
   name string
   age  int
}

type Student struct {
   p      Person
   school string
}

看看使用:

go 复制代码
student := Student{
   p:      Person{name: "jack", age: 18},
   school: "lili school",
}
fmt.Println(student.p.name)

结构体和指针

结构体的指针和值类型的指针使用上有个小的区别,就是结构体指针在使用的时候不用解引,请看下面例子:

go 复制代码
type UserInfo struct {
	name       string
	age, phone int
}

func main() {
	user := &UserInfo{
		name:  "一颗知足的心",
		age:   18,
		phone: 9527,
	}
	fmt.Println(user.name)
}

可以看到我们直接用user.name就可以调用,和普通的结构体调用一样,因为这是go的语法糖,编译器会自动编译成(*user).name

结构体的标签

这里简单提一点,了解一下就行,标签就是在结构体定义字段的时候,在后面打上标签

go 复制代码
type UserInfo struct {
	Name string `json:"name"`
	Age  int    `yaml:"age"`
}

结构体标签最广泛的应用就是在各种序列化格式中的别名定义,标签的使用需要结合反射才能完整发挥出其功能。

方法

方法与函数的区别在于,方法拥有接收者,而函数没有,且只有自定义类型能够拥有方法。先来看一个例子。

例子

go 复制代码
type IntSlice []int

func (i IntSlice) Get(index int) int {
  return i[index]
}
func (i IntSlice) Set(index, val int) {
  i[index] = val
}

func (i IntSlice) Len() int {
  return len(i)
}

先声明了一个类型IntSlice,其底层类型为[]int,再声明了三个方法Get,Set和Len,方法的长相与函数并无太大的区别,只是多了一小段(i IntSlice) 。i就是接收者,IntSlice就是接收者的类型,接收者就类似于其他语言中的this或self,只不过在 Go 中需要显示的指明。

go 复制代码
func main() {
   var intSlice IntSlice
   intSlice = []int{1, 2, 3, 4, 5}
   fmt.Println(intSlice.Get(0))
   intSlice.Set(0, 2)
   fmt.Println(intSlice)
   fmt.Println(intSlice.Len())
}

结合结构体

根据上面的例子,我们把方法和结构体结合一下。这里补充一点,接收者也分两种类型,值接收者和指针接收者

我们先看值接受者:

go 复制代码
type UserInfo struct {
	name       string
	age, phone int
}

func main() {
	user := &UserInfo{
		name:  "一颗知足的心",
		age:   18,
		phone: 9527,
	}
	user.updateAge(20)
	fmt.Println(user.age)
}

func (receiver UserInfo) updateAge(age int) {
	receiver.age = age
}

console打印:
18

Process finished with the exit code 0

我们可以看到,虽然我们在代码中,将age改为了20,但是最后user结构体中还是18,也就是说值接收者的方法,并不能改变接收者本身的属性

那要改变接收者本身的属性,就到了指针接收者,我们还是直接看代码:

go 复制代码
func main() {
	user := &UserInfo{
		name:  "一颗知足的心",
		age:   18,
		phone: 9527,
	}
	user.updateAge(20)
	fmt.Println(user.age)
}

func (receiver *UserInfo) updateAge(age int) {
	receiver.age = age
}

console打印:
20

Process finished with the exit code 0

看到变化没,我们只是把receiver UserInfo改为receiver *UserInfo,变成指针接受者,就可以改变接收者本身的属性。

这是为什么呢:因为值接收者可以简单的看成一个形参,而修改一个形参的值,并不会对方法外的值造成任何影响而用指针接收者,Go 会将其解释为(&receiver).age = age。所以方法的接收者为指针时,不管调用者是不是指针,都可以修改内部的值

总结

函数的参数传递过程中,是值拷贝的,如果传递的是一个整型,那就拷贝这个整型,如果是一个切片,那就拷贝这个切片,但如果是一个指针,就只需要拷贝这个指针,显然传递一个指针比起传递一个切片所消耗的资源更小,接收者也不例外,值接收者和指针接收者也是同样的道理。在大多数情况下,都推荐使用指针接收者,不过两者并不应该混合使用,要么都用,要么就都不用

相关推荐
云知谷11 小时前
【HTML】网络数据是如何渲染成HTML网页页面显示的
开发语言·网络·计算机网络·html
lang2015092811 小时前
Spring Boot 官方文档精解:构建与依赖管理
java·spring boot·后端
lly20240612 小时前
SQL ROUND() 函数详解
开发语言
大宝剑17012 小时前
python环境安装
开发语言·python
why技术12 小时前
从18w到1600w播放量,我的一点思考。
java·前端·后端
lly20240612 小时前
CSS3 多媒体查询
开发语言
间彧12 小时前
Redis Cluster vs Sentinel模式区别
后端
间彧12 小时前
🛡️ 构建高可用缓存架构:Redis集群与Caffeine多级缓存实战
后端
间彧12 小时前
构建本地缓存(如Caffeine)+ 分布式缓存(如Redis集群)的二级缓存架构
后端