接口的定义与使用</h2>
我们知道,Go语言里面声明一个接口,或者类型,有3种方式
<ul><li>定义型:<code>type Doer interface { Do() }</code></li><li>非定义型 <code>interface { Do() }</code></li><li>非定义型别名 <code>type Doer = interface { Do() }</code></li></ul>别名方式在编译器看来,和原始类型是同一类型,相互可以赋值
下面我们来看看如何实现一个接口呢
<pre><code class="lang-go hljs"><span class="token keyword">type</span> MyInt <span class="token builtin">int</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span><span class="token operator">*</span>MyInt<span class="token punctuation">)</span> <span class="token function">Do</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"i am do "</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre>
这样就实现了上面定义的那个接口了,这是一个指针接收者,可以定义一个<code>MyInt</code>变量去调用 <code>Do</code> 方法,下面列举了定义该类型和使用该类型的方法
<pre><code class="lang-go hljs"><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
a <span class="token operator">:=</span> <span class="token function">new</span><span class="token punctuation">(</span>MyInt<span class="token punctuation">)</span>
b <span class="token operator">:=</span> <span class="token function">MyInt</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>
<span class="token keyword">var</span> c MyInt
<span class="token keyword">var</span> d MyInt <span class="token operator">=</span> <span class="token number">1</span>
<span class="token keyword">var</span> e Doer <span class="token operator">=</span> <span class="token function">new</span><span class="token punctuation">(</span>MyInt<span class="token punctuation">)</span>
<span class="token keyword">var</span> f Doer <span class="token operator">=</span> <span class="token function">MyInt</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment">// 编译出错,因为Do方法为指针接收,所以该接口变量无法接收一个值类型的值,只能接收指针类型的值</span>
a<span class="token punctuation">.</span><span class="token function">do</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
b<span class="token punctuation">.</span><span class="token function">do</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
c<span class="token punctuation">.</span><span class="token function">do</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
d<span class="token punctuation">.</span><span class="token function">do</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
e<span class="token punctuation">.</span><span class="token function">do</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre>
除了使用指针接收方式,还可以使用值接收方式,比如像这样
<pre><code class="lang-go hljs"><span class="token keyword">func</span> <span class="token punctuation">(</span>MyInt<span class="token punctuation">)</span> <span class="token function">Do</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"i am do "</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre>
那么这两种有何区别呢,只需要记住,「指针接收」比「值接收」更严苛,区别就在于,使用「接口变量」能包裹的值的类型是<code>值</code>还是<code>指针</code>,所以上述定义为指针接收类型的方法的时候,接口变量就不能包裹值类型,所以上述<code>f</code>变量那行会出现编译错误
说得通俗点,定义为<code>值接收者</code> 方法时,指针和值都可以调用,我们可以通过下面这两段代码测试一下
<pre><code class="lang-go hljs"><span class="token keyword">package</span> main
<span class="token keyword">import</span> <span class="token punctuation">(</span>
<span class="token string">"fmt"</span>
<span class="token string">"reflect"</span>
<span class="token punctuation">)</span>
<span class="token keyword">type</span> A <span class="token keyword">struct</span> <span class="token punctuation">{</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span><span class="token operator">*</span>A<span class="token punctuation">)</span> <span class="token function">Do</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"call A do"</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">type</span> B <span class="token keyword">struct</span> <span class="token punctuation">{</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>B<span class="token punctuation">)</span> <span class="token function">Do</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"call B do"</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">//A{}.Do() // 编译有错,无法使用值去调用指针接收者的方法</span>
<span class="token function">new</span><span class="token punctuation">(</span>A<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Do</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 正确</span>
B<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">.</span><span class="token function">Do</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 值接收者时候,值和指针都能正确调用</span>
<span class="token function">new</span><span class="token punctuation">(</span>B<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Do</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//</span>
<span class="token function">printMethodList</span><span class="token punctuation">(</span>A<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// output: 无打印</span>
<span class="token function">printMethodList</span><span class="token punctuation">(</span><span class="token operator">&</span>A<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// output: Method Name is Do()</span>
<span class="token function">printMethodList</span><span class="token punctuation">(</span>B<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// output: Method Name is Do()</span>
<span class="token function">printMethodList</span><span class="token punctuation">(</span><span class="token operator">&</span>B<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// output: Method Name is Do()</span>
<span class="token punctuation">}</span>
<span class="token comment">// 打印p拥有的方法集</span>
<span class="token keyword">func</span> <span class="token function">printMethodList</span><span class="token punctuation">(</span>p <span class="token keyword">interface</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
v <span class="token operator">:=</span> reflect<span class="token punctuation">.</span><span class="token function">TypeOf</span><span class="token punctuation">(</span>p<span class="token punctuation">)</span>
<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> v<span class="token punctuation">.</span><span class="token function">NumMethod</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator"> </span> <span class="token punctuation">{</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"Method Name is "</span> <span class="token operator"> </span> v<span class="token punctuation">.</span><span class="token function">Method</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">.</span>Name <span class="token operator"> </span> <span class="token string">"()"</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
在使用<code>值接收</code> 定义方法时候,go编译器替我们省略了一步
<pre><code class="lang-go hljs"><span class="token comment">// new(B).Do() 为啥可以正确打印呢?这一步go编译器会做下面两步,属于一个语法糖</span>
p <span class="token operator">:=</span> <span class="token function">new</span><span class="token punctuation">(</span>B<span class="token punctuation">)</span>
<span class="token punctuation">(</span><span class="token operator">*</span>p<span class="token punctuation">)</span><span class="token punctuation">.</span>DO
</code></pre>
那么值接收和指针接收,除了以上说的那些,还有什么区别呢?
指针接收,指向的都是调用该方法的对象,对象只有一份;
值接收,是对源对象的拷贝,也就是说,每调用一个改对象的方法,则对其进行一次拷贝
那么什么时候使用<code>值接收</code>, 什么时候使用<code>指针接收呢</code>
推荐使用值接收的情况:
<ul><li>内置类型(int string) 等</li><li>slice,map,interface,channel 这些内置结构体类型</li></ul>在使用 比如 map 这种内置结构体类型的时候,拷贝是只会拷贝其 header,相当于共享一份底层
推荐使用指针接收的情况:
<ul><li>自定义结构体类型</li></ul><h2>实现的意义</h2>
首先,一个类型,它拥有的所有方法的集合,称之为方法集。
如果这个方法集是某个接口的「超集」,我们就说这个类型实现了该接口,于是这个类型的值可以赋值给该接口变量。
所有类型都是「空接口」的实现,所以任意类型都能赋值给空接口变量
由于Go中的实现关系是隐式的,所以如果你声明了一个接口,某些类型可能「被动」地实现了你这个接口的方法,于是就可以将该类型的实例,赋值给此接口变量。
Go里所有的类型都实现了 <code>interface{}</code> 这个空接口,所以可以将任何值赋值给空接口变量,比如下面这段代码
<pre><code class="lang-go hljs"><span class="token keyword">var</span> i <span class="token keyword">interface</span><span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token number">123</span>
</code></pre>
有一个很重要的思想方法就是,当你要赋值给一个接口变量时,你这个类型只要包含了接口所定义的方法集,那么就可以赋值给它
<pre><code class="lang-go hljs"><span class="token keyword">func</span> <span class="token function">Add</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span> b <span class="token keyword">interface</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> aa<span class="token punctuation">,</span> ok <span class="token operator">:=</span> a<span class="token punctuation">.</span><span class="token punctuation">(</span><span class="token keyword">interface</span><span class="token punctuation">{</span><span class="token function">Do</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> ok <span class="token punctuation">{</span> <span class="token comment">// 只要 a 这个类型有 Do 方法,则这里就可以编译通过</span>
aa<span class="token punctuation">.</span><span class="token function">Do</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<h2>类型断言</h2>
其实在Go语言里面,类型的断言,就类似于Java里的类型强转差不多(使用场景),目的是,将通用型接口变量,转向更定制化的方向。下面举个例子,不过有一点必须记住,::被断言的一定是接口类型::
<pre><code class="lang-go hljs"><span class="token keyword">type</span> Sayer <span class="token keyword">interface</span> <span class="token punctuation">{</span>
<span class="token function">Say</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">type</span> People <span class="token keyword">struct</span> <span class="token punctuation">{</span>
Name <span class="token builtin">string</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span><span class="token operator">*</span>People<span class="token punctuation">)</span> <span class="token function">Say</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"我是一个人,我正在说话"</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> sayer Sayer <span class="token operator">=</span> <span class="token operator">&</span>People<span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token comment">// 因为 People 实现了 Sayer接口,所以可以赋值给该接口的变量</span>
sayer<span class="token punctuation">.</span><span class="token function">Say</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 调用该接口方法,此时并不管关心具体实现方式是怎样的,只要实现了就行</span>
people <span class="token operator">:=</span> sayer<span class="token punctuation">.</span><span class="token punctuation">(</span><span class="token operator">*</span>People<span class="token punctuation">)</span> <span class="token comment">// 此时你很肯定此接口变量包裹的就是 *People 类型,所以不需要第二个参数进行判断</span>
<span class="token punctuation">}</span>
</code></pre>
上面简单使用了一下类型断言,它的表达式有2种
<pre><code class="lang-go hljs">v1 <span class="token operator">:=</span> i<span class="token punctuation">.</span><span class="token punctuation">(</span>Type<span class="token punctuation">)</span> <span class="token comment">// 确定就是这个类型的时候,无需第二个参数进行判断</span>
v2<span class="token punctuation">,</span> ok <span class="token operator">:=</span> i<span class="token punctuation">.</span><span class="token punctuation">(</span>Type<span class="token punctuation">)</span> <span class="token comment">// 无法确定它的类型,ok 是个 bool 值,可以通过它判断是否实现</span>
</code></pre>
假如使用了第一种,类型又断言错了的话,会直接产生一个 panic,用第二种则不会,可以通过 ok 值来进行判断是否实现了该接口
有时候新手肯定会纳闷,啥时候该断言呢?我的接口类型是该放外面,还是括号里面呢。
其实只要记住,断言的目的。
咱们平时说「断言」二字,其实就是就算断定的意思,你很断定这个接口的动态类型,就是某个具体的类型。
断言的时候,可以不断言出具体的类型,也就是断言的类型可以是接口 <code>i.(另一个接口类型B)</code> ,但是 i 和 此接口类型,一定是可以装载同一个动态类型。i 是比较通用的接口类型,而 i 中的动态类型,一定也是实现了 接口B,只不过此时我们只需要使用 接口B 的方法,所以没必要将整个动态类型断言出来(当然断言出整个动态类型也没什么错)
如果还是懵逼,可以记住下面的公式
<pre><code class="lang-go hljs">更通用的接口<span class="token punctuation">.</span><span class="token punctuation">(</span>较为定制的类型<span class="token punctuation">)</span>
</code></pre>
左边的一定是更加通用的,右边一定是更加实现的:通用 -> 实现
记住这个公式,可以避免犯错,百试不爽
[未完待续]。。。
<blockquote class="layui-elem-quote" style="width: 100%;overflow:hidden">
作者: IM_SEAN_YANG
链接: https://blog.csdn.net/IM_SEAN_YANG/article/details/105918145
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
</blockquote>
到此这篇关于“GO语言接口使用指南”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!
您可能感兴趣的文章:
想系统学习GO语言(Golang
Go 开发关键技术指南 | 为什么你要选择 Go?(内含超全知识大图)
关于golang面向接口
Go 语言一本通
Go语言空接口类型(interface{})
Go 语言到底适合干什么?
初识 go 语言:方法,接口及并发
想学一门新的编程语言?考虑一下Go (Golang)吧
go 获取函数地址_Go语言基础--接口浅析
Go语言接口interface