Go语言基础学习(二)

本文介绍了Go语言中的指针、结构体、数组和切片的概念及用法。重点讨论了切片的创建、长度、容量、追加元素以及切片原理,强调了切片对底层数组的影响,提到了使用make创建动态数组,并举例说明了映射(map)的创建和修改。此外,还涵盖了函数闭包和结构体方法的基础知识。
摘要由CSDN通过智能技术生成

指针

与c一样,go也支持指针,指针保存了值的内存地址。

// 定义一个T类型的指针,零值为 nil
var p *int
// & 操作符 会生成一个指向其操作数的指针
m := 1
p = &i  //指针p指向i
// * 操作符 表示引用指针指向的内容(间接引用/重定向)

fmt.println(*p)
*p = 333 // 修改指针指向的值
var p *int 
var p *int

结构体

  1. 定义
type s struct {
x int
y int
}

v = s{66,98}
v.x = 6 //给结构体赋值
  1. 结构体指针
    可以通过结构体指针来访问结构体
v1 := s{1,2}
v2 := s{}  		// x:=0 y:=0
v3 := s{x:1}  // y:=0
p := &v  //定义指针指向结构体
p.x = 26 //更改其内容 

数组

在 go 中一个数组变量表示整个数组,当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。

数组的指针并不是数组

  1. 表达式
var arr1 [2]string
arr[0] = "hello"
arr[1] = "world"
fmt.println(a)

//定义一个6元素的数组
n := [6]int{1,6,9,7,5,8}

数组不能改变大小,因此如果需要动态大小的数组需要用到 切片,因为切片长度不固定

切片

切片 由 上届 和 下届 来界定
a[low : high]

primes := [ ] int{4,9,7,3,2,5}
//定义一个 动态s切片 截取  下标  为2到5中的元素
var s []int = primes[2:5]
fmt.println(s)

切片不进行存储,只是描述底层数据的一段,更改切片的元素会更改底层数组中对应的元素,并且同步其他使用该底层数组的切片(相当于指针)

如果扩容之后,还没有触及原数组的容量,那么,切片中的指针指向的位置,就还是原数组,如果扩容之后,超过了原数组的容量,那么,Go就会开辟一块新的内存,把原来的值拷贝过来,这种情况丝毫不会影响到原数组。

切片小于原来数组。影响原数组,切片扩容后大于原数组。就复制了一份原数组。影响新的数组。

s := []bool{true,true,false,true,false}
q := []struct{
i int
b bool
c string
}{
//结构体的具体内容
{1,true,"molly"},
{2,false,"xixi"},
}

切片可以用默认行为来忽略上下界,切片下界默认是0,上界是切片的长度

var a [10]int{2,5,9,7,5,8,6,1}
a[0 : 10]
a[ : 10]
a[0 : ]  //已经给数组长度
a[ : ]	 //同上

注意 切片 的赋值!!

