【从零开始捡起GO】8. 指针,new和make,map

前言

在说map之前,感觉还是要先了解下指针和new,make

指针

我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。

Go 语言的取地址符和php一样都是 &,放到一个变量前使用就会返回相应变量的内存地址。

func main() {
	i := 1
	fmt.Printf("type:%T, value:%v",&i,&i)
	//	输出	type:*int, value:0xc00000a0a8
}

*int 代表他是一个int类型的指针,一个指针变量指向了一个值的内存地址0xc00000a0a8

声明一个指针

类似于变量和常量,在使用指针前你需要声明指针。指针声明格式:var 变量名 *类型,空指针的默认值是nil

func main() {
	var ptr *int
	fmt.Printf("%T,%v", ptr, ptr)
	//	输出	*int,<nil>
}

使用指针

和弱类型语言php不同,想要访问指针上的值,需要在一个指针变量前加上*

也就是说,用&访问一个变量的内存地址(指针),用*访问一个指针对应的内存

func main() {
	var ptr *int
	var i int
	i = 10
	ptr = &i
	fmt.Printf("%T,%v", *ptr, *ptr)
	//	输出	int,10
}

使用指针可能会出现的错误

指针在实际开发中使用的场景是非常少的,在php中能回忆到的使用&的场景也只有几个处理大型数据的时候防止内存溢出做的浅拷贝。但是go语言中,使用指针还是有可能出现错误的,例如在声明了一个指针但未初始化时,指针的值为nil,如果你为一个空指针nil写入内存就会报错了。情景如下

func main() {
	var ptr *int
	*ptr = 10
	//	其实到上面那就不能继续执行了
	//	runtime error: invalid memory address or nil pointer dereference
	//	无效的内存地址或引用了空指针
	var i int
	i = *ptr
	fmt.Printf("%T,%v", *ptr, *ptr)
	fmt.Printf("%T,%v", i, i)
}

所以在对指针写入内存的时候要记得先分配内存地址再写入指针。那么问题来了,没有变量怎么引用,不引用怎么获取内存地址呢?这个时候就要用到万物皆可为对象的new了(GO:并不!)

在go中,可以使用new()方法创建简单数据类型的内存地址,用来存储在一个指针中例如上面的代码,稍微改一改:

func main() {
	var ptr *int
	ptr = new(int)	//	给他分配一个内存地址
	*ptr = 10
	var i int
	i = *ptr
	fmt.Printf("%T,%v\r\n", ptr, ptr)
	//	输出	*int,0xc00000a0a8
	//	输出	int,10
	fmt.Printf("%T,%v\r\n", i, i)
}

这样的话就不会出现空指针错误了。

指针在函数中的使用方式

先皮一下,敲了一天博客都累了

首先,作为一个phper,看一眼php中引用在函数中的使用方式

$a = 1;
var_dump("1:a = {$a}");
//	输出	string(7) "1:a = 1"

function just_a_func(&$arg)
{
    $arg++;
    var_dump("2:arg = {$arg}");
	//	输出	string(9) "2:arg = 2"
}

just_a_func($a);

var_dump("3:a = {$a}");
//	输出	string(7) "3:a = 2"

在php中是不需要关心他是引用还是赋值的,即使是指针,也是操作指针对应内存地址的内存。 而在强类型语言go中是需要区分对待的。上面的代码用golang写需要这样

func main() {
	a := 1
	fmt.Printf("1:a = %v\r\n", a)
	//	输出	1:a = 1
	just_a_func(&a)	//	向方法内传入变量a的内存地址
	fmt.Printf("3:a = %v\r\n", a)	//	因为上面是该的内存地址的值(引用)所以a在这里也是2了
	//	输出	3:a = 2
}

func just_a_func(a *int) {
	*a = 2	//	通过变量a的内存地址修改值为2
	fmt.Printf("2:a = %v\r\n", *a)	//	这里输出a也要使用*来访问内存地址对应的值
	//	输出	2:a = 2
}

需要注意的是,在方法中接受一个指针记得声明该参数为一个指针,同时在使用的时候也需要使用*来通过地址寻址。

new和make

