Golang初识到入门(下)

1:fmt

1.1:常用占位符

动词 功能
%v 按值的原本值输出
%+v 在%v的基础上,对结构体字段名和值进行展开
%#v 输出Go语言语法的值
%T 输出Go语言语法格式的类型的值
%% 输出%%本体
%b 整型以二进制方式显示
%o 整型以八进制方式显示
%d 整型以十进制方式显示
%x 整型以十六进制方式显示
%X 整型以十六进制,字母大写方式显示
%U Unicode字符
%f 浮点数
%p 指针,十六进制方式显示

1.2:Print

1.2.1:Println

1:一次性输入多个值的时候Println中间有空格
2:Println会自动换行,而Print不会
package main

import "fmt"

func main() {
	a := "zhangsan"
	b := "lisi"
	fmt.Print(a, b) # // 打印的值不会换行
	fmt.Println(a, b) # // 打印的值会换行
}

1.2.2:Print

1:一次性输入多个值的时候Print没有中间的空格
2:Print不会自动换行

package main

import "fmt"

func main() {
	name := "zhangsan"
	age := 18

	fmt.Print("name", name, "age", age)
}

// 结果为:namezhangsanage18

1.2.3:Printf

1:Printf是格式化输出,很多场景下比Println更方便

package main

import "fmt"

func main() {
	name := "zhangsan"
	age := 18

	fmt.Printf("name: %s, age: %d\n", name, age)   // 适用占位符
}

// 结果为:name: zhangsan, age: 18

1.3:Sprint

1:Sprint系列函数会把传入的数据省成并返回一个字符串

package main

import "fmt"

func main() {
	s1 := fmt.Sprint("张三")
	fmt.Println(s1)
	// 张三

	name := "张三"
	age := 18
	s2 := fmt.Sprintf("name: %s age: %d", name, age)
	fmt.Println(s2)
    // name: 张三 age: 18

	s3 := fmt.Sprintln("张三")  // 这个输出是有空格的
	fmt.Println(s3)
    // 张三
    
}

2:time

2.1:时间转换

1:时间对象:Golang中定义的一个对象
2:时间戳:秒的整数形式  // 1970-01-01 到现在多少秒
3:格式化时间:可以供人识别的时间如:2022-06-30 22:35:11

2.1.1:实例化事件对象

package main

import (
	"fmt"
	"time"        // 导入time模块
)

func main() {
	// 时间对象
	now := time.Now()
	fmt.Printf("%T %v\n", now, now)
}

// 运行
PS E:\goland\demo> go run .\main.go
time.Time 2022-06-30 22:39:28.2721026 +0800 CST m=+0.004986701
// 类型为:time.Time 值为:2022-06-30 22:39:28.2721026 +0800 CST m=+0.004986701

2.1.2:格式化时间

package main

import (
	"fmt"
	"time"
)

func main() {
	// 时间对象
	now := time.Now()
	fmt.Printf("%T %v\n", now, now)

	// 格式化时间
	strTime := now.Format("2006-01-02 15:04:05")    // Format可以将time.Time类型转换为string类型,这里的时间是定死的!
	fmt.Printf("%T %v\n", strTime, strTime)
}
// 运行
PS E:\goland\demo> go run .\main.go
time.Time 2022-06-30 22:43:39.0639872 +0800 CST m=+0.009285001
string 2022-06-30 22:43:39
// 发现上下结果的类型变了

2.1.3:时间戳

package main

import (
	"fmt"
	"time"
)

func main() {
	// 时间对象
	now := time.Now()
	fmt.Printf("%T %v\n", now, now)

	// 格式化时间
	strTime := now.Format("2006-01-02 15:04:05")
	fmt.Printf("%T %v\n", strTime, strTime)

	// 时间戳
    timestamp := now.Unix()   // Unix()方法您转换为整型
	fmt.Printf("%T %v\n", timestamp, timestamp)
}

// 运行
PS E:\goland\demo> go run .\main.go
time.Time 2022-06-30 22:47:35.2310881 +0800 CST m=+0.004992001
string 2022-06-30 22:47:35
int64 1656600455     // 这里就是秒的时间戳

2.1.4:格式化时间转换时间对象

package main

import (
	"fmt"
	"time"
)

func main() {
	// 时间对象
	now := time.Now()
	fmt.Printf("%T %v\n", now, now)

	// 格式化时间
	strTime := now.Format("2006-01-02 15:04:05")
	fmt.Printf("%T %v\n", strTime, strTime)

	// 时间戳
	timestamp := now.Unix()
	fmt.Printf("%T %v\n", timestamp, timestamp)

	// 格式化时间转换时间对象
	loc, _ := time.LoadLocation("Asia/Shanghai")
	timeObj, _ := time.ParseInLocation("2006-01-02 15:04:05", strTime, loc)
	fmt.Printf("%T %v\n", timeObj, timeObj)
}
// 运行
PS E:\goland\demo> go run .\main.go
time.Time 2022-06-30 23:00:54.4282738 +0800 CST m=+0.005265901
string 2022-06-30 23:00:54
int64 1656601254
time.Time 2022-06-30 23:00:54 +0800 CST       // 将上面的strTime再次转回时间对象

2.2:时间类型

1:我们可以通过time.Now()函数获取当前的时间对象,然后获取时间对象年月日时分秒等信息
2:注意:%02d中的2表示宽度,如果整数不够2列就补上0

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now() // 获取当前时间
	fmt.Printf("current time: %v\n", now)

	year := now.Year()     // 获取年
	month := now.Month()   // 获取月
	day := now.Day()       // 获取日
	hour := now.Hour()     // 获取小时
	minute := now.Minute() // 获取分钟
	second := now.Second() // 获取秒

	strTime := fmt.Sprintf("%d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second)  // 自定义输出
	fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
	fmt.Printf("%T %v\n", strTime, strTime)
}

// 运行结果如下
PS E:\goland\demo> go run .\main.go
current time: 2022-07-01 01:29:38.3724471 +0800 CST m=+0.005294801
2022-07-01 01:29:38
string 2022-07-01 01:29:38

2.3:时间戳

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()            // 获取当前时间
	timestamp1 := now.Unix()     // 获取时间戳
	timestamp2 := now.UnixNano() // 获取纳秒时间戳
	fmt.Printf("Current timestamp: %v\n", timestamp1)
	fmt.Printf("Current timestamp: %v\n", timestamp2)
}
// 运行
PS E:\goland\demo> go run .\main.go
Current timestamp: 1656659722
Current timestamp: 1656659722987047900

// 适用time.Unix()函数可以将时间戳转换为时间格式


package main

import (
	"fmt"
	"time"
)

func main() {
	timestamp(1656659722)
}

func timestamp(timestamp int64) {
	timeObj := time.Unix(timestamp, 0)
	fmt.Println(timeObj)

	yead := timeObj.Year()
	month := timeObj.Month()
	day := timeObj.Day()
	hour := timeObj.Hour()
	minute := timeObj.Minute()
	second := timeObj.Second()
	fmt.Println(yead, month, day, hour, minute, second)
}
// 在main引用此函数,传入一个时间戳会得到一个时间
PS E:\goland\demo> go run .\main.go
2022-07-01 15:15:22 +0800 CST
2022 July 1 15 15 22

2.4:时间间隔

1:time.Duration是time包定义的一个类型,它代表两个时间点之间经过的时间,以纳秒为单位。
2:time.Duration表示一段时间的间隔,可表示最长时间大约是290年
3:time包中定义时间间隔类型的常量如下:

cost (
	Nanosecond Duration = 1
	Microsecond = 1000 * Nanosecond
	Millisecond = 1000 * Microsecond
	Second = 1000 * Millisecond
	Minute = 60 * Second
	Hour = 60 * Minute
)

package main

import (
	"fmt"
	"time"
)

func main() {
	time.Sleep(time.Second * 1)   // 这里可以直接使用, *1 就是间隔一秒
	fmt.Println("Overtime")
}

2.5:时间格式化

1:时间类型有一个自带的方法Format进行格式化
2:需要注意的是Go语言中的时间格式化不是常见的(Y-m-d H:M:S)
3:而且Go的诞生时间是2006年1月2号15时04分(2006 01 02 15 04)
4:如果想格式化为12小时方式,需要指定PM。

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	fmt.Printf("%T %v\n", now, now)  // 列出源类型

	now2 := now.Format("2006-01-02 15:04:05")  // 格式化数据赋值
	fmt.Printf("%T %v\n", now2, now2)      // 列出格式化后类型与值
}
// 运行
PS E:\goland\demo> go run .\main.go
time.Time 2022-07-02 02:00:15.9143699 +0800 CST m=+0.007082501
string 2022-07-02 02:00:15


// 字符串转实践类型

 package main

