第五篇:面向对象

面向对象

4.1 面向对象介绍

我们都知道,世间万物都是其属性和功能的集合。

我们人能够描述某种事物,那么计算机也得具备这样的功能。

面向对象说的及其简单和通俗的一点就是:用程序来描述事物的属性和功能

在这里插入图片描述

4.2结构体(struct)

我们人能够描述某种事物,那么计算机也得具备这样的功能。

在这里插入图片描述

这就引出了我们下面的知识点,结构体

4.2.1认识结构体

在这里插入图片描述

1、什么是结构体

结构体是一堆字段的集合,我可以存任意类型的数据,可以是数字、字符串、数组、也可以是另一个结构体。

思考:数组、切片、映射也都可以存一堆数据,为什么还要有结构体?

2、为什么要有结构

人能够描述世间万物,为了让计算机像人一样去工作,同样也需要某种机制来描述世间万物。

4.2.2结构体的定义

1、结构体的格式

struct {
    字段1 类型
    字段2 字段3 类型
    ...
}

2、声明一个结构体类型

我们使用type关键字来声明一个结构体类型

记住,记住,我们声明的只是一个类型!和int,string,slice一样,只是类型

type student struct{
    name string
    sex string
    id int
    score int
}

###4.2.3 实例化结构体

我们声明一个可以描述学生这个事物的数据结构,那么我们要描述某一个具体学生的时候,是不是得给字段赋值啊?这个赋值的操作叫做实例化

type student struct{
    name string
    sex string
    id int
    score int
}

func main(){
	var xiaoming = student {
		name:"小明",
		sex:"男",
		id:01,
		score:90,
	}
	fmt.Println(xiaoming)
}

//{小明 男 1 90}

如果我们实例化的时候不给字段赋值,那么每个字段就会使用初始值:

type student struct{
    name string
    sex string
    id int
    score int
}

func main(){
	var xiaoming = student {}    //var 变量名 = 类型{值}
	//var xiaoming student       //var 变量名 类型
	fmt.Println(xiaoming)
}

//{    0 0}

###4.2.4 访问实例字段

存不是目的,目的是取,那我们怎么取呢?我们通过实例.字段名去访问字段:

type student struct{
    name string
    sex string
    id int
    score int
}

func main(){
	var xiaoming student
	xiaoming.name = "小明"
	xiaoming.sex = "男"
	xiaoming.id = 1
	xiaoming.score = 90
	fmt.Println(xiaoming.name)
	fmt.Println(xiaoming)
}

//小明
//{小明 男 1 90}

4.2.5实例指针

我们把struct实例传给函数的时候,是值传递还是引用传递啊?试试憋

type student struct{
	name string
}

func test(aa student)  {
	aa.name = "试试就试试"
}

func main(){
	var xiaoming = student{
		name:"小明",
	}
	test(xiaoming)
	fmt.Println(xiaoming)
}

//{小明}

很明显,我的值没改掉,这货是值传递,传给函数的是个副本。

那指针就少不了了!

创建指针就那几种格式:

var aaa = &变量
var bbb = &类型{}
var ccc = new(类型)

超级简单有没有?!市面上的很多书都哔哔哒哒一大堆,其实真没啥?

我就拿数组和结构体来演示给你们看看有多容易:

//数组的例子:
var myArry = [2]int{1,2}

var pointer1 = &myArry                 //var aaa = &变量
var pointer2 = &[2]int{1,2}            //var bbb = &类型{值}
var pointer3 = new([2]int)             //var ccc = new(类型)

//结构体实例的例子
type student struct{
	name string
}
var xiaoming = student{name:"小明"}

var pointer1 = &xiaoming                 //var aaa = &变量
var pointer2 = &student{name:"小明"}      //var bbb = &类型{值}
var pointer3 = new(student)              //var ccc = new(类型)

通过指针访问实例字段

实例指针我们直接通过实例指针.字段名就可以访问其字段

在这里插入图片描述

4.2.6匿名结构体

匿名结构体顾名思义,就是没有名字的结构体。

形如下方代码的,叫做匿名结构体:

struct{
    字段1 类型
    字段2 类型
    ...
}

实例化匿名结构体:

var test1 = struct{
    name string
 	id int
}{
    name:"小明",
    id : 01,
}

//当然我们也可以不赋值,使用初始值
var test2 struct{
    name string
 	id int
}
//结果:{ 0}

匿名结构体正常多用来存储不同类型的数据,很少用来去描述一个事物

4.2.7 嵌套结构体

什么是嵌套结构体

很好理解啊,就是一个结构体套着另一个结构啊

为什么要有嵌套结构体

我可以用结构体来描述世间万物。

世间万物显然也是存在着嵌套关系的,比如果,我们用结构体描述了班级这个事物,班级这个事物里又有着老师和学生这两个事物。为了能够描述这种包含和被包含的关系,就需要嵌套结构体的存在了。

在这里插入图片描述

怎么用嵌套结构体

//首先,我需要三个结构体,分别是班级、老师、学生
type classes struct{
    className string
}

type teachers struct{
    teacherName string
}

type students struct{
    structName string
}

那我们是把classes放在teachers、students里,还是把teachers、students放在classes里啊?

显然的,把teachers、students放在classes里这种做法是不合理的,如果这样,我们每次实例化classes的时候,就必须要实例化一个老师和学生,难道一个班级就只有一个老师和一个学生吗?

所以,应该是把classes放在teachers、students里:

type classes struct{
    className string
}

type teachers struct{
    teacherName string
    class classes
}

type students struct{
    studentName string
    class classes
}

func main(){
    class1 := classes{
        className:"一年级一班",
    }
    teacher1 := teachers{
        teacherName:"mac",
        class:class1,
    }
    student1 := students{
        studentName:"小明",
        class:class1,
    }
    fmt.Println(teacher1)
    fmt.Println(student1)
}

//{mac {一年级一班}}
//{小明 {一年级一班}}

在这里插入图片描述

4.2.8 匿名字段

匿名字段很好理解,就是没有名字的字段

我们看看怎么用:

type students struct{
    studentName string
	int                              //只写类型,不写字段名
}

func main(){
    student1 := students{
        studentName:"小明",
        int:90,                     //赋值的时候,字段名就是类型名
    }
    fmt.Println(student1.int)       //访问的时候,直接 .类型名 访问
}

//结果:90

可是!!!

你这么写,你同事知道你的int表示的啥嘛?时间久了,你知道自己的int表示的啥嘛?这么写很快就会被同事打死!

匿名字段适用场景

匿名字段多用于嵌套关系,它的优点,我们往下看:

type classes struct{
    className string
}

type teachers struct{
    teacherName string
    classes                        //只写了类型
}

func main(){
    class1 := classes{
        className:"一年级一班",
    }
    teacher1 := teachers{
        teacherName:"mac",
        classes:class1,
    }
    //关键的地方来了
    fmt.Println(teacher1.className)
    //结果:"一年级一班"
}

//如果我只写类型名,那么我访问元素的时候,可以直接访问到该类型下的属性,就像访问自己的属性般,毫无违和感

##4.3方法

我们一开始就说了,世间万物都是其属性和功能的集合。我们之前说的结构体只能描述这类事物的属性,但却不能描述事物的功能。

聪明的童鞋就要反驳我了!what a fuck!老师,你说错了,我完全可以给字段指定函数类型啊,然后".字段()"运行啊!

这位同学,你说的没错,可以是可以,我们来看看有多繁琐!

type test struct {
	name string
	fn1 func(string)
	fn2 func(string)
}

func testfn1(s string){
	fmt.Println("我是",s)
}

func testfn2(s string){
	fmt.Println("我是",s,"哈哈哈")
}


func main(){
	c := test{
		name:"小明",
		fn1:testfn1,                 //每次实例化的时候,我都要去指定功能
		fn2:testfn2,
	}
	c.fn1(c.name)                    //每次调用的时候,我都要去指定参数
	c.fn2(c.name)
}
//我是 小明
//我是 小明 哈哈哈

就说烦不烦、方不方便!(除非你睁着眼睛说瞎话)

既然很烦,我们就来学习方法

4.3.1 方法的定义

