这是我的Go学习笔记的第四篇,面向对象!现代语言几乎都会面向对象进行了支持!当然,Go也具备面向对象的特性!
我的语言学习过程一般分为下面几个:
1. 变量和数据类型 2. 流程控制方法 3. 函数声明和调用 4. 面向对象 5. 语言特性 6. 标准库
Go语言中的面向对象有点特殊。在Go语言里面,没有显式的class、extends等面向对象语言经常使用的关键词,但是却有面向对象的特性。看看Go怎么实现的把!
创建一个类
按照我的理解,类实际上就是某种模板,这个模板里面含有有限多个属性和方法。在Go里面,定义这个模板的语法使用type来实现!
比如单个int类型可以构成一个类(没错,你甚至可以在int数据类型上定义一些操作)
type num int
比如某几种复合类型构成的类
type human struct { name string age int weight int stature int ... }
上面的type姑且称之为类,方法之后会通过某种方式绑定在这个类上!
创建一个方法
函数声明的时,在其前方增加一个特定类型的变量,即创建了一个属于这个变量类型的方法。
这个特定类型的变量称为方法接收器(Receiver),可以类比为一些面向对象语言里面的this,self!通常的做法是用类型的首字母作为方法接收器的名字。
比如下面的一个例子,在函数toAbs前面增加一个命名为n的num类型变量,即在num类型上增加了一个toAbs方法。
package main
import "fmt"
type num int
// 在toAbs函数前增加一个变量n,就定一个num类型上的方法了!
func (n num) toAbs() num {
if n > 0 {
return n
} else {
n = -n
return n
}
}
func main() {
// 调用上面的toAbs方法
var i num = -1
fmt.Printf("调用toAbs返回 %d\n", i.toAbs())
fmt.Printf("原来的i为 %d\n", i)
}
//上面将会输出
// 调用toAbs返回 1
// 原来的i为 -1
上面使用的是变量作为接收器,另一种做法是使用变量的指针作为方法接收器。这两者的区别很明显:使用变量作为接收器,方法内会对变量做拷贝,对变量的修改不影响外部变量,使用变量指针作为接收器,方法内不会对变量做拷贝,对变量的修改会影响外部变量。
package main
import "fmt"
type num int
// 在toAbs函数前增加一个变量n,就定一个num类型上的方法了!
func (n *num) toAbs() num {
if *n < 0 {
*n = -*n
}
return *n
}
func main() {
// 调用上面的toAbs方法
var i num = -1
fmt.Printf("调用toAbs返回 %d\n", i.toAbs())
fmt.Printf("原来的i为 %d\n", i)
}
// 上面输出
// 调用toAbs返回 1
// 原来的i为 1
使用指针作为接收器,将会改变原先的值!
继承
Go语言实际上是没有继承这种写法的,为了让类能够复用,Go使用了组合!
在设计模式中,“多用组合,少用继承”是一种非常常见的思想。Go语言干脆走向了一个极端,没有继承,只有组合。
无论是继承还是组合,本质上都是让代码能够更好地复用,让结构更加清晰。Go这种设计总体来讲还是利大于弊。
组合
接上面,接下来学习Go语言中类的组合。
来一个简单的例子,通过制作三明治的过程来看看组合是怎么用的
package main
import "fmt"
// 面包
type Bread struct {
}
// 培根
type Bacon struct {
}
//生菜
type Lettuct struct {
}
// 鸡蛋
type Egg struct {
}
// 通过组合的方法来做一个简单的三明治
type Sandwich struct {
bread Bread
bacon Bacon
lettuct Lettuct
egg Egg
}
func (b Bread) make() {
fmt.Println("将面包切片, 得到一些面包片")
}
func (b Bacon) make() {
fmt.Println("把培根放入面包片夹层中")
}
func (l Lettuct) make() {
fmt.Println("将生菜洗干净放入面包夹层")
}
func (e Egg) make() {
fmt.Println("将鸡蛋煎熟, 放入面包夹层")
}
func (s Sandwich) make() {
s.bread.make()
s.bacon.make()
s.lettuct.make()
s.egg.make()
fmt.Println("对着弄好的材料斜着切一下")
fmt.Println("得到两个三明治!")
}
func main() {
var sandwich Sandwich
sandwich.make()
}
上面的运行结果是
将面包切片, 得到一些面包片
把培根放入面包片夹层中
将生菜洗干净放入面包夹层
将鸡蛋煎熟, 放入面包夹层
对着弄好的材料斜着切一下
得到两个三明治!
封装
如果一个类里面的属性或者方法对外是不可见的,那么我们把这种做法叫做封装。Go语言只有一种封装的方法,那就是大写首字母字母开头标识符会被导出(公有),小写字母的则不会(私有),这个规则对struct也适用。
type Page struct {
title string
content string
}
// 这个结构体里面的title和content是不能被导出的
// 引用这个包直接对title和content赋值将会编译不通过
// 比如这个包叫page,那么可以直接给page.Page.title赋值将会编译失败
type Page struct {
Title string
Content string
}
//这个结构体里面的Title和Content是可以被导出的
// 在包外可以直接对Title和Content直接赋值
// 比如这个包叫page,那么可以直接给page.Page.Title赋值
接口
正如很多面向对象的语言一样,Go也拥有接口。接口实际上是一种契约,里面包含了一些必须要实现的方法,如果某个类实现了这个接口里面所有的方法,那么就称为这个类实现了这个接口。
我喜欢用实际生活中存在的事物去类比,比如锅盖。要怎么实现一个锅盖的接口,这个接口文字描述可以是:只要能够盖住一定大小范围内的锅,都是锅盖。至于用玻璃材质去实现还是用木头材质去实现还是其他材质,这个接口并不去关注。
接口的好处就是实现了这个接口的类可以互相替换,正如上面的锅盖,只要实现了锅盖接口,无论是铁的,玻璃的,木头的都可以互相替换。
Go语言的接口声明
package main
import "fmt"
// 声明一个锅盖接口
// 内有一个cover方法
// 只要实现了cover方法的类,都称可以称为锅盖
type PotCover interface {
cover()
}
type GlassCover struct {
}
func (g GlassCover) cover() {
fmt.Println("玻璃锅盖")
}
// 定义一个男人类型
type Man struct {
name string
}
func main() {
// 声明c是一个锅盖
var c PotCover
// 把玻璃锅盖赋值给c
// 玻璃锅盖实现了cover方法
// 因此是个锅盖
c = new(GlassCover)
c.cover()
// 男人没有cover方法,没有实现锅盖接口
// 这里将会编译出错
c = new(Man)
}
空接口
interface{} 称为空接口,空接口是一个很有用的接口,因为它没有实现任何方法。所以任意变量都可以赋值给空接口。
比如我们定义一个map[string]int,这个map的值只能是int类型,不管怎么定义,map的值看起来都只能是一个单一的类型。这里空接口就能让map的值支持任意类型。
i := make(map[string]interface{})
i["0"] = 1
i["1"] = "hello"
fmt.Println(i)
// map[0:1 1:hello]
同样,函数的参数也可以使用空接口,这允许我们传入任意类型的参数。比如fmt.Println的参数就是个空接口,可以传入任意类型的参数。
你可能还喜欢下面这些文章
这是我Go学习的第五篇笔记,学习的是go的语言的其他特性,这些特性是其他语言所不具备的。这次主要学习的是goroutine和channel。我的语言学习过程一般分为下面几个:1. 变量和数据类型2. 流程控制方法3. 函数声明和调用4. 面向对象5. 语言特性6. 常用标准库goroutine介绍和使用Go语言中,每个并发执行的单元称为goroutine(可类比线程)。当一个程序启动时候,main函数在一个main goroutine中运行。如果想要创建新的goroutine,使用go关键字!语法创建一个新的 goroutinechannel是goroutine的通信机制,比如创建一个能够接收
从程序员的角度来看, Shell本身是一种用C语言编写的程序,从用户的角度来看,Shell是用户与Linux操作系统沟通的桥梁。用户既可以输入命令执行,又可以利用 Shell脚本编程,完成更加复杂的操作。在Linux GUI日益完善的今天,在系统管理等领域,Shell编程仍然起着不可忽视的作用。深入地了解和熟练地掌握Shell编程,是每一个Linux用户的必修 功课之一。Linux的Shell种类众多,常见的有:Bourne Shell(/usr/bin/sh或/bin/sh)、Bourne Again Shell(/bin/bash)、C Shell(/usr/bin/csh)、K Shel
这是我的Go学习的第六篇笔记,也是Go入门的最后一篇笔记。在大多数语言中,了解了变量和数据类型,流程控制,函数,面向对象,再加上标准库,就可以用这门语言去写一些项目了。首先让我想想,在工作中通常会用语言频繁处理什么问题或者处理什么数据?最常见的应该是各种字符串操作,日期和时间,读写文件、socket等IO相关的操作!字符串处理 — StringsString提供了一组处理字符串的操作,常用的有:判断一个字符串是否在另一个字符串中分割字符串为[]string和组合[]string为一个字符串字符串替换...太多了,就不一一列举了,这里列出一些常用的字符串操作。字符串判断字符串分割与合并字符串转换
Excel宏使用的是vba,基本上就是运行在Excel里面的vb。所以学习vba和学习一门编程语言没有什么区别。所以我们最开始需要学的的就是一些基础语句。为了不让学习显得太枯燥,我们从一个hello world开始。首先需要打开Microsoft Excel,找到开发工具->宏,输入一个宏名称,点击创建创建了新的宏之后,就会出现一个编辑器界面Sub test()End Sub使用一个弹窗弹出hello worldSub test()MsgBox("hello world")End Sub到这里,一个简单的宏就创建完成了,虽然它现在什么也不能做,但是别着急,后面宏会为你做很多很多的事情,能
这是我Go学习笔记的第三篇!接下来学习的是Go的函数声明和调用。我的语言学习过程一般分为下面几个:1. 变量和数据类型2. 流程控制方法3. 函数声明和调用4. 面向对象5. 语言特性6. 标准库函数声明func 函数名称(参数表) 返回值类型 { // 函数体}写一个函数是非常简单的,掌握语法格式就可以了。函数是一个功能的封装,能让函数体内的代码得到很好的复用。比如我要输出个人信息,我可以把个人信息封装到函数里面,后续直接调用这个函数而不是每次都print一堆信息了上面定义的函数没有参数,也没有返回值,非常简单的一个函数。如果我想让姓名可变,那么可以定义一个带有参数的函数接下来定义一个有
赞赏微信赞赏支付宝赞赏