11-设计模式:Go常用设计模式概述

 

设计模式是啥呢?简单来说,就是将软件开发中需要重复性解决的编码场景,按最佳实践的方式抽象成一个模型,模型描述的解决方法就是设计模式。使用设计模式,可以使代码更易于理解,保证代码的重用性和可靠性。

在软件领域,GoF(四人帮,全拼 Gang of Four)首次系统化提出了3大类、共25种可复用的经典设计方案,来解决常见的软件设计问题,为可复用软件设计奠定了一定的理论基础。

从总体上说,这些设计模式可以分为创建型模式、结构型模式、行为型模式3大类,用来完成不同的场景。这一讲,我会介绍几个在Go项目开发中比较常用的设计模式,帮助你用更加简单快捷的方法应对不同的编码场景。其中,简单工厂模式、抽象工厂模式和工厂方法模式都属于工厂模式,我会把它们放在一起讲解。

创建型模式

首先来看创建型模式(Creational Patterns),它提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。

这种类型的设计模式里,单例模式和工厂模式(具体包括简单工厂模式、抽象工厂模式和工厂方法模式三种)在Go项目开发中比较常用。我们先来看单例模式。

单例模式

单例模式(Singleton Pattern),是最简单的一个模式。在Go中,单例模式指的是全局只有一个实例,并且它负责创建自己的对象。单例模式不仅有利于减少内存开支,还有减少系统性能开销、防止多个实例产生冲突等优点

因为单例模式保证了实例的全局唯一性,而且只被初始化一次,所以比较适合全局共享一个实例,且只需要被初始化一次的场景,例如数据库实例、全局配置、全局任务池等。

单例模式又分为饿汉方式懒汉方式。饿汉方式指全局的单例实例在包被加载时创建,而懒汉方式指全局的单例实例在第一次被使用时创建。 

接下来,我就来分别介绍下这两种方式。先来看饿汉方式

下面是一个饿汉方式的单例模式代码:

package singletontype singleton struct {
}var ins *singleton = &singleton{}func GetInsOr() *singleton {return ins
}

你需要注意,因为实例是在包被导入时初始化的,所以如果初始化耗时,会导致程序加载时间比较长。

懒汉方式是开源项目中使用最多的,但它的缺点是非并发安全,在实际使用时需要加锁。以下是懒汉方式不加锁的一个实现:

package singletontype singleton struct {
}var ins *singletonfunc GetInsOr() *singleton {if ins == nil {ins = &singleton{}}return ins
}

可以看到,在创建ins时,如果 ins==nil,就会再创建一个ins实例,这时候单例就会有多个实例。

为了解决懒汉方式非并发安全的问题,需要对实例进行加锁,下面是带检查锁的一个实现:

package singletonimport "sync"type singleton struct {
}var ins *singleton
var mu sync.Mutexfunc GetIns() *singleton {if ins == nil {mu.Lock()defer mu.Unlock()ins = &singleton{}}return ins
}

上述代码只有在创建时才会加锁,既提高了代码效率,又保证了并发安全。

除了饿汉方式和懒汉方式,在Go开发中,还有一种更优雅的实现方式,我建议你采用这种方式,代码如下:

package singletonimport ("sync"
)type singleton struct {
}var ins *singleton
var once sync.Oncefunc GetInsOr() *singleton {once.Do(func() {ins = &singleton{}})return ins
}

使用once.Do可以确保ins实例全局只被创建一次,once.Do函数还可以确保当同时有多个创建动作时,只有一个创建动作在被执行。

另外,IAM应用中大量使用了单例模式,如果你想了解更多单例模式的使用方式,可以直接查看IAM项目代码。IAM中单例模式有 GetStoreInsOr、GetEtcdFactoryOr、GetMySQLFactoryOr、GetCacheInsOr等。

工厂模式

工厂模式(Factory Pattern)是面向对象编程中的常用模式。在Go项目开发中,你可以通过使用多种不同的工厂模式,来使代码更简洁明了。Go中的结构体,可以理解为面向对象编程中的类,例如 Person结构体(类)实现了Greet方法。

type Person struct {Name stringAge int
}func (p Person) Greet() {fmt.Printf("Hi! My name is %s", p.Name)
}

有了Person“类”,就可以创建Person实例。我们可以通过简单工厂模式、抽象工厂模式、工厂方法模式这三种方式,来创建一个Person实例。

