Golang 学习笔记(7)

第十章 面向对象的编程

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

Golang语言面向对象编程说明

  1. Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言,所有我们说Golang支持面向对象编程特性是比较准确的
  2. Golang没有类(class),Go语言的结构体(struct)和其它编程语言的类(class)有同等地位,可以理解为Golang是基于struct来实现OOP特性的
  3. Golang面向对象编程非常简洁,去掉了传统OOP语言的继承、方法重载、构造函数、析构函数、隐藏的this指针等等。
  4. Golang仍然有面向对象编程的继承,封装,多态的特性,只是实现的方式和其它OOP语言不同,比如继承:Golang没有extends关键字,继承是通过匿名字段来实现。
  5. Golang面向对象(OOP)很优雅,OOP本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,非常灵活,也就是说在Golang中面向接口编程是非常重要的特性。

结构体

结构体与结构体变量(实例/对象)的关系示意图
在这里插入图片描述
对上图的说明:

  1. 将一类事物的特征提取,形成一个新的数据类型(结构体)
  2. 通过这个结构体,我们可以创建多个变量(实例/对象)
  3. 事物可以是不同的类

基本案例

package main

import "fmt"

// 定义一个Cat结构体,将Cat的各个字段放入到cat结构体中进行管理
type Cat struct {
	Name  string
	Age   int
	Color string
	Hobby string
}

func main() {
	//使用struct结构体,完成猫类
	//创建一个Cat变量
	var cat1 Cat
	cat1.Name = "小白"
	cat1.Age = 3
	cat1.Color = "白色"
	cat1.Hobby = "吃鱼"

	fmt.Println("cat1= ", cat1)
	fmt.Println("猫的信息如下")
	fmt.Println("name = ", cat1.Name)
	fmt.Println("age = ", cat1.Age)
	fmt.Println("color = ", cat1.Color)
	fmt.Println("hobby = ", cat1.Hobby)
}

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

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

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

var cat1 Cat
cat1.Name = “小白”
cat1.Age = 2
cat1.Color = “白色”
在这里插入图片描述

结构体声明

type 标识符struct{
field1 type
field2 type
}

字段/属性

基本介绍
  1. 从概念或叫法上看:结构体字段= 属性=field
  2. 字段是结构体的一个组成部分,一般是基本数据类型、数组,也可以是引用类型。例如:Name string就是属性。
注意事项和细节说明
  1. 字段声明语法同变量,示例:字段名 字段类型
  2. 字段的类型可以为:基本类型、数组或引用类型
  3. 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值)。
  4. 不同结构体变量的字段是独立的,互不影响,一个结构体变量字段的更改,不影响另外一个,结构体是值类型,默认为值拷贝。

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

  1. 方式一:直接声明
    var person Person
  2. 方式二:{}
    var person Person = Person{}
//方式二
	p2 := Person{"mary", 28}
	fmt.Println(p2)
}
  1. 方式三:&
//方式三
	var p3 *Person = new(Person)
	//因为p3是一个指针,一次标准的字段赋值方式是
	(*p3).Name = "john"
	//(*p3).Name = "john"也可以谢晨p3.Name = "john"
	//原因:为了程序员使用方便,底层会对p3.Name = "Smith"机型呢处理
	//会给 p3 加上取值运算(*p3).Name = "john"
	p3.Name = "smith"
	(*p3).Age = 19
	p3.Age = 18
	fmt.Println(*p3)
  1. 方式四:{}
//方式四:
	//下面的语句,也可以直接给字符赋值。
	// var person *Person = &Person{"marry",12}
	var person *Person = &Person{}
	//因为person是一个指针,因此标准的访问字段的方式
	//(*person).Name = "scott"
	//也可以写成 person.Name = "scott"
	person.Name = "scott"
	person.Age = 19
	fmt.Println(*person)

说明:

  1. 第三种和第四种的方式返回的是结构体指针。
  2. 结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,如:(*person).Name = “tom”
  3. 但Go做了一个简化,也支持结构体指针.字段名,如person.Name = “tom”。go编译器底层对person.Name做了转化(*person).Name

struct类型的内存分配机制

在这里插入图片描述

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

  1. 结构体的所有字段在内存中是连续的
    在这里插入图片描述

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

package main
import "fmt"
type A struct{
Num int
}
type B struct{
Num int
}
func main(){
	var a A
	var b B
	a = A(b)//a = b 是不可以转换,原代码可以转换,结构体的字段要完全一样(包括:名字、个数、类型)
	fmt.Println(a,b)
}
  1. 结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是可以相互间强转。
  2. struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列号和反序列化。
    序列化的使用场景:
    在这里插入图片描述
package main

import (
	"encoding/json"
	"fmt"
)

type Monster struct {
	Name  string `json:"name"` //`json:"name"`就是struct tag
	Age   int    `json:"age"`
	Skill string `json:"skill"`
}

func main() {
	var monster Monster
	monster.Name = "孙悟空"
	monster.Age = 10
	monster.Skill = "七十二变"
	//将monster变量序列化为json格式字串
	data, err := json.Marshal(monster)
	if err != nil {
		fmt.Println("json encoding err", err)
		return
	}
	fmt.Println("json后的数据 = ", string(data))
}

方法

基本介绍

在某些情况下,我们更需要声明/定义方法,比如Person结构体:输了有一些字段外(年龄,姓名…)Person结构体还有一些行为,比如:可以说话、跑步…。这是就要用方法才能完成。
Golang中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),一次自定义类型,都可以有方法,而不仅仅是struct

方法的声明和调用

type A struct{
Num int
}
func(a A)test(){
fmt.Println(a.Num)
}
对上面语法的说明:

  1. func(a A)test() {}表示A的结构体有一个方法,方法名为test
  2. (a A)体现test方法是和A类型绑定的
package main

import "fmt"

type Person struct {
	Name string
}

// 给Person类型绑定一个方法
func (p Person) test() {
	fmt.Println("test() name = ", p.Name)
}
func main() {
	var p Person
	p.Name = "tom"
	p.test() //调用方法
}

  1. test方法和Person类型绑定
  2. test方法只能通过Person类型的变量来调用,不能直接调用,也不能使用其它类型的变量来调用。
  3. func(p Person) test() {} …p 表示哪个Person变量调用,这个p就是它的副本,和函数传参相似。
  4. p这个名字是可以由程序员指定,不是固定的,比如修改成person

方法的调用和传参机制原理

说明:方法的调用和传参机制和函数基本一致,不一样的方法是方法调用时,会将调用方法的变量,当做实参也传递传递给方法。变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地址拷贝)

方法的声明(定义)

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

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

方法注意事项和细节讨论

  1. 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
  2. 如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
package main

import "fmt"

type Circle struct {
	radius float64
}

func (c *Circle) area() float64 {
	c.radius = 10
	return 3.14 * c.radius * c.radius
}

func main() {
	var c Circle
	c.radius = 7.0
	res2 := c.area()
	fmt.Println("面积=", res2)
	fmt.Println("c.radius = ", c.radius)
}

  1. Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct,比如int,float32等都可以有方法
  2. 方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本报和其它包访问
  3. 如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出

方法的练习

  1. 编写结构体(MethodUtils),编写一个方法,方法不需要参数,在方法中打印一个10*8的矩形,在main方法中调用该方法。
package main

import "fmt"

// 编写结构体(MethodUtils),编写一个方法,方法不需要参数,
// 在方法中打印一个10*8的矩形,在main方法中调用该方法。
type MethodUtils struct {
}

// 给MethodUtils编写方法
func (mu MethodUtils) Print() {
	for i := 1; i < 10; i++ {
		for j := 1; j <= 8; j++ {
			fmt.Print("*")
		}
		fmt.Println()
	}
}
func main() {
	var mu MethodUtils
	mu.Print()
}

  1. 编写一个方法,提供m和n两个参数,方法中打印一个m*n的矩形
package main

import "fmt"

// 编写结构体(MethodUtils),编写一个方法,方法不需要参数,
// 在方法中打印一个m*n的矩形,在main方法中调用该方法。
type MethodUtils struct {
}

// 给MethodUtils编写方法
func (mu MethodUtils) Print(m int, n int) {
	for i := 1; i < m; i++ {
		for j := 1; j <= n; j++ {
			fmt.Print("*")
		}
		fmt.Println()
	}
}
func main() {
	var mu MethodUtils
	mu.Print(10, 20)
}

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

