zoukankan      html  css  js  c++  java
  • 【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
    ", p2) //*main.person
        
        //没有初始化的结构体 所有的成员变量都是对应类型的零值
    	fmt.Printf("p2=%#v
    ", 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
    ", p4)
    
    	//结构体指针初始化
    	p5 := &person{
    		name: "RandyField",
    		age:  28,
    	}
        fmt.Printf("p5=%#v
    ", p5)
    }
    
    • 另类初始化

    不建议使用,但是为了能看懂别人的开源代码,还是知道机制为妙。

    p6 := &person{
        "RandyField",
        28,
    }
    fmt.Printf("p6=%#v
    ", 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
    ", data)
    	// fmt.Println(data)
    }
    
    json:{"Lesson":["语文","数学","英语","物理","化学"],"studentName":"小明","studentAge":13}
    

    注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

    再次强调:这个系列并不是教程,如果想系统的学习,博主可推荐学习资源。

  • 相关阅读:
    Sqlserver 批量数据更改
    mysql not in、left join、IS NULL、NOT EXISTS 效率问题记录
    SQLServer 与 MySQL
    MySQL 行号(类似SQLServer的row_number())
    c# 字符串排序 (面试题)
    c# 多线程里面创建byte数组发生内存溢出异常求解
    c# 遇到的问题,求解?
    solr-4.10.3.tgz.tgz下载
    VMware虚拟机克隆或复制linux后无法上网的解决方案
    通配符的匹配很全面, 但无法找到元素 'dubbo:application' 的声明。
  • 原文地址:https://www.cnblogs.com/RandyField/p/14031914.html
Copyright © 2011-2022 走看看