Go语言入门指南(下) | 青训营

526 阅读11分钟

Go语言入门指南(下)

实践记录 · 2023/7/28 · 玉米哥

目录

变量
循环
分支
函数
面向对象设计

在上一篇文章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函数参数进行判断,若输入无效,函数对应的返回值类型stringerror分别返回""空字符串和errors.New(),其中包含自定义的错误信息。
  • 若函数正常执行,stringerror分别返回对应的结果字符串和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结构体包含三个字段,分别是stringintstring类型。

我们都知道,类就是对数据和操作数据的方法的组合,可是在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语言入门指南:基础语法(上)

顺便说一句,这两篇文章只是青训营第一节课内容的总结,可以看出知识的密度很高,马上上又要开肝写视频的课后作业文章了,请您期待。

码字不易,如果您看到了这里,听我说谢谢你😀

如果您觉得本文还不错,请留下小小的赞😀

如果您有感而发,请留下宝贵的评论😀