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。所以方法的接收者为指针时,不管调用者是不是指针,都可以修改内部的值

总结

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

相关推荐
why15127 分钟前
腾讯(QQ浏览器)后端开发
开发语言·后端·golang
charade31230 分钟前
【C语言】内存分配的理解
c语言·开发语言·c++
浪裡遊30 分钟前
跨域问题(Cross-Origin Problem)
linux·前端·vue.js·后端·https·sprint
LinDaiuuj31 分钟前
判断符号??,?. ,! ,!! ,|| ,&&,?: 意思以及举例
开发语言·前端·javascript
声声codeGrandMaster38 分钟前
django之优化分页功能(利用参数共存及封装来实现)
数据库·后端·python·django
呼Lu噜1 小时前
WPF-遵循MVVM框架创建图表的显示【保姆级】
前端·后端·wpf
bing_1581 小时前
为什么选择 Spring Boot? 它是如何简化单个微服务的创建、配置和部署的?
spring boot·后端·微服务
小臭希1 小时前
Java——琐碎知识点一
java·开发语言
张帅涛_6661 小时前
golang goroutine(协程)和 channel(管道) 案例解析
jvm·golang·go
学c真好玩1 小时前
Django创建的应用目录详细解释以及如何操作数据库自动创建表
后端·python·django