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

GO语言学习笔记(与Java的比较学习)(四)

结构体

一个结构体(struct)就是一组字段(field)。

package main
​
import "fmt"
​
type Vertex struct {X intY int
}
​
func main() {fmt.Println(Vertex{1, 2})
}

结构体中的字段用 . 访问

package main
​
import "fmt"
​
type Vertex struct {X intY int
}
​
func main() {v := Vertex{1, 2}v.X = 4fmt.Println(v.X)
}

结构体指针

结构体字段可以通过结构体指针来访问。

如果我们有一个指向结构体的指针 p,那么可以通过 (*p).X 来访问其字段 X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 p.X 就可以。

package main
​
import "fmt"
​
type Vertex struct {X intY int
}
​
func main() {v := Vertex{1, 2}p := &vp.X = 1e9fmt.Println(v)
}

结构体文法

结构体文法通过直接列出字段的值来新分配一个结构体。

使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)

特殊的前缀 & 返回一个指向结构体的指针。

package main
​
import "fmt"
​
type Vertex struct {X, Y int
}
​
var (v1 = Vertex{1, 2}  // 创建一个 Vertex 类型的结构体v2 = Vertex{X: 1}  // Y:0 被隐式地赋予v3 = Vertex{}      // X:0 Y:0p  = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)
​
func main() {fmt.Println(v1, p, v2, v3)
}

数组与切片

数组声明和初始化

数组是具有相同 唯一类型 的一组以编号且长度固定的数据项序列。

注意事项

  • 如果我们想让数组元素类型为任意类型的话可以使用空接口作为类型,当使用值时我们必须先做一个类型判断。

  • 数组长度最大为 2Gb

声明格式:

var identifier [len]type

Go 语言中的数组是一种 值类型(不像 C/C++ 中是指向首元素的指针),所以可以通过 new() 来创建: var arr1 = new([5]int)。

提出问题:这种方式和 var arr2 [5]int 的区别是什么呢?arr1 的类型是 *[5]int,而 arr2 的类型是 [5]int

数组常量

如果数组值已经提前知道了,那么可以通过 数组常量 的方法来初始化数组,而不用依次使用 []= 方法(所有的组成元素都有相同的常量语法)。

package main
import "fmt"
​
func main() {// var arrAge = [5]int{18, 20, 15, 22, 16}// var arrLazy = [...]int{5, 6, 7, 8, 22}// var arrLazy = []int{5, 6, 7, 8, 22}var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}// var arrKeyValue = []string{3: "Chris", 4: "Ron"}
​for i:=0; i < len(arrKeyValue); i++ {fmt.Printf("Person at %d is %s\n", i, arrKeyValue[i])}
}

多维数组

数组通常是一维的,但是可以用来组装成多维数组,例如:[3][5]int[2][2][2]float64

内部数组总是长度相同的。Go 语言的多维数组是矩形式的

将数组传递给函数

把一个大数组传递给函数会消耗很多内存。有两种方法可以避免这种现象:

  • 传递数组的指针

  • 使用数组的切片

package main
import "fmt"
​
func main() {array := [3]float64{7.0, 8.5, 9.1}x := Sum(&array) // Note the explicit address-of operator// to pass a pointer to the arrayfmt.Printf("The sum of the array is: %f", x)
}
​
func Sum(a *[3]float64) (sum float64) {for _, v := range *a { // derefencing *a to get back to the array is not necessary!sum += v}return
}

切片

概念

  • 切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型)。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口。

  • 切片下界的默认值为 0,上界则是该切片的长度。

  • 切片是可索引的,并且可以由 len() 函数获取长度。

  • 和数组不同的是,切片的长度可以在运行时修改,最小为 0 最大为相关数组的长度:切片是一个 长度可变的数组

  • 切片提供了计算容量的函数 cap() 可以测量切片最长可以达到多少:它等于切片从第一个元素开始,到相关数组末尾的元素个数。

  • 切片的长度永远不会超过它的容量,所以对于 切片 s 来说该不等式永远成立:0 <= len(s) <= cap(s)

  • 多个切片如果表示同一个数组的片段,它们可以共享数据;因此一个切片和相关数组的其他切片是共享存储的,相反,不同的数组总是代表不同的存储。数组实际上是切片的构建块

  • 切片 s 的长度和容量可通过表达式 len(s)cap(s) 来获取。

  • 切片的零值是 nil。nil 切片的长度和容量为 0 且没有底层数组。

package main
​
import "fmt"
​
func main() {q := []int{2, 3, 5, 7, 11, 13}fmt.Println(q)
​r := []bool{true, false, true, true, false, true}fmt.Println(r)
​s := []struct {i intb bool}{{2, true},{3, false},{5, true},{7, true},{11, false},{13, true},}fmt.Println(s)
}

声明切片的格式是var identifier []type(不需要说明长度)。

一个切片在未初始化之前默认为 nil,长度为 0。

切片的初始化格式是:var slice1 []type = arr1[start:end]。(这表示 slice1 是由数组 arr1 从 start 索引到 end-1 索引之间的元素构成的子集(切分数组,start:end 被称为 slice 表达式)。所以 slice1[0] 就等于 arr1[start]。)

注意:

  • 如果某个人写:var slice1 []type = arr1[:] 那么 slice1 就等于完整的 arr1 数组(所以这种表示方式是 arr1[0:len(arr1)] 的一种缩写)。另外一种表述方式是:slice1 = &arr1。

  • 如果 s2 是一个 slice,你可以将 s2 向后移动一位 s2 = s2[1:],但是末尾没有移动。切片只能向后移动,s2 = s2[-1:] 会导致编译错误。切片不能被重新分片以获取数组的前一个元素。

用 make () 创建一个切片

当相关数组还没有定义时,我们可以使用 make () 函数来创建一个切片 同时创建好相关数组:var slice1 []type = make([]type, len)。也可以简写为 slice1 := make([]type, len),这里 len 是数组的长度并且也是 slice 的初始长度。

new () 和 make () 的区别

看起来二者没有什么区别,都在堆上分配内存,但是它们的行为不同,适用于不同的类型。

  • new (T) 为每个新的类型 T 分配一片内存,初始化为 0 并且返回类型为 * T 的内存地址:这种方法 返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体;它相当于 &T{}。

  • make(T) 返回一个类型为 T 的初始值,它只适用于 3 种内建的引用类型:切片、map 和 channel

多维 切片

和数组一样,切片通常也是一维的,但是也可以由一维组合成高维。通过分片的分片(或者切片的数组),长度可以任意动态变化,所以 Go 语言的多维切片可以任意切分。而且,内层的切片必须单独分配(通过 make 函数)。

bytes 包

类型 []byte 的切片十分常见,Go 语言有一个 bytes 包专门用来解决这种类型的操作方法。

bytes 包和字符串包十分类似(参见第 4.7 节)。而且它还包含一个十分有用的类型 Buffer:

import "bytes"
​
type Buffer struct {...
}

Buffer 可以这样定义:

var buffer bytes.Buffer

或者使用 new 获得一个指针:

var r *bytes.Buffer = new(bytes.Buffer)

或者通过函数:func NewBuffer(buf []byte) *Buffer,创建一个 Buffer 对象并且用 buf 初始化好;NewBuffer 最好用在从 buf 读取的时候使用。

通过 buffer 串联字符串

类似于 Java 的 StringBuilder 类。

var buffer bytes.Buffer
for {if s, ok := getNextString(); ok { //method getNextString() not shown herebuffer.WriteString(s)} else {break}
}
fmt.Print(buffer.String(), "\n")

切片重组

我们已经知道切片创建的时候通常比相关数组小,例如:

slice1 := make([]type, start_length, capacity)

其中 start_length 作为切片初始长度而 capacity 作为相关数组的长度。

这么做的好处是我们的切片在达到容量上限后可以扩容。改变切片长度的过程称之为切片重组 reslicing,做法如下:

slice1 = slice1[0:end]

,其中 end 是新的末尾索引(即长度)。

切片的复制与追加

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。

package main
import "fmt"
​
func main() {sl_from := []int{1, 2, 3}sl_to := make([]int, 10)
​n := copy(sl_to, sl_from)fmt.Println(sl_to)fmt.Printf("Copied %d elements\n", n) // n == 3
​sl3 := []int{1, 2, 3}sl3 = append(sl3, 4, 5, 6)fmt.Println(sl3)
}

如果使用append函数超过了切片的容量会怎样?

答:

  • 当使用 append 函数向切片添加元素时,如果超过了切片的容量,切片将会重新分配更大的内存空间,并将原来的元素复制到新的内存空间中。这意味着切片的长度和容量会增加,并且原来的元素会被复制到新的内存位置上。

  • 具体来说,如果切片的容量不足以容纳新的元素,则 append 函数会创建一个新的底层数组,并将原来的元素复制到这个新数组中。然后,新的元素会被添加到新的底层数组中,并返回一个指向新数组的切片。

  • 这也意味着如果你有一个指向原切片的指针,在调用 append 后,原切片可能会指向一个不同的内存地址,因为底层数组已经改变了。

学习参考资料:

《Go 入门指南》 | Go 技术论坛 (learnku.com)

Go 语言之旅

http://www.lryc.cn/news/308658.html

相关文章:

  • 在实训云平台上配置云主机
  • 什么是隔离式栅极驱动器?
  • 蓝桥杯算法赛 第 6 场 小白入门赛 解题报告 | 珂学家 | 简单场 + 元宵节日快乐
  • 附加Numpy数组
  • 收银系统源码-智慧新零售,ERP进销存功能详解
  • STM32使用PB3, PB4引脚的注意事项
  • OSCP靶场--DVR4
  • 【嵌入式——QT】日期与定时器
  • 如何决定使用HashMap还是TreeMap?
  • 平台工程与安全
  • 智能咖啡厅助手:人形机器人 +融合大模型,行为驱动的智能咖啡厅机器人(机器人大模型与具身智能挑战赛)
  • js处理IOS虚拟键盘弹出后输入框被遮住
  • 脚手架工程使用ElementUI
  • 163邮箱SMTP端口号及服务器地址详细设置?
  • 【STM32】STM32学习笔记-独立看门狗和窗口看门狗(47)
  • 计算机网络——IPV4数字报
  • java抽象方法和抽象类
  • echarts鼠标向右/向左绘制实现放大/还原
  • Go编译DLL与SO
  • css浮动
  • 小程序怎么开发?怎么开发自己的小程序
  • Unity(第十八部)物理力学,碰撞,触发、关节和材质
  • 内网搭建mysql8.0并搭建主从复制详细教程!!!
  • MYSQL 解释器小记
  • 具身智能计算系统,机器人时代的 Android | 新程序员
  • win11开启IPV6并手动设置地址
  • WPF中如何设置自定义控件
  • 【Leetcode每日一题】二分查找 - 寻找旋转排序数组中的最小值(难度⭐⭐)(22)
  • QT C++实战:实现用户登录页面及多个界面跳转
  • 我的世界游戏服务器平台推荐哪里找?