go语言学习笔记_结构体

自定义类型

  • 在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型, Go语言中可以使用type关键字来定义自定义类型。
  • 自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。例如:
//将MyInt定义为int类型
//通过type关键字的定义,MyInt就是一种新的类型,它具有int的特性。
type MyInt int
// 自定义一个person类型
type person struct {
    name string
    age int
}

类型别名

  • 类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。
type TypeAlias = Type
  • 我们之前见过的rune和byte就是类型别名,他们的定义如下:
type byte = uint8
type rune = int32
  • 类型别名只在代码编写 过程中有效,编译完之后就不存在
  • 自定义类型和类型别名 就差一个等号 可以理解位赋值
  • 以type 开头的都是一个新的类型
  • 自定义类型可以增加对应的类型方法。
  • golang里面的方法是针对类型的函数,类似与 类里面的函数。只不过通过接收者定义。

结构体

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

  • 结构体 struct是值类型
  • Go语言中通过struct来实现面向对象。
  • 结构体是一种数据类型,一种我们自己造的可以保存多个维度数据的类型.类似于其他语言的类,
  • 结构体字段名首字母尽量用大写
type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    …
}
type person struct {
    name string
    age int   	
    addr string
}
  • 类型名:标识自定义结构体的名称,在同一个包内不能重复。
  • 字段名:表示结构体字段名。结构体中的字段名必须唯一。
  • 字段类型:表示结构体字段的具体类型。
  • 同样类型的字段也可以写在一行,如 name,addr string 这样
  • 上面person方便保存一个人的信息了
  • 语言内置的基础数据类型是用来描述一个值的,而结构体是用来描述一组值的
匿名结构体

多用于临时场景.

var a = struct{
    x int
    y int
}
结构体实例化与初始化
  • 只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。

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

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

  • 当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值

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

type person struct {
	name string	
	age  int8
}
// 构造(结构体变量的)函数,返回值是对应的结构体类型
//通过一个函数 返回一个结构体  可能学习不到位 想不到有什么使用场景。
func newPerson(n string, a int) (p person) {
	p = person{
		name: n,
		age:  a,
	}
	return p
}

func main() {
	var p1 person // 结构体实例化
	p1.name = "张三"
	p1.age = 30
	p2 := person{"李四", 18} // 结构体实例化
	p2 := person{name:"思理理", age:18} // 结构体实例化
	// 调用构造函数生成person类型变量
	p4 := newPerson("王五", 18)
	p7 := &person{
	city: "北京",
}
fmt.Printf("p7=%#v\n", p7) //p7=&main.person{name:"", city:"北京", age:0}
}
  • 使用值的列表初始化
    • 必须初始化结构体的所有字段。
    • 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
    • 该方式不能和键值初始化方式混用。
p2 := person{"李四", 18} // 结构体实例化
  • 结构体占用一块连续的内存。
创建指针型结构体
  • 我们还可以通过使用new关键字对结构体进行实例化,得到的是结构体的地址
  • 从打印的结果中我们可以看出p2是一个结构体指针。
  • 语法糖需要注意的是在Go语言中支持对结构体指针直接使用.来访问结构体的成员。
  • 使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作
var p2 = new(person)
p2.name = "小王子"
p2.age = 28
fmt.Printf("%T\n", p2)     //*main.person
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"小王子",  age:28}
p3 := &person{}
fmt.Printf("%T\n", p3)     //*main.person
构造函数

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

// 构造(结构体变量的)函数,返回值是对应的结构体类型
//通过一个函数 返回一个结构体  可能学习不到位 想不到有什么使用场景。
func newPerson(n string, a int) (p *person) {
	p = &person{
		name: n,
		age:  a,
	}
	return p
}

func main() {	
	// 调用构造函数生成person类型变量
	p4 := newPerson("王五", 18)	
	fmt.Printf("%#v\n", p4) //&main.person{name:"王五",age:18}
}
方法和接收者
  • Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者self
  • 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等。
  • 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
  • 方法名、参数列表、返回参数:具体格式与函数定义相同。
  • 其实就是在一般函数定义的时候 在方法名前面 添加一个小括号 里面写上变量和变量类型
  • 方法与函数的区别是:函数不属于任何类型,方法属于特定的类型。
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}


  • 举例 下面dream 只有person 类型可以使用。
