go语言

发布于 2022-09-20  66 次阅读


  1. 使用vscode,安装go,chinese,vscode-go-syntax,code Runner三个插件, &取地址,*地址对应的值 go mod init go mod tidy
  2. 环境配置,go env进行测试
  3. 
    package main    //必须有

import (
//导入格式化包
"fmt"
)

func main() { //必须有
fmt.Println("hello world")
}

//有些仅有一个main函数


4. go run 产生一个临时文件,程序运行完删除
//go run ./main.go
go build 编译完运行,不删除
go build -ldflags "-w -s" 压缩构建

package main

import "fmt"

func main() {
//关键字var 变量名i 数据类型int
var i int
i = 6
fmt.Println("i=", i)
fmt.Printf("变量名i的默认值:%v\n", i)
// 方法1: var name string
// name = "jack"
// 方法2: var name string = "jack"
// 方法3: name := "jack"
// 方法4: var sex = "man"
多个变量的声明
法一:a,b,c = 1,2,3
法二:
var (
a int =3
b int =3
c int =3
)

package main

import "fmt"

var (
num1 = 1
num2 = 2
num3 = 3
)

func main() {
var (
a1 = 1
a2 = 2
a3 = 3
)
fmt.Println(a1, a2, a3)
}


5. Print()将参数的内容进行标准输出。
Println()则是在标准输出后进行换行。
\\fmt.Println(a,b,c,"admin")
Printf()用于格式化字符串的输出
fmt.Printf()进行格式化输出的时候需要使用占位符。使用方法如下:
fmt.Printf("占位符", 变量)
占位符用于声明以什么样的格式来输出变量的内容。

func main() {
var n = 10
fmt.Printf("%T\n", n) //查看变量数据类型
fmt.Printf("%v\n", n) //查看变量的值
fmt.Printf("%b\n", n) //查看变量对应的二进制
fmt.Printf("%d\n", n) //查看变量对应的十进制
fmt.Printf("%o\n", n) //查看变量对应的八进制
fmt.Printf("%x\n", n) //查看变量对应的十六进制
}
// \t : 表示一个制表符,通常使用它可以排版。
// \n:换行符
// \:一个\
// \":一个"
// \r:一个回车
// %v 按值的默认格式输出
// 整数:
// %s 字符串
// %b 表示为二进制
// %c 该值对应的unicode码值
// %d 表示为十进制
// %o 表示为八进制
// %x 表示为十六进制,使用a-f
// %X 表示为十六进制,使用A-F


6. //常量定义
const 常量名字 [type] = value

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次,iota 可理解为 const 语句块中的行索引。
iota 可以被用作枚举值:
代码:
const (
a = iota //iota 0 a=0
b = "gin" //iota 1 b="gin"
c = "mk" //iota 2 c="mk"
d = iota //iota 3 d=3
)
fmt.Println("a:", a)
fmt.Println("b:", b)
fmt.Println("c:", c)
fmt.Println("d:", d)

var a =false
fmt.Println("占用的空间",unsafe.Sizeof(a)) //unsafe.Sizeof查看占用空间

int8 有符号 8 位整型 (-128 ~ 127)
int16 有符号 16 位整型 (-32768 ~ 32767)
int32 有符号 32 位整型 (-2147483648 ~ 2147483647)
int64 有符号 64 位整型 (-9223372036854775808 ~9223372036854775807)
uint8 无符号 8 位整型 (0 ~ 255)
uint16 无符号 16 位整型 (0 ~ 65535)
uint32 无符号 32 位整型 (0 ~4294967295)
uint64 无符号 64 位整型 (0 ~ 18446744073709551615)
注:通常我们声明一个int类型用于存放整数的变量时,直接通过int关键词来声明,此时int所指定的具体类型取决于操作系统,编译32位程序时则为int32,编译64位时则为int64,uint也一样。

rune类型是Unicode字符类型,和int32类型等价,通常用于表示一个Unicode码点,rune可以和int32可以互换。 rune [ruːn] 符号,符文
byte是unit8类型的等价类型,byte一般用于强调数值是个原始的数据,而不是一个小的整数。
uintptr是一种无符号的整数类型,用于存放指针,uintptr类型只有在底层编程才需要,特别是Go语言和C语言函数库或操作系统接口交互的地方。
package main

import (
"fmt"
"unsafe"
)

//演示golang中整数类型使用
func main() {
//int类型在64位系统上默认为int64
var i int = 1111111111
fmt.Println("i=", i)
//unsafe.Sizeof(i) 是unsafe包的一个函数,可以返回i变量占用的字节数,8字节=64位
fmt.Printf("i的类型:%T,i占用的字节数是:%d\n", i, unsafe.Sizeof(i))

float32、float64
浮点数的存储分为三部分:符号位+指数位+尾数位,在存储过程中尾数部分可能丢失。
float32 4字节 -3.403E38~3.403E38
float64 8字节 -1.798E308~1.798E308

在Go中字符串是不可变的,字符串一旦赋值了,字符串就不能修改了
str[0] = 'a' //修改字符串中单个字符是不可以的,但是可以给变量重新赋值。


7. 字符串的表达:双引号识别转义符,反引号不识别转义符
+进行拼接
拼接很长时,+号留在上一行
默认UTF-8 7. var a1 float32 = float32(b)
将b强转为float32类型 7. strconv包概述:strconv包主要实现对字符串和基本数据类型之间的转换。 7. 取址符:每一个变量都有一个内存地址,Go 语言的取地址符是&,放到一个变量前,使用就会返回相应变量的内存地址。
&a 表示a的地址 7. 指针:是一种存储变量内存地址(Memory Address)的变量。指针的值为另一个变量的内存地址。

声明指针变量的格式:var var_name *var-type
var_name 为指针变量名
var-type 为指针类型

  • 用于指定变量,作为一个指针变量,而不是普通变量

var a int =10
var dir1 int = &a
此时dir1是a的地址,
dir1表示指向地址的值,也就是10


```go
package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "regexp"

    "golang.org/x/text/encoding/simplifiedchinese"
    "golang.org/x/text/transform"
)

func main() {
    var url, reg string
    fmt.Print("请输入url:")
    fmt.Scanln(&url)
    fmt.Println(`正则语法<a onclick=".*" href=".*" title="(.*)" target=".*">(.*)</a>,(.*)是要提取的部分,其他的.*填充`)
    fmt.Print(`请输入正则:`)
    fmt.Scanln(&reg)
    res, erro := http.Get(url)
    if erro != nil {
        fmt.Println("错误")
    }
    transformReader := transform.NewReader(res.Body, simplifiedchinese.GBK.NewDecoder())
    resByte, _ := ioutil.ReadAll(transformReader)
    contentString := string(resByte)
    //取样
    //<a onclick="cc('ck_tj_pc_sptj2');" href="/shuku/1577704.html" title="最强学霸系统" target="_blank">最强学霸系统</a>
    //<img alt="火柴人试炼地6" src="//imga3.5054399.com/upload_pic/2021/10/22/4399_16070306195.jpg">
    regexString := string(reg)
    zhengObject := regexp.MustCompile(regexString)
    findContentArr := zhengObject.FindAllStringSubmatch(contentString, -1)
    for _, value := range findContentArr {
        fmt.Println(value[1])
    }

}
  1. go mod init spiders
    go mod tidy
package main

import (
    "fmt"
)

func main() {
    var s = make([]string, 0)
    s = append(s, "hello world")
    s = append(s, "first")
    fmt.Println(s[1])
    var m = make(map[string]string)
    m["a"] = "b"
    m["c"] = "d"
    fmt.Println(m)
}
//结果
//first
//map[a:b c:d]
//
//
package main

import (
    "fmt"
    "time"
)

func f() {
    fmt.Println("f function")
}
func main() {
    go f()
    time.Sleep(600 * time.Second)
    fmt.Println("main function")
}
  1. 正式学go
  2. API包括:api接口,api库
package main

import "fmt"

func main() {
    //关键字var   变量名i   数据类型int
    var i int
    i = 6
    fmt.Println("i=", i)
    fmt.Printf("变量名i的默认值:%v\n", i)
    // 方法1:  var name string
    //          name = "jack"
    // 方法2:  var name string = "jack"
    // 方法3:  name := "jack"
    // 方法4:  var sex = "man"
  多个变量的声明
  法一:a,b,c = 1,2,3
  法二:
       var  (
             a int =3
             b int =3
             c int =3
       )
  常量
    const 常量名字 [type] =value
    //const XingMing = sunwu
func Mc(obj int){
    //xxxx
   attach := 1

}
func main() {
    var n = 10
    fmt.Printf("%T\n", n) //查看变量数据类型
    fmt.Printf("%v\n", n) //查看变量的值
    fmt.Printf("%b\n", n) //查看变量对应的二进制
    fmt.Printf("%d\n", n) //查看变量对应的十进制
    fmt.Printf("%o\n", n) //查看变量对应的八进制
    fmt.Printf("%x\n", n) //查看变量对应的十六进制
}
Print()将参数的内容进行标准输出。
Println()则是在标准输出后进行换行。
\\fmt.Println(a,b,c,"admin")
Printf()用于格式化字符串的输出
fmt.Printf()进行格式化输出的时候需要使用占位符。使用方法如下:
fmt.Printf("占位符", 变量)
占位符用于声明以什么样的格式来输出变量的内容。

转义符
\t 四个字符(tab)
\n 换行
\\ \
\" "
\r 回车
package unintptr

func Test(){
    var 1 int = 123
    j := &i
    fmt.println(j)    //j指向的地址
    fmt.println(*j)   //j指向地址的值
}
  1. ""包裹的识别转义符,``格式不变原文输出
    '' 只能包裹单个字符 //var b byte = 'a'
  2. 字符串拼接
    a := "a"+"b"+"c"
var a string = "170"
res,_=strconv.Atoi(a)    //strconv.Atoi将字符串转化为int类型//a,_返回两个值,第二个值不接收
      strconv.ParseBool   转布尔
      strconv。ParseFloat(a,64)   //转化为64为浮点

% 取余
alt+shift+左键多行控制

num int => fmt.Sprint(num)
&&  //and   左右两边都为真结果为真
||  //or     一真则真
!   //not    结果相反
&=  //按位与后赋值
^=  //按位异或后赋值
|=  //按位或后赋值
|   //或     0|1 = 1
^   //异或   不同为1
fmt.Scanln()或fmt。Scanf()
func main() {
    name := ""
    fmt.Scanln(&name)
    fmt.Println("是输入的是:", name)
}
func main() {
    name, sex := "", ""
    age := 0
    fmt.Scanf("%s %d %s", &name, &age, &sex)
    fmt.Println("是输入的是:", name)
    fmt.Println("是输入的是:", age)
    fmt.Println("是输入的是:", sex)
}
//if
if a == 1{
}else if a == 2{
}else{
}
//switch

switch (a){
    case 1,2:     //a是否等于1或2
       fmt.Println("a")
       fallthrough    //fallthrough函数不是必须的,带上这个会输出a和b
    case 3,4:
       fmt.Println("b")
    default:
       fmt.Println("c")
}

fallthrough  //往下继续执行一层
//for
for i :=1;i<=100;i+=1{
    fmt.Println(i)
}
for{
    fmt.Println("I LOVE YOU")
}
Break跳出一层循环
continue //跳过本次循环,不会执行continue以下的部分

//for 字符串循环
    //a := "你好,我要杀死小强"
    //c := "abcdef"
    b := []rune("你好,我要杀死小强"," 呵呵")     //定义字符串
    for _, key := range b {                //range返回两个值位置,汉字如 1,你 2,好
        fmt.Println(string(key))           //fmt.Println(a,string(b))
    }
goto无条件跳转
    goto c
c:
  fmt.Println(a)
  1. 第五章函数和包
package 函数名
func 函数名(形参列表)[返回值列表]{
    return  返回值列表//可有可无
}
func addFunc(a,b int) int{
    return "正在:"+a+b
}
Go 语言规定一个文件目录中只能存在 1 个 main 函数,由于我们在 c:\goproject\src\chapter-5
目录创建了 yunsuan.go 和 max.go,两个文件中都存在 main 函数,编辑器虽然提示存在问题,但在当
前环境中不影响正常执行和编译。
Go mod 介绍使用
包是函数的集合体
包名必须和文件夹名一致    文件夹下的文件名字无所谓,第一行为package <包名>
使用包import "包的路径" //导包
go mod init <取一个包管理器名>

go mod download 下载依赖的 module 到本地 cache 默认:$GOPATH/pkg/mod 目录
go mod edit 编辑 go.mod 文件
go mod graph 打印模块依赖图
go mod init 初始化当前文件夹, 创建 go.mod 文件
go mod tidy 整理当前项目 mod,没有的下载,无用的删除。
go mod vendor 将依赖复制到 vendor 下
go mod verify 校验依赖
go mod why 解释为什么需要依赖
在调用一个函数时,会给该函数分配一个新的空间;
在每个函数对应的栈(sp)中,数据空间是独立的,不会混淆;
当一个函数调用完毕(执行完毕)后,程序会销毁这个函数对应的栈空间

栈存储变量的偏移地址
go env GOPATH   环境变量地址
递归:自己调用自己
递归=递推+回归
归函数总结:
1. 函数的局部变量是独立的,不会相互影响
2. 递归必须向退出递归的条件逼近,否则就是无限递归
3. 当一个函数执行完毕,或者遇到 return,就会返回,遵守谁调用,就将结果返回给谁,同时当函
数执行完毕或者返回时,该函数本身也会被系统销毁
注:递归函数如果无限递归会导致栈溢出。所以一定要设置退出递归的条件,并保证能够退出。
package main
import "fmt"
var a = 1
func main() {
    add()
    fmt.Println(a)
}
func add() int {
    a++
    if a >= 10 {
        return a
    }
    add()
    return a
}
init初始化函数
每一个源文件都可以包含一个 init 函数,该函数会在 main 函数执行前,被 Go 运行框架调用,也
就是说 init 会在 main 函数前被调用。
func init(){
    fmt.Println(111)
}
如果一个文件同时包含全局变量定义,init 函数和 main 函数,则执行的流程为:
全局变量定义->init 函数->main 函数
import(
    _ "chapter/bao1"   //_作用是,引用包前,先把包里的init执行了
)
如果包里有init函数,则按import先后顺序表进行执行
  1. ''只能输出单个字符(1byte)
    ""字符串
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等) ,为了在函数执行完毕后,
及时的释放资源,Go 的设计者提供 defer (延时机制)。
func main(){
    defer fmt.Println("1")
    fmt.Println("2")
}
 当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到
一个栈中, 然后继续执行函数下一个语句。
2. 当函数执行完毕后,在从 defer 栈中,依次从栈顶取出语句执行(注:遵守栈先入后出的机制),
先入后出:
闭包:能够读取其他函数内部变量的函数
1. 统计字符串的长度,按字节 len(str)
2. 字符串遍历,同时处理带有中文的字符串问题,r := []rune(str)
3. 字符串转整数,n, err := strconv.Atoi("12")
4. 整数转字符串,str = strconv.Itoa(12345)
5. 字符串转[]byte,var bytes = []byte("hello go")
6. []byte 转字符串,str = string([]byte{97, 98, 99})
7. 10 进制转 2、8、16 进制,str = strconv.FormatInt(123, 2) // 2-> 8 , 16
8. 查找子串是否在指定的字符串中,strings.Contains("seafood", "foo") //true
9. 统计一个字符串有几个指定的子串 : strings.Count("ceheese", "e") //4
10. 不区分大小写的字符串比较(== 是区分字母大小写的): fmt.Println(strings.EqualFold("abc",
"Abc")) // true
11. 返回子串在字符串第一次出现的 index 值,如果没有返回-1 : strings.Index("NLT_abc",
"abc") // 4
12. 返回子串在字符串最后一次出现的 index,如没有返回-1 : strings.LastIndex("go golang",
"go")
13. 将指定的子串替换成 另外一个子串: strings.Replace("go go hello", "go", "go 语言", n) n
可以指定你希望替换几个,如果 n=-1 表示全部替换
14. 按 照 指 定 的 某 个 字 符 ,为 分 割 标 识 ,将 一 个 字 符 串 拆 分 成 字 符 串 数
组 :strings.Split("hello,wrold,ok", ",")
15. 将字符串的字母进行大小写的转换: strings.ToLower("Go") // go strings.ToUpper("Go") //
GO
16. 将字符串左右两边的空格去掉: strings.TrimSpace(" tn a lone gopher ntrn ")
17. 将字符串左右两边指定的字符去掉 : strings.Trim("! hello! ", " !") // ["hello"] //将左右两
边 !和 " "去掉
18. 将字符串左边指定的字符去掉 : strings.TrimLeft("! hello! ", " !") // ["hello"] //将左边 !
和 " "去掉
19. 将字符串右边指定的字符去掉 : strings.TrimRight("! hello! ", " !") // ["hello"] //将右
边 ! 和 " "去掉
20. 判断字符串是否以指定的字符串开头: strings.HasPrefix("ftp://192.168.10.1", "ftp") //
true
21. 判断字符串是否以指定的字符串结束: strings.HasSuffix("NLT_abc.jpg", "abc") //false
func main() {
    var juanwnag [5]string
    juanwnag[0] = "1"        //从0开始计数
    juanwnag[4] = "2"
    for key, value := range juanwnag {
        fmt.Println(key, "=", value)
    }
}
var a=[10]float64{1,2,3,4,5,6,7}
  1. 数组: 数组可以存放多个同一类型数据,不可以存储不同类型的数据。数组也是一种数据类型,在 Go 中,数组是值类型。 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型, 例如整型、字符串或者自定义类型。相对于去声明 number0, number1, ..., number99 的变量,使用 数组形式 numbers[0], numbers[1] ..., numbers[99] 更加方便且易于扩展。 数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二 个索引为 1,以此类推。
  2. 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化。
  3. 切片:Go 语言切片是对数组的抽象。 Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍 的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片 的容量增大。比方说存放学生成绩,那学生个数是不确定的,这样就可以用切片。
切片定义的基本语法: var 切片变量名 []类型
声明一个空的切片,示例: var arr1 [] int
声明一个空的数据组,示例: var arr1 [10] int
总结:切片声明时不需要说明长度,[]没有声明长度,说明这是一个切片,而不是一个数组。
var 切片变量名 []数据类型     //切片的定义,法一
var a []int=[]int{1,2,3,4,5,6}     //定义切片的同时并赋值,法二
var a []int=make([]int,10)        //利用make进行声明,法三
var a []int=make([]数据类型,切片长度,最大扩容到多大容量) //法四
    a[0]=1
len(a) 判断长度
cap(a) 判断容量

切片的遍历
var a = []int{1, 2, 3, 4, 5, 6}
//方法一,for->len
for i:=0;i<len(a);i++{
    fmt.Println(ndexslice[i])alue
}
//方法二,for ->range
for index,value:=range a{
    fmt.Println("下标", index, "数据", value)
}

切片的扩容
package main
import "fmt"
func main() {
    a := make([]int, 5)
    a[0] = 1
    a[1] = 2
    a[2] = 3
    a[3] = 4
    a[4] = 5
    a = append(a, 6, 7, 8, 9) //长度为有效个数,容量为上次扩容的倍数(长度超过1024,为扩容上一次的1/4),如果追加的长度超过原本的容量就是追加的元素的容量的两倍
    fmt.Println(a)
}

切片的截取
package main
import "fmt"
func main() {
    a := []int{1, 2, 3, 4, 5, 6}
    b := a[1:2]    //[起始标号:结束标号:容量大小],容量大小可以不写
    //左闭右开
    //修改截取的数据,源数据会改变
    fmt.Println(b)
    fmt.Printf("%p\n", a)
    fmt.Printf("%p\n", b)
}

切片的拷贝(在地址里面新开辟了一段空间)
copy(新的切片名字,源切片名字)
冒泡排序:左右两个数比较大小,然后排序,循环
package main
import "fmt"
func main() {
    var a = []int{9, 5, 8, 7, 6, 4, 1, 2, 3}
    //冒泡排序
    //外层控制行,表示执行次数
    for i := 0; i < len(a); i++ {
        //内层控制比较次数
        for j := 0; j < len(a)-1-i; j++ {
            if a[j] > a[j+1] {
                a[j], a[j+1] = a[j+1], a[j]
            }
        }
    }
    fmt.Println(a)
}
1 1 2 3 5 8 13 21 34 55
package main

import "fmt"

func FeiBoNaQi(n int) []int {
    a := make([]int, n)
    a[0] = 1
    a[1] = 1
    for i := 2; i < n; i++ {
        a[i] = a[i-1] + a[i-2]
    }
    return a
}
func main() {
    b := FeiBoNaQi(20)
    fmt.Println(b)
}
  1. 第7章-排序和查找
var  二维数组明 [行数][列数]数据类型
var arr [3][4]int                  //初始化数组
var arr [3][4]int=[3][4]int{{1,2,3,4},{5,6,7},{9,10,11,12}}  //初始化二维数组并赋值
var arr [3][4]int=[...][4]int{{1,2,3,4},{5,6,7},{9,10,11,12}}  //行可以不写,列不能不写

fmt.Println(len(arr))     //计算行的个数
fmt.Println(len(arr[0]))  //计算第0行的列数

二维数组的遍历
arr := [3][4]int{{1, 2, 3, 4}, {2, 3, 4, 5}, {6}}     //方法1
    for i := 0; i < len(arr); i++ {
        for j := 0; j < len(arr[i]); j++ {
            fmt.Print(arr[i][j], "  ")
        }
        fmt.Println()
    }

arr := [3][4]int{{1, 2, 3, 4}, {2, 3, 4, 5}, {6}}     //方法2
    for key, value := range arr {
        for k, v := range value {
            fmt.Print("行:", key, "列:", k, "值:", v, "    ")
        }
        fmt.Println()
    }
map:俗称字典
//var map的名称 map[健的类型]值的类型
//键的类型key必须支持== 不可以使用slice map function channel
//值的类型可以是任意类型
//map的数据是无序的
var m map[int]string            //map创建,地址为nil,不允许赋值
var m map[int]string =make(map[int]string,容量大小)    //map创建并初始化,容量通常不写
m := map[sering]int{"张三":1001,"李四":1020;"王五":1111} //创建并赋值
m[1001]=”张三“
interface{}  //数据类型为可以存储任意
var a = make(map[int]string)
a[1000] = "0"
a[1001] = "1"
a[1005] = "2"
a[1003] = "3"
fmt.Println(a)

map的遍历

for key,value := range m {
    fmt.Println(key,value)
}

map的增删改查
如果key不存在,表示创建数据  //曾
如果key存在,表示修改        //改
delete(map的名字,key)       //删
值,是否存在 := map的名字[健]                      //查,查到显示,查不到为空
value,ok := m[100]
if ok{
    fmt.Println(value)
}else{
    fmt.Println("未能找到对应的数据")
}
  1. 排序
    1. 内部排序
      1. 指将需要处理的所有数据都加载到内部存储器中进行排序。包括(交换式排序法、选择式排序法和插入式 排序法);
      2. 交换式排序法: 交换式排序属于内部排序法,是运用数据值比较后,依判断规则对数据位置进行交换,以达到排序的目 的。交换式排序法又可分为两种:
        1. 冒泡排序法
          1. 冒泡排序(Bubble Sorting)的基本思想是: 通过对待排序序列从后向前(从下标较大的元素开始),依次比较 相邻元素的排序码,若发现逆序则交换,使排序码较小的元素逐渐从后部移向前部(从下标较大的单元移 向下标较小的单元),就象水底下的气泡一样逐渐向上冒。
          2. 简单的说冒泡就是:第一个数依次和后面的每个数比较大小,如果大就往后移 ,被移的数继续和后面的数作比较,以此循环
        2. 快速排序法
    2. 外部排序
      1. 数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括(合并排序法和直接合并排序 法)。
  2. fmt.Scan(&name)键盘录入
  3. 第八章-面向对象
type 结构体名称 struct{
    //定义结构体是,不能为结构体成员赋值
    结构体变量名 结构体数据类型
    结构体变量名 结构体数据类型
}

// package main

// import "fmt"

// type Student struct { //定义结构体
//  id    int
//  name  string
//  age   uint8 //0-225
//  score float64
//  addr  string
// }
//var a Student=Student{101,"q",48,99,"c"}     //初始化a结构体并赋值
//a := Student{101,"q",48,99,"c"}              //初始化a结构体并赋值
//var a Student
  a.age=18        //结构体类变量名.成员

// func main() {
//  var a = Student{ //初始化a结构体
//      id:    1,
//      name:  "张三",
//      age:   1,
//      score: 1,
//      addr:  "bj",
//  }
//  fmt.Println(a)
// }

结构体切片
type Student struct {
    id   int
    name string
}
 func main() {
    var a []Student = []Student{
        {
            id:   1,
            name: "张三",
        },
        {
            id:   2,
            name: "李四",
        },
    }
    fmt.Println(a)
}
a[3].name="王五"   //可以添加,不能修改
结构体作为切片单元,无法直接更改结构体的内容中的一项
  1. 函数/全局变量明/结构体名首字母大写,表明允许被外部调用 ,小写表示私有
面向对象编程(Object Oriented Programming)
基本特征:
封装
继承
多态
封装是隐藏对象的属性和实现细节,仅对外提供公共访问方式。控制在程序中属性的读和修改的访问级
别,将抽象得到的数据和行为或功能相结合,形成一个有机的整体,将数据与操作数据的源代码进行有
机的结合,形成“类”,其中数据和函数都是类的成员。将变化隔离,便于使用,提高复用性和安全性。

继承是允许创建分等级层次的类。继承就是子类继承父类的特征和行为,使得子类对象实例具有父类的
实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。提高代码复用性,同时继承也
是多态的前提。
多态同一个行为具有多个不同表现形式或形态的能力。是指一个类对象实例的相同方法在不同情形有不
同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。虽然针对不同对象的具体
操作不同,但通过一个公共的类,可以通过相同的方式予以调用。提高了程序的拓展性。
严格意义上说,Go语言并不是完全面向对象的,Go语言中并没有类和对象的概念。
所谓的面向对象在Go语言中使用两个关键类型是struct和interface来实现的。
type Person struct{
    name string
    age uint8
}
type Student struct{
    //a   Person   //将结构体作为数据类型的继承,设置名称之后调用b.a.name="张三"比较麻烦,建议匿名不用谢名字了
    Person       //没有变量名引入结构体,匿名字段,实现继承关系
    score float64
}
func main(){
    var b Student
    b.可以选择Persion和Student的变量=12   //Person作为父,Student作为子
    var C Student = Student(Person{name:"张三",age:18},score:88.5})
}

同名字段:
当父与子结构体选项名重复,默认调用子选项名,父选项名a.person.name调用

多重继承
//A继承B ,B继承C
//A继承于B,C
//尽量减少多重继承
对象=属性+行为(方法)
//函数的定义
func 函数名(形参)返回值
//方法的定义
func (方法接收者)方法名(形参)返回值
方法接收者:值对象接收者,指针对象接收者
//值对象接收者
func (对象名 结构体)方法名(){

}
  1. %f 保留小数点后两位,%.2f保留小数点后两位
package main
import "fmt"
type Student struct {
    id    int
    name  string
    age   uint8
    score float64
}
func (stu Student) Shu() {
    fmt.Printf("大家好,我是%s,今年%d岁,我的分数是%.2f分", stu.name, stu.age, stu.score)
}
func main() {
    var stu Student = Student{id: 123, name: "张三", age: 21, score: 99.5}
    stu.Shu()
}
package main
import "fmt"
type Student struct {
    id    int
    name  string
    age   uint8
    score float64
}
func (stu *Student) Shu() {
    stu.name = "李四"
    fmt.Printf("大家好,我是%s,今年%d岁,我的分数是%.2f分", stu.name, stu.age, stu.score)
}
func main() {
    var stu Student = Student{id: 123, name: "张三", age: 21, score: 99.5}
    (&stu).Shu()
    fmt.Println(stu.name) //指针接收者改变原本的结构体的值
}
type Person struct {
    name string //名字
    sex rune //性别
    age int //年龄
}
//Person类型,实现了一个方法
func (tmp *Person) PrintInfo() {
fmt.Printf("name=%s, sex=%c, age=%d\n", tmp.name, tmp.sex, tmp.age)
}
//学生,继承Person字段,成员和方法都继承了
type Student struct {
    Person
    id int
}
func main() {
s := Student{Person{name: "刘源", sex: '男', age: 18}, 1001}
//就近原则:先找本作用域的方法,找不到再用继承的方法
s.PrintInfo()   //子结构体可以继承父类的行为

方法的重写
type Person struct {
name string //名字
sex rune //性别
age int //年龄
}
//Person类型,实现了一个方法
func (tmp *Person) PrintInfo() {
fmt.Printf("name=%s, sex=%c, age=%d\n", tmp.name, tmp.sex, tmp.age)
}
//学生,继承Person字段,成员和方法都继承了
type Student struct {
Person
id int
}
//Student也实现了一个方法,这个方法和Person方法同名,这种方法叫重写
func (tmp *Student) PrintInfo() {
fmt.Println("Student: tmp = ", tmp)
}
func main() {
s := Student{Person{"刘源", '男', 33}, 1001}
//就近原则:先找本作用域的方法,找不到再用继承的方法
s.PrintInfo()
//显式调用继承的方法
s.Person.PrintInfo()
}
typee 接口名er interface{ //接口通常er做后缀
    //方法只有定义没有实现
    方法(参数列表)返回值列表
    方法(参数列表)返回值列表
    方法(参数列表)返回值列表
}
对象的方法必须包含接口的所有方法,才可以赋值给接口,通过接口.方法调用
type Humaner interface {
//只有定义没有实现
SayHello()
}
type Student struct {
name string
age int
}
//Student实现了接口Humaner中的方法
func (stu Student) SayHello() {
fmt.Printf("大家好,我是%s,我今年%d岁了\n", stu.name, stu.age)
}
type Teacher struct {
name string
subject string
}
//Teacher实现了接口Humaner中的方法
func (tea *Teacher) SayHello() {
fmt.Printf("大家好,我是%s,我教授的科目是%s\n", tea.name, tea.subject)
}
func main() {
//创建Student对象
stu := Student{"刘源", 33}
//创建Teacher对象
tea := Teacher{"刘源", "Go语言"}
//定义接口类型变量
var human Humaner
//如果对象的方法为值接收者,直接将对象赋值给接口变量
human = stu
human.SayHello()
//如果对象的方法为指针接收者,将对象取地址后赋值给接口变量
human = &tea
human.SayHello()
func 函数名(接口类型){
接口变量.方法(参数列表)
}

type Humaner interface {
//只有定义没有实现
SayHello()
}
type Student struct {
name string
age int
}
//Student实现了接口Humaner中的方法
func (stu Student) SayHello() {
fmt.Printf("大家好,我是%s,我今年%d岁了\n", stu.name, stu.age)
}
type Teacher struct {
name string
subject string
}
//Teacher实现了接口Humaner中的方法
func (tea *Teacher) SayHello() {
fmt.Printf("大家好,我是%s,我教授的科目是%s\n", tea.name, tea.subject)
}
func WhoSayHello(h Humaner) {
h.SayHello()
}
func main() {
//创建Student对象
stu := Student{"刘源", 33}
//创建Teacher对象
tea := Teacher{"刘源", "Go语言"}
WhoSayHello(stu)
WhoSayHello(&tea)
}
接口无法参与运算
var a interface{}=123
var a interface{}={}interface{}{123,true,"hello"}
接口断言
value,ok :=a[1].(int)     //接口类型.(判断数据类型)   //两个返回值
if ok{fmt.Println(整形数据)             //if value,ok :=a[1].(int);ok {   如果这么些减少作用域范围,作用域为if循环内
}else{fmt.Println(不是整形数据)
}

for _,v := range a {
    switch v.(type){
    case int:
        fmt.Println(v)
    case bool:
        fmt.Println(v)
    case string:
        fmt.Println(v)
}
}
  1. 强化数据结构
  2. 并发编程
go  函数名   //函数启用协程
如果main函数结束,任何协程都会自动结束

package main

import (
    "fmt"
    "time"
)

func Task() {
    for a := 0; a < 3; a++ {
        fmt.Println("并发")
        time.Sleep(time.Second)
    }
}
func main() {
    //创建Goroutineq启动协程
    go Task()
    for i := 0; i < 3; i++ {
        fmt.Println("没有并发")
        time.Sleep(time.Second)
    }

}
func(){

}()        //函数后的()代表调用,也就是先定义后立即调用
func(){
    fmt.Println("hello world")
    runtime.Goexit()   //协程推出,之后的不会运行
    fmt.Println("不会执行的函数")
}()
time.Sleep(time.Second*10)
//func main(){
//    make(chan 要传递的数据的数据类型)  //无缓冲的chan
//}
在第 1 步,两个 goroutine 都到达通道,但哪个都没有开始执行发送或者接收。
在第 2 步,左侧的 goroutine 将它的手伸进了通道,这模拟了向通道发送数据的行为。这时,这个
goroutine 会在通道中被锁住,直到交换完成。
在第 3 步,右侧的 goroutine 将它的手放入通道,这模拟了从通道里接收数据。这个 goroutine 一
样也会在通道中被锁住,直到交换完成。
在第 4 步和第 5 步,进行交换,并最终,在第 6 步,两个 goroutine 都将手从通道里拿出来,这
模拟了被锁住的 goroutine 得到释放。两个 goroutine 现在都可以去做其它操作。
如果没有指定缓冲区容量,那么该通道就是同步的,因此会阻塞到发送者准备好发送和接收者准备好接
收。

package main
import (
    "fmt"
    "time"
)
func main() {
    //chan定义格式
    //make(chan 数据类型) 无缓冲区的chan
    c := make(chan int, 0)
    //发送数据
    go func() {
        for i := 0; i < 5; i++ {
            c <- i //无缓冲区的chan,将i阻塞在管道口上锁,等待接收方准备就绪,开始传输数据,传输完毕同时解锁
        }
    }()
    //设置延时
    time.Sleep(time.Second)
    for i := 0; i < 5; i++ {
        value := <-c                //将c值传出通道
        fmt.Println(value)
    }
    time.Sleep(time.Second)
}

有缓冲区的channel
在第 1 步,右侧的 goroutine 正在从通道接收一个值。
在第 2 步,右侧的这个 goroutine独立完成了接收值的动作,而左侧的 goroutine 正在发送一个新
值到通道里。
在第 3 步,左侧的goroutine 还在向通道发送新值,而右侧的 goroutine 正在从通道接收另外一个
值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。
最后,在第 4 步,所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值
package main

import "fmt"

func main() {
    c := make(chan int, 5) //创建一个有缓冲的chan,缓冲大小为5(0为无缓冲),
    go func() {            //程序每运行一次循环,都会极其快速的锁定并解锁放入
        for i := 0; i < 10; i++ {
            c <- i
        }
    }()
    for i := 0; i < 10; i++ {
        value := <-c
        fmt.Println(value)
        fmt.Println("缓冲区长度", len(c))
    }
}

关闭chan
c := make(chan int, 5)
close(c)    //关闭通道

单向chan
//双向可以转化为单向,反之不行
make(chan<- 数据类型,容量)   //接收
make(<-chan 数据类型,容量 )  //发送

c := make(chan int,5)
var send chan<- int = c
var recv <-chan int = c
send <- 100
value := <-recv

//chan<-写
func counter(value chan<- int){
    for i:=0;1<5;i++{
        value <- i
    }
    close(value)
}
//<-chan 读
func printer(value <-chan int){
    for data := range value{
        fmt.Println(data)
    }
}
func main(){
    c := make(chan int)
    go counter(c) //生产者
    go printer(c) //消费者
    time.Sleep(time.Second)
}
//方法1
<-time.After(2 * time.Second) //定时2s,阻塞2s,2s后产生一个事件,往channel写内容
fmt.Println("时间到")
//方法2
time.Sleep(2 * time.Second)
fmt.Println("时间到")
//方法3
timer:= time.NewTimer(2 * time.Second)
<-timer.C
fmt.Println("时间到")

定时器停止
timer := time.NewTimer(2 * time.Second)
go func() {
<-timer.C      //放进管道
fmt.Println("子go程可以打印了,因为定时器的时间到")
}()
timer.Stop() //停止定时器

定时器重置
timer := time.NewTimer(100 * time.Second)
ok := timer.Reset(2 * time.Second) //重新设置为2s
fmt.Println("ok = ", ok)
<-timer.C
fmt.Println("时间到")

select关键字
通过select可以监听channel上的数据流动。
select{
case <-chan1:
//如果chan1成功读到数据,则进行该case处理语句
case chan2<-1:
//如果成功向chan2写入数据,则进行该case处理语句
default:
//如果上面都没有成功,则进入default处理流程
}
select 如果没有case 会阻塞。
select 监测channel数据流向。
case 必须为IO操作。
select 对应异步时间处理,需要在for循环中使用。
select 可以做超时处理。
如果多个case都满足条件,会随机选择其中之一来执行。
  1. Go调度器GMP
  2. Go语言运行时环境提供了非常强大的管理goroutine和系统内核线程的调度器, 内部提供了三种对象: Goroutine,Machine,Processor。
    Goroutine : 指应用创建的goroutine
    Machine : 指系统内核线程。
    Processor : 指承载多个goroutine的运行器 在宏观上说,Goroutine与Machine因为Processor的存在,形成了多对多(M:N)的关系。M个用户线 程对应N个系统线程,缺点增加了调度器的实现难度 Goroutine是Go语言中并发的执行单位。 Goroutine底层是使用协程(coroutine)实现,coroutine是一 种运行在用户态的用户线程(参考操作系统原理:内核态,用户态)它可以由语言和框架层调度。Go在 语言层面实现了调度器,同时对网络,IO库进行了封装处理,屏蔽了操作系统层面的复杂的细节,在语 言层面提供统一的关键字支持。
  3. 一个Machine会对应一个内核线程(K),同时会有一个Processor与它绑定。一个Processor连接一个 或者多个Goroutine。Processor有一个运行时的Goroutine(上图中绿色的G),其它的Goroutine处于 等待状态。
    Machine执行过程中,随时会发生上下文切换。当发生上下文切换时,需要对执行现场进行保护, 以便下次被调度执行时进行现场恢复。Go调度器中Machine的栈保存在Goroutine对象上,只需要 将Machine所需要的寄存器(堆栈指针、程序计数器等)保存到Goroutine对象上即可。 如果此时Goroutine任务还没有执行完,Machine可以将Goroutine重新压入Processor的队列,等 待下一次被调度执行。 如果执行过程遇到阻塞并阻塞超时,Machine会与Processor分离,并等待阻塞结束。此时 Processor可以继续唤醒Machine执行其它的Goroutine,当阻塞结束时,Machine会尝试”偷取”一 个Processor,如果失败,这个Goroutine会被加入到全局队列中,然后Machine将自己转入 Waiting队列,等待被再次唤醒。
  4. go的sync包
  5. sync.Pool 临时对象池
    sync.WaitGroup 组等待
    sync.Mutex 互斥锁
    sync.RWMutex 读写互斥锁
    sync. Cond 条件等待
    sync. Once 单次执行
    sync. Map 并发安全字典结构
sync.Pool 临时对象池
Pool是用于存储那些被分配了但是没有被使用,而未来可能会使用的值,以减小垃圾回收的压力。
Pool是协程安全的,应该用于管理协程共享的变量,不推荐用于非协程间的对象管理。
调动 New 函数,将使用函数创建一个新对象返回。
从Pool中取出对象时,如果Pool中没有对象,将执行New(),如果没有对New进行赋值,则返回
nil。
先进后出存储原则,和栈类似Pool一个比较好的例子是fmt包,fmt包总是需要使用一些[]byte之类
的对象,Go建立了一个临时对象池,存放着这些对象,如果需要使用一个[]byte,就去Pool里面
拿,如果拿不到就分配一份。这比起不停生成新的 []byte,用完了再等待gc回收来要高效得多。
package main

import (
    "fmt"
    "sync"
)

func main() {
    var pool sync.Pool
    pool.Put("hello")     //存在临时对象池pool里
    pool.Put("world")
    pool.Put("!")
    fmt.Println(pool.Get())  //一次取一个   打印hello
    fmt.Println(pool.Get())  //打印!
    fmt.Println(pool.Get())  //打印world   ,先打印第一个存的数据,之后打印顺序按照出栈顺序打印
}

等待组(sync.WaitGroup)
WaitGroup 用于等待一组协程的结束。
主协程创建每个子协程的时候先调用Add增加等待计数,每个子协程在结束时调用Done减少协程
计数。
主协程通过 Wait 方法开始等待,直到计数器归零才继续执行。
package main

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

func demo1(wg *sync.WaitGroup) {
    fmt.Println("htllo1")
    time.Sleep(time.Second)
    wg.Done()
}
func demo2(wg *sync.WaitGroup) {
    fmt.Println("htllo2")
    time.Sleep(time.Second)
    wg.Done()
}
func main() {
    var wg sync.WaitGroup
    //添加等待组个数
    wg.Add(2)
    //开启协程
    go demo1(&wg)
    go demo2(&wg)
    //wait等待结束,继续执行
    //阻塞等待,满足条件向下执行
    wg.Wait()
    fmt.Println("主函数继续执行")
}

互斥锁 (sync.Mutex)
锁
在并发的情况下,多个线程或协程同时去修改一个变量。使用锁能保证在某一时间点内,只有一个协程
或线程修改这一变量。
死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现
象,若无外界干涉,都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁
互斥锁
每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。资源还是共享的,线程间
也还是竞争的,但通过“锁”就将资源的访问变成互斥操作。
互斥锁用来保证在任一时刻,只能有一个协程访问某对象。
Mutex的初始值为解锁状态,Mutex通常作为其它结构体的匿名字段使用,使该结构体具有Lock和
Unlock方法。
Mutex可以安全的再多个协程中并行使用。
如果对未加锁的进行解锁,则会引发panic。
互斥锁是传统并发编程对共享资源进行访问控制的主要手段,由标准库sync中的Mutex结构体类型
表示。
sync.Mutex类型只有两个公开的指针方法,Lock和Unlock。Lock锁定当前的共享资源,Unlock进
行解锁。
在使用互斥锁时,一定要注意:对资源操作完成后,一定要解锁,否则会出现流程执行异常,死锁
等问题。通常借助defer。锁定后,立即使用defer语句保证互斥锁及时解锁。
加锁,保证数据能够正确:
package main

import (
    "fmt"
    "sync"
)

var value int
var wg sync.WaitGroup
var mu sync.Mutex

func text() {
    mu.Lock() //加锁
    value++
    wg.Done()
    mu.Unlock() //解锁
}
func main() {
    wg.Add(1000)
    for i := 0; i < 1000; i++ {
        go text()
    }
    wg.Wait()
    fmt.Print(value)
}
读写锁(sync.RWMutex)
当对一个不会变化的数据只做“读”操作的话,是不存在资源竞争的问题的。因为数据是不变的,不管怎
么读取,多少goroutine同时读取,都是可以的。
所以问题不是出在“读”上,主要是修改,也就是“写”。修改的数据要同步,这样其他goroutine才可以感
知到。所以真正的互斥应该是读取和修改、修改和修改之间,读和读是没有互斥操作的必要的。因此,
衍生出另外一种锁,叫做读写锁。GO中的读写锁由结构体类型sync.RWMutex表示。
读写锁可以让多个读操作并发,同时读取,但是对于写操作是完全互斥的。也就是说,当一个goroutine
进行写操作的时候,其他goroutine既不能进行读操作,也不能进行写操作。
sync.RWMutex是对写操作的锁定和解锁,简称“写锁定”和“写解锁”:
RWMutex比Mutex多了一个“写锁定” 和 “读锁定”,可以让多个协程同时读取某对象。
RWMutex的初始值为解锁状态。RWMutex通常作为其它结构体的匿名字段使用。
RWMutex可以安全的在多个协程中并行使用。
// Lock 将 rw 设置为写状态,禁止其他协程读取或写入
func (rw *RWMutex) Lock()
// Unlock 解除 rw 的写锁定状态,如果rw未被锁定,则该操作会引发 panic。
func (rw *RWMutex) Unlock()
// RLock 将 rw 设置为锁定状态,禁止其他协程写入,但可以读取。
func (rw *RWMutex) RLock()
// Runlock 解除 rw 设置为读锁定状态,如果rw未被锁定,则该操作会引发 panic。
func (rw *RWMutex) RUnLock()
// RLocker 返回一个互斥锁,将 rw.RLock 和 rw.RUnlock 封装成一个 Locker 接口。
func (rw *RWMutex) RLocker() Locker
 条件等待(sync.Cond)
条件等待和互斥锁有不同,互斥锁是不同协程公用一个锁,条件等待是不同协程各用一个锁,但是wait()
方法调用会等待(阻塞),直到有信号发过来,不同协程是共用信号。
 单次执行(sync.Once)
Once的作用是多次调用但只执行一次,Once只有一个方法,Once.Do(),向Do传入一个函数,这
个函数在第一次执行Once.Do()的时候会被调用
以后再执行Once.Do()将没有任何动作,即使传入了其他的函数,也不会被执行,如果要执行其它
函数,需要重新创建一个Once对象。
Once可以安全的再多个协程中并行使用,是协程安全的。
// 多次调用仅执行一次指定的函数f
func (o *Once) Do(f func())
并发安全Map(sync.Map)
Go语言中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。
需要并发读写时,一般的做法是加锁,但这样性能并不高,Go语言在 1.9 版本中提供了一种效率较高的
并发安全的 sync.Map,sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结
构。
sync.Map 有以下特性:
无须初始化,直接声明即可。
sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,
Store 表示存储,Load 表示获取,Delete 表示删除。
使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数
中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。
  1. 文件处理
文件创建
长久创建及关闭
package main
import (
    "fmt"
    "os"
)
func main() {
    //创建新文件
    f, err := os.Create("D:/code/go_code/src/practice4/test.txt")
    if err != nil {
        //失败原因
        //权限
        //磁盘空间不足
        //打开文件上限
        fmt.Println("文件创建失败")
        return
    }
    defer f.Close()
}

临时文件创建,自动删除
b := os.NewFile(0, "D:/code/go_code/src/practice4/test.txt")
    b.Close()
OpenFile()可以选择打开name文件的读写权限。这个函数有三个默认参数:
参1:name,表示打开文件的路径。可使用相对路径 或 绝对路径
参2:flg,表示读写模式,常见的模式有:
O_RDONLY(只读模式), O_WRONLY(只写模式), O_RDWR(可读可写模式), O_APPEND(追加模式)。
如果读取目录只能指定O_RDONLY模式
参3:perm,表权限取值范围(0-7),表示如下:
0[---]:没有任何权限
1[--x]:执行权限(如果是可执行文件,是可以运行的)
2[-w-]:写权限
3[-wx]: 写权限与执行权限
4[r--]:读权限
5[r-x]: 读权限与执行权限
6[rw-]: 读权限与写权限
7[rwx]: 读权限,写权限,执行权限
f, err := os.OpenFile("D:/code/go_code/src/practice4/test.txt", os.O_RDWR|os.O_

package main

import (
    "fmt"
    "os"
)

func main() {
    //D:/code/go_code/src/practice4/test.txt
    f, err := os.OpenFile("D:/code/go_code/src/practice4/test.txt", os.O_RDWR|os.O_CREATE, 0666)
    if err != nil {
        fmt.Println("文件操作失败")
        return
    } else {
        fmt.Println("文件操作成功")
    }
    f.WriteString("admin")   //以字符串的形式写入
    defer f.Close()
}
package main
import (
    "fmt"
    "os"
)
func main() {
    //D:/code/go_code/src/practice4/test.txt
    f, err := os.OpenFile("D:/code/go_code/src/practice4/test.txt", os.O_RDWR|os.O_CREATE, 0666)
    buf := make([]byte, 1024)    //1024=1kb
    a, _ := f.Read(buf)          //返回值字符长度,读取失败的信息     //读取文件方法一
    fmt.Println(string(buf[:a])) //windows的汉字是三个字节
    if err != nil {
        fmt.Println("文件操作失败")
        return
    } else {
        fmt.Println("文件操作成功")
    }
    f.WriteString("admin")
    defer f.Close()
}

n,_:=f.ReadAt(buf,13)//读取buf,跳过13个字节读取    //读取文件方法二
buf := []byte("hello world\n")
f.Write(buf)        //写入

f.WriteAt(buf,5)    //从第五位开始写入(覆盖),并且光标不变

f.WriteString("admin") //写入字符串
  1. \r\n表示换行
err := os.Remove("D:/code/go_code/src/practice4/test.txt")

package main
import (
"fmt"
"io"
"os"
)
func main() {
args := os.Args //获取用户输入的所有参数
//如果用户没有输入,或参数个数不够,则调用该函数提示用户
if args == nil || len(args) != 3 {
fmt.Println("useage : xxx srcFile dstFile")
return
}
srcPath := args[1] //获取输入的第一个参数
dstPath := args[2] //获取输入的第二个参数
fmt.Printf("srcPath = %s, dstPath = %s\n", srcPath, dstPath)
if srcPath == dstPath {
fmt.Println("源文件和目的文件名字不能相同")
return
}
srcFile, err1 := os.Open(srcPath) //打开源文件
if err1 != nil {
fmt.Println(err1)
return
}
dstFile, err2 := os.Create(dstPath) //创建目的文件
if err2 != nil {
fmt.Println(err2)
return
}
buf := make([]byte, 1024) //切片缓冲区
for {
//从源文件读取内容,n为读取文件内容的长度
n, err := srcFile.Read(buf)
if err != nil && err != io.EOF {
fmt.Println(err)
break
}
if n == 0 {
fmt.Println("文件处理完毕")
break
}
//切片截取
tmp := buf[:n]
//把读取的内容写入到目的文件
dstFile.Write(tmp)
}
//关闭文件
srcFile.Close()
dstFile.Close()
}
package main

import (
    "fmt"
    "os"
)

func main() {
    //D:/code/go_code/src/practice4/test.txt
    dir, err := os.OpenFile("D:/code/", os.O_RDONLY, os.ModeDir) //路径,只读,目录
    if err != nil {
        fmt.Println("目录操作失败")
        return
    } else {
        fmt.Println("目录操作成功")
    }
    names, _ := dir.ReadDir(-1) //-1表示整个目录
    for _, names := range names {
        if names.IsDir() {
            fmt.Println(names.Name(), "是一个目录")
        } else {
            fmt.Println(names.Name(), "是一个文件")
        }
    }
}
  1. 第十七章go的测试和日志
  2. 日志的处理
package main

import (
    "errors"
    "fmt"
    "log"
    "os"
)

func div(a, b int) (r int, err error) {
    if b == 0 {
        err = errors.New("integer divide by zero") //更改错误信息
        return
    }
    return a / b, nil
}
func main() {
    a := 10
    b := 0
    r, err := div(a, b)
    if err != nil {
        //日志的存储
        fmt.Println(err)
        f, _ := os.OpenFile("D:\\code\\go_code\\src\\practice4\\log.txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) //创建,追加,只写
        defer f.Close()
        log.SetOutput(f) //将日志输出到文件里
        //log.Fatal(err)    //打印错误程序并退出
        // f.WriteString(time.Now().String())
        // f.WriteString(err.Error())
        return
    }
    fmt.Println(r)
}
  1. 网络编程

  1. 四次挥手:发送fin包
  2. 滑动窗口(tcp流量控制)
    mss可以理解为缓冲区,发送的数据放在这个区域内,每回应一个就释放一个请求

    tcp交互
服务端
package main

import (
    "fmt"
    "log"
    "net"
)

func main() {
    //1.监听,tcp只能小写     端口前加:,1024-65535
    listener, err := net.Listen("tcp", ":8000")
    if err != nil {
        log.Fatal(err)
    }
    //关闭监听
    defer listener.Close()
    //2.accept(等待)
    fmt.Println("等待客户端字符串……")
    conn, err := listener.Accept()
    if err != nil {
        log.Fatal(err)
    }
    //3.数据读写操作
    buf := make([]byte, 1024)
    n, err := conn.Read(buf)
    fmt.Println(string(buf[:n]))
    //4.关闭连接
    defer conn.Close()

}

客户端
package main

import (
    "fmt"
    "net"
)

func main() {
    //拨号--连接到服务器   协议 ip地址:端口
    conn, err := net.Dial("tcp", "127.0.0.1:8000")
    if err != nil {
        fmt.Println(err)
    }
    //数据读写
    a := ""
    fmt.Println("请输入要传输的字符串")
    fmt.Scanln(&a)
    _, err = conn.Write([]byte(a))
    if err != nil {
        fmt.Println(err)
    }
    //关闭
    defer conn.Close()
}
package main

import (
    "fmt"
    "net"
    "strings"
)

//和客户端保持长时间连接,并处理客户端字符串(小写转大写),当输入exit退出
func HandleConn(conn net.Conn) {
    // defer func(conn net.Conn) {
    //  err := conn.Close()
    //  if err != nil {
    //      return
    //  }
    // }(conn)
    defer conn.Close()
    addr := conn.RemoteAddr().String()
    fmt.Println(addr, "创建连接成功")
    buf := make([]byte, 1024)
    for {
        n, err := conn.Read(buf)
        if err != nil {
            return
        }
        fmt.Printf("[%s]:%s\n", addr, string(buf[:n]))
        //buf[:n-2]  去掉\r\n内容
        if strings.Contains(string(buf), "exit") {
            fmt.Println(addr, "断开连接")
            return
        }
        //大小写转换
        conn.Write([]byte(strings.ToUpper(string(buf[:n]))))
    }
}
func main() {
    //创建监听
    listen, err := net.Listen("tcp", "127.0.0.1:8000")
    if err != nil {
        return
    }
    //关闭
    //关闭监听
    // defer func(listen net.Listener) {
    //  err := listen.Close()
    //  if err != nil {
    //      return
    //  }
    // }(listen)
    defer listen.Close()
    //接收客户端accept
    for {
        conn, err := listen.Accept()
        if err != nil {
            fmt.Println(err)
            return
        }
        //开启协程,数据处理
        go HandleConn(conn)
    }

}

客户端
package main

import (
    "fmt"
    "net"
)

func main() {
    //拨号--连接到服务器   协议 ip地址:端口
    conn, err := net.Dial("tcp", "127.0.0.1:8000")
    if err != nil {
        fmt.Println(err)
    }
    //关闭
    defer conn.Close()
    //数据读写
    str := ""
    for {
        fmt.Println("请输入要传输的字符串")
        fmt.Scanln(&str)
        _, err = conn.Write([]byte(str))
        if err != nil {
            fmt.Println(err)
        }

        buf := make([]byte, 1024)
        n, err := conn.Read(buf)
        if err != nil {
            return
        }
        fmt.Println(string(buf[:n]))
    }

}
懒得看了
  1. web编程
  2. 第22章 json操作和正则表达式

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "regexp"
    "strconv"
    "time"
)

func HttpGet(url string) (result string, err error) {
    // resp, err := http.Get(url)
    // if err != nil {
    //  return
    // }
    client := &http.Client{
        Timeout: time.Second * 2,
    }
    reqest, err := http.NewRequest("GET", url, nil)
    reqest.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 OPR/66.0.3515.115")
    if err != nil {
        fmt.Println(err)
        return
    }
    resp, _ := client.Do(reqest)
    defer func(Body io.ReadCloser) {
        err := Body.Close()
        if err != nil {
            return
        }
    }(resp.Body)
    fmt.Println("状态码:", resp.StatusCode)
    buf := make([]byte, 4096)
    for {
        n, err := resp.Body.Read(buf)
        result += string(buf[:n])
        if err != nil {
            if err == io.EOF {
                break
            } else {
                return "", err
            }
        }
    }
    //fmt.Println(result)
    return

}
func SpiderPage(i int, page chan int) {
    url := "https://movie.douban.com/top250?start=" + strconv.Itoa((i-1)*25) + "&filter="
    //fmt.Println(url)
    result, err := HttpGet(url)
    if err != nil {
        fmt.Println(err)
        return
    }
    //使用中文字符进行操作
    filmnamereg := `<span class="title">([一-龥]+)</span>`
    reg1 := regexp.MustCompile(filmnamereg)
    filmname := reg1.FindAllStringSubmatch(result, -1)

    filmscorereg := `<span class="rating_num" property="v:average">([0-9].[0-9])</span>`
    reg2 := regexp.MustCompile(filmscorereg)
    filmscore := reg2.FindAllStringSubmatch(result, -1)

    filmcountreg := `<span>([0-9]{4,7})人评价</span>`
    reg3 := regexp.MustCompile(filmcountreg)
    filmcount := reg3.FindAllStringSubmatch(result, -1)

    //fmt.Println("电影名:", filmname, "电影评分:", filmscore, "评分人数:", filmcount)
    //使用循环 一页有25部电影
    for i := 0; i < len(filmname); i++ {
        fmt.Printf("电影名:%v  电影评分:%v  评分人数:%v\n", filmname[i][1], filmscore[i][1], filmcount[i][1])
        f, _ := os.OpenFile("./DouBanTop250.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
        f.WriteString("电影名:" + filmname[i][1] + "电影评分:" + filmscore[i][1] + "评分人数: " + filmcount[i][1] + "\r")
        f.Close()
    }

    page <- i
}
func DoWork(start int, end int) {
    fmt.Println("起始页:", start, "结束页:", end)
    page := make(chan int, 10)
    for i := start; i <= end; i++ {
        go SpiderPage(i, page)
    }
    for i := start; i <= end; i++ {
        fmt.Println("第", <-page, "页爬取完毕")
    }
}
func main() {
    //设置起始页和结束页
    var a, b int
    fmt.Println("豆瓣top250一共十页,请输入两个数字,例如:1 2")
    fmt.Scanln(&a, &b)
    var start, end int = a, b
    DoWork(start, end)

}
  1. GIN框架
import "github.com/gin-gonic/gin"

go mod init
go mod tidy   下载模块

gin框架打印helloworld
package main
import (
    "net/http"
    "github.com/gin-gonic/gin"
)
func xxxx(ctx *gin.Context) {
    //ctx.String(http.StatusOK, "%s", "hello")   //可以用占位符,也可以直接字符串
    ctx.String(http.StatusOK, "helloworld")
}
//业务的处理
func main() {
    //1.创建路由
    router := gin.Default()
    //2.创建路由的规则 绑定执行函数
    router.GET("/", xxxx) //前面的/xxxx是请求的资源如:http://127.0.0.1/xxxx ,后面的这个xxxx调用函数xxxx
    //3.设置监听端口,不写默认8080
    router.Run(":8000")    //别忘了:
}
package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func someGET(ctx *gin.Context) {
    ctx.String(http.StatusOK, "访问someGET成功")
}
func somePOST(ctx *gin.Context) {
    ctx.String(http.StatusOK, "访问somePOST成功")
}
func main() {
    router := gin.Default()
    router.GET("/someGET", someGET)    //这三个someGET一般保持一致
    router.POST("/somePOST", somePOST) //访问如果有缓存将不区分大小写,清除缓存可以ctrl+r/ctrl+f5
    router.Run(":8080")
}
:表示一定要传的,*表示可以不传也不会404,也就是说访问/user会404,/user/aaa 会正确回显你好aaa,/user/aaa/bbb 也会正常回显你好aaabbb

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func someGET(ctx *gin.Context) {
    name := ctx.Param("name")
    action := ctx.Param("action")
    ctx.String(http.StatusOK, "你好%s%s", name, action) //当访问http://127.0.0.1:8080/user/xxx/aaa时,回显你好xxxaaa
}

func main() {
    router := gin.Default()
    router.GET("/user/:name/*action", someGET) //:表示一定要传的,*表示可以不传,也就是说访问/user会404,/user/aaa 会正确回显你好aaa,/user/aaa/bbb 也会正常回显你好aaabbb
    router.Run(":8080")
}
URL参数可以通过DefaultQuery()或Query()方法获取
DefaultQuery()若参数不存在,返回默认值,Query()若不存在,返回空串
使用格式为:API ? name=zs

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func someGET(ctx *gin.Context) {
    //设置默认值,如果没有收到指定url参数,会使用默认值
    name := ctx.DefaultQuery("name", "Guest")
    //如果没有收到指定url参数,会使用空字符串
    //name := ctx.Query("name")
    ctx.String(http.StatusOK, "hello %s", name)     //访问127.0.0.1:8080/user 回显hello Guest   ,访问127.0.0.1:8080/user/?name=admin 回显hello admin
}

func main() {
    router := gin.Default()
    router.GET("/user", someGET)
    router.Run(":8080")
}
表单传输为post请求,http常见的传输格式为四种:
application/json
application/x-www-form-urlencoded
application/xml
multipart/form-data
参数可以通过PostForm()方法获取,该方法默认解析的是 x-www-form-urlencoded 或 fromdata 格式的参数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="http://127.0.0.1:8080/form" method="post" action="application/x-www-form-urlencoded">
        用户名:<input type="text" name="username" placeholder="请输入你的用户名"><br>
        密   码:<input type="password" name="userpassword" placeholder="请输入你的密码"><br>
        <input type="submit" value="提交">
    </form>
</body>
</html>

package main
import (
    "net/http"
    "github.com/gin-gonic/gin"
)

func somePOST(ctx *gin.Context) {
    //默认post表单数据捕获
    types := ctx.DefaultPostForm("type", "post")
    name := ctx.PostForm("username")
    passwd := ctx.PostForm("userpassword")
    ctx.String(http.StatusOK, "type:%s name:%s passwd:%s", types, name, passwd)
}
func main() {
    router := gin.Default()
    router.POST("/form", somePOST)
    router.Run(":8080")
}
multipart/form-data 格式用于文件上传
上传文件的文件名可以由用户自定义,所以可能包含非法字符串,为了安全起见,应该由服务端统一文
件名规则。
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
        <form action="http://localhost:8000/upload" method="post" enctype="multipart/form-data">
             上传文件:<input type="file" name="files" multiple>  <!--带上multiple即可多文件上传 -->
            <input type="submit" value="提交">
        </form>
    </body>
</html>
package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func somePOST(ctx *gin.Context) {
    file, err := ctx.FormFile("files")
    if err != nil {
        ctx.String(http.StatusInternalServerError, "上传失败")
        return
    }
    //指定文件目录和文件名
    dst := "./" + file.Filename
    ctx.SaveUploadedFile(file, dst)
    ctx.String(http.StatusOK, "文件%s上传成功", file.Filename)
}

func main() {
    router := gin.Default()
    //上传文件大小限制默认 32MB
    router.MaxMultipartMemory = 32 * 1024 * 1024 //32MB
    router.POST("/upload", somePOST)
    router.Run(":8080")
}
package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func somePOST(ctx *gin.Context) {
    form, err := ctx.MultipartForm()
    if err != nil {
        ctx.String(http.StatusInternalServerError, "上传失败,$s", err.Error())
        return
    }
    files := form.File["files"]
    for _, file := range files {
        dst := "./" + file.Filename
        err := ctx.SaveUploadedFile(file, dst)
        if err != nil {
            ctx.String(http.StatusInternalServerError, "文件%s保存失败", err.Error())
        }
        ctx.String(http.StatusOK, "文件%s上传成功\r\n", file.Filename)

    }
    //指定文件目录和文件名

}

func main() {
    router := gin.Default()
    //上传文件大小限制默认 32MB
    router.MaxMultipartMemory = 32 * 1024 * 1024 //32MB   //1.46
    router.POST("/upload", somePOST)
    router.Run(":8080")
}
当前目录创建user文件夹,里面创建User.go
package user
import (
    "github.com/gin-gonic/gin")
//登录
func Login(g *gin.Context) {   //方法名大写,全局可以调用
}

import (
"article"
"github.com/gin-gonic/gin"
"user"
)
func main() {
r := gin.Default()
//用户模块
userRouter:=r.Group("/user")           //分组,
    userRouter.POST("/login",user.Login)   //这里调用触发  //用户需要访问 http://127.0.0.1:8080/user/login触发这个方法
userRouter.POST("/register",user.Register)
articleRouter:=r.Group("/article")
articleRouter.POST("/add",article.Add)
articleRouter.POST("/update",article.Update)
r.Run()     //设好调用
404页面与跳转冲突

package main
import (
    "net/http"
    "github.com/gin-gonic/gin"
)
func someGET(ctx *gin.Context) {
    ctx.String(http.StatusOK, "hello world")
}
func main() {
    router := gin.Default()
    //上传文件大小限制默认 32MB
    router.GET("/someGET", someGET)
    router.NoRoute(func(ctx *gin.Context) {
        //ctx.String(http.StatusNotFound, "您访问的页面不存在")  //404页面与跳转冲突
        ctx.Request.URL.Path = "/someGET"        //内嵌调用
        router.HandleContext(ctx)
        //ctx.Redirect(http.StatusFound, "http://127.0.0.1:8080/someGET")     //302跳转
    })
    router.Run(":8080")
}

页面跳转两种方式:1.302跳转
2.内嵌跳转

  1. Bin模型解析与响应
package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

type Login struct {
    User     string `form:"username"  json:"user" uri:"user" xml:"user" binding:"required"` //binding:"required"不允许传递空的内容,如果传递就会报错400
    Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}   //结构体变量名需要大写

//json数据解析和绑定
func main() {
    router := gin.Default()
    router.POST("login", func(c *gin.Context) {
        var json Login
        err := c.ShouldBindJSON(&json) //接收客户端的json数据,并解析
        if err != nil {
            //通过gin.H生成对应的数据格式
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) //返回报错400的json格式的信息
            return
        }
        if json.User != "xueshen" || json.Password != "123" {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        c.JSON(http.StatusOK, gin.H{"starus": "200登陆成功"})
    })
    router.Run(":8090")
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="http://127.0.0.1:8090/login" method="post" enctype="application/x-www-form-urlencoded">
        用户名<input type="text" name="username"><br>
        密码<input type="password" name="password"><br>
        <input type="submit" value="提交">
    </form>
</body>
</html>

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

type Login struct {
    User     string `form:"username"  json:"user" uri:"user" xml:"user" binding:"required"` //binding:"required"不允许传递空的内容,如果传递就会报错400
    Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}

func main() {
    router := gin.Default()
    router.POST("/login", func(c *gin.Context) {
        var form Login
        err := c.ShouldBind(&form) //直接解析表单数据   // c.ShouldBindXML   XML数据,c.ShouldBindUri(&login) uri数据
        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        }
        if form.User != "xueshen" || form.Password != "123" {
            c.JSON(http.StatusBadRequest, gin.H{"error": "用户明或密码错误"})
            return
        }
        c.JSON(http.StatusOK, gin.H{"status": "身份验证成功"})
    })
    router.Run(":8090")
}

c.ShouldBindXML   XML数据,
XML数据提交:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<user>xueshen</user>
<password>123</password>
</root>

 c.ShouldBindUri(&login) uri数据
uri数据提交方式
路由:router。POST("/:user/:password",func(c *gin.Contest))
http://127.0.0.1:8090/xueshen/123   
package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/testdata/protoexample"
)

//多种响应格式
func main() {
    //创建路由
    router := gin.Default()
    //json
    router.POST("/somejson", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"message": "somejson", "status": 200})
    })
    // 结构体响应
    router.POST("/somestruct", func(c *gin.Context) {
        var msg struct {
            Name    string
            Message string
            Number  int
        }
        msg.Name = "root"
        msg.Number = 123
        msg.Message = "message"
        c.JSON(http.StatusOK, msg)
    })
    //XML
    router.POST("/somexml", func(c *gin.Context) {
        c.XML(http.StatusOK, gin.H{"message": "abc"})
    })
    //protobuf格式,谷歌开发的高效存储读取的工具
    router.POST("/someProtobuf", func(c *gin.Context) {
        label := "status 200"
        reps := []int64{1, 2, 3, 4, 5}
        pro := &protoexample.Test{
            Label: &label,
            Reps:  reps,
        }
        c.ProtoBuf(http.StatusOK, pro)
    })
    router.Run(":7878")
}
  1. 第四章GIN中间件
  2. gin.Default() 默认使用了Logger和Recovery中间件,其中: Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。 Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。 如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。 Gin中间件的作用有两个: 1. Web请求到到达定义的HTTP请求处理方法之前,拦截请求并进行相应处理(比如:权限验证,数据 过滤等),这个可以类比为前置拦截器或前置过滤器, 2. 在处理完成请求并响应客户端时,拦截响应并进行相应的处理(比如:添加统一响应部头或数据格式 等),这可以类型为后置拦截器或后置过滤器。
    无中间件启动 r := gin.New()
    带有中间件 r := gin.Default() //默认启动方式,包含 Logger、Recovery 中间件
  3. Gin内置中间件
    1. Gin内置中间件 在使用Gin框架开发Web应用时,常常需要自定义中间件,不过,Gin也内置一些中间件,可以直接使 用,下面是内置中间件列表:
func BasicAuth(accounts Accounts) HandlerFunc
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
func Bind(val interface{}) HandlerFunc //拦截请求参数并进行绑定
func ErrorLogger() HandlerFunc //错误日志处理
func ErrorLoggerT(typ ErrorType) HandlerFunc //自定义类型的错误日志处理
func Logger() HandlerFunc //日志记录
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
func LoggerWithFormatter(f LogFormatter) HandlerFunc
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
func Recovery() HandlerFunc //错误拦截
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF(f http.HandlerFunc) HandlerFunc //将http.HandlerFunc包装成中间件
func WrapH(h http.Handler) HandlerFunc //将http.Handler包装成中间件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"time"
)
// 定义中间
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
fmt.Println("中间件开始执行了")
// 设置变量到Context的key中,可以通过Get()取
c.Set("request", "中间件")
status := c.Writer.Status()
fmt.Println("中间件执行完毕", status)
t2 := time.Since(t)
fmt.Println("time:", t2)
}
}
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 注册中间件
r.Use(MiddleWare())
// {}为了代码规范
{
r.GET("/ce", func(c *gin.Context) {
// 取值
req, _ := c.Get("request")
fmt.Println("request:", req)
// 页面接收
c.JSON(200, gin.H{"request": req})
})
}
r.Run()
}
Next()方法
在程序进入中间件的时候需要先进行一些处理,然后去 执行核心业务,在执行完核心业务之后再回来执
行该中间件。
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"time"
)
// 定义中间
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
fmt.Println("中间件开始执行了")
// 设置变量到Context的key中,可以通过Get()取
c.Set("request", "中间件")
// 执行函数
c.Next()
// 中间件执行完后续的一些事情
status := c.Writer.Status()
fmt.Println("中间件执行完毕", status)
t2 := time.Since(t)
fmt.Println("time:", t2)
}
}
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 注册中间件
r.Use(MiddleWare())
// {}为了代码规范
{
r.GET("/ce", func(c *gin.Context) {
// 取值
req, _ := c.Get("request")
fmt.Println("request:", req)
// 页面接收
c.JSON(200, gin.H{"request": req})
})
}
r.Run()
}

Abort()方法
在程序进入中间件之后进行了一些操作,判断该用户不满足访问这个请求的条件,这个时候就需要终止
这个请求,不让其继续执行,这个时候就使用到了Abort。
router.GET("/login", LoginMiddleware, handler)
func LoginMiddleware(ctx *gin.Context) {
login := ctx.Query("login")
if login != "" {
ctx.Next()
fmt.Println("Hi, " + login + ",欢迎访客登录")
} else {
ctx.Abort()
fmt.Println("未登录,拒绝访问")
}
}
  1. 中间件推荐列表

  2. COOKIE会话

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.GET("/cookie", func(c *gin.Context) {
        cookie, err := c.Cookie("xueshen_cookie")
        if err != nil {            //如果报错说明没有cookie,则发送一个cookie
            cookie = "NotSet"
            //设置cookie传递给客户端
            //name string 名称
            //value string 值
            //maxAge  int 单位时间
            //path    string 路径
            //domain  string 域名
            //secure bool 能否通过https访问
            //HttpOnly bool 不允许使用document.cookie访问
            fmt.Println("当前没有COOKIE")
            c.SetCookie("xueshen_cookie", "123", 60, "/", "127.0.0.1", false, true)
            return
        }
        c.String(http.StatusOK, "欢迎访问")   //检测到cookie就直接成功
        fmt.Println("你的当前Cookie是", cookie)
    })
    r.Run(":7878")
}
package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func AuthMiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        cookie, err := c.Cookie("xueshen")
        if err == nil {
            if cookie == "123" {
                c.Next()
                return
            }
        }
        c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录,服务器拒绝访问"})
        c.Abort() //终止
        return
    }
}

func main() {
    r := gin.Default()
    r.GET("login", func(c *gin.Context) {
        c.SetCookie("xueshen", "123", 60, "/", "127.0.0.1", false, true)
        c.JSON(http.StatusOK, gin.H{"home": "登录成功"})
    })
    r.GET("/home", AuthMiddleWare(), func(c *gin.Context) { //前置处理
        c.JSON(http.StatusOK, gin.H{"home": "你有cookie,欢迎光临"})

    })
    r.Run(":7878")
}
Session 对象存储特定用户会话所需的属性及配置信息,当用户在应用程序的Web页之间跳转时,存储
在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。
当用户请求来自应用程序的 Web页时,如果该用户还没有会话,则Web服务器将自动创建一个 Session
对象。当会话过期或被放弃后,服务器将终止该会话。
Session 对象最常见的一个用法就是存储用户的首选项。
Session原理
当访问服务器否个网页的时候,会在服务器端的内存里开辟一块内存,这块内存就叫做session,而这个
内存是跟浏览器关联在一起的。这个浏览器指的是浏览器窗口,或者是浏览器的子窗口,意思就是,只
允许当前这个session对应的浏览器访问,就算是在同一个机器上新启的浏览器也是无法访问的。而另外
一个浏览器也需要记录session的话,就会再启一个属于自己的session。
HTTP协议是非连接性的,取完当前浏览器的内容,然后关闭浏览器后,链接就断开了,而没有任何机制
去记录取出后的信息。而当需要访问同一个网站的另外一个页面时(就好比如在第一个页面选择购买的商
品后,跳转到第二个页面去进行付款)这个时候取出来的信息,就读不出来了。所以必须要有一种机制让
页面知道原理页面的session内容。
在Gin框架中,可以依赖 gin-contrib/sessions 中间件处理session。
gin-contrib/sessions中间件支持的存储引擎:
cookie
memstore
redis
memcached
mongodb
package main

import (
    "net/http"

    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    //创建cookie存储,将secret用作与加密的密钥
    stroe := cookie.NewStore([]byte("secret"))
    //注册全局中间件
    r.Use(sessions.Sessions("xueshen_session", stroe))
    r.GET("/hello", func(c *gin.Context) {
        session := sessions.Default(c)
        if session.Get("hello") != "123" {
            //设置session数据以键值对的形式存储
            session.Set("hello", "123")
            //保存session
            session.Save()
            //清除session
            //session.Clear()
            //删除session
            //session.Delete()
        }
        //重定向
        c.Request.URL.Path = "/xueshen" //内嵌调用
        r.HandleContext(c)
        //c.Redirect(http.StatusFound, "http://127.0.0.1:7878/xueshen")     //302跳转
    })
    r.GET("/xueshen", func(c *gin.Context) {
        session := sessions.Default(c)
        value := session.Get("hello")
        c.JSON(http.StatusOK, gin.H{"session": value})
    })
    r.Run(":7878")
}
  1. Cookie与session区别
    1. 保存位置不同:
      1. cookie保存在浏览器端,session保存在服务端。
    2. 使用方式不同:
      1. cookie:如果在浏览器端对cookie进行设置对应的时间,则cookie保存在本地硬盘中,此时如果没 有过期,则就可以使用,如果过期则就删除。如果没有对cookie设置时间,则默认关闭浏览器,则 cookie就会删除。
      2. session:我们在请求中,如果发送的请求中存在sessionId,则就会找到对应的session对象,如果 不存在sessionId,则在服务器端就会创建一个session对象,并且将sessionId返回给浏览器,可以 将其放到cookie中,进行传输,如果浏览器不支持cookie,则应该将其通过 encodeURL(sessionID)进行调用,然后放到url中。
    3. 存储内容不同:
      1. cookie只能存储字符串,而session存储结构类似于hashtable的结构,可以存放任何类型。 存储大小不同: cookie最多可以存放4k大小的内容,session则没有限制。
    4. 安全性不同:
      1. session的安全性要高于cooKie
    5. cookie的session的应用场景不同:
      1. cookie可以用来保存用户的登陆信息,如果删除cookie则下一次用户仍需要重新登录
      2. session就类似于我们拿到钥匙去开锁,拿到的就是我们个人的信息,一般我们可以在session中存 放个人的信息或者购物车的信息。
    6. session和cookie的弊端:
      1. cookie的大小受限制,cookie不安全,如果用户禁用cookie则无法使用cookie。如果过多的依赖 session,当很多用户同时登陆的时候,此时服务器压力过大。sessionID存放在cookie中,此时如 果对于一些浏览器不支持cookie,此时还需要改写代码,将sessionID放到url中,也是不安全
  2. 第六章参数验证和渲染
    1. Gin参数校验使用的是 Validator库,完整的校验规则可参考https://godoc.org/github.com/go-playgro und/validator,以下为常用的校验规则规则: Validator 是基于 tag(标记)实现结构体和单个字段的值验证库,包含以下功能: 使用验证 tag(标记)或自定义验证器进行跨字段和跨结构体验证。 关于 slice、数组和 map,允许验证多维字段的任何或所有级别。 能够深入 map 键和值进行验证。 通过在验证之前确定接口的基础类型来处理类型接口。 处理自定义字段类型(如 sql 驱动程序 Valuer)。 别名验证标记,它允许将多个验证映射到单个标记,以便更轻松地定义结构体上的验证。 提取自定义的字段名称,例如,可以指定在验证时提取 JSON 名称,并在生成的 FieldError 中使用该名称。 可自定义 i18n 错误消息。 Web 框架 Gin 的默认验证器
标签 描述
eq 等于
gt 大于
gte 大于等于
lt 小于
lte 小于等于
ne 不等于
max 最大值
min 最小值
oneof 其中一个
required 必须的
unique 唯一的
isDefault 默认值
len 长度
email 邮箱格式
1.1 数字值限制
限制范围
max=10 # 最大值为10,即小于等于10
min=10 # 最小值为10,即大于等于10
gt=10 # 大于 10
gte=10 # 大于等于10
lt=10 # 小于10
lte=10 # 小于等于10
限制值
eq=10 # 等于10
ne=10 # 不等于10
oneof=1 2 3 # 值只能是1、2 或 3
len=3 # 限制位数为3位数
如果限制之间存在冲突,如 eq=10,ne=10,则会根据先后顺序,后面的会覆盖前面的定义,以后面定义
的为准;校验内容为eq=10, ne=10,只会生效ne=10,如果存在冲突,所有校验规则均符合此规律。
限制长度范围
max=10 # 最大长度为10
min=10 # 最小长度为10
gt=10 # 长度大于10
lt=10 # 长度小于10
gte=10 # 长度大于等于10
let=10 # 长度小于等于10
限制值内容
eq=aaa # 值为aaa
ne=aaa # 值不能为aaa
oneof=a b c # 枚举,只能为a、b 或 c
len=3 # 字符长度为3

required # 必选
omitempty,xxx=xxx # 可选
如果存在,则继续向后校验规则xxx=xxx,如果不存在,则xxx=xxx不生效,但是如果omitempty之前存
在校验规则,则前面的校验规则还是生效的,如 gte=-1,omitempty,len=3,则gte=-1规则始终生效,而
len=3只有在值不为0时生效。

1.4 关联校验
required_with=AAA # 当AAA存在时,此字段也必须存在
required_with_all=AAA BBB CCC # 当AAA BBB CCC 都存在时,此字段必须存在
required_without=AAA # 当AAA不存在时,此字段必须存在
required_without_all=AAA BBB # 当AAA BBB 都不存在时,此字段必须存在
1.5 结构体字段间校验
一个层级内部校验
eqfield=AAA # 和字段AAA值相等
nefield=AAA # 和字段AAA值不相等
gtfield=AAA # 大于字段AAA的值
gtefield=AAA # 大于等于字段AAA的值
ltfield=AAA # 小于字段AAA的值
ltefield=AAA # 小于等于AAA字段的值
多个层级之间校验
eqcsfield=AAA.B # 等于AAA中的B字段
necsfield=AAA.B # 不等于AAA中的B字段
gtcsfield=AAA.B # 大于AAA中的B字段
gtecsfield=AAA.B # 大于等于AAA中的B字段
ltcsfield=AAA.B # 小于AAA中的B字段
ltecsfield=AAA.B # 小于等于AAA中的B字段
此处要注意的是,多个结构体之间的校验必须是字段之间存在层级联系,且只能是上层的与下层的做关
联,不能反过来。

https://godoc.org/github.com/go-playground/validator
package main

import (
    "fmt"
    "time"

    "github.com/gin-gonic/gin"
)

//对结构体的字段进行校验
type Person struct {
    //非空且年龄大于10
    Age  int    `form:"age" binding:"required,gt=10"`
    Name string `form:"name" binding:"required"`
    //默认的时间格式为:2006-01-02 15:04:05
    Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"8"`
}

func main() {
    r := gin.Default()
    r.GET("/hello", func(c *gin.Context) {
        var penson Person
        err := c.ShouldBind(&penson)
        if err != nil {
            c.String(500, fmt.Sprintf("%#v", err))
            return
        }
        c.String(200, fmt.Sprintf("%#v", penson))
    })
    r.Run(":81")
    //访问http://127.0.0.1:81/hello?age=10&name=xueshen&birthday=1987-09-15
}
package main

import (
    "fmt"

    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
)

type Login struct {
    User     string `uri:"user" validate:"checkName"`
    Password string `uri:"password"`
}

//自定义校验函数
func checkName(level validator.FieldLevel) bool {
    if level.Field().String() != "xueshen" {
        return false
    }
    return true
}
func main() {
    r := gin.Default()
    validate := validator.New()
    r.GET("/:user/:password", func(c *gin.Context) {
        //注册校验器
        err := validate.RegisterValidation("checkName", checkName)
        if err != nil {
            fmt.Println(err)
            return
        }
        var login Login
        err = c.ShouldBindUri(&login)
        if err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        err = validate.Struct(login)
        if err != nil {
            for _, value := range err.(validator.ValidationErrors) {
                c.String(200, "登陆失败")
                fmt.Println(value)
            }
            return
        }
        c.String(200, "登陆成功")
    })
    r.Run()
}

//https://godoc.org/github.com/go-playground/validator详细介绍
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
"github.com/go-playground/locales/zh_Hant_TW"
ut "github.com/go-playground/universal-translator"
"gopkg.in/go-playground/validator.v9"
en_translations "gopkg.in/go-playground/validator.v9/translations/en"
zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
zh_tw_translations "gopkg.in/go-playground/validator.v9/translations/zh_tw"
)
var (
Uni *ut.UniversalTranslator
Validate *validator.Validate
)
type User struct {
Username string `form:"user_name" validate:"required"`
Tagline string `form:"tag_line" validate:"required,lt=10"`
Tagline2 string `form:"tag_line2" validate:"required,gt=1"`
}
func main() {
en := en.New()
zh := zh.New()
zh_tw := zh_Hant_TW.New()
Uni = ut.New(en, zh, zh_tw)
Validate = validator.New()
route := gin.Default()
route.GET("hello", startPage)
route.POST("hello", startPage)
route.Run()
}
访问网址为:http://localhost:8080/hello?local=zh&age=12&name=xueshen&address=beijing
运行结果:
func startPage(c *gin.Context) {
//这部分应放到中间件中
locale := c.DefaultQuery("locale", "zh")
trans, _ := Uni.GetTranslator(locale)
switch locale {
case "zh":
zh_translations.RegisterDefaultTranslations(Validate, trans)
break
case "en":
en_translations.RegisterDefaultTranslations(Validate, trans)
break
case "zh_tw":
zh_tw_translations.RegisterDefaultTranslations(Validate, trans)
break
default:
zh_translations.RegisterDefaultTranslations(Validate, trans)
break
}
//自定义错误内容
Validate.RegisterTranslation("required", trans, func(ut ut.Translator) error
{
return ut.Add("required", "{0} must have a value!", true) // see
universal-translator for details
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required", fe.Field())
return t
})
//这块应该放到公共验证方法中
user := User{}
c.ShouldBind(&user)
fmt.Println(user)
err := Validate.Struct(user)
if err != nil {
errs := err.(validator.ValidationErrors)
sliceErrs := []string{}
for _, e := range errs {
sliceErrs = append(sliceErrs, e.Translate(trans))
}
c.String(200, fmt.Sprintf("%#v", sliceErrs))
}
c.String(200, fmt.Sprintf("%#v", "user"))
}

访问网址为:http://localhost:8080/hello?local=zh&age=12&name=xueshen&address=beijing
personInfo:
Age 12
Name "xueshen"
Address "beijing"
访问网址为:http://localhost:8080/hello
message:
Person.Address "Address is a required field"
Person.Age "Age is a required field"
Person.Name "Name is a required field"
sliceErrs:
0 "Age is a required field"
1 "Name is a required field"
2 "Address is a required field"
访问网址为:http://localhost:8080/hello?local=zh
message:
Person.Address "Address为必填字段"
Person.Age "Age为必填字段"
Person.Name "Name为必填字段"
sliceErrs:
0 "Age为必填字段"
1 "Name为必填字段"
2 "Address为必填字段
  1. html模板渲染
package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.LoadHTMLGlob("tmp/*")
    r.GET("index", func(c *gin.Context) {
        c.HTML(200, "index.html", gin.H{"title": "学神", "ce": "123"})
    })
    r.Run()
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>{{.title}}</title>
</head>
<body>
    xueshen{{.ce}}
</body>
</html>
package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        //外部重定向
        c.Redirect(http.StatusMovedPermanently, "http://sunwu.world") //外部重定向
    })
    r.GET("xueshen", func(c *gin.Context) {
        c.Request.URL.Path = "/hello" //内部重定向
        r.HandleContext(c)
    })
    r.GET("/hello", func(ctx *gin.Context) {
        ctx.String(http.StatusOK, "hello world")
    })
    r.Run(":8081")
}
  1. 热更新
  2. Gin框架中,每次修改代码之后都需要重新build,Go目前没有内置代码热更新的工具,可以使用第三方 类库Fresh。 Fresh是一个命令行工具,每次保存Go或模版文件时,该工具都会生成或重新启动Web应用程序。Fresh 将监视文件事件,并且每次创建、修改、删除文件时,Fresh都会生成并重新启动应用程序。如果go build返回错误,会将记录在tmp文件夹中。
