go常用特性(embed、插件化开发)、常用包(并发)

news/2024/11/30 18:36:56/

go常用特性及常用包

1 常用特性

1.1 go:build

//go:build !windows
//go:build是前缀指令,!windows是逻辑判断的条件。这个指令的作用是在Windows系统外,编译当前源文件。// +build !windows
// +build是前缀指令,!windows是编译标记。这个指令的作用是告诉编译器只有当编译标记中不包含 windows时,才会编译当前源文件。

综合上述两个指令的作用,只有在非Windows系统下编译这个源文件时,会将其编译进目标可执行文件中。

  • //go:build 386 && windows
  • // +build 386,windows
    作用:只有当操作系统为windows,同时arch架构为386时,才编译当前文件

1.2 go:embed

//go:embed指令是Go 1.16版本新增的官方命令,可以用于在可执行文件中嵌入文件。

指令格式有三种形式:

  1. //go:embed path…
  2. //go:embed regexp
  3. //go:embed dir/*.ext

其中:

  • path… 是需要嵌入的文件或目录,可以为多个,用空格分隔。
  • regexp 是需要嵌入的文件名或目录名的正则表达式。
  • dir/*.ext 是需要嵌入的某个目录下特定扩展名的文件。

示例

假设我们有一个文件叫 data.txt,然后我们希望在程序中引用它,通过 //go:embed 指令即可嵌入。

package mainimport ("embed""fmt"
)//go:embed data.txt
var data stringfunc main() {fmt.Println(data)
}

在这个示例中,我们使用 //go:embed data.txt 将 data.txt 文件嵌入到了可执行文件中,并将其作为字符串赋值给了 var data string,然后在 main() 函数中输出了 data 变量的值。

1.3 其他

  • go:noinline:禁止编译器进行内联,即使启用了 -l 标志也不会内联。
  • go:noescape:告诉编译器某个函数或方法没有任何指针可以逃逸到外部,可以更好地进行优化。
  • go:linkname:用于跨包调用未导出的函数。
  • go:cgo_export_static、//go:cgo_export_dynamic、//go:cgo_import_static、//go:cgo_import_dynamic:用于在Go和C语言之间交互数据。

1.4 插件化开发

Golang官方提供了plugin模块,该模块可以支持插件开发.

目前很多思路都是在开发过程中支持插件话,当主体程序写完后,不能够临时绑定插件.但是本文将带领你进行主体程序自动识别并加载、控制插件调用.

1 基本思路

插件化开发中,一定存在一个主体程序,对其他插件进行控制、处理、调度.

1.1 基本业务

  • 我们首先开发一个简单的业务程序,进行两种输出.
  1. 当时间秒数为奇数的时候,输出hello
  2. 当时间秒数为偶数的时候,输出world

主体代码,MainFile.go:

package mainimport ("fmt""time"
)// init 函数将于 main 函数之前运行
func init() {fmt.Println("Process On ==========")
}func main() {// time.Now().Second 将会返回当前秒数nowSecond := time.Now().Second()doPrint(nowSecond)fmt.Println("Process Stop ========")
}// 执行打印操作
func doPrint(nowSecond int) {if nowSecond%2 == 0 {printWorld() //偶数} else {printHello() //奇数}
}func printHello() {fmt.Println("hello")
}func printWorld() {fmt.Println("world")
}

代码有一定的冗余,是为了模拟业务之间的调度

运行代码:
在这里插入图片描述

1.2 编写简单插件

然后我们编写一个插件代码,插件代码的入口package也要为main,但是可以不包含main方法

  • 设定插件逻辑为当当前秒数为奇数的时候,同时输出当前时间(与hello的判定不是一个时间)
  • 插件文件名:HelloPlugin.go

在当前目录下,执行插件生成指令:

注意:buildmode=plugin 只支持在Linux和Mac系统下的64位构建,并不支持在Windows系统下的64位构建。

// mac或者linux系统
go build --buildmode=plugin -o HelloPlugin.so HelloPlugin.go

当前目录下就会多出来一个文件HelloPlugin.so,然后,我们让主程序加载该插件

package mainimport ("fmt""plugin""time"
)// 定义插件信息
const pluginFile = "HelloPlugin.so"// 存储插件中将要被调用的方法或变量
var pluginFunc plugin.Symbol// init 函数将于 main 函数之前运行
func init() {// 查找插件文件pluginFile, err := plugin.Open(pluginFile)if err != nil {fmt.Println("An error occurred while opening the plug-in")} else {// 查找目标函数targetFunc, err := pluginFile.Lookup("PrintNowTime")if err != nil {fmt.Println("An error occurred while search target func")}pluginFunc = targetFunc}fmt.Println("Process On ==========")
}func main() {// time.Now().Second 将会返回当前秒数nowSecond := time.Now().Second()doPrint(nowSecond)fmt.Println("Process Stop ========")
}func doPrint(nowSecond int) {if nowSecond%2 == 0 {printWorld() //偶数} else {printHello() //奇数}
}func printHello() {// 执行插件调用if pluginFunc != nil {//将存储的信息转换为函数if targetFunc, ok := pluginFunc.(func()); ok {targetFunc()}}fmt.Println("hello")
}func printWorld() {fmt.Println("world")
}

运行代码:
在这里插入图片描述

2 常用包

2.1 标准库

文档地址:http://doc.golang.ltd/

①os模块

1 文件目录相关
//【1】创建文件
file, err := os.Create("file.txt")//【2】创建目录
err := os.Mkdir("test2", os.ModePerm) //单个目录
err := os.MkdirAll("/a/b/c", os.ModePerm) //层级目录//【3】删除文件或目录
err := os.Remove("test.txt")
err = os.RemoveAll("test2")//【4】获取工作目录
dir, err := os.Getwd()//【5】修改工作目录
err := os.Chdir("d:/")//【6】读写文件
bytes, err := os.ReadFile("test2.txt")
os.WriteFile("test2.txt", []byte("hello go"), os.ModePerm)//【7】文件重命名
err := os.Rename("test2.txt", "test3.txt")//【8】读取目录列表
2 File文件读操作
//【1】打开文件
file, err := os.Open("a.txt) //如果a.txt不存在,则报错[打开的文件为只读]
file, err := os.OpenFile("a1.txt", os.O_RDWR|os.OCREATE, 755) //如果不存在则创建//【2】循环读取文件
f, _ := os.Open("a.txt")
for {buf := make([]byte, 3)n, err := f.Read(buf)//读到文件末尾if err == io.EOF {break}fmt.Printf("n:%v\n", n)fmt.Printf("string(buf):%v\n", string(buf))
}
f.Close()//【3】从指定位置读取
方法一:
f, _ := os.Open("a.txt")
buf := make([]byte, 4)
//从offset为3的位置开始读取
n, _ := f.ReadAt(buf, 3)
fmt.Println("n=", n)
fmt.Println("string(buf)=", string(buf))方法二:
file, _ := os.Open("test/a.txt")
defer file.Close()
//从偏移量为3的位置开始读取
file.Seek(3, 0)
buf := make([]byte, 10)
n, _ := file.Read(buf)
fmt.Println("n=", n)
fmt.Println("string(buf)=", string(buf))//【4】读取目录
dir, _ := os.ReadDir("a/")
for _, v := range dir {fmt.Printf("v.IsDir():%v\n", v.IsDir())fmt.Printf("v.Name():%v\n", v.Name())
}
3 File文件写操作
//os.O_TRUNC //覆盖之前的
//os.O_APPEND //追加写//【1】写入字节
file, _ := os.OpenFile("a.txt", os.O_RDWR|os.O_APPEND, 0775)
file.Write([]byte("hello golang"))
file.Close()//【2】写入字符串
file.WriteString("hello java")//【3】从指定位置写
//从file的offset为3的位置开始写
file.WriteAt([]byte("aaa"), 3)
4 进程相关操作
package mainimport ("fmt""os""time"
)func main() {//获取当前正在运行的进程idfmt.Printf("os.Getpid():%v\n", os.Getpid())//父idfmt.Printf("os.Getppid():%v\n", os.Getppid())//设置新进程的属性attr := &os.ProcAttr{//files指定新进程继承的活动文件对象//前三个分别为:标准输入、标准输出、标准错误输出Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},//新进程的环境变量Env: os.Environ(),}//开始一个新进程p, err := os.StartProcess("D:\\Download\\EditPlus\\EditPlus.exe", []string{"D:\\Download\\EditPlus\\EditPlus.exe", "E:\\processDemo.txt"}, attr)if err != nil {fmt.Println(err)}fmt.Println(p)fmt.Println("进程ID:", p.Pid)//通过进程id查找进程p2, _ := os.FindProcess(p.Pid)fmt.Println(p2)//等待5s,执行函数time.AfterFunc(time.Second*5, func() {//向进程p发送退出信号【杀死进程】p.Signal(os.Kill)})//等待进程p的退出,返回进程状态ps, _ := p.Wait()fmt.Println(ps.String())}

运行结果:

os.Getpid()19828
os.Getppid():22092
&{20312 372 0 {{0 0} 0 0 0 0}}
进程ID: 20312                
&{20312 352 0 {{0 0} 0 0 0 0}}
exit status 1
5 环境变量
//【1】获取和设置
//获取所有环境变量
s := os.Environ()
fmt.Printf("s:%v\n", s)
//获取某个环境变量
s2 := os.Getenv("GOPATH")
fmt.Printf("s2:%v\n", s2)
//获取不存在的环境变量[获取的结果为空,不会报错;如果要看环境变量是否存在;推荐使用LookupEnv]
s2 = os.Getenv("lalala")
//设置环境变量
os.Setenv("env1", "env1Value")//【2】查找
s3, b := os.LookupEnv("env1")
if b {fmt.Println("s3=", s3)
}//【3】清空环境变量,慎用!!!!
//os.Clearenv()

②io包、ioutil包、bufio

1 io包

哪些类型实现了Reader和Writer接口:

  • os.File
  • string.Reader
  • bufio.Reader
  • bytes.Buffer
  • bytes.Reader
  • compress/gzip.Reader/Writer
  • encoding/csv.Reader/Writer

在这里插入图片描述
简单测试案例:

func main() {r := strings.NewReader("hello world")//os.Stdout返回的也是一个Writer_, err := io.Copy(os.Stdout, r)if err != nil {fmt.Println(err)}//控制台输出:hello world
}
2 iotuil包
名称作用
ReadAll读取数据,返回读到的字节
ReadDir读取一个目录,返回目录入口数组[]os.FileInfo
ReadFile读取一个文件,返回文件内容(字节slice)
WriteFile根据文件路径,写入字节slice
TempDir在一个目录中创建指定前缀名的临时目录,返回新临时目录的路径
TempFile在一个目录中创建指定前缀名的临时文件,返回os.File

简单案例:

func main() {fi, _ := ioutil.ReadDir(".")for _, v := range fi {if v.IsDir() {fmt.Println("dir=", v.Name())} else {fmt.Println("file=", v.Name())}}
}
3 bufio包

涉及到其他语言,如:中文,直接使用rune转换即可

Reader操作:

func main() {file, _ := os.Open("test/test1/test2/test.csv")defer file.Close()br := bufio.NewReader(file)buffer := make([]byte, 10)for {n, err := br.Read(buffer)if err == io.EOF {break} else {fmt.Println("value=", string(buffer[:n]))}}
}
  • Writer操作
func main() {//写入文件的话,需要使用OpenFile,并设置对应写入权限file, _ := os.OpenFile("test/test1/test2/test.csv", os.O_RDWR, 0777)defer file.Close()w := bufio.NewWriter(file)w.Write([]byte("hahaha~~~"))w.Flush()
}
  • Scanner
func main() {s := strings.NewReader("ABC DEF KIS")bs := bufio.NewScanner(s)//以空格作为分隔符bs.Split(bufio.ScanWords)for bs.Scan() {fmt.Println(bs.Text())}
}

③path/filepath

  1. Rel
func Rel(basepath, targpath string) (string, error)

该函数以basepath为基准,返回targpath相对于basepath的相对路径,也就是说如果basepath为/a,targpath为/a/b/c,那么则会返回/b/c,如果两个参数有一个为绝对路径,一个为相对路径,则会返回错误

  1. Join
func Join(elem ...string) string

Join 函数将多个路径进行连接,并且进行Clean操作,然后返回

④archive/zip包

  1. 压缩
package mainimport ("archive/zip""fmt""io""os""path/filepath""strings"
)func compressionDir(baseDir string) (string, error) {zipFileName := baseDir + ".zip"// 创建一个新的 zip 文件zipFile, err := os.Create(zipFileName)if err != nil {return "", err}defer zipFile.Close()// 创建一个 zip.WriterzipWriter := zip.NewWriter(zipFile)defer zipWriter.Close()// 遍历目录下的所有文件和子目录err = filepath.Walk(baseDir, func(path string, info os.FileInfo, err error) error {if err != nil {return err}// 创建一个 zip 文件中的文件或目录relativePath := strings.TrimPrefix(path, baseDir)zipPath := strings.TrimLeft(filepath.Join("/", relativePath), "/")// 如果是目录或空目录,则在 zip 文件中创建一个目录if info.IsDir() || isEmptyDir(path) {_, err := zipWriter.Create(zipPath + "/")if err != nil {return err}} else {// 如果是文件,则创建一个 zip 文件中的文件zipFile, err := zipWriter.Create(zipPath)if err != nil {return err}// 打开原始文件file, err := os.Open(path)if err != nil {return err}defer file.Close()// 将原始文件的内容拷贝到 zip 文件中_, err = io.Copy(zipFile, file)if err != nil {return err}}return nil})if err != nil {return "", err}return zipFileName, nil
}// 判断目录是否为空目录
func isEmptyDir(dirPath string) bool {dir, err := os.Open(dirPath)if err != nil {return false}defer dir.Close()_, err = dir.Readdirnames(1)return err == io.EOF
}func main() {// 调用压缩函数zipFile, err := compressionDir("E:\\Go\\GoPro\\src\\go_code\\gouitest\\test")if err != nil {fmt.Println("压缩目录失败:", err)return}fmt.Println("目录压缩成功,压缩文件:", zipFile)
}

运行结果:
在这里插入图片描述

  1. 解压

2.2 并发

①Mutex

- 互斥锁
- 不可重入锁

sync.Mutex是Go语言中的一个同步原语,用于实现互斥锁。它可以保证在同一个时刻只有一个goroutine可以访问共享资源,从而避免了竞态条件和数据竞争。

sync.Mutex的用法非常简单,我们可以通过调用Lock方法来获取锁,在获取锁之后访问共享资源,然后通过调用Unlock方法释放锁。如果在获取锁之前锁已经被其他goroutine获取了,那么当前goroutine将会被阻塞,直到被释放为止。

现在我们通过sync.Mutex来保证并发访问资源安全

  • 如果不使用sync.Mutex
package mainimport ("fmt""sync""time"
)var (i  = 100wg sync.WaitGroup
)func main() {for i := 0; i < 50; i++ {wg.Add(1)go add()wg.Add(1)go sub()}//通过waitGroup等待所有任务跑完wg.Wait()fmt.Println("main....i=", i)
}func add() {time.Sleep(time.Millisecond * 10)defer wg.Done()i += 1fmt.Println("i++, i=", i)
}func sub() {time.Sleep(time.Millisecond * 2)defer wg.Done()i -= 1fmt.Println("i--, i=", i)
}

在这里插入图片描述

正确结果应该是i=100,因此可以知道结果是错误的,因为我们没有控制并发。接下来,我们通过sync.Mutex互斥锁来控制并发

  • 使用sync.Mutex控制并发
package mainimport ("fmt""sync""time"
)var (i    = 100wg   sync.WaitGrouplock sync.Mutex
)func main() {for i := 0; i < 50; i++ {wg.Add(1)go add()wg.Add(1)go sub()}//通过waitGroup等待所有任务跑完wg.Wait()fmt.Println("main....i=", i)
}func add() {defer wg.Done()//访问共享资源的时候加锁lock.Lock()time.Sleep(time.Millisecond * 10)i += 1fmt.Println("i++, i=", i)lock.Unlock()
}func sub() {defer wg.Done()lock.Lock()time.Sleep(time.Millisecond * 2)i -= 1fmt.Println("i--, i=", i)lock.Unlock()
}

现在,不管我们怎么运行就都可以获取到正确的结果了

对于sync.Mutex能否unlock多次的问题,答案是不能。如果在没有获取锁的情况下调用unlock方法,或者在已经释放锁的情况下再次调用unlock方法,都会导致运行时错误。因此,在使用sync.Mutex时,需要确保每次获取锁之后都能及时释放锁,避免出现这种问题。

package mainimport "sync"var (mutex sync.Mutex
)func main() {/*【1】加锁一次,解锁两次=》报错mutex.Lock()mutex.Unlock()mutex.Unlock()//fatal error: sync: unlock of unlocked mutex*//*【2】加锁两次,解锁一次=》报错mutex.Lock()mutex.Lock()mutex.Unlock()//fatal error: all goroutines are asleep - deadlock!*//*【3】先连续加锁两次,然后连续解锁两次=》报错 `sync.Mutex是不可重入锁`mutex.Lock()mutex.Lock()mutex.Unlock()mutex.Unlock()//fatal error: all goroutines are asleep - deadlock!*/// 【4】可行,只有先加锁,然后释放锁之后才能继续加锁mutex.Lock()mutex.Unlock()mutex.Lock()mutex.Unlock()
}
  • 拓展:可重入锁
  • 当一个线程获取锁时,如果没有其它线程拥有这个锁,那么,这个线程就成功获取到这个锁。之后,如果其它线程再请求这个锁,就会处于阻塞等待的状态。但是,如果拥有这把锁的线程再请求这把锁的话,不会阻塞,而是成功返回,所以叫可重入锁。只要你拥有这把锁,你可以可着劲儿地调用,比如通过递归实现一些算法,调用者不会阻塞或者死锁。
  • Mutex 不是可重入的锁。Mutex 的实现中没有记录哪个 goroutine 拥有这把锁。理论上,任何 goroutine 都可以随意地 Unlock 这把锁,所以没办法计算重入条件。
