【Go语言圣经】第七节:接口

第七章:接口

Golang 当中接口类型的独特之处在于它是满足隐式实现的。即:没必要对于给定的具体类型定义所有满足的接口类型,简单地拥有一些必要的方法即可。这种设计使得我们可以创建一个新的接口类型来满足已经存在的具体类型,却不会改变这些类型的定义。

7.1 接口约定

Golang 当中存在一种名为“接口”(interface)的抽象类型。它不会暴露它所代表的对象的内部值的结构和这个对象支持的基础操作的集合,它只会表现出它们自己的方法。也就是说,当我们看到一个接口类型时,我们不知道它具体是什么,但是我们知道可以通过它的方法来做什么

例如,我们经常使用的fmt.Printffmt.Sprintf都是通过fmt.Fprintf来进行封装的,fmt.Fprintf这个函数对于它的计算结果将会被怎么使用是不知道的:

package fmtfunc Fprintf(w io.Writer, format string, args ...interface{}) (int, error)
func Printf(format string, args ...interface{}) (int, error) {return Fprintf(os.Stdout, format, args...)
}
func Sprintf(format string, args ...interface{}) string {var buf bytes.BufferFprintf(&buf, format, args...)return buf.String()
}

对于Fprintf的第一个参数,它不是一个文件类型,而是io.Writer类型,它是一个接口,它的定义如下:

package io// Writer is the interface that wraps the basic Write method.
type Writer interface {// Write writes len(p) bytes from p to the underlying data stream.// It returns the number of bytes written from p (0 <= n <= len(p))// and any error encountered that caused the write to stop early.// Write must return a non-nil error if it returns n < len(p).// Write must not modify the slice data, even temporarily.//// Implementations must not retain p.Write(p []byte) (n int, err error)
}

io.Writer类型定义了函数Fprintf和这个函数调用者(比如PrintfSprintf)之间的约定。一方面这个约定需要调用者提供具体类型的值,比如*os.File*bytes.Buffer,这些类型都有一个Write方法。另一方面这个约定确保Fprintf接受任何满足io.Writer接口的值都可以工作。

Fprintf函数可能没有假定写入的是一个文件或一段内存,而是写入了一个可以调用Write函数的值。

由于fmt.Fprintf没有对具体操作的值做任何假设,而仅仅通过io.Writer接口的约定来保证行为,所以第一个参数可以安全地传入一个只需要满足io.Writer接口的任意具体类型的值(意思是传入的值只要具备Write方法,这个函数调用就是安全的)。一个类型可以自由地被另一个满足相同接口的类型替换,称为可替换性,这是一个面向对象的特征。

下例设计了一个新的类型来检验上述理论,其声明的*ByteCounter类型里有一个Write方法:

type ByteCounter intfunc (c *ByteCounter) Write(p []byte) (int, error) {*c += ByteCounter(len(p))return len(p), nil
}

由于*ByteCounter满足io.Writer的约定,可以把它传入Fprintf函数当中:

var c ByteCounter
c.Write([]byte("hello"))
fmt.Println(c)			// 5
c = 0
var name = "Dolly"
fmt.Fprintf(&c, "hello, %s", name)
fmt.Println(c)			// 12

7.2 接口类型

接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。

io.Writer是最广泛使用的接口之一,因为它提供了所有类型的写入 bytes 的抽象。io 包中定义了许多其它有用的接口类型。Reader 可以代表任意可以读取 bytes 的类型,Closer 可以是任意可以关闭的值,例如一个文件或网络连接:

package io
type Reader interface {Read(p []byte) (n int, err error)
}
type Closer interface {Close() error
}

可以通过接口的组合来定义新的接口类型:

type ReadWriter interface {Reader Writer
}
type ReadWriteCloser interface {ReaderWriterCloser
}

上面用到的语法与结构定义当中的内嵌类似,我们当然可以将内嵌的方法展开,但略失简洁。可以混合使用展开定义和内嵌定义。

type ReadWriter interface {Read(p []byte) (n int, err error)Write(p []byte) (n int, err error)
}
// OR
type ReadWriter interface {Read(p []byte) (n int, err error)Writer
}

7.3 实现接口的条件

一个类型如果拥有一个接口所需的所有方法,那么这个类型就实现了这个接口。例如*os.File实现了io.ReaderWriterCloserReadWriter等接口。Golang 程序员通常会简要地把一个具体的类型描述成一个特定的接口类型。

接口的指定规则很简单,正如前面反复提到的,表达一个类型属于某个接口只需要这个类型实现了这个接口:

var w io.Writer
w = os.Stdout			// OK
w = new(bytes.Buffer)	// OK
w = time.Second			// compile error: time.Duration lacks Write methodvar rwc io.ReadWriteCloser
rwc = os.Stdout			// OK
rwc = new(bytes.Buffer)	// compile error: *bytes.Buffer lacks Close method// 上述规则适用于等式右侧本身也是一个接口的情况
w = rwc					// OK
rwc = w					// compile error: io.Writer lacks Close method

接口类型封装和隐藏了具体类型和它的值,即使具体类型有其它的方法,也只有接口暴露出来的方法可以被调用:

os.Stdout.Write([]byte("hello"))	// OK
os.Stdout.Close()					// OKvar w io.Writer
w = os.Stdout
w.Write([]byte("hello"))			// Ok
w.Close()							// compile error: io.Writer lacks Close method

对于没有定义方法的接口interface{},它被称为空接口类型,它是不可或缺的。因为空接口类型对实现它的类型没有要求,所以我们可以将任意一个值赋给空接口类型(甚至,一个接受空接口类型的切片可以存储任意值):

var any interface{}
any = true
any = 12.34
any = "hello"
any = map[string]int{"one": 1}
any = new(bytes.Buffer)

对于创建的一个interface{},我们不能直接对它持有的值进行操作,因为interface{}没有任何方法。

由于接口和实现只依赖于判断两个类型的方法是否相同,所以没必要定义一个具体类型和它实现的接口之间的关系。即:有意地在文档里说明或者程序上断言这种关系偶尔是有用的,但程序不强制这么做。下面的定义在编译器断言一个*bytes.Buffer的值实现了io.Writer接口类型:

var w io.Writer = new(bytes.Buffer)

因为任意*bytes.Buffer的值,甚至 nil 通过 (*bytes.Buffer)(nil)进行显式的转换都能实现这个接口,所以我们不必为其分配一个新的变量。

一个具体的类型可能实现了很多不相关的接口。例如,下例是一个数字文化产品销售的例子,以下列出了产品具有的具体类型:

Album
Book
Movie
Magazine
Podcast
TVEpisode
Track

可以把每个抽象的特点用接口来表示:

type Artifact interface {Title() stringCreators() []stringCreated() time.Time
}

一些特性只有特定的产品才具有:

type Text interface {Pages() intWords() intPageSize() int
}
type Audio interface {Stream() (io.ReadCloser, error)RunningTime() time.DurationFormat() string // e.g., "MP3", "WAV"
}
type Video interface {Stream() (io.ReadCloser, error)RunningTime() time.DurationFormat() string // e.g., "MP4", "WMV"Resolution() (x, y int)
}

可以定义一个 Streamer 接口来代表它们之间相同的部分而不必对已经存在的类型进行改变:

type Streamer interface {Stream() (io.ReadCloser, error)RunningTime() time.DurationFormat() string
}

7.4 flag.Value 接口

使用flag.Duration可以完成时间周期的标记,这个特性被构建到了 flag 包中。如果我们想要为自己的数据定义新的标记符号,只需要定义一个实现flag.Value接口的类型:

package flagtype Value interface {String() stringSet(string) error
}

String 方法格式化标记的值用在命令行帮助消息中;这样每一个 flag.Value 也是一个 fmt.StringerSet方法解析它的字符串参数并且更新标记变量的值。

下例定义了一个允许通过摄氏度或华氏度变换的形式指定温度的 celsiusFlag 类型。由于 celsiusFlag 内嵌了 Celsius 类型,因此它本身已经具有 String 方法了

type celsiusFlag struct{ Celsius }func (f *celsisuFlag) Set(s string) error {var unit stringvar value float64fmt.Sscanf(s, "%f%s", &value, &unit) // no error check neededswitch unit {case "C", "°C":f.Celsius = Celsius(value)return nilcase "F", "°F":f.Celsius = FToC(Fahrenheit(value))return nil}return fmt.Errorf("invalid temperature %q", s)
}

下面的 CelsiusFlag 函数将所有逻辑封装在一起:

// CelsiusFlag defines a Celsius flag with the specified name,
// default value, and usage, and returns the address of the flag variable.
// The flag argument must have a quantity and a unit, e.g., "100C".
func CelsiusFlag(name string, value Celsius, usage string) *Celsius {f := celsiusFlag{value}flag.CommandLine.Var(&f, name, usage)return &f.Celsius
}

现在我们可以在程序中使用新的标记:

var temp = tempconv.CelsiusFlag("temp", 20.0, "the temperature")func main() {flag.Parse()fmt.Println(*temp)
}

7.5 接口值

接口值由两部分组成,分别是一个具体的类型和那个类型的值。它们被称为接口的动态类型和动态值。对于 Golang 这种静态语言,类型是编译器的概念,因此一个类型不是一个值。

下面的四个语句中,变量 w 得到了 3 个值:

var w io.Writer // 此时默认为零值 nil
w = os.Stdout
w = new(bytes.Buffer)
w = nil

第二个语句将*os.File类型的值赋给了变量w:

w = os.Stdout

这个赋值过程调用了一个具体类型到接口类型的隐式转换,它和显式地使用io.Writer(os.Stdout)是等价的。这种转换不管是显式还是隐式的,都会刻画出操作到的类型和值。接口值的动态类型(type)被设为*os.File指针的类型描述符,它的动态值(value)持有os.Stdout的拷贝:
在这里插入图片描述

7.5.1 警告:一个包含 nil 指针的接口不是 nil 接口

一个不包含任何值的 nil 接口值和一个刚好包含 nil 指针的接口值是不同的。

7.6 sort.Interface 接口

Golang 的 sort.Sort 函数不会对具体的序列和它的元素做任何假设。相反,它使用了一个接口类型sort.Interface来指定通用的排序算法和可能被排序到的序列类型之间的约定。这个接口的实现由序列的具体表示和它希望排序的元素决定。

一个内置的排序算法需要知道三样东西:序列的长度、表示两个元素比较的结果、一种交换两个元素的方式,这就是sort.Interface的三个方法:

package sorttype Interface interface {Len() intLess(i, j int) boolswap(i, j int)
}

为了对序列进行排序,我们需要定义一个实现了上述三个方法的类型,然后对这个类型的一个实例应用sort.Sort函数:

type StringSlice []string
func (p StringSlice) Len() int           { return len(p) }
func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p StringSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

可以通过下面这样的方式对一个 string 切片排序:

sort.Sort(StringSlice(names))

7.7 http.Handler 接口

本小节会对那些基于 http.Handler 接口的服务器 API 进行进一步的学习:

package httptype Handler interface {ServeHTTP(w ResponseWriter, r *Request)
}func ListenAndServe(address string, h Handler) error

ListenAndServe函数需要一个例如"localhost:8080"这样的服务器地址,和一个所有请求都可以分派的Handler接口实例。它会一直运行直到这个服务因为一个错误而失败,它的返回值是一个非空错误。