安装
go get github.com/pilu/fresh
使用
进入项目根目录
cd $GOPATH/src/fresh_test
启动
fresh
这时控制台就开始编译打包执行了,注意控制台返回的信息,能知道项目的编译错误和日志,最后会有
访问 url, 项目go 文件有新增或修改,fresh 都会智能 reload 。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
c.String(http.StatusOK, "Hello Fresh!")
})
r.Run()
}
package main

import (
    "encoding/json"
    "fmt"
)

type IT struct {
    Company  string
    Subjects []string
    IsOk     bool
    Price    float64
}

func main() {
    //json编码
    // json.Marshal()
    // json.MarshalIndent()
    it := IT{
        Company:  "学神IT",
        Subjects: []string{"go", "python", "kali", "运维"},
        IsOk:     true,
        Price:    9999,
    }
    //默认的json格式
    //bytes, err := json.Marshal(it)
    //带有所进的json
    bytes, err := json.MarshalIndent(it, "", "    ")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(bytes))
}
package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    m := make(map[string]interface{})
    m["Company"] = "xueshenit"
    m["subject"] = []string{"go", "python", "web"}
    m["IsOk"] = true
    m["Price"] = 18980
    bytes, err := json.Marshal(m)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(bytes))
}
package main

import (
    "encoding/json"
    "fmt"
)

type IT struct {
    Company  string
    Subjects []string
    IsOk     bool
    Price    float64
}

func main() {
    bytes := []byte(`{
        "Company": "xueshenit",
        "Subjects": ["go", "python", "web"],
        "IsOk": true,
        "Price": 18980
    }`)
    //json解析
    var it IT
    err := json.Unmarshal(bytes, &it)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(it)
}
  1. 正则



#匹配中文、手机、电话
中文字符:^[\u4E00-\u9FA5]+$
手机号码:^(86)?0?1\d{10}$
电话号码:^((d{3,4})|d{3,4}-)?d{7,8}$
#匹配Email的地址
Email地址:^[\w-]+[\w-.]?@[\w-]+(\.[A-Za-z]{2,5})+$
Email地址:^w+[-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*$
Email地址:w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*
#匹配URL网址
URL网址:^http://([w-]+.)+[w-]+(([w-.)?%&=]*)?$
URL网址:http://([w-]+.)+[w-]+(/[w- ./?%&=]*)?
#设置密码的安全级别
密码(安全级别中):^(\d+[A-Za-z]\w*|[A-Za-z]+\d\w*)$
密码(安全级别高):^(\d+[a-zA-Z~!@#$%^&(){}][\w~!@#$%^&(){}]*|[a-zA-Z~!@#$%^&()
{}]+\d[\w~!@#$%^&(){}]*)$
预制符
[[:alnum:]] ///字母和数字
[[:alpha:]] ///任何英文大小写字符:A-Z,a-z
[[:lower:]] ///小写字母
[[:upper:]] ///大写字母
[[:blank:]] ///空白字符
[[:space:]] ///水平和垂直的空白字符
[[:cntrl:]] ///不可打印的控制字符:退格、删除、警铃...
[[:digit:]] ///十进制数字
[[:xdigit:]] ///十六进制数字
[[:graph:]] ///可打印的非空白字符
[[:print:]] ///可打印字符
[[:punct:]] ///标点符号
package main

import (
    "fmt"
    "regexp"
)

func main() {
    str := `abc a7c mfc cat 8ca azc cba`
    //匹配的正则表达式
    regstr := `a.c`
    reg := regexp.MustCompile(regstr)
    value := reg.FindAllStringSubmatch(str, -1)
    fmt.Println(value)
}
子夜不哭
最后更新于 2022-09-20