当前位置: 首页 > news >正文

吃透 Golang 基础:数据结构之 Struct

文章目录

  • 吃透 Golang 基础:数据结构之 Struct
    • 结构体的声明
    • 点运算符
    • 深入探讨结构体的初始化
    • 结构体与函数
    • 结构体比较
    • 结构体嵌入与匿名成员
    • LRU Cache:综合使用 struct 和 map 的例子

吃透 Golang 基础:数据结构之 Struct

在这里插入图片描述

结构体的声明

结构体是聚合类型,是由零个或多个任意类型的值聚合而成的实体。每个值称为结构体的成员

下例声明了一个名为 Employee 的结构体类型,并定义了一个 Employee 类型的变量:

type Employee struct {ID 			intName 		stringAddress 	stringDoB 		time.TimePosition 	stringSalary 		intManagerID 	int
}var dilbert Employee

在结构体定义时,如果两个相邻成员的类型是相同的,那么它们可以被合并到一行,比如:

type Employee struct {ID            intName, Address stringDoB           time.TimePosition      stringSalary        intManagerID     int
}

一个重要的性质是: 结构体成员的输入顺序有重要的意义。在定义时,如果定义的结构体成员的顺序不一致,那么我们定义的就是两个不同的结构体(相当于结构体定义的成员顺序也是标识结构体类型的因素之一,就像是长度不相等的数组不是同一类型的数组)。

如果结构体成员的名字以大写字母开头,那么该成员就是导出的,它对其他包可见。一个结构体可以同时包含导出和未导出的成员,结构体自身也可以是导出或未导出的。

通常,一个名为 S 的结构体的成员不能重复包含值类型的 S,但是可以包含一个 S 类型的指针,最常见的应用场景是链表(需要 next/prev 指针)、二叉树(需要 left/right 指针)等数据结构的定义。

点运算符

可以通过点访问符.来访问结构体变量当中的成员,比如dilbert.Namedilbert.DoB。需要注意的是,dilbert及其成员都是变量,因此可以直接修改dilbert的值。

dilbert.Salary -= 5000

由于结构体变量及变量包含的成员都是变量,故我们不仅可以建立针对dilbert的指针,还可以建立针对dilbert成员的指针。

position := &dilbert.Position
*position = "Senior" + *position

Golang 有一个语法糖,那就是点运算符可以直接作用在结构体类型的指针变量上,以直接访问指针所指向变量的成员,一个例子如下:

var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.Position += " (proactive team player)"// 等价于
(*employeeOfTheMonth).Position += " (proactive team player)"

深入探讨结构体的初始化

定义一个结构体类型之后,可以通过结构体字面值初始化一个结构体变量:

type Point struct { X, Y int }p := Point{1, 2}// 等价于
p := Point{}
p.X, p.Y = 1, 2

上面这种写法需要我们明确知道结构体每个成员的类型和顺序,另一种写法是以成员的名字和相应的值来进行初始化,可以包含全部或部分的成员(未被包含的成员将直接赋零值):

type Point struct { X, Y int }p := Point{X: 1, Y: 2}

上述两种写法不能混用。

结构体与函数

结构体可以作为函数的参数或返回值,下例通过Scale函数将Point类型的值缩放后返回:

func Scale(p Point, factor int) Point {return Point{p.X * factor, p.Y * factor}
}fmt.Println(Scale(Point{1, 2}, 5)	// "{5, 10}"

考虑效率的话,针对较大的结构体,可以通过指针的方式传入并返回(Golang 的函数默认只有传值调用,因此传递一个值作为实参到函数当中,函数会复制一个相同的变量,而不是引用。传指针需要注意潜在的内存逃逸问题,在对象频繁创建和删除的场景下,如果函数的返回值是指针,将会导致局部变量从栈逃逸到堆上,从而增加 GC 的开销,影响性能。一般情况下,对于需要修改原对象的值或是传递占用内存较大的结构体时,才选择传指针。对于只读的占用内存较小的结构体,直接传值的性能更好)。

由于结构体通常通过指针处理,下面的写法会创建并初始化一个结构体变量,并返回结构体的地址:

pp := &Point{1, 2}

上述语句等价于:

pp := new(Point)	// new 函数对所有变量通用, 它创建一块该变量的地址并返回其指针
*pp = Point{1, 2}

结构体比较

如果结构体的全部成员都是可比较的,那么结构体本身也是可比较的,也就是可以使用==!=进行比较:

type Point { X, Y int }p := Point{1, 2}
q := Point{2, 1}fmt.Println(p == q)	// "false"

可比较的结构体和其他可比较的类型一样,可以用作 Map 的 Key,这样 Map 就可以通过==!=判断当前 Key 是否存在:

type address struct {hostname stringport 	 int
}hits := make(map[address]int)
hits[address{"yggp", 443}] ++

结构体嵌入与匿名成员

这一部分是结构体当中比较重要的知识,这几天学习设计模式的过程中频繁见到结构体嵌入的写法。

需要事先重复强调的是,Golang 没有继承,而 Go 当中 struct 的嵌入可以被视为“继承”的一种方式,但从语法上来说确实不是继承,可以视为“组合”。

下例实现了一个简单的二维绘图程序库,定义了两个结构体:

type Circle struct {X, Y, Radius int
}type Wheel struct {X, Y, Radius, Spokes int
}

显然 Wheel 当中完全包含了 Circle 的重复成员,且 Circle 的成员可以进一步抽象成Point结构。因此我们对上述定义进行修改:

type Point struct {X, Y int
}type Circle struct {Center PointRadius int
}type Wheel struct {Circle CircleSpokes int
}

经过上述改动,结构体之间的嵌套关系非常清晰,但是想要修改具体的成员仍然非常麻烦:

var w Wheel
w.Circle.Center.X = 8
w.Circle.Center.Y = 8
w.Circle.Radius = 5
w.Spokes = 20

Go 的特性使得我们可以在结构体定义时只声明一个成员对应的数据类型,而不需要明确指出该成员的变量名,这类成员就是匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。下例是对之前非匿名嵌入的改写:

type Point struct {X, Y int
}type Circle struct {PointRadius int
}type Wheel struct {Circle Spokes int
}var w Wheel
w.X = 8            // equivalent to w.Circle.Point.X = 8
w.Y = 8            // equivalent to w.Circle.Point.Y = 8
w.Radius = 5       // equivalent to w.Circle.Radius = 5
w.Spokes = 20

针对包含匿名嵌入的结构体,通过字面值初始化结构体的时候必须遵循结构体声明时的结构,以下是通过字面值初始化结构体的例子:

w := Wheel{Circle{Point{8, 8}, 5}, 20}
m := Wheel{Circle: Circle{Point: Point{X: 8, Y: 8},Radius: 5,},Spokes: 20,
}

需要注意的是,如果在包内定义了匿名嵌入了的结构体,且嵌入的子结构体是非导出的,那么在包外将不能直接访问匿名嵌入的子结构体的成员。

LRU Cache:综合使用 struct 和 map 的例子

下面我们以 LeetCode 146. LRU Cache 这道经典的题目为例,综合使用一下 Golang 当中的 struct 与 map 数据结构。

type Node struct {key, val   intnext, prev *Node
}func newNode(_key, _val int) Node {return Node{key: _key, val: _val, next: nil, prev: nil}
}type LRUCache struct {cache           map[int]*Nodehead, tail      *Nodecapacity, size  int
}func Constructor(_capacity int) LRUCache {var lru_cache LRUCachelru_cache.cache = map[int]*Node{}lru_cache.capacity, lru_cache.size = _capacity, 0lru_cache.head, lru_cache.tail = new(Node), new(Node)lru_cache.head.next, lru_cache.tail.prev = lru_cache.tail, lru_cache.headreturn lru_cache
}func (this *LRUCache) Get(key int) int {if node, ok := this.cache[key]; !ok {return -1} else {this.moveToHead(node)return node.val}
}func (this *LRUCache) Put(key int, value int)  {if node, ok := this.cache[key]; !ok {new_node := newNode(key, value)this.cache[key] = &new_nodethis.addToHead(&new_node)this.size ++if this.size > this.capacity {t := this.removeTail()delete(this.cache, t.key)this.size --}} else {this.moveToHead(node)node.val = value}
}func (this *LRUCache) addToHead(node *Node) {node.next = this.head.nextnode.next.prev = nodethis.head.next = nodenode.prev = this.head
}func (this *LRUCache) removeNode(node *Node) {node.next.prev = node.prevnode.prev.next = node.next
}func (this *LRUCache) moveToHead(node *Node) {this.removeNode(node)this.addToHead(node)
}func (this *LRUCache) removeTail() *Node {t := this.tail.prevthis.removeNode(t)return t
}/*** Your LRUCache object will be instantiated and called as such:* obj := Constructor(capacity);* param_1 := obj.Get(key);* obj.Put(key,value);*/
http://www.xdnf.cn/news/863137.html

相关文章:

  • 涂胶协作机器人解决方案 | Kinova Link 6 Cobot在涂胶工业的方案应用与价值
  • 四、函数调用包含单个参数之Double类型-mmword,movsd,mulsd,addsd指令,总结汇编的数据类型
  • 4.1 HarmonyOS NEXT原生AI能力集成:盘古大模型端侧部署与多模态交互实战
  • 在compose中的Canvas用kotlin显示多数据波形闪烁的问题
  • 李飞飞World Labs开源革命性Web端3D渲染器Forge!3D高斯溅射技术首次实现全平台流畅运行
  • VR博物馆推动现代数字化科技博物馆
  • 【Linux】进程 信号保存 信号处理 OS用户态/内核态
  • bug:undefined is not iterable (cannot read property Symbol(Symbol.iterator))
  • Flutter面试题
  • 【学习笔记】Circuit Tracing: Revealing Computational Graphs in Language Models
  • 【OSG学习笔记】Day 15: 路径动画与相机漫游
  • 海康网络摄像头实时取帧转Opencv数组格式(h,w,3),已实现python、C#
  • [C]C语言日志系统宏技巧解析
  • 让视觉基础模型(VFMs)像大语言模型(LLMs)一样“会思考”​
  • 3D视觉重构工业智造:解码迁移科技如何用“硬核之眼“重塑生产节拍
  • SOLIDWORKS 2025教育版提供了丰富的自学资源
  • LabVIEW与Modbus/TCP温湿度监控系统
  • 扫地机产品--材质传感器算法开发与虚拟示波器
  • R语言基础| 下载、安装
  • Elasticsearch中的文档(Document)介绍
  • 代码随想录算法训练营第60期第五十七天打卡
  • Elasticsearch从安装到实战、kibana安装以及自定义IK分词器/集成整合SpringBoot详细的教程(二)
  • 第八部分:阶段项目 6:构建 React 前端应用
  • 华为OD机试_2025 B卷_区间交集(Python,100分)(附详细解题思路)
  • ann算法的种类有哪些,之间的区别,各自的适用场景
  • 每日算法刷题Day22 6.4:leetcode二分答案3道题,用时1h30min
  • 如何在 HTML 中添加按钮
  • 信号与系统汇总
  • Flutter、React Native 项目如何搞定 iOS 上架?从构建 IPA 到上传 App Store 的实战流程全解析
  • RabbitMQ 在解决数据库高并发问题中的定位和核心机制