下例实现了一个简单的电子商务网站,它将库存清单模型化为一个命名为 database 的 map 类型,我们给这个类型一个 ServeHttp 方法,这样它就可以满足 http.Handler 接口,这个 handler 会遍历整个 map 并输出物品信息:

func main() {db := database{"shoes": 50, "socks": 5}log.Fatal(http.ListenAndServe("localhost:8000", db))
}type dollars float32func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }type database map[string]dollarsfunc (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {for item, price := range db {fmt.Fprintf(w, "%s: %s\n", item, price)}
}

目前为止这个服务器不考虑 URL,只能为每个请求列出它全部的库存清单。更真实的服务器会定义多个不同的 URL,每一个都会触发一个不同的行为:

func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {switch req.URL.Path {case "/list":for item, price := range db {fmt.Fprintf(w, "%s: %s\n", item, price)}case "/price":item := req.URL.Query().Get("item")price, ok := db[item]if !ok {w.WriteHeader(http.StatusNotFound) // 404fmt.Fprintf(w, "no such item: %q\n", item)return}fmt.Fprintf(w, "%s\n", price)default:w.WriteHeader(http.StatusNotFound) // 404fmt.Fprintf(w, "no such page: %s\n", req.URL)}
}

现在的 handler 会根据 URL 路径部分来决定执行什么逻辑。

7.8 error 接口

在学习这本书的过程中已经大量接触过 error 类型。它实际上是一个 interface 类型,这个类型有一个返回错误信息的单一方法:

type error interface {Error() string
}

创建一个 error 最简单的方法就是调用 errors.New 函数,它会根据传入的错误信息返回一个新的 error。整个 errors 包只有四行:

package errorsfunc New(text string) error { return &errorString{text} }type errorString struct { text string }func (e *errorString) Error() string { return e.text }

调用 errors.New 的机会很少,因为有一个封装好的 fmt.Errorf

package fmtimport "errors"func Errorf(format string, args ...interface{}) error {return errors.New(Sprintf(format, args...)
}

7.9 示例:表达式求值

本节会构建一个简单的算术表达式求值器。我们将使用一个接口 Expr 来表示 Golang 中的任意表达式:

type Expr interface{}	// 现在这个接口不需要方法, 稍后我们会为它定义一些

表达式的例子如下:

sqrt(A / pi)
pow(x, 3) + pow(y, 3)
(F - 32) * 5 / 9

下面是五个具体的表达式类型。Var 类型表示对一个变量的引用。literal 类型表示一个浮点型常量。unary 和 binary 表示一到两个运算对象的运算符表达式,这些操作数可以是任意的 Expr 类型。call 类型表示对一个函数的调用:

type Var stringtype literal float64type unary struct {op rune	// + / -x Expr
}type binary struct {op rune	// + / - / × / ÷x, y Expr
}type call struct {fn string	// pow / sin / sqrtargs []Expr
}

为了计算包含变量的表达式,需要一个 environment 变量将变量的名字映射为对应的值:

type Env map[Var]float64

需要每个表达式定义一个 Eval 方法,这个方法会根据 environment 变量返回表达式的值。因为每个表达式都提供这个方法,因此将它加入到 Expr 接口中。这个包只会对外公开 Expr、Env、Var 类型:

type Expr interface {Eval(env Env) float64
}

具体的 Eval 方法如下:

func (v Var) Eval(env Env) float64 {return env[v]
}func (l literal) Eval(_Env) float64 {return float64(l)
}func (u unary) Eval(env Env) float64 {switch u.op {case '+':return +u.x.Eval(env)case '-':return -u.x.Eval(env)}panic(fmt.Sprintf("unsupported unary operator: %q", u.op))
}func (b binary) Eval(env Env) float64 {switch b.op {case '+':return b.x.Eval(env) + b.y.Eval(env)case '-':return b.x.Eval(env) - b.y.Eval(env)case '*':return b.x.Eval(env) * b.y.Eval(env)case '/':return b.x.Eval(env) / b.y.Eval(env)}panic(fmt.Sprintf("unsupported binary operator: %q", b.op))
}func (c call) Eval(env Env) float64 {switch c.fn {case "pow":return math.Pow(c.args[0].Eval(env), c.args[1].Eval(env))case "sin":return math.Sin(c.args[0].Eval(env))case "sqrt":return math.Sqrt(c.args[0].Eval(env))}panic(fmt.Sprintf("unsupported function call: %s", c.fn))
}

现在我们向 Expr 接口添加一个 Check 方法,它会对一个表达式语法树检查静态错误:

type Expr interface {Eval(env Env) float64// Check reports errors in this Expr and adds its Vars to the set.Check(vars map[Var]bool) error
}

具体的 Check 方法;

func (v Var) Check(vars map[Var]bool) error {vars[v] = truereturn nil
}func (literal) Check(vars map[Var]bool) error {return nil
}func (u unary) Check(vars map[Var]bool) error {if !strings.ContainsRune("+-", u.op) {return fmt.Errorf("unexpected unary op %q", u.op)}return u.x.Check(vars)
}func (b binary) Check(vars map[Var]bool) error {if !strings.ContainsRune("+-*/", b.op) {return fmt.Errorf("unexpected binary op %q", b.op)}if err := b.x.Check(vars); err != nil {return err}return b.y.Check(vars)
}func (c call) Check(vars map[Var]bool) error {arity, ok := numParams[c.fn]if !ok {return fmt.Errorf("unknown function %q", c.fn)}if len(c.args) != arity {return fmt.Errorf("call to %s has %d args, want %d",c.fn, len(c.args), arity)}for _, arg := range c.args {if err := arg.Check(vars); err != nil {return err}}return nil
}var numParams = map[string]int{"pow": 2, "sin": 1, "sqrt": 1}

7.10 类型断言

类型断言是一个使用在接口值上的操作。语法上看起来像x.(T),此处的x表示一个接口类型而T表示一个类型。类型断言会检查它操作对象的动态类型是否和断言的类型匹配。

类型断言有两种可能。第一种,如果断言的类型T是一个具体类型,类型断言检查x的动态类型是否和T相同。如果检查成功,那么断言的结果是x的动态值,当然它的类型是T。检查失败则抛出 panic:

var w io.Writer
w = os.Stdout
f := w.(*os.File)
c := w.(*bytes.Buffer)

一个替代的方案如下,使用 ok 作为类型断言的第二个接收值,此时当类型断言失败时,不会产生 panic:

var w io.Writer = os.Stdout
f, ok := w.(*os.File)
b, ok := w.(*bytes.Buffer)

7.11 基于类型断言区别错误类型

os 包中提供了三个帮助函数来对给定的错误值表示的失败进行分类:

package osfunc IsExist(err error) bool
func IsNotExist(err error) bool
func IsPermission(err error) bool

一个更可靠的方式是使用一个专门的类型来描述结构化的错误:

package os// PathError records an error and the operation and file path that caused it.
type PathError struct {Op   stringPath stringErr  error
}func (e *PathError) Error() string {return e.Op + " " + e.Path + ": " + e.Err.Error()
}

7.12 通过类型断言询问行为

7.13 类型分支

7.14 示例:基于标记的 XML 解码

7.15 一些建议

(7.12 ~ 7.14 这部分中文版资源机翻的痕迹太明显,读起来有些晦涩,打算真正需要用到的时候再说)
接口只有当有两个或以上的具体类型必须以相同的方式进行处理时才需要。

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

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

相关文章

【网络】3.HTTP(讲解HTTP协议和写HTTP服务)

目录 1 认识URL1.1 URI的格式 2 HTTP协议2.1 请求报文2.2 响应报文 3 模拟HTTP3.1 Socket.hpp3.2 HttpServer.hpp3.2.1 start()3.2.2 ThreadRun()3.2.3 HandlerHttp&#xff08;&#xff09; 总结 1 认识URL 什么是URI&#xff1f; URI 是 Uniform Resource Identifier的缩写&…

数据分析师使用Kutools for Excel 插件

数据分析师使用Kutools for Excel 插件 Kutools for Excel 是一款功能强大的 Excel 插件&#xff0c;旨在提高 Excel 用户的工作效率&#xff0c;简化复杂的操作。它提供了超过 300 个增强功能&#xff0c;帮助用户快速完成数据管理、格式化、排序、分析等任务&#xff0c;特别…

ElasticStack简介及应用

文章目录 1.Elastic Stack 技术栈2.ES 安装2.1 准备2.2 yum单机部署2.3 集群部署 3.Kibana3.1 安装配置3.2 web访问 4.Filebeat4.1 安装4.2 配置 inputs4.3 配置 output4.4 索引4.5 分片和副本 5.收集nginx日志5.1 原生日志5.2 nginx日志格式5.3 filebeat 配置 6.logstash6.1 安…

解决Mac安装软件的“已损坏,无法打开。 您应该将它移到废纸篓”问题

mac安装软件时&#xff0c;如果出现这个问题&#xff0c;其实很简单 首先打开终端&#xff0c;输入下面的命令 sudo xattr -r -d com.apple.quarantine 输入完成后&#xff0c;先不要回车&#xff0c;点击访达--应用程序--找到你无法打开的app图标&#xff0c;拖到终端窗口中…

pytorch实现长短期记忆网络 (LSTM)

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 LSTM 通过 记忆单元&#xff08;cell&#xff09; 和 三个门控机制&#xff08;遗忘门、输入门、输出门&#xff09;来控制信息流&#xff1a; 记忆单元&#xff08;Cell State&#xff09; 负责存储长期信息&…

后盾人JS--继承

继承是原型的继承 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </hea…

实际操作 检测缺陷刀片

号he 找到目标图像的缺陷位置&#xff0c;首先思路为对图像进行预处理&#xff0c;灰度-二值化-针对图像进行轮廓分析 //定义结构元素 Mat se getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1)); morphologyEx(thre, tc, MORPH_OPEN, se, Point(-1, -1), 1); …

从实数与复数在交流电路正弦量表示中的对比分析

引言 在交流电路领域&#xff0c;深入理解电压和电流等正弦量的表示方式对电路分析至关重要。其中&#xff0c;只用实数表示正弦量存在诸多局限性&#xff0c;而复数的引入则为正弦量的描述与分析带来了极大的便利。下面将从瞬时值角度&#xff0c;详细剖析只用实数的局限性&a…

Python3 OS模块中的文件/目录方法说明十四

一. 简介 前面文章简单学习了 Python3 中 OS模块中的文件/目录的部分函数。 本文继续来学习 OS 模块中文件、目录的操作方法&#xff1a;os.statvfs() 方法&#xff0c;os.symlink() 方法。 二. Python3 OS模块中的文件/目录方法 1. os.statvfs() 方法 os.statvfs() 方法用…

知识蒸馏教程 Knowledge Distillation Tutorial

来自于&#xff1a;Knowledge Distillation Tutorial 将大模型蒸馏为小模型&#xff0c;可以节省计算资源&#xff0c;加快推理过程&#xff0c;更高效的运行。 使用CIFAR-10数据集 import torch import torch.nn as nn import torch.optim as optim import torchvision.tran…

Turing Complete-1位开关

要求如下&#xff1a; 我的思考&#xff1a; 把输入1当作控制信号&#xff0c;把输入2当作输出信号。 通过非门和开关使输入2形成双通道输出&#xff0c; 通道一为输出输入2取反。 通道二为输出输入2本身。 通过输入1来控制两个通道的开闭。

从Transformer到世界模型:AGI核心架构演进

文章目录 引言:架构革命推动AGI进化一、Transformer:重新定义序列建模1.1 注意力机制的革命性突破1.2 从NLP到跨模态演进1.3 规模扩展的黄金定律二、通向世界模型的关键跃迁2.1 从语言模型到认知架构2.2 世界模型的核心特征2.3 混合架构的突破三、构建世界模型的技术路径3.1 …

深度求索DeepSeek横空出世

真正的强者从来不是无所不能&#xff0c;而是尽我所能。多少有关输赢胜负的缠斗&#xff0c;都是直面本心的搏击。所有令人骄傲振奋的突破和成就&#xff0c;看似云淡风轻寥寥数语&#xff0c;背后都是数不尽的焚膏继晷、汗流浃背。每一次何去何从的困惑&#xff0c;都可能通向…

性能优化中的数据过滤优化

目录 以下是一些关于数据过滤优化的策略和方法 索引使用 避免全表扫描 使用分区 数据预处理 合理设计查询 利用缓存机制 数据库层面优化 系统中通常会有一些统计和分析的功能&#xff0c;以前我们主要针对结构化数据&#xff08;关系型数据库存储&#xff09;进行分析&a…

与本地Deepseek R1:14b的第一次交流

本地部署DS的方法&#xff0c;见&#xff1a;本地快速部署DeepSeek-R1模型——2025新年贺岁-CSDN博客 只有16GB内存且没有强大GPU的个人电脑&#xff0c;部署和运行14b参数的DS大模型已是天花板了。 运行模型 ollama run deepseek-r1:14b C:\Users\Administrator>ollama r…

Python 梯度下降法(六):Nadam Optimize

文章目录 Python 梯度下降法&#xff08;六&#xff09;&#xff1a;Nadam Optimize一、数学原理1.1 介绍1.2 符号定义1.3 实现流程 二、代码实现2.1 函数代码2.2 总代码 三、优缺点3.1 优点3.2 缺点 四、相关链接 Python 梯度下降法&#xff08;六&#xff09;&#xff1a;Nad…

【狂热算法篇】探秘图论之Dijkstra 算法:穿越图的迷宫的最短路径力量(通俗易懂版)

羑悻的小杀马特.-CSDN博客羑悻的小杀马特.擅长C/C题海汇总,AI学习,c的不归之路,等方面的知识,羑悻的小杀马特.关注算法,c,c语言,青少年编程领域.https://blog.csdn.net/2401_82648291?typebbshttps://blog.csdn.net/2401_82648291?typebbshttps://blog.csdn.net/2401_8264829…

MySQL(Undo日志)

后面也会持续更新&#xff0c;学到新东西会在其中补充。 建议按顺序食用&#xff0c;欢迎批评或者交流&#xff01; 缺什么东西欢迎评论&#xff01;我都会及时修改的&#xff01; 大部分截图和文章采用该书&#xff0c;谢谢这位大佬的文章&#xff0c;在这里真的很感谢让迷茫的…

全面剖析 XXE 漏洞:从原理到修复

目录 前言 XXE 漏洞概念 漏洞原理 XML 介绍 XML 结构语言以及语法 XML 结构 XML 语法规则 XML 实体引用 漏洞存在原因 产生条件 经典案例介绍分析 XXE 漏洞修复方案 结语 前言 网络安全领域暗藏危机&#xff0c;各类漏洞威胁着系统与数据安全。XXE 漏洞虽不常见&a…

初级数据结构:栈和队列

目录 一、栈 (一)、栈的定义 (二)、栈的功能 (三)、栈的实现 1.栈的初始化 2.动态扩容 3.压栈操作 4.出栈操作 5.获取栈顶元素 6.获取栈顶元素的有效个数 7.检查栈是否为空 8.栈的销毁 9.完整代码 二、队列 (一)、队列的定义 (二)、队列的功能 (三&#xff09…