文章目录
--------------- 开坑!所有的成功都是靠着一点一滴的积累!欢迎监督ovo ---------------
参考文档:枫枫知道
20230822
defer关键字
- 延迟执行函数,压入栈中,按后进先出的顺序调用
- 【注】defer延迟执行的函数其参数会立刻求值
- 用途:异常捕获(类似try……catch)
func Defer() {
defer func() {
err := recover() // 接收异常
if err != nil {
fmt.Println(err)
}
}()
n := 0
fmt.Println(3 / n)
}
func main() {
Defer()
fmt.Println("Defer函数之后运行")
}
init函数
- 可以在所有程序执行之前被调用,先于main函数自动执行
- 引入包 package init_study,文件夹里不能有main.go文件
- 【注】golang 全局变量声明必须以 var 关键字开头, 如果想要在外部包中使用全局变量的首字母必须大写。
- 执行顺序:被依赖的全局变量 ==> 被依赖包的init函数 ==> main包中的全局变量 ==> main包中的init ==> main
- 用途:初始化
包
-
golang的最小执行单位 一个包就是一个目录,包名和目录名一致,包里的.go文件名无关
-
导包按绝对路径引用
-
下划线 _:引入某个包,但不直接使用包里的函数,而是调用包里的init函数
package main
import (
"fmt"
_ "GO/pkg" // 只用包里的init
. "GO/pkg/pkg" // 引入包里的全部函数
p "GO/ppp" // 起别名
)
func main() {
fmt.Println("xxx")
}
20230823
数组
- golang中数组用的比较少
- 定义
var arr [3]int // {0,0,0}
var arr = [3]int{1,2,3}
var arr2 = [...]int{1,2,3} // ...自动推断长度
arr3 := [...]int{1,2,3}
// 二维数组
var t = [3][4]int{
{1,2,3,4},
{2,3,4,5},
{3,4,5,6},
}
切片(常用)
- 切片是引用类型,默认值是nil。左闭右开
var arr = [5]int{3,4,5,6,7}
var slice = arr[0:2] // {3 4}
var slice1 = arr[0:] // {2 4 5 6 7}
- 切片是对数组的引用:改变切片中的元素,对应数组中也会变化。
- 切片也可以切切片。
- 分配内存空间:(如何扩容?超过当前capacity后容量自动翻倍)
// 1 使用make
s = make([]int, 3, 4) // 创建一个长度为3的切片[0 0 0],容量cap=4,不写就默认=len
// 2 系统自动创建底层数组
s1 := []int{1,2,3,4}
- 常用方法:
// 追加元素
s := []int{1, 2}
s = append(s, 3, 4) // s = [1 2 3 4]
// 复制数组
var s1 = make([]int, 2, 2)
copy(s1, s) // 把s的前两个元素复制到s1, s1 = [1 2]
// string 和 []byte
str := "hello world"
fmt.Printf("[]byte(str) = %s, %v\n", []byte(str), []byte(str))
// 输出结果:[]byte(str) = hello world, [104 101 108 108 111 32 119 111 114 108 100]
// 函数接收多个参数(常用)
package main
import "fmt"
func Sum(numList ...int) int {
sum := 0
for _, v := range numList {
sum += v
}
return sum
}
func main() {
sum := Sum(1,3,4,5,6)
fmt.Println(sum)
}
map
- key-value , key 只能是基本数据类型
//定义 var m map[key_type]value_type
var m map[string]string
// 分配空间
m = make(map[string]string, 2)
m1 := map[string]string{
"name": "ha",
"age": "18",
"sex": "female",
}
m["name"] = "hhh"
fmt.Println(m) // 输出结果:map[name:hhh]
// 查找
v, ok := m["name"]
fmt.Println(v, ok) // hhh true
// 删除 delete(map, key)
delete(m, "name")
// 遍历:map只有len 没有cap
// 遍历 map 只有 for range 一种方式
for key, value := range m {
fmt.Printf("key = %v, value = %v\n", key, value)
}
20230825
自定义数据类型和类型别名
type newType uint16 // 此时的uint16和newType就不是一个类型了
var sum newType = 100
var num uint16 = 100
sum = newType(num) // 类型转换
- 别名:byte是uint8的别名,rune是int32的别名。起了别名的类型在赋值时就不需要类型转换了!
结构体
- 由一些字段构成的自定义数据类型
- 结构体字段通过 . 访问
type User struct {
Name string
Age int
}
func main() {
a := User{"erica", 21}
var b User
b.Name = "elysia"
}
- 继承
type User struct {
Name string
Age int
}
type Acount struct {
User
money float32
}
func main() {
ac := Acount{
money: 21,
User: User{
Name: "yuki",
Age: 18,
},
}
fmt.Println("ac: %#v, %T\n", ac, ac) // ac: main.Acount{User:main.User{Name:"yuki", Age:18}, money:21}, main.Acount
}
- 方法
type User struct {
Name string
Age int
}
type Account struct {
User
Money float32
}
// 定义方法
func (u User) PrintName() string {
return u.Name
}
func main() {
user := User{"erica", 21}
name := user.PrintName() // 值传递
fmt.Println(name) // erica
// 继承也会把方法继承到
acc := Account{
Money: 100,
User: user,
}
name = acc.PrintName()
fmt.Println(name) // erica
}
- 结构体指针
type User struct {
Name string
Id uint16
}
var u = User{
Name: "zhangsan",
}
u.Id = 20
fmt.Println(u) // {zhangsan 20}
var u2 *User = &User{
Name: "李四",
}
u2.Id = 21
fmt.Println(u2) // &{李四 21}
fmt.Printf("u = %+v, type of %T\n", u, u) // u = {Name:zhangsan Id:20}, type of main.User
fmt.Printf("u2 = %+v, type of %T\n", u2, u2) // u2 = &{Name:李四 Id:21}, type of *main.User
- 结构体tag
type User struct {
Name string `json:"name"`
Id uint16 `json:"id"`
}
// 这样写好之后,结构体进行json转换的时候,Name就会自动变为name
package main
import (
"encoding/json"
"fmt"
)
type Article struct {
Title string `json:"title"`
Desc string `json:"desc"`
Content string `json:"content"`
Username string `json:"-"` // - 也不参与序列化
LookCount int `json:"look_count"`
Free bool `json:"free"`
password string // 小写字母开头的不会参与序列化
}
func main() {
article := Article{
Title: "go",
Desc: "golang笔记",
Content: "golang笔记",
Username: "erica",
LookCount: 1111,
password: "12345",
Free: true,
}
// 结构体转json
jsonData, err := json.Marshal(article)
if err != nil {
fmt.Println(err)
return
}
jsonStr := string(jsonData)
fmt.Println(jsonStr)
}
// {"title":"go","desc":"golang笔记","content":"golang笔记","look_count":1111,"free":true}
接口
-
特殊的数据类型
方法定义的合集
方法名(形参类型) 返回值类型
提高代码的复用率
// 接口
type Animal interface {
Sing()
Dance(time int)
Rap() string
}
type Chicken struct {
Name string
}
func (kk Chicken) Sing() {
fmt.Println("Chicken实现了Sing方法")
}
// 实现接口
func (kk Chicken) Dance(time int) {
fmt.Println("Chicken实现了Dance方法")
}
func (kk Chicken) Rap() string {
fmt.Println("Chicken实现了Rap方法")
return "rap"
}
func main() {
var animal Animal
animal = Chicken{Name: "ik"}
animal.Sing()
}
空接口
- interface{}, 空接口可以保存任何类型
type data interface{} // 空接口
type Dog struct {
Name string
}
func Print(d data) {
fmt.Println(d)
}
func main() {
d := Dog{"小黑"}
Print(d)
Print(12)
Print("123")
Print(true)
Print([]int{1, 2, 3})
Print(make(map[string]string, 2))
}
类型断言
-
还原为原始类型 interface.(Type)
如果接口没有保存类型,则会报错
可返回两个值
value, ok := interface.(Type)
// 结合类型断言和空接口,实现传入的参数既可以是int类型,又可以是string类型
func PrintNum(num interface{}) {
//if val, ok := num.(int); ok {
// fmt.Println("int", val)
//}
//if val, ok := num.(string); ok {
// fmt.Println("string", val)
//}
switch num.(type) {
case int:
fmt.Println("int", num)
}
}
- nil
- nil值:有类型没有值,接口本身并不是nil,可以处理
- nil接口:既没有报错值,也没有保存类型,使用时会报错
20230829
协程
- Goroutine 是 Go 运行时管理的轻量级线程
- 主线程结束时,协程会被中断,需要有效的阻塞机制
package main
import (
"fmt"
"time"
)
// 发送验证码
func SendCode() {
fmt.Println("start sending...")
time.Sleep(3 * time.Second)
fmt.Println("end")
}
func main() {
fmt.Println("用户校验完成")
go SendCode() // 协程使用
fmt.Println("收到验证码")
}
// 输出:用户校验完成
// 收到验证码
- 问题:
- 主线程结束,协程也会结束
- 协程安全
- 如何获取协程函数的返回值,如何在协程中传递数据?
- 定义一个全局切片或者map,在协程中进行数据追加
WaitGroup
-
前面我们通过让主程序延时的方式,可以成功让协程函数顺利结束
但是,延时多久没人能够知道,所以,睡眠这种方式肯定不靠谱
go 自带一个WaitGroup
-
wg.add(2)是有2个goroutine需要执行
wg.Done 相当于 wg.Add(-1) 意思就是我这个协程执行完了
wg.Wait() 就是告诉主线程要等一下,等他们2个都执行完再退出
package main
import (
"sync"
)
var wg sync.WaitGroup
func say(s string) {
for i := 0; i < 5; i++ {
println(s)
}
wg.Done() // 结束
}
func main() {
wg.Add(2)
go say("Hello")
go say("World")
wg.Wait()
}
// World
// World
// World
// World
// World
// Hello
// Hello
// Hello
// Hello
// Hello
协程安全
- cpu可能会把多个请求认定是一个
// 下面代码每次的运行结果都不同
package main
import (
"fmt"
"sync"
)
var w = sync.WaitGroup{}
var num = 0
func AddNum() {
for i := 0; i < 1000000; i++ {
num++
}
w.Done()
}
func main() {
w.Add(2)
go AddNum()
go AddNum()
w.Wait()
fmt.Println(num)
}
- 解决方法:加锁
package main
import (
"fmt"
"sync"
)
var lock = sync.Mutex{}
var w = sync.WaitGroup{}
var num = 0
func AddNum() {
lock.Lock() // 上锁,谁先抢到就先执行,其他人等待
for i := 0; i < 1000000; i++ {
num++
}
lock.Unlock() // 解锁,其他人可以去抢
w.Done()
}
func main() {
w.Add(2)
go AddNum()
go AddNum()
w.Wait()
fmt.Println(num)
}
Channel
-
是一种带有类型的管道引用类型,使用前需要make(Type, (缓冲容量)),
不带缓冲区的管道必须结合协程使用
func main() {
var ch chan string = make(chan string, 2) // 声明一个string信道,容量是2
ch <- "sss" // 写入数据
ch <- "abc"
fmt.Println(<- ch) // 读取数据
s, ok := <- ch
fmt.Println(s)
close(ch) // 关闭信道
}
// sss
// abc true
存入:channel <- value
取出:value, (ok) <- channel
丢弃:<- channel
先进先出,自动阻塞
数据需要保持流动,否则会阻死报错
close
- 使用close之后就不能在继续写入了,但是还可以继续从缓冲区读取
- close之后,读取的chan是数据类型的默认值
- close之后,不能再往chan里面写入数据
- for range之前必须要close
只读只写
var ch chan int = make(chan int, 2)
// 只读chan
var readCh <-chan int = ch
// 只写chan
var writeCh chan<- int = ch
writeCh <- 1
writeCh <- 2
fmt.Println(<-readCh)
fmt.Println(<-readCh)
select… case
- 用于不确认应该何时关闭信道的情况
- 通常结合for循环使用,select … case会阻塞到某个分支可以继续执行时执行该分支,当没有可执行的分支是执行default
func main() {
var ch1 chan int = make(chan int, 2)
var ch2 chan int = make(chan int, 2)
var ch3 chan int = make(chan int, 2)
ch1 <- 1
ch2 <- 2
ch3 <- 3
select {
// 监听多个chan的情况,是随机执行
case v := <-ch1:
fmt.Println(v)
case v := <-ch2:
fmt.Println(v)
case v := <-ch3:
fmt.Println(v)
default:
fmt.Println("没有数据")
}
}
20230904
泛型
- 要求go的版本 >= 1.18
泛型函数
- 没有泛型的问题:
- 对于相近类型的操作,可能会写多个一模一样的函数
- 不停的类型转换
func PrintSlice(slice []int) {
for _, v := range slice {
fmt.Println("%T %v", v, v)
}
}
func main() {
PrintSlice([]int{1,2,3,4,5})
var int64Slice = []int64{5,6,7}
PrintSlice(intt64Slice) // 会报错,需要进行类型转换
}
- 泛型函数
// 泛型函数 [T type1 | type2 | ...]
func PrintSliceTypeSlice[T int | int64 | string](slice []T) {
fmt.Printf("%T\n", slice)
for _, v := range slice {
fmt.Printf("%T %v\n", v, v)
}
}
func main() {
PrintSliceTypeSlice([]int{1, 2, 3, 4, 5})
PrintSliceTypeSlice([]int64{1, 2, 3, 4, 5})
PrintSliceTypeSlice([]string{"hello"})
// 标准写法
PrintSliceTypeSlice[int]([]int{1, 2, 3, 4, 5})
PrintSliceTypeSlice[int64]([]int64{1, 2, 3, 4, 5})
PrintSliceTypeSlice[string]([]string{"hello"})
}
泛型切片/map
type mySlice [T int|string] []T // 泛型切片
type myMap[K string|int, V any] map[K]V // 泛型map
type _User struct {
Name string
}
func main() {
v1 := mySlice[int]{1,2,3}
m1 := myMap[string, string]{
"key": "fengfeng",
}
fmt.Println(m1)
m2 := myMap[int, _User]{
0: _User{"枫枫"},
}
fmt.Println(m2)
}
泛型约束
// 约束参数
package main
import "fmt"
type NumStr interface {
Num | Str
}
// ~的意思就是底层数据类型
type Num interface {
~int | ~int32 | ~int64 | ~uint8
}
type Str interface {
string
}
type Status uint8
type mySlice1[T NumStr] []T
func main() {
m1 := mySlice1[int]{1, 2, 3}
fmt.Println(m1)
m2 := mySlice1[int64]{1, 2, 3}
fmt.Println(m2)
m3 := mySlice1[string]{"hello"}
fmt.Println(m3)
m4 := mySlice1[Status]{1, 2, 3}
fmt.Println(m4)
}
// 约束方法
package main
import (
"fmt"
"strconv"
)
type Price int
func (p Price) String() string {
// int转数字
return strconv.Itoa(int(p))
}
type Price2 string
func (p Price2) String() string {
// int转数字
return string(p)
}
type showPrice interface {
~int | ~string
String() string
}
func showPriceFunc[T showPrice](p T) {
fmt.Println(p.String())
}
func main() {
var p1 Price = 12
showPriceFunc(p1)
var p2 Price2 = "56"
showPriceFunc(p2)
}
20230905
反射 reflection
- 使用空接口,可以传任意类型的数据,但是不能修改原始值
- 用反射就能动态修改原始数据
- 缺点:1. 性能问题 2. 可读性差
type User struct {
Name string
Age int
}
func Print(inter interface{}) {
switch x := inter.(type) {
case User:
fmt.Println(x.Name, x.Age)
}
}
func main() {
Print(User{"erica", 23})
}
- reflect包
- 重要方法:TypeOf,ValueOf
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string `json:"name" feng:"name_xxx"`
Age int `json:"age" feng:"age_xxx"`
}
func FPrint(inter interface{}) {
t := reflect.TypeOf(inter)
v := reflect.ValueOf(inter)
//fmt.Println(t.Kind()) // 获取这个接口的底层类型
//fmt.Println(t.Elem()) // 变量的原始类型
for i := 0; i < t.NumField(); i++ {
//fmt.Println()
// 字段的类型
// 字段名
// 字段的值
// 字段的tag
fmt.Println(
t.Field(i).Type,
t.Field(i).Name,
v.Field(i),
t.Field(i).Tag.Get("feng"),
)
}
}
func main() {
user := User{"枫枫", 21}
FPrint(user)
}
// string Name 枫枫 name_xxx
// int Age 21 age_xxx
- 修改结构体的数据
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string `json:"name" feng:"name_xxx"`
Age int `json:"age" feng:"age_xxx"`
}
func FPrint(inter interface{}) {
v := reflect.ValueOf(inter)
e := v.Elem() // 必须用这个
e.FieldByName("Name").SetString("枫枫知道")
}
func main() {
user := User{"枫枫", 21}
FPrint(&user) // 必须传指针
fmt.Println(user)
}
文件操作
https://docs.fengfengzhidao.com/#/docs/golang%E5%9F%BA%E7%A1%80/25.%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C
TCP
- server/ client/
服务端
package main
import (
"fmt"
"io"
"net"
)
func main() {
// 创建tcp的监听地址
tcpAddr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
// tcp监听
listen, _ := net.ListenTCP("tcp", tcpAddr)
for {
// 等待连接
conn, err := listen.AcceptTCP()
if err != nil {
fmt.Println(err)
break
}
// 获取客户端的地址
fmt.Println(conn.RemoteAddr().String() + " 进来了")
// 读取客户端传来的数据
for {
var buf []byte = make([]byte, 1024)
n, err := conn.Read(buf)
// 客户端退出
if err == io.EOF {
fmt.Println(conn.RemoteAddr().String() + " 出去了")
break
}
fmt.Println(string(buf[0:n]))
}
}
}