GO 学习总结

1 篇文章 0 订阅
1 篇文章 0 订阅

目录

1. go 数组

         1.1 go的数组申明方式,初始化方式,以及赋值方式

        1.2  数组在函数间传值

2. go 切片

        2.1切片的申明定义

         2.2 切片的增删改查

        2.3 切片传参的使用

3. go Map

        3.1 申明定义一个Map

         3.2 Map的查看和遍历

        3.3 Map的传参以及增删改查

4. go 指针

        4.1 go指针定义

         4.2 go指针的运算

        4.3 go指针的传参 

       4.4 go指针进阶 

5. go 结构体

        5.1 go结构体的申明定义初始化

        5.2 go结构体传参使用

总结:

遗留问题


1. go 数组

         1.1 go的数组申明方式,初始化方式,以及赋值方式

代码如下:

    var arr1 [5]int // 数组的申明

    arr1[0] = 0     //数组的赋值

    arr1[1] = 1

    // ...

    arr1[4] = 4

    fmt.Println("arr1:", arr1)



    var arr2 = [5]int{0, 1, 2, 3, 4} //申明加初始化

    fmt.Println("arr2:", arr2)



    arr3 := [5]int{0: 5, 1: 0} //字面量声明加部分初始化 [index:value]

    fmt.Println("arr3:", arr3)



    //如果数据长度不确定我们可以使用[...] ,系统会自动推断数组长度

    //这个有点类似C中 int a[] = {1,2,3},系统会自动知道这个a 会有3个元素,无非就是go中你要给他写上[...]

    arr4 := [...]int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}

    fmt.Println("arr4:", arr4)

 运行结果如下:

arr1: [0 1 0 0 4]
arr2: [0 1 2 3 4]
arr3: [5 0 0 0 0]
arr4: [10 9 8 7 6 5 4 3 2 1]

        1.2  数组在函数间传值

go数组和C数组的传参上区别还是很大,虽然go中有指针(后面会讲),但是数组名作为参数时,并不会想C一样退化成指针,所以函数形参的一定要和数组大小一样才能传的,比如函数形参写的是tmp []int,要传的数组是arr := [5]int,这样子传arr是不行的,最常见的做法应该是把数据伪装成切片进行传参(sum(arr5[:])),要么就是把函数形参改成tmp [5]int,要么就把数组定义成切片(切片后面会说)

下面的代码展示数组在函数间传参的方式

/*
arr5 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

	sum(arr5) 这是一个值传递
	change(&arr5) 这是一个引用传递

传入的数组的申明定义
*/
func sum(para [10]int) int {
	fmt.Printf("len:%d\nsize:%d \n%v\n", len(para), unsafe.Sizeof(para), para)
	var Sum int
	for _, val := range para {
		Sum += val
	}
	para[0] = 100
	fmt.Println(Sum)
	return Sum
}

func change(para *[10]int) {
	for index, val := range para {
		para[index] = val * 10
		fmt.Print(val, " ")
	}
}

运行结果如下:

arr1: [0 1 0 0 4]
arr2: [0 1 2 3 4]
arr3: [5 0 0 0 0]
arr4: [10 9 8 7 6 5 4 3 2 1]
len:10
size:80 
[1 2 3 4 5 6 7 8 9 10]
55
----
before change :[1 2 3 4 5 6 7 8 9 10]
after change :[10 20 30 40 50 60 70 80 90 100]

 数据的值传递没啥好说的,跟其他语言大差不差。下面的change函数是go中的引用传递(不知道有没有理解错),这个跟C的指针传递写法与C++的引用传递写法还是要注意一下,写法上像C传指针,使用上像C++的引用。(这块可能描述的不是很好,大家可以评论区讨论一下)

2. go 切片

go的切片是抽象数组的一个概念,很容易混淆。简单说就是数组的长度是固定的,切片是可以变化长度的数组。

        2.1切片的申明定义