方法其实就是函数,只是这个函数是绑定给指定类型的,只有该类型的数据才能调用

格式如下:

func (参数 指定类型) 函数名 (参数及类型)返回值类型{
    代码体....
}

在这里插入图片描述

具体使用如下:

type students struct {
    name string            //存学生姓名
    score []int            //存学生成绩的切片
}

//定义一个方法,用来给学生添加成绩
func (student students) appendScore (score int) {
    student.score = append(student.score,score)
}

func main(){
    student1 := students{
        name:"小明",                    //实例化一个学生,score切片为默认值
    }
    student1.appendScore(90)           //调用绑定方法,传入成绩
    student1.appendScore(100)
    
    fmt.Println(student1)               //{小明 []}
}

**发现什么问题没有?**我添加的成绩并没有如期出现?

为什么?很显然,传给接收器的是个副本,是值传递!

那怎么办啊?

1、我给方法设定一个返回值,然后用student1去接收。但是!这样的方法显然不好,涉及了多次值的拷贝

2、我传递指针,直接函数内部修改。这个方法显然是最好的

接收器接收指针:

type students struct {
    name string            //存学生姓名
    score []int            //存学生成绩的切片
}

//定义一个方法,用来给学生添加成绩
func (student *students) appendScore (score int) {        //接收器接收指针类型
    student.score = append(student.score,score)
}

func main(){
    student1 := students{
        name:"小明",                    //实例化一个学生,score切片为默认值
    }
    student1.appendScore(90)           //调用绑定方法,传入成绩
    student1.appendScore(100)
    
    fmt.Println(student1)               //{小明 [90 100]}
}

补充:

方法是一种绑定函数的机制,当然不只是可以绑定给struct类型,只要是自定义类型都可以绑定:

type myint int       //自定义一个名字为myint的int类型

func (x myint) test(){
    fmt.Println("我的值是",x)
}

func main(){
	a := myint(666)
	a.test()         //我的值是 666
}

在这里插入图片描述

4.4 接口

###4.4.1 接口基础

我们来思考一个问题。

我有3个类型的数据,这3个类型的数据呢我想用同一个函数去处理?怎么玩?

问题是,我一个函数只能传一个类型,其它类型传不进去!

在这里插入图片描述

类比生活中的例子:

在这里插入图片描述

1、我不可能手机专门差手机的地方,键盘鼠标专门插在键盘鼠标的地方(这样的话电脑上得开多少洞啊!)

2、这个时候电脑就提供了一个叫做usb的东西,usb呢就相当于手机鼠标键盘和电脑签订的协议。

usb说"我不负责操作数据,但我不管你是谁,你把数据传给我,我来统一格式再传给电脑"

在这里插入图片描述

3、这里的usb就是电脑和手机鼠标键盘之间的一个接口

go语言里,也有像usb这样不负责数据操作,只管统一格式的接口(interface)

接口(interface)核心知识点:

1、任何类型的数据都可以传给interface(得符合interface的要求)

2、interface统一了功能调用方式

3、interface不管功能的实现

怎么去理解这3点,别急,我们一个一个的用例子去看:

1、任何类型的数据都可以传给interface:

//首先,我们定义一个接口类型
type myinterface interface{}       //先不纠结接口里写啥

//我们来定义3个类型的数据
type mystruct struct{
    name string
}

type myint int                     //定义一个名叫myint的int类型
type myarry [3]int                 //定义一个名叫myarry的数组类型

func myfunc(x myinterface){
    fmt.Println("我被传进来了")      //定义一个函数来接受接口
}


func main(){
    var testinfetface myinterface    //声明一个接口
    struct1 := mystruct{             //声明一个结构体
        name:"mac",
    }
    int1 := myint(10)                 //声明一个整数
    arry1 := myarry{1,2,3}            //声明一个数组
     
    testinfetface = struct1           //把struct1传给接口
    myfunc(testinfetface)         
    
    testinfetface = int1           //把int1传给接口
    myfunc(testinfetface)         
    
    testinfetface = arry1           //把arry1传给接口
    myfunc(testinfetface)         
    
}

//结果:
//我被传进来了
//我被传进来了
//我被传进来了

2、interface统一了功能调用方式

这两点我们放在一块说:

//定义一个接口类型
type myinterface interface{
    test1()
    test2() string
}       

这里定义了一个接口,接口里只写了函数,不写函数体代码。

那怎么就叫统一功能调用方式了呢?

type myinterface interface{
    test1()
    test2() string
}    

type mystruct struct{
    name string
}

func main(){
    var interface1 myinterface
    struct1 := mystruct{
        name:"mac",
    }    
    interface1 = struct1
    
    interface1.test1()            //接口的调用方式是一样的,不管你传进来的是谁?我调用方式是一样的
    interface2.test2()                
}

//这里会报错,因为我函数体代码还没写

统一调用方式的意思是:我接口不管你传进来的是谁,调用方式都是一样的

在这里插入图片描述

3、interface不管功能的实现

接口只管统一功能调用方式,不管函数怎么实现的

那函数实现交给谁啊?

a、谁要传进来就由谁来实现。

b、实现了接口里所有函数的,叫做实现了这个接口

那怎么实现啊?绑定方法啊!

type myinterface interface{
    test1()
    test2() string
}    

type mystruct struct{
    name string
}

func (x mystruct) test1(){
    fmt.Println("我是test1",x.name)
}

func (x mystruct) test2() string {
    msg := "我是test2 "+x.name
    return msg
}


func main(){
    struct1 := mystruct{
        name:"mac",
    }
    
    var interface1 myinterface
    interface1 = struct1
    
    interface1.test1()
    fmt.Println(interface1.test2())
    
}

//结果:
//我是test1 mac
//我是test2 mac

接口最核心的知识点我们这就讲完了,很简单对不对。

下面我们来说说接口的其它知识点。

###4.4.2 接口断言

我们已经知道了接口的核心是接收任意类型、统一调用方式。

现在由这么个需求,假设我有一个接口,可以接收结构体类型a和字符串类型b,但我现在需要根据不用的类型写不同的代码。这怎么处理?很明显的,这里需要一个类型判断

而断言,就是用来处理接口类型判断的

var v,ok := 具体接口.(实现了接口的类型a)

//ok得到的是bool值,用来判断传入接口的类型是不是类型a
//如果ok为true,则v就会得到该类型的值,否则就是类型a的初始值
type myinterface interface{
    test1()
}

type mystruct struct {
    name string
}

func (x mystruct) test1() {
    fmt.Println("我是mystruct的test1")
}

type mystring string

func (x mystring) test1() {
    fmt.Println("我是mystring的test1")
}

func main(){
    struct1 := mystruct{
        name:"mac",
    }
    
    var interface1 myinterface
    interface1 = struct1
    
    v1,ok1 := interface1.(mystring)       //判断传给接口的是不是mystring类型
    fmt.Println(v1,ok1)                   //结果:"" false
    
    v2,ok2 := interface1.(mystruct)         //判断传给接口的是不是mystruct类型
    fmt.Println(v2,ok2)                    //结果:{mac} true
}

为了简化连续的类型判断,go提供了type switch功能

在这里插入图片描述

4.4.3 type switch

type switch和switch的用法基本上一致,很简单:

switch t := 具体接口.(type) {       //具体接口.(type)只能用在switch判断中
    case mystring:
    	代码
    case mystruct:
    	代码
    ...
    default:
    	代码
}

4.4.4 接口的嵌套

很简单,我们直接看代码:

type mine1 interface {
    test1()
    test2()
}

type mine2 interface {
    mine1                    //程序会自动扩展
    test3()
}

//mine2就相当于:
//type mine2 interface {
//    test1()
//    test2()
//    test3()
//}

##4.5反射

在这里插入图片描述

在说反射这个概念之前,我先来说说动态语言和静态语言。

动态语言地特点:数据结构是在程序运行地时候才会确定,运行过程当中可以对它进行修改,比如说python中我可以给实例化对象增加字段。

静态语言地特点:数据结构在运行前就会确定,比如说go里地struct,我规定了几个字段就是几个字段,运行过程当中我是没法增加字段的。

