第一节:Go语言快速上手-基础语言

56 阅读12分钟

Go语言快速上手-基础语言

1. 什么是GO语言

  1. 高性能、高并发
  2. 语法简单、学习曲线平缓
    • 快速上手
  3. 丰富的标准库
    • 稳定性
  4. 完善的工具链
    • 编译、代码格式化、包管理、单元测试等测试
  5. 静态链接
    • 可以快速运行,部署非常简单
  6. 快速编译
    • 编译时间很快->修改一行一秒启动
  7. 跨平台
    • 很多奇怪的东西上运行
  8. 垃圾回收
    • 和java类似,无需考虑内存的释放

快速上手: 实现一个简单的http服务器

package main

import (
	"net/http"
)

func main() {
	http.Handle("/", http.FileServer(http.Dir(".")))
	http.ListenAndServe(":8080", null)
}

2. 为什么是Go

1.最初使用的 Python,由于性能问题换成了Go2.C++不太适合在线Web业务 3.早期团队非Java 背景 4.性能比较好 5.部署简单、学习成本低 6.内部 RPC和HTTP框架的推广

3. GO入门

3.1 Go

配置Go的开发环境 已下载,略

3.2 HelloWorld

讲解略

package main  
  
import (  
"fmt"  
)  
  
func main() {  
fmt.Println("helloword")  
}

3.3 变量&常量

和C/C++类似 变量是程序运行时可以改变其值的存储位置,而常量则是程序运行时不可更改的值。

package main  
  
import (  
"fmt"  
"math"  
)  
  
func main() {  
var a = "initial"  //声明方法一
var b, c int = 1, 2  
var d = true  
var e float64  
f := float32(e)  //声明方法二
g := a + "foo"  
fmt.Println(a, b, c, d, e, f)  
//initial 1 2 true 0 0
fmt.Println(g)  
const s string = "constant"  
const h = 50000000000  //常量定义
const l = 3e20 / h  
fmt.Println(s, h, l, math.Sin(h), math.Sin(l))  
}

3.4 基础语法 if-else

Go语言中的if-else语句与其他编程语言类似,用于根据条件执行不同的代码块。if-else语句的基本语法如下:

if condition {
    // code to execute if condition is true
} else {
    // code to execute if condition is false
}
package main

import "fmt"

func main() {
	if 7%2 == 0 {
		fmt.Println("7 is even")
	} else {
		fmt.Println("7 is odd")
	}
	if 8%4 == 0 {
		fmt.Println("8 is divisible by 4")
	}
	if num := 9; num < 0 {
		fmt.Println(num, "is negative")
	} else if num < 10 {
		fmt.Println(num, "has 1 digit")
	} else {
		fmt.Println(num, "has multiple digits")
	}
}

3.5 基础语法-循环

for for循环结构,用于重复执行一段代码块。for循环的基本语法如下:

for initialization; condition; post {
    // code to be executed
}

其中,initialization是循环变量的初始值;condition是循环条件,当条件为真时继续执行循环;post是每次循环后更新循环变量的操作。

package main

import "fmt"

func main() {

	i := 1
	for {
		fmt.Println("loop")
		break
	}
	for j := 7; j < 9; j++ {
		fmt.Println(j)
	}

	for n := 0; n < 5; n++ {
		if n%2 == 0 {
			continue
		}
		fmt.Println(n)
	}
	for i <= 3 {
		fmt.Println(i)
		i = i + 1
	}
}

3.6 switch分支

switch语句用于根据不同的条件执行不同的代码块。switch语句的基本语法如下:

switch expression {
case value1:
    // code to be executed if expression equals value1
case value2:
    // code to be executed if expression equals value2
...
default:
    // code to be executed if none of the above cases are true
}

其中,expression是需要进行比较的表达式,value1value2等是需要比较的值。如果expression等于某个值,则执行对应的代码块;如果没有任何一个值匹配,则执行default代码块。

package main

import (
	"fmt"
	"time"
)

