Go 语言入门指南:基础语法和常用特性解析

Go语言学习

Go语言是一门由Google主导开发的静态类型、编译型、并发型的高级编程语言。Go语言在语言设计上注重简洁、高效和易用,具有先进的内存管理和并发控制机制,被广泛应用于Web服务、分布式系统、网络编程、云计算等领域。本文将介绍Go语言的基础语法和常用特性,包括变量和常量、数据类型、控制流语句、函数、defer、goroutine、channel、结构体、接口、包和错误处理。

基础语法

变量和常量

在 Go 语言中,变量可以使用 var 关键字定义,例如:

csharp 复制代码
var x int
x = 1

在上面的例子中,我们定义了一个整型变量 x,并将其赋值为 1。除此之外,我们还可以使用短声明语法来定义变量:

go 复制代码
y := 2

这里我们没有显式地指定变量的类型,而是让 Go 语言自动推断出它的类型。

另外,常量可以使用 const 关键字定义,例如:

ini 复制代码
const pi = 3.14159

数据类型

Go语言支持多种数据类型,包括基本类型(如整型、浮点型、布尔型、字符型)和复合类型(如数组、切片、映射、结构体、接口)下面是一些常用的数据类型及其取值范围:

数据类型 取值范围
int8 -128 到 127
uint8 0 到 255
int16 -32768 到 32767
uint16 0 到 65535
int32 -2147483648 到 2147483647
uint32 0 到 4294967295
int64 -9223372036854775808 到 9223372036854775807
uint64 0 到 18446744073709551615
float32 精度约为 6 位小数
float64 精度约为 15 位小数
bool true 或 false
string 任意 UTF-8 字符串
go 复制代码
var a [3]int = [3]int{1, 2, 3}
var b []int = []int{1, 2, 3}
var c map[string]int = map[string]int{"one": 1, "two": 2}
var d struct{ x, y int } = struct{ x, y int }{1, 2}
var e interface{} = 3.14

控制流语句

Go语言支持控制流语句,包括条件语句(if、switch)、循环语句(for、range)和跳转语句(break、continue、goto)。下面是一个例子:

go 复制代码
x := 10
if x > 0 {
    fmt.Println("x is positive")
} else if x == 0 {
    fmt.Println("x is zero")
} else {
    fmt.Println("x is negative")
}
​
for i := 0; i < 10; i++ {
    fmt.Println(i)
}
​
switch x {
case 1:
    fmt.Println("x is 1")
case 2:
    fmt.Println("x is 2")
default:
    fmt.Println("x is neither 1 nor 2")
}

在上面的代码中,我们使用了 if 判断语句、for 循环语句和 switch 选择语句。需要注意的是,在 Go 语言中,iffor 的条件语句可以省略括号,例如:

less 复制代码
if x > 0 {
    // ...
}
​
for i := 0; i < 10; i++ {
    // ...
}

数组

在Go语言中,数组是一种基本的数据类型,它由固定数量的相同类型元素组成,并且在声明时需要指定数组的长度。数组在Go语言中是值类型,可以进行复制和比较,但是数组的长度是不可变的,因此需要在声明时确定好数组的长度。Go语言中的数组支持下标访问和修改元素的值,并且支持多维数组的定义和使用。

下面是一个数组例子:

less 复制代码
func main() {
​
    var a [5]int
    a[4] = 100
    fmt.Println("get:", a[2])
    fmt.Println("len:", len(a))
​
    b := [5]int{1, 2, 3, 4, 5}
    fmt.Println(b)
​
    var twoD [2][3]int
    for i := 0; i < 2; i++ {
        for j := 0; j < 3; j++ {
            twoD[i][j] = i + j
        }
    }
    fmt.Println("2d: ", twoD)
}

这段代码演示了Go语言中的数组的基本用法。我们可以用var关键字声明数组并指定它的长度,例如在这个例子中,我们定义了一个长度为5的整型数组a。通过下标访问数组的元素,例如a[4] = 100表示将a数组的第5个元素赋值为100。Go语言中的数组是定长的,一旦定义了其长度,就不能更改。可以使用len()函数获取数组的长度。

