第一章:基础语法 第二章:包和函数

2.1 包

闭包的使用:

package main

import (
	"fmt"
	"strings"
)

func main() {
	f := makeSuffix(".jpg")
	f1 := makeSuffix(".png")
	fmt.Println(f("1"))
	fmt.Println(f1("1"))
	fmt.Println(f("2.jpg"))
	fmt.Println(f1("2.jpg"))
}

func makeSuffix(suffix string) func(string) string {
    //闭包的使用: return的函数使用到了makeSuffix函数的suffix,因此构成闭包
    //闭包的好处: 
	return func(name string) string {
		if !strings.HasSuffix(name, suffix){
			return name + suffix
		}
		return name
	}
}

defer关键字:

为什么使用defer:在函数中,我们往往需要创建资源(比如数据库链接、文件句柄、锁......),为了在函数执行完毕后,即使的释放资源,golang提供了defer(延时机制),defer会将语句入栈,也会将相关的值拷贝同时入栈,当函数执行完毕后、在从defer栈中按照先入后出的方式出栈。

下面是一个例子:

package main

import (
	"fmt"
)

func main() {
	add, sub := addOrSub(100, 500)
	fmt.Println("add is ", add)
	fmt.Println("sub is ", sub)
}
func addOrSub(num1, num2 int) (add int, sub int) {
	defer fmt.Println("op before num1 is:", num1)
	defer fmt.Println("op before num2 is:", num2)
	add = num1 + num2
	sub = num1 - num2
	return
}

打印结果如下:

image-20200618193221870

2.2 函数的参数传递

两种方式:

1)值传递

2)引用传递

2.3 变量的作用域

1) 局部变量

2) 全局变量

package main

import (
	"fmt"
	"strings"
)

var (
	age int = 99
    //全局变量不能这样使用
    //name := "chen"  
    /*错误原因
    name := "chen"  相当于:
    var name string
    name = "chen"
    而赋值语句不能在这里使用
    */
)

func main() {
	var age int = 19
    fmt.Println("main age=",age)
    test()
}

func test(){
    fmt.Println("age=",age)
}

输出结果:

image-20200619104102579

第三章 字符串常用的系统函数
package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	/*
		1) 统计字符串长度,按字节返回
			len(str)
		2) 字符串遍历:如果含有中文,按照字节遍历就会出现乱码,使用切片就可以安装字符输出
			rune()
		3) 字符串转整数
			strconv.Atoi()
		4) 整数转字符串:
			strconv.Itoa()
		5) 字符串转byte
			var bytes = []byte(str)
		6) []byte转字符串:
			str:=string([]byte{97,98,99})
		7) 十进制转2,8,16进制
			str := strconv.FormatInt(123, 2)
		8) 查找子串
			strings.Contains()
		9) 统计字符串中有几个指定的字符串、
			strings.Count()
		10)字符串比较,不区分大小写
			strings.EqualFold()
		11)返回子串在字符串中第一次出现的下标
			index := strings.Index()
		12)返回子串在字符串中最后一次出现的位置
			lastIndex := strings.LastIndex()
		13)将指定的子串替换 n表示想要替换几个,-1表示全部替换
			strings.Replace()
		14)按照某个字符将字符串进行分割
			strings.Split()
		15)大小写转换
			strings.ToLower()  小写
			strings.ToUpper()  大写
		16)去掉左右两边的空格
			strings.TrimSpace()
		17)去掉左右两边指定的字符(去掉感叹号个空格)
			strings.Trim("! hello !", "! ")
		18)判断前缀或后缀
			strings.HasPrefix()
			strings.HasSuffix()
	*/
	str := "北京"
	fmt.Println(len(str)) //6

	str2 := "上海哦haha"
	str3 := []rune(str2)
	for i := 0; i < len(str3); i++ {
		fmt.Printf("%c\t", str3[i])
	}
	fmt.Println()
	n, err := strconv.Atoi("145.1")
	if err != nil {
		fmt.Println("error:", err)
	} else {
		fmt.Println("result:", n)
	}

	s := strconv.Itoa(78788)
	fmt.Println(s)

	str = strconv.FormatInt(123, 16)
	fmt.Println(str)

	b := strings.Contains("陈毫", "hao")
	fmt.Println(b)

	a := strings.Count("hdjddxjsk", "d")
	fmt.Println(a)

	if strings.EqualFold("AGDJ", "agdj") {
		fmt.Println("True")
	} else {
		fmt.Println("False")
	}

	index := strings.Index("HJHJJSDF_hjfh","_")
	fmt.Println(index)

	lastIndex := strings.LastIndex("HJHJJSDF_hjf_h","_")
	fmt.Println(lastIndex)

	s = strings.Replace("hahahaha java go py","py", "python", 1)
	fmt.Println(s)

	strArr := strings.Split(s, " ")
	fmt.Println(strArr)

	str = strings.TrimSpace("   hello go   ")
	fmt.Println(str)

	str = strings.Trim("! hello !", " !")
	fmt.Println(str)

}

输出结果:

6
上	海	哦	h	a	h	a	
error: strconv.Atoi: parsing "145.1": invalid syntax
78788
7b
false
3
True
8
12
hahahaha java go python
[hahahaha java go python]
hello go
hello
第三章 时间和日期相关的函数
package main

import (
	"fmt"
	"strconv"
	"time"
)

func main() {
	/**
	1) 获取当前时间
		now := time.Now()
	2) 获取其他相关日期
	3) 时间日期格式化 : "2006-01-02 15:04:05" 必须这样写
		fmt.Println(now.Format("2006-01-02 15:04:05"))
	4) 休眠: time.Sleep(2*time.Second) 休眠2秒
		Sleep()
	5) 时间常量:
		time.Second 秒钟
		time.Minute 分钟
		time.Hour 小时
		time.Microsecond 微秒
		time.Millisecond 毫秒
	6) 时间戳
		nowUnix := now.Unix() //秒
		nowUnix = now.UnixNano() //纳秒

	*/

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

	fmt.Println("年:", now.Year())
	fmt.Println("月:", now.Month())
	fmt.Println("月:", int(now.Month()))
	fmt.Println("日:", now.Day())
	fmt.Println("时:", now.Hour())
	fmt.Println("分:", now.Minute())
	fmt.Println("秒:", now.Second())

	fmt.Println(now.Format("2006-01-02 15:04:05"))

	//time.Sleep(2*time.Second)

	fmt.Println(123)

	startTime := time.Now().Unix() //秒
	fmt.Println(startTime)
	//nowUnix = now.UnixNano() //纳秒
	//fmt.Println(nowUnix)

	test()
	endTime := time.Now().Unix()
	fmt.Println(endTime - startTime)

}

func test() {
	str := ""
	for i := 0; i < 100000; i++ {
		str += strconv.Itoa(i)
	}
}

运行结果:

2020-06-19 15:16:59.0170538 +0800 CST m=+0.002992101
年: 2020
月: June
月: 6
日: 19
时: 15
分: 16
秒: 59
2020-06-19 15:16:59
123
1592551019
2
第四章 golang内置函数

todo

第五章 错误处理机制

一个例子:

image-20200619154431604

golang中使用defer、panic、recover来处理

错误处理的好处:让程序更加的健壮

package main

import "fmt"

func main() {
	test()
	fmt.Println(123)
}

func test() {
	//使用defer和recover处理
	defer func() {
		err := recover()
		if err != nil {
			//有异常
			fmt.Println("除数不能为0")
		}
	}()
	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println(res)
	//return res
}

自定义错误:

package main

import (
	"errors"
	"fmt"
)

func main() {
	test()
	fmt.Println(123)
	e := readConf("demo.conf")
	if e != nil {
		panic(e)
	}else {
		fmt.Println("读取正确")
	}
}
//读取配置文件信息
//如果文件名不正确,返回自定义错误
func readConf(name string) error  {
	if name =="config.ini"{
		return nil
	}else{
		return errors.New("文件读取错误")
	}
}

image-20200619161454284

第六章 数组与切片

6.1 数组与切片的介绍

在golang中,数组是值类型

package main

import "fmt"

