Go语言基础之8--面向对象编程1之结构体(struct)
一、结构体详解
1.1 声明和定义
1、Go中面向对象是通过struct来实现的, struct是用户自定义的类型
2、Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
3、结构体是由一系列具有相同类型或不同类型的数据构成的数据集合
4、结构体是将零个或者多个任意类型的命令变量组合在一起的聚合数据类型,每个变量都叫做结构体的成员。其实简单理解,Go语言的结构体struct和其他语言的类class有相等的地位,但是GO语言放弃了包括继承在内的大量面向对象的特性,只保留了组合这个基础的特性。所有的Go语言类型除了指针类型外,都可以有自己的方法。
下面我们来进行一个简单的定义:
type User struct { //type是用来定义一种类型,此处用于定义user为结构体(struct)类型 Username string Sex string Age int AvatarUrl string }
对于上面这个结构体,我们定义的有三种方式:
方法1:
var stu student
方法2:
var stu *Student = new(Student)
方法3:
var stu *Student = &Student
上面三种方法中,方法2和方法3的效果是一样的,返回的都是指向结构体的指针,访问的方式如下:
stu.Name,stu.Age
(*stu).Name,(*stu).Age而这种方法中可以换成上面的方法直接通过stu.Name访问
这里是go替我们做了转换了,当我们通过stu.Name访问访问的时候,go会先判断stu是值类型还是指针类型如果是指针类型,会替我们改成(*stu).Name
1.2 特点
1)结构体可以储存不同的类型
2)在内存中占据连续的内存空间
3)结构体每一个项所占用的内存大小不一定相同
4)结构体支持组合,即结构体可以保护结构体
5)通过操作结构体的项名t.x、t.y、t.z来获取字段值
6)判断两个结构体是否相同,需要看结构体的类型是否相同,
然后看项的顺序、项的名称、项的类型等等.
7)结构体的成员初始化是通过操作字段赋值来完成
1.3 应用场景
关于Go中的struct:
1、用于定义复杂的数据结构
2、struct里面可以包含多个字段(属性),字段可以是任意类型
3、struct类型可以定义方法(注意和函数的区别)
4、struct类型是值类型
5、struct类型可以嵌套
6、Go语言没有class类型,只有struct类型
1.4 初始化
1.4.1 初始化方法1
var user User user.Age = 18 user.Username = "user01" user.Sex = "男" user.AvatarUrl = "http://my.com/xxx.jpg" //注意:使用变量名+ ‘.’ + 字段名访问结构体中的字段
实例1-1
package main import ( "fmt" ) type User struct { Name string Sex string Age int AvatarUrl string } func main() { var user User user.Age = 100 user.Name = "jim" user.Sex = "男" user.AvatarUrl = "http://my.com/xxx.jpg" //注意:使用变量名+ ‘.’ + 字段名访问结构体中的字段 fmt.Printf("user.name:%s user.age:%d\n", user.Name, user.Age) }
执行结果如下:
1.4.2 初始化方法2
var user User = User { Username: "user01", Age: 18, Sex: "男", AvatarUrl: "http://my.com/xxx.jpg", }
更为简单的方法:
user := User { Username: "user01", Age: 18, Sex: "男", AvatarUrl: "http://my.com/xxx.jpg", }
注意:我们针对结构体也可以部分初始化
1.4.3 初始化的默认值
结构体初始化的默认值为各种类型的默认值,比如:int为0,string为" "
实例1-2
package main import "fmt" type User struct { Name string Sex string Age int AvatarUrl string } func main() { var user User fmt.Printf("%#v\n", user) //用Go的语法打印。比如main.People{name:”sam”, phone:main.Phone{mobile:”12345”, office:”67890”}} }
执行结果如下:
1.5 结构体类型的指针
注意: &User{}和new(User)
本质上是一样的,都是返回一个结构体的地址
1.5.1 &User{}
package main import "fmt" type User struct { Username string Age int Sex string AvatarUrl string } func main() { var user *User = &User{ Username: "user01", Age: 18, Sex: "男", AvatarUrl: "http://my.com/xxx.jpg", } fmt.Printf("%p %#v\n", &user, user) }
执行结果如下:
1.5.2 new(User)
package main import "fmt" type User struct { Username string Age int Sex string AvatarUrl string } func main() { var user *User = new(User) user.Age = 18 user.Username = "user01" user.Sex = "男" user.AvatarUrl = "http://my.com/xxx.jpg" fmt.Printf("%p %#v\n", &user, user) }
执行结果如下:
1.6 结构体的内存布局
结构体的内存布局: 占用一段连续的内存空间。
下面我们通过这个实例来看看:
实例1-3
package main import "fmt" type User struct { Name string Sex string Age int AvatarUrl string } func main() { var user User fmt.Printf("Name addr:%p\n", &user.Name) fmt.Printf("Sex addr:%p\n", &user.Sex) fmt.Printf("Age addr:%p\n", &user.Age) fmt.Printf("AvatarUrl addr:%p\n", &user.AvatarUrl) }
执行结果如下:
我们可以通过输出结果发现结构体的内存布局是相对连续的。
1.7 结构体无构造函数
结构体没有构造函数, 必要时需要自己实现
下面通过这个实例来进行理解:
实例1-4
package main import ( "fmt" ) type User struct { //定义User结构体 Name string Sex string Age int Url string } func NewUser(name string, sex string, age int, url string) User { //NewUser就是一个结构体的构造函数,之后我们就可以调用它 var user User user.Name = name user.Sex = sex user.Age = age user.Url = url return user } func main() { user1 := NewUser("harden", "male", 19, "xxxxxx") fmt.Printf("user1:%#v\n", user1) }
执行结果如下图所示:
1.8 结构体的比较
如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话,两个结构体将可以使用==或!=运算符进行比较。相等比较运算符将比较两个结构体的每个成员
实例如下:
实例1-5
import ( "fmt" ) type Point struct { x int y int } func main() { p1 := Point{5, 2} p2 := Point{1, 3} p3 := Point{5, 2} fmt.Println(p1 == p2) //false fmt.Println(p1 == p3) //true }
执行结果如下图所示:
1.9 匿名字段
匿名字段:既没有名字的字段
注意:匿名字段默认采用类型名作为字段名,当然匿名字段类型名是不能重复的,如果是重复的,那必然报错了,因为字段名就重复冲突了。
具体使用见如下实例:
实例1-6
package main import ( "fmt" ) type User struct { Name string Sex string Age int AvatarUrl string int //匿名字段 string //匿名字段 } func main() { var user User user.int = 100 user.string = "hello" fmt.Printf("user:%#v\n", user) }
执行结果如下:
1.10 结构体嵌套
就是一个结构体嵌套另一个结构体
具体使用见如下实例:
package main import ( "fmt" ) type Address struct { City string Province string } type User struct { Name string Sex string Age int AvatarUrl string int address Address //结构体嵌套,User中嵌套Address结构体 } func main() { var user User //方法1: user.address.City = "yuncheng" //引用赋值 user.address.Province = "shanxi" fmt.Printf("user:%#v\n", user) //方法2: user01 := User{ address: Address{ City: "beijing", Province: "beijing", }, int: 90, } fmt.Printf("user:%#v\n", user01) }
执行结果如下:
1.11 匿名结构体
匿名结构体还是建立在结构体嵌套的基础上
实例1-7
package main import ( "fmt" ) type Address struct { City string Province string } type User struct { Name string Sex string Age int AvatarUrl string int Address //匿名结构体,没有写字段名 } func main() { var user User //方法1: user.Address.City = "yuncheng" //引用赋值 user.Address.Province = "shanxi" fmt.Printf("user:%#v\n", user) //方法2: user01 := User{ Address: Address{ City: "beijing", Province: "beijing", }, int: 90, } fmt.Printf("user:%#v\n", user01) //方法3: fmt.Printf("city:%s province:%s\n", user.City, user.Province) //go语言中是如何实现的呢?其也是遍历,先从结构体中找字段名看是否能匹配上,如果不能匹配上再找匿名字段和匿名结构体(看匿名结构体中的字段是否能匹配上),直到匹配成功,如果匹配不到,就会报错 }
执行结果如下:
1.12 匿名结构体与继承
go语言里是可以实现继承的,其是通过匿名结构体来实现继承的。
下面通过一个简单实例来看下:
实例1-8
package main import ( "fmt" ) type Animal struct { Name string Age int } type People struct { Animal Sex string } func main() { var p People p.Age = 18 p.Name = "user01" p.Sex = "male" fmt.Printf("people:%#v\n", p) }
执行结果如下:
1.13 冲突解决
1、优先级:
优先从结构体自身找-->其次从匿名结构体找
2、如果结构体自身,和结构体继承的匿名结构体中都有同一字段,优先肯定是查找结构体自身,但是如果想查找继承的匿名结构体中的该字段,那就必须写全路径了,不能简写了。
下面通过一个实例来加深理解:
实例1-9
package main import ( "fmt" ) type Address struct { City string Province string } type User struct { Name string Sex string Age int AvatarUrl string City string int Address //匿名结构体,没有写字段名 } func main() { var user User user.City = "xian" user.Address.City = "yuncheng" //引用赋值 user.Address.Province = "shanxi" fmt.Printf("user:%#v\n", user) fmt.Printf("city:%s city_address:%s province:%s\n", user.City, user.Address.City, user.Province) }
执行结果如下所示:
解释:我们可以发现结构体本身有City字段,继承的Address匿名结构体其内部也有City字段,同时存在City字段,当我们引用时,user.City打印出来的是结构体本事的City字段,值为xian,要想打印出匿名结构体Address中City字段,需要写全路径:user.Address.City,值为yuncheng
1.14 字段可见性
指的是结构体中字段大写表示可公开访问,小写表示私有,和包里面的变量命名是一样的。总结下来:struct字段是大写字母开头,那么该字段就是导出的(包外可见),struct字段是小写字母开头,那么该字段就是不可导出的(包外不可见)
type User struct { Username string //公有 Sex string //公有 Age int //公有 avatarUrl string //私有 CreateTime string //公有 }
针对结构体的命名是否也分大小写(大写公有,小写私有),目前还不确定?之后确定了再来补充,正常来说是分的。
1.15 结构体与tag应用
tag是结构体的元信息,可以在运行的时候通过反射(映射)的机制读取出来
当定义了一个结构体,我们在结构体后面定义的tag有什么作用?
答:会将tag信息与结构体的类型信息都会存在一块,用的时候可以通过反射机制取出来。
格式:
type User struct { Username string `json:"username",db:"user_name"` //json就是在json序列化时用,db就是在操作数据库时用 Sex string `json:"sex"` Age int `json:"age"` avatarUrl string CreateTime string }
注意:字段类型后面,以反引号括起来的key-value结构的字符串,多个tag以逗号隔开。
那具体怎么使用呢?我们通过下面这个例子来了解一下。
实例1-10
package main import ( "encoding/json" //json序列化需要借助这个包 "fmt" ) type User struct { Name string `json:"name"` //默认我们go语言转化成json串后key还是用的字段名,但是其他语言可能是小写的,所以我们就可以在字段类型后面紧接着写一个tag,方便使用,这样就表示在json序列化完使用的是小写name Sex string `json:"sex"` Age int `json:"age"` AvatarUrl string `json:"avatar_url"` } func main() { var user User user.Age = 100 user.Sex = "male" user.Name = "jim" user.AvatarUrl = "https://baidu.com/xx.jpg" //json.Marshal就是将结构体或其他类型转化成一个json串 data, err := json.Marshal(user) //为什么要序列化为json(将go中的对象转化为json)?因为单单在go语言中我们用不到这个json,直接引用变量即可,用json是go程序要和其他程序(并不一定是go)通信才会转化成一个统一的json格式,其在序列化成自己语言的对象,这样就完成了一个信息数据的交互。就相当于一个桥梁 if err != nil { fmt.Printf("marshal failed, err:%v\n", err) return } fmt.Printf("json:%v\n", string(data)) //因为data是一个[]byte,所以必须要转化成字符串 }
执行结果如下所示: