B班课代表的Go学习笔记-【2】-熟悉Go基础语法

71 阅读17分钟

Go语言的基础语法可以跟着教程: learnku.com/docs/the-wa…

自己挨个敲一遍,熟悉一下Go的语法风格,如果你有其他编程语言基础,应该一两天就能练完。我个人是利用国庆节,用了三天,每天上午9点起床后,练到1点左右,平均一天3~4个小时。

一、基本语法

package main

import (
	"fmt"
	"math/rand"
	"os"
	"runtime"
	"sort"
	"time"
)

// 1. 基础变量定义
func variable() {
	//写法1:
	var a int
	var s string
	a = 10
	s = "你好"
	fmt.Print(a, s)
	//写法2,自动推断类型
	var b = 20
	var c = "Hello"
	fmt.Print(b, c)
	//写法3
	var e int = 11
	var f bool = false
	fmt.Print(e, f)
	//写法4,推荐简写
	g := 100
	h := "简写"
	fmt.Print(g, h)
}

// 2. 使用内置方法获取系统信息
func sysFn() {
	goos := runtime.GOOS      //获取操作系统类型,如Windows
	path := os.Getenv("PATH") //获取环境变量中的值
	fmt.Printf("The operating system is: %s\n", goos)
	fmt.Printf("Path is %s\n", path)
}

// 3. 布尔类型及运算(与或非)
func boolFn() {
	a := 1
	fmt.Print(a > 10) //false
	b := 0
	//fmt.Print(a && b) //会报错,无法隐式类型转换
	c := a > 0 && b > 0
	fmt.Print(c) //false
	d := a > 0 || b > 0
	fmt.Print(d)  //true
	fmt.Print(!d) //false
}

// 4. 算数运算符(+ - * / %)
func mathFn() {
	a := 9
	b := 4
	//fmt.Print(a + b) //13
	//fmt.Print(a - b) //5
	//fmt.Print(a * b) //36
	//fmt.Print(a / b) //2
	fmt.Print(a % b) //1
}

// 5. 随机数
func randFn() {
	//fmt.Print(rand.Int()) //1225083820117581013
	//fmt.Print(rand.Intn(10)) //0~10之间的随机数
	//fmt.Print(rand.Float32()) //0.20632032930679400
	fmt.Print(rand.Float64()) //0.05067825471427861173943200

	times := time.Now().Nanosecond() //随机时间戳 now~9999999999
	fmt.Print(times)

}

// 6. 字符串
func strFn() {
	//a := "Hello"
	//fmt.Print(len(a)) //获取字符串长度
	//fmt.Printf("序号是%d,值是%c ", a[3], a[2]) //打印字符串使用Printf
	//1. 前缀和后缀
	//b := strings.HasPrefix(a, "H") //a字符串是不是H字母打头的
	//fmt.Print(b)                   //true
	//c := strings.HasSuffix(a, "l") //a字符串是不是l结尾的
	//fmt.Print(c)                   //false

	//2. 字符串包含关系
	//d := strings.Contains(a, "l") //a字符串中是否包含l字母
	//fmt.Print(d)                  //true

	//3.判断子字符串或字符在父字符串中出现的位置(索引)
	//e := strings.Index(a, "l") //2   首次出现的位置下标
	//f := strings.Index(a, "h") //-1  不存在
	//fmt.Print(e, f)
	//g := strings.LastIndex(a, "l") // 最后一次出现的位置下标
	//fmt.Print(g)                   //3

	//4. 字符串替换
	//h := strings.Replace("Hello", "l", "L", 1)   // HeLlo, 只替换一个l
	//hh := strings.Replace("Hello", "l", "L", -1) // HeLLo, 替换所有l
	//fmt.Print(h, hh)

	//5.统计字符串出现次数
	//i := strings.Count("Hello", "l") //2
	//fmt.Print(i)

	//6.重复字符串
	//j := strings.Repeat("Hello", 3) //HelloHelloHello
	//fmt.Print(j)

	//7. 修改字符串大小写
	//aa := "Hello"
	//fmt.Print(strings.ToLower(aa)) //hello
	//fmt.Print(strings.ToUpper(aa)) //HELLO

	//8.修剪字符串
	//k := strings.TrimSpace(" Hello World ") //剔除首尾空格
	//fmt.Print(k)                            //Hello World
	//fmt.Print(strings.Trim("do something in this world", "d")) //剔除收尾的d
	//fmt.Print(strings.TrimLeft("Hello", "H"))  //ello , 剔除首位的H
	//fmt.Print(strings.TrimRight("Hello", "o")) //Hell , 剔除末尾的o

	//9. 字符串分割与拼接
	//l := strings.Fields("Hello World")    //按照空白符分割字符串,得到slice
	//fmt.Print(l)                          //[Hello World]
	//fmt.Print(strings.Split("Hello", "")) //[H e l l o] ,按指定字符分割,得到slice
	//fmt.Print(strings.Join(l, "-"))       //Hello-World
}