import (
	"fmt"
	"time"
)

func main() {
    // 定义要转的时区
	loc, _ := time.LoadLocation("Asia/Shanghai")
    // 按照指定时区去解析输入时间 2006-01-02 15:04:05 这个是固定值
	timeObj, err := time.ParseInLocation("2006-01-02 15:04:05", "2022-07-02 19:01:00", loc)  
	if err != nil {    // 如果err不为空,则打印
		panic(err)
	} else {           // 否则打印timeObj的值
		fmt.Printf("%T %v\n", timeObj, timeObj)
	}
}

// 运行
PS E:\goland\demo> go run .\main.go
time.Time 2022-07-02 19:01:00 +0800 CST

// 转当前时间

package main

import (
	"fmt"
	"time"
)

func main() {
	loc, _ := time.LoadLocation("Asia/Shanghai")
    // 直接用 time.Now()获取当前时间再用In()传入时区可直接帮你转换,可以去研究一下In()的源码时怎么写的。
    nowTime := time.Now().In(loc)
	fmt.Printf("%T %v\n", nowTime, nowTime)
}
// 运行
PS E:\goland\demo> go run .\main.go
time.Time 2022-07-02 19:08:17.2959165 +0800 CST

2.6:时间操作函数

2.6.1:Add

1:我们在日常日期编码过程中,可能回遇到要求时间加时间隔的要求
2:Go语言的时间对象有提供Add方法

2.6.2:Sub

1:求两个时间之间的差值

package main

import (
	"fmt"
	"time"
)

func main() {
	// 计算一分钟前

	now := time.Now() // 获取当前时间
	fmt.Println(now)
	m, _ := time.ParseDuration("-1m") // 解析时间
	m1 := now.Add(m)                  // 当前时间减去一分钟
	fmt.Println(m1)

	// 计算分钟后`
	mm, _ := time.ParseDuration("1m") // 解析时间
	mm1 := now.Add(mm)                // 当前时间加一分钟
	fmt.Println(mm1)
}

// 运行
PS E:\goland\demo> go run .\main.go
2022-07-02 22:43:16.05806 +0800 CST m=+0.006119901
2022-07-02 22:42:16.05806 +0800 CST m=-59.993880099
2022-07-02 22:44:16.05806 +0800 CST m=+60.006119901

3:encoding-json

3.1:stuct与json

1:比如Golang要给APP或者小程序提供API接口,这个时候休要用到结构体与Json之间的互相转换
2:GolangJSON的序列化是指把结构体数据转换成JSON格式的字符串
3:GolangJSION的序列化是指把JSON字符串转换成Golang中的结构体对象
4:Golang中的序列化与反序列化主要是通过encoding/json包中的"json.Marshal()"和"json.Unmashal()"方法实现

3.2:struct转json

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	var a = Student{
		Name: "John",
		Age:  20,
		ID:   1,
	}
	fmt.Printf("%T %v\n", a, a)

	var s, _ = json.Marshal(a)
	jsonStr := string(s)
	fmt.Printf("%T %v\n", jsonStr, jsonStr)
}

type Student struct {
    gender string              // 这里的私有属性不能被Json包访问
	Name string
	Age  int
	ID   int
}


// 运行
PS E:\goland\demo> go run .\main.go
main.Student {John 20 1}
string {"Name":"John","Age":20,"ID":1}


// 再看一种写法
package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	a := Student{
		Name: "John",
		Age:  20,
	}
	fmt.Printf("%T %v\n", a, a)

	var s, err = json.Marshal(a)
	if err != nil {
		fmt.Println(err)
	} else {
		jsonStr := string(s)
		fmt.Printf("%T %s\n", jsonStr, jsonStr)
	}
}

type Student struct {
	Name string
	Age  int
}

// 效果是一样的只是在main加了err输出

3.3:json转struct

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	var jsonStr = `{"Name":"John","Age":30,"ID":1}`
	var student Student    // 定义一个Monster实例
	err := json.Unmarshal([]byte(jsonStr), &student)
	if err != nil {
		panic(err)
	} else {
		fmt.Printf("%T %+v\n", student, student)
	}
}

type Student struct {
	Name string
	Age  int
	ID   int
}

// 运行
PS E:\goland\demo> go run .\main.go
main.Student {Name:John Age:30 ID:1}

// 转换成json
package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	a := Person{
		ID: 1,
		Stu: []Student{
			{
				Name: "zhangsan",
				Age:  18,
				Id:   1,
			},
		},
	}
	s, err := json.Marshal(a)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Printf("%T %v", string(s), string(s))
	}
}

type Person struct {
	ID  int
	Stu []Student
}

type Student struct {
	Name string
	Age  int
	Id   int
}
// 运行
PS E:\goland\demo\server> go run .\main.go
string {"ID":1,"Stu":[{"Name":"zhangsan","Age":18,"Id":1}]}

// That`s Good

3.4:结构体嵌套

package main

import "fmt"

func main() {
	a := Person{
		ID: 1,
		Stu: []Student{   // 这里有一个坑就是括号,括号内还有括号
			{
				Name: "zhangsan",
				Age:  18,
				Id:   1,
			},
		},
	}
	fmt.Printf("%+v\n", a)
}

type Person struct {
	ID  int
	Stu []Student
}

type Student struct {
	Name string
	Age  int
	Id   int
}

// 运行
PS E:\goland\demo\server> go run .\main.go
{ID:1 Stu:[{Name:zhangsan Age:18 Id:1}]}

3.5:struct tag

3.5.1:Tag标签说明

1:Tag是结构体的元信息,可以在运行的时候通过反射机制读取出来。
2:Tag在结构体后方定义的,由一组反引号包裹起来
3:具体格式如下
key1:"value1" key2:"value2"
4:结构体Tag由一个或多个键值对组成,键与值用冒号分隔,值需要使用双引号括起来
5:同一个结构体字段可以设置多个键值对Tag,不同键值对之间使用空格分隔
6:注意事项:
	6.1:为结构图编写tag时,必须严格遵守键值对的规则
	6.2:结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值
	6.3:例如不要再Key和value中间添加空格

3.5.2:Tag结构体转化Json字符串

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	var a = Student{
		Name: "Zhangsan",
		Age:  18,
		ID:   1,
	}
	fmt.Printf("%#v\n", a)

	var s, _ = json.Marshal(a)
	jsonStr := string(s)
	fmt.Println(jsonStr)
}

type Student struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
	Id   int    `json:"id"` // 通过指定Tag实现json序列化该字段时的Key
}

// 运行
PS E:\goland\demo\server> go run .\main.go
main.Student{Name:"Zhangsan", Age:18, ID:1}
{"name":"Zhangsan","age":18,"id":1}

3.5.3:Json字符串转成Tag结构体

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	var person Person
	var str = `{"name":"John","age":30,"id":1}`
	fmt.Printf("%T %v\n", str, str)
	err := json.Unmarshal([]byte(str), &person)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Printf("%T %v\n", person, person)
	}
}

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

// 运行
PS E:\goland\demo\server> go run .\main.go
string {"name":"John","age":30,"id":1}
main.Person {John 30 1}

4:Flag

4.1:Flag

1:Go语言内置的Flag包实现了命令行参数的解析,Flag包使得开发命令行工具更为简单。
2:flag.Parse(),用于解析命令参数
3:它支持以下几种命令参数
	3.1:-flag xxx (使用空格,一个-符号)
	3.2:--flag xxx (使用空格,两个-符号)
	3.3:-flag=xxx (使用等号,一个-符号)
	3.4:--flag=xxx (使用等号,两个-符号)
	// 注:布尔类型的参数必须使用等号的方式定义
Flag解析在第一个非flag参数(单个"-"不是flag参数)之前停止,或者在终止符"-"之后停止

4.2:其他函数

1:flag.Args()    // 返回命令行参数后的其他参数,以[]string类型
2:flag.NArg()    // 返回命令行参数后的其他参数的个数
3:flag.NFlag()   // 返回使用命令行参数的个数

4.3:Flag实战

package main

import (
	"flag"
	"fmt"
)

func main() {
	var name string
	flag.StringVar(&name, "name", "zhangsan", "name") // 这里的参数会在程序的 --help内展示
	flag.Parse()                                      // 解析命令行参数
	fmt.Println(flag.Args())                          // 输出命令行参数
}

// 运行
PS E:\goland\demo> go run .\main.go --help   // 查看帮助
Usage of C:\Users\ADMINI~1\AppData\Local\Temp\go-build2603138518\b001\exe\main.exe:
  -name string
        name (default "zhangsan")

// 最简单的传参(非flag命令行传参)
PS E:\goland\demo> go run .\main.go zhangsan
[zhangsan]

// flag命令行传参
package main

