go语言入门九 | 青训营笔记

81 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 11 天

结构体

  1. 特点:①不纯粹的面向对象语言②没有class,struct代替了class③仍然存在继承、封装、多态的概念

  2. 定义:

    **type 结构体名称 struct{
    	field1 type
    	field2 type
    }
    1.结构体名称如果首字母如果是大写,那么可以在其他包引用结构体,
    	不是大写也可以通过工厂模式来调用,就类似于面对对象语言的构造函数
    2. 结构体是自定义的数据类型,代表的是一类事务 -- Cat
    3. 结构体变量是具体的、实际的,代表的是一个具体的变量 -- cat1.Name
    4. 在创建一个结构体变量,如果没有赋值,则默认是默认值**
    
  3. 字段/属性

    1. 从概念上看,结构体字段 = 属性 = field
    2. 结构体中的变量如果没有被使用,不会报错(和普通变量不一样)
    3. 指针,slice和map的零值都是nil,还没有分配可见,如果要使用,要make后才能使用(编译时报错)
    4. 同一结构体的不同变量的字段是独立的,互不影响,若要影响可以使用&
    **type Cat struct{
        Name string
        Age int
        Scores [5]float64   //数组
     /*---上面是值类型 ,下面是引用类型 ---*/
        ptr *int       //指针
        slice []int     //切片
        map1 map[string]string   //map - key:string ,value:string
    }
    
    func main(){
        /* 使用slice */
        /* p.slice[0] = 100 会报错 */
        p.slice = make([]int,10)
        p.slice[0] = 100
        fmt.Println(p.slice)
    
        /*使用map */
        p.map1 = make(map[string]string)  //不需要分配空间也不报错,和slice不同,why
        p.map1["b"] = "100"
        p.map1["a"] = "200"
        fmt.Println(p.map1)
    }**
    
  4. 声明/初始化:

    1. 直接声明:var person Person

    2. {}-最方便

      **person := Person{}
      person.Name = "lisa"
      person.Age = 18**
      

      或:

      **person := Person{"lisa,18"}**
      
    3. new—返回指针

      **var person *Person = new(Person)
      /*--------或-------*/
      person := new(Person)
      
      /*person是一个指针,赋值: */
      (*person).Name = "lisa"
      (*person).Age = 18
      
      /*简化赋值 -- Go特性,在底层会对其处理*/
      person.Name = "lisa"
      person.Age = 18
      fmt.Println(*person) /*或
      fmt.Println(person) */**
      

      注意:使用new省略*只是Golang特性,其他语言需要加上,不可以省略

    4. &Person:返回之后指针

      **var person *Person = &Person{"lisa,18"}
      //  person.Name = "lisa"
      //  person.Age = 18
      /* --------------------------*/
      //  (*person).Name = "lisa"
      //  (*person).Age = 18
      fmt.Println(*person)**
      
      1. ***和方法三一样,在Golang中可以省略,``底层会对其处理,会自动加上 * ****
      2. 同时和方法二一样,在{}中可以直接赋值
  5. 访问:变量名.成员名

  6. 内存分配:默认为0,所以是值类型(帮助我理解c语言中难以理解的结构体和指针)

    1. 结构体内定义结构体:地址连续

      **package main
      import "fmt"
      type Point struct{
          x,y int
      }
      type Rect struct{
      	a,b Point
      }
      func main(){
          r := Rect{Point{1,2},Point{3,4}}
          //r有4个int整数,在内存中连续分布
          fmt.Printf("r.a.x的地址%p \n r.a.y的地址%p \n",&r.a.x,&r.a.y)
          fmt.Printf("r.b.x的地址%p \n r.b.y的地址%p \n",&r.b.x,&r.b.y)
          fmt.Println("r.a.x+1的地址为:",&(r.a.x+1))
      }**
      
    2. 结构体内定义结构体指针:地址跳跃

      **package main
      import "fmt"
      type Point struct{
          x,y int
      }
      type Rect struct{
      	a,b *Point
      }
      func main(){
          r := Rect{&Point{1,2},&Point{3,4}}
          //r有4个int整数,在内存中连续分布
          fmt.Printf("r.a本身的地址%p \n r.a本身的地址%p \n",&r.a,&r.b)
          fmt.Printf("r.b指向的地址%p \n r.b指向的地址%p \n",r.a,r.b)
          fmt.Printf("r.a指向的值%d \n r.b指向的值%d \n", *r.a, *r.b)//读取地址存储的内容
      }**
      
  7. 强制类型转换:需要有完全相同的字段(名字,个数和类型)a = A(b)

  8. tag标签:反射(在结构体内部字段首字母没有大写的情况下,依旧可以JSON格式化字符串)

    **package main
    import (
        "fmt"
        "encoding/json"
    )
    type Cat struct{
        Name string  `json:"姓名"`
        Age int	`json:"年龄"`
        Color string `json:"颜色"`
        Hobby string `json:"hobby"`
    }
    func main(){
        /*创建变量*/
        cat := Cat{"张三",20,"红色","hanhan"}
        //将cat变量序列化为json格式字串
        jsoncat,err := json.Marshal(cat)
        if err != nil{
            fmt.Println("json处理错误",err)
        }
        fmt.Println("jsoncat = ",string(jsoncat))
    }
    
    没有tag之前:
    jsoncat =  {"Name":"张三","Age":20,"Color":"红色","Hobby":"hanhan"}
    有tag之后:
    jsoncat =  {"姓名":"张三","年龄":20,"颜色":"红色","hobby":"hanhan"}**
    

