第十章 Golang面向对象编程(上)

简介: 第十章 Golang面向对象编程(上)

结构体

看一个养猫问题

张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示张老太没有这只猫猫

Golang语言面向对象说明

  1. Golang也支持面向对象(oop),但是和传统的面向对象编程有区别,并不是纯粹面向对象的语言,所以我们说Golang支持面向对象编程特性是比较准确的
  2. Golang没有类(class),Go语言的结构体(struct)和其他编程语言类(class)有同等的地位,你可以理解Golang是基于struct来实现oop特征的。
  3. Golang面向对象编程非常简洁,去掉了传统oop语言的继承,方法重载,构造函数和析构函数,隐藏的this指针等等

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

对上图的说明

  1. 将一类事物的特征提取出来,比如说上面的猫猫,有很多属性,形成一个新的数据类型。就是一个结构体。
  2. 通过这个结构体,我们可以创建多个变量(实例对象)
  3. 事物可以是猫类,也可以是person,fish或是一个工具类

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

代码演示

// 定义一个Cat结构体,将cat的各个字段/属性信息,放入到cat结构体进行管理
type Cat struct {
  Name string
  age int
  Color string
  Hobby string
}
func main(){
  // 创建一个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. 结构体变量(实例)是具体的,实际的,代表一个具体变量
    内存展示

如何声明结构体

基本语法

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

举例

type Student struct{
Name string // 字段
Age int // 字段
socure float32
}

字段/属性

基本介绍

  1. 从概念或叫法上看,结构体字段 = 属性 = field (即授课中,统一叫字段)
  2. 字段是结构体的一个组成部分,一般是基本数据类型,数组,也可是引用数据类型。比如我们前面定义结构体的name string就是属性

注意事项细节说明

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

案例

// 如果结构体的字段类型是:指针,slice,和map的零值都是nil,即还没有分配空间
// 如果需要使用这样的字段,需要先make,才能使用
type Person struct{
  Name string
  Age int
  Scores [5]float64
  ptr *int
  slice []int
  map1 map[string]string
  
}
type Monster struct{
  Name string
  Age int
}
func main(){
  // 定义结构体变量
  var p1 Person
  fmt.Println(p1)
  if p1.ptr == nil{
  fmt.Println("ok1")
  }
  if p1.slice == nil{
  fmt.Println("ok2")
  }
  if p1.map1 == nil{
  fmt.Println("ok3")
  }
  // 使用slice,再次说明,一定要make
  p1.slice = make([]int,10)
  p1.slice[0] = 100
  // 使用map,一定要先make
  p1.map1 = make(map[string]string)
  p1.map1["key1"]= "tom~"
  fmt.Println(p1)
  // 不同结构体变量的字段是独立,互不影响,一个结构体变量字段的改变,不影响另外一个,结构体是值类型
  
  var monster1 Monster
  monster1.Name = "牛魔王"
  monster1.Age = 500
  monster2 := monster1 // 结构体是值类型,默认为值拷贝
  monster2.Name = "狐狸精"
  fmt.Println("monster1=",monster1)
  fmt.Println("monster2=",monster2)
}

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

方式1 直接声明

案例演示:var person Person

前面我们已经说了。

方式2 -{}

案例演示:var person Person = Person {}

方式3-&

案例:var person *Person = new (Person)

方式4-{}

案例:var person *Person = &Person{}

type Person struct{
  Name string
  Age int
}
func main(){
  // 方式1
  // 方式2
  p2 := Person{"mary",20}
  // p2.Name = "tom"
  // p2.Age = 18
  fmt.Println(p2)
  // 方式3-&
  
  // 案例: var person *Person = new (Person)
  var p3 *Person = new(Person)
  //因为p3是一个指针,因此标准的给字段赋值方式
  // (*p3).Name = "smith"也可以这样写 p3.Name = "smith"
  // 原因:go的设计者 为了程序员使用方便,底层会对 p3.Name = "smith" 进行处理会给p3加上 取值运算(*p3).Name = "smith"
  (*p3).Name = "smith"
  p3.Name = "john"
  (*p3).Age = 30
  p3.Age = 100
  fmt.Println(*p3)
  // 方式4-{}
  // 案例:var person *Person = &Person{}
  // 下面的语句,也可以直接给字符赋值
  // var person *Person = &person{"mary",60}
  var person *Person = &Person{}
  // 因为person是一个指针,因此标准的访问字段的方法
  // (*person).Name = "scott"
  // go的设计者为了程序员使用方便,也可以person.Name = "scott"
  // 原因和上面一样,底层会对person.Name = "scott" 进行处理,会加上(*person)
  (*person).Name = "scott"
  person.Name = "scott~~"
  (*person).Age = 88
  personAge = 10
  fmt.Println(*person)
}

说明

  1. 第三种和第四种方式的返回值是 结构体指针。
  2. 结构体指针访问字段的标准方式应该是(*结构体指针)字段名,比如(*person)Name = “tom”
  3. 但go做了一个简化,也支持结构体指针字段名,比如 person.Name = “tom”.更加符合程序员使用的习惯。go编辑器底层对person.Name 做了转化(*person).Name

struct类型的内存分配机制

看一个思考题

我们定义一个Personal结构体(包括 名字,年龄)

我们看看下面一段代码,输出什么内容?

var p1 Person
p1.Age = 10
p1.Name = “小明”
var p2 Person = p1
fmt.Println(p2.Age)
p2.name=“tom”
fmt.Printf{“p2.Name=%v p1.Name=%v”,p2.Name,p1.Name}

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

  1. 结构体的所有字段在内存中是连续的
  2. 结构体是用户单独定义的类型,和其他类型进行转换时需要完全相同的字段(名字,个数和类型)
  3. 结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
  4. struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化。

方法

基本介绍

在某些情况下,我们需要声明(定义)方法,比如Person结构体,除了有一些字段外(年龄,姓名…)Person结构体还有一些行为比如:可以说话跑步,通过学习,还可以做算数题,这时就要用方法才能完成。

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

package main
import (
  "fmt"
)
type Person struct{
  Name string
}
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也是可以

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

说明

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

编写一个程序,要求如下:

  1. 声明一个结构体Circle,字段为radius
  2. 声明一个方法area和Circle绑定,可以返回面积
  3. 提示:画出area执行过程+说明

方法的声明(定义)

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

方法注意事项和细节

  1. 结构体类型是值类型,在方法调用中,遵循值类型的传递机制,是值拷贝传递方式
  2. 如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
  3. Golang中的方法作用在指针的数据类型上的(即;和指定的数据类型绑定)
  4. 方法的询问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其他包访问。
  5. 如果一个变量实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出

例子(案例)

package main
import (
 "fmt"
)
type MethodUtils struct{
}
func (mu MethodUtils) Print(){
  for i := 1;i<=10;i++{
    for j:=1;j<=8;j++{
      fmt.Print("*")
    }
    fmt.Println()
  }
}
/**
 编写一个方法提供m和n两个参数,方法中打印一个m*n的矩形
*/
func (mu MethodUtils) Print2(m int,n int){
  for i := 1;i<=m;i++{
    for j:=1;j<=n;j++{
      fmt.Print("*")
    }
    fmt.Println()
  }
}
/**
编写一个方法算该矩形的面积(可以接收长len,和宽width)
将其作为方法返回值,在main方法中调用该方法,接收返回的面积并打印
*/
func (mu MethodUtils) area(len float64,width float64)(float64){
  return len*width
}
func main(){
  /**
  1.编写一个结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10*8的矩形,在main方法中调用该方法
  */
  var mu MethodUtils
  mu.Print()
  fmt.Println()
  mu.Print2(5,20)
  areaRes := mu.area(2.5,8.7)
  fmt.Println()
  fmt.Println("面积为=",areaRes)
}

方法和函数的区别

  1. 调用方式不一样
    函数的调用方式:函数名(实参列表)
    方法的调用方式:变量,方法名(实参列表)
  2. 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,返之亦然
  3. 对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以
    总结:
  4. 不管调用形式如何,真正决定是值拷贝还是地址拷贝,主要是看这个方法是和那个类型绑定
  5. 如果是和值类型,比如(p Person)是值拷贝,如果和指针类型,比如是(p *Person)则是地址拷贝

面向对象编程应用实例

步骤

  1. 声明(定义)结构体,确定结构体名
  2. 编写结构体的字段
  3. 编写结构体的方法

学生案例

  1. 编写一个Student结构体,包含name,gender,age,id,score字段,分别为string,string,int,int,float64类型
  2. 结构体中声明一个say方法,返回string类型,方法返回信息中包含所有字段值
  3. 在main方法中,创建Student结构体实例(变量),并返回信息中包含所有字段值
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:1000,
  score:99.98
   }
   str := stu.say();
   fmt.println(str)
}

