【Go语言圣经】第四节:复合数据类型
第四章:复合数据类型
本节主要讨论四种类型——数组、slice、map和结构体。
数组和结构体都是有固定内存大小的数据结构。相比之下,slice 和 map 则是动态的数据结构,它们可以根据需要动态增长。
4.1 数组
数组是一个定长的由特定类型元素构成的序列。由于数组定长,因此 Golang 当中很少直接使用数组,而是使用 slice。一个使用数组的例子如下:
package mainimport "fmt"func main() {var a [3]int // 初始化一个空的数组fmt.Println(a)var q [3]int = [3]int{1, 2, 3} // 使用数组字面值语法用一组值来初始化数组var p [3]int = [3]int{4, 5} // 如果花括号当中给定的元素个数少于数组长度, 将使用零值填充fmt.Println(q)fmt.Println(p)t := [...]int{1, 2, 3} // 可以使用 ... 省略长度fmt.Println(t)
}
如果在数组长度位置出现的是...,则表示数组的长度根据初始值的个数来计算,...只能用于声明阶段。
数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是不同的数组类型。
我们不难发现,数组、slice、map 和 结构体字面值的写法很相似。上面的形式是直接提供顺序初始化值序列,但是也可以指定一个索引和对应值列表的方式来初始化:
package mainimport "fmt"func main() {type Currency intconst (USD Currency = iotaEURGBPRMB)symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"}fmt.Println(symbol)
}
未指定的元素将用零值初始化,例如:
r := [...]int{99: -1} // 99 个 0 和 1 个 -1
类型相同的数组可以相互比较,完全相等时,==为 true。
4.2 Slice
Slice 代表变长的序列,序列中每个元素都有相同的类型。一个 slice 一般写作 []T,其中 T 代表 slice 种元素的类型;slice 的语法和数组很像,一个明显的区别是 slice 没有固定的长度。
数组和 slice 的关系紧密,一个 slice 是一个轻量级的数据结构,提供了访问数组子序列(或全部数组)的能力,并且 slice 的底层确实引用了一个数组对象(一说 slice 是数组对象的视图,当 slice 显式地和数组绑定时,修改 slice 也将修改数组)。
一个 slice 由三部分构成,分别是:指针、长度和容量。
- 指针指向第一个 slice 元素对应地底层元素的地址,要注意的是 slice 的第一个元素未必是数组的第一个元素,因为 slice 可能是数组局部的视图;
- 长度对应 slice 种元素的数目,长度不能超过容量;
- 容量一般是从 slice 的开始位置到底层数据的结束位置。
内置的 cap 和 len 函数分别返回 slice 的长度和容量,但是要注意它们不是 slice 的方法。
多个 slice 之间可以共享底层数据,引用的数组部分区间可能重叠:

如果切片操作超出cap(s)的上限将导致一个 panic 异常,但超出len(s)意味着拓展了 slice(这里指的是切片操作仍然在底层数组的上限范围内的时候,如果切片操作超出了底层数组的上限,将会导致 panic 异常)。
和数组不同的是,slice 之间不能比较,因此不能使用==操作符来判断 slice 是否含有全部相等的元素,slice 唯一合法的比较操作是和 nil 进行比较。一个零值的 slice 等于 nil。一个 nil 值的 slice 并没有底层数组。nil 值的 slice 长度和容量都是 0(但是也有非 nil 的 slice 长度和容量为 0,例如[]int{}或make([]int, 3)[3:])。
内置的 make 函数可以创建一个指定元素类型、长度和容量的 slice:
make([]T, len) // cap 可以省略, 此时 len 和 cap 相等
make([]T, len, cap)
4.2.1 append 函数
内置的 append 函数用于向 slice 追加元素:
package mainimport "fmt"func main() {var runes []runefor _, r := range "Happy New Year" {runes = append(runes, r)}fmt.Printf("%q\n", runes)
}// OUTPUT
['H' 'a' 'p' 'p' 'y' ' ' 'N' 'e' 'w' ' ' 'Y' 'e' 'a' 'r']
注意,append 仍然是内置的函数,而非 slice 的方法。append 函数对于理解 slice 底层是如何工作的非常重要。当调用 append 追加元素时,必须先检测 slice 底层数组是否有足够的容量来保存新添加的元素。如果空间足够,直接拓展 slice,并返回 slice。如果没有足够的增长空间,append 则先分配一个足够大的 slice 同于保存新的结果,先将输入的 slice 复制到新空间,再追加元素。(与 C++ 当中 vector 的容量扩张策略类似,首先检测是否超过 capacity,如果超过了,则将 capacity 扩大两倍)
4.2.2 Slice 内存技巧
一个 slice 可以用于模拟 stack(实际上 Golang 没有内置的 stack 和 queue,但可以直接使用 slice 来模拟)。
可以使用 slice 模拟一个 stack,最初给定的空 slice 对应一个空的 stack,可以使用 append 将元素入栈:
stack = append(stack, v)
使用 slice 切片操作返回顶部元素:
top := stack[len(stack) - 1]
收缩 stack 完成出栈:
stack = stack[:len(stack)-1]
一个完整的代码片段如下:
package mainimport "fmt"func main() {stack := make([]int, 0)for i := 0; i < 10; i++ {stack = append(stack, i)}var top intfor i := 0; i < 10; i++ {top = stack[len(stack)-1]stack = stack[:len(stack)-1]fmt.Println(top)}
}
4.3 Map
Map (哈希表)是一个无序的 key/value 对的集合,其中所有的 key 都是不同的。通过 key 可以在常数时间内检索、更新或删除对应的 value。
在 Golang 当中,一个 map 就是一个哈希表的引用,map 类型可以写为 map[K]V,其中 K 和 V 分别对应 Key 和 Value。和其它语言类似,map 中所有的 key 都有相同的类型,所有 vakue 也有着相同的类型,但 key 和 value 之间可以是不同类型。其中 K 对应的 key 必须是支持==比较运算符的数据类型(所以 map 可以通过测试 key 是否相等来判断是否存在,因为相同的原因,使用浮点数作为 key 是一个糟糕的想法)。
与 slice 类似,可以使用内建的 make 创造一个 map:
ages := make(map[string]int)
// OR
ages := map[string]int{}
// 实际上可以使用相同的策略创建一个空的 slice
names := []string{}
可以用 map 字面值的语法创建 map,同时还可以指定一些最初的 key/value:
ages := map[string]int {"alice": 31,"charlie": 34,
}
可以使用下标访问 map 当中 key 的 value:
ages["alice"] = 32
fmt.Println(ages["alice"])
可以使用内置的 delete 删除元素:
delete(ages, "alice")
所有这些操作都是安全的,如果访问到一个不存在的 key,将会直接放回 value 类型对应的零值。此外,+=、++这些操作都可以应用在 map 上。
但由于 map 中的元素并不是一个变量,因此我们不能对 map 中的元素进行取地址操作。禁止对 map 取地址的原因是 map 可能随着元素数量的增长而重新分配个更大的内存空间,从而导致之前的地址失效。
遍历 map 当中的元素可以采用 range 风格的方法:
for name, age := range ages {fmt.Println("%s\t%d\n", name, age)
}
map 的迭代顺序不确定,并且不同的哈希函数可能导致不同的遍历顺序。如果要按顺序遍历 key/value 对,必须显式地对 key 进行排序:
import "sort"var names []string
for name := range ages {names.append(names, name)
}
sort.Strings(names)
for _, name := range names {fmt.Printf("%s\t%d\n", name, ages[name])
}
和 slice 一样,map 之间不能进行相等比较。唯一的例外是和 nil 比较。要判断两个 map 是否含有相同的 key 和 value,比如通过循环实现。
Golang 并没有 set 的实现,但 map 中的 key 也是不同的,可以用 map 实现类似 set 的效果。
package mainimport ("bufio""fmt""os"
)func main() {seen := make(map[string]bool)input := bufio.NewScanner(os.Stdin)for input.Scan() {line := input.Text()if !seen[line] {seen[line] = truefmt.Println(line)}}if err := input.Err(); err != nil {fmt.Fprintf(os.Stderr, "dedup: %v\n", err)os.Exit(1)}
}
有时候我们需要 map 或 set 的 key 是一个 slice 类型,但是由于 map 的 key 必须支持 == 操作,slice 并不满足这个条件。但我们可以通过两步绕过这个限制。首先,定义一个辅助函数 k,将 slice 转为 map 对应地 string 类型的 key,确保只有 x 和 y 相等时k(x) == k(y)成立。然后创建一个 key 为 string 类型的 map,在每次对 map 操作时先用 k 辅助函数将 slice 转为 string 类型。
使用同样的技术可以处理任何不可比较的 key 类型,而不仅仅是 slice 类型。
4.4 结构体
结构体是一种聚合的数据类型,由零个或多个任意类型的值聚合而成,每个值称为成员。
下面两个语句声明了一个叫 Employee 的结构体类型,且声明了一个 Employee 类型的变量 dilbert:
type Employee struct {ID intName stringAddress stringDoB time.TimePosition stringSalary intManageID int
}var dilbert Employee
dilbert 结构体变量的成员可以通过点操作符访问。比如dilbert.Name和dilbert.DoB。由于 dilbert 是一个变量,其所有成员都是变量,可以直接对每个成员赋值:
dilbert.Salary = 5000
或是取地址,然后通过指针访问:
position := &dilbert.Position
*position = "Senior " + *position
上述代码可运行的片段如下:
package mainimport ("fmt""time"
)type Employee struct {ID intName stringAddress stringDoB time.TimePosition stringSalary intManageID int
}func main() {var dilbert Employeedilbert.Salary = 5000dilbert.Position = "Dalian"position := &dilbert.Position*position = "Senior " + *positionfmt.Println(dilbert)
}
点操作符也可以和指向结构体的指针一起工作:
var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.Position += " (proactive team player)"// 等价于
(*employeeOfTheMonth).Position += " (proactive team player)"
(意思是,可以直接对指向结构体的指针使用点操作符,并对指针指向的结构体的成员进行修改)
下面的 EmployeeByID 函数将根据给定的员工 ID 返回对应的员工信息结构体的指针。可以直接对函数使用点操作符来访问结构体当中的成员:
func EmployeeByID(id int) *Employee {/* ... ... ... */}
id := dilbert.ID
EmployeeByID(id).Salary = 0
后面的语句通过 EmployeeByID 返回的结构体指针更新了 Employee 结构体的成员。如果将函数的返回值从指针改为值,编译将不能通过。
在声明结构体时,通常应该一行对应一个成员,不过如果向量的成员类型相同,可以它们合并:
type Employee struct {ID intName, Address, Position stringDoB time.TimeSalary intManageID int
}
结构体成员的输入顺序有重要的意义,如果在声明时成员名称出现的顺序不同,对应的将是不同的结构体。
如果结构体成员名字是以大写字母开头的,那么该成员就是导出的;这是Go语言导出规则决定的。一个结构体可能同时包含导出和未导出的成员。
4.4.1 结构体字面值
结构体值可以用结构体的字面值表示:
type Point struct { X, Y int }
var p Point = Point{1, 2}
// OR
p := Point{1, 2}
另一种更常见的写法是,以成员名字和对应的值来初始化,可以包含部分或全部的成员:
anim := gif.GIF{LoopCount: nframes}
两种形式不能混用,并且我们不能在另一个包中用第一种赋值来初始化结构体中未导出的成员。
结构体可以作为函数的返回值:
func Scale(p Point, factor int) Point {return Point{p.X * factor, p.Y * factor}
}
fmt.Println(Scale(Point{1, 2}, 5))
考虑效率的话,传输较大的结构体应该使用指针。
4.4.2 结构体比较
如果结构体当中全部成员都是可比较的,那么结构体也是可比较的。可比较的结构体和其它可比较的类型一样,可以用于 map 和 key 类型。
4.4.3 结构体嵌入和匿名成员
一个例子如下:
type Circle struct {X, Y, Radius int
}
type Wheel struct {X, Y, Radius, Spokes int
}
可以将相同的属性独立出来:
type Point struct {X, Y int
}
type Circle struct {Center PointRadius int
}
type Wheel struct {Circle CircleSpokes int
}
Golang 的一个特性允许我们只声明一个成员对应的数据类型而不指明成员的名字,这样的成员称为匿名成员。匿名成员的数据类型必须是命名的类型或是指向一个命名的类型的指针。一个例子如下:
type Circle struct {PointRadius int
}
type Wheel struct {CircleSpokes int
}
我们称类型 Point 嵌入到了 Circle 结构体中,同理 Circle 嵌入到了 Wheel 当中。
得益于匿名嵌入的特性,我们可以直接访问叶子属性而不给出完整路径:
var w Wheel
w.X = 8 // 等价于 w.Circle.Point.X = 8
w.Y = 8 // 等价于 w.Circle.Point.Y = 8
w.Radius = 5 // 等价于 w.Circle.Radius = 5
w.Spokes = 20
不幸的是,结构体字面值没有简短表示匿名成员的语法,因此下面的语句不能编译通过:
w = Wheel{8, 8, 5, 20} // compile error
w = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20} // compile error
我们只能用以下两种语法:
w = Wheel{Circle{Point{8, 8}, 5}, 20}
// 等价于
w = Wheel {Circle: Circle{Point: Point{X:8, Y:8},Radius: 5,},Spokes: 20,
}相关文章:
【Go语言圣经】第四节:复合数据类型
第四章:复合数据类型 本节主要讨论四种类型——数组、slice、map和结构体。 数组和结构体都是有固定内存大小的数据结构。相比之下,slice 和 map 则是动态的数据结构,它们可以根据需要动态增长。 4.1 数组 数组是一个定长的由特定类型元素…...
【Vite + Vue + Ts 项目三个 tsconfig 文件】
Vite Vue Ts 项目三个 tsconfig 文件 为什么 Vite Vue Ts 项目会有三个 tsconfig 文件?首先我们先了解什么是 tsconfig.json ? 为什么 Vite Vue Ts 项目会有三个 tsconfig 文件? 在使用 Vite 创建 vue-ts 模板的项目时,会发现除了 ts…...
论文和代码解读:RF-Inversion 图像/视频编辑技术
Diffusion Models专栏文章汇总:入门与实战 前言:Rectified Flow的反演和DDIM这些不太一样,上一篇博客中介绍了腾讯提出的一种方法《基于Rectified Flow FLUX的图像编辑方法 RF-Solver》,主要就是用泰勒展开和一阶导数近似来分解反演公式。这篇博客介绍谷歌提出的方法RF-Inv…...
完美还是完成?把握好度,辨证看待
完美还是完成? 如果说之前这个答案有争议,那么现在,答案毋庸置疑 ■为什么完美大于完成 ●时间成本: 做事不仅要考虑结果,还要考虑时间和精力,要说十年磨一剑的确质量更好,但是现实没有那么多…...
Many Whelps! Handle It! (10 player) Many Whelps! Handle It! (25 player)
http://db.nfuwow.com/80/?achievement4403 http://db.nfuwow.com/80/?achievement4406 最少扣你50DKP! 第二阶段 当奥妮克希亚升空后,在10秒内引出50只奥妮克希亚雏龙,随后击败奥妮克希亚。 World of Warcraft [CLASSIC][80猎人][Grandel][最少扣你5…...
3.4 Go函数作用域(标识符)
只有精准分析每个标识符的作用域范围,才能编写出优质、健壮的代码,避免逻辑错误的发生。 作用域标识符 简单来说,作用域指的是标识符可以起作用的范围,即其可见范围。将标识符的可见性限制在一定范围内,这个范围就是…...
【React】PureComponent 和 Component 的区别
前言 在 React 中,PureComponent 和 Component 都是用于创建组件的基类,但它们有一个主要的区别:PureComponent 会给类组件默认加一个shouldComponentUpdate周期函数。在此周期函数中,它对props 和 state (新老的属性/状态)会做一…...
MongoDb user自定义 role 添加 action(collStats, EstimateDocumentCount)
使用 mongosh cd mongsh_bin_path mongosh “mongodb://user:passip:port/db”这样就直接进入了对应的db 直接输入: 这样 role “read_only_role" 就获得了3个 action, 分别是 查询,列举集合,集合元数据查询 P.S: 如果没有 …...
fastadmin中require-form.js的data-favisible控制显示隐藏
只要在任意元素上添加data-favisible属性就可以轻松的控制显示隐藏了 其中reportype是php传到前端的一个变量??? <div class"form-group" data-favisible"reportype6"><label class"control-label col-xs-12 col-sm-2">{:__(Ove_…...
Day51:type()函数
在 Python 中,type() 是一个内置函数,用于返回对象的类型。它可以用于检查变量的类型,也可以用于动态创建新的类型。今天,我们将深入了解 type() 函数的使用方法。 1. 使用 type() 获取变量的类型 最常见的使用方式是将一个对象…...
vue 无法 局域网内访问
资料 Vue项目设置可以局域网访问_vue.js_脚本之家 过程 上午,前端vue服务能够在局域网内访问, 下午就不行了,但是后端服务能够正常访问,本机也能正常访问ip:端口号 前端服务 判定不是下面的问题: 同一…...
【llm对话系统】大模型 Llama 源码分析之 LoRA 微调
1. 引言 微调 (Fine-tuning) 是将预训练大模型 (LLM) 应用于下游任务的常用方法。然而,直接微调大模型的所有参数通常需要大量的计算资源和内存。LoRA (Low-Rank Adaptation) 是一种高效的微调方法,它通过引入少量可训练参数,固定预训练模型…...
蓝桥杯刷题DAY2:二维前缀和 一维前缀和 差分数组
闪耀的灯光 📌 题目描述 蓝桥公园是一个适合夜间散步的好地方,公园可以被视为由 n m 个矩形区域构成。每个区域都有一盏灯,初始亮度为 a[i][j]。 小蓝可以选择一个大的矩形区域,并按下开关一次,这将使得该区域内每盏…...
网件r7000刷回原厂固件合集测评
《网件R7000路由器刷回原厂固件详解》 网件R7000是一款备受赞誉的高性能无线路由器,其强大的性能和可定制性吸引了许多高级用户。然而,有时候用户可能会尝试第三方固件以提升功能或优化网络性能,但这也可能导致一些问题,如系统不…...
算法随笔_35: 每日温度
上一篇:算法随笔_34: 最后一个单词的长度-CSDN博客 题目描述如下: 给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升…...
C++初阶 -- 手撕string类(模拟实现string类)
目录 一、string类的成员变量 二、构造函数 2.1 无参版本 2.2 有参版本 2.3 缺省值版本 三、析构函数 四、拷贝构造函数 五、c_str函数 六、operator重载 七、size函数 八、迭代器iterator 8.1 正常版本 8.2 const版本 九、operator[] 9.1 正常版本 9.2 const版…...
【Unity3D】实现2D角色/怪物死亡消散粒子效果
核心:这是一个Unity粒子系统自带的一种功能,可将粒子生成控制在一个Texture图片网格范围内,并且粒子颜色会自动采样图片的像素点颜色,之后则是粒子编辑出消散效果。 Particle System1物体(爆发式随机速度扩散10000个粒…...
构建一个数据分析Agent:提升分析效率的实践
在上一篇文章中,我们讨论了如何构建一个智能客服Agent。今天,我想分享另一个实际项目:如何构建一个数据分析Agent。这个项目源于我们一个金融客户的真实需求 - 提升数据分析效率,加快决策速度。 从分析师的痛点说起 记得和分析师团队交流时的场景: 小张ÿ…...
85.[1] 攻防世界 WEB easyphp
进入靶场 属于代码审计 <?php // 高亮显示当前 PHP 文件的源代码,常用于调试或展示代码 highlight_file(__FILE__);// 初始化两个标志变量,用于后续条件判断 $key1 0; $key2 0;// 从 GET 请求中获取参数 a 和 b $a $_GET[a]; $b $_GET[b];// 检…...
嵌入式硬件篇---CPUGPUTPU
文章目录 第一部分:处理器CPU(中央处理器)1.通用性2.核心数3.缓存4.指令集5.功耗和发热 GPU(图形处理器)1.并行处理2.核心数量3.内存带宽4.专门的应用 TPU(张量处理单元)1.为深度学习定制2.低精…...
pytorch图神经网络处理图结构数据
人工智能例子汇总:AI常见的算法和例子-CSDN博客 图神经网络(Graph Neural Networks,GNNs)是一类能够处理图结构数据的深度学习模型。图结构数据由节点(vertices)和边(edges)组成&a…...
海外问卷调查,最常用到的渠道查有什么特殊之处
市场调研,包含市场调查和市场研究两个步骤,是企业和机构根据经营方向而做出的决策问题,最终通过海外问卷调查中的渠道查,来系统地设计、收集、记录、整理、分析、研究市场反馈的工作流程。 市场调研的工作流程包括:确…...
【Uniapp-Vue3】解决uni-popup弹窗在安全区显示透明问题
我们在使用uni-popup时,如果想要给弹出内容添加一个背景颜色,我们会发现在安全区域是不显示该背景颜色的。 首先根据如下的目录结构找到uni-popup.vue文件 在该文件中找到bottom配置,将红箭头所指代码注释掉 下面的安全区域就没有了ÿ…...
什么是麦克斯韦方程
飞书链接,格式更好,⭐⭐⭐:长尾 - 什么是麦克斯韦方程 基于作者的内容,做了一些扩展(问了 DeepSeek) 最美的公式:你也能懂的麦克斯韦方程组(积分篇) 04通量的引入 用一个…...
webrtc peerconnection_client peerconnection_server 连接失败问题解决 win10 win11
0 常见问题 (1) webrtc peerconnection_client 连接 peerconnection_server 无连接列表 (2)连接导致崩溃debug状态下因为这个断言 RTC_DCHECK_RUN_ON(&capture_checker_); 1 在 peerconnection\client\main.cc 当中 定义类 class CustomSock…...
项目练习:重写若依后端报错cannot be cast to com.xxx.model.LoginUser
文章目录 一、情景说明二、解决办法 一、情景说明 在重写若依后端服务的过程中 使用了Redis存放LoginUser对象数据 那么,有存就有取 在取值的时候,报错 二、解决办法 方法1、在TokenService中修改如下 getLoginUser 方法中:LoginUser u…...
2025年2月1日(Keep calm and code Python)
“Keep calm and code Python” 的意思是 “保持冷静,编写 Python 代码”。 这句话来源于 “Keep Calm and Carry On”(保持冷静,继续前进),这是二战时期英国政府的宣传口号。后来,这种句式被广泛模仿&…...
核心集:DeepCore: A Comprehensive Library for CoresetSelection in Deep Learning
目录 一、TL;DR 二、为什么研究核心集? 三、问题定义和如何做 3.1 问题定义 3.2 业界方法 3.2.1 基于几何的方法 3.2.2 基于不确定性的方法 3.2.3 基于误差/损失的方法 3.2.5 GraNd 和 EL2N 分数 3.2.6 重要性采样 3.2.7 基于决策边界的办法 …...
Hot100之矩阵
73矩阵置零 题目 思路解析 收集0位置所在的行和列 然后该行全部初始化为0 该列全部初始化为0 代码 class Solution {public void setZeroes(int[][] matrix) {int m matrix.length;int n matrix[0].length;List<Integer> list1 new ArrayList<>();List<…...
可视化相机pose colmap形式的相机内参外参
目录 内参外参转换 可视化相机pose colmap形式的相机内参外参 内参外参转换 def visualize_cameras(cameras, images):fig plt.figure()ax fig.add_subplot(111, projection3d)for image_id, image_data in images.items():qvec image_data[qvec]tvec image_data[tvec]#…...