func main() {

	a := 2
	switch a {
	case 1:
		fmt.Println("one")
	case 2:
		fmt.Println("two")
	case 3:
		fmt.Println("three")
	case 4, 5:
		fmt.Println("four or five")
	default:
		fmt.Println("other")
	}

	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("It's before noon")
	default:
		fmt.Println("It's after noon")
	}
}

3.7 数组

个人感觉像是java和C++的结合体 数组是一种固定长度、存储相同类型元素的数据结构。数组的定义方式如下:

var arr [n]T

其中,n表示数组的长度,T表示数组中元素的类型。例如,定义一个长度为5、元素类型为int的数组可以写成:

package main

import "fmt"

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

3.7.2 切片

感觉像java中的list和python数组的整合 切片(slice)是一种动态长度的数据结构,类似于动态数组。切片由三个部分组成:指向底层数组的指针、切片长度和切片容量。切片的定义方式如下:

var slice []T

其中,T表示切片中元素的类型。

package main  
  
import "fmt"  
  
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]  
}

2.8map

map是一种无序的键值对集合,类似于其他语言中的字典或哈希表。map中的键和值可以是任意类型,但需要满足以下条件:

  • 键必须支持相等运算符(==、!=),例如基本类型、字符串、指针等。
  • 值可以是任意类型。

map的定义方式如下:

var m map[keyType]valueType

其中,keyType表示键的类型,valueType表示值的类型。

package main

import "fmt"

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

在遍历map时,元素的顺序是随机的。如果需要按照特定顺序遍历map,则需要先将key排序或者使用其他数据结构

3.9 函数

package main

import "fmt"

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
}

3.10 指针

初步支持,常用于对常用的数修改 指针是一种特殊的变量,它存储了一个变量的内存地址。指针变量的类型为*T,其中T表示指向的变量的类型。例如:

var p *int // 定义一个int类型的指针变量p

要获取一个变量的地址,可以使用取地址操作符(&)。例如:

var a int = 10
p = &a // 将a的地址赋值给p

要访问指针所指向的变量,可以使用解引用操作符(* )。例如:

fmt.Println(*p) // 输出10

在Go语言中,不支持指针运算和“->”操作符。

package main

import "fmt"

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
}

3.11 结构体

结构体是一种用户自定义的数据类型,它由一系列具有相同或不同类型的数据字段组成。结构体可以用来表示复杂的数据结构,例如一个人的信息、一本书的属性等等。

定义一个结构体可以使用type关键字和struct关键字。例如:

type Person struct {
    name string
    age int
}

上面的代码定义了一个名为Person的结构体,它有两个字段:nameage,分别表示人的姓名和年龄。

要创建一个结构体变量,可以使用以下语法:

var p Person // 定义一个Person类型的变量p
p.name = "Tom"
p.age = 18

也可以在定义时直接初始化:

var p Person = Person{"Tom", 18}

或者使用键值对初始化:

var p Person = Person{name: "Tom", age: 18}

访问结构体字段可以使用.操作符。例如:

fmt.Println(p.name, p.age) // 输出Tom 18

在Go语言中,还支持匿名结构体和嵌套结构体。匿名结构体没有类型名称,只能用于临时场景。嵌套结构体则是将一个结构体作为另一个结构体的字段。例如:

// 匿名结构体
var p struct {
    name string
    age int
} = struct {
    name string
    age int
}{name: "Tom", age: 18}

// 嵌套结构体
type Address struct {
    city string
    street string
}

type Person struct {
    name string
    age int
    address Address // 嵌套结构体作为字段
}

var p Person = Person{
    name: "Tom",
    age: 18,
    address: Address{
        city: "Beijing",
        street: "Chaoyang Road",
    },
}
package main

import "fmt"

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
}

3.12 结构体方法

结构体可以定义方法。方法是一种与特定类型关联的函数,它可以访问该类型的数据。定义方法的语法如下:

func (receiver Type) methodName(parameters) (results) {
    // 方法体
}

其中,receiver表示方法所属的类型,可以是结构体类型或非结构体类型;Type表示接收者的类型;methodName表示方法名;parameters表示参数列表;results表示返回值列表。

