Go_面向对象编程

面向"对象"编程

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

  • Golang也支持面向对象编程(oop),但是和传统的面向对象编程有区别,不是纯粹的面向对象语言

  • Golang支持面向对象编程特性

  • Go语言中,没有类(class)的概念,用结构体(struct);Golang是基于struct来实现OOP特性

  • Golang面向对象编程非常简洁,去掉传统OOP语言的方法重载、构造函数和析构函数、隐藏的this指针等等

  • Golang仍然有面向对象编程的继承、封装和多态的特性,只是实现的方式和其他OOP语言不一样;如继承:Golang没有extends关键字,继承是通过匿名字段来实现

  • Golang面向对象很优雅,通过接口(interface)关联,耦合性低,也非常灵活。

  • Golang中面向接口编程是其非常重要的特性

#结构体(struct)
问题引入:养猫问题
养了两只猫,一只名字叫小白,今年3岁,白色;另一只叫小花,今年2岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名字错误,就显示没养这只猫。

//使用现有技术解决
/*
	1.使用变量来处理
	2.使用数组
*/
func main() {
	//1.使用变量来处理
	var cat1Name string = "小白"
	var cat1Age int = 2
	var cat1Color string = "白色"
	var cat2Name string = "小花"
	var cat2Age int = 1
	var cat2Color string = "花色"

	//2.使用数组
	var catNames = [2]string{"小白", "小花"}
	var catAges = [2]int{2, 1}
}
  • 现有技术的缺陷:
    • 不利于数据的管理和维护
    • 如果希望对一只猫的属性(名字、年龄、颜色)进行操作(绑定方法)也不好处理

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

  • 结构体是自定义的数据类型

  • 结构体变量(实例)是具体的、实际的、代表一个具体变量
    结构体与结构体变量关系

  • 上图说明:

    • 将一类事物的特性提取出来(比如猫类),形成一个新的数据类型,就是一个结构体
    • 通过这个结构体,可以创建多个变量(实例/对象)
    • 事物可以是猫类、也可以是person、fish 或者某一个工具类
  • 使用struct来完成上述养猫问题

package main

import "fmt"

//定义一个Cat结构体,将cat的各个字段/属性信息,放入到cat结构体进行管理

type Cat struct {
	Name  string
	Age   int
	Color string
}

func main() {
	//创建一个Cat变量
	var cat1 Cat
	fmt.Println(cat1)
	cat1.Name = "小白"
	cat1.Age = 3
	cat1.Color = "白色"
	fmt.Println("猫猫的信息如下")
	fmt.Printf("猫猫的名字%v,它的年龄%v, 它的颜色%v", cat1.Name, cat1.Age, cat1.Color)
	fmt.Println()

	cat2 := Cat{"小花", 2, "花色"}
	fmt.Printf("猫猫的名字%v,它的年龄%v, 它的颜色%v", cat2.Name, cat2.Age, cat2.Color)
}

结构体变量在内存中的布局

结构体内存布局

结构体声明和使用陷阱

  • 结构体声明
    • type 结构体名称 struct{
      field type
      field type
      }
//举例
type Cat struct {
	Name  string  //字段
	Age   int
	Color string
}
  • 从概念或叫法上看:结构体字段 = 属性 = field

  • 字段是结构体的一个组成部分,一般是基本数据类型、数组、也可以是引用类型。

  • 注意事项

    • 字段声明语法同变量(变量名 类型)
    • 在创建一个结构体变量后,若没有给字段赋值,都对应默认零值(若字段类型是指针、slice 和 map ,零值都是nil; 它们使用时需要先make,才能使用)
    • 不同结构体变量的字段是独立,互不影响
    • 结构体是值类型,默认为值拷贝
package main

import "fmt"

// 若结构体字段类型是 指针、slice、map
type Person struct {
	Name   string
	Age    int
	Scores [5]float64
	ptr    *int              //指针
	slice  []int             //切片
	map1   map[string]string // map
}

func main() {
	//创建一个Cat变量
	var p1 Person
	fmt.Println(p1)
	//使用slice
	p1.slice = make([]int, 10)
	p1.slice[0] = 100
	fmt.Println(p1)
	p1.map1 = make(map[string]string)
	p1.map1["地址"] = "北京"
	fmt.Println(p1)

}

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

  • 方式一:直接声明
    • var person Person
  • 方式二:
    • var person Person = Person{}
  • 方式三:直接返回结构体指针
    • var person *Person = new(Person)
  • 方式四:返回结构体指针
    • var person *Person = &Person{}