这三种工厂模式中,简单工厂模式是最常用、最简单的。它就是一个接受一些参数,然后返回Person实例的函数:

type Person struct {Name stringAge int
}func (p Person) Greet() {fmt.Printf("Hi! My name is %s", p.Name)
}func NewPerson(name string, age int) *Person {return Person{Name: name,Age: age}
}

p:=&Person {}这种创建实例的方式相比,简单工厂模式可以确保我们创建的实例具有需要的参数,进而保证实例的方法可以按预期执行。例如,通过NewPerson创建Person实例时,可以确保实例的name和age属性被设置。

再来看抽象工厂模式,它和简单工厂模式的唯一区别,就是它返回的是接口而不是结构体。

通过返回接口,可以在你不公开内部实现的情况下,让调用者使用你提供的各种功能,例如:

type Person interface {Greet()
}type person struct {name stringage int
}func (p person) Greet() {fmt.Printf("Hi! My name is %s", p.name)
}// Here, NewPerson returns an interface, and not the person struct itself
func NewPerson(name string, age int) Person {return person{name: name,age: age}
}

上面这个代码,定义了一个不可导出的结构体person,在通过NewPerson创建实例的时候返回的是接口,而不是结构体。

通过返回接口,我们还可以实现多个工厂函数,来返回不同的接口实现,例如:

// We define a Doer interface, that has the method signature
// of the `http.Client` structs `Do` method
type Doer interface {Do(req *http.Request) (*http.Response, error)
}// This gives us a regular HTTP client from the `net/http` package
func NewHTTPClient() Doer {return &http.Client{}
}type mockHTTPClient struct{}func (*mockHTTPClient) Do(req *http.Request) (*http.Response, error) {// The `NewRecorder` method of the httptest package gives us// a new mock request generatorres := httptest.NewRecorder()// calling the `Result` method gives us// the default empty *http.Response objectreturn res.Result(), nil
}// This gives us a mock HTTP client, which returns
// an empty response for any request sent to it
func NewMockHTTPClient() Doer {return &mockHTTPClient{}
}

NewHTTPClientNewMockHTTPClient都返回了同一个接口类型Doer,这使得二者可以互换使用。当你想测试一段调用了Doer接口Do方法的代码时,这一点特别有用。因为你可以使用一个Mock的HTTP客户端,从而避免了调用真实外部接口可能带来的失败。

来看个例子,假设我们想测试下面这段代码:

func QueryUser(doer Doer) error {req, err := http.NewRequest("Get", "http://iam.api.marmotedu.com:8080/v1/secrets", nil)if err != nil {return req}_, err := doer.Do(req)if err != nil {return err}return nil
}

其测试用例为:

func TestQueryUser(t *testing.T) {doer := NewMockHTTPClient()if err := QueryUser(doer); err != nil {t.Errorf("QueryUser failed, err: %v", err)}
}

另外,在使用简单工厂模式和抽象工厂模式返回实例对象时,都可以返回指针。例如,简单工厂模式可以这样返回实例对象:

return &Person{Name: name,Age: age
}

抽象工厂模式可以这样返回实例对象:

return &person{name: name,age: age
}

在实际开发中,我建议返回非指针的实例,因为我们主要是想通过创建实例,调用其提供的方法,而不是对实例做更改。如果需要对实例做更改,可以实现SetXXX的方法。通过返回非指针的实例,可以确保实例的属性,避免属性被意外/任意修改。

简单工厂模式中,依赖于唯一的工厂对象,如果我们需要实例化一个产品,就要向工厂中传入一个参数,获取对应对象;如果要增加一种产品,就要在工厂中修改创建产品的函数。这会导致耦合性过高,这时我们就可以使用工厂方法模式

工厂方法模式中,依赖工厂接口,我们可以通过实现工厂接口来创建多种工厂,将对象创建从由一个对象负责所有具体类的实例化,变成由一群子类来负责对具体类的实例化,从而将过程解耦。

下面是工厂方法模式的一个代码实现:

type Person struct {name stringage int
}func NewPersonFactory(age int) func(name string) Person {return func(name string) Person {return Person{name: name,age: age,}}
}

然后,我们可以使用此功能来创建具有默认年龄的工厂:

newBaby := NewPersonFactory(1)
baby := newBaby("john")newTeenager := NewPersonFactory(16)
teen := newTeenager("jill")

