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 }
打印结果如下:
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) }
输出结果:
第三章 字符串常用的系统函数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
第五章 错误处理机制一个例子:
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("文件读取错误") } }第六章 数组与切片
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) }
切片的内存布局
6.2 排序与查找
冒泡排序算法原理:
-
一共会经过 arr.length-1次的轮数比较,每一轮将会确定一个数的位置。
-
每一轮的比较次数再逐渐的减少。
-
当发现前面的一个数比后面的一个数大的时候,就进行了交换
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]] }
二维数组内存布局:
第七章 Mapmap的基本使用
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]) } }第八章 面向对象(结构体)
8.1 面向对象基本介绍
Golan语言面向对象编程说明:
- Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说 Golan支持面向对象编程特性是比较准确的
- Golang没有类(class),Go语言的结构体( struct)和其它编程语言的类( class )同等的地位,你可以理解 Golan是基于 struct来实现OOP特性的。
- Golang面向对象编程非常简洁,去掉了传统OOP语言的继承、方法重载、构造函数和析构函数、隐藏的this指针等等
- Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不样,比如继承:Golan没有 extends关键字,继承是通过匿名字段来实现。
- 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)结构体变量(实例)是具体的,实际的,代表一个具体变量
结构体内存图
创建结构体的四种方式
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)代码的扩展性和维护性提高了
基本语法:
/* @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)当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。
4)结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。
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)接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量
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 多态
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(¬e) 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(¬e) 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 */
通过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
单元测试的细节
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:协程
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) } }
运行可能会出现如下错误:
改进方法:
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 反射的使用场景
- 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
- 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。
使用反射的注意点:Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。
举一个例子:csm系统中的代码。这是一个鉴权函数,兼容第三方企业微信调用接口。
这个地方如果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) }
会出现以下异常:
如何解决呢?
直接传入指针形式的参数
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
小案例: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)把具有不同值的元素的行列及值记录在一个小规模的数組中,从而缩小程序的规
一个例子:
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 队列
队列是一个有序列表,可以用数组或是链表来实现。 遵循先入先出的原则即:先存入队列的数据,要先取出。后存入的要后取出。