import (
	"flag"
	"fmt"
)

func main() {
	var name string
	flag.StringVar(&name, "name", "null", "name") // 这里的参数会在程序的 --help内展示
	flag.Parse()                                  // 解析命令行参数
	fmt.Println(flag.Args(), name)                // 输出命令行参数 flag.Args() 获取指定参数
}

// 运行
PS E:\goland\demo> go run .\main.go -name "zhangsan"
[] zhangsan
// 第一个是获取指定参数,第二个是我们传进去的参数
              
PS E:\goland\demo> go run .\main.go -name "zhangsan" 123 456 789
[123 456 789] zhangsan

// 我们发现的是位置参数给传进去了
              
// 还有以下几个参数
flag.StringVar()           // 针对于String
flag.Float64Var()          // 针对于浮点数
flag.BoolVar()             // 针对于布尔值
flag.IntVar()              // 针对于Int

5:OS模块

5.1:os.Create()

package main

import (
	"fmt"
	"os"
)

func main() {
	createFile()
}

func createFile() {
	f, err := os.Create("test.txt")
	if err != nil {
		panic(err)
	} else {
		fmt.Printf("f: %v\n", f)  // 也可以是:f.Close()
	}
}

// 运行
PS E:\goland\demo> go run .\main.go
f: &{0xc00010e780}

PS E:\goland\demo> ls


    目录: E:\goland\demo


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2022-07-03     20:13                .idea
-a----        2022-06-18     23:14             21 go.mod
-a----        2022-07-03     23:18            173 main.go
-a----        2022-07-03     23:18              0 test.txt   // 创建成功

5.2:os.Mkdir()与os.MkdirAll()

package main

import (
	"fmt"
	"os"
)

func main() {
	createDirectory()
}

func createDirectory() {
	err := os.Mkdir("directory", os.ModePerm)
	if err != nil {
		fmt.Printf("err: %v\n", err)
	}
}

// 运行
PS E:\goland\demo> ls


    目录: E:\goland\demo


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2022-07-03     20:13                .idea
d-----        2022-07-03     23:22                directory  // 创建成功
-a----        2022-06-18     23:14             21 go.mod
-a----        2022-07-03     23:23            196 main.go
-a----        2022-07-03     23:18              0 test.txt



package main

import (
	"fmt"
	"os"
)

func main() {
	createDirectory()
}

func createDirectory() {
	//err := os.Mkdir("directory", os.ModePerm)
	//if err != nil {
	//	fmt.Printf("err: %v\n", err)
	//}
	err := os.MkdirAll("directory/subdirectory", os.ModePerm)
	if err != nil {
		fmt.Printf("err: %v\n", err)
	}
}

// 运行
PS E:\goland\demo> ls .\directory\


    目录: E:\goland\demo\directory


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2022-07-03     23:25                subdirectory

5.3:os.Remove()

package main

import (
	"fmt"
	"os"
)

func main() {
	removeDir()
}

func removeDir() {
	err := os.Remove("./test.txt")  // 指定要删除的文件
	if err != nil {
		fmt.Printf("remove file error: %v\n", err)
	} else {
		fmt.Println("remove file success")
	}
}
// 运行
PS E:\goland\demo> ls


    目录: E:\goland\demo


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2022-07-03     20:13                .idea
d-----        2022-07-03     23:25                directory
-a----        2022-06-18     23:14             21 go.mod
-a----        2022-07-03     23:30            271 main.go


// 删除目录
package main

import (
	"fmt"
	"os"
)

func main() {
	removeDir()
}

func removeDir() {
	//err := os.Remove("./test.txt")
	//if err != nil {
	//	fmt.Printf("remove file error: %v\n", err)
	//} else {
	//	fmt.Println("remove file success")
	//}
	err := os.RemoveAll("./directory")   // 和删除文件一样
	if err != nil {
		fmt.Printf("remove directory error: %v\n", err)
	} else {
		fmt.Println("remove directory success")
	}
}

// 运行
PS E:\goland\demo> ls


    目录: E:\goland\demo


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2022-07-03     20:13                .idea
-a----        2022-06-18     23:14             21 go.mod
-a----        2022-07-03     23:32            404 main.go

5.4:os.Getwd()

package main

import (
	"fmt"
	"os"
)

func main() {
	getWd()
}

func getWd() {
	wd, err := os.Getwd()
	if err != nil {
		fmt.Printf("Error: %s\n", err)
	} else {
		fmt.Printf("Current working directory: %s\n", wd)
	}
}

// 运行
PS E:\goland\demo> go run .\main.go
Current working directory: E:\goland\demo

5.5:os.Chdir()

package main

import (
	"fmt"
	"os"
)

func main() {
	chDir()
}

func chDir() {
	err := os.Chdir("C:/")  // 指定修改的工作目录
	if err != nil {
		fmt.Printf("Error: %s\n", err)
	} else {
		fmt.Println("Success")
	}
}

// 这里可以看到切换成功的话会打印 Success
PS E:\goland\demo> go run .\main.go
Success

5.6:os.TempDir()

package main

import (
	"fmt"
	"os"
)

func main() {
	tempDir()
}

func tempDir() {
	s := os.TempDir()
	fmt.Printf("TempDir is: %s\n", s)
}

// 运行
PS E:\goland\demo> go run .\main.go
TempDir is: C:\Users\ADMINI~1\AppData\Local\Temp

5.7:os.Rename()

// 列出文件
PS E:\goland\demo> ls


    目录: E:\goland\demo


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2022-07-03     20:13                .idea
-a----        2022-06-18     23:14             21 go.mod
-a----        2022-07-03     23:47            185 main.go
-a----        2022-07-03     23:47              0 old

// 重命名old为new
package main

import (
	"fmt"
	"os"
)

func main() {
	reName("old", "new")    // 调用函数并传参
}

func reName(o string, n string) {   // 传入参数 o 为老文件名 n 为新文件名
	err := os.Rename(o, n)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("rename success")
	}
}

// 运行
PS E:\goland\demo> go run .\main.go
rename success

PS E:\goland\demo> ls


    目录: E:\goland\demo


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2022-07-03     20:13                .idea
-a----        2022-06-18     23:14             21 go.mod
-a----        2022-07-03     23:49            219 main.go
-a----        2022-07-03     23:47              0 new

// 修改完成

5.8:os.ReadFile()

package main

import (
	"fmt"
	"os"
)

func main() {
	redadFile("text.txt")  // 指定读取文件
}

func redadFile(file string) {
	b, err := os.ReadFile(file)
	if err != nil {
		fmt.Printf("err: %v\n", err)
	} else {
		fmt.Printf("%v\n", string(b[:]))
	}
}

// 运行
PS E:\goland\demo> go run .\main.go
123
456
789

5.9:os.WriteFile()

package main

import (
	"fmt"
	"os"
)

func main() {
	writeFile("text.txt", "Hello World")
	readFile("text.txt")
}

func readFile(filename string) {
	b, err := os.ReadFile(filename)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
	} else {
		fmt.Printf("%s\n", string(b[:]))
	}
}

func writeFile(file, data string) {
	os.WriteFile(file, []byte(data), os.ModePerm)
}

// 运行
PS E:\goland\demo> go run .\main.go
Hello World

6:内置运算符

6.1:算数运算符及使用

运算符 描述
+ 相加
- 相减
* 相乘
/ 相除
% 求余=被除数-(被除数/除数)*除数
package main

import "fmt"

func main() {
	fmt.Println(10 + 1)
	fmt.Println(10 - 1)
	fmt.Println(10 * 2)
	// 加减乘都没什么问题,但是除法有问题
	// 如果运算的数都为整数,那么结果为整数,去掉小数部分,保留整数部分
	fmt.Println(10 / 3)
	fmt.Println(10.0 / 3)
	// 取余数需要注意余数=被除数-(被除数/除数)*除数
	fmt.Println(10 % 3)
	fmt.Println(-10 % 3)
	fmt.Println(10 % -3)
	fmt.Println(-10 % -3)
}
// 运行
PS E:\goland\demo\server> go run .\main.go
11
9
20
3
3.3333333333333335
1
-1
1
-1

6.2:i++

package main

import "fmt"

func main() {
	var i = 1
	i++
	fmt.Printf("%d\n", i)
}
// 运行
PS E:\goland\demo\server> go run .\main.go
2

// 当代码运行到我们的 i++的时候,我们定义的初始值都会被加1

6.3:关系运算符

运算符 描述
== 检查两个值是否相等,如果相等则返回True,否则返回False
!= 检查两个值是否不相等,如果不相等则返回True,否则返回False
> 检查左边的值是否大于右边的值,如果是则返回True,否则返回False
>= 检查左边的值是否大于等于右边的值,如果是则返回True,否则返回False
< 检查左边的值是否小于右边的值,如果是则返回True,否额返回False
<= 检查左边的值是否小于等于右边的值,如果是则返回True,否则返回False
package main