package mainimport ("fmt""sync""time"
)var (mutex sync.Mutexcount int
)func main() {go func() {mutex.Lock()count++}()time.Sleep(time.Second * 1)mutex.Unlock()fmt.Println("main.....")fmt.Println("count=", count)//main.....//count= 1
}

可以看到我们通过一个协程(goroutine)加的锁,但是可以在main方法中释放(main方法也可以看做一个特殊的goroutine)中释放,因此可以知道sync.Mutex不会记录哪个goroutine持有这把锁。

②WaitGroup

两个协程之间相互等待,如果没有waitGroup,可能协程里面的任务还没有完成,主程序就退出了,导致所有的协程也退出

package mainimport ("fmt""sync"
)var (wg sync.WaitGroup
)func hello(i int) {defer wg.Done() //wd.Add(-1)fmt.Println("hello", i)//wg.Done() //wd.Add(-1)
}func main() {for i := 0; i < 10; i++ {wg.Add(1)go hello(i)}//等待所有协程中的任务全部完成wg.Wait()fmt.Println("main====")
}

③Timer、Ticker

1 Timer

定时器,timer.C本质上是一个管道

package mainimport ("fmt""time"
)func main() {//[1]time.NewTimer 等待两秒钟//timer := time.NewTimer(time.Second * 2)//t := <-timer.C//fmt.Println(t)//[2]time.After(time.Second * 2) 等待两秒钟//time.After(time.Second * 2)//[3]timer := time.NewTimer(time.Second * 5)timer.Reset(time.Second * 6) //重新设置定时器时间//<-timer.Ctimer.Stop() //停止定时器(如果没有timer.C 那么就不会阻塞暂停)fmt.Println("--")
}
2 Ticker

