实际编程时,经常需要用相关的不同类型的数据来描述一个数据对象。C#中有类(Class),结构(Struct),当然类就不介绍了。Golang中叫结构体(C,C++好像还是结构体),但是单词还是Struct,无论是在Golang还是C#,struct都是一个值类型。
struct结构体C#的结构struct1.构造函数struct有默认无参构造函数,不能再显式定义这个无参构造函数,编译器始终会生成一个默认的构造器结构不能包含显式的无参数构造函数,默认构造器会把所有字段的自动初始化
publicstructPosition{//publicPosition()//{}//这是不允许的publicdoubleLon{get;set;}publicdoubleLat{get;set;}}
//没有自定义构造函数,可不使用newPositionpositon;positon.Lon=39.26;positon.Lat=.25;自定义的有参构造函数必须初始化所有的字段
publicstructPosition{//自定义构造函数需要初始化所有字段、属性publicPosition(doublelon,doublelat){Lon=lon;Lat=lat;}//结构中不能实例属性或字段初始值设定项//publicdoubleLon{get;set;}=5.5;publicdoubleLon{get;set;}publicdoubleLat{get;set;}}
//有参构造函数,必须使用new为struct类型的变量赋值Positionpositon=newPosition(39.26,39.26);2.方法
结构是可以包含自己的方法。
publicstructPosition{//自定义构造函数需要初始化所有字段、属性publicPosition(doublelon,doublelat){Lon=lon;}//结构中不能实例属性或字段初始值设定项//publicdoubleLon{get;set;}=5.5;publicdoubleLon{get;set;}publicdoubleLat{get;set;}//重写方法publicoverridestringToString()=$"经度:{Lon},纬度{Lat})";}
虽然struct在实际开发过程中使用频率较低,但是使用时需要注意:
将结构类型变量作为参数传递给方法或从方法返回结构类型值时,将复制结构类型的整个实例。这可能会影响高性能方案中涉及大型结构类型的代码的性能。通过按引用传递结构类型变量,可以避免值复制操作。使用ref、out或in方法参数修饰符,指示必须按引用传递参数。使用ref返回值按引用返回方法结果。在Golang中也会存在这个问题,下一节会提到。Golang的结构体struct1.定义像定义函数类型那样type开头,只是把func()换为关键字struct
typepersonstruct{namestringageint8}//结构体匿名字段typestudentstruct{stringint}funcmain(){varp1personp1.name="RandyField"p1.age=28//匿名结构体varuserstruct{NamestringAgeint}user.Name="Randy"user.Age=18}2.2*结构体指针--重点2.2.1new
只要是指针,都可以用new()来进行分配内存地址,以达到初始化的目的:
typepersonstruct{namestringageint8}//结构体匿名字段/*这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。*/typeStudentstruct{stringint}funcmain(){//new分配结构体实例的指针(内存地址)实例化varp2=new(person)fmt.Printf("thetypeofp2is%T\n",p2)//*main.person//没有初始化的结构体所有的成员变量都是对应类型的零值fmt.Printf("p2=%#v\n",p2)stu:=Student{}stu.int=18stu.string="中学生"}
thetypeofp2is*main.personp2=main.person{name:"",age:0}2.2.2取地址符号
这个看起来比new()方便
typepersonstruct{namestringageint8}funcmain(){p3:=person{}//使用对结构体进行取地址操作=使用new实例化p3.name="kobe"p3.age=30//这是语法糖(*p3).age=29//其实底层是这样的}初始化
当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值。这点跟C#存在有参构造函数的结构是不一致。
funcmain(){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*空结构体
特殊地:空结构体是不占用空间的。
varvstruct{}fmt.Println(unsafe.Sizeof(v))//02.4构造函数
Golang是没有构造函数的,但是我们可以通过方法去创建一个,返回struct类型。复杂的结构体,值拷贝性能开销会比较大,故返回结构体指针。
typepersonstruct{namestringageint8}//复杂的结构体,值拷贝性能开销会比较大,故返回结构体指针。funcnewPerson(namestring,ageint8)*person{returnperson{name:name,age:age,}}2.5方法
Golang结构体的方法并不像C#的结构那样直接就在结构的{}中定义即可。它必须分开定义,这就出现一个问题,定义的这个方法是属于这个结构体的,并不希望其他地方都能使用这个方法,但是又必须分开定义,怎么办?
接收者应运而生,指明这个方法是属于结构体,只能通过结构体来调用。
func(接收者变量接收者类型)方法名(参数列表)(返回参数){函数体}
typepersonstruct{namestringageint8}func(p*person)MakeMoney(workstring)(resstring){return"赚钱了"}接收者既可以是指针类型,也可以是值类型值类型,如果做出了操作,只针对副本有效
使用指针类型场景:
需要修改接收者中的值接收者是拷贝代价比较大的大对象如果有某个方法使用了指针类型接收者,其他的方法也应该使用指针类型接收者。2.5.1类型定义与类型别名方法的接收者不仅仅可以是结构体,还可以是类型定义:
typeNewIntint//类型定义新类型可以作为方法的接收者typeMyInt=int//类型别名编译完成时并不会有`MyInt`类型,这个不能作为方法接收者的func(mNewInt)Say(){fmt.Println("我其实是int。")}类型定义创造一种新类型类型别名,编译后不存在2.6嵌套结构体
typestudentstruct{namestringageint}typemiddleSchoolStudentstruct{lesson[]string*student}func(stu*student)play(sport[]string){for_,v:=rangesport{fmt.Println(stu.name,"参加如下运行项目:",v)}}func(m*middleSchoolStudent)learn(){for_,v:=rangem.lesson{fmt.Println(m.name,"学习如下课程:",v)}}funcmain(){s:=middleSchoolStudent{lesson:[]string{"语文","数学","英语","物理","化学"},student:student{name:"小明",age:13,},}s.learn()s.play([]string{"篮球","足球","乒乓球"})}
小明学习如下课程:语文小明学习如下课程:数学小明学习如下课程:英语小明学习如下课程:物理小明学习如下课程:化学小明参加如下运行项目:篮球小明参加如下运行项目:足球小明参加如下运行项目:乒乓球
有点像继承,其实这又是一个语法糖:
s.play([]string{"篮球","足球","乒乓球"})内部是s.student.play([]string{"篮球","足球","乒乓球"})如果在定义时,给嵌套结构体一个字段名称:
typemiddleSchoolStudentstruct{lesson[]stringstu*student}
调用方式必须为:s.stu.play([]string{"篮球","足球","乒乓球"})
2.7TagTag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
`key1:"value1"key2:"value2"`
typeMiddleSchoolStudentstruct{Lesson[]stringNamestring`json:"studentName"`Ageint`json:"studentAge"`}funcmain(){ms:=MiddleSchoolStudent{Name:"小明",Age:13,Lesson:[]string{"语文","数学","英语","物理","化学"},}data,err:=json.Marshal(ms)iferr!=nil{fmt.Println("jsonmarshalfailed")return}fmt.Printf("json:%s\n",data)//fmt.Println(data)}
json:{"Lesson":["语文","数学","英语","物理","化学"],"studentName":"小明","studentAge":13}
注意事项:为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。
再次强调:这个系列并不是教程,如果想系统的学习,博主可推荐学习资源。
长按