1.编程创建一个Box结构体,在其中声明三个字段表示一个立方体的长,宽,高,长宽高要从终端获取

2. 声明一个方法获取立方体的体积

3. 创建一个Box结构体变量,打印给定尺寸的立方体的体积

type Box struct{
  len float64
  width float64
  width float64
}
func (box *Box) getVolumn() float64{
  return box.len * box.width * box.height
}
// 调用
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结构体,根据年龄段觉得能够购买的门票价格并输出
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()
  }
}

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

说明

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

package main 
import (
  "fmt"
)
type Stu struct{
  Name string
  Age int 
}
func main(){
  // 方式1
  // 在创建结构体变量时,就直接指定字段的值
  var stu1 = Stu{"小明",19}
  stu2 := Stu{"小明~",20}
  // 在创建结构体变量时,把字段名和字段值写在一起,这种写法,就不依赖字段的定义顺序
  var stu3 = Stu{
    Name:"jack",
    Age:20
  }
  stu4 := Stu{
    Age:30,
    Name:"mary"
  }
  fmt.Println(stu1,stu2,stu3,stu4)
  
  // 方式2,返回结构体的指针类型(!!!)
   var stu5 *Stu = &Stu{"小王",29}
   stu6 := &Stu{"小王~",30}
   //在创建结构体指针变量时,把字段名和字段值写在一起,这种写法,就不依赖字段的定义顺序
   var Stu7 = &Stu{
    Name:"小李",
    Age:49
  }
  stu8 := &Stu{
    Age:59,
    Name:"小李~"
  }
  fmt.Println(*stu5,*stu6,*stu7,*stu8)
}