Timer只执行一次,Ticker可以周期的执行

案例一:

package mainimport ("fmt""time"
)func main() {fmt.Println("start...")//定时器,每隔5秒执行ticker := time.NewTicker(time.Second * 5)for _ = range ticker.C {fmt.Println("middle...")}fmt.Println("end...") //永远不会执行到,因为ticker.C是可以定时器,for中没有break
}

案例二:

package mainimport ("fmt""time"
)func main() {//定时器,每隔一秒执行ticker := time.NewTicker(time.Second)chanInt := make(chan int)go func() {//ticker.C触发for _ = range ticker.C {select {case chanInt <- 1:case chanInt <- 2:case chanInt <- 3:}}}()sum := 0for v := range chanInt {fmt.Println("接收到:", v)sum += vif sum >= 10 {break}}
}

运行结果:

接收到: 2
接收到: 2
接收到: 1
接收到: 3
接收到: 1
接收到: 2

④runtime

让出CPU时间片,重新安排任务

  • runtime.Gosched():让出时间片,让其他协程来执行
  • runtime.Goexit():直接退出当前协程
  • runtime.NumCPU():获取当前系统的cpu核心数
  • runtime.GOMAXPROCS(num int):设置当前系统cpu核心数(默认为当前系统最大cpu核心数)

⑤原子变量

在并发操作资源时,我们可以通过两种方式来保证数据正确:

  1. 加锁
  2. 原子操作

atomic常见操作有:

  • 增减
  • 载入 read
  • 比较并交换 cas
  • 交换
  • 存储 write
package mainimport ("fmt""sync/atomic"
)func main() {//test_add_sub()//test_load_store()test_cas()//除了cas比较并交换,atomic还有比较暴力的`直接交换`,但是这种用法比较少
}func test_add_sub() {var i int32 = 100atomic.AddInt32(&i, 1)fmt.Println("i=", i)atomic.AddInt32(&i, -1)fmt.Println("i=", i)
}func test_load_store() {var j int64 = 200val := atomic.LoadInt64(&j) //readfmt.Println("val=", val)atomic.StoreInt64(&j, -100) //writefmt.Println("j=", j)
}func test_cas() {var k int32 = 8f := atomic.CompareAndSwapInt32(&k, 8, 100)fmt.Println("flag=", f)fmt.Println("k=", k)
}

2.3 操作数据库

以MySQL为例,首先需要安装MySQL8版本,然后导入go操作MySQL的依赖库

  • 地址:https://pkg.go.dev/github.com/go-sql-driver/mysql#section-readme
  • 也可以直接通过在终端执行go get下载
    go get -u github.com/go-sql-driver/mysql
package mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql"
)var db *sql.DBfunc initDB() (err error) {dataSource := "root:200151@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=true"//不会校验账号密码是否正确//注意!!不要使用:=,因为我们这里是给全局变量赋值,然后在main函数中使用全局变量dbdb, err = sql.Open("mysql", dataSource)if err != nil {return err}//尝试与数据库建立连接(校验dataSource是否正确)err = db.Ping()if err != nil {return err}return nil
}func main() {err := initDB()if err != nil {fmt.Println("initDB fail, err=", err)} else {fmt.Println("连接成功!")}//以插入操作为例【其他操作类似】s := "insert into account (name, money, question) values (?, ?, ?)"result, err := db.Exec(s, "ziyi", 300.0, "what")if err != nil {fmt.Println("insert fail err=", err)} else {//insert success  {0xc000102000 0xc00009ea00}fmt.Println("insert success ", result)}
}

教程:https://duoke360.com/tutorial/golang


http://www.ppmy.cn/news/413964.html

相关文章

ros中使用yolov5(1)

编辑器 提示&#xff1a;环境ubuntu18.04 anaconda下python3.8torch1.9 ros中使用yolov5 前言一、先将yolov5封装二、步骤1.创建一个新的脚本2.修改detect#修改yolov5/utils/dataset.py再次修改detect.py结果 前言 提示&#xff1a;参考的博客&#xff1a; 封装yolov5: https…

