- 使用vscode,安装go,chinese,vscode-go-syntax,code Runner三个插件, &取地址,*地址对应的值 go mod init go mod tidy
- 环境配置,go env进行测试
-
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(®)
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])
}
}
- 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")
}
- 正式学go
- 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指向地址的值
}
- ""包裹的识别转义符,``格式不变原文输出
'' 只能包裹单个字符 //var b byte = 'a' - 字符串拼接
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)
- 第五章函数和包
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先后顺序表进行执行
- ''只能输出单个字符(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}
- 数组: 数组可以存放多个同一类型数据,不可以存储不同类型的数据。数组也是一种数据类型,在 Go 中,数组是值类型。 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型, 例如整型、字符串或者自定义类型。相对于去声明 number0, number1, ..., number99 的变量,使用 数组形式 numbers[0], numbers[1] ..., numbers[99] 更加方便且易于扩展。 数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二 个索引为 1,以此类推。
- 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化。
- 切片: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)
}
- 第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("未能找到对应的数据")
}
- 排序
- 内部排序
- 指将需要处理的所有数据都加载到内部存储器中进行排序。包括(交换式排序法、选择式排序法和插入式 排序法);
- 交换式排序法: 交换式排序属于内部排序法,是运用数据值比较后,依判断规则对数据位置进行交换,以达到排序的目 的。交换式排序法又可分为两种:
- 冒泡排序法
- 冒泡排序(Bubble Sorting)的基本思想是: 通过对待排序序列从后向前(从下标较大的元素开始),依次比较 相邻元素的排序码,若发现逆序则交换,使排序码较小的元素逐渐从后部移向前部(从下标较大的单元移 向下标较小的单元),就象水底下的气泡一样逐渐向上冒。
- 简单的说冒泡就是:第一个数依次和后面的每个数比较大小,如果大就往后移 ,被移的数继续和后面的数作比较,以此循环
- 快速排序法
- 冒泡排序法
- 外部排序
- 数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括(合并排序法和直接合并排序 法)。
- 内部排序
- fmt.Scan(&name)键盘录入
- 第八章-面向对象
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="王五" //可以添加,不能修改
结构体作为切片单元,无法直接更改结构体的内容中的一项
- 函数/全局变量明/结构体名首字母大写,表明允许被外部调用 ,小写表示私有
面向对象编程(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 (对象名 结构体)方法名(){
}
- %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)
}
}
- 强化数据结构
- 并发编程
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都满足条件,会随机选择其中之一来执行。
- Go调度器GMP
- Go语言运行时环境提供了非常强大的管理goroutine和系统内核线程的调度器, 内部提供了三种对象: Goroutine,Machine,Processor。
Goroutine : 指应用创建的goroutine
Machine : 指系统内核线程。
Processor : 指承载多个goroutine的运行器 在宏观上说,Goroutine与Machine因为Processor的存在,形成了多对多(M:N)的关系。M个用户线 程对应N个系统线程,缺点增加了调度器的实现难度 Goroutine是Go语言中并发的执行单位。 Goroutine底层是使用协程(coroutine)实现,coroutine是一 种运行在用户态的用户线程(参考操作系统原理:内核态,用户态)它可以由语言和框架层调度。Go在 语言层面实现了调度器,同时对网络,IO库进行了封装处理,屏蔽了操作系统层面的复杂的细节,在语 言层面提供统一的关键字支持。 - 一个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队列,等待被再次唤醒。 - go的sync包
- 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。
- 文件处理
文件创建
长久创建及关闭
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") //写入字符串
- \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(), "是一个文件")
}
}
}
- 第十七章go的测试和日志
- 日志的处理
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)
}
- 网络编程
- 四次挥手:发送fin包
- 滑动窗口(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]))
}
}
懒得看了
- web编程
- 第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)
}
- 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.内嵌跳转
- 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")
}
- 第四章GIN中间件
- 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 中间件 - Gin内置中间件
- 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("未登录,拒绝访问")
}
}
-
中间件推荐列表
-
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")
}
- Cookie与session区别
- 保存位置不同:
- cookie保存在浏览器端,session保存在服务端。
- 使用方式不同:
- cookie:如果在浏览器端对cookie进行设置对应的时间,则cookie保存在本地硬盘中,此时如果没 有过期,则就可以使用,如果过期则就删除。如果没有对cookie设置时间,则默认关闭浏览器,则 cookie就会删除。
- session:我们在请求中,如果发送的请求中存在sessionId,则就会找到对应的session对象,如果 不存在sessionId,则在服务器端就会创建一个session对象,并且将sessionId返回给浏览器,可以 将其放到cookie中,进行传输,如果浏览器不支持cookie,则应该将其通过 encodeURL(sessionID)进行调用,然后放到url中。
- 存储内容不同:
- cookie只能存储字符串,而session存储结构类似于hashtable的结构,可以存放任何类型。 存储大小不同: cookie最多可以存放4k大小的内容,session则没有限制。
- 安全性不同:
- session的安全性要高于cooKie
- cookie的session的应用场景不同:
- cookie可以用来保存用户的登陆信息,如果删除cookie则下一次用户仍需要重新登录
- session就类似于我们拿到钥匙去开锁,拿到的就是我们个人的信息,一般我们可以在session中存 放个人的信息或者购物车的信息。
- session和cookie的弊端:
- cookie的大小受限制,cookie不安全,如果用户禁用cookie则无法使用cookie。如果过多的依赖 session,当很多用户同时登陆的时候,此时服务器压力过大。sessionID存放在cookie中,此时如 果对于一些浏览器不支持cookie,此时还需要改写代码,将sessionID放到url中,也是不安全
- 保存位置不同:
- 第六章参数验证和渲染
- 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 | 长度 |
邮箱格式 |
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为必填字段
- 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")
}
- 热更新
- 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)
}
- 正则
-
#匹配中文、手机、电话
中文字符:^[\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)
}