// 7. 时间
func timeFn() {
	t := time.Now()
	fmt.Print(t.Year(), "-")
	fmt.Print(t.Month(), "-")
	fmt.Print(t.Day(), "-")
	fmt.Print(t.Hour(), "-")
	fmt.Print(t.Minute(), "-")
	fmt.Print(t.Second(), "-")
}

// 8. 条件语句
func ieFn() {
	//1.基本语法
	if runtime.GOOS == "windows" {
		fmt.Print("这是Windows系统")
	} else {
		fmt.Print("这不是windows系统")
	}
	//2.if 可以包含一个初始化语句(如:给一个变量赋值)。这种写法具有固定的格式(在初始化语句后方必须加上分号)
	if val := 10; val > 9 {
		fmt.Print("10大于9")
	}
	//3.switch
	i := 1
	switch i {
	case 1:
		fmt.Print("传入了1")
	case 2:
		fmt.Print("传入了2")
	default:
		fmt.Print("传入了其他")
	}

}

// 9.for循环
func forFn() {
	//1.基本语法;永远不要在循环体内修改计数器,这在任何语言中都是非常差的实践!
	//for i := 0; i < 10; i++ {
	//	fmt.Print(i, "\n")
	//}

	//2. 拓展练习,打印下列需求
	//G
	//GG
	//GGG
	//GGGG
	//GGGGG
	//GGGGGG
	//for i := 0; i < 6; i++ {
	//	fmt.Print(strings.Repeat("G", i+1), "\n")
	//}

	//3. 基于条件判断的迭代
	//i := 5
	//for i > 0 {
	//	i = i - 1
	//	fmt.Print(i, "\n")
	//}
	//4.for-range 结构
	str := "Hello"
	for i, chart := range str {
		fmt.Printf("序号是%d,值是%c \n", i, chart)
	}
	//5. break
	for i := 0; i < 3; i++ {
		for j := 0; j < 10; j++ {
			if j > 5 {
				break //只会每次跳出j循环
			}
			print(j)
		}
		print("  ")
	}
	//6. continue
	for i := 0; i < 10; i++ {
		if i == 5 {
			continue //跳过5的打印
		}
		print(i)
	}
}

// 10. 函数
// 10-1.函数及其返回值
func sum(a int, b int) int {
	return a * b
}

// 10-2 函数返回多个值
func more(arg int) (a int, b int) {
	x := 2 * arg
	y := 3 * arg
	return x, y
}

// 10-3 传递变长参数
func restFn(a int, b int, arg ...int) int {
	c := a + b
	for _, i2 := range arg {
		c += i2
	}
	return c
}

// 10-4 关键字 defer
// 允许我们推迟到函数返回之前(或任意位置执行 return 语句之后)一刻才执行某个语句或函数(为什么要在返回之后才执行这些语句?因为 return 语句同样可以包含一些操作,而不是单纯地返回某个值)。
// (译者注:return 是非原子性的,需要两步,执行前首先要得到返回值 (为返回值赋值),return 将返回值返回调用处。
// defer 和 return 的执行顺序是先为返回值赋值,然后执行 defer,然后 return 到函数调用处。)
func fn1() {
	fmt.Print("1111", "\n")
	defer fn2() //1111 2222 3333  对比使用defer与不使用defer的区别
	fmt.Print("2222", "\n")
}
func fn2() {
	fmt.Print("3333", "\n")
}

// 10-5 内置函数
//Go 语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作,
//例如:len、cap 和 append,或必须用于系统级的操作,
//例如:panic。
//因此,它们需要直接获得编译器的支持。

// 10-6 递归函数
//计算斐波那契数列,即前两个数为 1,从第三个数开始每个数均为前两个数之和。
//1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, …

func fibonacci(n int) (res int) {
	if n < 1 {
		res = 1 //可以直接为返回值赋值
	} else {
		res = fibonacci(n-1) + fibonacci(n-2)
	}
	return
}
func testFibonacci() {
	for i := 0; i < 10; i++ {
		m := fibonacci(i)
		fmt.Printf("fibonacci(%d),is: %d \n", i, m)
	}
}

// 递归练习
// 重写本节中生成斐波那契数列的程序并返回两个命名返回值,
// 即数列中的位置和对应的值,例如 5 与 4,89 与 10。
func fibonacci6(n int) (i int, res int) {
	if n < 1 {
		res = 1 //可以直接为返回值赋值
		i = n
	} else {
		_, m := fibonacci6(n - 1)
		_, w := fibonacci6(n - 2)
		res = m + w
		i = n + 1
	}
	return
}
func testFibonacci6() {
	for i := 0; i < 10; i++ {
		j, m := fibonacci6(i)
		fmt.Printf("fibonacci(%d),is: %d \n", j, m)
	}
}

// 10-7 将函数作为参数 【回调函数】
func add(a int, b int) {
	fmt.Printf("累加结果为 %d", a+b)
}
func callbackFn(a int, b int, f func(int, int)) {
	f(a, b)
}

// 10-8 计算函数执行时间 Now()、Sub()
func runTimeFn() {
	start := time.Now() //开始时间
	for i := 0; i < 100; i++ {
		print(i)
	}
	end := time.Now()       //结束时间
	delta := end.Sub(start) //差值计算
	fmt.Printf("执行时间为 %f 秒", delta.Seconds())
}
func runFn() {
	//print(sum(3, 7))
	//
	//var a, b int
	//a, b = more(3)
	//fmt.Printf("函数返回多个值,分别为 %d 和 %d ", a, b)
	//
	//d := restFn(1, 2, 3, 4, 5, 6, 7, 8, 9)
	//print(d)

	//fn1()

	//testFibonacci() //测试递归
	//testFibonacci6() //测试递归练习

	//callbackFn(2, 3, add) //测试回调函数

	runTimeFn()
}

// 11. 数组
func arrFn() {
	//11-1 数组定义方式
	//var array1 [5]int                       //定义空数组
	//array2 := [3]int{1, 2, 3}               //:写法必须要设定初始值
	//array3 := [...]int{1, 2, 3, 4, 5, 6, 7} // 让go自动识别数组长度
	//var array4 [4][5]int                    //定义4行5列的二维数组
	//fmt.Println(array1, array2, array3)
	//fmt.Println(array4)
	//
	//11-2 使用for循环遍历数组
	//var arr1 [5]int
	//for i := 0; i < len(arr1); i++ {
	//	arr1[i] = i * 2
	//}
	//for i := 0; i < len(arr1); i++ {
	//	fmt.Printf("数组的序号是 %d ,对应的值是 %d \n", i, arr1[i])
	//}

	//11-3 使用for-range遍历数组【获取一个数组中的最大值及其对应下标】
	arr2 := [...]int{2, 5, 7, 33, 9, 10}
	maxi := -1
	maxValue := -1
	for i, v := range arr2 {
		if v > maxValue {
			maxi, maxValue = i, v
		}
	}
	fmt.Print(maxi, maxValue)

	//11-4-1 数组是值类型  【跟js不同,go数组默认没有引用关系】
	a1 := [3]int{2, 3, 4}
	//printArr1(a1)
	//fmt.Print("函数外部 \n")
	//fmt.Println(a1)

	// 11-4-2 通过指针传递引用操作数组
	printArr2(&a1)
	fmt.Print("函数外部 \n")
	fmt.Println(a1)
}
func printArr1(arr [3]int) {
	arr[0] = 111 //此处的修改不会影响传入的a1
	fmt.Print("函数1内部 \n")
	fmt.Println(arr)
}
func printArr2(arr *[3]int) {
	arr[0] = 222 //此处的修改会影响传入的a1
	fmt.Print("函数2内部 \n")
	fmt.Println(arr)
}

// 12. 切片 【引用类型】
// 切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),
// 所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型)。
// 这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。
// 需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口。
func sliceFn() {
	//12-1 基本操作
	arr := [...]int{0, 1, 2, 3, 4, 5, 6}
	s := arr[:]    //slice可以是数组的一段view
	s1 := arr[2:6] //slice可以向后扩展。虽然s1的值是[2,3,4,5],但是s1的容量是5---[2,3,4,5,6]
	s2 := s1[3:5]  //[5,6],slice可以获取内容之外,容量之内的值6
	fmt.Println(s, s1, s2)
	//fmt.Printf("s1的值:%v s1的长度:%d s1的容量:%d", s1, len(s1), cap(s1))

	//12-2 向slice中添加新值
	s3 := append(s1, 10)
	s4 := append(s3, 11)
	s5 := append(s4, 12)
	//fmt.Println("s3=", s3)   //[2 3 4 5 10] ,s1容量的最后一位,也是arr的最后一位
	//fmt.Println("arr=", arr) // [0 1 2 3 4 5 10] 对于s1容量范围内的操作,会影响arr
	//fmt.Println("s4=", s4)   //[2 3 4 5 10 11] 此处append的11,因为已经超出了arr的范围,go会自动在内存中为s4分配一个更大的空间,重新存放,后续操作将无法再影响arr
	fmt.Println("s5=", s5) // [2 3 4 5 10 11 12]

	//12-3 其他创建slice的方法
	var ss []int //初始定义的空slice默认值为nil【类似js中的null】
	fmt.Println(ss, len(ss))
	ss1 := append(ss, 1, 2, 3, 4)
	fmt.Println(ss1, len(ss1))

	ss2 := make([]int, 10) //创建一个长度为10的slice
	fmt.Println(ss2, len(ss2), cap(ss2))
	ss3 := make([]int, 6, 10) //创建一个长度为6,容量为10的slice
	fmt.Println(ss3, len(ss3), cap(ss3))

	// 12-4 拷贝
	copy(ss2, ss1)              //将ss1拷贝到ss2中【目标在前,源头在后】
	fmt.Println("拷贝后的ss2", ss2) // [1 2 3 4 0 0 0 0 0 0]

	// 12-5 删除
	// 删除中间位,例如:删除ss2中的3
	ss2 = append(ss2[:2], ss2[3:]...) //没有直接删除,而是跳过截取,...类似js的拓展运算符
	fmt.Println("删除3后的ss2", ss2)      //[1 2 4 0 0 0 0 0 0]
	front := ss2[0]
	ss2 = ss2[1:]
	fmt.Println("删除首位后的ss2", ss2, "被删除的值为", front)
	tail := ss2[len(ss2)-1]
	ss2 = ss2[:len(ss2)-1]
	fmt.Println("删除末尾后的ss2", ss2, "被删除的值为", tail)
	//总结
	// 可以发现,对于slice的删除操作,其本质是不同姿势的截取操作
}

