从零基础学Go(四)——Go的基本数据结构

前言📃

数据结构是编程的基础组件,它在计算机科学中扮演着关键角色,决定了数据的存储和组织方式,从而影响程序的效率和性能。

数据结构提供了一种组织数据的方式,使程序能够更高效地存取和管理数据。

不同的数据结构适用于不同类型的问题,例如数组适合随机访问,链表适合频繁插入和删除。

数据结构直接影响算法的设计与效率。选择合适的数据结构可以大幅提升算法的性能。

例如,使用哈希表进行查找操作比使用链表快得多,因为哈希表平均情况下能在常数时间内完成查找。

Go语言(也称为Golang)是一种静态类型、编译型的编程语言,提供了多种内置数据结构,方便开发者高效地处理数据,接下来我们一起走进Go的数据结构世界。

基本数据类型❔️

字符类型

Go中的字符串默认为UTF-8编码,这在21世纪更有意义。 由于UTF-8支持ASCII字符集,因此在大多数情况下无需担心编码。我们有时候在页面上或者代码输出里看到乱码,这背后都是文件编码的锅。默认UTF-8的编码为中文提供了更好的支持,国人开发起来也更加顺畅。

字符串中的每一个元素叫做“字符”,在遍历或者单个获取字符串元素时可以获得字符。

Go语言的字符类型有以下两种:

  • 一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。
  • 另一种是 rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型等价于 int32 类型。

byte 类型是 uint8 的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题,例如 var ch byte = ‘A’,字符使用单引号括起来。

在 ASCII 码表中,A 的值是 65,使用 16 进制表示则为 41,所以下面的写法是等效的:

var ch byte = 65var ch byte = '\x41'  //(\x 总是紧跟着长度为 2 的 16 进制数)

另外一种可能的写法是\后面紧跟着长度为 3 的八进制数,例如 \377。

Go语言支持 Unicode(UTF-8),因此字符同样称为 Unicode 代码点或者 runes,并在内存中使用 int 来表示。在文档中,一般使用格式 U+hhhh 来表示,其中 h 表示一个 16 进制数。

在书写 Unicode 字符时,需要在 16 进制数之前加上前缀\u或者\U。因为 Unicode 至少占用 2 个字节,所以我们使用 int16 或者 int 类型来表示。如果需要使用到 4 字节,则使用\u前缀,如果需要使用到 8 个字节,则使用\U前缀。

一个简单的代码示例:

package main

import (
	"fmt"
)

func main() {
	// 使用 \u 前缀表示 4 字节的 Unicode 字符
	char1 := '\u4F60' // '你'
	char2 := '\u597D' // '好'

	// 使用 \U 前缀表示 8 字节的 Unicode 字符
	char3 := '\U0001F600' // 😀, Unicode 表情符号
	
	// 打印字符及其 Unicode 代码点
	fmt.Printf("char1: %c, Unicode: U+%04X\n", char1, char1)
	fmt.Printf("char2: %c, Unicode: U+%04X\n", char2, char2)
	fmt.Printf("char3: %c, Unicode: U+%06X\n", char3, char3)
	
	// 使用 rune 来存储 Unicode 字符
	var r rune = '世' // 也可以直接使用字符
	fmt.Printf("Rune: %c, Unicode: U+%04X\n", r, r)

}

字符串类型

Go 语言中的字符串类型是内置的数据类型之一。字符串在 Go 中是一个字节切片的不可变序列,专门用于表示文本。字符串可以包含任意的字节数据,因此可以用于表示二进制数据,但在大多数情况下用于处理文本数据。

  • 在 Go 中,字符串是不可变的。一旦创建,字符串的内容不能被更改。这意味着对字符串的任何操作都会产生一个新的字符串,而不是修改原有字符串。

  • 字符串在底层是一个只读的字节数组(即 []byte),每个字符串都有一个指向该字节数组的指针以及长度信息。

  • Go 字符串是 UTF-8 编码的,这意味着可以直接处理 Unicode 文本。字符在字符串中以一个或多个字节的形式存在。

字符串声明和初始化

字符串可以使用双引号 " 或反引号 ```来声明:

package main

import "fmt"

func main() {
    // 使用双引号创建字符串
    var str1 string = "Hello, 世界"
    

    // 使用反引号创建原始字符串字面量
    var str2 string = `Hello,
    世界`
    
    fmt.Println(str1)
    fmt.Println(str2)

}
  • 双引号:用于包含可以转义的字符串。支持转义字符,例如 \n\t 等。
  • 反引号:用于原始字符串字面量,其中不进行转义处理,适合多行字符串。

