1.Go语言简介
Go语言是由Google开发的一种开源编程语言,于2009年首次发布。Go语言以其简洁、高效和并发特性而受到广泛关注和喜爱,引入了goroutine(轻量级线程)和通道(channel)等特性,使得编写并发程序更加简单和高效。并发是Go语言的一大特色,它使得Go在处理高并发情况下具有出色的性能。
2.安装与环境配置
2.1 安装Golang
下载Go语言安装包 go.dev/dl/
配置环境变量
2.2 配置集成开发环境
在VSC中下载拓展Go
git clone github.com/wangkechun/… 克隆课程示例项目
进入课程示例项目代码目录,运行 go run example/01-hello/main.go 如果正确输出 hello world,则说明环境配置正确
3.基础语法
3.1 变量与数据类型
Go语言是静态强类型语言,这意味着在声明变量时必须指定其数据类型
Go语言中的基本数据类型有字符串,整数,浮点型,布尔型等。
变量声明有两种方法:
方法一
var a = "initial"
var b, c int = 1, 2
方法二
f := float32(e)
常量声明如下:
const s string = "constant"
3.2 流程控制
学习流程控制是编程的基础,它决定了程序的执行顺序和逻辑。在这一节中,我们将学习Go语言中的条件语句(if-else)、循环语句(for)、以及跳转语句(break、continue)。这些控制结构将帮助您实现更加复杂的逻辑控制。
3.2.1 条件语句 if / else
if 后面没有括号,如果写的话会被编译器自动去掉
示例:
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
3.2.2 循环语句 for
循环语句用于重复执行某段代码,Go语言中只有for循环一种形式。它的基本语法如下:
for 初始化语句; 条件表达式; 后续语句 {
// 循环体,满足条件表达式时执行
}
其中,初始化语句在循环开始前执行一次,条件表达式在每次循环开始前判断是否满足循环条件,后续语句在每次循环结束后执行。
for循环中的初始化语句和后续语句都可以省略,但分号必须保留。
3.2.3 跳转语句 break / continue
跳转语句用于在循环中控制流程的跳转。break语句用于提前终止循环,跳出循环体;continue语句用于跳过当前循环体中剩余的代码,继续下一次循环。
3.2.4 switch语句
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")
}
不需要加break,默认只走一个分支
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
可以使用任意类型,如字符串、结构体
switch后面可以不加条件变量,在case里面写条件分支
3.3 数组、切片与映射
在Go语言中,数组、切片和映射是常用的数据结构,用于存储和管理一组数据。它们各自有不同的特性和用途,数组和切片是用于存储多个元素的数据结构,而映射则用于存储键值对。
3.3.1 数组
数组是一种固定长度、存储相同类型元素的数据结构。在Go语言中,数组的长度是固定的,在声明时必须指定长度。数组的索引从0开始,可以通过索引来访问和修改数组元素。
示例:
// 声明一个包含5个整数的数组
var numbers [5]int
// 初始化数组元素
numbers[0] = 10
numbers[1] = 20
numbers[2] = 30
numbers[3] = 40
numbers[4] = 50
// 访问数组元素
fmt.Println(numbers[2]) // 输出:30
// 修改数组元素
numbers[2] = 35
fmt.Println(numbers[2]) // 输出:35
3.3.2 切片
切片是一个动态数组,它是对数组的一个引用,并且可以自动调整大小。切片没有固定的长度,可以根据需要动态增长或缩减。切片的长度表示当前元素个数,容量表示底层数组的长度。
示例:
// 创建一个切片,包含初始元素
nums := []int{10, 20, 30, 40, 50}
// 切片的长度和容量
fmt.Println(len(nums)) // 输出:5
fmt.Println(cap(nums)) // 输出:8
使用append添加新元素到切片末尾
nums = append(nums, 60)
必须把append赋值回原数组,切片存储了长度、容量、指向数组的指针,否则会创建新的切片
// 使用make创建切片
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
// 使用copy进行拷贝
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]
3.3.3 映射 Map
映射是一种键值对的集合,类似于字典或哈希表。在映射中,每个键只能出现一次,而值可以重复。映射的大小是动态的,可以根据需要动态增加或删除键值对。
示例:
// 声明一个映射,键是字符串类型,值是整数类型
scores := map[string]int{
"Alice": 85,
"Bob": 92,
"Carol": 78,
}
// 添加新的键值对
scores["David"] = 90
// 访问映射的值
fmt.Println(scores["Bob"]) // 输出:92
// 修改映射的值
scores["Carol"] = 82
// 删除映射的键值对
delete(scores, "Alice")
fmt.Println(scores) // 输出:map[Bob:92 Carol:82 David:90]
// 遍历map
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
}
3.4 函数
函数是Go语言的核心组件之一。它使得我们能够将代码组织成模块化的块,并可以多次重用。
3.4.1 函数的定义
在Go语言中,函数的定义以关键字func开头,后面是函数名、参数列表、返回值类型和函数体。基本语法如下:
func 函数名(参数列表) 返回值类型 {
// 函数体
return 返回值
}
示例:
// 定义一个简单的函数,将两个整数相加并返回结果
func add(a, b int) int {
return a + b
}
3.4.2 函数的调用
要调用一个函数,只需要写出函数名和参数列表即可。如果函数有返回值,可以用一个变量接收。
示例:
result := add(10, 20)
fmt.Println(result) // 输出:30
3.4.3 多返回值函数
Go语言支持函数返回多个值,这使得函数能够更灵活地处理多种情况。
示例:
// 定义一个函数,返回两个整数的商和余数
func divide(a, b int) (int, int) {
quotient := a / b
remainder := a % b
return quotient, remainder
}
// 调用函数并接收返回值
q, r := divide(13, 5)
fmt.Println("商:", q) // 输出:2
fmt.Println("余数:", r) // 输出:3
3.4.4 可变参数函数
有时候我们不确定函数会接收多少个参数,Go语言提供了可变参数的特性。在参数类型前面加上...表示该参数是可变参数,函数内部可以通过切片来获取所有传入的参数值。
示例:
// 定义一个可变参数函数,计算所有整数的和
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
// 调用函数
result := sum(1, 2, 3, 4, 5)
fmt.Println(result) // 输出:15
3.4.5 匿名函数
在Go语言中,函数可以是匿名的,也就是没有函数名。匿名函数可以直接赋值给变量,或者作为参数传递给其他函数使用。
示例:
// 将匿名函数赋值给变量,然后调用它
addFunc := func(a, b int) int {
return a + b
}
result := addFunc(10, 20)
fmt.Println(result) // 输出:30
// 将匿名函数作为参数传递给其他函数
calculate := func(op func(int, int) int) int {
return op(5, 3)
}
result := calculate(func(x, y int) int {
return x * y
})
fmt.Println(result) // 输出:15
3.5 指针
在Go语言中,指针是一种特殊的数据类型,它存储的是变量的内存地址。指针允许我们直接访问变量在内存中的存储位置,并可以通过指针对变量进行操作。在Go语言中,使用*来声明指针类型,使用&来获取变量的内存地址,使用*来获取指针指向的值。
指针变量的零值是nil,表示该指针不指向任何有效的内存地址。
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.6 结构体与方法
结构体是一种用户自定义的数据类型,它可以包含不同类型的字段,组合成一个新的复合类型。结构体允许我们将多个相关的数据字段封装在一起,形成更有组织和可读性的数据结构。
3.6.1 结构体的定义
在Go语言中,结构体的定义以关键字type开头,后面是结构体的名称和字段列表。字段列表是由字段名和字段类型组成的。
示例:
// 定义一个结构体表示学生信息
type Student struct {
Name string
Age int
Grade int
}
3.6.2 结构体的实例化
结构体的实例化可以通过直接初始化结构体的字段,也可以通过new关键字来实现。new关键字返回的是一个指向新分配的零值结构体的指针。
示例:
// 初始化结构体的字段
student1 := Student{
Name: "Alice",
Age: 18,
Grade: 12,
}
// 使用 new 关键字创建结构体指针
student2 := new(Student)
student2.Name = "Bob"
student2.Age = 19
student2.Grade = 11
3.6.3 结构体的方法
Go语言中的方法是一种特殊类型的函数,它与结构体关联,并可以在结构体类型上进行调用。方法是将行为与数据关联的一种方式,可以通过方法来操作结构体的字段。
方法的定义需要在函数名前面添加一个接收者(Receiver),它用来指定该方法所属的结构体类型。
示例:
// 在 Student 结构体上定义一个方法,用于打印学生信息
func (s Student) PrintInfo() {
fmt.Printf("Name: %s, Age: %d, Grade: %d\n", s.Name, s.Age, s.Grade)
}
3.6.4 结构体的方法调用
要调用结构体的方法,只需要用结构体实例加上方法名即可。
示例:
student := Student{
Name: "Carol",
Age: 17,
Grade: 10,
}
student.PrintInfo() // 输出:Name: Carol, Age: 17, Grade: 10
3.6.5 指针接收者的方法
在方法的接收者中,还可以使用指针类型。使用指针接收者的方法能够修改结构体实例的字段,而普通接收者的方法只能对结构体进行只读操作。
示例:
// 在 Student 结构体上定义一个修改年龄的方法
func (s *Student) UpdateAge(newAge int) {
s.Age = newAge
}
student := Student{
Name: "David",
Age: 20,
Grade: 12,
}
student.UpdateAge(21)
fmt.Println(student.Age) // 输出:21
3.6.6 嵌套结构体
在一个结构体中可以嵌套其他结构体,形成更复杂的数据结构。
示例:
// 定义 Address 结构体
type Address struct {
City string
State string
}
// 定义 Person 结构体,并嵌套 Address 结构体
type Person struct {
Name string
Age int
Address Address
}
person := Person{
Name: "Eve",
Age: 25,
Address: Address{
City: "New York",
State: "NY",
},
}
fmt.Println(person.Address.City) // 输出:New York
3.7 错误处理
在编程中,错误是不可避免的。Go语言提供了一套简洁而强大的错误处理机制,可以让我们更好地管理和处理错误情况。通常使用返回值来表示函数的执行状态,并通过错误值来传递错误信息。标准库中的errors包提供了创建简单错误的函数errors.New()
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)
}
}
3.8 字符串
3.8.1 字符串操作
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
3.8.2 字符串格式化
fmt.Printf("s=%v\n", s) // s=hello
fmt.Printf("n=%v\n", n) // n=123
fmt.Printf("p=%v\n", p) // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14
3.9 时间处理
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)
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
time.parse将字符串解析成时间
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
unix获取时间戳
fmt.Println(now.Unix()) // 1648738080
3.10 数字解析
f, _ := strconv.ParseFloat("1.234", 64)
n, _ := strconv.ParseInt("111", 10, 64)
0代表自动推测
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
由此可见,Go语言的语法设计简洁明了,代码量少,易于学习和阅读,使得代码更易于维护和理解。它的简洁性和丰富的标准库使得开发效率很高,能够快速构建出可靠和高效的应用程序,特别适合构建高性能和高并发的网络服务和系统。
4.Go语言实战案例
4.1 猜谜游戏
4.1.1 生成随机数
使用种子让每次生成的随机数不同
rand.Seed(time.Now().UnixNano())
4.1.2 读取用户输入
fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
return
}
input = strings.Trim(input, "\r\n")
4.1.3 实现判断逻辑
if guess > secretNumber {
fmt.Println("Your guess is bigger than the secret number. Please try again")
} else if guess < secretNumber {
fmt.Println("Your guess is smaller than the secret number. Please try again")
} else {
fmt.Println("Correct, you Legend!")
}
4.1.4 实现游戏循环
for 实现循环