Go语言进阶

个人笔记,大量摘自Go语言高级编程、Go|Dave Cheney等

更新

go get -u all

  • 在非go目录运行go install golang.org/x/tools/gopls@latest
  • 更新go tools:在go目录运行go get -u golang.org/x/tools/...,会更新bin目录下的应用;
    运行go install golang.org/x/tools/...@latest
  • language server/linter: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
  • 生成调用关系图: go install github.com/ofabry/go-callvis@latest

数组

  • var f = [...]int{}定义一个长度为0的数组
    长度为0的数组在内存中并不占用空间。空数组虽然很少直接使用,但是可以用于强调某种特有类型的操作时避免分配额外的内存空间,比如用于管道的同步操作:
    c1 := make(chan [0]int)
    go func() {
        fmt.Println("c1")
        c1 <- [0]int{}
    }()
    <-c1

在这里,我们并不关心管道中传输数据的真实类型,其中管道接收和发送操作只是用于消息的同步。对于这种场景,我们用空数组来作为管道类型可以减少管道元素赋值时的开销。当然一般更倾向于用无类型的匿名结构体代替:

    c2 := make(chan struct{})
    go func() {
        fmt.Println("c2")
        c2 <- struct{}{} // struct{}部分是类型, {}表示对应的结构体值
    }()
    <-c2
  • 我们可以用fmt.Printf函数提供的%T或%#v谓词语法来打印数组的类型和详细信息:
    fmt.Printf("b: %T\n", b)  // b: [3]int
    fmt.Printf("b: %#v\n", b) // b: [3]int{1, 2, 3}

切片

a = a[N:] // 删除开头N个元素
a = append(a[:0], a[N:]...) // 删除开头N个元素
a = a[:copy(a, a[N:])] // 删除开头N个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
a = a[:i+copy(a[i:], a[i+N:])]  // 删除中间N个元素

结构

