golang接口三个特性
类型和接口
因为映射建设在类型的基础之上,首先我们对类型进行全新的介绍。
go是一个静态性语言,每个变量都有静态的类型,因此每个变量在编译阶段中有明确的变量类型,比如像:int、float32、MyType。。。
比如:
type MyInt int
var i int
var j MyInt
变量i的类型为int,变量j的类型为MyInt,变量i、j具有确定的类型,虽然i、j的潜在类型是一样的,但是在没有转换的情况下他们之间不能相互赋值。
在类型中有重要的一类为接口类型(interface),接口类型为一系列方法的集合。一个接口型变量可以存储接口方法中声明的任何具体的值。像io.Reader和io.Writer是一个很好的例子,这两个接口在io包中定义。
type Reader interface{
Read(p []byte)(n int, err error)
}
type Writer interface{
Writer(p []byte)(n int,er error)
}
任何声明为io.Reader或者io.Writer类型的变量都可以使用Read或者Writer 方法。也就意味着io.Reader类型的变量可以赋值任何有Read方法的的变量。
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
无论变量r被赋值什么类型的值,变量r的类型依旧是io.Reader。go语言是静态类型语言,并且r的类型永远是io.Reader。
在接口类型中有一个重要的极端接口类型--空接口。
interface{}
他代表一个空的方法集合并且可以被赋值为任何值,因为任何一个变量都有0个或者多个方法。
有一种错误的说法是go的接口类型是动态定义的,其实在go中他们是静态定义的,一个接口类型的变量总是有着相同类型的类型,尽管在运行过程中存储在接口类型变量的值具有不同的类型,但是接口类型的变量永远是静态的类型。
接口的表示方法
关于go中接口类型的表示方法Russ Cox大神在一篇博客中已经详细介绍[blog:http://research.swtch.com/2009/12/go-data-structures-interfaces.html]
一个接口类型的变量存储一对信息:具体值,值的类型描述。更具体一点是,值是实现接口的底层具体数据项,类型是数据项类型的完整描述。
举个例子:
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
变量r包含两个数据项:值(tty),类型(os.File)。注意os.File实现的方法不仅仅是Read,即使接口类型仅包含Read方法,但是值(tty)却用于其完整的类型信息,因此我们可以按照如下方法调用
var w io.Writer
w = r.(io.Writer)
这条语句是一个断言语句,断言的意思是变量r中的数据项声明为io.Writer,因为我们可以将r赋值给w。执行完这条语句以后,变量w将和r一样包含值(tty)、类型(*os.File)。即使具体值可能包含很多方法,但是接口的静态类型决定什么方法可以通过接口型变量调用。
同样我们可以
var empty interface{}
empty = w
这个接口型变量同样包含一个数据对(tty,*os.File)。空接口可以接受任何类型的变量,并且包含我们可能用到的关于这个变量的所有信息。在这里我们不需要断言是因为w变量满足于空接口。在上一个从Reader向Writer移动数据的例子中,我们需要类型断言,因为Reader接口中不包含Writer方法
切记接口的数据对中的内容只能来自于(value , concrete type)而不能是(value, interface type),也就是接口类型不能接受接口类型的变量。
1.从接口类型到映射对象
在最底层,映射是对存储在接口内部数据对(值、类型)的解释机制。首先我们需要知道在reflect包中的两种类型Type和Value,这两种类型提供了对接口变量内部内容的访问,同时reflect.TypeOf和reflect.ValueOf两个方法检索接口类型的变量。
首先我们开始TypeOf
package main
import (
"fmt"
"reflect"
)
func main() {
var f float64 = 13.4
fmt.Println(reflect.TypeOf(f))
fmt.Println("Hello, playground")
}
结果
float64
Hello, playground
我们可以会感到奇怪这里没有接口呀?因为在程序中我们可以得知f的变量类型应为float32,不应该是什么变量类型。但是我们在golang源码中我们得知,reflect.TypeOf包含一个空接口类型的变量.
func TypeOf(i interface{})Type
当我们在调用reflect.TypeOf方法时,x首先存储在一个空的接口中,然后再作为一个参数传送到reflect.TypeOf方法中,然后该方法解压这个空的接口得到类型信息。
同样reflect.ValueOf方法,得到值。
var f float64 = 13.4
fmt.Println(reflect.ValueOf(f))
结果
13.4
reflect.Type和reflec.Value有许多方法让我们检查和修改它们。一个比较重要的方法是Value有一个能够返回reflect.Value的类型的方法Type。另外一个比较重要的是Type和Value都提供一个Kind方法,该方法能够返回存储数据项的字长(Uini,Floatr64,Slice等等)。同样Value方法也提供一些叫做Int、Float的方法让我们修改存储在内部的值。
var f float64 = 13.44444
v := reflect.ValueOf(f)
fmt.Println(v)
fmt.Println(v.Type())
fmt.Println(v.Kind())
fmt.Println(v.Float())
结果
13.444444444444445
float64
float64
13.444444444444445
同时有像SetInt、SetFloat之类的方法,但是我们必须谨慎的使用它们。
反射机制有两个重要的性质。首先,为了保证接口的简洁行,getter
和setter
两个方法是可以接受最大类型值的赋值,比如int64
可以接受任何符号整数。所以值的Int方法会返回一个int64
类型的值,SetInt
接受int64
类型的值,因此它可能转化为所涉及的实际类型。
var x uint8 = 'x'
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type()) // uint8.
fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.
x = uint8(v.Uint()) // v.Uint returns a uint64.
第二个特性:接口保存了数据项底层类型,而不是静态的类型,如果一个接口包含用户定义的整数类型的值,比如
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
则v的Kind
方法调用仍然返回的是reflect.Int
,尽管x的静态类型是MyInt。也可以说,Kind`不会像
Type`一样将MyInt和int当作两种类型来对待。
2.从映射对象到接口的值
像物理映射一样,Go中的映射也有其自身的相反性。
通过利用Interface
的方法我们可以将interface.Value
恢复至接口类型,实际上这个方法将type和value信息包装至interface类型并且返回该值。
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}
因此我们可以说
y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)
打印float64类型的值,其实是接口类型变量v的映射。
或者我们可以这样做,fmt.Println
, fmt.Printf
等函数的参数尽管是空的接口类型也能运行,在fmt包里面解析出type和value的方法和我们上面的例子相似。因此所有正确打印reflect.Value
的方法都试通过interface的方法将值传递给格式化打印函数。
fmt.Println(v.Interface())
(为什么不是fmt.Println(v)
?因为通过v是reflect.Value类型.)因为我们的值底层是float64类型,因此我们甚至可以浮点类型的格式打印.
fmt.Printf("value is %7.1e\n", v.Interface())
结果是
3.4e+00
因此我们不用类型断言v.Interface{}到float64类型。因为接口类型内部保存着值的信息,Printf函数能够恢复这些信息。
简单的说Interface是ValueOf的反操作,除非这个值总是静态的Interface类型。
改变接口对象,他的值必须是可改变的
第三法则比较微妙并且容易混淆,但是如果从第一准则开始看的话,那么还是比较容易理解的。
这是一条错误的语句,但是这个错误值得我们研究
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
如果你运行这条语句则会有下面的报错信息
panic: reflect.Value.SetFloat using unaddressable value
因为变量v是不可更改的,所以提示值7.1是不可寻址的。可赋值是value的一个特性,但是并不是所以的value都具有这个特性。
CanSet
方法返回该值是否是可以改变的,比如
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())
结果是
settability of v: false
如果在不可以赋值的变量上进行赋值,就回引起错误。但是到底是什么才是可以赋值的呢?
可赋值的有点像是可寻址的,但是会更严格。映射对象可以更改存储值的特性可以用来创建新的映射对象。映射对象包含原始的数据项是决定映射对象可赋值的关键。当下面代码运行时
var x float64 = 3.4
v := reflect.ValueOf(x)
只是将x的拷贝到reflect.ValueOf
,因此reflect.ValueOf
的返回值是x的复制项,而不是x本身。假如下面这条语句可以正常运行
v.SetFloat(5.4)
尽管v看起来是由x创建的,但是并不会更新x的值,因为这条语句会更新x拷贝值的值,但是并不影响x本身,因此可更改的这一特性就是为了避免这种操作。
虽然这看起来很古怪,但其实这是一种很熟悉的操作。比如我们将x值赋值给一个方法
f(x)
我们本身不想修改x的值,因为传入的只是x值的拷贝,但是如果我们想修改x的值,那么我们需要传送x的地址(也就是x的指针)
f(&x)
这种操作是简单明了的,其实对于映射也是一样的。如果我们想通过映射修改x的值,那么我们需要传送x的指针。比如
var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())
结果
type of p: *float64
settability of p: false
映射对象p仍然是不可修改的,但是其实我们并不想修改p,而是*p。为了得到指针的指向,我们需要使用Elem()
方法,该方法将会指向*p的值,并且将其保存到映射变量中
v := p.Elem()
fmt.Println("settability of v:", v.CanSet())
结果为
settability of v: true
现在v是一个可修改的映射对象。并且v代表x,因此我们可以使用v.SetFloat()
来修改x的值。
v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)
输出结果为
7.1
7.1
映射是比较难理解的,尽管我们通过映射的Values``Types
隐藏了到底发生了什么操作。我们只需要记住如果想改变它的值,那在调用ValuesOf
方法时应该使用指向它的指针。
Struct
在上一个例子中v并不是指向自身的指针,而是通过其他方式产生的。还有一种常用的操作就是修改结构体的某个字段,只要我们知道了结构体的地址,我们就能修改它的字段。
这有一个修改结构体变量t的例子。因为我们要修改结构体的字段,所以我们使用结构体指针创建结构体对象。我们使用typeOfT代表t的数据类型,并通过NumField方法迭代结构体的字段。主意:我们只是提取出结构体类型字段的的名字,而他们的reflect.Value
对象。
type T struct {
A int
B string
}
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
typeOfT.Field(i).Name, f.Type(), f.Interface())
}
输出结果是
0: A int = 23
1: B string = skidoo
值得注意的是只有可导出的字段才能使可修改的。
因为s包含一个可修改的映射对象,所以我们可以修改结构体的字段
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)
结果为
t is now {77 Sunset Strip}
如果s是通过t创建而不是&t,那么SetInt和SetString方法都会出错,因为t的字段是不可以修改的。
golang接口三个特性的更多相关文章
- 【Golang 接口自动化06】微信支付md5签名计算及其优化
前言 可能看过我博客的朋友知道我主要是做的支付这一块的测试工作.而我们都知道现在比较流行的支付方式就是微信支付和支付宝支付,当然最近在使用低手续费大力推广的京东金融(已改名为京东数科)以后也可能站到第 ...
- Golang 接口与反射知识要点
目录 Golang 接口与反射知识要点 1. 接口类型变量 2. 类型断言 3. 鸭子类型 4. 反射机制 5. reflect 包 TypeOf().ValueOf() Type().Kind() ...
- Golang 接口
1 接口是什么 Golang中没有像Python.Java拥有类和对象的概念,其封装对象或说明对象是通过接口来实现的.比如谁能够实现什么样的功能,便能够将其抽象化封装. 接口定义了一组方法(抽象方法集 ...
- php开发面试题---php面向对象详解(对象的主要三个特性)
php开发面试题---php面向对象详解(对象的主要三个特性) 一.总结 一句话总结: 对象的行为:可以对 对象施加那些操作,开灯,关灯就是行为. 对象的形态:当施加那些方法是对象如何响应,颜色,尺寸 ...
- 黑马程序员——OC基础 三种特性之封装
Java培训.Android培训.iOS培训..Net培训.期待与您交流! (以下内容是对黑马苹果入学视频的个人知识点总结) 三种特性之一封装 (一)set方法和get方法 1) set方法 1&g ...
- Golang的iota的特性
Golang的iota的特性: 1. iota在每个ConstBlock中自动归0. 2. iota在每个ConstSpec后自动增1. 换言之: iota是ConstBlock中ConstSpec的 ...
- java多线程中的三种特性
java多线程中的三种特性 原子性(Atomicity) 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行. 如果一个操作时原子性的,那么多线程并 ...
- 分布式CAP定理,为什么不能同时满足三个特性?
在弄清楚这个问题之前,我们先了解一下什么是分布式的CAP定理. 根据百度百科的定义,CAP定理又称CAP原则,指的是在一个分布式系统中,Consistency(一致性). Availability(可 ...
- 【Golang 接口自动化00】为什么要用Golang做自动化?
为什么使用Golang做自动化 顺应公司的趋势学习了Golang之后,因为没有开发那么多的时间和项目来实践,怕步此前学习Java缺少练习遗忘殆尽的后尘,决定利用工作之余的时间把此前用Python的写的 ...
随机推荐
- Django(一)入门基础——hello world
环境配置 windows7 python3.6 Django 2.0 PyCharm 2018.1 专业版(PS:不建议社区版,因为被"阉割"了很多功能,比如cmd的Termina ...
- Ubuntu系统下配置IP地址方法介绍
配置IP方式有两种: 1.通过命令直接配置 sudo ifconfig eth0 IP地址 netmask 子网掩码------配置IP地 sudo route add default gw 网关-- ...
- AttributeError: module 'enum' has no attribute 'IntFlag'
Mac PyCharm新建以Python3.6.1为解释器的Django项目的时候出现以下错误提示: AttributeError: module 'enum' has no attribute 'I ...
- HTML 学习笔记 day one
HTML学习笔记 day one Chapter one 网站开发基础 1.2网站的基本架构 网站的基本要素:内容,页面,超链接 动态网页和静态网页的区别在于:动态网页会自动更新,后缀名是.asp或者 ...
- C#备份及还原数据库的实现
使用前要导入SQLDMO.dll 下载地址:http://down.51cto.com/data/853937 1.在用户的配置时,我们需要列出当前局域网内所有的数据库服务器,并且要列出指定服务器的所 ...
- Eeffective C++ 读书笔记( 32-38)
条款三十二:确定你的public继承塑模出is-a关系 1.所谓最佳设计,取决于系统希望做什么事,包括现在和未来. 2.好的接口可以防止无效的代码通过编译,因此你应该宁可采取“在编译期拒绝企鹅飞行”的 ...
- [ Java面试题 ]基础篇之二
1.String s = new String("xyz");创建了几个StringObject?是否可以继承String类? 两个或一个都有可能,"xyz"对 ...
- hi-nginx-javascript vs node.js
hi-nginx-1.4.9已经支持javascript,这意味着把javascript应用于后端开发,将不再只有nodejs这唯一的途径和方法.由于java本身对javascript的极好支持,使得 ...
- i++ 和 ++i;&& 和 &
一.算数运算符(自增运算符i++.自减运算符i++) ※ i++是先赋值(计算)再加1 :++i是先加1再赋值(计算) : int m = 5; boolean bool = ++m > 5 ...
- 40多行python代码开发一个区块链。
40多行python代码开发一个区块链?可信吗?我们将通过Python 2动手开发实现一个迷你区块链来帮你真正理解区块链技术的核心原理.python开发区块链的源代码保存在Github. 尽管有人认为 ...