import "fmt"

func main() {
	var a int = 1
	var b int = 2

	fmt.Println(a == b)
	fmt.Println(a != b)
	fmt.Println(a < b)
	fmt.Println(a > b)
	fmt.Println(a <= b)
	fmt.Println(a >= b)

	flag := a < b
	fmt.Println("flag=", flag)
}

// 运行
PS E:\goland\demo\server> go run .\main.go
false
true
true
false
true
false
flag= true

6.4:逻辑运算符

运算符 描述
&& 逻辑AND运算符,如果两边操作都是True,则为True,否则为False
|| 逻辑OR运算符,如果两边操作有一个为True,则为True,否则为False
| 逻辑NOT运算符,如果条件为True。则为False,否则为True
package main

import "fmt"

func main() {
	var age int = 40

	if age > 30 && age < 50 {
		fmt.Println("OK1")
	}

	if age > 30 || age < 40 {
		fmt.Println("OK2")
	}

	if age > 30 || age < 50 {
		fmt.Println("OK3")
	}

	if age > 30 || age < 40 {
		fmt.Println("OK4")
	}

	if age > 30 {
		fmt.Println("OK5")
	}

	if !(age > 30) {
		fmt.Println("OK6")
	} else {
		fmt.Println("OK7")  // 这里的age是40,所以按理来说应该输出的是OK7
	}
}

// 运行
PS E:\goland\demo\server> go run .\main.go
OK1
OK2
OK3
OK4
OK5
OK7

6.5:赋值运算符

运算符 描述
= 简单的赋值运算符,将一个表达式的值赋值给左值
+= 相加后再赋值
-= 相减后再赋值
*= 相乘后再赋值
/= 相除后再赋值
%= 求余后再赋值
package main

import "fmt"

func main() {
	d := 10 + 2*10
	fmt.Printf("%d\n", d)

	x := 10
	x += 2
	fmt.Printf("%d\n", x)
}

// 运行
PS E:\goland\demo\server> go run .\main.go
30
12

7:条件循环

7.1:if else(分支结构)

7.1.1:判断条件的基本写法

package main

import "fmt"

func main() {
	source := 60

	if source >= 60 {
		fmt.Println("passed_1")
	} else if source > 70 {
		fmt.Println("passed_2")
	} else {
		fmt.Println("failed")
	}
}

// 运行
PS E:\goland\demo\server> go run .\main.go
passed_1

// 因为是先匹配到了第一条规则,随笔直接执行了打印,所以没有再往下执行,就没有打印 passed_2

7.1.2:if 语句判断特殊写法

1:if语句判断还有一种特殊的写法,可以在if表达式之前添加一个执行语句,再根据变量值进行判断
// 但是这里就有一个缺点了,这个变量就只能应用在if语句内了

package main

import "fmt"

func main() {
	if source := 80; source > 60 {
		fmt.Println("source is greater than 60")
	} else if source < 60 {
		fmt.Println("source is less than 60")
	}
}
// 运行
PS E:\goland\demo\server> go run .\main.go
source is greater than 60

7.2:for(循环结构)

7.2.1:for循环

// 内部定义i参数

package main

import "fmt"

func main() {
	// 列出0-9的数字
	for i := 0; i < 10; i++ {
		fmt.Println(i)
	}
}

// 运行
PS E:\goland\demo\server> go run .\main.go
0
1
2
3
4
5
6
7
8
9


// 外部定义 i 参数

package main

import "fmt"

func main() {
	// 列出0-9的数字
	i := 0
	for i < 10 {
		fmt.Println(i)
		i++
	}
}
// 运行
PS E:\goland\demo\server> go run .\main.go
0
1
2
3
4
5
6
7
8
9
// 前面讲了i++,这里就应用到了

7.2.2:模拟while循环

1:Go语言中是没有while循环语句的,但是我们可以模拟出来一个

package main

import "fmt"

func main() {
	// 模拟一个while循环
	i := 1
	for {   // 这里没有结束条件,所以理论来说这个循环无法停止
		if i > 10 {        // 但是这里循环内部有一个限制的结束条件来跳出循环
			break
		} else {
			fmt.Println(i)
			i++       // 这里有个i++的顺序,在打印前的话就先加再打印,在打印后就是先打印再加再循环
		}
	}
}

// 运行
PS E:\goland\demo\server> go run .\main.go
1
2
3
4
5
6
7
8
9
10

7.2.3:for range(键值循环)

package main

import "fmt"

func main() {
	str := "Hello Shanghai"
	for i, v := range str {
		fmt.Printf("键:%v,值:%c\n", i, v)
	}
}
// 执行
PS E:\goland\demo\server> go run .\main.go
键:0,值:H
键:1,值:e
键:2,值:l
键:3,值:l
键:4,值:o
键:5,值:
键:6,值:S
键:7,值:h
键:8,值:a
键:9,值:n
键:10,值:g
键:11,值:h
键:12,值:a
键:13,值:i

// 在前面讲过range()函数返回的是两个值,一个是索引一个是数,所以我们需要两个参数来接收
i:接收索引
v:接收数据

7.3:switch case

使用 switch 语句可方便地替代if else 来解决部分问题

package main

import "fmt"

func main() {
	name := "zhangsan"
	switch name {
	case "lisi":
		fmt.Println("lisi")
	case "zhangsan":
		fmt.Println("zhangsan")
	default:    // 这里有个默认值,是因为前面的都匹配不上则进入default处理
		fmt.Println("is not lisi or zhangsan")
	}
}
// case 是从上到下匹配的
// 运行
PS E:\goland\demo\server> go run .\main.go
zhangsan

7.4:break和continue

1:break:跳出整个循环
2:continue:跳出本次循环

7.4.1:break

package main

import "fmt"

func main() {
	for i := 0; i < 10; i++ {
		if i == 5 {
			fmt.Println("跳出循环")
			break
		} else {
			fmt.Println(i)
		}
	}
}
// 运行
PS E:\goland\demo\server> go run .\main.go
0
1
2
3
4
跳出循环

7.4.2:continue

package main

import "fmt"

func main() {
	for i := 0; i < 10; i++ {
		if i == 5 {
			fmt.Println("跳出本次循环")
			continue
		} else {
			fmt.Println(i)
		}
	}
}
// 运行
PS E:\goland\demo\server> go run .\main.go
0
1
2
3
4
跳出本次循环
6
7
8
9

8:函数

8.1:函数基础

8.1.1:函数定义

1:函数是组织好的,可重复使用的,用于执行指定代码块

package main

import "fmt"

func main() {
    // 所有函数的触发或调用都是在 main()函数内
	a := isSum(1, 2)   // 调用函数 1,2对应函数内的x,y 类型为int
	fmt.Println(a)
	fmt.Println(isSum)  // 获取函数的内存地址
}
// func 定义函数的关键字  isSum是函数的名称 x,y是传入的值(可自定义) int为传值的类型,int为函数类型
func isSum(x, y int) int {    
	return x + y
}
// 运行
PS E:\goland\demo\server> go run .\main.go
3

8.2:函数变量作用域

8.2.1:全局变量与局部变量

1:全局变量是定义在函数内的外部变量,函数内定义的变量无法在该函数外使用

package main

import "fmt"

func main() {
    // 这个时候 main内的变量是无法解析的,因为name这个时候是 Test()函数的内部变量
	fmt.Println(name)
	Test()
}

name := "zhangsan" // 这个就是全局变量,特点就是任何的函数都可以使用!

func Test() {
	name := "zhangsan"   // 这个是局部变量
	fmt.Println(name)
}

// 运行
PS E:\goland\demo\server> go run .\main.go
# command-line-arguments
.\main.go:6:14: undefined: name

// 发现无法运行,注释掉	fmt.Println(name)
PS E:\goland\demo\server> go run .\main.go
zhangsan
// 这样就OK了


package main

import "fmt"

func main() {
	TestLocalVar()
}

var name string = "zhangsan"

func TestLocalVar() {
	// 定义一个全局变量
	var globalVar int = 10
	// 定义一个局部变量
	var localVar int = 20
	fmt.Printf("name: %v, globalVar: %d, localVar: %d\n", name, globalVar, localVar) // 函数内可任意读取全局变量
}


// 以下问题
package main

import "fmt"

func main() {
	TestLocalVar()
}

var name string = "zhangsan"

func TestLocalVar() {
	// 定义一个全局变量
	var globalVar int = 10
	// 定义一个局部变量
	var localVar int = 20
	var name string = "lisi"
	fmt.Printf("name: %v, globalVar: %d, localVar: %d\n", name, globalVar, localVar)
}