type structName struct {
    memberName string `fieldTag`
type Vertex struct {
	X, Y int
}
var (
	v1 = Vertex{1, 2}  // has type Vertex
	v2 = Vertex{X: 1}  // Y:0 is implicit
	v3 = Vertex{}      // X:0 and Y:0
  • 空结构: struct{} occupies no space, yet it can have member functions. So you can use it as value types of a map to substitute a boolean map for efficiency.
  • 分配内存:new(结构名)&结构名{}

函数

  • 可变数量的参数
func f(a ...interface{}) {
var a = []interface{}{1, "a"}
f(a...)

解包调用,相当于f(1, "a")

  • 通过叫方法表达式的特性可以将方法还原为普通类型的函数:
var CloseFile = (*File).Close
CloseFile(f)

结合闭包特性

f, _ := OpenFile("foo.dat")
var Close = func Close() error {
    return (*File).Close(f)
}
Close()

用方法值特性可以简化实现:

f, _ := OpenFile("foo.dat")
var Close = f.Close
Close()
  • 通过在结构体内置匿名的成员来实现继承
type Point struct{ X, Y float64 }
type ColoredPoint struct {
    Point
直接使用匿名成员的成员:
var cp ColoredPoint
cp.X = 1

接口

  • 有时候对象和接口之间太灵活了,导致我们需要人为地限制这种无意之间的适配。常见的做法是定义一个仅此接口需要的特殊方法。这个方法不对应实际有意义的实现。
  • 再严格一点的做法是给接口定义一个私有方法。只有满足了这个私有方法的对象才可能满足这个接口,而私有方法的名字是包含包的绝对路径名的,因此只能在包内部实现这个私有方法才能满足这个接口。不过这种通过私有方法禁止外部对象实现接口的做法也是有代价的:首先是这个接口只能包内部使用,外部包正常情况下是无法直接创建满足该接口对象的;其次,这种防护措施也不是绝对的,恶意的用户依然可以绕过这种保护机制。

using io.Reader

  • prefer
sc := bufio.NewScanner(r)
sc.Scan()
sc.Err()

to

_, err = bufio.NewReader(r).ReadString('\n')

because err == io.EOF when it reaches the end of file, making it more difficult to tell it from errors

modules

  • go mod init 本模块名如gitee.com/bon-ami/eztools/v4
  • use local modules
    go mod edit -replace example.com/a@v1.0.0=./a
    or manually in mod file
    replace url.com/of/the/module => /direct/path/to/files
  • update depended module, specifying a newer version
    go get gitee.com/bon-ami/eztools@v1.1.3
  • print path of main module go list -m. use go list -m example.com/hello@v0.1.0to confirm the latest version is available
  • download modules go mod download
  • change go.mod
# Remove a replace directive.
$ go mod edit -dropreplace example.com/a@v1.0.0

# Set the go version, add a requirement, and print the file
# instead of writing it to disk.
$ go mod edit -go=1.14 -require=example.com/m@v1.0.0 -print

# Format the go.mod file.
$ go mod edit -fmt

# Format and print a different .mod file.
$ go mod edit -print tools.mod

# Print a JSON representation of the go.mod file.
$ go mod edit -json
  • add/remove (un-)necessary modules go mod tidy [-v]
  • show go version go version -m
  • clean cache go clean -modcache
  • show module dependency in detail go mod graph
  • 离线使用:将在线下载好的${GOPATH}/go\pkg\mod拷贝到目标设备(包括其中

错误与异常

  • syscall.Errno对应C语言中errno类型的错误log.Fatal(err.(syscall.Errno))
  • err := recover()将内部异常转为错误处理,比如在defer时使用;必须在defer函数中直接调用recover——如果defer中调用的是recover函数的包装函数的话,异常的捕获工作将失败;如果defer语句直接调用recover函数,依然不能正常捕获异常——必须和有异常的栈帧只隔一个栈帧。
defer func() { //分别处理各种异常,但是不符合异常的设计思想
        if r := recover(); r != nil {
            switch x := r.(type) {
            case string:
                err = errors.New(x)
            case runtime.Error: // 这是运行时错误类型异常
            case error: // 普通错误类型异常
            default: // 其他类型异常
            }
        }
    }()
  • fmt.Errorf(..., error)将错误通过printf样格式化(%v)后重新生成错误
  • var p *MyError = nil将p以error类型返回时,返回的不是nil,而是一个正常的错误,错误的值是一个MyError类型的空指针:当接口对应的原始值为空的时候,接口对应的原始类型并不一定为空。

tests

  • Replace user interaction with file content. Use fmt.Fscanf, fmt.Fscanand ioutil.TempFile, io.WriteString, in.Seek.
  • failures of sub tests do not block main tests
t.Run(name, func(t *testing.T) {
            ...
        })
  • go test -run=.*/name -vto run subtest name. go test -run TestName
    to run TestName. -args后面参数可传到代码中:在init()flag.Int()等,在测试代码执行时
	if !flag.Parsed() {
		flag.Parse()
	}
  • go test -coverprofile=c.outto see branch coverage of tests
  • go tool cover -func=c.outto see function coverage of tests
  • github.com/pkg/expect 一般用不着
func TestOpenFile(t *testing.T) {
        f, err := os.Open("notfound")
        expect.Nil(err) //t.Fatal if err is not nil

部分具体实现:

func getT() *testing.T {
        var buf [8192]byte
        n := runtime.Stack(buf[:], false)
        sc := bufio.NewScanner(bytes.NewReader(buf[:n]))
        for sc.Scan() {
                var p uintptr
                n, _ := fmt.Sscanf(sc.Text(), "testing.tRunner(%v", &p)
                if n != 1 {
                        continue
                }
                return (*testing.T)(unsafe.Pointer(p))
        }
        return nil
}
  • Benchmark
    run all tests before all benchmarks
go test -bench=.

run *BenchmarksAdd* with no tests

go test -bench=BenchmarkAdd -run=XXXorNONEtoMakeItSimple -v

信号

sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
//停等,截获^C之后继续

并发与同步

  • sync.WaitGroup实例的Add(数目)后,Done()释放一个,Wait()等待指定数目释放完成
  • sync.Mutex实例的Lock()Unlock()
  • 原子操作:atomic.AddUint64(*uint64, uint64)计算、atomic.LoadUint32(*uint32)获取、atomic.StoreUint32(*uint32, uint32)存储
  • singleton:
once.Do(func() {
 由原子操作保证仅执行一次
})
  • trylock. 可用channel+select模拟,但是容易导致活锁,所以不建议使用
// try lock
type Lock struct {
    c chan struct{}
}

// NewLock generate a try lock
func NewLock() Lock {
    var l Lock
    l.c = make(chan struct{}, 1)
    l.c <- struct{}{}
    return l
}

// Lock try lock, return lock result
func (l Lock) Lock() bool {
    lockResult := false
    select {
    case <-l.c:
        lockResult = true
    default:
    }
    return lockResult
}

// Unlock , Unlock the try lock
func (l Lock) Unlock() {
    l.c <- struct{}{}
}
  • atomic.Value原子对象提供了LoadStore两个原子方法,分别用于加载和保存数据
  • 同一个Goroutine线程内部,顺序一致性内存模型是得到保证的。但是不同的Goroutine之间,并不满足顺序一致性内存模型,需要通过明确定义的同步事件来作为同步的参考。
  • 函数启动顺序:可能有多个init(),在常量、变量初始化之后、main()之前执行
  • 函数参数中指定通道为:输出用out chan<- 类型;输入用in <-chan 类型。用for v := range in能无限等待、循环消费所有in通道数据。
  • 无缓存的Channel上总会先完成接收再完成发送。带缓冲的Channel在缓冲满了时也是这样
  • 最简结构用于流量控制
 var tokenBucket = make(chan struct{}, capacity)
 tokenBucket <- struct{}{}:
  • 若在关闭Channel后继续从中接收数据,接收者就会收到该Channel返回的零值。因此close(管道)可代替向管道发送完成信号:所有从关闭管道接收的操作均会收到一个零值和一个可选的失败标志。读取,直到管道关闭:for v := range ch
  • 无限循环:select{}、for{}、<-make(chan struct{})空管道读、写永远阻塞
  • 生产者消费者模型:消息发送到一个队列;
    发布订阅(publish-and-subscribe)模型,即pub/sub:消息发布给一个主题
// Package pubsub implements a simple multi-topic pub-sub library.
package pubsub

import (
    "sync"
    "time"
)

type (
    subscriber chan interface{}         // 订阅者为一个管道
    topicFunc  func(v interface{}) bool // 主题为一个过滤器
)

// 发布者对象
type Publisher struct {
    m           sync.RWMutex             // 读写锁
    buffer      int                      // 订阅队列的缓存大小
    timeout     time.Duration            // 发布超时时间
    subscribers map[subscriber]topicFunc // 订阅者信息
}

// 构建一个发布者对象, 可以设置发布超时时间和缓存队列的长度
func NewPublisher(publishTimeout time.Duration, buffer int) *Publisher {
    return &Publisher{
        buffer:      buffer,
        timeout:     publishTimeout,
        subscribers: make(map[subscriber]topicFunc),
    }
}

// 添加一个新的订阅者,订阅全部主题
func (p *Publisher) Subscribe() chan interface{} {
    return p.SubscribeTopic(nil)
}

// 添加一个新的订阅者,订阅过滤器筛选后的主题
func (p *Publisher) SubscribeTopic(topic topicFunc) chan interface{} {
    ch := make(chan interface{}, p.buffer)
    p.m.Lock()
    p.subscribers[ch] = topic
    p.m.Unlock()
    return ch
}

// 退出订阅
func (p *Publisher) Evict(sub chan interface{}) {
    p.m.Lock()
    defer p.m.Unlock()

    delete(p.subscribers, sub)
    close(sub)
}

// 发布一个主题
func (p *Publisher) Publish(v interface{}) {
    p.m.RLock()
    defer p.m.RUnlock()

    var wg sync.WaitGroup
    for sub, topic := range p.subscribers {
        wg.Add(1)
        go p.sendTopic(sub, topic, v, &wg)
    }
    wg.Wait()
}

// 关闭发布者对象,同时关闭所有的订阅者管道。
func (p *Publisher) Close() {
    p.m.Lock()
    defer p.m.Unlock()

    for sub := range p.subscribers {
        delete(p.subscribers, sub)
        close(sub)
    }
}

// 发送主题,可以容忍一定的超时
func (p *Publisher) sendTopic(
    sub subscriber, topic topicFunc, v interface{}, wg *sync.WaitGroup,
) {
    defer wg.Done()
    if topic != nil && !topic(v) {
        return
    }

    select {
    case sub <- v:
    case <-time.After(p.timeout):
    }
}

select加延时:case <-time.After(时长):
select中条件检查channel读入值时不会阻塞case <-chan
只有其它条件不满足时,才会直到default:
匿名routine:go func() {}()

import "path/to/pubsub"

func main() {
    p := pubsub.NewPublisher(100*time.Millisecond, 10)
    defer p.Close()

    all := p.Subscribe()
    golang := p.SubscribeTopic(func(v interface{}) bool {
        if s, ok := v.(string); ok {
            return strings.Contains(s, "golang")
        }
        return false
    })

    p.Publish("hello,  world!")
    p.Publish("hello, golang!")

    go func() {
        for msg := range all {
            fmt.Println("all:", msg)
        }
    } ()

    go func() {
        for msg := range golang {
            fmt.Println("golang:", msg)
        }
    } ()

    // 运行一定时间后退出
    time.Sleep(3 * time.Second)
}
  • 控制并发数
var limit = make(chan int, 3)
func main() {
    for _, w := range work {
        go func() {
            limit <- 1
            w()
            <-limit
        }()
    }
    select{}
}
  • 用context实现素数筛,保证各goroutine收到退出的消息在这里插入图片描述
// 返回生成自然数序列的管道: 2, 3, 4, ...
func GenerateNatural(ctx context.Context) chan int {
    ch := make(chan int)
    go func() {
        for i := 2; ; i++ {
            select {
            case <- ctx.Done():
                return nil
            case ch <- i:
            }
        }
    }()
    return ch
}
// 管道过滤器: 删除能被素数整除的数
func PrimeFilter(ctx context.Context, in <-chan int, prime int) chan int {
    out := make(chan int)
    go func() {
        for {
            if i := <-in; i%prime != 0 {
                select {
                case <- ctx.Done():
                    return nil
                case out <- i:
                }
            }
        }
    }()
    return out
}
func main() {
    // 通过 Context 控制后台Goroutine状态
    ctx, cancel := context.WithCancel(context.Background())
    ch := GenerateNatural() // 自然数序列: 2, 3, 4, ...
    for i := 0; i < 100; i++ {
        prime := <-ch // 在每次循环迭代开始的时候,管道中的第一个数必定是素数
        fmt.Printf("%v: %v\n", i+1, prime)
        ch = PrimeFilter(ch, prime) // 基于管道中剩余的数列,并以当前取出的素数为筛子过滤后面的素数。不同的素数筛子对应的管道串联在一起
    }
    cancel()
}

CGo

  • 以注释方式添加
//#include <stdlib.h>
/* void test() {} */
  • .c和.go源文件一起编译
  • 导入C包import "C"
  • C.CString转换字符串
  • C.puts屏幕打印
  • 通过桥接,用Go实现C声明的函数,再导入Go
//void SayHello(_GoString_ s);
import "C"
import "fmt"
func main() {
    C.SayHello("Hello, World\n")
}
//export SayHello
func SayHello(s string) {
    fmt.Print(s)
}

Remote Procedure Call

  • RPC规则:方法只能有两个可序列化的参数,其中第二个参数是指针类型,并且返回一个error类型,同时必须是公开的方法。
    服务器端:
type HelloService struct {}
func (p *HelloService) Hello(request string, reply *string) error {
    *reply = "hello:" + request
    return nil
}
func main() {
    //将对象类型中所有满足RPC规则的对象方法注册为RPC函数,所有注册的方法会放在HelloService服务空间之下
    rpc.RegisterName("HelloService", new(HelloService))
    listener, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal("ListenTCP error:", err)
    }
    conn, err := listener.Accept()
    if err != nil {
        log.Fatal("Accept error:", err)
    }
    rpc.ServeConn(conn)
}

客户端:

func main() {
    client, err := rpc.Dial("tcp", "localhost:1234")
    if err != nil {
        log.Fatal("dialing:", err)
    }
    var reply string
    err = client.Call("HelloService.Hello", "hello", &reply)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(reply)
}
  • 改进的服务器端:明确服务的名字和接口
const HelloServiceName = "path/to/pkg.HelloService"
type HelloServiceInterface = interface {
    Hello(request string, reply *string) error
}
func RegisterHelloService(svc HelloServiceInterface) error {
    return rpc.RegisterName(HelloServiceName, svc)
}
type HelloService struct {}
func (p *HelloService) Hello(request string, reply *string) error {
    *reply = "hello:" + request
    return nil
}
func main() {
    RegisterHelloService(new(HelloService))
    listener, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal("ListenTCP error:", err)
    }
    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Fatal("Accept error:", err)
        }
        go rpc.ServeConn(conn)
    }
}

客户端

type HelloServiceClient struct {
    *rpc.Client
}
var _ HelloServiceInterface = (*HelloServiceClient)(nil)
func DialHelloService(network, address string) (*HelloServiceClient, error) {
    c, err := rpc.Dial(network, address)
    if err != nil {
        return nil, err
    }
    return &HelloServiceClient{Client: c}, nil
}
func (p *HelloServiceClient) Hello(request string, reply *string) error {
    return p.Client.Call(HelloServiceName+".Hello", request, reply)
}
func main() {
    client, err := DialHelloService("tcp", "localhost:1234")
    if err != nil {
        log.Fatal("dialing:", err)
    }
    var reply string
    err = client.Hello("hello", &reply)
    if err != nil {
        log.Fatal(err)
    }
}
  • 跨语言的RPC:标准库的RPC默认采用Go语言特有的gob编码,因此从其它语言调用Go语言实现的RPC服务将比较困难。
    rpc.ServeCodec(jsonrpc.NewServerCodec(conn)代替rpc.ServeConn(conn);用rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))代替rpc.Client
  • HTTP实现RPC:
func main() {
    rpc.RegisterName("HelloService", new(HelloService))
    http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {
        var conn io.ReadWriteCloser = struct {
            io.Writer
            io.ReadCloser
        }{
            ReadCloser: r.Body,
            Writer:     w,
        }
        rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
    })
    http.ListenAndServe(":1234", nil)
}

