十、面向对象编程_上

10.1 结构体

10.1.1 结构体

看一个问题

t2bcPf.png

10.1.2 一个程序就是一个世界,有很多对象(变量)

10.1.3 Golang语言面向对象编程说明

t2qBSU.png

t2LM7R.png

10.1.4 结构体与结构体变量(实例/对象)的关系示意图

43672846238468.PNG

对上图的说明

1、将一类事物的特性提取出来(比如:猫类),形成一个新的数据类型,就是一个结构体

2、通过这个结构体,我们可以创建多个变量(实例对象)

3、事物可以是猫类,也可以是person,也可以是鱼类

t2OIZd.png

10.1.5 快速入门-面向对象的方式(struct)解决养猫的问题

t2jmjS.png

10.1.6 结构体和结构体变量(实例)的区别联系

  • 结构体是自定义的数据类型,代表一类事物
  • 结构体变量(实例)是具体的,实际的,代表一个具体变量

10.1.7 结构体变量(实例)在内存中的布局—重要

结构体是值类型

tREowt.png

10.1.8 如何声明结构体

type 结构体名称 struct {
    field1 type
    field2 type
    .....
}

// 举例

type Cat struct {
	Name string   //Name 大写则表示可以被其他包引用
	Age int 
	Color string
	Hobby string
}

10.1.9 字段/属性

基本介绍

  • 从概念或叫法上:结构体=属性=field
  • 字段是结构体的一个组成部分,一般是基本数据类型、数组、也可是引用类型,比如我们前面定义猫的结构体的Name string就是属性

注意事项

  • 字段声明语法同变量,示例: 字段名 字段类型
  • 字段的类型可以为:基本类型、数组或引用类型
  • 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则同前面说的一样
  • 布尔类型是 false 、整型是 0 、 字符串是 “”
    • 数组类型的默认值和它的元素类型相关,比如:socore [3]int 则为[0,0,0]
    • 指针,slice和map的零值都是nil,即还没有分配空间
  • 不同结构体变量的字段是独立的,互不影响,一个结构体变量字段的更改,不影响另外一个,结构体是值类型

43672846238468.PNG

上述代码的内存示意图

43672846238468.PNG

10.1.10 创建结构体变量和访问结构体字段

10.1.10.1 方式1

直接声明

var person Person

10.1.10.2 方式2

var person Person = Person {}

43672846238468.PNG

10.1.10.3 方式3

43672846238468.PNG

43672846238468.PNG

10.1.10.4 方式4

43672846238468.PNG

说明

  • 第三种和第四种方式返回的是 结构体指针

  • 结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,

    • (*person).Name = "tom"
      
  • 但go做了一个简化,也支持 结构体指针.字段名 比如:person.Name = "tom" 更加符合程序员使用的习惯,go编译器底层对 person.Name做了转化(*person).Name

10.1.11 struct类型的E内存分配机制

思考?

43672846238468.PNG

变量总是在内存中的,那么结构体变量在内存中究竟是怎样存在的?

结构体在内存中示意图

43672846238468.PNG

43672846238468.PNG

上述代码内存图

43672846238468.PNG

43672846238468.PNG

10.1.12 结构体的注意事项和使用细节

1、结构体的所有字段在内存中是连续的

tRxWPs.png

tROdXD.png

2、结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字、个数和类型)

987654345678.PNG

3、结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转

987654345678.PNG

10.1.13 结构体注意事项

  • struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列号和反序列化

序列化的使用场景

tWnX7j.png

代码

tWnRne.png

10.2 方法

10.2.1 基本介绍

​ 在某些情况下,我们需要声明/定义(方法),比如:Person结构体,除了有一些字段外(年龄,姓名...)Person结构体还有一些行为,这时就要用方法才能完成

​ Golang中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法。而不仅仅时struct

10.2.2 方法的声明和调用

type A struct {
    Num int
}
func (a A) test() {
    fmt.Println(a.Num)
}

对上面语法的说明

1、func (a A) test() {} 表示A结构体有一方法,方法名为 test

2、(a A) 体现方法是和A类型绑定的

举例

tWc2Js.png

对上面代码说明

1、 test方法和Person类型绑定

2、test方法只能通过Person类型的变量来调用,也不能使用其他类型变量来调用

3、func (p Person) test(){....} p表示哪个Person变量调用,这个p就是它的副本,这点和函数的传参十分相似

4、func (p Person) test(){....} p这个名字,有程序员固定,不是固定不变的

10.2.3 方法快速入门

1、给Person结构体添加 speak方法,输出 xxx是一个好人

=

2、给Person结构体添加 jisuan方法,可以计算从1+....100的结果,说明方法体内可以函数一样,进行各种运算

tWoGcQ.png

3、给Person结构体添加 jisuan2方法,可以接收一个数n,计算从1+...+n的结果

