前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang中的接口、函数、方法

golang中的接口、函数、方法

作者头像
ccf19881030
发布2020-12-16 16:23:29
1.2K0
发布2020-12-16 16:23:29
举报
文章被收录于专栏:ccf19881030的博客

For-learning-Go-Tutorial

Go语言是谷歌2009发布的第二款开源编程语言。

Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。

因而一直想的是自己可以根据自己学习和使用Go语言编程的心得,写一本Go的书可以帮助想要学习Go语言的初学者快速入门开发和使用!

在 Golang 中,interface 是一个非常重要的概念和特性。

接口(Interface)

在Go语言中,函数和方法不太一样,有明确的概念区分。其他语言中,比如Java,一般来说,函数就是方法,方法就是函数,但是在Go语言中, 函数是指不属于任何结构体、类型的方法,也就是说,函数是没有接收者的;而方法是有接收者的,我们说的方法要么是属于一个结构体的,要么属于一个新定义的类型的。

在 Golang 中,interface 是一种抽象类型,相对于抽象类型的是具体类型(concrete type):int,string。如下是 io 包里面的例子。

代码语言:javascript
复制
// Writer is the interface that wraps the basic Write method.
//
// 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.
type Writer interface {
    Write(p []byte) (n int, err error)
}

// Closer is the interface that wraps the basic Close method.
//
// The behavior of Close after the first call is undefined.
// Specific implementations may document their own behavior.
type Closer interface {
    Close() error
}

在 Golang中,interface是一组 method 的集合,是 duck-type programming 的一种体现。不关心属性(数据),只关心行为(方法)。 具体使用中你可以自定义自己的 struct,并提供特定的 interface 里面的 method 就可以把它当成 interface 来使用。 下面是一种 interface 的典型用法,定义函数的时候参数定义成 interface,调用函数的时候就可以做到非常的灵活。

代码语言:javascript
复制
type FirstInterface interface{
    Print()
}

func TestFunc(x FirstInterface) {}
type PatentStruct struct {}
func (pt PatentStruct) Print() {}

func main() {
    var pt PatentStruct
    TestFunc(me)
}
Why Interface
  • writing generic algorithm (泛型编程)
  • hiding implementation detail (隐藏具体实现))
  • providing interception points (提供拦截端点)
writing generic algorithm

在 Golang 中并不支持泛型编程。在 C++ 等高级语言中使用泛型编程非常的简单,所以泛型编程一直是 Golang 诟病最多的地方。 但是使用 interface 我们可以实现泛型编程,我这里简单说一下,具体可以参考我前面给出来的那篇文章。比如我们现在要写一个泛型算法, 形参定义采用 interface 就可以了,以标准库的 sort 为例。

代码语言:javascript
复制
package sort

// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package.  The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {
    // Len is the number of elements in the collection.
    Len() int
    // Less reports whether the element with
    // index i should sort before the element with index j.
    Less(i, j int) bool
    // Swap swaps the elements with indexes i and j.
    Swap(i, j int)
}

...

// Sort sorts data.
// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data Interface) {
    // Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.
    n := data.Len()
    maxDepth := 0
    for i := n; i > 0; i >>= 1 {
        maxDepth++
    }
    maxDepth *= 2
    quickSort(data, 0, n, maxDepth)
}

Sort 函数的形参是一个 interface,包含了三个方法:Len(),Less(i,j int),Swap(i, j int)。 使用的时候不管数组的元素类型是什么类型(int, float, string…),只要我们实现了这三个方法就可以使用 Sort 函数,这样就实现了“泛型编程”。 有一点比较麻烦的是,我们需要将数组自定义一下。下面是一个例子。

代码语言:javascript
复制
type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s: %d", p.Name, p.Age)
}

// ByAge implements sort.Interface for []Person based on
// the Age field.
type ByAge []Person //自定义

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

func main() {
    people := []Person{
        {"Bob", 31},
        {"John", 42},
        {"Michael", 17},
        {"Jenny", 26},
    }

    fmt.Println(people)
    sort.Sort(ByAge(people))
    fmt.Println(people)
}
函数

函数和方法,虽然概念不同,但是定义非常相似。函数的定义声明没有接收者,所以我们直接在go文件里,go包之下定义声明即可。

代码语言:javascript
复制
func main() {
	sum := add(3, 4)
	fmt.Println(sum)
}
func add(m, n int) int {
	return m+ n
}

在上面我们定义了add就是一个函数,它的函数签名是func add(m, n int) int,没有接收者,直接定义在go的一个包之下,可以直接调用,比如例子中的main函数调用了add函数。 例子中的这个函数名称是小写开头的add,所以它的作用域只属于所声明的包内使用,不能被其他包使用,如果我们把函数名以大写字母开头,该函数的作用域就大了,可以被其他包调用。 这也是Go语言中大小写的用处,比如Java中,就有专门的关键字来声明作用域private、protect、public等。

代码语言:javascript
复制
func Add(a,b int) int {
        return a + b
}

这个新定义的Add方法就是可以被其他包调用的.

方法

方法的声明和函数类似,他们的区别是:方法在定义的时候,会在func和方法名之间增加一个参数,这个参数就是接收者,这样我们定义的这个方法就和接收者绑定在了一起,称之为这个接收者的方法

代码语言:javascript
复制
type person struct {
	name string
}
func (p person) String() string{
	return "the person name is "+p.name
}

留意例子中,func和方法名之间增加的参数(p person),这个就是接收者。现在我们说,类型person有了一个String方法,现在我们看下如何使用它。

代码语言:javascript
复制
func main() {
	p:=person{name:"你好"}
	fmt.Println(p.String())
}

Go语言里有两种类型的接收者:值接收者和指针接收者。我们上面的例子中,就是使用值类型接收者的示例。 使用值类型接收者定义的方法,在调用的时候,使用的其实是值接收者的一个副本,所以对该值的任何操作,不会影响原来的类型变量。

代码语言:javascript
复制
func main() {
	p:=person{name:"你好"}
	p.modify() //值接收者,修改无效
	fmt.Println(p.String())
}
type person struct {
	name string
}
func (p person) String() string{
	return "the person name is "+p.name
}
func (p person) modify(){
	p.name = "真的好"
}

只需要改动一下,变成指针的接收者,就可以完成了修改。

代码语言:javascript
复制
在调用方法的时候,传递的接收者本质上都是副本,只不过一个是这个值副本,一是指向这个值指针的副本。指针具有指向原有值的特性,所以修改了指针指向的值,也就修改了原有的值。我们可以简单的理解为值接收者使用的是值的副本来调用方法,而指针接收者使用实际的值来调用方法。

在上面的例子中,有没有发现,我们在调用指针接收者方法的时候,使用的也是一个值的变量,并不是一个指针,如果我们使用下面的也是可以的。

代码语言:javascript
复制
p:=person{name:"年后"}
(&p).modify() //指针接收者,修改有效

这样也是可以的。如果我们没有这么强制使用指针进行调用,Go的编译器自动会帮我们取指针,以满足接收者的要求。 同样的,如果是一个值接收者的方法,使用指针也是可以调用的,Go编译器自动会解引用,以满足接收者的要求,比如例子中定义的String()方法,也可以这么调用:

代码语言:javascript
复制
p:=person{name:"你好"}
fmt.Println((&p).String())

总之,方法的调用,既可以使用值,也可以使用指针,我们不必要严格的遵守这些,Go语言编译器会帮我们进行自动转义的,这大大方便了我们开发者。 不管是使用值接收者,还是指针接收者,一定要搞清楚类型的本质:对类型进行操作的时候,是要改变当前值,还是要创建一个新值进行返回?这些就可以决定我们是采用值传递,还是指针传递。

多值返回

Go语言支持函数方法的多值返回,也就说我们定义的函数方法可以返回多个值,比如标准库里的很多方法,都是返回两个值,第一个是函数需要返回的值,第二个是出错时返回的错误信息,这种的好处,我们的出错异常信息再也不用像Java一样使用一个Exception这么重的方式表示了,非常简洁。

代码语言:javascript
复制
func main() {
	file, err := os.Open("/usr/bin")
	if err != nil {
		log.Fatal(err)
		return
	}
	fmt.Println(file)
}

如果返回的值,我们不想使用,可以使用_进行忽略.

代码语言:javascript
复制
file, _ := os.Open("/usr/bin")

多个值返回的定义也非常简单,看个例子。

代码语言:javascript
复制
func add(a, b int) (int, error) {
	return a + b, nil
}

函数方法声明定义的时候,采用逗号分割,因为时多个返回,还要用括号括起来。返回的值还是使用return 关键字,以逗号分割,和返回的声明的顺序一致。

可变参数

函数方法的参数,可以是任意多个,这种我们称之为可以变参数,比如我们常用的fmt.Println()这类函数,可以接收一个可变的参数。

代码语言:javascript
复制
func main() {
	fmt.Println("a","b","c")
}

可以变参数,可以是任意多个。我们自己也可以定义可以变参数,可变参数的定义,在类型前加上省略号…即可。

代码语言:javascript
复制
func main() {
	print("a","b","c")
}
func print (a ...interface{}){
	for _,v:=range a{
		fmt.Print(v)
	}
	fmt.Println()
}

例子中我们自己定义了一个接受可变参数的函数,效果和fmt.Println()一样。

可变参数本质上是一个数组,所以我们向使用数组一样使用它,比如例子中的 for range 循环。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/12/12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • For-learning-Go-Tutorial
    • 接口(Interface)
      • Why Interface
        • writing generic algorithm
          • 函数
            • 方法
              • 多值返回
                • 可变参数
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档