func main() {
	var hens [6] float64
	hens[0] = 5.6
	hens[1] = 5.5
	hens[2] = 5.4
	hens[3] = 5.3
	hens[4] = 5.2
	hens[5] = 5.1
	var sum float64
	for i := 0; i < len(hens); i++ {
		sum += hens[i]
	}
	avg := fmt.Sprintf("%.2f", sum/float64(len(hens)))
	fmt.Println(avg)
    
    
    //几种初始化的方式
	var nuArr [3]int = [3]int{1, 2, 3}
	fmt.Println(nuArr)

	var nuArr2 = [2]int{5,6}
	fmt.Println(nuArr2)

	var nuArr3 = [...]int{5,6}
	fmt.Println(nuArr3)

	//指定下标
	var nuArr4 = [...]int{1:800,0:500}
	fmt.Println(nuArr4)
    
    nuArr5 := [...]int{1:8000,0:800000}
	fmt.Println(nuArr5)
}
5.35
[1 2 3]
[5 6]
[5 6]
[500 800]
[800000 8000]
//数组反转
func ReversionArr(arr *[5]int)  {
	len := len(arr)
	for i:=0; i < len /2;i++{
		temp := arr[len -1 - i]
		arr[len -1 - i] = arr[i]
		arr[i] = temp
	}
}

// ================应用这个方法===============
package main

import (
	"TestDemo/src/array/arrtest"
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano())
	var arr [5]int

	for i:=0;i<5;i++{
		arr[i] = rand.Intn(100)
	}

	fmt.Println(arr)
	arrtest.ReversionArr(&arr)
	fmt.Println(arr)
}

切片的使用

// 切片的定义
var a []int
// test func
func Demo01()  {
	// 方式一:应用数组
	var intArr [9]int = [...]int{1,2,3,4,5,6,7,8,9}
	//slice1 := []int{1,2,3,4,5}
	slice1 := intArr[0:3]
	fmt.Println(intArr)
	fmt.Println(slice1)
	slice1[1] = 888  // 原数组的值会变化
	fmt.Println(slice1)
	fmt.Println(intArr)
	fmt.Println("slice len = ", len(slice1))
	// 容量
	fmt.Println("slice cap = ", cap(slice1))

	// 方式二: 使用make
	slice2 := make([]int, 5, 6)
	slice2[0] = 6
	slice2 = append(slice2, 2)

	fmt.Println(slice2)
    // 方式三
	slice3 := []int{1,2,3,4,5}
	fmt.Println(slice3)
    
    // 切片的遍历,和数组一致
    
    selice := []int{1,2,3,4,5,6,7,8,9}
	selice = append(selice, 1)
	fmt.Println(selice)
	
	// 切片追加切片
	selice = append(selice, selice...)
    
    // 切片的拷贝
    //var se []int = []int{1,2,3,4,5}
	var co = make([]int, 40)
	copy(co, selice)

	fmt.Println(co)
    
}

切片的内存布局

image-20200709191910252

6.2 排序与查找

冒泡排序算法原理:

  1. 一共会经过 arr.length-1次的轮数比较,每一轮将会确定一个数的位置。

  2. 每一轮的比较次数再逐渐的减少。

  3. 当发现前面的一个数比后面的一个数大的时候,就进行了交换

func BubbleSort(arr *[5]int) {
   for i := len(arr) - 1; i > 0; i-- {
   	for j := 0; j < i; j++ {
   		if arr[j] > arr[j + 1]{
   			temp := arr[j]
   			arr[j] = arr[j +1]
   			arr[j + 1] = temp
   		}
   	}
   }
}

顺序查找方法:

/**
顺序查找
 */
func FindByName(arr [4]string, name string) int {
	index := -1
	for k,v := range arr{
		if v == name{
			return k
		}
	}
	return index
}

二分查找方法:

二分查找的思路: 比如我们要查找的数是 findVal
1. arr是一个有序数组,并且是从小到大排序
2.  先找到 中间的下标 middle = (leftIndex + rightIndex) / 2, 然后让 中间下标的值和findVal进行比较
2.1 如果 arr[middle] > findVal ,  就应该向  leftIndex ---- (middle - 1)
2.2 如果 arr[middle] < findVal ,  就应该向  middel+1---- rightIndex
2.3 如果 arr[middle] == findVal , 就找到
2.4 上面的2.1 2.2 2.3 的逻辑会递归执行
/**
二分查找法
*/
func BinarySearch(arr *[15]int, findValue, leftIndex, rigthIndex int) int {
	if leftIndex > rigthIndex {
		return -1
	}
	middleIndex := (leftIndex + rigthIndex) / 2
	if arr[middleIndex] > findValue {
		return BinarySearch(arr,findValue,leftIndex,middleIndex-1)
	}else if arr[middleIndex] < findValue {
		return BinarySearch(arr,findValue,middleIndex +1,rigthIndex)
	}else {
		return  middleIndex
	}
}

func main(){
    var arr [15]int
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < len(arr); i++ {
		arr[i] = rand.Intn(1000)
	}
    slice := arr[:]
	slice[2] = 187
	sort.BubbleSort(&arr)
	fmt.Println(arr)
    index := BinarySearch(&arr, 187, 0, len(arr) - 1)
	if index == -1 {
		fmt.Println("没找到")
	} else {
		fmt.Println("找到", "下标为:", index)
	}
}

打印结果为:

[119 187 214 247 285 351 381 408 483 507 534 681 825 912 974]
找到 下标为: 1

6.3 二维数组

func DemoOne()  {
	var arr [4][6]int
	rand.Seed(time.Now().UnixNano())
	for k,v := range arr{
		for k1,_ := range v{
			arr[k][k1] = rand.Intn(100)
		}
	}
	fmt.Println(arr)
    
    // 输出结果:
    //[[25 47 36 64 79 13] [46 99 12 48 78 72] [94 75 16 76 50 40] [39 19 41 18 30 36]]
}

二维数组内存布局:

image-20200711163726734

第七章 Map

map的基本使用

func DemoMap() {
	// map 声明后不会分配内存,需要make后才能使用
	var myMap map[string]string = make(map[string]string, 10)
	myMap["name"] = "Alice"
	myMap["age"] = "10"
	fmt.Println(myMap["age"])
    
    // 案例
	students := make(map[string]map[string]string)

	students["s1"] = make(map[string]string, 3)
	students["s1"]["name"] = "Alice"
	students["s1"]["sex"] = "女"

	students["s2"] = make(map[string]string, 3)
	students["s2"]["name"] = "Bob"
	students["s2"]["sex"] = "男"

	fmt.Println(students)
    
    // map的增加, 如果key存在,则更新key的值
	students["s2"]["address"] = "北京"
	fmt.Println(students["s2"]) 
    
    // 删除
	delete(students["s2"], "name")
	fmt.Println(students["s2"])
    
    // map查询
	v, res := students["s2"]
	fmt.Println(v, res)
	// 如果没有就返回 res = false
    
    // map的遍历,这两种都可以使用
	//for s := range students {
	for _,s := range students {
		for k2, s2 := range s {
			fmt.Println(k2,s2)
		}
	}
    
    // map 切片
	people := make([]map[string]string,0)
	// 如果初始长度为0的话,容易出错
	/*if people[0] == nil{
		people[0] = make(map[string]string, 2)
		people[0]["name"] = "Alice"
		people[0]["age"] = "10"
	}*/
	my := make(map[string]string, 2)
	my["name"] = "ch"
	my["age"] = "25"
	people = append(people, my)
	fmt.Println(people)
    
    // map 根据key排序
	map1 := make(map[int]int,4)
	map1[9] = 11
	map1[1] = 13
	map1[8] = 12
	map1[7] = 10

	fmt.Println(map1)
	var keys []int

	for k,_ := range map1{
		keys = append(keys, k)
	}

	sort.Ints(keys)
	fmt.Println(keys)
    for _, k := range keys {
        fmt.Println("Key:", k, "Value:", map1[k])
    }
}

image-20200711172446615

image-20200711173822356

第八章 面向对象(结构体)

8.1 面向对象基本介绍

Golan语言面向对象编程说明:

  1. Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说 Golan支持面向对象编程特性是比较准确的
  2. Golang没有类(class),Go语言的结构体( struct)和其它编程语言的类( class )同等的地位,你可以理解 Golan是基于 struct来实现OOP特性的。
  3. Golang面向对象编程非常简洁,去掉了传统OOP语言的继承、方法重载、构造函数和析构函数、隐藏的this指针等等
  4. Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不样,比如继承:Golan没有 extends关键字,继承是通过匿名字段来实现。
  5. Golang面向对象(OOP)很优雅,OOP本身就是语言类型系统( type system)的一部分,通过接口 ( interface)关联,耦合性低,也非常灵活。后面同学们会充分体会到这个特点。也就是说在 Golang中面向接口编程是非常重要的特性。

案例:张老太养了两只猫猫:一只名字叫小白今年3岁白色。还有只叫小花今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示张老太没有这只猫猫。

// 定义
type Cat struct {
	Name string
	Age uint
	Color string
}

// 使用
func CatFunc()  {
	var cat Cat
	cat.Name = "啾咪"
	fmt.Println(cat)
}