结构型模式

我已经向你介绍了单例模式、工厂模式这两种创建型模式,接下来我们来看结构型模式(Structural Patterns),它的特点是关注类和对象的组合。这一类型里,我想详细讲讲策略模式和模板模式。

策略模式

策略模式(Strategy Pattern)定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。

在什么时候,我们需要用到策略模式呢?

 

在实际应用中,随着功能和体验的不断增长,我们需要经常添加/修改策略,这样就需要不断修改已有代码,不仅会让这个函数越来越难维护,还可能因为修改带来一些bug。所以为了解耦,需要使用策略模式,定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法(即策略)。

下面是一个实现策略模式的代码:

package strategy// 策略模式// 定义一个策略类
type IStrategy interface {do(int, int) int
}// 策略实现:加
type add struct{}func (*add) do(a, b int) int {return a + b
}// 策略实现:减
type reduce struct{}func (*reduce) do(a, b int) int {return a - b
}// 具体策略的执行者
type Operator struct {strategy IStrategy
}// 设置策略
func (operator *Operator) setStrategy(strategy IStrategy) {operator.strategy = strategy
}// 调用策略中的方法
func (operator *Operator) calculate(a, b int) int {return operator.strategy.do(a, b)
}

在上述代码中,我们定义了策略接口 IStrategy,还定义了 add 和 reduce 两种策略。最后定义了一个策略执行者,可以设置不同的策略,并执行,例如:

func TestStrategy(t *testing.T) {operator := Operator{}operator.setStrategy(&add{})result := operator.calculate(1, 2)fmt.Println("add:", result)operator.setStrategy(&reduce{})result = operator.calculate(2, 1)fmt.Println("reduce:", result)
}

可以看到,我们可以随意更换策略,而不影响Operator的所有实现。

模版模式

模版模式 (Template Pattern)定义一个操作中算法的骨架,而将一些步骤延迟到子类中。这种方法让子类在不改变一个算法结构的情况下,就能重新定义该算法的某些特定步骤。

简单来说,模板模式就是将一个类中能够公共使用的方法放置在抽象类中实现,将不能公共使用的方法作为抽象方法,强制子类去实现,这样就做到了将一个类作为一个模板,让开发者去填充需要填充的地方。

以下是模板模式的一个实现:

package templateimport "fmt"type Cooker interface {fire()cooke()outfire()
}// 类似于一个抽象类
type CookMenu struct {
}func (CookMenu) fire() {fmt.Println("开火")
}// 做菜,交给具体的子类实现
func (CookMenu) cooke() {
}func (CookMenu) outfire() {fmt.Println("关火")
}// 封装具体步骤
func doCook(cook Cooker) {cook.fire()cook.cooke()cook.outfire()
}type XiHongShi struct {CookMenu
}func (*XiHongShi) cooke() {fmt.Println("做西红柿")
}type ChaoJiDan struct {CookMenu
}func (ChaoJiDan) cooke() {fmt.Println("做炒鸡蛋")
}

这里来看下测试用例:

func TestTemplate(t *testing.T) {// 做西红柿xihongshi := &XiHongShi{}doCook(xihongshi)fmt.Println("\n=====> 做另外一道菜")// 做炒鸡蛋chaojidan := &ChaoJiDan{}doCook(chaojidan)}

行为型模式

然后,让我们来看最后一个类别,行为型模式(Behavioral Patterns),它的特点是关注对象之间的通信。这一类别的设计模式中,我们会讲到代理模式和选项模式。

代理模式

代理模式 (Proxy Pattern),可以为另一个对象提供一个替身或者占位符,以控制对这个对象的访问。

以下代码是一个代理模式的实现:

package proxyimport "fmt"type Seller interface {sell(name string)
}// 火车站
type Station struct {stock int //库存
}func (station *Station) sell(name string) {if station.stock > 0 {station.stock--fmt.Printf("代理点中:%s买了一张票,剩余:%d \n", name, station.stock)} else {fmt.Println("票已售空")}}// 火车代理点
type StationProxy struct {station *Station // 持有一个火车站对象
}func (proxy *StationProxy) sell(name string) {if proxy.station.stock > 0 {proxy.station.stock--fmt.Printf("代理点中:%s买了一张票,剩余:%d \n", name, proxy.station.stock)} else {fmt.Println("票已售空")}
}

上述代码中,StationProxy代理了Station,代理类中持有被代理类对象,并且和被代理类对象实现了同一接口。

选项模式

选项模式(Options Pattern)也是Go项目开发中经常使用到的模式,例如,grpc/grpc-go的NewServer函数,uber-go/zap包的New函数都用到了选项模式。使用选项模式,我们可以创建一个带有默认值的struct变量,并选择性地修改其中一些参数的值。

在Python语言中,创建一个对象时,可以给参数设置默认值,这样在不传入任何参数时,可以返回携带默认值的对象,并在需要时修改对象的属性。这种特性可以大大简化开发者创建一个对象的成本,尤其是在对象拥有众多属性时。

而在Go语言中,因为不支持给参数设置默认值,为了既能够创建带默认值的实例,又能够创建自定义参数的实例,不少开发者会通过以下两种方法来实现:

第一种方法,我们要分别开发两个用来创建实例的函数,一个可以创建带默认值的实例,一个可以定制化创建实例。

package optionsimport ("time"
)const (defaultTimeout = 10defaultCaching = false
)type Connection struct {addr    stringcache   booltimeout time.Duration
}// NewConnect creates a connection.
func NewConnect(addr string) (*Connection, error) {return &Connection{addr:    addr,cache:   defaultCaching,timeout: defaultTimeout,}, nil
}// NewConnectWithOptions creates a connection with options.
func NewConnectWithOptions(addr string, cache bool, timeout time.Duration) (*Connection, error) {return &Connection{addr:    addr,cache:   cache,timeout: timeout,}, nil
}

使用这种方式,创建同一个Connection实例,却要实现两个不同的函数,实现方式很不优雅。

另外一种方法相对优雅些。我们需要创建一个带默认值的选项,并用该选项创建实例:

package optionsimport ("time"
)const (defaultTimeout = 10defaultCaching = false
)type Connection struct {addr    stringcache   booltimeout time.Duration
}type ConnectionOptions struct {Caching boolTimeout time.Duration
}func NewDefaultOptions() *ConnectionOptions {return &ConnectionOptions{Caching: defaultCaching,Timeout: defaultTimeout,}
}// NewConnect creates a connection with options.
func NewConnect(addr string, opts *ConnectionOptions) (*Connection, error) {return &Connection{addr:    addr,cache:   opts.Caching,timeout: opts.Timeout,}, nil
}

使用这种方式,虽然只需要实现一个函数来创建实例,但是也有缺点:为了创建Connection实例,每次我们都要创建ConnectionOptions,操作起来比较麻烦。

那么有没有更优雅的解决方法呢?答案当然是有的,就是使用选项模式来创建实例。以下代码通过选项模式实现上述功能:

package optionsimport ("time"
)type Connection struct {addr    stringcache   booltimeout time.Duration
}const (defaultTimeout = 10defaultCaching = false
)type options struct {timeout time.Durationcaching bool
}// Option overrides behavior of Connect.
type Option interface {apply(*options)
}type optionFunc func(*options)func (f optionFunc) apply(o *options) {f(o)
}func WithTimeout(t time.Duration) Option {return optionFunc(func(o *options) {o.timeout = t})
}func WithCaching(cache bool) Option {return optionFunc(func(o *options) {o.caching = cache})
}// Connect creates a connection.
func Connect(addr string, opts ...Option) (*Connection, error) {options := options{timeout: defaultTimeout,caching: defaultCaching,}for _, o := range opts {o.apply(&options)}return &Connection{addr:    addr,cache:   options.caching,timeout: options.timeout,}, nil
}

在上面的代码中,首先我们定义了options结构体,它携带了timeout、caching两个属性。接下来,我们通过NewConnect创建了一个连接,NewConnect函数中先创建了一个带有默认值的options结构体变量,并通过调用

for _, o := range opts {o.apply(&options)
}

来修改所创建的options结构体变量。

需要修改的属性,是在NewConnect时,通过Option类型的选项参数传递进来的。可以通过WithXXX函数来创建Option类型的选项参数:WithTimeout、WithCaching。

Option类型的选项参数需要实现apply(*options)函数,结合WithTimeout、WithCaching函数的返回值和optionFunc的apply方法实现,可以知道o.apply(&options)其实就是把WithTimeout、WithCaching传入的参数赋值给options结构体变量,以此动态地设置options结构体变量的属性。  

不过,为了实现选项模式,我们增加了很多代码,所以在开发中,要根据实际场景选择是否使用选项模式。选项模式通常适用于以下场景:

  • 结构体参数很多,创建结构体时,我们期望创建一个携带默认值的结构体变量,并选择性修改其中一些参数的值。
  • 结构体参数经常变动,变动时我们又不想修改创建实例的函数。例如:结构体新增一个retry参数,但是又不想在NewConnect入参列表中添加retry int这样的参数声明。

总结

设计模式,是业界沉淀下来的针对特定场景的最佳解决方案。在软件领域,GoF首次系统化提出了3大类设计模式:创建型模式、结构型模式、行为型模式。

这一讲,我介绍了Go项目开发中6种常用的设计模式。每种设计模式解决某一类场景,我给你总结成了一张表格,你可以根据自己的需要进行选择。

课后练习

  1. 你当前开发的项目中,哪些可以用单例模式、工厂模式、选项模式来重新实现呢?如果有的话,我建议你试着重写下这部分代码。
  2. 除了这一讲我们学习的 6 种设计模式之外,你还用过其他的设计模式吗?欢迎你在留言区和我分享下你的经验,或者你踩过的坑

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

动态规划之方格取数

方格取数 动态规划&#xff0c;数字三角形模型 题目链接 https://www.luogu.com.cn/problem/P1004 题目描述 解法一 O ( n 4 ) O(n^4) O(n4) #include<bits/stdc.h> using namespace std; int n, i, j, l, k, x, y, s; int d[55][55], f[55][55][55][55]; int main()…

nginx界面管理工具之nginxWebUI 搭建与使用

nginx界面管理工具之nginxWebUI 搭建与使用 一、nginxWebUI 1.nginx网页配置工具 官网地址: http://www.nginxwebui.cn 源码地址&#xff1a;https://git.chihiro.org.cn/chihiro/nginxWebUI 2.功能说明 本项目可以使用WebUI配置nginx的各项功能, 包括http协议转发, tcp协议…

CQ 社区版2.10.0 | 新增 SQL 审核、全新英文版上线…

三月中旬&#xff0c;我们预告了 CloudQuery 社区版即将上线的「SQL 审核」功能。现在&#xff0c;它来了&#xff01; 本次社区版 v2.10.0&#xff0c;除了 SQL 审核功能&#xff0c;我们还在手动授权、连接分组等模块做了新功能和优化。 新增功能 新增 SQL 审核功能 支持…

Linux编译器-gcc/g++/gdb使用

Linux编译器-gcc/g/gdb使用 一、背景知识二、 gcc如何完成2.1 预处理(进行宏替换)2.2 编译&#xff08;生成汇编&#xff09;2.3 汇编&#xff08;生成机器可识别代码&#xff09;2.4 连接&#xff08;生成可执行文件或库文件&#xff09; 三、函数库四、gcc选项五、gdb5.1 背景…

产品之美10| 小小提示词(hint),便于用户交互

最近AIGC功能火热&#xff0c;有文生图和图生图两种。当用户初次接触到文生图的时候&#xff0c;会有一刻停顿&#xff1a;我该怎用输入呢&#xff1f;这时候的hint就可以发挥作用了&#xff1a; 编辑框&#xff08;EditView)里面有可爱的小女孩&#xff0c;加风格卡通。用户看…

基于单片机的二维码LCD显示控制设计

**单片机设计介绍&#xff0c;基于单片机的二维码LCD显示控制设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的二维码LCD显示控制设计是一个集硬件、软件与通信于一体的综合性项目。此设计的主要目标是实现单片机…

【动手学深度学习-pytorch】 9.4 双向循环神经网络

在序列学习中&#xff0c;我们以往假设的目标是&#xff1a; 在给定观测的情况下 &#xff08;例如&#xff0c;在时间序列的上下文中或在语言模型的上下文中&#xff09;&#xff0c; 对下一个输出进行建模。 虽然这是一个典型情景&#xff0c;但不是唯一的。 还可能发生什么其…

【详细讲解yarn的安装和使用】

&#x1f308;个人主页:程序员不想敲代码啊&#x1f308; &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家&#x1f3c6; &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提…

Java复习第十二天学习笔记(JDBC),附有道云笔记链接

【有道云笔记】十二 3.28 JDBC https://note.youdao.com/s/HsgmqRMw 一、JDBC简介 面向接口编程 在JDBC里面Java这个公司只是提供了一套接口Connection、Statement、ResultSet&#xff0c;每个数据库厂商实现了这套接口&#xff0c;例如MySql公司实现了&#xff1a;MySql驱动…

C语言: 指针讲解

为什么需要指针? &#xff08;1&#xff09;指针的使用使得不同区域的代码可以轻易的共享内存数据。当然你也可以通过数据的复制达到相同的效果&#xff0c;但是这样往往效率不太好&#xff0c;因为诸如结构体等大型数据&#xff0c;占用的字节数多&#xff0c;复制很消耗性能…

论文笔记:TALK LIKE A GRAPH: ENCODING GRAPHS FORLARGE LANGUAGE MODELS

ICLR 2024&#xff0c;reviewer评分 6666 1 intro 1.1 背景 当下LLM的限制 限制1&#xff1a;对非结构化文本的依赖 ——>模型有时会错过明显的逻辑推理或产生错误的结论限制2&#xff1a;LLMs本质上受到它们训练时间的限制&#xff0c;将“最新”信息纳入到不断变化的世…

Android熄屏/亮屏,旋转屏幕/横竖屏切换生命周期变化与activity销毁重建

Android熄屏/亮屏&#xff0c;旋转屏幕/横竖屏切换生命周期变化与activity销毁重建 1、熄屏/亮屏 熄屏后&#xff0c;Android生命周期走&#xff1a; onPause onStop 接着点亮Android手机屏幕&#xff0c;生命周期走&#xff1a; onRestart onStart onResume 2、旋转屏幕&…

云架构(二) 大使模式

Ambassador pattern &#xff08;https://learn.microsoft.com/en-us/azure/architecture/patterns/ambassador&#xff09; 简单描述 创建一个助手服务&#xff0c;这个服务代表消费服务或者应用程序发送网络请求。大使服务可以看做是与客户机同一个位置的进程外代理。 这种…

Linux——逻辑卷(LVM)管理

目录 LVM简介 LVM机制的基本概念 PV&#xff08;Physical Volume&#xff0c;物理卷&#xff09; VG&#xff08;Volume Group&#xff0c;卷组&#xff09; LV&#xff08;Logical Volume&#xff0c;逻辑卷&#xff09; PE&#xff08;Physical Extent&#xff0…

手写SpringBoot(三)之自动配置

系列文章目录 手写SpringBoot&#xff08;一&#xff09;之简易版SpringBoot 手写SpringBoot&#xff08;二&#xff09;之动态切换Servlet容器 手写SpringBoot&#xff08;三&#xff09;之自动配置 手写SpringBoot&#xff08;四&#xff09;之bean动态加载 手写SpringBoot…

GPU-CPU-ARM-X86-RISC-CUDA

CPU更适合处理复杂逻辑运算和单线程任务&#xff0c;而GPU则更适合处理大规模并行计算任务。 CPU&#xff08;中央处理器&#xff09;通常具有较少的核心数量&#xff08;一般在2到16个之间&#xff09;&#xff0c;但每个核心的性能较强&#xff0c;擅长执行复杂的运算和逻辑…

【C++】const限定符|const引用

const的引用 说const引用之前需要说明&#xff0c;这是建立在引用的前提下&#xff0c;如果是普通的拷贝赋值就基本不需要使用到const(有关权限)。 1 权限不能放大&#xff08;可平移、缩小&#xff09; 如何解释权限不能放大&#xff1f; 阅读下面的代码 可以看到&#xff1a…

sadtalker学习用于风格化音频驱动单图像说话人脸动画的真实 3D 运动系数的应用

论文出处 https://arxiv.org/abs/2211.12194 使用方法 1. 打开项目的colab链接 https://colab.research.google.com/github/Winfredy/SadTalker/blob/main/quick_demo.ipynb#scrollTofAjwGmKKYl_I 在examples/source_image文件夹中添加希望动起来说话的图片&#xff0c;这…

基于GA遗传优化的离散交通网络双层规划模型设计matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于GA遗传优化的离散交通网络双层规划模型设计.优化输出路段1和路段2的收费情况收敛过程。 2.测试软件版本以及运行结果展示 MATLAB2022a版本运行 3.核心程序…