import "fmt"

// 编写一个方法算该矩阵的面积(可以接收长len,和宽width),
// 将其作为方法返回值。
// 在main方法中调用该方法,接收返回的面积值并打印。
type MethodUtils struct {
}

// 给MethodUtils编写方法
func (mu MethodUtils) Area(len float64, width float64) float64 {
	return len * width
}
func main() {
	var mu MethodUtils
	areaRes := mu.Area(2.5, 2.7)
	fmt.Println(areaRes)
}

  1. 编写方法:判断一个数是奇数还是偶数
package main

import "fmt"

// 编写方法:判断一个数是奇数还是偶数
type MethodUtils struct {
}

func (mu *MethodUtils) JudgeNum(num int) {
	if num%2 == 0 {
		fmt.Println("是偶数")
	} else {
		fmt.Println("是奇数")
	}
}

func main() {
	var mu MethodUtils
	(&mu).JudgeNum(10)//mu.JudgeNum(10)
}

  1. 根据行、列、字符打印对应行数和列数的字符,比如:行:3,列:2,字符:*,则打印相应的效果
package main

import "fmt"

// 根据行、列、字符打印对应行数和列数的字符,
// 比如:行:3,列:2,字符:*,则打印相应的效果
type MethodUtils struct {
}

func (mu *MethodUtils) print(n int, m int, key string) {
	for i := 1; i <= n; i++ {
		for j := 1; j <= m; j++ {
			fmt.Print(key)
		}
		fmt.Println()
	}
}

func main() {
	var mu MethodUtils
	mu.print(3, 20, "$")
}

  1. 定义小小计算器结构体(Calcuator),实现加减乘除的四个功能
    实现形式1:分四个方法完成
    实现形式2:用一个方法搞定
package main

import "fmt"

// 定义小小计算器结构体(Calcuator),实现加减乘除的四个功能
//
//	实现形式1:分四个方法完成
//	实现形式2:用一个方法搞定,需要接收两个数和一个运算符
type Calculator struct {
}

func (calculator *Calculator) getRes(n float64, m float64, operator byte) float64 {
	res := 0.0
	switch operator {
	case '+':
		res = n + m
	case '-':
		res = n - m
	case '*':
		res = n * m
	case '/':
		res = n / m
	default:
		fmt.Println("输入运算符出错")
	}
	return res
}

func main() {
	var calculator Calculator
	res := calculator.getRes(10.8, 10.5, '*')
	fmt.Println("res = ", res)

}

  1. 编写方法,使给定的一个二维数组(3*3)转置:
package main

import "fmt"

//编写方法,使给定的一个二维数组(3×3)转置:

type MethodUtils struct {
}

func (mu MethodUtils) exchange(arr [3][3]int) {
	var arr2 [3][3]int
	//遍历数组并进行转置
	for i := 0; i < len(arr); i++ {
		for j := 0; j < len(arr[i]); j++ {
			arr2[j][i] = arr[i][j]
		}
	}
	fmt.Println("转置后的矩阵为:")
	//遍历数组
	for i := 0; i < len(arr2); i++ {
		for j := 0; j < len(arr2[i]); j++ {
			fmt.Print(arr2[i][j], " ")
		}
		fmt.Println()
	}
}

func main() {
	var mu MethodUtils
	var arr = [3][3]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
	fmt.Println("转置前的矩阵为:")
	//遍历数组
	for i := 0; i < len(arr); i++ {
		for j := 0; j < len(arr[i]); j++ {
			fmt.Print(arr[i][j], " ")
		}
		fmt.Println()
	}
	mu.exchange(arr)
}

方法和函数的区别

  1. 调用方式不一样
    函数的调用方式:函数名(实参列表)
    方法的调用方法:变量.方法名(实参列表)
  2. 对于普通函数,接受者为值类型时,不能将指针类型的数据直接传递,反之亦然
  3. 对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同时也可以

面向对象编程应用实例