例如,下面定义了一个名为Person的结构体,并为其定义了一个名为SayHello()的方法:

type Person struct {
    name string
    age int
}

func (p Person) SayHello() {
    fmt.Printf("Hello, my name is %s, I'm %d years old.\n", p.name, p.age)
}

在上面的代码中,(p Person)就是接收者,表示该方法属于Person类型。在方法内部,可以通过接收者访问该类型的数据。

要调用结构体方法,需要先创建一个结构体变量,并通过该变量调用相应的方法。例如:

var p Person = Person{name: "Tom", age: 18}
p.SayHello() // 输出:Hello, my name is Tom, I'm 18 years old.

在调用时,不需要显式地传递接收者参数,Go语言会自动将调用者作为接收者传递给方法。

除了普通方法外,在Go语言中还支持指针接收者方法。指针接收者方法可以修改接收者的值,而普通方法只能读取接收者的值。指针接收者方法的定义方式与普通方法类似,只是在接收者类型前加上*符号。例如:

func (p *Person) SetName(name string) {
    p.name = name
}

在上面的代码中,(p *Person)就是指针接收者,表示该方法属于`*Person

package main

import "fmt"

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
}

3.13 错误处理

在Go语言中,错误处理是编程的重要方面。当一个函数遇到错误时,它可以返回一个错误值给调用者。调用者可以检查返回的值是否是一个错误,并相应地处理它。Go语言提供了一些内置函数和约定来帮助处理错误,例如使用error类型来表示错误,使用defer关键字来确保资源释放等

package main

import (
	"errors"
	"fmt"
)

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结构体,包含用户名和密码两个字段。
  • 然后定义了一个findUser函数,该函数接收一个user类型的切片和一个用户名作为参数,返回找到的用户指针和错误信息。在函数内部,使用循环遍历切片中的每个用户,如果找到了与传入的用户名相同的用户,则返回该用户指针和空错误;否则返回空指针和自定义错误信息。
  • 在主函数中,首先调用findUser函数查找名为"wang"的用户,并将结果赋值给变量uerr。如果查找失败,则打印错误信息并退出程序;否则打印找到的用户的名字。
  • 接着,在同一行中使用if语句调用了另一个查找用户的函数,并将结果赋值给变量u和err。如果查找失败,则打印错误信息并退出程序;否则打印找到的用户的名字。
  • 需要注意的是,在第二次调用findUser时使用了短变量声明(:=)来声明u和err变量,这意味着它们只在if语句块中有效。因此,在if语句块外部无法访问这些变量。

3.14 字符串操作

GO语言中字符串的常用操作: 常用的字符串操作函数有:

  1. len(str):返回字符串的长度。
  2. strings.Contains(str, substr):判断字符串str是否包含子串substr,返回bool类型。
  3. strings.Index(str, substr):返回子串substr在字符串str中第一次出现的位置,如果没有找到则返回-1。
  4. strings.LastIndex(str, substr):返回子串substr在字符串str中最后一次出现的位置,如果没有找到则返回-1。
  5. strings.Replace(str, old, new, n):将字符串str中前n个old子串替换为new子串,并返回新的字符串。
  6. strings.Split(str, sep):将字符串str按照sep分割成多个子串,并返回一个切片。
  7. strings.ToLower(str):将字符串str转换为小写字母形式,并返回新的字符串。
  8. strings.ToUpper(str):将字符串str转换为大写字母形式,并返回新的字符串。
  9. strings.TrimSpace(str):去除字符串str首尾的空格,并返回新的字符串。
  10. strings.Trim(str, cutset):去除字符串str首尾包含在cutset中的字符,并返回新的字符串。
  11. strings.HasPrefix(str, prefix):判断字符串str是否以prefix开头,返回bool类型。
  12. strings.HasSuffix(str, suffix):判断字符串str是否以suffix结尾,返回bool类型。
  13. strings.Join(strs []string, sep string):将切片strs中的所有元素按照sep连接成一个字符串,并返回新的字符串。
package main

import (
	"fmt"
	"strings"
)

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
}

3.15 JSON处理

Go语言中提供了内置的encoding/json包来处理JSON数据。该包提供了将JSON数据解析为Go语言中的结构体对象,以及将Go语言中的结构体对象转换为JSON格式数据的功能。

具体来说,该包提供了以下几个函数:

  1. json.Marshal(v interface{}) ([]byte, error):将Go语言中的结构体对象v转换为JSON格式数据,并返回字节数组和错误信息。
  2. json.Unmarshal(data []byte, v interface{}) error:将JSON格式数据data解析为Go语言中的结构体对象v,并返回错误信息。

例如,假设我们有一个名为Person的结构体:

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

我们可以使用json.Marshal()函数将该结构体转换为JSON格式数据:

p := Person{Name: "Alice", Age: 25}
data, err := json.Marshal(p)
if err != nil {
    fmt.Println("Error:", err)
}
fmt.Println(string(data)) // 输出:{"name":"Alice","age":25}

同样地,我们也可以使用json.Unmarshal()函数将JSON格式数据解析为Person结构体:

data := []byte(`{"name":"Bob","age":30}`)
var p Person
err := json.Unmarshal(data, &p)
if err != nil {
    fmt.Println("Error:", err)
}
fmt.Println(p.Name) // 输出:Bob
fmt.Println(p.Age)  // 输出:30

需要注意的是,在使用json.Unmarshal()函数时,需要传入一个指向Person结构体的指针,否则无法将解析后的数据赋值给该结构体。

package main

import (
	"encoding/json"
	"fmt"
)

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"}}
}

3.16 时间处理 Go语言中提供了内置的time包来处理时间。该包提供了表示时间的结构体类型、时间格式化、时间计算等功能。

以下是该包中常用的函数和结构体:

  1. time.Now():获取当前本地时间。
  2. time.Parse(layout, value string) (Time, error):将字符串value按照layout指定的格式解析为Time类型的值。
  3. time.Time:表示时间的结构体类型,包含年月日时分秒等信息。
  4. Time.Format(layout string) string:将Time类型的值按照layout指定的格式转换为字符串。
  5. Time.Add(d Duration) Time:将Time类型的值加上Duration类型的值d,返回新的Time类型的值。

例如,我们可以使用以下代码获取当前本地时间并输出:

now := time.Now()
fmt.Println(now)

我们也可以使用time.Parse()函数将一个字符串解析为Time类型:

t, err := time.Parse("2006-01-02 15:04:05", "2022-01-01 00:00:00")
if err != nil {
    fmt.Println("Error:", err)
}
fmt.Println(t)

在上面这个例子中,我们使用了一个特殊的日期格式"2006-01-02 15:04:05"作为layout参数。这是因为Go语言规定,在进行日期格式化时必须使用这个固定格式。

我们也可以使用Time.Format()函数将一个Time类型转换为字符串:

t := time.Date(2022, time.January, 1, 0, 0, 0, 0, time.Local)
fmt.Println(t.Format("2006-01-02 15:04:05"))

最后,我们可以使用Time.Add()函数对时间进行加减操作:

t := time.Now()
t2 := t.Add(24 * time.Hour)
fmt.Println(t2)

在上面这个例子中,我们将当前时间加上24小时,并输出结果。需要注意的是,在进行时间计算时,需要使用time.Duration类型表示时间

package main

import (
	"fmt"
	"time"
)

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
}

3.16 数字解析

package main

import (
	"fmt"
	"strconv"
)

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
}

3.16 进程信息

一些运行环境、进程信息

package main

import (
	"fmt"
	"os"
	"os/exec"
)

func main() {
	// go run example/20-env/main.go a b c d
	fmt.Println(os.Args)           // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
	fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
	fmt.Println(os.Setenv("AA", "BB"))

	buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
	if err != nil {
		panic(err)
	}
	fmt.Println(string(buf)) // 127.0.0.1       localhost
}