结构体和结构体变量(实例)的区别和联系

1)结构体是自定义的数据类型,代表一类事物

2)结构体变量(实例)是具体的,实际的,代表一个具体变量

结构体内存图

image-20200712161304084

创建结构体的四种方式

func CreateStruct()  {
	// 方式一
	var cat Cat
	fmt.Println(cat)

	// 方式二
	var cat1 Cat = Cat{"啾咪", 12, "红"}
	fmt.Println(cat1)

	// 方式三
    // 注意:go的设计者为了程序员使用方便,底层会对cat3.Name="啾咪2”进行处理,会给cat3加上取值运算(*cat3).Name="啾咪2"
	var cat3 *Cat = new(Cat)
	cat3.Name = "啾咪2"
	(*cat3).Age = 12
	fmt.Println(*cat3)

	// 方式四
	var cat4 *Cat = &Cat{}
	cat4.Name = "啾咪3"
	(*cat4).Age = 50
	fmt.Println(*cat4)
}
// 打印结果
/*
{ 0 }
{啾咪 12 红}
{啾咪2 12 }
{啾咪3 50 }
*/

结构体使用的注意事项:

  • 1) 结构体的所有字段在内存中是连续的

  • 2) 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)

  • 3) 结构体进行type重新定义(相当于取别名), Golang认为是新的数据类型,但是相互间可以强转

  • 4) struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化。

package main 
import "fmt"
import "encoding/json"

type A struct {
	Num int
}
type B struct {
	Num int
}
type C A
type Monster struct{
	Name string `json:"name"` // `json:"name"` 就是 struct tag
	Age int `json:"age"`
	Skill string `json:"skill"`
}
func main() {
	var a A
	var b B
	a = A(b) // 可以转换,但是有要求,就是结构体的的字段要完全一样(包括:名字、个数和类型!)
	fmt.Println(a, b)
	
    //结构体进行type重新定义(相当于取别名), Golang认为是新的数据类型,但是相互间可以强转
    var c C
    c = a //错误,可以这样修改 c = (C)a
    
    // struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化。
	//1. 创建一个Monster变量
	monster := Monster{"牛魔王", 500, "芭蕉扇~"}

	//2. 将monster变量序列化为 json格式字串
	//   json.Marshal 函数中使用反射,这个讲解反射时,我会详细介绍
	jsonStr, err := json.Marshal(monster)
	if err != nil {
		fmt.Println("json 处理错误 ", err)
	}
	fmt.Println("jsonStr", string(jsonStr))

}

8.2 方法的使用

package StructDemo

import "fmt"

type Person struct{
	Name string
	Age int
	Sex string
}

func (p Person) ToString() string {
	return fmt.Sprintf("name: %v, age: %v, sex: %v", p.Name, p.Age, p.Sex)
}
package main

import (
	"TestDemo/src/StructDemo"
	"fmt"
)

func main() {
	var p StructDemo.Person = StructDemo.Person{"熊建川",22, "男"}
	str := p.ToString()
	fmt.Println(str)
    // name: 熊建川, age: 22, sex: 男
}

方法案例:创建一个圆对象,写一个方法算出其面积

package StructDemo

import "math"

type Circle struct {
	Radius float64
}

func (c Circle) Area() float64  {
	return math.Pi * math.Pow(2, c.Radius)
}

// 精确两位小数
func (c Circle) Area1() float64  {
	res := fmt.Sprintf("%.2f", math.Pi * math.Pow(2, c.Radius))
	v2, _ := strconv.ParseFloat(res, 64)
	return v2
}
circle := StructDemo.Circle{4}
area := circle.Area()
fmt.Println(area)

// 50.26548245743669

8.3 方法发注意事项和使用细节

1)结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式

2)如程序员希望在方法中,修改结构体变量的值, 可以通过结构体指针的方式来处理

func (c *Circle) Area2() float64  {
	// 这两种方式都可以, go底层做了优化,方便程序员的使用
	// res := fmt.Sprintf("%.2f", math.Pi * math.Pow(2, c.Radius))
	res := fmt.Sprintf("%.2f", math.Pi * math.Pow(2, (*c).Radius))
	v2, _ := strconv.ParseFloat(res, 64)
	return v2
}

func main(){
    circle2 := StructDemo.Circle{5}
	//area2 := (&circle2).Area2()
	// 这两种方式都可以, go底层做了优化,方便程序员的使用
	area2 := circle2.Area2()
	fmt.Println(area2)
}
// 100.53

3)Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct,比 如int , float32等都可以有方法

4)方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。

5)如果一个类型实现了String() 这个方法,那么fmt.Println 默认会调用这个变量的String()进行输出

8.4 工厂模式

golang里面没有构造函数,通常可以使用工厂模式来解决这个问题。(getter,setter方法的实现)

一个例子

/*
@Time : 2020/7/26 10:24
@Author : 23290
@File : student
@Software: GoLand
*/
package factory

import "fmt"

type Student struct {
	Name string
	Age  int
}

// 如在在其他包中想应用这个类,那么需要使用工厂模式
type student struct {
	name string
	age  int
}

func NewStudent(name string, age int) *student {
	return &student{name: name, age: age}
}

func (student student) String() string{
	return fmt.Sprintf("name:%v,age:%v", student.name, student.age)
}
/*
@Time : 2020/7/26 10:25
@Author : 23290
@File : FactoryMain
@Software: GoLand
*/
package main

import (
	"day_one/src/exercise/factory"
	"fmt"
)

func main() {
	stu1 := factory.Student{"Alice", 50}
	fmt.Println(stu1)

	stu2 := factory.NewStudent("Bob", 15)
	fmt.Println(stu2)
}

8.5 接口和继承

8.5.1 继承

继承可以解决代码复用,让我们的编程更加靠近人类思维。 当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。继承给编程带来的便利

1)代码的复用性提高了

2)代码的扩展性和维护性提高了

image-20200726174531577

基本语法:

/*
@Time : 2020/7/26 18:24
@Author : 23290
@File : Animals
@Software: GoLand
*/
package extends

import "fmt"

type Animals struct {
	Name string
	Color string
	Class string
}

func (a *Animals) Eat()  {
	fmt.Println("吃")
}

func (a *Animals) ShowColor()  {
	fmt.Println(a.Color)
}

type Cat struct {
	Animals
}
/*
@Time : 2020/7/26 18:34
@Author : 23290
@File : ExtendsMain
@Software: GoLand
*/
package main

import (
	"day_one/src/exercise/extends"
	"fmt"
)

func main() {
	cat := &extends.Cat{}
	cat.Animals.Name = "啾咪"
	cat.Animals.Color = "黑色"
	cat.Class = "男"
	fmt.Println(*cat)
}
//{{啾咪 黑色 男}}

继承使用的细节

1)结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。

2)匿名结构体字段访问可以简化

3)当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。

image-20200726184927133

4)结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。

image-20200726185046374

5)如果一个 struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字

6)嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

type Goods struct {
	Name string
	Price string
}

type Brand struct {
	Name string
	Address string
}

type TV struct {
	Goods
	Brand
}

type TV2 struct {
	*Goods
	*Brand	
}
/*
@Time : 2020/7/26 18:34
@Author : 23290
@File : ExtendsMain
@Software: GoLand
*/
package main

import (
	"day_one/src/exercise/extends"
	"fmt"
)

func main() {

	tv1 := extends.TV{
		Goods: extends.Goods{
			Name:  "电视机器",
			Price: "1.2k",
		},
		Brand: extends.Brand{
			Name:    "长虹",
			Address: "四川",
		},
	}

	fmt.Println(tv1) //{{电视机器 1.2k} {长虹 四川}}
    
    tv2 := extends.TV2{
		Goods: &extends.Goods{
			Name:  "电视机器12",
			Price: "1.3k",
		},
		Brand: &extends.Brand{
			Name:    "长虹",
			Address: "广州",
		},
	}
	fmt.Println(*tv2.Brand, *tv2.Goods)//{长虹 广州} {电视机器12 1.3k}
}

多重继承(尽量避免使用)

8.5.2 接口

package interfaceDemo

import "fmt"

type USB interface {
	//声明了两个没有实现的方法
	Start()
	End()
}

//实现USB
type Phone struct {
}

func (p Phone) Start() {
	fmt.Println("手机开机")
}

func (p Phone) End() {
	fmt.Println("手机关机")
}

type Computer struct {
}

func (c Computer) Work(usb USB) {
	usb.Start()
	usb.End()
}
package main

import "TestDemo/src/interfaceDemo"

func main() {
	phone := interfaceDemo.Phone{}

	c := interfaceDemo.Computer{}

	c.Work(phone)
}