// 13. Map 【引用类型】
// 声明语法
// var map1 map[keytype]valuetype
// var map1 map[string]int
func mapFn() {
	//13-1 基本定义及引用特征
	var map1 map[string]int
	var map2 map[string]int
	map1 = map[string]int{"one": 1, "two": 2}
	map2 = map1       //发生引用关系
	map2["three"] = 3 //map2的操作会影响map1
	fmt.Println(map1) //map[one:1 three:3 two:2]
	fmt.Println(map2) //map[one:1 three:3 two:2]

	// 13-2 使用make创建map 【永远使用make来构造map,不要使用new】
	var map3 = make(map[string]float32)
	map3["key1"] = 3.14
	map3["key2"] = 4.28
	fmt.Println(map3)

	// 13-3 map的值可以是任意类型
	// 如:函数
	map4 := map[int]func() int{
		1: func() int {
			return 10
		},
		2: func() int {
			return 20
		},
	}
	fmt.Println(map4)      //map[1:0xc3f220 2:0xc3f240] 值为函数的地址
	fmt.Println(map4[1]()) //10

	//如:切片
	map5 := map[string][]int{
		"first":  []int{1, 2, 3, 4},
		"second": []int{4, 5, 6, 7},
	}
	fmt.Println(map5)
	fmt.Println(map5["second"][1:3]) //[5,6]

	//13-4 判断map的key是否存在
	v, ok := map5["first"] //此处的v如果不使用,也可以使用_占位
	if ok {
		fmt.Println("map5数据包中存在名为first的key,值为", v)
	}

	//13-5 使用for-range操作map
	map6 := map[int]float32{1: 1.2, 2: 2.34, 3: 3.14}
	for key, v := range map6 {
		fmt.Println("键名为", key, "键值为", v)
	}

	//13-6 map类型的切片 【可以两次make】
	items := make([]map[int]int, 10)
	//方法1:
	//for i := 0; i < len(items); i++ {
	//	items[i] = map[int]int{i: i * 2}
	//}
	//fmt.Println(items)
	//方法2:
	for i := range items {
		items[i] = make(map[int]int, 1) //每个map都是key为1,value变化
		items[i][1] = i * 2
	}
	fmt.Println(items)

	//13-7 map的排序
	//按需提取key,形成slice后,对slice进行排序,然后再按排序后的key提取value
	var (
		barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23,
			"delta": 87, "echo": 56, "foxtrot": 12,
			"golf": 34, "hotel": 16, "indio": 87,
			"juliet": 65, "kili": 43, "lima": 98}
	)
	for k, v := range barVal {
		fmt.Println("key是:", k, "value是:", v) //无序打印
	}
	keys := make([]string, len(barVal))
	i := 0
	for k1 := range barVal {
		keys[i] = k1
		i++
	}
	fmt.Println("整合map的所有key为slice数据", keys)
	sort.Strings(keys) //按字母排序
	fmt.Println("排序后的slice", keys)
	for _, key := range keys {
		fmt.Println("key是:", key, "value是:", barVal[key]) //有序打印
	}
}

// 14. 结构体 struct
// 特点:结构体内部只有属性,没有方法
// 在 C 家族的编程语言中它也存在,并且名字也是 struct,在面向对象的编程语言中,
// 跟一个无方法的轻量级类一样。不过因为 Go 语言中没有类的概念,
// 因此在 Go 中结构体有着更为重要的地位。

// 1. 定义一个游戏玩家结构体
type Player struct {
	Name  string
	level int
	exp   int
}

// 5. 为结构体挂载方法
func (p *Player) levelUpdate() {
	p.level++
	p.exp = 0
	fmt.Printf("玩家升级成功,当前等级%d,当前经验%d \n", p.level, p.exp)
}

// 7. 为结构体,封装工厂函数
func NewPlayer(name string, lv int, exp int) *Player {
	return &Player{
		Name:  name,
		level: lv,
		exp:   exp,
	}
}
func structFn() {

	//2.创建玩家实例
	player := Player{Name: "三丰", level: 10, exp: 100}
	//3.访问结构体字段
	fmt.Println(player.Name)
	//4.修改结构体数据
	player.level = 11
	fmt.Println(player.level)

	//6. 调用为结构体挂载的方法
	player.levelUpdate()

	//8. 调用工厂函数,创建结构体实例
	player1 := NewPlayer("无忌", 1, 10)
	fmt.Println(player1.Name)
}

func main() {
	fmt.Println("Hello World")
	//variable()
	//sysFn()
	//boolFn()
	//mathFn()
	//randFn()
	//strFn()
	//timeFn()
	//ieFn()
	//forFn()
	//runFn()
	//arrFn()
	//sliceFn()
	//mapFn()
	structFn()
}

二、闭包【函数式编程】

package main

import "fmt"

// 1. 封装闭包函数