在golang中,make()new()都是用来申请内存的,不同的是

  1. new()只能用来申请简单数据类型的内存分配(数字,字符串,不二),make()是用来申请slicemapchannel这三种数据类型的内存分配的。
  2. new()返回的是一个指针(内存地址),而make()返回的是slice,map,channel本身(因为这3种数据类型本身就是引用传递的)

~~其实在开发中,&就已经很不常用了,new就几乎见不到了。所以对于go的开发者来说,他们真的可能一辈子都不会new(对象)了。心疼golang开发人员一秒。~~

询问了两波人,一波人说不怎么用,一波人说极其常用,所以先划掉

map

Map就是phper喜闻乐见的k-v结构,在go中的map只有hashmap一种,不像隔壁java有LinkedHashMap可以做队列,有TreeMap可以做排序。golang中的map只有hashmap,这也导致了golang中的map是无序的。不过Map也是一种集合,所以我们可以像迭代数组和切片那样迭代它。

声明一个map

声明一个map:var 变量名 map[key的数据类型]value的数据类型

func main() {
	var m1 map[string]string
	fmt.Printf("%T,%v",m1,m1)
	//	map[string]string,map[]
}

初始化一个map

上面有说到,make()可以用来初始化map例如:

func main() {
	var m1 map[string]string
	m1 = make(map[string]string, 3)
}

需要注意的是,在初始化的时候最好预先想好可能出现多少个key,避免系统对map进行扩容操作浪费内存

为map添加元素及访问

添加元素和访问和其他语言一样,直接变量名[key]就可以了

func main() {
	var m1 map[string]string
	m1 = make(map[string]string, 3)
	m1["k1"] = "v1"
	m1["k2"] = "v2"
	m1["k3"] = "v3"
	fmt.Printf("type:%T, value:%v\r\n", m1, m1)
	// 输出 type:map[string]string, value:map[k1:v1 k2:v2 k3:v3]
}

在添加及访问时会遇到的问题

一个未初始化的map是无法赋值的,这是因为在初始化前,map的默认值是nil map。由于map是引用类型,在仅声明的情况下和指针一样,默认是没有分配内存地址的,只有在使用make()初始化申请内存地址之后才可以正常的进行赋值,否则会出现:panic: assignment to entry in nil map向一个nil map赋值

判断key是否存在

在访问一个map的key的时候,会返回2个数据,即对应的value和是否存在,在golang中习惯用ok来接收是否存在的返回值(boolean)

func main() {
	var m1 map[string]string
	m1 = make(map[string]string, 3)
	v, ok := m1["a"]
	fmt.Println(v, ok)
	if (ok) {
		fmt.Println(v)
	} else {
		fmt.Println("不存在")
	}
}

删除一个key

删除map中的一个key使用delete()方法 func delete(m map[Type]Type1, key Type)

func main() {
	var m1 map[string]string
	m1 = make(map[string]string, 3)
	m1["k1"] = "v1"
	m1["k2"] = "v2"
	m1["k3"] = "v3"
	fmt.Println(m1)
	// 输出 map[k1:v1 k2:v2 k3:v3]
	delete(m1,"k1")
	// 输出 map[k2:v2 k3:v3]
	fmt.Println(m1)
}

map的遍历

map的遍历和数组,切片一样可以使用for range

func main() {
	var m1 map[string]string
	m1 = make(map[string]string, 3)
	m1["k1"] = "v1"
	m1["k2"] = "v2"
	m1["k3"] = "v3"
	for k, v := range m1 {
		fmt.Printf("k => %v, v => %v\r\n", k, v)
		// 输出 k => k1, v => v1
		// k => k2, v => v2
		// k => k3, v => v3
	}
}

虽然不能直接对map排序,但是可以按照一定顺序来取值

具体代码不贴了,流程是

  1. 声明一个key对应类型的切片
  2. 遍历map,将key挨个append到切片中
  3. 使用sort包对待排序的切片进行排序
  4. 遍历排序后的切片(里面有map里的key),循环取出所有map里的值

总结

map入门对于phper来说还是比较简单的,毕竟日常开发中需要应对的大部分都是类似的k-v结构数据。

入门思维导图下载(使用xmind打开):golang.xmind

程序幼儿员-龚学鹏
请先登录后发表评论
  • latest comments
  • 总共0条评论