// 发现变量冲突了,这个时候是函数内的变量先生效哦,意思是局部作用域的优先级更高哦
PS E:\goland\demo\server> go run .\main.go
name: lisi, globalVar: 10, localVar: 20

8.2.2:for循环语句中定义变量

1:我们之前讲过for循环定义变量也只是在for语句中生效

package main

import "fmt"

func main() {
	TestLocalVar()
}

func TestLocalVar() {
	for i := 0; i < 10; i++ {      //  i 只在函数内的当前的for语句中生效,
		fmt.Println(i)
	}
    fmt.Println(i)                 // 这个操作是无法实现的
}

// 运行
PS E:\goland\demo\server> go run .\main.go
0
1
2
3
4
5
6
7
8
9

9:结构体

9.1:什么是结构体

1:Go语言中没有类的概念,也不支持类的继承等面向对象的概念
2:Go语言是通过结构体的内嵌再配合接口比面向对象有更高的扩展性和灵活性

9.2:结构体定义

1:基本实例化(1)
	1.1:只有当结构体实例化时,才可以真正的分配到内存,也就是说必须实例化后才能使用结构体的字段
	1.2:结构体本身也是一种类型,我们可以像声明内置类型一样使用var 关键字声明结构体类型、
2:结构体与函数的区别,什么时候用结构体
	2.1:结构体是Go语言中实现类方法的一种方案,简单来说一个是类,一个是方法


package main

import "fmt"
// 定义一个结构体   type关键字,Person结构体名字,struct定义结构体的关键字
type Person struct {
	Name string
	City string
	Age  int
}

func main() {
	var a Person   // 第一种实例化
	a.Name = "John"
	a.City = "Shanghai"
	a.Age = 30

	var na = new(Person)   // 第二种实例化,返回指针类型
	na.Name = "Zhangsan"
	na.City = "Beijing"
	na.Age = 20
	
    aaa := Person{     // 第三种 (常用)
		Name: "Lisi",
		City: "Hangzhou",
		Age:  19,
	}
	fmt.Printf("%v\n%v\n%v", a, na, aaa)
}
// 运行
PS E:\goland\demo\server> go run .\main.go
{John Shanghai 30}
&{Zhangsan Beijing 20}
{Lisi Hangzhou 19}

9.3:结构体方法和接收者

9.3.1:结构体说明

1:在Go语言中,没有类的概念,但是可以给类型(结构体,自定义类型)定义方法
2:所谓的方法就是定义接收者的函数
	2.1:Go语言中的方法(Method)是一种作用于特定类型变量的函数
	2.2:这种特定类型变量叫做接收者(Receiver)
	2.3:接收者的概念就类似于其他语言的this或者self
3:方法定义格式如下
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
	// 方法体
}
4:给Person定义一个方法打印Person信息

9.3.2:结构体方法和接收者

package main

import "fmt"
// 定义结构体
type Person struct {
	Name string
	Age  int
}
// 绑定方法与接收者,基于函数 func printInfo() {}  在 func 与  printInfo() 中间加 一个接收者(p Person)
// 接收者的概念类似于,Python中类函数中的self,或者是js中类中的this
// Persion 就是绑定接收者的结构体,而p就是别名
// printInfo() 这个时候就不叫函数了,它叫做:结构体方法
func (p Person) printInfo() {
	fmt.Printf("%s is %d years old\n", p.Name, p.Age)
}

func main() {
    // 实例化结构体
	a := Person{
		Name: "John",
		Age:  30,
	}
	a.printInfo()  // 通过实例化名称调用结构体方法,类似于调用类方法
}
// 运行
PS E:\goland\demo\server> go run .\main.go
John is 30 years old

// 这个时候可能会有人问我实例化第二个结构体和第一个有冲突么,那么下面的代码来看一下

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func (p Person) printInfo() {
	fmt.Printf("%s is %d years old\n", p.Name, p.Age)
}

func main() {
	a := Person{
		Name: "John",
		Age:  30,
	}
	a.printInfo()

	b := Person{
		Name: "Mary",
		Age:  25,
	}
	b.printInfo()
}

// 运行
PS E:\goland\demo\server> go run .\main.go
John is 30 years old
Mary is 25 years old
// 可以看到,完全没有冲突的

9.3.3:值类型和指针类型的接收者

1:实例1:给结构体Person定义一个方法打印Person的信息
	1.1:值类型的接收者
		1.1.1:当前类型的方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份
		1.1.2:在值类型接收者的方法中可以获取接收者的成员信息,但修改操作只是针对副本。无法修改接收者变量本身。
	1.2:指针类型接收者
		1.2.1:指针类型接收者由一个结构体的指针组成
		1.2.2:由于指针的特性,调用方法时修改接收者指针的任意成员变量。在方法结束后,修改都是有效的。
		1.2.3:这种方法就十分接近于其他方法的面向对象中的this或self

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

// 指针类型接收者
func (p *Person) setInfo() {
	fmt.Printf("%s is %d years old\n", p.Name, p.Age)
	p.Name = "李四"
}

// 值类型接收者
func (p Person) personInfo() {
	fmt.Printf("%s is %d years old\n", p.Name, p.Age)
	p.Name = "王五"
}

func main() {
	p := Person{
		Name: "张三",
		Age:  18,
	}
	p.setInfo()    // 调用值类型接收者的方法
	p.personInfo() // 调用值类型接收者的方法
}

// 运行
PS E:\goland\demo\server> go run .\main.go
张三 is 18 years old
李四 is 18 years old

// 解释1:这里可以清晰的看出,当我们针对指针类型的接收者进行修改参数的时候它是可以调用方法就会生效的。即 李四
// 解释2:可以看到我们实例化的时候定义的是 张三。然而我们在值类型接收者也修改了参数但是没有生效,这因为Go在值类型接收者运行前拷贝了一份代码,而我们的修改恰恰是拷贝的那个,所以我们的修改没有生效,它的生效只在接收者内生效

// 接下来我们换个方法优化一下代码
package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

// 定义一个指针类型接收者的方法
func (p *Person) setInfo(name string, age int) {
	fmt.Printf("%s %d\n", p.Name, p.Age)
	p.Name = name
	p.Age = age
}

// 定义值类型接收者的方法
func (p Person) personInfo(name string, age int) {
	fmt.Printf("%s %d\n", p.Name, p.Age)
	p.Name = name
	p.Age = age
}

func main() {
	p := Person{
		Name: "张三",
		Age:  18,
	}
	// 调用指针类型接收者的方法
	p.setInfo("李四", 20)
	// 调用值类型接收者的方法
	p.personInfo("王五", 30)
}

// 运行
PS E:\goland\demo\server> go run .\main.go
张三 18
李四 20
// 我们将修改值以参数的方式传进去处理并返回

9.4:Struct与Json

详看:3:encoding-json

10:面向对象

10.1:Golang的接口定义

10.1.1:Golang中的接口

1:在Go语言中接口(interface)是一种类型,一种抽象的类型
2:接口(interface)定义一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范细节,和API接口没有任何关系
3:实现一个接口的条件
	3.1:一个对象只要全部实现了接口中的方法,那么就实现了这个接口
	3.2:换句话说,接口就是一个需要实现的方法列表

10.1.2:为什么要使用接口

1:比如你定义了一个电车和自行车,它们都会跑,这里就有一个重复的,它们都会跑
2:如果你在后面加了:摩托车,拉力车,汽车等,那他们还是一个功能,都会跑
3:那么我们是否可以把它们全部当作"会跑的车"来处理呢

package main

import "fmt"

type Car struct {
	Name string
}

type BigCar struct {
	Name string
}

func (c Car) GetName() string {
	return c.Name
}

func (b BigCar) GetName() string {
	return b.Name
}

func main() {
	car := Car{Name: "宝马"}
	fmt.Println(car.GetName())

	bigcar := BigCar{Name: "奥迪"}
	fmt.Println(bigcar.GetName())
}

// 运行
PS E:\goland\demo\server> go run .\main.go
宝马
奥迪
// 这个时候我们会发发现我们的重复代码还是非常的多的

// 那么接下来我们就是要定定义一个接口来处理这个问题
package main

import "fmt"

type Server interface { // 这里定义了Server接口
	start()
	stop()
}

type Computer struct { // 这里实现了Server接口
	Name string
}

func (st Computer) start() { // 这里实现了Server接口的start方法
	fmt.Printf(st.Name + " Starting...\n")
}

func (sp Computer) stop() { // 这里实现了Server接口的stop方法
	fmt.Println(sp.Name + " Stopping...")
}

func main() {
	var s Server = Computer{ // 通过接口来调用方法
		Name: "Linux",
	}
	s.start() // 这里调用了Server接口的start方法
	s.stop()  // 这里调用了Server接口的stop方法
}