工厂模式

说明

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

看一个需求

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

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

package model
// 定义一个结构体
type Student struct{
   Name string
   Score float64
}
package main 
import (
  "fmt"
  "go_code/chapter10/factory/model"
)
func main(){
  // 创建要给Student实例
  var stu = model.Student{
    Name:"tom",
    Score:78.9,
  }
  fmt.Println(stu)
}

改变后

package model
// 定义一个结构体
type Student struct{
   Name string
   score float64
}
// 因为student结构体首字母是小写,因此是只能用在model使用
// 我们通过工厂模式来解决
func NewStudent(n string,s float64) *student{
   return &student{
    Name:n,
    score:s,
  }
}
// 如果score字段首字母小写看,则在其他包不可以直接访问,我们可以提供一个方法
func (s *student) GetScore() float64{
  return s.score
}
package main 
import (
  "fmt"
  "go_code/chapter10/factory/model"
)
func main(){
  // 创建要给Student实例
  //var stu = model.Student{
 //   Name:"tom",
  //  Score:78.9,
  //}
  // 定义student结构体是收字母小写,我们可以通过工厂模式来解决
  var stu = model.NewStudent("tom~~",88.8)
  
  fmt.Println(*stu)
  fmt.Println("name=",stu.Name,"score=",stu.GetScore())
}

感谢观看,我们下次见

目录
相关文章
|
6月前
|
安全 Java 编译器
第十一章 Golang面向对象编程(下)
第十一章 Golang面向对象编程(下)
61 2
|
安全 Go
Golang 语言是面向对象编程风格的编程语言吗?
Golang 语言是面向对象编程风格的编程语言吗?
47 0
|
2月前
|
Go
Golang语言结构体(struct)面向对象编程进阶篇(封装,继承和多态)
这篇文章是关于Go语言中结构体(struct)面向对象编程进阶篇的教程,涵盖了Go语言如何实现封装、继承和多态,以及结构体内存布局的相关概念和案例。
126 4
|
2月前
|
Go
Golang语言结构体(struct)面向对象编程基础篇
这篇文章是关于Go语言中结构体(struct)面向对象编程的基础教程,详细介绍了面向对象编程在Go语言中的应用、结构体的定义与初始化、方法定义、跨包实例化结构体以及结构体方法和普通函数的区别。
31 4
|
6月前
|
Go 开发者
Golang深入浅出之-Go语言方法与接收者:面向对象编程初探
【4月更文挑战第22天】Go语言无类和继承,但通过方法与接收者实现OOP。方法是带有接收者的特殊函数,接收者决定方法可作用于哪些类型。值接收者不会改变原始值,指针接收者则会。每个类型有相关方法集,满足接口所有方法即实现该接口。理解并正确使用这些概念能避免常见问题,写出高效代码。Go的OOP机制虽不同于传统,但具有灵活性和实用性。
49 1
Golang面向对象编程之构造函数【struct&new】
Golang面向对象编程之构造函数【struct&new】
Golang面向对象编程之继承&虚基类【组合&接口】
Golang面向对象编程之继承&虚基类【组合&接口】
|
缓存 安全 Java
Golang 面向对象编程
Golang 面向对象编程
81 0
|
2月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
98 4
Golang语言之管道channel快速入门篇
|
2月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
62 4
Golang语言文件操作快速入门篇