接口使用的注意事项:

1)接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量

image-20200727114300590

2)接口中所有方法都没有方法体

3)一个自定义类型需要将接口中的所有方法都实现,才能说这个自定义类型实现了该接口

4)只要是自定义类型,就可以实现某个接口,而非只能是结构体能实现接口。

5)一个自定义类型可以同时实现多个接口

6)golang接口中不能有任何常量

7)一个接口如果继承了其他接口,那么在实现这个接口的时候也要将其继承的接口中的方法也实现。

8)intserface是引用类型

9)空接口没有任何方法,所有类型都实现了空接口。

利用接口对结构体进行排序

package StructSort

type Hero struct {
	Name string
	Age int
}

// Hero 切片类型
type HeroSlice []Hero

func (hs HeroSlice) Len() int{
	return len(hs)
}

// 决定使用什么标准进行排序
// 按照年龄,降序排序
func (hs HeroSlice) Less(i ,j int) bool  {
	return hs[i].Age > hs[j].Age
}

func (hs HeroSlice) Swap(i,j int)  {
	//temp := hs[i]
	//hs[i] = hs[j]
	//hs[j] = temp
	hs[i], hs[j] = hs[j], hs[i]
}
package main

import (
	"TestDemo/src/StructSort"
	"fmt"
	"math/rand"
	"sort"
)

func main() {
	var heros StructSort.HeroSlice
	for i := 0; i < 10; i++ {

		hero := StructSort.Hero{
			Name: fmt.Sprintf("英雄~%d", rand.Intn(100)),
			Age:  rand.Intn(100),
		}
		heros = append(heros, hero)
	}
	print(heros)
	fmt.Println("===============")
	sort.Sort(heros)
	print(heros)

}
func print(slice StructSort.HeroSlice)  {
	for _, v := range slice{
		fmt.Print(v," ")
	}
	fmt.Println()
}

8.6 go面向对象三大特性

8.6.1 抽象

抽象实质上就是把一类事物的共有属性和行为提取出来,形成一个模型

/*
@Time : 2020/7/26 11:36
@Author : 23290
@File : Account
@Software: GoLand
*/
package abstract

type Account struct {
	AccountNo string
	Pwd string
	Balance float64
}

func Query()  {
	//...
}

func WithDraw()  {
	//...
}

func Deposite()  {
	//...
}

8.6.2 封装

封装( encapsulation)就是把抽象出的字段和对字段的操作封装在一起数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作。(setter和getter方法的实现)

一个案例

/*
@Time : 2020/7/26 11:57
@Author : 23290
@File : person
@Software: GoLand
*/
package fengzhuang

type person struct {
	name string
	age int
	sal float64
}

func NewPerson(name string, age int,sal float64)  *person{
	return &person{
		name: name,
		age:  age,
		sal:  sal,
	}
}

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

func (p *person) getName() string  {
	return p.name
}

8.6.3 多态

image-20200727145249583

8.7 类型断言

如何将一个接口变量赋值给自定义类型变量?

package main

import (
	"fmt"
)

type Point struct {
	x int
	y int
}
func main() {
	var a interface{}
	var p Point = Point{1, 2}
	a = p
	var b Point
	//b = a // 报错
	b = a.(Point) // 这就是类型断言,表示判断a是否是指向Point类型的变量,如果是就转换成Point类型的变量并且赋值给b,否则报错
    fmt.Println(b)
}

什么是类型断言?

类型断言:由于接口是一般类型,不知道具体的类型,如果需要转换成具体类型,就需要使用到类型断言。

package interfaceDemo

import "fmt"

// 类型断言 测试方法,带检测
func AssertionDemo()  {
	var x interface{}
	var b float64 = 2.2
	x = b
	y, flag:= x.(float64)
	fmt.Println(y, flag)
	fmt.Println("ok")
}

//2.2 true
//ok

一个简单的示例,当对象为电话的时候,休要调用call方法

package interfaceDemo

import "fmt"

type USB interface {
	//声明了两个没有实现的方法
	Start()
	End()
}

//实现USB
type Phone struct {
	Name string
}
type Camera struct {
	Name string
}

func (p Camera) Start() {
	fmt.Println(p.Name, "相机开机")
}

func (p Camera) End() {
	fmt.Println(p.Name, "相机关机")
}
func (p Phone) Start() {
	fmt.Println(p.Name,"手机开机")
}
func (p Phone) Call() {
	fmt.Println(p.Name,"手机打电话")
}
func (p Phone) End() {
	fmt.Println(p.Name,"手机关机")
}

type Computer struct {
}

type UsbSlice []USB

func (c Computer) Work(usb USB) {
	usb.Start()
	if p, flag:= usb.(Phone); flag{
		p.Call()
	}
	usb.End()
}
func Demo()  {
	var usbs UsbSlice
	usbs = append(usbs, Phone{"VIVO"})
	usbs = append(usbs, Phone{"三星"})
	usbs = append(usbs, Camera{"sss"})

	//fmt.Println(usbs)
	var c Computer
	for _, v := range usbs{
		c.Work(v)
	}
}
package main

import (
	"TestDemo/src/interfaceDemo"
	"fmt"
)

func main() {
	interfaceDemo.Demo()
}

/**
VIVO 手机开机
VIVO 手机打电话
VIVO 手机关机
三星 手机开机
三星 手机打电话
三星 手机关机
sss 相机开机
sss 相机关机
*/

判断参数的类型

func Demo02(items... interface{})  {
	for i, x := range items{
		switch x.(type) {
		case float64, float32:
			fmt.Println("第",i+1,"个参数类型是浮点型,值为:",x)
		case int, int32, int64:
			fmt.Println("第",i+1,"个参数类型是浮点型,值为:",x)
		}
	}
}
第九章 项目实战

9.1 家庭收支记账软件

面向过程开发版

package main

import "fmt"

func main() {
	loop := true
	option := ""
	balance := 10000.0
	money := 0.0
	note := ""
	details := "收支\t\t账户金额\t\t收支金额\t\t说明"
	for ; loop; {
		fmt.Println("--------------家庭收支记账软件--------------")
		fmt.Println("\t\t 1. 收支明细")
		fmt.Println("\t\t 2. 登记收入")
		fmt.Println("\t\t 3. 登记支出")
		fmt.Println("\t\t 4. 退出程序")
		fmt.Print("\n\t\t 请选择(1-4):")

		_, _ = fmt.Scanln(&option)
		switch option {
		case "1":
			fmt.Println("------------------收 支 明 细------------------")
			fmt.Println(details)
		case "2":
			fmt.Print("本次收入金额:")
			fmt.Scan(&money)
			balance += money
			fmt.Print("本次收入说明:")
			fmt.Scan(&note)
			details += fmt.Sprintf("\n收入\t\t%v\t\t%v\t\t%v", balance, money, note)
		case "3":
			fmt.Print("本次支出金额:")
			fmt.Scan(&money)
			if money > balance {
				fmt.Println("余额不足")
				break
			}
			balance -= money
			fmt.Print("本次支出说明:")
			fmt.Scan(&note)
			details += fmt.Sprintf("\n收入\t\t%v\t\t%v\t\t%v", balance, money, note)
		case "4":
			fmt.Print("你确定要退出吗?(y/n):")
			flag := ""
			for {
				fmt.Scan(&flag)
				if flag == "y" || flag == "n" {
					break
				}
				fmt.Println("你的输入有误!")
			}
			if flag == "y"{
				loop = false
			}
		default:
			fmt.Println("请输入正确的指令..")
		}
	}
	fmt.Println("已退出")
}

面向对象版

package main

import "fmt"

type FamilyAccount struct {
	option string
	loop bool
	balance float64
	money float64
	note string
	details string
}

func (this *FamilyAccount) showDetails()  {
	fmt.Println("------------------收 支 明 细------------------")
	fmt.Println(this.details)
}
func (this *FamilyAccount) add()  {
	fmt.Print("本次收入金额:")
	fmt.Scan(&this.money)
	this.balance += this.money
	fmt.Print("本次收入说明:")
	fmt.Scan(&this.note)
	this.details += fmt.Sprintf("\n收入\t\t%v\t\t%v\t\t%v", this.balance, this.money, this.note)
}

