基础-2

作者: hellomyshadow | 来源:发表于2019-09-26 06:53 被阅读0次

    结构体

    1. 结构体也是一种类型,它可以存储不同的数据类型,定义在函数外部:type 结构体名 struct { }
      type Student struct {
          id int
          name string
          sex byte
      }
      
    2. 结构体是一种数据存储格式,不能在声明结构体时初始化成员,每个成员的值就是所属类型的默认值;
      var stu Student
      fmt.Println(stu)  // {0 "" 0}
      
    3. 结构体的赋值
      1. 通过结构体变量为其成员赋值;
          var stu Student
          stu.id = 10020
          stu.name = "Mack"
          stu.sex = 1
          fmt.Println(stu)  // {10020 Mack 1}
      
      1. 结构体的成员顺序是不变的,匿名初始化时必须初始化结构体成员的所有成员,而指定名称的初始化则不需要;
          var stu Student = Student{10020, "Mack", 1}
          fmt.Println(stu)  // {10020 Mack 1}
      
          var stu Student = Student{name: "Mack"}
      
    4. 结构体的传递是值传递,如果结构体的成员都支持==、!=,那么该结构体也支持==、!=
      stu := Student{10020, "Mack", 1}
      m := stu
      m.id = 2
      fmt.Println(stu)  // {10020 Mack 1}
      fmt.Println(m)  // {2 Mack 1}
      fmt.Println(m==stu)  // false
      
    5. 结构体与数组、切片、字典
      //结构体数组
      var arr [5]Student
      arr3 := [2]Student{{100, "Mack", 1}, {200, "Rose", 0}}
      //结构体切片
      s := []Student{{100, "Mack", 1}, {200, "Rose", 0}}
      //结构体Map
      m := make(map[string]Student)  //key为string型,value为结构体Student
      m["10010"] = {100, "Mack", 1}
      //结构体切片Map
      ms := make(map[string][]Student)  //key为string型,value为切片,且切片元素类型为结构体Student
      ms["10012"] = append(ms["10012"], Student{1, "Mack", 1})
      fmt.Println(ms)  //map[10012:[{1 Mack 1}]]
      
    6. 结构体本身在定义时存储在数据区,其中的成员则根据数据类型存储在不同区域,如数组栈区,切片在堆区。

    指针

    指针用于存储变量的内存地址,指针的类型与变量的类型保持一致;

    1. 定义:var p *类型,一级指针,指针的默认值为nil(空指针)
      var p *int
      fmt.Println(p)  // <nil>
      var num int = 1
      p = &num
      fmt.Println(p)  // 0xc00000a0c0
      fmt.Printf("%p", &num)  // 0xc00000a0c0
      
    2. * 取值运算符,用于从内存地址中存储的值,通过指针还可以修改内存中的值;
      num := 1
      p := &num
      fmt.Println(*p)  // 1
      
      *p = 22
      fmt.Println(num)  // 22
      
    3. 空指针指向内存编号为 0 的空间,但是内存编号 0-255被系统占用,不允许用户进行读写操作;
      var p *int
      fmt.Println(*p)  // runtime error
      *p = 1  //runtime error
      
    4. 野指针:指向一块未知的空间,与空指针类似,都不允许进行读写操作;
      var p *int
      p = 0x04803c60
      *p = 1  //runtime error
      
    5. 数组指针
      1. 数组存储在一块连续的内存空间,数组指针指向数组的首地址;
          var arr [5]int = [5]int{1,2,3,4,5}
          var p *[5]int = &arr
      
          fmt.Println(p)  // &[1 2 3 4 5] 不会直接打印数组指针指向的地址
      
          fmt.Printf("%p", &arr)  // 0xc00006c060
          fmt.Printf("%p", &arr[0])  // 0xc00006c060
          fmt.Printf("%p", p)     // 0xc00006c060
          fmt.Printf("%p", &arr[1])  // 0xc00006c068
      
      1. 数组指针操作数据元素:(*)p[0]、p[0]
          fmt.Println(*p)  // [1 2 3 4 5]
          
          (*p)[0] = 100   // [] 操作符的优先级高于 *
      
          //Go语言做了优化,使用简写形式
          p[1] = 200
      
          fmt.Println(arr)  // [100 200 3 4 5]
      
          len(p)  // 5 通过指针变量获取数组长度
      
    6. 切片数组
      1. 与数组名不同,切片名本身就是一个地址,且这个地址就是切片存储的堆区内存地址;
          slice := []int{1,2,3,4,5}
          fmt.Printf("%p", slice)      // 0xc00006c060
          fmt.Printf("%p", &slice[0])  // 0xc00006c060
          p := &slice
          fmt.Printf("%p", p)   // 0xc00004c420
      
      1. &slice 获取的是栈区中 slice 变量的地址,而不是切片存储在堆区的地址!
      2. 通过指针操作切片时的查找过程:p -> slice变量地址 -> slice指向的堆区地址
          (*p)[0] = 200
          fmt.Println(slice)  // [200 2 3 4 5]
          fmt.Println(*p)  // [200 2 3 4 5]
      
      1. Go没有对指针访问切片做优化,仍需要使用取地址操作符*
      2. 这种没有直接指向堆区地址的指针,称为二级指针!
      3. 既然指针指向的是切片变量的地址,那么对指针使用append()导致切片扩容时,切片变量指向的堆区地址也会随之变化!
          slice := []int{1,2,3}
          fmt.Printf("%p", slice)  // 0xc00006c060
          p := &slice
          *p = append(*p, 6, 7, 8, 9)
          fmt.Printf("%p", slice)  // 0xc00008a000
          fmt.Println(slice)  // [1 2 3 6 7 8 9]
      
    7. 手动创建指针空间:new(数据类型)
      var p *int
      p = new(int)  // 为指针变量在堆区创建一块内存空间
      fmt.Println(p)  // 0xc000056080
      fmt.Println(*p)  // 0
      
      1. 开辟的空间位于堆区,存储的值是数据类型的默认值,并返回内存地址;
      2. Go语言中无需关心内存空间的释放,交由系统管理.
    8. 指针的传递是地址传递,形参可以改变实参的值;
    9. 指针数组/切片:元素为指针的数组/切片
      a, b := 1, 2
      var arr [2]*int
      arr[0] = &a
      arr[1] = &b
      fmt.Println(arr)  // [0xc00000a098 0xc00000a0b0]
      
      var slice []*int
      slice = append(slice, &a, &b)
      fmt.Println(slice)  // [0xc00000a098 0xc00000a0b0]
      
    10. 结构体指针:也是指向结构体的首地址,还可以简化操作结构体成员
      type Student struct {
          id int
          name string
      }
      func main() {
          var stu Student = Student{1, "Alpa"}
          var p *Student
          p = &stu
          fmt.Printf("%p\n", &stu.id)  // 0xc000004480
          fmt.Printf("%p\n", p)        // 0xc000004480
          
          p.name = "Babel"   // (*p).name = "Babel"
          fmt.Println(stu)  // {1 Babel}
      }
      
    11. 多级指针
      a := 10
      p := &a  // 一级指针
      P2 := &p   // 二级指针
      P3 := &p2   // 三级指针
      

    内存模型

    1. 内存是一块连续的一维数组空间:低地址 -> 高地址,其中 0 - 255被系统所占用;
    2. 对于一个应用程序来说,内存可以简单分为四个区域,从低地址到高地址依次为:代码区、数据区、堆区、栈区;
    3. 代码区:存储计算指令信息,只读
    4. 数据区:又可分为常量区、初始化数据区,未初始化数据区,其中常量区不允许显示内存地址,而结构体就位于未初始化数据区;
    5. 堆区:切片数据、string类型数据、new()...
    6. 栈区:局部变量、函数调用时的信息
    7. 它们之间并不是相邻的,中间还有一些小的区域,其中最高地址段分配给了注册表。

    面向对象

    Go语言中没有类的概念,但可以将结构体比作类,结构体成员比较类的属性、方法;
    Go语言中也没有继承,但可以通过 匿名组合 来实现继承的效果。

    1. 匿名字段,又叫匿名组合:用一个结构体的名称作为另一个结构体的成员;
      1. 在初始化子结构体时,可以直接访问父结构体的成员;
          type Person struct {
              name string
              age int
          }
          type Student struct {
              Person
              id int
              score float32
          }
          func main() {
              var stu Student
              stu.id = 101
              stu.name = "Mack"  //直接访问父结构体成员
              stu.Person.age = 20  //间接访问
              stu.score = 60.5
              fmt.Println(stu)  //{{Mack 20} 101 60.5}
          }
      
      1. 定义时的初始化
          var stu Student = Student{Person{"Jack", 17}, 102, 87}
      
      1. 但是,如果子结构体和父结体有同名成员,则采用就近原则:初始化父结构体成员时,必须指定父结构体名称;
          type Person struct {
              name string
              age int
          }
          type Student struct {
              Person
              id int
              name string
              score float32
          }
          func main() {
              var stu Student
              stu.id = 101
              stu.name = "Mack"  //子结构体自己的name
              stu.Person.name = "Jason"  //父结构的name
              stu.age = 13
              stu.score = 60.5
              fmt.Println(stu)  //{{Jason 13} 101 Mack 60.5}
          }
      
    2. 指针匿名字段:继承父结构体的指针
          type Person struct {
              name string
              age int
          }
          type Student struct {
              *Person
              id int
              score float32
          }
      
      1. 指针的默认值是nil,称为空指针,无法直接访问
          var stu Student
          stu.name = "Mack"
          stu.Person.name = "Jason"  //runtime error: invalid memory address or nil...
      
      1. new() 初始化指针变量
          var stu Student
          stu.Person = new(Person)
          stu.Person.name = "Mack"
          stu.age = 20
          fmt.Println(stu.name, stu.age)  // Mack 20
      
      1. 亦或者:先初始化父结构体,再把地址赋与子结构体的指针;
          var p = Person{"Mack", 14}
          var stu Student
          stu.Person = &p
          //定义时初始化
          var stu2 = Student{ &Person{"Mack", 14}, 103, 78.5 }
      
    3. 支持多重继承,但自己不允许继承自己,却可以继承自己的结构体指针,这也是链表的实现原理;
          type Person struct {
              *Person
              name string
              age int
          }
      

    方法

    1. Go中的方法和函数是有明显区别的
          func (方法接收者) 方法名(参数列表) 返回值类型 {
              方法体
          }
      
      1. 根据数据类型绑定方法,方法接收者也可以为方法传递参数;
          type Integer int  //为 int 定义别名
          func (a Integer) Test(b Integer) Integer {
              return a+b
          }
          func main() {
              var c Integer = 3
              r := c.Test(3)
              fmt.Println(r)  // 6
          }
      
      1. Go语言不允许方法直接使用数据类型,必须定义别名!
      2. 方法接收者是一种类型,为这种类型绑定了方法之后,此类型的变量都具有该方法!
    2. 结构体也是在为 struct 起别名,那么就可以为结构体绑定方法;
          type Student struct {
              id int
              name string
              age int
          }
          func (s Student) print() {
              fmt.Println(s.name)
          }
          func (s Student) edit(name string) {
              s.name = name
              s.print()  // 调用另一个方法
          }
          func main() {
              s := Student{ 101, "Mart", 20}
              s.edit("Jose")  // Jose
          }
      
      1. 结构体指针访问成员时,支持简化操作,访问方法时也支持简化操作;
          func (s *Student) edit(name string) {
              s.name = name
              s.print()  // (*s).edit("Jose")
          }
          func main() {
              s := Student{ 101, "Mart", 20}
              s.edit("Jose")  // (&s).edit("Jose")
      
              var s2 *Student  //空指针
              s2 = new(Student)  // 为指针s2 开启空间
              s2.edit("Jose")  // Jose
          }
      
      1. 考虑到结构体是值传递,所以都应该使用结构体指针作为接收者;
      2. 结构体中不允许出现同名方法,即使一个方法接收者是结构体名Student、另一个接收者是结构体指针*Student,也不允许!
    3. 方法继承:匿名字段也可以实现方法的继承,同名方法也遵循就近原则;
    4. 方法名允许与函数名重名,但在同级别文件中可以直接调用函数,却不能调用方法,必须先定义结构体;
    5. 方法的本质还是函数,类型都是func(),也都存储在代码区。

    接口

    1. 定义
      type 接口名 interface {
          方法列表  //只有声明,没有具体实现
      }
      
    2. 对象(结构体)必须实现所有的接口方法,接口指向对象的内存地址,通过接口调用对象实现的方法,这就是多态;
      type Humaner interface {
          say()
      }
      type Student struct {
          name string
          age int
      }
      func (s *Student) say() {
          fmt.Printf("Student: %s", s.name)
      }
      type Teacher struct {
          name string
          age int
      }
      func (t *Teacher) say() {
          fmt.Printf("Teacher: %s", t.name)
      }
      func main() {
          var stu Student = Student{ "Mack", 20 }
          var h Humaner
          h = &stu
          h.say()  // Student: Mack
          h = &Teacher{ "Eason", 46 }
          h.say()  // Teacher: Eason
      }
      
    3. 接口也可以通过匿名字段实现继承,且接口中的形参可以省略参数名;
      type Humaner interface {
          Say()
      }
      type Male interface {
          Humaner
          Dance(string)   //形参可以省略参数名
      }
      type Student struct {
          name string
      }
      func (s *Student) Say() {
          fmt.Printf("%s say", s.name)
      }
      func (s *Student) Dance(label string) {
          fmt.Printf("dance: %s", label)
      }
      func main() {
          var m Male
          m = &Student{ "Mack" }
          m.Say()  // Mack say
          m.Dance("AAA")  // dance: AAA
      
          var h Humaner
          h =  &Student{ "Eason" }
          h.Say()  // Eason say
      }
      
    4. 接口的转换:允许父接口指向子接口,那么父接口在调用方法时,指向的是针对子接口的实现方法;
          h = m
          h.Say()  // Mack say
      
    5. 空接口:var a interface{}
      1. 空接口可以接收任意类型的数据,但空接口的地址不变!
          var a interface{}
          fmt.Printf("%p", &a)  // 0xc0000501c0
          a = 10
          fmt.Printf("%p", &a)  // 0xc0000501c0
          a = "Hello Go"
          fmt.Printf("%p", &a)  // 0xc0000501c0
          a = false
          fmt.Printf("%p", &a)  // 0xc0000501c0
      
      1. 空接口切片
          var a []interface{}
          a = append(a, 12, "Hello", [3]int{1,2,3})
          fmt.Println(a)  // [12 Hello [1 2 3]]
      
          b := make([]interface{}, 3)
      
    6. 类型断言:类型判断
          var b []interface{}
          b = append(a, 12, "Hello", [3]int{1,2,3})
          for _,v:=range b {
              data,ok := v.(int)  // 判断元素v 是不是 int 类型
          }
      
      1. ok 表示判断结果,如果是,则为true,否则为false;
      2. data 表示判断类型的值,当前判断是不是int类型,如果是,data 值为元素值,否则为int类型的默认值0;
          for _,v:=range b {
              if data,ok := v.(int); ok {  // ok 为 true 时,则进入此分支
              } else if  data,ok := v.([3]int); ok {
              }
          }
      

    相关文章

      网友评论

          本文标题:基础-2

          本文链接:https://www.haomeiwen.com/subject/tedsyctx.html