数组在上一节讲过了,那切片和数组在申明上有哪些区别呢? 在用var申明定义时,其实切片和数组就是一个有没有指定长度的区别 ,数组在定义式它的地址是不为空的,切片在这里的地址是为nil(0x0)。同样也可以使用短变量定义 ,如s2,内置函数make可以初始化切片,这里会初始化一个长度为5,容量为10的切片,并且前五个元素会被初始化为0.

代码如下:

	var arr1 = [5]int{} //定义一个数组,但未初始化
	var s1 []int        //定义一个切片,但未初始化

	fmt.Printf("数组的地址:%p,切片的地址:%p\n", &arr1, s1)

	s1 = make([]int, 5, 10)
	s2 := make([]int, 5, 10) //等价s1
	fmt.Printf("s1切片的地址:%p,s2切片的地址:%p\n", s1, s2)

运行结果如下:

数组的地址:0xc0001a4000,切片的地址:0x0
s1切片的地址:0xc0001ae000,s2切片的地址:0xc0001ae050

         2.2 切片的增删改查

这里使用引用数组的切片,使用make定义的也行。切片本质也是数组,所以同样可以使用下标进行修改和查看内容,删除也和数组差不多(删除的方法很多这里是随便写一个)。还有一个就是增加,go中有一个内置函数append,可以用来增加切片的内容。如果切片的长度小于容量则只增加长度,并填入内容;如果长度等于容量,它会自动扩展容量(倍增)。

代码如下:

//切片引用数组
	arr1 := [...]int{1, 2, 3, 4, 5, 6}
	s1 := arr1[:]
	s1[0] = 100 // 修改
	//s1[6] = 1 错误,因为s1的容量只有6
	fmt.Println("cap:", cap(s1))
	s1 = append(s1, 99)          //增加切片内容
	fmt.Println("cap:", cap(s1)) //当切片的长度和容量一样大的时候,使用append会是切片容量翻倍

	//删除切片的某个元素,假设我要删除s1[2]的元素
	index := 2
	for i, _ := range s1 {
		if index <= i && i != len(s1)-1 {
			s1[i] = s1[i+1]
			i++
		}
	}
	s1 = s1[:len(s1)-1]
    //s1 = append(s1[:index], s1[index+1:]...) 这样写也是可以的,但是如果s1是形参,这是改变不了s1本身的
	fmt.Println(s1)

运行代码如下:

cap: 6
cap: 12
[100 2 4 5 6 99]

        2.3 切片传参的使用

切片属于引用传递,所以函数可以直接使用切片的形参修改切片本身

/*
	s3 := make([]int, 1)
	sli((s3))
	fmt.Println(s3)

*/

func sli(s []int) []int {
	fmt.Println("len:", len(s), "cap:", cap(s), "s:", s)
	s[0] = 1
	return s
}

运行结果如下:

len: 1 cap: 1 s: [0]
[1]

 切片的内容就讲到这边,但是切片的内容远远不止这些,比如操作切片的内置函数有哪些?(append,copy,len,cap,make)这里面有许多的知识点和注意点,就不在这里说了。

3. go Map

Map 是一个键值对的集合,并且包含键值对的索引。像是一个数组中每个元素是一对键值对,这对键值对在数组中有特定的索引。但是Map是一个无序的,意味着我们每次访问前,都不知道每个键值对的索引。简单说Ma就是一个无序的键值对集合。

        3.1 申明定义一个Map

下面演示了两种定义初始化方式,一种make(map[key-type]value-type,capcity),一种是短变量初始化。这个用make 定义的时候虽然第二个参数是cap参数,但是内置函数cap是没法获得一个Map的容量大小的,可以使用len()获得键值对个数。

	m1 := make(map[string]int, 5)  创建一个初始容量为 5 的 Map
	m1["age"] = 1     //对Map进行赋值
	m1["class"] = 2
	m1["hello"] = 3
	m1["world"] = 4
	m1["welcome"] = 5
	fmt.Printf("&m1:%p, m1_sz:%d,len:%d , m1:%v\n", m1, unsafe.Sizeof(m1), len(m1), m1)
	m1["add"] = 6
	m1["del"] = 7
	fmt.Printf("&m1:%p, m1_sz:%d,len:%d , m1:%v\n", m1, unsafe.Sizeof(m1), len(m1), m1)

	m2 := map[string]int{
		"age":   11,
		"class": 1, //最后一对键值对的逗号不能去掉!
	}
	fmt.Printf("len:%d , m1:%v\n", len(m2), m2)