func adder() func(int) int {
	sum := 0
	// 当我们调用下面这个闭包函数的时候,可以拿到整个adder内部的自由变量
	return func(v int) int {
		sum += v
		return sum
	}
}
func main() {
	a := adder()
	fmt.Printf("入参:%d,闭包返回:%d \n", 1, a(1))
	fmt.Printf("入参:%d,闭包返回:%d \n", 2, a(2))
	fmt.Printf("入参:%d,闭包返回:%d \n", 3, a(3))
}

三、接口interface

package main

import "fmt"

// 1. 创建一个攻击接口,凡是具备攻击能力的游戏角色,统一由这个接口约束
type Attacker interface {
	Attack(skill string)
}

// 2. 实现一个【战士】结构体
type Worrior struct {
	Name string
}

// 3. 为【战士】结构体挂载方法
func (w Worrior) Attack(skill string) {
	fmt.Println("战士:", w.Name, "施展了", skill, "技能进行攻击")
}

// 4. 封装【战士】工厂函数
func NewWorrior(name string) Worrior {
	return Worrior{Name: name}
}
func main() {
	//使用接口约定player,以确保所有player后续可以调用Attack,
	//注意Go中的interface使用的是duck typing【鸭子辨型】,
	//意思就是说未来我们为player赋值的时候,只要长得像player就行,
	//并不需要跟接口的格式一模一样,可以让接口的运用更灵活
	var player Attacker
	player = NewWorrior("孙策")
	player.Attack("飞船")
	//player.Say("Hello")  //此处会报错,因为Attacker接口上并没有Say方法

	player = NewWorrior("刘备")
	player.Attack("大喷子")
}

四、pacakge包

1. 目录结构

2. 包功能测试代码逻辑说明

假设我们正在开发一个图书管理系统,需要处理图书的借阅、归还等操作。我们可以将代码组织为多个包,以便更好地管理和复用代码。

3. 图书结构体

我们可以创建一个名为 book 的包来定义图书的结构体和相关函数,如下所示:

package book

import (
	"GoGo/pkg/lib"
	"fmt"
	"log"
)

// 图书结构体
type Book struct {
	Title    string
	Author   string
	Borrowed bool
}

// 借阅图书
func Borrow(b *Book) {
	if b.Borrowed {
		log.Printf("图书《%s》已经被借出", b.Title)
		return
	}

	b.Borrowed = true
	log.Printf("图书《%s》借阅成功", b.Title)
}

// 归还图书
func Return(b *Book) {
	if !b.Borrowed {
		log.Printf("图书《%s》尚未被借出", b.Title)
		return
	}

	b.Borrowed = false
	log.Printf("图书《%s》归还成功", b.Title)
}

// 打印图书信息
func PrintBookInfo(b *Book) {
	fmt.Println("图书信息:")
	fmt.Println("标题:", b.Title)
	fmt.Println("作者:", b.Author)

	if b.Borrowed {
		fmt.Println("状态: 已借出")
	} else {
		fmt.Println("状态: 未借出")
	}
}

// 记录借阅日志
func LogBorrowedBook(b *Book) {
	lib.Log(fmt.Sprintf("借阅图书《%s》", b.Title))
}

// 记录归还日志
func LogReturnedBook(b *Book) {
	lib.Log(fmt.Sprintf("归还图书《%s》", b.Title))
}

4. book引用的lib模块定义

在这个修改后的 book 包中,我们引入了一个名为 library 的包,用于记录图书的借阅和归还日志。

我们在 Book 结构体中添加了一个 Borrowed 字段,用于表示图书的借阅状态。然后,我们修改了 Borrow 和 Return 函数,以便在借阅和归还图书时更新借阅状态,并通过日志记录相应的操作。

此外,我们还添加了 PrintBookInfo 函数,用于打印图书的详细信息,包括借阅状态。

在 LogBorrowedBook 和 LogReturnedBook 函数中,我们调用了 library.Log 函数来记录借阅和归还的操作日志。

最后,我们需要创建一个名为 library 的包来定义日志记录的函数,如下所示:

package lib

import "fmt"

// 记录日志
func Log(message string) {
	fmt.Println("图书馆日志:", message)
}

5. main包及逻辑