另一种定义数组的方式是使用"数组字面量",即使用花括号{}和逗号分隔符指定数组的元素,例如在这个例子中,我们定义了一个长度为5的整型数组b,并将其元素初始化为1、2、3、4和5。这种方式可以用于创建任意长度的数组。

Go语言中还支持多维数组,例如在这个例子中,我们定义了一个2行3列的整型数组twoD。我们可以使用嵌套的for循环来遍历二维数组,并使用下标访问和修改数组元素的值。在这个例子中,我们将第i行第j列的元素设置为i+j

切片

在Go语言中,切片是一种动态数组,它可以根据需要自动增长或缩小大小,而不需要在声明时指定其长度。切片是对数组的抽象,它通过底层数组来实现其功能。切片是一个引用类型,它包含指向底层数组的指针、长度和容量。

切片的长度是指切片中元素的数量,而容量是指底层数组中可以容纳的元素的数量。在创建切片时,可以使用make()函数指定切片的长度和容量,也可以使用切片字面量创建切片。切片可以使用索引访问和修改其元素,也可以使用append()函数向切片中添加新的元素。当切片的长度超过其容量时,底层数组将会重新分配更大的空间,并将原来的元素复制到新的空间中。

go 复制代码
​
func main() {
​
    s := make([]string, 3)
    s[0] = "a"
    s[1] = "b"
    s[2] = "c"
    fmt.Println("get:", s[2])   // c
    fmt.Println("len:", len(s)) // 3
​
    s = append(s, "d")
    s = append(s, "e", "f")
    fmt.Println(s) // [a b c d e f]
​
    c := make([]string, len(s))
    copy(c, s)
    fmt.Println(c) // [a b c d e f]
​
    fmt.Println(s[2:5]) // [c d e]
    fmt.Println(s[:5])  // [a b c d e]
    fmt.Println(s[2:])  // [c d e f]
​
    good := []string{"g", "o", "o", "d"}
    fmt.Println(good) // [g o o d]
}

这段代码展示了Go语言中切片的基本用法。切片是一种动态数组,它可以按需增长或缩小,而不需要事先声明其长度。在这个例子中,我们使用make()函数创建了一个长度为3的字符串切片s,并给其赋值为a、b、c。我们可以使用下标访问切片的元素并修改其值,也可以使用len()函数获取切片的长度。

Go语言中的切片支持使用append()函数在切片的末尾添加新的元素,这也是切片与数组的一个重要区别。在这个例子中,我们使用append()函数向切片s中添加了d、e和f这三个元素。同样地,我们也可以一次添加多个元素,例如在这个例子中,我们使用append()函数一次添加了e和f。

为了复制切片的值,我们可以使用copy()函数。在这个例子中,我们使用copy()函数将切片s的值复制到了一个新的切片c中,并打印出了这个新切片的值。

Go语言中的切片支持使用切片表达式来访问切片的子集。在这个例子中,我们使用s[2:5]获取切片s中索引从2到5的元素(不包括索引为5的元素),使用s[:5]获取切片s中前5个元素,使用s[2:]获取切片s中从索引2开始的所有元素。

map集合

在Go语言中,map是一种无序的键值对集合,它的键必须是唯一的,可以是任意类型,但必须支持相等运算符。map可以使用make()函数创建,使用键值对语法添加键值对,使用下标访问和修改元素。检查map中是否存在某个键时,可以使用两个返回值的形式来检查。删除map中的键值对可以使用delete()函数。map是Go语言中实用的数据结构之一,可以用于存储无序的键值对集合。

go 复制代码
func main() {
    m := make(map[string]int)
    m["one"] = 1
    m["two"] = 2
    fmt.Println(m)           // map[one:1 two:2]
    fmt.Println(len(m))      // 2
    fmt.Println(m["one"])    // 1
    fmt.Println(m["unknow"]) // 0
​
    r, ok := m["unknow"]
    fmt.Println(r, ok) // 0 false
​
    delete(m, "one")
​
    m2 := map[string]int{"one": 1, "two": 2}
    var m3 = map[string]int{"one": 1, "two": 2}
    fmt.Println(m2, m3)
}