运行代码如下:

&m1:0xc000094150, m1_sz:8,len:5 , m1:map[age:1 class:2 hello:3 welcome:5 world:4]
&m1:0xc000094150, m1_sz:8,len:7 , m1:map[add:6 age:1 class:2 del:7 hello:3 welcome:5 world:4]
len:2 , m1:map[age:11 class:1]

         3.2 Map的查看和遍历

        下面代码展示了Map的遍历和查看,这里提一下这句 val, t := m1["e"],val指的是m1["e"]所对应的value,如果没有这个key,那么val是这个类型的初始值;t是bool类型,存在key,t就为true,反之为false.

	m1 := map[string]string{
		"h": "w",
		"e": "o",
		"l": "r",
		"o": "l",
	}
	fmt.Println(m1) //可以直接print

	for val, t := range m1 { //也可以通过range
		fmt.Println(val, ":", t)
	}

	fmt.Println("m1[\"h\"]", m1["h"]) //也可以通过key 作为索引 查看对应的value

	val, t := m1["e"] // 查找存在的key
	fmt.Println(val, t)

	val, t = m1["z"] // 查找不存在的key
	fmt.Println(val, t)

 代码运行如下,通过打印m1也可以看出Map的无序性

map[e:o h:w l:r o:l]
l : r
o : l
h : w
e : o
m1["h"] w
o true
 false

        3.3 Map的传参以及增删改查

Map也是属于引用传递

/*
	m1 := map[string]int{
		"age":   11,
		"class": 1,
		"grade": 1,
	}
	fmt.Println("befor modify:", m1)
	Map_crud(m1)
	fmt.Println("after modify:", m1)
*/

func Map_crud(m map[string]int) map[string]int {
	fmt.Println(m)
	m["age"] = 2023 // 修改
	m["goal"] = 150 // 增
	delete(m, "class")
	return m
}

运行结果如下:

befor modify: map[age:11 class:1 grade:1]
map[age:11 class:1 grade:1]
after modify: map[age:2023 goal:150 grade:1]

4. go 指针

        4.1 go指针定义

        go的指针比C的指针限定的条件比较多,没有C那样可以随意操作指针。

需要注意几点:

1. go的数组名不是首地址,所以不能当做指针使用

2. go的指针不支持下标检索

3. go的指针是不支持运算的,例如p++,p--之类(但是有办法可以运算,下面会说)

	var a int = 5
	var p *int = &a
	fmt.Printf("&a:%p,p:%p\n", &a, p) // a 的地址和 p指向的地址一样
	fmt.Printf("a:%d,*p:%d\n", a, *p) //a = *p = 5

	a1 := [...]int{1, 2, 3, 4, 5}
	p = &a1[0] //Go中的数组是不会退化成指针的
	fmt.Printf("*p : %d\n", *p)

运行结果如下:

&a:0xc000122000,p:0xc000122000
a:5,*p:5
*p : 1

         4.2 go指针的运算

上面有提到go是不支持指针运算的,所以我们是没办法直接对指针进行运行,需借助unsafe包,写到这边我也知道为什么叫做unsafe(不让这样做,但是可以做,所以不安全嘛)。

对go的指针运算我们用到了unsafe.Pointer()和uintptr.通过这两个内置我们可以把指针转换为无符号整形数进行计算,最后再把这个整形数在转换为指针。

