go语言指针、数组和切片

go语言指针、数组和切片,第1张

go语言指针被拆分成两个核心概念:指针类型和切片,他们分别对应变量和数组的引用。

指针类型:指向变量在底层的内存,但是go语言的指针不允许偏移和运算。切片:是对数组一个连续片段的引用,切片底层数据结构都是数组,切片内部结构包括:地址、大小和容量。切片一般用于快速 *** 作一块数据集合。 变量

参考前一篇文章:go语言变量与常量

指针

变量由内存地址,数据类型和值组成,指针即变量内存地址的别名,通过这个别名同样可以访问和修改变量的内存。
go语言中指针类型只能 *** 作单个变量(不像c语言中可以通过指针偏移 *** 作一大片内存。感觉更像c++的引用类型)。
不能偏移的指针更方便垃圾回收。

指针变量定义:
var name *T
指针变量赋值

有两种方式:
1、 " & " *** 作符取出变量的地址,然后赋值给指针,所以说指针实际上就是地址。
2、 使用new(Type)直接在内存中开辟一个空间并把地址赋值给指针。

对指针使用" * " *** 作符来取指针指向的值。

需要注意的是,指针变量也是一个变量,同样可以使用 " & "取其地址。

下面的例子中:将ptr和ptr1都指向a,然后对ptr指向的内存的值做" ++ " *** 作,打印其值。

var a int = 10
var ptr *int = &a
(*ptr)++
fmt.Printf("Typeof ptr:%T, Address of ptr:%v,ptr=%v, *ptr=%d, a=%d \n", ptr, &ptr, ptr, *ptr, a)

ptr1 := new(string)
*ptr1 = "hello go"
fmt.Printf("Type of ptr1: %T, ptr1= %v, *ptr1=", ptr1, ptr1, *ptr1)

// 输出:
// Typeof ptr:*int, Address of ptr:0xc0000ac018,ptr=0xc0000b2008,*ptr=11,a=11 
// Type of ptr1: *string, ptr1= 0xc000096210, *ptr1=%!(EXTRA string=hello go)

new(string)引发的思考
由于c++ new的惯性思维,认为new(string)的时候就要申请好容纳string值的内存,但是从上面的例子里面看到,new(string)的时候并没有给字符串长度之类的参数。搜索了一下发现go的string定义时这样的:

type stringStruct struct {
	str unsafe.Pointer  //str首地址
	len int             //str长度
}

包含了一个底层指针和一个长度,这两个类型的长度都是明确的,所以new的时候实际上是new了一个指针变量和一个int变量。
而容纳string内容的内存开辟出现在赋值的时候,即*ptr1 = "hello go",先在只读区存入"hello go"字符串,然后将地址和长度赋值给ptr1指向的stringStruct的str和len。

一个小例子

flag包的使用,flag是go语言提供的用来解析命令行参数的工具包

//定义命令行参数
var key1 = flag.String("key1", "", "key1 xxxxxx,value set to 1/2")
var key2 = flag.String("key2", "", "key2 xxxxxx,value set to 3/4")
//解析命令行参数
flag.Parse()
fmt.Println("key1 = ", *key1, "key2=", *key2)
数组

数组是0个或多个相同数据类型组成的一个数据结构,数组的长度是固定的,数组声明就自动完成初始化。

数组的声明
var name [count]Type  //其中count必须在编译期就确定
初始化和访问数组元素

使用" [index] "访问index索引所指向的元素

var nums = [3]int{1,2,3}
fmt.Println(nums[0], nums[1], nums[2]) //输出 1 2 3

var nums1 = [3]int{0: 2, 1: 3} //给前两个位置赋值2/3
fmt.Println("array nums1:", nums1[0], nums1[1], nums1[2])  //输出 2 3 0
for…range遍历

使用range遍历返回索引和值,可以使用匿名变量" _ "来忽略其中一个返回值。

var arr [3]int
arr[2] = 10
for index, value := range arr {
	fmt.Printf("value of index %d is %d\n", index, value)
}

//输出
value of index 0 is 0
value of index 1 is 0
value of index 2 is 10

自动推到数组大小

//自动推到数组大小
var arr1 = [...]int{1, 2, 3, 4} //数组大小推到为4
for _, value := range arr1 { //忽略索引
	fmt.Print(value, " ")
}
数组相等判断

可以使用 " == " " != "符号判断两个数组是否相等。
只有数组类型相同才能进行比较,其中类型包括数组长度,比如[2]int[3]int是两种不同的类型,不能进行比较。
只有两个数组所有元素一一相等两个数组才相等,否则不相等。