常用操作

字符串长度:

  • 使用 len() 函数获取字符串的字节长度。
  • 注意:len() 返回的是字节数而不是字符数,因为 Go 字符串是以字节为单位的 UTF-8 编码。
str := "Hello, 世界"
fmt.Println(len(str)) // 输出: 13,因为 "世" 和 "界" 是 3 个字节

字符串拼接:

  • 使用 + 操作符可以连接字符串。
greeting := "Hello"
name := "World"
message := greeting + ", " + name + "!"
fmt.Println(message) // 输出: Hello, World!

字符串索引:

  • 使用索引可以获取字符串中的单个字节。
str := "Hello"
fmt.Println(str[1]) // 输出: 101 (对应于字符 'e' 的 ASCII 码)

字符串遍历:

  • 使用 for 循环结合 range 可以遍历字符串,获取 Unicode 字符。
go复制代码str := "Hello, 世界"
for i, r := range str {
    fmt.Printf("Index: %d, Rune: %c\n", i, r)
}

字符串转换:

  • 字符串和字节切片之间可以相互转换。
  • 字符串和 rune 切片之间也可以转换,以便进行更复杂的 Unicode 操作。
// 字符串转字节切片
bytes := []byte("Hello, World")

// 字节切片转字符串
str := string(bytes)

// 字符串转 rune 切片
runes := []rune("Hello, 世界")

// rune 切片转字符串
str2 := string(runes)

字符串包 strings

Go 语言标准库提供了一个 strings 包,包含许多用于操作字符串的函数:

  • 字符串查找和替换
    • strings.Contains(): 判断字符串是否包含子串。
    • strings.Index(): 返回子串首次出现的位置。
    • strings.Replace(): 替换字符串中的子串。
  • 字符串修改
    • strings.ToUpper(): 将字符串转换为大写。
    • strings.ToLower(): 将字符串转换为小写。
    • strings.TrimSpace(): 去除字符串前后的空白字符。
  • 字符串拆分和连接
    • strings.Split(): 将字符串分割为子串切片。
    • strings.Join(): 将字符串切片连接为一个字符串。

代码示例:

package main

import (
    "fmt"
    "strings"
)

func main() {
    str := " Hello, 世界! Go语言 "

    // 去除空白
    trimmed := strings.TrimSpace(str)
    fmt.Println("Trimmed:", trimmed)
    
    // 转为大写
    upper := strings.ToUpper(str)
    fmt.Println("Upper:", upper)
    
    // 查找子串
    contains := strings.Contains(str, "Go")
    fmt.Println("Contains 'Go':", contains)
    
    // 替换子串
    replaced := strings.Replace(str, "世界", "World", 1)
    fmt.Println("Replaced:", replaced)
    
    // 分割字符串
    split := strings.Split(str, ",")
    fmt.Println("Split:", split)
    
    // 连接字符串
    joined := strings.Join(split, " -")
    fmt.Println("Joined:", joined)

}

布尔型

在计算机编程中,布尔型(Boolean)是一种逻辑数据类型,只有两个取值:true和false。在Go语言中,布尔型用bool表示,可以用来表示真假、开关等状态。

一个布尔类型的值只有两种:true和false。if和for语句的条件部分都是布尔类型的值,并且==和<等比较操作也会产生布尔型的值。

package main

import "fmt"

func main() {
    var isGoAwesome bool = true
    fmt.Println("Is Go awesome?", isGoAwesome)
}

布尔型的运算

一元操作符!对应逻辑非操作,因此!true的值为false,更罗嗦的说法是(!true==false)==true,虽然表达方式不一样,不过我们一般会采用简洁的布尔表达式,就像用x来表示x==true

在Go语言中,布尔型变量可以进行与、或、非等逻辑运算。常用的逻辑运算符包括:

  • &&:与运算符,只有两个操作数都为true时,结果才为true。
  • ||:或运算符,只要有一个操作数为true时,结果就为true。
  • !:非运算符,对操作数取反,即true变为false,false变为true。
var b2 bool = true 
var b3 bool = false 
fmt.Println(b2 && b3)   // 输出 false 
fmt.Println(b2 || b3)   // 输出 true 
fmt.Println(!b2)       // 输出 false