Protobuf & gRPC

  • 下载protoc工具 后安装针对Go语言的代码生成插件: go get github.com/golang/protobuf/protoc-gen-go
    通过以下命令生成相应的Go代码:protoc --go_out=. hello.proto
    其中go_out参数告知protoc编译器去加载对应的protoc-gen-go工具,然后通过该工具生成代码,生成代码放到当前目录。最后是一系列要处理的protobuf文件的列表。
    gRPC: protoc --go_out=plugins=grpc:. hello.proto
  • hello.proto示例
syntax = "proto3";

package main;

message String {
    string value = 1;
}

service HelloService {
    rpc Hello (String) returns (String);
}

REST API

web

  • 开源界有这么几种框架,第一种是对httpRouter进行简单的封装,然后提供定制的中间件和一些简单的小工具集成比如gin,主打轻量,易学,高性能。第二种是借鉴其它语言的编程风格的一些MVC类框架,例如beego,方便从其它语言迁移过来的程序员快速上手,快速开发。还有一些框架功能更为强大,除了数据库schema设计,大部分代码直接生成,例如goa。A famous router is Gorilla mux
  • net/http
func echo(wr http.ResponseWriter, r *http.Request) {
	msg, err := ioutil.ReadAll(r.Body)
	wr.Write(msg)
}

