实际编程时,经常需要用相关的不同类型的数据来描述一个数据对象。C#
中有类(Class
),结构(Struct
),当然类就不介绍了。Golang
中叫结构体(C
,C++
好像还是结构体),但是单词还是Struct
,无论是在Golang
还是C#
, struct
都是一个值类型。
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);
结构是可以包含自己的方法。
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
中也会存在这个问题,下一节会提到。像定义函数类型那样 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
}
只要是指针,都可以用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}
这个看起来比new()
方便
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)
特殊地:空结构体是不占用空间的。
var v struct{}
fmt.Println(unsafe.Sizeof(v)) // 0
Golang
是没有构造函数的,但是我们可以通过方法去创建一个,返回struct
类型。复杂的结构体,值拷贝性能开销会比较大,故返回结构体指针。
type person struct {
name string
age int8
}
// 复杂的结构体,值拷贝性能开销会比较大,故返回结构体指针。
func newPerson(name string, age int8) *person {
return &person{
name: name,
age: age,
}
}
Golang
结构体的方法并不像C#
的结构那样直接就在结构的{}
中定义即可。它必须分开定义,这就出现一个问题,定义的这个方法是属于这个结构体的,并不希望其他地方都能使用这个方法,但是又必须分开定义,怎么办?
接收者应运而生,指明这个方法是属于结构体,只能通过结构体来调用。
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
type person struct {
name string
age int8
}
func (p *person) MakeMoney(work string) (res string) {
return "赚钱了"
}
使用指针类型场景:
方法的接收者不仅仅可以是结构体,还可以是类型定义:
type NewInt int //类型定义 新类型 可以作为方法的接收者
type MyInt = int //类型别名 编译完成时并不会有`MyInt`类型, 这个不能作为方法接收者的
func (m NewInt) Say() {
fmt.Println("我其实是int。")
}
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{"篮球", "足球", "乒乓球"})
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之间添加空格。
再次强调:这个系列并不是教程,如果想系统的学习,博主可推荐学习资源。