文章每周持续更新,原创不易,「三连」让更多人看到是对我最大的肯定。可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇)

正文共4302字,预计阅读时长 11 分钟

对于一般的语言使用者来说 ,20% 的语言特性就能够满足 80% 的使用需求,剩下在使用中掌握。基于这一理论,Go 基础系列的文章不会刻意追求面面俱到,但该有知识点都会覆盖,目的是带你快跑赶上 Golang 这趟新车。

最近工作上和生活上的事情都很多,这篇文章计划是周末发的,但是周末太忙时间不够,同时为了保证文章质量,反复修改到现在才算完成。

有时候还是很想回到学校,一心只用读书睡觉打游戏的日子,成年人的世界总是被各种中断。不过,不用担心 lemon 能处理好,答应大家要写完的 Go 基础系列可能会迟到,但不会缺席。

今天我们来继续学习,Go 中的面向对象编程思想,包括 方法 和 接口 两大部分学习内容。

通过学习本文,你将了解:

  • Go 的方法定义
  • 方法和函数的区别
  • 方法传值和传指针差异
  • 什么是接口类型
  • 如何判断接口底层值类型
  • 什么是空接口
  • nil 接口 和nil 底层值
classmethodclassstruct

方法

定义

方法就是一类带特殊的接收者参数的函数 ,这些特殊的参数可以是结构体也可以是结构体指针,但不能是内置类型。

Personnameage
type Person struct {
	name string
	age  int
}
Personnameage
func (p Person) GetName() string {
	return p.name + "'s age is"
}

func (p Person) GetAge() int {
	return p.age
}

和函数定义的区别

看了上面的方法定义是不是觉得和函数定义有点类似,还记得函数的定义吗?为了唤起你的记忆,下面分别定义两个相同功能的函数,大家可以对比一下。

func GetNameF(p Person) string {
	return p.name + "'s age is"
}

func GetNameF(p Person) int {
	return p.age
}
fmt.Println.()
p := Person{"lemon", 18}
fmt.Println(GetNameF(p), GetNameF(p), p.GetName(), p.GetAge()) 
//输出 lemon's age is 18 lemon's age is 18

修改接收者的值

GetNameGetAgePersonPersonnameage
ageWriteablepname
func (p *Person) ageWriteable() int {
	p.age += 10
	return p.age
}
p*PersonC++
func (p *Person) ageWriteable() int {
	p.age += 10
	return p.age
}

隐式值与指针转换

Golang 非常的聪明,为了不让你麻烦,它能自动识别方法的实际接收者类型(指针或值),并默默的帮你做转换,以便「方法」能正确的工作。

还是用我们上面定义的方法举例,先来看以「值」作为接收者的方法调用。方便阅读,我把前面的定义再写一遍。

func (p Person) GetName() string {
	return p.name + "'s age is"
}
pppGetName
pp(*pp).GetName*()
	p := Person{"lemon", 18}
	pp := &Person{"lemon", 18}
	fmt.Println(p.GetName(), pp.GetName()) // p 和 pp都能调用 GetName 方法

同理,对接收者是指针的方法,也可以按给它传递值的方式来调用,这里不再赘述。

对方法的说明,就简单介绍到这里,更多细节不去深究,留给大家在使用中学习。

接口

接口我想不到准确的描述语句来说明他,通俗来讲接口类型就是一类预先约定好的方法声明集合。

接口定义就是把一系列可能实现的方法先声明出来,后面只要哪个类型完全实现了某个接口声明的方法,就可用这个「接口变量」来保存这些方法的值,其实是抽象设计的概念。

C++

定义

为了说明接口如何定义,我们要做一些准备工作。

nameage
type man struct {
	name string
	age  int
}

type woman struct {
	name string
	age  int
}
getNamegetAge
func (m *man) getName() string {
	return m.name
}

func (m *woman) getName() string {
	return m.name
}

func (m *man) getAge() int {
	return m.age
}

func (m *woman) getAge() int {
	return m.age
}
humanIfgetName()manwomengetNamegetAge()
type humanIf interface {
    getName() string
    getAge() int
}
humanIf
var m humanIf = &man{"lemon", 18}
var w humanIf = &woman{"hanmeimei", 19}
fmt.Println(m.getName(), w.getName()) 

接口类型

humanIfmwmanwomen
mw
  • 类型断言
panic

如果断言是符合预期的类型,会把调用者实际的底层值返回。

v0 := w.(man) // w保存的不是 man 类型,程序终止

v1 := m.(man) // m保存的符合 man 类型,v1被赋值 m 的底层值 

v, right := a.(man)  // 两个返回值,第一个是值,第二代表是否断言正确的布尔值
fmt.Println(v, right)
  • 类型选择

相比类型断言直接粗暴的让程序终止,「类型选择」语法更加的温和,即使类型不符合也不会让程序挂掉。

v3wcasetype
	
	switch v3 := w.(type) {
	case man:
		fmt.Println("it is type:man", v3)
	case women:
		fmt.Println("it is type:women", v3)
	default:
		fmt.Printf("unknow type:%T value:%v", v3, v3)
	}

空接口

interface{}
man
  type nilIf interface{}
  var ap nilIf = &man{"lemon", 18}

  //等价定义
  var ap interface{} = &man{"lemon", 18} //等价于上面一句
nil
  // 接收指针
  var ap nilIf = &man{"lemon", 18}
  fmt.Println("interface", ap)
  // 接收值
  var a nilIf = man{"lemon", 18}
  fmt.Println("interface", a)
  // 接收nil值
  var b nilIf
  fmt.Println("interface", b)

处理nil接口调用

nil底层值不会引发异常

对 C 或 C++ 程序员来说空指针是噩梦,如果对空指针做操作,结果是不可预知的,很大概率会导致程序崩溃,程序莫名其妙挂掉,想想就令人头秃。

Golangnil
func (m *man) getName() string {
	if m == nil {
		return "nil"
	}

	return m.name
}
nilnilMangetName
	var nilMan *man // 定义了一个空指针 nilMan
	var w humanIf = nilMan
	fmt.Println(w.getName())

nil接口引发程序异常

nil
	manIf = nil
	fmt.Println("interface", manIf.getName())

总结

Golang

感谢各位的阅读,文章的目的是分享对知识的理解,技术类文章我都会反复求证以求最大程度保证准确性,若文中出现明显纰漏也欢迎指出,我们一起在探讨中学习。

今天的技术分享就到这里,我们下期再见。


创作不易,白票不是好习惯,如果在我这有收获,动动手指「点赞」「关注」是对我持续创作的最大支持。

可以微信搜索公众号「 后端技术学堂 」回复「资料」「1024」有我给你准备的各种编程学习资料。文章每周持续更新,我们下期见!