第四篇:结构体

第四篇:结构体

一、结构体的使用

结构体是一系列属性的集合。【没有方法】

1 有名结构体

// 定义结构体【在entity包中定义】
type Person struct {
    Name string  // 大写,外部包可以进行引用;小写,仅表明内部包可以使用
    Age int  // 
    Sex string
}

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
import (
    "day01/entity"
    "fmt"
)


// 1 结构体的基本使用【访问结构体字段使用".",但是注意大小写】
func main() {    
    var per entity.Person  // 生成一个结构体对象 【包名.结构体名】(大写代表外部包可以进行引用,小写仅表明内部包可以使用)
    fmt.Println(per)  // { 0 }  打印结构体对象
    per.Name = "yangyi"  // 结构体属性赋值
    per.Age = 18
    per.Gender = "male"
    fmt.Println(per)  // {yangyi 18 male}
}

// 2 结构体定义并附初值
var person1 entity.Person = entity.Person{Age: 18, Name: "yangyi"}  // 指名道姓,可以不按位置,并且少传,其余为传递的参数默认为零值
person2 := entity.Person{"yangyi", 18, "male"}  // 不指名道姓,必须按位置

在python每个文件就是一个模块。而go中的一个包可以想象成一个大的文件,在其中的名字不能重复。

2 匿名结构体

// 匿名结构体(定义在内部【函数,结构体】,仅使用一次。就像python中的匿名函数一样)
// 有什么用?
// 假如说有多个变量,一个一个用,还不如放到匿名结构体中,一个代表多个
school := struct {  // 将匿名结构体赋值给一个变量【因为没有名字,变量只能用类型推导和简略声明两种方式】
    SchoolId int64
    SchoolName string
}{100, "ccut"}  // 实例化匿名结构体

school.SchoolId = 99  // 修改匿名函数的值

3 结构体零值

属性的零值,值类型,参数传递属于copy传递【函数传参,修改,不会改变原来的】。简单来说,结构体传递属于值传递。

var person entity.Person = entity.Person{"yangyi", 18, "male"}
fmt.Println(person)  // {yangyi 18 male}
test(person)  // {lei 18 male}
fmt.Println(person)  // {yangyi 18 male}

func test(person entity.Person)  {
    person.Name = "lei"
    fmt.Println(person)
}

4 结构体指针

// 1 定义结构体指针
var person *entity.Person  // 结构体指针
fmt.Println(person)  // <nil>  指针的零值

// 2 定义并初始化
var person *entity.Person = &entity.Person{"yangyi", 18, "male"}  // 定义一个结构体指针
person1 := &entity.Person{"yangyi", 18, "male"}
fmt.Println(person)  // &{yangyi 18 male}
fmt.Println(person1)  // &{yangyi 18 male}

// 修改属性值
// 第一种方式: 不推荐
(*person).Name = "lei"
// 第二种方式: 推荐【语言层面自动进行处理】
person.Age = 20
fmt.Println(person)  // &{lei 20 male}

5 匿名字段

匿名字段,内含匿名字段(字段没有名字),匿名字段的类型就是字段名,所以类型不能重复。

作用:变量提升。【其实就是面向对象的继承】

// 匿名字段,内含匿名字段(字段没有名字)
type Person struct {
    string  // 字段没有名字
    Age int
    Gender string
}

// 实例化匿名字段结构体对象
// 第一种方式: 按位置
person := Person{"yangyi", 18, "male"}  
// 第二种方式: 按名字
person1 := Person{string:"yangyi", Age: 18, Gender: "male"}  // 如果字段匿名,类型就是字段名【这个string有点过分了呀】
fmt.Println(person1.string)  // yangyi  通过类型可以取到值。【哈哈】

6 结构体嵌套

结构体中套用结构体。

type Person struct {
    Name string 
    Age int
    Gender string
    Hobby Hobby  // 属性名是Hobby,属性类型为Hobby结构体
}

type Hobby struct {
    HobbyId int
    HobbyName string
}

// 按位置传递
// 第一种方式: 按位置
var person Person = Person{"yangyi", 18, "male", Hobby{2, "song"}}
// 第二种方式: 按名字
var person Person = Person{Name: "yangyi", Age: 18, Gender: "male", Hobby: Hobby{HobbyId: 2, HobbyName: "song"}}

// 结构体使用
fmt.Println(person.Name)  // yangyi
fmt.Println(person.Hobby.HobbyName)  // song

7 字段提升

字段提升像面向对象的继承。可以简单认为,字段提升就是面向对象的继承。【面向对象的继承: 子类继承父类,子类可以直接调用父类的方法】

type Person struct {
    Name string  
    Age int
    Gender string
    Hobby  // 这里将Hobby设置为匿名字段,此处仅为Hobby类型
}

type Hobby struct {
    HobbyId int
    HobbyName string
}

// 第一种方式: 按位置
var person Person = Person{"yangyi", 18, "male", Hobby{2, "song"}}
// 第二种方式: 按名字【此时Hobby: 中的Hobby是字段名】
var person Person = Person{Name: "yangyi", Age: 18, Gender: "male", Hobby: Hobby{HobbyId: 2, HobbyName: "song"}}

// 此处体现了字段提升的效果【匿名字段+结构体嵌套】
fmt.Println(person.Hobby.HobbyName)  // song  person.Hobby类似于面向对象中的super【java python中皆有】
fmt.Println(person.HobbyName)   // song

如果结构体嵌套中,属性名重复,则可认为是派生,子类重写了父类中的方法。【不严谨的说明】

8 导出结构体和字段

大写字母开头,在外部包可以使用。

