首页 > 其他分享 >struct 结构体【GO 基础】

struct 结构体【GO 基础】

时间:2023-10-12 17:36:39浏览次数:29  
标签:struct fmt name Person GO main type 结构

〇、前言

虽然 Go 语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念,但是可以通过结构体的内嵌,再配合接口,来实现面向对象,甚至具有更高的扩展性和灵活性。那么本文就将详细看下怎么使用结构体。

一、结构体的定义和实例化

Go 语言中的基础数据类型可以表示一些事物的基本属性,但是当想要表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了。

Go 语言提供了一种自定义数据类型可以封装多个基本数据类型,这种数据类型叫结构体 struct。通过 struct 可以封装自己所需的各种复杂类型。

1.1 什么叫做自定义类型和类型别名?

在详解结构体之前,需要先了解两个概念,到底什么叫做自定义类型和类型别名?

【自定义类型】

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

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

type MyInt int // 新定义一个类型 MyInt,以 int 类型为参照

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

【类型别名】

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

类型别名规定:TypeAlias 只是 Type 的别名,本质上 TypeAlias 与 Type 是同一个类型。就像一个小孩子有自己的小名和户口本的大名,都是指的同一个人。

type TypeAlias = Type

例如长江的 rune 和 byte 就是类型别名,他们的定义如下:

type byte = uint8
type rune = int32

【区别】

那么它们之前的区别是啥呢?下面分别定义一个变量看下它们的类型:

package main

import "fmt"

// 自定义类型
type NewInt int

// 类型别名
type MyInt = int

func main() {
	var a NewInt
	var b MyInt
	fmt.Printf("NewInt type of a : %T\n", a)
	fmt.Printf("MyInt  type of b : %T\n", b)
}

有输出结果可知,自定义类型的 type 将变成新的名字 NewInt;类型别名 MyInt,则仍属于基本的 int 类型。

1.2 结构体的定义

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

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

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

type Person struct {
    name string
    city string
    age  int8
}
// 类型相同的字段可以放到一起,如下:
type Person struct {
    name, city string
    age  int8
}

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

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

1.3 结构体的实例化

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

// 结构体实例化格式:
var 结构体实例名 结构体类型

下面是一个 Person 的示例:

package main

import "fmt"

type Person struct {
	name string
	city string
	age  int8
}

func main() {
	var person Person
	person.name = "中国"
	person.city = "北京"
	person.age = 18
	fmt.Printf("person.name = %v\n", person.name)
	fmt.Printf("person %%v   = %v\n", person)
	fmt.Printf("person %%#v  = %#v\n", person)
}

二、结构体的使用

2.1 匿名结构体

结构体还可以直接用于临时的或仅使用一次的场景中,不用声明结构体的名称,例如

package main

import (
	"fmt"
)

func main() {
	var user struct {
		Name string
		Age  int
	}
	user.Name = "张三"
	user.Age = 18
	fmt.Printf("%#v\n", user)
}

通过匿名结构体声明得来的实例 user。

2.2 指针类型的结构体

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

另外,Go 语言中支持对结构体指针直接使用‘.’来访问结构体的成员。如下示例:

package main

import (
	"fmt"
)

type Person struct {
	name string
	city string
	age  int8
}

func main() {
	var p = new(Person) // 声明结构体指针
    // p3 := &Person{} // 还可以通过 & 对结构体进行取地址操作,相当于对该结构体类型进行了一次 new 实例化操作
	p.name = "测试"
	p.age = 18
	p.city = "北京"
	fmt.Printf("%T\n", p) // 打印出类型
	fmt.Printf("p = %#v\n", p)
}

2.3 结构体的初始化

【声明即初始化】

此时结构体的值均为默认零值:

package main

import (
	"fmt"
)

type Person struct {
	name string
	city string
	age  int8
}

func main() {
	// 声明即初始化
	var p = new(Person)
	fmt.Printf("p = %#v\n", p)
}

【使用键值对初始化】

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

// 使用键值对初始化
p1 := Person{
    name: "张三",
    city: "北京",
    age:  18,
}
fmt.Printf("p1 = %#v\n", p1)
// 对结构体指针进行键值对初始化
p2 := &Person{
    name: "张三",
    city: "北京",
    age:  18,
}
fmt.Printf("p2 = %#v\n", p2)

【使用值的列表初始化】

初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值。
// 使用值的列表初始化
p3 := &Person{
    "张三",
    "北京",
    18,
}
fmt.Printf("p3 = %#v\n", p3)

