Go语言基础十一——结构体Struct

139 阅读8分钟

我正在参加「掘金·启航计划」

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

1、类型别名和自定义类型

1.1、自定义类型

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

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

//将MyInt定义为int类型
type MyInt int //MyInt是一种新的类型,具有int的特性

1.2、类型别名

TypeAlias只是Type的别名,本质上TypeAlias与type是同一个类型。

type TypeAlias = type
//之前我们见过的rune和byte就是类型别名
type byte = uint8
type rune = int32

1.2、类型定义和类型别名的区别

//类型定义
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
}

2、结构体

Go语言中基础数据类型可以表示一些事务的基本属性,但是表达一个事务的全部或者部分属性时,这时再用单一的基本类型就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名struct

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

2.1、结构体的定义

使用type和struct关键字来定义结构体

type 类型名 struct{
	字段名 字段类型
    字段名 字段类型
    ...
}
//类型名:表示自定义接结构体的名称,在同一个包内不能重复
//字段名:表示结构体字段名,结构体中的字段名必须唯一
//字段类型:表示结构体字段的具体类型
//示例:
type Person struct{
	name string
    age  int8
}
//同样类型的字段也可以写在一行
type Person1 struct{
	name, city  string
    age         int8
}

2.2、结构体实例化

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

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

var 结构体实例 结构体类型

2.2.1、基本实例化

type Person struct {
	name string
    city string
    age  uint8
}

func main(){
	var p1 Person
    p1.name = "test" //通过.访问成员变量
    p1.city = "北京"
    p1.age = 18
    fmt.Printf("p1=%v\n", p1)//p1={test 北京 18}
    fmt.Printf("p1=%#v\n", p1) //p1={name:"test", city:"北京", age:18}
}

2.2.2、匿名结构体

定义一些临时数据结构等场景下可以使用匿名结构体

func main(){
    var user struct{Name string; Age int}
    user.Name ="test"
}

2.2.3、创建指针类型结构体

我们可以使用new关键字对结构体进行实例化,得到的是结构体地址

var p2 = new(Person)
p2.name = "test"
fmt.Printf("%T\n", p2)//*main.Person p2是一个结构体指针

2.2.4、取结构体的地址实例化

使用&对结构体进行取地址操作相当于对结构体类型进行了一次new实例化操作。

p3 := &Person{}

2.3、结构体初始化

没有初始化的结构体,其成员变量都是对应类型的零值。

2.3.2、使用键值对初始化

使用键值对对结构体进行初始化,键对应结构体的字段,值对应该字段的初始值。

p := Person{
    Name :"test",
    City :"北京",
    Age :18,
}

2.3.2、使用值得列表初始化

初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值。

p := Person{
	"test",
    "北京",
    18,
}

使用这种方式初始化时,需要注意:

  • 必须初始化结构体的所有字段
  • 初始值的填充顺序必须与字段在结构体中的声明顺序一致
  • 该方式不能和键值初始化方式混用

2.4、结构体内存布局

结构体占用一块连续的内存。参考内存对齐

2.5、空结构体

空结构体是不占用空间的

var v struct{}
fmt.Println(unsafe.Sizeof(v)) //0

2.6、构造函数

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

func newPerson(name, city string, age uint8) *Person{
    return &Person{
        Name:name,
        City:city,
        Age:age,
     
    }
}
//调用构造函数
newPerson("test", "北京", 18)

2.7、方法和接收者

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

方法的定义格式如下:

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

其中:

  • 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写
  • 接收者类型:接收者类型和参数类似,可以使用指针类型和非指针类型,建议格式统一。
  • 方法名、参数列表、返回参数:具体格式与函数定义相同。

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

2.7.1、值类型的接收者

当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对的副本,无法修改接收者变量本身。

2.7.2、指针类型的接收者

指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。