http.HandleFunc("/", echo)
http.ListenAndServe(":80", nil)

context.Cancel向要求处理线程结束

  • http的mux可完成简单的路由
  • RESTful: HEAD PATCH CONNECT OPTIONS TRACE
    常见的请求路径:
    • 查找GET /repos/:owner/:repo/comments/:id/reactions
    • 增加POST /projects/:project_id/columns
    • 修改PUT /user/starred/:owner/:repo
    • 删除DELETE /user/starred/:owner/:repo
  • httprouter: 目前开源界最为流行(star数最多)的Web框架gin使用的就是其变种
r := httprouter.New()
r.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("customized 404"))
})

压缩字典树(Radix Tree):以路由中相同一段作为节点的字典树(Trie Tree)

r.PUT("路由", 函数)

节点类型:
- static/default // 非根节点的普通字符串节点
- root // 根节点
- param // 参数节点,例如 :id
- catchAll // 通配符节点,只能在路由最后部分,例如 *anyway

  • 使用中间件剥离非业务逻辑
    a separate repository for middle wares of gin

Validator

  • https://github.com/go-playground/validator: 使用参见

遍历结构

  • 结构成员的标签:在成员
  • 反射 reflect
    • vt := reflect.TypeOf()
    • 解析单一成员的标签vt.Field(i).Tag
    • vv := reflect.ValueOf()
    • 成员数vv.NumField()
    • 单一成员vv.Field(i)
      • 分析其类型vv.Field(i).Kind()
        • reflect.Int
        • reflect.String
        • reflect.Struct
      • 取其为Interface{}vv.Field(i).Interface()
        vv.Field(i).Int()
        vv.Field(i).String()