// 运行
PS E:\goland\demo\server> go run .\main.go
Linux Starting...
Linux Stopping...

10.1.3:Go中类

1:Go语言中没有类的概念,也不支持类的继承等面向对象的概念
2:Go语言是通过结构体的内嵌	再配合接口比面向对象有更高的扩展性

10.2:空接口

10.2.1:空接口说明

1:Golang中空接口也可以当作类来使用,可以表示任意类
2:Golang中的接口可以不定义任何方法,没有定义任何方法的接口就是空接口。
3:空接口表示没有任何约束,因此任何类型变量都可以实现空接口。
4:空接口在实际项目中使用是非常的多的,用空接口可以表示任意数据类型。

10.2.2:空接口作为函数的参数

package main

import "fmt"

func show(a interface{}) {
	fmt.Printf("值:%v,类型:%T\n", a, a)
}

func main() {
	// 这里就可以直接引用接口作为参数使用
	show(1) // 值:1,类型:int
	show("你好!") // 值:你好!,类型:string
	slice := []int{1, 2, 3, 4, 5}
	show(slice) // 值:[1 2 3 4 5],类型:[]int
}

运行结果

PS E:\code\go> go run .\main.go
值:1,类型:int
值:你好!,类型:string     
值:[1 2 3 4 5],类型:[]int

10.2.3:切片实现空接口

// 一般来讲我们定义切片的时候比如:
slice := []int {1,2,3}
slice := []string {"zhangsan","lisi"}

// 但是不能是这种:
slice := []int {1,2,"zhangsan"}
slice := []string {"zhangsan",1}

// 可是接口类型的切片就可以是这样的
package main

import "fmt"

func main() {
	slice := []interface{}{1, 2, 3, "zhangsan"}
	fmt.Printf("值:%v\n类型:%T\n", slice, slice)
}

运行结果

PS E:\code\go> go run .\main.go
值:[1 2 3 zhangsan]
类型:[]interface {}

10.2.4:Map实现空接口

package main

import "fmt"

func main() {
    // 定有一个空接口类型的map并初始化赋值给studentinfo
	var studentinfo = make(map[string]interface{})
	studentinfo["Name"] = "Layzer"
	studentinfo["Age"] = "23"
	studentinfo["City"] = "Shanghai"
	fmt.Printf("值:%v\n类型:%T\n", studentinfo, studentinfo)
}


执行结果

PS E:\code\go> go run .\main.go
值:map[Age:23 City:Shanghai Name:Layzer]
类型:map[string]interface {}

10.2.5:断言使用

1:一个接口的值(简称结构值)是由一个具体的类型和具体类型的值两部分组成的
2:这两部分分别称为接口的动态类型和动态值
3:如果我们要判断空接口中值的类型,那么这个时候可以使用断言类型断言
4:语法格式:a.(X)
	4.1:a表示类型为interface{}的变量
	4.2:X表示断言X可能是的类型

package main

import "fmt"

func main() {
	var a interface{}
	a = 1
	a = a + 1
	fmt.Printf("%T\n", a)
}

这里这样的操作是不允许的,我们可以看一下报错
PS E:\code\go> go run .\main.go
# command-line-arguments                                                      
.\main.go:8:6: invalid operation: a + 1 (mismatched types interface{} and int)

那么这个时候我们就要用到断言语句了,如下方法:

package main

import "fmt"

func main() {
	var a interface{}
	a = 1
	fmt.Printf("%T %v \n", a, a)

	v, ok := a.(int)     // 这里顶定义断言为int的话则输出为true,否则为false
	fmt.Println(v, ok)   // 取值这里的ok就是判断我们的a是否是我们上面定义的类型,是则为true,否则false
	v = v + 1            // 这个时候上面取出来的值赋值给了v,然后就可以拿着v再次进行使用了
	fmt.Println(v)
}

这里只需要记住一句话,使用空接口作为数据输入,比如定义一个断言来取值。

运行结果

PS E:\code\go> go run .\main.go
int 1 
1 true
2     
PS E:\code\go> 

10.3:值接收者与指针接收者

10.3.1:值接收者

1:当方法作用与值类型接收者时,Go语言会在代码运行时将接收的值复制一份
2:在值类型接收者方法中可以获取接收者的成员值,但修改操作只针对复制出来的一份,无法修改接收者变量本身。

package main

import "fmt"

type Server interface {
	start()
	stop()
}

type Computer struct {
	Name string
}

func (st *Computer) start() {
	fmt.Println(st.Name + " started")
}

func (sp Computer) stop() {
	fmt.Println(sp.Name + " stopped")
}

func main() {
	Server_one := Computer{ // 实例化值类型
		Name: "Linux",
	}
	var Son Server = &Server_one // Server_one实现了Server接口Server_one是Computer类型
	Son.start()

	Server_two := &Computer{ //是实例化指针类型
		Name: "Windows",
	}
	var Stw Server = Server_two // Server_two实现了Server接口Server_two是*Computer类型
	Stw.start()
}

执行结果

PS E:\code\go> go run .\main.go
Linux started
Windows started

10.3.2:指针接收者

1:指针类型的接收者是由一个结构体的指针组成
2:由于指针的特性,调用方法时修改接收者指针的任意成员变量,修改都是有效的
3:这种方式十分接近于其它语言中的面型对象的this或者self
4:例如我们为Person添加一个SetAge方法,来修改实例变量的年龄。

10.3.3:指针类型接收者使用时机

1:需要修改接收者的值
2:接收者是拷贝代价较大的对象
3:保证一致性,如果有某个方法使用了指针类型接收者,那么其他方法也应该使用指针接收者。

11:并发编程

11.1:并发介绍

11.1.1:并发和并行

1:多线程程序在一个核的CPU上运行,就是并发。
2:多线程程序在多个核的CPU上运行,就是并行。
11.1.1.1:并发
并发的本质其实还是串行

image

11.1.1.2:并行
任务分布在不同的CPU上,在同一时间点同时执行

image

11.1.2:协程与线程

1:协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上类似于用户级线程,这些用户级线程的调度也是自己实现的。
2:线程:一个线程上可以跑多个协程,协程是轻量级的线程。
3:线程和协程最大的区别
	3.1:开启一个线程需要大概需要2M的空间,而且需要CPU调度才可以执行,线程之间是抢占式
	3.2:开启一个协程大概是4K左右的空间,是由Go解释器自己实现的GPM调度,是主动退出的
	3.3:所以我们可以同时启动成千上万个Goroutine,而不会过大的占用内存。
	3.4:相反,如果我们开启成千上万个线程的话,第一,会大量占用内存导致服务器宕机,第二,操作系统调度线程,本身也需要	  大量的时间,
4:协程如果需要CPU时才会去用,如果不需要,它会主动退出让出CPU
5:线程在时间片内即使不使用CPU,比如当前正在读磁盘数据,它的CPU也不会让出去。

11.2:goroutine

11.2.1:多线程编程缺点

1:在java/c++中我们要实现并发编程的时候,我们通常需要自己维护一个线程池
2:并且需要自己去包装一个又一个的任务,同时需要自己去调度线程执行任务并维护上下文切换

11.2.2:goroutine

1:Go语言中,goroutine就是这样一种机制,goroutine的概念类似于线程,但goroutine是由Go的运行时(runtime)调度和管理
2:Go程序会智能的将goroutine中的任务合理的分配给每个CPU
3:Go语言之所以被称为现代化编程语言,就是因为它在语言层面已经内置了调度和上下文切换机制
4:在Go语言编程中你不需要去自己写进程,线程,协程,你的技能包里只有一个技能,goroutine
5:当你需要让某个任务并发执行的时候,你只需要把这个任务封装成一个函数
6:开启一个goroutine去执行这个函数就可以了,就这么简单。

11.3:协程的基本使用

11.3.1:启动一个协程

1:主线程中每100毫秒打印一次,总打印两次
2:另外开启一个协程,打印10次
3:情况1:打印是交替的,证明是并行
4:情况2:开启的协程打印两次,就退出了(因为主线程退出了)


package main

import (
	"fmt"
	"time"
)

func test() {
	for i := 0; i < 10; i++ {
		fmt.Println("test() Hello Golang")
		time.Sleep(time.Millisecond * 100)
	}
}

func main() {
	go test()
	fmt.Println("Over")
}

这个时候我们只能看到Over输出了,是因为我们的main函数执行完了,但是当我们延迟main函数的时间之后我们就会发现test()函数也会执行,

package main

import (
	"fmt"
	"time"
)

func test() {
	for i := 0; i < 10; i++ {
		fmt.Println("test() Hello Golang")
	}
}

func main() {
	go test()
	time.Sleep(time.Second)
	fmt.Println("Over")
}

PS E:\code\go> go run .\main.go
test() Hello Golang
test() Hello Golang
test() Hello Golang
test() Hello Golang
test() Hello Golang
test() Hello Golang
test() Hello Golang
test() Hello Golang
test() Hello Golang
test() Hello Golang
Over

我们看到协程内的内容也执行了,只要主进程有时间就可以执行协程。	

package main

import (
	"fmt"
	"time"
)

func test() {
	for i := 0; i < 10; i++ {
		fmt.Println("test() Hello Golang")
		time.Sleep(time.Millisecond * 100)
	}
}

func main() {
    go test()     // 这里表示开启了 test()函数使用协程
	for i := 0; i < 2; i++ {
		fmt.Println("main() Hello Golang")
		time.Sleep(time.Millisecond * 100)
	}
}

执行结果

PS E:\code\go> go run .\main.go
main() Hello Golang
test() Hello Golang
test() Hello Golang
main() Hello Golang

这个结果就是我们所谓的并行了。

11.3.2:WaitGroup

1:主线程退出后所有的协程无论有没有执行完毕都会退出
2:所以我们在主进程中可以通过WaitGroup等待协程执行完毕
    2.1:sync.WaitGroup内部维护着一个计数器,计数器的值可以增加或减少
    2.2:例如当我们启动了N个并发任务时,就将计数器值增加N
	2.3:每个任务完成时通过调用Demo()方法将计数器减1
	2.4:通过调用Wait()来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成
	var wg sync.WaitGroup   // 定义一个计数器
	wg.Add()                // 开启一个协程计数器+1
	wg.Down()               // 协程执行完毕,计时器-1
	wg.Wait()               // 计数器为0时退出

package main

import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func test1() {
	for i := 0; i < 10; i++ {
		fmt.Println("test1()", i)
		time.Sleep(100 * time.Millisecond)
	}
	wg.Done()
}

func test2() {
	for i := 0; i < 2; i++ {
		fmt.Println("test2()", i)
		time.Sleep(100 * time.Millisecond)
	}
	wg.Done()
}

func main() {
	wg.Add(1)
	go test1()
	wg.Add(1)
	go test2()
	wg.Wait()
	fmt.Println("main Over")
}

执行结果

PS E:\code\go> go run .\main.go
PS E:\code\go> go run .\main.go
test2() 0
test1() 0
test1() 1
test2() 1
test1() 2
test1() 3
test1() 4
test1() 5
test1() 6
test1() 7
test1() 8
test1() 9
main Over

11.3.3:开启多个协程

1:在Go语言中实现并发就是这样简单,我们还可以启动多个goroutine。
2:这里使用sync.WaitGroup来实现等待goroutine执行完毕
3:多次执行下面的代码,会发现每次打印的顺序都不一样。
4:这是因为10个goroutine时并发执行的,而goroutine的调度是随机的

package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup

func hello(i int) {
	defer wg.Done()     // goroutine结束就-1
	fmt.Println("Hello Goroutine!", i)
}

func main() {
	for i := 0; i < 10; i++ {
		wg.Add(1)    // 启动一个goroutine就登记+1
		go hello(i)
	}
	wg.Wait()    // 等待所有登记的Goroutine结束
}

执行结果

PS E:\code\go> go run .\main.go
Hello Goroutine! 1
Hello Goroutine! 3
Hello Goroutine! 4
Hello Goroutine! 9
Hello Goroutine! 6
Hello Goroutine! 5
Hello Goroutine! 2
Hello Goroutine! 7
Hello Goroutine! 8
Hello Goroutine! 0

多次执行你会发现每次顺序都不一样,这就是goroutine的随即调度

11.4:channel

11.4.1:channel说明

11.4.1.1:共享内存交互数据弊端
1:单纯的将函数并发执行是没有意义的,函数与函数间需要交换数据才能体现并发执行函数的意义
2:虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题
3:为保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必会造成性能问题。
11.4.1.2:channel的好处
1:go语言中通道(channel)是一种特殊的类型
2:通道像是一个传递带或者队列,总是遵循先入先出(First in First Out)的规则,保证收发数据的顺序。
3:每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型
4:如果说goroutine是go程序并发的执行体,那么channel就是它们之间的连接。
5:channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制

11.4.2:channel类型

1:channel是一种引用类型
2:声明管道类型的格式如下:

var 变量名 chan 元素类型
var chan1 chan int  // 声明一个传递整型的管道
var chan2 chan bool  // 声明一个传递布尔型的管道
var chan3 chan []int  // 声明一个传递int切片的管道

11.4.3:创建channal

1:声明管道后需要使用make函数进行初始化后才可以使用
2:创建channel的格式如下:make(chan 元素类型, 容量)

// 创建一个能够存储10个int类型数据的管道
chan1 := make(chan int, 10)
// 创建一个能够存储3个bool类型数据的管道
chan2 := make(chan bool, 3)
// 创建一个能存储3个[]init切片类型数据的管道
chan3 := make(chan []int, 3)


channel操作:

package main

import "fmt"

func main() {
    // 声明一个管道
	ch := make(chan int, 5)
    // 给管道传值
	ch <- 1
	
    // 取管道值
	v1 := <-ch
	fmt.Println(v1)

    // 再次取值就会报错说我们没有值了
	v2 := <-ch
	fmt.Println(v2)
}

执行结果

PS E:\code\go> go run .\main.go
1
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        E:/code/go/main.go:12 +0xb3
exit status 2

但是如果我们在取值之间加上一个close,那么这个时候结果就不一样了

package main

import "fmt"

func main() {
	ch := make(chan int, 5)
	ch <- 1

	v1 := <-ch
	fmt.Println(v1)
	close(ch)
	v2 := <-ch
	fmt.Println(v2)
}

执行结果

PS E:\code\go> go run .\main.go
1
0

这个其实就是我们所说的空channel取值,如果我们向一个空的且没有关闭的channel取值会报错。

11.4.4:如何优雅的从channel取数值

1:当通过通道发送有限的数据时,我们可以通过close函数关闭通道来告知从该通道接收值的goroutine停止等待
2:当通道关闭时,往该通道发送值会引发panic,从该通道接收值一直都是类型零值
3:那如何判断一个通道是否关闭了呢?
4:我们可以使用for range()判断

package main

import "fmt"

func main() {
	ch := make(chan int, 5)
	ch <- 1
	ch <- 2
	ch <- 3
	ch <- 4
	ch <- 5
	close(ch)
	for i := range ch { // 通道关闭后会退出for循环
		fmt.Println(i)
	}
}

执行结果

PS E:\code\go> go run .\main.go
1
2
3
4
5

11.4.5:select多路复用

11.4.5.1:select说明
1:传统的方法在遍历管道时,如果不关闭管道会阻塞而导致deadlock,在实际开发中,可能我们不好确定什么关闭通道。
2:这种方式虽然可以实现从多个管道接收值,但是运行性能会差很多。
3:为了应对这种场景,Go内置了select关键字,可以同时响应多个管道的操作。
4:select的使用类似于switch语句,它有一系列case分支和一个default分支。
5:每个case会对应一个管道通信(接收或者发送)过程。
6:select会一直等待,知道某个case的通信操作完成时,就会执行case分支对应的语句。
7:具体格式如下:

select {
case <- chan1:
   // 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
   // 如果成功向chan2写入数据,则执行该cese处理语句    
default:
   // 如果上面都没有成功,则进入default处理流程
}
11.4.5.2:select的使用
1:使用select语句能够提高代码的可读性
2:可处理一个或多个channel的发送/接收操作
3:如果多个case同时满足,select会随机选择一个
4:对于没有case的select{}会一直等待,可用于阻塞main函数

package main

import (
	"fmt"
	"time"
)

func main() {
	// 在某些场景下我们需要从多个管道中读取数据,这时候我们需要使用select语句。

	// 1:定义一个管道,10个数据int
	intChan := make(chan int, 10)
	for i := 0; i < 10; i++ {
		intChan <- i
	}

	// 2:定义一个管道,5个数据string
	stringChan := make(chan string, 5)
	for i := 0; i < 5; i++ {
		stringChan <- "hello" + fmt.Sprintf("%d", i)
	}

	// 使用select来获取channel中的数据的时候不需要关闭channel
	for {
		select {
		case v := <-intChan:
            // 这里面就是我们要处理的业务逻辑
			fmt.Printf("从intChan中获取到数据:%d\n", v)
			time.Sleep(time.Millisecond * 50)
		case v := <-stringChan:
            // 这里面就是我们要处理的业务逻辑
			fmt.Printf("从stringChan中获取到数据:%s\n", v)
			time.Sleep(time.Millisecond * 50)
		default:
			fmt.Printf("数据获取完毕\n")
			return  // 注意退出
		}
	}
}