步骤

  1. 声明(定义)结构体,确定结构体名
  2. 编写结构体的字段
  3. 编写结构体的方法
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],"+"id = [%v],score = [%v]",
		student.name, student.gender, student.age, student.id, student.score)
	return infoStr
}
func main() {
	var stu = Student{
		name:   "tom",
		gender: "male",
		age:    18,
		id:     042040,
		score:  60.5,
	}
	fmt.Println(stu.say())
}

盒子案例

  1. 编程创建一个Box结构体,在其中声明三个三个字段表示一个立方体的长、宽和高,长传高要从终端获取
  2. 声明一个方法获取立方体的体积。
  3. 创建一个Box结构体变来给你,打印给定尺寸的立方体的体积。
package main

import "fmt"

// 1. 编程创建一个Box结构体,在其中声明三个三个字段表示一个立方体的长、宽和高,长传高要从终端获取
// 2. 声明一个方法获取立方体的体积。
// 3. 创建一个Box结构体变来给你,打印给定尺寸的立方体的体积。
type Box struct {
	len    float64
	width  float64
	height float64
}

func (box *Box) getVolumn() float64 {
	return box.len * box.width * box.height
}
func main() {
	var box Box
	box.len = 1.1
	box.width = 2.0
	box.height = 3.0
	volumn := box.getVolumn()
	fmt.Printf("体积为 = %.2f", volumn)
}

景区门票案例

  1. 一个景区根据游人的年龄收取不同价格的门票,比如年龄大于18,收费20元,其它情况门票免费
  2. 请编写Visitor结构体,根据年龄段决定能够购买的门票价格并输出
package main

import "fmt"

// 1. 一个景区根据游人的年龄收取不同价格的门票,比如年龄大于18,
// 收费20元,其它情况门票免费
// 2. 请编写Visitor结构体,根据年龄段决定能够购买的门票价格并输出
type Visitor struct {
	Name string
	Age  int
}

func (visitor *Visitor) showPrice() {
	if visitor.Age > 18 {
		fmt.Printf("游客的名字为 %v 年龄为 %v 收费为20元\n", visitor.Name, visitor.Age)
	} else {
		fmt.Println("免费")
	}
}
func main() {
	var visitor Visitor
	for {
		fmt.Println("请输入你的名字")
		fmt.Scanln(&visitor.Name)
		if visitor.Name == "n" {
			fmt.Println("退出程序")
			break
		}
		fmt.Println("请输入你的年龄")
		fmt.Scanln(&visitor.Age)
		visitor.showPrice()
	}

}

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

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

  1. 方式1
var stu1 Student = Student{"tom",10}
stu2 := Student{"tom~",10}
var stu3 Student = Student{
	Name : "mary",
	Age : 30 ,
}
stu4 := Student{
	Name = "mary~",
	Age : 20,
}
  1. 方式2
var stu5 *Student = &Student{"smith",30}
var stu6 *Student = &Student{
	Name : "scott",
	Age : 80,
}

在创建结构体指针变量时,把字段名和字段值写在一起,这种写法,就不依赖字段的定义顺序。

工厂模式

说明:
Golang的结构体没有构造函数,通常可以使用工厂模式来解决这个问题。
使用工厂模式实现挎包穿甲结构体实例的案例:

  1. 如果model包的结构体变量首字母大写,引入后,直接使用
package model

// 定义一个结构体
type Student struct {
	Name  string
	Score float64
}

func main() {

}

package main

import (
	"fmt"
	"go_code/chapter10/factory/model"
)

func main() {
	//创建要给Student实例
	var stu = model.Student{
		Name:  "tom",
		Score: 78.9,
	}
	fmt.Println(stu)
}

  1. 如果model包的结构体变量首字母小写,引入后,不能直接使用,可以工厂模式解决。
package model

// 定义一个结构体
type student struct {
	Name  string
	Score float64
}

// 因为student的结构体首字母小写,因此只能在model包中使用
// 通过工厂模式解决
func NewStudent(n string, s float64) *student {
	return &student{
		Name:  n,
		Score: s,
	}
}


package main

import (
	"fmt"
	"go_code/chapter10/factory/model"
)

func main() {
	//创建要给Student实例
	var stu = model.NewStudent("tom", 88.8)

	fmt.Println(stu)
	fmt.Println(*stu)

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值