Go语言程序设计-第7章--接口

Go语言程序设计-第7章–接口

接口类型是对其他类型行为的概括与抽象。

Go 语言的接口的独特之处在于它是隐式实现。对于一个具体的类型,无须声明它实现了哪些接口,只要提供接口所必须实现的方法即可。

7.1 接口即约定

7.2 接口类型

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

组合已有接口得到新的接口。

  • 嵌入式声明
type ReadWriter interface {ReaderWriter
}
  • 直接声明
type ReadWriter interface {Read(p []byte) (n int, err error)Close() error
}
  • 混合声明
type ReadWriter interface {Read(p []byte) (n int, err error)Writer
}

7.3 实现接口

接口的实现规则很简单,仅当一个表达式实现了一个接口时,这个表达式才可以赋值给接口。

var w io.Writer
w = os.Stdout // OK: *os.File 有 Writable 方法
w = new(bytes.Buffer) // OK: * bytes.Buffer 有 Writable 方法
w = time.Second // 编译错误: time.Duration 没有 Writable 方法=

如果方法的接收者是一个指针类型,就不可以从一个无地址的对象上调用该方法,如:

type IntSet struct {}
func (*IntSet) String() stringvar _ = IntSet{}.String() // 编译错误:String 方法需要 *IntSet 接收者

可以从一个 IntSet 变量上调用此方法

var s IntSet
var _ = s.String() // OK: s 是一个变量,&s 有 String 方法。

因为只有 *IntSet 有 String 方法,所以也只有 *IntSet 实现了 fmt.Stringer 接口。

var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s // 编译错误: IntSet 缺少 String 方法。

可以把任何数据赋值给空接口类型。

var any interface{} // 空接口类型
any = true
any = 12.34
any = "hello"
any = map[string]int {"one":1}
fmt.Println(any)

判断是否实现接口只需要比较具体类型和接口类型的方法,所以没有必要在具体类型的定义中声明这种关系。

非空的接口类型(如 io.Writer)通常由一个指针类型来实现,特别是当接口类型的一个或多个方法暗示会修改接收者的情形。一个指向结构的指针才是最常见的方法接收者。

7.4 使用 flat.Value 来解析参数

tempflag.go

package mainimport ("flag""fmt"
)type Celsius float64
type Fahrenheit float64func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9.0/5.0 + 32.0) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32.0) * 5.0 / 9.0) }func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }/*
//!+flagvalue
package flag// Value is the interface to the value stored in a flag.
type Value interface {String() stringSet(string) error
}
//!-flagvalue
*///!+celsiusFlag
// *celsiusFlag satisfies the flag.Value interface.
type celsiusFlag struct{ Celsius }func (f *celsiusFlag) 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// 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 = CelsiusFlag("temp", 20.0, "the temperature")
func main() {flag.Parse()fmt.Println(*temp)
}

7.5 接口值

一个接口类型的值(简称接口值)其实有两个部分:一个具体类型和该类型的一个值。二者称为接口的动态类型和动态值。

类型描述符来提供每个类型的具体信息,比如它的名字和方法。对于一个接口值,类型部分就用对应的类型描述符来表述。

如下四个语句中,变量 w 有三个不同的值(最初和最后是同一个值,都是 nil)

var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil

每个语句后 w 的值和相关的动态行为。第一个语句声明了 w: var w io.Writer
在 Go 语言中,变量总是初始化为一个特定的值,接口也不例外。接口的零值就是把它的动态类型和值都设置为 nil

一个接口值是否是 nil 取决于它的动态类型,所以现在这是一个 nil 接口值。可以用 w == nil 或者 w != nil 来检测一个接口值是否是 nil

第二个语句把一个 *os.File 类型的值赋给了 w: w = os.Stdout
这次赋值把一个具体类型隐私转换为一个接口类型,它与对应的显式转换 io.Writer(os.Stdout) 等价。接口的动态类型会设置为指针类型 *os.File 的类型描述符,它的动态值会设置为 os.Stdout 的副本,即一个指向代表进程的标准输出的 os.File 类型的指针。