使用make()函数可以创建一个空的map,例如make(map[string]int)可以创建一个字符串到整数的map。我们可以使用键值对语法将键值对添加到map中,例如m["one"] = 1可以将键值对"one": 1添加到map中。我们可以使用len()函数获取map的长度,也可以使用下标访问map中的元素并修改其值,如果访问不存在的键,则返回默认值0。

如果我们需要检查map中是否存在某个键,可以使用两个返回值的形式来检查。如果map中存在该键,则返回其对应的值和true,否则返回默认值0和false。我们也可以使用delete()函数从map中删除键值对。

range关键字

在Go语言中,range关键字可以用于遍历数组、切片、字符串、map等数据结构中的元素。range关键字返回两个值,第一个值是当前元素的索引或键,第二个值是当前元素的值。使用range关键字可以简化遍历数据结构的代码,提高代码的可读性和可维护性。

go 复制代码
func main() {
    nums := []int{2, 3, 4}
    sum := 0
    for i, num := range nums {
        sum += num
        if num == 2 {
            fmt.Println("index:", i, "num:", num) // index: 0 num: 2
        }
    }
    fmt.Println(sum) // 9
​
    m := map[string]string{"a": "A", "b": "B"}
    for k, v := range m {
        fmt.Println(k, v) // b 8; a A
    }
    for k := range m {
        fmt.Println("key", k) // key a; key b
    }
}

在这个例子中,我们使用for循环和range关键字遍历了一个整数切片和一个字符串到字符串的map。在遍历整数切片时,我们使用了两个变量i和num来分别获取当前元素的索引和值,并将所有元素的值求和。在遍历字符串到字符串的map时,我们使用两个变量k和v分别获取当前键和值,并将它们打印出来。我们还展示了如何使用for循环和range关键字遍历map的键,只需要省略第二个变量即可。

函数

在Go语言中,函数是一个封装了一段代码的独立单元,可以在需要的时候被调用。函数可以有多个参数和返回值,也可以使用可变参数和匿名函数等高级特性。函数的定义以关键字func开头,后面跟着函数名和参数列表,函数体中的代码可以使用参数来完成一些操作,并通过return语句返回结果。函数是Go语言中一个非常重要的概念,可以用于封装一些常用的代码,提高代码的可复用性和可维护性。

go 复制代码
func add(a int, b int) int {
    return a + b
}
​
func add2(a, b int) int {
    return a + b
}
​
func exists(m map[string]string, k string) (v string, ok bool) {
    v, ok = m[k]
    return v, ok
}
​
func main() {
    res := add(1, 2)
    fmt.Println(res) // 3
​
    v, ok := exists(map[string]string{"a": "A"}, "a")
    fmt.Println(v, ok) // A True
}

在第一个函数add()中,我们使用了两个参数a和b来表示要相加的两个整数,使用int类型来表示参数的类型,并在函数体中使用加法运算符计算两个整数的和,最后使用return语句返回结果。

在第二个函数add2()中,我们可以看到函数参数的简化形式,即省略了每个参数的类型声明。这是因为Go语言中的编译器可以根据参数的值自动推断参数的类型。这种简化形式可以提高代码的可读性和可维护性。

在第三个函数exists()中,我们定义了两个返回值v和ok,用于返回map中对应键的值和是否存在。在函数体中,我们使用了两个参数m和k,分别表示要查找的map和键。我们使用m[k]的形式来获取map中对应键的值,如果键存在,则ok的值为true,否则为false。

在main()函数中,我们调用了add()和exists()函数,并使用fmt包中的Println()函数将结果打印出来。在调用exists()函数时,我们使用了两个返回值的形式来获取map中对应键的值和是否存在。

point

Go语言中函数参数的传递方式包括传值和传指针两种方式。传值适用于简单类型的数据,传指针适用于复杂类型的数据。使用指针参数可以避免复制大量数据,提高代码效率,同时可以修改原始变量的值,使得函数更加灵活和可重用。

go 复制代码
func add2(n int) {
    n += 2
}
​
func add2ptr(n *int) {
    *n += 2
}
​
func main() {
    n := 5
    add2(n)
    fmt.Println(n) // 5
    add2ptr(&n)
    fmt.Println(n) // 7
}