Web项目分层

  • MVC=Module负责业务 View展示 Controller分发
  • CLD=Controller+Logic业务逻辑 DataAccessObject存储
  • 应用协议有(SOAP、REST、Thrift对比):
    • SOAP 有状态,易鉴权
    • REST 简单,无状态
    • thrift 二进制,高效
    • http
    • gRPC
      protocol-controller-logic-dao

源文解析

AST simple explanation

internal packages

only code under /a/b or its subdirectories can include /a/b/internal/d/e

跨平台编码

  • 查看当前平台编译某目录会编译哪些文件go list -f '{{.GoFiles}}' 代码目录
  • 方式一 为代码文件名加后缀而成为_$GOOS.go_$GOARCH.go_$GOOS_$GOARCH.go
  • 方式二 Build Tags在代码文件中尽量靠前,它控制本文件是否参与编译:
// +build linux darwin
// +build 386,!amd64

只在linux, darwin, 386,且非64位上参与编译。

  1. a build tag is evaluated as the OR of space-separated options
  2. each option evaluates as the AND of its comma-separated terms
  3. each term is an alphanumeric word or, preceded by !, its negation

在声明与包声明之间需要空行,以免被认为是包的说明

go command parameters

  • The commands are:

      bug         start a bug report
     build       compile packages and dependencies
      clean       remove object files and cached files
        -modcache clean module cache
     doc         show documentation for package or symbol
      env         print Go environment information
      fix         update packages to use new APIs
      fmt         gofmt (reformat) package sources
      generate    generate Go files by processing source
     get         download and install packages and dependencies
     install     compile and install packages and dependencies
     list        list packages (or modules)
       -m list modules. "-m all" list with dependants.
     mod         module maintenance
     run         compile and run Go program
     test        test packages.
       -v makes t.Log() print immediately instead of on the end of the test
       -bench=. runs benchmark
      tool        run specified go tool
      version     print Go version
        -m about a module
      vet         report likely mistakes in packages
    
  • Additional help topics:

     buildmode   build modes
     c           calling between Go and C
     cache       build and test caching
     environment environment variables
     filetype    file types
     go.mod      the go.mod file
     gopath      GOPATH environment variable
     gopath-get  legacy GOPATH go get
     goproxy     module proxy protocol
     importpath  import path syntax
     modules     modules, module versions, and more
     module-get  module-aware go get
     module-auth module authentication using go.sum
     module-private module configuration for non-public modules
     packages    package lists and patterns
     testflag    testing flags
     testfunc    testing functions
    

