Golang面向对象编程-结构体

面向对象(变量)编程-结构体

1.结构体
type 结构体名词 struct{字段/属性 type }
与隐射map[type1]map[type2][type3]所含的元素必须是相同类型相较,struct能包含更多类型的元素,即使得一个物品的不同属性得到表示,并且更利于数据的管理和维护。
案例说明

type Cat struct {
	Name  string
	Age   int
	Color string
}

func main() {
	var cat1 Cat
	fmt.Println(cat1) //{ 0 }  字符串未赋值默认为“”,int默认值为0

	cat1.Name = "小花"
	cat1.Age = 4
	cat1.Color = "花色"
	// 只取猫的年龄
	fmt.Println(cat1.Age) //4

	cat2 := Cat{Name: "小白", Age: 3, Color: "白色"}
	fmt.Println(cat2) //{小白 3 白色}

2.Golang OOP 说明
1)Golang支持面向对象编程(oop),但是和传统的面向对象编程有区别,并不是纯粹的面向对象编程(例如面向对象编程的典型语言Java,Java的面向对象编程不存在全局变量,但在Golang中具有全局变量)。所以说Golang语言支持面向对象编程特性是比较准确的。
2) Golang中没有类(class)。Go语言的结构体struct与其它编程语言的类(class)有同等地位,可以理解为Golang是基于struct实现OOP特性的。
3)Golang面向对象编程十分简洁,去掉了传统OOP语言的方法重载、构造函数、析构函数、隐藏指针等等,继承方法也更加简化。
4)GOlang仍然有面向对象编程的继承封装多态的特性,但实现方式和其它OOP语言不同。比如继承: Golang中没有其它语言使用继承时的extend关键字,而是通过匿名函数来实现继承。
5)Golang本身就是一种天然的支持面向对象编程的语言,OOP本身是语言类型系统(type system)的一部分 ,通过接口(interface)关联,耦合性低,非常灵活。所以在Golang中面向接口编程是十分重要的特性。(故有说法:Golang中将面向对象编程称为面向接口编程)

3.结构体变量(实例)在内存布局
在这里插入图片描述
从上图可知,结构变量是值传递;结构变量的地址与其首个元素地址相同;结构变量中元素间地址的间隔根据其类型决定的,例如string占16个字节,int占8个字节。

4.结构体字段/属性的注意事项
1)字段声明语法同变量,即 字段名 字段类型
2)字段类型可以是:基本类型、数组或引用类型
3)创建一个结构体变量后,在未赋值的情况下,其字段对应的都是一个零值(默认值),规则如下:
bool类型:false;数值类型:0;字符串:"";数组类型的默认值与其元素类型相关;指针,slice,map的零值都是nil,即还未分配空间,要使用指针直接赋地址即可,但slice和map的使用须先进行make操作。

type Person struct {
	Name  string
	Age   int
	Score [5]float64
	ptr   *int
	slice []int
	m     map[string]string
}
func main() {
	var p1 Person
	fmt.Println(p1) //{ 0 [0 0 0 0 0] <nil> [] map[]}

	p1.slice = make([]int, 5)
	p1.slice[0] = 5

	p1.m = make(map[string]string)
	p1.m["Name"] = "Tom"

	var n int = 10
	p1.ptr = &n

	fmt.Println(p1) //{ 0 [0 0 0 0 0] 0xc0000120d0 [5 0 0 0 0] map[Name:Tom]}
}

4)不同结构体变量的字段是相互独立的,互不影响,一个结构体变量字段的更改,不影响另外一个。结构体是值类型默认为值拷贝

type monster struct {
	Name string
	Age  int
}

func main() {
	var monster1 monster
	monster1.Name = "牛魔王"
	monster1.Age = 500

	monster2 := monster1
	monster2.Name = "青牛精"
	fmt.Println("monster1:", monster1)// monster1: {牛魔王 500}
	fmt.Println("monster2:", monster2)// monster2: {青牛精 500}
}

在这里插入图片描述

5.创建结构体变量和访问结构体字段
1)直接声明
2) 类型推导
3)使用new(),指针赋值,引用传递
4)指针传递,并能声明时赋值

type Person struct {
	Name string
	Age  int
}

func main() {

	// 方式一 直接声明
	var p1 Person
	p1.Name = "tom"
	p1.Age = 15
	fmt.Println(p1) //{tom 15}

	//方式二 类型推导
	p2 := Person{Name: "Marry", Age: 18}
	fmt.Println(p2) //{Marry 18}
	p3 := Person{"John", 18}
	fmt.Println(p3) // {John 18}
	p4 := Person{}
	p4.Name = "Alice"
	fmt.Println(p4) // {Alice 0}

	// 方式三 使用new(),指针赋值,引用传递
	p5 := new(Person)
	p5.Name = "Lisa" //等价于(*p5).Name = "Lisa"
	p5.Age = 18      //等价于(*p5).Age = 18
	fmt.Println(*p5) //{Lisa 18}

	//方式四,指针传递,并能声明时赋值
	var p6 *Person = &Person{"Jisoo", 19}
	fmt.Println(*p6) //{Jisoo 19}
	p7 := &Person{}
	p7.Name = "Rose" //等价于(*p7).Name = "Rose"
	p7.Age = 18      //等价于(*p7).Age = 18
	fmt.Println(*p7) //{Rose 18}
	p8 := &Person{
		Name : "Jinne"
		Age:= 19
	}
}