在第一个函数add2()中,我们使用了一个普通参数n,表示要加2的整数。在函数体中,我们对n进行了加2的操作,但由于n是一个普通参数,函数内部的修改不会影响到函数外部的变量。因此,在函数调用结束后,外部变量n的值仍然是5。

在第二个函数add2ptr()中,我们使用了一个指针参数n,表示要加2的整数的指针。在函数体中,我们通过*n的形式来获取指针指向的变量的值,并对其进行加2的操作。由于n是一个指针参数,函数内部的修改会直接影响到函数外部指针指向的变量。因此,在函数调用结束后,外部变量n的值变成了7。

在main()函数中,我们分别调用了add2()和add2ptr()函数,并使用fmt包中的Println()函数将结果打印出来。我们可以看到,在调用add2()函数后,外部变量n的值没有变化,而在调用add2ptr()函数后,外部变量n的值变成了7。

struct

Go语言中结构体是一种用户自定义的数据类型,用于封装多个字段,表示一个复杂的数据结构。通过结构体可以将相应的字段进行组合和封装,使得程序的结构更加清晰和易于维护。结构体支持点运算符和指针访问,还支持方法,可以将操作和结构体绑定在一起,提高程序的可读性和可维护性。

go 复制代码
type user struct {
    name     string
    password string
}
​
func main() {
    a := user{name: "wang", password: "1024"}
    b := user{"wang", "1024"}
    c := user{name: "wang"}
    c.password = "1024"
    var d user
    d.name = "wang"
    d.password = "1024"
​
    fmt.Println(a, b, c, d)                 // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
    fmt.Println(checkPassword(a, "haha"))   // false
    fmt.Println(checkPassword2(&a, "haha")) // false
}
​
func checkPassword(u user, password string) bool {
    return u.password == password
}
​
func checkPassword2(u *user, password string) bool {
    return u.password == password
}

在这个例子中,我们定义了一个名为user的结构体,包含了两个字段name和password,分别表示用户的姓名和密码。然后在main()函数中,我们创建了四个user类型的变量a、b、c、d,分别使用了不同的赋值方式来初始化它们的值。其中,变量a和变量b分别使用了结构体字面值和结构体字段初始化的方式来创建。变量c使用了结构体字段初始化的方式来创建,并在之后通过赋值语句来修改password字段的值。变量d使用了结构体零值初始化的方式来创建,并在之后通过赋值语句来初始化它的值。

在checkPassword()函数中,我们使用了一个普通参数u和一个字符串参数password,分别表示需要检查密码的用户和密码。在函数体中,我们比较了参数u的password字段和字符串参数password的值,如果相等则返回true,否则返回false。在checkPassword2()函数中,我们使用了一个指针参数u和一个字符串参数password,分别表示需要检查密码的用户的指针和密码。在函数体中,我们通过*u的形式来获取指针指向的变量的值,然后比较了其password字段和字符串参数password的值,如果相等则返回true,否则返回false。

在main()函数中,我们分别调用了checkPassword()和checkPassword2()函数,并使用fmt包中的Println()函数将结果打印出来。我们可以看到,在调用checkPassword()函数和checkPassword2()函数时,传递的参数类型分别是普通参数和指针参数。在checkPassword2()函数中,我们使用了指针参数来传递user类型的变量,这样可以直接访问原始变量的值,而不需要对变量进行复制。

struct-method

在Go语言中,结构体方法是一种特殊类型的函数,可以与结构体类型相关联。结构体方法为结构体类型提供了一种自定义行为的方式,使得程序更加直观和易于理解。结构体方法的调用方式比较特殊,需要使用点运算符来访问结构体变量的方法。熟练掌握结构体方法的定义和使用对于编写高效、可读性强的代码至关重要。

go 复制代码
type user struct {
    name     string
    password string
}
​
func (u user) checkPassword(password string) bool {
    return u.password == password
}
​
func (u *user) resetPassword(password string) {
    u.password = password
}
​
func main() {
    a := user{name: "wang", password: "1024"}
    a.resetPassword("2048")
    fmt.Println(a.checkPassword("2048")) // true
}

