GO语言基础
从这一节起,入门一下 Go 语音。
基本数据类型
基本的数据类型如下:
- 布尔型(boolean):
true
和false
; - 整型(integer):
- 有符号:
int
(依据系统为 32/64 位)、int8
、int16
、int32
、int64
; - 无符号:
uint
(依据系统为 32/64 位)、uint8
、uint16
、uint32
、uint64
、uintptr
(无符号整型,用于存放指针);
- 有符号:
- 浮点型(float):
float32
、float64
; - 复数类型(complex):
complex64
、complex128
; - 字符串类型:
string
, 字符串是不可变的字节序列; - 字符类型(character):Go 语言没有专门的字符类型,但可以用
byte
(表示单个字节的 ASCII 字符,等同于uint8
的别名)或rune
(表示 Unicode 码点,等同于int32
)来表示字符。
和 js 类似,go 语音中用 var
定义一个变量; const
定义一个常量。
// 一般声明格式
var 变量名字 类型 = 值
// 简短声明
变量名字 := 值
数值的大小:
int8
的范围是 -128 到 127,uint8 的范围是 0 到 255,即2^8
有符号的情况下正负对半,0 占一数值,以此类推。float32
浮点数,精确到小数点后 7 位,32/2/2 - 1
正负各一半,再加上小数点的偏移量,所以精确到小数点后 7 位。; 以此类推,float64
浮点数,精确到小数点后 15 位(64/2/2 - 1
)。- 复数类型,
complex64
:由两个float32
类型的值分别表示复数的实数部分和虚数部分。可通过real()
和imag()
函数获取复数的实部和虚部。 rune
类型,rune
类型是 Go 语言中的一种特殊类型,用于表示 Unicode 码点。它实际上是int32
类型的别名,因此可以存储任何 Unicode 字符。rune
类型通常用于处理 Unicode 字符串,因为它们可以表示任何语言的字符。
特殊字符:
iota
是一个常量生成器,用于简化常量的声明。它在每个 const 声明块中从 0 开始自动递增。iota 常用于枚举类型的定义,可以帮助你生成一系列相关的常量,而不必手动赋值。
package main
import "fmt"
const (
// iota 在每个 const 关键字出现时被重置为 0
// 每调用一次,iota 的值加 1
a = iota // a == 0
b = iota // b == 1
c = iota // c == 2
)
const (
d = iota // d == 0
e = iota // e == 1
f = iota // f == 2
)
const (
g = iota + 1 // g == 1
h // h == 2 (默认情况下,h = iota + 1)
i // i == 3 (默认情况下,i = iota + 1)
)
func main() {
fmt.Println(a, b, c) // 输出: 0 1 2
fmt.Println(d, e, f) // 输出: 0 1 2
fmt.Println(g, h, i) // 输出: 1 2 3
}
nil
是一个预定义标识符,用于表示接口、指针、映射、切片、通道和函数类型的零值。可以把 nil
理解为这些类型的“空值”或“零值”。当这些类型的变量没有被初始化或没有指向任何有效的内存位置时,它们的默认值就是 nil
。
var s []int
fmt.Println(s == nil) // 输出: true
s = []int{}
fmt.Println(s == nil) // 输出: false
在 Go 语言中,切片的零值是
nil
。但是,空切片[]int{}
和nil
切片之间存在一些微妙的区别。
空切片
[]int{}
:有一个指向底层数组的指针(即使这个数组是长度为零的数组)。通常用来表示明确的空集合,表示初始化了但没有元素。
nil
切片:没有指向任何底层数组的指针。通常用来表示未初始化的切片,表示没有分配内存。
用法: 可以将 nil
作为参数传递给函数,以表示空值或未初始化的状态。
func process(slice []int) {
if slice == nil {
fmt.Println("收到一个 nil 切片")
} else {
fmt.Println("收到一个非 nil 切片")
}
}
process(nil) // 输出: 收到一个 nil 切片
值符号*
和地址符号&
:*
是获取变量的值,&
是获取变量的地址。
func main() {
var a int = 10
var p *int = &a
fmt.Println(*p) // 输出: 10
fmt.Println(p) // 输出: 0xc0000140a0
}
复合数据类型
- 数组(array): 一组相同类型元素的集合,长度固定;
- 切片(slice): 动态数组,长度可变,底层是数组;
- 结构体(struct): 一组不同类型字段的集合,用于表示一个对象,大小固定;
- 映射(map): 键值对的集合,键是唯一的,值可以是任意类型;
简单理解,在 go 语音中,复合数据类型分为俩类:同构元素组成的类型数组和异构元素组成的结构体。这俩者都有固定的内存大小;而相对应的动态增长的数据结构则为切片slice
和映射map
。
数组 array
和 js 类似,数据的每个元素可以通过索引下标来访问,索引下标的范围是从 0 开始到数组长度减 1 的位置。数组长度由内置方法 len()
获取。值得注意的是数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是 0。js 中则为 undefined
。
var m [3]int = [3]int{1, 2}
fmt.Println(m[0]) // 输出: 1
fmt.Println(m[1]) // 输出: 2
fmt.Println(m[2]) // 输出: 0
技巧: 在 go 中, 如果数组长度位置由
...
省略号代替,则表示数组的长度由初始化时元素的个数决定。var m = [...]int{1, 2} fmt.Println(len(m)) // 输出: 2
此外数组若作为函数的参数传入,此时是值传递,即传入的是数组的副本,函数内部对数组的修改不会影响到原数组。如果希望函数内部修改数组,可以将数组作为指针传递。
切片 slice
切片是 Go 语言中的一种动态数组,它允许在运行时动态地增加和减少元素的数量。切片是基于数组实现的,它包含一个指向底层数组的指针、切片的长度和容量。
切片的创建方式主要有俩种:1. 基于数组创建; 2. 直接创建。
// 基于数组创建
arr := [3]int{1, 2, 3}
s := arr[0:2: 2] // s 是一个切片,包含 arr 的前两个元素, 第三个参数表示切片的容量,即切片可以容纳的最大元素数量。可省略,但不建议省略。
// 直接创建 1
s := []int{1, 2, 3} // s 是一个切片,包含三个元素
// 直接创建 2
s := make([]int, 3, 5) // s 是一个切片,长度为 3,容量为 5
内置的
make
函数创建一个指定元素类型、长度和容量的slice
。容量部分可以省略,在这种情况下,容量将等于长度。实际上,
make
函数创建的slice
是指向一个底层数组的指针,这个底层数组的大小由容量决定。当向slice
添加元素时,如果slice
的容量不足,Go 语言会自动分配一个新的底层数组,并将原来的元素复制到新的数组中。
不常见的直接创建:
var p *[]int = new([]int)
- 创建了一个指向切片类型
[]int
的指针p
。 - 使用
new
函数分配了一块内存,这块内存是一个[]int
类型的切片,并将指向这块内存的指针赋值给p
。
实际上这样做的结果是 p
是一个指向 []int 类型的指针,它本身的值是一个内存地址,指向一个 []int
类型的切片。
package main
import "fmt"
func main() {
var p *[]int = new([]int) // 分配一个空的 []int 切片,并返回一个指向它的指针 p
*p = append(*p, 1, 2, 3) // 通过 *p 解引用这个指针,访问并修改切片的内容
fmt.Println(*p) // 输出: [1 2 3]
}
上文中创建切片中的第一种方式,需要着重说一下踩坑点。
package main
import "fmt"
func main() {
arr1 := []int{1, 2, 3, 4}
arr2 := arr1[:2]
arr2 = append(arr2, 5)
fmt.Println("the arr1 value:", arr1) // the arr1 value: [1 2 5 4]
fmt.Println("the arr2 value:", arr2) // the arr2 value: [1 2 5]
}
这里有点反直觉,为什么 append
篡改了原数组的值。原因在于 go 允许多个 slice
指向同一个底层数组,这就导致了,slice 在没有扩容的情况下直接修改了原数组的值,正确的做法是给 slice 第一 cap 容量。arr2 := arr1[:2:2]
即第三个参数 cap 值。
使用小技巧:
去除空值:
package main import "fmt" func nonempty(strings []string) []string { i := 0 // range strings 会依次返回切片中每个元素的索引和值。 for _, s := range strings { if s != "" { strings[i] = s i++ } } return strings[:i] } func main() { data := []string{"one", "", "three"} fmt.Printf("%q\n", nonempty(data)) // ["one" "three"] fmt.Printf("%q\n", data) // ["one" "three" "three"] }
在 Go 编程语言中,range 是一个关键字,用于迭代不同类型的数据结构。它通常与 for 循环结合使用,可以用于迭代数组、切片(slice)、映射(map)、字符串和通道(channel)。
切片复制:
package main import "fmt" func main() { src := []int{1, 2, 3, 4, 5} dst := make([]int, len(src)) copy(dst, src) src[0] = 100 fmt.Println("src:", src) // src: [100 2 3 4 5] fmt.Println("dst:", dst) // dst: [1 2 3 4 5] }
切片删除元素:
package main import "fmt" func main() { s := []int{1, 2, 3, 4, 5} s = append(s[:2], s[3:]...) fmt.Println(s) // [1 2 4 5] }
切片截取:
package main import "fmt" func main() { s := []int{1, 2, 3, 4, 5} s = s[1:4] fmt.Println(s) // [2 3 4] }
无序集合 Map
Map 是无序的 key/value
对集合,Go 语言中的 Map 是引用类型,必须初始化才能使用。
声明:
var m map[string] string
。创建:
// 1. 使用内置 make 函数创建 m := make(map[string] string) // 2. 直接声明并赋值(字面量创建) m := map[string] string { "name": "Tom", "age": "18", } // 等价于 m := make(map[string] string) m["name"] = "Tom" m["age"] = "18"
获取元素:
m["name"]
。但不可对 map 元素进行取址操作,原因可能在于 map 的元素是无序的,无法确定取值顺序。且元素数量会变化,因此地址不固定。当然也可接着数组对 map 的 key 进行显示排序。import sort var keys []string for k := range m { keys = append(keys, k) } // 对 key 进行排序 sort.Strings(keys) for _, k := range keys { fmt.Println(k, m[k]) }
删除元素:
delete(m, "name")
。判断元素是否存在:
v, ok := m["name"]
。
结构体 struct
结构体是一种聚合数据类型,它是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。结构体成员可以是任意类型,包括内置类型、数组、指针、结构体、甚至是其他结构体。
声明:
type Person struct { Name string; Age int }
。创建:
// 1. 使用字面量创建 p := Person{"Tom", 18} // 2. 使用结构体字面量创建 p := Person{Name: "Tom", Age: 18}
访问成员:
p.Name
。
值得注意的是,结构体成员的定义是有顺序的,可以依据结构体成员的顺序给它们复制,而不用采用 key:value
的方式。
type Person struct {
Name string
Age int
}
func main() {
p := Person{"Tom", 18}
fmt.Println(p) // {Tom 18}
}
不过结构体的传递参数的方式,是值传递,因此如果需要修改结构体成员的值,需要使用指针。
func main() {
p := Person{"Tom", 18}
fmt.Println(p) // {Tom 18}
change(&p)
fmt.Println(p) // {Jerry 20}
}
func change(p *Person) {
p.Name = "Jerry"
p.Age = 20
}
数据解析
在 go 中,数据解析一般使用 encoding/json
包,它提供了对 JSON 数据的编码和解码功能。
编码:
json.Marshal()
。// 序列化 结构体=> json func Marshal(v interface{}) ([]byte, error)
解码:
json.Unmarshal()
。// 反序列化 json => 结构体 func Unmarshal(data []byte, v interface{}) error