func (this *FamilyAccount) sub()  {
	fmt.Print("本次支出金额:")
	fmt.Scan(&this.money)
	if this.money > this.balance {
		fmt.Println("余额不足")
		return
	}
	this.balance -= this.money
	fmt.Print("本次支出说明:")
	fmt.Scan(&this.note)
	this.details += fmt.Sprintf("\n收入\t\t%v\t\t%v\t\t%v", this.balance, this.money, this.note)

}
func (this *FamilyAccount)  out()  {
	fmt.Print("你确定要退出吗?(y/n):")
	flag := ""
	for {
		fmt.Scan(&flag)
		if flag == "y" || flag == "n" {
			break
		}
		fmt.Println("你的输入有误!")
	}
	if flag == "y"{
		this.loop = false
	}
}


func (this *FamilyAccount) MainMenu() {
	for ;this.loop;{
		fmt.Println("--------------家庭收支记账软件--------------")
		fmt.Println("\t\t 1. 收支明细")
		fmt.Println("\t\t 2. 登记收入")
		fmt.Println("\t\t 3. 登记支出")
		fmt.Println("\t\t 4. 退出程序")
		fmt.Print("\n\t\t 请选择(1-4):")

		_, _ = fmt.Scanln(&this.option)
		switch this.option {
		case "1":
			this.showDetails()
		case "2":
			this.add()
		case "3":
			this.sub()
		case "4":
			this.out()
		default:
			fmt.Println("请输入正确的指令..")
		}
	}
	fmt.Println("已退出")
}
func NewMyFamilyAccount() *FamilyAccount {
	return &FamilyAccount{
		option:"",
		loop:true,
		balance:10000.0,
		money:0.0,
		details:"收支\t\t账户金额\t\t收支金额\t\t说明",
		note:"",
	}
}

func main() {
	NewMyFamilyAccount().MainMenu()
}

9.2 客户信息管理系统

main.go

package main

import (
	"ProjectDemo/src/Customer/service"
	"ProjectDemo/src/Customer/view"
)

func main() {
	v := view.View{
		Key:"",
		Loop:true,
	}
	v.CustomerService_ = service.NewCustomerService()
	v.MianMenu()
}

view.go

package view

import (
	"ProjectDemo/src/Customer/entity"
	"ProjectDemo/src/Customer/service"
	"fmt"
)

type View struct {
	Key  string
	Loop bool
	CustomerService_ *service.CustomerService
}

func (this *View) out() {
	fmt.Print("你确定要退出吗?(y/n):")
	flag := ""
	for {
		fmt.Scan(&flag)
		if flag == "y" || flag == "n" || flag == "Y" || flag == "N" {
			break
		}
		fmt.Println("你的输入有误!")
	}
	if flag == "y" || flag == "Y" {
		this.Loop = false
	}
}

func (this *View) Show() {
	customers := this.CustomerService_.ListCustomer()
	fmt.Println("ID","\t","姓名","\t","性别","\t","年龄", "\t", "电话","\t","邮件")
	for _,v := range customers{
		fmt.Println(v)
	}
}

func (this *View) add()  {
	fmt.Println("==========添加客户===========")
	cu := entity.Customer{}
	fmt.Print("请输入姓名:")
	fmt.Scanln(&cu.Name)
	fmt.Print("请输入性别:")
	fmt.Scanln(&cu.Gender)
	fmt.Print("请输入年龄:")
	fmt.Scanln(&cu.Age)
	fmt.Print("请输入电话:")
	fmt.Scanln(&cu.Phone)
	fmt.Print("请输入邮箱:")
	fmt.Scanln(&cu.Email)
	this.CustomerService_.AddCustomer(cu)
	fmt.Println("==========添加完成===========")
}

func (this *View) edit()  {
	fmt.Println("==========编辑客户===========")
	cu := entity.Customer{}
	fmt.Print("请输入需要编辑客户的ID:")
	fmt.Scanln(&cu.Id)
	cu_new, exist := this.CustomerService_.Edit(cu.Id)
	if exist{
		fmt.Print("请输入姓名:")
		fmt.Scanln(&cu_new.Name)
		fmt.Print("请输入性别:")
		fmt.Scanln(&cu_new.Gender)
		fmt.Print("请输入年龄:")
		fmt.Scanln(&cu_new.Age)
		fmt.Print("请输入电话:")
		fmt.Scanln(&cu_new.Phone)
		fmt.Print("请输入邮箱:")
		fmt.Scanln(&cu_new.Email)
		fmt.Println("==========编辑完成===========")
	}else {
		fmt.Println("你要编辑的用户不存在~")
	}
}

func (this *View)  del()  {
	fmt.Println("==========编辑客户===========")
	id := 0
	fmt.Print("请输入需要编辑客户的ID:")
	fmt.Scanln(&id)
	exist := this.CustomerService_.Del(id)
	if exist{
		fmt.Println("==========删除成功===========")
	}else {
		fmt.Println("你要删除的用户不存在~")
	}

}
// 显示主菜单
func (this *View) MianMenu() {
	for ; this.Loop; {
		fmt.Println("--------------客户信息管理系统--------------")
		fmt.Println("\t\t 1. 添加客户")
		fmt.Println("\t\t 2. 修改客户")
		fmt.Println("\t\t 3. 删除客户")
		fmt.Println("\t\t 4. 客户列表")
		fmt.Println("\t\t 5. 退出程序")
		fmt.Print("\n\t\t 请选择(1-5):")

		fmt.Scanln(&this.Key)
		switch this.Key {
		case "1":
			this.add()
		case "2":
			this.edit()
		case "3":
			this.del()
		case "4":
			this.Show()
		case "5":
			this.out()
		default:
			fmt.Println("请输入正确的指令..")
		}
	}
}

service.go

package service

import "ProjectDemo/src/Customer/entity"

type CustomerService struct {
	customers   []entity.Customer
	customerNum int
}

func NewCustomerService() *CustomerService {
	customer := entity.NewCustomer(1, 20, "Alice", "女", "135XXX", "")
	customerService := &CustomerService{}
	customerService.customers = append(customerService.customers, customer)
	customerService.customerNum = len(customerService.customers)
	return customerService
}

func (this *CustomerService) AddCustomer(customer entity.Customer) {
	customer.Id = this.customerNum + 1
	this.customers = append(this.customers, customer)
	this.customerNum += 1
}

func (this *CustomerService) ListCustomer() []entity.Customer {
	return this.customers
}

func (this *CustomerService) EditCustomer(id int, customer entity.Customer) {

}

func (this *CustomerService) Edit(id int) (*entity.Customer, bool) {
	for i := 0; i < len(this.customers); i++ {
		if this.customers[i].Id == id {
			return &this.customers[i], true
		}
	}
	// 坑,不能使用这个,v值拷贝
	//for _, v := range this.customers {}
	return nil, false
}

func (this *CustomerService) Del(id int) bool {

	for index, v := range this.customers {
		if v.Id == id {
			this.customers = append(this.customers[:index], this.customers[index+1:]...)
			return true
		}
	}
	return false
}

customer.go

package entity

import "fmt"

type Customer struct {
	Id     int
	Name   string
	Gender string
	Age    int
	Phone  string
	Email  string
}

func NewCustomer(id, age int, name, gender, phone, email string) Customer {
	return Customer{
		Id:     id,
		Name:   name,
		Gender: gender,
		Age:    age,
		Phone:  phone,
		Email:  email,
	}
}

func (v Customer) String() string {
	return fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v", v.Id, v.Name, v.Gender, v.Age, v.Phone, v.Email)
}
第十章 文件操作

10.1 文件读与写

package filedemo

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

func OpenFile() {
	file, err := os.Open("D:/workgocode/src/TestDemo/src/filedemo/test.txt")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(file.Name())
	defer file.Close()
}

func ReaderFile() {
	file, err := os.Open("D:/workgocode/src/TestDemo/src/filedemo/test.txt")
	if err != nil {
		fmt.Println(err)
	}
	reader := bufio.NewReader(file)
	for {
		str, err := reader.ReadString('\n')
		if err == io.EOF {
			break
		}
		fmt.Printf("%v", string(str))
	}
	defer file.Close()
}

func ReaderFileOne() {
	// 大文件不推荐使用该方法
	str, err := ioutil.ReadFile("D:/workgocode/src/TestDemo/src/filedemo/test.txt")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("%v", string(str))

}

func NewFileDemo1() {
	filePath := "D:/workgocode/src/TestDemo/src/filedemo/test1.txt"
	file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		fmt.Println(err)
	}
	str := "hello java"
	writer := bufio.NewWriter(file)
	_, _ = writer.WriteString(str)
	_ = writer.Flush()
	defer file.Close()
}
func Exe1() {
	//打开一个存在的文件,覆盖其中的类容
	filePath := "D:/workgocode/src/TestDemo/src/filedemo/test.txt"
	file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0666)
	if err != nil {
		fmt.Println(err)
	}
	str := "hello java | hello python"
	writer := bufio.NewWriter(file)
	_, _ = writer.WriteString(str)
	_ = writer.Flush()
	defer file.Close()
}

