【Golang】快速复习指南QuickReview(六)——结构体struct
实际编程时,经常需要用相关的不同类型的数据来描述一个数据对象。C#中有类(Class
),结构(Struct
),当然类就不介绍了。Golang
中叫结构体(C
,C++
好像还是结构体),单词还是Struct
,无论是在Golang还是C#,struct都是一个值类型。
struct 结构体
1.C#的结构struct
1.1 构造函数
- struct有默认无参构造函数,不能再显式定义这个无参构造函数,编译器始终会生成一个默认的构造器
结构不能包含显式的无参数构造函数,默认构造器会把所有字段的自动初始化
public struct Position
{
//public Position()
//{} // 这是不允许的
public double Lon { get; set; }
public double Lat { get; set; }
}
//没有自定义构造函数,可不适用new
Position positon;
positon.Lon = 39.26;
positon.Lat = 115.25;
- 自定义的有参构造函数必须初始化所有的字段
public struct Position
{
//自定义构造函数需要初始化所有字段、属性
public Position(double lon, double lat)
{
Lon = lon;
Lat = lat;
}
//结构中不能实例属性或字段初始值设定项
//public double Lon { get; set; }=5.5;
public double Lon { get; set; }
public double Lat { get; set; }
}
//有参构造函数,必须使用new为struct类型的变量赋值
Position positon = new Position(39.26, 39.26);
1.2 方法
结构是可以包含自己的方法。
public struct Position
{
//自定义构造函数需要初始化所有字段、属性
public Position(double lon, double lat)
{
Lon = lon;
}
//结构中不能实例属性或字段初始值设定项
//public double Lon { get; set; }=5.5;
public double Lon { get; set; }
public double Lat { get; set; }
//重写方法
public override string ToString() => $"经度:{Lon}, 纬度{Lat})";
}
虽然struct在实际开发过程中使用频率较低,但是使用时需要注意:
- 将结构类型变量作为参数传递给方法或从方法返回结构类型值时,将复制结构类型的整个实例。这可能会影响高性能方案中涉及大型结构类型的代码的性能。 通过按引用传递结构类型变量,可以避免值复制操作。 使用 ref、out 或 in 方法参数修饰符,指示必须按引用传递参数。使用 ref 返回值按引用返回方法结果。在
Golang
中也会存在这个问题,下一节会提到。
2.Golang的结构体struct
2.1 定义
像定义函数类型那样 type
开头,只是把func()
换为关键字struct
type person struct {
name string
age int8
}
//结构体匿名字段
type student struct {
string
int
}
func main() {
var p1 person
p1.name = "RandyField"
p1.age = 28
//匿名结构体
var user struct {
Name string
Age int
}
user.Name = "Randy"
user.Age = 18
}
2.2* 结构体指针--重点
2.2.1 new
type person struct {
name string
age int8
}
//结构体匿名字段
/*
这里匿名字段的说法并不代表没有字段名,
而是默认会采用类型名作为字段名,
结构体要求字段名称必须唯一,
因此一个结构体中同种类型的匿名字段只能有一个。
*/
type Student struct {
string
int
}
func main() {
//new分配结构体实例的指针(内存地址) 实例化
var p2 = new(person)
fmt.Printf("the type of p2 is %T\n", p2) //*main.person
//没有初始化的结构体 所有的成员变量都是对应类型的零值
fmt.Printf("p2=%#v\n", p2)
stu := &Student{}
stu.int = 18
stu.string = "中学生"
}
the type of p2 is *main.person
p2=&main.person{name:"", age:0}
2.2.2 &
type person struct {
name string
age int8
}
func main() {
p3 := &person{} //使用&对结构体进行取地址操作=> 使用new实例化
p3.name = "kobe"
p3.age = 30 //这是语法糖
(*p3).age = 29 //底层
}
- 初始化
当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值。这点跟C#存在有参构造函数的结构是不一致。
func main() {
p4 := person{
name: "RandyField",
age: 18,
}
fmt.Printf("p4=%#v\n", p4)
//结构体指针初始化
p5 := &person{
name: "RandyField",
age: 28,
}
fmt.Printf("p5=%#v\n", p5)
}
- 另类初始化
不建议使用,但是为了能看懂别人的开源代码,还是知道机制为妙。
p6 := &person{
"RandyField",
28,
}
fmt.Printf("p6=%#v\n", p6)
2.3* 空结构体
特殊地:空结构体是不占用空间的。
var v struct{}
fmt.Println(unsafe.Sizeof(v)) // 0
2.4 构造函数
Golang
是没有构造函数的,但是我们可以通过方法去创建一个返回struct
。
type person struct {
name string
age int8
}
// 复杂的结构体,值拷贝性能开销会比较大,故返回结构体指针。
func newPerson(name string, age int8) *person {
return &person{
name: name,
age: age,
}
}
2.5 方法
Golang
结构体的方法并不像C#
那样直接就在定义的{}
中定义即可。它必须分开定义,这就出现一个难题,定义的这个方法是属于这个结构体的,现在分开定义,怎么办?接收者应运而生,指明这个方法是属于结构体,只能通过结构体来调用。
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
type person struct {
name string
age int8
}
func (p *person) MakeMoney(work string) (res string) {
return "赚钱了"
}
- 接收者既可以是指针类型,也可以是值类型
- 值类型,如果做出了操作,只针对副本有效
使用指针类型场景:
- 需要修改接收者中的值
- 接收者是拷贝代价比较大的大对象
- 如果有某个方法使用了指针类型接收者,其他的方法也应该使用指针类型接收者。
2.5.1 类型定义 与 类型别名
方法的接收者不仅仅可以是结构体,还可以是类型定义
type NewInt int //类型定义 新类型 可以作为方法的接收者
type MyInt = int //类型别名 编译完成时并不会有`MyInt`类型, 这个不能作为方法接收者的
func (m MyInt) Say() {
fmt.Println("我其实是int。")
}
2.6 嵌套结构体
type student struct {
name string
age int
}
type middleSchoolStudent struct {
lesson []string
*student
}
func (stu *student) play(sport []string) {
for _, v := range sport {
fmt.Println(stu.name, "参加如下运行项目:", v)
}
}
func (m *middleSchoolStudent) learn() {
for _, v := range m.lesson {
fmt.Println(m.name, "学习如下课程:", v)
}
}
func main(){
s := &middleSchoolStudent{
lesson: []string{"语文", "数学", "英语", "物理", "化学"},
student: &student{
name: "小明",
age: 13,
},
}
s.learn()
s.play([]string{"篮球", "足球", "乒乓球"})
}
小明 学习如下课程: 语文
小明 学习如下课程: 数学
小明 学习如下课程: 英语
小明 学习如下课程: 物理
小明 学习如下课程: 化学
小明 参加如下运行项目: 篮球
小明 参加如下运行项目: 足球
小明 参加如下运行项目: 乒乓球
有点像继承,其实这又是一个语法糖, s.play([]string{"篮球", "足球", "乒乓球"})
,内部是s.student.play([]string{"篮球", "足球", "乒乓球"})
如果在定义时,给嵌套结构体一个字段名称:
type middleSchoolStudent struct {
lesson []string
stu *student
}
调用方式必须为:s.stu.play([]string{"篮球", "足球", "乒乓球"})
2.7 Tag
Tag
是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag
在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
`key1:"value1" key2:"value2"`
type MiddleSchoolStudent struct {
Lesson []string
Name string `json:"studentName"`
Age int `json:"studentAge"`
}
func main(){
ms := &MiddleSchoolStudent{
Name: "小明",
Age: 13,
Lesson: []string{"语文", "数学", "英语", "物理", "化学"},
}
data, err := json.Marshal(ms)
if err != nil {
fmt.Println("json marshal failed")
return
}
fmt.Printf("json:%s\n", data)
// fmt.Println(data)
}
json:{"Lesson":["语文","数学","英语","物理","化学"],"studentName":"小明","studentAge":13}
注意事项: 为结构体编写
Tag
时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。
再次强调:这个系列并不是教程,如果想系统的学习,博主可推荐学习资源。
作者:Garfield
同步更新至个人博客:http://www.randyfield.cn/
本文版权归作者所有,未经许可禁止转载,否则保留追究法律责任的权利,若有需要请联系287572291@qq.com
微信公众号
扫描下方二维码关注个人微信公众号,实时获取更多干货