Golang学习笔记(自用)持续更新


--------------- 开坑!所有的成功都是靠着一点一滴的积累!欢迎监督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("收到验证码")
}
// 输出:用户校验完成
//  	收到验证码

  • 问题:
    1. 主线程结束,协程也会结束
    2. 协程安全
    3. 如何获取协程函数的返回值,如何在协程中传递数据?
      • 定义一个全局切片或者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之后就不能在继续写入了,但是还可以继续从缓冲区读取
    1. close之后,读取的chan是数据类型的默认值
    2. close之后,不能再往chan里面写入数据
    3. 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

泛型函数

  • 没有泛型的问题:
    1. 对于相近类型的操作,可能会写多个一模一样的函数
    2. 不停的类型转换
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]))
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值