现在,我们可以在 main 包中使用 book 包来进行图书管理操作,并同时记录借阅和归还的日志,如下所示:

package main

import "GoGo/pkg/book"

func main() {
	// 创建图书实例
	bk := &book.Book{
		Title:  "The Great Gatsby",
		Author: "F. Scott Fitzgerald",
	}

	// 借阅图书并记录日志
	book.Borrow(bk)
	book.LogBorrowedBook(bk)

	// 再次尝试借阅相同的图书
	book.Borrow(bk)
	book.LogBorrowedBook(bk)

	// 归还图书并记录日志
	book.Return(bk)
	book.LogReturnedBook(bk)

	// 打印图书信息
	book.PrintBookInfo(bk)
}
  1. 运行main包,查看结果

五、IO读写

1. 终端获取键盘输入

package main

import (
	"bufio"
	"fmt"
	"os"
)

// 1. 基本键盘输入案例
func welcome() {
	inputReader := bufio.NewReader(os.Stdin) //1. 创建标准键盘输入
	fmt.Println("请输入您的姓名:")
	input, err := inputReader.ReadString('\n') //2. 读取输入内容
	if err != nil {
		fmt.Println("输入错误请重试")
		return
	}
	fmt.Printf("您的名字是%s", input) //3. 使用输入内容
	switch input {               //4. 判断输入内容
	case "sanfeng\r\n":
		fmt.Println("三丰,你要去闭关")
	case "wuji\r\n":
		fmt.Println("无忌,你要去练功")
	default:
		fmt.Println("一边玩儿去,拜拜")
	}
	//welcome()

}

// 练习:实现逆波兰式计数器
// https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/exercises/chapter_12/stack/stack_struct.go

func main() {
	welcome()
}

2. 文件读写操作

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

// 1. 创建文件并写入内容
func writeFn() {
	//1. 打开或创建文件
	outputFile, outputError := os.OpenFile("test.txt", os.O_WRONLY|os.O_CREATE, 06666)
	if outputError != nil {
		fmt.Println("写入或创建文件时发生错误")
		return
	}
	defer outputFile.Close()                    //2. 函数执行完毕后,关闭文件
	outputWriter := bufio.NewWriter(outputFile) //3. 创建写入程序
	for i := 0; i < 10; i++ {
		text := fmt.Sprintf("第%d行,值为%d \n", i, i*2)
		outputWriter.WriteString(text) //4. 写入内容【缓冲区】
	}
	outputWriter.Flush() //5.刷新写入内容
}

// 2. 读取指定文件内容
func readFn() {
	inputFile, inputErr := os.Open("test.txt") //1.打开文件
	if inputErr != nil {
		fmt.Println("文件读取打开失败,文件路径错误或没有权限!")
		return
	}
	defer inputFile.Close()                   //2.函数执行完毕后,关闭文件
	inputReader := bufio.NewReader(inputFile) //3. 创建读程序
	for {
		readString, readErr := inputReader.ReadString('\n') //4. 逐行读取文件中的字符串
		fmt.Println(readString)
		if readErr == io.EOF { //5.读取结束的判断
			fmt.Println("读取完毕")
			return
		}
	}
}

// 3. 文件拷贝
func copyFn(dst string, src string) {
	srcFile, srcErr := os.Open(src) //1. 打开被拷贝文件
	if srcErr != nil {
		fmt.Println("打开目标文件失败")
		return
	}
	defer srcFile.Close() //2. 函数执行完毕后关闭文件

	dstFile, desErr := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, 0666) //3.创建新文件
	if desErr != nil {
		fmt.Println("新文件创建失败")
		return
	}

	defer dstFile.Close()                         //4. 使用完毕后,关闭新文件。切记加defer,否则会在拷贝前关闭,导致内容拷贝失败
	bytesCopied, err := io.Copy(dstFile, srcFile) //5. 执行拷贝
	if err != nil {
		fmt.Println("文件内容复制失败", err)
		return
	}
	fmt.Printf("已成功复制 %d 字节的文件内容。\n", bytesCopied)
}
func main() {
	//writeFn()
	//readFn()
	copyFn("test1.txt", "test.txt")
}