//Person 结构体
type Person struct {
	name string
	age  int8
}

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

func main() {
	p1 := Person("小王", 25)
	p1.Dream() //小王的梦想是学好Go语言!
}
指针类型接收者 和值类型接收者
  • 值类型接收者:当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。

  • 指针类型的接收者:由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self

  • 区分:两者的区分就是 接收者的类型是不是指针类型。不是指针类型就值值类型接收者

  • 什么情况使用指针类型接收者

    • 需要修改接收者中的值
    • 接收者是拷贝代价比较大的大对象
    • 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。
  • 我觉得应该尽量使用指针接收者,使用起来和python 类似。属性可以看成是类属性。

给任意类型添加方法
  • 接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。 举个例子,我们基于内置的int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法
  • 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法 比如 不能给 int 增加方法,只能给当前main包里面的类型添加方法
//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 {
	Province string
	City     string
}

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

func main() {
	user1 := User{
		Name:   "张三",
		Gender: "男",
		Addr: Address{
			Province: "北京",
			City:     "房山",
		},
	}
	fmt.Printf("user1=%#v\n", user1)//user1=main.User{Name:"张三", Gender:"男", Address:main.Address{Province:"北京", City:"房山"}}
	fmt.Printf("%s的所在城市是:%#v\n", user1.Name, user1.Addr.City)//张三的所在城市是:"房山"
}
嵌套匿名结构体
  • 匿名结构体:和匿名字段一样,就是结构体没有命名, 默认以结构体类型当作名字
  • 当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找。
  • 嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段
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 = "范闲"
	user3.Gender = "男"
	user3.City = "北京" // 
	user3.Address.CreateTime = "2001" //指定Address结构体中的CreateTime
	user3.Email.CreateTime = "2002"   //指定Email结构体中的CreateTime
	fmt.Printf("%#v\n", user3)
	//main.User{Name:"范闲", Gender:"男", Address:main.Address{Province:"", City:"北京", CreateTime:"2001"}, Email:main.Email{Account:"", CreateTime:"2002"}}
}

结构体的继承
  • Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。
//Animal 动物
type Animal struct {
	name string
}

func (a *Animal) move() {
	fmt.Printf("%s会动!\n", a.name)
}

//Dog 狗
type Dog struct {
	Feet    int8
	*Animal //通过嵌套匿名结构体实现继承
}

func (d *Dog) wang() {
	fmt.Printf("%s会汪汪汪~\n", d.name)
}

func main() {
	d1 := &Dog{
		Feet: 4,
		Animal: &Animal{ //注意嵌套的是结构体指针
			name: "乐乐",
		},
	}
	d1.wang() //乐乐会汪汪汪~
	d1.move() //乐乐会动!
}

注意事项

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

结构体与json序列化

  • JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。
  • JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。
  • 序列化: 把Go语言中的结构体变量 --> json格式的字符串
  • 反序列化: json格式的字符串 --> Go语言中能够识别的结构体变量
  • 反序列化 ,要传入指针

// person 基类
type person struct {
	Name string `json:"name" db:"name" ini:"name"`
	Age  int    `json:"age"`
}

func main() {
	p1 := person{
		Name: "张三",
		Age:  23,
	}
	// 序列化
	b, err := json.Marshal(p1)
	if err != nil {
		fmt.Printf("marshal failed, err:%v", err)
		return
	}
	fmt.Printf("%v\n", string(b))//{"name":"张三","age":23}
	// 反序列化
	str := `{"name":"李四","age":18}`
	var p2 person
	json.Unmarshal([]byte(str), &p2) // 传指针是为了能在json.Unmarshal内部修改p2的值
	fmt.Printf("%#v\n", p2) //main.person{Name:"李四", Age:18}
}
结构体标签 Tag
  • Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
  • key1:"value1" key2:"value2"
  • 像上面例子里面的`json:“name” db:“name” ini:“name”`就是标签
  • 结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。
  • 注意事项: 为结构体编写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":"男"}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值