var nums = [3]int{1, 2, 3}
var nums1 = [3]int{0: 2, 1: 3} 
fmt.Printf("nums == nums1 : %v\n", nums == nums1)  //输出 false
多维数组
//多维数组
//声明一个二维数组,初始化为0
var arr2 [2][3]int
fmt.Println(arr2)   //输出: [[0 0 0] [0 0 0]]
//赋值
arr2 = [2][3]int{{1, 2, 3}, {4, 5, 6}} 
fmt.Println(arr2)  //输出: [[1 2 3] [4 5 6]]
//指定赋值的索引
arr2 = [2][3]int{1: {7, 8, 9}}
fmt.Println(arr2)  //输出: [[0 0 0] [7 8 9]]
//通过索引取值和赋值
num := arr2[0][0]
fmt.Println(num)  //输出: 0
arr2[1][1] = 10
fmt.Println(arr2)//输出:[[0 0 0] [7 10 9]]
//for...range遍历
for index, value := range arr2 {
	fmt.Printf("value in index %d of arr2:%v \n", index, value)
}
//数组赋值,只能赋值长度一样的数组
var tmp [3]int = arr2[1]
fmt.Println(tmp)  //输出:[7 10 9]
切片

切片是在数组基础上的引用,所以也只能 *** 作数组范围内的内存,不会产生非法修改内存等错误,因此切片比指针类型更安全,当越界时,会报宕机并打出堆栈,指针越界时直接崩溃。

切片有三种生成方式:从数组生成,自定义,使用make函数生成
使用len和cap函数取切片的长度和容量

从数组生成切片

切片可以从数组中直接生成,切片也可以生成新的切片。

//从数组生成切片
var srcArr = [4]int{1, 2, 3, 4}
//start:end 对应目标数组需要引用部分的起始和结束索引,左闭右开
fmt.Println(srcArr[1:3]) //输出[2 3]
//从切片生成新的切片
fmt.Println(srcArr[1:3][0:1]) //输出 [2]
//缺省起始索引表示从0开始
fmt.Println(srcArr[:3]) //[1 2 3]
//缺省结束索引表示到数组末尾
fmt.Println(srcArr[1:]) //[2 3 4]
//缺省起始和结束索引,与原数组等效
fmt.Println(srcArr[:]) //[1 2 3 4]
//起始和结束都为0,为空
fmt.Println(srcArr[0:0]) //[]
//切片长度和容量
fmt.Println("len:", len(srcArr[0:2]), ",cap", cap(srcArr[0:2])) //输出 len: 1 ,cap 3
//切片越界
//fmt.Println(srcArr[1:100]) //编译时即报错
主动声明新切片

除了可以从原有数组生成外,还可以主动声明一个新的切片,这种方式类似于c++的动态数组std::vector
这种方式没有初始容量,所以每次append都会发生扩容 *** 作。

//申明切片
var slice []string                          //注意与数组声明的区别:数组必须给定长度
fmt.Println(slice, "is nil:", slice == nil) //[] true
//切片是动态类型,只能与nil进行比较,不能相互比较
var slice1 = []string{}
fmt.Println(slice1, "is nil:", slice1 == nil) //[]  false ——注意,一旦赋值就不是nil
//使用append向切片中添加元素
slice = append(slice, "aaa")
slice = append(slice, "bbb")
fmt.Println(slice) //[aaa bbb]

使用make创建切片

使用make创建的切片会在make的时候申请cap大小的内存空间,在cap范围内append元素不会发生扩容,提高了性能。

var name = make([]Type,size,cap)  //size表示初始len,表示初始已经有的元素个数,cap表示容量

var slice2 = make([]string, 2, 10)
fmt.Println(slice2, " len:", len(slice2), " cap:", cap(slice2)) //[ ] len: 2 cap: 10

//可以缺省cap参数,此时cap和size是一样大的
var slice3 = make([]string, 2)
fmt.Println(slice3, " len:", len(slice3), " cap:", cap(slice3)) //[ ] len: 2 cap: 2
切片复制

使用copy函数将src切片元素复制到dst中,返回值为实际复制的元素个数,copy只会复制min(len(src),len(dst))个元素。

copy(dst,src,[]Type) int

var arr3 = [6]int{1, 2, 3, 4, 5, 6}
copy(arr3[:2], arr3[3:6])
fmt.Print(arr3) //[4 5 3 4 5 6]

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/995471.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存