灰度发布 Canary

  • 分批次部署:从一个开始以2的级数增加发布用户数
  • 按业务规则:根据用户信息生成一个简单的哈希值,然后求模——比如对1000,比2小则为3‰

inlining

  • inlining budget. 编译选项-gcflags=-m=2可显示哪些函数会被inline
  • hariness. recover, break, select, go, for, range都会由于hairy造成不被inline

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/67754.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

灰度非线性变换之c++实现(qt + 不调包)

本章介绍灰度非线性变换&#xff0c;具体内容包括&#xff1a;对数变换、幂次变换、指数变换。他们的共同特点是使用非线性变换关系式进行图像变换。 1.灰度对数变换 变换公式&#xff1a;y a log(1x) / b&#xff0c;其中&#xff0c;a控制曲线的垂直移量&#xff1b;b为正…

两个状态的马尔可夫链

手动推导如下公式。 证明&#xff1a; 首先将如下矩阵对角化&#xff1a; { 1 − a a b 1 − b } \begin {Bmatrix} 1-a & a \\ b & 1-b \end {Bmatrix} {1−ab​a1−b​} (1)求如下矩阵的特征值&#xff1a; { 1 − a a b 1 − b } { x 1 x 2 } λ { x 1 x 2 }…

数据结构——空间复杂度