TRX和TRC10交易三种合约

在TRON中检测TRX或TRC10事务涉及3种类型的合约&#xff1a; TransferConract&#xff08;系统合同类型&#xff09;TransferAssetContract&#xff08;系统合同类型&#xff09;TriggerSmartContract&#xff08;智能合约类型&#xff09; Transaction&#xff0c;Transactio…

3: 00npm start的时候报了奇怪的错误A42E6F node::MakeCallback+4719 4: 00007FF75AA170F0 node::DecodeWrite+13120

报了一堆奇怪的错 node_contextify.cc:635: Assertion args[1]->IsString()’ failed. 1: 00007FF75AA6832A v8::internal::GCIdleTimeHandler::GCIdleTimeHandler4506 2: 00007FF75AA42DB6 node::MakeCallback4534 3: 00007FF75AA42E6F node::MakeCallback4719 4: 00007FF7…

激光SLAM多层料箱机器人HEGERLS A42M SLAM|灵活匹配多种作业高度|支持多尺寸纸箱/料箱混合拣选

作为厢式仓储物流智能机器人的领跑者&#xff0c;海格里斯HEGERLS致力于通过机器人技术和人工智能算法&#xff0c;提供高效、智能、柔性、定制化的仓储自动化解决方案&#xff0c;为每个工厂和物流仓库创造价值。海格里斯HEGERLS专注于箱式仓储机器人系统研发设计&#xff0c;…