在上面的示例代码中,我们定义了两个bool类型的变量b2和b3,并对它们进行了与、或、非等逻辑运算。可以看到,与运算只有两个操作数都为true时,结果才为true;或运算只要有一个操作数为true时,结果就为true;非运算则对操作数取反。

我们在其他语言或者生活中也习惯用0,1来表示真与假或者开与关,在Go语言中布尔值并不会隐式转换为数字值0或1,反之亦然。必须使用一个显式的if语句辅助转换:

i := 0
if b {
    i = 1
}

数字类型

整数

整数也是计算机高级语言中最多使用最普通的数据类型。整数类型同样是Go语言中非常基础的概念,它们在处理数值计算、数组索引、内存地址等方面都非常有用。Go语言的整数数据类型有以下几种:

int、int8、int16、int32、int64
uint、uint8(别名为byte)、uint16、uint32、uint64、uintptr

Go语言同时提供了有符号和无符号类型的整数运算。这里有int8、int16、int32和int64四种截然不同大小的有符号整数类型,分别对应8、16、32、64bit大小的有符号整数,与此对应的是uint8、uint16、uint32和uint64四种无符号整数类型。

这里还有两种一般对应特定CPU平台机器字大小的有符号和无符号整数int和uint;其中int是应用最广泛的数值类型。这两种类型都有同样的大小,32或64bit,但是我们不能对此做任何的假设;因为不同的编译器即使在相同的硬件平台上可能产生不同的大小。

Unicode字符rune类型是和int32等价的类型,通常用于表示一个Unicode码点。这两个名称可以互换使用。同样byte也是uint8类型的等价类型,byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。

最后,还有一种无符号的整数类型uintptr,没有指定具体的bit大小但是足以容纳指针。uintptr类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。我们将在第十三章的unsafe包相关部分看到类似的例子。

不管它们的具体大小,int、uint和uintptr是不同类型的兄弟类型。其中int和int32也是不同的类型,即使int的大小也是32bit,在需要将int当作int32类型的地方需要一个显式的类型转换操作,反之亦然。

其中有符号整数采用2的补码形式表示,也就是最高bit位用来表示符号位,一个n-bit的有符号数的值域是从-2n-1到2n-1-1。无符号整数的所有bit位都用于表示非负数,值域是0到2n-1。例如,int8类型整数的值域是从-128到127,而uint8类型整数的值域是从0到255。

  • int
    • int是最基本的整数类型,它代表一个有符号的整数。在不同的平台上,int的大小可能会有所不同,通常是32位或64位。在32位系统上,int通常是32位的,而在64位系统上,int可能是32位或64位,这取决于编译器和操作系统。
  • int8
    • int8是一个有符号的8位整数,可以存储从-128到127的整数。
  • int16
    • int16是一个有符号的16位整数,可以存储从-32768到32767的整数。
  • int32
    • int32是一个有符号的32位整数,可以存储从-2147483648到2147483647的整数。
  • int64
    • int64是一个有符号的64位整数,可以存储非常大的整数范围,从-9223372036854775808到9223372036854775807。
  • uint
    • uint是无符号的整数类型,它和int的大小相同,但是只能存储非负整数。
  • uint8, uint16, uint32, uint64
    • 这些类型分别对应于int8, int16, int32, int64,但它们是无符号的,也就是说它们只能存储正整数或零。
var myInt int = 42          // 声明一个int类型的变量myInt,并赋值为42
var myByte int8 = 127       // 声明一个int8类型的变量myByte,并赋值为127
var myUint uint = 42        // 声明一个uint类型的变量myUint,并赋值为42
var myLargeInt int64 = 9223372036854775807 // 声明一个int64类型的变量myLargeInt,并赋值为最大值

在Go语言中,你可以在不同类型的整数之间进行转换,但是需要显式地进行类型转换。例如:

var myInt int = 42
var myInt8 int8 = int8(myInt) // 将int类型的变量转换为int8类型

注意事项

  • 当你尝试将一个较大范围的整数赋值给一个较小范围的整数类型时,需要小心溢出的问题。
  • 无符号整数类型不能存储负数。

浮点数

Go语言提供了两种精度的浮点数,float32和float64。它们的算术规范由IEEE754浮点数国际标准定义,该浮点数规范被所有现代的CPU支持。

这些浮点数类型的取值范围可以从很微小到很巨大。浮点数的范围极限值可以在math包找到。常量math.MaxFloat32表示float32能表示的最大数值,大约是 3.4e38;对应的math.MaxFloat64常量大约是1.8e308。它们分别能表示的最小值近似为1.4e-45和4.9e-324。