golang中根据首字母的大小写来确定可以访问的权限。

无论是方法名、常量、变量名还是结构体的名称,如果首字母大写,则可以被其他的包访问;如果首字母小写,则只能在本包中使用。

9 结构体相等性

  • 结构体都是值类型
  • 如果结构体中的每一个字段都是可比较的,则该结构体也是可比较的。
  • 如果两个结构体变量的对应字段相等,则这两个变量也是相等的。
  • 如果结构体包含不可比较字段,则结构体变量也不可比较。
// 属性全是值类型,可以进行比较
type Person struct {
    Name string  
    Age int
    Gender string
}

// 值类型直接==比较,引用类型只能跟nil用==比较,且引用类型不能匿名
person1 := Person{"yangyi"}
person2 := Person{"yangyi"}
fmt.Println(person1 == person2)  // true
person1 := Person{"yangyi"}
person2 := Person{"yangyi", 18}
fmt.Println(person1 == person2)  // false

二、方法的使用

1 方法的定义和使用

方法其实就是一个函数,在func这个关键字和方法名中间加入了一个特殊的接收器类型。接收器可以是结构体类型或者是非结构体类型。接收器可以在方法的内部调用。

// 1) 定义一个结构体
type Person struct {
    Name string  // 字段没有名字
    Age int
    Gender string
}

// 2) 给结构体绑定一个方法【绑定给对象的方法】
func(p Person) tellName(){  // p就是python中的self,绑定给Person的结构体对象
    fmt.Println(p.Name)
}

// python写法
def tell_name(self):  // 其实p就相当于python中的self。方法就是绑定给类或者结构体的
    pass 
/* 方法和函数的区别 */
// 方法【对象.方法调用】
func (p Person)TellName() {
    // 该方法类可以使用p
    fmt.Println(p.Name)
}
// 函数【正常调用】
func TestName(p Person)  {
    fmt.Println(p.Name)
}

1.1 值类型接收器

// 修改名字
// 1)修改名字【错误,其实名字并没有改,因为结构体是值类型】
func (p Person) changeName(name string)  {
    p.Name=name
    fmt.Println(p)
}
person.changeName("lei")  // 即可修改名字【并没有修改】

1.2 指针类型接收器

func (p *Person) changeName(name string)  {
    (*p).Name=name  // 第一种方式
    p.Name=name  // 第二种方式【C中指针是 p->name = name】
    fmt.Println(p)
}

当拷贝一个结构体的代价过于昂贵时,这种情况下就需要使用指针类型接收器,只会传递一个指针到方法内部使用。

2 匿名字段方法提升

方法提升。【结构体嵌套+字段提升,其中嵌套的结构体中绑定了方法,就能实现方法提升】。如果方法名重复,优先使用子结构体的。

type Person struct {
    Name string
    Age int
    Gender string
    Hobby  // 这里将Hobby设置为匿名字段,此处仅为Hobby类型
}

type Hobby struct {
    HobbyId int
    HobbyName string
}

// 给结构体绑定方法
func (p *Person) tellName(){
    fmt.Println((*p).Name)
}
func (p *Hobby) tellHobbyName(){
    fmt.Println((*p).HobbyName)
}


func main() {
    person := Person{"yangyi", 18, "male", Hobby{222, "song"}}
    person.tellName()  // yangyi
    person.tellHobbyName()  // song  方法提升
    person.Hobby.tellHobbyName()  // song
}
// 在方法中使用值接收器 与 在函数中使用值参数
type Person2 struct {
    Name string
    Age int
    Gender string
}

// 1)在方法中使用值接收器
func (p Person)printName()  {
    fmt.Println(p.Name)
}

// 2)在函数中使用值参数
func printName(p Person)  {
    fmt.Println(p.Name)
}
// 在方法中使用指针接收器 与 在函数中使用指针参数
type Person struct {
    Name string
    Age int
    Gender string
}

// 给结构体绑定方法
func (p Person) tellName(){  // 此时结构体是值传递
    fmt.Println(p.Name)
}

person:=&Person{Name: "yangyi"}  // 生成一个结构体对象指针,指针也可以直接点击方法【或属性】,进行调用
person.tellName()  // yangyi

// 值类型接收器: 可以接受值类型;也可以接收指针类型;
// 函数的传参: 必须值对值,指针对指针

// 总结: 1 不管是结构体对象,还是结构体指针对象,都可以直接使用.来进行属性和方法的调用。2 方法的类型接收器,只是决定方法中使用的是值或者指针【是值类型的话,不进行修改;是指针类型的话,进行修改】

简单总结:不管是值类型接收器还是指针类型接收器,值和指针都能进行调用。如果是值类型接收器,不可以对结构体内容进行修改;如果是指针类型接收器,可以对结构体内容进行修改。

3 在非结构体上使用方法

// 在int类型上绑定一个add方法【不允许在非结构体上绑定方法】
func (i int) add() {
    i = i + 1
    // i++
}

可以在自定义的类型上绑定方法【不只是在结构体上可以绑定方法】

type Myint int

func (p *Myint)add()  {  // 指针类型接收器,可以进行原值修改
    *p += 1  // (*p) += 1
}

func main() {
    //person:=Person{Name: "yangyi"}
    //person.tellName()
    //fmt.Println(person.Name)
    var num Myint = 1
    num.add()
    fmt.Println(num)  // 2
    num.add()
    fmt.Println(num)  // 3
    num.add()
    fmt.Println(num)  // 4
}
posted @ 2023-04-03 20:43  YangYi215  阅读(39)  评论(0编辑  收藏  举报