3.空间复杂度 空间复杂度也是一个数学表达式&#xff0c;是对一个算法在运行过程中临时占用存储空间大小的量度 。 空间复杂度不是程序占用了多少bytes的空间&#xff0c;因为这个也没太大意义&#xff0c;所以空间复杂度算的是变量的个数。 空间复杂度计算规则基本跟实践复杂…

yolov5代码解读之yolo.py【网络结构】

​这个文件阿对于做模型修改、模型创新有很好大好处。 首先加载一些python库和模块&#xff1a; 如果要执行这段代码&#xff0c;直接在终端输入python yolo.py. yolov5的模型定义和网络搭建都用到了model这个类(也就是以下图片展示的东西)&#xff1a;&#xff08;以前代码没…

EasyPoi导出 导入(带校验)简单示例 EasyExcel

官方文档 : http://doc.wupaas.com/docs/easypoi pom的引入: <!-- easyPoi--><dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>4.0.0</version></dep…

学习笔记-JVM-工具包(JVM分析工具)

常用工具 JDK工具 ① jps: JVM Process status tool&#xff1a;JVM进程状态工具&#xff0c;查看进程基本信息 ② jstat: JVM statistics monitoring tool &#xff1a; JVM统计监控工具&#xff0c;查看堆&#xff0c;GC详细信息 ③ jinfo&#xff1a;Java Configuration I…

【学习】若依源码(前后端分离版)之 “ 获取角色权限信息及动态路由”

大型纪录片&#xff1a;学习若依源码&#xff08;前后端分离版&#xff09;之 “ 获取角色权限信息及动态路由” 获取用户信息获取路由信息 承接上回&#xff0c;我们发现在login请求后面跟了两个请求&#xff0c;今天我们就来了解一下两个请求的含义。 获取用户信息 先看 ‘…

MySQL及SQL语句(3)

MySQL及SQL语句(3) 文章目录 MySQL及SQL语句(3)一、多表查询1.1 准备sql1.2 笛卡尔积1.3 多表查询的分类&#xff1a;内连接查询外连接查询子查询多表查询练习 二、事务2.1 事务的基本介绍概念操作实例事务提交的两种方式 2.2 事务的四大特征原子性持久性隔离性一致性 2.3 事务…

SpringBoot学习——springboot整合email springboot整合阿里云短信服务

目录 引出springboot整合email配置邮箱导入依赖application.yml配置email业务类测试类 springboot整合阿里云短信服务申请阿里云短信服务测试短信服务获取阿里云的accessKeyspringboot整合阿里云短信导包工具类 总结 引出 1.springboot整合email&#xff0c;qq邮箱&#xff0c;…

第7章 通过内网本机IP获取微信code值及其对code值的回调。