就灵活性上而言,动态语言是大于静态语言的。你定义变量的时候,必须规定死它的数据类型,运行时类型已经确定,无法修改。go语言就是一种静态语言,但为了弥补灵活性上的不足,就提供了反射机制。允许你通过反射去查看和修改其类型信息

4.5.1 reflect.TypeOf()

我们之前一直在用reflect.Typeof()来查看类型,但事实上它的功能远远不止这么简单。

我们使用reflect.Typeof(变量)得到的是它的反射类型对象:

type mystruct struct{
    name string
}

func mian(){
    struct1 := mystruct{
        name:"mac",
    }
    
    a := reflect.Typeof(struct1)
    fmt.Ptintf("%T",a)                //*reflect.rtype
}

它首先是个指针类型,指针意味着什么?是不是意味着我对它的修改是修改的它的本身,而不是副本啊!

是个毛线啊,虽然它是指针,但是不允许你修改,它只是通过内存地址去访问类型信息

接着它又是个对象,对象意味着什么?是不是它封装了很多方法可以供我使用啊!

我们下面来看看它有哪些方法可以使用:

1、.Name()/.Kind()

type mystruct struct{
    name string
}

func main(){
    struct1 := mystruct{
        name:"mac",
    }
    
    a := reflect.TypeOf(struct1)
    fmt.Println(a.Name())           //mystruct
    fmt.Println(a.Kind())           //struct
}

.Name( ) 得到的是类型名称

.Kind( ) 得到的是类型种类

2、.Elem()

如果我传入的是个指针,那么我获得的类型名称和类型种类如下:

type mystruct struct{
    name string
}

func main(){
    struct1 := mystruct{
        name:"mac",
    }
    
    a := reflect.TypeOf(&struct1)
    fmt.Println(a.Name())           //""
    fmt.Println(a.Kind())           //Ptr
}

这个时候我可以通过.Elem()来获取指针指向的元素:

func main(){
    struct1 := mystruct{
        name:"mac",
    }
    
    a := reflect.TypeOf(&struct1)
    //a.Elem()相当于 reflect.TypeOf(struct1) 或者 reflect.TypeOf(*&struct1)
    fmt.Println(a.Elem().Name())           //mystruct
    fmt.Println(a.Elem().Kind())           //struct
}

在这里插入图片描述

下面几点是针对结构体的操作

3、.Field()

我们通过.Field(索引)可以拿到结构体中指定字段的详细信息,它的结果是个结构体。

我们看看源码里拿到的这个结构体里有些啥:

在这里插入图片描述

name是字段名;pkgpath是所属包名;type是字段类型;tag是标签;index是索引;Anonymous是判断字段是否是匿名字段。

type mystruct struct{
    name string
}

func main(){
    struct1 := mystruct{
        name:"mac",
    }
    
    a := reflect.TypeOf(struct1)
    afield_1 := a.Field(0)
    fmt.Println(afield_1)                    //{{name main string  0 [0] false}}
    fmt.Println(afield_1.Name)               //name
    fmt.Println(afield_1.PkgPath)            //main
    fmt.Println(afield_1.Type)               //string
    fmt.Println(afield_1.Tag)                //""
    fmt.Println(afield_1.Index)              //[0]
    fmt.Println(afield_1.Anonymous)          //false
}

我们重点看一下tag,又叫标签,是用来描述字段的。

如何描述字段:

type mystruct struct{
	name string `我是描述信息`
}
........
fmt.Println(afield_1.Tag)       //我是描述信息

但正常我们不这么用,我们通常会给描述信息建立字段

type mystruct struct{
    name string `key1:"value1" key2:"value2"`
}
........
fmt.Println(afield_1.Tag)                    //key1:"value1" key2:"value2"
fmt.Println(afield_1.Tag.Get("key1"))        //value1
fmt.Println(afield_1.Tag.Get("key2"))        //value2

在这里插入图片描述

4、NumField()**

NumFild()可以得到结构体字段数

type mystruct struct{
    name string
    id int
    sex string
    address string
}
........
struct1 := mystruct{}
a := reflect.TypeOf(struct1)
fmt.Println(a.NumField())                 // 4

5、FieldByName()

根据给定字符串去找字段,返回字段信息的结构体。