在这个例子中,我们定义了一个名为user的结构体,包含了两个字段name和password,分别表示用户的姓名和密码。然后,我们定义了两个结构体方法checkPassword()和resetPassword(),分别用于检查用户的密码和重置用户的密码。checkPassword()方法使用了值类型接收器,用于检查用户的密码是否匹配,而resetPassword()方法使用了指针类型接收器,用于修改用户的密码。

在main()函数中,我们创建了一个名为a的user类型的变量,并使用结构体字面值初始化它的值。然后,我们调用了a的resetPassword()方法,将其密码从"1024"重置为"2048"。接着,我们调用了a的checkPassword()方法,检查其密码是否为"2048",并将结果打印出来。因为我们已经将其密码重置为"2048",所以checkPassword()方法返回了true,因此程序输出了true。

我们可以看到结构体方法的定义和使用非常简单和直观。通过在方法名前面加上接收器,并指定接收器的类型,就可以将方法与结构体类型关联起来,成为结构体类型的一部分。通过使用点运算符来访问结构体变量的方法,我们可以直接在结构体变量上调用相关的方法,而不需要传递结构体变量作为参数,这样可以使代码更加紧凑和易于理解。

error

在Go语言中,错误处理是一种重要的机制。函数通常会返回一个error类型的值来表示错误的类型和详细信息。我们可以通过检查函数返回的error值来判断函数执行过程中是否出现了错误,以及错误的类型和原因。熟练掌握error类型的定义和使用对于编写高效、可读性强的代码至关重要。

go 复制代码
type user struct {
    name     string
    password string
}
​
func findUser(users []user, name string) (v *user, err error) {
    for _, u := range users {
        if u.name == name {
            return &u, nil
        }
    }
    return nil, errors.New("not found")
}
​
func main() {
    u, err := findUser([]user{{"wang", "1024"}}, "wang")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(u.name) // wang
​
    if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
        fmt.Println(err) // not found
        return
    } else {
        fmt.Println(u.name)
    }
}

在这个例子中,我们定义了一个名为user的结构体,包含了两个字段name和password,分别表示用户的姓名和密码。然后,我们定义了一个findUser()函数,用于查找指定姓名的用户。该函数接受一个用户列表和一个姓名作为参数,返回一个指向用户的指针和一个error类型的值。

在findUser()函数中,我们使用了for循环和if语句来遍历用户列表,并查找指定姓名的用户。如果找到了用户,函数会返回该用户的指针和一个nil的error值;否则,函数会返回一个nil的指针和一个表示错误的error值。在这个例子中,我们使用了errors.New()函数来创建一个新的error值,用于表示用户未找到的错误。

在main()函数中,我们分别调用了findUser()函数来查找一个已存在的用户和一个不存在的用户。在第一个调用中,我们传递了一个包含一个用户的列表和一个已存在的姓名,函数返回了该用户的指针和一个nil的error值,我们可以通过指针访问该用户的字段,例如u.name。在第二个调用中,我们传递了一个包含一个用户的列表和一个不存在的姓名,函数返回了一个nil的指针和一个表示错误的error值,我们可以通过判断error值来处理该错误,例如打印错误信息。

我们可以看到在Go语言中如何使用错误处理机制来处理函数执行过程中可能出现的错误。通过返回一个error类型的值,我们可以表示函数执行过程中是否出现了错误,以及错误的类型和原因。通过检查函数返回的error值,我们可以采取相应的措施来处理错误

string

在Go语言中,字符串是一个不可变的序列类型,用于表示文本数据。字符串支持比较、连接、切片等操作,并提供了一些内置的函数和方法,用于字符串的处理和转换。字符串是一个重要的数据类型,熟练掌握它的使用对于编写高效、可读性强的代码至关重要。

