Go语言基础之结构体

类型别名和自定义类型

 

自定义类型

Go语言使用type来自定义类型,自定义类型是定义了一个全新的类型

type NewInt int

 

类型别名

使用type和=来进行类型别名定义

type TypeAlisa = Type

 

自定义类型和类型别名区别

//NewInt 基于int的自定义类型
type NewInt int

//MyInt 为类型别名
type MyInt = int

func main() {
	var a NewInt
	var b MyInt
	fmt.Printf("%T %v\n", a, a)    // main.NewInt 0
	fmt.Printf("%T %v\n", b, b)    // int 0
}

 

结构体

Go语言中的结构体相当于其他语言的类,表示一些事物的基本属性。使用type和struct来定义结构体。

 

结构体定义

type 类型名 struct{
    字段名, 字段类型
    ...
}
type person struct{
    name, city string
    age        int8
}

 

结构体实例化

只有结构体实例化后,才会真正的分配内存。也就是只有实例化之后才可以使用结构体的字段。

var 结构体实例 结构体类型
func main() {
	var p1 person
	p1.name = "小三"
	p1.city = "北京"
	p1.age = 18
	fmt.Printf("p1=%v\n", p1)     // p1={小三 北京 18}
	fmt.Printf("p1=%#v\n", p1)    // p1=main.person{name:"小三", city:"北京", age:18}
}

 

匿名结构体

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

func main() {
	var user struct {
		Name string
		Age  int8
	}
	user.Name = "小四"
	user.Age = 44
	fmt.Printf("%#v\n", user)    // struct { Name string; Age int8 }{Name:"小四", Age:44}
}

 

创建指针类型结构体

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

func main() {
	var p2 = new(person)
	p2.name = "小三"        // 这里其实应该是(*p2).name  Go做了封装可以简写
	p2.city = "上海"
	fmt.Printf("%T\n", p2)    // *main.person
	fmt.Printf("%v\n", p2)    // &{小三 上海 0}
}

 

取结构体的地址实例化

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

func main() {
	p3 := &person{}
	fmt.Printf("%T\n", p3)        // *main.person
	p3.name = "小四"
	p3.city = "广州"
	p3.age = 44
	fmt.Printf("%#v", p3)        // &main.person{name:"小四", city:"广州", age:44}
}

 

使用键值对初始化

func main() {
	p3 := person{
		name: "小三",
		city: "北京",
		age:  19,
	}
	fmt.Println(p3)
}

也可以对结构体指针进行健值初始化

func main() {
	p3 := &person{
		name: "小三",
		city: "北京",
		age:  19,
	}
	fmt.Println(p3)
}

 

使用值的列表初始化

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

func main() {
	p3 := &person{
		"小三",
		"北京",
		19,
	}
	fmt.Println(p3)
}

 

构造函数

Go中结构体没有构造函数,可以自己实现。一般构造结构体使用指针,来提升性能。一般使用New+结构体名字

func NewPerson(name, city string, age int8) *person {
	return &person{
		name: name,
		city: city,
		age:  age,
	}
}

调用构造体函数

func main() {
	p3 := NewPerson("张三", "上海", 19)
	fmt.Println(p3)
}

 

方法和接收者

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

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数){
    函数体
}
  • 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母
  • 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型
  • 方法名、参数列表、返回参数:具体格式与函数定义相同
// 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在做梦", p.name)
}

func main() {
	p1 := NewPerson("小红", 18)
	p1.Dream()
}

 

指针类型的接收者

指针类型的接收者有一个构造体的指针组成,调用该方法可以修改任意成员的变量。

func (p *Person) setAge(newAge int8) {
	p.age = newAge
}

func main() {
	p1 := NewPerson("小红", 18)
	p1.setAge(20)
	fmt.Println(p1.age)       // 20
}

 

值类型的接收者

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

func (p Person) setAge2(newAge int8) {
	p.age = newAge
}

func main() {
	p1 := NewPerson("小红", 18)
	p1.setAge2(20)
	fmt.Println(p1.age)    // 20
}

 

什么时候只用指针类型接收者

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

 

任意类型添加方法

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

//MyInt 将int定义为自定义MyInt类型
type MyInt int

//SayHello 为MyInt添加一个SayHello的方法
func (m MyInt) SayHello() {
	fmt.Println("Hello, 我是一个int。")
}
func main() {
	var m1 MyInt
	m1.SayHello() //Hello, 我是一个int。
	m1 = 100
	fmt.Printf("%#v  %T\n", m1, m1) //100  main.MyInt
}

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

 

结构体的匿名字段

结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。

//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 {
	city string
}

// User 为结构体
type User struct {
	name    string
	age     int8
	Address Address
}

func main() {
	u := User{
		name: "用户a",
		age:  18,
		Address: Address{
			city: "北京",
		},
	}
	fmt.Printf("%#v", u)     
// main.User{name:"用户a", age:18, Address:main.Address{city:"北京"}}
}

 

嵌套匿名结构体

// Address 为结构体
type Address struct {
	city    string
	country string
}

// User 为结构体
type User struct {
	name string
	Address
}

func main() {
	var u User
	u.name = "小二"
	u.Address.country = "China"   // 通过匿名结构体.字段名访问
	u.city = "北京"               // 直接访问匿名结构体的字段名
	fmt.Printf("%#v", u)         
// main.User{name:"小二", Address:main.Address{city:"北京", country:"China"}}
}

 

嵌套结构体的字段冲突

// Address 为结构体
type Address struct {
	name string
	city string
}

// Email 为结构体
type Email struct {
	name string
}

// User 为结构体
type User struct {
	name string
	Address
	Email
}

func main() {
	u := User{
		name: "小二",
		Address: Address{
			name: "四街",
			city: "上海",
		},
		Email: Email{
			name: "xiaoer@qq.com",
		},
	}
	fmt.Println(u.name, u.Email.name, u.Address.name)
}

 

结构体中的继承

type animal struct {
	name string
}

type dog struct {
	*animal
}

func (a *animal) move() {
	fmt.Printf("%s会跑\n", a.name)
}

func (d *dog) wang() {
	fmt.Printf("%s会汪汪叫", d.name)
}

func main() {
	d1 := &dog{
		animal: &animal{
			name: "小黄",
		},
	}
	d1.move()
	d1.wang()
}

 

结构体字段的可见性

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

 

结构体与json序列化

package main

import (
	"encoding/json"
	"fmt"
)

// Student 学生结构体
type Student struct {
	ID   int8
	Name string
}

// Class 班级结构体
type Class struct {
	Title    string
	Students []Student
}

// NewStudent 为Student的构造函数
func NewStudent(id int8, name string) Student {
	return Student{
		ID:   id,
		Name: name,
	}
}

func main() {
	c1 := Class{
		Title:    "高中一班",
		Students: make([]Student, 0, 20),
	}
	for i := 0; i < 10; i++ {
		ns := NewStudent(int8(i), fmt.Sprintf("stu%02d", i))
		c1.Students = append(c1.Students, ns)
	}
	// JSON序列化
	data, err := json.Marshal(c1)
	if err != nil {
		fmt.Println("json序列化失败")
		return
	}
	fmt.Printf("序列化结果为%s\n:", data)
	// JSON反序列化
	str := `{"Title":"高中一班","Students":[{"ID":0,"Name":"stu00"},{"ID":1,"Name":"stu01"},{"ID":2,"Name":"stu02"},{"ID":3,"Name":"stu03"}]}`
	// c2 := &Class{}
	var c2 Class
	err = json.Unmarshal([]byte(str), &c2)
	if err != nil {
		fmt.Println("json反序列化失败")
		return
	}
	fmt.Println("反序列化结果为",c2)

}

因为上边定义的变量都为大写的,所以反序列化能够成功显示,如果为小写,那就对外部不可见,反序列化后小写字段是修改不成功的。

 

结构体Tag标签

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

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

结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。 注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

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

func main() {
	s1 := Student{
		ID:     1,
		Gender: "男",
		name:   "唐僧",
	}
	data, err := json.Marshal(s1)
	if err != nil {
		fmt.Println("json marshal failed!")
		return
	}
	fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"男"}
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值