func Exe2() {
	// 追加 | hello golang
	filePath := "D:/workgocode/src/TestDemo/src/filedemo/test.txt"
	file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println(err)
	}
	str := " | hello golang"
	writer := bufio.NewWriter(file)
	_, _ = writer.WriteString(str)
	_ = writer.Flush()
	defer file.Close()

}

func Exe3() {
	// 先读取, 再追加 | hello #c
	filePath := "D:/workgocode/src/TestDemo/src/filedemo/test.txt"
	file, err := os.OpenFile(filePath, os.O_RDWR|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println(err)
	}
	reader := bufio.NewReader(file)
	/*for {
		line, _, _ := reader.ReadLine()
		if len(line) ==0 {
			break
		}
		fmt.Println(string(line))
	}*/
	/*for {
		b,e := reader.ReadBytes('\n')
		if e == io.EOF {  // 如果读取到文件末尾
			break
		}
		fmt.Println(string(b))
	}*/
	/*for {
		str, err := reader.ReadString('\r')
		if err == io.EOF {
			break
		}
		fmt.Printf("%v", string(str))
	}*/
	var p []byte = make([]byte, 1024, 1024)
	for {
		n, _ := reader.Read(p)
		if n == 0 {
			break
		}
		fmt.Println(string(p))
	}

	str := " | hello #c"
	writer := bufio.NewWriter(file)
	_, _ = writer.WriteString(str)
	_ = writer.Flush()
	defer file.Close()
}

func CopyFile() {
	filePath := "D:/workgocode/src/TestDemo/src/filedemo/1.jpg"
	outfilePath := "D:/workgocode/src/TestDemo/src/filedemo/12.jpg"
	file, err := os.Open(filePath)
	file2, _ := os.OpenFile(outfilePath, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		fmt.Println(err)
	}
	reader := bufio.NewReader(file)
	writer := bufio.NewWriter(file2)
	var p []byte = make([]byte, 1024, 1024)
	for {
		n, _ := reader.Read(p)
		if n == 0 {
			break
		}
		_, _ = writer.Write(p)
		_ = writer.Flush()
	}
	defer file.Close()
	defer file2.Close()

}
func CopyFile2() {
	filePath := "D:/workgocode/src/TestDemo/src/filedemo/1.jpg"
	outfilePath := "D:/workgocode/src/TestDemo/src/filedemo/13.jpg"
	file, err := os.Open(filePath)
	file2, _ := os.OpenFile(outfilePath, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		fmt.Println(err)
	}
	reader := bufio.NewReader(file)
	writer := bufio.NewWriter(file2)
	_, _ = io.Copy(writer, reader)
	defer file.Close()
	defer file2.Close()
}
func PathExists(path string) (bool, error) {
	// 判断文件或文件夹是否存在
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

10.2 命令行参数

args := os.Args
for _,v := range args {
	fmt.Println(v)
}

/**
在终端输入如下命令,或者在goland中添加参数
go run FileMain.go ddd cc
C:\Users\CDCH\AppData\Local\Temp\go-build288453710\b001\exe\FileMain.exe
ddd
cc
*/

image-20200730160651497

通过flag包解析命令行参数

var name string
var age int
var say string

flag.StringVar(&name, "name","chtw", "用户名")
flag.IntVar(&age, "age",0,"年龄")
flag.StringVar(&say, "say","hello golang", "留言")
flag.Parse() //必须使用这个方法
fmt.Println(name, age, say)

//chenhao 25 hello
第十一章 json和序列化

11.1 json格式介绍

[
    {"name":"chenhao","age":25}
]

json格式很常用,是目前最流行的数据传送格式

11.2 序列化和反序列化

json序列化:将key-value结构的数据类型(比如结构体、map、切片等)序列化成json字符串格式的操作

package jsondemo

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	Name string `json:"name"` // json序列化后显示name,更加标准(tag的使用)
	Age int `json:"age"`
	Gender string `json:"gender"`
}

func ToJsonType()  {
	// 结构体序列化
	stu := Student{
		Name:   "chenhao",
		Age:    25,
		Gender: "男",
	}
	s, e := json.Marshal(&stu)
	if e != nil {
		fmt.Println(e)
	}
	fmt.Println(string(s))

	// map
	var a map[string]interface{}
	a = make(map[string]interface{})
	a["name"] = "xxxx"
	a["age"] = 5000
	a["gender"] = "女"
	s, e = json.Marshal(&a)
	if e != nil {
		fmt.Println(e)
	}
	fmt.Println(string(s))
	//切片
	var slice []map[string]interface{}
	slice = append(slice, a)
	slice = append(slice, a)
	s, e = json.Marshal(&slice)
	if e != nil {
		fmt.Println(e)
	}
    fmt.Println(string(s))
	s, e = json.Marshal(22.2)
	fmt.Println(string(s))
}

/*
{"name":"chenhao","age":25,"gender":"男"}
{"age":5000,"gender":"女","name":"xxxx"}
[{"age":5000,"gender":"女","name":"xxxx"},{"age":5000,"gender":"女","name":"xxxx"}]
22.2
*/

反序列化:在反序列化一个json字符串时,要确保数据类型和反序列化之前保持一致。

func JsonToOther() {
	// 反序列化
	// json 反序列化成 struct
	jsonStr := "{\"age\":5000,\"gender\":\"女\",\"name\":\"xxxx\"}"
	var stu Student
	err := json.Unmarshal([]byte(jsonStr), &stu)
	if err == nil {
		fmt.Println(stu)
	}else{
		fmt.Println(err)
	}

	// 反序列化成 map  这里不用make,反序列化底层会自动make
	var myMap map[string]interface{}
	//myMap := make(map[string]interface{})
	err = json.Unmarshal([]byte(jsonStr), &myMap)
	if err == nil {
		fmt.Println(myMap)
	}else{
		fmt.Println(err)
	}

	newJson := "[{\"age\":5000,\"gender\":\"女\",\"name\":\"xxxx\"},{\"age\":5000,\"gender\":\"女\",\"name\":\"xxxx\"}]"
	var mySlice []map[string]interface{}
	err = json.Unmarshal([]byte(newJson), &mySlice)
	if err == nil {
		fmt.Println(mySlice)
	}else{
		fmt.Println(err)
	}

}
/**
{xxxx 5000 女}
map[age:5000 gender:女 name:xxxx]
[map[age:5000 gender:女 name:xxxx] map[age:5000 gender:女 name:xxxx]]
*/
第十二章 单元测试

传统的测试方法、缺点:

1) 不方便,需要在主函数中调用,如果项目在运行中、有可能需要停止运行取修改代码。

2) 不利于管理,如果需要测试多个方法,需要注释掉其他函数

package test

import "fmt"

func addUpper(n int) int {
	res := 0
	for i:=1;i<=n;i++{
		res += i
	}
	return res
}

func main() {
	res := addUpper(10)
	if res == 55 {
		fmt.Println("方法正确")
	}else {
		fmt.Println("方法有误")
	}
}

golang中,自带有testing测试框架。可使用go test命令来实现单元测试和性能测试。

测试例子:

首先写一个用于测试的方法:

main.go

package test

func addUpper(n int) int {
	res := 0
	for i:=1;i<=n;i++{
		res += i
	}
	return res
}

然后写一个main_test.go

package test

import (
	_ "fmt"
	"testing"
)
// TestXXX() XXX不能使用小写字母开头
func TestAddUpper(t *testing.T)  {
	// 调用AddUpper()
	res := addUpper(10)
	if res == 55 {
		t.Logf("方法正确")
	}else {
		t.Fatalf("方法有误")
	}
}

在终端输入 :go test -v

image-20200730200126996

单元测试的细节

1)测试用例文件必须以_test.go结尾。

2) 测试用例函数必须以Test开头

3)测试用例函数的形参必须是*testint.T

4)一个测试用例文件中可以有多个测试函数。

5)运行指令

​ (1)go test 测试运行正确,无日志打印,错误时输出日志,并且退出程序

​ (2)go test -v 运行正确或错误都会输出日志

6)当出现错误时可以使用t.Fatalf来格式化输出错误信息。t.Logf方法可以输出相应的日志

7)PASS表示测试用例运行成功,FAIL表示测试用例运行失败。

8)测试单个文件 go test -v xxx_test.go xxx.go

9)测试单个方法 go test -v -test.run TestXXX

-v 显示测试的详细命令。

第十三章 协程和管道

13.1 goroutine协程

13.1.1 协程介绍

进程和线程:

1) 进程就是程序在操作系统中一次执行的过程,是系统进行资源分配和调度的基本单位。