scss 复制代码
func main() {
    a := "hello"
    fmt.Println(strings.Contains(a, "ll"))                // true
    fmt.Println(strings.Count(a, "l"))                    // 2
    fmt.Println(strings.HasPrefix(a, "he"))               // true
    fmt.Println(strings.HasSuffix(a, "llo"))              // true
    fmt.Println(strings.Index(a, "ll"))                   // 2
    fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
    fmt.Println(strings.Repeat(a, 2))                     // hellohello
    fmt.Println(strings.Replace(a, "e", "E", -1))         // hEllo
    fmt.Println(strings.Split("a-b-c", "-"))              // [a b c]
    fmt.Println(strings.ToLower(a))                       // hello
    fmt.Println(strings.ToUpper(a))                       // HELLO
    fmt.Println(len(a))                                   // 5
    b := "你好"
    fmt.Println(len(b)) // 6
}

首先,我们定义了一个字符串a,其值为"hello"。然后,我们使用strings包中的Contains()函数来判断字符串a是否包含子串"ll",使用Count()函数来统计字符串a中子串"l"出现的次数,使用HasPrefix()函数来判断字符串a是否以子串"he"开头,使用HasSuffix()函数来判断字符串a是否以子串"llo"结尾,使用Index()函数来查找子串"ll"在字符串a中第一次出现的位置。

接下来,我们使用Join()函数将一个字符串数组[]string{"he", "llo"}连接成一个新的字符串,使用Repeat()函数将字符串a重复两次,使用Replace()函数将字符串a中的字母"e"替换为字母"E",使用Split()函数将字符串"a-b-c"按照分隔符"-"进行分割,返回一个字符串数组。

然后,我们使用ToLower()函数将字符串a中的所有字母转换为小写,使用ToUpper()函数将字符串a中的所有字母转换为大写,使用len()函数获取字符串a和另一个字符串b的长度。

json

在Go语言中,可以使用encoding/json包来处理JSON数据,包括将Go语言的数据结构转换为JSON格式的数据,以及将JSON格式的数据转换为Go语言的数据结构。熟练掌握JSON的使用可以帮助我们更加高效地处理数据,实现不同系统之间的数据交换。

go 复制代码
type userInfo struct {
    Name  string
    Age   int `json:"age"`
    Hobby []string
}
​
func main() {
    a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
    buf, err := json.Marshal(a)
    if err != nil {
        panic(err)
    }
    fmt.Println(buf)         // [123 34 78 97...]
    fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
​
    buf, err = json.MarshalIndent(a, "", "\t")
    if err != nil {
        panic(err)
    }
    fmt.Println(string(buf))
​
    var b userInfo
    err = json.Unmarshal(buf, &b)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}

首先,我们定义了一个名为userInfo的结构体,包含了三个字段Name、Age和Hobby。其中,Age字段使用了json标签指定了在转换为JSON格式时使用的字段名。然后,我们创建了一个userInfo类型的实例a,并使用json.Marshal()函数将其转换为JSON格式的数据,返回一个字节数组buf。如果转换出现错误,将会抛出一个异常。我们可以使用fmt.Println()函数将buf输出到控制台上,也可以使用string()函数将其转换为一个字符串输出。需要注意的是,JSON格式的数据在Go语言中是以字节数组的形式存储的。

接下来,我们使用json.MarshalIndent()函数将userInfo类型的实例a转换为格式化后的JSON格式的数据,并将其赋值给buf变量。该函数的第一个参数是需要转换的数据,第二个参数是行前缀,第三个参数是每个缩进级别的缩进字符串。我们可以使用fmt.Println()函数将buf输出到控制台上,也可以使用string()函数将其转换为一个字符串输出。格式化后的JSON数据更加易于阅读和理解。

然后,我们使用json.Unmarshal()函数将buf中的JSON格式的数据解析为一个userInfo类型的实例b,并将其赋值给b变量。需要注意的是,Unmarshal()函数的第一个参数是一个字节数组,第二个参数是一个指向userInfo类型的指针,用于接收解析后的数据。如果解析出现错误,将会抛出一个异常。我们可以使用fmt.Printf()函数将b输出到控制台上,使用%#v格式化输出,以便查看结构体的详细信息。

time

在Go语言中,time包提供了一组函数和类型,用于处理时间和日期。该包支持多种时间格式和时区,可以方便地进行时间的计算、比较、格式化等操作。熟练掌握这些函数和类型的使用可以帮助我们更加高效地处理时间和日期相关的问题。