type Person struct {
	Name string
	Age  int
}

func main() {
	//方式一
	var p1 Person
	p1.Name = "tom"
	p1.Age = 33
	fmt.Println(p1)
	//方式二
	p2 := Person{"mary", 20}
	fmt.Println(p2)
	//方式三
	p3 := new(Person)
	fmt.Printf("p3的类型%T\n", p3)

	var p4 *Person = new(Person)
	fmt.Printf("p4的类型%T\n", p4)
	//因为p3、p4是一个指针,标准的给字段赋值方式
	(*p3).Name = "anger"
	(*p3).Age = 23
	fmt.Println(*p3)
	//上述标准赋值方法,可以这样写 p3.Name = "anger";
	//原因:go的设计者 为了程序员使用方便,底层对 p3.Name = "anger" 进行处理,会给 p3 加上 取值运算 (*p3).Name = "anger"
	/*
	(*p3).Name = "anger"不能写成*p3.Name = "anger",因为 . 运算符的优先级比 * 高
	
	*/
	
	
	//方式四
	var person *Person = &Person{"joy", 8}
	//因为person 是一个指针,因此标准的访问字段的方法:(*person).Name = "bob"
	//(*person).Name = "Bob"
	//person.Age = 12
	fmt.Println(*person)
}

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

  • 注意:

    • (*p3).Name 不能写成*p3.Name ,因为 . 运算符的优先级比 * 高
  • 当结构体是值传递时,结构体的所有字段在内存中连续分布

结构体字段内存分布

package main

import "fmt"

type Rect struct {
	leftUp, rightDown Point
}
type Point struct {
	x int
	y int
}

func main() {
	r1 := Rect{Point{1, 2}, Point{3, 4}}

	//r1有四个int ,在内存中是连续分布的
	fmt.Printf("r1.leftUp.x 地址=%p, r1.leftUp.y 地址=%p, r1.rightDown.x 地址=%p, r1.rightDown.y 地址=%p", &r1.leftUp.x, &r1.leftUp.y, &r1.rightDown.x, &r1.rightDown.y)
}
  • 当结构体是引用传递时,结构体的所有字段在内存中连续分布
package main

import "fmt"

type Rect2 struct {
	leftUp, rightDown *Point
}
type Point struct {
	x int
	y int
}

func main() {
	//r2有两个 *Point类型,这两个 *Point类型的本身地址也是连续的,但是他们指向的地址不一定是连续的(这个要看系统当时在运行时是怎么分配的)
	r2 := Rect2{&Point{10, 20}, &Point{30, 40}}

	//r1有四个int ,在内存中是连续分布的
	fmt.Printf("r2.leftUp本身地址=%p,  r2.rightDown本身地址=%p\n", &r2.leftUp, &r2.rightDown)
	fmt.Printf("r2.leftUp指向的地址=%p,  r2.rightDown指向的地址=%p\n", r2.leftUp, r2.rightDown)
}

  • 结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字、个数和类型)
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) //可以转换,但是结构体的字段要完全一样
	fmt.Println(a, b)
}
  • 结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
案例一:

type Student struct{
	Name string
	Age int
}
type Stu Student

func main(){
	var stu1 Student
	var stu2 Stu
	stu2 = stu1//该处错误,可以修改为stu2 = Stu(stu1)
	fmt.Println(stu1, stu2)
}

案例二
type integer int

func main(){
	var i integer = 10
	var j int = 20
	j = i//该处错误,修改为 j = int(i)
	fmt.Println(i, j)
}
  • struct的每个字段上,可以写上一个tag,该tag可以通过反序列机制获取。常见的使用场景就是序列化和反序列化。

    • 序列化使用场景
      序列化场景
  • 案例演示:

package main

import (
	"encoding/json"
	"fmt"
)

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

func main() {

	//先创建一个monster变量
	monster := Monster{"牛魔王", 500, "芭蕉扇"}

	//将monster变量序列化成 json格式字串
	// json.Marshel 函数中使用到反射,其返回是一个[]byte 和 error
	jsonStr, err := json.Marshal(monster)
	if err != nil {
		fmt.Println("json处理错误", err)
	}
	fmt.Println("jsonStr", string(jsonStr))

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值