GoLang笔记

本文详细介绍了Go语言中的Slice切片,包括声明、初始化和切片操作,以及单元测试中的打桩技巧。还涵盖了数组、map、结构体、函数、方法和接口等核心概念,深入探讨了它们的特性和使用方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Slice切片

slice是这样的结构:先创建一个有特定长度和数据类型的底层数组,然后从这个底层数组中选取一部分元素,返回这些元素组成的集合(或容器),并将slice指向集合中的第一个元素。换句话说,slice自身维护了一个指针属性,指向它底层数组中的某些元素的集合

声明和初始化

声明:

var a []int

初始化:

a := new([]int)
fmt.Println(*a)
b := make([]int, 0, 5)
fmt.Println(b)

当声明一个slice,但不做初始化的时候,这个slice就是一个nil slice,这个slice不会指向哪个底层数组。初始化后的slice有指向的底层数组,只不过指向的底层数组暂时是长度为0的空数组。

make()比new()函数多一些操作,new()函数只会进行内存分配并做默认的赋0初始化,而make()可以先为底层数组分配好内存,然后从这个底层数组中再额外生成一个slice并初始化。另外,make只能构建slice、map和channel这3种结构的数据对象,因为它们都指向底层数据结构,都需要先为底层数据结构分配好内存并初始化。

初始化时直接赋初值:

// 创建长度和容量都为4的slice,并初始化赋值
color_slice := []string{"red","blue","black","green"}

// 创建长度和容量为100的slice,并为第100个元素赋值为3
slice := []int{99:3}

slice能被访问的元素只有length范围内的元素,那些在length之外,但在capacity之内的元素暂时还不属于slice,只有在slice被扩展时(见下文append),capacity中的元素才被纳入length,才能被访问。

可以通过len()函数获取slice的长度,通过cap()函数获取slice的Capacity。

my_slice := make([]int,3,5)

fmt.Println(len(my_slice))  // 3
fmt.Println(cap(my_slice))  // 5
对slice切片

可以从slice中继续切片生成一个新的slice,这样能实现slice的缩减。

对slice切片的语法为:

SLICE[A:B]
SLICE[A:B:C]

其中A表示从SLICE的第几个元素开始切,B控制切片的长度(B-A),C控制切片的容量(C-A),如果没有给定C,则表示切到底层数组的最尾部

还有几种简化形式:

SLICE[A:]  // 从A切到最尾部
SLICE[:B]  // 从最开头切到B(不包含B)
SLICE[:]   // 从头切到尾,等价于复制整个SLICE
单元测试
打桩

mock一个函数

// json.NetworkCompute 为方法名
patches := gomonkey.ApplyFunc(json.NetworkCompute, func(a, b int) (int,error){
    return 2, nil
})
defer patches.Reset()

mock 一个方法

// CanalNetConfEx 是方法所在的结构体
// getPodID 是方法名
patches := gomonkey.ApplyFunc((*CanalNetConfEx).getPodID, func(_ *CanalNetConfEx) (string, error) {
	return "123", nil
})
defer  patches.Reset()
数组

数组长度固定,所以数组很少被使用。

初始化:

// 数组的长度必须是常量表达式
// 数组的长度是数组类型的一部分,所以 [3]int 和 [4]int 是两种数据类型
q := [3]int{1, 2, 3}
// 如果省略号出现在数组长度的位置,那么数组的长度由初始化数组的元素个数决定
p := [...]int{1, 2, 3, 4}
// 定义了一个长度为100的数组,q[99]为1,其余为int的默认值0
q := [...]int{99: 1}

如果一个数组的元素类型可比较,那么这个数组就可以用 == 和 != 比较,当两边元素完全相同时返回true。

数组在传入一个函数时,函数接受的是一个副本,而不是地址(与java不同)。

slice

slice是可变长度的序列,slice包含三个属性:指针、长度和容量,len和cap方法可以返回slice的长度和容量,指针直接指向底层数组,所以对slice中的元素修改可以改变底层数组的数据。

slice底层数组的元素是间接引用的,但是指针、长度和容量不是。

初始化:

p := [...]int{1, 2, 3, 4}
// s1指向 p[1]~p[2]
s1 := p[1:3]
s2 := p[1:]
s3 := p[:3]
s4 := p[:]
// 指针为nil,实际上没有初始化
var s []int
// 指针不为nil
s := []int{}
//创建一个长度和容量都为3的slice
p := make([]int, 3)
//创建一个长度为3容量为6的slice
p := make([]int, 3, 6)

slice无法直接用 == 和 != 做比较。slice唯一允许的比较操作是和nil做比较。

想检查一个slice是否为空,使用 len(s) == 0 ,而不是 s == nil ,因为 s != nil 的情况下,slice也有可能是空(指针指向了空数组)。

append函数可以用来将元素追加到slice的后面。

