Go语言基础之10--面向对象编程2之方法
一、方法的定义
之前我们学习了结构体(struct),其仅仅是对数据的封装,并没有行为方法,还不是一个完全的面向对象的思路,所以现在我们来学习在结构体的基础上如何去定义一个方法。结构体(类)+方法=完整的面向对象
1.1 定义与声明
1)和其他语言不一样, Go的方法采用另外一种方式实现。
2)Go的方法是在函数前面加上一个接受者,这样编译器就知道这个方法属于哪个类型了。接受者是值类型还是指针类型也就决定了该方法是属于值类型还是指针类型。
3)定义一个类(结构体)里面的方法有两种:
第一种:值类型形式
第二种:指针类型形式
声明:
解释:
一般传入参数分为2部分:
1、func (a A)这里要传入的是类型的参数
2、Test(s string)这里要传入的是方法的参数
1.2 基于已有类型定义另外一种类型
具体见如下实例:
package main import (
"fmt"
) //用已经存在的一种类型,去定义另外一种类型。
//定义一个Integer 类型,是int64的别名(Integer是新定义的一个类型,其底层就是int64类型)
type Integer int64 //定义一个值类型为Integer的Print0方法
func (i Integer) Print0() {
fmt.Printf("i=%d\n", i)
} //定义一个指针类型为Integer的Set方法
func (i *Integer) Set(b int64) {
*i = Integer(b)
} func main() {
var a Integer
a = fmt.Printf("a=%v\n", a) var b int64 =
a = Integer(b) //a是Integer类型(新定义一个类型),虽然底层和b一样也是int64,但是此处还是需要强转一下,不然会报错。
fmt.Printf("a=%v\n", a) a.Print0()
a.Set() a.Print0()
}
执行结果如下图所示:
注意:
计算机中内存地址用16进制来表示。
1.3 函数和方法的区别
函数不属于任何类型,方法属于特定的类型
通过下面这个实例来更好理解:
解释:
我们定义一个int64类型的变量,但是int64并不能调用Print0方法,因为Print0方法是属于特定的Integer类型的。(如上图,已经标红,证明是有问题的。)
1.4 重点实例
代码结构如下:(包含main.go和util.go两个)
util.go的内容主要是为了掩饰同一个包不同文件去调取同一个类(结构体)。
main.go代码如下:
package main import (
"fmt"
) //1. 定义个Student的类型
type Student struct {
Name string
Age int
} //下面定义了类型为student的两个方法:Getname方法和Setname方法
//定义了一个值类型(完全拷贝副本)为Student的GetName方法
func (s Student) GetName() string { //s为变量,student为类型
return s.Name //return这里返回的是拷贝的那个副本的Name,其并没有修改,所以还是s5
} //定义了一个指针类型(拷贝的是地址)为Student的SetName方法
func (s *Student) SetName(name string) {
s.Name = name //在这里将s1.Name对应内存地址的值改为了s2,所以s1中的Name值由s5改为了s2
} func main() {
//2.定义一个类型为Student的s1变量
var s1 Student = Student{ //s1是Student类型的实例,所以其也可以调用Getname方法和Setname方法。
Name: "s5",
Age: ,
} name := s1.GetName() //执行时,s1传递给s,Getname方法中的s就相当于s1的拷贝,如果函数Getname()中有参数,就和函数传参是一样的了。
fmt.Printf("name=%s\n", name) //(&s1).SetName("s2") //正规写法
s1.SetName("s2") //这里直接写s1而不是&s1,是因为Setname方法发现student是一个指针类型,所以在s1这里,go语言就帮我们自动做了取址,这也就是为什么不会报错的原因。
//此处SetName有参数,s2,所以其就传递给SetName(name string)的name,所以name就是s2了。
//SetName方法的s相当于s1的拷贝,只不过这次传递的是内存地址
name = s1.GetName()
fmt.Printf("name=%s\n", name) s1.Print0() //调用main包的Print0方法
}
util.go内容如下:
package main import (
"fmt"
) //定义一个指针类型的Print0方法
func (s *Student) Print0() {
fmt.Printf("student:%#v", *s)
}
执行结果如下图所示:
注意:写项目时尽量将同一个功能的类和方法放在一个文件中,这样比较好维护,如果都混搭在一块,不仅混乱还难维护。
二、 值类型和指针类型
2.1 指针类型和值类型作为接受者的区别?
值类型可以作为接受者,同样指针类型也可以作为接受者,如下图所示:
具体区别:
1、值类型是要拷贝的是结构体字段值的副本,针对该副本值的修改,原有结构体里面字段的值的修改是不会生效的;
2、要修改结构体里面的字段,必须要用指针类型(指针类型拷贝的是内存地址)
2.2 什么时候用值类型/指针类型作为接受者?
A. 需要修改接受者中的值的时候
B. 接受者是大对象的时候,副本拷贝代价比较大
C. 一般来说,通常使用指针类型作为接受者
下面我们来通过一个实例来进行验证当值较大时,值类型和指针类型的耗时,进而验证上述理论
当接受者为值类型:
package main import (
"fmt"
"time"
) type User struct {
s1 []int64
s2 []int64
s3 []int64
s4 []int64
} func (u User) Set() {
for i := ; i < len(u.s1); i++ {
u.s1[i] =
u.s2[i] =
u.s3[i] =
u.s4[i] =
}
} func main() {
var u *User = new(User) //结构体初始化 start := time.Now().UnixNano()
u.Set()
end := time.Now().UnixNano() fmt.Printf("cost:%d ns", (end-start)/)
}
执行结果:
当接受者为指针类型:
package main import (
"fmt"
"time"
) type User struct {
s1 []int64
s2 []int64
s3 []int64
s4 []int64
} func (u *User) Set() {
for i := ; i < len(u.s1); i++ {
u.s1[i] =
u.s2[i] =
u.s3[i] =
u.s4[i] =
}
} func main() {
var u *User = new(User) //结构体初始化 start := time.Now().UnixNano()
u.Set()
end := time.Now().UnixNano() fmt.Printf("cost:%d ns", (end-start)/)
}
执行结果如下:
结论:可以发现接受者作为指针类型耗时明显少于接受者作为值类型。
三、面向对象和继承
3.1 匿名结构体与继承
之前已经学会了数据的继承,接下来我们来学习一下方法的继承。
注意:继承并不是一个实例的继承,而是一个类型的继承。
正常的匿名结构体数据继承:
type Animal struct {
Name string
} type People struct {
Sex string
Age int
Animal //or *Animal
}
方法的继承示例如下:
package main import (
"fmt"
) type Animal struct {
Name string
Age int
} func (a *Animal) SetName(name string) {
a.Name = name
} func (a *Animal) SetAge(age int) {
a.Age = age
} func (a *Animal) Print() {
fmt.Printf("a.name=%s a.age=%d\n", a.Name, a.Age)
} type Birds struct {
*Animal //继承Animal,正常是a *Animal 那么a如果要用a里面的字段就需要初始化,现在我们是匿名字段,所以字段名和类型名就都是*Animal,所以如果下面要使用的话,必须要初始化
//指针类型要使用必须要初始化
} func (b *Birds) Fly() {
fmt.Printf("name %s is fly\n", b.Name) //使用继承的Animal中的Name字段
} func main() {
var b *Birds = &Birds{
Animal: &Animal{}, //针对结构体指针进行初始化(分配内存),不初始化,就是一个空内存地址,程序就会崩溃,字段名是继承的Animal,初始化值为Animal类型所在的内存地址。
}
b.SetName("bird") //SetName是Animal的方法,Bird继承了Setname,所以其也有了其父类bird的方法
b.SetAge() //SetAge同理 b.Fly() //SetFly同理
b.Print()
}
执行结果如下图所示:
注意:继承并不是一个实例的继承,而是一个类型的继承。
比如说如下这个例子,环境还是上述的例子:
var a Animal
var b Birds
a.Name = "birds" fmt.Printf("b.Name:%s\n",b)
解释:Birds继承Animal类型中的Name和Age字段,a和b是两个不同变量,继承仅仅是类型的继承,并不是实例的继承,a和b是两个相互独立的东西,相互不影响的不同实例,不能说b的Birds继承a的Animal,b就有了a中的东西,b只是有了a中的字段(Name和Age),也就是继承了a的类型,具体实例化时还要为其赋值,所以a.Name为birds,b.Name肯定不是birds了
3.2 多重继承与冲突解决
不推荐多重继承
type Mother struct {
Name string
} type Father struct {
Name string
} type People struct {
Sex string
Age int
*Mother
*Father
}
比如说对于继承来说,Mother和Father中都有Name字段,而People又继承了Mother和Father类型,所以要引用Mother中的Name字段就要写全了。比如:.Mother.Name.
多重继承的话,方法也是一样,调取路径写全即可调用,但是多重继承不推荐,建议少用。
四、结构体和json序列化
4.1 序列化
结构体序列化:结构体转成json数据格式
package main import (
"encoding/json"
"fmt"
) type Animal struct {
Name string
Age int
} func (a *Animal) SetName(name string) {
a.Name = name
} func (a *Animal) SetAge(age int) {
a.Age = age
} func (a *Animal) Print() {
fmt.Printf("a.name=%s a.age=%d\n", a.Name, a.Age)
} type Birds struct {
*Animal //继承Animal,正常是a *Animal 那么a如果要用a里面的字段就需要初始化,现在我们是匿名字段,所以字段名和类型名就都是*Animal,所以如果下面要使用的话,必须要初始化
//指针类型要使用必须要初始化
} func (b *Birds) Fly() {
fmt.Printf("name %s is fly\n", b.Name) //使用继承的Animal中的Name字段
} func main() {
var b *Birds = &Birds{
Animal: &Animal{}, //针对结构体指针进行初始化(分配内存),不初始化,就是一个空内存地址,程序就会崩溃,字段名是继承的Animal,初始化值为Animal类型所在的内存地址。
}
b.SetName("bird") //SetName是Animal的方法,Bird继承了Setname,所以其也有了其父类bird的方法
b.SetAge() //SetAge同理 b.Fly() //SetFly同理
b.Print() //json.Marshal是序列化的方法
data, err := json.Marshal(b) //序列化成json时,会返回一个byte数组(其中值为json序列化后的值),和error,字节数组可以返回到一个文件中,或者返回给调用方,调用方在反序列化为其支持的数据类型,这样我们不同语言写的程序就可以通信了。
fmt.Printf("marshal result:%s err:%v\n", string(data), err)
}
执行结果如下图所示:
4.2 反序列化
结构体反序列化: json数据格式转成结构体
就是把上述例子中的序列化后的json数据序列化为结构体
package main import (
"encoding/json"
"fmt"
) type Animal struct {
Name string
Age int
} func (a *Animal) SetName(name string) {
a.Name = name
} func (a *Animal) SetAge(age int) {
a.Age = age
} func (a *Animal) Print() {
fmt.Printf("a.name=%s a.age=%d\n", a.Name, a.Age)
} type Birds struct {
*Animal //继承Animal,正常是a *Animal 那么a如果要用a里面的字段就需要初始化,现在我们是匿名字段,所以字段名和类型名就都是*Animal,所以如果下面要使用的话,必须要初始化
//指针类型要使用必须要初始化
} func (b *Birds) Fly() {
fmt.Printf("name %s is fly\n", b.Name) //使用继承的Animal中的Name字段
} func main() {
var b *Birds = &Birds{
Animal: &Animal{}, //针对结构体指针进行初始化(分配内存),不初始化,就是一个空内存地址,程序就会崩溃,字段名是继承的Animal,初始化值为Animal类型所在的内存地址。
}
b.SetName("bird") //SetName是Animal的方法,Bird继承了Setname,所以其也有了其父类bird的方法
b.SetAge() //SetAge同理 b.Fly() //SetFly同理
b.Print() data, err := json.Marshal(b)
fmt.Printf("marshal result:%s err:%v\n", string(data), err) var c Birds //定义一个新结构体c,因为反序列后就是给c
//反序列化用json.Unmarshal方法
err = json.Unmarshal(data, &c) //需要传入第一个json字符数组,第二个是一个空的接口(就是反序列化后谁来接受),也就是任意类型都可以(因为我们之后要反序列化后的格式并不确定,各种类型都有可能),并且这里必须要传入地址&c,因为Birds结构体是值类型,要想真正修改c,必须要传地址。
fmt.Printf("c:%#v, err:%v\n", c.Animal, err)
}
执行结果如下图所示:
Go语言基础之10--面向对象编程2之方法的更多相关文章
- Go语言基础之接口(面向对象编程下)
1 接口 1.1 接口介绍 接口(interface)是Go语言中核心部分,Go语言提供面向接口编程,那么接口是什么? 现实生活中,有许多接口的例子,比如说电子设备上的充电接口,这个充电接口能干什么, ...
- C# 篇基础知识3——面向对象编程
面向过程的结构化编程,例如1972年美国贝尔研究所推出的C语言,这类编程方式重点放在在定函数上,将较大任务分解成若干小任务,每个小任务由函数实现,分而治之的思想,然而随着软件规模的不断扩张,软件的复杂 ...
- R语言基于S4的面向对象编程
前言 本文接上一篇文章 R语言基于S3的面向对象编程,本文继续介绍R语言基于S4的面向对象编程. S4对象系统具有明显的结构化特征,更适合面向对象的程序设计.Bioconductor社区,以S4对象系 ...
- Java基础教程:面向对象编程[1]
Java基础教程:面向对象编程 内容大纲 Java语言概述 Java语言特点 1.Java为纯面向对象的语言,它能够直接反映现实生活中的对象.总之,Everything is object! 2.平台 ...
- [.net 面向对象编程基础] (2) 关于面向对象编程
[.net 面向对象编程基础] (2) 关于面向对象编程 首先是,面向对象编程英文 Object-Oriented Programming 简称 OOP 通俗来说,就是 针对对象编程的意思 那么问 ...
- Java基础教程:面向对象编程[2]
Java基础教程:面向对象编程[2] 内容大纲 访问修饰符 四种访问修饰符 Java中,可以使用访问控制符来保护对类.变量.方法和构造方法的访问.Java 支持 4 种不同的访问权限. default ...
- Java基础教程:面向对象编程[3]
Java基础教程:面向对象编程[3] 内容大纲 基础编程 获取用户输入 java.util.Scanner 是 Java5 的新特征,我们可以通过 Scanner 类来获取用户的输入.我们可以查看Ja ...
- 带你学够浪:Go语言基础系列 - 10分钟学方法和接口
文章每周持续更新,原创不易,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) 对于一般的语言使用者来说 ,20% 的语言特性就能够满 ...
- Python学习--10 面向对象编程
面向对象编程--Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. 本节对于面向对象的概念不做 ...
- go语言之进阶篇面向对象编程
1.面向对象编程 对于面向对象编程的支持Go 语言设计得非常简洁而优雅.因为, Go语言并没有沿袭传统面向对象编程中的诸多概念,比如继承(不支持继承,尽管匿名字段的内存布局和行为类似继承,但它并不是继 ...
随机推荐
- sublime3 There are no packages available for installation
我的是网上下载的绿色版 1.找到sublime\Data\Packages 删除Packages control相关的文件夹和文件 下载https://packagecontrol.io/Packa ...
- 玩转Jquery
一 jquery简介 1 jquery是什么 jQuery由美国人John Resig创建,至今已吸引了来自世界各地的众多 javascript高手加入其team. jQuery是继prototype ...
- android 中context的具体作用和意义
context在android中是非常重要的一个类,此类一般用于activity之中 从字面意思来看,这是环境变量,内部实现了一些方法,但是此类也可以看做是一个句柄,用来唯一标示activity 举个 ...
- 深入理解asp.net中的 __doPostBack函数
前段时间做一个.net网站的时候,用到了模拟前端按钮刷新updatePanel进行局部刷新的时候,遇见了这个问题,当时没顾上记下来,查看网上资料,记下来留着以后查看. 很早以前,当我刚接触asp.NE ...
- vue 跨域访问http
axios用法: npm install axios --save-dev 2.导入: import axios from 'axios'; 3.使用($(form)需要先按装jQuery) axio ...
- cocos2dx中替代goto的用法:do{}while(0)和CC_BREAK_IF
我们时常会调用某个函数来创建一个对象,但由于内存不足或其他异常情况发生时对象可能会创建失败,创建失败我们就要结束当前程序转到错误处理地方去处理错误或释放已生成的对象. int* p1 = new in ...
- function几种自执行的形式
1.(function(){})();这种是最常用的形式 2.var t = function(){}(); 3.-function(){}(); 这三种形式都能自执行
- Zbar和Z*算法对比
博客转载自:https://blog.csdn.net/qishandaxue/article/details/45481387 移植zbar和zxing源码到linux平台,zbar移植的是C源码, ...
- wpf textblock超出显示范围后显示tooltip
public static class TextTrmmingShowToolTip { public static readonly DependencyProperty IsToolTipProp ...
- WPARAM和LPARAM的含义
lParam 和 wParam 是宏定义,一般在消息函数中带这两个类型的参数,通常用来存储窗口消息的参数. LRESULT CALLBACK WindowProc(HWND hwnd, UINT uM ...