【Go系列】 Go语言数据结构

时间:2024-07-12 07:04:26

承上启下

        在上一次的思维碰撞中,我们的小试牛刀是一段温馨的代码小练习——将“Hello World”这个熟悉的问候,替换成了我们自己的名字。是的,你没听错,就是这么简单!以我为例,我将“Hello World”轻轻一变,就成了“飞雪无情”。这不仅仅是一个文本的替换,更是我们与代码对话的开始。

随着上篇博客的落幕,你对 Go 语言的魅力已经有了初步的领略,你的开发工具也已整装待发。但是,亲爱的读者,编程的世界远不止于此。我们知道,一个成熟的项目就像是一部精心编织的剧本,充满了错综复杂的情节,远非一句“Hello World”所能概括。接下来,我们将深入探讨 Go 语言的灵魂——变量、常量、类型、函数方法、接口、结构体,这些关键词如同构建程序的基石,我将带你一一解锁它们的奥秘,让你的 Go 语言之旅更加精彩纷呈。准备好了吗?让我们启程吧!????????????

开始学习

变量声明

在编程的广阔天地中,变量就像是一个灵活的小精灵,它承载着程序运行时的各种数据,而这些数据在程序的旅途中可能会经历无数次的变化。是的,变量就是这样的多变而有趣。

在 Go 语言的语法森林中,我们使用 var 这个关键字来召唤这些小精灵。当我们声明一个变量时,我们需要为它指定一个身份——也就是类型,并且给它一个响亮的名字,最后,我们还要为它赋予一个初始的值,让它在程序的舞台上有一个起点。以下是 Go 语言中声明变量的标准姿势:

var 变量名 类型 = 表达式

现在我通过一个示例来演示如何定义一个变量,并且设置它的初始值:

package main

import "fmt"

func main() {

    var a int = 10

    fmt.Println(a)

}

在 Go 语言的 main 函数中,var i int = 10 用于声明一个名为 i 的整数类型变量,并初始化为 10。添加 fmt.Println(i) 这行代码是为了满足两个目的:

  1. 确保变量 i 被使用,因为 Go 语言要求所有声明的变量都必须使用,否则编译不会通过。
  2. 在程序运行时,可以通过打印输出查看变量 i 的值,这对于调试和验证程序很有帮助。

通过输入 go run main.go 命令回车运行,即可看到如下结果:

$ go run ch02/main.go 

10

打印的结果是10,和变量的初始值一样。

Go 语言的类型推导功能让变量声明变得更加简洁。如果你不显式指定变量的类型,Go 编译器会根据变量初始化的值自动推导出其类型。以下是使用类型推导来声明变量 i 的方式:

var i = 10

或者,如果你想在声明的同时进行赋值,可以使用更简短的语法:

i := 10

这里的 := 是一个简短变量声明操作符,它不仅可以声明变量,还可以自动推导变量类型,并且赋初值。这种方式在 Go 语言中非常常见,尤其是在函数内部声明局部变量时。

你也可以一次声明多个变量,把要声明的多个变量放到一个括号中即可,如下面的代码所示:

var (
    j int= 0
    k int= 1
)

同理因为类型推导,以上多个变量声明也可以用以下代码的方式书写:

var (
    j = 0
    k = 1
)

后续会出现的的 float64、bool、string 等基础类型都可以被自动推导,也就是可以省略定义类型。

基础类型

每种编程语言都会定义一套基础类型,这些类型直接映射到我们现实世界中的数据。Go 语言也不例外,它提供了一系列的基础类型,用于表示不同的数据。以下是 Go 语言中常用的几种基础类型:

整型

在 Go 语言中,整型分为:

  • 有符号整型:如 int、int8、int16、int32 和 int64。
  • 无符号整型:如 uint、uint8、uint16、uint32 和 uint64。

        有符号整型与无符号整型的区别主要体现在它们所能表示的数值范围上。有符号整型能够表示负数、零以及正数,而无符号整型则仅限于零和正数。
        在整型的世界里,除了明确指定位宽的类型,还存在 `int` 和 `uint` 这两种类型,它们的具体位宽并不固定,可能是 32 位,也可能是 64 位,这一切取决于所运行的 CPU 硬件。因此,在明确知道所需位宽的情况下,选择具体的整型会增强程序的移植性。
        在 Go 语言的类型系统中,`byte` 类型实际上是与 `uint8` 等同的,可以视为 `uint8` 的另一种称呼。它用于表示单个字节的数据,因此,`byte` 类型同样归属于整型家族。

浮点数

        浮点数在 Go 语言中用于表示现实世界中的小数。Go 提供了两种精度的浮点数:float32 和 float64。在大多数项目中,float64 是更为常用的选择,因为它提供了更高的精度,从而在进行浮点运算时,相较于 float32,误差更小。

var f32 float32 = 3.3

var f64 float64 = 3.1415326

fmt.Println("f32 is",f32,",f64 is",f64)

运行这段程序,会看到如下结果:

$ go run main.go

f32 is 3.3 ,f64 is 3.1415926
布尔型

布尔型变量仅包含两种可能的值:true 和 false,它们分别对应现实世界中的“是”与“否”。这类变量的值常用于逻辑判断,例如 if 语句(我们将在后续博客中详细探讨)。在 Go 语言中,布尔型通过关键字 bool 来声明。

以下是一段代码示例,其中声明了两个布尔型变量。你可以亲自运行这段代码,观察控制台输出的结果:

var bf bool = false

var bt bool = true

fmt.Println("bf is",bf,",bt is",bt)