代码如下(如没有特殊需求,建议安装上方注释的方法写。个人理解:Go团队似乎很不建议开发者对指针进行运算,所以中间过程尽量不要用变量接收,防止误改,而且这里没注释的代码,也有一个警告:possible misuse of unsafe.pointerunsafeptr):

	/*
		a2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
		p1 := &a2[0]
		tmp := ((*int)(unsafe.Pointer((uintptr(unsafe.Pointer(p1))) + 16))) //tmp 的地址等于 &a2[2]
		fmt.Println(*tmp, tmp, &a2[2])
	*/
	a2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
	p1 := unsafe.Pointer(&a2[0])
	uint_p1 := uintptr(p1)
	uint_p1 += 16
	p2 := (*int)(unsafe.Pointer(uint_p1))

	fmt.Println(*p2)  //输出 7 ,与a2[2]相等

        4.3 go指针的传参 

这里ptr_test()函数 中的a 讲道理是局部变量,p1是一个指针指向a,但是return出p1,外部依然可以访问p1这个指针的内容,这个应该是go的内存机制有关,似乎是变量逃逸的知识点。写到这里的时候我还没怎么了解变量逃逸,后面单独出一个章节讲讲变量逃逸吧

代码如下:

/*
main

	a2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
	p2 := &a2[0]
	p3 := ptr_test(p2)
	fmt.Println(*p3)
	fmt.Println(a2)
*/

func ptr_test(p *int) *int {
	fmt.Println(*p) // 9
	*p = 99         //修改

	a := 5
	p1 := &a
	return p1 //这里与C语言不一样,需注意
}

运行结果如下:

9
5
[99 8 7 6 5 4 3 2 1 0]

       4.4 go指针进阶 

指针数组  (点击直接跳转)

多级指针(这里只讲二级指针):还没写

函数指针 :还没写

(这部分后期打算单独写,以连接的形式跳转)

5. go 结构体

        5.1 go结构体的申明定义初始化

type是Go中的类型定义关键字,这里定义一个结构体类型 ,写法 type [NewTypeName] struct

type Class struct {
	num   int
	grade int
	name  string
}

func main() {
	c1 := Class{1, 1, "hello"}                   //直接初始化
	c2 := Class{num: 2, grade: 2, name: "world"} //通过键值对初始化

	fmt.Println(c1, "\n", c2)
}

        5.2 go结构体传参使用

go结构体属于普通类型,传参时需注意值传递,地址传递的区别

/*
c1 := Class{1, 1, "hello"}     

	struct_test(c1)
	fmt.Println(c1)

	p_struct_test(&c1)
	fmt.Println(c1)
*/

// 值传递
func struct_test(c Class) {
	c.name = "new Name"
	c.num = 99
	c.grade = 888
}

// 地址传递
func p_struct_test(c *Class) {
	c.name = "new Name"
	c.num = 99
	c.grade = 888
}

运行结果如下:

{1 1 hello} 

{1 1 hello}
{99 888 new Name}

总结:

        码到现在算是差不多了,如果存在说法错误或者不严谨,大家可以留言评论。因为我自己是只会C,其他啥也不会。所以文中多出拿C与Go的概念作比较,有些也不知道合不合适。有一些遗留问题,后面会在评论区里解答。或者大家帮我哈。互相学习进步!!!

遗留问题

1. slice 的用Sizeof 计算为什么是24?16不也直接对齐吗?

2. 同上Sizeof(map)为什么是8,map不是引用类型吗?

3. make 和 new 是动态分配内存,但似乎Go的垃圾回收机制会自动回收,那这个垃圾回收的原理有是什么?

4. 地址问题,为啥地址一开始加8个字节,中间变成加2个字节,然后又8个字节?

	a2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
	fmt.Println(&a2[0], &a2[1], &a2[2], &a2[3]) // 0xc000026050 0xc000026058 0xc000026060 0xc000026068??

5. 怎么去除掉这个警告possible misuse of unsafe.pointerunsafeptr?

p2 := (*int)(unsafe.Pointer(uint_p1)) //(unsafe.Pointer(uint_p1))这里似乎有一个警告possible misuse of unsafe.Pointerunsafeptr

  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ouzw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值