Day_04 面向对象
概述
对于面向对象编程的支持,Go语言设计得非常简洁而优雅。因为,Go语言并没有沿袭传统
面向对象编程中的诸多概念,比如继承(不支持继承,尽管匿名字段的内存布局和行为类似继承,但它并不是继承)、
虚函数、构造函数和析构函数、隐藏的this指针等等 尽管Go语言中没有封装、继承、多态这些概念,但同样通过别的方式实现这些特性:
封装:通过方法实现
继承:通过匿名字段实现
多态:通过接口实现 接口概述:
在Go语言中,接口(interface)是一个自定义类型,接口类型具体描述了一系列方法的集合 接口类型是一种抽象的类型,它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合,
它们只会展示出它们自己的方法。因此接口类型不能将其实例化。 Go通过接口实现了鸭子类型(duck-typing):“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子、那么这只鸟就可以被称为鸭子了”
我们并不关心对象是什么类型,到底是不是鸭子,只关心行为
01.匿名字段初始化
//匿名字段体现的是面向对象的继承特点 package main import "fmt" type Person struct {
name string //名字
sex byte //性别
age int //年龄 } type Student struct {
Person //只有类型,没有名字,匿名字段,继承了Person的成员
id int
addr string
} func main() {
//顺序初始化
var s1 Student = Student{Person{"Mike", 'm', 18}, 1, "bj"}
fmt.Println("s1 = ", s1) //自动推导类型
s2 := Student{Person{"Jason", 'f', 19}, 2, "sz"}
//%+v, 显示更详细
fmt.Println("s2普通输出 = ", s2)
fmt.Printf("s2详细输出 = %+v\n", s2) //指定成员初始化,没有初始化的成员自动赋值为0(int型),其他类型的为空
s3 := Student{id: 1}
fmt.Printf("s3详细输出 = %+v\n", s3) s4 := Student{Person: Person{name: "Nicole"}, id: 88}
fmt.Printf("s4详细输出 = %+v\n", s4) //s5 := Student{"Hanson",'m', 19,77,"sh"} //这种写法是错误的,编不过 }
02.成员的操作
package main import "fmt" type Person struct {
name string //名字
sex byte //性别
age int //年龄 } type Student struct {
Person //只有类型,没有名字,匿名字段,继承了Person的成员
id int
addr string
} func main() {
s1 := Student{Person{"mike", 'm', 18}, 1, "bj"}
s1.name = "yoyo"
s1.sex = 'f'
s1.age = 22
s1.id = 666
s1.addr = "sz" fmt.Println(s1.name, s1.sex, s1.age, s1.id, s1.addr) s1.Person = Person{"go", 'm', 19}
fmt.Println(s1.name, s1.sex, s1.age, s1.id, s1.addr) }
03.同名字段
package main import "fmt" type Person struct {
name string //名字
sex byte //性别
age int //年龄 } type Student struct {
Person //只有类型,没有名字,匿名字段,继承了Person的成员
id int
addr string
name string //和Person里面的string同名了
} func main() {
//声明(定义一个变量)
var s Student //默认规则(就近原则),如果能在本作用域找到此成员,就操作此成员
// 如果没有找到,找到继承的字段
s.name = "mike" //s = {Person:{name: sex:102 age:18} id:0 addr:gz name:mike} 可以看到,操作的是student的name,还是Person的name?
s.sex = 'f'
s.age = 18
s.addr = "gz" //显式调用
s.Person.name = "yoyo" //Person的name fmt.Printf("s = %+v\n", s) }
04.非结构体匿名字段
package main import "fmt" type mystr string //自定义类型,给一个类型改名 type Person struct {
name string //名字
sex byte //性别
age int //年龄 } type Student struct {
Person //结构体匿名字段
int
mystr //基础类型的匿名字段
} func main() {
s := Student{Person{"mike", 'm', 18}, 666, "hehehehe"}
fmt.Printf("s = %+v\n", s) s.Person = Person{"go", 'm', 22}
fmt.Println(s.name, s.age, s.sex, s.int, s.mystr)
fmt.Println(s.Person, s.int, s.mystr)
}
05.结构体指针类型匿名字段
/*
方法体现的是面向对象的封装特点 在面向对象编程中,一个对象其实就是一个简单的值或者一个变量,在这个对象中会包含
一些函数,这种带有接收者的函数,我们称为方法(method)。本质上,一个方法则是一个和特殊类型关联的函数 一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对象的用户就不需要直接取操作对象,
而是借助方法来做这些事情。
在Go语言中,可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法。 方法总是绑定对象实例,并隐式将实例作为第一参数(receiver),方法的语法如下:
func (receiver ReceiverType) funcName(parameters) (results) */ package main import "fmt" type Person struct {
name string //名字
sex byte //性别,字符类型
age int //年龄
} type Student struct {
*Person //指针类型
id int
addr string
} func main() {
s := Student{&Person{"mike", 'm', 18}, 666, "gz"}
fmt.Println(s.name, s.sex, s.age, s.id, s.addr) //先定义变量
var s1 Student
s1.Person = new(Person) //分配空间
s1.name = "yoyo"
s1.sex = 'm'
s1.age = 18
s1.id = 222
s1.addr = "sz" fmt.Println(s1.name, s1.sex, s1.age, s1.id, s1.addr) }
06.面向过程和对象函数的区别
/*
方法体现的是面向对象的封装特点 在面向对象编程中,一个对象其实就是一个简单的值或者一个变量,在这个对象中会包含
一些函数,这种带有接收者的函数,我们称为方法(method)。本质上,一个方法则是一个和特殊类型关联的函数 一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对象的用户就不需要直接取操作对象,
而是借助方法来做这些事情。
在Go语言中,可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法。 方法总是绑定对象实例,并隐式将实例作为第一参数(receiver),方法的语法如下:
func (receiver ReceiverType) funcName(parameters) (results) */
package main import "fmt" //实现2数相加
//面向过程
func Add01(a, b int) int {
return a + b
} //面向对象,方法:给某个类型绑定一个函数
type long int //tmp叫接收者,接收者就是传递的一个参数
func (tmp long) Add02(other long) long {
return tmp + other
} func main() {
var result int
result = Add01(1, 1) //普通函数调用方式
fmt.Println("面向过程:result = ", result) //定义一个变量
var a long = 2
//调用方法格式:变量名.函数(所需参数)
result02 := a.Add02(3)
fmt.Println("面向对象:result02 = ", result02) // 面向对象只是换了一种表现形式 }
07.为结构体类型添加方法
package main import "fmt" type Person struct {
name string //名字
sex byte //性别,字符类型
age int //年龄
} //带有接收者的函数叫方法
//Person为接收者类型,它本身不能是指针类型
func (tmp Person) PrintInfo() {
fmt.Println("tmp = ", tmp) } func (p *Person) SetInfo(n string, s byte, a int) {
p.name = n
p.sex = s
p.age = a
} //接收者只要类型不一样,它就是不同的方法,就算同名也没有关系,不会出现重复定义函数的错误
type long int func (tmp long) test() { } type char byte func (tmp char) test() { } func main() {
//定义同时初始化
p := Person{"mike", 'm', 19}
p.PrintInfo() //定义一个结构体变量
var p2 Person
(&p2).SetInfo("yoyo", 'f', 22)
p2.PrintInfo()
}
08.值语义和引用语义
package main import "fmt" type Person struct {
name string //名字
sex byte //性别,字符类型
age int //年龄
} //修改成员变量的值 //参数为普通变量,非指针,值语义,一份拷贝
func (tmp Person) PrintInfo() {
fmt.Println("tmp = ", tmp)
} func (p Person) SetInfoValue(n string, s byte, a int) {
p.name = n
p.sex = s
p.age = a fmt.Println("p = ", p)
fmt.Printf("SetInfoValue &p = %p\n", &p) } //接收者为指针变量,引用传递
func (p *Person) SetInfoPointer(n string, s byte, a int) {
p.name = n
p.sex = s
p.age = a fmt.Printf("SetInfoPointer p = %p\n", p) } func main() {
s1 := Person{"go", 'f', 22}
fmt.Printf("&s1 = %p\n", &s1) //打印地址 //值语义
s1.SetInfoValue("Mike", 'm', 16)
fmt.Println("s1 = ", s1) //打印内容 //引用语义
//(&s1).SetInfoPointer("Jason", 'f', 66)
//fmt.Println("s1 = ", s1) //打印内容 } /*
值语义结果:
&s1 = 0xc000004460
p = {Mike 109 16}
SetInfoValue &p = 0xc0000044a0
s1 = {go 102 22}
总结:值语义只是一份拷贝,里面的修改不会影响到外面 引用语义结果:
&s1 = 0xc000058420
SetInfoPointer p = 0xc000058420
s1 = {Jason 102 66}
总结:引用语义从内存地址可以看出操作的是同一份,里面的修改会影响到外面 */
09.指针变量的方法集
package main import "fmt" type Person struct {
name string //名字
sex byte //性别,字符类型
age int //年龄
} func (p Person) SetInfoValue() {
fmt.Println("SetInfoValue\n") } func (p *Person) SetInfoPointer() {
fmt.Printf("SetInfoPointer\n")
} func main() {
//结构体变量是一个指针变量,它能够调用哪些方法,这些方法就是一个集合,简称方法集
p := &Person{"mike", 'f', 18} p.SetInfoPointer() //func (p *Person) SetInfoPointer()调用了这个方法
(*p).SetInfoPointer() //把(*p)转换成p后再调用,等价于上面 //内部自己做了转换,先把指针p,转成*p后再调用
//(*p).SetInfoValue
p.SetInfoValue() //func (p Person) SetInfoValue() 可以发现它相当灵活,这个非指针类型的也可以调用 } /*
总结:
类型的方法集是指可以被该类型的值调用的所有方法的集合
用实例value和pointer调用方法(含匿名字段)不受方法集约束,编译器会总是查找全部方法,并自动转换receiver实参。
你不用关心方法是否是指针,都可以相互调用
*/
10.普通变量的方法集
package main import "fmt" type Person struct {
name string //名字
sex byte //性别,字符类型
age int //年龄
} func (p Person) SetInfoValue() {
fmt.Println("SetInfoValue\n") } func (p *Person) SetInfoPointer() {
fmt.Printf("SetInfoPointer\n")
} func main() {
//普通变量调指针
p := Person{"mike", 'f', 18}
p.SetInfoPointer() //发现也能够成功调用
//内部先把p转为&p再调用,(&p).SetInfoPointer() }
11.方法的继承
package main import "fmt" type Person struct {
name string //名字
sex byte //性别,字符类型
age int //年龄 } //Person类型,实现了一个方法
func (tmp *Person) PrintInfo() {
fmt.Printf("name=%s,sex=%c,age=%d\n", tmp.name, tmp.sex, tmp.age)
} //有个学生,继承了Person字段,成员和方法都继承了
type Student struct {
Person //匿名字段
id int
addr string
} func main() {
s := Student{Person{"mike", 'm', 18}, 666, "bj"}
s.PrintInfo() }
12.方法的重写
package main import "fmt" type Person struct {
name string //名字
sex byte //性别,字符类型
age int //年龄 } //Person类型,实现了一个方法
func (tmp *Person) PrintInfo() {
fmt.Printf("name=%s,sex=%c,age=%d\n", tmp.name, tmp.sex, tmp.age)
} //有个学生,继承了Person字段,成员和方法都继承了
type Student struct {
Person //匿名字段
id int
addr string
} //Student也实现了一个方法,这个方法和Person方法同名,这种方法叫重写
func (tmp *Student) PrintInfo() {
fmt.Println("Student: tmp = ", tmp)
} func main() {
s := Student{Person{"mike", 'm', 18}, 666, "bj"}
//就近原则:先找本作用域的方法,找不到再用继承的方法
s.PrintInfo() //到底调用的是Person,还是Student,结论是Student //显式调用继承的方法
s.Person.PrintInfo() }
13.方法值
package main import "fmt" type Person struct {
name string
sex byte
age int
} func (p Person) SetInfoValue() {
fmt.Printf("SetInfoValue: %p,%v\n", &p, p)
} func (p *Person) SetInfoPointer() {
fmt.Printf("SetInfoPointer:%p,%v\n", p, p) } func main() {
p := Person{"mike", 'f', 19}
fmt.Printf("main: %p, %v\n", &p, p) p.SetInfoPointer() // 传统调用方式 //保存方式入口地址,这样之后其他需要调用的地方就可以直接调用
pFunc := p.SetInfoPointer //这个就是方法值,调用函数时,无需再传递接收者,隐藏了接收者
pFunc() //等价于 p.SetInfoPointer() vFunc := p.SetInfoValue
vFunc() //等价于p.SetInfoValue() }
14.方法表达式
package main import "fmt" type Person struct {
name string // 名字
sex byte // 性别,字符类型
age int // 年龄
} func (p Person) SetInfoValue() {
fmt.Printf("SetInfoValue: %p,%v\n", &p, p)
} func (p *Person) SetInfoPointer() {
fmt.Printf("SetInfoPointer:%p,%v\n", p, p) } func main() {
p := Person{"mike", 'f', 19}
fmt.Printf("Main: %p,%v\n", &p, p) //方法值: f := p.SetInfoPointer // 隐藏了接收者
//方法表达式
f := (*Person).SetInfoPointer
f(&p) //显式把接收者传递过去========>p.SetInfoPointer() f2 := (Person).SetInfoValue
f2(p) //显式把接收者传递过去========>p.SetInfoValue()
}
15.接口的定义和实现
package main import "fmt" //定义接口类型
type Humaner interface {
//方法,只有声明,没有实现,由别的类型(自定义类型)实现
sayhi()
} type Student struct {
name string
id int
} //Student实现了此方法
func (tmp *Student) sayhi() {
fmt.Printf("Student[%s,%d] sayhi\n", tmp.name, tmp.id)
} type Teacher struct {
addr string
group string
} //Teacher实现了此方法
func (tmp *Teacher) sayhi() {
fmt.Printf("Teacher[%s,%s] sayhi\n", tmp.addr, tmp.group)
} type Mystr string //Mystr实现了此方法
func (tmp *Mystr) sayhi() {
fmt.Printf("Mystr[%s] sayhi\n", *tmp)
} //定义一个普通函数,函数的参数为接口类型
//只有一个函数,可以有不同表现,多态
func WhoSayHi(i Humaner) {
i.sayhi()
}
func main() {
s := &Student{"Jason", 777}
t := &Teacher{"bj", "go"}
var str Mystr = "Hello Mike"
//调用同一函数,不同表现,多态,多种形态
WhoSayHi(s)
WhoSayHi(t)
WhoSayHi(&str) //创建一个切片
x := make([]Humaner, 3)
x[0] = s
x[1] = t
x[2] = &str //第一个返回下标,第二个返回下标所对应的值
for _, i := range x {
i.sayhi() } } func main_bak() {
//定义接口类型的变量
var i Humaner //只要实现了此接口方法的类型,那么这个类型的变量(接收者类型)就可以给i赋值
s := &Student{"Jason", 777}
i = s
i.sayhi() t := &Teacher{"bj", "go"}
i = t
i.sayhi() var str Mystr = "Hello Mike"
i = &str
i.sayhi() } /*
Student[Jason,777] sayhi
Teacher[bj,%!d(string=go)] sayhi
Mystr[Hello Mike] sayhi 最终发现都是调用的同一个Human接口,却是不同表现
这就是Go语言里的接口(实现的是面向对象的多态特性) */
16.接口的继承
package main import "fmt" type Humaner interface { //子集
sayhi()
}
type Personer interface { //超集
Humaner //匿名字段,继承了Humaner的sayhi方法
sing(lrc string)
} type Student struct {
name string
id int
} //Student实现了sayhi()方法
func (tmp *Student) sayhi() {
fmt.Printf("Student[%s,%d] sayhi\n", tmp.name, tmp.id)
} func (tmp *Student) sing(lrc string) {
fmt.Println("Student在唱着:", lrc)
} func main() {
//定义一个接口类型的变量
var i Personer
s := &Student{"Jason", 666}
i = s
i.sayhi() //这个是继承过来的方法
i.sing("好高兴好高兴")
}
17.接口转换
package main import "fmt" type Humaner interface { //子集
sayhi()
}
type Personer interface { //超集
Humaner //匿名字段,继承了Humaner的sayhi方法
sing(lrc string)
} type Student struct {
name string
id int
} //Student实现了sayhi()方法
func (tmp *Student) sayhi() {
fmt.Printf("Student[%s,%d] sayhi\n", tmp.name, tmp.id)
} //Student实现了sing()方法
func (tmp *Student) sing(lrc string) {
fmt.Println("Student在唱着:", lrc)
} func main() {
//子集可以转换为超集,反过来不可以
var iPro Personer //超集
iPro = &Student{"Jason", 777}
var i Humaner //子集 //iPro = i //err,因为超集不能转换为子集
i = iPro // 可以,子集可以转换为超集
i.sayhi() }
18.空接口
//空接口(interface{})不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值,它有点类似于C语言的void *类型。 package main import "fmt" func xxx(arg ...interface{}) {
//fmt函数就是对空接口的典型应用
} func main() {
//空接口万能类型,保存任意类型的值
var i interface{} = 1
fmt.Println("i = ", i) i = "abc"
fmt.Println("i = ", i) }
19.类型断言:if
package main import "fmt" type Student struct {
name string
id int
} func main() {
i := make([]interface{}, 3)
i[0] = 1 //int
i[1] = "Hello Go" //string
i[2] = Student{"mike", 666} //Student //类型查询,类型断言
//第一个返回下标,第二个返回下标对应的值,data分别是i[0],i[1],i[2]
for index, data := range i {
//第一个返回的是值,第二个返回判断结果的真假
if value, ok := data.(int); ok == true {
fmt.Printf("x[%d]类型为int,内容为%d\n", index, value)
} else if value, ok := data.(string); ok == true {
fmt.Printf("x[%d]类型为string,内容为%s\n", index, value)
} else if value, ok := data.(Student); ok == true {
fmt.Printf("x[%d]类型为Student,内容为name = %s,id = %d\n", index, value.name, value.id)
}
} }
20.类型断言:switch
package main import "fmt" type Student struct {
name string
id int
} func main() {
i := make([]interface{}, 3)
i[0] = 1 //int
i[1] = "Hello Go" //string
i[2] = Student{"mike", 666} //Student //类型查询,类型断言,推荐用这种
for index, data := range i {
switch value := data.(type) {
case int:
fmt.Printf("x[%d] 类型为int,内容为%d\n", index, value)
case string:
fmt.Printf("x[%d] 类型为string,内容为%s\n", index, value)
case Student:
fmt.Printf("x[%d] 类型为Student,内容为name = %s,id = %d\n", index, value.name, value.id)
}
}
}
Day_04 面向对象的更多相关文章
- angular2系列教程(六)两种pipe:函数式编程与面向对象编程
今天,我们要讲的是angualr2的pipe这个知识点. 例子
- 一起学 Java(二)面向对象
一.方法函数 函数也称为方法,就是定义在类中的具有特定功能的一段独立代码.用于定义功能,提高代码的复用性. 函数的特点1> 定义函数可以将功能代码进行封装,便于对该功能进行复用:2> 函数 ...
- js面向对象学习 - 对象概念及创建对象
原文地址:js面向对象学习笔记 一.对象概念 对象是什么?对象是“无序属性的集合,其属性可以包括基本值,对象或者函数”.也就是一组名值对的无序集合. 对象的特性(不可直接访问),也就是属性包含两种,数 ...
- 前端开发:面向对象与javascript中的面向对象实现(二)构造函数与原型
前端开发:面向对象与javascript中的面向对象实现(二)构造函数与原型 前言(题外话): 有人说拖延症是一个绝症,哎呀治不好了.先不说这是一个每个人都多多少少会有的,也不管它究竟对生活有多么大的 ...
- .NET 基础 一步步 一幕幕[面向对象之对象和类]
对象和类 本篇正式进入面向对象的知识点简述: 何为对象,佛曰:一花一世界,一木一浮生,一草一天堂,一叶一如来,一砂一极乐,一方一净土,一笑一尘缘,一念一清静.可见"万物皆对象". ...
- 简单分析JavaScript中的面向对象
初学JavaScript的时候有人会认为JavaScript不是一门面向对象的语言,因为JS是没有类的概念的,但是这并不代表JavaScript没有对象的存在,而且JavaScript也提供了其它的方 ...
- Java程序员应该了解的10个面向对象设计原则
面向对象设计原则: 是OOPS(Object-Oriented Programming System,面向对象的程序设计系统)编程的核心,但大多数Java程序员追逐像Singleton.Decorat ...
- JavaScript学习笔记(三)——this、原型、javascript面向对象
一.this 在JavaScript中this表示:谁调用它,this就是谁. JavaScript是由对象组成的,一切皆为对象,万物皆为对象.this是一个动态的对象,根据调用的对象不同而发生变化, ...
- 带你一分钟理解闭包--js面向对象编程
上一篇<简单粗暴地理解js原型链--js面向对象编程>没想到能攒到这么多赞,实属意外.分享是个好事情,尤其是分享自己的学习感悟.所以网上关于原型链.闭包.作用域等文章多如牛毛,很多文章写得 ...
随机推荐
- Spring Boot 2 + jpa + mysql例子
Spring Data框架为数据访问提供了一个通用的模型,无论访问哪种数据库,都可以使用同样的方式,主要有以下几个功能:(1)提供数据与对象映射的抽象层,同一个对象,可以被映射为不同数据库的数据:(2 ...
- ABP入门教程2 - 体系架构
点这里进入ABP入门教程目录 介绍 应用程序代码库的分层是一种广泛接受的技术,可帮助降低复杂性并提高代码可重用性.为了实现分层体系结构,ASP.NET Boilerplate遵循域驱动设计的原理. D ...
- Shell变量概述
目录 1. Shell变量概述 1.定义变量,变量名=变量值.不能出现"-横杠"命令 2.引用变量,$变量名 3.查看变量,set显示所有变量,包括自定义变量和环境变量 4.取消变 ...
- java 反编译工具 jd-gui
下载地址 http://java-decompiler.github.io/ 一般使用windows 版本 看你使用的操作系统了 解压 点击exe 进入 选择你编译后的cla ...
- 机器学习--支持向量机 (SVM)算法的原理及优缺点
一.支持向量机 (SVM)算法的原理 支持向量机(Support Vector Machine,常简称为SVM)是一种监督式学习的方法,可广泛地应用于统计分类以及回归分析.它是将向量映射到一个更高维的 ...
- git(1) 比较两个不同版本的文件
git diff commit_id1:file_name commit_id2:file_name 或者 git diff commit_id1 commit_id2 -- file_name co ...
- 【CentOS7】CentOS7各个版本镜像下载地址(转)
链接:https://www.cnblogs.com/caidingyu/p/10679422.html # CentOS7.6 下载地址 # CentOS-7-x86_64-DVD-18 ...
- 数据可视化-matplotlib包
pyplot官网教程https://matplotlib.org/users/pyplot_tutorial.html #导入matplotlib的pyplot模块 import matplotlib ...
- 关于scanf的一些知识
10.22,对现阶段已知道的scanf的一些用法或注意事项的一些总结: 1.scanf中,赋值的那个数据前面一定加&! 2.若情景要求必须输入空格的,scanf("%d%c%d&qu ...
- Python连载44-XML其他注意点
一.XML文件注意点 1.内容中不能出现尖括号 例如:下面是不合法的 <grade>成绩<90</grade> 解决方案:使用实体引用<EntityReferenc ...