动态调宽箱式机器人HEGERLS A42-FW|仓储存储密度再提高60%|刷新高密度的“天花板”

在智能搬运、拣选、分拣等仓储物流的关键环节&#xff0c;满足多重需求的“货箱到人”的箱式仓储机器人脱颖而出。由于箱式仓储机器人是拣选搬运货箱而不是货架&#xff0c;所以可以把货架之间的巷道设置得更窄&#xff0c;存储密度更高&#xff0c;更节省空间&#xff0c;进而…

第10篇:强化学习Q-learning求解迷宫问题 代码实现

你好&#xff0c;我是郭震&#xff08;zhenguo&#xff09; 今天重新发布强化学习第10篇&#xff1a;强化学习Q-learning求解迷宫问题 代码实现 我想对此篇做一些更加详细的解释。 1 创建地图 创建迷宫地图&#xff0c;包括墙网格&#xff0c;走到墙网格就是负奖励。 注意&…

总结712

今天看了高数换元法&#xff0c;分部积分&#xff0c;有理函数的积分的内容。之后写了一篇长篇阅读作文。之后就听到能回家的消息了。也不是很清楚到底是什么状况&#xff0c;但这学期过得确实快。走的时候先去图书馆借一波书先&#xff0c;暑假就是遗憾没能借些计算机类的书籍…

712. 两个字符串的最小ASCII删除和(c++)

712. 两个字符串的最小ASCII删除和(c) 中等 相关算法标签&#xff1a;LCS、动态规划 代码描述&#xff1a;使用迭代法&#xff08;从前到后&#xff0c;与从后到前&#xff09;、递归方法实现 /* 问题&#xff1a; 1.边界条件选取2.memo[i][j] min(memo[i-1][j]s1[i-1] , memo…