func main()
{ 
s := []int{2,3,5,7,11}
s := s[1:4]
fmt.println(s) // 357

s:= s[:2]
fmt.println(s)// 35

s := s[1:]
fmt.println(s) //5

s最开始是[]int数组。
s :=s[ 1 : 4] s 的起点被重新赋值
现在s数组是
s{3,5,7} 对应 s[0] = 3 s[1] = 5 s[2] = 7

第二次
s := s[ : 2] s范围取到 s[0] = 3 , s[ 1] = 5

第三次
s := s[1 : ] s范围取到 s[0] = 5

切片的长度和容量

切片拥有 长度 和 容量。

切片的长度就是它所包含的元素个数。

切片的容量是从它的第一个元素开始数,到其底层数组元素 末尾的个数。

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

func main() {
	s := []int{2, 3, 5, 7,}
	printSlice(s)

	// 截取切片使其长度为 0
	s = s[:0]
	printSlice(s)

	// 拓展其长度
	s = s[:4]
	printSlice(s)

	// 舍弃前4个值
	s = s[4:]
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

len=4 cap=4 [2 3 5 7]
len=0 cap=4 []
len=4 cap=4 [2 3 5 7]
len=0 cap=0 []

由结果可知,只改变下界不会改变容量,只有改变上界才会改变容量并且每次s切片的变化与上一次的变化有关

	// 拓展其长度  切片长度已经变成了2个
	s = s[:2]
	printSlice(s)

	// 舍弃前两个值    
	s = s[2:]
	printSlice(s)

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

刚定义的数组会被初始化为 0

var s []int
fmt.println(len(s),cap(s))
if s == nil{
fmt.println("nil")
}

[] 0 0
nil!

使用make创建切片

创建动态数组可以使用内置函数make函数

make会创建一个元素为0值的数组,并返回一个引用这个数组的切片。

func make([] T,len ,cap)[ ]T
//写法一:
var s []byte
s = make([]byte,5,5)  // s == []byte{0,0,0,0,0}

//写法二:
a := make([]int,5,5)
fmt.Println(a, len(a), cap(a))

[0 0 0 0 0] 5 5
第一个参数:类型
第二个参数:数组长度
第三个参数:数组容量

为切片追加新的元素

Go内置函数 append()

func append(s []T , vs ...T)[]T 

append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。

结果是 包含原切片的 所有元素 加上新添加元素的切片

当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的切片。将原有的元素复制到新的切片,返回的切片会指向这个新分配的数组。

t := make([]int,len(s),(cap(s)+1)*2)  // +1 cap(s) ==0
for i := range s{
t[ i ] = s[ i ]
}
s = t

循环复制部分 可以用copy内置函数替代。copy返回复制元素的数目

func copy(dst,src []T) int
t := make([]int,len(s),(cap(s)+1)*2) 
copy(t, s)
s = t

将数据追加到切片尾部 AppendByte

AppendByte(slice []int, data..int) []byte{
	m := len(slice)
	n := m + len(data)
	if n > cap(slice){
	//扩容
	newslice := make([]int, (n+1)*2)
	copy(newslice,slice)
	slice = newslice
	}
	//直接放入后面
	slice := slice[0:n]
	copy(slice[m : n], data)
	return slice
//使用
p := []int{2,5,9}
p = AppendByte(p,5,9,4,6,55)
fmt.println(p)

最后输出结果为{2,5,9,5,9,4,6,55}

切片原理

一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度, 和容量(片段的最大长度)。

在这里插入图片描述
前面使用 make([]byte, 5) 创建的切片变量 s 的结构如下:
在这里插入图片描述

长度是切片引用的元素数目。容量是底层数组的元素数目(从切片指针开始)

继续进行切片 s = s[ 2:4 ]

在这里插入图片描述
创建一个新的切片,这个切片使用原来切片的底层数组。因此切片修改元素会影响原始切片对应的元素(因为底层数组发生改变)

创建一个切片长度为它的容量的

s = s[ : cap(s) ]

在这里插入图片描述
切片长度不能超过其容量,不能使用小于零的索引去访问切片之前的元素

注意
切片操作不会复制底层数组,而是将整个数组保存在内存当中,直到不会在被引用。
但是会有一个问题:
如果一个数组内容很多很大,但是我只需要用一点数据,但是却要把整个数组保存在内存中,如何进行优化呢?

//例如, FindDigits 函数加载整个文件到内存
//然后搜索第一个连续的数字,最后结果以切片方式返回。

//搜索第一个连续数字放到 digitRegexp 数组中
var digitRegexp = regexp.MustCompile("[0-9]+")

func FindDigits(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    return digitRegexp.Find(b)
}

因为切片引用了原始的数组, 导致 不能释放数组的空间。只用到少数几个字节却导致整个文件的内容都一直保存在内存里。

解决办法:要修复整个问题,可以将感兴趣的数据复制到一个新的切片中:

func CopyDigits(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    b = digitRegexp.Find(b)
    c := make([]byte, len(b))
    copy(c, b)
    return c
}

注意:内存有个引用计数,第二个你已经开辟了新的切片,运行时b的引用计数为0,内存会自动释放

Range

for 循环的 range 形式可遍历切片或映射。

当使用 for 循环遍历切片时,每次迭代都会返回两个值。

第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。

使用 _来忽略变量。

for i, _ := range pow       //索引下标
for _, value := range pow	//对应元素
映射map

make 函数会返回给定类型的映射,并将其初始化备用。

规则:

  • 若顶级类型只是一个类型名,你可以在文法的元素中省略它。
  • 映射的文法与结构体相似,不过必须有键名。
  • 映射将键映射到值。
    映射的零值为 nil 。nil 映射既没有键,也不能添加键。
type Vertex struct {
	Lat, Long float64
}
//规则1 省略value类型 Vertex
var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

//规则2 key:value
var m = map[string]Vertex{
	"Bell Labs": Vertex{ 40.68433, -74.39967},
	"Google": 	 Vertex{ 37.42202, -122.08408},
}

//规则3
var m1 map[string]Vertex
m1 = make(map[string]Vertex)
	m1["Bell Labs"] = Vertex{
		40.68433, -74.39967,
	}
	fmt.Println(m1["Bell Labs"])
修改映射

在映射 m 中插入或修改元素:m[key] = elem
获取元素:elem = m[key]
删除元素:delete(m, key)
通过双赋值检测某个键是否存在:elem, ok = m[key]

若 key 在 m 中,ok 为 true ;否则,ok 为 false。
若 key 不在映射中,那么 elem 是该映射元素类型的零值。

当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。

注 :若 elem 或 ok 还未声明,你可以使用短变量声明:
elem, ok := m[key]

	m := make(map[string]int)

	m["Answer"] = 42
	fmt.Println("The value:", m["Answer"])

	m["Answer"] = 48
	fmt.Println("The value:", m["Answer"])

	delete(m, "Answer")
	fmt.Println("The value:", m["Answer"])

	v, ok := m["Answer"]
	fmt.Println("The value:", v, "Present?", ok)
函数的闭包

Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。

方法

Go 没有类。不过可以为结构体类型定义方法。
方法就是一类带特殊的 接收者 参数的函数。

方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。

记住:方法只是个带接收者参数的函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值