s = append(s, 1)

append时,如slice长度不够,但容量足够,则不会改变指针地址,而是在底层数组基础上做更改,所以会改变原数组的值。

如果容量不足,则新建一个底层数组,将元素复制到这个数组,并将指针指向新地址,然后就和原来的底层数组没关系了。

每次容量扩展时,默认扩展一倍。

为了从slice的中间移除一个元素,并保留剩余元素的顺序,可以使用函数copy来将高位索引的元素向前移动来覆盖被移除元素所在的位置:

copy(slice[i:], slice[i+1:])
map

键的类型,必须是可以通过 == 来进行比较的数据类型

初始化

ages := make(map[string]int)
ages := map[string]int{}
ages := map[string]int{
    "郭哥": 18,
    "马哥": 98,
}

移除元素

delete(ages, "郭哥")

map元素不是一个变量,不可以获取它的地址。

map不可比较,唯一合法的比较就是和nil做比较。

结构体
type Man struct {
	age int
	name string
}

//结构体方法
func (m Man) getName() string {
	return m.name
}

结构体的成员变量名称首字母大小写决定了这个变量是否可导出(可导出代表可以在别的包访问)。

结构体不可以拥有相同类型的结构体成员变量,但是可以拥有该结构体类型的指针。

初始化:

man1 := new(Man)
// 通过指定部分或全部成员变量的名称和值来初始化结构体变量:
man1 := Man{age : 1, name : "郭哥"}
man1 := &Man{age : 1, name : "郭哥"}

如果结构体的所有成员变量都可以比较,那么这个结构体就可以使用 == 或者 != 比较,并且可以作为map的键类型。

匿名成员:没有指定成员的名称,只指定成员的类型,是一种内嵌的结构体

type Student struct {
	Man
    *Animal
	school string
}

匿名成员初始化:

student1 := &student{
		Man:    Man{
			age:  1,
			name: "李雷",
		},
		school: "浙江农林大学",
}

访问匿名成员的成员变量不需要指定匿名成员的名称:

fmt.Println(student1.age)

同样的,访问匿名成员的方法不需要指定匿名成员的名称:

fmt.Println(student1.getName())

因为匿名成员以类型名称为自己的名字,所以不能在一个结构体里定义两个相同类型的匿名成员。同理,匿名成员的可导出性也取决于该类型名称首字母是否大写。

函数
func initEniNeutron(req *ntk.ReqNtkContent) (logLevel string, message error) {
	return "abc", nil
}

实参是按值传递的,所以函数接收到的是每个实参的副本。

函数的类型称作函数签名。当两个函数拥有相同的形参列表和返回列表时,认为这两个函数的类型或签名是相同的。

函数也是一种变量,它们可以赋给变量或者传递或者从其他函数中返回。

函数类型的零值为nil,函数变量可以和空值相比较,但函数变量之间不可比较,

匿名函数:

abc := func(req *ntk.ReqNtkContent) (logLevel string, message error) {
	return "abc", nil
}

变长函数:

abc := func(names ...string) {
	return "abc", nil
}

在参数列表最后的类型名称之前使用省略号表示声明一个变长函数,调用这个函数的时候可以传递该类型任意数目的参数、

在函数体内,names是一个slice,向该函数传参的时候可以传slice数组,只需要在参数后面放一个省略号:

names := []string{"abc", "cde"}
abc(names...)

延迟函数调用:

defer f.close()

defer的表达式推迟到该函数执行完毕时,以调用defer语句顺序的倒序执行。

return操作分为 赋值+返回 两步,defer在赋值与返回间执行:

func test3() int {
	var i int
	defer i++
	return i
}

//等价
//func test3() int {
//	var i int
//  ret:=i
//	defer i++
//	return ret
//}

延迟执行的匿名函数可以观察到函数的返回结果。

当宕机发生时,正常的程序执行会终止,goroutine中的所有延迟函数会执行。

方法
func (p Point) initEniNeutron(req string) error {
	return nil
}
// 指针接收者
func (p *Point) initEniNeutron(req string) error {
	return nil
}

结构体的方法和字段来自于同一个命名空间,所以方法名和字段名不能相同。

方法可以绑定在同一个包下的任何一种类型上(除指针和接口以外),可以方便地为简单的类型定义附加的行为。

方法和函数一样,会像复制实参变量一样复制接收者,如果想避免复制过大的接收者,或者需要更新一个接收者,可以使用指针接收者。

习惯上遵循如果Point的任何一个方法使用指针接收者,那么所有的Point方法都应该使用指针接收者。

和函数一样,方法也可以赋值给一个变量,但是调用的时候必须提供接收者,并且接收者替换成函数的第一个形参:

type1 := type2.getB
ff := &type2{}
type1(*ff)

方法的首字母是否大写也决定了方法是否被封装。

接口
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值