说明:
(1)第三种和第四中返回方式是结构体指针
(2)结构体指针访问字段的标准方式应是**:(结构体指针).字段名*,(*p5).Name = "Lisa"
(3)在Golang中,设计者对结构体指针访问字段做了简化,也支持结构体指针.字段名p5.Name = "Lisa"。其实质上,go编译器底层会对p5.Name做转化,转成标准格式为(*p5).Name

6.struct类型的内存分布机制
通过一个变量修改结构体的内容时,使用引用传递。
在这里插入图片描述
noting:

var p1 Person
p1.Age = 10
p1.Name = "Alex"
var p2 *Person = &p1
fmt.Println(*p2.Age)//不能这样写
fmt.Println(p2.Age)
fmt.Println((*p2).Age)

fmt.Println(*p2.Age)写法是错误的,会报错,原因是.的运算优先级比*高。

这里是引用

7.结构体的注意事项和使用细节
1)结构体的字段/属性在内存中是连续存在的
noting:
(1)对于结构体字段是基础类型的、数组,非引用类型的,结构体的字段在内存中都是连续存在的
(2)对于结构体字段是ptrslicemap等引用类型。结构体实例所包含的应用类型的本身地址在内存中是连续存在的;但所指向的引用内容的地址并不是一定连续存在的,这取决于系统运行时是如何分配的。
(3)故对于字段是基础类型等的结构体可以采用地址的方式进行编程,能大大提高程序运行效率;对于引用类型字段的结构体使用地址方式进行编程时须谨慎。

案例说明:

package main

import "fmt"

type Point struct {
	x, y int
}

type Rectangle struct {
	leftUp, rightDown Point
}

type Rec struct {
	leftUp, rightDown *Point
}

func main() {
	res1 := Rectangle{Point{1, 2}, Point{3, 4}}
	//res1 有四个int,在内存中是连续分布的
	// 打印地址
	fmt.Printf("res1.leftUp.x 地址 =%p,res1.leftUp.y 地址 =%p,res1.rightDown.x 地址 =%p,res1.rightDown.y 地址 =%p\n",
		&res1.leftUp.x, &res1.leftUp.y, &res1.rightDown.x, &res1.rightDown.y)
	//res1.leftUp.x 地址 =0xc0000101c0,res1.leftUp.y 地址 =0xc0000101c8,res1.rightDown.x 地址 =0xc0000101d0,res1.rightDown.y 地址 =0xc0000101d8
	//int类型 地址间隔 8个字节

	res2 := Rec{&Point{10, 20}, &Point{30, 40}}
	// res2 有两个*Point类型指针内容,这两个*Point类型的本身地址也是连续的
	// 但是这两个指针指向的内容的地址不一定是连续的,这取决于系统在运行时是如何分配的

	// 打印res2中的两个指针本身地址
	fmt.Printf("res2.leftUp 本身地址 =%p,res1.rightDown 本身地址 =%p\n", &res2.leftUp, &res2.rightDown)
	// res2.leftUp 本身地址 =0xc00004e230,res1.rightDown 本身地址 =0xc00004e238
	// 连续,隔8个字节 *int

	// 打印指针指向的内容的地址
	fmt.Printf("res2.leftUp 指向的地址 =%p,res1.rightDown 指向的地址 =%p\n", res2.leftUp, res2.rightDown)
	// res2.leftUp 指向的地址 =0xc0000120c0,res1.rightDown 指向的地址 =0xc0000120d0
	//  连续隔16个字节 2个int
}

图例说明:
在这里插入图片描述

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

type student struct {
	Name string
	Age  int
}
type student1 struct {
	Name string
	Age  int
}
func main() {
	var a student
	var b student1
	a = student(b)
	//结构体之间允许交换,但有严格要求:结构体的字段要完全一致(名字、类型、个数)
	fmt.Println(a, b) //{ 0} { 0}

}

3)结构体进行type重新定义(类似取别名),Golang会将其视为新的数据类型。前后两者可以强制相互转换。

type student struct {
	Name string
	Age  int
}

type Stu student

type integer int

func main() {
	//结构体的重新定义
	var a student
	var b Stu
	a = student(b)
	fmt.Println(a, b) //{ 0} { 0}

	//内置类型取别名
	var i integer = 10
	var j int = 11
	j = int(i)
	println(i, j) //10 10

}

4)
struct的每个字段上,可以写上一个标签tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化。(序列化即将结构体变量转换成字符串形式,即对代码说明:将struct变量进行json处理)。

序列化使用场景:
在这里插入图片描述

使用案例:

type monster struct {
	Name   string `json:"name"` // 字段在json里的键为"name"    struct tag
	Age    int    `json:"age"`  // 字段在json里的键为"age"
	Attack string `json:",omitempty"`
	//字段在Json里为默认值输出,如果遇到字段为空值会跳过
}

func main() {

	m1 := monster{"牛魔王", 1000, "芭蕉扇"}
	// 将monster变量序列化为json格式字符串
	jsonStr, err := json.Marshal(m1)
	// json.Marshal(v interface{}) ([]byte, error) 使用到反射机制

	if err != nil {
		println("json处理错误,", err)
	}
	fmt.Println(jsonStr) //输出[]byte切片
	fmt.Println(string(jsonStr))//{"name":"牛魔王","age":1000,"Attack":"芭蕉扇"}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值