scss 复制代码
func main() {
    now := time.Now()
    fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
    t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
    t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
    fmt.Println(t)                                                  // 2022-03-27 01:25:36 +0000 UTC
    fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
    fmt.Println(t.Format("2006-01-02 15:04:05"))                    // 2022-03-27 01:25:36
    diff := t2.Sub(t)
    fmt.Println(diff)                           // 1h5m0s
    fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
    t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
    if err != nil {
        panic(err)
    }
    fmt.Println(t3 == t)    // true
    fmt.Println(now.Unix()) // 1648738080
}

首先,我们使用time.Now()函数获取了当前时间now,并使用fmt.Println()函数将其输出到控制台上。该函数返回的时间是一个time.Time类型的值,表示一个具体的时间点。输出的时间包括年、月、日、小时、分钟、秒、纳秒和时区等信息。

接下来,我们使用time.Date()函数创建了两个自定义的时间t和t2,分别表示2022年3月27日1时25分36秒和2时30分36秒。我们可以使用fmt.Println()函数将t输出到控制台上,并使用t.Year()、t.Month()、t.Day()、t.Hour()、t.Minute()等方法获取时间的具体信息。需要注意的是,该函数的参数依次为年、月、日、时、分、秒、纳秒和时区。

然后,我们使用t2.Sub(t)函数计算了t2和t之间的时间差,并将结果输出到控制台上。该函数返回的结果是一个time.Duration类型的值,表示两个时间点之间的时间段。我们可以使用diff.Minutes()和diff.Seconds()等方法获取时间段的具体信息。

接着,我们使用time.Parse()函数将一个字符串解析为时间,并将其赋值给t3变量。该函数的第一个参数是时间的格式,第二个参数是需要解析的字符串。如果解析出现错误,将会抛出一个异常。在这个例子中,我们将"2022-03-27 01:25:36"解析为时间,并判断其是否等于t。

最后,我们使用now.Unix()函数将当前时间转换为Unix时间戳,并使用fmt.Println()函数将其输出到控制台上。Unix时间戳是指从1970年1月1日00:00:00 UTC到指定时间的秒数,是计算机系统中常用的时间表示方式之一。

strconv

在Go语言中,strconv包提供了一组函数,用于将字符串和基本数据类型之间进行转换。该包支持多种进制、格式和错误处理方式,可以方便地进行字符串和基本数据类型之间的转换。熟练掌握这些函数的使用可以帮助我们更加高效地进行字符串和基本数据类型之间的转换。

css 复制代码
func main() {
    f, _ := strconv.ParseFloat("1.234", 64)
    fmt.Println(f) // 1.234
​
    n, _ := strconv.ParseInt("111", 10, 64)
    fmt.Println(n) // 111
​
    n, _ = strconv.ParseInt("0x1000", 0, 64)
    fmt.Println(n) // 4096
​
    n2, _ := strconv.Atoi("123")
    fmt.Println(n2) // 123
​
    n2, err := strconv.Atoi("AAA")
    fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}

首先,我们使用strconv.ParseFloat()函数将字符串"1.234"转换为float64类型的值,并将其赋值给f变量。该函数的第一个参数是需要转换的字符串,第二个参数是返回值的位数,可以使用64表示float64类型。在这个例子中,我们使用fmt.Println()函数将f输出到控制台上。

接着,我们使用strconv.ParseInt()函数将字符串"111"转换为十进制表示的int64类型的值,并将其赋值给n变量。该函数的第一个参数是需要转换的字符串,第二个参数是指定的进制,可以使用10表示十进制,第三个参数是返回值的位数,可以使用64表示int64类型。我们可以使用fmt.Println()函数将n输出到控制台上。

然后,我们使用strconv.ParseInt()函数将字符串"0x1000"转换为十六进制表示的int64类型的值,并将其赋值给n变量。在这个例子中,我们使用0作为第二个参数,表示自动识别字符串的进制。我们可以使用fmt.Println()函数将n输出到控制台上。

