GO语言基础

Huy大约 10 分钟框架ssr

从这一节起,入门一下 Go 语音。

golang
golang

基本数据类型

基本的数据类型如下:

  1. 布尔型(boolean): truefalse;
  2. 整型(integer):
    • 有符号:int(依据系统为 32/64 位)、int8int16int32int64;
    • 无符号:uint(依据系统为 32/64 位)、uint8uint16uint32uint64uintptr(无符号整型,用于存放指针);
  3. 浮点型(float): float32float64;
  4. 复数类型(complex): complex64complex128;
  5. 字符串类型:string, 字符串是不可变的字节序列;
  6. 字符类型(character):Go 语言没有专门的字符类型,但可以用 byte(表示单个字节的 ASCII 字符,等同于 uint8 的别名)或 rune(表示 Unicode 码点,等同于 int32)来表示字符。

和 js 类似,go 语音中用 var 定义一个变量; const 定义一个常量。

// 一般声明格式
var 变量名字 类型 =// 简短声明
变量名字 :=

数值的大小:

  1. int8 的范围是 -128 到 127,uint8 的范围是 0 到 255,即 2^8 有符号的情况下正负对半,0 占一数值,以此类推。
  2. float32 浮点数,精确到小数点后 7 位,32/2/2 - 1 正负各一半,再加上小数点的偏移量,所以精确到小数点后 7 位。; 以此类推, float64 浮点数,精确到小数点后 15 位(64/2/2 - 1)。
  3. 复数类型,complex64:由两个float32类型的值分别表示复数的实数部分和虚数部分。可通过 real()imag() 函数获取复数的实部和虚部。
  4. 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 切片之间存在一些微妙的区别。

  1. 空切片 []int{}:有一个指向底层数组的指针(即使这个数组是长度为零的数组)。通常用来表示明确的空集合,表示初始化了但没有元素。

  2. 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
}

复合数据类型

  1. 数组(array): 一组相同类型元素的集合,长度固定;
  2. 切片(slice): 动态数组,长度可变,底层是数组;
  3. 结构体(struct): 一组不同类型字段的集合,用于表示一个对象,大小固定;
  4. 映射(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)
  1. 创建了一个指向切片类型 []int 的指针 p
  2. 使用 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 值。

使用小技巧:

  1. 去除空值:

    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)。

  2. 切片复制:

    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]
    }
    
  3. 切片删除元素:

    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]
    }
    
  4. 切片截取:

    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 是引用类型,必须初始化才能使用。

  1. 声明:var m map[string] string

  2. 创建:

    // 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"
    
  3. 获取元素: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])
    }
    
  4. 删除元素:delete(m, "name")

  5. 判断元素是否存在:v, ok := m["name"]

结构体 struct

结构体是一种聚合数据类型,它是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。结构体成员可以是任意类型,包括内置类型、数组、指针、结构体、甚至是其他结构体。

  1. 声明:type Person struct { Name string; Age int }

  2. 创建:

    // 1. 使用字面量创建
    p := Person{"Tom", 18}
    
    // 2. 使用结构体字面量创建
    p := Person{Name: "Tom", Age: 18}
    
  3. 访问成员: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 数据的编码和解码功能。

  1. 编码:json.Marshal()

    // 序列化 结构体=> json
    func Marshal(v interface{}) ([]byte, error)
    
  2. 解码:json.Unmarshal()

    // 反序列化 json => 结构体
    func Unmarshal(data []byte, v interface{}) error
    
Loading...