Go语言基础 – 结构体

  • A+
所属分类:go语言编程 学编程

Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。

自定义类型与类型别名

1.自定义类型
在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型, Go语言中可以使用type关键字来定义自定义类型。

自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。例如:

//将MyInt定义为int类型

type MyInt int

通过Type关键字的定义,MyInt就是一种新的类型,它具有int的特性。

2.类型别名
类型别名是Go1.9版本添加的新功能。

类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。

type TypeAlias = Type

自定义类型与类型别名的区别示例:

//类型定义
type NewInt int

//类型别名
type MyInt = int

func main() {
    var a NewInt
    var b MyInt

    fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt
    fmt.Printf("type of b:%T\n", b) //type of b:int
}

结果显示a的类型是main.NewInt,表示main包下定义的NewInt类型。b的类型是int。MyInt类型只会在代码中存在,编译完成时并不会有MyInt类型。

结构体

Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是我们可以通过struct来定义自己的类型了。

1.Go语言中通过struct来实现面向对象。

2.struct类型可以定义方法

3.struct类型是值类型

4.struct类型可以嵌套

结构体定义
使用type和struct关键字来定义结构体,具体代码格式如下:

type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    …
}

其中:

类型名:标识自定义结构体的名称,在同一个包内不能重复。
字段名:表示结构体字段名。结构体中的字段名必须唯一。
字段类型:表示结构体字段的具体类型。

举个例子,我们定义一个Person(人)结构体,代码如下:

type person struct {
    name string
    city string
    age  int8
}

同样类型的字段也可以写在一行,

type person1 struct {
    name, city string
    age        int8
}

这样我们就拥有了一个person的自定义类型,它有name、city、age三个字段,分别表示姓名、城市和年龄。这样我们使用这个person结构体就能够很方便的在程序中表示和存储人信息了。

语言内置的基础数据类型是用来描述一个值的,而结构体是用来描述一组值的。比如一个人有名字、年龄和居住城市等,本质上是一种聚合型的数据类型

结构体定义的另外两种方法:

var stu *Student = new(Student)    // 使用new分配一个指针(指针指向结构体)
var stu *Student = &Student{}      // 定义一个指针 (指针指向结构体)

结构体实例化

只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。

结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型。

var 结构体实例 结构体类型

结构体实例化的四个方法:

// 1.struct定义
type Student struct {
    Name string        // Name首字母大写和小写的区别:大写在外部是可以访问,小写只可以在struct里面访问
    Age int
    Score float32
}

// 1.1、struct初始化方法一
func initStruct1() {
    var stu Student      // 定义Student类型的变量stu
    stu.Age = 18       // 给stu增加值
    stu.Name = "Head"
    stu.Score = 80
    fmt.Println(stu)        // 结果:{dream 18 80}
    fmt.Printf("Name:%p\n",&stu.Name)     // 结果:Name:0xc000050420  (结构体里面字段与字段之间的内存是连续的)
    fmt.Printf("Age:%p\n",&stu.Age)       // 结果:Age:0xc000050430
    fmt.Printf("Score:%p\n",&stu.Score)   // 结果:Score:0xc000050438
}

// struct初始化方法二
func initStruct2() {
    var stu1 *Student = &Student{     //直接通过指针与内存地址赋值 
        Age: 20,
        Name: "Dream",
    }
    fmt.Println(stu1.Name)  
}

// struct初始化方法三
func initStruct3() {
    var stu2 = Student{        // 初始化同时直接添加值
        Age: 20,
        Name: "Dreams",
    }
    fmt.Println(stu2.Name)  
}

// struct初始化方法四
func initStruct4() {
    p6 := &Student{
        name: "追梦人",
        city: "北京",
        age:  18,
    }
    fmt.Println(p6)
}

我们通过.来访问结构体的字段(成员变量),例如p1.name和p1.age等。

构造函数

Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。

// 结构体
type person struct {
    name string
    city string
    age  int8
}

//  构造函数
func newPerson(name, city string, age int8) *person {
    return &person{
        name: name,
        city: city,
        age:  age,
    }
}

调用构造函数

p9 := newPerson("张三", "BJ", 19)
fmt.Printf("%#v\n", p9) //&main.person{name:"张三", city:"BJ", age:19}

方法

Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。

方法的定义格式如下:

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}

其中,

1.接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等。

2.接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。

3.方法名、参数列表、返回参数:具体格式与函数定义相同。

举个例子:

//Person 结构体
type Person struct {
    name string
    age  int8
}