2) 线程是进程的一个执行实例,是程序执行的最小单元,他是比进程更小的能独立运行的基本单位。

3) 一个进程可以创建多个线程,同一个进程中的多个线程可以并发执行。

4) 一个程序至少有一个进程,一个进程至少有一个线程。

并发和并行

并发:多线程程序在单核上运行

并行:多线程程序在多核上运行

测试代码

/*
@Time : 2020/8/8 14:38
@Author : 23290
@File : Demo
@Software: GoLand
*/
package goroutineDemo

import (
	"fmt"
	"time"
)

func TestPrint()  {
	for i:=1;i<10 ;i++  {
		fmt.Println("Test hello world", i)
		time.Sleep(time.Second)
	}
}

main.go

/*
@Time : 2020/8/8 14:43
@Author : 23290
@File : goroutineMain
@Software: GoLand
*/
package main

import (
	"day_one/src/exercise/goroutineDemo"
	"fmt"
	"time"
)

func main() {
	// go 开启协程
	go goroutineDemo.TestPrint()

	for i:=1;i<10 ;i++  {
		fmt.Println("main hello world", i)
		time.Sleep(time.Second)
	}
}

注意:

1) 如果主线程退出了,则协程还没有结束也会退出

2) 协程可以在主线程结束前退出

13.1.2 MPG模式

M:操作系统的主线程

P:协程执行需要的上下文(运行需要的资源)

G:协程

image-20200808151223338

image-20200808151424017

func CPUTest()  {
	cpuNum := runtime.NumCPU()
	fmt.Println(cpuNum)
	//设置使用cpu个数
	// go 1.8 之后不需要设置,默认多核
	// go 1.8 之前需要设置一下
	runtime.GOMAXPROCS(cpuNum - 1)
}

13.2 管道channel

13.2.1 channel的引出

需求:现在要计算1-200的各个数的阶乘,并且把各个数的阶乘放入到map中。最后显示出来要求使用 goroutine完成分析思路。

1)使用 goroutine来完成,效率高,但是会出现并发/并行安全问题。 2)这里就提出了不同 goroutine如何通信的问题

代码实现 1)使用 goroutine来完成(看看使用 gorotine并发完成会出现什么问题?然后我们会去解决) 2)在运行某个程序时,如何知道是否存在资源竞争问题。方法很简单,在编译该程序时,增加个参数race即可

package goroutineDemo

import (
	"fmt"
)

var (
	MyFactorialResult = make(map[int]string)
	lock sync.Mutex
)
func Factorial(n int) {
	res := 1
	for i := 1; i <= n; i++ {
		res *= i
	}

	MyFactorialResult[n] = strconv.Itoa(res)
}
package main

import (
	"day_one/src/exercise/goroutineDemo"
	"fmt"
	"time"
)

func main() {
	
	for i := 1; i < 200; i++ {
		go goroutineDemo.Factorial(i)
	}

	time.Sleep(time.Second * 10)
	for i, v := range goroutineDemo.MyFactorialResult {
		fmt.Printf("map[%d] = %v\n", i, v)
	}
}

运行可能会出现如下错误:

image-20200808163049274

改进方法:

1)使用全局的互斥锁

2)使用管道(为何需要channel?)

  • 前面使用全局变量加锁同步来解决 goroutine的通讯,但不完美
  • 主线程在等待所有 goroutine全部完成的时间很难确定,我们这里设置10秒,仅仅是估算。
  • 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有 goroutine处于工作状态,这时也会随主线程的退出而销毁
  • 通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作。
  • 上面种种分析都在呼唤一个新的通讯机制- channel

使用互斥锁

/*
@Time : 2020/8/8 14:38
@Author : 23290
@File : Demo
@Software: GoLand
*/
package goroutineDemo

import (
	"fmt"
	"runtime"
	"strconv"
	"sync"
)

var (
	MyFactorialResult = make(map[int]string)
	lock sync.Mutex
)



func Factorial(n int) {
	res := 1
	for i := 1; i <= n; i++ {
		res *= i
	}

	lock.Lock()
	MyFactorialResult[n] = strconv.Itoa(res)
	lock.Unlock()
}

13.2.2 channel的介绍和基本使用

管道的介绍:

1) channel本质就是一个数据结构-队列

2)数据是先进先出

3)线程安全,多 goroutine访问时,不需要加锁,就是说 channel本身就是线程安全的

4) channel有类型的,一个 string的 channel只能存放 string类型数据。

管道的基本使用:

var intChan chan int
var mapChan chan map[int]string
/**
channel是引用类型channel必须初始化才能写入数据,即make后才能使用管道是有类型的, 
intChan只能写入整数int
*/
package chennelDemo

import "fmt"

func TestChan01() {
	var intChan chan int
	intChan = make(chan int, 3)
	fmt.Println(intChan)

	// 向管道写入数据
	intChan <- 10
	num := 11
	intChan <- num

	fmt.Printf("channel len = %d, cap = %d\n", len(intChan), cap(intChan))

	// 取数,注意,在没有协程的时候,如果chan中的数据取出完毕了,再去取数的时候会报错
	num2 := <-intChan
	num3 := <-intChan
	num4 := <-intChan
	fmt.Println(num2, num3, num4) // fatal error: all goroutines are asleep - deadlock!

}
/**
channel使用的注意事项
1) channel中只能存放指定的数据类型
2) channle的数据放满后,就不能再放入了
3)如果从 channel取出数据后,可以继续放入
4)在没有使用协程的情况下,如果 channel数据取完了,再取,就会报 deadlock
*/

channel使用的注意事项

  • 1) channel中只能存放指定的数据类型
  • 2) channle的数据放满后,就不能再放入了
  • 3)如果从 channel取出数据后,可以继续放入
  • 4)在没有使用协程的情况下,如果 channel数据取完了,再取,就会报 deadlock

13.2.3 channel的关闭和遍历

channel的关闭:

使用内置函数 close可以关闭 channel,当 channel关闭后,就不能再向 channel写数据了,但是仍然可以从该 channel读取数据。

channel的遍历:

channel支持for- range的方式进行遍历,请注意两个细节

1)在遍历时,如果 channel没有关闭,则回出现 deadlock的错误

2)在遍历时,如果 channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。

package chennelDemo

import "fmt"

func ForChannelDemo() {
	allChan := make(chan interface{}, 10)
	allChan <- 10
	allChan <- 12
	allChan <- 11
	close(allChan)
	for i := range allChan {
		fmt.Println(i)
	}
}

13.3 协程配合管道

请完成 goroutine和 channel协同工作的案例,具体要求

1)开启一个 write Data协程,向管道 Cinchan!中写入50个整数

2)开启一个 radaTa协程,从管道 TintChan中读取 write Data写入的数据。

3)注意:write Data和 read Date操作的是同一个管道

4)主线程需要等待 write Data和 read Date协程都完成工作才能退出

/*
@Time : 2020/8/8 17:00
@Author : 23290
@File : ChanMain
@Software: GoLand
*/
package main

import "day_one/src/exercise/chennelDemo"

func main() {
	//chennelDemo.TestChan01()
	//chennelDemo.Test02()
	//chennelDemo.ForChannelDemo()

	intChan := make(chan int, 50)
	exitChan := make(chan bool, 1)

	go chennelDemo.WriteData(intChan)
	go chennelDemo.ReadData(intChan, exitChan)

	// 保持主线程不停止
	for{
		f,ok := <- exitChan
		if !ok && !f{
			break
		}

	}
}
package chennelDemo

import "fmt"
func WriteData(intChan chan int) {
	for i := 1; i <= 50; i++ {
		intChan <- i
		fmt.Printf("write data %v \n", i)
	}
	close(intChan)
}

func ReadData(intChan chan int, exitChan chan bool) {
	for {
		a, ok := <-intChan
		if !ok {
			break
		}
		fmt.Printf("read data %v \n", a)
	}
	exitChan <- true
	close(exitChan)
}

管道的注意事项:

1)channel可以声明为只可读或只可写状态

var chan2 chan<- int  //可写不可读
var chan3 <-chan int //可读不可写

2)使用select可以解决从管道取数据的阻塞问题

第十四章 反射机制

14.1 什么是反射?

《Go 语言圣经》中这样定义反射的:Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。

维基百科上的定义:在计算机科学中,反射是指计算机程序在运行时可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

14.2 反射的使用场景

  1. 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
  2. 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。

使用反射的注意点:Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。

举一个例子:csm系统中的代码。这是一个鉴权函数,兼容第三方企业微信调用接口。

image-20201220104135058