tWTKKJ.png

4、给Person结构体添加 getSum方法,可以计算两个数的和,并返回结果

tWT5aq.png

5、方法调用的几种形式

tW7pi6.png

10.2.4 方法的调用和传参机制原理(重要!!)

说明:方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将方法的变量,当作实参也传递给方法

案例1

前面getSum方法的执行过程+说明

tWbIII.png

上述代码图示

tWjmnO.png

说明

1、在通过一个变量去调用方法时,其调用机制和函数一样

2、不一样的地方是,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地址拷贝)

案例2

编写一个程序,要求如下

  • 声明一个结构体Circle,字段为radius
  • 声明一个方法area和Circle绑定,可以返回面积

tWvcRI.png

上述代码图示

tWvLQ0.png

10.2.5 方法的声明/定义

func (recevier type) methodName (参数列表) (返回值列表) {
    方法体
    return 返回值
}

1、参数列表:表示方法输入
2、recevier type:表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型
3、recevier type:type可以是结构体,也可以是其他的自定义类型
4、receiver:就是type类型的一个变量(实例),比如:Person结构体的一个变量(实例)
5、返回值列表:表示返回的值,可以多个
6、方法主体:表示为了实现某一功能代码块
7、return :语句不是必须的

10.2.6 方法注意事项和细节讨论

1、结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式

2、如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理

tWvcRI.png

tWvLQ0.png

3、Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定)。。因此自定义类型,都可以有方法,而不仅仅时struct,比如:int float32等都可以有方法

987654345678.PNG

4、方法的访问范文控制的规则,和函数一样,方法名首字母小写,只能在本包访问,方法名大写,可以在本包和其他包访问

5、如果一个类型实现了 String() 这个方法,那么fmt.Println 默认会调用这个变量的String()进行输出

987654345678.PNG

10.2.7 方法练习题

1、编写结构体,编程一个方法,方法不需要参数,在方法中打印一个10*8的矩形,在main方法中调用该方法

987654345678.PNG

2、编写一个方法,提供m和n两个参数,方法中打印一个 m*n的矩形

987654345678.PNG

3、编写一个方法,算该矩形的面积(可以接收长len,和宽width),将其作为方法返回值,在main方法中调用该方法,接收返回的面积并打印

987654345678.PNG

4、编写一个方法,判断一个数是奇数还是偶数

987654345678.PNG

5、根据行、列、字符打印对应行数和列数的字符,比如:行:3、列:2、字符:*

987654345678.PNG

6、定义小小计算器结构体(Calcuator),实现加减乘除四个功能

  • 实现形式1:分四个方法完成,分别计算 + - * /
package main 
import (
	"fmt"
)
type Calcuator struct {
	Num1 float64
	Num2 float64
}

func (calcuator *Calcuator) getSum() float64 {
	return calcuator.Num1 + calcuator.Num2
}

func (calcuator *Calcuator) jianfa() float64 {
	return calcuator.Num1 - calcuator.Num2
}

func (calcuator *Calcuator) chengfa() float64 {
	return calcuator.Num1 * calcuator.Num2
}

func (calcuator *Calcuator) chufa() float64 {
	return calcuator.Num1 / calcuator.Num2
}

func main() {
	var calcuator Calcuator
	calcuator.Num1 = 1.2
	calcuator.Num2 = 2.2
	fmt.Println("加法=", calcuator.getSum())
	// 下一行的  fmt.Sprintf("%.2f")  是格式化处
	fmt.Printf("减法= %v\n", fmt.Sprintf("%.2f",calcuator.jianfa())) 
	fmt.Println("乘法=", calcuator.chengfa())
	fmt.Println("除法=", calcuator.chufa())
}
  • 实现形式2:用一个方法完成 ,需要接收两个数,还有一个运算符
package main 
import (
	"fmt"
)
type Calcuator struct {
	Num1 float64
	Num2 float64
}

func (calcuator *Calcuator) getRes(operator byte) float64 {
	res := 0.0
	switch operator {
	case '+':
			res = calcuator.Num1 + calcuator.Num2
	case '-':
			res = calcuator.Num1 - calcuator.Num2
	case '*':
			res = calcuator.Num1 * calcuator.Num2
	case '/':
			res = calcuator.Num1 / calcuator.Num2
	default:
		fmt.Println("运算符输出有误.....")
	}
	return res
}

func main() {
	var calcuator Calcuator
	calcuator.Num1 = 1.2
	calcuator.Num2 = 2.2
	res := calcuator.getRes('+')
	fmt.Println("res=", res)
}

10.3 方法和函数区别

1、调用方式不一样

​ 函数的调用方式: 函数名(实参列表)

​ 方法的调用方式: 变量.方法名(实参列表)

2、对于普通函数,接受者为值类型时,不能将指针类型的数据直接传递,反之亦然

3、对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以

10.4 面向对象编程实例

10.4.1 步骤

  • 声明(定义)结构体,确定结构体名
  • 编写结构体的字段
  • 编写结构体的方法

10.4.2 案例

学生个人信息案例

package main 
import (
	"fmt"
)

type Student struct {
	name string
	gender string
	age int
	id int
	score float64
}

func (student *Student) say() string {
	infoStr := fmt.Sprintf("Student的信息 name=[%v] gender=[%v]  age=[%v] id=[%v] score=[%v]",
	student.name, student.gender, student.age, student.id, student.score)
	return infoStr
}

func main() {
	// 测试
	// 创建一个Student实例变量
	var stu = Student{
		name : "tom",
		gender : "male",
		age : 18,
		id : 10000,
		score : 99.99,
	}
	fmt.Println(stu.say())
}

10.4.3 盒子案例

  • 编程创建一个Box结构体,在其中声明三个字段表示一个立方体的长、宽、高、这三者要从终端获取
  • 声明一个方法获取立方体的体积
  • 创建一个Box结构体变量,打印给定尺寸的立方体的体积

tf6YS1.png

10.4.4 景区门票案例

  • 一个景区根据游人的年龄收取不同价格的门票,比如年龄大于18,收费20元,其他情况门票免费
  • 请编写Visitor结构体,根据年龄段决定能购买的门票价格并输出
package main 
import (
	"fmt"
)
// 创建一个 Visitor 结构体,定义了长宽高
type Visitor struct {
	Name string
	Age int
}

func (visitor *Visitor) showprice() {
	if visitor.Age > 90 || visitor.Age <= 8 {
		fmt.Println("考虑到安全,请不要去玩耍")
		return
	}
	if visitor.Age > 18 {
		fmt.Printf("游客的名字为%v 年龄为%v  收费20元 \n", visitor.Name, visitor.Age)
	} else {
		fmt.Printf("游客的名字为%v 年龄为%v  免费 \n", visitor.Name, visitor.Age)
	}
}

func main() {
	// 测试
	var v Visitor 
	for {
		fmt.Println("请输入你的名字:")
		fmt.Scanln(&v.Name)
		if v.Name == "n" {
			fmt.Println("谢谢、退出程序")
			break
		}
		fmt.Println("请输入你的年龄:")
		fmt.Scanln(&v.Age)
		v.showprice()
	}
}

10.5 创建结构体变量时指定字段值

​ Golang在创建结构体实例(变量)时,可以直接指定字段的值。

创建结构体变量时指定字段值方式

方式1

package main 
import (
	"fmt"
)

type Student struct {
	Name string
	Age int
}

func main() {
	//方式1
	// 在创建结构体变量时,就直接指定字段的值
	var stu1 = Student{"小明", 19} // stu1 --> 结构体数据空间
	stu2 := Student{"小明~", 22}

	// 方式2
	//  在创建结构体变量时,把字段名和字段值写在一起
	// 这种写法,就不依赖字段的定义顺序
	var stu3 = Student{
		Name : "Jack",
		Age : 9,
	}
	stu4 := Student {
		Age : 30,
		Name : "Marry",
	}

	fmt.Println(stu1, stu2, stu3, stu4)
	fmt.Println()
}

方式2

package main 
import (
	"fmt"
)

type Student struct {
	Name string
	Age int
}

func main() {
	// 方式2 返回结构体的指针类型!!!!!!!!!!
	var stu5 = &Student{"小王", 29} //stu5 --> 地址 --> 结构体数据
	stu6 := &Student{"小王", 33}
	// 在创建结构体指针变量时,把字段名和字段值写在一起
	// 这种写法,就不依赖字段的定义顺序
	var stu7 = &Student {
		Name : "小李",
		Age : 49,
	}
	stu8 := &Student {
		Name : "小李~",
		Age : 49,
	}
	fmt.Println(*stu5, *stu6, *stu7, *stu8)
}

10.6 工厂模式

10.6.1 说明

​ Golang的结构体没有构造函数,通常可以使用工厂模式来解决这个问题。

10.6.2 看一个需求

一个结构体的声明是这样的:

package model
type Student struct {
    Name string....
}

​ 因为这里的Student的首字母S是大写的,如果我们想在其它包创建Student的实例(比如:main包),引入model包后,就可以直接创建Student结构体的变量,但是问题来了,如果首字母是小写的,比如:type strdent struct {....} 就不行了,怎么办,------> 工厂模式解决

student.go

987654345678.PNG

main.go

987654345678.PNG

如果model包的student 的结构体的字段 Score改成score,我们还能正常访问吗?又该如何解决这个问题?

解决办法

987654345678.PNG

987654345678.PNG

posted on 2020-06-07 18:26  九酒馆  阅读(172)  评论(0编辑  收藏  举报

导航