一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供约15个十进制数的精度;通常应该优先使用float64类型,因为float32类型的累计计算误差很容易扩散,并且float32能精确表示的正整数并不是很大(译注:因为float32的有效bit位只有23个,其它的bit位用于指数和符号;当整数大于23bit能表达的范围时,float32的表示将出现误差)

浮点数类型的使用

在Go语言中,声明和使用浮点数类型非常简单:

var myFloat float32 = 3.14159265358979323846 // 声明一个float32类型的变量myFloat
var myDouble float64 = 2.718281828459045235360287 // 声明一个float64类型的变量myDouble

浮点数字面量

在Go中,浮点数字面量默认是float64类型,除非你显式地指定它是float32

var defaultDouble float64 = 1.41  // 默认float64
var explicitFloat32 float32 = 1.41 // 显式指定float32

精度问题

浮点数在计算机中的表示是不精确的,因为它们使用二进制形式来近似十进制小数。这意味着某些十进制小数无法精确表示为浮点数。例如,0.1在二进制中是一个无限循环小数,因此在计算机中它会被近似表示。

浮点数类型转换

如果你需要在float32float64之间进行转换,你需要显式地进行类型转换:

var myFloat float32 = 3.14
var myDouble float64 = float64(myFloat) // 将float32转换为float64

注意事项

  • 当进行浮点数运算时,可能会遇到精度问题,特别是当涉及到非常小或非常大的数值时。
  • 在比较两个浮点数是否相等时,应该考虑使用一个小的误差范围来判断它们是否足够接近,而不是直接比较。

复数

Go语言提供了两种精度的复数类型:complex64和complex128,分别对应float32和float64两种浮点数精度。内置的complex函数用于构建复数,内建的real和imag函数分别返回复数的实部和虚部:

var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y)                 // "(-5+10i)"
fmt.Println(real(x*y))           // "-5"
fmt.Println(imag(x*y))           // "10"

如果一个浮点数面值或一个十进制整数面值后面跟着一个i,例如3.141592i或2i,它将构成一个复数的虚部,复数的实部是0:

fmt.Println(1i * 1i) // "(-1+0i)", i^2 = -1

在常量算术规则下,一个复数常量可以加到另一个普通数值常量(整数或浮点数、实部或虚部),我们可以用自然的方式书写复数,就像1+2i或与之等价的写法2i+1。上面x和y的声明语句还可以简化:

x := 1 + 2i
y := 3 + 4i

复数也可以用==和!=进行相等比较。只有两个复数的实部和虚部都相等的时候它们才是相等的(译注:浮点数的相等比较是危险的,需要特别小心处理精度问题)。

总结📜

Go语言(Golang)提供了一系列强大而灵活的数据结构,可以用于各种编程需求。理解这些数据结构及其特点,可以帮助我们编写高效、可靠的程序。

基本数据类型

  1. 字符类型

    • byteuint8的别名,表示ASCII字符。
    • runeint32的别名,用于表示UTF-8字符。
    • 支持Unicode,字符可通过'\uXXXX''\UXXXXXXXX'表示。
  2. 字符串类型

    • 字符串是不可变的字节序列。
    • 默认UTF-8编码。
    • 使用双引号"表示可转义字符串,反引号```表示原始字符串。
    • 常用操作:长度(len())、拼接(+)、索引([])、遍历(range)。
    • strings包提供丰富的字符串处理函数。
  3. 布尔类型

    • 只有两个值:truefalse
    • 常用于条件语句和逻辑运算。
  4. 数字类型

    • 整数:

      • 有符号整数:int, int8, int16, int32, int64
      • 无符号整数:uint, uint8 (byte), uint16, uint32, uint64, uintptr
    • 浮点数:

      • float32, float64
      • 支持IEEE 754标准。
    • 复数:

      • complex64, complex128
      • 通过complex()构造,real()imag()获取实部和虚部。

数据结构与效率

  • 选择合适的数据结构可以显著提高程序的效率和性能。
  • Go语言内置数据结构的灵活性,使其适用于多种编程场景。
  • 理解数据结构在内存中的存储和操作方式是编写高效程序的关键。

欢迎关注公众号:“全栈开发指南针” 这里是技术潮流的风向标,也是你代码旅程的导航仪!🚀
Let’s code and have fun!🎉

  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值