接着,我们使用strconv.Atoi()函数将字符串"123"转换为int类型的值,并将其赋值给n2变量。在这个例子中,由于字符串中只包含数字字符,因此转换成功,我们可以使用fmt.Println()函数将n2输出到控制台上。

最后,我们使用strconv.Atoi()函数将字符串"AAA"转换为int类型的值,并将其赋值给n2变量。由于字符串中包含非数字字符,因此转换失败,函数将返回一个错误,并将n2赋值为0。我们可以使用fmt.Println()函数将n2和错误信息输出到控制台上。

常用特性

除了基础语法,Go 语言还具有一些常用的特性,下面我们来介绍一下。

defer

defer 语句可以用来延迟函数的执行,直到外层函数返回前才执行。这个特性可以用来释放资源、关闭文件等操作。下面是一个例子:

go 复制代码
func main() {
    file, err := os.Open("test.txt")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer file.Close()
​
    // ...
}

在上面的例子中,我们打开了一个文件,并在 defer 语句中调用了 file.Close() 函数,这样即使在函数中出现了错误,文件也会被正确关闭。

goroutine

Go 语言内置了轻量级线程------goroutine,可以在程序中方便地实现并发。下面是一个例子:

css 复制代码
func main() {
    go count("sheep")
    go count("fish")
​
    time.Sleep(time.Second * 2)
}
​
func count(name string) {
    for i := 1; i <= 5; i++ {
        fmt.Println(i, name)
        time.Sleep(time.Millisecond * 500)
    }
}

在上面的例子中,我们定义了一个 count 函数,它接受一个字符串参数,并在循环中打印数字和字符串。然后我们在 main 函数中用 go 关键字启动了两个 count 函数的 goroutine,并使用 time.Sleep() 函数让程序睡眠 2 秒钟,以便观察输出。

channel

goroutine 之间的通信可以使用 channel 来实现。channel 是一种类型,类似于队列,支持发送和接收操作。下面是一个例子:

go 复制代码
func main() {
    ch := make(chan int)
​
    go sum(1, 2, ch)
    go sum(3, 4, ch)
​
    x := <-ch
    y := <-ch
​
    fmt.Println(x + y)
}
​
func sum(x, y int, ch chan int) {
    time.Sleep(time.Millisecond * 500)
    ch <- x + y
}

在上面的例子中,我们定义了一个 sum 函数,它接受两个整型参数,并将它们的和发送到一个 channel 中。然后我们在 main 函数中启动了两个 sum 函数的 goroutine,每个 goroutine 都将结果发送到同一个 channel 中。最后我们从 channel 中接收两个结果,并将它们相加输出。

总结

Go语言是由Google主导开发的静态类型、编译型、并发型的高级编程语言,注重简洁、高效和易用,具有先进的内存管理和并发控制机制,被广泛应用于Web服务、分布式系统、网络编程、云计算等领域。

我们还探讨了Go语言的变量和常量、数据类型、控制流语句、数组和切片等基础语法,以及函数、defer、goroutine、channel、结构体、接口、包和错误处理等常用特性。在这些内容中,特别介绍了Go语言的切片特性,包括动态增长或缩小大小、引用类型、长度和容量、make()函数、append()函数、copy()函数等。

相关推荐
CallBack8 个月前
Typora+PicGo+阿里云OSS搭建个人图床,纵享丝滑!
前端·青训营笔记
Taonce1 年前
站在Android开发者的角度认识MQTT - 源码篇
android·青训营笔记
AB_IN1 年前
打开抖音会发生什么 | 青训营
青训营笔记
monster1231 年前
结营感受(go) | 青训营
青训营笔记
翼同学1 年前
实践记录:使用Bcrypt进行密码安全性保护和验证 | 青训营
青训营笔记
hu1hu_1 年前
Git 的正确使用姿势与最佳实践(1) | 青训营
青训营笔记
星曈1 年前
详解前端框架中的设计模式 | 青训营
青训营笔记
tuxiaobei1 年前
文件上传漏洞 Upload-lab 实践(中)| 青训营
青训营笔记
yibao1 年前
高质量编程与性能调优实战 | 青训营
青训营笔记
小金先生SG1 年前
阿里云对象存储OSS使用| 青训营
青训营笔记