Go语言入门指南(下)
实践记录 · 2023/7/28 · 玉米哥
目录
变量
循环
分支
函数
面向对象设计
在上一篇文章Go语言入门指南:基础语法(上)中,我总结了与Go语言相关的变量、循环、分支等方面的知识。今天,我会整理Go语言函数和面向对象方面的知识点。
以下内容主要总结于
- 字节跳动青训营后端入门 - Go语言原理与实践
- Go语言官方文档教程
- Go语言圣经
函数
对于学习过C++、Java、C#等编程语言的同学来说,函数的概念很容易掌握,即执行某个功能的代码段,程序可以将其包装为一个函数,以便在运行过程中反复调用。
函数的声明
在Go语言中,使用func关键字声明一个函数,函数声明的语法与其他语言略有差异,回忆一下函数签名的组成部分:函数名、参数列表、返回值。
声明不接受参数、无返回值的函数
func greetings() {
fmt.Print("你好,字节跳动")
}
熟悉其他编程语言的同学可能会疑惑,函数没有返回值,为什么不使用void修饰呢?
其实很简单,Go语言为了简洁,当函数没有返回值时,不需要使用void关键字,Go语言也没有这个关键字。
声明接受参数、有返回值的函数
出于简单考虑,我们实现一个复读机函数,接受字符串,不做任何处理,然后返回它。
func repeater(slogan string) string {
return string
}
细心的同学可能会发现,欸?为什么参数列表中的参数类型在参数名的右侧,与之类似,为什么返回值类型也在函数名右侧。其实这就是Go语言与其他语言产生差异的地方,咱们还是需要细心分辨。
函数名规范
Go语言约定,函数名首字母大写,表示该函数是可以被其他模块的函数调用;函数名首字母小写,表示该函数只在模块内使用。不知道同学们有没有观察到,当我们向控制台打印输出时,使用的是fmt.PrintLn(),可以看到大写的P,同时需要使用import "fmt",正好说明了该函数的使用需要导入外部模块。
函数的调用
在Go语言中,函数的调用与其他函数类似,在这里我想强调一下,Go语言像Python一样,可以同时返回多个值,那么具体的写法是什么呢?
func greeting() (string, int) {
return "恭喜发财", 666
}
name, luckyNumber := greeting()
当有多个返回值时,需要使用一对儿括号将返回值的类型括起来。
接受返回值时,需要使用相等个数的变量来接受。
常用函数
下面介绍一些我们会经常使用到的函数,我将分为几大板块来介绍:错误处理、字符串操作、JSON处理、获取时间、数字解析等
错误处理
提到错误处理,学过Java的朋友们可能立刻会想到Error、Exception等词汇,之前在学校学习Java的时候,面对庞大的异常处理体系,总是令人摸不着头脑。
而在Go中,错误处理却显得异常的简单。当我们在编写执行一些特定功能的函数时,出现异常是很正常的事情,如果函数无法正确执行下去,那么应该及时地向调用它的老大报告这个错误,而老大也应该想法设法地捕获这些异常,而Go语言则巧妙地利用了函数可以返回多个值来实现这一个机制。
我们编写一个打招呼的函数来进一步说明。该函数接受一个姓名,返回定制的问候语。
func greeting(name string) string {
GreetToYou := fmt.Sprintf("你好,%v", name)
return GreetToYou
}
抛出异常
很容易想到,该函数希望接受一个姓名作为参数,如果name为空字符串""时,应该抛出一个错误,只需要添加一个类型为Error的返回值就可以。
func greeting(name string) (string, error) {
if name == "" {
return name, errors.New("姓名为空")
}
GreetToYou := fmt.Sprintf("你好,%v", name)
return GreetToYou, nil
}
errors.New()需要导入errors包。
对于该函数,有这几点可以好好说道说道。
- 添加了一个新的返回值类型
error,用于返回函数的执行状态 - 对
name函数参数进行判断,若输入无效,函数对应的返回值类型string、error分别返回""空字符串和errors.New(),其中包含自定义的错误信息。 - 若函数正常执行,
string、error分别返回对应的结果字符串和nil类型。在Go语言中,nil表示指针、通道、切片、接口等类型的零值,在错误处理中,nil就表示程序没有错误。
接受异常
当程序调用该函数时,可以使用以下的方式接受异常。
name := "玉米哥"
slogan, err := greeting(name)
// 若函数抛出异常
if err != nil {
// 打印日志,发现错误信息
log.Fatal(err)
}
// 否则正常执行
log.Fatal()需要导入log包。
从上面的示例看出,Go语言的错误处理十分的简单,你学会了吗?
字符串操作
针对字符串的操作是老生常谈的话题,废话不多说,直接给出代码和运行结果。
str := "Go is a powerful and efficient programming language"
fmt.Println(strings.Contains(str, "Go")) // true
fmt.Println(strings.Count(str, "g")) // 4
fmt.Println(strings.HasPrefix(str, "Go")) // true
fmt.Println(strings.HasSuffix(str, "language")) // true
fmt.Println(strings.Index(str, "powerful")) // 8
fmt.Println(strings.Join([]string{"Go", "is", "language"}, " ")) // Go is language
fmt.Println(strings.Repeat(str, 2)) // Go is a powerful and efficient programming languageGo is a powerful and efficient programming language
fmt.Println(strings.Replace(str, "language", "GoLang", 1)) // Go is a powerful and efficient programming GoLanguage
fmt.Println(strings.Split("apple,orange,banana", ",")) // [apple orange banana]
fmt.Println(strings.ToLower(str)) // go is a powerful and efficient programming language
fmt.Println(strings.ToUpper(str)) // GO IS A POWERFUL AND EFFICIENT PROGRAMMING LANGUAGE
fmt.Println(len(str)) // 51
strWithSpaces := " trim spaces "
fmt.Println(strings.TrimSpace(strWithSpaces)) // "trim spaces"
上述函数需要导入
strings包。
JSON处理
在介绍JSON处理的函数之前,请允许先介绍两个名词:序列化、反序列化。
序列化
序列化是指将数据结构或对象转换为线性数据流(通常是字节流),以便在存储或传输时能够保持其状态和结构。这样的数据流可以保存到文件中,通过网络传输,或者存储到数据库中。
在序列化过程中,数据结构中的字段和值被编码成一个特定格式,通常是JSON、XML等。这样,数据就能够被传输或保存,并在后续需要时可以重新还原为原始的数据结构。
在Go语言中,使用json.Marshal函数可以将一个Go数据结构序列化为JSON格式的字节流。
反序列化
反序列化是序列化的逆过程,它将之前序列化的数据流(通常是字节流)重新转换回原始的数据结构或对象。在反序列化过程中,解码器会根据预定的格式和规则解析数据流,并恢复原始数据结构的字段和值。
在Go语言中,使用json.Unmarshal函数可以将JSON格式的字节流反序列化为相应的Go数据结构。
现在你应该对序列化和反序列化有个基本的认知了。简而言之,程序中的数据结构和对象,要想保存到本地磁盘或者在网络上传输,就必须先序列化。当需要读取这些信息时,再进行反序列化即可。
但是我有一个新的疑惑,为什么数值类型和字符串类型在传输时不需要序列化呢?
答案其实很简单,数值和字符串在内存中已经是一段连续的字节序列,不需要额外的序列化操作。它们是原始数据类型,直接存储在内存中的连续地址上。因此,直接将它们用于传输或存储时,就是在传输或存储它们的原始字节序列。
例如,一个整数类型的变量在内存中存储为几个字节的二进制表示,而一个字符串在内存中存储为一系列字符的连续字节。
这也从侧面印证了,对于复杂的数据结构和对象,尤其是涉及到指针、切片、映射等数据结构时,它们在内存中的存储可能是非连续的。因为这些数据结构中的元素可以动态地分配内存,它们在内存中可能以散布的方式存储,并使用指针链接起来。
所以,在对复杂的数据结构和对象进行传输时,我们需要将它们转换为一段连续的字节序列,以便能够方便地进行传输和存储。
到这里,你总该熟悉序列化和反序列化了吧。
下面则是代码时间!
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Gender string `json:"gender"`
}
func main() {
// 序列化:将数据结构转换为JSON格式的字节流
person := Person{Name: "玉米哥", Age: 22, Gender: "男"}
jsonData, err := json.Marshal(person)
if err != nil {
fmt.Println("Serialization error:", err)
return
}
fmt.Println("Serialized data:", string(jsonData))
// 反序列化:将JSON格式的字节流转换为数据结构
var newPerson Person
err = json.Unmarshal(jsonData, &newPerson)
if err != nil {
fmt.Println("Deserialization error:", err)
return
}
fmt.Println("Deserialized data:", newPerson)
}
需要导入
encoding/json包
函数的输出。
Serialized data: {"name":"玉米哥","age":22,"gender":"男"}
Deserialized data: {玉米哥 22 男}
使用函数时,有以下几点需要注意。
- json.Marshal函数返回json格式的字节流和序列化结果(成功/失败)。
- json.Unmarshal函数接受两个参数,第一个参数
jsonData是序列化之后的字节流,第二个参数是一个对特定对象的引用。
面向对象设计
有过其他编程语言学习经验的朋友,肯定会熟悉类、对象、构造函数、析构函数等概念,但是在Go语言中,面向对象的实现方式却有很大的不同,我们一一道来。
结构体
有同学可能会疑惑,我不是要介绍面向对象吗,为什么会讲到结构体,请耐心往下看。在其他编程语言中,定义一个结构体很简单,使用struct关键字即可。在本文的JSON处理部分中,我们实际上已经见过了结构体。
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Gender string `json:"gender"`
}
Person结构体包含三个字段,分别是string、int、string类型。
我们都知道,类就是对数据和操作数据的方法的组合,可是在Go语言中,并没有class关键字来声明类,那么我们该如何操作Person中的数据呢?
答案很简单,直接使用函数即可。与普通函数不同的一点是,我们将函数与该结构体进行绑定。
绑定值类型接收器的方法
func (p Person) Introduce() {
fmt.Printf("Hello, my name is %v. I am %v years old. I am %v.\n", p.Name, p.Age, p.Gender)
}
绑定指针类型接收器的方法
func (p *Person) GrowUp() {
p.Age++
}
在函数名前面添加一对儿()圆括号,写好参数名称和需要绑定的结构体即可。细心的同学可能会发现,示例中的p和其它语言中的this指针有着异曲同工之妙。只不过,在Go中,结构体是值类型,因此若要修改结构体的值,必须使用指针。
小结
在绑定方法时,可以根据需要选择使用值类型接收器或指针类型接收器。如果需要修改结构体实例的数据,通常会选择使用指针类型接收器,以便在方法内部修改结构体的字段。而如果只需要读取结构体实例的数据而不做修改,则可以使用值类型接收器。
下一步
以上就是Go语言入门指南:基础语法的全部教程,如果你还没有看过上篇,请点击Go语言入门指南:基础语法(上)。
顺便说一句,这两篇文章只是青训营第一节课内容的总结,可以看出知识的密度很高,马上上又要开肝写视频的课后作业文章了,请您期待。
码字不易,如果您看到了这里,听我说谢谢你😀
如果您觉得本文还不错,请留下小小的赞😀
如果您有感而发,请留下宝贵的评论😀