在第5章中讲述了怎样通过内网穿透外外网从而获取微信code值&#xff0c;实际上微信测试帐号管理页中也支持通过内网本机IP获取微信code值。 1 重构launchSettings.json "https": { "commandName": "Project", "dotnetRunMessages": t…

vscode ssh远程的config/配置文件无法保存解决

问题 之前已经有了一个config&#xff0c;我想更改连接的地址和用户名&#xff0c;但是无法保存&#xff0c;显示需要管理员权限&#xff0c;但以管理员启动vscode或者以管理员权限保存都不行 未能保存“config”: Command failed: “D:\vscode\Microsoft VS Code\bin\code.c…

Docker 方式 部署 vue 项目 (docker + vue + nginx)

1.安装好 nginx 。 2. 把 vue 项目的源码克隆到确定目录下。用 git 管理&#xff0c;所以直接 git clone 到既定目录就行了。 如我的目录是&#xff1a;/root/jiangyu/projects/gentle_vue/gentle_vue_code 。 3. 项目打包&#xff1a; npm run build 复制 会自动生成 dist…

【QT】 QT开发PDF阅读器

很高兴在雪易的CSDN遇见你 &#xff0c;给你糖糖 欢迎大家加入雪易社区-CSDN社区云 前言 本文分享QT开发PDF阅读器技术&#xff0c;希望对各位小伙伴有所帮助&#xff01; 感谢各位小伙伴的点赞关注&#xff0c;小易会继续努力分享&#xff0c;一起进步&#xff01; 你的点…

matplotlib 笔记 plt.grid

用于添加网格线 主要参数 visible 布尔值&#xff0c;True表示画网格 which表示要显示的刻度线类型&#xff0c;可以是 major&#xff08;主刻度&#xff09;或 minor&#xff08;次刻度&#xff09;&#xff0c;或者同时显示&#xff08;both&#xff09;alpha 透明度 …

培训报名小程序-订阅消息发送

目录 1 创建API2 获取模板参数3 编写自定义代码4 添加订单编号5 发送消息6 发布预览 我们上一篇讲解了小程序如何获取用户订阅消息授权&#xff0c;用户允许我们发送模板消息后&#xff0c;按照模板的参数要求&#xff0c;我们需要传入我们想要发送消息的内容给模板&#xff0c…

Vue实战技巧:从零开始封装全局防抖和节流函数

前言 你是否曾经遇到过用户频繁点击按钮或滚动页面导致反应迟钝的问题&#xff1f;这是因为事件被连续触发&#xff0c;导致性能下降。在本文中&#xff0c;我将为大家介绍 vue 中的防抖和节流策略&#xff0c;并展示如何封装全局的防抖节流函数&#xff0c;以避免频繁触发事件…

米尔瑞萨RZ/G2L开发板-01 开箱+环境搭建+交叉编译FFMPEG

标题有点长哈&#xff0c;首先要感谢米尔电子提供的开发板&#xff0c;异构的板子说实话还真的是最近才开始接触的&#xff0c;在我提交申请后&#xff0c;很快就收到板子了&#xff0c;而且还是顺丰给发来的&#xff0c;其实我估计很多人就是为了骗板子&#xff0c;因为米尔的…

LeetCode150道面试经典题-移除元素(简单)

目录 1.题目 2.解题思路 3.解题代码 1.题目 移除元素 给你一个数组 nums 和一个值 val&#xff0c;你需要原地移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并原地修改输入数组。 元素…

Idea中侧面栏不见了,如何设置?

一、打开idea点击File然后点击Setting 二、点击Appearance,然后划到最下面&#xff0c;勾选Show tool windows bars和Side-by-side layout on the left 三、侧面栏目正常显示

Chatgpt AI newbing作画,文字生成图 BingImageCreator 二次开发,对接wxbot

开源项目 https://github.com/acheong08/BingImageCreator 获取cookie信息 cookieStore.get("_U").then(result > console.log(result.value)) pip3 install --upgrade BingImageCreator import os import BingImageCreatoros.environ["http_proxy"]…