//NewPerson 构造函数
func NewPerson(name string, age int8) *Person {
    return &Person{
        name: name,
        age:  age,
    }
}

//Dream Person做梦的方法
func (p Person) Dream() {
    fmt.Printf("%s的梦想是学好Go语言!\n", p.name)
}

func main() {
    p1 := NewPerson("小王子", 25)
    p1.Dream()
}

方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。

接收者

指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为Person添加一个SetAge方法,来修改实例变量的年龄。

// SetAge 设置p的年龄
// 使用指针接收者
func (p *Person) SetAge(newAge int8) {
    p.age = newAge
}

调用该方法:

func main() {
    p1 := NewPerson("小王子", 25)
    fmt.Println(p1.age) // 25
    p1.SetAge(30)
    fmt.Println(p1.age) // 30
}

结构体匿名字段

结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。

//Person 结构体Person类型
type Person struct {
    string
    int
}

func main() {
    p1 := Person{
        "小王子",
        18,
    }
    fmt.Printf("%#v\n", p1)        //main.Person{string:"北京", int:18}
    fmt.Println(p1.string, p1.int) //北京 18
}

匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。

嵌套结构体

一个结构体中可以嵌套包含另一个结构体或结构体指针。

//Address 地址结构体
type Address struct {
    Province string
    City     string
}

//User 用户结构体
type User struct {
    Name    string
    Gender  string
    Address Address
}

func main() {
    user1 := User{
        Name:   "小王子",
        Gender: "男",
        Address: Address{
            Province: "山东",
            City:     "威海",
        },
    }
    fmt.Printf("user1=%#v\n", user1)//user1=main.User{Name:"小王子", Gender:"男", Address:main.Address{Province:"山东", City:"威海"}}
}

嵌套结构体字段冲突

嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段。

//Address 地址结构体
type Address struct {
    Province   string
    City       string
    CreateTime string
}

//Email 邮箱结构体
type Email struct {
    Account    string
    CreateTime string
}

//User 用户结构体
type User struct {
    Name   string
    Gender string
    Address
    Email
}

func main() {
    var user3 User
    user3.Name = "Dream"
    user3.Gender = "男"
    // user3.CreateTime = "2019" //ambiguous selector user3.CreateTime
    user3.Address.CreateTime = "2000" //指定Address结构体中的CreateTime
    user3.Email.CreateTime = "2000"   //指定Email结构体中的CreateTime
}

结构体的继承(多重继承)与组合

继承:Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。

多重继承:继承多个结构体叫多重继承

组合:如果一个结构体嵌套了另一个有名的结构体,那么这个模式就叫组合

   // 定义结构体名为Car
    type Car struct {
        weight int
        Name string
    }
    // 定义结构体名为Car1
    type Car1 struct {
        weight int
        name string
    }

    // 定义run方法(方法也可以继承)
    func (self Car) run() {
        fmt.Println(" runing")
    }

    // 定义结构体,名为bike,继承了两个结构体
    type Bike struct {
        Car          // 继承多个结构体叫多重继承
        c Car1
        lunzi int
    }
    type Train struct {
        c Car1       // 组合:如果一个结构体嵌套了另一个有名的结构体,那么这个模式就叫组合
        Car
    }

func main() {
    // 继承使用
    var a Bike
    a.c.weight = 100     // 多重继承时,需要使用结构体名.方法名调用方法
    a.c.name = "bike"
    a.Car.run()       // 结果:runing
    a.lunzi = 2
    fmt.Println(a)    // 结果:{{0 } {100 bike} 2}

    var t Train
    t.c.weight = 180        // 组合需要使用结构体名(c)来实现访问
    t.Car.Name = "train"
    t.Car.run()       // 结果:runing

}

结构体中标签(Tag)的使用

Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

`key1:"value1" key2:"value2"`

结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。

下面是一段使用了tag的代码示例:

// tag使用
func structTag() {
    // 定义struct并使用tag
    type Student struct {
        Name string `json:"name"`         // `json:"name"`:意思为在json序列化时指定显示的名称为"name"
        Age int `json:"age"`
        Score int `json:"score"`
    } 

    var stu Student = Student {
        Name: "stu01",
        Age : 18,
        Score: 90,
    }
    data, err := json.Marshal(stu)        // json.Marshal():序列化,有两个返回值,分别为bytes类型和错误信息
    if err != nil {
        fmt.Println("json encode stu faild", err)
        return
    }
    fmt.Println(string(data))        // 结果为:{"name":"stu01","age":18,"score":90}
}

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: