【Go语言圣经】第四节:复合数据类型

news2025/2/2 14:33:43

第四章:复合数据类型

本节主要讨论四种类型——数组、slice、map和结构体。

数组和结构体都是有固定内存大小的数据结构。相比之下,slice 和 map 则是动态的数据结构,它们可以根据需要动态增长。

4.1 数组

数组是一个定长的由特定类型元素构成的序列。由于数组定长,因此 Golang 当中很少直接使用数组,而是使用 slice。一个使用数组的例子如下:

package main

import "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 main

import "fmt"

func main() {
	type Currency int
	const (
		USD Currency = iota
		EUR
		GBP
		RMB
	)
	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 main

import "fmt"

func main() {
	var runes []rune
	for _, 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 main

import "fmt"

func main() {
	stack := make([]int, 0)
	for i := 0; i < 10; i++ {
		stack = append(stack, i)
	}
	var top int
	for 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 main

import (
	"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] = true
			fmt.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       int
	Name     string
	Address  string
	DoB      time.Time
	Position string
	Salary   int
	ManageID int
}

var dilbert Employee

dilbert 结构体变量的成员可以通过点操作符访问。比如dilbert.Namedilbert.DoB。由于 dilbert 是一个变量,其所有成员都是变量,可以直接对每个成员赋值:

dilbert.Salary = 5000

或是取地址,然后通过指针访问:

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

上述代码可运行的片段如下:

package main

import (
	"fmt"
	"time"
)

type Employee struct {
	ID       int
	Name     string
	Address  string
	DoB      time.Time
	Position string
	Salary   int
	ManageID int
}

func main() {
	var dilbert Employee
	dilbert.Salary = 5000
	dilbert.Position = "Dalian"
	position := &dilbert.Position
	*position = "Senior " + *position
	fmt.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                      int
	Name, Address, Position string
	DoB                     time.Time
	Salary                  int
	ManageID                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 Point
	Radius int
}
type Wheel struct {
	Circle Circle
	Spokes int
}

Golang 的一个特性允许我们只声明一个成员对应的数据类型而不指明成员的名字,这样的成员称为匿名成员。匿名成员的数据类型必须是命名的类型或是指向一个命名的类型的指针。一个例子如下:

type Circle struct {
	Point
	Radius int
}
type Wheel struct {
	Circle
	Spokes 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,
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2289817.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

完美还是完成?把握好度,辨证看待

完美还是完成&#xff1f; 如果说之前这个答案有争议&#xff0c;那么现在&#xff0c;答案毋庸置疑 ■为什么完美大于完成 ●时间成本&#xff1a; 做事不仅要考虑结果&#xff0c;还要考虑时间和精力&#xff0c;要说十年磨一剑的确质量更好&#xff0c;但是现实没有那么多…

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! 第二阶段 当奥妮克希亚升空后&#xff0c;在10秒内引出50只奥妮克希亚雏龙&#xff0c;随后击败奥妮克希亚。 World of Warcraft [CLASSIC][80猎人][Grandel][最少扣你5…

【React】PureComponent 和 Component 的区别

前言 在 React 中&#xff0c;PureComponent 和 Component 都是用于创建组件的基类&#xff0c;但它们有一个主要的区别&#xff1a;PureComponent 会给类组件默认加一个shouldComponentUpdate周期函数。在此周期函数中&#xff0c;它对props 和 state (新老的属性/状态)会做一…

MongoDb user自定义 role 添加 action(collStats, EstimateDocumentCount)

使用 mongosh cd mongsh_bin_path mongosh “mongodb://user:passip:port/db”这样就直接进入了对应的db 直接输入&#xff1a; 这样 role “read_only_role" 就获得了3个 action&#xff0c; 分别是 查询&#xff0c;列举集合&#xff0c;集合元数据查询 P.S: 如果没有 …

蓝桥杯刷题DAY2:二维前缀和 一维前缀和 差分数组

闪耀的灯光 &#x1f4cc; 题目描述 蓝桥公园是一个适合夜间散步的好地方&#xff0c;公园可以被视为由 n m 个矩形区域构成。每个区域都有一盏灯&#xff0c;初始亮度为 a[i][j]。 小蓝可以选择一个大的矩形区域&#xff0c;并按下开关一次&#xff0c;这将使得该区域内每盏…

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角色/怪物死亡消散粒子效果

核心&#xff1a;这是一个Unity粒子系统自带的一种功能&#xff0c;可将粒子生成控制在一个Texture图片网格范围内&#xff0c;并且粒子颜色会自动采样图片的像素点颜色&#xff0c;之后则是粒子编辑出消散效果。 Particle System1物体&#xff08;爆发式随机速度扩散10000个粒…

85.[1] 攻防世界 WEB easyphp

进入靶场 属于代码审计 <?php // 高亮显示当前 PHP 文件的源代码&#xff0c;常用于调试或展示代码 highlight_file(__FILE__);// 初始化两个标志变量&#xff0c;用于后续条件判断 $key1 0; $key2 0;// 从 GET 请求中获取参数 a 和 b $a $_GET[a]; $b $_GET[b];// 检…

pytorch图神经网络处理图结构数据

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 图神经网络&#xff08;Graph Neural Networks&#xff0c;GNNs&#xff09;是一类能够处理图结构数据的深度学习模型。图结构数据由节点&#xff08;vertices&#xff09;和边&#xff08;edges&#xff09;组成&a…

海外问卷调查,最常用到的渠道查有什么特殊之处

市场调研&#xff0c;包含市场调查和市场研究两个步骤&#xff0c;是企业和机构根据经营方向而做出的决策问题&#xff0c;最终通过海外问卷调查中的渠道查&#xff0c;来系统地设计、收集、记录、整理、分析、研究市场反馈的工作流程。 市场调研的工作流程包括&#xff1a;确…

【Uniapp-Vue3】解决uni-popup弹窗在安全区显示透明问题

我们在使用uni-popup时&#xff0c;如果想要给弹出内容添加一个背景颜色&#xff0c;我们会发现在安全区域是不显示该背景颜色的。 首先根据如下的目录结构找到uni-popup.vue文件 在该文件中找到bottom配置&#xff0c;将红箭头所指代码注释掉 下面的安全区域就没有了&#xff…

项目练习:重写若依后端报错cannot be cast to com.xxx.model.LoginUser

文章目录 一、情景说明二、解决办法 一、情景说明 在重写若依后端服务的过程中 使用了Redis存放LoginUser对象数据 那么&#xff0c;有存就有取 在取值的时候&#xff0c;报错 二、解决办法 方法1、在TokenService中修改如下 getLoginUser 方法中&#xff1a;LoginUser u…

核心集:DeepCore: A Comprehensive Library for CoresetSelection in Deep Learning

目录 一、TL&#xff1b;DR 二、为什么研究核心集&#xff1f; 三、问题定义和如何做 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]#…

数据库内存与Buffer Pool

数据库内存与Buffer Pool 文章目录 数据库内存与Buffer Pool一&#xff1a;MySQL内存结构1&#xff1a;MySQL工作组件2&#xff1a;工作线程的本地内存3&#xff1a;共享内存区域4&#xff1a;存储引擎缓冲区 二&#xff1a;InnoDB的核心&#xff1a;Buffer Pool1&#xff1a;数…

程序员学英文之At the Airport Customs

Dialogue-1 Making Airline Reservation预定机票 My cousin works for Xiamen Airlines. 我表哥在厦航上班。I’d like to book an air ticket. 我想预定一张机票。Don’t judge a book by its cover. 不要以貌取人。I’d like to book / re-serve a table for 10. 我想预定一…

Redis代金卷(优惠卷)秒杀案例-单应用版

优惠卷表:优惠卷基本信息,优惠金额,使用规则 包含普通优惠卷和特价优惠卷(秒杀卷) 优惠卷的库存表:优惠卷的库存,开始抢购时间,结束抢购时间.只有特价优惠卷(秒杀卷)才需要填写这些信息 优惠卷订单表 卷的表里已经有一条普通优惠卷记录 下面首先新增一条秒杀优惠卷记录 { &quo…

51单片机 01 LED

一、点亮一个LED 在STC-ISP中单片机型号选择 STC89C52RC/LE52RC&#xff1b;如果没有找到hex文件&#xff08;在objects文件夹下&#xff09;&#xff0c;在keil中options for target-output- 勾选 create hex file。 如果要修改编程 &#xff1a;重新编译-下载/编程-单片机重…

MusicFree-开源的第三方音乐在线播放和下载工具, 支持歌单导入[对标落雪音乐]

MusicFree 链接&#xff1a;https://pan.xunlei.com/s/VOI0RrVLTTWE9kkpt0U7ofGBA1?pwd4ei6#