调用 w.Write([]byte(“hello”)) //
等价于 os.Stdout.Write([]byte("hello"))

接口值可以用 == 和 != 操作符来比较。

在比较两个接口值时,如果两个接口值的动态类型一致,但对应的动态值是不可比较的(如 slice),那么这个比较会导致宕机。

var w io.Writer
fmt.Printf("%T\n", w) // "<nil>"w = os.Stdout
fmt.Printf("%T\n", w) // "*os.File"w = new(bytes.Buffer)
fmt.Printf("%T\n", w) // "*bytes.Buffer"

注意:含有空指针的非空接口
一个接口值是否是 nil 取决于它的动态类型。

const debug = truefunc f(out io.Writer) {if out != nil {out.Write([]byte("done!\n"))}
}
func main() {var buf *bytes.Bufferif debug {buf = new(bytes.Buffer) // 启用输出收集}f(buf)
}

当 main 调用 f 时,把一个类型为*bytes.Buffer 的空指针赋给了 out 参数,所以 out 的动态类型是 *bytes.Buffer,动态值是 nil。但是判断 out != nil 判断的是它的动态类型,所以 out != nil 返回true。
调用 out.Write 时,不允许接收者为空。

正确方法:

var buf io.Writer
if debug {buf = new(bytes.Buffer)
}

7.6 使用 sort.Interface 来排序

package sort
type Interface interface {Len() intLess(i, j int) bool // i, j 是序列元素的下标Swap(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]}
sort.Sort(StringSlice(names))

7.7 http.Handler 函数

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

net/http 包提供了一个请求多工转发器 ServeMux。

下面的代码中,将 /list, price 这样的 URL 和对应的处理程序关联起来。

package mainimport ("fmt""log""net/http"
)func main() {db := database{"shoes": 50, "socks": 5}mux := http.NewServeMux()//!+mainmux.HandleFunc("/list", db.list)mux.HandleFunc("/price", db.price)//!-mainlog.Fatal(http.ListenAndServe("localhost:8000", mux))
}type database map[string]intfunc (db database) list(w http.ResponseWriter, req *http.Request) {for item, price := range db {fmt.Fprintf(w, "%s: $%d\n", item, price)}
}func (db database) price(w http.ResponseWriter, req *http.Request) {item := req.URL.Query().Get("item")if price, ok := db[item]; ok {fmt.Fprintf(w, "$%d\n", price)} else {w.WriteHeader(http.StatusNotFound) // 404fmt.Fprintf(w, "no such item: %q\n", item)}
}

7.8 error 接口

Error 是一个接口类型,包含返回错误消息的方法

type error interface {Error() string
}func 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 jmtimport "errors"func Errorf(format string, args ...interface{}) error {return errors.New(Sprintf(format, args...))
}

7.10 断言类型

类型断言是一个作用在接口值上的操作,写出来类似 x.(T), 其中 x 是一个接口类型的表达式,而 T 是一个断言类型。类型断言会检查作为操作数的动态类型是否满足指定的断言类型。

if f, ok := w.(*os.File); ok {// 使用 f
}

7.11 使用断言类型来识别错误

os.PathError

type PathError struct {Op stringPath stringErr error
}func (e *PathError) Error() string {return e.Op + " " + ": " + e.Err.Error()
}

可以根据类型断言来检查错误的特定类型,这些类型包含的细节远远多于一个简单的字符串。

_, err := os.Open("/no/such/file")
fmt.Println(err)
fmt.Printf("%#v\n", err)

7.12 通过接口类型断言来查询特性

func writeString(w io.Writer, s string)(n int, err error) {type stringWriter interface {WritesString(string) (n int, err error)}if sw, ok := w.(StringWriter); ok {return sw.WriteString(s) // 避免了内存复制}return w.Write([]byte(s))
}

7.13 类型分支

类型分支与普通的分支语句类似,差别是操作数改为 x.(type).

switch x.(type) {case nil:  //case init, unit: //case bool: //
}

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

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

相关文章

计算机组成原理-进位计数制(进制表示 进制转换 真值和机器树)

文章目录 现代计算机的结构总览最古老的计数方法十进制计数法推广&#xff1a;r进制计数法任意进制->十进制二进制<--->八进制&#xff0c;十六进制 各种进制常见的书写方式十进制->任意进制整数部分小数部分 十进制->二进制&#xff08;拼凑法&#xff09;真值…

Oracle OCP怎么样线上考试呢

大家好&#xff01;今天咱们就来聊聊Oracle OCP这个让人又爱又恨的认证。为啥说又爱又恨呢&#xff1f;因为它既是IT界的“金字招牌”&#xff0c;又是一块硬骨头&#xff0c;不是那么容易啃下来的。好了&#xff0c;废话不多说&#xff0c;我们直奔主题&#xff0c;来看看关于…

解决vue3中watch 监听不到旧值的问题,亲测有效!

问题描述 这个问题是我在公司vue3项目的时候发现的一个问题&#xff0c;watch 在监听对象/数组变量的变化时&#xff0c;发现对象的数据变化时 旧数据 获取到的和新数据是一样的 类似于下面这样 const objref({a:我是原来的值,b:6, })obj.a改变值watch(obj,(nel,old)>{ c…

studio3T mongodb 根据查询条件更新字段 或 删除数据

1. mongodb 等于、不等于$ne、不包含 $nin 以及批量更新数据的使用。 业务场景&#xff1a; 在集合中&#xff0c;根据查询条件&#xff0c;更新数据状态。 实现代码&#xff1a; 1. 部门名称为XXX、状态不等于“完好”的、并且不包含这些编码的数据先查询出来2. 再把状态更…

rust sqlx包(数据库相关)使用方法+问题解决

可以操作pgsql、mysql、mssql、sqlite 异步的&#xff0c;性能应该不错&#xff0c;具体使用有几个坑 除了sqlx库&#xff0c;还有对于具体数据库的库&#xff0c;比如postgres库 演示以pgsql为例&#xff0c;更新时间2024.1.6 官方github: sqlx github rust官方文档&#xff1…

【Python学习】Python学习4-运算符

目录 【Python学习】Python学习4-运算符 前言算术运算符比较&#xff08;关系&#xff09;运算符赋值运算符逻辑运算符位运算符成员运算符身份运算符运算符优先级参考 文章所属专区 Python学习 前言 本章节主要说明Python的运算符。主要有 算术运算符 比较&#xff08;关系&…

强化学习3——马尔可夫性质、马尔科夫决策、状态转移矩阵和回报与策略(上)

与多臂老虎机问题不同&#xff0c;马尔可夫决策过程包含状态信息以及状态之间的转移机制。如果要用强化学习去解决一个实际问题&#xff0c;第一步要做的事情就是把这个实际问题抽象为一个马尔可夫决策过程。 马尔可夫决策过程描述 马尔可夫决策过程以智能体在与环境交互的过…

【linux笔记1】

目录 【linux笔记1】文件内容的理解用户管理用户管理命令添加用户切换用户修改用户信息删除用户 用户组 【linux笔记1】 文件内容的理解 etc文件夹&#xff1a;etc是拉丁语"et cetera"的缩写&#xff0c;意思是“和其他的”或“等等”。在linux系统中&#xff0c;“…

[嵌入式C][入门篇] 快速掌握基础2 (数据类型、常量、变量)

开发环境&#xff1a; 网页版&#xff1a;跳转本地开发(Vscode)&#xff1a;跳转 文章目录 一、基本变量大小和范围&#xff08;1&#xff09;在8位/32位单⽚机中&#xff1a;测试代码结果&#xff1a;64位机器结果&#xff1a;32位机器&#xff08;单片机&#xff09;无对齐限…

[NAND Flash 5.3] QLC NAND 已来未热,是时候该拥抱了?

依公知及经验整理,原创保护,禁止转载。 专栏 《深入理解NAND Flash》 <<<< 返回总目录 <<<< 全文 3600 字。 前言 伴随着闪存芯片的发展趋势,现如今便宜、大容量的SSD基本上都需要上QLC闪存芯片了。一时间QLC有山雨欲来之势,大容量QLC SSD的普及…

Geoserver扩展发布MySQL视图功能

Geoserver中并不自带mysql数据发布功能&#xff0c;需要扩展外部插件。 1、示例以geoserver-2.20.5版本进行演示&#xff0c;所以MySQL插件需要到该版本对应的“Extensions”标题下查找&#xff0c;下载地址&#xff1a;GeoServer&#xff0c;详见下图 2、选择MySQL进入下载页…

Linux第11步_解决“挂载后的U盘出现中文乱码”

学习完“通过终端挂载和卸载U盘”&#xff0c;我们发现U盘下的中文文件名会出现乱码&#xff0c;现在讲解怎么解决这个问题。其实就是复习一下“通过终端挂载和卸载U盘”&#xff0c;单独讲解&#xff0c;是为了解决问题&#xff0c;一次性搞好&#xff0c;我们会不长记性。 在…

【100条mysql常用命令】

当然&#xff0c;这里是一些常用的 MySQL 命令列表&#xff0c;用于管理数据库和执行各种查询操作&#xff1a; SHOW DATABASES; - 显示所有数据库CREATE DATABASE database_name; - 创建新数据库USE database_name; - 选择要操作的数据库DROP DATABASE database_name; - 删除…

数据在内存中的存储之大小端

今天也是努力学编程&#xff0c;敲代码的一天&#xff01; 1.什么是大小端 其实超过一个字节的数据在内存中存储的时候&#xff0c;就有存储顺序的问题&#xff0c;按照不同的存储顺序&#xff0c;我们分为大端字节序 存储和小端字节序存储&#xff0c;下面是具体的概念: &…

HIL(硬件在环)技术汇总梳理

HIL&#xff08;Hardware-in-the-Loop&#xff09;测试领域的知名公司有dSPACE、NI、Vector和speedgoat等&#xff0c;以下是针对这几家HIL技术的对比分析&#xff1a; 文章目录 dSPACE NI Vector speedgoat 总结 dSPACE dSPACE成立于1988年&#xff0c;起源自德国的帕德…

vue3项目中axios的常见用法和封装拦截(详细解释)

1、axios的简单介绍 Axios是一个基于Promise的HTTP客户端库&#xff0c;用于浏览器和Node.js环境中发送HTTP请求。它提供了一种简单、易用且功能丰富的方式来与后端服务器进行通信。能够发送常见的HTTP请求&#xff0c;并获得服务端返回的数据。 此外&#xff0c;Axios还提供…

C++学习笔记(二十二):c++ 隐式转换与explicit关键字

c允许编译器对代码执行一次隐式转换。 #include<iostream> #include<string>class Entity { public:std::string name;int age;Entity(int x) {age x;}Entity(std::string x){name x;} }; void PrintEntity(Entity e) {std::cout << e.age << "…

buuctf 逆向 number_game

这是一题二叉树逆向 正常递归建立树的代码为&#xff1a; VOID BinaryTree::BuildTree(Tree*& root) {cout << "输入-1即结束" << endl;INT Val 0;cin >> Val;if (Val -1)return;root new Tree(Val);cout << "请输入" &l…

FSMC—扩展外部SRAM

一、SRAM控制原理 STM32控制器芯片内部有一定大小的SRAM及FLASH作为内存和程序存储空间&#xff0c;但当程序较大&#xff0c;内存和程序空间不足时&#xff0c;就需要在STM32芯片的外部扩展存储器了。STM32F103ZE系列芯片可以扩展外部SRAM用作内存。 给STM32芯片扩展内存与给…

基于云平台技术的自动泊车浅谈

基于云平台技术的自动泊车浅谈 一、引言 自动泊车技术是当前汽车技术的重要发展方向之一&#xff0c;它能够帮助驾驶员自动完成泊车操作&#xff0c;提高驾驶安全性。基于云平台技术的自动泊车创新&#xff0c;将为自动泊车技术的发展带来新的突破。 二、云平台技术概述 云…