2.7.3、什么时候应该使用指针类型接收者

  1. 需要修改接收者中的值
  2. 接收者时拷贝代价比较大的对象
  3. 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。

2.8、任意类型添加方法

在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法,例:

type MyInt int

func(m MyInt)Hello(){
    fmt.Println("hello")
}
func main(){
	var m1 MyInt
    m1.Hello() //调用
}

注意: 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。

2.9、结构体的匿名字段

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

注意:匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。

type Person struct{
	string
    int
}
func main(){
    p1 := Person{
        "test",
        18
    }
    fmt.Println(p1.string) //test
    fmt.Pringln(p1.int) //18
}

2.10、嵌套结构体

一个结构体中可以嵌套包含另一个结构体或结构体指针,如下:

type Address struct{
	Provice string
    City    string
}

type Person struct {
	Name    string
    Address Address
}

func main(){
    user1 := User{
        Name:"test",
        Address:Address{
            Province:"山东",
            City:"济南",
        }
    }
}

2.10.1、嵌套匿名字段

上面user结构体中嵌套的Address结构体也可以采用匿名字段方式,如下:

type Address struct {
	Province string
    City 	 string
}
type User struct {
	Name string
    Address //匿名字段
}
func main(){
    var user User
    user.Name ="test"
    user.Address.Province ="山东" //匿名字段默认使用类型名作为字段名
    user.City ="济南"				//匿名字段也可以省略
}

当访问结构体成员时会现在结构体中查找该字段,找不到去嵌套的匿名字段中查找。

2.10.2、嵌套结构体的字段名冲突

嵌套结构体内部可能存在相同的字段名,在这种情况下为了避免歧义需要通过指定具体的内嵌结构体字段名。

type Address struct{
	Name string
}
type Email struct {
	Name string
}

type User struct{
    Id int
    Address
    Email
}
func main(){
    u := User{}
    u.Id = 1
    u.Address.Name="山东"
    u.Email.Name = "email"
}

2.11、结构体的“继承”

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

type Animal struct{
	Name string
}
type (a *Animal) move(){
    fmt.Printf("%s会运动\n",a.name)
}
type Dog struct {
	Feet int8
    *Animal //通过嵌套匿名结构体实现继承
}
func (d *Dog) wang(){
    fmt.Printf("%s会汪汪汪", d.name)
}
func main(){
    d1 := &Dog{
        Feet:4,
        Animal:&Animal{
            name:"乐乐",
        } 
    }
    d1.wang()
    d1.move()
}

2.12、结构体字段的可见性

结构体中字段大写开头表示公开访问,小写表示私有(仅在定义当前结构体的包中可访问)

2.13、结构体与Json序列化

type Student struct{
	Id int
    Gender string
    Name string
}

type Class struct {
	Title string
    Students []*Student
}

func main(){
    c := &Class{
        Title:"一班",
        Students:make([]*Student, 0, 100)
    }
    for i := 0; i<10; i++ {
        stu := &Student{
            Name: fmt.Sprintf("stu_%02d", i),
            Gender:"男",
            Id: i,
        }
        c.Students = append(c.Students, stu)
    }
    //json序列化 结构体--》json格式字符串
    data, err := json.Marshal(c)
    if err != nil {
        fmt.Println(err)
    	return 
    }
    fmt.Prints("json:%s\n", data)
    //json反序列化,json字符串--》结构体
    c1 :=&Class{}
    err = json.Unmarshal([]byte(data), c1)
    if err != nil {
        fmt.Println(err)
        return 
    }
    fmt.Printf("%#v\n", c1)
}

2.12、结构体标签(Tag)

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

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

结构体tag由一个或多个键值对组成,键与值使用冒号分隔,值使用双银行包括起来。同一个结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。

type Student struct {
    ID     int 	  `json:"id"` //通过指定tag实现json序列化该字段时的key
    Gender string			  //json序列化是默认使用字段名作为key
    name   string             //私有不能被json包访问
}