注意,值列表需要满足以下要求:

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

2.4 结构体的内存布局

package main

import (
	"fmt"
)

type test struct {
	a int8
	b int8
	c int8
	d int8
}

func main() {
	n := test{
		1, 2, 3, 4,
	}
	fmt.Printf("n.a %p\n", &n.a)
	fmt.Printf("n.b %p\n", &n.b)
	fmt.Printf("n.c %p\n", &n.c)
	fmt.Printf("n.d %p\n", &n.d)
}

2.5 结构体的构造函数

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

package main

import (
	"fmt"
)

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,
	}
}

func main() {
	p9 := newPerson("张三", "测试", 90)
	fmt.Printf("%#v\n", p9)
}

2.6 方法和接收者

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

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}
// 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是 self、this 之类的命名
//     例如,Person 类型的接收者变量应该命名为 p,Connector 类型的接收者变量应该命名为 c 等
// 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型
// 方法名、参数列表、返回参数:具体格式与函数定义相同

下面是一个简单示例:

package main

import (
	"fmt"
)

// 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 方法,来修改实例变量的年龄。

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

package main

import (
	"fmt"
)

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

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

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

// SetAge2 设置p的年龄
// 使用值接收者
func (p Person) SetAge2(newAge int8) {
	p.age = newAge
}

func main() {
	p1 := NewPerson("张三同学", 25)
	fmt.Println("修改前的 age :", p1.age)
	p1.SetAge(18)
	fmt.Println("修改后的 age :", p1.age)
	p1.SetAge2(20)
	fmt.Println("修改后的 age :", p1.age)
}

使用指针类型的情况:

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

【任意类型都可以添加方法】

比如下面示例代码,给自定义类型添加方法:

package main

import (
	"fmt"
)

// 自定义一个类型 MyInt,参考 int 类型
type MyInt int

// 为 MyInt 添加一个 SayHello 的方法
func (m MyInt) SayHello() {
	fmt.Println("Hello, 我是一个 int。")
}

func main() {
	var m1 MyInt
	m1.SayHello()
	m1 = 100
	fmt.Printf("%#v  %T\n", m1, m1)
}

2.7 结构体允许包含匿名字段

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

package main

import (
	"fmt"
)

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

func main() {
	p1 := Person{
		"张三",
		18,
	}
	fmt.Printf("%#v\n", p1)
	fmt.Println(p1.string, p1.int)
}

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

2.8 嵌套结构体

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

package main

import (
	"fmt"
)

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

// User 用户结构体
type User struct {
	Name    string
	Gender  string
	Address Address // 此处可以省略第二个 Address 以匿名类型方式
}

func main() {
	user1 := User{
		Name:   "张三",
		Gender: "女",
		Address: Address{
			Province: "北京",
			City:     "北京",
		},
	}
	fmt.Printf("user1 = %#v\n", user1)
}

当嵌套结构体内部存在相同的字段名时,为了避免歧义需要指定具体的内嵌结构体的字段,否则会提示异常。

2.9 结构体实现“继承”

如下示例代码,通过嵌套匿名结构体实现继承:
package main

import (
	"fmt"
)

// 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()
}

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

2.10 结构体与 JSON 序列化

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。

JSON 键值对是用来保存 JS 对象的一种方式,键/值对组合中的键名写在前面并用双引号 "" 包裹,使用冒号 : 分隔,然后紧接着值;多个键值之间使用英文 , 分隔。

package main

import (
	"encoding/json"
	"fmt"
)

// Student 学生
type Student struct {
	ID     int
	Gender string
	Name   string
}

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

func main() {
	c := &Class{
		Title:    "101",
		Students: make([]*Student, 0, 200),
	}
	for i := 0; i < 2; i++ { // 创建 2 个学生对象
		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("json marshal failed")
		return
	}
	fmt.Printf("json:%s\n\n", data)
	// JSON 反序列化:JSON 格式的字符串-->结构体
	str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"}]}`
	c1 := &Class{}
	err = json.Unmarshal([]byte(str), c1)
	if err != nil {
		fmt.Println("json unmarshal failed!")
		return
	}
	fmt.Printf("%#v\n", c1)
}

2.11 结构体标签 Tag

Tag 是结构体的元信息,可以在运行的时候通过反射的机制读取出来。

Tag 在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

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

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

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

例如我们为 Student 结构体的每个字段定义 json 序列化时使用的 Tag:

package main

import (
	"encoding/json"
	"fmt"
)

// 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)
}

2.12 删除 map 类型的结构体子项

package main