type mystruct struct{
    name string
}
........
struct1 := mystruct{}
a := reflect.TypeOf(struct1)

fieldmsg1,exists1 := a.FieldByName("name")
fmt.Println(exists1)                         //true
fmt.Println(fieldmsg1)                       //{name main string  0 [0] false}

fieldmsg2,exists2 := a.FieldByName("id")  
fmt.Println(exists2)                         //false


6、FieldByIndex()

但结构体有多层的时候,我们通过FiendByIndex()去访问里层结构体字段

FieldByIndex()里面传一个切片,[]int{第一次索引,第二层索引,…

type mystruct struct{
    name string
    address struct{
    	country string
        city string
    }
}
........
struct1 := mystruct{}
a := reflect.TypeOf(struct1)

fmt.Println(a.FieldByIndex([]int{1,1}))         //{city main string  16 [1] false}
fmt.Println(a.FieldByIndex([]int{1,1}).Name)    //city

4.5.2 reflect.ValueOf()

我们使用reflect.ValueOf(变量)得到的是它的反射值对象:

type mystruct struct{
    name string
}

func mian(){
    struct1 := mystruct{
        name:"mac",
    }
    
    a := reflect.ValueOf(struct1)
    fmt.Ptintf("%T",a)                //reflect.Value
}

同样的,它也封装了很多方法供我们使用:

1、Interface()

将值以空接口的形式返回:

type mystruct struct{
    name string
}

func mian(){
    struct1 := mystruct{
        name:"mac",
    }
    
    a := reflect.ValueOf(struct1).Interface()
    fmt.Println(a.(mystruct).name)                //mac
}

同样的,还有:

**Int() / Uint() / Floaat() / Bool() / Bytes() / String() **

在这里插入图片描述

下面几点是针对结构体的操作

2、Field()

Field(索引),根据索引,得到对应的字段的值对象:

type mystruct struct{
    name string
}

func mian(){
    struct1 := mystruct{
        name:"mac",
    }
    
    a := reflect.ValueOf(struct1)
    fmt.Println(a.Field(0))                //mac  类型不是string,是reflect.Value
    fmt.Println(a.Field(0).String())       //mac  类型string
}

3、NumField()

返回字段数量

4、FieldByName()/FieldByIndex()

这和TypeOf()方法中的FieldByName()/ FieldByIndex()用法一样,只不过这里的FieldByName()/FieldByIndex()得到的是字段的值对象(reflect.Value)

5、CanSet()

判断值是否能被修改:

func main() {
    var a int = 1000
    avalue := reflect.ValueOf(a)
    apvalue := reflect.ValueOf(&a).Elem()
    
    fmt.Println(avalue.CanSet())           //false
    fmt.Println(apvalue.CanSet())          //true
}

很明显,reflect.ValueOf(变量),传进去的是个副本,要想改值,就得转指针进去

6、SetInt() / SetUint() / SetFloat()/SetBool()/SetBytes()/SetString()

这些方法都是通过反射来设置变量的值。

可以被改值的前提:

a、需要传入指针

设置值的第一个要求我们将CanSet()的时候已经提过,就是需要传入指针才可以设置

func main() {
    var a int = 1000
    apvalue := reflect.ValueOf(&a).Elem()
    
    apvalue.SetInt(2000)
    fmt.Println(a)                   //2000
}

b、如果是结构体,字段首字母要大写

type mystruct struct{
    name string
}

func main(){
    struct1 := mystruct{
        name:"mac",
    }
    
    valueOfStruct1 := reflect.ValueOf(&struct1).Elem()
    
    fmt.Println(valueOfStruct1.FieldByName("name").CanSet())   // false
    
}
type mystruct struct{
    Name string
}

func main(){
    struct1 := mystruct{
        Name:"mac",
    }
    
    valueOfStruct1 := reflect.ValueOf(&struct1).Elem()
    
    fmt.Println(valueOfStruct1.FieldByName("Name").CanSet())   // true
    
    valueOfStruct1.FieldByName("Name").SetString("supermac")
    
    fmt.Println(valueOfStruct1.FieldByName("Name").String())    //supermac
    
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值