方法

  1. 方法声明:方法声明和定义是一样的,可以先声明,也可不声明,不会产生影响

    **func (variable_name variable_data_type) function_name(参数列表) (int,int){
    /* 函数体*/
    }
    
    type A struct{
        Age int
    }
    func (a A)test(){
        fmt.Println(a.Age)
    }**
    
    • (int,int) 返回值类型,和函数中返回值类型使用相同,可以进行绑定
    • return和返回值列表是相对应的
    • 参数列表:表示方法的输入
    • variable_data_type不一定要和结构体绑定,可以是自定义类型,都可以有方法
    • 方法函数里面的是值拷贝,所以方法里面的变量并改变不会影响变量的值
  2. 方法调用

    • 方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当作实参也传递给方法
  3. 传参机制

    • 在调用方法中,是值类型,所以被被拷贝,和函数不同的是:变量调用方法时,该变量也会作为一个参数传递到方法(如果是引用类型,则进行地址拷贝),指针的 和传参时的&都可以省略*

      **type Circle struct {
      	radius float64   //定义半径
      }
      
      //2)声明一个方法area和Circle绑定,可以返回面积。
      //法一:结构体Circle做绑定
      func (c Circle) area() float64 {
      	return 3.14 * c.radius * c.radius
      }
      //法二:结构体指针Circle做绑定
      //为了提高效率,通常我们方法和结构体的指针类型绑定
      func (c *Circle) area2() float64 {
      	//因为 c是指针,因此我们标准的访问其字段的方式是 (*c).radius
      	//return 3.14 * (*c).radius * (*c).radius
      	// (*c).radius 等价  c.radius
      	fmt.Printf("c 是  *Circle 指向的地址=%p", c)
      	c.radius = 10
      	return 3.14 * c.radius * c.radius
      }
      
      func main() {
      	//创建一个Circle 变量
      	var c Circle
      	fmt.Printf("main c 结构体变量地址 =%p\n", &c)
      	c.radius = 7.0
      	//res2 := (&c).area2()
      	//编译器底层做了优化  (&c).area2() 等价 c.area()
      	//因为编译器会自动的给加上 &c
      	res2 := c.area2()
      	fmt.Println("面积=", res2)
      	fmt.Println("c.radius = ", c.radius) //10
      }**
      
  4. 函数和方法的区别

    1. 调用方式不一样

      1. 函数:函数名(实参列表)
      2. 方法:变量.方法名(实参列表)
    2. 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然

      • 传递的时候&不可以省略,接受的时候&可以省略
    3. 对于方法(如strucr的方法),接收者为值类型时候,可以直接用指针的变量调用方法,反过来同样可以(接受者为指针类型,也可以传递值类型)

      1. 传参时加不加&不重要,重要的是接受者是否有*
      2. 方法体内有没有不重要,重要的是方法关联的结构体加没加