这个地方如果args是一个结构体,不是指针类型的话就会直接panic,导致鉴权直接失败,那么企业微信那边创建客户咨询工单的接口都会直接报500的错误。

type VerificationReq struct {
	CurrentUserName string `json:"current_user_name" form:"current_user_name"`                  // 兼容企业微信,传入当前登陆人,企业微信没有该系统的session
	RequestPlatform string `json:"request_platform" form:"request_platform" binding:"required"` // 兼容企业微信,不周session的外部接口为了安全需要鉴权
	Sign            string `json:"sign" form:"sign"`                                            // 鉴权参数
	Ts              int64  `json:"ts" form:"ts"`
	AppId           string `json:"app_id" form:"app_id"`
}

func TestVerificationSign(t *testing.T) {
	v := VerificationReq{
		CurrentUserName: "陈毫",
		RequestPlatform: "qywx",
		Sign:            "xxxx",
		Ts:              0,
		AppId:           "xxxxxx",
	}
	isTrue, _, err := VerificationSign(context.Background(), v, "qywx")
	if err != nil{
		t.Fatal(err)
	}
	fmt.Println(isTrue)
}

会出现以下异常:

image-20201220105204430

如何解决呢?

直接传入指针形式的参数

14.3 反射的基本函数

reflect 包中提供了两个基础的关于反射的函数来获取上述的接口和结构体:

func TypeOf(i interface{}) Type // 提取一个接口中值的类型信息
func ValueOf(i interface{}) Value // 提供实际变量的各种信息

为了进一步认识反射,先来认识一下这两个很重要的结构体:

type Type interface {
	// 此类型的变量对齐后所占用的字节数
	Align() int
	// 如果是 struct 的字段,对齐后占用的字节数
	FieldAlign() int
	// 返回类型方法集里的第 `i`个方法
	Method(int) Method
	// 通过名称获取方法
	MethodByName(string) (Method, bool)
	// 获取类型方法集里导出的方法个数
	NumMethod() int
	// 类型名称
	Name() string
	// 返回类型所在的路径,如:encoding/base64
	PkgPath() string
	// 返回类型的大小,和 unsafe.Sizeof 功能类似
	Size() uintptr
	// 返回类型的字符串表示形式
	String() string
	// 返回类型的类型值
	Kind() Kind
	// 类型是否实现了接口 u
	Implements(u Type) bool
	// 是否可以赋值给 u
	AssignableTo(u Type) bool
	// 是否可以类型转换成 u
	ConvertibleTo(u Type) bool
	// 类型是否可以比较
	Comparable() bool
	// 下面这些函数只有特定类型可以调用
	// 类型所占据的位数
	Bits() int
	// 返回通道的方向,只能是 chan 类型调用
	ChanDir() ChanDi
	// 返回类型是否是可变参数,只能是 func 类型调用
	// 比如 t 是类型 func(x int, y ... float64)
	// 那么 t.IsVariadic() == true
	IsVariadic() bool
	// 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
	Elem() Type
	// 返回结构体类型的第 i 个字段,只能是结构体类型调用
	// 如果 i 超过了总字段数,就会 panic
	Field(i int) StructField
	// 返回嵌套的结构体的字段
	FieldByIndex(index []int) StructField
	// 通过字段名称获取字段
	FieldByName(name string) (StructField, bool)
	// FieldByNameFunc returns the struct field with a name
	// 返回名称符合 func 函数的字段
	FieldByNameFunc(match func(string) bool) (StructField, bool)
	// 获取函数类型的第 i 个参数的类型
	In(i int) Type
	// 返回 map 的 key 类型,只能由类型 map 调用
	Key() Type
	// 返回 Array 的长度,只能由类型 Array 调用
	Len() int
	// 返回类型字段的数量,只能由类型 Struct 调用
	NumField() int
	// 返回函数类型的输入参数个数
	NumIn() int
	// 返回函数类型的返回值个数
	NumOut() int
	// 返回函数类型的第 i 个值的类型
	Out(i int) Type
    // 返回类型结构体的相同部分
	common() *rtype
	// 返回类型结构体的不同部分
	uncommon() *uncommonType
}
第十五章 网络编程和redis

image-20200824180058553

小案例:server.go

package main

import (
	"fmt"
	"net"
)

func myPrint(conn *net.Conn) {
	defer (*conn).Close()
	for {
		buf := make([]byte, 1024)
		n, err := (*conn).Read(buf)
		if err != nil {
			//fmt.Println(err)
			break
		}
		fmt.Print((*conn).RemoteAddr(), " say ", string(buf[:n]))
	}
}

func main() {

	// 监听8888端口
	listen, err := net.Listen("tcp", "127.0.0.1:8888")
	if err != nil {
		fmt.Println("listen error=", err)
	}
	defer listen.Close()
	for {
		fmt.Println("等待客户端连接.....")
		conn, err := listen.Accept()
		if err != nil {
			// Accept用于实现Listener接口的Accept方法;等待下一个呼叫,并返回一个该呼叫的Conn接口。
			fmt.Println("Accept() err=", err)
		} else {
			fmt.Println("Accept() suc ip is", conn.RemoteAddr())
		}
		go myPrint(&conn)
	}
}

client.go

package main

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

func main() {
	//Dial函数和服务端建立连接:
	conn, err := net.Dial("tcp", "127.0.0.1:8888")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer conn.Close()
	fmt.Println("连接成功....")
	status, err := bufio.NewReader(os.Stdin).ReadString('\n')
	_, _ = conn.Write([]byte(status))
}

golang操作redis:

package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
)

func main() {
	conn, err := redis.Dial("tcp", "192.168.44.128:6379")
	if err != nil {
		fmt.Println("err==>", err)
		return
	}
	defer conn.Close()
	_, err = conn.Do("lpush", "address", "成都")
	if err != nil {
		fmt.Println("err==>", err)
		return
	}
	fmt.Println("写入完成")

	r, err := redis.String(conn.Do("get", "work1"))
	if err != nil {
		fmt.Println("err==>", err)
		return
	}
	fmt.Println("read data", r)
}

redis连接池:

1)事先初始化一定数量的链接,放入到连接池

2)go需要操作redis的时候,直接从连接池中取出链接

3)这样可以节省临时获取redis的链接时间,从而提高效率

连接池的使用:

package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
)
var (
	pool *redis.Pool
)

func init() {
	pool =&redis.Pool{
		Dial: func() (conn redis.Conn, err error) {
			return redis.Dial("tcp", "192.168.44.128:6379")
		},
		TestOnBorrow:    nil,
		MaxIdle:         8,
		MaxActive:       0,
		IdleTimeout:     100,
		Wait:            false,
		MaxConnLifetime: 0,
	}
}

func main() {
	conn := pool.Get()
	defer conn.Close()
	s, err := redis.String(conn.Do("get", "work2"))
	if err != nil {
		fmt.Println("err ==> ", err)
	}
	fmt.Println(s)
}
第十六章 数据结构

16.1 稀疏数组

基本介绍:当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。

稀疏数组的处理方法是

  • 1)记录数组一共有几行几列,有多少个不同的值

  • 2)把具有不同值的元素的行列及值记录在一个小规模的数組中,从而缩小程序的规

一个例子:

image-20200904095843481

package sparse

import "fmt"

type ValueNode struct {
	row int
	col int
	value int
}


func SparseTest()  {
	var chessMap [11][11]int
	chessMap[1][2] = 1
	chessMap[2][3] = 2
	printArray(chessMap)

	/* 转成稀疏数组
	 * 思路
	 * (1)遍历 chessMan,如果我们发现有一个元素的值不为0,创建一个node结构体
	 * (2)将其放入到对应的切片即可
	 */
	sparseArr := make([]ValueNode, 0)
	sparseArr = append(sparseArr, ValueNode{
		row:11,
		col:11,
		value:0,
	})
	for i,v := range chessMap{
		for j, v2 := range v{
			if v2 != 0{
				sparseArr = append(sparseArr, ValueNode{
					row:i,
					col:j,
					value:v2,
				})
			}
		}
	}
	for _,v := range sparseArr{
		fmt.Println(v)
	}

	var chessMap2 [11][11]int
	for i,v := range sparseArr{
		if i == 0{
			continue
		}
		chessMap2[v.row][v.col] = v.value
	}
	printArray(chessMap2)
}

func printArray(chessMap [11][11]int)  {
	for _,v := range chessMap{
		for _, v2 := range v{
			fmt.Print(v2, " ")
		}
		fmt.Println()
	}
}

16.2 队列

队列是一个有序列表,可以用数组或是链表来实现。 遵循先入先出的原则即:先存入队列的数据,要先取出。后存入的要后取出。