import "fmt"

type student struct {
	id   int
	name string
	age  int
}

func main() {
	ce := make(map[int]student)
	ce[1] = student{1, "张三", 22}
	ce[2] = student{2, "李四", 23}
	fmt.Println(ce)
	delete(ce, 2)
	fmt.Println(ce)
}

参考:http://www.topgoer.com/go%E5%9F%BA%E7%A1%80/%E7%BB%93%E6%9E%84%E4%BD%93.html

标签:struct,fmt,name,Person,GO,main,type,结构
From: https://www.cnblogs.com/hnzhengfy/p/Go_struct.html

相关文章

  • RunnerGo UI自动化测试功能使用体验
    首先需要进入官网,RunnerGo支持开源,可以自行下载安装,也可以点击右上角体验企业版按钮快速体验 点击体验企业版进入工作台后可以点击页面上方的UI自动化进入到测试页面 创建元素我们可以在元素管理中创建我们测试时需要的元素 这里我们以一个打开百度搜索的场景,添加了......
  • 动物识别系统python+Django网页界面+TensorFlow算法模型+数据集训练
    一、简介动物识别系统。基于Python+TensorFlow+Django网页框架+ResNet50算法模型实现实现步骤如下:收集多种动物的图片数据集,并整理归类然后使用TensorFlow搭建ResNet50算法模型网络对数据集进行多次迭代训练最后得到一个精度较高的H5模型文件基于训练好的模型,使用Django开......
  • 什么是PMP里的组织分解结构(OBS)?
    在PMP和PMI的PMBOK(项目管理知识体系指南)中,OBS代表“组织分解结构”(OrganizationalBreakdownStructure)。OBS是一种项目管理工具,用于表示组织的层次结构,特别是与特定项目相关的部分。它为项目中的工作分配到不同的组织单位或团队提供了一个清晰的框架。以下......
  • golang之异步队列Asynq
    Asynq[1]是一个Go实现的分布式任务队列和异步处理库,基于redis,类似Ruby的sidekiq[2]和Python的celery[3]。Go生态类似的还有machinery[4]和goworker同时提供一个WebUI asynqmon[5],可以源码形式安装或使用Dockerimage,还可以和Prometheus集成dockerrun--rm--nameasynqmon......
  • Go接口 - 构建可扩展Go应用
    本文深入探讨了Go语言中接口的概念和实际应用场景。从基础知识如接口的定义和实现,到更复杂的实战应用如解耦与抽象、多态、错误处理、插件架构以及资源管理,文章通过丰富的代码示例和详细的解释,展示了Go接口在软件开发中的强大功能和灵活性。关注【TechLeadCloud】,分享互联网架......
  • struct.error: 'H' format requires 0 <= number <= 65535
    全部代码如下:frompymodbus.clientimportModbusTcpClient#避坑:write_registers和write_register函数差一个s。多一个s的参数用整型列表,没有的只能用整型defsplit_float_to_integer_and_fraction_parts(number):"""将浮点数拆分为整数部分和小数部分的函数......
  • 交通标志识别系统python+TensorFlow+算法模型+Django网页+数据集
    一、介绍交通标志识别系统。技术涉及:Python编程语言开发TensorFlow搭建算法模型对数据集进行训练得到一个精度较高的模型文件Django开发网页端界面平台实现对58种交通标志图片进行识别二、效果图片展示三、演示视频and代码视频+代码+介绍:https://s7bacwcxv4.feishu.......
  • 微服务框架之go-zero
    官方文档:  https://go-zero.dev/ [安装]脚手架goctl:goinstallgithub.com/zeromicro/go-zero/tools/goctl@latest 依赖protoc,protoc-gen-go,protoc-grpc-go安装:protoc:下载:https://github.com/protocolbuffers/protobuf/releases/download/v21.11/protoc-21......
  • MongoDB 中的索引分析
    MongoDB的索引前言MongoDB使用B树还是B+树索引单键索引创建单键索引使用expireAfterSeconds创建TTL索引复合索引最左匹配原则ESR规则如何使用排序条件多键索引创建多键索引局限性哈希索引注意事项创建索引总结参考MongoDB的索引......
  • 数据结构之队列(循环队列)
    循环队列又称为环形队列,有如下4个特点:在循环队列的定义中规定了两个索引指针:front和rear。front指向第一个有效元素的位置,而rear可以理解为用来记录队尾元素的下一个位置。当队列为空时,front==rear;当队列满时,(rear+1)%n=front.这样可以让队列看起来像一个环状......