布尔值可以用于一元操作符 !,表示逻辑非的意思,也可以用于二元操作符 &&、||,它们分别表示逻辑和、逻辑或。

字符串

Go 语言中的字符串可以表示为任意的数据,比如以下代码,在 Go 语言中,字符串通过类型 string 声明:

var s1 string = "Hello"

var s2 string = "World"

fmt.Println("s1 is",s1,",s2 is",s2)

运行程序就可以看到打印的字符串结果。

在 Go 语言中,可以通过操作符 + 把字符串连接起来,得到一个新的字符串,比如将上面的 s1 和 s2 连接起来,如下所示:

fmt.Println("s1+s2=",s1+s2)

由于 s1 表示字符串“Hello”,s2 表示字符串“World”,在终端输入 go run main.go 后,就可以打印出它们连接起来的结果“HelloWorld”,如以下代码所示:

s1+s2= HelloWorld

字符串也可以通过 += 运算符操作,你自己可以试试 s1+=s2 会得到什么新的字符串。

零值

零值,可以被理解为变量的预设状态。在 Go 语言中,当你声明一个变量而未显式赋予初值时,Go 会自动为其分配一个零值。这些零值根据类型的不同而有所区别:数值类型的零值是 0,布尔类型的零值是 false,而字符串类型的零值则是一个空字符串 ""

以下代码示例将展示这些基础类型的零值是如何被设置的:

var zi int

var zf float64

var zb bool

var zs string

fmt.Println(zi,zf,zb,zs)

变量

变量简短声明

        你或许已经注意到,在之前的示例中,我们每次声明变量都使用了 var 关键字。然而,这种写法在代码中显得有些繁琐。为了简化这一过程,Go 语言提供了更为便捷的变量声明方式,即使用 := 操作符进行类型推导。这种简短声明的方式如下所示:

变量名:=表达式

借助 Go 语言简短声明功能,变量声明就会非常简洁,比如以上示例中的变量,可以通过如下代码简短声明:

i := 10

bf := false

s1 := "Hello"

在实际的项目实战中,如果你能为声明的变量初始化,那么就选择简短声明方式,这种方式也是使用最多的。

指针

在 Go 语言的世界里,指针扮演着至关重要的角色,它代表了变量在内存中的具体位置。换言之,指针的值实际上就是变量的内存地址。我们可以通过 & 操作符来获取一个变量的地址,进而得到该变量的指针。

在下面的代码示例中,pi 即为指向变量 i 的指针。若要获取 pi 指针所指向的变量值,可以使用 *pi 这一表达式。运行这段程序,你将观察到输出的结果与变量 i 的值相同。

pi:=&i

fmt.Println(*pi)

赋值操作

当我们提到变量时,曾提及它们是可以被修改的。那么,如何实现这一修改呢?这就需要用到赋值语句。最常见且最简单的赋值操作符是 =,如下所示:

i = 20
fmt.Println("i的新值是", i)

通过上述代码,变量 i 的值被更新为 20,从而实现了变量的修改。

常量概念

在编程语言中,常量与变量相辅相成。在 Go 语言里,常量的值在编译时期就已经确定,并且一旦设定便不可更改,这保证了运行时的数据安全性。

常量的定义

定义常量与变量类似,不过使用的关键字是 const。以下是一个定义常量的示例:

const name = "飞雪无情"

在 Go 语言中,常量可以是布尔型、字符串或数字类型。

iota 的使用

iota 是一个常量生成器,它用于初始化具有规律性的常量,避免了重复的初始化过程。例如,定义一系列连续的数字常量,可以使用 iota 来简化:

const (
    one = iota + 1
    two
    three
    four
)
fmt.Println(one, two, three, four)

iota 的初始值为 0,并在每个常量声明后递增。上面的常量分解如下:

  • one = (0) + 1,此时 iota 为 0,计算后 one 的值为 1
  • two = (0 + 1) + 1iota 递增为 1,计算后 two 的值为 2
  • 以此类推,直到 four

字符串类型

字符串是 Go 语言中常用的数据类型。下面我们将详细介绍字符串的使用。

字符串与数字的互转

Go 语言是强类型语言,不同类型的变量不能直接互相使用或计算。因此,在进行赋值或计算前,通常需要进行类型转换。以下是一个字符串与数字互转的示例:

i2s := strconv.Itoa(i)
s2i, err := strconv.Atoi(i2s)
fmt.Println(i2s, s2i, err)

使用 strconv 包的 Itoa 函数可以将 int 类型转换为 string,而 Atoi 函数则用于将 string 转换为 int

对于浮点数和布尔型,Go 语言提供了相应的转换函数,如 strconv.ParseFloat 和 strconv.ParseBool 等,你可以自行尝试。

数字类型之间的转换可以通过强制转换实现,如下所示:

i2f := float64(i)
f2i := int(f64)
fmt.Println(i2f, f2i)

请注意,强制转换可能会丢失精度,尤其是在将浮点型转换为整型时。

Strings 包

在处理字符串时,Go SDK 提供了一个强大的标准库 strings。这个包包含了多种用于操作字符串的函数,如查找、去除空格、拆分字符串、检查前缀或后缀等。掌握这些函数对于高效编程至关重要。

以下是一些使用 strings 包的示例:

// 判断 s1 的前缀是否为 "H"
fmt.Println(strings.HasPrefix(s1, "H"))

// 在 s1 中查找字符串 "o"
fmt.Println(strings.Index(s1, "o"))

// 将 s1 全部转换为大写
fmt.Println(strings.ToUpper(s1))

你可以根据 strings 包的文档编写更多示例,以便更好地熟悉和掌握这些函数的使用。