执行结果

PS E:\code\go> go run .\main.go
从intChan中获取到数据:0
从stringChan中获取到数据:hello0
从intChan中获取到数据:1
从intChan中获取到数据:2
从intChan中获取到数据:3
从stringChan中获取到数据:hello1
从stringChan中获取到数据:hello2
从stringChan中获取到数据:hello3
从intChan中获取到数据:4
从stringChan中获取到数据:hello4
从intChan中获取到数据:5
从intChan中获取到数据:6
从intChan中获取到数据:7
从intChan中获取到数据:8
从intChan中获取到数据:9
数据获取完毕

11.4.6:互斥锁

1:互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源
2:Go语言中使用sync包中的Mutex类型来实现互斥锁

首先我们来看一个不加互斥锁的demo是怎样的

package main

import "fmt"

var x int

func main() {
	fmt.Println(x)
	Add()
	fmt.Println(x)
}

func Add() {
	for i := 1; i <= 5000; i++ {
		x = x + 1
	}
}

执行结果

PS E:\code\go> go run .\main.go
0
5000



再看下加入协程后的demo
package main

import (
	"fmt"
	"sync"
)

var x int
var wg sync.WaitGroup

func main() {
	wg.Add(2)
	fmt.Println(x)
	go Add()
	go Add()
	wg.Wait()
	fmt.Println(x) // 期待结果是10000
}

func Add() {
	for i := 1; i <= 5000; i++ {
		x = x + 1
	}
	wg.Done()
}

运行结果

PS E:\code\go> go run .\main.go
0
7331
PS E:\code\go> go run .\main.go
0
6463
PS E:\code\go> go run .\main.go
0
6918

这个时候我们就发现并没有达到我们的预期,这是为什么呢?
这个问题主要是因为我们开启多个协程对同一个资源进行操作的时候可能会出现资源竞争
比如开两个协程对x同时加5000次,正确的结果应该是10000,但是实际结果是不相符的,这个时候我们就应该用到了互斥锁了,看demo

package main

import (
	"fmt"
	"sync"
)

var x int
var wg sync.WaitGroup
var lock sync.Mutex   // 引入互斥锁的方法

func main() {
	wg.Add(2)
	fmt.Println(x)
	go Add()
	go Add()
	wg.Wait()
	fmt.Println(x) // 期待结果是10000
}

func Add() {
	for i := 1; i <= 5000; i++ {
        // 当我们操作资源的时候先加一把互斥锁再操作
        // 保证同一时间只能由一个协程操作资源
		lock.Lock()    
		x = x + 1
        // 操作完成之后取消互斥锁
        lock.Unlock()
	}
	wg.Done()
}

运行结果会发现达到了我们的理想值

PS E:\code\go> go run .\main.go
0
10000

如果还不够清晰,我换个demo来解释一下协程

package main

import (
	"fmt"
	"sync"
)

var x int
var wg sync.WaitGroup
var lock sync.Mutex

func main() {
	fmt.Println(x)
	for i := 0; i < 4; i++ {
		wg.Add(1)   // 这里就清楚的表明了我开了多少的协程,因为时循环了4次每次都是5000,所以期待值是20000
		go Add()
	}
	wg.Wait()
	fmt.Println(x)
}

func Add() {
	for i := 1; i <= 5000; i++ {
		lock.Lock()
		x = x + 1
		lock.Unlock()
	}
	wg.Done()
}

运行结果

PS E:\code\go> go run .\main.go
0
20000

12:Web开发之net-http

12.1:介绍

ClientGet
	main.go    # 发送Get请求
ClientPost
	main.go    # 发送Post请求
Server
	main.go    # web服务
	
1:Go语言内置的net/http包非常的不错,提供了HTTP客户端和服务端实现

12.2:web服务

12.2.1:Server/main.go

1:客户端请求信息封装在http.Request对象中
2:服务端返回的响应报文会被保存在http.Response结构体中
3:发给客户端响应的并不是http.Response,而是通过http.ResponseWriter接口实现的
方法名 描述
Header() 用户设置或获取响应头信息
Write() 用于写入数据到响应体
WriteHeader() 用于设置相应状态码,若不调用则默认为200 OK,
// 使用 net/http实现一个web server
	1:Get请求
	2:Post请求

// http协议,与web服务器交互的规范
	1:Get:一般用于读取数据
	2:Post:一般用于写入数据
	3:Put:一般用于更新数据
	4:Delete:一般用于删除数据

// 开发一个web服务的步骤
	1:路由
	2:处理函数
		2.1:解析请求数据(根据具体数据做出具体的请求)
		2.2:响应数据(将数据返回给请求方或者浏览器)
	3:启动服务
	4:请求服务

// 方法定义
	1:处理函数的名字:驼峰法,以xxxxHandler()

// 处理Get请求的demo如下

package main

import "net/http"

func main() {
    // 编写路由
    // http.HandleFunc() 它接收两个参数,第一个是路由地址如:/, 第二个是绑定的处理函数如:dealGetHandler
    http.HandleFunc("/", dealGetHandler)
    // http.ListenAndServe() 它接收两个参数,第一个是监听端口和IP如::8080,第二个是处理函数一般为nil
	http.ListenAndServe(":8080", nil)
}
// 是响应数据(将数据返回给请求方或者浏览器)
// http.ResponseWriter 本身是一个interface接口,定义三个方法用于返回数据
// 解析请求数据(根据具体数据做出具体的请求)
// http.Request 解析URL中的数据或Post请求中Body中的数据
func dealGetHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, world!"))
}

// 运行
PS E:\goland\demo> go run .\server\main.go

C:\Users\Administrator>curl 127.0.0.1:8080
Hello, world!
// 发现可以请求了!

具体实现一个请求是这样的

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

type Data struct {
	Name string `json:"name"`
}

// 处理Get请求 127.0.0.1/get?name=<请求名>
func dealGetRequestHandler(w http.ResponseWriter, r *http.Request) {
	// 获取请求参数
	query := r.URL.Query()
	if len(query["name"]) > 0 {
		name := query["name"][0]
		fmt.Println("通过字典下标获取name:", name)
	}
	// 方法2:使用Get方法获取请求参数
	name2 := query.Get("name")
	fmt.Println("通过Get方法获取name:", name2)
	type data struct {
		Name2 string
	}
	d := data{
		Name2: name2,
	}
	// w.Write([]byte(string(d))) // 返回string类型的数据
	json.NewEncoder(w).Encode(d) // 返回json类型的数据
}

// 处理Post请求 127。0.0.1/post {"name": "layzer"}
func dealPostRequestHandler(w http.ResponseWriter, r *http.Request) {
	boduyContent, _ := ioutil.ReadAll(r.Body)
	strData := string(boduyContent)
	var d Data
	json.Unmarshal([]byte(strData), &d)
	fmt.Printf("Body Content: %s\n", string(boduyContent))
	json.NewEncoder(w).Encode(fmt.Sprintf("Hello:%s", d.Name))
}

func main() {
	http.HandleFunc("/get", dealGetRequestHandler)
	http.HandleFunc("/post", dealPostRequestHandler)
	http.ListenAndServe(":80", nil)
}

12.2.2:ClientGet/main.go

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
)

func RequestGet() {
	apiUrl := "http://127.0.0.1/get"
	data := url.Values{}
	data.Set("name", "layzer")
	u, _ := url.ParseRequestURI(apiUrl)
	u.RawQuery = data.Encode()
	fmt.Println("请求的路由是:", u.String())
	resp, _ := http.Get(u.String())
	b, _ := ioutil.ReadAll(resp.Body)
	fmt.Println("返回的数据是:", string(b))
}

func main() {
	RequestGet()
}

运行结果

PS E:\code\go> go run .\Client\main.go
请求的路由是: http://127.0.0.1/get?name=layzer
返回的数据是: {"Name2":"layzer"}

12.2.3:ClientPost/main.go

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

func main() {
	RequestPost()
}

func RequestPost() {
	apiUrl := "http://127.0.0.1/post"
	// 模拟from表单
	//contentType := "application/x-www-form-urlencoded"
	// 模拟json数据
	contentType := "application/json"
	data := `{"name": "layzer"}`
	resp, _ := http.Post(apiUrl, contentType, strings.NewReader(data))
	b, _ := ioutil.ReadAll(resp.Body)
	fmt.Println("返回的数据是:", string(b))
}

执行结果

PS E:\code\go> go run .\ClientPost\main.go
返回的数据是: "Hello:layzer"

服务端
PS E:\code\go> go run .\Server\main.go
Body Content: {{"name": "layzer"}}
posted @ 2022-08-12 05:50  Layzer  阅读(189)  评论(0编辑  收藏  举报