闭包的使用:
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
}
打印结果如下:
两种方式:
1)值传递
2)引用传递
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
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("文件读取错误")
}
}
在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)
}
切片的内存布局
冒泡排序算法原理:
一共会经过 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
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]]
}
二维数组内存布局:
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])
}
}
Golan语言面向对象编程说明:
案例:张老太养了两只猫猫:一只名字叫小白今年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))
}
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
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()进行输出
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)
}
继承可以解决代码复用,让我们的编程更加靠近人类思维。 当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。继承给编程带来的便利
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}
}
多重继承(尽量避免使用)
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()
}
抽象实质上就是把一类事物的共有属性和行为提取出来,形成一个模型
/*
@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() {
//...
}
封装( 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
}
如何将一个接口变量赋值给自定义类型变量?
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)
}
}
}
面向过程开发版
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()
}
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)
}
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
}
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
[
{"name":"chenhao","age":25}
]
json格式很常用,是目前最流行的数据传送格式
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 显示测试的详细命令。
进程和线程:
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) 协程可以在主线程结束前退出
M:操作系统的主线程
P:协程执行需要的上下文(运行需要的资源)
G:协程
func CPUTest() {
cpuNum := runtime.NumCPU()
fmt.Println(cpuNum)
//设置使用cpu个数
// go 1.8 之后不需要设置,默认多核
// go 1.8 之前需要设置一下
runtime.GOMAXPROCS(cpuNum - 1)
}
需求:现在要计算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?)
使用互斥锁
/*
@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()
}
管道的介绍:
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使用的注意事项
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)
}
}
请完成 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可以解决从管道取数据的阻塞问题
《Go 语言圣经》中这样定义反射的:Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。
维基百科上的定义:在计算机科学中,反射是指计算机程序在运行时可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。
使用反射的注意点: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)
}
会出现以下异常:
如何解决呢?
直接传入指针形式的参数
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
}
tracert www.baidu.com
小案例: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)
}
基本介绍:当一个数组中大部分元素为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()
}
}
队列是一个有序列表,可以用数组或是链表来实现。 遵循先入先出的原则即:先存入队列的数据,要先取出。后存入的要后取出。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。